Merge branch 'main' into audiobookshelf
This commit is contained in:
commit
3bf84c732e
17 changed files with 1045 additions and 175 deletions
|
@ -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
|
||||
|
|
38
demo/homeassistant/flake.lock
generated
38
demo/homeassistant/flake.lock
generated
|
@ -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": {
|
||||
|
|
|
@ -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 = [
|
||||
|
|
|
@ -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
|
||||
|
|
30
flake.lock
generated
30
flake.lock
generated
|
@ -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": 1707956935,
|
||||
"narHash": "sha256-ZL2TrjVsiFNKOYwYQozpbvQSwvtV/3Me7Zwhmdsfyu4=",
|
||||
"lastModified": 1709237383,
|
||||
"narHash": "sha256-cy6ArO4k5qTx+l5o+0mL9f5fa86tYUX3ozE1S+Txlds=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "a4d4fe8c5002202493e87ec8dbc91335ff55552c",
|
||||
"rev": "1536926ef5621b09bba54035ae2bb6d806d72ac8",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -51,11 +51,11 @@
|
|||
},
|
||||
"nixpkgs-stable": {
|
||||
"locked": {
|
||||
"lastModified": 1707603439,
|
||||
"narHash": "sha256-LodBVZ3+ehJP2azM5oj+JrhfNAAzmTJ/OwAIOn0RfZ0=",
|
||||
"lastModified": 1708819810,
|
||||
"narHash": "sha256-1KosU+ZFXf31GPeCBNxobZWMgHsSOJcrSFA6F2jhzdE=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "d8cd80616c8800feec0cab64331d7c3d5a1a6d98",
|
||||
"rev": "89a2a12e6c8c6a56c72eb3589982c8e2f89c70ea",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -67,11 +67,11 @@
|
|||
},
|
||||
"nixpkgs_2": {
|
||||
"locked": {
|
||||
"lastModified": 1707451808,
|
||||
"narHash": "sha256-UwDBUNHNRsYKFJzyTMVMTF5qS4xeJlWoeyJf+6vvamU=",
|
||||
"lastModified": 1708751719,
|
||||
"narHash": "sha256-0uWOKSpXJXmXswOvDM5Vk3blB74apFB6rNGWV5IjoN0=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "442d407992384ed9c0e6d352de75b69079904e4e",
|
||||
"rev": "f63ce824cd2f036216eb5f637dfef31e1a03ee89",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -112,11 +112,11 @@
|
|||
"nixpkgs-stable": "nixpkgs-stable"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1707842202,
|
||||
"narHash": "sha256-3dTBbCzHJBinwhsisGJHW1HLBsLbj91+a5ZDXt7ttW0=",
|
||||
"lastModified": 1708987867,
|
||||
"narHash": "sha256-k2lDaDWNTU5sBVHanYzjDKVDmk29RHIgdbbXu5sdzBA=",
|
||||
"owner": "Mic92",
|
||||
"repo": "sops-nix",
|
||||
"rev": "48afd3264ec52bee85231a7122612e2c5202fa74",
|
||||
"rev": "a1c8de14f60924fafe13aea66b46157f0150f4cf",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
|
|
@ -89,14 +89,22 @@
|
|||
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 "audiobookshelf" ./test/vm/audiobookshelf.nix)
|
||||
// (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)
|
||||
// (vm_test "monitoring" ./test/vm/monitoring.nix)
|
||||
// (vm_test "nextcloud" ./test/vm/nextcloud.nix)
|
||||
|
|
109
lib/default.nix
109
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
|
||||
mkdir -p $(dirname ${templatePath})
|
||||
ln -fs ${file} ${templatePath}
|
||||
rm ${newPath} || :
|
||||
sed ${sedPatterns} ${templatePath} > ${newPath}
|
||||
rm -f ${resultPath}
|
||||
${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
|
||||
[];
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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,22 +382,36 @@ in
|
|||
</BrandingOptions>
|
||||
'';
|
||||
in
|
||||
shblib.template ldapConfig "/var/lib/jellyfin/plugins/configurations/LDAP-Auth.xml" {
|
||||
"%LDAP_PASSWORD%" = "$(cat ${cfg.ldapPasswordFile})";
|
||||
}
|
||||
+ shblib.template ssoConfig "/var/lib/jellyfin/plugins/configurations/SSO-Auth.xml" {
|
||||
"%SSO_SECRET%" = "$(cat ${cfg.ssoSecretFile})";
|
||||
}
|
||||
+ shblib.template brandingConfig "/var/lib/jellyfin/config/branding.xml" {"%a%" = "%a%";};
|
||||
lib.strings.optionalString cfg.ldap.enable (shblib.replaceSecretsScript {
|
||||
file = ldapConfig;
|
||||
resultPath = "/var/lib/jellyfin/plugins/configurations/LDAP-Auth.xml";
|
||||
replacements = {
|
||||
"%LDAP_PASSWORD%" = "$(cat ${cfg.ldap.passwordFile})";
|
||||
};
|
||||
})
|
||||
+ lib.strings.optionalString cfg.sso.enable (shblib.replaceSecretsScript {
|
||||
file = ssoConfig;
|
||||
resultPath = "/var/lib/jellyfin/plugins/configurations/SSO-Auth.xml";
|
||||
replacements = {
|
||||
"%SSO_SECRET%" = "$(cat ${cfg.sso.secretFile})";
|
||||
};
|
||||
})
|
||||
+ 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";
|
||||
secretFile = 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}" ];
|
||||
}
|
||||
];
|
||||
|
||||
|
|
|
@ -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.";
|
||||
|
@ -239,6 +246,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.";
|
||||
|
@ -595,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 = {
|
||||
|
@ -649,10 +684,23 @@ 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" ];
|
||||
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";
|
||||
|
@ -829,8 +877,8 @@ in
|
|||
{
|
||||
id = cfg.apps.sso.clientID;
|
||||
description = "Nextcloud";
|
||||
secretFile = cfg.apps.sso.secretFileForAuthelia;
|
||||
public = "false";
|
||||
secret.source = cfg.apps.sso.secretFileForAuthelia;
|
||||
public = false;
|
||||
authorization_policy = cfg.apps.sso.authorization_policy;
|
||||
redirect_uris = [ "${protocol}://${fqdnWithPort}/apps/oidc_login/oidc" ];
|
||||
scopes = [
|
||||
|
|
|
@ -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}
|
||||
|
@ -281,6 +293,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
|
||||
|
@ -322,6 +343,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
|
||||
|
|
|
@ -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 = [
|
||||
{
|
||||
|
|
110
test/modules/lib.nix
Normal file
110
test/modules/lib.nix
Normal file
|
@ -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; } ];
|
||||
}
|
||||
);
|
||||
};
|
||||
}
|
|
@ -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" ];
|
||||
|
|
326
test/vm/jellyfin.nix
Normal file
326
test/vm/jellyfin.nix
Normal file
|
@ -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']}")
|
||||
'';
|
||||
};
|
||||
}
|
82
test/vm/lib.nix
Normal file
82
test/vm/lib.nix
Normal file
|
@ -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))
|
||||
'';
|
||||
};
|
||||
}
|
Loading…
Add table
Reference in a new issue