From 438821de68f117acefe73851cfaa92fb49ab70c4 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <github-actions[bot]@users.noreply.github.com> Date: Mon, 19 Feb 2024 01:04:14 +0000 Subject: [PATCH 01/18] flake.lock: Update MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Flake lock file updates: • Updated input 'nixpkgs': 'github:nixos/nixpkgs/a4d4fe8c5002202493e87ec8dbc91335ff55552c' (2024-02-15) → 'github:nixos/nixpkgs/5863c27340ba4de8f83e7e3c023b9599c3cb3c80' (2024-02-16) • Updated input 'sops-nix': 'github:Mic92/sops-nix/48afd3264ec52bee85231a7122612e2c5202fa74' (2024-02-13) → 'github:Mic92/sops-nix/ffed177a9d2c685901781c3c6c9024ae0ffc252b' (2024-02-18) • Updated input 'sops-nix/nixpkgs': 'github:NixOS/nixpkgs/442d407992384ed9c0e6d352de75b69079904e4e' (2024-02-09) → 'github:NixOS/nixpkgs/6e2f00c83911461438301db0dba5281197fe4b3a' (2024-02-17) • Updated input 'sops-nix/nixpkgs-stable': 'github:NixOS/nixpkgs/d8cd80616c8800feec0cab64331d7c3d5a1a6d98' (2024-02-10) → 'github:NixOS/nixpkgs/69405156cffbdf2be50153f13cbdf9a0bea38e49' (2024-02-17) --- flake.lock | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/flake.lock b/flake.lock index 8e636f8..dbc3e2b 100644 --- a/flake.lock +++ b/flake.lock @@ -35,11 +35,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1707956935, - "narHash": "sha256-ZL2TrjVsiFNKOYwYQozpbvQSwvtV/3Me7Zwhmdsfyu4=", + "lastModified": 1708118438, + "narHash": "sha256-kk9/0nuVgA220FcqH/D2xaN6uGyHp/zoxPNUmPCMmEE=", "owner": "nixos", "repo": "nixpkgs", - "rev": "a4d4fe8c5002202493e87ec8dbc91335ff55552c", + "rev": "5863c27340ba4de8f83e7e3c023b9599c3cb3c80", "type": "github" }, "original": { @@ -51,11 +51,11 @@ }, "nixpkgs-stable": { "locked": { - "lastModified": 1707603439, - "narHash": "sha256-LodBVZ3+ehJP2azM5oj+JrhfNAAzmTJ/OwAIOn0RfZ0=", + "lastModified": 1708210246, + "narHash": "sha256-Q8L9XwrBK53fbuuIFMbjKvoV7ixfLFKLw4yV+SD28Y8=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "d8cd80616c8800feec0cab64331d7c3d5a1a6d98", + "rev": "69405156cffbdf2be50153f13cbdf9a0bea38e49", "type": "github" }, "original": { @@ -67,11 +67,11 @@ }, "nixpkgs_2": { "locked": { - "lastModified": 1707451808, - "narHash": "sha256-UwDBUNHNRsYKFJzyTMVMTF5qS4xeJlWoeyJf+6vvamU=", + "lastModified": 1708151420, + "narHash": "sha256-MGT/4aGCWQPQiu6COqJdCj9kSpLPiShgbwpbC38YXC8=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "442d407992384ed9c0e6d352de75b69079904e4e", + "rev": "6e2f00c83911461438301db0dba5281197fe4b3a", "type": "github" }, "original": { @@ -112,11 +112,11 @@ "nixpkgs-stable": "nixpkgs-stable" }, "locked": { - "lastModified": 1707842202, - "narHash": "sha256-3dTBbCzHJBinwhsisGJHW1HLBsLbj91+a5ZDXt7ttW0=", + "lastModified": 1708225343, + "narHash": "sha256-Q0uVUOfumc1DcKsIJIfMCHph08MjkOvZxvPb/Vi8hWw=", "owner": "Mic92", "repo": "sops-nix", - "rev": "48afd3264ec52bee85231a7122612e2c5202fa74", + "rev": "ffed177a9d2c685901781c3c6c9024ae0ffc252b", "type": "github" }, "original": { From 810d8e5aaa5ae98a0ebac2790e992f4c2fd43be2 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <github-actions[bot]@users.noreply.github.com> Date: Tue, 20 Feb 2024 01:02:02 +0000 Subject: [PATCH 02/18] flake.lock: Update MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Flake lock file updates: • Updated input 'nixpkgs': 'github:nixos/nixpkgs/5863c27340ba4de8f83e7e3c023b9599c3cb3c80' (2024-02-16) → 'github:nixos/nixpkgs/b98a4e1746acceb92c509bc496ef3d0e5ad8d4aa' (2024-02-18) --- flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flake.lock b/flake.lock index dbc3e2b..4182546 100644 --- a/flake.lock +++ b/flake.lock @@ -35,11 +35,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1708118438, - "narHash": "sha256-kk9/0nuVgA220FcqH/D2xaN6uGyHp/zoxPNUmPCMmEE=", + "lastModified": 1708296515, + "narHash": "sha256-FyF489fYNAUy7b6dkYV6rGPyzp+4tThhr80KNAaF/yY=", "owner": "nixos", "repo": "nixpkgs", - "rev": "5863c27340ba4de8f83e7e3c023b9599c3cb3c80", + "rev": "b98a4e1746acceb92c509bc496ef3d0e5ad8d4aa", "type": "github" }, "original": { From 0fccc02d84386e9e6bfc8c2c5762ddccb00cad2f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <github-actions[bot]@users.noreply.github.com> Date: Wed, 21 Feb 2024 01:03:17 +0000 Subject: [PATCH 03/18] flake.lock: Update MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Flake lock file updates: • Updated input 'sops-nix': 'github:Mic92/sops-nix/ffed177a9d2c685901781c3c6c9024ae0ffc252b' (2024-02-18) → 'github:Mic92/sops-nix/acfcce2a36da17ebb724d2e100d47881880c2e48' (2024-02-20) --- flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flake.lock b/flake.lock index 4182546..9e8f03d 100644 --- a/flake.lock +++ b/flake.lock @@ -112,11 +112,11 @@ "nixpkgs-stable": "nixpkgs-stable" }, "locked": { - "lastModified": 1708225343, - "narHash": "sha256-Q0uVUOfumc1DcKsIJIfMCHph08MjkOvZxvPb/Vi8hWw=", + "lastModified": 1708456161, + "narHash": "sha256-Rh5kJvLZySEPkOxCIX1XA0SpDnYjjXSvixLwKsrcpVE=", "owner": "Mic92", "repo": "sops-nix", - "rev": "ffed177a9d2c685901781c3c6c9024ae0ffc252b", + "rev": "acfcce2a36da17ebb724d2e100d47881880c2e48", "type": "github" }, "original": { From 6649d7622baf0f1ac62a4dc6c58cbba85da092a7 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <github-actions[bot]@users.noreply.github.com> Date: Thu, 22 Feb 2024 01:02:25 +0000 Subject: [PATCH 04/18] flake.lock: Update MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Flake lock file updates: • Updated input 'nixpkgs': 'github:nixos/nixpkgs/b98a4e1746acceb92c509bc496ef3d0e5ad8d4aa' (2024-02-18) → 'github:nixos/nixpkgs/0e74ca98a74bc7270d28838369593635a5db3260' (2024-02-21) • Updated input 'sops-nix': 'github:Mic92/sops-nix/acfcce2a36da17ebb724d2e100d47881880c2e48' (2024-02-20) → 'github:Mic92/sops-nix/f6b80ab6cd25e57f297fe466ad689d8a77057c11' (2024-02-21) --- flake.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/flake.lock b/flake.lock index 9e8f03d..4ddf2ac 100644 --- a/flake.lock +++ b/flake.lock @@ -35,11 +35,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1708296515, - "narHash": "sha256-FyF489fYNAUy7b6dkYV6rGPyzp+4tThhr80KNAaF/yY=", + "lastModified": 1708475490, + "narHash": "sha256-g1v0TsWBQPX97ziznfJdWhgMyMGtoBFs102xSYO4syU=", "owner": "nixos", "repo": "nixpkgs", - "rev": "b98a4e1746acceb92c509bc496ef3d0e5ad8d4aa", + "rev": "0e74ca98a74bc7270d28838369593635a5db3260", "type": "github" }, "original": { @@ -112,11 +112,11 @@ "nixpkgs-stable": "nixpkgs-stable" }, "locked": { - "lastModified": 1708456161, - "narHash": "sha256-Rh5kJvLZySEPkOxCIX1XA0SpDnYjjXSvixLwKsrcpVE=", + "lastModified": 1708500294, + "narHash": "sha256-mvJIecY3tDKZh7297mqOtOuAvP7U1rqjfLNfmfkjFpU=", "owner": "Mic92", "repo": "sops-nix", - "rev": "acfcce2a36da17ebb724d2e100d47881880c2e48", + "rev": "f6b80ab6cd25e57f297fe466ad689d8a77057c11", "type": "github" }, "original": { From 4538b64b92dac52ea0ceaaa88ab0c485ff0bdbe0 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <github-actions[bot]@users.noreply.github.com> Date: Sat, 24 Feb 2024 01:00:02 +0000 Subject: [PATCH 05/18] flake.lock: Update MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Flake lock file updates: • Updated input 'nixpkgs': 'github:nixos/nixpkgs/0e74ca98a74bc7270d28838369593635a5db3260' (2024-02-21) → 'github:nixos/nixpkgs/cbc4211f0afffe6dfd2478a62615dd5175a13f9a' (2024-02-23) --- flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flake.lock b/flake.lock index 4ddf2ac..48ac82a 100644 --- a/flake.lock +++ b/flake.lock @@ -35,11 +35,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1708475490, - "narHash": "sha256-g1v0TsWBQPX97ziznfJdWhgMyMGtoBFs102xSYO4syU=", + "lastModified": 1708655239, + "narHash": "sha256-ZrP/yACUvDB+zbqYJsln4iwotbH6CTZiTkANJ0AgDv4=", "owner": "nixos", "repo": "nixpkgs", - "rev": "0e74ca98a74bc7270d28838369593635a5db3260", + "rev": "cbc4211f0afffe6dfd2478a62615dd5175a13f9a", "type": "github" }, "original": { From a0cd48b5bc35c26b00ae3bcf98845870aa9da77c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <github-actions[bot]@users.noreply.github.com> Date: Mon, 26 Feb 2024 01:04:46 +0000 Subject: [PATCH 06/18] flake.lock: Update MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Flake lock file updates: • Updated input 'nixpkgs': 'github:nixos/nixpkgs/cbc4211f0afffe6dfd2478a62615dd5175a13f9a' (2024-02-23) → 'github:nixos/nixpkgs/73de017ef2d18a04ac4bfd0c02650007ccb31c2a' (2024-02-24) • Updated input 'sops-nix': 'github:Mic92/sops-nix/f6b80ab6cd25e57f297fe466ad689d8a77057c11' (2024-02-21) → 'github:Mic92/sops-nix/2874fbbe4a65bd2484b0ad757d27a16107f6bc17' (2024-02-25) • Updated input 'sops-nix/nixpkgs': 'github:NixOS/nixpkgs/6e2f00c83911461438301db0dba5281197fe4b3a' (2024-02-17) → 'github:NixOS/nixpkgs/f63ce824cd2f036216eb5f637dfef31e1a03ee89' (2024-02-24) • Updated input 'sops-nix/nixpkgs-stable': 'github:NixOS/nixpkgs/69405156cffbdf2be50153f13cbdf9a0bea38e49' (2024-02-17) → 'github:NixOS/nixpkgs/89a2a12e6c8c6a56c72eb3589982c8e2f89c70ea' (2024-02-25) --- flake.lock | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/flake.lock b/flake.lock index 48ac82a..3b91889 100644 --- a/flake.lock +++ b/flake.lock @@ -35,11 +35,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1708655239, - "narHash": "sha256-ZrP/yACUvDB+zbqYJsln4iwotbH6CTZiTkANJ0AgDv4=", + "lastModified": 1708807242, + "narHash": "sha256-sRTRkhMD4delO/hPxxi+XwLqPn8BuUq6nnj4JqLwOu0=", "owner": "nixos", "repo": "nixpkgs", - "rev": "cbc4211f0afffe6dfd2478a62615dd5175a13f9a", + "rev": "73de017ef2d18a04ac4bfd0c02650007ccb31c2a", "type": "github" }, "original": { @@ -51,11 +51,11 @@ }, "nixpkgs-stable": { "locked": { - "lastModified": 1708210246, - "narHash": "sha256-Q8L9XwrBK53fbuuIFMbjKvoV7ixfLFKLw4yV+SD28Y8=", + "lastModified": 1708819810, + "narHash": "sha256-1KosU+ZFXf31GPeCBNxobZWMgHsSOJcrSFA6F2jhzdE=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "69405156cffbdf2be50153f13cbdf9a0bea38e49", + "rev": "89a2a12e6c8c6a56c72eb3589982c8e2f89c70ea", "type": "github" }, "original": { @@ -67,11 +67,11 @@ }, "nixpkgs_2": { "locked": { - "lastModified": 1708151420, - "narHash": "sha256-MGT/4aGCWQPQiu6COqJdCj9kSpLPiShgbwpbC38YXC8=", + "lastModified": 1708751719, + "narHash": "sha256-0uWOKSpXJXmXswOvDM5Vk3blB74apFB6rNGWV5IjoN0=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "6e2f00c83911461438301db0dba5281197fe4b3a", + "rev": "f63ce824cd2f036216eb5f637dfef31e1a03ee89", "type": "github" }, "original": { @@ -112,11 +112,11 @@ "nixpkgs-stable": "nixpkgs-stable" }, "locked": { - "lastModified": 1708500294, - "narHash": "sha256-mvJIecY3tDKZh7297mqOtOuAvP7U1rqjfLNfmfkjFpU=", + "lastModified": 1708830076, + "narHash": "sha256-Cjh2xdjxC6S6nW6Whr2dxSeh8vjodzhTmQdI4zPJ4RA=", "owner": "Mic92", "repo": "sops-nix", - "rev": "f6b80ab6cd25e57f297fe466ad689d8a77057c11", + "rev": "2874fbbe4a65bd2484b0ad757d27a16107f6bc17", "type": "github" }, "original": { From 382544e06e2ed22c84110b479d4e66d291b644e0 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <github-actions[bot]@users.noreply.github.com> Date: Tue, 27 Feb 2024 01:02:19 +0000 Subject: [PATCH 07/18] flake.lock: Update MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Flake lock file updates: • Updated input 'sops-nix': 'github:Mic92/sops-nix/2874fbbe4a65bd2484b0ad757d27a16107f6bc17' (2024-02-25) → 'github:Mic92/sops-nix/a1c8de14f60924fafe13aea66b46157f0150f4cf' (2024-02-26) --- flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flake.lock b/flake.lock index 3b91889..23ac301 100644 --- a/flake.lock +++ b/flake.lock @@ -112,11 +112,11 @@ "nixpkgs-stable": "nixpkgs-stable" }, "locked": { - "lastModified": 1708830076, - "narHash": "sha256-Cjh2xdjxC6S6nW6Whr2dxSeh8vjodzhTmQdI4zPJ4RA=", + "lastModified": 1708987867, + "narHash": "sha256-k2lDaDWNTU5sBVHanYzjDKVDmk29RHIgdbbXu5sdzBA=", "owner": "Mic92", "repo": "sops-nix", - "rev": "2874fbbe4a65bd2484b0ad757d27a16107f6bc17", + "rev": "a1c8de14f60924fafe13aea66b46157f0150f4cf", "type": "github" }, "original": { From 65a37456e0a8e817ba8bae701d9f04812d49ec8c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <github-actions[bot]@users.noreply.github.com> Date: Wed, 28 Feb 2024 01:02:19 +0000 Subject: [PATCH 08/18] flake.lock: Update MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Flake lock file updates: • Updated input 'nixpkgs': 'github:nixos/nixpkgs/73de017ef2d18a04ac4bfd0c02650007ccb31c2a' (2024-02-24) → 'github:nixos/nixpkgs/13aff9b34cc32e59d35c62ac9356e4a41198a538' (2024-02-26) --- flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flake.lock b/flake.lock index 23ac301..6773800 100644 --- a/flake.lock +++ b/flake.lock @@ -35,11 +35,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1708807242, - "narHash": "sha256-sRTRkhMD4delO/hPxxi+XwLqPn8BuUq6nnj4JqLwOu0=", + "lastModified": 1708984720, + "narHash": "sha256-gJctErLbXx4QZBBbGp78PxtOOzsDaQ+yw1ylNQBuSUY=", "owner": "nixos", "repo": "nixpkgs", - "rev": "73de017ef2d18a04ac4bfd0c02650007ccb31c2a", + "rev": "13aff9b34cc32e59d35c62ac9356e4a41198a538", "type": "github" }, "original": { From 8cb1f323f44fb87b0191147edd1c18514ac8d1ce Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <github-actions[bot]@users.noreply.github.com> Date: Thu, 29 Feb 2024 01:02:04 +0000 Subject: [PATCH 09/18] flake.lock: Update MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Flake lock file updates: • Updated input 'flake-utils': 'github:numtide/flake-utils/1ef2e671c3b0c19053962c07dbda38332dcebf26' (2024-01-15) → 'github:numtide/flake-utils/d465f4819400de7c8d874d50b982301f28a84605' (2024-02-28) --- flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flake.lock b/flake.lock index 6773800..b0542ba 100644 --- a/flake.lock +++ b/flake.lock @@ -5,11 +5,11 @@ "systems": "systems" }, "locked": { - "lastModified": 1705309234, - "narHash": "sha256-uNRRNRKmJyCRC/8y1RqBkqWBLM034y4qN7EprSdmgyA=", + "lastModified": 1709126324, + "narHash": "sha256-q6EQdSeUZOG26WelxqkmR7kArjgWCdw5sfJVHPH/7j8=", "owner": "numtide", "repo": "flake-utils", - "rev": "1ef2e671c3b0c19053962c07dbda38332dcebf26", + "rev": "d465f4819400de7c8d874d50b982301f28a84605", "type": "github" }, "original": { From 6cf83e737ecd5b133c461213f640b80cebadef3f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <github-actions[bot]@users.noreply.github.com> Date: Fri, 1 Mar 2024 01:08:28 +0000 Subject: [PATCH 10/18] flake.lock: Update MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Flake lock file updates: • Updated input 'nixpkgs': 'github:nixos/nixpkgs/13aff9b34cc32e59d35c62ac9356e4a41198a538' (2024-02-26) → 'github:nixos/nixpkgs/9099616b93301d5cf84274b184a3a5ec69e94e08' (2024-02-28) --- flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flake.lock b/flake.lock index b0542ba..b709bcb 100644 --- a/flake.lock +++ b/flake.lock @@ -35,11 +35,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1708984720, - "narHash": "sha256-gJctErLbXx4QZBBbGp78PxtOOzsDaQ+yw1ylNQBuSUY=", + "lastModified": 1709150264, + "narHash": "sha256-HofykKuisObPUfj0E9CJVfaMhawXkYx3G8UIFR/XQ38=", "owner": "nixos", "repo": "nixpkgs", - "rev": "13aff9b34cc32e59d35c62ac9356e4a41198a538", + "rev": "9099616b93301d5cf84274b184a3a5ec69e94e08", "type": "github" }, "original": { From fa206d0e1515fb0e49393e7ada6d7e5c6ec1df58 Mon Sep 17 00:00:00 2001 From: ibizaman <ibizapeanut@gmail.com> Date: Thu, 29 Feb 2024 15:34:53 -0800 Subject: [PATCH 11/18] move templating code to lib file --- flake.nix | 7 ++ lib/default.nix | 109 +++++++++++++++++++++++-- modules/blocks/authelia.nix | 61 ++++++++++++-- modules/services/home-assistant.nix | 78 ++++++++++++------ modules/services/jellyfin.nix | 26 ++++-- modules/services/nextcloud-server.nix | 2 +- modules/services/vaultwarden.nix | 17 ++-- test/modules/lib.nix | 110 ++++++++++++++++++++++++++ test/vm/authelia.nix | 5 +- test/vm/lib.nix | 82 +++++++++++++++++++ 10 files changed, 439 insertions(+), 58 deletions(-) create mode 100644 test/modules/lib.nix create mode 100644 test/vm/lib.nix diff --git a/flake.nix b/flake.nix index 2191f9a..f91c79b 100644 --- a/flake.nix +++ b/flake.nix @@ -88,13 +88,20 @@ mergeTests (importFiles [ ./test/modules/arr.nix ./test/modules/davfs.nix + ./test/modules/lib.nix ./test/modules/nginx.nix ./test/modules/postgresql.nix ]); }; + + lib = nix-flake-tests.lib.check { + inherit pkgs; + tests = pkgs.callPackage ./test/modules/lib.nix {}; + }; } // (vm_test "authelia" ./test/vm/authelia.nix) // (vm_test "ldap" ./test/vm/ldap.nix) + // (vm_test "lib" ./test/vm/lib.nix) // (vm_test "postgresql" ./test/vm/postgresql.nix) // (vm_test "monitoring" ./test/vm/monitoring.nix) // (vm_test "nextcloud" ./test/vm/nextcloud.nix) diff --git a/lib/default.nix b/lib/default.nix index fdb48ea..c47643f 100644 --- a/lib/default.nix +++ b/lib/default.nix @@ -1,13 +1,110 @@ -{ lib }: -{ - template = file: newPath: replacements: +{ pkgs, lib }: +rec { + replaceSecrets = { userConfig, resultPath, generator }: let - templatePath = newPath + ".template"; + configWithTemplates = withReplacements userConfig; + + nonSecretConfigFile = pkgs.writeText "${resultPath}.template" (generator configWithTemplates); + + replacements = getReplacements userConfig; + in + replaceSecretsScript { + file = nonSecretConfigFile; + inherit resultPath replacements; + }; + + template = file: newPath: replacements: replaceSecretsScript { inherit file replacements; resultPath = newPath; }; + replaceSecretsScript = { file, resultPath, replacements }: + let + templatePath = resultPath + ".template"; sedPatterns = lib.strings.concatStringsSep " " (lib.attrsets.mapAttrsToList (from: to: "-e \"s|${from}|${to}|\"") replacements); in '' + set -euo pipefail + set -x ln -fs ${file} ${templatePath} - rm ${newPath} || : - sed ${sedPatterns} ${templatePath} > ${newPath} + rm -f ${resultPath} + ${pkgs.gnused}/bin/sed ${sedPatterns} ${templatePath} + ${pkgs.gnused}/bin/sed ${sedPatterns} ${templatePath} > ${resultPath} ''; + + secretFileType = lib.types.submodule { + options = { + source = lib.mkOption { + type = lib.types.path; + description = "File containing the value."; + }; + + transform = lib.mkOption { + type = lib.types.raw; + description = "An optional function to transform the secret."; + default = null; + example = lib.literalExpression '' + v: "prefix-$${v}-suffix" + ''; + }; + }; + }; + + secretName = name: + "%SECRET${lib.strings.toUpper (lib.strings.concatMapStrings (s: "_" + s) name)}%"; + + withReplacements = attrs: + let + valueOrReplacement = name: value: + if !(builtins.isAttrs value && value ? "source") + then value + else secretName name; + in + mapAttrsRecursiveCond (v: ! v ? "source") valueOrReplacement attrs; + + getReplacements = attrs: + let + addNameField = name: value: + if !(builtins.isAttrs value && value ? "source") + then value + else value // { name = name; }; + + secretsWithName = mapAttrsRecursiveCond (v: ! v ? "source") addNameField attrs; + + allSecrets = collect (v: builtins.isAttrs v && v ? "source") secretsWithName; + + t = { transform ? null, ... }: if isNull transform then x: x else transform; + + genReplacement = secret: + lib.attrsets.nameValuePair (secretName secret.name) ((t secret) "$(cat ${toString secret.source})"); + in + lib.attrsets.listToAttrs (map genReplacement allSecrets); + + # Inspired lib.attrsets.mapAttrsRecursiveCond but also recurses on lists. + mapAttrsRecursiveCond = + # A function, given the attribute set the recursion is currently at, determine if to recurse deeper into that attribute set. + cond: + # A function, given a list of attribute names and a value, returns a new value. + f: + # Attribute set or list to recursively map over. + set: + let + recurse = path: val: + if builtins.isAttrs val && cond val + then lib.attrsets.mapAttrs (n: v: recurse (path ++ [n]) v) val + else if builtins.isList val && cond val + then lib.lists.imap0 (i: v: recurse (path ++ [(builtins.toString i)]) v) val + else f path val; + in recurse [] set; + + # Like lib.attrsets.collect but also recurses on lists. + collect = + # Given an attribute's value, determine if recursion should stop. + pred: + # The attribute set to recursively collect. + attrs: + if pred attrs then + [ attrs ] + else if builtins.isAttrs attrs then + lib.lists.concatMap (collect pred) (lib.attrsets.attrValues attrs) + else if builtins.isList attrs then + lib.lists.concatMap (collect pred) attrs + else + []; } diff --git a/modules/blocks/authelia.nix b/modules/blocks/authelia.nix index 6f19f1e..af3928d 100644 --- a/modules/blocks/authelia.nix +++ b/modules/blocks/authelia.nix @@ -94,9 +94,54 @@ in }; oidcClients = lib.mkOption { - type = lib.types.listOf lib.types.anything; description = "OIDC clients"; default = []; + type = lib.types.listOf (lib.types.submodule { + freeformType = lib.types.attrsOf lib.types.anything; + + options = { + id = lib.mkOption { + type = lib.types.str; + description = "Unique identifier of the OIDC client."; + }; + + description = lib.mkOption { + type = lib.types.nullOr lib.types.str; + description = "Human readable description of the OIDC client."; + default = null; + }; + + secret = lib.mkOption { + type = shblib.secretFileType; + description = "File containing the shared secret with the OIDC client."; + }; + + public = lib.mkOption { + type = lib.types.bool; + description = "If the OIDC client is public or not."; + default = false; + apply = v: if v then "true" else "false"; + }; + + authorization_policy = lib.mkOption { + type = lib.types.enum [ "one_factor" "two_factor" ]; + description = "Require one factor (password) or two factor (device) authentication."; + default = "one_factor"; + }; + + redirect_uris = lib.mkOption { + type = lib.types.listOf lib.types.str; + description = "List of uris that are allowed to be redirected to."; + }; + + scopes = lib.mkOption { + type = lib.types.listOf lib.types.str; + description = "Scopes to ask for"; + example = [ "openid" "profile" "email" "groups" ]; + default = []; + }; + }; + }); }; smtp = lib.mkOption { @@ -291,13 +336,13 @@ in systemd.services."authelia-${fqdn}".preStart = let mkCfg = clients: - let - addTemplate = client: (builtins.removeAttrs client ["secretFile"]) // {secret = "%SECRET_${client.id}%";}; - tmplFile = pkgs.writeText "oidc_clients.yaml" (lib.generators.toYAML {} {identity_providers.oidc.clients = map addTemplate clients;}); - replace = client: {"%SECRET_${client.id}%" = "$(cat ${toString client.secretFile})";}; - replacements = lib.foldl (container: client: container // (replace client) ) {} clients; - in - shblib.template tmplFile "/var/lib/authelia-${fqdn}/oidc_clients.yaml" replacements; + shblib.replaceSecrets { + userConfig = { + identity_providers.oidc.clients = clients; + }; + resultPath = "/var/lib/authelia-${fqdn}/oidc_clients.yaml"; + generator = lib.generators.toYAML {}; + }; in lib.mkBefore (mkCfg cfg.oidcClients); diff --git a/modules/services/home-assistant.nix b/modules/services/home-assistant.nix index 6f16d5d..6790c10 100644 --- a/modules/services/home-assistant.nix +++ b/modules/services/home-assistant.nix @@ -4,6 +4,7 @@ let cfg = config.shb.home-assistant; contracts = pkgs.callPackage ../contracts {}; + shblib = pkgs.callPackage ../../lib {}; fqdn = "${cfg.subdomain}.${cfg.domain}"; @@ -18,6 +19,15 @@ let export PATH=${pkgs.gnused}/bin:${pkgs.curl}/bin:${pkgs.jq}/bin exec ${pkgs.bash}/bin/bash ${ldap_auth_script_repo}/example_configs/lldap-ha-auth.sh $@ ''; + + # Filter secrets from config. Secrets are those of the form { source = <path>; } + secrets = lib.attrsets.filterAttrs (k: v: builtins.isAttrs v) cfg.config; + + nonSecrets = (lib.attrsets.filterAttrs (k: v: !(builtins.isAttrs v)) cfg.config); + + configWithSecretsIncludes = + nonSecrets + // (lib.attrsets.mapAttrs (k: v: "!secret ${k}") secrets); in { options.shb.home-assistant = { @@ -41,6 +51,41 @@ in default = null; }; + config = lib.mkOption { + description = "See all available settings at https://www.home-assistant.io/docs/configuration/basic/"; + type = lib.types.submodule { + freeformType = lib.types.attrsOf lib.types.str; + options = { + name = lib.mkOption { + type = lib.types.oneOf [ lib.types.str shblib.secretFileType ]; + description = "Name of the Home Assistant instance."; + }; + country = lib.mkOption { + type = lib.types.oneOf [ lib.types.str shblib.secretFileType ]; + description = "Two letter country code where this instance is located."; + }; + latitude = lib.mkOption { + type = lib.types.oneOf [ lib.types.str shblib.secretFileType ]; + description = "Latitude where this instance is located."; + }; + longitude = lib.mkOption { + type = lib.types.oneOf [ lib.types.str shblib.secretFileType ]; + description = "Longitude where this instance is located."; + }; + time_zone = lib.mkOption { + type = lib.types.oneOf [ lib.types.str shblib.secretFileType ]; + description = "Timezone of this instance."; + example = "America/Los_Angeles"; + }; + unit_system = lib.mkOption { + type = lib.types.oneOf [ lib.types.str (lib.types.enum [ "metric" "us_customary" ]) ]; + description = "Timezone of this instance."; + example = "America/Los_Angeles"; + }; + }; + }; + }; + ldap = lib.mkOption { description = '' LDAP Integration App. [Manual](https://docs.nextcloud.com/server/latest/admin_manual/configuration_user/user_auth_ldap.html) @@ -91,12 +136,6 @@ in }; }; - sopsFile = lib.mkOption { - type = lib.types.path; - description = "Sops file location"; - example = "secrets/homeassistant.yaml"; - }; - backupCfg = lib.mkOption { type = lib.types.anything; description = "Backup configuration for home-assistant"; @@ -144,14 +183,8 @@ in trusted_proxies = "127.0.0.1"; }; logger.default = "info"; - homeassistant = { + homeassistant = configWithSecretsIncludes // { external_url = "https://${cfg.subdomain}.${cfg.domain}"; - name = "!secret name"; - country = "!secret country"; - latitude = "!secret latitude_home"; - longitude = "!secret longitude_home"; - time_zone = "!secret time_zone"; - unit_system = "metric"; auth_providers = (lib.optionals (!cfg.ldap.enable || cfg.ldap.keepDefaultAuth) [ { @@ -256,23 +289,18 @@ in } } ''; - storage = "${config.services.home-assistant.configDir}/.storage"; - file = "${storage}/onboarding"; + storage = "${config.services.home-assistant.configDir}"; + file = "${storage}/.storage/onboarding"; in '' if ! -f ${file}; then mkdir -p ${storage} && cp ${onboarding} ${file} fi - ''); - - sops.secrets."home-assistant" = { - inherit (cfg) sopsFile; - mode = "0440"; - owner = "hass"; - group = "hass"; - path = "${config.services.home-assistant.configDir}/secrets.yaml"; - restartUnits = [ "home-assistant.service" ]; - }; + '' + shblib.replaceSecrets { + userConfig = cfg.config; + resultPath = "${config.services.home-assistant.configDir}/secrets.yaml"; + generator = lib.generators.toYAML {}; + }); systemd.tmpfiles.rules = [ "f ${config.services.home-assistant.configDir}/automations.yaml 0755 hass hass" diff --git a/modules/services/jellyfin.nix b/modules/services/jellyfin.nix index 148418d..510bb51 100644 --- a/modules/services/jellyfin.nix +++ b/modules/services/jellyfin.nix @@ -348,19 +348,33 @@ in </BrandingOptions> ''; in - shblib.template ldapConfig "/var/lib/jellyfin/plugins/configurations/LDAP-Auth.xml" { - "%LDAP_PASSWORD%" = "$(cat ${cfg.ldapPasswordFile})"; + shblib.replaceSecretsScript { + file = ldapConfig; + resultPath = "/var/lib/jellyfin/plugins/configurations/LDAP-Auth.xml"; + userConfig = { + "%LDAP_PASSWORD%" = "$(cat ${cfg.ldapPasswordFile})"; + }; } - + shblib.template ssoConfig "/var/lib/jellyfin/plugins/configurations/SSO-Auth.xml" { - "%SSO_SECRET%" = "$(cat ${cfg.ssoSecretFile})"; + + shblib.replaceSecretsScript { + file = ssoConfig; + resultPath = "/var/lib/jellyfin/plugins/configurations/SSO-Auth.xml"; + userConfig = { + "%SSO_SECRET%" = "$(cat ${cfg.ssoSecretFile})"; + }; } - + shblib.template brandingConfig "/var/lib/jellyfin/config/branding.xml" {"%a%" = "%a%";}; + + shblib.replaceSecretsScript { + file = brandingConfig; + resultPath = "/var/lib/jellyfin/config/branding.xml"; + userConfig = { + "%a%" = "%a%"; + }; + }; shb.authelia.oidcClients = [ { id = cfg.oidcClientID; description = "Jellyfin"; - secretFile = cfg.ssoSecretFile; + secret.source = cfg.ssoSecretFile; public = false; authorization_policy = "one_factor"; redirect_uris = [ "https://${cfg.subdomain}.${cfg.domain}/sso/OID/r/${cfg.oidcProvider}" ]; diff --git a/modules/services/nextcloud-server.nix b/modules/services/nextcloud-server.nix index f3c7e65..e0a4f2a 100644 --- a/modules/services/nextcloud-server.nix +++ b/modules/services/nextcloud-server.nix @@ -829,7 +829,7 @@ in { id = cfg.apps.sso.clientID; description = "Nextcloud"; - secretFile = cfg.apps.sso.secretFileForAuthelia; + secret.source = cfg.apps.sso.secretFileForAuthelia; public = "false"; authorization_policy = cfg.apps.sso.authorization_policy; redirect_uris = [ "${protocol}://${fqdnWithPort}/apps/oidc_login/oidc" ]; diff --git a/modules/services/vaultwarden.nix b/modules/services/vaultwarden.nix index 3430960..4405097 100644 --- a/modules/services/vaultwarden.nix +++ b/modules/services/vaultwarden.nix @@ -148,16 +148,15 @@ in "f /var/lib/bitwarden_rs/vaultwarden.env 0640 vaultwarden vaultwarden" ]; systemd.services.vaultwarden.preStart = - let - envFile = pkgs.writeText "vaultwarden.env" '' - DATABASE_URL=postgresql://vaultwarden:%DB_PASSWORD%@127.0.0.1:5432/vaultwarden - SMTP_PASSWORD=%SMTP_PASSWORD% - ''; - in - shblib.template envFile "/var/lib/bitwarden_rs/vaultwarden.env" { - "%DB_PASSWORD%" = "$(cat ${cfg.databasePasswordFile})"; - "%SMTP_PASSWORD%" = "$(cat ${cfg.smtp.passwordFile})"; + shblib.replaceSecrets { + userConfig = { + DATABASE_URL.source = cfg.databasePasswordFile; + DATABASE_URL.transform = v: "postgresql://vaultwarden:${v}@127.0.0.1:5432/vaultwarden"; + SMTP_PASSWORD.source = cfg.smtp.passwordFile; }; + resultPath = "/var/lib/bitwarden_rs/vaultwarden.env"; + generator = v: lib.generators.toINIWithGlobalSection {} { globalSection = v; }; + }; shb.nginx.autheliaProtect = [ { diff --git a/test/modules/lib.nix b/test/modules/lib.nix new file mode 100644 index 0000000..a34dcf5 --- /dev/null +++ b/test/modules/lib.nix @@ -0,0 +1,110 @@ +{ pkgs, lib, ... }: +let + shblib = pkgs.callPackage ../../lib {}; +in +{ + # Tests that withReplacements can: + # - recurse in attrs and lists + # - .source field is understood + # - .transform field is understood + # - if .source field is found, ignores other fields + testLibWithReplacements = { + expected = + let + item = root: { + a = "A"; + b = "%SECRET_${root}B%"; + c = "%SECRET_${root}C%"; + }; + in + (item "") // { + nestedAttr = item "NESTEDATTR_"; + nestedList = [ (item "NESTEDLIST_0_") ]; + doubleNestedList = [ { n = (item "DOUBLENESTEDLIST_0_N_"); } ]; + }; + expr = + let + item = { + a = "A"; + b.source = "/path/B"; + b.transform = null; + c.source = "/path/C"; + c.transform = v: "prefix-${v}-suffix"; + c.other = "other"; + }; + in + shblib.withReplacements ( + item // { + nestedAttr = item; + nestedList = [ item ]; + doubleNestedList = [ { n = item; } ]; + } + ); + }; + + testLibWithReplacementsRootList = { + expected = + let + item = root: { + a = "A"; + b = "%SECRET_${root}B%"; + c = "%SECRET_${root}C%"; + }; + in + [ + (item "0_") + (item "1_") + [ (item "2_0_") ] + [ { n = (item "3_0_N_"); } ] + ]; + expr = + let + item = { + a = "A"; + b.source = "/path/B"; + b.transform = null; + c.source = "/path/C"; + c.transform = v: "prefix-${v}-suffix"; + c.other = "other"; + }; + in + shblib.withReplacements [ + item + item + [ item ] + [ { n = item; } ] + ]; + }; + + testLibGetReplacements = { + expected = + let + secrets = root: { + "%SECRET_${root}B%" = "$(cat /path/B)"; + "%SECRET_${root}C%" = "prefix-$(cat /path/C)-suffix"; + }; + in + (secrets "") // + (secrets "NESTEDATTR_") // + (secrets "NESTEDLIST_0_") // + (secrets "DOUBLENESTEDLIST_0_N_"); + expr = + let + item = { + a = "A"; + b.source = "/path/B"; + b.transform = null; + c.source = "/path/C"; + c.transform = v: "prefix-${v}-suffix"; + c.other = "other"; + }; + in + shblib.getReplacements ( + item // { + nestedAttr = item; + nestedList = [ item ]; + doubleNestedList = [ { n = item; } ]; + } + ); + }; +} diff --git a/test/vm/authelia.nix b/test/vm/authelia.nix index da1bf98..45d3cc6 100644 --- a/test/vm/authelia.nix +++ b/test/vm/authelia.nix @@ -10,7 +10,6 @@ in imports = [ { options = { - shb.ssl.enable = lib.mkEnableOption "ssl"; shb.backup = lib.mkOption { type = lib.types.anything; }; }; } @@ -49,7 +48,7 @@ in { id = "client1"; description = "My Client 1"; - secretFile = pkgs.writeText "secret" "mysecuresecret"; + secret.source = pkgs.writeText "secret" "mysecuresecret"; public = false; authorization_policy = "one_factor"; redirect_uris = [ "http://client1.machine/redirect" ]; @@ -57,7 +56,7 @@ in { id = "client2"; description = "My Client 2"; - secretFile = pkgs.writeText "secret" "myothersecret"; + secret.source = pkgs.writeText "secret" "myothersecret"; public = false; authorization_policy = "one_factor"; redirect_uris = [ "http://client2.machine/redirect" ]; diff --git a/test/vm/lib.nix b/test/vm/lib.nix new file mode 100644 index 0000000..5a79c0a --- /dev/null +++ b/test/vm/lib.nix @@ -0,0 +1,82 @@ +{ pkgs, lib, ... }: +let + shblib = pkgs.callPackage ../../lib {}; +in +{ + template = + let + aSecret = pkgs.writeText "a-secret.txt" "Secret of A"; + bSecret = pkgs.writeText "b-secret.txt" "Secret of B"; + userConfig = { + a.a.source = aSecret; + b.source = bSecret; + b.transform = v: "prefix-${v}-suffix"; + c = "not secret C"; + d.d = "not secret D"; + }; + + wantedConfig = { + a.a = "Secret of A"; + b = "prefix-Secret of B-suffix"; + c = "not secret C"; + d.d = "not secret D"; + }; + + configWithTemplates = shblib.withReplacements userConfig; + + nonSecretConfigFile = pkgs.writeText "config.yaml.template" (lib.generators.toJSON {} configWithTemplates); + + replacements = shblib.getReplacements userConfig; + + replaceInTemplate = shblib.replaceSecretsScript { + file = nonSecretConfigFile; + resultPath = "/var/lib/config.yaml"; + inherit replacements; + }; + + replaceInTemplate2 = shblib.replaceSecrets { + inherit userConfig; + resultPath = "/var/lib/config2.yaml"; + generator = lib.generators.toJSON {}; + }; + in + pkgs.nixosTest { + name = "lib-template"; + nodes.machine = { config, pkgs, ... }: + { + imports = [ + { + options = { + libtest.config = lib.mkOption { + type = lib.types.attrsOf (lib.types.oneOf [ lib.types.str shblib.secretFileType ]); + }; + }; + } + ]; + + system.activationScripts = { + libtest = replaceInTemplate; + libtest2 = replaceInTemplate2; + }; + }; + + testScript = { nodes, ... }: '' + import json + start_all() + + wantedConfig = json.loads('${lib.generators.toJSON {} wantedConfig}') + gotConfig = json.loads(machine.succeed("cat /var/lib/config.yaml")) + gotConfig2 = json.loads(machine.succeed("cat /var/lib/config2.yaml")) + + # For debugging purpose + print(machine.succeed("cat ${pkgs.writeText "replaceInTemplate" replaceInTemplate}")) + print(machine.succeed("cat ${pkgs.writeText "replaceInTemplate2" replaceInTemplate2}")) + + if wantedConfig != gotConfig: + raise Exception("\nwantedConfig: {}\n!= gotConfig: {}".format(wantedConfig, gotConfig)) + + if wantedConfig != gotConfig2: + raise Exception("\nwantedConfig: {}\n!= gotConfig2: {}".format(wantedConfig, gotConfig)) + ''; + }; +} From 53d46cda56a0be3b267a26a623bc023eebe0dff4 Mon Sep 17 00:00:00 2001 From: ibizaman <ibizapeanut@gmail.com> Date: Thu, 29 Feb 2024 20:34:09 -0800 Subject: [PATCH 12/18] update home-assistant demo --- demo/homeassistant/README.md | 11 +++------- demo/homeassistant/flake.lock | 38 ++++++++++++++++----------------- demo/homeassistant/flake.nix | 35 ++++++++++++++++++++++++++++++ demo/homeassistant/secrets.yaml | 10 ++++++--- 4 files changed, 64 insertions(+), 30 deletions(-) diff --git a/demo/homeassistant/README.md b/demo/homeassistant/README.md index 348f5b1..7b5b835 100644 --- a/demo/homeassistant/README.md +++ b/demo/homeassistant/README.md @@ -230,21 +230,16 @@ SOPS_AGE_KEY_FILE=keys.txt nix run --impure nixpkgs#sops -- \ The `secrets.yaml` file must follow the format: ```yaml -home-assistant: | - name: "My Instance" +home-assistant: country: "US" - latitude_home: "0.100" - longitude_home: "-0.100" + latitude: "0.100" + longitude: "-0.100" time_zone: "America/Los_Angeles" - unit_system: "metric" lldap: user_password: XXX... jwt_secret: YYY... ``` -> Important: the value of the `home-assistant` field is a string that looks like yaml. Do _not_ -> remove the pipe (|) sign. - You can generate random secrets with: ```bash diff --git a/demo/homeassistant/flake.lock b/demo/homeassistant/flake.lock index 6bf946d..fcd21e0 100644 --- a/demo/homeassistant/flake.lock +++ b/demo/homeassistant/flake.lock @@ -5,11 +5,11 @@ "systems": "systems" }, "locked": { - "lastModified": 1705309234, - "narHash": "sha256-uNRRNRKmJyCRC/8y1RqBkqWBLM034y4qN7EprSdmgyA=", + "lastModified": 1709126324, + "narHash": "sha256-q6EQdSeUZOG26WelxqkmR7kArjgWCdw5sfJVHPH/7j8=", "owner": "numtide", "repo": "flake-utils", - "rev": "1ef2e671c3b0c19053962c07dbda38332dcebf26", + "rev": "d465f4819400de7c8d874d50b982301f28a84605", "type": "github" }, "original": { @@ -35,11 +35,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1707092692, - "narHash": "sha256-ZbHsm+mGk/izkWtT4xwwqz38fdlwu7nUUKXTOmm4SyE=", + "lastModified": 1709150264, + "narHash": "sha256-HofykKuisObPUfj0E9CJVfaMhawXkYx3G8UIFR/XQ38=", "owner": "nixos", "repo": "nixpkgs", - "rev": "faf912b086576fd1a15fca610166c98d47bc667e", + "rev": "9099616b93301d5cf84274b184a3a5ec69e94e08", "type": "github" }, "original": { @@ -51,27 +51,27 @@ }, "nixpkgs-stable": { "locked": { - "lastModified": 1705957679, - "narHash": "sha256-Q8LJaVZGJ9wo33wBafvZSzapYsjOaNjP/pOnSiKVGHY=", + "lastModified": 1708819810, + "narHash": "sha256-1KosU+ZFXf31GPeCBNxobZWMgHsSOJcrSFA6F2jhzdE=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "9a333eaa80901efe01df07eade2c16d183761fa3", + "rev": "89a2a12e6c8c6a56c72eb3589982c8e2f89c70ea", "type": "github" }, "original": { "owner": "NixOS", - "ref": "release-23.05", + "ref": "release-23.11", "repo": "nixpkgs", "type": "github" } }, "nixpkgs_2": { "locked": { - "lastModified": 1706925685, - "narHash": "sha256-hVInjWMmgH4yZgA4ZtbgJM1qEAel72SYhP5nOWX4UIM=", + "lastModified": 1708751719, + "narHash": "sha256-0uWOKSpXJXmXswOvDM5Vk3blB74apFB6rNGWV5IjoN0=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "79a13f1437e149dc7be2d1290c74d378dad60814", + "rev": "f63ce824cd2f036216eb5f637dfef31e1a03ee89", "type": "github" }, "original": { @@ -111,11 +111,11 @@ "sops-nix": "sops-nix" }, "locked": { - "lastModified": 1707374005, - "narHash": "sha256-W3p8hBLUdlHAG7yxT250jImnFmXe83tN119/jRiBYdo=", + "lastModified": 1709267447, + "narHash": "sha256-5Q467FhpS18L/+5iB3wsWaR9tBqdzNt0fpdkZJNqNxc=", "owner": "ibizaman", "repo": "selfhostblocks", - "rev": "7d0276e9f2509bc6f175358c318374fedfc64422", + "rev": "fa206d0e1515fb0e49393e7ada6d7e5c6ec1df58", "type": "github" }, "original": { @@ -130,11 +130,11 @@ "nixpkgs-stable": "nixpkgs-stable" }, "locked": { - "lastModified": 1707015547, - "narHash": "sha256-YZr0OrqWPdbwBhxpBu69D32ngJZw8AMgZtJeaJn0e94=", + "lastModified": 1708987867, + "narHash": "sha256-k2lDaDWNTU5sBVHanYzjDKVDmk29RHIgdbbXu5sdzBA=", "owner": "Mic92", "repo": "sops-nix", - "rev": "23f61b897c00b66855074db471ba016e0cda20dd", + "rev": "a1c8de14f60924fafe13aea66b46157f0150f4cf", "type": "github" }, "original": { diff --git a/demo/homeassistant/flake.nix b/demo/homeassistant/flake.nix index bc20515..e8d5eba 100644 --- a/demo/homeassistant/flake.nix +++ b/demo/homeassistant/flake.nix @@ -18,7 +18,42 @@ enable = true; domain = "example.com"; subdomain = "ha"; + config = { + name = "SHB Home Assistant"; + country.source = config.sops.secrets."home-assistant/country".path; + latitude.source = config.sops.secrets."home-assistant/latitude".path; + longitude.source = config.sops.secrets."home-assistant/longitude".path; + time_zone.source = config.sops.secrets."home-assistant/time_zone".path; + unit_system = "metric"; + }; + }; + sops.secrets."home-assistant/country" = { sopsFile = ./secrets.yaml; + mode = "0440"; + owner = "hass"; + group = "hass"; + restartUnits = [ "home-assistant.service" ]; + }; + sops.secrets."home-assistant/latitude" = { + sopsFile = ./secrets.yaml; + mode = "0440"; + owner = "hass"; + group = "hass"; + restartUnits = [ "home-assistant.service" ]; + }; + sops.secrets."home-assistant/longitude" = { + sopsFile = ./secrets.yaml; + mode = "0440"; + owner = "hass"; + group = "hass"; + restartUnits = [ "home-assistant.service" ]; + }; + sops.secrets."home-assistant/time_zone" = { + sopsFile = ./secrets.yaml; + mode = "0440"; + owner = "hass"; + group = "hass"; + restartUnits = [ "home-assistant.service" ]; }; nixpkgs.config.permittedInsecurePackages = [ diff --git a/demo/homeassistant/secrets.yaml b/demo/homeassistant/secrets.yaml index e0e488c..5b1900d 100644 --- a/demo/homeassistant/secrets.yaml +++ b/demo/homeassistant/secrets.yaml @@ -1,4 +1,8 @@ -home-assistant: ENC[AES256_GCM,data:acEXqx3bdQp0zB5FnHCBsic/kgu2L8Q6h/fsfrLmdk7SOfzEibPpPLCCv8eYmh4D5VuIAsq/PeJ3k+uqWGbTrJt7EIcxt0kYTLRuWZRG8YJH1+HCxoKcO/mx9bwbRd3LtXiVscgP9zIZLoLPK2XieFKOeg==,iv:dJ7FUkquMI4g4K2Nnv3kFFQk/va2QgwfgGoWif5f2tU=,tag:6LIBt9whdRPVsoF1RY3Pew==,type:str] +home-assistant: + country: ENC[AES256_GCM,data:2Ng=,iv:/VMB6yi3e8piAx8DzLGGhLsozxWUWX2R7NcmACFng8Q=,tag:Tx0Iy1AnLmPrnYu7XtbesA==,type:str] + latitude: ENC[AES256_GCM,data:p/O1HW4=,iv:CRgL4wcM3gMNu/OAHVoQuLcRD9J3SbkxsjvobiabQ0g=,tag:uIo5Rv7geOtVcarp4Qkqww==,type:str] + longitude: ENC[AES256_GCM,data:sVyww6F7,iv:9EZYXSkv+rhD77lqmC+c8i+wf46KPYloVoK+ok3bWYY=,tag:c+lmtcGvULtMdu9ZTDewjA==,type:str] + time_zone: ENC[AES256_GCM,data:JKXdsQZrtB1B77klxuemw1tZbg==,iv:nItJfpwp2XWmBHbohrjNMWQ8TpL2Xsv22UujZRgDscw=,tag:wrHbA1yycutUUn79F9wy6Q==,type:str] lldap: user_password: ENC[AES256_GCM,data:JrFraqFSqAhRVjB5fagIoB864aejt24q+qqWeu8ySC0=,iv:RS7VS+9tsSknn9SwpfyYVi41m3lN4SkZ4CSwrzH/Eso=,tag:5L7fx6/KhDtjHPruwac/sw==,type:str] jwt_secret: ENC[AES256_GCM,data:W1T/QoxuzMD+2AL7sP5KkMcC+GvFdd4kfd70rHLnQD+jWNs9G0igkC/BxxgbIfnSASwtSnBaaiU6/pxLFOcUVh0Nyd0Zmb/KTbagpUvSl//AZnTt/WKF9Q/8sqKzsGv0QdMyZKWi4cxiEILcTbxOsgwriFGgOJ1k5N8JEif15ig=,iv:rHlRt6nWMz8rVmU0aKH6VWWVXunOfJcDvZOxgWbK1FI=,tag:qC6N61rE8CfPSXrsEqFoIQ==,type:str] @@ -26,8 +30,8 @@ sops: VlJpS1BYd2UrZU1mZTEwU1BYODhqM2sKvQnFV8xsy1tEmYZu4izBYb7XQqTPOLTL bRkU6n17uiyXNbiXDAbX0Png/XmVG96/+Zl38BBXPQvARX8c2tzq6w== -----END AGE ENCRYPTED FILE----- - lastmodified: "2024-01-23T00:46:58Z" - mac: ENC[AES256_GCM,data:kBkUCStabQ32JK/UDPATgOz3HoI/dVkNLsl6uEhHk8ODbF+ZBg6BDEaxtMFFh0bV+71klAmF0KsL/kHKiHlbNuoNWOxwbsANGeL8xtV6JCU58zTF0nfgAP/3KJYveridgylRRZS5hYl5Mg+z6Zdgw+43r3Iiizf86BZVc5OaDyY=,iv:ZXWLXQUrVIwYCCVnXI0jTf5paOWNuujG/Pw+Nf/M34A=,tag:+P/UJqBI3prcxEUO4Zqu/A==,type:str] + lastmodified: "2024-02-12T05:07:51Z" + mac: ENC[AES256_GCM,data:MOmvK0g6Wj+fND154QUhmXujsDOKMO5CRRckru+eDRPeHcJZUnI/jjolcI8y+LEdhUVf0Ln8E38GSxZT/8EW3CfCNkOUikGFdfxuQ2uzNp/1wMvNaF988lrXMBfQ7Il18AiYVK0QhGReGXJa6wBVUb2Qfrg41WC65UvQtMOByqI=,iv:Rscvq1l7YgNapC0NkabQHBzirzsPEr8ykAQqx+qGoi0=,tag:ud+K72bnUV1hnsjcewNrsw==,type:str] pgp: [] unencrypted_suffix: _unencrypted version: 3.8.1 From d0d94e61c87771e4afcfcf095cd8e626eb6fb0ef Mon Sep 17 00:00:00 2001 From: ibizaman <ibizapeanut@gmail.com> Date: Thu, 29 Feb 2024 20:39:51 -0800 Subject: [PATCH 13/18] use better defaults for nextcloud preview app --- modules/services/nextcloud-server.nix | 33 +++++++++++++++++++ .../services/nextcloud-server/docs/default.md | 9 +++++ 2 files changed, 42 insertions(+) diff --git a/modules/services/nextcloud-server.nix b/modules/services/nextcloud-server.nix index e0a4f2a..3c2943e 100644 --- a/modules/services/nextcloud-server.nix +++ b/modules/services/nextcloud-server.nix @@ -239,6 +239,27 @@ in options = { enable = lib.mkEnableOption "Nextcloud Preview Generator App"; + recommendedSettings = lib.mkOption { + type = lib.types.bool; + description = '' + Better defaults than the defaults. Taken from [this article](http://web.archive.org/web/20200513043150/https://ownyourbits.com/2019/06/29/understanding-and-improving-nextcloud-previews/). + + Sets the following options: + + ``` + nextcloud-occ config:app:set previewgenerator squareSizes --value="32 256" + nextcloud-occ config:app:set previewgenerator widthSizes --value="256 384" + nextcloud-occ config:app:set previewgenerator heightSizes --value="256" + nextcloud-occ config:system:set preview_max_x --value 2048 + nextcloud-occ config:system:set preview_max_y --value 2048 + nextcloud-occ config:system:set jpeg_quality --value 60 + nextcloud-occ config:app:set preview jpeg_quality --value="60" + ``` + ''; + default = true; + example = false; + }; + debug = lib.mkOption { type = lib.types.bool; description = "Enable more verbose logging."; @@ -649,6 +670,18 @@ in inherit ((nextcloudApps cfg.version)) previewgenerator; }; + # Values taken from + # http://web.archive.org/web/20200513043150/https://ownyourbits.com/2019/06/29/understanding-and-improving-nextcloud-previews/ + systemd.services.nextcloud-setup.script = lib.mkIf cfg.apps.previewgenerator.recommendedSettings '' + ${occ} config:app:set previewgenerator squareSizes --value="32 256" + ${occ} config:app:set previewgenerator widthSizes --value="256 384" + ${occ} config:app:set previewgenerator heightSizes --value="256" + ${occ} config:system:set preview_max_x --value 2048 + ${occ} config:system:set preview_max_y --value 2048 + ${occ} config:system:set jpeg_quality --value 60 + ${occ} config:app:set preview jpeg_quality --value="60" + ''; + # Configured as defined in https://github.com/nextcloud/previewgenerator systemd.timers.nextcloud-cron-previewgenerator = { wantedBy = [ "timers.target" ]; diff --git a/modules/services/nextcloud-server/docs/default.md b/modules/services/nextcloud-server/docs/default.md index faf7191..3f293ed 100644 --- a/modules/services/nextcloud-server/docs/default.md +++ b/modules/services/nextcloud-server/docs/default.md @@ -281,6 +281,15 @@ Note that you still need to generate the previews for any pre-existing files wit nextcloud-occ -vvv preview:generate-all ``` +The default settings generates all possible sizes which is a waste since most are not used. SHB will +change the generation settings to optimize disk space and CPU usage as outlined in [this +article](http://web.archive.org/web/20200513043150/https://ownyourbits.com/2019/06/29/understanding-and-improving-nextcloud-previews/). +You can opt-out with: + +```nix +shb.nextcloud.apps.previewgenerator.recommendedSettings = false; +``` + ### Enable OnlyOffice App {#services-nextcloud-server-usage-onlyoffice} The following snippet installs and enables the [Only From 937902a7f030b1d9647682e28a9a6cfdf3ff5fac Mon Sep 17 00:00:00 2001 From: ibizaman <ibizapeanut@gmail.com> Date: Thu, 29 Feb 2024 20:41:24 -0800 Subject: [PATCH 14/18] add section about nextcloud appdata folder --- .../services/nextcloud-server/docs/default.md | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/modules/services/nextcloud-server/docs/default.md b/modules/services/nextcloud-server/docs/default.md index 3f293ed..5a28cd2 100644 --- a/modules/services/nextcloud-server/docs/default.md +++ b/modules/services/nextcloud-server/docs/default.md @@ -331,6 +331,31 @@ See [my blog post](http://blog.tiserbox.com/posts/2023-08-12-what%27s-up-with-nextcloud-webdav-slowness.html) for how to look at the traces. +### Appdata Location {#services-nextcloud-server-server-usage-appdata} + +The appdata folder is a special folder located under the `shb.nextcloud.dataDir` directory. It is +named `appdata_<instanceid>` with the Nextcloud's instance ID as a suffix. You can find your current +instance ID with `nextcloud-occ config:system:get instanceid`. In there, you will find one subfolder +for every installed app that needs to store files. + +For performance reasons, it is recommended to store this folder on a fast drive that is optimized +for randomized read and write access. The best would be either an SSD or an NVMe drive. + +If you intentionally put Nextcloud's `shb.nextcloud.dataDir` folder on a HDD with spinning disks, +for example because they offer more disk space, then the appdata folder is also located on spinning +drives. You are thus faced with a conundrum. The only way to solve this is to bind mount a folder +from an SSD over the appdata folder. SHB does not provide (yet?) a declarative way to setup this but +this command should be enough: + +```bash +mount /dev/sdd /srv/sdd +mkdir -p /srv/sdd/appdata_nextcloud +mount --bind /srv/sdd/appdata_nextcloud /var/lib/nextcloud/data/appdata_ocxvky2f5ix7 +``` + +Note that you can re-generate a new appdata folder by issuing the command `occ config:system:delete +instanceid`. + ## Demo {#services-nextcloud-server-demo} Head over to the [Nextcloud demo](demo-nextcloud-server.html) for a demo that installs Nextcloud with or From 8c2373430d9a42f0ac27a80daab217008e91b38b Mon Sep 17 00:00:00 2001 From: ibizaman <ibizapeanut@gmail.com> Date: Thu, 29 Feb 2024 21:52:49 -0800 Subject: [PATCH 15/18] fix jellyfin and nextcloud-server after changes to lib --- modules/services/jellyfin.nix | 6 +++--- modules/services/nextcloud-server.nix | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/services/jellyfin.nix b/modules/services/jellyfin.nix index 510bb51..1571f12 100644 --- a/modules/services/jellyfin.nix +++ b/modules/services/jellyfin.nix @@ -351,21 +351,21 @@ in shblib.replaceSecretsScript { file = ldapConfig; resultPath = "/var/lib/jellyfin/plugins/configurations/LDAP-Auth.xml"; - userConfig = { + replacements = { "%LDAP_PASSWORD%" = "$(cat ${cfg.ldapPasswordFile})"; }; } + shblib.replaceSecretsScript { file = ssoConfig; resultPath = "/var/lib/jellyfin/plugins/configurations/SSO-Auth.xml"; - userConfig = { + replacements = { "%SSO_SECRET%" = "$(cat ${cfg.ssoSecretFile})"; }; } + shblib.replaceSecretsScript { file = brandingConfig; resultPath = "/var/lib/jellyfin/config/branding.xml"; - userConfig = { + replacements = { "%a%" = "%a%"; }; }; diff --git a/modules/services/nextcloud-server.nix b/modules/services/nextcloud-server.nix index 3c2943e..b0199b3 100644 --- a/modules/services/nextcloud-server.nix +++ b/modules/services/nextcloud-server.nix @@ -863,7 +863,7 @@ in id = cfg.apps.sso.clientID; description = "Nextcloud"; secret.source = cfg.apps.sso.secretFileForAuthelia; - public = "false"; + public = false; authorization_policy = cfg.apps.sso.authorization_policy; redirect_uris = [ "${protocol}://${fqdnWithPort}/apps/oidc_login/oidc" ]; scopes = [ From 046ae67083046cfeb9e65b98e569446fd2f0679b Mon Sep 17 00:00:00 2001 From: ibizaman <ibizapeanut@gmail.com> Date: Thu, 29 Feb 2024 21:54:45 -0800 Subject: [PATCH 16/18] optionally make nextcloud systemd services depend on mount point --- modules/services/nextcloud-server.nix | 17 ++++++++++++++++- .../services/nextcloud-server/docs/default.md | 12 ++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/modules/services/nextcloud-server.nix b/modules/services/nextcloud-server.nix index b0199b3..197f251 100644 --- a/modules/services/nextcloud-server.nix +++ b/modules/services/nextcloud-server.nix @@ -82,6 +82,13 @@ in default = "/var/lib/nextcloud"; }; + mountPointServices = lib.mkOption { + description = "If given, all the systemd services and timers will depend on the specified mount point systemd services."; + type = lib.types.listOf lib.types.str; + default = []; + example = lib.literalExpression ''["var.mount"]''; + }; + adminUser = lib.mkOption { type = lib.types.str; description = "Username of the initial admin user."; @@ -616,10 +623,17 @@ in systemd.services.phpfpm-nextcloud.preStart = '' mkdir -p /var/log/xdebug; chown -R nextcloud: /var/log/xdebug ''; + systemd.services.phpfpm-nextcloud.requires = cfg.mountPointServices; + systemd.services.phpfpm-nextcloud.after = cfg.mountPointServices; systemd.services.nextcloud-cron.path = [ pkgs.perl ]; + systemd.timers.nextcloud-cron.requires = cfg.mountPointServices; + systemd.timers.nextcloud-cron.after = cfg.mountPointServices; + + systemd.services.nextcloud-setup.requires = cfg.mountPointServices; + systemd.services.nextcloud-setup.after = cfg.mountPointServices; # Sets up backup for Nextcloud. shb.backup.instances.nextcloud = { @@ -685,7 +699,8 @@ in # Configured as defined in https://github.com/nextcloud/previewgenerator systemd.timers.nextcloud-cron-previewgenerator = { wantedBy = [ "timers.target" ]; - after = [ "nextcloud-setup.service" ]; + requires = cfg.mountPointServices; + after = [ "nextcloud-setup.service" ] + cfg.mountPointServices; timerConfig.OnBootSec = "10m"; timerConfig.OnUnitActiveSec = "10m"; timerConfig.Unit = "nextcloud-cron-previewgenerator.service"; diff --git a/modules/services/nextcloud-server/docs/default.md b/modules/services/nextcloud-server/docs/default.md index 5a28cd2..d89e2f1 100644 --- a/modules/services/nextcloud-server/docs/default.md +++ b/modules/services/nextcloud-server/docs/default.md @@ -83,6 +83,18 @@ shb.nextcloud = { After deploying, the Nextcloud server will be reachable at `http://nextcloud.example.com`. +### Mount Point {#services-nextcloud-server-mount-point} + +If the `dataDir` exists in a mount point, it is highly recommended to make the various Nextcloud +services wait on the mount point before starting. Doing that is just a matter of setting the `mountPointServices` option. + +Assuming a mount point on `/var`, the configuration would look like so: + +```nix +fileSystems."/var".device = "..."; +shb.nextcloud.mountPointServices = [ "var.mount" ]; +``` + ### With LDAP Support {#services-nextcloud-server-usage-ldap} :::: {.note} From 97f213a137aaf976d84300a1e0568ffaeb782060 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <github-actions[bot]@users.noreply.github.com> Date: Sat, 2 Mar 2024 01:01:07 +0000 Subject: [PATCH 17/18] flake.lock: Update MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Flake lock file updates: • Updated input 'nixpkgs': 'github:nixos/nixpkgs/9099616b93301d5cf84274b184a3a5ec69e94e08' (2024-02-28) → 'github:nixos/nixpkgs/1536926ef5621b09bba54035ae2bb6d806d72ac8' (2024-02-29) --- flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flake.lock b/flake.lock index b709bcb..ab89304 100644 --- a/flake.lock +++ b/flake.lock @@ -35,11 +35,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1709150264, - "narHash": "sha256-HofykKuisObPUfj0E9CJVfaMhawXkYx3G8UIFR/XQ38=", + "lastModified": 1709237383, + "narHash": "sha256-cy6ArO4k5qTx+l5o+0mL9f5fa86tYUX3ozE1S+Txlds=", "owner": "nixos", "repo": "nixpkgs", - "rev": "9099616b93301d5cf84274b184a3a5ec69e94e08", + "rev": "1536926ef5621b09bba54035ae2bb6d806d72ac8", "type": "github" }, "original": { From e80cc0d3aad5301ec7a57869e5bc2f36c6b0871d Mon Sep 17 00:00:00 2001 From: ibizaman <ibizapeanut@gmail.com> Date: Sat, 2 Mar 2024 22:58:36 -0800 Subject: [PATCH 18/18] add vm tests for jellyfin and regroup ldap and sso options --- flake.nix | 1 + lib/default.nix | 2 +- modules/services/jellyfin.nix | 188 ++++++++++++-------- test/vm/jellyfin.nix | 326 ++++++++++++++++++++++++++++++++++ 4 files changed, 439 insertions(+), 78 deletions(-) create mode 100644 test/vm/jellyfin.nix diff --git a/flake.nix b/flake.nix index f91c79b..2c171bb 100644 --- a/flake.nix +++ b/flake.nix @@ -100,6 +100,7 @@ }; } // (vm_test "authelia" ./test/vm/authelia.nix) + // (vm_test "jellyfin" ./test/vm/jellyfin.nix) // (vm_test "ldap" ./test/vm/ldap.nix) // (vm_test "lib" ./test/vm/lib.nix) // (vm_test "postgresql" ./test/vm/postgresql.nix) diff --git a/lib/default.nix b/lib/default.nix index c47643f..4846a3e 100644 --- a/lib/default.nix +++ b/lib/default.nix @@ -22,9 +22,9 @@ rec { '' set -euo pipefail set -x + mkdir -p $(dirname ${templatePath}) ln -fs ${file} ${templatePath} rm -f ${resultPath} - ${pkgs.gnused}/bin/sed ${sedPatterns} ${templatePath} ${pkgs.gnused}/bin/sed ${sedPatterns} ${templatePath} > ${resultPath} ''; diff --git a/modules/services/jellyfin.nix b/modules/services/jellyfin.nix index 1571f12..dcc1450 100644 --- a/modules/services/jellyfin.nix +++ b/modules/services/jellyfin.nix @@ -30,62 +30,94 @@ in default = null; }; - ldapHost = lib.mkOption { - type = lib.types.str; - description = "host serving the LDAP server"; - example = "127.0.0.1"; + ldap = lib.mkOption { + description = "LDAP configuration."; + default = {}; + type = lib.types.submodule { + options = { + enable = lib.mkEnableOption "LDAP"; + + host = lib.mkOption { + type = lib.types.str; + description = "Host serving the LDAP server."; + example = "127.0.0.1"; + }; + + port = lib.mkOption { + type = lib.types.int; + description = "Port where the LDAP server is listening."; + example = 389; + }; + + dcdomain = lib.mkOption { + type = lib.types.str; + description = "DC domain for LDAP."; + example = "dc=mydomain,dc=com"; + }; + + userGroup = lib.mkOption { + type = lib.types.str; + description = "LDAP user group"; + default = "jellyfin_user"; + }; + + adminGroup = lib.mkOption { + type = lib.types.str; + description = "LDAP admin group"; + default = "jellyfin_admin"; + }; + + passwordFile = lib.mkOption { + type = lib.types.path; + description = "File containing the LDAP admin password."; + }; + }; + }; }; - ldapPort = lib.mkOption { - type = lib.types.int; - description = "port where the LDAP server is listening"; - example = 389; - }; + sso = lib.mkOption { + description = "SSO configuration."; + default = {}; + type = lib.types.submodule { + options = { + enable = lib.mkEnableOption "SSO"; - dcdomain = lib.mkOption { - type = lib.types.str; - description = "dc domain for ldap"; - example = "dc=mydomain,dc=com"; - }; + provider = lib.mkOption { + type = lib.types.str; + description = "OIDC provider name"; + default = "Authelia"; + }; - oidcProvider = lib.mkOption { - type = lib.types.str; - description = "OIDC provider name"; - default = "Authelia"; - }; + endpoint = lib.mkOption { + type = lib.types.str; + description = "OIDC endpoint for SSO"; + example = "https://authelia.example.com"; + }; - authEndpoint = lib.mkOption { - type = lib.types.str; - description = "OIDC endpoint for SSO"; - example = "https://authelia.example.com"; - }; + clientID = lib.mkOption { + type = lib.types.str; + description = "Client ID for the OIDC endpoint"; + default = "jellyfin"; + }; - oidcClientID = lib.mkOption { - type = lib.types.str; - description = "Client ID for the OIDC endpoint"; - default = "jellyfin"; - }; + adminUserGroup = lib.mkOption { + type = lib.types.str; + description = "OIDC admin group"; + default = "jellyfin_admin"; + }; - oidcAdminUserGroup = lib.mkOption { - type = lib.types.str; - description = "OIDC admin group"; - default = "jellyfin_admin"; - }; + userGroup = lib.mkOption { + type = lib.types.str; + description = "OIDC user group"; + default = "jellyfin_user"; + }; - oidcUserGroup = lib.mkOption { - type = lib.types.str; - description = "OIDC user group"; - default = "jellyfin_user"; - }; - - ldapPasswordFile = lib.mkOption { - type = lib.types.path; - description = "File containing the LDAP admin password."; - }; - - ssoSecretFile = lib.mkOption { - type = lib.types.path; - description = "File containing the SSO shared secret."; + secretFile = lib.mkOption { + type = lib.types.path; + description = "File containing the OIDC shared secret."; + }; + }; + }; }; }; @@ -107,6 +139,8 @@ in }; }; + services.nginx.enable = true; + # Take advice from https://jellyfin.org/docs/general/networking/nginx/ and https://nixos.wiki/wiki/Plex services.nginx.virtualHosts."${fqdn}" = { forceSSL = !(isNull cfg.ssl); @@ -238,17 +272,17 @@ in ldapConfig = pkgs.writeText "LDAP-Auth.xml" '' <?xml version="1.0" encoding="utf-8"?> <PluginConfiguration xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> - <LdapServer>${cfg.ldapHost}</LdapServer> - <LdapPort>${builtins.toString cfg.ldapPort}</LdapPort> + <LdapServer>${cfg.ldap.host}</LdapServer> + <LdapPort>${builtins.toString cfg.ldap.port}</LdapPort> <UseSsl>false</UseSsl> <UseStartTls>false</UseStartTls> <SkipSslVerify>false</SkipSslVerify> - <LdapBindUser>uid=admin,ou=people,${cfg.dcdomain}</LdapBindUser> + <LdapBindUser>uid=admin,ou=people,${cfg.ldap.dcdomain}</LdapBindUser> <LdapBindPassword>%LDAP_PASSWORD%</LdapBindPassword> - <LdapBaseDn>ou=people,${cfg.dcdomain}</LdapBaseDn> - <LdapSearchFilter>(memberof=cn=jellyfin_user,ou=groups,${cfg.dcdomain})</LdapSearchFilter> - <LdapAdminBaseDn>ou=people,${cfg.dcdomain}</LdapAdminBaseDn> - <LdapAdminFilter>(memberof=cn=jellyfin_admin,ou=groups,${cfg.dcdomain})</LdapAdminFilter> + <LdapBaseDn>ou=people,${cfg.ldap.dcdomain}</LdapBaseDn> + <LdapSearchFilter>(memberof=cn=${cfg.ldap.userGroup},ou=groups,${cfg.ldap.dcdomain})</LdapSearchFilter> + <LdapAdminBaseDn>ou=people,${cfg.ldap.dcdomain}</LdapAdminBaseDn> + <LdapAdminFilter>(memberof=cn=${cfg.ldap.adminGroup},ou=groups,${cfg.ldap.dcdomain})</LdapAdminFilter> <EnableLdapAdminFilterMemberUid>false</EnableLdapAdminFilterMemberUid> <LdapSearchAttributes>uid, cn, mail, displayName</LdapSearchAttributes> <LdapClientCertPath /> @@ -271,22 +305,22 @@ in <OidConfigs> <item> <key> - <string>${cfg.oidcProvider}</string> + <string>${cfg.sso.provider}</string> </key> <value> <PluginConfiguration> - <OidEndpoint>${cfg.authEndpoint}</OidEndpoint> - <OidClientId>${cfg.oidcClientID}</OidClientId> + <OidEndpoint>${cfg.sso.endpoint}</OidEndpoint> + <OidClientId>${cfg.sso.clientID}</OidClientId> <OidSecret>%SSO_SECRET%</OidSecret> <Enabled>true</Enabled> <EnableAuthorization>true</EnableAuthorization> <EnableAllFolders>true</EnableAllFolders> <EnabledFolders /> <AdminRoles> - <string>${cfg.oidcAdminUserGroup}</string> + <string>${cfg.sso.adminUserGroup}</string> </AdminRoles> <Roles> - <string>${cfg.oidcUserGroup}</string> + <string>${cfg.sso.userGroup}</string> </Roles> <EnableFolderRoles>false</EnableFolderRoles> <FolderRoleMappings /> @@ -305,15 +339,15 @@ in brandingConfig = pkgs.writeText "branding.xml" '' <?xml version="1.0" encoding="utf-8"?> <BrandingOptions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> - <LoginDisclaimer><a href="https://${cfg.subdomain}.${cfg.domain}/SSO/OID/p/${cfg.oidcProvider}" class="raised cancel block emby-button authentik-sso"> - Sign in with ${cfg.oidcProvider}&nbsp; + <LoginDisclaimer><a href="https://${cfg.subdomain}.${cfg.domain}/SSO/OID/p/${cfg.sso.provider}" class="raised cancel block emby-button authentik-sso"> + Sign in with ${cfg.sso.provider}&nbsp; <img alt="OpenID Connect (authentik)" title="OpenID Connect (authentik)" class="oauth-login-image" src="https://raw.githubusercontent.com/goauthentik/authentik/master/web/icons/icon.png"> </a> <a href="https://${cfg.subdomain}.${cfg.domain}/SSOViews/linking" class="raised cancel block emby-button authentik-sso"> - Link ${cfg.oidcProvider} config&nbsp; + Link ${cfg.sso.provider} config&nbsp; </a> - <a href="${cfg.authEndpoint}" class="raised cancel block emby-button authentik-sso"> - ${cfg.oidcProvider} config&nbsp; + <a href="${cfg.sso.endpoint}" class="raised cancel block emby-button authentik-sso"> + ${cfg.sso.provider} config&nbsp; </a> </LoginDisclaimer> <CustomCss> @@ -348,36 +382,36 @@ in </BrandingOptions> ''; in - shblib.replaceSecretsScript { + lib.strings.optionalString cfg.ldap.enable (shblib.replaceSecretsScript { file = ldapConfig; resultPath = "/var/lib/jellyfin/plugins/configurations/LDAP-Auth.xml"; replacements = { - "%LDAP_PASSWORD%" = "$(cat ${cfg.ldapPasswordFile})"; + "%LDAP_PASSWORD%" = "$(cat ${cfg.ldap.passwordFile})"; }; - } - + shblib.replaceSecretsScript { + }) + + lib.strings.optionalString cfg.sso.enable (shblib.replaceSecretsScript { file = ssoConfig; resultPath = "/var/lib/jellyfin/plugins/configurations/SSO-Auth.xml"; replacements = { - "%SSO_SECRET%" = "$(cat ${cfg.ssoSecretFile})"; + "%SSO_SECRET%" = "$(cat ${cfg.sso.secretFile})"; }; - } - + shblib.replaceSecretsScript { + }) + + lib.strings.optionalString cfg.sso.enable (shblib.replaceSecretsScript { file = brandingConfig; resultPath = "/var/lib/jellyfin/config/branding.xml"; replacements = { "%a%" = "%a%"; }; - }; + }); - shb.authelia.oidcClients = [ + shb.authelia.oidcClients = lib.lists.optionals (!(isNull cfg.sso)) [ { - id = cfg.oidcClientID; + id = cfg.sso.clientID; description = "Jellyfin"; - secret.source = cfg.ssoSecretFile; + secret.source = cfg.sso.secretFile; public = false; authorization_policy = "one_factor"; - redirect_uris = [ "https://${cfg.subdomain}.${cfg.domain}/sso/OID/r/${cfg.oidcProvider}" ]; + redirect_uris = [ "https://${cfg.subdomain}.${cfg.domain}/sso/OID/r/${cfg.sso.provider}" ]; } ]; diff --git a/test/vm/jellyfin.nix b/test/vm/jellyfin.nix new file mode 100644 index 0000000..0cdef24 --- /dev/null +++ b/test/vm/jellyfin.nix @@ -0,0 +1,326 @@ +{ pkgs, lib, ... }: +{ + basic = pkgs.nixosTest { + name = "jellyfin-basic"; + + nodes.server = { config, pkgs, ... }: { + imports = [ + { + options = { + shb.backup = lib.mkOption { type = lib.types.anything; }; + shb.authelia = lib.mkOption { type = lib.types.anything; }; + }; + } + ../../modules/services/jellyfin.nix + ]; + + shb.jellyfin = { + enable = true; + domain = "example.com"; + subdomain = "j"; + }; + # Nginx port. + networking.firewall.allowedTCPPorts = [ 80 ]; + }; + + nodes.client = {}; + + # TODO: Test login + testScript = { nodes, ... }: '' + import json + + def curl(target, format, endpoint): + return json.loads(target.succeed( + "curl --fail-with-body --silent --show-error --output /dev/null --location" + + " --connect-to j.example.com:443:server:443" + + " --connect-to j.example.com:80:server:80" + + f" --write-out '{format}'" + + " " + endpoint + )) + + start_all() + server.wait_for_unit("jellyfin.service") + server.wait_for_unit("nginx.service") + server.wait_for_open_port(8096) + + response = curl(client, """{"code":%{response_code}}""", "http://j.example.com") + + if response['code'] != 200: + raise Exception(f"Code is {response['code']}") + ''; + }; + + ldap = pkgs.nixosTest { + name = "jellyfin-ldap"; + + nodes.server = { config, pkgs, ... }: { + imports = [ + { + options = { + shb.backup = lib.mkOption { type = lib.types.anything; }; + shb.authelia = lib.mkOption { type = lib.types.anything; }; + }; + } + ../../modules/blocks/ldap.nix + ../../modules/services/jellyfin.nix + ]; + + shb.ldap = { + enable = true; + domain = "example.com"; + subdomain = "ldap"; + ldapPort = 3890; + webUIListenPort = 17170; + dcdomain = "dc=example,dc=com"; + ldapUserPasswordFile = pkgs.writeText "ldapUserPassword" "ldapUserPassword"; + jwtSecretFile = pkgs.writeText "jwtSecret" "jwtSecret"; + }; + + shb.jellyfin = { + enable = true; + domain = "example.com"; + subdomain = "j"; + + ldap = { + enable = true; + host = "127.0.0.1"; + port = config.shb.ldap.ldapPort; + dcdomain = config.shb.ldap.dcdomain; + passwordFile = pkgs.writeText "ldapUserPassword" "ldapUserPassword"; + }; + }; + # Nginx port. + networking.firewall.allowedTCPPorts = [ 80 ]; + }; + + nodes.client = {}; + + # TODO: Test login with ldap user + testScript = { nodes, ... }: '' + import json + + def curl(target, format, endpoint): + return json.loads(target.succeed( + "curl --fail-with-body --silent --show-error --output /dev/null --location" + + " --connect-to j.example.com:443:server:443" + + " --connect-to j.example.com:80:server:80" + + f" --write-out '{format}'" + + " " + endpoint + )) + + start_all() + server.wait_for_unit("jellyfin.service") + server.wait_for_unit("nginx.service") + server.wait_for_unit("lldap.service") + server.wait_for_open_port(8096) + + response = curl(client, """{"code":%{response_code}}""", "http://j.example.com") + + if response['code'] != 200: + raise Exception(f"Code is {response['code']}") + ''; + }; + + cert = pkgs.nixosTest { + name = "jellyfin_cert"; + + nodes.server = { config, pkgs, ... }: { + imports = [ + { + options = { + shb.backup = lib.mkOption { type = lib.types.anything; }; + shb.authelia = lib.mkOption { type = lib.types.anything; }; + }; + } + ../../modules/blocks/nginx.nix + ../../modules/blocks/postgresql.nix + ../../modules/blocks/ssl.nix + ../../modules/services/jellyfin.nix + ]; + + shb.certs = { + cas.selfsigned.myca = { + name = "My CA"; + }; + certs.selfsigned = { + n = { + ca = config.shb.certs.cas.selfsigned.myca; + domain = "*.example.com"; + group = "nginx"; + }; + }; + }; + + systemd.services.nginx.after = [ config.shb.certs.certs.selfsigned.n.systemdService ]; + systemd.services.nginx.requires = [ config.shb.certs.certs.selfsigned.n.systemdService ]; + + shb.jellyfin = { + enable = true; + domain = "example.com"; + subdomain = "j"; + ssl = config.shb.certs.certs.selfsigned.n; + }; + # Nginx port. + networking.firewall.allowedTCPPorts = [ 80 443 ]; + + shb.nginx.accessLog = true; + }; + + nodes.client = {}; + + # TODO: Test login + testScript = { nodes, ... }: '' + import json + import os + import pathlib + + def curl(target, format, endpoint): + return json.loads(target.succeed( + "curl --fail-with-body --silent --show-error --output /dev/null --location" + + " --connect-to j.example.com:443:server:443" + + " --connect-to j.example.com:80:server:80" + + f" --write-out '{format}'" + + " " + endpoint + )) + + start_all() + server.wait_for_unit("jellyfin.service") + server.wait_for_unit("nginx.service") + server.wait_for_open_port(8096) + + server.copy_from_vm("/etc/ssl/certs/ca-certificates.crt") + client.succeed("rm -r /etc/ssl/certs") + client.copy_from_host(str(pathlib.Path(os.environ.get("out", os.getcwd())) / "ca-certificates.crt"), "/etc/ssl/certs/ca-certificates.crt") + + response = curl(client, """{"code":%{response_code}}""", "https://j.example.com") + + if response['code'] != 200: + raise Exception(f"Code is {response['code']}") + ''; + }; + + sso = pkgs.nixosTest { + name = "jellyfin_sso"; + + nodes.server = { config, pkgs, ... }: { + imports = [ + { + options = { + shb.backup = lib.mkOption { type = lib.types.anything; }; + }; + } + ../../modules/blocks/authelia.nix + ../../modules/blocks/ldap.nix + ../../modules/blocks/postgresql.nix + ../../modules/blocks/ssl.nix + ../../modules/services/jellyfin.nix + ]; + + shb.ldap = { + enable = true; + domain = "example.com"; + subdomain = "ldap"; + ldapPort = 3890; + webUIListenPort = 17170; + dcdomain = "dc=example,dc=com"; + ldapUserPasswordFile = pkgs.writeText "ldapUserPassword" "ldapUserPassword"; + jwtSecretFile = pkgs.writeText "jwtSecret" "jwtSecret"; + }; + + shb.certs = { + cas.selfsigned.myca = { + name = "My CA"; + }; + certs.selfsigned = { + n = { + ca = config.shb.certs.cas.selfsigned.myca; + domain = "*.example.com"; + group = "nginx"; + }; + }; + }; + + systemd.services.nginx.after = [ config.shb.certs.certs.selfsigned.n.systemdService ]; + systemd.services.nginx.requires = [ config.shb.certs.certs.selfsigned.n.systemdService ]; + + shb.authelia = { + enable = true; + domain = "example.com"; + subdomain = "auth"; + ssl = config.shb.certs.certs.selfsigned.n; + + ldapEndpoint = "ldap://127.0.0.1:${builtins.toString config.shb.ldap.ldapPort}"; + dcdomain = config.shb.ldap.dcdomain; + + secrets = { + jwtSecretFile = pkgs.writeText "jwtSecret" "jwtSecret"; + ldapAdminPasswordFile = pkgs.writeText "ldapUserPassword" "ldapUserPassword"; + sessionSecretFile = pkgs.writeText "sessionSecret" "sessionSecret"; + storageEncryptionKeyFile = pkgs.writeText "storageEncryptionKey" "storageEncryptionKey"; + identityProvidersOIDCHMACSecretFile = pkgs.writeText "identityProvidersOIDCHMACSecret" "identityProvidersOIDCHMACSecret"; + identityProvidersOIDCIssuerPrivateKeyFile = (pkgs.runCommand "gen-private-key" {} '' + mkdir $out + ${pkgs.openssl}/bin/openssl genrsa -out $out/private.pem 4096 + '') + "/private.pem"; + }; + }; + + shb.jellyfin = { + enable = true; + domain = "example.com"; + subdomain = "j"; + ssl = config.shb.certs.certs.selfsigned.n; + + ldap = { + enable = true; + host = "127.0.0.1"; + port = config.shb.ldap.ldapPort; + dcdomain = config.shb.ldap.dcdomain; + passwordFile = pkgs.writeText "ldapUserPassword" "ldapUserPassword"; + }; + + sso = { + enable = true; + endpoint = "https://${config.shb.authelia.subdomain}.${config.shb.authelia.domain}"; + secretFile = pkgs.writeText "ssoSecretFile" "ssoSecretFile"; + }; + }; + # Nginx port. + networking.firewall.allowedTCPPorts = [ 80 443 ]; + }; + + nodes.client = {}; + + # TODO: Test login with ldap user + testScript = { nodes, ... }: '' + import json + import os + import pathlib + + def curl(target, format, endpoint): + return json.loads(target.succeed( + "curl --fail-with-body --silent --show-error --output /dev/null --location" + + " --connect-to j.example.com:443:server:443" + + " --connect-to j.example.com:80:server:80" + + f" --write-out '{format}'" + + " " + endpoint + )) + + start_all() + server.wait_for_unit("jellyfin.service") + server.wait_for_unit("nginx.service") + server.wait_for_unit("lldap.service") + server.wait_for_unit("authelia-auth.example.com.service") + server.wait_for_open_port(8096) + + server.copy_from_vm("/etc/ssl/certs/ca-certificates.crt") + client.succeed("rm -r /etc/ssl/certs") + client.copy_from_host(str(pathlib.Path(os.environ.get("out", os.getcwd())) / "ca-certificates.crt"), "/etc/ssl/certs/ca-certificates.crt") + + response = curl(client, """{"code":%{response_code}}""", "https://j.example.com") + + if response['code'] != 200: + raise Exception(f"Code is {response['code']}") + ''; + }; +}