From 9a89479f6638e561c687ea8d1a54683841bc6ca0 Mon Sep 17 00:00:00 2001 From: soraefir Date: Thu, 4 Jun 2026 00:30:29 +0200 Subject: [PATCH] Refactor --- modules/server/containers/apps/authentik.nix | 181 ++++++------ modules/server/containers/apps/calibre.nix | 58 ++-- modules/server/containers/apps/collabora.nix | 53 ++-- modules/server/containers/apps/ethercalc.nix | 41 +-- modules/server/containers/apps/etherpad.nix | 87 +++--- modules/server/containers/apps/freshrss.nix | 93 +++--- modules/server/containers/apps/frigate.nix | 85 +++--- modules/server/containers/apps/gitea.nix | 255 ++++++++--------- modules/server/containers/apps/handbrake.nix | 86 +++--- .../server/containers/apps/homeassistant.nix | 101 +++---- modules/server/containers/apps/homepage.nix | 52 ++-- modules/server/containers/apps/immich.nix | 176 ++++++------ modules/server/containers/apps/influx.nix | 121 ++++---- modules/server/containers/apps/invidious.nix | 93 +++--- modules/server/containers/apps/jellyfin.nix | 265 +++++++++--------- modules/server/containers/apps/nextcloud.nix | 111 ++++---- modules/server/containers/apps/openhab.nix | 134 ++++----- modules/server/containers/apps/searxng.nix | 42 +-- modules/server/containers/apps/selfmark.nix | 161 +++++------ modules/server/containers/apps/servarr.nix | 28 +- modules/server/containers/apps/suwayomi.nix | 83 +++--- modules/server/containers/apps/traefik.nix | 140 ++++----- .../server/containers/apps/transmission.nix | 81 +++--- modules/server/containers/apps/umami.nix | 70 ++--- modules/server/containers/builder.nix | 83 +++--- modules/server/containers/default.nix | 85 +++--- 26 files changed, 1385 insertions(+), 1380 deletions(-) diff --git a/modules/server/containers/apps/authentik.nix b/modules/server/containers/apps/authentik.nix index 8c7fa26..fb1f8b4 100644 --- a/modules/server/containers/apps/authentik.nix +++ b/modules/server/containers/apps/authentik.nix @@ -16,99 +16,104 @@ let // (if serverCfg.containers?nextcloud then { NEXTCLOUD_DOMAIN = "${serverCfg.containers.nextcloud.subdomain}.${serverCfg.domain}";} else {}); }; in { - sops = true; - db = true; - paths = [{ - path="${serverCfg.path.config}/authentik"; - owner = "1000:1000"; - dirs = ["media" "templates"]; - mode = "0755"; - }]; - - containers = { - server = builder.mkContainer { - subdomain = containerCfg.subdomain; - image = "ghcr.io/goauthentik/server:${version}"; - port = 9000; - secret = name; - extraEnv = { - AUTHENTIK_REDIS__HOST = builder.host; - AUTHENTIK_POSTGRESQL__HOST = builder.host; - AUTHENTIK_POSTGRESQL__USER = "authentik_user"; - AUTHENTIK_POSTGRESQL__NAME = "authentik_db"; - AUTHENTIK_POSAUTHENTIK_POSTGRESQL__SSLMODE = "false"; - AUTHENTIK_EMAIL__HOST = serverCfg.mailDomain; - AUTHENTIK_EMAIL__PORT = "587"; - AUTHENTIK_EMAIL__USERNAME = "noreply@${serverCfg.domain}"; - AUTHENTIK_EMAIL__USE_TLS = "true"; - AUTHENTIK_EMAIL__USE_SSL = "false"; - AUTHENTIK_EMAIL__TIMEOUT = "10"; - AUTHENTIK_EMAIL__FROM = "sso@noreply.${serverCfg.domain}"; - AUTHENTIK_DISABLE_UPDATE_CHECK = "true"; - AUTHENTIK_POSTGRESQL__SSLMODE = "disable"; - }; - overrides = { - environmentFiles = [ config.sops.secrets."AUTHENTIK".path config.sops.secrets."CUSTOM".path ] ; - - cmd = [ "server" ]; - volumes = [ - "${serverCfg.path.config}/authentik/media:/media" - "${serverCfg.path.config}/authentik/templates:/templates" - "${authentikData}:/blueprints/custom:ro" - ]; - }; - }; - - worker = builder.mkContainer { - image = "ghcr.io/goauthentik/server:${version}"; - secret = name; - extraEnv = { - AUTHENTIK_REDIS__HOST = builder.host; - AUTHENTIK_POSTGRESQL__HOST = builder.host; - AUTHENTIK_POSTGRESQL__USER = "authentik_user"; - AUTHENTIK_POSTGRESQL__NAME = "authentik_db"; - AUTHENTIK_POSAUTHENTIK_POSTGRESQL__SSLMODE = "false"; - AUTHENTIK_DISABLE_UPDATE_CHECK = "true"; - AUTHENTIK_POSTGRESQL__SSLMODE = "disable"; - }; - overrides = { - cmd = [ "worker" ]; - volumes = [ - "${serverCfg.path.config}/authentik/media:/media" - "${serverCfg.path.config}/authentik/templates:/templates" - "${authentikData}:/blueprints/custom:ro" - ]; - }; - }; - - ldap = builder.mkContainer { - image = "ghcr.io/goauthentik/ldap:${version}"; - secret = name; - extraEnv = { - AUTHENTIK_HOST = "https://${containerCfg.subdomain}.${serverCfg.domain}"; - AUTHENTIK_INSECURE = "false"; - }; - }; + requires = { + secrets = [ name ]; + databases = [ name ]; }; - setup = { - trigger = "worker"; - script = pkgs.writeShellScript "setup" '' - # Define the command wrapper - AK="${pkgs.podman}/bin/podman --events-backend=none exec --env-file ${config.sops.secrets."CUSTOM".path} -e DOMAIN=${serverCfg.domain} -u root authentik-worker ak" + runtime = { + paths = [{ + path="${serverCfg.path.config}/authentik"; + owner = "1000:1000"; + dirs = ["media" "templates"]; + mode = "0755"; + }]; - $AK apply_blueprint /blueprints/custom/authentik.yaml - $AK apply_blueprint /blueprints/custom/traefik.yaml - $AK apply_blueprint /blueprints/custom/ldap.yaml + containers = { + server = builder.mkContainer { + subdomain = containerCfg.subdomain; + image = "ghcr.io/goauthentik/server:${version}"; + port = 9000; + secret = name; + extraEnv = { + AUTHENTIK_REDIS__HOST = builder.host; + AUTHENTIK_POSTGRESQL__HOST = builder.host; + AUTHENTIK_POSTGRESQL__USER = "authentik_user"; + AUTHENTIK_POSTGRESQL__NAME = "authentik_db"; + AUTHENTIK_POSAUTHENTIK_POSTGRESQL__SSLMODE = "false"; + AUTHENTIK_EMAIL__HOST = serverCfg.mailDomain; + AUTHENTIK_EMAIL__PORT = "587"; + AUTHENTIK_EMAIL__USERNAME = "noreply@${serverCfg.domain}"; + AUTHENTIK_EMAIL__USE_TLS = "true"; + AUTHENTIK_EMAIL__USE_SSL = "false"; + AUTHENTIK_EMAIL__TIMEOUT = "10"; + AUTHENTIK_EMAIL__FROM = "sso@noreply.${serverCfg.domain}"; + AUTHENTIK_DISABLE_UPDATE_CHECK = "true"; + AUTHENTIK_POSTGRESQL__SSLMODE = "disable"; + }; + overrides = { + environmentFiles = [ config.sops.secrets."AUTHENTIK".path config.sops.secrets."CUSTOM".path ] ; + + cmd = [ "server" ]; + volumes = [ + "${serverCfg.path.config}/authentik/media:/media" + "${serverCfg.path.config}/authentik/templates:/templates" + "${authentikData}:/blueprints/custom:ro" + ]; + }; + }; - ${lib.optionalString (serverCfg.containers ? gitea) ''$AK apply_blueprint /blueprints/custom/gitea.yaml''} - ${lib.optionalString (serverCfg.containers ? jellyfin) ''$AK apply_blueprint /blueprints/custom/jellyfin.yaml''} - ${lib.optionalString (serverCfg.containers ? nextcloud) ''$AK apply_blueprint /blueprints/custom/nextcloud.yaml''} - ${lib.optionalString (serverCfg.containers ? immich) ''$AK apply_blueprint /blueprints/custom/immich.yaml''} - ${lib.optionalString (serverCfg.containers ? freshrss) ''$AK apply_blueprint /blueprints/custom/freshrss.yaml''} - ${lib.optionalString (serverCfg.containers ? homepage) ''$AK apply_blueprint /blueprints/custom/homepage.yaml''} + worker = builder.mkContainer { + image = "ghcr.io/goauthentik/server:${version}"; + secret = name; + extraEnv = { + AUTHENTIK_REDIS__HOST = builder.host; + AUTHENTIK_POSTGRESQL__HOST = builder.host; + AUTHENTIK_POSTGRESQL__USER = "authentik_user"; + AUTHENTIK_POSTGRESQL__NAME = "authentik_db"; + AUTHENTIK_POSAUTHENTIK_POSTGRESQL__SSLMODE = "false"; + AUTHENTIK_DISABLE_UPDATE_CHECK = "true"; + AUTHENTIK_POSTGRESQL__SSLMODE = "disable"; + }; + overrides = { + cmd = [ "worker" ]; + volumes = [ + "${serverCfg.path.config}/authentik/media:/media" + "${serverCfg.path.config}/authentik/templates:/templates" + "${authentikData}:/blueprints/custom:ro" + ]; + }; + }; - echo "Completed Authentik Setup" + ldap = builder.mkContainer { + image = "ghcr.io/goauthentik/ldap:${version}"; + secret = name; + extraEnv = { + AUTHENTIK_HOST = "https://${containerCfg.subdomain}.${serverCfg.domain}"; + AUTHENTIK_INSECURE = "false"; + }; + }; + }; + + setup = { + trigger = "worker"; + script = pkgs.writeShellScript "setup" '' + # Define the command wrapper + AK="${pkgs.podman}/bin/podman --events-backend=none exec --env-file ${config.sops.secrets."CUSTOM".path} -e DOMAIN=${serverCfg.domain} -u root authentik-worker ak" + + $AK apply_blueprint /blueprints/custom/authentik.yaml + $AK apply_blueprint /blueprints/custom/traefik.yaml + $AK apply_blueprint /blueprints/custom/ldap.yaml + + ${lib.optionalString (serverCfg.containers ? gitea) ''$AK apply_blueprint /blueprints/custom/gitea.yaml''} + ${lib.optionalString (serverCfg.containers ? jellyfin) ''$AK apply_blueprint /blueprints/custom/jellyfin.yaml''} + ${lib.optionalString (serverCfg.containers ? nextcloud) ''$AK apply_blueprint /blueprints/custom/nextcloud.yaml''} + ${lib.optionalString (serverCfg.containers ? immich) ''$AK apply_blueprint /blueprints/custom/immich.yaml''} + ${lib.optionalString (serverCfg.containers ? freshrss) ''$AK apply_blueprint /blueprints/custom/freshrss.yaml''} + ${lib.optionalString (serverCfg.containers ? homepage) ''$AK apply_blueprint /blueprints/custom/homepage.yaml''} + + echo "Completed Authentik Setup" ''; + }; }; } diff --git a/modules/server/containers/apps/calibre.nix b/modules/server/containers/apps/calibre.nix index 9f2be1e..d93462a 100644 --- a/modules/server/containers/apps/calibre.nix +++ b/modules/server/containers/apps/calibre.nix @@ -3,36 +3,34 @@ let version = "latest"; serverCfg = config.syscfg.server; in { - sops = false; - db = false; + runtime = { + containers = { + server = builder.mkContainer { + subdomain = containerCfg.subdomain; + image = "crocodilestick/calibre-web-automated:${version}"; + port = 8083; + # secret = name; + extraEnv = { + CWA_PORT_OVERRIDE = "8083"; - - containers = { - server = builder.mkContainer { - subdomain = containerCfg.subdomain; - image = "crocodilestick/calibre-web-automated:${version}"; - port = 8083; - # secret = name; - extraEnv = { - CWA_PORT_OVERRIDE = "8083"; - - PUID = "1000"; - PGID = "1000"; - #HARDCOVER_TOKEN= .... - TRUSTED_PROXY_COUNT= "1"; - }; - extraLabels = { - "traefik.http.routers.${containerCfg.subdomain}-login.rule" = "Host(`${containerCfg.subdomain}.${serverCfg.domain}`)"; - "traefik.http.routers.${containerCfg.subdomain}-login.middlewares" = if (serverCfg.containers?authentik) then "authentik" else ""; - "traefik.http.routers.${containerCfg.subdomain}-login.priority" = "100"; - "traefik.http.routers.${containerCfg.subdomain}-login.entrypoints" = "web-secure"; - "traefik.http.routers.${containerCfg.subdomain}-login.tls" = "true"; - }; - overrides = { - volumes = [ - "${serverCfg.path.book}:/calibre-library" - "${serverCfg.path.dlBook}:/cwa-book-ingest" - ]; + PUID = "1000"; + PGID = "1000"; + #HARDCOVER_TOKEN= .... + TRUSTED_PROXY_COUNT= "1"; + }; + extraLabels = { + "traefik.http.routers.${containerCfg.subdomain}-login.rule" = "Host(`${containerCfg.subdomain}.${serverCfg.domain}`)"; + "traefik.http.routers.${containerCfg.subdomain}-login.middlewares" = if (serverCfg.containers?authentik) then "authentik" else ""; + "traefik.http.routers.${containerCfg.subdomain}-login.priority" = "100"; + "traefik.http.routers.${containerCfg.subdomain}-login.entrypoints" = "web-secure"; + "traefik.http.routers.${containerCfg.subdomain}-login.tls" = "true"; + }; + overrides = { + volumes = [ + "${serverCfg.path.book}:/calibre-library" + "${serverCfg.path.dlBook}:/cwa-book-ingest" + ]; + }; }; }; }; @@ -41,4 +39,4 @@ in { # -X POST # -H 'Content-Type: application/x-www-form-urlencoded; charset=UTF-8' # --data-raw 'csrf_token=${CSRF_TOKEN}&config_certfile=&config_keyfile=&config_updatechannel=0&config_trustedhosts=&config_log_level=20&config_logfile=%2Fdev%2Fstdout&config_access_logfile=%2Fconfig%2Faccess.log&config_embed_metadata=on&config_uploading=on&config_upload_formats=m4b%2Cacsm%2Cdoc%2Cpdf%2Cmp3%2Codt%2Ccbr%2Crtf%2Clit%2Cprc%2Cm4a%2Cdjv%2Cfb2%2Copus%2Cdocx%2Cazw3%2Cepub%2Cdjvu%2Cwav%2Ccb7%2Ccbz%2Cmp4%2Ckfx-zip%2Cmobi%2Ccbt%2Cogg%2Ckfx%2Ckepub%2Ctxt%2Cazw%2Chtml%2Cflac&config_external_port=8083&config_goodreads_api_key=&config_hardcover_token=&config_use_https=on&config_reverse_proxy_login_header_name=&config_login_type=1&config_ldap_provider_url=sso.test.helcel.net&config_ldap_port=389&config_ldap_encryption=0&config_ldap_cacert_path=&config_ldap_cert_path=&config_ldap_key_path=&config_ldap_authentication=2&config_ldap_serv_username=cn%3Dldap-service%2Cou%3Dusers%2C%24%7BLDAP_DC_DOMAIN%7D&config_ldap_serv_password_e=%24DEFAULT_LDAP_PASSWORD&config_ldap_dn=%24%7BLDAP_DC_DOMAIN%7D&config_ldap_user_object=(memberOf%3Dcn%3Dcloud%2Cou%3Dgroups%2C%24%7BLDAP_DC_DOMAIN%7D)&config_ldap_openldap=on&config_ldap_auto_create_users=on&config_ldap_group_object_filter=(memberOf%3Dcn%3Dcloud%2Cou%3Dgroups%2C%24%7BLDAP_DC_DOMAIN%7D)&config_ldap_group_name=cloud&config_ldap_group_members_field=memberUid&ldap_import_user_filter=0&config_ldap_member_user_object=&config_generic_oauth_metadata_url=&config_generic_oauth_server_url=&config_generic_oauth_auth_url=&config_generic_oauth_token_url=&config_generic_oauth_userinfo_url=&config_generic_oauth_scope=email+openid+profile&config_oauth_redirect_host=&config_generic_oauth_client_id=&config_generic_oauth_client_secret=&config_generic_oauth_username_mapper=preferred_username&config_generic_oauth_email_mapper=email&config_generic_oauth_admin_group=admin&config_generic_oauth_login_button=OpenID+Connect&config_1_oauth_client_id=&config_1_oauth_client_secret=&config_2_oauth_client_id=&config_2_oauth_client_secret=&config_binariesdir=%2Fusr%2Fbin&config_calibre=&config_kepubifypath=%2Fusr%2Fbin%2Fkepubify&config_rarfile_location=%2Fusr%2Fbin%2Funrar&config_enable_oauth_group_admin_management=on&config_ratelimiter=on&config_limiter_uri=&config_limiter_options=&config_check_extensions=on&config_session=1&config_password_policy=on&config_password_min_length=8&config_password_number=on&config_password_lower=on&config_password_upper=on&config_password_character=on&config_password_special=on' -} \ No newline at end of file +} diff --git a/modules/server/containers/apps/collabora.nix b/modules/server/containers/apps/collabora.nix index b9106dd..0580041 100644 --- a/modules/server/containers/apps/collabora.nix +++ b/modules/server/containers/apps/collabora.nix @@ -3,31 +3,34 @@ let version = "latest"; serverCfg = config.syscfg.server; in { - sops = true; - containers = { - server = builder.mkContainer { - subdomain = containerCfg.subdomain; - image = "collabora/code:${version}"; - port = 9980; - secret = name; - extraEnv = { - "aliasgroup1" = "https://${serverCfg.containers.nextcloud.subdomain}.${serverCfg.domain}"; - "server_name" = "${containerCfg.subdomain}.${serverCfg.domain}"; - "username" = "collabora_user"; - "VIRTUAL_HOST" = "${containerCfg.subdomain}.${serverCfg.domain}"; - "VIRTUAL_PORT" = "9980"; - "VIRTUAL_PROTO" = "http"; - "DONT_GEN_SSL_CERT" = "true"; - "RESOLVE_TO_PROXY_IP" = "true"; - "extra_params" = "--o:ssl.enable=false --o:ssl.termination=true"; - "dictionaries" = "en fr de jp no"; - }; - - overrides = { - volumes = [ - "${pkgs.noto-fonts}/share/fonts/noto:/opt/collaboraoffice/share/fonts/truetype/noto:ro" - "${pkgs.ibm-plex}/share/fonts/opentype:/opt/collaboraoffice/share/fonts/opentype/plex:ro" - ]; + requires.secrets = [ name ]; + + runtime = { + containers = { + server = builder.mkContainer { + subdomain = containerCfg.subdomain; + image = "collabora/code:${version}"; + port = 9980; + secret = name; + extraEnv = { + "aliasgroup1" = "https://${serverCfg.containers.nextcloud.subdomain}.${serverCfg.domain}"; + "server_name" = "${containerCfg.subdomain}.${serverCfg.domain}"; + "username" = "collabora_user"; + "VIRTUAL_HOST" = "${containerCfg.subdomain}.${serverCfg.domain}"; + "VIRTUAL_PORT" = "9980"; + "VIRTUAL_PROTO" = "http"; + "DONT_GEN_SSL_CERT" = "true"; + "RESOLVE_TO_PROXY_IP" = "true"; + "extra_params" = "--o:ssl.enable=false --o:ssl.termination=true"; + "dictionaries" = "en fr de jp no"; + }; + + overrides = { + volumes = [ + "${pkgs.noto-fonts}/share/fonts/noto:/opt/collaboraoffice/share/fonts/truetype/noto:ro" + "${pkgs.ibm-plex}/share/fonts/opentype:/opt/collaboraoffice/share/fonts/opentype/plex:ro" + ]; + }; }; }; }; diff --git a/modules/server/containers/apps/ethercalc.nix b/modules/server/containers/apps/ethercalc.nix index 9d00b73..83b633a 100644 --- a/modules/server/containers/apps/ethercalc.nix +++ b/modules/server/containers/apps/ethercalc.nix @@ -13,27 +13,30 @@ let }; }; in { - sops = true; - paths = [{ - path="${serverCfg.path.data}/ethercalc/"; - mode = "0666"; - }]; + requires.secrets = [ name ]; - containers = { - server = builder.mkContainer { - subdomain = containerCfg.subdomain; - imageStream = image; - port = 8080; - secret = name; - extraEnv = { - ETHERCALC_PORT = "8080"; - #CONNECT TO REDIS + runtime = { + paths = [{ + path="${serverCfg.path.data}/ethercalc/"; + mode = "0666"; + }]; + + containers = { + server = builder.mkContainer { + subdomain = containerCfg.subdomain; + imageStream = image; + port = 8080; + secret = name; + extraEnv = { + ETHERCALC_PORT = "8080"; + #CONNECT TO REDIS + }; + overrides = { + volumes = [ + "${serverCfg.path.data}/ethercalc:/data" + ]; + }; }; - overrides = { - volumes = [ - "${serverCfg.path.data}/ethercalc:/data" - ]; - }; }; }; } diff --git a/modules/server/containers/apps/etherpad.nix b/modules/server/containers/apps/etherpad.nix index 6887708..116ce0b 100644 --- a/modules/server/containers/apps/etherpad.nix +++ b/modules/server/containers/apps/etherpad.nix @@ -76,49 +76,54 @@ let }; }; in { - sops = true; - db = true; - paths = [{ - path="${serverCfg.path.config}/etherpad/"; - mode = "0444"; - }]; + requires = { + secrets = [ name ]; + databases = [ name ]; + }; - containers = { - server = builder.mkContainer { - subdomain = containerCfg.subdomain; - imageStream = image; - port = 8080; - secret = name; - extraEnv = { - TITLE = "Pad"; - PORT ="8080"; - DB_TYPE = "postgres"; - DB_HOST = builder.host; - DB_NAME = "etherpad_db"; - DB_USER = "etherpad_user"; - TRUST_PROXY = "true"; - DB_CHARSET = "utf8mb4"; - DEFAULT_PAD_TEXT = ""; - PAD_OPTIONS_SHOW_LINE_NUMBERS = "true"; - PAD_OPTIONS_USE_MONOSPACE_FONT = "true"; - SKIN_VARIANTS = "super-dark-toolbar light-editor dark-background"; + runtime = { + paths = [{ + path="${serverCfg.path.config}/etherpad/"; + mode = "0444"; + }]; + + containers = { + server = builder.mkContainer { + subdomain = containerCfg.subdomain; + imageStream = image; + port = 8080; + secret = name; + extraEnv = { + TITLE = "Pad"; + PORT ="8080"; + DB_TYPE = "postgres"; + DB_HOST = builder.host; + DB_NAME = "etherpad_db"; + DB_USER = "etherpad_user"; + TRUST_PROXY = "true"; + DB_CHARSET = "utf8mb4"; + DEFAULT_PAD_TEXT = ""; + PAD_OPTIONS_SHOW_LINE_NUMBERS = "true"; + PAD_OPTIONS_USE_MONOSPACE_FONT = "true"; + SKIN_VARIANTS = "super-dark-toolbar light-editor dark-background"; + }; + overrides = { + cmd = [ "--settings" "/etc/etherpad/settings.json" "--apikey" "/etc/etherpad/APIKEY.txt" ]; + volumes = [ + "${settings}:/etc/etherpad/settings.json" + "${serverCfg.path.config}/etherpad/APIKEY.txt:/etc/etherpad/APIKEY.txt:ro" + ]; + }; }; - overrides = { - cmd = [ "--settings" "/etc/etherpad/settings.json" "--apikey" "/etc/etherpad/APIKEY.txt" ]; - volumes = [ - "${settings}:/etc/etherpad/settings.json" - "${serverCfg.path.config}/etherpad/APIKEY.txt:/etc/etherpad/APIKEY.txt:ro" - ]; - }; + }; + + setup = { + trigger = "server"; + envFile = config.sops.secrets."ETHERPAD".path; + script = pkgs.writeShellScript "setup" '' + echo "$APIKEY" > ${serverCfg.path.config}/etherpad/APIKEY.txt + chmod 444 ${serverCfg.path.config}/etherpad/APIKEY.txt + ''; }; }; - - setup = { - trigger = "server"; - envFile = config.sops.secrets."ETHERPAD".path; - script = pkgs.writeShellScript "setup" '' - echo "$APIKEY" > ${serverCfg.path.config}/etherpad/APIKEY.txt - chmod 444 ${serverCfg.path.config}/etherpad/APIKEY.txt - ''; - }; } diff --git a/modules/server/containers/apps/freshrss.nix b/modules/server/containers/apps/freshrss.nix index 3152a87..05abd1a 100644 --- a/modules/server/containers/apps/freshrss.nix +++ b/modules/server/containers/apps/freshrss.nix @@ -3,54 +3,59 @@ let version = "latest"; serverCfg = config.syscfg.server; in { - sops = true; - db = true; - paths = [ - { - path = "${serverCfg.path.config}/freshrss"; - owner = "1000:1000"; - mode = "0755"; - } - ]; + requires = { + secrets = [ name ]; + databases = [ name ]; + }; - containers = { - server = builder.mkContainer { - subdomain = containerCfg.subdomain; - image = "ghcr.io/freshrss/freshrss:${version}"; - port = 80; - - extraEnv = { - CRON_MIN = "5,35"; - TRUSTED_PROXY = "10.0.0.0/8 192.168.0.1/16"; - LISTEN = "80"; - OIDC_ENABLED = "1"; - OIDC_PROVIDER_METADATA_URL = "https://${serverCfg.containers.authentik.subdomain}.${serverCfg.domain}/application/o/freshrss/.well-known/openid-configuration"; - OIDC_REMOTE_USER_CLAIM = "preferred_username"; - OIDC_CLIENT_ID = "freshrss"; - OIDC_SCOPES = "openid profile"; - OIDC_X_FORWARDED_HEADERS = "X-Forwarded-Host X-Forwarded-Port X-Forwarded-Proto"; - }; + runtime = { + paths = [ + { + path = "${serverCfg.path.config}/freshrss"; + owner = "1000:1000"; + mode = "0755"; + } + ]; - overrides = { - environmentFiles = [ config.sops.secrets."FRESHRSS".path config.sops.secrets."CUSTOM".path ]; - volumes = ["${serverCfg.path.config}/freshrss:/var/www/FreshRSS/data"]; + containers = { + server = builder.mkContainer { + subdomain = containerCfg.subdomain; + image = "ghcr.io/freshrss/freshrss:${version}"; + port = 80; + + extraEnv = { + CRON_MIN = "5,35"; + TRUSTED_PROXY = "10.0.0.0/8 192.168.0.1/16"; + LISTEN = "80"; + OIDC_ENABLED = "1"; + OIDC_PROVIDER_METADATA_URL = "https://${serverCfg.containers.authentik.subdomain}.${serverCfg.domain}/application/o/freshrss/.well-known/openid-configuration"; + OIDC_REMOTE_USER_CLAIM = "preferred_username"; + OIDC_CLIENT_ID = "freshrss"; + OIDC_SCOPES = "openid profile"; + OIDC_X_FORWARDED_HEADERS = "X-Forwarded-Host X-Forwarded-Port X-Forwarded-Proto"; + }; + + overrides = { + environmentFiles = [ config.sops.secrets."FRESHRSS".path config.sops.secrets."CUSTOM".path ]; + volumes = ["${serverCfg.path.config}/freshrss:/var/www/FreshRSS/data"]; + }; }; }; - }; - setup = { - trigger = "server"; # Triggers atomic environment verification on main controller - envFile = [ config.sops.secrets."FRESHRSS".path config.sops.secrets."CUSTOM".path]; - script = pkgs.writeShellScript "setup-freshrss" '' + setup = { + trigger = "server"; # Triggers atomic environment verification on main controller + envFile = [ config.sops.secrets."FRESHRSS".path config.sops.secrets."CUSTOM".path]; + script = pkgs.writeShellScript "setup-freshrss" '' - RSS="${pkgs.podman}/bin/podman --events-backend=none exec -u www-data freshrss-server" - $RSS ./cli/prepare.php - $RSS ./cli/do-install.php --default-user $DEFAULT_ADMIN_USERNAME --auth-type http_auth --base-url https://${containerCfg.subdomain}.${serverCfg.domain} --language en \ - --title RSS --api-enabled --db-type pgsql --db-host ${builder.host} --db-user freshrss_user --db-password $DB_PASSWORD --db-base freshrss_db - $RSS ./cli/create-user.php --user $DEFAULT_ADMIN_USERNAME --password $DEFAULT_ADMIN_PASSWORD --email $DEFAULT_ADMIN_EMAIL - $RSS ./cli/reconfigure.php - # $RSS ./cli/access-permissions.sh - - ''; + RSS="${pkgs.podman}/bin/podman --events-backend=none exec -u www-data freshrss-server" + $RSS ./cli/prepare.php + $RSS ./cli/do-install.php --default-user $DEFAULT_ADMIN_USERNAME --auth-type http_auth --base-url https://${containerCfg.subdomain}.${serverCfg.domain} --language en \ + --title RSS --api-enabled --db-type pgsql --db-host ${builder.host} --db-user freshrss_user --db-password $DB_PASSWORD --db-base freshrss_db + $RSS ./cli/create-user.php --user $DEFAULT_ADMIN_USERNAME --password $DEFAULT_ADMIN_PASSWORD --email $DEFAULT_ADMIN_EMAIL + $RSS ./cli/reconfigure.php + # $RSS ./cli/access-permissions.sh + + ''; + }; }; -} \ No newline at end of file +} diff --git a/modules/server/containers/apps/frigate.nix b/modules/server/containers/apps/frigate.nix index 94aef57..d1a7fd7 100644 --- a/modules/server/containers/apps/frigate.nix +++ b/modules/server/containers/apps/frigate.nix @@ -27,51 +27,51 @@ let }; }; in { - sops = true; # Enabled to safeguard sensitive camera RTSP stream credentials - db = false; # Internal SQLite is used by default in Frigate + requires.secrets = [ name ]; - paths = [ - { - path = "${serverCfg.path.config}/frigate/"; - mode = "0755"; - } - { - path = "/var/lib/frigate/storage/"; - mode = "0755"; # Dedicated path for heavy video recordings and media - } - ]; + runtime = { + paths = [ + { + path = "${serverCfg.path.config}/frigate/"; + mode = "0755"; + } + { + path = "/var/lib/frigate/storage/"; + mode = "0755"; # Dedicated path for heavy video recordings and media + } + ]; - containers = { - server = builder.mkContainer { - subdomain = containerCfg.subdomain; - imageStream = image; - port = 5000; - secret = name; - extraEnv = { - PLUS_API_KEY = ""; # Optional: For Frigate Plus users - }; - overrides = { - cmd = [ ]; - volumes = [ - "${serverCfg.path.config}/frigate:/config" - "/var/lib/frigate/storage:/media/frigate" - "/dev/bus/usb:/dev/bus/usb" # Passes Google Coral USB TPU to the container - "/dev/dri:/dev/dri" # Passes Intel/AMD GPU for hardware video decoding - ]; + containers = { + server = builder.mkContainer { + subdomain = containerCfg.subdomain; + imageStream = image; + port = 5000; + secret = name; + extraEnv = { + PLUS_API_KEY = ""; # Optional: For Frigate Plus users + }; + overrides = { + cmd = [ ]; + volumes = [ + "${serverCfg.path.config}/frigate:/config" + "/var/lib/frigate/storage:/media/frigate" + "/dev/bus/usb:/dev/bus/usb" # Passes Google Coral USB TPU to the container + "/dev/dri:/dev/dri" # Passes Intel/AMD GPU for hardware video decoding + ]; + }; }; }; - }; - setup = { - trigger = "server"; - envFile = config.sops.secrets."FRIGATE_ENV".path; - script = pkgs.writeShellScript "setup-frigate" '' - mkdir -p "${serverCfg.path.config}/frigate" - mkdir -p "/var/lib/frigate/storage" - - # Bootstrap a standard configuration layout if missing - if [ ! -f "${serverCfg.path.config}/frigate/config.yml" ]; then - cat < "${serverCfg.path.config}/frigate/config.yml" + setup = { + trigger = "server"; + envFile = config.sops.secrets."FRIGATE_ENV".path; + script = pkgs.writeShellScript "setup-frigate" '' + mkdir -p "${serverCfg.path.config}/frigate" + mkdir -p "/var/lib/frigate/storage" + + # Bootstrap a standard configuration layout if missing + if [ ! -f "${serverCfg.path.config}/frigate/config.yml" ]; then + cat < "${serverCfg.path.config}/frigate/config.yml" mqtt: enabled: False # Set to True and define host if connecting to Home Assistant @@ -89,7 +89,8 @@ cameras: detect: enabled: false EOF - fi - ''; + fi + ''; + }; }; } diff --git a/modules/server/containers/apps/gitea.nix b/modules/server/containers/apps/gitea.nix index 65c3d28..24c349e 100644 --- a/modules/server/containers/apps/gitea.nix +++ b/modules/server/containers/apps/gitea.nix @@ -5,137 +5,142 @@ let LDAP_DC_DOMAIN = "dc=ldap," + (lib.concatMapStringsSep "," (x: "dc=${x}") (lib.splitString "." serverCfg.domain)); in { - sops = true; - db = true; - paths = [{ - path="${serverCfg.path.data}/gitea"; - owner = "1000:1000"; - dirs = ["data" "runner"]; - mode = "0755"; - }]; - containers = { - server = builder.mkContainer { - subdomain = containerCfg.subdomain; - image = "gitea/gitea:${version}"; - port = 8080; - secret = name; - - extraEnv = { # app.ini -> GITEA__
__ = ""; - GITEA__DEFAULT__APP_NAME = if(containerCfg.extra ? name) then containerCfg.extra.name else "Gitea"; - GITEA__repository__DISABLED_REPO_UNITS = "repo.ext_issues,repo.ext_wiki"; - GITEA__repository__DISABLE_STARS = "true"; - GITEA__repository__DEFAULT_MERGE_STYLE = "squash"; - # GITEA__ui__THEMES = ""; - # GITEA__ui__DEFAULT_THEME = ""; - - # GITEA__security__SECRET_KEY = "SECRET_ENV"; - # GITEA__security__INTERNAL_TOKEN = "SECRET_ENV"; - # GITEA__database__PASSWD = "SECRET_ENV"; - # GITEA__mailer__PASSWD="SECRET_ENV"; - - GITEA__database__DB_TYPE = "postgres"; - GITEA__database__HOST = builder.host; - GITEA__database__NAME = "gitea_db"; - GITEA__database__USER = "gitea_user"; - - - GITEA__mailer__ENABLED = "true"; - GITEA__mailer__FROM = ""; - GITEA__mailer__PROTOCOL = "smtps"; - GITEA__mailer__SMTP_ADDR = ""; - GITEA__mailer__SMTP_PORT = ""; - GITEA__mailer__USER= ""; - - GITEA__server__DOMAIN = "${containerCfg.subdomain}.${serverCfg.domain}"; - GITEA__server__ROOT_URL = "https://${containerCfg.subdomain}.${serverCfg.domain}/"; - GITEA__server__PROTOCOL = "http"; - GITEA__server__HTTP_PORT = "8080"; - GITEA__server__LFS_START_SERVER = "true"; - GITEA__security__INSTALL_LOCK = "true"; - - } // ( if serverCfg.containers?authentik then { - GITEA__service__ENABLE_BASIC_AUTHENTICATION = "false"; - GITEA__service__ENABLE_REVERSE_PROXY_AUTHENTICATION = "true"; - GITEA__service__ENABLE_REVERSE_PROXY_AUTHENTICATION_API = "true"; - GITEA__service__ENABLE_REVERSE_PROXY_AUTO_REGISTRATION = "true"; - GITEA__service__ENABLE_REVERSE_PROXY_EMAIL = "true"; - GITEA__service__ENABLE_REVERSE_PROXY_FULL_NAME = "true"; - GITEA__service__ALLOW_ONLY_EXTERNAL_REGISTRATION = "true"; - GITEA__security__REVERSE_PROXY_LOGOUT_REDIRECT = "https://${serverCfg.containers.authentik.subdomain}.${serverCfg.domain}/outpost.goauthentik.io/sign_out"; - GITEA__security__REVERSE_PROXY_AUTHENTICATION_USER = "X-authentik-username"; - GITEA__security__REVERSE_PROXY_AUTHENTICATION_EMAIL = "X-authentik-email"; - GITEA__security__REVERSE_PROXY_AUTHENTICATION_FULL_NAME = "X-authentik-name"; - GITEA__security__RREVERSE_PROXY_LIMIT = "1"; - GITEA__security__REVERSE_PROXY_TRUSTED_PROXIES = "127.0.0.0/8,::1/128,10.0.0.0/8"; - } else {}); - extraLabels = { - "traefik.http.routers.${containerCfg.subdomain}-login.rule" = "Host(`${containerCfg.subdomain}.${serverCfg.domain}`) && Path(`/user/login`) "; - "traefik.http.routers.${containerCfg.subdomain}-login.middlewares" = if (serverCfg.containers?authentik && containerCg.extra?proxyauth) then "authentik" else ""; - "traefik.http.routers.${containerCfg.subdomain}-login.priority" = "100"; - "traefik.http.routers.${containerCfg.subdomain}-login.entrypoints" = "web-secure"; - "traefik.http.routers.${containerCfg.subdomain}-login.tls" = "true"; - }; - - overrides = { - volumes = [ - "${serverCfg.path.data}/gitea/data:/data" - ]; - ports = [ "2222:22" ]; - }; - }; - - runner = builder.mkContainer { - image = "gitea/act_runner:${version}"; - secret = name; - extraEnv = { - CONFIG_FILE="/data/config.yml"; - GITEA_INSTANCE_URL="https://${containerCfg.subdomain}.${serverCfg.domain}"; - GITHUB_INSTANCE_URL="https://${containerCfg.subdomain}.${serverCfg.domain}"; - }; - - overrides = { - volumes = [ - "${serverCfg.path.data}/gitea/runner:/data" - "/var/run/podman/podman.sock:/var/run/docker.sock" - ]; - # ports = [ "8088:8088" ]; - }; - }; + requires = { + secrets = [ name ]; + databases = [ name ]; }; + runtime = { + paths = [{ + path="${serverCfg.path.data}/gitea"; + owner = "1000:1000"; + dirs = ["data" "runner"]; + mode = "0755"; + }]; + containers = { + server = builder.mkContainer { + subdomain = containerCfg.subdomain; + image = "gitea/gitea:${version}"; + port = 8080; + secret = name; + + extraEnv = { # app.ini -> GITEA__
__ = ""; + GITEA__DEFAULT__APP_NAME = if(containerCfg.extra ? name) then containerCfg.extra.name else "Gitea"; + GITEA__repository__DISABLED_REPO_UNITS = "repo.ext_issues,repo.ext_wiki"; + GITEA__repository__DISABLE_STARS = "true"; + GITEA__repository__DEFAULT_MERGE_STYLE = "squash"; + # GITEA__ui__THEMES = ""; + # GITEA__ui__DEFAULT_THEME = ""; - setup = { - trigger = "server"; - envFile = config.sops.secrets."CUSTOM".path; - script = pkgs.writeShellScript "setup" '' - # Define the command wrapper - GT="${pkgs.podman}/bin/podman --events-backend=none exec -u git gitea-server gitea" - GTR="${pkgs.podman}/bin/podman --events-backend=none exec -u git gitea-runner ./act_runner" + # GITEA__security__SECRET_KEY = "SECRET_ENV"; + # GITEA__security__INTERNAL_TOKEN = "SECRET_ENV"; + # GITEA__database__PASSWD = "SECRET_ENV"; + # GITEA__mailer__PASSWD="SECRET_ENV"; - $GT admin user create --username "$DEFAULT_ADMIN_USERNAME" --password "$DEFAULT_ADMIN_PASSWORD" --email "$DEFAULT_ADMIN_EMAIL" --admin || true - - touch ${serverCfg.path.data}/gitea/data-runner/config.yml - - RUNNER_TOKEN=$($GT actions generate-runner-token) - $GTR register \ - --instance "https://${containerCfg.subdomain}.${serverCfg.domain}" \ - --token "$RUNNER_TOKEN" \ - --name "Runner" \ - --labels "ubuntu-latest:docker://catthehacker/ubuntu:act-latest" \ - --no-interactive + GITEA__database__DB_TYPE = "postgres"; + GITEA__database__HOST = builder.host; + GITEA__database__NAME = "gitea_db"; + GITEA__database__USER = "gitea_user"; - ${lib.optionalString (serverCfg.containers ? authentik) '' - $GT admin auth add-ldap --name Authentik --host authentik-ldap --port 6636 --security-protocol ldaps --skip-tls-verify \ - --bind-dn "cn=ldap-service,ou=users,${LDAP_DC_DOMAIN}" --bind-password $DEFAULT_LDAP_PASSWORD \ - --user-search-base "ou=users,${LDAP_DC_DOMAIN}" \ - --user-filter "(&(objectClass=user)(|(uid=%[1]s)(mail=%[1]s)))" \ - --admin-filter "(memberOf=cn=admin,ou=groups,${LDAP_DC_DOMAIN})" \ - --username-attribute "username" --firstname-attribute "givenName" --surname-attribute "sn" --email-attribute "mail" \ - --synchronize-users - ''} + GITEA__mailer__ENABLED = "true"; + GITEA__mailer__FROM = ""; + GITEA__mailer__PROTOCOL = "smtps"; + GITEA__mailer__SMTP_ADDR = ""; + GITEA__mailer__SMTP_PORT = ""; + GITEA__mailer__USER= ""; - echo "Completed Gitea Setup" + GITEA__server__DOMAIN = "${containerCfg.subdomain}.${serverCfg.domain}"; + GITEA__server__ROOT_URL = "https://${containerCfg.subdomain}.${serverCfg.domain}/"; + GITEA__server__PROTOCOL = "http"; + GITEA__server__HTTP_PORT = "8080"; + GITEA__server__LFS_START_SERVER = "true"; + GITEA__security__INSTALL_LOCK = "true"; + + } // ( if serverCfg.containers?authentik then { + GITEA__service__ENABLE_BASIC_AUTHENTICATION = "false"; + GITEA__service__ENABLE_REVERSE_PROXY_AUTHENTICATION = "true"; + GITEA__service__ENABLE_REVERSE_PROXY_AUTHENTICATION_API = "true"; + GITEA__service__ENABLE_REVERSE_PROXY_AUTO_REGISTRATION = "true"; + GITEA__service__ENABLE_REVERSE_PROXY_EMAIL = "true"; + GITEA__service__ENABLE_REVERSE_PROXY_FULL_NAME = "true"; + GITEA__service__ALLOW_ONLY_EXTERNAL_REGISTRATION = "true"; + GITEA__security__REVERSE_PROXY_LOGOUT_REDIRECT = "https://${serverCfg.containers.authentik.subdomain}.${serverCfg.domain}/outpost.goauthentik.io/sign_out"; + GITEA__security__REVERSE_PROXY_AUTHENTICATION_USER = "X-authentik-username"; + GITEA__security__REVERSE_PROXY_AUTHENTICATION_EMAIL = "X-authentik-email"; + GITEA__security__REVERSE_PROXY_AUTHENTICATION_FULL_NAME = "X-authentik-name"; + GITEA__security__RREVERSE_PROXY_LIMIT = "1"; + GITEA__security__REVERSE_PROXY_TRUSTED_PROXIES = "127.0.0.0/8,::1/128,10.0.0.0/8"; + } else {}); + extraLabels = { + "traefik.http.routers.${containerCfg.subdomain}-login.rule" = "Host(`${containerCfg.subdomain}.${serverCfg.domain}`) && Path(`/user/login`) "; + "traefik.http.routers.${containerCfg.subdomain}-login.middlewares" = if (serverCfg.containers?authentik && containerCg.extra?proxyauth) then "authentik" else ""; + "traefik.http.routers.${containerCfg.subdomain}-login.priority" = "100"; + "traefik.http.routers.${containerCfg.subdomain}-login.entrypoints" = "web-secure"; + "traefik.http.routers.${containerCfg.subdomain}-login.tls" = "true"; + }; + + overrides = { + volumes = [ + "${serverCfg.path.data}/gitea/data:/data" + ]; + ports = [ "2222:22" ]; + }; + }; + + runner = builder.mkContainer { + image = "gitea/act_runner:${version}"; + secret = name; + extraEnv = { + CONFIG_FILE="/data/config.yml"; + GITEA_INSTANCE_URL="https://${containerCfg.subdomain}.${serverCfg.domain}"; + GITHUB_INSTANCE_URL="https://${containerCfg.subdomain}.${serverCfg.domain}"; + }; + + overrides = { + volumes = [ + "${serverCfg.path.data}/gitea/runner:/data" + "/var/run/podman/podman.sock:/var/run/docker.sock" + ]; + # ports = [ "8088:8088" ]; + }; + }; + }; + + + setup = { + trigger = "server"; + envFile = config.sops.secrets."CUSTOM".path; + script = pkgs.writeShellScript "setup" '' + # Define the command wrapper + GT="${pkgs.podman}/bin/podman --events-backend=none exec -u git gitea-server gitea" + GTR="${pkgs.podman}/bin/podman --events-backend=none exec -u git gitea-runner ./act_runner" + + $GT admin user create --username "$DEFAULT_ADMIN_USERNAME" --password "$DEFAULT_ADMIN_PASSWORD" --email "$DEFAULT_ADMIN_EMAIL" --admin || true + + touch ${serverCfg.path.data}/gitea/data-runner/config.yml + + RUNNER_TOKEN=$($GT actions generate-runner-token) + $GTR register \ + --instance "https://${containerCfg.subdomain}.${serverCfg.domain}" \ + --token "$RUNNER_TOKEN" \ + --name "Runner" \ + --labels "ubuntu-latest:docker://catthehacker/ubuntu:act-latest" \ + --no-interactive + + + ${lib.optionalString (serverCfg.containers ? authentik) '' + $GT admin auth add-ldap --name Authentik --host authentik-ldap --port 6636 --security-protocol ldaps --skip-tls-verify \ + --bind-dn "cn=ldap-service,ou=users,${LDAP_DC_DOMAIN}" --bind-password $DEFAULT_LDAP_PASSWORD \ + --user-search-base "ou=users,${LDAP_DC_DOMAIN}" \ + --user-filter "(&(objectClass=user)(|(uid=%[1]s)(mail=%[1]s)))" \ + --admin-filter "(memberOf=cn=admin,ou=groups,${LDAP_DC_DOMAIN})" \ + --username-attribute "username" --firstname-attribute "givenName" --surname-attribute "sn" --email-attribute "mail" \ + --synchronize-users + ''} + + echo "Completed Gitea Setup" ''; + }; }; -} \ No newline at end of file +} diff --git a/modules/server/containers/apps/handbrake.nix b/modules/server/containers/apps/handbrake.nix index 710da57..7f11e41 100644 --- a/modules/server/containers/apps/handbrake.nix +++ b/modules/server/containers/apps/handbrake.nix @@ -2,55 +2,47 @@ let serverCfg = config.syscfg.server; version = "latest"; - - routerName = if containerCfg.subpath != null - then "${containerCfg.subdomain}-${lib.strings.sanitizeDerivationName containerCfg.subpath}" - else containerCfg.subdomain; in { - - paths = [{ - path = "${serverCfg.path.config}/handbrake"; - mode = "0755"; - }]; + runtime = { + paths = [{ + path = "${serverCfg.path.config}/handbrake"; + mode = "0755"; + }]; - containers = { - server = builder.mkContainer { - subdomain = containerCfg.subdomain; - subpath = containerCfg.subpath; - image = "ghcr.io/jlesage/handbrake:${version}"; - port = 5800; - - extraEnv = { - USER_ID = "1000"; - GROUP_ID = "1000"; - AUTOMATED_CONVERSION_PRESET = "Custom/AV1 MKV 1080p30"; - AUTOMATED_CONVERSION_FORMAT = "mkv"; - AUTOMATED_CONVERSION_OUTPUT_SUBDIR = "SAME_AS_SRC"; + containers = { + server = builder.mkContainer { + authentik = true; + tmpfs = true; + subdomain = containerCfg.subdomain; + subpath = containerCfg.subpath; + image = "ghcr.io/jlesage/handbrake:${version}"; + port = 5800; + + extraEnv = { + USER_ID = "1000"; + GROUP_ID = "1000"; + AUTOMATED_CONVERSION_PRESET = "Custom/AV1 MKV 1080p30"; + AUTOMATED_CONVERSION_FORMAT = "mkv"; + AUTOMATED_CONVERSION_OUTPUT_SUBDIR = "SAME_AS_SRC"; + }; + + overrides = { + volumes = [ + "${serverCfg.path.config}/handbrake:/config:rw" + "${serverCfg.path.dlComplete}:/watch:rw" + "${serverCfg.path.dlConverted}:/output:rw" + ]; + }; }; - extraLabels = { } // (if serverCfg.containers ? authentik then { - "traefik.http.routers.${routerName}.middlewares" = "authentik"; - } else {}); - extraOptions = [ - "--tmpfs=/tmp:rw,noexec,nosuid,size=512m" - ]; - - overrides = { - volumes = [ - "${serverCfg.path.config}/handbrake:/config:rw" - "${serverCfg.path.dlComplete}:/watch:rw" - "${serverCfg.path.dlConverted}:/output:rw" - ]; - }; + }; + + + setup = { + trigger = "server"; + script = pkgs.writeShellScript "setup" '' + mkdir -p ${serverCfg.path.data}/handbrake/{watch,output} + + ''; }; }; - - - setup = { - trigger = "server"; - script = pkgs.writeShellScript "setup" '' - mkdir -p ${serverCfg.path.data}/handbrake/{watch,output} - - ''; - }; - -} \ No newline at end of file +} diff --git a/modules/server/containers/apps/homeassistant.nix b/modules/server/containers/apps/homeassistant.nix index 629451e..bac33fc 100644 --- a/modules/server/containers/apps/homeassistant.nix +++ b/modules/server/containers/apps/homeassistant.nix @@ -4,62 +4,63 @@ let serverCfg = config.syscfg.server; in { - vm = { - portForward = [ 8123 ]; - cfg = {cfg,...}:{ - services.home-assistant = { - enable = true; - openFirewall = true; - - extraComponents = [ - "matter" "thread" "cast" "zha" - "default_config" "met" "esphome" "radio_browser" - "telegram_bot" "swiss_public_transport" "nextcloud" "jellyfin" - ] ++ (if containerCfg.extra ? components then containerCfg.extra.components else []); - - - extraPackages = pp: with pp; [ - python-telegram gtts - ]; - lovelaceConfig = {}; - - config = { - homeassistant = { - name = "Home"; - latitude = "${if containerCfg.extra ? latitude then toString containerCfg.extra.latitude else toString 0}"; - longitude = "${if containerCfg.extra ? longitude then toString containerCfg.extra.longitude else toString 0}"; - elevation = "${if containerCfg.extra ? elevation then toString containerCfg.extra.elevation else toString 0}"; - unit_system = "metric"; - time_zone = config.time.timeZone; - }; - lovelace = { mode = "yaml"; }; - customLovelaceModules = []; + runtime = { + vm = { + portForward = [ 8123 ]; + cfg = {cfg,...}: { + services.home-assistant = { + enable = true; + openFirewall = true; - # default_config = {}; - http = { - use_x_forwarded_for = true; - trusted_proxies = [ "10.0.0.0/8" "127.0.0.1" ]; + extraComponents = [ + "matter" "thread" "cast" "zha" + "default_config" "met" "esphome" "radio_browser" + "telegram_bot" "swiss_public_transport" "nextcloud" "jellyfin" + ] ++ (if containerCfg.extra ? components then containerCfg.extra.components else []); + + + extraPackages = pp: with pp; [ + python-telegram gtts + ]; + lovelaceConfig = {}; + + config = { + homeassistant = { + name = "Home"; + latitude = "${if containerCfg.extra ? latitude then toString containerCfg.extra.latitude else toString 0}"; + longitude = "${if containerCfg.extra ? longitude then toString containerCfg.extra.longitude else toString 0}"; + elevation = "${if containerCfg.extra ? elevation then toString containerCfg.extra.elevation else toString 0}"; + unit_system = "metric"; + time_zone = config.time.timeZone; + }; + lovelace = { mode = "yaml"; }; + customLovelaceModules = []; + + # default_config = {}; + http = { + use_x_forwarded_for = true; + trusted_proxies = [ "10.0.0.0/8" "127.0.0.1" ]; + }; }; }; }; }; - }; - containers = { - dummy = builder.mkContainer { - subdomain = containerCfg.subdomain; - image = "alpine:latest"; - extraLabels = { - "traefik.http.services.${containerCfg.subdomain}.loadbalancer.server.url" = "http://${builder.hostIp}:8123"; + containers = { + dummy = builder.mkContainer { + subdomain = containerCfg.subdomain; + image = "alpine:latest"; + extraLabels = { + "traefik.http.services.${containerCfg.subdomain}.loadbalancer.server.url" = "http://${builder.hostIp}:8123"; + }; + overrides = {cmd = [ "sleep" "infinity" ];}; }; - overrides = {cmd = [ "sleep" "infinity" ];}; }; - }; - setup = { - trigger = "dummy"; - envFile = config.sops.secrets."CUSTOM".path; - script = pkgs.writeShellScript "setup" '' + setup = { + trigger = "dummy"; + envFile = config.sops.secrets."CUSTOM".path; + script = pkgs.writeShellScript "setup" '' HASS_URL="https://${containerCfg.subdomain}.${serverCfg.domain}" until [[ "$(${pkgs.curl}/bin/curl -s -o /dev/null -w "%{http_code}" "$HASS_URL/manifest.json")" =~ (200|301|302) ]]; do @@ -95,7 +96,7 @@ in { -H "Content-Type: application/json" \ -d '{"client_id":"'"$HASS_URL"'","redirect_uri":"'"$HASS_URL"'/?auth_callback=1"}' > /dev/null 2>&1 || true fi - ''; + ''; + }; }; - -} \ No newline at end of file +} diff --git a/modules/server/containers/apps/homepage.nix b/modules/server/containers/apps/homepage.nix index af2e605..2caa398 100644 --- a/modules/server/containers/apps/homepage.nix +++ b/modules/server/containers/apps/homepage.nix @@ -258,31 +258,29 @@ let ];}#)];} ]; in { - sops = false; - db = false; - - containers = { - server = builder.mkContainer { - subdomain = containerCfg.subdomain; - image = "ghcr.io/gethomepage/homepage:${version}"; - port = 3000; - extraEnv = { - HOMEPAGE_VAR_TITLE="${serverCfg.domain}"; - HOMEPAGE_ALLOWED_HOSTS = "${containerCfg.subdomain}.${serverCfg.domain},${builder.host}"; - }; - extraLabels = { - "traefik.http.routers.${containerCfg.subdomain}.service" = "${containerCfg.subdomain}"; - }; - overrides = { - environmentFiles = [ config.sops.secrets."CUSTOM".path ]; - volumes = [ - "${settings}:/app/config/settings.yaml:ro" - "${services}:/app/config/services.yaml:ro" - "${widgets}:/app/config/widgets.yaml:ro" - "${bookmarks}:/app/config/bookmarks.yaml:ro" - ]; - }; + runtime = { + containers = { + server = builder.mkContainer { + subdomain = containerCfg.subdomain; + image = "ghcr.io/gethomepage/homepage:${version}"; + port = 3000; + extraEnv = { + HOMEPAGE_VAR_TITLE="${serverCfg.domain}"; + HOMEPAGE_ALLOWED_HOSTS = "${containerCfg.subdomain}.${serverCfg.domain},${builder.host}"; + }; + extraLabels = { + "traefik.http.routers.${containerCfg.subdomain}.service" = "${containerCfg.subdomain}"; + }; + overrides = { + environmentFiles = [ config.sops.secrets."CUSTOM".path ]; + volumes = [ + "${settings}:/app/config/settings.yaml:ro" + "${services}:/app/config/services.yaml:ro" + "${widgets}:/app/config/widgets.yaml:ro" + "${bookmarks}:/app/config/bookmarks.yaml:ro" + ]; + }; + }; + }; }; - }; - -} \ No newline at end of file +} diff --git a/modules/server/containers/apps/immich.nix b/modules/server/containers/apps/immich.nix index 7c1a7ba..e6b5d86 100644 --- a/modules/server/containers/apps/immich.nix +++ b/modules/server/containers/apps/immich.nix @@ -4,97 +4,101 @@ let serverCfg = config.syscfg.server; in { - sops = true; - db = true; + requires = { + secrets = [ name ]; + databases = [ name ]; + }; - paths = [{ - path = "${serverCfg.path.config}/immich"; - dirs = ["cache"]; - mode = "0750"; - }{ - path = "${serverCfg.path.data}/immich/"; - dirs = ["upload" "thumbs" "encoded-video" "backups"]; - mode = "0755"; - }]; + runtime = { + paths = [{ + path = "${serverCfg.path.config}/immich"; + dirs = ["cache"]; + mode = "0750"; + }{ + path = "${serverCfg.path.data}/immich/"; + dirs = ["upload" "thumbs" "encoded-video" "backups"]; + mode = "0755"; + }]; - containers = { - server = builder.mkContainer { - subdomain = containerCfg.subdomain; - image = "ghcr.io/immich-app/immich-server:${version}"; - port = 2283; - secret = name; - extraEnv = { - DB_HOSTNAME = builder.host; - REDIS_HOSTNAME = builder.host; - DB_USERNAME = "immich_user"; - DB_DATABASE_NAME = "immich_db"; - IMMICH_TRUSTED_PROXIES = "10.0.0.0/8"; - IMMICH_MACHINE_LEARNING_URL = "http://immich-ml:3003"; - # IMMICH_ALLOW_SETUP = "false"; - # IMMICH_IGNORE_MOUNT_CHECK_ERRORS = "true"; + containers = { + server = builder.mkContainer { + subdomain = containerCfg.subdomain; + image = "ghcr.io/immich-app/immich-server:${version}"; + port = 2283; + secret = name; + extraEnv = { + DB_HOSTNAME = builder.host; + REDIS_HOSTNAME = builder.host; + DB_USERNAME = "immich_user"; + DB_DATABASE_NAME = "immich_db"; + IMMICH_TRUSTED_PROXIES = "10.0.0.0/8"; + IMMICH_MACHINE_LEARNING_URL = "http://immich-ml:3003"; + # IMMICH_ALLOW_SETUP = "false"; + # IMMICH_IGNORE_MOUNT_CHECK_ERRORS = "true"; + }; + overrides = { + volumes = [ + "${serverCfg.path.photo}:/data/upload" + "${serverCfg.path.data}/immich/backups:/data/backups" + "${serverCfg.path.config}/immich/thumbs:/data/thumbs" + "${serverCfg.path.config}/immich/encoded-video:/data/encoded-video" + ]; + }; }; - overrides = { - volumes = [ - "${serverCfg.path.photo}:/data/upload" - "${serverCfg.path.data}/immich/backups:/data/backups" - "${serverCfg.path.config}/immich/thumbs:/data/thumbs" - "${serverCfg.path.config}/immich/encoded-video:/data/encoded-video" - ]; + + ml = builder.mkContainer { + image = "ghcr.io/immich-app/immich-machine-learning:${version}"; + port = 3003; + overrides = { + volumes = [ + "${serverCfg.path.config}/immich/cache:/cache" + ]; + }; }; }; - ml = builder.mkContainer { - image = "ghcr.io/immich-app/immich-machine-learning:${version}"; - port = 3003; - overrides = { - volumes = [ - "${serverCfg.path.config}/immich/cache:/cache" - ]; - }; + setup = { + trigger = "server"; + envFile = config.sops.secrets."CUSTOM".path; + script = pkgs.writeShellScript "setup" '' + PSQL="${pkgs.postgresql}/bin/psql -U postgres" + $PSQL -d "immich_db" -tAc "CREATE EXTENSION IF NOT EXISTS vchord CASCADE;" + $PSQL -d "immich_db" -tAc "CREATE EXTENSION IF NOT EXISTS earthdistance CASCADE;" + + IMMICH_URL="https://${containerCfg.subdomain}.${serverCfg.domain}" + until [[ "$(${pkgs.curl}/bin/curl -s -o /dev/null -w "%{http_code}" "$IMMICH_URL")" =~ (200|301|302) ]]; do + sleep 5 + done + ${pkgs.curl}/bin/curl -X POST "$IMMICH_URL/api/auth/admin-sign-up" \ + -H "Content-Type: application/json" -H "Accept: application/json" \ + -d '{ "email": "'"$DEFAULT_ADMIN_EMAIL"'", "password": "'"$DEFAULT_ADMIN_PASSWORD"'", "name": "'"$DEFAULT_ADMIN_USERNAME"'" }' + + IMMICH_TOKEN=$(${pkgs.curl}/bin/curl -sSf -X POST "$IMMICH_URL/api/auth/login" \ + -H "Content-Type: application/json" \ + -d '{ "email": "'"$DEFAULT_ADMIN_EMAIL"'", "password": "'"$DEFAULT_ADMIN_PASSWORD"'"}' \ + | ${pkgs.jq}/bin/jq -r '.accessToken') + + ${lib.optionalString (serverCfg.containers ? authentik) '' + ${pkgs.curl}/bin/curl -s -X GET "$IMMICH_URL/api/system-config" -H "Cookie: immich_access_token=$IMMICH_TOKEN; immich_auth_type=password; immich_is_authenticated=true" | \ + ${pkgs.jq}/bin/jq '.oauth.enabled = true | + .oauth.autoRegister = true | + .oauth.autoLaunch = true | + .oauth.signingAlgorithm = "RS256" | + .oauth.profileSigningAlgorithm = "RS256" | + .oauth.clientId = "immich" | + .oauth.clientSecret = "'"$IMMICH_OAUTH_SECRET"'" | + .oauth.issuerUrl = "https://${serverCfg.containers.authentik.subdomain}.${serverCfg.domain}/application/o/immich/" | + .oauth.scope = "openid profile email" | + .oauth.buttonText = "Login with SSO"' | \ + ${pkgs.curl}/bin/curl -s -X PUT "$IMMICH_URL/api/system-config" -H "Cookie: immich_access_token=$IMMICH_TOKEN; immich_auth_type=password; immich_is_authenticated=true" -H "Content-Type: application/json" -d @- + ''} + + ${pkgs.curl}/bin/curl -s -X GET "$IMMICH_URL/api/system-config" -H "Cookie: immich_access_token=$IMMICH_TOKEN; immich_auth_type=password; immich_is_authenticated=true" | \ + ${pkgs.jq}/bin/jq '.storageTemplate.enable = true | + .storageTemplate.template = "{{y}}/{{#if album}}{{album}}{{else}}{{MM}}{{/if}}/{{filename}}"' | \ + ${pkgs.curl}/bin/curl -s -X PUT "$IMMICH_URL/api/system-config" -H "Cookie: immich_access_token=$IMMICH_TOKEN; immich_auth_type=password; immich_is_authenticated=true" -H "Content-Type: application/json" -d @- + + ''; }; }; - - setup = { - trigger = "server"; - envFile = config.sops.secrets."CUSTOM".path; - script = pkgs.writeShellScript "setup" '' - PSQL="${pkgs.postgresql}/bin/psql -U postgres" - $PSQL -d "immich_db" -tAc "CREATE EXTENSION IF NOT EXISTS vchord CASCADE;" - $PSQL -d "immich_db" -tAc "CREATE EXTENSION IF NOT EXISTS earthdistance CASCADE;" - - IMMICH_URL="https://${containerCfg.subdomain}.${serverCfg.domain}" - until [[ "$(${pkgs.curl}/bin/curl -s -o /dev/null -w "%{http_code}" "$IMMICH_URL")" =~ (200|301|302) ]]; do - sleep 5 - done - ${pkgs.curl}/bin/curl -X POST "$IMMICH_URL/api/auth/admin-sign-up" \ - -H "Content-Type: application/json" -H "Accept: application/json" \ - -d '{ "email": "'"$DEFAULT_ADMIN_EMAIL"'", "password": "'"$DEFAULT_ADMIN_PASSWORD"'", "name": "'"$DEFAULT_ADMIN_USERNAME"'" }' - - IMMICH_TOKEN=$(${pkgs.curl}/bin/curl -sSf -X POST "$IMMICH_URL/api/auth/login" \ - -H "Content-Type: application/json" \ - -d '{ "email": "'"$DEFAULT_ADMIN_EMAIL"'", "password": "'"$DEFAULT_ADMIN_PASSWORD"'"}' \ - | ${pkgs.jq}/bin/jq -r '.accessToken') - - ${lib.optionalString (serverCfg.containers ? authentik) '' - ${pkgs.curl}/bin/curl -s -X GET "$IMMICH_URL/api/system-config" -H "Cookie: immich_access_token=$IMMICH_TOKEN; immich_auth_type=password; immich_is_authenticated=true" | \ - ${pkgs.jq}/bin/jq '.oauth.enabled = true | - .oauth.autoRegister = true | - .oauth.autoLaunch = true | - .oauth.signingAlgorithm = "RS256" | - .oauth.profileSigningAlgorithm = "RS256" | - .oauth.clientId = "immich" | - .oauth.clientSecret = "'"$IMMICH_OAUTH_SECRET"'" | - .oauth.issuerUrl = "https://${serverCfg.containers.authentik.subdomain}.${serverCfg.domain}/application/o/immich/" | - .oauth.scope = "openid profile email" | - .oauth.buttonText = "Login with SSO"' | \ - ${pkgs.curl}/bin/curl -s -X PUT "$IMMICH_URL/api/system-config" -H "Cookie: immich_access_token=$IMMICH_TOKEN; immich_auth_type=password; immich_is_authenticated=true" -H "Content-Type: application/json" -d @- - ''} - - ${pkgs.curl}/bin/curl -s -X GET "$IMMICH_URL/api/system-config" -H "Cookie: immich_access_token=$IMMICH_TOKEN; immich_auth_type=password; immich_is_authenticated=true" | \ - ${pkgs.jq}/bin/jq '.storageTemplate.enable = true | - .storageTemplate.template = "{{y}}/{{#if album}}{{album}}{{else}}{{MM}}{{/if}}/{{filename}}"' | \ - ${pkgs.curl}/bin/curl -s -X PUT "$IMMICH_URL/api/system-config" -H "Cookie: immich_access_token=$IMMICH_TOKEN; immich_auth_type=password; immich_is_authenticated=true" -H "Content-Type: application/json" -d @- - - ''; - }; -} \ No newline at end of file +} diff --git a/modules/server/containers/apps/influx.nix b/modules/server/containers/apps/influx.nix index 545f7f6..92e97a1 100644 --- a/modules/server/containers/apps/influx.nix +++ b/modules/server/containers/apps/influx.nix @@ -5,64 +5,65 @@ let in { - sops = true; - db = true; - - paths = [{ - path = "${serverCfg.path.config}/influxdb/"; - owner = "1500:1500"; - mode = "0700"; - }{ - path = "${serverCfg.path.data}/influxdb/"; - owner = "1500:1500"; - mode = "0700"; - }]; - - containers = { - # db = builder.mkContainer { - # subdomain = containerCfg.subdomain; - # image = "influxdata/influxdb:3.0"; - # port = 8181; - # secret = name; - # extraEnv = { - # INFLUXD_DB_PATH = "/db"; - # INFLUXD_CONFIG_PATH = "/config"; - # }; - # overrides = { - # volumes = [ - # "${serverCfg.path.data}/influxdb:/db:rw" - # "${serverCfg.path.config}/influxdb:/config:ro" - # ]; - # }; - # }; - - server = builder.mkContainer { - subdomain = containerCfg.subdomain; - image = "influxdata/influxdb3-ui:${version}"; - port = 8080; - secret = name; - extraEnv = { - SESSION_SECRET_KEY = "7b0024c13ae770000f797c201e2f210b9932a689c04d34de04379faa44e88e97"; - DATABASE_URL = "/db/sqlite.db"; - }; - extraOptions = [ - "--tmpfs=/tmp:rw,noexec,nosuid,size=512m" - ]; - overrides = { - ports = [ "8080:8080" ]; - cmd = [ "--mode=admin" ]; - volumes = [ - "${serverCfg.path.data}/influxdb:/db:rw" - "${serverCfg.path.config}/influxdb/:/app-root/config:ro" - ]; - }; - }; + requires = { + secrets = [ name ]; + databases = [ name ]; }; - setup = { - trigger = "server"; - script = pkgs.writeShellScript "setup" '' - cat > ${serverCfg.path.config}/influxdb/config.json << 'EOF' + runtime = { + paths = [{ + path = "${serverCfg.path.config}/influxdb/"; + owner = "1500:1500"; + mode = "0700"; + }{ + path = "${serverCfg.path.data}/influxdb/"; + owner = "1500:1500"; + mode = "0700"; + }]; + + containers = { + # db = builder.mkContainer { + # subdomain = containerCfg.subdomain; + # image = "influxdata/influxdb:3.0"; + # port = 8181; + # secret = name; + # extraEnv = { + # INFLUXD_DB_PATH = "/db"; + # INFLUXD_CONFIG_PATH = "/config"; + # }; + # overrides = { + # volumes = [ + # "${serverCfg.path.data}/influxdb:/db:rw" + # "${serverCfg.path.config}/influxdb:/config:ro" + # ]; + # }; + # }; + + server = builder.mkContainer { + tmpfs = true; + subdomain = containerCfg.subdomain; + image = "influxdata/influxdb3-ui:${version}"; + port = 8080; + secret = name; + extraEnv = { + SESSION_SECRET_KEY = "7b0024c13ae770000f797c201e2f210b9932a689c04d34de04379faa44e88e97"; + DATABASE_URL = "/db/sqlite.db"; + }; + overrides = { + ports = [ "8080:8080" ]; + cmd = [ "--mode=admin" ]; + volumes = [ + "${serverCfg.path.data}/influxdb:/db:rw" + "${serverCfg.path.config}/influxdb/:/app-root/config:ro" + ]; + }; + }; + }; + + setup = { + trigger = "server"; + script = pkgs.writeShellScript "setup" '' + cat > ${serverCfg.path.config}/influxdb/config.json << 'EOF' { "DEFAULT_INFLUX_SERVER": "http://${builder.host}:8181", "DEFAULT_INFLUX_DATABASE": "main", @@ -70,8 +71,8 @@ in { "DEFAULT_SERVER_NAME": "${serverCfg.domain}" } EOF - chmod -R 755 ${serverCfg.path.config}/influxdb - ''; + chmod -R 755 ${serverCfg.path.config}/influxdb + ''; + }; }; - -} \ No newline at end of file +} diff --git a/modules/server/containers/apps/invidious.nix b/modules/server/containers/apps/invidious.nix index e380a28..164e128 100644 --- a/modules/server/containers/apps/invidious.nix +++ b/modules/server/containers/apps/invidious.nix @@ -24,55 +24,60 @@ let }; in { - sops = true; - db = true; - paths = [{ - path="${serverCfg.path.config}/invidious"; - mode = "0755"; - }]; + requires = { + secrets = [ name ]; + databases = [ name ]; + }; - containers = { - server = builder.mkContainer { - subdomain = containerCfg.subdomain; - imageStream = image; - port = 3000; - secret = name; - extraLabels = { - "traefik.http.routers.${containerCfg.subdomain}-login.rule" = "Host(`${containerCfg.subdomain}.${serverCfg.domain}`) && Path(`/login`) "; - "traefik.http.routers.${containerCfg.subdomain}-login.middlewares" = if serverCfg.containers?authentik then "authentik" else ""; - "traefik.http.routers.${containerCfg.subdomain}-login.priority" = "100"; - "traefik.http.routers.${containerCfg.subdomain}-login.entrypoints" = "web-secure"; - "traefik.http.routers.${containerCfg.subdomain}-login.tls" = "true"; + runtime = { + paths = [{ + path="${serverCfg.path.config}/invidious"; + mode = "0755"; + }]; + + containers = { + server = builder.mkContainer { + subdomain = containerCfg.subdomain; + imageStream = image; + port = 3000; + secret = name; + extraLabels = { + "traefik.http.routers.${containerCfg.subdomain}-login.rule" = "Host(`${containerCfg.subdomain}.${serverCfg.domain}`) && Path(`/login`) "; + "traefik.http.routers.${containerCfg.subdomain}-login.middlewares" = if serverCfg.containers?authentik then "authentik" else ""; + "traefik.http.routers.${containerCfg.subdomain}-login.priority" = "100"; + "traefik.http.routers.${containerCfg.subdomain}-login.entrypoints" = "web-secure"; + "traefik.http.routers.${containerCfg.subdomain}-login.tls" = "true"; + }; + extraEnv = { + INVIDIOUS_CONFIG_FILE = "/data/config.yml"; + }; + overrides = { + volumes = [ + "${serverCfg.path.config}/invidious:/data:ro" + ]; + }; }; - extraEnv = { - INVIDIOUS_CONFIG_FILE = "/data/config.yml"; - }; - overrides = { - volumes = [ - "${serverCfg.path.config}/invidious:/data:ro" + + companion = builder.mkContainer { + image = "quay.io/invidious/invidious-companion:latest"; + port = 8282; + secret = name; #SERVER_SECRET_KEY = INVIDIOUS_COMPANION_KEY + extraOptions = [ + "--cap-drop=all" + "--security-opt=no-new-privileges" ]; }; }; - companion = builder.mkContainer { - image = "quay.io/invidious/invidious-companion:latest"; - port = 8282; - secret = name; #SERVER_SECRET_KEY = INVIDIOUS_COMPANION_KEY - extraOptions = [ - "--cap-drop=all" - "--security-opt=no-new-privileges" - ]; + setup = { + trigger = "server"; + envFile = [ config.sops.secrets."INVIDIOUS".path config.sops.secrets."CUSTOM".path ]; + script = pkgs.writeShellScript "setup" '' + export DB_HOST=${builder.host} + export INVIDIOUS_DOMAIN=${containerCfg.subdomain}.${serverCfg.domain} + + ${pkgs.gettext}/bin/envsubst < "${../data/invidious/config.yml}" > "${serverCfg.path.config}/invidious/config.yml" + ''; }; }; - - setup = { - trigger = "server"; - envFile = [ config.sops.secrets."INVIDIOUS".path config.sops.secrets."CUSTOM".path ]; - script = pkgs.writeShellScript "setup" '' - export DB_HOST=${builder.host} - export INVIDIOUS_DOMAIN=${containerCfg.subdomain}.${serverCfg.domain} - - ${pkgs.gettext}/bin/envsubst < "${../data/invidious/config.yml}" > "${serverCfg.path.config}/invidious/config.yml" - ''; - }; -} \ No newline at end of file +} diff --git a/modules/server/containers/apps/jellyfin.nix b/modules/server/containers/apps/jellyfin.nix index 094c38a..71a2c04 100644 --- a/modules/server/containers/apps/jellyfin.nix +++ b/modules/server/containers/apps/jellyfin.nix @@ -25,152 +25,151 @@ let }; }; in { - paths = [ - { - path = "${serverCfg.path.config}/jellyfin/"; - owner = "1000:1000"; - mode = "0755"; - } - ]; + runtime = { + paths = [ + { + path = "${serverCfg.path.config}/jellyfin/"; + owner = "1000:1000"; + mode = "0755"; + } + ]; - containers = { - server = builder.mkContainer { - subdomain = containerCfg.subdomain; - imageStream = image; - port = 8096; - extraEnv = { - HOME = "/config/data"; - DOTNET_SYSTEM_GLOBALIZATION_INVARIANT = "1"; - JELLYFIN_HttpListenerHost__BindAddress= "0.0.0.0"; #we can use settings.xml override - JELLYFIN_ServerName = if containerCfg.extra?name then containerCfg.extra.name else "Flix"; - }; - extraOptions = [ - "--tmpfs=/tmp:rw,noexec,nosuid,size=512m" - ]; - overrides = { - cmd = [ - "--datadir" "/config/data" - "--cachedir" "/config/cache" - "--configdir" "/config/config" - "--logdir" "/config/log" - ]; - volumes = [ - "${serverCfg.path.film}:/media:ro" - "${serverCfg.path.config}/jellyfin:/config" - ]; - # If you have an Intel/AMD GPU for transcoding, add the device: - devices = lib.optionals (builtins.pathExists "/dev/dri") [ "/dev/dri:/dev/dri" ]; + containers = { + server = builder.mkContainer { + tmpfs = true; + subdomain = containerCfg.subdomain; + imageStream = image; + port = 8096; + extraEnv = { + HOME = "/config/data"; + DOTNET_SYSTEM_GLOBALIZATION_INVARIANT = "1"; + JELLYFIN_HttpListenerHost__BindAddress= "0.0.0.0"; #we can use settings.xml override + JELLYFIN_ServerName = if containerCfg.extra?name then containerCfg.extra.name else "Flix"; + }; + overrides = { + cmd = [ + "--datadir" "/config/data" + "--cachedir" "/config/cache" + "--configdir" "/config/config" + "--logdir" "/config/log" + ]; + volumes = [ + "${serverCfg.path.film}:/media:ro" + "${serverCfg.path.config}/jellyfin:/config" + ]; + # If you have an Intel/AMD GPU for transcoding, add the device: + devices = lib.optionals (builtins.pathExists "/dev/dri") [ "/dev/dri:/dev/dri" ]; + }; }; }; - }; - setup = { - trigger = "server"; - envFile = config.sops.secrets."CUSTOM".path; - script = pkgs.writeShellScript "setup" '' - JELLYFIN_URL="https://${containerCfg.subdomain}.${serverCfg.domain}" - until [ "$(${pkgs.curl}/bin/curl -sf "$JELLYFIN_URL/health")" = "Healthy" ]; do - sleep 5 - done - echo "Jellyfin is up. Sleeping for 20 seconds..." - sleep 20 - WIZARD_COMPLETE=$(${pkgs.curl}/bin/curl -sSf "$JELLYFIN_URL/System/Info/Public" 2>/dev/null | \ - ${pkgs.jq}/bin/jq -r '.StartupWizardCompleted // false') - if [ "$WIZARD_COMPLETE" = "false" ]; then - if ! ${pkgs.curl}/bin/curl -sSf -X POST "$JELLYFIN_URL/Startup/Configuration" \ - -H "Content-Type: application/json" \ - -d '{"ServerName":"Flix","UICulture":"en-US","MetadataCountryCode":"US","PreferredMetadataLanguage":"en"}'; then - echo "ERROR: Failed to set startup configuration." - exit 1 - fi - - if ! ${pkgs.curl}/bin/curl -sSf -X GET "$JELLYFIN_URL/Startup/User"; then - echo "ERROR: Failed to get base user." - exit 1 - fi - - if ! ${pkgs.curl}/bin/curl -sSf -X POST "$JELLYFIN_URL/Startup/User" \ - -H 'accept: */*' -H "Content-Type: application/json" \ - -d '{"Name": "'"$DEFAULT_ADMIN_USERNAME"'", "Password": "'"$DEFAULT_ADMIN_PASSWORD"'"}'; then - echo "ERROR: Failed to set admin user." - exit 1 - fi - - if ! ${pkgs.curl}/bin/curl -sSf -X POST "$JELLYFIN_URL/Startup/RemoteAccess" \ - -H "Content-Type: application/json" \ - -d '{"EnableRemoteAccess":true,"EnableAutomaticPortMapping":false}'; then - echo "ERROR: Failed to configure remote access." - exit 1 - fi - - if ! ${pkgs.curl}/bin/curl -sSf -X POST "''$JELLYFIN_URL/Startup/Complete"; then - echo "ERROR: Failed to complete wizard." - exit 1 - fi - echo "Jellyfin initialization successfully completed!" - fi - - ${lib.optionalString (serverCfg.containers ? authentik) '' - JELLYFIN_TOKEN=$(${pkgs.curl}/bin/curl -sSf -X POST "$JELLYFIN_URL/Users/AuthenticateByName" \ - -H "Content-Type: application/json" \ - -H "Authorization: MediaBrowser Client=\"Bash Script\", Device=\"Server Terminal\", DeviceId=\"script-12345\", Version=\"1.0.0\"" \ - -d "{\"Username\": \"$DEFAULT_ADMIN_USERNAME\", \"Pw\": \"$DEFAULT_ADMIN_PASSWORD\"}" \ - | ${pkgs.jq}/bin/jq -r '.AccessToken') - - # Verify we got a token - if [ "$JELLYFIN_TOKEN" = "null" ] || [ -z "$JELLYFIN_TOKEN" ]; then - echo "ERROR: Authentication failed." - exit 1 - fi - if ${pkgs.curl}/bin/curl -sSf -H "Authorization: MediaBrowser Token=\"$JELLYFIN_TOKEN\"" \ - "$JELLYFIN_URL/Plugins" | ${pkgs.gnugrep}/bin/grep -q "958aad6637844d2ab89aa7b6fab6e25c"; then - echo "LDAP Plugin is already installed. Skipping setup." - else - if ! ${pkgs.curl}/bin/curl -sSf -X POST "$JELLYFIN_URL/Packages/Installed/LDAP%20Authentication?assemblyGuid=958aad6637844d2ab89aa7b6fab6e25c" \ - -H "Authorization: MediaBrowser Token=\"$JELLYFIN_TOKEN\"" \ - -H "Content-Length: 0"; then - echo "ERROR: LDAP Plugin Setup Failed." - exit 1 - fi - - if ! ${pkgs.curl}/bin/curl -sSf -X POST "$JELLYFIN_URL/System/Restart" \ - -H "Authorization: MediaBrowser Token=\"$JELLYFIN_TOKEN\"" \ - -H "Content-Length: 0"; then - echo "ERROR: Server failed to accept restart command." - exit 1 - fi - sleep 1- + setup = { + trigger = "server"; + envFile = config.sops.secrets."CUSTOM".path; + script = pkgs.writeShellScript "setup" '' + JELLYFIN_URL="https://${containerCfg.subdomain}.${serverCfg.domain}" until [ "$(${pkgs.curl}/bin/curl -sf "$JELLYFIN_URL/health")" = "Healthy" ]; do sleep 5 done echo "Jellyfin is up. Sleeping for 20 seconds..." sleep 20 - fi + WIZARD_COMPLETE=$(${pkgs.curl}/bin/curl -sSf "$JELLYFIN_URL/System/Info/Public" 2>/dev/null | \ + ${pkgs.jq}/bin/jq -r '.StartupWizardCompleted // false') + if [ "$WIZARD_COMPLETE" = "false" ]; then + if ! ${pkgs.curl}/bin/curl -sSf -X POST "$JELLYFIN_URL/Startup/Configuration" \ + -H "Content-Type: application/json" \ + -d '{"ServerName":"Flix","UICulture":"en-US","MetadataCountryCode":"US","PreferredMetadataLanguage":"en"}'; then + echo "ERROR: Failed to set startup configuration." + exit 1 + fi + + if ! ${pkgs.curl}/bin/curl -sSf -X GET "$JELLYFIN_URL/Startup/User"; then + echo "ERROR: Failed to get base user." + exit 1 + fi - if ! ${pkgs.curl}/bin/curl -sSf -X POST "$JELLYFIN_URL/Plugins/958aad66-3784-4d2a-b89a-a7b6fab6e25c/Configuration" \ - -H "Authorization: MediaBrowser Token=\"$JELLYFIN_TOKEN\"" \ - -H "Content-Type: application/json" -H 'accept: */*' \ - -d '{"LdapUsers":[],"LdapServer":"authentik-ldap","LdapPort":6636,"UseSsl":true,"UseStartTls":false,"SkipSslVerify":true, - "LdapBindUser":"cn=ldap-service,ou=users,${LDAP_DC_DOMAIN}","LdapBindPassword": "'"$DEFAULT_LDAP_PASSWORD"'", - "LdapBaseDn":"${LDAP_DC_DOMAIN}","LdapSearchFilter":"(memberOf=cn=flix,ou=groups,${LDAP_DC_DOMAIN})", - "LdapSearchAttributes":"uid, cn, mail, displayName", - "LdapAdminBaseDn":"","LdapAdminFilter":"(memberOf=cn=admin,ou=groups,${LDAP_DC_DOMAIN})", - "EnableLdapAdminFilterMemberUid":false,"LdapUidAttribute":"uid","LdapUsernameAttribute":"cn","LdapPasswordAttribute":"userPassword", - "EnableLdapProfileImageSync":false,"RemoveImagesNotInLdap":false,"LdapProfileImageAttribute":"jpegphoto","LdapProfileImageFormat":"Default", - "LdapClientCertPath":"","LdapClientKeyPath":"","LdapRootCaPath":"","CreateUsersFromLdap":true,"AllowPassChange":false, - "EnableAllFolders":true,"EnabledFolders":[],"PasswordResetUrl":""}'; then - echo "ERROR: LDAP Plugin Setup Failed." - exit 1 - fi - ''} + if ! ${pkgs.curl}/bin/curl -sSf -X POST "$JELLYFIN_URL/Startup/User" \ + -H 'accept: */*' -H "Content-Type: application/json" \ + -d '{"Name": "'"$DEFAULT_ADMIN_USERNAME"'", "Password": "'"$DEFAULT_ADMIN_PASSWORD"'"}'; then + echo "ERROR: Failed to set admin user." + exit 1 + fi - ${pkgs.sqlite}/bin/sqlite3 ${serverCfg.path.config}/jellyfin/data/data/jellyfin.db < one db for each + runtime = { paths = [ { path = "${serverCfg.dataPath}/media/"; mode = "0755"; } { path = "${serverCfg.configPath}/servarr/prowlarr"; mode = "0755"; } @@ -44,6 +45,8 @@ in containers = { }// lib.optionalAttrs (builtins.elem "prowlarr" (containerCfg.extra.modules or defaultModules)) { prowlarr = builder.mkContainer { + authentik = true; + tmpfs = true; subdomain = containerCfg.subdomain; subpath = "prowlarr"; imageStream = images.prowlarr; @@ -58,17 +61,15 @@ in }; extraOptions = [ "--user=0:0" - "--tmpfs=/tmp:rw,noexec,nosuid,size=512m" "--passwd-entry=root:x:0:0:root:/root:/bin/sh" ]; - extraLabels = { } // (if serverCfg.containers ? authentik then { - "traefik.http.routers.${containerCfg.subdomain}-prowlarr.middlewares" = "authentik"; - } else {}); overrides.volumes = sharedVolumes ++ [ "${serverCfg.configPath}/servarr/prowlarr:/config" ]; }; }// lib.optionalAttrs (builtins.elem "radarr" (containerCfg.extra.modules or defaultModules)) { radarr = builder.mkContainer { + authentik = true; + tmpfs = true; subdomain = containerCfg.subdomain; subpath = "radarr"; imageStream = images.radarr; @@ -83,17 +84,15 @@ in }; extraOptions = [ "--user=0:0" - "--tmpfs=/tmp:rw,noexec,nosuid,size=512m" "--passwd-entry=root:x:0:0:root:/root:/bin/sh" ]; - extraLabels = { } // (if serverCfg.containers ? authentik then { - "traefik.http.routers.${containerCfg.subdomain}-radarr.middlewares" = "authentik"; - } else {}); overrides.volumes = sharedVolumes ++ [ "${serverCfg.configPath}/servarr/radarr:/config" ]; }; }// lib.optionalAttrs (builtins.elem "sonarr" (containerCfg.extra.modules or defaultModules)) { sonarr = builder.mkContainer { + authentik = true; + tmpfs = true; subdomain = containerCfg.subdomain; subpath = "sonarr"; imageStream = images.sonarr; @@ -108,17 +107,15 @@ in }; extraOptions = [ "--user=0:0" - "--tmpfs=/tmp:rw,noexec,nosuid,size=512m" "--passwd-entry=root:x:0:0:root:/root:/bin/sh" ]; - extraLabels = { } // (if serverCfg.containers ? authentik then { - "traefik.http.routers.${containerCfg.subdomain}-sonarr.middlewares" = "authentik"; - } else {}); overrides.volumes = sharedVolumes ++ [ "${serverCfg.configPath}/servarr/sonarr:/config" ]; }; }// lib.optionalAttrs (builtins.elem "lidarr" (containerCfg.extra.modules or defaultModules)) { lidarr = builder.mkContainer { + authentik = true; + tmpfs = true; subdomain = containerCfg.subdomain; subpath = "lidarr"; imageStream = images.lidarr; @@ -133,12 +130,8 @@ in }; extraOptions = [ "--user=0:0" - "--tmpfs=/tmp:rw,noexec,nosuid,size=512m" "--passwd-entry=root:x:0:0:root:/root:/bin/sh" ]; - extraLabels = { } // (if serverCfg.containers ? authentik then { - "traefik.http.routers.${containerCfg.subdomain}-lidarr.middlewares" = "authentik"; - } else {}); overrides.volumes = sharedVolumes ++ [ "${serverCfg.configPath}/servarr/lidarr:/config" ]; }; @@ -526,4 +519,5 @@ in ''; }; + }; } diff --git a/modules/server/containers/apps/suwayomi.nix b/modules/server/containers/apps/suwayomi.nix index 13fe21e..7cf52f3 100644 --- a/modules/server/containers/apps/suwayomi.nix +++ b/modules/server/containers/apps/suwayomi.nix @@ -3,48 +3,51 @@ let version = "stable"; serverCfg = config.syscfg.server; in { - sops = true; - db =true; - containers = { - - server = builder.mkContainer { - subdomain = containerCfg.subdomain; - image = "ghcr.io/suwayomi/suwayomi-server:${version}"; - port = 4567; - secret = name; + requires = { + secrets = [ name ]; + databases = [ name ]; + }; - extraEnv = { - BIND_PORT = "4567"; - AUTH_MODE = "ui_login"; - WEB_UI_ENABLED = "true"; - WEB_UI_FLAVOR = "WebUI"; - # AUTO_DOWNLOAD_CHAPTERS = true; - # AUTO_DOWNLOAD_EXCLUDE_UNREAD = true; - # AUTO_DOWNLOAD_NEW_CHAPTERS_LIMIT = 0; - # AUTO_DOWNLOAD_IGNORE_REUPLOADS = false; - # DOWNLOAD_CONVERSIONS = {}; - # SERVE_CONVERSIONS = {}; - # MAX_SOURCES_IN_PARALLEL = 6; - # UPDATE_EXCLUDE_UNREAD = true; - # UPDATE_EXCLUDE_STARTED = true; - # UPDATE_EXCLUDE_COMPLETED = true; - # UPDATE_INTERVAL = 12; #Hours - # UPDATE_MANGA_INFO = false; - DATABASE_TYPE = "POSTGRESQL"; - DATABASE_URL = "postgresql://${builder.host}/suwayomi_db"; - DATABASE_USERNAME = "suwayomi_user"; - FLARESOLVERR_ENABLED = lib.boolToString (builtins.elem "flaresolverr" (((config.syscfg.server.containers.servarr or {}).extra or {}).modules or [])); - FLARESOLVERR_URL = "http://servarr-flaresolverr:8191"; - EXTENSION_REPOS = "[\"https://raw.githubusercontent.com/keiyoushi/extensions/repo/index.min.json\"]"; #https://raw.githubusercontent.com/keiyoushi/extensions/repo/index.min.json - }; + runtime = { + containers = { + server = builder.mkContainer { + subdomain = containerCfg.subdomain; + image = "ghcr.io/suwayomi/suwayomi-server:${version}"; + port = 4567; + secret = name; - overrides = { - volumes = [ - "${serverCfg.path.manga}:/home/suwayomi/.local/share/Tachidesk/downloads" - # "${serverCfg.path.config}/suwayomi:/home/suwayomi/.local/share/Tachidesk" - ]; + extraEnv = { + BIND_PORT = "4567"; + AUTH_MODE = "ui_login"; + WEB_UI_ENABLED = "true"; + WEB_UI_FLAVOR = "WebUI"; + # AUTO_DOWNLOAD_CHAPTERS = true; + # AUTO_DOWNLOAD_EXCLUDE_UNREAD = true; + # AUTO_DOWNLOAD_NEW_CHAPTERS_LIMIT = 0; + # AUTO_DOWNLOAD_IGNORE_REUPLOADS = false; + # DOWNLOAD_CONVERSIONS = {}; + # SERVE_CONVERSIONS = {}; + # MAX_SOURCES_IN_PARALLEL = 6; + # UPDATE_EXCLUDE_UNREAD = true; + # UPDATE_EXCLUDE_STARTED = true; + # UPDATE_EXCLUDE_COMPLETED = true; + # UPDATE_INTERVAL = 12; #Hours + # UPDATE_MANGA_INFO = false; + DATABASE_TYPE = "POSTGRESQL"; + DATABASE_URL = "postgresql://${builder.host}/suwayomi_db"; + DATABASE_USERNAME = "suwayomi_user"; + FLARESOLVERR_ENABLED = lib.boolToString (builtins.elem "flaresolverr" (((config.syscfg.server.containers.servarr or {}).extra or {}).modules or [])); + FLARESOLVERR_URL = "http://servarr-flaresolverr:8191"; + EXTENSION_REPOS = "[\"https://raw.githubusercontent.com/keiyoushi/extensions/repo/index.min.json\"]"; #https://raw.githubusercontent.com/keiyoushi/extensions/repo/index.min.json + }; + + overrides = { + volumes = [ + "${serverCfg.path.manga}:/home/suwayomi/.local/share/Tachidesk/downloads" + # "${serverCfg.path.config}/suwayomi:/home/suwayomi/.local/share/Tachidesk" + ]; + }; }; }; }; - -} \ No newline at end of file +} diff --git a/modules/server/containers/apps/traefik.nix b/modules/server/containers/apps/traefik.nix index 69a7371..d2b9d74 100644 --- a/modules/server/containers/apps/traefik.nix +++ b/modules/server/containers/apps/traefik.nix @@ -11,77 +11,79 @@ let }; }; in { - sops = true; - paths = [{ - path="${serverCfg.path.config}/traefik"; - owner = "1000:1000"; - mode = "0755"; - }]; + requires.secrets = [ name ]; - containers = { - server = builder.mkContainer { - imageStream = image; - subdomain = containerCfg.subdomain; - port = 8080; - secret = name; - extraLabels = { - "traefik.http.routers.${containerCfg.subdomain}.priority" = "10"; - "traefik.http.routers.${containerCfg.subdomain}.service" = "api@internal"; - - "traefik.http.routers.${containerCfg.subdomain}.middlewares" = if serverCfg.containers?authentik then "authentik" else ""; - } // (if serverCfg.containers?authentik then { - "traefik.http.middlewares.authentik.forwardauth.maxResponseBodySize" = "10485760"; - "traefik.http.middlewares.authentik.forwardauth.address" = "http://authentik-server:9000/outpost.goauthentik.io/auth/traefik"; - "traefik.http.middlewares.authentik.forwardauth.trustForwardHeader" = "true"; - "traefik.http.middlewares.authentik.forwardauth.authResponseHeaders" = "X-authentik-username,X-authentik-groups,X-authentik-email,X-authentik-name,X-authentik-uid,X-authentik-jwt,X-authentik-meta-jwks,X-authentik-meta-outpost,X-authentik-meta-provider,X-authentik-meta-app,X-authentik-meta-version"; - } else {}) // (if serverCfg.containers?umami then { - "traefik.http.middlewares.umami-global.plugin.umami-feeder.umamiHost" = "http://umami-server:3000"; - "traefik.http.middlewares.umami-global.plugin.umami-feeder.umamiUsername" = "admin"; - "traefik.http.middlewares.umami-global.plugin.umami-feeder.umamiPassword" = "umami"; - "traefik.http.middlewares.umami-global.plugin.umami-feeder.createNewWebsites" = "true"; - } else {}) // (if containerCfg.extra ? provider || serverCfg.domain != "localhost" then { - "traefik.http.routers.${containerCfg.subdomain}.tls.certresolver" = "default"; - "traefik.http.routers.${containerCfg.subdomain}.tls.domains[0].main" = "${serverCfg.domain}"; - "traefik.http.routers.${containerCfg.subdomain}.tls.domains[0].sans" = "*.${serverCfg.domain}"; - } else {}); - extraEnv = { }; - overrides = { - cmd = [ - "--api" - "--log.level=INFO" - "--providers.docker=true" - "--global.checknewversion=false" - "--global.sendanonymoususage=false" - "--api.insecure=true" - "--api.dashboard=true" - "--providers.docker.exposedByDefault=false" - "--entrypoints.web.address=:80" - "--entrypoints.web-secure.address=:443" - "--entrypoints.web.http.redirections.entrypoint.to=web-secure" - "--entrypoints.web.http.redirections.entrypoint.scheme=https" - "--entrypoints.web-secure.transport.respondingtimeouts.readtimeout=0s" - "--entrypoints.web-secure.proxyprotocol.trustedips=127.0.0.1/32,192.168.1.1/16,10.10.0.0/16" - ] ++ (if serverCfg.containers ? umami then [ - "--experimental.plugins.umami-feeder.moduleName=github.com/astappiev/traefik-umami-feeder" - "--experimental.plugins.umami-feeder.version=v1.4.1" - "--entrypoints.web-secure.http.middlewares=umami-global@docker" - ] else []) ++ (if containerCfg.extra ? provider then [ - "--certificatesresolvers.default.acme.email=acme@${serverCfg.domain}" - "--certificatesresolvers.default.acme.dnschallenge=true" - "--certificatesresolvers.default.acme.dnschallenge.provider=${containerCfg.extra.provider}" - "--certificatesresolvers.default.acme.storage=/custom/acme.json" - ] else []) ++ (if serverCfg.domain != "localhost" then [ - "--certificatesresolvers.default.acme.httpchallenge=false" - "--certificatesresolvers.default.acme.tlschallenge=true" - ] else []); - ports = [ "443:443" "80:80" ] ++ (if containerCfg.port!=null then [ "${toString containerCfg.port}:8080" ] else []); - volumes = [ - "/var/run/podman/podman.sock:/var/run/docker.sock" - # "${serverCfg.path.config}/traefik/access.log:/etc/traefik/access.log" - "${serverCfg.path.config}/traefik:/custom" - ]; + runtime = { + paths = [{ + path="${serverCfg.path.config}/traefik"; + owner = "1000:1000"; + mode = "0755"; + }]; + + containers = { + server = builder.mkContainer { + imageStream = image; + subdomain = containerCfg.subdomain; + port = 8080; + secret = name; + extraLabels = { + "traefik.http.routers.${containerCfg.subdomain}.priority" = "10"; + "traefik.http.routers.${containerCfg.subdomain}.service" = "api@internal"; + + "traefik.http.routers.${containerCfg.subdomain}.middlewares" = if serverCfg.containers?authentik then "authentik" else ""; + } // (if serverCfg.containers?authentik then { + "traefik.http.middlewares.authentik.forwardauth.maxResponseBodySize" = "10485760"; + "traefik.http.middlewares.authentik.forwardauth.address" = "http://authentik-server:9000/outpost.goauthentik.io/auth/traefik"; + "traefik.http.middlewares.authentik.forwardauth.trustForwardHeader" = "true"; + "traefik.http.middlewares.authentik.forwardauth.authResponseHeaders" = "X-authentik-username,X-authentik-groups,X-authentik-email,X-authentik-name,X-authentik-uid,X-authentik-jwt,X-authentik-meta-jwks,X-authentik-meta-outpost,X-authentik-meta-provider,X-authentik-meta-app,X-authentik-meta-version"; + } else {}) // (if serverCfg.containers?umami then { + "traefik.http.middlewares.umami-global.plugin.umami-feeder.umamiHost" = "http://umami-server:3000"; + "traefik.http.middlewares.umami-global.plugin.umami-feeder.umamiUsername" = "admin"; + "traefik.http.middlewares.umami-global.plugin.umami-feeder.umamiPassword" = "umami"; + "traefik.http.middlewares.umami-global.plugin.umami-feeder.createNewWebsites" = "true"; + } else {}) // (if containerCfg.extra ? provider || serverCfg.domain != "localhost" then { + "traefik.http.routers.${containerCfg.subdomain}.tls.certresolver" = "default"; + "traefik.http.routers.${containerCfg.subdomain}.tls.domains[0].main" = "${serverCfg.domain}"; + "traefik.http.routers.${containerCfg.subdomain}.tls.domains[0].sans" = "*.${serverCfg.domain}"; + } else {}); + extraEnv = { }; + overrides = { + cmd = [ + "--api" + "--log.level=INFO" + "--providers.docker=true" + "--global.checknewversion=false" + "--global.sendanonymoususage=false" + "--api.insecure=true" + "--api.dashboard=true" + "--providers.docker.exposedByDefault=false" + "--entrypoints.web.address=:80" + "--entrypoints.web-secure.address=:443" + "--entrypoints.web.http.redirections.entrypoint.to=web-secure" + "--entrypoints.web.http.redirections.entrypoint.scheme=https" + "--entrypoints.web-secure.transport.respondingtimeouts.readtimeout=0s" + "--entrypoints.web-secure.proxyprotocol.trustedips=127.0.0.1/32,192.168.1.1/16,10.10.0.0/16" + ] ++ (if serverCfg.containers ? umami then [ + "--experimental.plugins.umami-feeder.moduleName=github.com/astappiev/traefik-umami-feeder" + "--experimental.plugins.umami-feeder.version=v1.4.1" + "--entrypoints.web-secure.http.middlewares=umami-global@docker" + ] else []) ++ (if containerCfg.extra ? provider then [ + "--certificatesresolvers.default.acme.email=acme@${serverCfg.domain}" + "--certificatesresolvers.default.acme.dnschallenge=true" + "--certificatesresolvers.default.acme.dnschallenge.provider=${containerCfg.extra.provider}" + "--certificatesresolvers.default.acme.storage=/custom/acme.json" + ] else []) ++ (if serverCfg.domain != "localhost" then [ + "--certificatesresolvers.default.acme.httpchallenge=false" + "--certificatesresolvers.default.acme.tlschallenge=true" + ] else []); + ports = [ "443:443" "80:80" ] ++ (if containerCfg.port!=null then [ "${toString containerCfg.port}:8080" ] else []); + volumes = [ + "/var/run/podman/podman.sock:/var/run/docker.sock" + # "${serverCfg.path.config}/traefik/access.log:/etc/traefik/access.log" + "${serverCfg.path.config}/traefik:/custom" + ]; + }; }; }; }; } - diff --git a/modules/server/containers/apps/transmission.nix b/modules/server/containers/apps/transmission.nix index c12c4bc..b523b75 100644 --- a/modules/server/containers/apps/transmission.nix +++ b/modules/server/containers/apps/transmission.nix @@ -13,52 +13,47 @@ let }; }; }; - routerName = if containerCfg.subpath != null - then "${containerCfg.subdomain}-${lib.strings.sanitizeDerivationName containerCfg.subpath}" - else containerCfg.subdomain; in { - paths = [{ - path = "${serverCfg.path.config}/transmission"; - owner = "1000:1000"; - mode = "0755"; - }]; + runtime = { + paths = [{ + path = "${serverCfg.path.config}/transmission"; + owner = "1000:1000"; + mode = "0755"; + }]; - containers = { - server = builder.mkContainer { - subdomain = containerCfg.subdomain; - subpath = containerCfg.subpath; - imageStream = image; - port = 9091; - - extraEnv = { - PUID = "1000"; - PGID = "1000"; - WHITELIST = "";# 127.0.0.1,::1,10.*"; - # HOST_WHITELIST = "traefik-server,authentik-server,authentik-worker"; - }; - extraLabels = { - } // (if serverCfg.containers ? authentik then { - "traefik.http.routers.${routerName}.middlewares" = "authentik"; - } else {}); + containers = { + server = builder.mkContainer { + authentik = true; + subdomain = containerCfg.subdomain; + subpath = containerCfg.subpath; + imageStream = image; + port = 9091; + + extraEnv = { + PUID = "1000"; + PGID = "1000"; + WHITELIST = "";# 127.0.0.1,::1,10.*"; + # HOST_WHITELIST = "traefik-server,authentik-server,authentik-worker"; + }; - overrides = { - volumes = [ - "${serverCfg.path.dlComplete}:/downloads/complete" - "${serverCfg.path.dlIncomplete}:/downloads/incomplete" - "${serverCfg.path.config}/transmission:/config" - ]; + overrides = { + volumes = [ + "${serverCfg.path.dlComplete}:/downloads/complete" + "${serverCfg.path.dlIncomplete}:/downloads/incomplete" + "${serverCfg.path.config}/transmission:/config" + ]; + }; }; }; + + + setup = { + trigger = "server"; + envFile = [ config.sops.secrets."CUSTOM".path ]; + script = pkgs.writeShellScript "setup" '' + + ${pkgs.gettext}/bin/envsubst < "${../data/transmission/settings.json}" > "${serverCfg.path.config}/transmission/config/settings.json" + ''; + }; }; - - - setup = { - trigger = "server"; - envFile = [ config.sops.secrets."CUSTOM".path ]; - script = pkgs.writeShellScript "setup" '' - - ${pkgs.gettext}/bin/envsubst < "${../data/transmission/settings.json}" > "${serverCfg.path.config}/transmission/config/settings.json" - ''; - }; - -} \ No newline at end of file +} diff --git a/modules/server/containers/apps/umami.nix b/modules/server/containers/apps/umami.nix index 63e047e..b1ba347 100644 --- a/modules/server/containers/apps/umami.nix +++ b/modules/server/containers/apps/umami.nix @@ -15,40 +15,40 @@ let }; }; in { - sops = true; - db = true; - paths = [{ - path = "${serverCfg.path.config}/umami/"; - mode = "0444"; - }]; - - containers = { - server = builder.mkContainer { - subdomain = containerCfg.subdomain; - image = "${pkgs.umami.name}:${pkgs.umami.version}"; - imageStream = image; - port = 3000; - secret = name; - extraEnv = { - PORT = "3000"; - # HOSTNAME = "${containerCfg.subdomain}.${serverCfg.domain}"; - DATABASE_TYPE = "postgresql"; - REDIS_URL = "redis://${builder.host}"; - CLIENT_IP_HEADER = "X-Forwarded-For"; - BASE_PATH = lib.optionalString (containerCfg.subpath or null != null) "/${containerCfg.subpath}"; - # DISABLE_LOGIN = "1";#(if serverCfg.containers?authentik then "1" else "0"); - - }; - extraLabels = { } // ( if serverCfg.containers?authentik then { - "traefik.http.routers.${containerCfg.subdomain}.middlewares" = if serverCfg.containers?authentik then "authentik" else ""; - } else {}); - extraOptions = [ - "--tmpfs=/tmp:rw,noexec,nosuid,size=512m" - ]; - overrides = { - cmd = [ "start" ]; # Specific command for the umami binary - }; - }; + requires = { + secrets = [ name ]; + databases = [ name ]; }; -} \ No newline at end of file + runtime = { + paths = [{ + path = "${serverCfg.path.config}/umami/"; + mode = "0444"; + }]; + + containers = { + server = builder.mkContainer { + authentik = true; + tmpfs = true; + subdomain = containerCfg.subdomain; + image = "${pkgs.umami.name}:${pkgs.umami.version}"; + imageStream = image; + port = 3000; + secret = name; + extraEnv = { + PORT = "3000"; + # HOSTNAME = "${containerCfg.subdomain}.${serverCfg.domain}"; + DATABASE_TYPE = "postgresql"; + REDIS_URL = "redis://${builder.host}"; + CLIENT_IP_HEADER = "X-Forwarded-For"; + BASE_PATH = lib.optionalString (containerCfg.subpath or null != null) "/${containerCfg.subpath}"; + # DISABLE_LOGIN = "1";#(if serverCfg.containers?authentik then "1" else "0"); + + }; + overrides = { + cmd = [ "start" ]; # Specific command for the umami binary + }; + }; + }; + }; +} diff --git a/modules/server/containers/builder.nix b/modules/server/containers/builder.nix index 8c64b53..985fdac 100644 --- a/modules/server/containers/builder.nix +++ b/modules/server/containers/builder.nix @@ -1,16 +1,32 @@ { config, lib, pkgs, serverCfg }: let + mkRouterName = { subdomain, subpath ? null }: + if subpath != null + then "${subdomain}-${lib.strings.sanitizeDerivationName subpath}" + else subdomain; + getOr = attrs: path: default: lib.attrByPath path default attrs; + mkTmpfsOption = size: "--tmpfs=/tmp:rw,noexec,nosuid,size=${size}"; + mkAuthentikLabels = + { subdomain + , subpath ? null + , routerName ? mkRouterName { inherit subdomain subpath; } + , middleware ? "authentik" + }: + lib.optionalAttrs (serverCfg.containers ? authentik) { + "traefik.http.routers.${routerName}.middlewares" = middleware; + }; contBuilder = { image ? null, imageStream ? null, imageFile ? null , secret ? null , subdomain ? null, subpath?null, port ? null + , authentik ? false + , tmpfs ? false + , tmpfsSize ? "512m" , extraEnv ? { }, extraLabels ? { }, extraOptions ? [ ] , overrides ? { } }: let - routerName = if subpath != null - then "${subdomain}-${lib.strings.sanitizeDerivationName subpath}" - else subdomain; + routerName = mkRouterName { inherit subdomain subpath; }; base = { image = if imageStream != null then "${imageStream.imageName}:${imageStream.imageTag}" else if imageFile != null then "${imageFile.imageName}:${imageFile.imageTag}" else image; @@ -33,11 +49,15 @@ let "traefik.http.services.${routerName}.loadbalancer.server.port" = toString port; }) else { "traefik.enable" = "false"; - }) // extraLabels; + }) + // lib.optionalAttrs authentik (mkAuthentikLabels { inherit subdomain subpath routerName; }) + // extraLabels; extraOptions = [ "--add-host=host.containers.internal:host-gateway" - ] ++ extraOptions; + ] + ++ lib.optional tmpfs (mkTmpfsOption tmpfsSize) + ++ extraOptions; }; in lib.recursiveUpdate base overrides; vmBuilder = { name, vm }: ((import "${pkgs.path}/nixos/lib/eval-config.nix" { @@ -70,54 +90,27 @@ in { mkContainer = contBuilder; mkVm = vmBuilder; mkApp = name: app: - let - # Keep legacy app modules working while storing a stricter internal contract. - legacySetup = - if app ? setup then app.setup else null; - in { + { inherit name; requires = { - secrets = - if app ? requires && app.requires ? secrets then app.requires.secrets - else if app ? sops && app.sops then [ name ] - else [ ]; - databases = - if app ? requires && app.requires ? databases then app.requires.databases - else if app ? db && app.db then [ name ] - else [ ]; + secrets = getOr app [ "requires" "secrets" ] [ ]; + databases = getOr app [ "requires" "databases" ] [ ]; }; exports = { authentik = { - blueprints = - if app ? exports && app.exports ? authentik && app.exports.authentik ? blueprints - then app.exports.authentik.blueprints - else [ ]; + blueprints = getOr app [ "exports" "authentik" "blueprints" ] [ ]; }; }; runtime = { - paths = - if app ? runtime && app.runtime ? paths then app.runtime.paths - else if app ? paths then app.paths - else [ ]; - containers = - if app ? runtime && app.runtime ? containers then app.runtime.containers - else if app ? containers then app.containers - else { }; - vm = - if app ? runtime && app.runtime ? vm then app.runtime.vm - else if app ? vm then app.vm - else null; - cron = - if app ? runtime && app.runtime ? cron then app.runtime.cron - else if app ? cron then app.cron - else [ ]; - setup = - if app ? runtime && app.runtime ? setup then app.runtime.setup - else ({ - trigger = ""; - script = null; - envFile = [ ]; - } // (if legacySetup != null then legacySetup else { })); + paths = getOr app [ "runtime" "paths" ] [ ]; + containers = getOr app [ "runtime" "containers" ] { }; + vm = getOr app [ "runtime" "vm" ] null; + cron = getOr app [ "runtime" "cron" ] [ ]; + setup = { + trigger = ""; + script = null; + envFile = [ ]; + } // getOr app [ "runtime" "setup" ] { }; }; }; mkData = { name, dir, vars?{} }: pkgs.runCommand name vars '' diff --git a/modules/server/containers/default.nix b/modules/server/containers/default.nix index 5d4633b..931d9c0 100644 --- a/modules/server/containers/default.nix +++ b/modules/server/containers/default.nix @@ -2,51 +2,48 @@ let serverCfg = config.syscfg.server; builder = import ./builder.nix { inherit config lib pkgs serverCfg; }; - -in{ - config = lib.mkMerge [{ - syscfg.server.loadedContainers = lib.mapAttrs (name: containerCfg: - builder.mkApp name ((import (./apps + "/${name}.nix")) { inherit config pkgs lib containerCfg builder name; }) - ) config.syscfg.server.containers; - } (lib.mkIf ( serverCfg.containers != {} ) ( + loadApp = name: containerCfg: + builder.mkApp name ((import (./apps + "/${name}.nix")) { + inherit config pkgs lib containerCfg builder name; + }); + loadedContainers = lib.mapAttrs loadApp serverCfg.containers; + appsList = builtins.attrValues loadedContainers; + concatRuntimeLists = field: lib.concatMap (app: app.runtime.${field}) appsList; + mkNamedUnits = mkUnit: items: lib.listToAttrs (map mkUnit items); + mergedContainers = lib.concatMapAttrs (appName: app: + lib.mapAttrs' (cName: cCfg: lib.nameValuePair "${appName}-${cName}" cCfg) app.runtime.containers + ) loadedContainers; + allPathConfigs = map (path: { + inherit path; + mode = "0755"; + }) (lib.unique (builtins.attrValues serverCfg.path)) ++ concatRuntimeLists "paths"; + allSetupConfigs = map (app: ({ name = app.name; envFile = ""; } // app.runtime.setup)) appsList; + allCronsConfigs = concatRuntimeLists "cron"; + allVMConfigs = builtins.filter (app: app.runtime.vm != null) appsList; + mkPathSetup = cfg: let - appsList = builtins.attrValues config.syscfg.server.loadedContainers; - mergedContainers = lib.concatMapAttrs (appName: app: - lib.mapAttrs' (cName: cCfg: lib.nameValuePair "${appName}-${cName}" cCfg) app.runtime.containers - ) config.syscfg.server.loadedContainers; - serverPathConfigs = map (path: { - inherit path; - mode = "0755"; - }) (lib.unique (builtins.attrValues serverCfg.path)); - allPathConfigs = serverPathConfigs ++ lib.concatMap (app: app.runtime.paths) appsList; - allSetupConfigs = lib.concatMap (app: - if app.runtime.setup ? script - then [ ({ name = app.name; envFile = ""; } // app.runtime.setup) ] - else [ ] - ) appsList; - allCronsConfigs = lib.concatMap (app: app.runtime.cron) appsList; - allVMConfigs = builtins.filter (app: app.runtime.vm != null) appsList; - in{ + effectiveCfg = { + owner = "root:root"; + mode = "0400"; + dirs = []; + } // cfg; + in '' + ${pkgs.coreutils}/bin/mkdir -p "${effectiveCfg.path}" + ${lib.concatMapStringsSep "\n" (dir: "${pkgs.coreutils}/bin/mkdir -p ${effectiveCfg.path}/${lib.escapeShellArg dir}") effectiveCfg.dirs} + ${pkgs.coreutils}/bin/chown ${effectiveCfg.owner} "${effectiveCfg.path}" + ${pkgs.coreutils}/bin/chmod ${effectiveCfg.mode} "${effectiveCfg.path}" + ''; +in { + config = lib.mkMerge [{ + syscfg.server.loadedContainers = loadedContainers; + } (lib.mkIf (loadedContainers != {}) { virtualisation.oci-containers = { backend = "podman"; containers = mergedContainers; }; system.activationScripts.container-setup-dirs = { deps = [ "users" "groups" ]; - text = lib.concatStringsSep "\n" (map (cfg: - let - effectiveCfg = { - owner = "root:root"; - mode = "0400"; - dirs = []; - } // cfg; - in '' - ${pkgs.coreutils}/bin/mkdir -p "${effectiveCfg.path}" - ${lib.concatMapStringsSep "\n" (dir: "${pkgs.coreutils}/bin/mkdir -p ${effectiveCfg.path}/${lib.escapeShellArg dir}") effectiveCfg.dirs} - ${pkgs.coreutils}/bin/chown ${effectiveCfg.owner} "${effectiveCfg.path}" - ${pkgs.coreutils}/bin/chmod ${effectiveCfg.mode} "${effectiveCfg.path}" - - '') allPathConfigs); + text = lib.concatStringsSep "\n" (map mkPathSetup allPathConfigs); }; systemd.services = { @@ -60,7 +57,7 @@ in{ startAt = "weekly"; }; } - // lib.listToAttrs (lib.concatMap (e: [{ + // mkNamedUnits (e: { name = "${e.name}-vm"; value = { description = "Isolated NixOS Guest VM for ${e.name}"; @@ -81,8 +78,8 @@ in{ ''; }; }; - }]) allVMConfigs) - // lib.listToAttrs (lib.concatMap (e: [{ + }) allVMConfigs + // mkNamedUnits (e: { name = "${e.name}-setup"; value = { description = "Run ${e.name} setup"; @@ -98,13 +95,11 @@ in{ User = "root"; }; }; - }]) allSetupConfigs ); + }) allSetupConfigs; services.cron = { enable = true; systemCronJobs = allCronsConfigs; }; - - }))]; - + })]; }