diff --git a/machines/deedee/configuration.nix b/machines/deedee/configuration.nix index bd3520f..ee72061 100644 --- a/machines/deedee/configuration.nix +++ b/machines/deedee/configuration.nix @@ -186,6 +186,13 @@ rec { enable = true; forwardedPort = 17307; }; + homepage = { + enable = true; + greeting = "ajgon.casa"; + disks = { + DATA = "/"; + }; + }; jellyfin = { inherit videoPath; enable = true; diff --git a/machines/deedee/secrets.sops.yaml b/machines/deedee/secrets.sops.yaml index f6371ab..cd8932b 100644 --- a/machines/deedee/secrets.sops.yaml +++ b/machines/deedee/secrets.sops.yaml @@ -22,6 +22,7 @@ system: apps: audiobookshelf: env: + HOMEPAGE_API_KEY: ENC[AES256_GCM,data:/vcddj618f5wwj3yQ4aAFFK8hy38R8287YXq6/H87NvR1SXNzcLbG0jxvi/XakeOsw/uCDomSn48hMOG85/D0g+8Q4NERFKYXDtxJw3TxU3Zgc4GsUQH8IcEEET90FBRqPwYP2mYk0Mmlc1FkHeEPlTNj6RY5Tc9lUBWDAGADBfUKCIp2MOgMznLCtrjuaOxDU02b1UG/XgAPrzg5ixHXpN/Ph0N00tszzgm5zr46l4SYIPietlWqCqSnAYl8d7X9kva,iv:+8gxQEhwk7G73CCBf/RQucTjYN0equzaqUBSL6+KGQs=,tag:MSs7HZ7qYFscFBBov7uNMQ==,type:str] OIDC_SECRET_ENCRYPTED: ENC[AES256_GCM,data:KWh3ZPiqSvoLSjONdQCwQqr8+ygw0pbyCyy210JWZJ66YT5bTjG2le+PBYpaTQOO1VTnzBh4uV4/xbkFdeWWmD12SUuha2h0gdct/CYviW5zVQ6bjS6EDk9mVvFcs0VkTLLGInrEYqtf06EmYNRnedgEAICvaKqN5sBAYOQ2qBWXutU=,iv:VKI246I8itOahs2gqDWONbg2NnL7Wn5N0TAOPSDez/g=,tag:kON5jjmpbygqw0SV76xX6w==,type:str] OIDC_SECRET_RAW: ENC[AES256_GCM,data:af1X0LIiN2MqAtKLKCkPWRK0I4yZ3RTyK8uKoL+VIQroiuMwrZufU+l6es6+DyWyJDaYBgOmML895LrTA17OJcOaxkOGTfY1,iv:JJXIEk1zJEEQ0uHM2Da4c6q8WPNHiev7aM5VIQT2njM=,tag:8cD6OlMhruZGJ0aGEZILZw==,type:str] authelia: @@ -65,6 +66,7 @@ system: env: OIDC_SECRET_ENCRYPTED: ENC[AES256_GCM,data:hRAudzG+gyAW5he21z2aY191Qon56UQxOdQanjWc9qC7ToPTqoVQQUsSwN6T8n4hCLSwfOEXZF6jQUFFQocnvMHfz/IjZGrhl2uGR/wOm8bADYU9UUuyJCB1mdq8wBWbi22hv+V1unl0uWDz/6GMLv4VVouwN6y7fqk7FLpwbQkeT1g=,iv:Ck3iV8ySo9t8MYjqZeTC0Kpe+srolUjg5FabKMBM5+k=,tag:C/d3sFzmeBJDS2iMryQ9zA==,type:str] OIDC_SECRET_RAW: ENC[AES256_GCM,data:Lg52MRh9SMIql0nPFCy562ec6mRw47a+E7KTJ2cMgat1ZXtPBEYIiYmSExU83yTO1D7keHodDtaqQaCymWKOgBgU3xGSpRr9,iv:FwLhp+bnkHYHDDp7krzoodZnCUXehlv85jioVofcqig=,tag:OlFNPRA2mmGQYAjFhCyEuw==,type:str] + HOMEPAGE_API_KEY: ENC[AES256_GCM,data:kTkdsEPPihhHuX7YJkVEovGz9Cu5XHdM6aDvMFVbrZc=,iv:tPlp6YT0TtVPgfGi3PywHvDncjE1HbtRqLa6+SdNAKA=,tag:FwSbVrUISOQfEYeXvJTYDg==,type:str] letsencrypt: envfile: ENC[AES256_GCM,data:5sbCKlde84OLiBp8ayIveI2BDwrmnaSyXHQO+qmvbyJxvuRk1W0OOhn1izCD4jSlWOTqOdx21+ibIAj8eCUD7XEk,iv:+GjzjWgMreis9GwLzMaArFcBe9f4NM3Q/7FrjsmAibg=,tag:VFVN+RDnrala3n+ZeLCkmw==,type:str] lldap: @@ -92,9 +94,11 @@ system: env: ADMIN_PASSWORD: ENC[AES256_GCM,data:SxXo9/V0AoIF/BPK4eovgjK/87g5X9VhdsoBj/wlgBbPRXzLmZLh3g==,iv:778z4gq1fOms4HAvG4QsLE2eLBw2sGco0T5b/OW+mbA=,tag:T0wifbrfYiaDvm1oO7ickg==,type:str] ADMIN_USERNAME: ENC[AES256_GCM,data:7dJuqX0=,iv:3+QR4jWD5xQoJKhYx++JlOWEERzRRy0VKYkEN8Wbze4=,tag:t6nT3otCm8unu1eGcEc7bA==,type:str] + HOMEPAGE_API_KEY: ENC[AES256_GCM,data:W+EMaZoB8edgv+PtgiCYbSeZTFSddplr51qHZqD2lsEnLl1goQBtp5cPJuo=,iv:a/UgKGBp2zYSAludSv+KNfzX2sXaLAVQasol24Nuf5E=,tag:RWu3sAva1MNbyRLgs7/n5A==,type:str] MINIFLUX__POSTGRES_PASSWORD: ENC[AES256_GCM,data:80m7hK4wowmL2VwxlOMjnwDSJRw1c6FgOO/WwX3ZLkdX0TabpcJUTQ==,iv:7GF9skSt5hS8puIx2Cun68g0JXiRR1tONErjIIY7QAk=,tag:APZ+XBu3Vx1bNjbNmJMhcg==,type:str] paperless-ngx: env: + HOMEPAGE_API_KEY: ENC[AES256_GCM,data:9a92OwwPXKu25Gk+QuBCHGxD4KjbNgVXgQLgWC93q9UJ8NlKa9cTXw==,iv:ZwCqMzMSmii+zol12zuPZNvLFC0bOGBzSDJe5U+8eZ8=,tag:HEKDhSg2mzU2OorvrKHNVA==,type:str] PAPERLESS_DBPASS: ENC[AES256_GCM,data:tbRklBvErWIJikqv5mXGkqZkGdmjQYkUz1ytNMhbn73d6J58mWMyYw==,iv:BrRBG+oYYmGF662ennH4DRxmrJwjg1Gmpt9BCcJ8lik=,tag:w5IFutT7GLvEMHrC4wV4cw==,type:str] PAPERLESS_REDIS: ENC[AES256_GCM,data:r2APL4IzQDPe05rxVSDelMvk8T8nmuysgZjCRmWND/6oFHrr6apWjt8PvgGdRXRfRUOCThdKFDg2twvMLQdPtQSMf/hHe+7hYAgE,iv:PWMcP0J5QmjWMxQ3XBJZx+/V6IYu4x5S0IbWeUtAP98=,tag:GAKQ3nfesYuqnMB2inX1CQ==,type:str] PAPERLESS_SECRET_KEY: ENC[AES256_GCM,data:hrEUbEmR1H7NRzMeDiGA8pKprY9eXuc92p+6xokKD53spuv0foulgw==,iv:BzR9s7DbNfqZLgS2NRZq1WJU4ScjBnRQ8XVLkysrNXM=,tag:nQUZJm6/PzI/bvlv/mdIjg==,type:str] @@ -105,6 +109,10 @@ system: env: PROWLARR__AUTH__APIKEY: ENC[AES256_GCM,data:WSTamM2q3b68F9ZCkhkbtzQXtQhP58O6Xybs+MnW8iE=,iv:syGy+3QLsawFSOwHnlU+TKK8qxSlCVIB0YEiLBn2rSM=,tag:0KQpsFAdBUZsFMoHyuK28Q==,type:str] PROWLARR__POSTGRES__PASSWORD: ENC[AES256_GCM,data:dNCqGIccKGuhqRleIoDIUa8+z4vA6pT3bOmG19ngUI9whaIFJEAdcg==,iv:tIUVAAfVe9jGzY89A/BhUUZtw+0rogEt+IK7AgKn9oc=,tag:7HMtbJDy5txzR9d4kdUpqA==,type:str] + qbittorrent: + env: + WEBUI_USERNAME: ENC[AES256_GCM,data:maAhq6Y=,iv:RrGbfHMxihbZUPgLkgdqeTi1o9uU72O1F05+F/kWm+g=,tag:wS689kNCUm3Hco19W55fxA==,type:str] + WEBUI_PASSWORD: ENC[AES256_GCM,data:/UC1o7IP3qzF1RX2OAbUzA==,iv:/mwFIOL6lp3tU1Op3Y5KmDnhvdTVFCPTjXkQ4u0RuT8=,tag:lZ1FCxJwL9F8juiItxWxKw==,type:str] radarr: env: RADARR__AUTH__APIKEY: ENC[AES256_GCM,data:8ULsc9Xe2wq8noCqXcLVMk9Ef7sAFxGJzEHG5nhUSXI=,iv:QRNqAxs/5Gf0EDOJ72HUC0x6vmJQMdGdqez/+rg62bs=,tag:yfUJclyw51p+zyELC0yIqQ==,type:str] @@ -154,8 +162,8 @@ sops: c3FoaFNzbjJubzlBckdDb2lNOUZtOGMKRbHxa1B3QAdredBMTd7W7g3kRz6l8uyV bBclsA8Gm7p+6ndV39sN+Daqm5MyggY1Prwv/Ukdd5Q+1C+XsEW6OQ== -----END AGE ENCRYPTED FILE----- - lastmodified: "2024-11-01T16:15:00Z" - mac: ENC[AES256_GCM,data:Z88ZU/SgwuH0xe9yiZZeuxQeyoaW7CVEawIx6SrB+Py0rt/HgnomN86qC/lzBl+H9Qgc0d4EFymv+pR5YYVVpxLuJPce3OJe+bzg9HZ3xPMxjsoG4WTVQzW5DSgx6+hWjsFk7m0a/AjqSClMncorVXcc6+RcAJ1NXkjfVy4SvLg=,iv:lQICt+VCx24P5bL+EnhSY+j41ffL3sKhK3FnHS7/Tdw=,tag:5x2YA/9Kx7PtI9Wkm51RXQ==,type:str] + lastmodified: "2024-11-01T19:53:58Z" + mac: ENC[AES256_GCM,data:KUCjZMJQyfWKKMzPCvL4jRqmMpBoDV2jgz2V5fr1rzMKm1PgtO95c6au4rpcv/f1ZhwbGVHwqOtVyrOu2dbBM5bNFgcAnwhGicCv4ssoHniBtatxnL9zptMIolRaXkIZP1I5Z71BTw/DUkFfcHnVv9dthyJSf85hzW/XGdkYhNA=,iv:IeNRUlXPNcIVm1c3R2SVmEBAto66qNdZWNSQ9RlB82w=,tag:cN7dPy7p73XJvfqDdfXkSg==,type:str] pgp: [] unencrypted_suffix: _unencrypted version: 3.8.1 diff --git a/modules/system/apps/docker/default.nix b/modules/system/apps/docker/default.nix index e5e5ac2..d567e0e 100644 --- a/modules/system/apps/docker/default.nix +++ b/modules/system/apps/docker/default.nix @@ -1,6 +1,7 @@ { config, lib, + svc, pkgs, ... }: @@ -81,11 +82,41 @@ in }; }; }; + startDockerSockProxy = lib.mkOption { + type = lib.types.bool; + description = "Start read-only proxy with minimal permissions, for docker.sock, to avoid mounting it directly in containers."; + default = false; + example = true; + }; }; config = lib.mkIf cfg.enable { + assertions = [ + { + assertion = (!cfg.rootless) && cfg.startDockerSockProxy; + message = "docker.sock proxy is currently only supported in root mode"; + } + ]; + virtualisation = { - oci-containers.backend = "docker"; + oci-containers = { + backend = "docker"; + containers.socket-proxy = lib.mkIf cfg.startDockerSockProxy ( + svc.mkContainer { + cfg = { + image = "ghcr.io/tecnativa/docker-socket-proxy:0.3@sha256:2f92c6e85a1199b3403c99d7439695898a162c69689b11130450ffadb352f0a0"; + environment = { + CONTAINERS = "1"; + POST = "0"; + }; + volumes = [ "/var/run/docker.sock:/var/run/docker.sock:ro" ]; # in rootless mode, socket lives under /run//.... + }; + opts = { + disableReadOnly = true; + }; + } + ); + }; docker = { enable = true; diff --git a/modules/system/containers/audiobookshelf/default.nix b/modules/system/containers/audiobookshelf/default.nix index 258d1e1..f5f000a 100644 --- a/modules/system/containers/audiobookshelf/default.nix +++ b/modules/system/containers/audiobookshelf/default.nix @@ -27,11 +27,18 @@ in type = lib.types.str; description = "Path to directory containing podcasts."; }; + sopsSecretPrefix = lib.mkOption { + type = lib.types.str; + description = "Prefix for sops secret, under which all ENVs will be appended."; + default = "system/apps/audiobookshelf/env"; + }; }; config = lib.mkIf cfg.enable { warnings = [ (lib.mkIf (!cfg.backup) "WARNING: Backups for audiobookshelf are disabled!") ]; + sops.secrets."${cfg.sopsSecretPrefix}/HOMEPAGE_API_KEY" = { }; + virtualisation.oci-containers.containers.audiobookshelf = svc.mkContainer { cfg = { image = "ghcr.io/advplyr/audiobookshelf:2.15.1@sha256:9096480cb2b8cbfb3da155ea3cea5e9bfd4f3c2aae6196225c5b26d31bad1a99"; @@ -112,5 +119,24 @@ in environment.persistence."${config.mySystem.impermanence.persistPath}" = lib.mkIf config.mySystem.impermanence.enable { directories = [ cfg.dataDir ]; }; + + mySystemApps.homepage = { + services.Media.Audiobookshelf = svc.mkHomepage "audiobookshelf" // { + description = "Podcasts and audiobooks manager"; + widget = { + type = "audiobookshelf"; + url = "http://audiobookshelf:3000"; + key = "@@AUDIOBOOKSHELF_API_KEY@@"; + fields = [ + "books" + "booksDuration" + "podcasts" + "podcastsDuration" + ]; + }; + }; + secrets.AUDIOBOOKSHELF_API_KEY = + config.sops.secrets."${cfg.sopsSecretPrefix}/HOMEPAGE_API_KEY".path; + }; }; } diff --git a/modules/system/containers/bazarr/default.nix b/modules/system/containers/bazarr/default.nix index 9ffd0de..6453092 100644 --- a/modules/system/containers/bazarr/default.nix +++ b/modules/system/containers/bazarr/default.nix @@ -89,5 +89,21 @@ in environment.persistence."${config.mySystem.impermanence.persistPath}" = lib.mkIf config.mySystem.impermanence.enable { directories = [ cfg.dataDir ]; }; + + mySystemApps.homepage = { + services.Media.Bazarr = svc.mkHomepage "bazarr" // { + description = "Subtitles downloader and autosync"; + widget = { + type = "bazarr"; + url = "http://bazarr:6767"; + key = "@@BAZARR_API_KEY@@"; + fields = [ + "missingMovies" + "missingEpisodes" + ]; + }; + }; + secrets.BAZARR_API_KEY = config.sops.secrets."${cfg.sopsSecretPrefix}/BAZARR__API_KEY".path; + }; }; } diff --git a/modules/system/containers/default.nix b/modules/system/containers/default.nix index 920ffaf..154da23 100644 --- a/modules/system/containers/default.nix +++ b/modules/system/containers/default.nix @@ -9,6 +9,7 @@ _: { ./flaresolverr ./forgejo ./gluetun + ./homepage ./jellyfin ./lldap ./maddy diff --git a/modules/system/containers/firefly-iii/default.nix b/modules/system/containers/firefly-iii/default.nix index 229a7a9..427b16a 100644 --- a/modules/system/containers/firefly-iii/default.nix +++ b/modules/system/containers/firefly-iii/default.nix @@ -117,5 +117,12 @@ in }; }; + mySystemApps.homepage = { + services.Apps.FireflyIII = svc.mkHomepage "firefly" // { + container = "firefly-iii"; + icon = "firefly.png"; + description = "Personal finance management"; + }; + }; }; } diff --git a/modules/system/containers/forgejo/default.nix b/modules/system/containers/forgejo/default.nix index 780c467..e918df1 100644 --- a/modules/system/containers/forgejo/default.nix +++ b/modules/system/containers/forgejo/default.nix @@ -119,5 +119,12 @@ in environment.persistence."${config.mySystem.impermanence.persistPath}" = lib.mkIf config.mySystem.impermanence.enable { directories = [ cfg.dataDir ]; }; + + mySystemApps.homepage = { + services.Apps.Forgejo = svc.mkHomepage "forgejo" // { + href = "https://git.${config.mySystem.rootDomain}"; + description = "Git repositories"; + }; + }; }; } diff --git a/modules/system/containers/homepage/bookmarks.yaml b/modules/system/containers/homepage/bookmarks.yaml new file mode 100644 index 0000000..ed97d53 --- /dev/null +++ b/modules/system/containers/homepage/bookmarks.yaml @@ -0,0 +1 @@ +--- diff --git a/modules/system/containers/homepage/custom.css b/modules/system/containers/homepage/custom.css new file mode 100644 index 0000000..1bebe01 --- /dev/null +++ b/modules/system/containers/homepage/custom.css @@ -0,0 +1,298 @@ +/* +* Taken and inspired by: https://old.reddit.com/r/selfhosted/comments/1gfl70m/my_personal_dashboard_made_with_homepage_config/ +*/ + +/*================================== + ROOT VARIABLES +==================================*/ +:root { + /* Color palette for consistent theming */ + --color-border: #4d4e5e; + --color-tab-border: #4a4a54; + --color-tab-hover: #AC2747; + --color-background-primary: #11141d; + --color-background-hover: #08081c; + --color-block: #222432; + --color-block-hover: #2d2d4a; + --color-bookmark-icon: #1f233c; + + /* Gradient definition for animated borders */ + --gradient-border: linear-gradient(90deg, #F75050, #6C033D, #F75050); + + /* Consistent spacing scale */ + --spacing-xs: 5px; + --spacing-sm: 8px; + --spacing-md: 10px; + --spacing-lg: 30px; + --spacing-xl: 40px; + + /* Border properties for consistent styling */ + --border-width: 2px; + --border-radius: .375rem; + --border-radius-sm: .275rem; + + /* Animation timings for consistent motion */ + --animation-duration: 3s; + --transition-duration: 0.3s; + + /* Layout measurements */ + --logo-size: 66px; + + /* Tab-specific styling variables */ + --tab-border-width: 2px; + --tab-border-radius: .375rem; + --tab-backdrop-filter: blur(1px); +} + +/*================================== + TAB STYLES +==================================*/ +/* Container for all tabs */ +#myTab { + padding: var(--spacing-xs); + background: none; + backdrop-filter: none; + display: flex; + justify-content: flex-start; + gap: var(--spacing-xl); +} + +/* Individual tab styling */ +button[id$='-tab'] { + border-width: var(--tab-border-width); + border-radius: var(--tab-border-radius); + border-color: var(--color-tab-border); + backdrop-filter: var(--tab-backdrop-filter); + padding: 1px; + margin: 1px; + border-spacing: 0; + transition: border-color var(--transition-duration) ease; +} + +/* Tab hover effect */ +button[id$='-tab']:hover { + border-color: var(--color-tab-hover); +} + +/*================================== + HEADING STYLES +==================================*/ +/* Apply Merriweather font to the greeting */ +.information-widget-logo + .information-widget-greeting span { + font-weight: 700 !important; +} + +/* Logo size and positioning */ +.information-widget-logo img { + width: var(--logo-size) !important; + height: var(--logo-size) !important; + max-width: none; + max-height: none; + margin-left: var(--spacing-lg); +} + +/*================================== + SERVICE CARD STYLES +==================================*/ +/* Base service card container */ +.service-card { + position: relative; + padding: 0; + margin: 0; + border-radius: var(--border-radius); + background: transparent; + border: var(--border-width) solid var(--color-border); + overflow: visible; +} + +/* Background color for service data blocks */ +.bg-theme-200\/50, +.flex-1.service-block { + background-color: var(--color-block) !important; + transition: background-color var(--transition-duration) ease; +} + +/* Change block colors on service card hover */ +.service-card:hover [class*="bg-theme-200/50"], +.service-card:hover .flex-1.service-block { + background-color: var(--color-block-hover) !important; +} + +/* Animated border gradient effect */ +.service-card::before { + content: ''; + position: absolute; + inset: calc(-1 * var(--border-width)); + background: var(--gradient-border); + z-index: -2; + border-radius: var(--border-radius); + background-size: 200% 100%; + animation: wave var(--animation-duration) linear infinite; + opacity: 0; + transition: opacity var(--transition-duration) ease; +} + +/* Show animated border on hover */ +.service-card:hover::before { + opacity: 1; +} + +/* Animation keyframes for border gradient */ +@keyframes wave { + 0% { background-position: 100% 0; } + 100% { background-position: -100% 0; } +} + +/* Inner card background effect */ +.service-card::after { + content: ''; + position: absolute; + inset: var(--border-width); + background: var(--color-background-primary); + border-radius: var(--border-radius-sm); + z-index: -1; + transition: background-color var(--transition-duration) ease; +} + +/* Change background color on hover */ +.service-card:hover::after { + background: var(--color-background-hover); +} + +/* Hide default border on hover */ +.service-card:hover { + border-color: transparent; +} + +/*================================== + BOOKMARK SECTION STYLES +==================================*/ +/* Bookmark section container */ +.flex.flex-col.mt-3.bookmark-list { + border: var(--border-width) solid var(--color-border); + border-radius: var(--border-radius); + padding: var(--spacing-md) var(--spacing-sm) 0 var(--spacing-sm); + margin-bottom: var(--spacing-sm); +} + +/* Bookmark icon background */ +.bookmark-icon { + background-color: var(--color-bookmark-icon) !important; +} + +/*================================== + LAYOUT GROUP STYLES - HOMEPAGE +==================================*/ +/* Homepage layout container with calculations */ +:is([id="inner_wrapper"]:has(#tabs [aria-selected="true"][id="HOME-tab"])) #layout-groups { + --gap: var(--spacing-md); + --cols-4: calc(25% - (var(--gap) * 3 / 4)); + --cols-5: calc(20% - (var(--gap) * 4 / 5)); + + display: flex; + flex-wrap: wrap; + gap: var(--gap) !important; +} +/* First four services - 25% width each */ +:is([id="inner_wrapper"]:has(#tabs [aria-selected="true"][id="HOME-tab"])) #layout-groups > .services-group:nth-child(-n+4) { + flex: 1 1 var(--cols-4) !important; + max-width: var(--cols-4) !important; +} +/* Services 5-9 - 20% width each */ +:is([id="inner_wrapper"]:has(#tabs [aria-selected="true"][id="HOME-tab"])) #layout-groups > .services-group:nth-child(n+5):nth-child(-n+9) { + flex: 1 1 var(--cols-5) !important; + max-width: var(--cols-5) !important; +} +/* Hide services after the ninth one */ +:is([id="inner_wrapper"]:has(#tabs [aria-selected="true"][id="HOME-tab"])) #layout-groups > .services-group:nth-child(n+10) { + display: none !important; +} + +/*================================== + LAYOUT GROUP STYLES - STAT +==================================*/ +/* Media layout container with calculations */ +:is([id="inner_wrapper"]:has(#tabs [aria-selected="true"][id="STAT-tab"])) #layout-groups { + --gap: var(--spacing-md); + --cols-2: calc(50% - (var(--gap) / 2)); + --cols-3: calc(33.33% - (var(--gap) * 2 / 3)); + + display: flex; + flex-wrap: wrap; + gap: var(--gap) !important; +} +/* First 6 services - 33.33% width each */ +:is([id="inner_wrapper"]:has(#tabs [aria-selected="true"][id="STAT-tab"])) #layout-groups > .services-group:nth-child(-n+6) { + flex: 1 1 var(--cols-3) !important; + max-width: var(--cols-3) !important; +} +/* Services 7-8 - 50% width each */ +:is([id="inner_wrapper"]:has(#tabs [aria-selected="true"][id="STAT-tab"])) #layout-groups > .services-group:nth-child(n+7):nth-child(-n+8) { + flex: 1 1 var(--cols-2) !important; + max-width: var(--cols-2) !important; +} +/* Service 9 - 100% width */ +:is([id="inner_wrapper"]:has(#tabs [aria-selected="true"][id="STAT-tab"])) #layout-groups > .services-group:nth-child(9) { + flex: 1 1 100% !important; + max-width: 100% !important; +} +/* Hide services after the ninth one */ +:is([id="inner_wrapper"]:has(#tabs [aria-selected="true"][id="STAT-tab"])) #layout-groups > .services-group:nth-child(n+10) { + display: none !important; +} + +/*================================== + LAYOUT GROUP STYLES - DEPS +==================================*/ +/* DEPS layout container with calculations */ +:is([id="inner_wrapper"]:has(#tabs [aria-selected="true"][id="DEPS-tab"])) #layout-groups { + --gap: var(--spacing-md); + --cols-6: calc(16.667% - (var(--gap) * 5 / 6)); + --cols-1: 100%; + + display: flex; + flex-wrap: wrap; + gap: var(--gap) !important; +} + +/* First row: Six services - 16.667% width each */ +:is([id="inner_wrapper"]:has(#tabs [aria-selected="true"][id="DEPS-tab"])) #layout-groups > .services-group:nth-child(-n+6) { + flex: 1 1 var(--cols-6) !important; + max-width: var(--cols-6) !important; +} + +/* Second row: Single service - 100% width */ +:is([id="inner_wrapper"]:has(#tabs [aria-selected="true"][id="DEPS-tab"])) #layout-groups > .services-group:nth-child(7) { + flex: 1 1 var(--cols-1) !important; + max-width: var(--cols-1) !important; +} + +/* Hide services after the seventh one */ +:is([id="inner_wrapper"]:has(#tabs [aria-selected="true"][id="DEPS-tab"])) #layout-groups > .services-group:nth-child(n+8) { + display: none !important; +} + +/*================================== + TEXT STYLES +==================================*/ +/* Small text styling with pre-line whitespace */ +.text-sm { + white-space: pre-line; + text-align: center; + font-size: 1em; + + &::first-line { + margin-bottom: var(--spacing-xs); + } +} + +/* Hide elements with _hidden prefix */ +[data-name^="_hidden"] .service-title, +.bookmark-group .bookmark-group-name { + display: none; +} + +/* Hide footer completely */ +#footer { + display: none; +} diff --git a/modules/system/containers/homepage/default.nix b/modules/system/containers/homepage/default.nix new file mode 100644 index 0000000..b0be39d --- /dev/null +++ b/modules/system/containers/homepage/default.nix @@ -0,0 +1,165 @@ +{ + config, + lib, + pkgs, + svc, + ... +}: +let + cfg = config.mySystemApps.homepage; + wrapSet = + set: builtins.map (key: { "${key}" = builtins.getAttr key set; }) (builtins.attrNames set); + + settings = (pkgs.formats.yaml { }).generate "settings.yaml" cfg.settings; + services = (pkgs.formats.yaml { }).generate "services.yaml" ( + wrapSet (builtins.mapAttrs (_name: wrapSet) cfg.services) + ); + widgets = ./widgets.yaml; +in +{ + options.mySystemApps.homepage = { + enable = lib.mkEnableOption "homepage container"; + settings = lib.mkOption { + type = lib.types.attrs; + description = "Base settings for homepage"; + default = { + language = "en"; + theme = "dark"; + color = "zinc"; + target = "_self"; + headerStyle = "boxed"; + statusStyle = "dot"; + hideVersion = true; + disableCollapse = true; + useEqualHeights = true; + base = "https://www.${config.mySystem.rootDomain}"; + layout = { + Apps = { + style = "row"; + columns = 4; + }; + Media = { + style = "row"; + columns = 4; + }; + }; + }; + }; + services = lib.mkOption { + type = lib.types.attrsOf (lib.types.attrsOf lib.types.attrs); + description = "Services sorted by group/name config."; + default = { }; + example = { + "Media" = { + "jellyfin" = { + href = "https://jellyfin.example.com"; + }; + }; + }; + }; + secrets = lib.mkOption { + type = lib.types.attrsOf lib.types.str; + description = "Key-value pair of secret alias and file containing the value. These will be replaced in services.yaml file."; + example = lib.options.literalExpression '' + { MY_SECRET_VAR = config.sops.secrets."MY_SECRET_VAR".path } + ''; + default = { }; + }; + greeting = lib.mkOption { + type = lib.types.str; + description = "Title displayed in upper left corner."; + default = "homelab"; + }; + disks = lib.mkOption { + type = lib.types.attrsOf lib.types.str; + description = "Key-value pair of disks to be displayed in metrics, where key is the label, and value is mountpath."; + example = { + SYSTEM = "/"; + }; + default = { }; + }; + }; + + config = lib.mkIf cfg.enable { + mySystemApps.docker.startDockerSockProxy = true; + + virtualisation.oci-containers.containers.homepage = svc.mkContainer { + cfg = { + dependsOn = [ "socket-proxy" ]; + user = "1000:1000"; + image = "ghcr.io/gethomepage/homepage:v0.9.11@sha256:d41dca72f3a68d2c675eb232a448104af200096f05e2610ffbfdb16bc7f71410"; + environment = { + LOG_LEVEL = "debug"; + }; + extraOptions = [ + "--mount" + "type=tmpfs,destination=/app/config,tmpfs-mode=1777" + ]; + volumes = [ + "${./bookmarks.yaml}:/app/config/bookmarks.yaml:ro" + "${./custom.css}:/app/config/custom.css:ro" + "${settings}:/app/config/settings.yaml:ro" + "${./docker.yaml}:/app/config/docker.yaml:ro" + "/run/homepage/services.yaml:/app/config/services.yaml:ro" + "/run/homepage/widgets.yaml:/app/config/widgets.yaml:ro" + ]; + }; + opts = { + disableReadOnly = true; + }; + }; + + services = { + nginx.virtualHosts.homepage = svc.mkNginxVHost { + host = "www"; + proxyPass = "http://homepage.docker:3000"; + customCSP = '' + default-src 'self' 'unsafe-inline' data: blob: wss:; + connect-src 'self' https://api.github.com *.${config.mySystem.rootDomain}; + manifest-src 'self' *.${config.mySystem.rootDomain}; + img-src 'self' https://cdn.jsdelivr.net; object-src 'none'; + ''; + }; + }; + + systemd.services.docker-homepage = { + preStart = lib.mkAfter ( + '' + mkdir -p /run/homepage + cp ${services} /run/homepage/services.yaml + cp ${widgets} /run/homepage/widgets.yaml + sed -i"" 's,@@GREETING@@,${cfg.greeting},g' /run/homepage/widgets.yaml + '' + + ( + if config.mySystemApps.whoogle.enable then + '' + sed -i"" 's,@@SEARCH_PROVIDER@@,custom,g' /run/homepage/widgets.yaml + sed -i"" 's,@@SEARCH_URL@@,https://whoogle.${config.mySystem.rootDomain}/search?q=,g' /run/homepage/widgets.yaml + '' + else + '' + sed -i"" 's,@@SEARCH_PROVIDER@@,duckduckgo,g' /run/homepage/widgets.yaml + sed -i"" 's,@@SEARCH_URL@@,,g' /run/homepage/widgets.yaml + '' + ) + + '' + sed -i"" 's#@@DISKS_CONFIG@@#${ + builtins.concatStringsSep "" ( + builtins.map ( + label: + "\\n- { resources: { label: ${label}, expanded: true, disk: [\"${builtins.getAttr label cfg.disks}\"] } }\\n" + ) (builtins.attrNames cfg.disks) + ) + }#g' /run/homepage/widgets.yaml + chown 1000:1000 /run/homepage /run/homepage/services.yaml /run/homepage/widgets.yaml + '' + + (builtins.concatStringsSep "\n" ( + builtins.map ( + name: + "sed -i'' \"s,@@${name}@@,$(cat ${builtins.getAttr name cfg.secrets}),g\" /run/homepage/services.yaml" + ) (builtins.attrNames cfg.secrets) + )) + ); + }; + }; +} diff --git a/modules/system/containers/homepage/docker.yaml b/modules/system/containers/homepage/docker.yaml new file mode 100644 index 0000000..e5d0809 --- /dev/null +++ b/modules/system/containers/homepage/docker.yaml @@ -0,0 +1,4 @@ +--- +deedee: + host: socket-proxy + port: 2375 diff --git a/modules/system/containers/homepage/widgets.yaml b/modules/system/containers/homepage/widgets.yaml new file mode 100644 index 0000000..3dc16eb --- /dev/null +++ b/modules/system/containers/homepage/widgets.yaml @@ -0,0 +1,45 @@ +--- +- greeting: + text_size: 4xl + text: "@@GREETING@@" + +- resources: + expanded: false + cpu: true + cputemp: true + memory: true + refresh: 1000 + +- greeting: + text_size: 4xl + text: — + +# @@DISKS_CONFIG@@ + +- greeting: + text_size: 4xl + text: — + +- resources: + expanded: false + uptime: true + refresh: 1000 + +- search: + provider: "@@SEARCH_PROVIDER@@" + url: "@@SEARCH_URL@@" + focus: true + showSearchSuggestions: false + target: _parent + +- datetime: + text_size: md + format: + dateStyle: long + hour12: false + +- datetime: + text_size: 4xl + format: + timeStyle: short + hour12: false diff --git a/modules/system/containers/jellyfin/default.nix b/modules/system/containers/jellyfin/default.nix index 554d1a5..dbad0d2 100644 --- a/modules/system/containers/jellyfin/default.nix +++ b/modules/system/containers/jellyfin/default.nix @@ -23,11 +23,18 @@ in type = lib.types.str; description = "Path to directory containing movies and tv shows."; }; + sopsSecretPrefix = lib.mkOption { + type = lib.types.str; + description = "Prefix for sops secret, under which all ENVs will be appended."; + default = "system/apps/jellyfin/env"; + }; }; config = lib.mkIf cfg.enable { warnings = [ (lib.mkIf (!cfg.backup) "WARNING: Backups for jellyfin are disabled!") ]; + sops.secrets."${cfg.sopsSecretPrefix}/HOMEPAGE_API_KEY" = { }; + virtualisation.oci-containers.containers.jellyfin = svc.mkContainer { cfg = { image = "ghcr.io/deedee-ops/jellyfin:10.10.0@sha256:ff066c8f56f971d6e6ba7ac864c649bcb774e5c6ca860fccd475cab1c0623a68"; @@ -103,5 +110,26 @@ in environment.persistence."${config.mySystem.impermanence.persistPath}" = lib.mkIf config.mySystem.impermanence.enable { directories = [ cfg.dataDir ]; }; + + mySystemApps.homepage = { + services.Media.Jellyfin = svc.mkHomepage "jellyfin" // { + description = "Multimedia streaming library"; + widget = { + type = "jellyfin"; + url = "http://jellyfin:8096"; + key = "@@JELLYFIN_API_KEY@@"; + enableBlocks = true; + enableNowPlaying = false; + enableUser = false; + showEpisodeNumber = true; + expandOneStreamToTwoRows = false; + fields = [ + "movies" + "series" + ]; + }; + }; + secrets.JELLYFIN_API_KEY = config.sops.secrets."${cfg.sopsSecretPrefix}/HOMEPAGE_API_KEY".path; + }; }; } diff --git a/modules/system/containers/lldap/default.nix b/modules/system/containers/lldap/default.nix index 1bf32dd..f0cd314 100644 --- a/modules/system/containers/lldap/default.nix +++ b/modules/system/containers/lldap/default.nix @@ -91,5 +91,12 @@ in }; postgresqlBackup = lib.mkIf cfg.backup { databases = [ "lldap" ]; }; }; + + mySystemApps.homepage = { + services.Apps.LLDAP = svc.mkHomepage "lldap" // { + icon = "badge.png"; + description = "LDAP server"; + }; + }; }; } diff --git a/modules/system/containers/mail-archive/default.nix b/modules/system/containers/mail-archive/default.nix index 0f939aa..2fc509b 100644 --- a/modules/system/containers/mail-archive/default.nix +++ b/modules/system/containers/mail-archive/default.nix @@ -46,5 +46,13 @@ in environment.persistence."${config.mySystem.impermanence.persistPath}" = lib.mkIf config.mySystem.impermanence.enable { directories = [ cfg.dataDir ]; }; + + mySystemApps.homepage = { + services.Apps."Mail Archive" = svc.mkHomepage "mail" // { + description = "Mail archive browser."; + container = "mail-archive-dovecot"; + icon = "roundcube.svg"; + }; + }; }; } diff --git a/modules/system/containers/miniflux/default.nix b/modules/system/containers/miniflux/default.nix index 2e26722..0a9734d 100644 --- a/modules/system/containers/miniflux/default.nix +++ b/modules/system/containers/miniflux/default.nix @@ -9,6 +9,7 @@ let secretEnvs = [ "ADMIN_PASSWORD" "ADMIN_USERNAME" + "HOMEPAGE_API_KEY" "MINIFLUX__POSTGRES_PASSWORD" ]; in @@ -89,5 +90,21 @@ in }; postgresqlBackup = lib.mkIf cfg.backup { databases = [ "miniflux" ]; }; }; + + mySystemApps.homepage = { + services.Apps.Miniflux = svc.mkHomepage "miniflux" // { + description = "RSS Reader"; + widget = { + type = "miniflux"; + url = "http://miniflux:3000"; + key = "@@MINIFLUX_API_KEY@@"; + fields = [ + "unread" + "read" + ]; + }; + }; + secrets.MINIFLUX_API_KEY = config.sops.secrets."${cfg.sopsSecretPrefix}/HOMEPAGE_API_KEY".path; + }; }; } diff --git a/modules/system/containers/navidrome/default.nix b/modules/system/containers/navidrome/default.nix index 9c1b47a..ca8313d 100644 --- a/modules/system/containers/navidrome/default.nix +++ b/modules/system/containers/navidrome/default.nix @@ -75,5 +75,11 @@ in environment.persistence."${config.mySystem.impermanence.persistPath}" = lib.mkIf config.mySystem.impermanence.enable { directories = [ cfg.dataDir ]; }; + + mySystemApps.homepage = { + services.Media.Navidrome = svc.mkHomepage "navidrome" // { + description = "Music collection manager and player"; + }; + }; }; } diff --git a/modules/system/containers/paperless-ngx/paperless-ngx.nix b/modules/system/containers/paperless-ngx/paperless-ngx.nix index 5119c7a..939bfde 100644 --- a/modules/system/containers/paperless-ngx/paperless-ngx.nix +++ b/modules/system/containers/paperless-ngx/paperless-ngx.nix @@ -7,6 +7,7 @@ let cfg = config.mySystemApps.paperless-ngx; secretEnvs = [ + "HOMEPAGE_API_KEY" "PAPERLESS_DBPASS" "PAPERLESS_REDIS" "PAPERLESS_SECRET_KEY" @@ -111,13 +112,32 @@ in lib.mkIf config.mySystem.impermanence.enable { directories = [ cfg.dataDir ]; }; - mySystemApps.syncthing.extraPaths = { - "paperless-ngx/consume" = { - dest = "${cfg.dataDir}/data/consume"; + mySystemApps = { + syncthing.extraPaths = { + "paperless-ngx/consume" = { + dest = "${cfg.dataDir}/data/consume"; + }; + "paperless-ngx/documents" = { + dest = "${cfg.dataDir}/data/media/documents/archive"; + readOnly = true; + }; }; - "paperless-ngx/documents" = { - dest = "${cfg.dataDir}/data/media/documents/archive"; - readOnly = true; + + homepage = { + services.Apps.Paperless-NGX = svc.mkHomepage "paperless-ngx" // { + href = "https://paperless.${config.mySystem.rootDomain}"; + description = "Documents OCR and archive"; + widget = { + type = "paperlessngx"; + url = "http://paperless-ngx:8000"; + key = "@@PAPERLESSNGX_API_KEY@@"; + fields = [ + "inbox" + "total" + ]; + }; + }; + secrets.PAPERLESSNGX_API_KEY = config.sops.secrets."${cfg.sopsSecretPrefix}/HOMEPAGE_API_KEY".path; }; }; }; diff --git a/modules/system/containers/piped/frontend.nix b/modules/system/containers/piped/frontend.nix index c785853..6b53aae 100644 --- a/modules/system/containers/piped/frontend.nix +++ b/modules/system/containers/piped/frontend.nix @@ -36,5 +36,13 @@ in useAuthelia = false; }; }; + + mySystemApps.homepage = { + services.Apps.Piped = svc.mkHomepage "piped" // { + container = "piped-api"; + icon = "https://cdn.jsdelivr.net/gh/TeamPiped/Piped/public/img/icons/logo.svg"; + description = "Private YouTube proxy"; + }; + }; }; } diff --git a/modules/system/containers/prowlarr/default.nix b/modules/system/containers/prowlarr/default.nix index 31bad25..0c464ea 100644 --- a/modules/system/containers/prowlarr/default.nix +++ b/modules/system/containers/prowlarr/default.nix @@ -80,5 +80,25 @@ in }; postgresqlBackup = lib.mkIf cfg.backup { databases = [ "prowlarr" ]; }; }; + + mySystemApps.homepage = { + services.Media.Prowlarr = svc.mkHomepage "prowlarr" // { + description = "Torrent tracker management"; + widget = { + type = "prowlarr"; + url = "http://prowlarr:9696"; + key = "@@PROWLARR_API_KEY@@"; + fields = [ + "numberOfGrabs" + "numberOfFailGrabs" + "numberOfQueries" + "numberOfFailQueries" + ]; + }; + }; + secrets.PROWLARR_API_KEY = + config.sops.secrets."${cfg.sopsSecretPrefix}/PROWLARR__AUTH__APIKEY".path; + }; + }; } diff --git a/modules/system/containers/qbittorrent/default.nix b/modules/system/containers/qbittorrent/default.nix index b3310a9..286efa2 100644 --- a/modules/system/containers/qbittorrent/default.nix +++ b/modules/system/containers/qbittorrent/default.nix @@ -22,6 +22,11 @@ in type = lib.types.str; description = "Path to directory containing downloads."; }; + sopsSecretPrefix = lib.mkOption { + type = lib.types.str; + description = "Prefix for sops secret, under which all ENVs will be appended."; + default = "system/apps/qbittorrent/env"; + }; }; config = lib.mkIf cfg.enable { @@ -33,6 +38,11 @@ in } ]; + sops.secrets = { + "${cfg.sopsSecretPrefix}/WEBUI_USERNAME" = { }; + "${cfg.sopsSecretPrefix}/WEBUI_PASSWORD" = { }; + }; + mySystemApps.gluetun.extraPorts = [ 8080 ]; virtualisation.oci-containers.containers.qbittorrent = svc.mkContainer { @@ -75,5 +85,28 @@ in environment.persistence."${config.mySystem.impermanence.persistPath}" = lib.mkIf config.mySystem.impermanence.enable { directories = [ cfg.dataDir ]; }; + + mySystemApps.homepage = { + services.Media.qBittorrent = svc.mkHomepage "qbittorrent" // { + href = "https://torrents.${config.mySystem.rootDomain}"; + description = "Torrent downloader"; + widget = { + type = "qbittorrent"; + url = "http://gluetun:8080"; + username = "@@QBITTORRENT_USERNAME@@"; + password = "@@QBITTORRENT_PASSWORD@@"; + fields = [ + "leech" + "download" + "seed" + "upload" + ]; + }; + }; + secrets = { + QBITTORRENT_USERNAME = config.sops.secrets."${cfg.sopsSecretPrefix}/WEBUI_USERNAME".path; + QBITTORRENT_PASSWORD = config.sops.secrets."${cfg.sopsSecretPrefix}/WEBUI_PASSWORD".path; + }; + }; }; } diff --git a/modules/system/containers/radarr/default.nix b/modules/system/containers/radarr/default.nix index ff5e980..dd735c9 100644 --- a/modules/system/containers/radarr/default.nix +++ b/modules/system/containers/radarr/default.nix @@ -107,5 +107,23 @@ in environment.persistence."${config.mySystem.impermanence.persistPath}" = lib.mkIf config.mySystem.impermanence.enable { directories = [ cfg.dataDir ]; }; + + mySystemApps.homepage = { + services.Media.Radarr = svc.mkHomepage "radarr" // { + description = "Movies management"; + widget = { + type = "radarr"; + url = "http://radarr:7878"; + key = "@@RADARR_API_KEY@@"; + fields = [ + "wanted" + "movies" + "queued" + "missing" + ]; + }; + }; + secrets.RADARR_API_KEY = config.sops.secrets."${cfg.sopsSecretPrefix}/RADARR__AUTH__APIKEY".path; + }; }; } diff --git a/modules/system/containers/redlib/default.nix b/modules/system/containers/redlib/default.nix index f806937..b8d3c5e 100644 --- a/modules/system/containers/redlib/default.nix +++ b/modules/system/containers/redlib/default.nix @@ -62,5 +62,12 @@ in useAuthelia = false; }; }; + + mySystemApps.homepage = { + services.Apps.Redlib = svc.mkHomepage "redlib" // { + icon = "https://cdn.jsdelivr.net/gh/redlib-org/redlib@main/static/logo.svg"; + description = "Private Reddit proxy"; + }; + }; }; } diff --git a/modules/system/containers/sonarr/default.nix b/modules/system/containers/sonarr/default.nix index 24ba87b..929b3c0 100644 --- a/modules/system/containers/sonarr/default.nix +++ b/modules/system/containers/sonarr/default.nix @@ -108,5 +108,22 @@ in environment.persistence."${config.mySystem.impermanence.persistPath}" = lib.mkIf config.mySystem.impermanence.enable { directories = [ cfg.dataDir ]; }; + + mySystemApps.homepage = { + services.Media.Sonarr = svc.mkHomepage "sonarr" // { + description = "TV Shows management"; + widget = { + type = "sonarr"; + url = "http://sonarr:8989"; + key = "@@SONARR_API_KEY@@"; + fields = [ + "wanted" + "series" + "queued" + ]; + }; + }; + secrets.SONARR_API_KEY = config.sops.secrets."${cfg.sopsSecretPrefix}/SONARR__AUTH__APIKEY".path; + }; }; } diff --git a/modules/system/containers/syncthing/default.nix b/modules/system/containers/syncthing/default.nix index bca4ac2..4b81cb6 100644 --- a/modules/system/containers/syncthing/default.nix +++ b/modules/system/containers/syncthing/default.nix @@ -120,5 +120,11 @@ in options = [ ("bind" + (lib.optionalString value.readOnly ",ro")) ]; }; }) cfg.extraPaths; + + mySystemApps.homepage = { + services.Apps.Syncthing = svc.mkHomepage "syncthing" // { + description = "Continuous File Synchronization"; + }; + }; }; } diff --git a/modules/system/containers/vaultwarden/default.nix b/modules/system/containers/vaultwarden/default.nix index 6d6138c..77d2969 100644 --- a/modules/system/containers/vaultwarden/default.nix +++ b/modules/system/containers/vaultwarden/default.nix @@ -101,5 +101,11 @@ in environment.persistence."${config.mySystem.impermanence.persistPath}" = lib.mkIf config.mySystem.impermanence.enable { directories = [ cfg.dataDir ]; }; + + mySystemApps.homepage = { + services.Apps.Vaultwarden = svc.mkHomepage "vaultwarden" // { + description = "Password manager"; + }; + }; }; } diff --git a/modules/system/containers/wakapi/default.nix b/modules/system/containers/wakapi/default.nix index 927d108..641f358 100644 --- a/modules/system/containers/wakapi/default.nix +++ b/modules/system/containers/wakapi/default.nix @@ -94,5 +94,12 @@ in }; postgresqlBackup = lib.mkIf cfg.backup { databases = [ "wakapi" ]; }; }; + + mySystemApps.homepage = { + services.Apps.Wakapi = svc.mkHomepage "wakapi" // { + icon = "wakapi.png"; + description = "Coding time tracker"; + }; + }; }; } diff --git a/modules/system/containers/whoogle/default.nix b/modules/system/containers/whoogle/default.nix index eb29441..8dee7a0 100644 --- a/modules/system/containers/whoogle/default.nix +++ b/modules/system/containers/whoogle/default.nix @@ -50,5 +50,12 @@ in useAuthelia = false; }; }; + + mySystemApps.homepage = { + services.Apps.Whoogle = svc.mkHomepage "whoogle" // { + icon = "whoogle.png"; + description = "Google Proxy and Anonymizer"; + }; + }; }; } diff --git a/modules/system/lib.nix b/modules/system/lib.nix index a2da087..0ca0175 100644 --- a/modules/system/lib.nix +++ b/modules/system/lib.nix @@ -6,6 +6,12 @@ }: { _module.args.svc = { + mkHomepage = name: { + icon = "${name}.svg"; + href = "https://${name}.${config.mySystem.rootDomain}"; + server = "deedee"; + container = name; + }; mkNginxVHost = { host, @@ -130,9 +136,7 @@ }; } cfg) // { - dependsOn = [ - "network-prepare" - ] ++ (lib.optionals args.routeThroughVPN [ "gluetun" ]) ++ (cfg.dependsOn or [ ]); + dependsOn = (lib.optionals args.routeThroughVPN [ "gluetun" ]) ++ (cfg.dependsOn or [ ]); extraOptions = (lib.optionals (!args.disableReadOnly) [ "--read-only" ]) ++ [