From ab6f8e21dc153872836a79641496db0e1fe0c57c Mon Sep 17 00:00:00 2001 From: Nickolaj Jepsen Date: Sat, 26 Apr 2025 20:02:56 +0200 Subject: [PATCH] feat(homelab): implement SSO --- hosts/homelab/arr.nix | 49 +++++--- hosts/homelab/flame.nix | 5 +- hosts/homelab/home-assistant.nix | 20 ++-- hosts/homelab/netdata.nix | 13 --- hosts/homelab/nextcloud.nix | 3 + hosts/homelab/nginx.nix | 4 + hosts/homelab/postgres.nix | 2 +- hosts/homelab/prometheus.nix | 52 +++++++++ hosts/homelab/sso.nix | 105 ++++++++++++++++++ hosts/homelab/vaultwarden.nix | 8 +- secrets/grafana-cloud-prometheus.age | Bin 0 -> 477 bytes ...88a8c1c51e34fc4251-netdata-claim-token.age | 7 -- ...b82f1df1b080438c4cb959aea-oauth2-proxy.age | Bin 0 -> 470 bytes ...5a930-grafana-cloud-prometheus-api-key.age | 8 ++ ...f032b4bdc227653db033537-zitadel-master.age | 7 ++ .../hosts/homelab/oauth2-proxy-keyfile.age | Bin 0 -> 636 bytes secrets/hosts/homelab/zitadel-master.age | 10 ++ 17 files changed, 245 insertions(+), 48 deletions(-) delete mode 100644 hosts/homelab/netdata.nix create mode 100644 hosts/homelab/prometheus.nix create mode 100644 hosts/homelab/sso.nix create mode 100644 secrets/grafana-cloud-prometheus.age delete mode 100644 secrets/hosts/homelab/.rekey/14b416bbae3cdc88a8c1c51e34fc4251-netdata-claim-token.age create mode 100644 secrets/hosts/homelab/.rekey/26a0da2b82f1df1b080438c4cb959aea-oauth2-proxy.age create mode 100644 secrets/hosts/homelab/.rekey/a9a1f1e6e1af161328d1360759d5a930-grafana-cloud-prometheus-api-key.age create mode 100644 secrets/hosts/homelab/.rekey/ae35b3940f032b4bdc227653db033537-zitadel-master.age create mode 100644 secrets/hosts/homelab/oauth2-proxy-keyfile.age create mode 100644 secrets/hosts/homelab/zitadel-master.age diff --git a/hosts/homelab/arr.nix b/hosts/homelab/arr.nix index 7985fb6..b93fc89 100644 --- a/hosts/homelab/arr.nix +++ b/hosts/homelab/arr.nix @@ -12,18 +12,15 @@ locations."/" = { proxyPass = "http://localhost:${toString port}"; }; - basicAuthFile = "${config.age.secrets.arr-basic-auth.path}"; + locations."/api" = { + proxyPass = "http://localhost:${toString port}"; + extraConfig = '' + auth_request off; + ''; + }; }; in { # for linux ISOs - age.secrets = { - arr-basic-auth = { - rekeyFile = ../../secrets/hosts/homelab/basic-auth.age; - owner = config.services.nginx.user; - inherit (config.services.nginx) group; - }; - }; - users.groups."${group}" = { members = [username]; }; @@ -33,19 +30,37 @@ in { }; services = { + oauth2-proxy.nginx.virtualHosts = { + "radarr.nickolaj.com".allowed_groups = ["arr"]; + "sonarr.nickolaj.com".allowed_groups = ["arr"]; + "prowlarr.nickolaj.com".allowed_groups = ["arr"]; + "sabnzbd.nickolaj.com".allowed_groups = ["arr"]; + "bazarr.nickolaj.com".allowed_groups = ["arr"]; + }; nginx.virtualHosts = { "radarr.nickolaj.com" = mkVirtualHost 7878; "sonarr.nickolaj.com" = mkVirtualHost 8989; "prowlarr.nickolaj.com" = mkVirtualHost 9696; "sabnzbd.nickolaj.com" = mkVirtualHost 8080; + "bazarr.nickolaj.com" = mkVirtualHost config.services.bazarr.listenPort; }; - restic.backups.homelab.paths = [ - "/var/lib/radarr" - "/var/lib/sonarr" - "/var/lib/prowlarr" - "/var/lib/sabnzbd" - ]; + restic.backups.homelab = { + paths = [ + "/var/lib/radarr" + "/var/lib/sonarr" + "/var/lib/prowlarr" + "/var/lib/sabnzbd" + "/var/lib/bazarr" + ]; + exclude = [ + # arrs logs and media cover + "/var/lib/*/.config/*/logs/" + "/var/lib/*/.config/*/MediaCover/" + "/var/lib/sabnzbd/Downloads/" + "/var/lib/sabnzbd/logs/" + ]; + }; sabnzbd = { inherit user group; @@ -59,6 +74,10 @@ in { inherit user group; enable = true; }; + bazarr = { + inherit user group; + enable = true; + }; prowlarr.enable = true; }; } diff --git a/hosts/homelab/flame.nix b/hosts/homelab/flame.nix index 9e4f5e5..3739109 100644 --- a/hosts/homelab/flame.nix +++ b/hosts/homelab/flame.nix @@ -2,7 +2,10 @@ _: let dataDir = "/var/lib/flame"; domain = "flame.nickolaj.com"; in { - services.restic.backups.homelab.paths = [dataDir]; + services.restic.backups.homelab = { + paths = [dataDir]; + exclude = ["/var/lib/flame/db_backups"]; + }; services.nginx.virtualHosts."${domain}" = { enableACME = true; diff --git a/hosts/homelab/home-assistant.nix b/hosts/homelab/home-assistant.nix index 7e8d88b..17ae55e 100644 --- a/hosts/homelab/home-assistant.nix +++ b/hosts/homelab/home-assistant.nix @@ -14,11 +14,6 @@ in { owner = "zigbee2mqtt"; group = "zigbee2mqtt"; }; - z2m-basic-auth = { - rekeyFile = ../../secrets/hosts/homelab/basic-auth.age; - owner = config.services.nginx.user; - inherit (config.services.nginx) group; - }; mosquitto-zigbee2mqtt.rekeyFile = ../../secrets/hosts/homelab/mosquitto-zigbee2mqtt.age; mosquitto-sas.rekeyFile = ../../secrets/hosts/homelab/mosquitto-sas.age; mosquitto-ha.rekeyFile = ../../secrets/hosts/homelab/mosquitto-ha.age; @@ -29,11 +24,17 @@ in { ]; services = { - restic.backups.homelab.paths = [ - config.services.zigbee2mqtt.dataDir - config.services.home-assistant.configDir - ]; + restic.backups.homelab = { + paths = [ + config.services.zigbee2mqtt.dataDir + config.services.home-assistant.configDir + ]; + exclude = [ + "/var/lib/zigbee2mqtt/log/" + ]; + }; + oauth2-proxy.nginx.virtualHosts."zigbee.nickolaj.com".allowed_groups = ["iot-admin"]; nginx.virtualHosts = { "zigbee.nickolaj.com" = { enableACME = true; @@ -42,7 +43,6 @@ in { proxyPass = "http://localhost:${toString zigbee2mqttPort}"; proxyWebsockets = true; }; - basicAuthFile = "${config.age.secrets.z2m-basic-auth.path}"; }; "ha.nickolaj.com" = { enableACME = true; diff --git a/hosts/homelab/netdata.nix b/hosts/homelab/netdata.nix deleted file mode 100644 index 4cf4c00..0000000 --- a/hosts/homelab/netdata.nix +++ /dev/null @@ -1,13 +0,0 @@ -{ - config, - pkgsUnstable, - ... -}: { - age.secrets.netdata-claim-token.rekeyFile = ../../secrets/netdata-claim-token.age; - - services.netdata = { - enable = true; - package = pkgsUnstable.netdataCloud; - claimTokenFile = "${config.age.secrets.netdata-claim-token.path}"; - }; -} diff --git a/hosts/homelab/nextcloud.nix b/hosts/homelab/nextcloud.nix index b5c6b9b..6f6be6d 100644 --- a/hosts/homelab/nextcloud.nix +++ b/hosts/homelab/nextcloud.nix @@ -27,6 +27,9 @@ adminpassFile = "${config.age.secrets.nextcloud-admin-pass.path}"; dbtype = "pgsql"; }; + extraApps = { + inherit (config.services.nextcloud.package.packages.apps) sociallogin; + }; }; }; } diff --git a/hosts/homelab/nginx.nix b/hosts/homelab/nginx.nix index ec4495e..7e02436 100644 --- a/hosts/homelab/nginx.nix +++ b/hosts/homelab/nginx.nix @@ -3,7 +3,11 @@ _: { services.nginx = { enable = true; + recommendedTlsSettings = true; + recommendedOptimisation = true; recommendedProxySettings = true; + recommendedGzipSettings = true; + recommendedBrotliSettings = true; }; security.acme = { acceptTerms = true; diff --git a/hosts/homelab/postgres.nix b/hosts/homelab/postgres.nix index 29fd3da..95e7d69 100644 --- a/hosts/homelab/postgres.nix +++ b/hosts/homelab/postgres.nix @@ -5,4 +5,4 @@ postgresql.enable = true; postgresqlBackup.enable = true; }; -} \ No newline at end of file +} diff --git a/hosts/homelab/prometheus.nix b/hosts/homelab/prometheus.nix new file mode 100644 index 0000000..48bb919 --- /dev/null +++ b/hosts/homelab/prometheus.nix @@ -0,0 +1,52 @@ +{ + config, + hostname, + ... +}: let + mkScrapeConfig = name: { + job_name = name; + static_configs = [ + { + labels = { + instance = hostname; + }; + + targets = [ + "${toString config.services.prometheus.exporters.${name}.listenAddress}:${toString config.services.prometheus.exporters.${name}.port}" + ]; + } + ]; + }; +in { + age.secrets.grafana-cloud-prometheus-api-key = { + rekeyFile = ../../secrets/grafana-cloud-prometheus.age; + owner = "prometheus"; + group = "prometheus"; + }; + + services.prometheus = { + enable = true; + enableAgentMode = true; + globalConfig.scrape_interval = "1m"; + remoteWrite = [ + { + url = "https://prometheus-prod-01-eu-west-0.grafana.net/api/prom/push"; + basic_auth = { + username = "432120"; + password_file = "${config.age.secrets.grafana-cloud-prometheus-api-key.path}"; + }; + } + ]; + + scrapeConfigs = [ + (mkScrapeConfig "node") + ]; + + exporters.node = { + enable = true; + extraFlags = [ + "--web.disable-exporter-metrics" + ]; + }; + }; +} diff --git a/hosts/homelab/sso.nix b/hosts/homelab/sso.nix new file mode 100644 index 0000000..b34aa91 --- /dev/null +++ b/hosts/homelab/sso.nix @@ -0,0 +1,105 @@ +{config, pkgsUnstable, ...}: let + port = 9190; + rootDomain = "nickolaj.com"; + zitadelDomain = "sso.${rootDomain}"; + oathproxyDomain = "oauth2-proxy.${rootDomain}"; +in { + age.secrets.zitadel-master = { + rekeyFile = ../../secrets/hosts/homelab/zitadel-master.age; + owner = config.services.zitadel.user; + }; + age.secrets.oauth2-proxy = { + rekeyFile = ../../secrets/hosts/homelab/oauth2-proxy-keyfile.age; + owner = "oauth2-proxy"; + }; + + services.nginx.virtualHosts."${zitadelDomain}" = { + enableACME = true; + forceSSL = true; + locations."/" = { + proxyPass = "http://127.0.0.1:${toString port}"; + extraConfig = '' + grpc_pass grpc://127.0.0.1:${toString port}; + grpc_set_header Host $host:$server_port; + ''; + }; + }; + services.nginx.virtualHosts."${oathproxyDomain}" = { + enableACME = true; + forceSSL = true; + locations."/" = { + proxyWebsockets = true; + proxyPass = "http://127.0.0.1:4180"; + }; + }; + + services.postgresql = { + ensureDatabases = ["zitadel"]; + ensureUsers = [ + { + name = "zitadel"; + ensureDBOwnership = true; + ensureClauses.login = true; + ensureClauses.superuser = true; + } + ]; + }; + + services.zitadel = { + enable = true; + package = pkgsUnstable.zitadel; + masterKeyFile = config.age.secrets.zitadel-master.path; + settings = { + Port = port; + Database.postgres = { + Host = "/var/run/postgresql/"; + Port = 5432; + Database = "zitadel"; + User = { + Username = "zitadel"; + SSL.Mode = "disable"; + }; + Admin = { + Username = "zitadel"; + SSL.Mode = "disable"; + ExistingDatabase = "zitadel"; + }; + }; + ExternalDomain = zitadelDomain; + ExternalPort = 443; + ExternalSecure = true; + }; + steps.FirstInstance = { + InstanceName = "Fireproof Auth"; + Org = { + Name = "Fireproof Auth"; + Human = { + UserName = "nickolaj1177@gmail.com"; + FirstName = "Nickolaj"; + LastName = "Jepsen"; + Email.Verified = true; + Password = "Password1!"; + PasswordChangeRequired = true; + }; + }; + LoginPolicy.AllowRegister = false; + }; + }; + + services.oauth2-proxy = { + enable = true; + package = pkgsUnstable.oauth2-proxy; + provider = "oidc"; + reverseProxy = true; + redirectURL = "https://${oathproxyDomain}/oauth2/callback"; + validateURL = "https://${zitadelDomain}/oauth2/"; + oidcIssuerUrl = "https://${zitadelDomain}:443"; + keyFile = config.age.secrets.oauth2-proxy.path; + nginx.domain = oathproxyDomain; + email.domains = ["*"]; + extraConfig = { + whitelist-domain = ".${rootDomain}"; + cookie-domain = ".${rootDomain}"; + }; + }; +} diff --git a/hosts/homelab/vaultwarden.nix b/hosts/homelab/vaultwarden.nix index 6336076..c1d915b 100644 --- a/hosts/homelab/vaultwarden.nix +++ b/hosts/homelab/vaultwarden.nix @@ -11,7 +11,13 @@ in { ROCKET_PORT = 8222; }; }; - restic.backups.homelab.paths = ["/var/lib/vaultwarden"]; + restic.backups.homelab = { + paths = ["/var/lib/vaultwarden"]; + exclude = [ + "/var/lib/vaultwarden/icon_cache" + "/var/lib/vaultwarden/tmp" + ]; + }; nginx.virtualHosts."${domain}" = { enableACME = true; diff --git a/secrets/grafana-cloud-prometheus.age b/secrets/grafana-cloud-prometheus.age new file mode 100644 index 0000000000000000000000000000000000000000..520e57a1f8c84b5bc53020a2050854ec6f65a976 GIT binary patch literal 477 zcmYdHPt{G$OD?J`D9Oyv)5|YP*Do{V(zR2FFfuhYv{Wc4bI-~vOm+$LjZBX+OEZYf zPBC;eu`JBU^(*tYC`$I#cg#vOb#@L8jN~fu@XCk^&C9bic1qDt$}V>`@pN->E;02n zbMY&22?_Es45{)6DlZT8vH;mokXfc%U}S2hP-yJq7gny|n3-j6T5g^il$GNh?o<)( zZR(MpkrHST6l79aU}l*fneA;{=@V9D;9KI$73Ad+;#raB?p17T=u~OpQV^8xpAs1n z7UGkbnCe*MlI0!Y?wu3tS(4}pvP&nmRyVyUHL*BV!7r)C#6Uqk!cajcF+|@h&4SCN zGMP(PS69I!T|YH7%O%~{&%IQ?(k;l#H6kF;EyyW9JhCDwJJUb1)Y!Sq(AhL7(~&Ei zRohlX=6y%1$a_J#DANxn-&W5Hs=b}g&3fD6MT511PLcOtNp0VVkP^Ad!eaK^P><)+Djw_{&B3r`N!>ESQtX7V=BF8p@r zetE!?s ssh-ed25519 uxq+Zw O7Lgl8nCBN+7PkfnDJg1QlJuJsIWHsC0ph5HRvc+U3E -bdllRkmn3RDwg1DVPSHLtYWfay/Y+hUeCtdiCYc1rDM --> JC5p)q|.-grease J<5Gb -#' }(d &;SYO -9sFJvUrPneffSRN4a5VLMZBMJYluYh7efNGdJHnEnDLS2NJt33px ---- Ct3YI+Fb2sCve9NRhIMZfAlDEN0jANs6kJREaSeRxUo -I|#+R : OFY\c|XӲFmX/OI]Q5t!v‚SVRMׄ42oczMFXݽђ+$#Iia(K8Ϧ̦i22}ЦO@ zqP \ No newline at end of file diff --git a/secrets/hosts/homelab/.rekey/26a0da2b82f1df1b080438c4cb959aea-oauth2-proxy.age b/secrets/hosts/homelab/.rekey/26a0da2b82f1df1b080438c4cb959aea-oauth2-proxy.age new file mode 100644 index 0000000000000000000000000000000000000000..ea247281a10cecdb9ea09568c9e5bcdf1bc35d84 GIT binary patch literal 470 zcmYdHPt{G$OD?J`D9Oyv)5|YP*Do{V(zR14F3!+RO))YxHMCSHttixvDpxScNJ>x7 zEzfq;56bXK%qc8!PH{B!$w)O#%PlMpNpT9cG&8HJN(@NNN#_cT$|*2POUn*0%L=h@ za&t5ZaP^NU$t-kn^GFKH4fiN3bI#YVOw`Y)36KZL7LwuR9bOb^XiyPgW*Qb=sU4XfkzcA^W?_~d5$SCnoaGhBrK_u}U}5G_YG4)- zkl~S8W@v7d>JgIepB-72pX%uuR8pF6p04j?6y}j<;Z`2XRpY|ozHX0O_{+pgm)0hF zcrxz_Ws<1tNzY*vJJF}K_4TSpN{cguc-1f7`8Gjx<>3QUm9>2~_fC|KE1zC_=+5c- z^a=-;)oZ^BX1|*y7MZ-w*F(~D{zutGHrM$yHmS{@v|!(&8|-fTO^b3qUR^Gr-uI(s zxxkm;^Bv}v&A~2LCp>wxDr4gI^UL@BdVM ssh-ed25519 uxq+Zw QFvSKN7maaWY8fYzwxBASBvTXg55zUhkZLJZVeQCBSg +tNvUOsiH0p1QfaAFbj1XqNmOUijykTaEdmxohFuNqlQ +-> tqn,r-grease FV& }tddM +vfFh7GoVlDh4YaZkFvROgw +--- SCregMmWnU6Ihuzv8E14/jnXY3Kd09235BvQZXePLRI +d6ȤN/,na 1 儌R$4HU'Q +xA[sv2@ 9{ Vl=)X~DA[un ѰiċڶtMXmklu]-)߬*i0  런ѿ$ ssh-ed25519 uxq+Zw g1Pw9Cgly2Wy9jiaKGqzVCJOuDDPfxfxDLe0XIyTtgU +vnYYw7sfYFc5hBI8ub5hzEXJaK04vno+uUChpkd1g9s +-> q,.q!V-grease 9$zhS U^zTl +nJEewmCjlrNA+8UTjveja2AYbl481OTdb6JkaZMr +--- rw1uP9RF+ehWosrklvIK9CbcKbwc9founpsG0XEXcgk +Z_8xOe}klK l>qxd;z&- >`0_3 \ No newline at end of file diff --git a/secrets/hosts/homelab/oauth2-proxy-keyfile.age b/secrets/hosts/homelab/oauth2-proxy-keyfile.age new file mode 100644 index 0000000000000000000000000000000000000000..f3d842925e7859a0c3536a7619baee9084929ced GIT binary patch literal 636 zcmYdHPt{G$OD?J`D9Oyv)5|YP*Do{V(zR2FFfuhYv{W#w^7G5_j&d(8Obd?4POkDd zD>HIRb8)LM@o)>bEDbmIaW2bCj!1V(_vK164J~muGc!*$H%rQ@FbcEuvM}?j%rnc* z4vNUm&aVh9NzE*AF*XP;O9t6ckXfc%U}S2hP-yJq7gny|Selwwpf;(79vM+sP-0%fmE#_2u3cnUmY5Z07HsSoXjp611Bz9Fpczl>^(Z|p&%{!>j zG$lXZ$3Md-BtO@HE3C-Hu`$$TG`aJJh7YtjsXPxFRUT!ZOLj*(=eLYiCvUt8GF% z6Qwvr*4*5;SaGXcW#C?s_UEC7mzi!_)aGri)cULcB;Z=M7W1(Q)gQmzJ-;#GpSk@Xwve9kk@3^LVb+k`(De{s1Bl?2jUr){d-69^| zQkypDJG*Y*-ZMpH&d*B=8#9^nyUr~MS!vYICjIeh$^XwnRrRuIU;Q6(e-(Z3{cK5= z!JYem>Qgv>mLAX%4ENo*J42vhhI}9AoLmV;K_=sOJVfcb literal 0 HcmV?d00001 diff --git a/secrets/hosts/homelab/zitadel-master.age b/secrets/hosts/homelab/zitadel-master.age new file mode 100644 index 0000000..43593b1 --- /dev/null +++ b/secrets/hosts/homelab/zitadel-master.age @@ -0,0 +1,10 @@ +age-encryption.org/v1 +-> X25519 8fy15guw0en6anMwXtYtgNORD8Lj5RiymCgYZqMPoHo +AQJP0q/gkG/JbwuUCCeILemcGM8sgXVsPzTyZK2Gm14 +-> piv-p256 q3LNVw A+5W/lITCh0xb5xDxwQQWCjfhKrAEFU1Ix/SwbKjGxSG +kSentadMboREK630i0dAp/NWcgta2BDO878p6Rxg1vU +-> ?/=3-grease x7td +3S2C9b7mavQzhSWlkEzioYuD2+HmjkXdiyKMgkd1KwUw6Jz/FoT6+skE5wXVT0jA +q9B0aa66NhAoT7WKxlqvrUJPnjq9QuFeV7UNN3PCG1pstkIk+s4 +--- 2B0VffwckYQT11chc0laW9oRHaaZSy3wrnjP0xe/Yrc +I>Q}Zz'z=`ƻ@]48ZLzpk^;IۗHro: \ No newline at end of file