From 28dc0896b9de3f943fd4127e49de47b0f43667c9 Mon Sep 17 00:00:00 2001 From: Lucy Hochkamp Date: Thu, 4 Dec 2025 00:21:41 +0100 Subject: [PATCH] navidrome --- flake.lock | 42 +-- instances/nemesis/configuration.nix | 10 + instances/nemesis/default.nix | 2 +- instances/nemesis/hardware-configuration.nix | 2 +- instances/nemesis/secrets/dyndns.yaml | 62 ++-- instances/nemesis/secrets/kanidm.yaml | 80 +++++ instances/nemesis/services/navidrome.nix | 48 +++ instances/nemesis/services/traccar.nix | 93 ++++++ instances/theseus/configuration.nix | 1 + modules/module-list.nix | 4 + modules/presets/cli.nix | 2 + modules/services/kanidm.nix | 303 +++++++++++++++---- modules/services/oauth2Proxy/default.nix | 85 ++++++ modules/services/oauth2Proxy/integration.nix | 208 +++++++++++++ modules/services/postgres.nix | 6 +- modules/services/traefik.nix | 192 +++++++----- 16 files changed, 969 insertions(+), 171 deletions(-) create mode 100644 instances/nemesis/secrets/kanidm.yaml create mode 100644 instances/nemesis/services/navidrome.nix create mode 100644 instances/nemesis/services/traccar.nix create mode 100644 modules/services/oauth2Proxy/default.nix create mode 100644 modules/services/oauth2Proxy/integration.nix diff --git a/flake.lock b/flake.lock index c99b237f..cb75e271 100644 --- a/flake.lock +++ b/flake.lock @@ -391,11 +391,11 @@ ] }, "locked": { - "lastModified": 1764194569, - "narHash": "sha256-iUM9ktarEzThkayyZrzQ7oycPshAY2XRQqVKz0xX/L0=", + "lastModified": 1764304195, + "narHash": "sha256-bO7FN/bF6gG7TlZpKAZjO3VvfsLaPFkefeUfJJ7F/7w=", "owner": "nix-community", "repo": "home-manager", - "rev": "9651819d75f6c7ffaf8a9227490ac704f29659f0", + "rev": "86ff0ef506c209bb397849706e85cc3a913cb577", "type": "github" }, "original": { @@ -704,11 +704,11 @@ "rust-overlay": "rust-overlay_2" }, "locked": { - "lastModified": 1764160906, - "narHash": "sha256-6Dud/oe0UnkgCe+JAGF+GjXl6S6ynpcAUYWCdbXjJL4=", + "lastModified": 1764306526, + "narHash": "sha256-Lwz8CwV4ryaqbKNZ4ftnNrhwZI29aoHQ5MRJponTDTY=", "owner": "YaLTeR", "repo": "niri", - "rev": "8370c539fb584f78924b826d0ac0ad8fa068cf95", + "rev": "0652342df8fffa93abcc5e567be1afb3eab002f6", "type": "github" }, "original": { @@ -782,11 +782,11 @@ }, "nixos-hardware": { "locked": { - "lastModified": 1764080039, - "narHash": "sha256-b1MtLQsQc4Ji1u08f+C6g5XrmLPkJQ1fhNkCt+0AERQ=", + "lastModified": 1764328224, + "narHash": "sha256-hFyF1XQd+XrRx7WZCrGJp544dykexD8Q5SrJJZpEQYg=", "owner": "NixOS", "repo": "nixos-hardware", - "rev": "da17006633ca9cda369be82893ae36824a2ddf1a", + "rev": "d62603a997438e19182af69d3ce7be07565ecad4", "type": "github" }, "original": { @@ -814,11 +814,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1763966396, - "narHash": "sha256-6eeL1YPcY1MV3DDStIDIdy/zZCDKgHdkCmsrLJFiZf0=", + "lastModified": 1764242076, + "narHash": "sha256-sKoIWfnijJ0+9e4wRvIgm/HgE27bzwQxcEmo2J/gNpI=", "owner": "nixos", "repo": "nixpkgs", - "rev": "5ae3b07d8d6527c42f17c876e404993199144b6a", + "rev": "2fad6eac6077f03fe109c4d4eb171cf96791faa4", "type": "github" }, "original": { @@ -858,11 +858,11 @@ }, "nixpkgs-master": { "locked": { - "lastModified": 1764245472, - "narHash": "sha256-udCSZoKawLiNJKFQgikkN96tvVJggs61ihRq8sRt8tM=", + "lastModified": 1764330598, + "narHash": "sha256-tIMoFVAMzjGq3iLiA0hQ+C6CtLkmJEBXuEm0XtwlgtU=", "owner": "nixos", "repo": "nixpkgs", - "rev": "6c16b7ce10e5ef1fee3aecfb327be61888818a7a", + "rev": "b8ee623e26961c7f34e78248d553fa35e1f8032b", "type": "github" }, "original": { @@ -1177,11 +1177,11 @@ ] }, "locked": { - "lastModified": 1764211126, - "narHash": "sha256-p5y13PnMZYd5WdHk+XCzyUaLGBUCwnz2n4KYKEZM0Pw=", + "lastModified": 1764297505, + "narHash": "sha256-qrLpVu2/hA9Cu6IovMEsgh9YRyvmmWS+bSx7C1JGChA=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "895935bff08cfcfb663fb9c8263c43596e7cd1ed", + "rev": "9623580f8ce09ec444b9aca107566ec5db110e62", "type": "github" }, "original": { @@ -1411,11 +1411,11 @@ "rust-overlay": "rust-overlay_6" }, "locked": { - "lastModified": 1763704521, - "narHash": "sha256-ceYEV6PnvUN8Zixao4gpPuN+VT3B0SlAXKuPNHZhqUY=", + "lastModified": 1764304415, + "narHash": "sha256-KBSPRgS7SC/U2t0GSuEojwAIEgl4HTK2I0M4558PQwo=", "owner": "Supreeeme", "repo": "xwayland-satellite", - "rev": "f379ff5722a821212eb59ada9cf8e51cb3654aad", + "rev": "5d23452874f6d0b8fabfcd69b84098ea6ef31352", "type": "github" }, "original": { diff --git a/instances/nemesis/configuration.nix b/instances/nemesis/configuration.nix index f157e5a6..72217765 100644 --- a/instances/nemesis/configuration.nix +++ b/instances/nemesis/configuration.nix @@ -9,6 +9,8 @@ nixpkgs.system = "x86_64-linux"; imports = [ ./hardware-configuration.nix + ./services/traccar.nix + ./services/navidrome.nix # ./services/attic.nix # ./services/immich.nix # ./services/jellyfin.nix @@ -63,6 +65,7 @@ networking.nat.internalInterfaces = [ "ve-+" ]; networking.nat.externalInterface = "enp1s0f1"; # TODO: changeme + services.traefik.staticConfigOptions.entryPoints.websecure.proxyProtocol.trustedIPs = ["10.0.0.1"]; services.traefik.dynamicConfigOptions.http.routers.simpleproxy-oldds9-router.rule = lib.mkForce "HostRegexp(`^.+\.hailsatan\.eu$`)"; # services.traefik.dynamicConfigOptions.http.routers.simpleproxy-oldds9-router.tls.options = "old"; @@ -94,6 +97,13 @@ sopsFile = ./secrets/dyndns.yaml; }; + xyno.services.kanidm = { + enable = true; + setupTraefik = true; + }; + xyno.services.oauth2Proxy.enable = true; + xyno.services.postgres.enable = true; + xyno.presets.cli.enable = true; xyno.presets.server.enable = true; xyno.impermanence.enable = true; diff --git a/instances/nemesis/default.nix b/instances/nemesis/default.nix index b512efd1..25b8ed78 100644 --- a/instances/nemesis/default.nix +++ b/instances/nemesis/default.nix @@ -6,6 +6,6 @@ }; # xyno.services.wireguard.pubKey = "aZvSeAhKG3B5I2My5IqQoSlntMzbCHM6OU92WEScohc="; deployment = { - targetHost = "10.0.0.2"; + targetHost = "nemesis.xyno.systems"; }; } diff --git a/instances/nemesis/hardware-configuration.nix b/instances/nemesis/hardware-configuration.nix index 4415af24..89b00b5d 100644 --- a/instances/nemesis/hardware-configuration.nix +++ b/instances/nemesis/hardware-configuration.nix @@ -87,7 +87,7 @@ zramSwap.enable = true; zramSwap.writebackDevice = "/dev/zvol/spool/nemesis/zswap"; - fileSystems."/persistent/var/lib/postgres" = { + fileSystems."/persistent/var/lib/postgresql" = { # has things of https://wiki.archlinux.org/title/ZFS#Databases set device = "spool/nemesis/postgres"; fsType = "zfs"; diff --git a/instances/nemesis/secrets/dyndns.yaml b/instances/nemesis/secrets/dyndns.yaml index 2d6cfa8a..824a4121 100644 --- a/instances/nemesis/secrets/dyndns.yaml +++ b/instances/nemesis/secrets/dyndns.yaml @@ -1,38 +1,56 @@ -dyndns: ENC[AES256_GCM,data:yDDTTBimaFcd/MG+eSbYmOHq/Ij0fAoz0zDY+C4bv7ZNn1yAA6C8DE3fXnvAvWhatLLSbRGDxHRRm7JuAHOfGGisN9cXwL+fkB7rZMVZemaL2wrOAQxyzHDEeZnZHRwsVGYhAmTsbUCVLi4CyzjRHGbCLZtZ8b+DzT0644uTi9IQovcLrWun62KMt7yjFS+Wb85XItMrIk4gJRCOhllrAXKY/z0idgv8GCNRDsDsmKL8arfcWzo2+bU/Uqhqt1Md6++jySPNPxwwcORYUudqQM63N0g47jSnmbXJ11XRRq6ZBQJaHhx25YOj2WiDH1382Kn71hnnObWS8HcXI3pwAg51mQ7Wlu+Mlt/3iZjOS1ble4IrQGItLVQUqWjyKSLvTbPfe6P/AuQ9VFGLZ7VGhVUAuyum727bx7jg1oQmqETrkL9BwaW/2FsBCVfKobgBOM6s/XMO07dOrzkO14BxOgM7zcILkz0J3IClXDOnK+Qtqe9d0E4JiSItk1hlL+PI256dGt3J2oHHAZTNOhCn1f3/t1nwkDaPM379LhVHVGhGNFtPkwp1itAQPe/zY6apHCOrcd4sti7QjVssW+ekWjMGqIC0D41aw5oRUhE0pGSwVzVTwrxqc/IiLag8UxjFpBEWO9826I5qD3LQhH+sAfsrcBp9SeRrrt5icNbqLhKDxeB2,iv:Bj9aQZ8X1j5tV+iRPwFQ9NvNaxtlaRn6u60qqzHmWmw=,tag:gDKAPd8FEomSwyiT3yy2iw==,type:str] +dyndns: ENC[AES256_GCM,data:E+yFHhfpyB3kFb59Nokjr4kIBpgRVZPLZzpHjwp9Ixd9b4rr8Fky0yz4b+/5MlXsSKsR4zfhUi/4yudeYVrSwMWeLibKHeAtoWYTlnEjhBn30wvlEA08M3zmvfMRvGB0Ur2bmax5OzCQbh2v0NKB+4mrIN9SVhBIR89Y5DLm7sd7KIJJxTruo2d+ODphAzFFNtuuyg4s3iq2+Y5H/zwrpgT4Td39h1ys+hfk4ght/OAseTZfda4qCdHhT0S4+aoCkhiyPUxr80KtuHhmt6UvpsazSDJmZyKyVJ3PpYcfS1VfBqAIdNc2enxxquNBRwppd3V8pXjIZKzPRTySFnLpgPIJBhnFkozJm44jaOttoSFrbrF1+7/iL/ssNWpHQvtr0Ke5jS1EPb6k0MZZnJxhGKUiZWzDL/Xb7FJex8pE5gTTK+24VD13mvIW2qCrtXybTydnid/76SzYpdz407mHgBsQUorA,iv:WGbR31NhtayYfdn89diNlOwWkUOulYmBVs9qqZSNieo=,tag:yvqJ6Ok2i0GC5ZSFYWySsg==,type:str] sops: - lastmodified: "2025-11-24T15:13:32Z" - mac: ENC[AES256_GCM,data:f+3n2Xc7jU+qW+vHk98BTgwLwVHTXzWHMjLwNr3Y3Pp8VIQf8d8iF8QwhDFPWN1uqsxsb/XD48CXbd5AwxuVcgcSM7FIHkjfIn0pHRk0jTAkXTxjWIwg9pqnGAZ5B/fA8XAvcyPpE299gMYzU2soAL11DZtrEVVXJWN2sD5IlL8=,iv:Uykx2R6xO/KW1XP0Nm8GyaoepmdU96AfGQQhLz6BlNQ=,tag:bqe0gZF6WhQg55NhWyNC5A==,type:str] + lastmodified: "2025-12-01T18:59:41Z" + mac: ENC[AES256_GCM,data:DGjRJWTAl2q58KDAcCxk30gvsin8C7/yBvw5qt+gGcHgJr4ggdVU1afW2Hn+qkexuSK0vLZP6tPJo6reiwyEAZNREPXnU21DUm83lMybu/zRdLFenA03ffPgJ4V+a6m9Ya/CmJzz/vaUxxtyeqCgynUI24otI/+ta0Hh1LQCbC4=,iv:t7NbX7mMMh2r2b0FLrmssxlFJSd9TTGAb7kjoYeKnzc=,tag:MKvgoxYoOj5TT/EzD8hKzw==,type:str] pgp: - created_at: "2025-11-24T13:05:23Z" enc: |- -----BEGIN PGP MESSAGE----- - hL4DAAAAAAAAAAASBAMEDMeIdplYrk7VppUFRCTcB7Pv+xnVKSLhhSoN6kqbHXqA - YYv8o4AbT9bf1+L9wywA+Eg55tj8aocse/tdRQWTymy4+8grb/sm0oZQT22T51JP - mWxhWIX+sliCqHnS/W2tuVLyDzAiJ/Tg19kOc576OH1z6f2HMIM7yvADoKupfqIw - rbwo0G8CdZCW1hzutCnC2mD4Op2BxK1b26oZdNA06y+BMHHefyDTl/wXwiNQKWiq - 0l4BoaYNx2ma5ZTSBk7YNeIXikwM3XtZnUZU+V17EgClNbxhmTRc0h7fmeprjYcW - zETvhSIm+JFTR0mdQsrDvTs7kt+PnaYptoM0CHaYCHTq48pyYKL3Gm0NtCAHuFc3 - =UlnU + hL4DAAAAAAAAAAASBAMEIEXAYYvAspPhhSKpflgdFds2UrMJYMBl1TyKwMsTxQd/ + Chj1aQqRRdrQ0tF3Mkd13EEDJwU3kgJWrrh8uB5ukU+yYuig5kS5Gl51eW5H+FlD + YFYJFsvJBIqbpNzpI7zitD4OvBZ6UCmwul+X9ibYVZRJjrZ6e4zSPmOk9D4Srecw + Pz9nn6NnUA5PXd627Kt4JFBQ1OiLon/ZMBTKhk/vHuUKdYYJNQleJFnpWCU3lNN2 + 0l4BTf26neMgV1qi34Nb86n3Jk8zqC/pMOgtoN2IrY45kR5lUDqGHTemKHw3JdmO + 9oiuqPnlGJktOxdAk+8jHxDrVwRsth+2f+U0cLkMNGTBcY8g1OPWk65ObwIpZ1rZ + =NWw0 -----END PGP MESSAGE----- fp: 0D98D5964AC8BB1CA034CE4EC456133700066642 - created_at: "2025-11-24T15:13:16Z" enc: |- -----BEGIN PGP MESSAGE----- - hQGMAwAAAAAAAAAAAQv/SQPw9p1r4Sg9zYTyX1n3n6xJzhr+hEGLwwGhcmJh8mup - D9n+aaZU4WK2Huha8Jd1bNqNo3J9UpqQMIaexfd64HoVdDHCxBzfThiMvgwpQ30a - kTQqC0DGkLaFM5W8IVOremwjhLzrTBesu8ny8CasQYJ0tZ+QFU51OvxD4q0r9OLT - BcCGqDsKZ3HDr7OIi4FsqdZRfxinJ+zteK93uxA+fB8Hkxok2g7Z2A5Nv2cOo54X - NJxwsCN4l2Hs8MaVMTC+KGASn87m+Q1xwcVD7ujPmGGjMYb84j2sBn0DP3mNQh7y - 89jz6HPbq9goF++Sp172bwswmJwZLVCWkgvzQo+szfCqGnq6LTlG9JaVswkkSz7N - z9kvF6L2dg7N2XlkLLP9UnxMkNxUrA615LvSn/z9AA7Dp6ay7TCsOeSWrz5m7+PF - VVfTp4PN3y7hsZXvWWk1eqsOaThI5GY7N7W6aQlNmvhibjw7FJEF4Iv4FBJsOD23 - vYv+38yn377hnimtzoVt0lgBFtO8QhedShEvVjROUNjjEE2WB2jpP5/wZVIsrefs - tl87MAyLedE/Zs5MByubb0buC2jifQnkxsA1QSvbZB2/NTi/SLSPeCMdFrVkYdcG - hP8cp1HkI81F - =iDKg + hQGMAwAAAAAAAAAAAQv9EPOBFyrp+Zysk3SaNiYaQl928f0pe84uSO9Fm4VPqKtC + xjov8740j38gvQIXpI2zADMlemLGzftbJPfBarSFVlukYoQkjs0SXQD0ukXsiCw2 + eLpEyOx57BFQMRwiNeh6gSkS6WeHAJNiQj1rY11MvyAsDIup8su5XPEw5DWjr3R0 + JdY029HuF6wqpmtgZnaFn6vmPYWYdtnYsxjqOJSPx+rXbWagiDgYy3DAOc7ltyIs + bZoAsjVwQW/tl3hjWhKFeOSfsd9vdUX1TacKqqNZy4mLeEHZOYANeeLkOb+T1T4r + Cexau6H3xZqc87T4S1ZBpEyWawIJHmIalFDoj466mUMOTJth7LxW7vfAdMpwbl1K + HHbTK7vyYxLeo/e5rw646eVmbyPeFf3gF/IXRWB8qoL4g/atGbbQ+WJ86mLgMDg9 + 9AqgVhSbVLoaWooqjh40tJ53raNm5HsO8ozfrJ9hx7Dq3QmCivpA14Q7UD6Yu1nT + RxY6Ng7ykHGGIx3LacV70lgBoinGEzvbxe2Se7B+FOlEL+zFywRt8yFwqc0SXY6o + qLriyhU2p05gvV45oR3pm9336VtwKu1lsN+Z9guKDZmKSgfIxZ22NulsA7E2zV8Z + MacEdQskbB7J + =zk6P -----END PGP MESSAGE----- fp: b730b2bf54eb792a14bfd3e68c14c08894376c5f + - created_at: "2025-11-29T08:16:39Z" + enc: |- + -----BEGIN PGP MESSAGE----- + + hQGMAwAAAAAAAAAAAQv+OaIQywQwV7AS4gi4GjVHFVyhk7Wd3/5fLlMt9UeB56gt + l5+4VVkIzslwiuu9NF5KfvuXr4OQG3cOnb7KRgU5UEUEQtaDrMLFO/TwbBzVTC40 + YNHOya0Ow3MRcEegnM3ox5vL4nHLQwevL0jY8TUxa1xfcTvSj8qOQ9zzzkxzR3L6 + XvD6+dL9pHh1gtU6vlmPIm1PLsWalp0LJAOij3FQdzaxWpDRc0U+Wwr4lt5LiYS2 + jOFc5OZFeIOqMfzul5GCy933eu+V+Ch8PXa5/6PtWxFHx5bvF2pclubguRLziux1 + koeCxBpK3coHT1NX5AEmbtCEieFY9bMy6JTiih4I8eM2nkPjWyqByGh2mFuzjf4V + OBGyHUZHK+KpDkRkwSS/6GqX3n89KMsf6aZwM35CkBvdG3PamyO8eVmE36OTGe4g + 1oj12rIjIj4dKtQ68vRgexvlH1Qq3GBHfsJRF5lRi4dKtPPLjU2P1fJWFcjszIEe + q2QEcpDbCPYelI7mhwzT0lgBND6Yxso3tpW/Il/uRKao/9H+DgIYaIIRBml/cqaq + VqDnFIYodxuW73R8n5GlfctY+gya9ZiGlK5uJwlghRE8gCVUjFrDnFS+uiVC6QI6 + SepzVJKOMUB8 + =7yY4 + -----END PGP MESSAGE----- + fp: fada7e7be28e186e463ad745a38d17f36849d8a7 unencrypted_suffix: _unencrypted version: 3.11.0 diff --git a/instances/nemesis/secrets/kanidm.yaml b/instances/nemesis/secrets/kanidm.yaml new file mode 100644 index 00000000..ed564412 --- /dev/null +++ b/instances/nemesis/secrets/kanidm.yaml @@ -0,0 +1,80 @@ +oauth2Proxy: + cookieSecret: ENC[AES256_GCM,data:CA6VBdPT3tuit4eWfBi1ycau4kErwAMfLwJ0maYr+/8Th8q51ZFHaWOcCA8=,iv:HSWCUEgVTkB9tKfzZWXUzH/sCoVZztzwbr1ZUwZBLBs=,tag:qOdClvQl2Cgn3lHXXA/o1Q==,type:str] +kanidm: + adminPassword: ENC[AES256_GCM,data:JQHTQHwXKMgbc0SRdkhMjKjZBznuIbHdcR4TXsmikKWX+5T4VOSqo4CwlXj0XQSV,iv:ruCMDfPlwcAUXdRypB0lNSH1UuV9ryS4vLSleVHnWwc=,tag:V4phitbR0tk6jUrdyFlZWQ==,type:str] + idmAdminPassword: ENC[AES256_GCM,data:D128I5u5pTP6xgSiP+EIZWJmbkXDfHHLh7Bw8wAiMxYPpjGG3EDsdG+8CwOUP8jZ,iv:5h/UZ7BHviKYIY0zpTo/seFHvMcGucDLeey9bc+GDlo=,tag:DSelYBlzIN94K7tOI3QtDA==,type:str] +sops: + lastmodified: "2025-12-03T21:25:56Z" + mac: ENC[AES256_GCM,data:R3cxPrzR5tT0CMNvrtBWDCE8RTvPj9tPslkGhubfOREQBR+qIw7e84uSrswo2+KJkc+AL7CPzVSgsdcSjalNxIqLlL2Gg1y/BKgE6UDB5GDT49+cfCL68hFYkZ+4Cl6bKdLpOuuhsR92NwQk171bx+KCgjE2KUBRFDjOllo+V7M=,iv:LdeRN1jnLNKL3pXVVEQ3BzI3csJSRil5gkQOnxyLi+U=,tag:Ti9Ryuc4kyeuKDwRelQI2A==,type:str] + pgp: + - created_at: "2025-11-29T10:45:57Z" + enc: |- + -----BEGIN PGP MESSAGE----- + + hL4DAAAAAAAAAAASBAMEYHWKHKXoJ9J5HDHfY9FhItdcvUdWz57Wl79xKzH94s42 + B7k57At1H6mW3BOrbnDNgNyBs6dHIG1jTA+4yITgIAcn/bl9JRbMOjmDZZzzevJi + 4yQdnm8C6mxQhbUNDpWJ4He6+m7vMZx8uq0S6dq0a5aAE/7ph7Z4gvIbBjZIA6gw + z/GwpL9SyZjSyTW8Z9XPLebonCp1lhD0tsVHk9GKoL46UWlYzq87XSyJOo4AHGAO + 0l4BWq3e7iDmzdxwtCoIC7PKHPmigUielz1qDdCGAIMQsAhaJqOa24gCUW7cklgk + A18EVlSyfeqGaqr//cuGqm8TnrG9cfLtMgnjUv3UQfBWVOh5P8ccKTbl4SUY2BfQ + =JRop + -----END PGP MESSAGE----- + fp: 0D98D5964AC8BB1CA034CE4EC456133700066642 + - created_at: "2025-11-29T10:45:57Z" + enc: |- + -----BEGIN PGP MESSAGE----- + + hQGMAwAAAAAAAAAAAQv9GuE7hANGXNsAGi3e1oB4c2y9jpvi3RkEifcxBg19yf9n + L9t3Av1Ym2H/Bc5Sw5++qrF7Jy3Aujt1T8ytayxzVsuLesgkuDzjtot0JgUsRSWK + r2dXDoo9gaftX434fGViamHTX2wHBo9VEwqe/c2NjwLeIx/O6u4FwJmUAgzGy1pp + I5TSpYMky4coLbFpDX7AuUuPymI6lGNad6JoeOoTP2gb5Kt2Ycr7ZqzOp2kaPxua + yw5HuoiXLkzxywuWh+IIbqanyN8qxbjvEOS4ZJHegECZhDS27kocwiXhUlcT1NC1 + 1pFW7EJaVMzikSBy06LiPWsYjNmQIR9o8uoeP//XMcq34N0036IxVaOWs8Qnls97 + TJM6lCov4c0XcNrfop15hWNK2gxSzNwxWkmG5PUHwfRi9JGvL6Ng/EHbJS+6F3Nx + z7H2jAIdzc9StMd411SJrZSpDk1wpnecroUWYdO5OIKJu70J5FvNSaKripEECy1x + wBrqY5RLmi/qYsfyCupg0lgBPGUyJbV2XLt1Qcf4eStzy5ZYi8gnn02T0EWvCspf + 0tKMs7hbLm6FwK7vG7Cmc1LQCm73oW09cZCrLurd9JtiRftfww9pBEc85MfPUBIt + PYAWvU26TshG + =vU9m + -----END PGP MESSAGE----- + fp: fada7e7be28e186e463ad745a38d17f36849d8a7 + - created_at: "2025-11-29T10:45:57Z" + enc: |- + -----BEGIN PGP MESSAGE----- + + hQGMAwAAAAAAAAAAAQwAnX5FoTN8ft2sFXRotgVGt2he9RJMiq0orduWeTXZPLK8 + mvyYOcrQ71fOIm9sIg6hJfabeR2GjBsm09XIXJ71zLl0lWgb8SvcYmbgaJJ+svfY + pPrW+ZninOUZgwkJ8L1gM3ZWOJlx+92NKtdxifIY98SMKacT+aVe7vNTrQVwmRn9 + cZZQd8B75zN7IV99bJCG2BYvSt+p8gZ1vm3GxhowctbSgCl6knHDHJlpXE7SyLHv + ReHjfCFetJQd/XqLL4vZifrlpGKyGPP5pYnEQT5bbsJr9exBeZQ7PsAB0OG+KpOB + r4IvB78XGJlbHTnqlP6GKAKek6NSldlEq5tCsKo5wl3Jg2/SgxSbOhbb5YmOqwcq + S+JJhbfjRGSJMMXLOP9QtLQU5qktwip5g+ZxgKDrcgQvUPUsbV3PEW9l/0WoCf1Q + 7e+mBJ+TRJVOjS9hC1mr/C6kkJJurdpH3PvUSncSk4s0+bkw66nLwmc3QLpI6DV7 + BLJVkOUQzUWaB6k6NVkE0lgBbVYeyaOS3JcoFHrYex6bDHjliD/SH+xjc8wyJR8s + wz5CwfFPqudE9ZYvilJ3lN2AtJrRBaxtR5dgrG2fUx9OB6FSOoRVOpmnFC3YUt6a + FNZYBn5X10t/ + =Mj7y + -----END PGP MESSAGE----- + fp: b730b2bf54eb792a14bfd3e68c14c08894376c5f + - created_at: "2025-11-29T10:45:57Z" + enc: |- + -----BEGIN PGP MESSAGE----- + + hQIMAwAAAAAAAAAAAQ//a9QFgxhKeW4xcFeERaCiO5nb07wS8BxENBw57RDTp52F + 0kzEdp+HnkVujYMC5cjKlz3x6ZT+CFcVT8HZTwTjlrtCdO/R1OGeO0NcC/tRQNvV + LUgaV9mCzYAbfJG1skdz9EMZZ3W9qPBgKXbX7vGhPirwXyAUUEfQsWon8eOymrd6 + FoapcdLo9ymTnar14WH0kM7B0i4Kqv6+NKquc0OiL8eSufL4wubJrXZZkOdtnXCD + omwFtYmF+IgQAzaCBWLGY3sOW9nD27Y9F1vToOpKQvxE+IT3VGTZN02Xy2J4lhnJ + IE2SmaeY29g2QWIRePCrehpaRNCjlmTS7j7SRT2K9bOVcbiwljUidwkC58323EmC + UlacFRSY5Cfeav2VL188ywh4vihnUpWJoncHKvcUEEeLykbQcdpy2Zwl6u6UB+2g + oDmhk1pFV4ejJlRe1BpLHiBcnA5/OFsWxMGQp57mZ2vyGWYQgtsQZ71JVENq7nSt + TKi6OPhRTZX2Wk8lcZMjm+5XeNrGMSUEYwCPRza+5C0f3LzSfjmmGDLxwaQmCaYI + ZjKSuj14N9VQNJJ8Yxn9uni1acKs+q1XM1b3gJUT2gpFbOjh1BjtAGrat1f4mKk9 + 0PwxBA7cZkdmgJ0t/oNja8ElpOMLMTbI7yYOLY3hD64CtFKWWwpxbD7bK65neRTS + WAE9cEyjhq3TOKNdKaIP5N6a6Sq+98N9xE7/3/lVYncFZ1zV4+9l8Gyjr4rsSSV+ + 68gg9t6FUPIHyNHdrvmqLLQP+paA9RMb7yOMkULB8zBKJrZMXnu+wsI= + =Du56 + -----END PGP MESSAGE----- + fp: 4019fd893bba15618c2f93a38ef418ce360bc418 + unencrypted_suffix: _unencrypted + version: 3.11.0 diff --git a/instances/nemesis/services/navidrome.nix b/instances/nemesis/services/navidrome.nix new file mode 100644 index 00000000..769f1db8 --- /dev/null +++ b/instances/nemesis/services/navidrome.nix @@ -0,0 +1,48 @@ +{ + config, + ... +}: +let + host = "music.xyno.systems"; + internalIp = "127.0.0.5"; +in +{ + services.kanidm.provision = { + groups = { + navidrome_users.members = [ "application_admins" ]; + }; + }; + xyno.services.oauth2Proxy.hosts."${host}" = { + allowedGroups = [ "navidrome_users" ]; + }; + xyno.services.traefik.simpleProxy = { + navidrome = { + inherit host; + inherit (config.xyno.services.oauth2Proxy.hosts.${host}) middlewares; + internal = "http://${internalIp}:4533"; + }; + navidrome-subsonic = { + inherit host; + rule = "Host(`${host}`) && PathPrefix(`/rest/`) && !Query(`c`, `NavidromeUI`)"; + internal = "http://${internalIp}:4533"; + }; + }; + + services.navidrome = { + enable = true; + settings = { + Address = internalIp; + MusicFolder = "/data/media/beets/music"; + ReverseProxyWhitelist = "127.0.0.1/32"; + BaseUrl = "https://${host}"; + Prometheus = { + Enabled = false; # TODO + }; + ReverseProxyUserHeader = "X-Auth-Request-Preferred-Username"; + Scanner = { + Schedule = "45 0 * * *"; # daily at 0:45 + }; + }; + }; + xyno.impermanence.directories = [ "/var/lib/navidrome" ]; +} diff --git a/instances/nemesis/services/traccar.nix b/instances/nemesis/services/traccar.nix new file mode 100644 index 00000000..43cc14dc --- /dev/null +++ b/instances/nemesis/services/traccar.nix @@ -0,0 +1,93 @@ +{ + pkgs, + lib, + config, + ... +}: +{ + xyno.services.traefik.simpleProxy.traccar = { + host = "track.66642.bot"; + internal = "http://127.0.0.4:8082"; + }; + services.kanidm.provision = { + groups = { + traccar_users.members = [ "traccar_admins" ]; + traccar_admins.members = [ "application_admins" ]; + }; + systems.oauth2.traccar = { + displayName = "Traccar"; + originUrl = "https://track.66642.bot/api/session/openid/callback"; + originLanding = "https://track.66642.bot/login"; + imageFile = "${pkgs.traccar}/web/logo.svg"; + # public = true; + scopeMaps."traccar_users" = [ + "openid" + "profile" + "email" + "groups" + ]; + allowInsecureClientDisablePkce = true; + }; + }; + xyno.services.kanidm.templates."traccar" = { + text = p: '' + OPENID_CLIENT_ID=${p.clientId} + OPENID_CLIENT_SECRET=${p.basicSecret} + DATABASE_URL='jdbc:postgresql://localhost/traccar?socketFactory=org.newsclub.net.unix.AFUNIXSocketFactory$FactoryArg&socketFactoryArg=/run/postgresql/.s.PGSQL.5432' + ''; + wantedBy = [ "traccar.service" ]; + }; + systemd.services.traccar.serviceConfig.ExecStart = + lib.mkForce "${pkgs.openjdk}/bin/java -cp './tracker-server.jar:./lib/*:${pkgs.junixsocket-common}/share/java/junixsocket-common-${pkgs.junixsocket-common.version}.jar:${pkgs.junixsocket-native-common}/share/java/junixsocket-native-common-${pkgs.junixsocket-common.version}.jar' org.traccar.Main /var/lib/traccar/config.xml"; # forgive it for what it has done + services.traccar = { + enable = true; + environmentFile = config.xyno.services.kanidm.templates.traccar.path; + settings = { + database.driver = "org.postgresql.Driver"; + database.url = "$DATABASE_URL"; + database.user = "traccar"; + mail.debug = "true"; # log mail content instead of sending email + openid.adminGroup = "traccar_admins@idm.xyno.systems"; + openid.allowGroup = "traccar_users@idm.xyno.systems"; + openid.clientId = "$OPENID_CLIENT_ID"; + openid.clientSecret = "$OPENID_CLIENT_SECRET"; + openid.force = "true"; + openid.issuerUrl = "https://idm.xyno.systems/oauth2/openid/traccar"; + web.address = "127.0.0.4"; + web.url = "https://track.66642.bot"; + }; + }; + systemd.services.postgresql-install-timescale-in-traccar = { + after = [ + "postgresql.service" + "postgresql-setup.service" + ]; + requires = [ + "postgresql.service" + "postgresql-setup.service" + ]; + requiredBy = [ "traccar.service" ]; + + serviceConfig = { + User = "postgres"; + Group = "postgres"; + Type = "oneshot"; + RemainAfterExit = true; + }; + + path = [ config.services.postgresql.finalPackage ]; + environment.PGPORT = builtins.toString config.services.postgresql.settings.port; + script = '' + psql -d traccar -tAc "CREATE EXTENSION IF NOT EXISTS timescaledb;" + ''; + + }; + services.postgresql.settings.shared_preload_libraries = [ "timescaledb" ]; + services.postgresql.ensureDatabases = [ "traccar" ]; + services.postgresql.ensureUsers = [ + { + name = "traccar"; + ensureDBOwnership = true; + } + ]; +} diff --git a/instances/theseus/configuration.nix b/instances/theseus/configuration.nix index cc235155..be23ba0d 100644 --- a/instances/theseus/configuration.nix +++ b/instances/theseus/configuration.nix @@ -88,6 +88,7 @@ in ptouch-print hledger + super-productivity ]; environment.variables."LEDGER_FILE" = "~/docs/hledger/main.journal"; diff --git a/modules/module-list.nix b/modules/module-list.nix index 8b2f5bac..0aa44847 100644 --- a/modules/module-list.nix +++ b/modules/module-list.nix @@ -25,7 +25,11 @@ ./presets/server.nix # ./services/authentik # ./services/caddy + ./services/kanidm.nix ./services/traefik.nix + ./services/postgres.nix + ./services/oauth2Proxy/default.nix + ./services/oauth2Proxy/integration.nix ./services/monitoring.nix ./services/wireguard.nix ./system/impermanence.nix diff --git a/modules/presets/cli.nix b/modules/presets/cli.nix index 53ddffaf..a1d7cafa 100644 --- a/modules/presets/cli.nix +++ b/modules/presets/cli.nix @@ -84,6 +84,8 @@ in # https://github.com/NixOS/nixpkgs/issues/361592 needed for run0 security.pam.services.systemd-run0 = { }; + # services.ssh.knownHosts = lib.mapAttrs' (n: v: lib.nameValuePair v.deployment.targetHost { publicKey = v.}) + programs.yazi = { enable = true; initLua = pkgs.writeText "yazi-init.lua" '' diff --git a/modules/services/kanidm.nix b/modules/services/kanidm.nix index 953f1f66..274c4413 100644 --- a/modules/services/kanidm.nix +++ b/modules/services/kanidm.nix @@ -5,13 +5,59 @@ ... }: let - inherit (lib) mkEnableOption mkIf mkOption; - inherit (lib.types) str nullOr pathWith; + inherit (lib) + mkEnableOption + mkIf + mkOption + converge + mkMerge + filterAttrsRecursive + literalExpression + mapAttrsToList + concatStringsSep + attrNames + ; + inherit (lib.types) + str + nullOr + pathWith + submodule + listOf + functionTo + attrsOf + lines + ; absPath = pathWith { inStore = false; absolute = true; }; cfg = config.xyno.services.kanidm; + tlsDir = "/run/generated/kanidm-tls"; + package = pkgs.kanidmWithSecretProvisioning_1_8.overrideAttrs (old: { + doCheck = false; + patches = old.patches ++ [ + (pkgs.writeText "patch-kanidm-name" '' + diff --git a/server/lib/src/value.rs b/server/lib/src/value.rs + index 86b5a74c1..c83b2f93d 100644 + --- a/server/lib/src/value.rs + +++ b/server/lib/src/value.rs + @@ -64,7 +64,7 @@ lazy_static! { + /// Only lowercase+numbers, with limited chars. + pub static ref INAME_RE: Regex = { + #[allow(clippy::expect_used)] + - Regex::new("^[a-z][a-z0-9-_\\.]{0,63}$").expect("Invalid Iname regex found") + + Regex::new("^[a-z0-9-_\\.]{0,64}$").expect("Invalid Iname regex found") + }; + + /// Only alpha-numeric with limited special chars and space + '') + ]; + }); + templatePlaceholders = { + clientId = ''\($get.attrs.name[0])''; + basicSecret = ''\($secret.secret)''; + env = v: ''\(env.${v})''; + }; in { options.xyno.services.kanidm.enable = mkEnableOption "enables kanidm"; @@ -19,8 +65,75 @@ in default = "idm.xyno.systems"; type = str; }; - options.xyno.services.kanidm.isReplica = mkEnableOption "replica"; + options.xyno.services.kanidm.isReplica = mkEnableOption "replica"; # TODO options.xyno.services.kanidm.setupTraefik = mkEnableOption "traefik"; + options.xyno.services.kanidm.templates = mkOption { + type = attrsOf ( + submodule ( + { name, ... }: + { + options = { + path = mkOption { + type = absPath; + default = "/run/generated/kanidmTemplates/${name}"; + }; + user = mkOption { + type = str; + default = "root"; + }; + group = mkOption { + type = str; + default = "kanidm"; + }; + chmod = mkOption { + type = str; + default = "440"; + }; + wantedBy = mkOption { + type = listOf str; + default = [ ]; + example = [ "traccar.service" ]; + }; + text = mkOption { + type = functionTo lines; + description = '' + jq templated string + + current placeholders: ${concatStringsSep ", " (attrNames templatePlaceholders)} + ''; + example = literalExpression '' + p: ${"''"} + OAUTH2_PROXY_CLIENT_ID=''${p.clientId} + OAUTH2_PROXY_CLIENT_SECRET=''${p.clientSecret} + ${"''"} + ''; + }; + environmentFiles = mkOption { + type = listOf absPath; + default = [ ]; + description = '' + add environment variables to the template file. + the environment variable BANANA would be accessible as + ``` + COOKIE_SECRET=''${p.env "BANANA"} + ``` + in the template + ''; + }; + + }; + + } + ) + ); + + example = { + traccar.text = p: '' + OPENID_CLIENTID=${p.clientId} + OPENID_SECRET=${p.basicSecret} + ''; + }; + }; options.xyno.services.kanidm.tls = { keyPem = mkOption { @@ -29,60 +142,144 @@ in description = "autogenerated if unset"; }; certPem = mkOption { - default = "/run/generated/kanidm-tls/cert.pem"; + default = "${tlsDir}/cert.pem"; type = absPath; }; }; - config = mkIf cfg.enable { - services.kanidm = { - enableServer = true; - enableClient = true; - adminPasswordFile = config.sops.secrets."kanidm.password".path; - provision = { - adminPasswordFile = config.sops.secrets."kanidm.password".path; - }; - serverSettings = { - tls_key = if cfg.tls.keyPem != null then cfg.tls.keyPem else "/run/generated/key.pem"; - tls_chain = cfg.tls.certPem; - bindaddress = "127.0.0.3:8443"; - }; - }; - xyno.services.traefik.simpleProxy = mkIf cfg.setupTraefik { - host = cfg.domain; - internal = "https://127.0.0.3:8443"; - transport = "kanidm-https"; - }; - services.traefik.dynamicConfigOptions.http = mkIf cfg.setupTraefik { - serversTransports."kanidm-https" = { - serverName = cfg.domain; - certificates = [ - cfg.certPem - ]; - }; - }; + config = mkMerge [ + (mkIf cfg.enable { + services.kanidm = { + enableServer = true; + enableClient = true; + inherit package; + clientSettings.uri = "https://${cfg.domain}"; - systemd.services.generate-kanidm-tls = mkIf (cfg.tls.keyPem == null) { - serviceConfig = { - User = "root"; - Group = "kanidm"; + provision = { + enable = true; + adminPasswordFile = config.sops.secrets."kanidm/adminPassword".path; + idmAdminPasswordFile = config.sops.secrets."kanidm/idmAdminPassword".path; + instanceUrl = "https://127.0.0.3:8443"; + acceptInvalidCerts = true; + autoRemove = true; + groups.application_admins = {}; + }; + serverSettings = { + trust_x_forward_for = true; + tls_key = if cfg.tls.keyPem != null then cfg.tls.keyPem else "${tlsDir}/key.pem"; + tls_chain = cfg.tls.certPem; + bindaddress = "127.0.0.3:8443"; + origin = "https://${cfg.domain}"; + domain = cfg.domain; + }; }; - wantedBy = [ - "kanidm.service" - "traefik.service" - ]; - script = '' - mkdir -p /run/generated/kanidm-tls - ${pkgs.openssl}/bin/openssl req -x509 -newkey ed25519 -noenc -subj "/CN=generated.${cfg.domain}" -addext "subjectAltName=DNS:${cfg.domain}" -keyout /run/generated/key.pem -out /run/generated/cert.pem - ''; - }; - sops.secrets."kanidm.password" = { - sopsFile = ../../instances/${config.networking.hostName}/secrets/kanidm.yaml; - }; - # sops.templates."kanidm.env".content = '' - # DESEC_TOKEN=${config.sops.placeholder.desec_token} - # DESEC_PROPAGATION_TIMEOUT=1200 - # ''; - # sops.templates."kanidm.env".reloadUnits = [ "kanidm.service" ]; + systemd.tmpfiles.rules = [ "d /run/generated/kanidmTemplates 1755 root kanidm -" ]; + systemd.services = mkMerge ( + (mapAttrsToList (n: v: { + "generate-kanidm-template-${n}" = { + serviceConfig = { + User = "root"; + Group = "kanidm"; + Type = "oneshot"; + PrivateTmp = true; + EnvironmentFile = v.environmentFiles; + }; + requires = [ "kanidm.service" ] ++ (lib.optional cfg.setupTraefik "traefik.service"); + after = [ + "kanidm.service" + "systemd-tmpfiles-setup.service" + ] + ++ (lib.optional cfg.setupTraefik "traefik.service"); + before = v.wantedBy; + partOf = v.wantedBy; + wantedBy = if (builtins.length v.wantedBy) == 0 then [ "multi-user.target" ] else v.wantedBy; + enableStrictShellChecks = true; + path = [ + package + pkgs.jq + ]; + environment.KANIDM_TOKEN_CACHE_PATH = "/tmp/kanidm-token-cache"; + script = + let + templateText = v.text templatePlaceholders; + in + '' + KANIDM_PASSWORD=$(cat "${ + config.sops.secrets."kanidm/idmAdminPassword".path + }") kanidm login -D idm_admin + jq -r -s \ + -f "${pkgs.writeText "kanidm-template-${n}" ''"${templateText}"''}" \ + --argjson get "$(kanidm system oauth2 get --output json "${n}")" \ + --argjson secret "$(kanidm system oauth2 show-basic-secret --output json "${n}")" \ + > "${v.path}" + chown "${v.user}:${v.group}" "${v.path}" + chmod "${v.chmod}" "${v.path}" + ''; + }; + }) cfg.templates) + ++ [ + (mkIf (cfg.tls.keyPem == null) { + + generate-kanidm-tls = + let + units = [ + "kanidm.service" + ] + ++ (lib.optional cfg.setupTraefik "traefik.service"); + in + { + serviceConfig = { + User = "root"; + Group = "kanidm"; + Type = "oneshot"; + }; + wantedBy = units; + before = units; + script = '' + mkdir -p ${tlsDir} + cd ${tlsDir} + ${config.services.kanidm.package}/bin/kanidmd cert-generate -c ${ + let + toml = pkgs.formats.toml { }; + filterConfig = converge (filterAttrsRecursive (_: v: v != null)); + in + toml.generate "kanidm-tls.conf" (filterConfig (config.services.kanidm.serverSettings)) + } + chmod +g ${tlsDir}/* + ''; + }; + }) + ] + ); + sops.secrets."kanidm/adminPassword" = { + sopsFile = ../../instances/${config.networking.hostName}/secrets/kanidm.yaml; + reloadUnits = [ "kanidm.service" ]; + owner = "kanidm"; + }; + sops.secrets."kanidm/idmAdminPassword" = { + sopsFile = ../../instances/${config.networking.hostName}/secrets/kanidm.yaml; + reloadUnits = [ "kanidm.service" ]; + owner = "kanidm"; + }; + + xyno.impermanence.directories = [ "/var/lib/kanidm" ]; + }) + (mkIf (cfg.enable && cfg.setupTraefik) { + + xyno.services.traefik.simpleProxy.kanidm = { + host = cfg.domain; + internal = "https://127.0.0.3:8443"; + transport = "kanidm-https"; + }; + services.traefik.dynamicConfigOptions.http = mkIf (cfg.tls.keyPem == null) { + serversTransports."kanidm-https" = { + serverName = cfg.domain; + rootcas = [ + "${tlsDir}/ca.pem" + ]; + }; + }; + + }) + ]; - }; } diff --git a/modules/services/oauth2Proxy/default.nix b/modules/services/oauth2Proxy/default.nix new file mode 100644 index 00000000..d38615ee --- /dev/null +++ b/modules/services/oauth2Proxy/default.nix @@ -0,0 +1,85 @@ +{ + pkgs, + lib, + config, + ... +}: +let + inherit (lib) + mkEnableOption + mkIf + mkOption + getExe + ; + inherit (lib.types) + pathWith + listOf + ; + cfg = config.xyno.services.oauth2Proxy; + settingsFormat = pkgs.formats.toml { }; + configFile = settingsFormat.generate "oauth2-proxy.conf" cfg.settings; + absPath = pathWith { + inStore = false; + absolute = true; + }; +in +{ + options.xyno.services.oauth2Proxy = { + enable = mkEnableOption "oauth2-proxy"; + package = lib.mkPackageOption pkgs "oauth2-proxy" { }; + settings = mkOption { + type = settingsFormat.type; + description = "what to add to the config toml file"; + }; + environmentFiles = mkOption { + type = listOf absPath; + default = [ ]; + example = [ "/run/secrets/oauth2Proxy" ]; + }; + }; + config = mkIf cfg.enable { + systemd.services.oauth2-proxy = { + wantedBy = [ "multi-user.target" ]; + description = "OAuth2 Proxy (66642's less weird version)"; + confinement.enable = true; + after = [ "network.target" ]; + serviceConfig = { + BindReadOnlyPaths = [ + "-/etc/resolv.conf" + "-/run/systemd" + "/etc/hosts" + "${config.security.pki.caBundle}:/etc/ssl/certs/ca-certificates.crt" + ]; + ExecStart = "${getExe cfg.package} --config=${configFile}"; + EnvironmentFile = cfg.environmentFiles; + DynamicUser = true; + CapabilityBoundingSet = [ "" ]; + RestrictAddressFamilies = [ + "AF_INET" + "AF_INET6" + ]; + + PrivateDevices = true; + UMask = "0022"; + SystemCallFilter = [ "@system-service" ]; + SystemCallErrorNumber = "EPERM"; + LockPersonality = true; + PrivateTmp = true; + ProcSubset = "pid"; + ProtectClock = true; + ProtectControlGroups = true; + ProtectHome = true; + ProtectHostname = true; + ProtectKernelLogs = true; + ProtectKernelModules = true; + ProtectKernelTunables = true; + ProtectProc = "invisible"; + ProtectSystem = "strict"; + RestrictNamespaces = true; + RestrictRealtime = true; + RestrictSUIDSGID = true; + SystemCallArchitectures = "native"; + }; + }; + }; +} diff --git a/modules/services/oauth2Proxy/integration.nix b/modules/services/oauth2Proxy/integration.nix new file mode 100644 index 00000000..2d152637 --- /dev/null +++ b/modules/services/oauth2Proxy/integration.nix @@ -0,0 +1,208 @@ +{ + lib, + config, + ... +}: +let + inherit (lib) + mkIf + mkOption + mkMerge + attrNames + mapAttrsToList + ; + inherit (lib.types) + str + nullOr + submodule + listOf + attrsOf + ; + cfg = config.xyno.services.oauth2Proxy; + kanidmCfg = config.xyno.services.kanidm; + oauth2ProxyInternalHostPort = "127.0.0.4:4180"; + oauth2ProxyInternalHostPortMetrics = "127.0.0.4:4181"; +in +{ + options.xyno.services.oauth2Proxy = { + domain = mkOption { + default = "oauth.xyno.systems"; + type = str; + }; + hosts = mkOption { + type = attrsOf ( + submodule ( + { name, ... }: + { + options.allowedGroups = mkOption { + type = nullOr (listOf str); + default = null; + }; + options.allowed_email_domains = mkOption { + type = nullOr (listOf str); + default = null; + }; + options.allowed_emails = mkOption { + type = nullOr (listOf str); + default = null; + }; + options.middlewares = mkOption { + type = listOf str; + description = "add to your service"; + default = [ + "oauth-errors" + "oauth-host-${name}" + ]; + }; + } + ) + ); + example = { + "navidrome.xyno.systems" = { + allowedGroups = [ "navidrome_access@idm.xyno.systems" ]; + }; + }; + default = { }; + }; + }; + config = mkIf (cfg.enable && config.xyno.services.kanidm.enable) { + services.kanidm.provision = { + groups = { + proxy_users.members = [ "application_admins" ]; + }; + systems.oauth2.oauth2_proxy = { + displayName = "oauth2 proxy"; + originUrl = [ + "https://${cfg.domain}/oauth2/callback" + ] + ++ (mapAttrsToList (n: v: "https://${n}/oauth2/callback") cfg.hosts); + originLanding = "https://${cfg.domain}/oauth2/sign_in"; + preferShortUsername = true; + claimMaps = { + "proxy_group" = { + joinType = "array"; + valuesByGroup = { + "proxy_users" = [ + "proxy_users" + ]; + }; + }; + }; + scopeMaps."proxy_users" = [ + "email" + "openid" + ]; + }; + }; + xyno.services.kanidm.templates.oauth2_proxy = { + wantedBy = [ + "oauth2-proxy.service" + ]; + text = p: '' + OAUTH2_PROXY_CLIENT_ID=${p.clientId} + OAUTH2_PROXY_CLIENT_SECRET=${p.basicSecret} + OAUTH2_PROXY_COOKIE_SECRET=${p.env "COOKIE_SECRET"} + OAUTH2_PROXY_OIDC_ISSUER_URL=https://${kanidmCfg.domain}/oauth2/openid/${p.clientId} + ''; + environmentFiles = [ config.sops.templates.oauth2ProxyEnv.path ]; + }; + sops.secrets."oauth2Proxy/cookieSecret" = { + sopsFile = ../../../instances/${config.networking.hostName}/secrets/kanidm.yaml; + }; + sops.templates."oauth2ProxyEnv" = { + restartUnits = [ "generate-kanidm-template-oauth2_proxy.service" ]; + content = '' + COOKIE_SECRET=${config.sops.placeholder."oauth2Proxy/cookieSecret"} + ''; + }; + + xyno.services.monitoring.exporters.oauth2Proxy = "http://${oauth2ProxyInternalHostPortMetrics}"; + + systemd.services.oauth2Proxy.after = [ "traefik.service" ]; + xyno.services.oauth2Proxy = { + environmentFiles = [ kanidmCfg.templates.oauth2_proxy.path ]; + settings = mkMerge [ + { + provider = "oidc"; + scope = "openid email"; + oidc_groups_claim = "proxy_group"; + allowed_groups = [ "proxy_users" ]; + + http_address = "${oauth2ProxyInternalHostPort}"; + https_address = ""; + whitelist_domains = attrNames cfg.hosts; + email_domains = "*"; + skip_provider_button = true; + code_challenge_method = "S256"; + set_xauthrequest = true; + } + (mkIf config.xyno.services.monitoring.enable { + metrics_address = "http://${oauth2ProxyInternalHostPortMetrics}"; + + }) + ]; + }; + + xyno.services.traefik.simpleProxy = mkMerge ( + [ + { + oauth = { + rule = "Host(`${cfg.domain}`) && PathPrefix(`/oauth2`)"; + internal = "http://${oauth2ProxyInternalHostPort}"; + middlewares = [ "auth-headers" ]; + host = cfg.domain; + }; + } + ] + ++ (mapAttrsToList (n: v: { + "oauth-host-${n}" = { + rule = "Host(`${n}`) && PathPrefix(`/oauth2`)"; + internal = "http://${oauth2ProxyInternalHostPort}"; + middlewares = [ "auth-headers" ]; + host = n; + }; + }) cfg.hosts) + ); + services.traefik.dynamicConfigOptions.http.middlewares = mkMerge ( + (mapAttrsToList (n: v: { + "oauth-host-${n}" = + let + maybeQueryArg = + name: value: + if name == "middlewares" || value == null then + null + else + "${name}=${lib.concatStringsSep "," (builtins.map lib.escapeURL value)}"; + allArgs = lib.mapAttrsToList maybeQueryArg v; + cleanArgs = builtins.filter (x: x != null) allArgs; + cleanArgsStr = lib.concatStringsSep "&" cleanArgs; + in + { + forwardAuth = { + address = "https://${cfg.domain}/oauth2/auth?${cleanArgsStr}"; + authResponseHeaders = [ + "X-Auth-Request-User" + "X-Auth-Request-Groups" + "X-Auth-Request-Email" + "X-Auth-Request-Preferred-Username" + ]; + trustForwardHeader = true; + }; + }; + }) cfg.hosts) + ++ [ + { + auth-headers.headers = { + frameDeny = true; + contentTypeNosniff = true; + }; + oauth-errors.errors = { + status = [ "401-403" ]; + service = config.xyno.services.traefik.simpleProxy.oauth.serviceName; + query = "/oauth2/sign_in?rd={url}"; + }; + } + ] + ); + }; +} diff --git a/modules/services/postgres.nix b/modules/services/postgres.nix index a06ded97..bcdb16e4 100644 --- a/modules/services/postgres.nix +++ b/modules/services/postgres.nix @@ -12,7 +12,9 @@ in config = lib.mkIf cfg.enable { services.postgresql = { enable = true; - package = pkgs.postgresql_17_jit; + package = pkgs.postgresql_18; + enableJIT = true; + extensions = ps: with ps; [ timescaledb-apache ]; identMap = '' # ArbitraryMapName systemUser DBUser superuser_map root postgres @@ -26,7 +28,7 @@ in }; xyno.services.monitoring.exporters.postgres = config.services.prometheus.exporters.postgres.port; - xyno.impermanence.extraDirectories = [ "/var/lib/postgresql" ]; + xyno.impermanence.directories = [ "/var/lib/postgresql" ]; services.borgmatic.settings.postgresql_databases = [ { diff --git a/modules/services/traefik.nix b/modules/services/traefik.nix index 45a9626a..cdd65064 100644 --- a/modules/services/traefik.nix +++ b/modules/services/traefik.nix @@ -5,61 +5,82 @@ ... }: let + inherit (lib) + mapAttrsToList + mkMerge + splitString + concatStringsSep + mkIf + mkOption + mkEnableOption + ; + inherit (lib.types) + str + bool + nullOr + anything + attrsOf + submodule + listOf + ; cfg = config.xyno.services.traefik; - simpleProxyOpts = lib.mapAttrsToList ( + simpleProxyOpts = mapAttrsToList ( n: v: let - router = "simpleproxy-${n}-router"; - service = "simpleproxy-${n}-service"; - spl = lib.splitString "." v.host; - certDomain = - if (builtins.length spl) > 2 then lib.concatStringsSep "." (builtins.tail spl) else spl; + router = v.routerName; + service = v.serviceName; + spl = splitString "." v.host; + certDomain = if (builtins.length spl) > 2 then concatStringsSep "." (builtins.tail spl) else spl; in - { - routers."${router}-robotstxt" = { - service = "robotstxt"; - rule = "Host(`${v.host}`) && Path(`/robots.txt`)"; - tls.certResolver = "letsencrypt"; - tls.domains = [ - { - main = certDomain; - sans = [ "*.${certDomain}" ]; - } - ]; - - }; - routers.${router} = { - inherit service; - rule = "Host(`${v.host}`)"; - tls.certResolver = "letsencrypt"; - tls.domains = [ - { - main = certDomain; - sans = [ "*.${certDomain}" ]; - } - ]; - }; - services.${service} = { - loadBalancer.servers = [ - { url = v.internal; } - ]; - loadBalancer.serverTransport = lib.mkIf (v.transport != null) v.transport; - }; - services.robotstxt = { - loadBalancer.servers = [ - { url = "http://127.0.0.2:8080"; } - ]; - }; - } + mkMerge [ + (mkIf v.robotProtection { + routers."${router}-robotstxt" = { + service = "robotstxt"; + rule = "Host(`${v.host}`) && Path(`/robots.txt`)"; + tls.certResolver = "letsencrypt"; + tls.domains = [ + { + main = certDomain; + sans = [ "*.${certDomain}" ]; + } + ]; + }; + services.robotstxt = { + loadBalancer.servers = [ + { url = "http://127.0.0.2:8080"; } + ]; + }; + }) + { + routers.${router} = { + inherit service; + inherit (v) middlewares; + rule = if v.rule != null then v.rule else "Host(`${v.host}`)"; + tls.certResolver = "letsencrypt"; + tls.domains = [ + { + main = certDomain; + sans = [ "*.${certDomain}" ]; + } + ]; + }; + services.${service} = { + loadBalancer.servers = [ + { url = v.internal; } + ]; + loadBalancer.serversTransport = mkIf (v.transport != null) v.transport; + }; + } + ] ) cfg.simpleProxy; in { - options.xyno.services.traefik.enable = lib.mkEnableOption "enables traefik"; - options.xyno.services.traefik.noBots = lib.mkOption { - type = lib.types.bool; + options.xyno.services.traefik.enable = mkEnableOption "enables traefik"; + options.xyno.services.traefik.noBots = mkOption { + type = bool; default = true; }; - options.xyno.services.traefik.simpleProxy = lib.mkOption { + options.xyno.services.traefik.simpleProxy = mkOption { example = { "example" = { host = "example.org"; @@ -68,38 +89,63 @@ in }; }; default = { }; - type = lib.types.attrsOf ( - lib.types.submodule { - options = { - middlewares = lib.mkOption { - type = lib.types.nullOr (lib.types.listOf lib.types.str); - }; - internal = lib.mkOption { - type = lib.types.str; - }; - host = lib.mkOption { - type = lib.types.str; - }; - transport = lib.mkOption { - type = lib.types.nullOr lib.types.anything; - default = null; - }; + type = attrsOf ( + submodule ( + { config, name,... }: + { + options = { + middlewares = mkOption { + type = listOf str; + default = []; - }; - } + }; + internal = mkOption { + type = str; + description = "where to proxy to"; + }; + host = mkOption { + type = str; + description = "used for the route and tls"; + }; + routerName = mkOption { + type = str; + default = "simpleproxy-${name}-router"; + }; + serviceName = mkOption { + type = str; + default = "simpleproxy-${name}-service"; + }; + robotProtection = mkOption { + type = bool; + default = true; + description = "robots.txt and (soon) iocane"; + }; + rule = mkOption { + type = str; + default = "Host(`${config.host}`)"; + description = "overrides the Host(`\${host}`) rule with something custom if set"; + }; + transport = mkOption { + type = nullOr anything; + default = null; + }; + + }; + } + ) ); }; - config = lib.mkIf cfg.enable { + config = mkIf cfg.enable { services.nginx = { - enable = lib.mkIf cfg.noBots true; - defaultListen = lib.mkIf cfg.noBots [ + enable = mkIf cfg.noBots true; + defaultListen = mkIf cfg.noBots [ { addr = "127.0.0.2"; port = 8080; } ]; - virtualHosts._.default = true; + virtualHosts._.default = true; virtualHosts._.locations."/".root = pkgs.writeTextFile { name = "robots.txt"; destination = "/robots.txt"; @@ -115,7 +161,8 @@ in config.sops.templates."traefik.env".path ]; staticConfigOptions = { - metrics = lib.mkIf config.xyno.services.monitoring.enable { + accessLog = {}; + metrics = mkIf config.xyno.services.monitoring.enable { otlp.http.endpoint = "http://localhost:8429/v1/metrics"; }; entryponits.web = { @@ -137,13 +184,16 @@ in email = "ssl@xyno.systems"; caServer = "https://acme-v02.api.letsencrypt.org/directory"; dnsChallenge = { - resolvers = [ "8.8.8.8" "1.1.1.1" ]; + resolvers = [ + "8.8.8.8" + "1.1.1.1" + ]; provider = "desec"; }; }; }; dynamicConfigOptions = { - http = lib.mkMerge simpleProxyOpts; + http = mkMerge simpleProxyOpts; # tls.options.default = { # # mozilla modern # minVersion = "VersionTLS13";