chore: format all files
This commit is contained in:
parent
eaad978e37
commit
9fed9a669f
77 changed files with 10265 additions and 8177 deletions
|
|
@ -5,8 +5,8 @@ let
|
|||
targetPort = 2222;
|
||||
in
|
||||
{
|
||||
imports =
|
||||
[ # Include the results of the hardware scan.
|
||||
imports = [
|
||||
# Include the results of the hardware scan.
|
||||
./hardware-configuration.nix
|
||||
];
|
||||
|
||||
|
|
@ -26,7 +26,10 @@ in
|
|||
|
||||
# Options above are needed to deploy in a VM.
|
||||
|
||||
nix.settings.experimental-features = [ "nix-command" "flakes" ];
|
||||
nix.settings.experimental-features = [
|
||||
"nix-command"
|
||||
"flakes"
|
||||
];
|
||||
|
||||
# We need to create the user we will deploy with.
|
||||
users.users.${targetUser} = {
|
||||
|
|
@ -41,9 +44,11 @@ in
|
|||
|
||||
# The user we're deploying with must be able to run sudo without password.
|
||||
security.sudo.extraRules = [
|
||||
{ users = [ targetUser ];
|
||||
{
|
||||
users = [ targetUser ];
|
||||
commands = [
|
||||
{ command = "ALL";
|
||||
{
|
||||
command = "ALL";
|
||||
options = [ "NOPASSWD" ];
|
||||
}
|
||||
];
|
||||
|
|
|
|||
|
|
@ -6,13 +6,20 @@
|
|||
sops-nix.url = "github:Mic92/sops-nix";
|
||||
};
|
||||
|
||||
outputs = inputs@{ self, selfhostblocks, sops-nix }:
|
||||
outputs =
|
||||
inputs@{
|
||||
self,
|
||||
selfhostblocks,
|
||||
sops-nix,
|
||||
}:
|
||||
let
|
||||
system = "x86_64-linux";
|
||||
nixpkgs' = selfhostblocks.lib.${system}.patchedNixpkgs;
|
||||
inherit (selfhostblocks.lib.${system}) pkgs;
|
||||
|
||||
basic = { config, ... }: {
|
||||
basic =
|
||||
{ config, ... }:
|
||||
{
|
||||
imports = [
|
||||
./configuration.nix
|
||||
selfhostblocks.nixosModules.authelia
|
||||
|
|
@ -67,7 +74,9 @@
|
|||
];
|
||||
};
|
||||
|
||||
ldap = { config, ... }: {
|
||||
ldap =
|
||||
{ config, ... }:
|
||||
{
|
||||
shb.lldap = {
|
||||
enable = true;
|
||||
domain = "example.com";
|
||||
|
|
@ -122,7 +131,9 @@
|
|||
specialArgs = inputs;
|
||||
};
|
||||
|
||||
basic = { config, ... }: {
|
||||
basic =
|
||||
{ config, ... }:
|
||||
{
|
||||
imports = [
|
||||
basic
|
||||
];
|
||||
|
|
@ -135,7 +146,9 @@
|
|||
};
|
||||
};
|
||||
|
||||
ldap = { config, ... }: {
|
||||
ldap =
|
||||
{ config, ... }:
|
||||
{
|
||||
imports = [
|
||||
basic
|
||||
ldap
|
||||
|
|
|
|||
|
|
@ -3,50 +3,63 @@
|
|||
# Do not modify this file! It was generated by ‘nixos-generate-config’
|
||||
# and may be overwritten by future invocations. Please make changes
|
||||
# to /etc/nixos/configuration.nix instead.
|
||||
{ config, lib, pkgs, modulesPath, ... }:
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
modulesPath,
|
||||
...
|
||||
}:
|
||||
|
||||
{
|
||||
imports =
|
||||
[ (modulesPath + "/profiles/qemu-guest.nix")
|
||||
imports = [
|
||||
(modulesPath + "/profiles/qemu-guest.nix")
|
||||
];
|
||||
|
||||
boot.initrd.availableKernelModules = [ "ata_piix" "uhci_hcd" "virtio_pci" "floppy" "sr_mod" "virtio_blk" ];
|
||||
boot.initrd.availableKernelModules = [
|
||||
"ata_piix"
|
||||
"uhci_hcd"
|
||||
"virtio_pci"
|
||||
"floppy"
|
||||
"sr_mod"
|
||||
"virtio_blk"
|
||||
];
|
||||
boot.initrd.kernelModules = [ ];
|
||||
boot.kernelModules = [ "kvm-intel" ];
|
||||
boot.extraModulePackages = [ ];
|
||||
|
||||
fileSystems."/" =
|
||||
{ device = "/dev/vda";
|
||||
fileSystems."/" = {
|
||||
device = "/dev/vda";
|
||||
fsType = "ext4";
|
||||
};
|
||||
|
||||
fileSystems."/nix/.ro-store" =
|
||||
{ device = "nix-store";
|
||||
fileSystems."/nix/.ro-store" = {
|
||||
device = "nix-store";
|
||||
fsType = "9p";
|
||||
};
|
||||
|
||||
fileSystems."/nix/.rw-store" =
|
||||
{ device = "tmpfs";
|
||||
fileSystems."/nix/.rw-store" = {
|
||||
device = "tmpfs";
|
||||
fsType = "tmpfs";
|
||||
};
|
||||
|
||||
fileSystems."/tmp/shared" =
|
||||
{ device = "shared";
|
||||
fileSystems."/tmp/shared" = {
|
||||
device = "shared";
|
||||
fsType = "9p";
|
||||
};
|
||||
|
||||
fileSystems."/tmp/xchg" =
|
||||
{ device = "xchg";
|
||||
fileSystems."/tmp/xchg" = {
|
||||
device = "xchg";
|
||||
fsType = "9p";
|
||||
};
|
||||
|
||||
fileSystems."/nix/store" =
|
||||
{ device = "overlay";
|
||||
fileSystems."/nix/store" = {
|
||||
device = "overlay";
|
||||
fsType = "overlay";
|
||||
};
|
||||
|
||||
fileSystems."/boot" =
|
||||
{ device = "/dev/vdb2";
|
||||
fileSystems."/boot" = {
|
||||
device = "/dev/vdb2";
|
||||
fsType = "vfat";
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@ let
|
|||
targetPort = 2222;
|
||||
in
|
||||
{
|
||||
imports =
|
||||
[ # Include the results of the hardware scan.
|
||||
imports = [
|
||||
# Include the results of the hardware scan.
|
||||
./hardware-configuration.nix
|
||||
];
|
||||
|
||||
|
|
@ -27,7 +27,10 @@ in
|
|||
|
||||
# Options above are needed to deploy in a VM.
|
||||
|
||||
nix.settings.experimental-features = [ "nix-command" "flakes" ];
|
||||
nix.settings.experimental-features = [
|
||||
"nix-command"
|
||||
"flakes"
|
||||
];
|
||||
|
||||
# We need to create the user we will deploy with.
|
||||
users.users.${targetUser} = {
|
||||
|
|
@ -42,9 +45,11 @@ in
|
|||
|
||||
# The user we're deploying with must be able to run sudo without password.
|
||||
security.sudo.extraRules = [
|
||||
{ users = [ targetUser ];
|
||||
{
|
||||
users = [ targetUser ];
|
||||
commands = [
|
||||
{ command = "ALL";
|
||||
{
|
||||
command = "ALL";
|
||||
options = [ "NOPASSWD" ];
|
||||
}
|
||||
];
|
||||
|
|
|
|||
|
|
@ -6,13 +6,20 @@
|
|||
sops-nix.url = "github:Mic92/sops-nix";
|
||||
};
|
||||
|
||||
outputs = inputs@{ self, selfhostblocks, sops-nix }:
|
||||
outputs =
|
||||
inputs@{
|
||||
self,
|
||||
selfhostblocks,
|
||||
sops-nix,
|
||||
}:
|
||||
let
|
||||
system = "x86_64-linux";
|
||||
nixpkgs' = selfhostblocks.lib.${system}.patchedNixpkgs;
|
||||
inherit (selfhostblocks.lib.${system}) pkgs;
|
||||
|
||||
basic = { config, ... }: {
|
||||
basic =
|
||||
{ config, ... }:
|
||||
{
|
||||
imports = [
|
||||
./configuration.nix
|
||||
selfhostblocks.nixosModules.authelia
|
||||
|
|
@ -49,7 +56,9 @@
|
|||
shb.nginx.debugLog = false;
|
||||
};
|
||||
|
||||
ldap = { config, ... }: {
|
||||
ldap =
|
||||
{ config, ... }:
|
||||
{
|
||||
shb.lldap = {
|
||||
enable = true;
|
||||
domain = "example.com";
|
||||
|
|
@ -78,7 +87,9 @@
|
|||
};
|
||||
};
|
||||
|
||||
sso = { config, lib, ... }: {
|
||||
sso =
|
||||
{ config, lib, ... }:
|
||||
{
|
||||
shb.certs = {
|
||||
cas.selfsigned.myca = {
|
||||
name = "My CA";
|
||||
|
|
@ -103,8 +114,7 @@
|
|||
domain-needed = true;
|
||||
# no-resolv = true;
|
||||
bogus-priv = true;
|
||||
address =
|
||||
map (hostname: "/${hostname}/127.0.0.1") [
|
||||
address = map (hostname: "/${hostname}/127.0.0.1") [
|
||||
"example.com"
|
||||
"n.example.com"
|
||||
"ldap.example.com"
|
||||
|
|
@ -136,10 +146,14 @@
|
|||
request = config.shb.authelia.secrets.ldapAdminPassword.request;
|
||||
settings.key = "lldap/user_password";
|
||||
};
|
||||
shb.sops.secret."authelia/session_secret".request = config.shb.authelia.secrets.sessionSecret.request;
|
||||
shb.sops.secret."authelia/storage_encryption_key".request = config.shb.authelia.secrets.storageEncryptionKey.request;
|
||||
shb.sops.secret."authelia/hmac_secret".request = config.shb.authelia.secrets.identityProvidersOIDCHMACSecret.request;
|
||||
shb.sops.secret."authelia/private_key".request = config.shb.authelia.secrets.identityProvidersOIDCIssuerPrivateKey.request;
|
||||
shb.sops.secret."authelia/session_secret".request =
|
||||
config.shb.authelia.secrets.sessionSecret.request;
|
||||
shb.sops.secret."authelia/storage_encryption_key".request =
|
||||
config.shb.authelia.secrets.storageEncryptionKey.request;
|
||||
shb.sops.secret."authelia/hmac_secret".request =
|
||||
config.shb.authelia.secrets.identityProvidersOIDCHMACSecret.request;
|
||||
shb.sops.secret."authelia/private_key".request =
|
||||
config.shb.authelia.secrets.identityProvidersOIDCIssuerPrivateKey.request;
|
||||
|
||||
shb.nextcloud.apps.sso = {
|
||||
enable = true;
|
||||
|
|
@ -198,7 +212,9 @@
|
|||
specialArgs = inputs;
|
||||
};
|
||||
|
||||
basic = { config, ... }: {
|
||||
basic =
|
||||
{ config, ... }:
|
||||
{
|
||||
imports = [
|
||||
basic
|
||||
];
|
||||
|
|
@ -210,7 +226,9 @@
|
|||
};
|
||||
};
|
||||
|
||||
ldap = { config, ... }: {
|
||||
ldap =
|
||||
{ config, ... }:
|
||||
{
|
||||
imports = [
|
||||
basic
|
||||
ldap
|
||||
|
|
@ -223,7 +241,9 @@
|
|||
};
|
||||
};
|
||||
|
||||
sso = { config, ... }: {
|
||||
sso =
|
||||
{ config, ... }:
|
||||
{
|
||||
imports = [
|
||||
basic
|
||||
ldap
|
||||
|
|
|
|||
|
|
@ -3,50 +3,63 @@
|
|||
# Do not modify this file! It was generated by ‘nixos-generate-config’
|
||||
# and may be overwritten by future invocations. Please make changes
|
||||
# to /etc/nixos/configuration.nix instead.
|
||||
{ config, lib, pkgs, modulesPath, ... }:
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
modulesPath,
|
||||
...
|
||||
}:
|
||||
|
||||
{
|
||||
imports =
|
||||
[ (modulesPath + "/profiles/qemu-guest.nix")
|
||||
imports = [
|
||||
(modulesPath + "/profiles/qemu-guest.nix")
|
||||
];
|
||||
|
||||
boot.initrd.availableKernelModules = [ "ata_piix" "uhci_hcd" "virtio_pci" "floppy" "sr_mod" "virtio_blk" ];
|
||||
boot.initrd.availableKernelModules = [
|
||||
"ata_piix"
|
||||
"uhci_hcd"
|
||||
"virtio_pci"
|
||||
"floppy"
|
||||
"sr_mod"
|
||||
"virtio_blk"
|
||||
];
|
||||
boot.initrd.kernelModules = [ ];
|
||||
boot.kernelModules = [ "kvm-intel" ];
|
||||
boot.extraModulePackages = [ ];
|
||||
|
||||
fileSystems."/" =
|
||||
{ device = "/dev/vda";
|
||||
fileSystems."/" = {
|
||||
device = "/dev/vda";
|
||||
fsType = "ext4";
|
||||
};
|
||||
|
||||
fileSystems."/nix/.ro-store" =
|
||||
{ device = "nix-store";
|
||||
fileSystems."/nix/.ro-store" = {
|
||||
device = "nix-store";
|
||||
fsType = "9p";
|
||||
};
|
||||
|
||||
fileSystems."/nix/.rw-store" =
|
||||
{ device = "tmpfs";
|
||||
fileSystems."/nix/.rw-store" = {
|
||||
device = "tmpfs";
|
||||
fsType = "tmpfs";
|
||||
};
|
||||
|
||||
fileSystems."/tmp/shared" =
|
||||
{ device = "shared";
|
||||
fileSystems."/tmp/shared" = {
|
||||
device = "shared";
|
||||
fsType = "9p";
|
||||
};
|
||||
|
||||
fileSystems."/tmp/xchg" =
|
||||
{ device = "xchg";
|
||||
fileSystems."/tmp/xchg" = {
|
||||
device = "xchg";
|
||||
fsType = "9p";
|
||||
};
|
||||
|
||||
fileSystems."/nix/store" =
|
||||
{ device = "overlay";
|
||||
fileSystems."/nix/store" = {
|
||||
device = "overlay";
|
||||
fsType = "overlay";
|
||||
};
|
||||
|
||||
fileSystems."/boot" =
|
||||
{ device = "/dev/vdb2";
|
||||
fileSystems."/boot" = {
|
||||
device = "/dev/vdb2";
|
||||
fsType = "vfat";
|
||||
};
|
||||
|
||||
|
|
|
|||
115
docs/default.nix
115
docs/default.nix
|
|
@ -1,40 +1,49 @@
|
|||
# Taken nearly verbatim from https://github.com/nix-community/home-manager/pull/4673
|
||||
# Read these docs online at https://shb.skarabox.com.
|
||||
{ pkgs
|
||||
, buildPackages
|
||||
, lib
|
||||
, nmdsrc
|
||||
, stdenv
|
||||
, documentation-highlighter
|
||||
, nixos-render-docs
|
||||
{
|
||||
pkgs,
|
||||
buildPackages,
|
||||
lib,
|
||||
nmdsrc,
|
||||
stdenv,
|
||||
documentation-highlighter,
|
||||
nixos-render-docs,
|
||||
|
||||
, release
|
||||
, allModules
|
||||
release,
|
||||
allModules,
|
||||
|
||||
, version ? builtins.readFile ../VERSION
|
||||
, substituteVersionIn
|
||||
version ? builtins.readFile ../VERSION,
|
||||
substituteVersionIn,
|
||||
|
||||
, modules
|
||||
modules,
|
||||
}:
|
||||
|
||||
let
|
||||
shbPath = toString ./..;
|
||||
|
||||
gitHubDeclaration = user: repo: subpath:
|
||||
let urlRef = "main";
|
||||
gitHubDeclaration =
|
||||
user: repo: subpath:
|
||||
let
|
||||
urlRef = "main";
|
||||
end = if subpath == "" then "" else "/" + subpath;
|
||||
in {
|
||||
in
|
||||
{
|
||||
url = "https://github.com/${user}/${repo}/blob/${urlRef}${end}";
|
||||
name = "<${repo}${end}>";
|
||||
};
|
||||
|
||||
ghRoot = (gitHubDeclaration "ibizaman" "selfhostblocks" "").url;
|
||||
|
||||
buildOptionsDocs = { modules, filterOptionPath ? null }: args:
|
||||
buildOptionsDocs =
|
||||
{
|
||||
modules,
|
||||
filterOptionPath ? null,
|
||||
}:
|
||||
args:
|
||||
let
|
||||
config = {
|
||||
_module.check = false;
|
||||
_module.args = {};
|
||||
_module.args = { };
|
||||
system.stateVersion = "22.11";
|
||||
};
|
||||
|
||||
|
|
@ -52,41 +61,56 @@ let
|
|||
};
|
||||
|
||||
options = lib.setAttrByPath filterOptionPath (lib.getAttrFromPath filterOptionPath eval.options);
|
||||
in buildPackages.nixosOptionsDoc ({
|
||||
in
|
||||
buildPackages.nixosOptionsDoc (
|
||||
{
|
||||
inherit options;
|
||||
|
||||
transformOptions = opt:
|
||||
opt // {
|
||||
transformOptions =
|
||||
opt:
|
||||
opt
|
||||
// {
|
||||
# Clean up declaration sites to not refer to the Home Manager
|
||||
# source tree.
|
||||
declarations = map (decl:
|
||||
gitHubDeclaration "ibizaman" "selfhostblocks"
|
||||
(lib.removePrefix "/" (lib.removePrefix shbPath (toString decl)))) opt.declarations;
|
||||
declarations = map (
|
||||
decl:
|
||||
gitHubDeclaration "ibizaman" "selfhostblocks" (
|
||||
lib.removePrefix "/" (lib.removePrefix shbPath (toString decl))
|
||||
)
|
||||
) opt.declarations;
|
||||
};
|
||||
} // builtins.removeAttrs args [ "includeModuleSystemOptions" ]);
|
||||
}
|
||||
// builtins.removeAttrs args [ "includeModuleSystemOptions" ]
|
||||
);
|
||||
|
||||
scrubbedModule = {
|
||||
_module.args.pkgs = lib.mkForce (nmd.scrubDerivations "pkgs" pkgs);
|
||||
_module.check = false;
|
||||
};
|
||||
|
||||
allOptionsDocs = paths: (buildOptionsDocs
|
||||
allOptionsDocs =
|
||||
paths:
|
||||
(buildOptionsDocs
|
||||
{
|
||||
modules = paths ++ allModules ++ [ scrubbedModule ];
|
||||
filterOptionPath = [ "shb" ];
|
||||
}
|
||||
{
|
||||
variablelistId = "selfhostblocks-options";
|
||||
}).optionsJSON;
|
||||
}
|
||||
).optionsJSON;
|
||||
|
||||
individualModuleOptionsDocs = filterOptionPath: paths: (buildOptionsDocs
|
||||
individualModuleOptionsDocs =
|
||||
filterOptionPath: paths:
|
||||
(buildOptionsDocs
|
||||
{
|
||||
modules = paths ++ [ scrubbedModule ];
|
||||
inherit filterOptionPath;
|
||||
}
|
||||
{
|
||||
variablelistId = "selfhostblocks-options";
|
||||
}).optionsJSON;
|
||||
}
|
||||
).optionsJSON;
|
||||
|
||||
nmd = import nmdsrc {
|
||||
inherit lib;
|
||||
|
|
@ -94,15 +118,15 @@ let
|
|||
# `nmd` uses to work around the broken stylesheets in
|
||||
# `docbook-xsl-ns`, so we restore the patched version here.
|
||||
pkgs = pkgs // {
|
||||
docbook-xsl-ns =
|
||||
pkgs.docbook-xsl-ns.override { withManOptDedupPatch = true; };
|
||||
docbook-xsl-ns = pkgs.docbook-xsl-ns.override { withManOptDedupPatch = true; };
|
||||
};
|
||||
};
|
||||
|
||||
outputPath = "share/doc/selfhostblocks";
|
||||
|
||||
manpage-urls = pkgs.writeText "manpage-urls.json" ''{}'';
|
||||
in stdenv.mkDerivation {
|
||||
in
|
||||
stdenv.mkDerivation {
|
||||
name = "self-host-blocks-manual";
|
||||
|
||||
nativeBuildInputs = [ nixos-render-docs ];
|
||||
|
|
@ -135,28 +159,41 @@ in stdenv.mkDerivation {
|
|||
${nmdsrc}/static/highlightjs/highlight.load.js
|
||||
|
||||
''
|
||||
+ lib.concatStringsSep "\n" (map (m: ''
|
||||
+ lib.concatStringsSep "\n" (
|
||||
map (m: ''
|
||||
substituteInPlace ${m} --replace '@VERSION@' ${version}
|
||||
'') substituteVersionIn)
|
||||
'') substituteVersionIn
|
||||
)
|
||||
+ ''
|
||||
substituteInPlace ./options.md \
|
||||
--replace \
|
||||
'@OPTIONS_JSON@' \
|
||||
${allOptionsDocs [
|
||||
${
|
||||
allOptionsDocs [
|
||||
(pkgs.path + "/nixos/modules/services/misc/forgejo.nix")
|
||||
]}/share/doc/nixos/options.json
|
||||
]
|
||||
}/share/doc/nixos/options.json
|
||||
''
|
||||
+ lib.concatStringsSep "\n" (lib.mapAttrsToList (name: cfg':
|
||||
+ lib.concatStringsSep "\n" (
|
||||
lib.mapAttrsToList (
|
||||
name: cfg':
|
||||
let
|
||||
cfg = if builtins.isAttrs cfg' then cfg' else { module = cfg'; };
|
||||
module = if builtins.isList cfg.module then cfg.module else [ cfg.module ];
|
||||
optionRoot = cfg.optionRoot or [ "shb" (lib.last (lib.splitString "/" name)) ];
|
||||
in ''
|
||||
optionRoot =
|
||||
cfg.optionRoot or [
|
||||
"shb"
|
||||
(lib.last (lib.splitString "/" name))
|
||||
];
|
||||
in
|
||||
''
|
||||
substituteInPlace ./modules/${name}/docs/default.md \
|
||||
--replace-fail \
|
||||
'@OPTIONS_JSON@' \
|
||||
${individualModuleOptionsDocs optionRoot module}/share/doc/nixos/options.json
|
||||
'') modules)
|
||||
''
|
||||
) modules
|
||||
)
|
||||
+ ''
|
||||
find . -name "*.md" -print0 | \
|
||||
while IFS= read -r -d ''' f; do
|
||||
|
|
|
|||
153
flake.nix
153
flake.nix
|
|
@ -11,7 +11,17 @@
|
|||
};
|
||||
};
|
||||
|
||||
outputs = inputs@{ self, nixpkgs, nix-flake-tests, flake-utils, nmdsrc, ... }: flake-utils.lib.eachDefaultSystem (system:
|
||||
outputs =
|
||||
inputs@{
|
||||
self,
|
||||
nixpkgs,
|
||||
nix-flake-tests,
|
||||
flake-utils,
|
||||
nmdsrc,
|
||||
...
|
||||
}:
|
||||
flake-utils.lib.eachDefaultSystem (
|
||||
system:
|
||||
let
|
||||
originPkgs = nixpkgs.legacyPackages.${system};
|
||||
shbPatches = originPkgs.lib.optionals (system == "x86_64-linux") [
|
||||
|
|
@ -24,20 +34,24 @@
|
|||
# hash = "sha256-hoLrqV7XtR1hP/m0rV9hjYUBtrSjay0qcPUYlKKuVWk=";
|
||||
# })
|
||||
];
|
||||
patchNixpkgs = {
|
||||
patchNixpkgs =
|
||||
{
|
||||
nixpkgs,
|
||||
patches,
|
||||
system,
|
||||
}: nixpkgs.legacyPackages.${system}.applyPatches {
|
||||
}:
|
||||
nixpkgs.legacyPackages.${system}.applyPatches {
|
||||
name = "nixpkgs-patched";
|
||||
src = nixpkgs;
|
||||
inherit patches;
|
||||
};
|
||||
patchedNixpkgs = (patchNixpkgs {
|
||||
patchedNixpkgs = (
|
||||
patchNixpkgs {
|
||||
nixpkgs = inputs.nixpkgs;
|
||||
patches = shbPatches;
|
||||
inherit system;
|
||||
});
|
||||
}
|
||||
);
|
||||
pkgs = import patchedNixpkgs {
|
||||
inherit system;
|
||||
config.allowUnfree = true;
|
||||
|
|
@ -45,11 +59,18 @@
|
|||
(final: prev: {
|
||||
lib = prev.lib // {
|
||||
shb = self.lib.${system};
|
||||
evalModules = args: ((prev.lib.makeOverridable prev.lib.evalModules) args).override (prevAttrs: {
|
||||
specialArgs = (prevAttrs.specialArgs or {}) // { inherit (pkgs) lib; };
|
||||
evalModules =
|
||||
args:
|
||||
((prev.lib.makeOverridable prev.lib.evalModules) args).override (prevAttrs: {
|
||||
specialArgs = (prevAttrs.specialArgs or { }) // {
|
||||
inherit (pkgs) lib;
|
||||
};
|
||||
});
|
||||
};
|
||||
nixosSystem = args: ((prev.lib.makeOverridable (import "${patchedNixpkgs}/nixos/lib/eval-config.nix")) args).override (prevAttrs: {
|
||||
nixosSystem =
|
||||
args:
|
||||
((prev.lib.makeOverridable (import "${patchedNixpkgs}/nixos/lib/eval-config.nix")) args).override
|
||||
(prevAttrs: {
|
||||
inherit (pkgs) lib;
|
||||
});
|
||||
})
|
||||
|
|
@ -67,10 +88,12 @@
|
|||
|
||||
packages.manualHtml = pkgs.callPackage ./docs {
|
||||
inherit nmdsrc;
|
||||
allModules = self.nixosModules.default.imports
|
||||
allModules =
|
||||
self.nixosModules.default.imports
|
||||
++ [
|
||||
self.nixosModules.sops
|
||||
] ++ contractDummyModules;
|
||||
]
|
||||
++ contractDummyModules;
|
||||
release = builtins.readFile ./VERSION;
|
||||
|
||||
substituteVersionIn = [
|
||||
|
|
@ -82,7 +105,10 @@
|
|||
"blocks/lldap" = ./modules/blocks/lldap.nix;
|
||||
"blocks/ssl" = {
|
||||
module = ./modules/blocks/ssl.nix;
|
||||
optionRoot = [ "shb" "certs" ];
|
||||
optionRoot = [
|
||||
"shb"
|
||||
"certs"
|
||||
];
|
||||
};
|
||||
"blocks/mitmdump" = ./modules/blocks/mitmdump.nix;
|
||||
"blocks/monitoring" = ./modules/blocks/monitoring.nix;
|
||||
|
|
@ -99,26 +125,45 @@
|
|||
"services/karakeep" = ./modules/services/karakeep.nix;
|
||||
"services/nextcloud-server" = {
|
||||
module = ./modules/services/nextcloud-server.nix;
|
||||
optionRoot = [ "shb" "nextcloud" ];
|
||||
optionRoot = [
|
||||
"shb"
|
||||
"nextcloud"
|
||||
];
|
||||
};
|
||||
"services/open-webui" = ./modules/services/open-webui.nix;
|
||||
"services/pinchflat" = ./modules/services/pinchflat.nix;
|
||||
"services/vaultwarden" = ./modules/services/vaultwarden.nix;
|
||||
"contracts/backup" = {
|
||||
module = ./modules/contracts/backup/dummyModule.nix;
|
||||
optionRoot = [ "shb" "contracts" "backup" ];
|
||||
optionRoot = [
|
||||
"shb"
|
||||
"contracts"
|
||||
"backup"
|
||||
];
|
||||
};
|
||||
"contracts/databasebackup" = {
|
||||
module = ./modules/contracts/databasebackup/dummyModule.nix;
|
||||
optionRoot = [ "shb" "contracts" "databasebackup" ];
|
||||
optionRoot = [
|
||||
"shb"
|
||||
"contracts"
|
||||
"databasebackup"
|
||||
];
|
||||
};
|
||||
"contracts/secret" = {
|
||||
module = ./modules/contracts/secret/dummyModule.nix;
|
||||
optionRoot = [ "shb" "contracts" "secret" ];
|
||||
optionRoot = [
|
||||
"shb"
|
||||
"contracts"
|
||||
"secret"
|
||||
];
|
||||
};
|
||||
"contracts/ssl" = {
|
||||
module = ./modules/contracts/ssl/dummyModule.nix;
|
||||
optionRoot = [ "shb" "contracts" "ssl" ];
|
||||
optionRoot = [
|
||||
"shb"
|
||||
"contracts"
|
||||
"ssl"
|
||||
];
|
||||
};
|
||||
};
|
||||
};
|
||||
|
|
@ -165,7 +210,8 @@
|
|||
in
|
||||
(self.packages.${system}.manualHtml.override {
|
||||
nixos-render-docs = nixos-render-docs-patched;
|
||||
}).overrideAttrs (old: {
|
||||
}).overrideAttrs
|
||||
(old: {
|
||||
installPhase = ''
|
||||
${old.installPhase}
|
||||
ln -sf share/doc/selfhostblocks/redirects.json $out/redirects.json
|
||||
|
|
@ -173,35 +219,52 @@
|
|||
});
|
||||
|
||||
lib =
|
||||
(pkgs.callPackage ./lib {})
|
||||
// (pkgs.callPackage ./test/common.nix {})
|
||||
(pkgs.callPackage ./lib { })
|
||||
// (pkgs.callPackage ./test/common.nix { })
|
||||
// {
|
||||
contracts = pkgs.callPackage ./modules/contracts {};
|
||||
contracts = pkgs.callPackage ./modules/contracts { };
|
||||
patches = shbPatches;
|
||||
inherit patchNixpkgs patchedNixpkgs pkgs;
|
||||
};
|
||||
|
||||
checks =
|
||||
let
|
||||
inherit (pkgs.lib) foldl foldlAttrs removeAttrs mergeAttrs optionalAttrs;
|
||||
inherit (pkgs.lib)
|
||||
foldl
|
||||
foldlAttrs
|
||||
removeAttrs
|
||||
mergeAttrs
|
||||
optionalAttrs
|
||||
;
|
||||
|
||||
importFiles = files:
|
||||
map (m: pkgs.callPackage m {}) files;
|
||||
importFiles = files: map (m: pkgs.callPackage m { }) files;
|
||||
|
||||
mergeTests = foldl mergeAttrs {};
|
||||
mergeTests = foldl mergeAttrs { };
|
||||
|
||||
flattenAttrs = root: attrset: foldlAttrs (acc: name: value: acc // {
|
||||
flattenAttrs =
|
||||
root: attrset:
|
||||
foldlAttrs (
|
||||
acc: name: value:
|
||||
acc
|
||||
// {
|
||||
"${root}_${name}" = value;
|
||||
}) {} attrset;
|
||||
}
|
||||
) { } attrset;
|
||||
|
||||
vm_test = name: path: flattenAttrs "vm_${name}" (
|
||||
removeAttrs (pkgs.callPackage path {}) [ "override" "overrideDerivation" ]
|
||||
vm_test =
|
||||
name: path:
|
||||
flattenAttrs "vm_${name}" (
|
||||
removeAttrs (pkgs.callPackage path { }) [
|
||||
"override"
|
||||
"overrideDerivation"
|
||||
]
|
||||
);
|
||||
in (optionalAttrs (system == "x86_64-linux") ({
|
||||
in
|
||||
(optionalAttrs (system == "x86_64-linux") (
|
||||
{
|
||||
modules = pkgs.lib.shb.check {
|
||||
inherit pkgs;
|
||||
tests =
|
||||
mergeTests (importFiles [
|
||||
tests = mergeTests (importFiles [
|
||||
./test/modules/davfs.nix
|
||||
# TODO: Make this not use IFD
|
||||
./test/modules/lib.nix
|
||||
|
|
@ -211,7 +274,7 @@
|
|||
# TODO: Make this not use IFD
|
||||
lib = nix-flake-tests.lib.check {
|
||||
inherit pkgs;
|
||||
tests = pkgs.callPackage ./test/modules/lib.nix {};
|
||||
tests = pkgs.callPackage ./test/modules/lib.nix { };
|
||||
};
|
||||
}
|
||||
// (vm_test "arr" ./test/services/arr.nix)
|
||||
|
|
@ -245,8 +308,13 @@
|
|||
|
||||
# To see the traces, run:
|
||||
# nix run .#playwright -- show-trace $(nix eval .#checks.x86_64-linux.vm_grocy_basic --raw)/trace/0.zip
|
||||
packages.playwright =
|
||||
pkgs.callPackage ({ stdenvNoCC, makeWrapper, playwright }: stdenvNoCC.mkDerivation {
|
||||
packages.playwright = pkgs.callPackage (
|
||||
{
|
||||
stdenvNoCC,
|
||||
makeWrapper,
|
||||
playwright,
|
||||
}:
|
||||
stdenvNoCC.mkDerivation {
|
||||
name = "playwright";
|
||||
|
||||
src = playwright;
|
||||
|
|
@ -261,14 +329,19 @@
|
|||
--set PLAYWRIGHT_BROWSERS_PATH ${pkgs.playwright-driver.browsers} \
|
||||
--set PLAYWRIGHT_SKIP_VALIDATE_HOST_REQUIREMENTS true
|
||||
'';
|
||||
}) {};
|
||||
}
|
||||
) { };
|
||||
|
||||
# Run "nix run .#update-redirects" to regenerate docs/redirects.json
|
||||
apps.update-redirects = {
|
||||
type = "app";
|
||||
program = "${pkgs.writeShellApplication {
|
||||
program = "${
|
||||
pkgs.writeShellApplication {
|
||||
name = "update-redirects";
|
||||
runtimeInputs = [ pkgs.nix pkgs.jq ];
|
||||
runtimeInputs = [
|
||||
pkgs.nix
|
||||
pkgs.jq
|
||||
];
|
||||
text = ''
|
||||
echo "=== SelfHostBlocks Redirects Updater ==="
|
||||
echo "Generating fresh ./docs/redirects.json..."
|
||||
|
|
@ -283,10 +356,12 @@
|
|||
echo " Updated docs/redirects.json"
|
||||
echo "To verify: nix build .#manualHtml"
|
||||
'';
|
||||
}}/bin/update-redirects";
|
||||
}
|
||||
}/bin/update-redirects";
|
||||
};
|
||||
}
|
||||
) // {
|
||||
)
|
||||
// {
|
||||
herculesCI.ciSystems = [ "x86_64-linux" ];
|
||||
|
||||
nixosModules.default = {
|
||||
|
|
|
|||
258
lib/default.nix
258
lib/default.nix
|
|
@ -9,7 +9,14 @@ rec {
|
|||
# - resultPath is the location the config file should have on the filesystem.
|
||||
# - generator is a function taking two arguments name and value and returning path in the nix
|
||||
# nix store where the
|
||||
replaceSecrets = { userConfig, resultPath, generator, user ? null, permissions ? "u=r,g=r,o=" }:
|
||||
replaceSecrets =
|
||||
{
|
||||
userConfig,
|
||||
resultPath,
|
||||
generator,
|
||||
user ? null,
|
||||
permissions ? "u=r,g=r,o=",
|
||||
}:
|
||||
let
|
||||
configWithTemplates = withReplacements userConfig;
|
||||
|
||||
|
|
@ -24,21 +31,40 @@ rec {
|
|||
};
|
||||
|
||||
replaceSecretsFormatAdapter = format: format.generate;
|
||||
replaceSecretsGeneratorAdapter = generator: name: value: pkgs.writeText "generator " (generator value);
|
||||
toEnvVar = replaceSecretsGeneratorAdapter (v: (lib.generators.toINIWithGlobalSection {} { globalSection = v; }));
|
||||
replaceSecretsGeneratorAdapter =
|
||||
generator: name: value:
|
||||
pkgs.writeText "generator " (generator value);
|
||||
toEnvVar = replaceSecretsGeneratorAdapter (
|
||||
v: (lib.generators.toINIWithGlobalSection { } { globalSection = v; })
|
||||
);
|
||||
|
||||
template = file: newPath: replacements: replaceSecretsScript {
|
||||
template =
|
||||
file: newPath: replacements:
|
||||
replaceSecretsScript {
|
||||
inherit file replacements;
|
||||
resultPath = newPath;
|
||||
};
|
||||
|
||||
genReplacement = secret:
|
||||
genReplacement =
|
||||
secret:
|
||||
let
|
||||
t = { transform ? null, ... }: if isNull transform then x: x else transform;
|
||||
t =
|
||||
{
|
||||
transform ? null,
|
||||
...
|
||||
}:
|
||||
if isNull transform then x: x else transform;
|
||||
in
|
||||
lib.attrsets.nameValuePair (secretName secret.name) ((t secret) "$(cat ${toString secret.source})");
|
||||
|
||||
replaceSecretsScript = { file, resultPath, replacements, user ? null, permissions ? "u=r,g=r,o=" }:
|
||||
replaceSecretsScript =
|
||||
{
|
||||
file,
|
||||
resultPath,
|
||||
replacements,
|
||||
user ? null,
|
||||
permissions ? "u=r,g=r,o=",
|
||||
}:
|
||||
let
|
||||
templatePath = resultPath + ".template";
|
||||
|
||||
|
|
@ -47,13 +73,15 @@ rec {
|
|||
# step. Otherwise, the $(cat ...) commands inside the sed
|
||||
# replacements could fail but not fail individually but
|
||||
# not fail the whole script.
|
||||
checkPermissions = concatMapStringsSep "\n" (pattern: "cat ${pattern.source} > /dev/null") replacements;
|
||||
checkPermissions = concatMapStringsSep "\n" (
|
||||
pattern: "cat ${pattern.source} > /dev/null"
|
||||
) replacements;
|
||||
|
||||
sedPatterns = concatMapStringsSep " " (pattern: "-e \"s|${pattern.name}|${pattern.value}|\"") (map genReplacement replacements);
|
||||
sedPatterns = concatMapStringsSep " " (pattern: "-e \"s|${pattern.name}|${pattern.value}|\"") (
|
||||
map genReplacement replacements
|
||||
);
|
||||
|
||||
sedCmd = if replacements == []
|
||||
then "cat"
|
||||
else "${pkgs.gnused}/bin/sed ${sedPatterns}";
|
||||
sedCmd = if replacements == [ ] then "cat" else "${pkgs.gnused}/bin/sed ${sedPatterns}";
|
||||
in
|
||||
''
|
||||
set -euo pipefail
|
||||
|
|
@ -64,9 +92,11 @@ rec {
|
|||
ln -fs ${file} ${templatePath}
|
||||
rm -f ${resultPath}
|
||||
touch ${resultPath}
|
||||
'' + (lib.optionalString (user != null) ''
|
||||
''
|
||||
+ (lib.optionalString (user != null) ''
|
||||
chown ${user} ${resultPath}
|
||||
'') + ''
|
||||
'')
|
||||
+ ''
|
||||
${sedCmd} ${templatePath} > ${resultPath}
|
||||
chmod ${permissions} ${resultPath}
|
||||
'';
|
||||
|
|
@ -89,26 +119,25 @@ rec {
|
|||
};
|
||||
};
|
||||
|
||||
secretName = names:
|
||||
"%SECRET${lib.strings.toUpper (lib.strings.concatMapStrings (s: "_" + s) names)}%";
|
||||
secretName =
|
||||
names: "%SECRET${lib.strings.toUpper (lib.strings.concatMapStrings (s: "_" + s) names)}%";
|
||||
|
||||
withReplacements = attrs:
|
||||
withReplacements =
|
||||
attrs:
|
||||
let
|
||||
valueOrReplacement = name: value:
|
||||
if !(builtins.isAttrs value && value ? "source")
|
||||
then value
|
||||
else secretName name;
|
||||
valueOrReplacement =
|
||||
name: value: if !(builtins.isAttrs value && value ? "source") then value else secretName name;
|
||||
in
|
||||
mapAttrsRecursiveCond (v: ! v ? "source") valueOrReplacement attrs;
|
||||
mapAttrsRecursiveCond (v: !v ? "source") valueOrReplacement attrs;
|
||||
|
||||
getReplacements = attrs:
|
||||
getReplacements =
|
||||
attrs:
|
||||
let
|
||||
addNameField = name: value:
|
||||
if !(builtins.isAttrs value && value ? "source")
|
||||
then value
|
||||
else value // { name = name; };
|
||||
addNameField =
|
||||
name: value:
|
||||
if !(builtins.isAttrs value && value ? "source") then value else value // { name = name; };
|
||||
|
||||
secretsWithName = mapAttrsRecursiveCond (v: ! v ? "source") addNameField attrs;
|
||||
secretsWithName = mapAttrsRecursiveCond (v: !v ? "source") addNameField attrs;
|
||||
in
|
||||
collect (v: builtins.isAttrs v && v ? "source") secretsWithName;
|
||||
|
||||
|
|
@ -121,13 +150,16 @@ rec {
|
|||
# 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;
|
||||
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 =
|
||||
|
|
@ -142,16 +174,23 @@ rec {
|
|||
else if builtins.isList attrs then
|
||||
lib.lists.concatMap (collect pred) attrs
|
||||
else
|
||||
[];
|
||||
[ ];
|
||||
|
||||
indent = i: str: lib.concatMapStringsSep "\n" (x: (lib.strings.replicate i " ") + x) (lib.splitString "\n" str);
|
||||
indent =
|
||||
i: str:
|
||||
lib.concatMapStringsSep "\n" (x: (lib.strings.replicate i " ") + x) (lib.splitString "\n" str);
|
||||
|
||||
# Generator for XML
|
||||
formatXML = {
|
||||
enclosingRoot ? null
|
||||
}: {
|
||||
type = with lib.types; let
|
||||
valueType = nullOr (oneOf [
|
||||
formatXML =
|
||||
{
|
||||
enclosingRoot ? null,
|
||||
}:
|
||||
{
|
||||
type =
|
||||
with lib.types;
|
||||
let
|
||||
valueType =
|
||||
nullOr (oneOf [
|
||||
bool
|
||||
int
|
||||
float
|
||||
|
|
@ -159,21 +198,31 @@ rec {
|
|||
path
|
||||
(attrsOf valueType)
|
||||
(listOf valueType)
|
||||
]) // {
|
||||
])
|
||||
// {
|
||||
description = "XML value";
|
||||
};
|
||||
in valueType;
|
||||
in
|
||||
valueType;
|
||||
|
||||
generate = name: value: pkgs.callPackage ({ runCommand, python3 }: runCommand "config" {
|
||||
value = builtins.toJSON (
|
||||
if enclosingRoot == null then
|
||||
value
|
||||
else
|
||||
{ ${enclosingRoot} = value; });
|
||||
generate =
|
||||
name: value:
|
||||
pkgs.callPackage (
|
||||
{ runCommand, python3 }:
|
||||
runCommand "config"
|
||||
{
|
||||
value = builtins.toJSON (if enclosingRoot == null then value else { ${enclosingRoot} = value; });
|
||||
passAsFile = [ "value" ];
|
||||
} (pkgs.writers.writePython3 "dict2xml" {
|
||||
libraries = with python3.pkgs; [ python dict2xml ];
|
||||
} ''
|
||||
}
|
||||
(
|
||||
pkgs.writers.writePython3 "dict2xml"
|
||||
{
|
||||
libraries = with python3.pkgs; [
|
||||
python
|
||||
dict2xml
|
||||
];
|
||||
}
|
||||
''
|
||||
import os
|
||||
import json
|
||||
from dict2xml import dict2xml
|
||||
|
|
@ -185,18 +234,28 @@ rec {
|
|||
os.exit(2)
|
||||
with open(os.environ["out"], "w") as out:
|
||||
out.write(dict2xml(content))
|
||||
'')) {};
|
||||
''
|
||||
)
|
||||
) { };
|
||||
|
||||
};
|
||||
|
||||
parseXML = xml:
|
||||
parseXML =
|
||||
xml:
|
||||
let
|
||||
xmlToJsonFile = pkgs.callPackage ({ runCommand, python3 }: runCommand "config" {
|
||||
xmlToJsonFile = pkgs.callPackage (
|
||||
{ runCommand, python3 }:
|
||||
runCommand "config"
|
||||
{
|
||||
inherit xml;
|
||||
passAsFile = [ "xml" ];
|
||||
} (pkgs.writers.writePython3 "xml2json" {
|
||||
}
|
||||
(
|
||||
pkgs.writers.writePython3 "xml2json"
|
||||
{
|
||||
libraries = with python3.pkgs; [ python ];
|
||||
} ''
|
||||
}
|
||||
''
|
||||
import os
|
||||
import json
|
||||
from collections import ChainMap
|
||||
|
|
@ -219,34 +278,54 @@ rec {
|
|||
|
||||
with open(os.environ["out"], "w") as out:
|
||||
out.write(j)
|
||||
'')) {};
|
||||
''
|
||||
)
|
||||
) { };
|
||||
in
|
||||
builtins.fromJSON (builtins.readFile xmlToJsonFile);
|
||||
|
||||
renameAttrName = attrset: from: to:
|
||||
(lib.attrsets.filterAttrs (name: v: name == from) attrset) // {
|
||||
renameAttrName =
|
||||
attrset: from: to:
|
||||
(lib.attrsets.filterAttrs (name: v: name == from) attrset)
|
||||
// {
|
||||
${to} = attrset.${from};
|
||||
};
|
||||
|
||||
# Taken from https://github.com/antifuchs/nix-flake-tests/blob/main/default.nix
|
||||
# with a nicer diff display function.
|
||||
check = { pkgs, tests }:
|
||||
check =
|
||||
{ pkgs, tests }:
|
||||
let
|
||||
formatValue = val:
|
||||
if (builtins.isList val || builtins.isAttrs val) then builtins.toJSON val
|
||||
else builtins.toString val;
|
||||
formatValue =
|
||||
val:
|
||||
if (builtins.isList val || builtins.isAttrs val) then
|
||||
builtins.toJSON val
|
||||
else
|
||||
builtins.toString val;
|
||||
|
||||
resultToString = { name, expected, result }:
|
||||
builtins.readFile (pkgs.runCommand "nix-flake-tests-error" {
|
||||
resultToString =
|
||||
{
|
||||
name,
|
||||
expected,
|
||||
result,
|
||||
}:
|
||||
builtins.readFile (
|
||||
pkgs.runCommand "nix-flake-tests-error"
|
||||
{
|
||||
expected = formatValue expected;
|
||||
result = formatValue result;
|
||||
passAsFile = [ "expected" "result" ];
|
||||
} ''
|
||||
passAsFile = [
|
||||
"expected"
|
||||
"result"
|
||||
];
|
||||
}
|
||||
''
|
||||
echo "${name} failed (- expected, + result)" > $out
|
||||
cp ''${expectedPath} ''${expectedPath}.json
|
||||
cp ''${resultPath} ''${resultPath}.json
|
||||
${pkgs.deepdiff}/bin/deep diff ''${expectedPath}.json ''${resultPath}.json >> $out
|
||||
'');
|
||||
''
|
||||
);
|
||||
|
||||
results = pkgs.lib.runTests tests;
|
||||
in
|
||||
|
|
@ -255,8 +334,14 @@ rec {
|
|||
else
|
||||
pkgs.runCommand "nix-flake-tests-success" { } "echo > $out";
|
||||
|
||||
|
||||
genConfigOutOfBandSystemd = { config, configLocation, generator, user ? null, permissions ? "u=r,g=r,o=" }:
|
||||
genConfigOutOfBandSystemd =
|
||||
{
|
||||
config,
|
||||
configLocation,
|
||||
generator,
|
||||
user ? null,
|
||||
permissions ? "u=r,g=r,o=",
|
||||
}:
|
||||
{
|
||||
loadCredentials = getLoadCredentials "source" config;
|
||||
preStart = lib.mkBefore (replaceSecrets {
|
||||
|
|
@ -267,32 +352,33 @@ rec {
|
|||
});
|
||||
};
|
||||
|
||||
updateToLoadCredentials = sourceField: rootDir: attrs:
|
||||
updateToLoadCredentials =
|
||||
sourceField: rootDir: attrs:
|
||||
let
|
||||
hasPlaceholderField = v: isAttrs v && hasAttr sourceField v;
|
||||
|
||||
valueOrLoadCredential = path: value:
|
||||
if ! (hasPlaceholderField value)
|
||||
then value
|
||||
else value // { ${sourceField} = rootDir + "/" + concatStringsSep "_" path; };
|
||||
valueOrLoadCredential =
|
||||
path: value:
|
||||
if !(hasPlaceholderField value) then
|
||||
value
|
||||
else
|
||||
value // { ${sourceField} = rootDir + "/" + concatStringsSep "_" path; };
|
||||
in
|
||||
mapAttrsRecursiveCond (v: ! (hasPlaceholderField v)) valueOrLoadCredential attrs;
|
||||
mapAttrsRecursiveCond (v: !(hasPlaceholderField v)) valueOrLoadCredential attrs;
|
||||
|
||||
getLoadCredentials = sourceField: attrs:
|
||||
getLoadCredentials =
|
||||
sourceField: attrs:
|
||||
let
|
||||
hasPlaceholderField = v: isAttrs v && hasAttr sourceField v;
|
||||
|
||||
addPathField = path: value:
|
||||
if ! (hasPlaceholderField value)
|
||||
then value
|
||||
else value // { inherit path; };
|
||||
addPathField =
|
||||
path: value: if !(hasPlaceholderField value) then value else value // { inherit path; };
|
||||
|
||||
secretsWithPath = mapAttrsRecursiveCond (v: ! (hasPlaceholderField v)) addPathField attrs;
|
||||
secretsWithPath = mapAttrsRecursiveCond (v: !(hasPlaceholderField v)) addPathField attrs;
|
||||
|
||||
allSecrets = collect (v: hasPlaceholderField v) secretsWithPath;
|
||||
|
||||
genLoadCredentials = secret:
|
||||
"${concatStringsSep "_" secret.path}:${secret.${sourceField}}";
|
||||
genLoadCredentials = secret: "${concatStringsSep "_" secret.path}:${secret.${sourceField}}";
|
||||
in
|
||||
map genLoadCredentials allSecrets;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,16 @@
|
|||
{ config, options, pkgs, lib, ... }:
|
||||
{
|
||||
config,
|
||||
options,
|
||||
pkgs,
|
||||
lib,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
cfg = config.shb.authelia;
|
||||
opt = options.shb.authelia;
|
||||
|
||||
contracts = pkgs.callPackage ../contracts {};
|
||||
contracts = pkgs.callPackage ../contracts { };
|
||||
|
||||
fqdn = "${cfg.subdomain}.${cfg.domain}";
|
||||
fqdnWithPort = if isNull cfg.port then fqdn else "${fqdn}:${toString cfg.port}";
|
||||
|
|
@ -148,25 +154,25 @@ in
|
|||
extraOidcClaimsPolicies = lib.mkOption {
|
||||
description = "Extra OIDC claims policies.";
|
||||
type = lib.types.attrsOf lib.types.attrs;
|
||||
default = {};
|
||||
default = { };
|
||||
};
|
||||
|
||||
extraOidcScopes = lib.mkOption {
|
||||
description = "Extra OIDC scopes.";
|
||||
type = lib.types.attrsOf lib.types.attrs;
|
||||
default = {};
|
||||
default = { };
|
||||
};
|
||||
|
||||
extraOidcAuthorizationPolicies = lib.mkOption {
|
||||
description = "Extra OIDC authorization policies.";
|
||||
type = lib.types.attrsOf lib.types.attrs;
|
||||
default = {};
|
||||
default = { };
|
||||
};
|
||||
|
||||
extraDefinitions = lib.mkOption {
|
||||
description = "Extra definitions.";
|
||||
type = lib.types.attrsOf lib.types.attrs;
|
||||
default = {};
|
||||
default = { };
|
||||
};
|
||||
|
||||
oidcClients = lib.mkOption {
|
||||
|
|
@ -178,10 +184,11 @@ in
|
|||
client_secret.source = pkgs.writeText "dummy.secret" "dummy_client_secret";
|
||||
public = false;
|
||||
authorization_policy = "one_factor";
|
||||
redirect_uris = [];
|
||||
redirect_uris = [ ];
|
||||
}
|
||||
];
|
||||
type = lib.types.listOf (lib.types.submodule {
|
||||
type = lib.types.listOf (
|
||||
lib.types.submodule {
|
||||
freeformType = lib.types.attrsOf lib.types.anything;
|
||||
|
||||
options = {
|
||||
|
|
@ -222,7 +229,13 @@ in
|
|||
};
|
||||
|
||||
authorization_policy = lib.mkOption {
|
||||
type = lib.types.enum ([ "one_factor" "two_factor" ] ++ lib.attrNames cfg.extraOidcAuthorizationPolicies);
|
||||
type = lib.types.enum (
|
||||
[
|
||||
"one_factor"
|
||||
"two_factor"
|
||||
]
|
||||
++ lib.attrNames cfg.extraOidcAuthorizationPolicies
|
||||
);
|
||||
description = "Require one factor (password) or two factor (device) authentication.";
|
||||
default = "one_factor";
|
||||
};
|
||||
|
|
@ -235,8 +248,13 @@ in
|
|||
scopes = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
description = "Scopes to ask for. See https://www.authelia.com/integration/openid-connect/openid-connect-1.0-claims";
|
||||
example = [ "openid" "profile" "email" "groups" ];
|
||||
default = [];
|
||||
example = [
|
||||
"openid"
|
||||
"profile"
|
||||
"email"
|
||||
"groups"
|
||||
];
|
||||
default = [ ];
|
||||
};
|
||||
|
||||
claims_policy = lib.mkOption {
|
||||
|
|
@ -250,7 +268,8 @@ in
|
|||
default = "default";
|
||||
};
|
||||
};
|
||||
});
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
smtp = lib.mkOption {
|
||||
|
|
@ -263,7 +282,8 @@ in
|
|||
default = "/tmp/authelia-notifications";
|
||||
type = lib.types.oneOf [
|
||||
lib.types.str
|
||||
(lib.types.nullOr (lib.types.submodule {
|
||||
(lib.types.nullOr (
|
||||
lib.types.submodule {
|
||||
options = {
|
||||
from_address = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
|
|
@ -299,14 +319,15 @@ in
|
|||
};
|
||||
};
|
||||
};
|
||||
}))
|
||||
}
|
||||
))
|
||||
];
|
||||
};
|
||||
|
||||
rules = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.anything;
|
||||
description = "Rule based clients";
|
||||
default = [];
|
||||
default = [ ];
|
||||
};
|
||||
|
||||
mount = lib.mkOption {
|
||||
|
|
@ -324,8 +345,12 @@ in
|
|||
```
|
||||
'';
|
||||
readOnly = true;
|
||||
default = { path = "/var/lib/authelia-authelia.${cfg.domain}"; };
|
||||
defaultText = { path = "/var/lib/authelia-authelia.example.com"; };
|
||||
default = {
|
||||
path = "/var/lib/authelia-authelia.${cfg.domain}";
|
||||
};
|
||||
defaultText = {
|
||||
path = "/var/lib/authelia-authelia.example.com";
|
||||
};
|
||||
};
|
||||
|
||||
mountRedis = lib.mkOption {
|
||||
|
|
@ -343,7 +368,9 @@ in
|
|||
```
|
||||
'';
|
||||
readOnly = true;
|
||||
default = { path = "/var/lib/redis-authelia"; };
|
||||
default = {
|
||||
path = "/var/lib/redis-authelia";
|
||||
};
|
||||
};
|
||||
|
||||
debug = lib.mkOption {
|
||||
|
|
@ -370,7 +397,7 @@ in
|
|||
|
||||
# Overriding the user name so we don't allow any weird characters anywhere. For example, postgres users do not accept the '.'.
|
||||
users = {
|
||||
groups.${autheliaCfg.user} = {};
|
||||
groups.${autheliaCfg.user} = { };
|
||||
users.${autheliaCfg.user} = {
|
||||
isSystemUser = true;
|
||||
group = autheliaCfg.user;
|
||||
|
|
@ -395,7 +422,9 @@ in
|
|||
AUTHELIA_IDENTITY_PROVIDERS_OIDC_HMAC_SECRET_FILE = toString cfg.secrets.identityProvidersOIDCHMACSecret.result.path;
|
||||
AUTHELIA_IDENTITY_PROVIDERS_OIDC_ISSUER_PRIVATE_KEY_FILE = toString cfg.secrets.identityProvidersOIDCIssuerPrivateKey.result.path;
|
||||
|
||||
AUTHELIA_NOTIFIER_SMTP_PASSWORD_FILE = lib.mkIf (!(builtins.isString cfg.smtp)) (toString cfg.smtp.password.result.path);
|
||||
AUTHELIA_NOTIFIER_SMTP_PASSWORD_FILE = lib.mkIf (!(builtins.isString cfg.smtp)) (
|
||||
toString cfg.smtp.password.result.path
|
||||
);
|
||||
};
|
||||
settings = {
|
||||
server.address = "tcp://127.0.0.1:${toString listenPort}";
|
||||
|
|
@ -428,10 +457,12 @@ in
|
|||
# Inspired from https://www.authelia.com/configuration/session/introduction/ and https://www.authelia.com/configuration/session/redis
|
||||
session = {
|
||||
name = "authelia_session";
|
||||
cookies = [{
|
||||
cookies = [
|
||||
{
|
||||
domain = if isNull cfg.port then cfg.domain else "${cfg.domain}:${toString cfg.port}";
|
||||
authelia_url = "https://${cfg.subdomain}.${cfg.domain}";
|
||||
}];
|
||||
}
|
||||
];
|
||||
same_site = "lax";
|
||||
expiration = "1h";
|
||||
inactivity = "5m";
|
||||
|
|
@ -468,7 +499,11 @@ in
|
|||
networks = [
|
||||
{
|
||||
name = "internal";
|
||||
networks = [ "10.0.0.0/8" "172.16.0.0/12" "192.168.0.0/18" ];
|
||||
networks = [
|
||||
"10.0.0.0/8"
|
||||
"172.16.0.0/12"
|
||||
"192.168.0.0/18"
|
||||
];
|
||||
}
|
||||
];
|
||||
rules = [
|
||||
|
|
@ -479,7 +514,8 @@ in
|
|||
"^/api/.*"
|
||||
];
|
||||
}
|
||||
] ++ cfg.rules;
|
||||
]
|
||||
++ cfg.rules;
|
||||
};
|
||||
telemetry = {
|
||||
metrics = {
|
||||
|
|
@ -489,17 +525,25 @@ in
|
|||
};
|
||||
|
||||
log.level = if cfg.debug then "debug" else "info";
|
||||
} // {
|
||||
}
|
||||
// {
|
||||
identity_providers.oidc = {
|
||||
claims_policies = {
|
||||
# This default claim should go away at some point.
|
||||
# https://www.authelia.com/integration/openid-connect/openid-connect-1.0-claims/#restore-functionality-prior-to-claims-parameter
|
||||
default.id_token = [ "email" "preferred_username" "name" "groups" ];
|
||||
} // cfg.extraOidcClaimsPolicies;
|
||||
default.id_token = [
|
||||
"email"
|
||||
"preferred_username"
|
||||
"name"
|
||||
"groups"
|
||||
];
|
||||
}
|
||||
// cfg.extraOidcClaimsPolicies;
|
||||
scopes = cfg.extraOidcScopes;
|
||||
authorization_policies = cfg.extraOidcAuthorizationPolicies;
|
||||
};
|
||||
} // lib.optionalAttrs (cfg.extraDefinitions != {}) {
|
||||
}
|
||||
// lib.optionalAttrs (cfg.extraDefinitions != { }) {
|
||||
definitions = cfg.extraDefinitions;
|
||||
};
|
||||
|
||||
|
|
@ -508,18 +552,22 @@ in
|
|||
|
||||
systemd.services."authelia-${fqdn}".preStart =
|
||||
let
|
||||
mkCfg = clients:
|
||||
mkCfg =
|
||||
clients:
|
||||
lib.shb.replaceSecrets {
|
||||
userConfig = {
|
||||
identity_providers.oidc.clients = clients;
|
||||
};
|
||||
resultPath = "/var/lib/authelia-${fqdn}/oidc_clients.yaml";
|
||||
generator = lib.shb.replaceSecretsGeneratorAdapter (lib.generators.toYAML {});
|
||||
generator = lib.shb.replaceSecretsGeneratorAdapter (lib.generators.toYAML { });
|
||||
};
|
||||
in
|
||||
lib.mkBefore (mkCfg cfg.oidcClients + ''
|
||||
lib.mkBefore (
|
||||
mkCfg cfg.oidcClients
|
||||
+ ''
|
||||
${pkgs.bash}/bin/bash -c '(while ! ${pkgs.netcat-openbsd}/bin/nc -z -v -w1 ${cfg.ldapHostname} ${toString cfg.ldapPort}; do echo "Waiting for port ${cfg.ldapHostname}:${toString cfg.ldapPort} to open..."; sleep 2; done); sleep 2'
|
||||
'');
|
||||
''
|
||||
);
|
||||
|
||||
services.nginx.virtualHosts.${fqdn} = {
|
||||
forceSSL = !(isNull cfg.ssl);
|
||||
|
|
@ -579,7 +627,8 @@ in
|
|||
after = [ "authelia-${fqdn}.service" ];
|
||||
enabledAddons = [ config.shb.mitmdump.addons.logger ];
|
||||
extraArgs = [
|
||||
"--set" "verbose_pattern=/api"
|
||||
"--set"
|
||||
"verbose_pattern=/api"
|
||||
];
|
||||
};
|
||||
|
||||
|
|
@ -600,7 +649,7 @@ in
|
|||
job_name = "authelia";
|
||||
static_configs = [
|
||||
{
|
||||
targets = ["127.0.0.1:9959"];
|
||||
targets = [ "127.0.0.1:9959" ];
|
||||
labels = {
|
||||
"hostname" = config.networking.hostName;
|
||||
"domain" = cfg.domain;
|
||||
|
|
@ -610,13 +659,16 @@ in
|
|||
}
|
||||
];
|
||||
|
||||
systemd.targets."authelia-${fqdn}" = let
|
||||
systemd.targets."authelia-${fqdn}" =
|
||||
let
|
||||
services = [
|
||||
"authelia-${fqdn}.service"
|
||||
] ++ lib.optionals cfg.debug [
|
||||
]
|
||||
++ lib.optionals cfg.debug [
|
||||
config.shb.mitmdump.instances."authelia-${fqdn}".serviceName
|
||||
];
|
||||
in {
|
||||
in
|
||||
{
|
||||
after = services;
|
||||
requires = services;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,10 @@
|
|||
{ config, pkgs, lib, utils, ... }:
|
||||
{
|
||||
config,
|
||||
pkgs,
|
||||
lib,
|
||||
utils,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
cfg = config.shb.borgbackup;
|
||||
|
|
@ -17,7 +23,9 @@ let
|
|||
type = lib.types.path;
|
||||
};
|
||||
|
||||
encryption_passcommand = "cat /run/secrets/borgmatic/passphrases/${if isNull instance.secretName then name else instance.secretName}";
|
||||
encryption_passcommand = "cat /run/secrets/borgmatic/passphrases/${
|
||||
if isNull instance.secretName then name else instance.secretName
|
||||
}";
|
||||
borg_keys_directory = "/run/secrets/borgmatic/keys";
|
||||
|
||||
sourceDirectories = lib.mkOption {
|
||||
|
|
@ -28,7 +36,7 @@ let
|
|||
excludePatterns = lib.mkOption {
|
||||
description = "Exclude patterns.";
|
||||
type = lib.types.listOf lib.types.str;
|
||||
default = [];
|
||||
default = [ ];
|
||||
};
|
||||
|
||||
secretName = lib.mkOption {
|
||||
|
|
@ -39,7 +47,8 @@ let
|
|||
|
||||
repositories = lib.mkOption {
|
||||
description = "Repositories to back this instance to.";
|
||||
type = lib.types.nonEmptyListOf (lib.types.submodule {
|
||||
type = lib.types.nonEmptyListOf (
|
||||
lib.types.submodule {
|
||||
options = {
|
||||
path = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
|
|
@ -60,12 +69,18 @@ let
|
|||
};
|
||||
};
|
||||
};
|
||||
});
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
retention = lib.mkOption {
|
||||
description = "Retention options.";
|
||||
type = lib.types.attrsOf (lib.types.oneOf [ lib.types.int lib.types.nonEmptyStr ]);
|
||||
type = lib.types.attrsOf (
|
||||
lib.types.oneOf [
|
||||
lib.types.int
|
||||
lib.types.nonEmptyStr
|
||||
]
|
||||
);
|
||||
default = {
|
||||
keep_within = "1d";
|
||||
keep_hourly = 24;
|
||||
|
|
@ -78,7 +93,7 @@ let
|
|||
consistency = lib.mkOption {
|
||||
description = "Consistency frequency options.";
|
||||
type = lib.types.attrsOf lib.types.nonEmptyStr;
|
||||
default = {};
|
||||
default = { };
|
||||
example = {
|
||||
repository = "2 weeks";
|
||||
archives = "1 month";
|
||||
|
|
@ -87,19 +102,19 @@ let
|
|||
|
||||
hooks = lib.mkOption {
|
||||
description = "Hooks to run before or after the backup.";
|
||||
default = {};
|
||||
default = { };
|
||||
type = lib.types.submodule {
|
||||
options = {
|
||||
beforeBackup = lib.mkOption {
|
||||
description = "Hooks to run before backup";
|
||||
type = lib.types.listOf lib.types.str;
|
||||
default = [];
|
||||
default = [ ];
|
||||
};
|
||||
|
||||
afterBackup = lib.mkOption {
|
||||
description = "Hooks to run after backup";
|
||||
type = lib.types.listOf lib.types.str;
|
||||
default = [];
|
||||
default = [ ];
|
||||
};
|
||||
};
|
||||
};
|
||||
|
|
@ -113,7 +128,8 @@ let
|
|||
};
|
||||
};
|
||||
|
||||
repoSlugName = name: builtins.replaceStrings ["/" ":"] ["_" "_"] (lib.strings.removePrefix "/" name);
|
||||
repoSlugName =
|
||||
name: builtins.replaceStrings [ "/" ":" ] [ "_" "_" ] (lib.strings.removePrefix "/" name);
|
||||
|
||||
in
|
||||
{
|
||||
|
|
@ -132,10 +148,12 @@ in
|
|||
|
||||
instances = lib.mkOption {
|
||||
description = "Each instance is a backup setting";
|
||||
default = {};
|
||||
type = lib.types.attrsOf (lib.types.submodule {
|
||||
default = { };
|
||||
type = lib.types.attrsOf (
|
||||
lib.types.submodule {
|
||||
options = instanceOptions;
|
||||
});
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
borgServer = lib.mkOption {
|
||||
|
|
@ -148,7 +166,7 @@ in
|
|||
# Taken from https://github.com/HubbeKing/restic-kubernetes/blob/73bfbdb0ba76939a4c52173fa2dbd52070710008/README.md?plain=1#L23
|
||||
performance = lib.mkOption {
|
||||
description = "Reduce performance impact of backup jobs.";
|
||||
default = {};
|
||||
default = { };
|
||||
type = lib.types.submodule {
|
||||
options = {
|
||||
niceness = lib.mkOption {
|
||||
|
|
@ -157,7 +175,11 @@ in
|
|||
default = 15;
|
||||
};
|
||||
ioSchedulingClass = lib.mkOption {
|
||||
type = lib.types.enum [ "idle" "best-effort" "realtime" ];
|
||||
type = lib.types.enum [
|
||||
"idle"
|
||||
"best-effort"
|
||||
"realtime"
|
||||
];
|
||||
description = "ionice scheduling class, defaults to best-effort IO.";
|
||||
default = "best-effort";
|
||||
};
|
||||
|
|
@ -171,10 +193,11 @@ in
|
|||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf (cfg.instances != {}) (
|
||||
config = lib.mkIf (cfg.instances != { }) (
|
||||
let
|
||||
enabledInstances = lib.attrsets.filterAttrs (k: i: i.enable) cfg.instances;
|
||||
in lib.mkMerge [
|
||||
in
|
||||
lib.mkMerge [
|
||||
# Secrets configuration
|
||||
{
|
||||
users.users = {
|
||||
|
|
@ -195,70 +218,97 @@ in
|
|||
|
||||
sops.secrets =
|
||||
let
|
||||
mkSopsSecret = name: instance: (
|
||||
mkSopsSecret =
|
||||
name: instance:
|
||||
(
|
||||
[
|
||||
{
|
||||
"${instance.backend}/passphrases/${if isNull instance.secretName then name else instance.secretName}" = {
|
||||
"${instance.backend}/passphrases/${
|
||||
if isNull instance.secretName then name else instance.secretName
|
||||
}" =
|
||||
{
|
||||
sopsFile = instance.keySopsFile;
|
||||
mode = "0440";
|
||||
owner = cfg.user;
|
||||
group = cfg.group;
|
||||
};
|
||||
}
|
||||
] ++ lib.optional ((lib.filter ({path, ...}: lib.strings.hasPrefix "s3" path) instance.repositories) != []) {
|
||||
"${instance.backend}/environmentfiles/${if isNull instance.secretName then name else instance.secretName}" = {
|
||||
]
|
||||
++
|
||||
lib.optional
|
||||
((lib.filter ({ path, ... }: lib.strings.hasPrefix "s3" path) instance.repositories) != [ ])
|
||||
{
|
||||
"${instance.backend}/environmentfiles/${
|
||||
if isNull instance.secretName then name else instance.secretName
|
||||
}" =
|
||||
{
|
||||
sopsFile = instance.keySopsFile;
|
||||
mode = "0440";
|
||||
owner = cfg.user;
|
||||
group = cfg.group;
|
||||
};
|
||||
} ++ lib.optionals (instance.backend == "borgmatic") (lib.flatten (map ({path, ...}: {
|
||||
}
|
||||
++ lib.optionals (instance.backend == "borgmatic") (
|
||||
lib.flatten (
|
||||
map (
|
||||
{ path, ... }:
|
||||
{
|
||||
"${instance.backend}/keys/${repoSlugName path}" = {
|
||||
key = "${instance.backend}/keys/${if isNull instance.secretName then name else instance.secretName}";
|
||||
key = "${instance.backend}/keys/${
|
||||
if isNull instance.secretName then name else instance.secretName
|
||||
}";
|
||||
sopsFile = instance.keySopsFile;
|
||||
mode = "0440";
|
||||
owner = cfg.user;
|
||||
group = cfg.group;
|
||||
};
|
||||
}) instance.repositories))
|
||||
}
|
||||
) instance.repositories
|
||||
)
|
||||
)
|
||||
);
|
||||
in
|
||||
lib.mkMerge (lib.flatten (lib.attrsets.mapAttrsToList mkSopsSecret enabledInstances));
|
||||
}
|
||||
# Borgmatic configuration
|
||||
{
|
||||
systemd.timers.borgmatic = lib.mkIf (enabledInstances != {}) {
|
||||
systemd.timers.borgmatic = lib.mkIf (enabledInstances != { }) {
|
||||
timerConfig = {
|
||||
OnCalendar = "hourly";
|
||||
};
|
||||
};
|
||||
|
||||
systemd.services.borgmatic = lib.mkIf (enabledInstances != {}) {
|
||||
systemd.services.borgmatic = lib.mkIf (enabledInstances != { }) {
|
||||
serviceConfig = {
|
||||
User = cfg.user;
|
||||
Group = cfg.group;
|
||||
ExecStartPre = [ "" ]; # Do not sleep before starting.
|
||||
ExecStart = [ "" "${pkgs.borgmatic}/bin/borgmatic --verbosity -1 --syslog-verbosity 1" ];
|
||||
ExecStart = [
|
||||
""
|
||||
"${pkgs.borgmatic}/bin/borgmatic --verbosity -1 --syslog-verbosity 1"
|
||||
];
|
||||
# For borgmatic, since we have only one service, we need to merge all environmentFile
|
||||
# from all instances.
|
||||
EnvironmentFile = lib.mapAttrsToList (name: value: value.environmentFile) enabledInstances;
|
||||
};
|
||||
};
|
||||
|
||||
systemd.packages = lib.mkIf (enabledInstances != {}) [ pkgs.borgmatic ];
|
||||
systemd.packages = lib.mkIf (enabledInstances != { }) [ pkgs.borgmatic ];
|
||||
environment.systemPackages = (
|
||||
lib.optionals cfg.borgServer [ pkgs.borgbackup ]
|
||||
++ lib.optionals (enabledInstances != {}) [ pkgs.borgbackup pkgs.borgmatic ]
|
||||
++ lib.optionals (enabledInstances != { }) [
|
||||
pkgs.borgbackup
|
||||
pkgs.borgmatic
|
||||
]
|
||||
);
|
||||
|
||||
environment.etc =
|
||||
let
|
||||
mkSettings = name: instance: {
|
||||
"borgmatic.d/${name}.yaml".text = lib.generators.toYAML {} {
|
||||
location =
|
||||
{
|
||||
"borgmatic.d/${name}.yaml".text = lib.generators.toYAML { } {
|
||||
location = {
|
||||
source_directories = instance.sourceDirectories;
|
||||
repositories = map ({path, ...}: path) instance.repositories;
|
||||
repositories = map ({ path, ... }: path) instance.repositories;
|
||||
}
|
||||
// (lib.attrsets.optionalAttrs (builtins.length instance.excludePatterns > 0) {
|
||||
excludePatterns = instance.excludePatterns;
|
||||
|
|
@ -291,5 +341,6 @@ in
|
|||
in
|
||||
lib.mkMerge (lib.attrsets.mapAttrsToList mkSettings enabledInstances);
|
||||
}
|
||||
]);
|
||||
]
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,8 +7,9 @@ in
|
|||
options.shb.davfs = {
|
||||
mounts = lib.mkOption {
|
||||
description = "List of mounts.";
|
||||
default = [];
|
||||
type = lib.types.listOf (lib.types.submodule {
|
||||
default = [ ];
|
||||
type = lib.types.listOf (
|
||||
lib.types.submodule {
|
||||
options = {
|
||||
remoteUrl = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
|
|
@ -66,7 +67,8 @@ in
|
|||
default = true;
|
||||
};
|
||||
};
|
||||
});
|
||||
}
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,16 +1,26 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
let
|
||||
cfg = config.shb.hardcodedsecret;
|
||||
|
||||
contracts = pkgs.callPackage ../contracts {};
|
||||
contracts = pkgs.callPackage ../contracts { };
|
||||
|
||||
inherit (lib) mapAttrs' mkOption nameValuePair;
|
||||
inherit (lib.types) attrsOf nullOr str submodule;
|
||||
inherit (lib.types)
|
||||
attrsOf
|
||||
nullOr
|
||||
str
|
||||
submodule
|
||||
;
|
||||
inherit (pkgs) writeText;
|
||||
in
|
||||
{
|
||||
options.shb.hardcodedsecret = mkOption {
|
||||
default = {};
|
||||
default = { };
|
||||
description = ''
|
||||
Hardcoded secrets. These should only be used in tests.
|
||||
'';
|
||||
|
|
@ -26,7 +36,10 @@ in
|
|||
};
|
||||
}
|
||||
'';
|
||||
type = attrsOf (submodule ({ name, ... }: {
|
||||
type = attrsOf (
|
||||
submodule (
|
||||
{ name, ... }:
|
||||
{
|
||||
options = contracts.secret.mkProvider {
|
||||
settings = mkOption {
|
||||
description = ''
|
||||
|
|
@ -62,15 +75,20 @@ in
|
|||
path = "/run/hardcodedsecrets/hardcodedsecret_${name}";
|
||||
};
|
||||
};
|
||||
}));
|
||||
}
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
config = {
|
||||
system.activationScripts = mapAttrs' (n: cfg':
|
||||
system.activationScripts = mapAttrs' (
|
||||
n: cfg':
|
||||
let
|
||||
source = if cfg'.settings.source != null
|
||||
then cfg'.settings.source
|
||||
else writeText "hardcodedsecret_${n}_content" cfg'.settings.content;
|
||||
source =
|
||||
if cfg'.settings.source != null then
|
||||
cfg'.settings.source
|
||||
else
|
||||
writeText "hardcodedsecret_${n}_content" cfg'.settings.content;
|
||||
in
|
||||
nameValuePair "hardcodedsecret_${n}" ''
|
||||
mkdir -p "$(dirname "${cfg'.result.path}")"
|
||||
|
|
|
|||
|
|
@ -1,9 +1,14 @@
|
|||
{ config, pkgs, lib, ... }:
|
||||
{
|
||||
config,
|
||||
pkgs,
|
||||
lib,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
cfg = config.shb.lldap;
|
||||
|
||||
contracts = pkgs.callPackage ../contracts {};
|
||||
contracts = pkgs.callPackage ../contracts { };
|
||||
|
||||
fqdn = "${cfg.subdomain}.${cfg.domain}";
|
||||
|
||||
|
|
@ -145,7 +150,9 @@ in
|
|||
```
|
||||
'';
|
||||
readOnly = true;
|
||||
default = { path = "/var/lib/lldap"; };
|
||||
default = {
|
||||
path = "/var/lib/lldap";
|
||||
};
|
||||
};
|
||||
|
||||
backup = lib.mkOption {
|
||||
|
|
@ -340,10 +347,16 @@ in
|
|||
locations."/" = {
|
||||
extraConfig = ''
|
||||
proxy_set_header Host $host;
|
||||
'' + (if isNull cfg.restrictAccessIPRange then "" else ''
|
||||
''
|
||||
+ (
|
||||
if isNull cfg.restrictAccessIPRange then
|
||||
""
|
||||
else
|
||||
''
|
||||
allow ${cfg.restrictAccessIPRange};
|
||||
deny all;
|
||||
'');
|
||||
''
|
||||
);
|
||||
proxyPass = "http://${toString config.services.lldap.settings.http_host}:${toString config.shb.lldap.webUIListenPort}/";
|
||||
};
|
||||
};
|
||||
|
|
@ -354,7 +367,7 @@ in
|
|||
group = "lldap";
|
||||
isSystemUser = true;
|
||||
};
|
||||
users.groups.lldap = {};
|
||||
users.groups.lldap = { };
|
||||
|
||||
services.lldap = {
|
||||
enable = true;
|
||||
|
|
@ -382,9 +395,13 @@ in
|
|||
};
|
||||
|
||||
inherit (cfg) ensureGroups ensureUserFields ensureGroupFields;
|
||||
ensureUsers = lib.mapAttrs (n: v: (lib.removeAttrs v [ "password" ]) // {
|
||||
ensureUsers = lib.mapAttrs (
|
||||
n: v:
|
||||
(lib.removeAttrs v [ "password" ])
|
||||
// {
|
||||
"password_file" = toString v.password.result.path;
|
||||
}) cfg.ensureUsers;
|
||||
}
|
||||
) cfg.ensureUsers;
|
||||
};
|
||||
|
||||
shb.mitmdump.instances."lldap-web" = lib.mkIf cfg.debug {
|
||||
|
|
@ -393,7 +410,8 @@ in
|
|||
after = [ "lldap.service" ];
|
||||
enabledAddons = [ config.shb.mitmdump.addons.logger ];
|
||||
extraArgs = [
|
||||
"--set" "verbose_pattern=/api"
|
||||
"--set"
|
||||
"verbose_pattern=/api"
|
||||
];
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,15 +1,34 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
let
|
||||
inherit (lib) mapAttrs' mkOption nameValuePair types;
|
||||
inherit (types) attrsOf listOf port submodule str;
|
||||
inherit (lib)
|
||||
mapAttrs'
|
||||
mkOption
|
||||
nameValuePair
|
||||
types
|
||||
;
|
||||
inherit (types)
|
||||
attrsOf
|
||||
listOf
|
||||
port
|
||||
submodule
|
||||
str
|
||||
;
|
||||
|
||||
cfg = config.shb.mitmdump;
|
||||
|
||||
mitmdumpScript = pkgs.writers.writePython3Bin "mitmdump"
|
||||
mitmdumpScript =
|
||||
pkgs.writers.writePython3Bin "mitmdump"
|
||||
{
|
||||
libraries = let
|
||||
libraries =
|
||||
let
|
||||
p = pkgs.python3Packages;
|
||||
in [
|
||||
in
|
||||
[
|
||||
p.systemd
|
||||
p.mitmproxy
|
||||
];
|
||||
|
|
@ -81,8 +100,8 @@ let
|
|||
proc.wait()
|
||||
'';
|
||||
|
||||
logger = toString (pkgs.writers.writeText "loggerAddon.py"
|
||||
''
|
||||
logger = toString (
|
||||
pkgs.writers.writeText "loggerAddon.py" ''
|
||||
import logging
|
||||
from collections.abc import Sequence
|
||||
from mitmproxy import ctx, http
|
||||
|
|
@ -131,13 +150,14 @@ let
|
|||
|
||||
|
||||
addons = [RegexLogger()]
|
||||
'');
|
||||
''
|
||||
);
|
||||
in
|
||||
{
|
||||
options.shb.mitmdump = {
|
||||
addons = mkOption {
|
||||
type = attrsOf str;
|
||||
default = [];
|
||||
default = [ ];
|
||||
description = ''
|
||||
Addons available to the be added to the mitmdump instance.
|
||||
|
||||
|
|
@ -146,11 +166,14 @@ in
|
|||
};
|
||||
|
||||
instances = mkOption {
|
||||
default = {};
|
||||
default = { };
|
||||
description = "Mitmdump instance.";
|
||||
type = attrsOf (submodule ({ name, ... }: {
|
||||
type = attrsOf (
|
||||
submodule (
|
||||
{ name, ... }:
|
||||
{
|
||||
options = {
|
||||
package = lib.mkPackageOption pkgs "mitmproxy" {};
|
||||
package = lib.mkPackageOption pkgs "mitmproxy" { };
|
||||
|
||||
serviceName = mkOption {
|
||||
type = str;
|
||||
|
|
@ -201,7 +224,7 @@ in
|
|||
|
||||
after = mkOption {
|
||||
type = listOf str;
|
||||
default = [];
|
||||
default = [ ];
|
||||
description = ''
|
||||
Systemd services that must be started before this mitmdump proxy instance.
|
||||
|
||||
|
|
@ -212,7 +235,7 @@ in
|
|||
|
||||
enabledAddons = mkOption {
|
||||
type = listOf str;
|
||||
default = [];
|
||||
default = [ ];
|
||||
description = ''
|
||||
Addons to enable on this mitmdump instance.
|
||||
'';
|
||||
|
|
@ -221,7 +244,7 @@ in
|
|||
|
||||
extraArgs = mkOption {
|
||||
type = listOf str;
|
||||
default = [];
|
||||
default = [ ];
|
||||
description = ''
|
||||
Extra arguments to pass to the mitmdump instance.
|
||||
|
||||
|
|
@ -230,12 +253,16 @@ in
|
|||
example = lib.literalExpression ''[ "--set" "verbose_pattern=/api" ]'';
|
||||
};
|
||||
};
|
||||
}));
|
||||
}
|
||||
)
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
config = {
|
||||
systemd.services = mapAttrs' (name: cfg': nameValuePair "mitmdump-${name}" {
|
||||
systemd.services = mapAttrs' (
|
||||
name: cfg':
|
||||
nameValuePair "mitmdump-${name}" {
|
||||
environment = {
|
||||
"HOME" = "/var/lib/private/mitmdump-${name}";
|
||||
"MITMDUMP_BIN" = "${cfg'.package}/bin/mitmdump";
|
||||
|
|
@ -250,7 +277,8 @@ in
|
|||
WorkingDirectory = "/var/lib/mitmdump-${name}";
|
||||
StateDirectory = "mitmdump-${name}";
|
||||
|
||||
ExecStart = let
|
||||
ExecStart =
|
||||
let
|
||||
addons = lib.concatMapStringsSep " " (addon: "-s ${addon}") cfg'.enabledAddons;
|
||||
extraArgs = lib.concatStringsSep " " cfg'.extraArgs;
|
||||
in
|
||||
|
|
@ -259,7 +287,8 @@ in
|
|||
requires = cfg'.after;
|
||||
after = cfg'.after;
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
}) cfg.instances;
|
||||
}
|
||||
) cfg.instances;
|
||||
|
||||
shb.mitmdump.addons = {
|
||||
inherit logger;
|
||||
|
|
|
|||
|
|
@ -1,9 +1,14 @@
|
|||
{ config, pkgs, lib, ... }:
|
||||
{
|
||||
config,
|
||||
pkgs,
|
||||
lib,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
cfg = config.shb.monitoring;
|
||||
|
||||
contracts = pkgs.callPackage ../contracts {};
|
||||
contracts = pkgs.callPackage ../contracts { };
|
||||
|
||||
fqdn = "${cfg.subdomain}.${cfg.domain}";
|
||||
|
||||
|
|
@ -53,7 +58,10 @@ in
|
|||
};
|
||||
|
||||
lokiMajorVersion = lib.mkOption {
|
||||
type = lib.types.enum [ 2 3 ];
|
||||
type = lib.types.enum [
|
||||
2
|
||||
3
|
||||
];
|
||||
description = ''
|
||||
Switching from version 2 to 3 requires manual intervention
|
||||
https://grafana.com/docs/loki/latest/setup/upgrade/#main--unreleased. So this let's the user
|
||||
|
|
@ -84,7 +92,7 @@ in
|
|||
contactPoints = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
description = "List of email addresses to send alerts to";
|
||||
default = [];
|
||||
default = [ ];
|
||||
};
|
||||
|
||||
adminPassword = lib.mkOption {
|
||||
|
|
@ -114,7 +122,8 @@ in
|
|||
smtp = lib.mkOption {
|
||||
description = "SMTP options.";
|
||||
default = null;
|
||||
type = lib.types.nullOr (lib.types.submodule {
|
||||
type = lib.types.nullOr (
|
||||
lib.types.submodule {
|
||||
options = {
|
||||
from_address = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
|
|
@ -144,7 +153,8 @@ in
|
|||
description = "File containing the password to connect to the SMTP host.";
|
||||
};
|
||||
};
|
||||
});
|
||||
}
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
|
|
@ -204,12 +214,14 @@ in
|
|||
services.grafana.provision = {
|
||||
dashboards.settings = lib.mkIf cfg.provisionDashboards {
|
||||
apiVersion = 1;
|
||||
providers = [{
|
||||
providers = [
|
||||
{
|
||||
folder = "Self Host Blocks";
|
||||
options.path = ./monitoring/dashboards;
|
||||
allowUiUpdates = true;
|
||||
disableDeletion = true;
|
||||
}];
|
||||
}
|
||||
];
|
||||
};
|
||||
datasources.settings = {
|
||||
apiVersion = 1;
|
||||
|
|
@ -245,26 +257,35 @@ in
|
|||
};
|
||||
alerting.contactPoints.settings = {
|
||||
apiVersion = 1;
|
||||
contactPoints = [{
|
||||
contactPoints = [
|
||||
{
|
||||
inherit (cfg) orgId;
|
||||
name = "grafana-default-email";
|
||||
receivers = lib.optionals ((builtins.length cfg.contactPoints) > 0) [{
|
||||
receivers = lib.optionals ((builtins.length cfg.contactPoints) > 0) [
|
||||
{
|
||||
uid = "sysadmin";
|
||||
type = "email";
|
||||
settings.addresses = lib.concatStringsSep ";" cfg.contactPoints;
|
||||
}];
|
||||
}];
|
||||
}
|
||||
];
|
||||
}
|
||||
];
|
||||
};
|
||||
alerting.policies.settings = {
|
||||
apiVersion = 1;
|
||||
policies = [{
|
||||
policies = [
|
||||
{
|
||||
inherit (cfg) orgId;
|
||||
receiver = "grafana-default-email";
|
||||
group_by = [ "grafana_folder" "alertname" ];
|
||||
group_by = [
|
||||
"grafana_folder"
|
||||
"alertname"
|
||||
];
|
||||
group_wait = "30s";
|
||||
group_interval = "5m";
|
||||
repeat_interval = "4h";
|
||||
}];
|
||||
}
|
||||
];
|
||||
# resetPolicies seems to happen after setting the above policies, effectively rolling back
|
||||
# any updates.
|
||||
};
|
||||
|
|
@ -275,13 +296,15 @@ in
|
|||
in
|
||||
{
|
||||
apiVersion = 1;
|
||||
groups = [{
|
||||
groups = [
|
||||
{
|
||||
inherit (cfg) orgId;
|
||||
name = "SysAdmin";
|
||||
folder = "Self Host Blocks";
|
||||
interval = "10m";
|
||||
inherit rules;
|
||||
}];
|
||||
}
|
||||
];
|
||||
# deleteRules seems to happen after creating the above rules, effectively rolling back
|
||||
# any updates.
|
||||
};
|
||||
|
|
@ -295,9 +318,13 @@ in
|
|||
services.loki = {
|
||||
enable = true;
|
||||
dataDir = "/var/lib/loki";
|
||||
package = if cfg.lokiMajorVersion == 3 then pkgs.grafana-loki else
|
||||
package =
|
||||
if cfg.lokiMajorVersion == 3 then
|
||||
pkgs.grafana-loki
|
||||
else
|
||||
# Comes from https://github.com/NixOS/nixpkgs/commit/8f95320f39d7e4e4a29ee70b8718974295a619f4
|
||||
(pkgs.grafana-loki.overrideAttrs (finalAttrs: previousAttrs: rec {
|
||||
(pkgs.grafana-loki.overrideAttrs (
|
||||
finalAttrs: previousAttrs: rec {
|
||||
version = "2.9.6";
|
||||
|
||||
src = pkgs.fetchFromGitHub {
|
||||
|
|
@ -315,7 +342,11 @@ in
|
|||
# Removes "cmd/lokitool"
|
||||
];
|
||||
|
||||
ldflags = let t = "github.com/grafana/loki/pkg/util/build"; in [
|
||||
ldflags =
|
||||
let
|
||||
t = "github.com/grafana/loki/pkg/util/build";
|
||||
in
|
||||
[
|
||||
"-s"
|
||||
"-w"
|
||||
"-X ${t}.Version=${version}"
|
||||
|
|
@ -324,7 +355,8 @@ in
|
|||
"-X ${t}.Branch=unknown"
|
||||
"-X ${t}.Revision=unknown"
|
||||
];
|
||||
}));
|
||||
}
|
||||
));
|
||||
configuration = {
|
||||
auth_enabled = false;
|
||||
|
||||
|
|
@ -448,41 +480,52 @@ in
|
|||
services.prometheus.scrapeConfigs = [
|
||||
{
|
||||
job_name = "node";
|
||||
static_configs = [{
|
||||
targets = ["127.0.0.1:${toString config.services.prometheus.exporters.node.port}"];
|
||||
static_configs = [
|
||||
{
|
||||
targets = [ "127.0.0.1:${toString config.services.prometheus.exporters.node.port}" ];
|
||||
labels = commonLabels;
|
||||
}];
|
||||
}
|
||||
];
|
||||
}
|
||||
{
|
||||
job_name = "netdata";
|
||||
metrics_path = "/api/v1/allmetrics";
|
||||
params.format = [ "prometheus" ];
|
||||
honor_labels = true;
|
||||
static_configs = [{
|
||||
static_configs = [
|
||||
{
|
||||
targets = [ "127.0.0.1:19999" ];
|
||||
labels = commonLabels;
|
||||
}];
|
||||
}
|
||||
];
|
||||
}
|
||||
{
|
||||
job_name = "smartctl";
|
||||
static_configs = [{
|
||||
targets = ["127.0.0.1:${toString config.services.prometheus.exporters.smartctl.port}"];
|
||||
static_configs = [
|
||||
{
|
||||
targets = [ "127.0.0.1:${toString config.services.prometheus.exporters.smartctl.port}" ];
|
||||
labels = commonLabels;
|
||||
}];
|
||||
}
|
||||
];
|
||||
}
|
||||
{
|
||||
job_name = "prometheus_internal";
|
||||
static_configs = [{
|
||||
targets = ["127.0.0.1:${toString config.services.prometheus.port}"];
|
||||
static_configs = [
|
||||
{
|
||||
targets = [ "127.0.0.1:${toString config.services.prometheus.port}" ];
|
||||
labels = commonLabels;
|
||||
}];
|
||||
}
|
||||
] ++ (lib.lists.optional config.services.nginx.enable {
|
||||
];
|
||||
}
|
||||
]
|
||||
++ (lib.lists.optional config.services.nginx.enable {
|
||||
job_name = "nginx";
|
||||
static_configs = [{
|
||||
targets = ["127.0.0.1:${toString config.services.prometheus.exporters.nginx.port}"];
|
||||
static_configs = [
|
||||
{
|
||||
targets = [ "127.0.0.1:${toString config.services.prometheus.exporters.nginx.port}" ];
|
||||
labels = commonLabels;
|
||||
}];
|
||||
}
|
||||
];
|
||||
# }) ++ (lib.optional (builtins.length (lib.attrNames config.services.redis.servers) > 0) {
|
||||
# job_name = "redis";
|
||||
# static_configs = [
|
||||
|
|
@ -497,12 +540,15 @@ in
|
|||
# targets = ["127.0.0.1:${toString config.services.prometheus.exporters.openvpn.port}"];
|
||||
# }
|
||||
# ];
|
||||
}) ++ (lib.optional config.services.dnsmasq.enable {
|
||||
})
|
||||
++ (lib.optional config.services.dnsmasq.enable {
|
||||
job_name = "dnsmasq";
|
||||
static_configs = [{
|
||||
targets = ["127.0.0.1:${toString config.services.prometheus.exporters.dnsmasq.port}"];
|
||||
static_configs = [
|
||||
{
|
||||
targets = [ "127.0.0.1:${toString config.services.prometheus.exporters.dnsmasq.port}" ];
|
||||
labels = commonLabels;
|
||||
}];
|
||||
}
|
||||
];
|
||||
});
|
||||
services.prometheus.exporters.nginx = lib.mkIf config.services.nginx.enable {
|
||||
enable = true;
|
||||
|
|
@ -513,7 +559,7 @@ in
|
|||
services.prometheus.exporters.node = {
|
||||
enable = true;
|
||||
# https://github.com/prometheus/node_exporter#collectors
|
||||
enabledCollectors = ["ethtool"];
|
||||
enabledCollectors = [ "ethtool" ];
|
||||
port = 9112;
|
||||
listenAddress = "127.0.0.1";
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,9 +1,14 @@
|
|||
{ config, pkgs, lib, ... }:
|
||||
{
|
||||
config,
|
||||
pkgs,
|
||||
lib,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
cfg = config.shb.nginx;
|
||||
|
||||
contracts = pkgs.callPackage ../contracts {};
|
||||
contracts = pkgs.callPackage ../contracts { };
|
||||
|
||||
fqdn = c: "${c.subdomain}.${c.domain}";
|
||||
|
||||
|
|
@ -42,9 +47,10 @@ let
|
|||
|
||||
autheliaRules = lib.mkOption {
|
||||
type = lib.types.listOf (lib.types.attrsOf lib.types.anything);
|
||||
default = [];
|
||||
default = [ ];
|
||||
description = "Authelia rule configuration";
|
||||
example = lib.literalExpression ''[{
|
||||
example = lib.literalExpression ''
|
||||
[{
|
||||
policy = "two_factor";
|
||||
subject = ["group:service_user"];
|
||||
}]'';
|
||||
|
|
@ -77,12 +83,15 @@ in
|
|||
vhosts = lib.mkOption {
|
||||
description = "Endpoints to be protected by authelia.";
|
||||
type = lib.types.listOf vhostConfig;
|
||||
default = [];
|
||||
default = [ ];
|
||||
};
|
||||
};
|
||||
|
||||
config = {
|
||||
networking.firewall.allowedTCPPorts = [ 80 443 ];
|
||||
networking.firewall.allowedTCPPorts = [
|
||||
80
|
||||
443
|
||||
];
|
||||
|
||||
services.nginx.enable = true;
|
||||
services.nginx.logError = lib.mkIf cfg.debugLog "stderr warn";
|
||||
|
|
|
|||
|
|
@ -1,9 +1,15 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
let
|
||||
cfg = config.shb.postgresql;
|
||||
contracts = pkgs.callPackage ../contracts {};
|
||||
contracts = pkgs.callPackage ../contracts { };
|
||||
|
||||
upgrade-script = old: new:
|
||||
upgrade-script =
|
||||
old: new:
|
||||
let
|
||||
oldStr = builtins.toString old;
|
||||
newStr = builtins.toString new;
|
||||
|
|
@ -55,7 +61,7 @@ in
|
|||
Backup configuration.
|
||||
'';
|
||||
|
||||
default = {};
|
||||
default = { };
|
||||
type = lib.types.submodule {
|
||||
options = contracts.databasebackup.mkRequester {
|
||||
user = "postgres";
|
||||
|
|
@ -75,7 +81,8 @@ in
|
|||
|
||||
ensures = lib.mkOption {
|
||||
description = "List of username, database and/or passwords that should be created.";
|
||||
type = lib.types.listOf (lib.types.submodule {
|
||||
type = lib.types.listOf (
|
||||
lib.types.submodule {
|
||||
options = {
|
||||
username = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
|
|
@ -94,8 +101,9 @@ in
|
|||
example = "/run/secrets/postgresql/password";
|
||||
};
|
||||
};
|
||||
});
|
||||
default = [];
|
||||
}
|
||||
);
|
||||
default = [ ];
|
||||
};
|
||||
};
|
||||
|
||||
|
|
@ -123,16 +131,19 @@ in
|
|||
dbConfig = ensureCfgs: {
|
||||
services.postgresql.enable = lib.mkDefault ((builtins.length ensureCfgs) > 0);
|
||||
services.postgresql.ensureDatabases = map ({ database, ... }: database) ensureCfgs;
|
||||
services.postgresql.ensureUsers = map ({ username, database, ... }: {
|
||||
services.postgresql.ensureUsers = map (
|
||||
{ username, database, ... }:
|
||||
{
|
||||
name = username;
|
||||
ensureDBOwnership = true;
|
||||
ensureClauses.login = true;
|
||||
}) ensureCfgs;
|
||||
}
|
||||
) ensureCfgs;
|
||||
};
|
||||
|
||||
pwdConfig = ensureCfgs: {
|
||||
systemd.services.postgresql-setup.script = lib.mkAfter
|
||||
(let
|
||||
systemd.services.postgresql-setup.script = lib.mkAfter (
|
||||
let
|
||||
prefix = ''
|
||||
psql -tA <<'EOF'
|
||||
DO $$
|
||||
|
|
@ -143,17 +154,24 @@ in
|
|||
END $$;
|
||||
EOF
|
||||
'';
|
||||
exec = { username, passwordFile, ... }: ''
|
||||
exec =
|
||||
{ username, passwordFile, ... }:
|
||||
''
|
||||
password := trim(both from replace(pg_read_file('${passwordFile}'), E'\n', '''));
|
||||
EXECUTE format('ALTER ROLE ${username} WITH PASSWORD '''%s''';', password);
|
||||
'';
|
||||
cfgsWithPasswords = builtins.filter (cfg: cfg.passwordFile != null) ensureCfgs;
|
||||
in
|
||||
if (builtins.length cfgsWithPasswords) == 0 then "" else
|
||||
prefix + (lib.concatStrings (map exec cfgsWithPasswords)) + suffix);
|
||||
if (builtins.length cfgsWithPasswords) == 0 then
|
||||
""
|
||||
else
|
||||
prefix + (lib.concatStrings (map exec cfgsWithPasswords)) + suffix
|
||||
);
|
||||
};
|
||||
|
||||
debugConfig = enableDebug: lib.mkIf enableDebug {
|
||||
debugConfig =
|
||||
enableDebug:
|
||||
lib.mkIf enableDebug {
|
||||
services.postgresql.settings.shared_preload_libraries = "auto_explain, pg_stat_statements";
|
||||
};
|
||||
in
|
||||
|
|
|
|||
|
|
@ -1,15 +1,55 @@
|
|||
{ config, pkgs, lib, utils, ... }:
|
||||
{
|
||||
config,
|
||||
pkgs,
|
||||
lib,
|
||||
utils,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
cfg = config.shb.restic;
|
||||
|
||||
contracts = pkgs.callPackage ../contracts {};
|
||||
contracts = pkgs.callPackage ../contracts { };
|
||||
|
||||
inherit (lib) concatStringsSep filterAttrs flatten literalExpression optionals listToAttrs mapAttrsToList mkEnableOption mkOption mkMerge;
|
||||
inherit (lib) hasPrefix mkIf nameValuePair optionalAttrs removePrefix;
|
||||
inherit (lib.types) attrsOf enum int ints oneOf nonEmptyStr nullOr str submodule;
|
||||
inherit (lib)
|
||||
concatStringsSep
|
||||
filterAttrs
|
||||
flatten
|
||||
literalExpression
|
||||
optionals
|
||||
listToAttrs
|
||||
mapAttrsToList
|
||||
mkEnableOption
|
||||
mkOption
|
||||
mkMerge
|
||||
;
|
||||
inherit (lib)
|
||||
hasPrefix
|
||||
mkIf
|
||||
nameValuePair
|
||||
optionalAttrs
|
||||
removePrefix
|
||||
;
|
||||
inherit (lib.types)
|
||||
attrsOf
|
||||
enum
|
||||
int
|
||||
ints
|
||||
oneOf
|
||||
nonEmptyStr
|
||||
nullOr
|
||||
str
|
||||
submodule
|
||||
;
|
||||
|
||||
commonOptions = { name, prefix, config, ... }: {
|
||||
commonOptions =
|
||||
{
|
||||
name,
|
||||
prefix,
|
||||
config,
|
||||
...
|
||||
}:
|
||||
{
|
||||
enable = mkEnableOption ''
|
||||
this backup intance.
|
||||
|
||||
|
|
@ -41,7 +81,7 @@ let
|
|||
|
||||
secrets = mkOption {
|
||||
type = attrsOf lib.shb.secretFileType;
|
||||
default = {};
|
||||
default = { };
|
||||
description = ''
|
||||
Secrets needed to access the repository where the backups will be stored.
|
||||
|
||||
|
|
@ -76,7 +116,10 @@ let
|
|||
|
||||
retention = mkOption {
|
||||
description = "For how long to keep backup files.";
|
||||
type = attrsOf (oneOf [ int nonEmptyStr ]);
|
||||
type = attrsOf (oneOf [
|
||||
int
|
||||
nonEmptyStr
|
||||
]);
|
||||
default = {
|
||||
keep_within = "1d";
|
||||
keep_hourly = 24;
|
||||
|
|
@ -101,15 +144,18 @@ let
|
|||
};
|
||||
};
|
||||
|
||||
repoSlugName = name: builtins.replaceStrings ["/" ":"] ["_" "_"] (removePrefix "/" name);
|
||||
repoSlugName = name: builtins.replaceStrings [ "/" ":" ] [ "_" "_" ] (removePrefix "/" name);
|
||||
fullName = name: repository: "restic-backups-${name}_${repoSlugName repository.path}";
|
||||
in
|
||||
{
|
||||
options.shb.restic = {
|
||||
instances = mkOption {
|
||||
description = "Files to backup following the [backup contract](./contracts-backup.html).";
|
||||
default = {};
|
||||
type = attrsOf (submodule ({ name, config, ... }: {
|
||||
default = { };
|
||||
type = attrsOf (
|
||||
submodule (
|
||||
{ name, config, ... }:
|
||||
{
|
||||
options = contracts.backup.mkProvider {
|
||||
settings = mkOption {
|
||||
description = ''
|
||||
|
|
@ -117,7 +163,10 @@ in
|
|||
'';
|
||||
|
||||
type = submodule {
|
||||
options = commonOptions { inherit name config; prefix = "instances"; };
|
||||
options = commonOptions {
|
||||
inherit name config;
|
||||
prefix = "instances";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
|
|
@ -129,13 +178,18 @@ in
|
|||
backupServiceText = "${fullName "<name>" { path = "path/to/repository"; }}.service";
|
||||
};
|
||||
};
|
||||
}));
|
||||
}
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
databases = mkOption {
|
||||
description = "Databases to backup following the [database backup contract](./contracts-databasebackup.html).";
|
||||
default = {};
|
||||
type = attrsOf (submodule ({ name, config, ... }: {
|
||||
default = { };
|
||||
type = attrsOf (
|
||||
submodule (
|
||||
{ name, config, ... }:
|
||||
{
|
||||
options = contracts.databasebackup.mkProvider {
|
||||
settings = mkOption {
|
||||
description = ''
|
||||
|
|
@ -143,7 +197,10 @@ in
|
|||
'';
|
||||
|
||||
type = submodule {
|
||||
options = commonOptions { inherit name config; prefix = "databases"; };
|
||||
options = commonOptions {
|
||||
inherit name config;
|
||||
prefix = "databases";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
|
|
@ -155,13 +212,15 @@ in
|
|||
backupServiceText = "${fullName "<name>" { path = "path/to/repository"; }}.service";
|
||||
};
|
||||
};
|
||||
}));
|
||||
}
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
# Taken from https://github.com/HubbeKing/restic-kubernetes/blob/73bfbdb0ba76939a4c52173fa2dbd52070710008/README.md?plain=1#L23
|
||||
performance = mkOption {
|
||||
description = "Reduce performance impact of backup jobs.";
|
||||
default = {};
|
||||
default = { };
|
||||
type = submodule {
|
||||
options = {
|
||||
niceness = mkOption {
|
||||
|
|
@ -170,7 +229,11 @@ in
|
|||
default = 15;
|
||||
};
|
||||
ioSchedulingClass = mkOption {
|
||||
type = enum [ "idle" "best-effort" "realtime" ];
|
||||
type = enum [
|
||||
"idle"
|
||||
"best-effort"
|
||||
"realtime"
|
||||
];
|
||||
description = "ionice scheduling class, defaults to best-effort IO. Only used for `restic backup`, `restic forget` and `restic check` commands.";
|
||||
default = "best-effort";
|
||||
};
|
||||
|
|
@ -184,19 +247,24 @@ in
|
|||
};
|
||||
};
|
||||
|
||||
config = mkIf (cfg.instances != {} || cfg.databases != {}) (
|
||||
config = mkIf (cfg.instances != { } || cfg.databases != { }) (
|
||||
let
|
||||
enabledInstances = filterAttrs (k: i: i.settings.enable) cfg.instances;
|
||||
enabledDatabases = filterAttrs (k: i: i.settings.enable) cfg.databases;
|
||||
in mkMerge [
|
||||
in
|
||||
mkMerge [
|
||||
{
|
||||
environment.systemPackages = optionals (enabledInstances != {} || enabledDatabases != {}) [ pkgs.restic ];
|
||||
environment.systemPackages = optionals (enabledInstances != { } || enabledDatabases != { }) [
|
||||
pkgs.restic
|
||||
];
|
||||
}
|
||||
{
|
||||
# Create repository if it is a local path.
|
||||
systemd.tmpfiles.rules =
|
||||
let
|
||||
mkSettings = name: instance: optionals (hasPrefix "/" instance.settings.repository.path) [
|
||||
mkSettings =
|
||||
name: instance:
|
||||
optionals (hasPrefix "/" instance.settings.repository.path) [
|
||||
"d '${instance.settings.repository.path}' 0750 ${instance.request.user} root - -"
|
||||
];
|
||||
in
|
||||
|
|
@ -219,8 +287,8 @@ in
|
|||
|
||||
inherit (instance.settings.repository) timerConfig;
|
||||
|
||||
pruneOpts = mapAttrsToList (name: value:
|
||||
"--${builtins.replaceStrings ["_"] ["-"] name} ${builtins.toString value}"
|
||||
pruneOpts = mapAttrsToList (
|
||||
name: value: "--${builtins.replaceStrings [ "_" ] [ "-" ] name} ${builtins.toString value}"
|
||||
) instance.settings.retention;
|
||||
|
||||
backupPrepareCommand = concatStringsSep "\n" instance.request.hooks.beforeBackup;
|
||||
|
|
@ -234,7 +302,8 @@ in
|
|||
++ (optionals (instance.settings.limitDownloadKiBs != null) [
|
||||
"--limit-download=${toString instance.settings.limitDownloadKiBs}"
|
||||
]);
|
||||
} // optionalAttrs (builtins.length instance.request.excludePatterns > 0) {
|
||||
}
|
||||
// optionalAttrs (builtins.length instance.request.excludePatterns > 0) {
|
||||
exclude = instance.request.excludePatterns;
|
||||
};
|
||||
};
|
||||
|
|
@ -258,8 +327,8 @@ in
|
|||
|
||||
inherit (instance.settings.repository) timerConfig;
|
||||
|
||||
pruneOpts = mapAttrsToList (name: value:
|
||||
"--${builtins.replaceStrings ["_"] ["-"] name} ${builtins.toString value}"
|
||||
pruneOpts = mapAttrsToList (
|
||||
name: value: "--${builtins.replaceStrings [ "_" ] [ "-" ] name} ${builtins.toString value}"
|
||||
) instance.settings.retention;
|
||||
|
||||
extraBackupArgs =
|
||||
|
|
@ -269,13 +338,14 @@ in
|
|||
++ (optionals (instance.settings.limitDownloadKiBs != null) [
|
||||
"--limit-download=${toString instance.settings.limitDownloadKiBs}"
|
||||
])
|
||||
++
|
||||
(let
|
||||
++ (
|
||||
let
|
||||
cmd = pkgs.writeShellScriptBin "dump.sh" instance.request.backupCmd;
|
||||
in
|
||||
[
|
||||
"--stdin-filename ${instance.request.backupName} --stdin-from-command -- ${cmd}/bin/dump.sh"
|
||||
]);
|
||||
]
|
||||
);
|
||||
};
|
||||
};
|
||||
in
|
||||
|
|
@ -284,7 +354,8 @@ in
|
|||
{
|
||||
systemd.services =
|
||||
let
|
||||
mkSettings = name: instance:
|
||||
mkSettings =
|
||||
name: instance:
|
||||
let
|
||||
serviceName = fullName name instance.settings.repository;
|
||||
in
|
||||
|
|
@ -298,8 +369,7 @@ in
|
|||
# BindReadOnlyPaths = instance.sourceDirectories;
|
||||
};
|
||||
}
|
||||
(optionalAttrs (instance.settings.repository.secrets != {})
|
||||
{
|
||||
(optionalAttrs (instance.settings.repository.secrets != { }) {
|
||||
serviceConfig.EnvironmentFile = [
|
||||
"/run/secrets_restic/${serviceName}"
|
||||
];
|
||||
|
|
@ -308,8 +378,8 @@ in
|
|||
})
|
||||
];
|
||||
|
||||
"${serviceName}-pre" = mkIf (instance.settings.repository.secrets != {})
|
||||
(let
|
||||
"${serviceName}-pre" = mkIf (instance.settings.repository.secrets != { }) (
|
||||
let
|
||||
script = lib.shb.genConfigOutOfBandSystemd {
|
||||
config = instance.settings.repository.secrets;
|
||||
configLocation = "/run/secrets_restic/${serviceName}";
|
||||
|
|
@ -321,19 +391,23 @@ in
|
|||
script = script.preStart;
|
||||
serviceConfig.Type = "oneshot";
|
||||
serviceConfig.LoadCredential = script.loadCredentials;
|
||||
});
|
||||
}
|
||||
);
|
||||
};
|
||||
in
|
||||
mkMerge (flatten (mapAttrsToList mkSettings (enabledInstances // enabledDatabases)));
|
||||
}
|
||||
{
|
||||
systemd.services = let
|
||||
mkEnv = name: instance:
|
||||
systemd.services =
|
||||
let
|
||||
mkEnv =
|
||||
name: instance:
|
||||
nameValuePair "${fullName name instance.settings.repository}_restore_gen" {
|
||||
enable = true;
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
serviceConfig.Type = "oneshot";
|
||||
script = (lib.shb.replaceSecrets {
|
||||
script = (
|
||||
lib.shb.replaceSecrets {
|
||||
userConfig = instance.settings.repository.secrets // {
|
||||
RESTIC_PASSWORD_FILE = toString instance.settings.passphrase.result.path;
|
||||
RESTIC_REPOSITORY = instance.settings.repository.path;
|
||||
|
|
@ -341,14 +415,17 @@ in
|
|||
resultPath = "/run/secrets_restic_env/${fullName name instance.settings.repository}";
|
||||
generator = lib.shb.toEnvVar;
|
||||
user = instance.request.user;
|
||||
});
|
||||
}
|
||||
);
|
||||
};
|
||||
in
|
||||
listToAttrs (flatten (mapAttrsToList mkEnv (cfg.instances // cfg.databases)));
|
||||
}
|
||||
{
|
||||
environment.systemPackages = let
|
||||
mkResticBinary = name: instance:
|
||||
environment.systemPackages =
|
||||
let
|
||||
mkResticBinary =
|
||||
name: instance:
|
||||
pkgs.writeShellScriptBin (fullName name instance.settings.repository) ''
|
||||
set -euo pipefail
|
||||
|
||||
|
|
@ -366,8 +443,10 @@ in
|
|||
flatten (mapAttrsToList mkResticBinary cfg.instances);
|
||||
}
|
||||
{
|
||||
environment.systemPackages = let
|
||||
mkResticBinary = name: instance:
|
||||
environment.systemPackages =
|
||||
let
|
||||
mkResticBinary =
|
||||
name: instance:
|
||||
pkgs.writeShellScriptBin (fullName name instance.settings.repository) ''
|
||||
set -euo pipefail
|
||||
|
||||
|
|
@ -384,5 +463,6 @@ in
|
|||
in
|
||||
flatten (mapAttrsToList mkResticBinary cfg.databases);
|
||||
}
|
||||
]);
|
||||
]
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,14 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
let
|
||||
inherit (lib) mapAttrs mkOption;
|
||||
inherit (lib.types) attrsOf anything submodule;
|
||||
|
||||
contracts = pkgs.callPackage ../contracts {};
|
||||
contracts = pkgs.callPackage ../contracts { };
|
||||
|
||||
cfg = config.shb.sops;
|
||||
in
|
||||
|
|
@ -11,8 +16,11 @@ in
|
|||
options.shb.sops = {
|
||||
secret = mkOption {
|
||||
description = "Secret following the [secret contract](./contracts-secret.html).";
|
||||
default = {};
|
||||
type = attrsOf (submodule ({ name, options, ... }: {
|
||||
default = { };
|
||||
type = attrsOf (
|
||||
submodule (
|
||||
{ name, options, ... }:
|
||||
{
|
||||
options = contracts.secret.mkProvider {
|
||||
settings = mkOption {
|
||||
description = ''
|
||||
|
|
@ -25,7 +33,7 @@ in
|
|||
'';
|
||||
|
||||
type = attrsOf anything;
|
||||
default = {};
|
||||
default = { };
|
||||
};
|
||||
|
||||
resultCfg = {
|
||||
|
|
@ -33,13 +41,17 @@ in
|
|||
pathText = "/run/secrets/<name>";
|
||||
};
|
||||
};
|
||||
}));
|
||||
}
|
||||
)
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
config = {
|
||||
sops.secrets = let
|
||||
sops.secrets =
|
||||
let
|
||||
mkSecret = n: secretCfg: secretCfg.request // secretCfg.settings;
|
||||
in mapAttrs mkSecret cfg.secret;
|
||||
in
|
||||
mapAttrs mkSecret cfg.secret;
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,23 @@
|
|||
{ config, pkgs, lib, ... }:
|
||||
{
|
||||
config,
|
||||
pkgs,
|
||||
lib,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
cfg = config.shb.certs;
|
||||
|
||||
contracts = pkgs.callPackage ../contracts {};
|
||||
contracts = pkgs.callPackage ../contracts { };
|
||||
|
||||
inherit (builtins) dirOf;
|
||||
inherit (lib) flatten mapAttrsToList optionalAttrs optionals unique;
|
||||
inherit (lib)
|
||||
flatten
|
||||
mapAttrsToList
|
||||
optionalAttrs
|
||||
optionals
|
||||
unique
|
||||
;
|
||||
in
|
||||
{
|
||||
options.shb.certs = {
|
||||
|
|
@ -19,8 +30,11 @@ in
|
|||
};
|
||||
cas.selfsigned = lib.mkOption {
|
||||
description = "Generate a self-signed Certificate Authority.";
|
||||
default = {};
|
||||
type = lib.types.attrsOf (lib.types.submodule ({ config, ...}: {
|
||||
default = { };
|
||||
type = lib.types.attrsOf (
|
||||
lib.types.submodule (
|
||||
{ config, ... }:
|
||||
{
|
||||
options = {
|
||||
name = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
|
|
@ -54,12 +68,17 @@ in
|
|||
default = "shb-certs-ca-${config._module.args.name}.service";
|
||||
};
|
||||
};
|
||||
}));
|
||||
}
|
||||
)
|
||||
);
|
||||
};
|
||||
certs.selfsigned = lib.mkOption {
|
||||
description = "Generate self-signed certificates signed by a Certificate Authority.";
|
||||
default = {};
|
||||
type = lib.types.attrsOf (lib.types.submodule ({ config, ... }: {
|
||||
default = { };
|
||||
type = lib.types.attrsOf (
|
||||
lib.types.submodule (
|
||||
{ config, ... }:
|
||||
{
|
||||
options = {
|
||||
ca = lib.mkOption {
|
||||
type = lib.types.nullOr contracts.ssl.cas;
|
||||
|
|
@ -85,7 +104,7 @@ in
|
|||
description = ''
|
||||
Other domains to generate a certificate for.
|
||||
'';
|
||||
default = [];
|
||||
default = [ ];
|
||||
example = lib.literalExpression ''
|
||||
[
|
||||
"sub1.example.com"
|
||||
|
|
@ -131,17 +150,22 @@ in
|
|||
The list of systemd services to call `systemctl try-reload-or-restart` on.
|
||||
'';
|
||||
type = lib.types.listOf lib.types.str;
|
||||
default = [];
|
||||
default = [ ];
|
||||
example = [ "nginx.service" ];
|
||||
};
|
||||
};
|
||||
}));
|
||||
}
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
certs.letsencrypt = lib.mkOption {
|
||||
description = "Generate certificates signed by [Let's Encrypt](https://letsencrypt.org/).";
|
||||
default = {};
|
||||
type = lib.types.attrsOf (lib.types.submodule ({ config, ... }: {
|
||||
default = { };
|
||||
type = lib.types.attrsOf (
|
||||
lib.types.submodule (
|
||||
{ config, ... }:
|
||||
{
|
||||
options = {
|
||||
domain = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
|
|
@ -157,7 +181,7 @@ in
|
|||
description = ''
|
||||
Other domains to generate a certificate for.
|
||||
'';
|
||||
default = [];
|
||||
default = [ ];
|
||||
example = lib.literalExpression ''
|
||||
[
|
||||
"sub1.example.com"
|
||||
|
|
@ -203,7 +227,7 @@ in
|
|||
Systemd service(s) that must start successfully before attempting to reach acme.
|
||||
'';
|
||||
type = lib.types.listOf lib.types.str;
|
||||
default = [];
|
||||
default = [ ];
|
||||
example = lib.literalExpression ''
|
||||
[ "dnsmasq.service" ]
|
||||
'';
|
||||
|
|
@ -214,7 +238,7 @@ in
|
|||
The list of systemd services to call `systemctl try-reload-or-restart` on.
|
||||
'';
|
||||
type = lib.types.listOf lib.types.str;
|
||||
default = [];
|
||||
default = [ ];
|
||||
example = [ "nginx.service" ];
|
||||
};
|
||||
|
||||
|
|
@ -258,7 +282,7 @@ in
|
|||
|
||||
additionalEnvironment = lib.mkOption {
|
||||
type = lib.types.attrsOf lib.types.str;
|
||||
default = {};
|
||||
default = { };
|
||||
description = ''
|
||||
Additional environment variables used to configure the DNS provider.
|
||||
|
||||
|
|
@ -300,7 +324,9 @@ in
|
|||
default = false;
|
||||
};
|
||||
};
|
||||
}));
|
||||
}
|
||||
)
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
|
|
@ -311,7 +337,8 @@ in
|
|||
lib.mkMerge [
|
||||
# Config for self-signed CA.
|
||||
{
|
||||
systemd.services = lib.mapAttrs' (_name: caCfg:
|
||||
systemd.services = lib.mapAttrs' (
|
||||
_name: caCfg:
|
||||
lib.nameValuePair (serviceName caCfg.systemdService) {
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
wants = [ config.shb.certs.systemdService ];
|
||||
|
|
@ -352,7 +379,8 @@ in
|
|||
}
|
||||
# Config for self-signed CA bundle.
|
||||
{
|
||||
systemd.services.${serviceName config.shb.certs.systemdService} = (lib.mkIf (cfg.cas.selfsigned != {}) {
|
||||
systemd.services.${serviceName config.shb.certs.systemdService} = (
|
||||
lib.mkIf (cfg.cas.selfsigned != { }) {
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
serviceConfig.Type = "oneshot";
|
||||
script = ''
|
||||
|
|
@ -363,16 +391,20 @@ in
|
|||
|
||||
cat /etc/static/ssl/certs/ca-bundle.crt > /etc/ssl/certs/ca-bundle.crt
|
||||
cat /etc/static/ssl/certs/ca-bundle.crt > /etc/ssl/certs/ca-certificates.crt
|
||||
for file in ${lib.concatStringsSep " " (mapAttrsToList (_name: caCfg: caCfg.paths.cert) cfg.cas.selfsigned)}; do
|
||||
for file in ${
|
||||
lib.concatStringsSep " " (mapAttrsToList (_name: caCfg: caCfg.paths.cert) cfg.cas.selfsigned)
|
||||
}; do
|
||||
cat "$file" >> /etc/ssl/certs/ca-bundle.crt
|
||||
cat "$file" >> /etc/ssl/certs/ca-certificates.crt
|
||||
done
|
||||
'';
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
# Config for self-signed cert.
|
||||
{
|
||||
systemd.services = lib.mapAttrs' (_name: certCfg:
|
||||
systemd.services = lib.mapAttrs' (
|
||||
_name: certCfg:
|
||||
lib.nameValuePair (serviceName certCfg.systemdService) {
|
||||
after = [ certCfg.ca.systemdService ];
|
||||
requires = [ certCfg.ca.systemdService ];
|
||||
|
|
@ -382,8 +414,7 @@ in
|
|||
script =
|
||||
let
|
||||
extraDnsNames = lib.strings.concatStringsSep "\n" (map (n: "dns_name = ${n}") certCfg.extraDomains);
|
||||
chmod = cert:
|
||||
''
|
||||
chmod = cert: ''
|
||||
chown root:${certCfg.group} ${cert}
|
||||
chmod 640 ${cert}
|
||||
'';
|
||||
|
|
@ -421,7 +452,7 @@ in
|
|||
${chmod certCfg.paths.cert}
|
||||
'';
|
||||
|
||||
postStart = lib.optionalString (certCfg.reloadServices != []) ''
|
||||
postStart = lib.optionalString (certCfg.reloadServices != [ ]) ''
|
||||
systemctl --no-block try-reload-or-restart ${lib.escapeShellArgs certCfg.reloadServices}
|
||||
'';
|
||||
|
||||
|
|
@ -432,89 +463,140 @@ in
|
|||
}
|
||||
# Config for Let's Encrypt cert.
|
||||
{
|
||||
users.users = lib.mkMerge (mapAttrsToList (name: certCfg: {
|
||||
users.users = lib.mkMerge (
|
||||
mapAttrsToList (name: certCfg: {
|
||||
${certCfg.makeAvailableToUser}.extraGroups = lib.mkIf (!(isNull certCfg.makeAvailableToUser)) [
|
||||
config.security.acme.defaults.group
|
||||
];
|
||||
}) cfg.certs.letsencrypt);
|
||||
}) cfg.certs.letsencrypt
|
||||
);
|
||||
|
||||
security.acme.acceptTerms = lib.mkIf (cfg.certs.letsencrypt != {}) true;
|
||||
security.acme.acceptTerms = lib.mkIf (cfg.certs.letsencrypt != { }) true;
|
||||
|
||||
security.acme.certs = let
|
||||
extraDomainsCfg = certCfg: map (name: {
|
||||
security.acme.certs =
|
||||
let
|
||||
extraDomainsCfg =
|
||||
certCfg:
|
||||
map (name: {
|
||||
"${name}" = {
|
||||
email = certCfg.adminEmail;
|
||||
enableDebugLogs = certCfg.debug;
|
||||
server = lib.mkIf certCfg.stagingServer "https://acme-staging-v02.api.letsencrypt.org/directory";
|
||||
};
|
||||
}) certCfg.extraDomains;
|
||||
in lib.mkMerge (flatten (mapAttrsToList (name: certCfg:
|
||||
[{
|
||||
in
|
||||
lib.mkMerge (
|
||||
flatten (
|
||||
mapAttrsToList (
|
||||
name: certCfg:
|
||||
[
|
||||
{
|
||||
"${name}" = {
|
||||
extraDomainNames = [ certCfg.domain ] ++ certCfg.extraDomains;
|
||||
email = certCfg.adminEmail;
|
||||
enableDebugLogs = certCfg.debug;
|
||||
server = lib.mkIf certCfg.stagingServer "https://acme-staging-v02.api.letsencrypt.org/directory";
|
||||
} // lib.optionalAttrs (certCfg.dnsProvider != null) {
|
||||
}
|
||||
// lib.optionalAttrs (certCfg.dnsProvider != null) {
|
||||
inherit (certCfg) dnsProvider dnsResolver;
|
||||
inherit (certCfg) group reloadServices;
|
||||
credentialsFile = certCfg.credentialsFile;
|
||||
};
|
||||
}]
|
||||
}
|
||||
]
|
||||
++ lib.optionals (certCfg.dnsProvider == null) (extraDomainsCfg certCfg)
|
||||
) cfg.certs.letsencrypt));
|
||||
) cfg.certs.letsencrypt
|
||||
)
|
||||
);
|
||||
|
||||
services.nginx = let
|
||||
extraDomainsCfg = extraDomains: map (name: {
|
||||
services.nginx =
|
||||
let
|
||||
extraDomainsCfg =
|
||||
extraDomains:
|
||||
map (name: {
|
||||
virtualHosts."${name}" = {
|
||||
# addSSL = true;
|
||||
enableACME = true;
|
||||
};
|
||||
}) extraDomains;
|
||||
in lib.mkMerge (flatten (mapAttrsToList (name: certCfg:
|
||||
in
|
||||
lib.mkMerge (
|
||||
flatten (
|
||||
mapAttrsToList (
|
||||
name: certCfg:
|
||||
lib.optionals (certCfg.dnsProvider == null) (
|
||||
[{
|
||||
[
|
||||
{
|
||||
virtualHosts."${name}" = {
|
||||
# addSSL = true;
|
||||
enableACME = true;
|
||||
};
|
||||
}]
|
||||
}
|
||||
]
|
||||
++ extraDomainsCfg certCfg.extraDomains
|
||||
)) cfg.certs.letsencrypt));
|
||||
)
|
||||
) cfg.certs.letsencrypt
|
||||
)
|
||||
);
|
||||
|
||||
systemd.services = let
|
||||
extraDomainsCfg = certCfg: flatten (map (name:
|
||||
lib.optionals (certCfg.additionalEnvironment != {} && certCfg.dnsProvider == null) [{
|
||||
systemd.services =
|
||||
let
|
||||
extraDomainsCfg =
|
||||
certCfg:
|
||||
flatten (
|
||||
map (
|
||||
name:
|
||||
lib.optionals (certCfg.additionalEnvironment != { } && certCfg.dnsProvider == null) [
|
||||
{
|
||||
"acme-${name}".environment = certCfg.additionalEnvironment;
|
||||
}]
|
||||
++ lib.optionals (certCfg.afterAndWants != [] && certCfg.dnsProvider == null) [{
|
||||
}
|
||||
]
|
||||
++ lib.optionals (certCfg.afterAndWants != [ ] && certCfg.dnsProvider == null) [
|
||||
{
|
||||
"acme-${name}" = {
|
||||
after = certCfg.afterAndWants;
|
||||
wants = certCfg.afterAndWants;
|
||||
};
|
||||
}]
|
||||
) certCfg.extraDomains);
|
||||
in lib.mkMerge (flatten (mapAttrsToList (name: certCfg:
|
||||
lib.optionals (certCfg.additionalEnvironment != {} && certCfg.dnsProvider == null) [{
|
||||
}
|
||||
]
|
||||
) certCfg.extraDomains
|
||||
);
|
||||
in
|
||||
lib.mkMerge (
|
||||
flatten (
|
||||
mapAttrsToList (
|
||||
name: certCfg:
|
||||
lib.optionals (certCfg.additionalEnvironment != { } && certCfg.dnsProvider == null) [
|
||||
{
|
||||
"acme-${certCfg.domain}".environment = certCfg.additionalEnvironment;
|
||||
}]
|
||||
++ lib.optionals (certCfg.afterAndWants != [] && certCfg.dnsProvider == null) [{
|
||||
}
|
||||
]
|
||||
++ lib.optionals (certCfg.afterAndWants != [ ] && certCfg.dnsProvider == null) [
|
||||
{
|
||||
"acme-${certCfg.domain}" = {
|
||||
after = certCfg.afterAndWants;
|
||||
wants = certCfg.afterAndWants;
|
||||
};
|
||||
}]
|
||||
}
|
||||
]
|
||||
++ lib.optionals (certCfg.dnsProvider == null) (extraDomainsCfg certCfg)
|
||||
) cfg.certs.letsencrypt));
|
||||
) cfg.certs.letsencrypt
|
||||
)
|
||||
);
|
||||
|
||||
services.prometheus.exporters.node-cert = optionalAttrs (cfg.certs.letsencrypt != {}) {
|
||||
services.prometheus.exporters.node-cert = optionalAttrs (cfg.certs.letsencrypt != { }) {
|
||||
enable = true;
|
||||
listenAddress = "127.0.0.1";
|
||||
user = "acme";
|
||||
paths = let
|
||||
pathCfg = name: certCfg:
|
||||
paths =
|
||||
let
|
||||
mainDomainPaths = map dirOf [ certCfg.paths.cert certCfg.paths.key ];
|
||||
pathCfg =
|
||||
name: certCfg:
|
||||
let
|
||||
mainDomainPaths = map dirOf [
|
||||
certCfg.paths.cert
|
||||
certCfg.paths.key
|
||||
];
|
||||
# Not sure this will work for all cases.
|
||||
mainPath = dirOf (dirOf certCfg.paths.cert);
|
||||
extraDomainsPath = map (x: "${mainPath}/${x}") certCfg.extraDomains;
|
||||
|
|
@ -524,19 +606,24 @@ in
|
|||
unique (flatten (mapAttrsToList pathCfg cfg.certs.letsencrypt));
|
||||
};
|
||||
|
||||
services.prometheus.scrapeConfigs = let
|
||||
scrapeCfg = name: certCfg: [{
|
||||
services.prometheus.scrapeConfigs =
|
||||
let
|
||||
scrapeCfg = name: certCfg: [
|
||||
{
|
||||
job_name = "node-cert-${name}";
|
||||
static_configs = [{
|
||||
targets = ["127.0.0.1:${toString config.services.prometheus.exporters.node-cert.port}"];
|
||||
static_configs = [
|
||||
{
|
||||
targets = [ "127.0.0.1:${toString config.services.prometheus.exporters.node-cert.port}" ];
|
||||
labels = {
|
||||
"hostname" = config.networking.hostName;
|
||||
"domain" = certCfg.domain;
|
||||
};
|
||||
}];
|
||||
}];
|
||||
}
|
||||
];
|
||||
}
|
||||
];
|
||||
in
|
||||
optionals (cfg.certs.letsencrypt != {}) (flatten (mapAttrsToList scrapeCfg cfg.certs.letsencrypt));
|
||||
optionals (cfg.certs.letsencrypt != { }) (flatten (mapAttrsToList scrapeCfg cfg.certs.letsencrypt));
|
||||
}
|
||||
];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,28 +1,41 @@
|
|||
# Inspired from https://github.com/NixOS/nixpkgs/pull/231152 but made it so we can have multiple instances.
|
||||
{ config, lib, pkgs, ... }:
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.shb.tinyproxy;
|
||||
|
||||
mkValueStringTinyproxy = with lib; v:
|
||||
if true == v then "yes"
|
||||
else if false == v then "no"
|
||||
else generators.mkValueStringDefault {} v;
|
||||
mkValueStringTinyproxy =
|
||||
with lib;
|
||||
v:
|
||||
if true == v then
|
||||
"yes"
|
||||
else if false == v then
|
||||
"no"
|
||||
else
|
||||
generators.mkValueStringDefault { } v;
|
||||
|
||||
mkKeyValueTinyproxy = {
|
||||
mkValueString ? mkValueStringDefault {}
|
||||
}: sep: k: v:
|
||||
if null == v then ""
|
||||
else "${lib.strings.escape [sep] k}${sep}${mkValueString v}";
|
||||
mkKeyValueTinyproxy =
|
||||
{
|
||||
mkValueString ? mkValueStringDefault { },
|
||||
}:
|
||||
sep: k: v:
|
||||
if null == v then "" else "${lib.strings.escape [ sep ] k}${sep}${mkValueString v}";
|
||||
|
||||
settingsFormat = (pkgs.formats.keyValue {
|
||||
settingsFormat = (
|
||||
pkgs.formats.keyValue {
|
||||
mkKeyValue = mkKeyValueTinyproxy {
|
||||
mkValueString = mkValueStringTinyproxy;
|
||||
} " ";
|
||||
listsAsDuplicateKeys= true;
|
||||
});
|
||||
listsAsDuplicateKeys = true;
|
||||
}
|
||||
);
|
||||
|
||||
configFile = name: cfg: settingsFormat.generate "tinyproxy-${name}.conf" cfg.settings;
|
||||
in
|
||||
|
|
@ -33,7 +46,7 @@ in
|
|||
options = {
|
||||
enable = mkEnableOption "Tinyproxy daemon";
|
||||
|
||||
package = mkPackageOption pkgs "tinyproxy" {};
|
||||
package = mkPackageOption pkgs "tinyproxy" { };
|
||||
|
||||
dynamicBindFile = mkOption {
|
||||
description = ''
|
||||
|
|
@ -47,7 +60,8 @@ in
|
|||
Configuration for [tinyproxy](https://tinyproxy.github.io/).
|
||||
'';
|
||||
default = { };
|
||||
example = literalExpression ''{
|
||||
example = literalExpression ''
|
||||
{
|
||||
Port 8888;
|
||||
Listen 127.0.0.1;
|
||||
Timeout 600;
|
||||
|
|
@ -55,7 +69,9 @@ in
|
|||
Anonymous = ['"Host"' '"Authorization"'];
|
||||
ReversePath = '"/example/" "http://www.example.com/"';
|
||||
}'';
|
||||
type = types.submodule ({name, ...}: {
|
||||
type = types.submodule (
|
||||
{ name, ... }:
|
||||
{
|
||||
freeformType = settingsFormat.type;
|
||||
options = {
|
||||
Listen = mkOption {
|
||||
|
|
@ -74,7 +90,7 @@ in
|
|||
};
|
||||
Anonymous = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [];
|
||||
default = [ ];
|
||||
description = ''
|
||||
If an `Anonymous` keyword is present, then anonymous proxying is enabled. The
|
||||
headers listed with `Anonymous` are allowed through, while all others are denied.
|
||||
|
|
@ -91,7 +107,8 @@ in
|
|||
'';
|
||||
};
|
||||
};
|
||||
});
|
||||
}
|
||||
);
|
||||
};
|
||||
};
|
||||
};
|
||||
|
|
@ -99,7 +116,7 @@ in
|
|||
{
|
||||
shb.tinyproxy = mkOption {
|
||||
description = "Tinyproxy instances.";
|
||||
default = {};
|
||||
default = { };
|
||||
type = types.attrsOf instanceOption;
|
||||
};
|
||||
};
|
||||
|
|
@ -107,7 +124,9 @@ in
|
|||
config = {
|
||||
systemd.services =
|
||||
let
|
||||
instanceConfig = name: c: mkIf c.enable {
|
||||
instanceConfig =
|
||||
name: c:
|
||||
mkIf c.enable {
|
||||
"tinyproxy-${name}" = {
|
||||
description = "TinyProxy daemon - instance ${name}";
|
||||
after = [ "network.target" ];
|
||||
|
|
@ -126,12 +145,15 @@ in
|
|||
RestartMaxDelaySec = "10s";
|
||||
ConfigurationDirectory = "tinyproxy";
|
||||
};
|
||||
preStart = concatStringsSep "\n" ([
|
||||
preStart = concatStringsSep "\n" (
|
||||
[
|
||||
"cat ${configFile name c} > /etc/tinyproxy/${name}.conf"
|
||||
] ++ optionals (c.dynamicBindFile != "") [
|
||||
]
|
||||
++ optionals (c.dynamicBindFile != "") [
|
||||
"echo -n 'Bind ' >> /etc/tinyproxy/${name}.conf"
|
||||
"cat ${c.dynamicBindFile} >> /etc/tinyproxy/${name}.conf"
|
||||
]);
|
||||
]
|
||||
);
|
||||
};
|
||||
};
|
||||
in
|
||||
|
|
@ -141,7 +163,7 @@ in
|
|||
group = "tinyproxy";
|
||||
isSystemUser = true;
|
||||
};
|
||||
users.groups.tinyproxy = {};
|
||||
users.groups.tinyproxy = { };
|
||||
};
|
||||
|
||||
meta.maintainers = with maintainers; [ tcheronneau ];
|
||||
|
|
|
|||
|
|
@ -1,4 +1,9 @@
|
|||
{ config, pkgs, lib, ... }:
|
||||
{
|
||||
config,
|
||||
pkgs,
|
||||
lib,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
cfg = config.shb.vpn;
|
||||
|
|
@ -6,12 +11,14 @@ let
|
|||
quoteEach = lib.concatMapStrings (x: ''"${x}"'');
|
||||
|
||||
nordvpnConfig =
|
||||
{ name
|
||||
, dev
|
||||
, authFile
|
||||
, remoteServerIP
|
||||
, dependentServices ? []
|
||||
}: ''
|
||||
{
|
||||
name,
|
||||
dev,
|
||||
authFile,
|
||||
remoteServerIP,
|
||||
dependentServices ? [ ],
|
||||
}:
|
||||
''
|
||||
client
|
||||
dev ${dev}
|
||||
proto tcp
|
||||
|
|
@ -103,10 +110,16 @@ let
|
|||
</tls-auth>
|
||||
'';
|
||||
|
||||
routeUp = name: dependentServices: pkgs.writeShellApplication {
|
||||
routeUp =
|
||||
name: dependentServices:
|
||||
pkgs.writeShellApplication {
|
||||
name = "routeUp.sh";
|
||||
|
||||
runtimeInputs = [ pkgs.iproute2 pkgs.systemd pkgs.nettools ];
|
||||
runtimeInputs = [
|
||||
pkgs.iproute2
|
||||
pkgs.systemd
|
||||
pkgs.nettools
|
||||
];
|
||||
|
||||
text = ''
|
||||
echo "Running route-up..."
|
||||
|
|
@ -150,10 +163,17 @@ let
|
|||
'';
|
||||
};
|
||||
|
||||
routeDown = name: dependentServices: pkgs.writeShellApplication {
|
||||
routeDown =
|
||||
name: dependentServices:
|
||||
pkgs.writeShellApplication {
|
||||
name = "routeDown.sh";
|
||||
|
||||
runtimeInputs = [ pkgs.iproute2 pkgs.systemd pkgs.nettools pkgs.coreutils ];
|
||||
runtimeInputs = [
|
||||
pkgs.iproute2
|
||||
pkgs.systemd
|
||||
pkgs.nettools
|
||||
pkgs.coreutils
|
||||
];
|
||||
|
||||
text = ''
|
||||
echo "Running route-down..."
|
||||
|
|
@ -205,7 +225,7 @@ in
|
|||
options = {
|
||||
enable = lib.mkEnableOption "OpenVPN config";
|
||||
|
||||
package = lib.mkPackageOption pkgs "openvpn" {};
|
||||
package = lib.mkPackageOption pkgs "openvpn" { };
|
||||
|
||||
provider = lib.mkOption {
|
||||
description = "VPN provider, if given uses ready-made configuration.";
|
||||
|
|
@ -246,7 +266,7 @@ in
|
|||
{
|
||||
shb.vpn = lib.mkOption {
|
||||
description = "OpenVPN instances.";
|
||||
default = {};
|
||||
default = { };
|
||||
type = lib.types.attrsOf instanceOption;
|
||||
};
|
||||
};
|
||||
|
|
@ -254,7 +274,9 @@ in
|
|||
config = {
|
||||
services.openvpn.servers =
|
||||
let
|
||||
instanceConfig = name: c: lib.mkIf c.enable {
|
||||
instanceConfig =
|
||||
name: c:
|
||||
lib.mkIf c.enable {
|
||||
${name} = {
|
||||
autoStart = true;
|
||||
|
||||
|
|
@ -270,17 +292,20 @@ in
|
|||
in
|
||||
lib.mkMerge (lib.mapAttrsToList instanceConfig cfg);
|
||||
|
||||
systemd.tmpfiles.rules = map (name:
|
||||
"d /tmp/openvpn/${name}.status 0700 root root"
|
||||
) (lib.attrNames cfg);
|
||||
systemd.tmpfiles.rules = map (name: "d /tmp/openvpn/${name}.status 0700 root root") (
|
||||
lib.attrNames cfg
|
||||
);
|
||||
|
||||
networking.iproute2.enable = true;
|
||||
networking.iproute2.rttablesExtraConfig =
|
||||
lib.concatStringsSep "\n" (lib.mapAttrsToList (name: c: "${toString c.routingNumber} ${name}") cfg);
|
||||
networking.iproute2.rttablesExtraConfig = lib.concatStringsSep "\n" (
|
||||
lib.mapAttrsToList (name: c: "${toString c.routingNumber} ${name}") cfg
|
||||
);
|
||||
|
||||
shb.tinyproxy =
|
||||
let
|
||||
instanceConfig = name: c: lib.mkIf (c.enable && c.proxyPort != null) {
|
||||
instanceConfig =
|
||||
name: c:
|
||||
lib.mkIf (c.enable && c.proxyPort != null) {
|
||||
${name} = {
|
||||
enable = true;
|
||||
# package = pkgs.tinyproxy.overrideAttrs (old: {
|
||||
|
|
@ -299,7 +324,10 @@ in
|
|||
Listen = "127.0.0.1";
|
||||
Syslog = "On";
|
||||
LogLevel = "Info";
|
||||
Allow = [ "127.0.0.1" "::1" ];
|
||||
Allow = [
|
||||
"127.0.0.1"
|
||||
"::1"
|
||||
];
|
||||
ViaProxyName = ''"tinyproxy"'';
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,4 +1,9 @@
|
|||
{ config, pkgs, lib, ... }:
|
||||
{
|
||||
config,
|
||||
pkgs,
|
||||
lib,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
cfg = config.shb.zfs;
|
||||
|
|
@ -21,11 +26,12 @@ in
|
|||
This block implements the following contracts:
|
||||
- mount
|
||||
'';
|
||||
default = {};
|
||||
default = { };
|
||||
example = lib.literalExpression ''
|
||||
shb.zfs."safe/postgresql".path = "/var/lib/postgresql";
|
||||
'';
|
||||
type = lib.types.attrsOf (lib.types.submodule {
|
||||
type = lib.types.attrsOf (
|
||||
lib.types.submodule {
|
||||
options = {
|
||||
enable = lib.mkEnableOption "shb.zfs.datasets";
|
||||
|
||||
|
|
@ -40,19 +46,23 @@ in
|
|||
description = "Path this dataset should be mounted on.";
|
||||
};
|
||||
};
|
||||
});
|
||||
}
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
config = {
|
||||
assertions = [
|
||||
{
|
||||
assertion = lib.any (x: x.poolName == null) (lib.mapAttrsToList (n: v: v) cfg.datasets) -> cfg.defaultPoolName != null;
|
||||
assertion =
|
||||
lib.any (x: x.poolName == null) (lib.mapAttrsToList (n: v: v) cfg.datasets)
|
||||
-> cfg.defaultPoolName != null;
|
||||
message = "Cannot have both datasets.poolName and defaultPoolName set to null";
|
||||
}
|
||||
];
|
||||
|
||||
system.activationScripts = lib.mapAttrs' (name: cfg':
|
||||
system.activationScripts = lib.mapAttrs' (
|
||||
name: cfg':
|
||||
let
|
||||
dataset = (if cfg'.poolName != null then cfg'.poolName else cfg.defaultPoolName) + "/" + name;
|
||||
in
|
||||
|
|
@ -68,6 +78,7 @@ in
|
|||
mountpoint=${cfg'.path} \
|
||||
${dataset}
|
||||
'';
|
||||
}) cfg.datasets;
|
||||
}
|
||||
) cfg.datasets;
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,22 +1,35 @@
|
|||
{ lib, ... }:
|
||||
let
|
||||
inherit (lib) concatStringsSep literalMD mkOption optionalAttrs optionalString;
|
||||
inherit (lib.types) listOf nonEmptyListOf submodule str;
|
||||
inherit (lib)
|
||||
concatStringsSep
|
||||
literalMD
|
||||
mkOption
|
||||
optionalAttrs
|
||||
optionalString
|
||||
;
|
||||
inherit (lib.types)
|
||||
listOf
|
||||
nonEmptyListOf
|
||||
submodule
|
||||
str
|
||||
;
|
||||
inherit (lib.shb) anyNotNull;
|
||||
in
|
||||
{
|
||||
mkRequest =
|
||||
{ user ? "",
|
||||
{
|
||||
user ? "",
|
||||
userText ? null,
|
||||
sourceDirectories ? [ "/var/lib/example" ],
|
||||
sourceDirectoriesText ? null,
|
||||
excludePatterns ? [],
|
||||
excludePatterns ? [ ],
|
||||
excludePatternsText ? null,
|
||||
beforeBackup ? [],
|
||||
beforeBackup ? [ ],
|
||||
beforeBackupText ? null,
|
||||
afterBackup ? [],
|
||||
afterBackup ? [ ],
|
||||
afterBackupText ? null,
|
||||
}: mkOption {
|
||||
}:
|
||||
mkOption {
|
||||
description = ''
|
||||
Request part of the backup contract.
|
||||
|
||||
|
|
@ -31,70 +44,100 @@ in
|
|||
};
|
||||
};
|
||||
|
||||
defaultText = optionalString (anyNotNull [
|
||||
defaultText =
|
||||
optionalString
|
||||
(anyNotNull [
|
||||
userText
|
||||
sourceDirectoriesText
|
||||
excludePatternsText
|
||||
beforeBackupText
|
||||
afterBackupText
|
||||
]) (literalMD ''
|
||||
])
|
||||
(literalMD ''
|
||||
{
|
||||
user = ${if userText != null then userText else user};
|
||||
sourceDirectories = ${if sourceDirectoriesText != null then sourceDirectoriesText else "[ " + concatStringsSep " " sourceDirectories + " ]"};
|
||||
excludePatterns = ${if excludePatternsText != null then excludePatternsText else "[ " + concatStringsSep " " excludePatterns + " ]"};
|
||||
hooks.beforeBackup = ${if beforeBackupText != null then beforeBackupText else "[ " + concatStringsSep " " beforeBackup + " ]"};
|
||||
hooks.afterBackup = ${if afterBackupText != null then afterBackupText else "[ " + concatStringsSep " " afterBackup + " ]"};
|
||||
sourceDirectories = ${
|
||||
if sourceDirectoriesText != null then
|
||||
sourceDirectoriesText
|
||||
else
|
||||
"[ " + concatStringsSep " " sourceDirectories + " ]"
|
||||
};
|
||||
excludePatterns = ${
|
||||
if excludePatternsText != null then
|
||||
excludePatternsText
|
||||
else
|
||||
"[ " + concatStringsSep " " excludePatterns + " ]"
|
||||
};
|
||||
hooks.beforeBackup = ${
|
||||
if beforeBackupText != null then
|
||||
beforeBackupText
|
||||
else
|
||||
"[ " + concatStringsSep " " beforeBackup + " ]"
|
||||
};
|
||||
hooks.afterBackup = ${
|
||||
if afterBackupText != null then afterBackupText else "[ " + concatStringsSep " " afterBackup + " ]"
|
||||
};
|
||||
};
|
||||
'');
|
||||
|
||||
type = submodule {
|
||||
options = {
|
||||
user = mkOption {
|
||||
user =
|
||||
mkOption {
|
||||
description = ''
|
||||
Unix user doing the backups.
|
||||
'';
|
||||
type = str;
|
||||
example = "vaultwarden";
|
||||
default = user;
|
||||
} // optionalAttrs (userText != null) {
|
||||
}
|
||||
// optionalAttrs (userText != null) {
|
||||
defaultText = literalMD userText;
|
||||
};
|
||||
|
||||
sourceDirectories = mkOption {
|
||||
sourceDirectories =
|
||||
mkOption {
|
||||
description = "Directories to backup.";
|
||||
type = nonEmptyListOf str;
|
||||
example = "/var/lib/vaultwarden";
|
||||
default = sourceDirectories;
|
||||
} // optionalAttrs (sourceDirectoriesText != null) {
|
||||
}
|
||||
// optionalAttrs (sourceDirectoriesText != null) {
|
||||
defaultText = literalMD sourceDirectoriesText;
|
||||
};
|
||||
|
||||
excludePatterns = mkOption {
|
||||
excludePatterns =
|
||||
mkOption {
|
||||
description = "File patterns to exclude.";
|
||||
type = listOf str;
|
||||
default = excludePatterns;
|
||||
} // optionalAttrs (excludePatternsText != null) {
|
||||
}
|
||||
// optionalAttrs (excludePatternsText != null) {
|
||||
defaultText = literalMD excludePatternsText;
|
||||
};
|
||||
|
||||
hooks = mkOption {
|
||||
description = "Hooks to run around the backup.";
|
||||
default = {};
|
||||
default = { };
|
||||
type = submodule {
|
||||
options = {
|
||||
beforeBackup = mkOption {
|
||||
beforeBackup =
|
||||
mkOption {
|
||||
description = "Hooks to run before backup.";
|
||||
type = listOf str;
|
||||
default = beforeBackup;
|
||||
} // optionalAttrs (beforeBackupText != null) {
|
||||
}
|
||||
// optionalAttrs (beforeBackupText != null) {
|
||||
defaultText = literalMD beforeBackupText;
|
||||
};
|
||||
|
||||
afterBackup = mkOption {
|
||||
afterBackup =
|
||||
mkOption {
|
||||
description = "Hooks to run after backup.";
|
||||
type = listOf str;
|
||||
default = afterBackup;
|
||||
} // optionalAttrs (afterBackupText != null) {
|
||||
}
|
||||
// optionalAttrs (afterBackupText != null) {
|
||||
defaultText = literalMD afterBackupText;
|
||||
};
|
||||
};
|
||||
|
|
@ -104,12 +147,14 @@ in
|
|||
};
|
||||
};
|
||||
|
||||
mkResult = {
|
||||
mkResult =
|
||||
{
|
||||
restoreScript ? "restore",
|
||||
restoreScriptText ? null,
|
||||
backupService ? "backup.service",
|
||||
backupServiceText ? null,
|
||||
}: mkOption {
|
||||
}:
|
||||
mkOption {
|
||||
description = ''
|
||||
Result part of the backup contract.
|
||||
|
||||
|
|
@ -119,10 +164,13 @@ in
|
|||
inherit restoreScript backupService;
|
||||
};
|
||||
|
||||
defaultText = optionalString (anyNotNull [
|
||||
defaultText =
|
||||
optionalString
|
||||
(anyNotNull [
|
||||
restoreScriptText
|
||||
backupServiceText
|
||||
]) (literalMD ''
|
||||
])
|
||||
(literalMD ''
|
||||
{
|
||||
restoreScript = ${if restoreScriptText != null then restoreScriptText else restoreScript};
|
||||
backupService = ${if backupServiceText != null then backupServiceText else backupService};
|
||||
|
|
@ -131,7 +179,8 @@ in
|
|||
|
||||
type = submodule {
|
||||
options = {
|
||||
restoreScript = mkOption {
|
||||
restoreScript =
|
||||
mkOption {
|
||||
description = ''
|
||||
Name of script that can restore the database.
|
||||
One can then list snapshots with:
|
||||
|
|
@ -148,11 +197,13 @@ in
|
|||
'';
|
||||
type = str;
|
||||
default = restoreScript;
|
||||
} // optionalAttrs (restoreScriptText != null) {
|
||||
}
|
||||
// optionalAttrs (restoreScriptText != null) {
|
||||
defaultText = literalMD restoreScriptText;
|
||||
};
|
||||
|
||||
backupService = mkOption {
|
||||
backupService =
|
||||
mkOption {
|
||||
description = ''
|
||||
Name of service backing up the database.
|
||||
|
||||
|
|
@ -164,7 +215,8 @@ in
|
|||
'';
|
||||
type = str;
|
||||
default = backupService;
|
||||
} // optionalAttrs (backupServiceText != null) {
|
||||
}
|
||||
// optionalAttrs (backupServiceText != null) {
|
||||
defaultText = literalMD backupServiceText;
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{ pkgs, lib, ... }:
|
||||
let
|
||||
contracts = pkgs.callPackage ../. {};
|
||||
contracts = pkgs.callPackage ../. { };
|
||||
|
||||
inherit (lib) mkOption;
|
||||
inherit (lib.types) submodule;
|
||||
|
|
|
|||
|
|
@ -1,10 +1,17 @@
|
|||
{ pkgs, lib }:
|
||||
let
|
||||
inherit (lib) concatMapStringsSep getAttrFromPath mkIf optionalAttrs setAttrByPath;
|
||||
inherit (lib)
|
||||
concatMapStringsSep
|
||||
getAttrFromPath
|
||||
mkIf
|
||||
optionalAttrs
|
||||
setAttrByPath
|
||||
;
|
||||
in
|
||||
{ name,
|
||||
{
|
||||
name,
|
||||
providerRoot,
|
||||
modules ? [],
|
||||
modules ? [ ],
|
||||
username ? "me",
|
||||
sourceDirectories ? [
|
||||
"/opt/files/A"
|
||||
|
|
@ -12,10 +19,13 @@ in
|
|||
],
|
||||
settings, # { repository, config } -> attrset
|
||||
extraConfig ? null, # { username, config } -> attrset
|
||||
}: lib.shb.runNixOSTest {
|
||||
}:
|
||||
lib.shb.runNixOSTest {
|
||||
inherit name;
|
||||
|
||||
nodes.machine = { config, ... }: {
|
||||
nodes.machine =
|
||||
{ config, ... }:
|
||||
{
|
||||
imports = [ lib.shb.baseImports ] ++ modules;
|
||||
|
||||
config = lib.mkMerge [
|
||||
|
|
@ -36,16 +46,21 @@ in
|
|||
group = "root";
|
||||
};
|
||||
})
|
||||
(optionalAttrs (extraConfig != null) (extraConfig { inherit username config; }))
|
||||
(optionalAttrs (extraConfig != null) (extraConfig {
|
||||
inherit username config;
|
||||
}))
|
||||
];
|
||||
};
|
||||
|
||||
extraPythonPackages = p: [ p.dictdiffer ];
|
||||
skipTypeCheck = true;
|
||||
|
||||
testScript = { nodes, ... }: let
|
||||
testScript =
|
||||
{ nodes, ... }:
|
||||
let
|
||||
provider = (getAttrFromPath providerRoot nodes.machine).result;
|
||||
in ''
|
||||
in
|
||||
''
|
||||
from dictdiffer import diff
|
||||
|
||||
username = "${username}"
|
||||
|
|
|
|||
|
|
@ -1,12 +1,19 @@
|
|||
{ lib, ... }:
|
||||
let
|
||||
inherit (lib) mkOption literalExpression literalMD optionalAttrs optionalString;
|
||||
inherit (lib)
|
||||
mkOption
|
||||
literalExpression
|
||||
literalMD
|
||||
optionalAttrs
|
||||
optionalString
|
||||
;
|
||||
inherit (lib.types) submodule str;
|
||||
inherit (lib.shb) anyNotNull;
|
||||
in
|
||||
{
|
||||
mkRequest =
|
||||
{ user ? "root",
|
||||
{
|
||||
user ? "root",
|
||||
userText ? null,
|
||||
backupName ? "dump",
|
||||
backupNameText ? null,
|
||||
|
|
@ -14,7 +21,8 @@ in
|
|||
backupCmdText ? null,
|
||||
restoreCmd ? "",
|
||||
restoreCmdText ? null,
|
||||
}: mkOption {
|
||||
}:
|
||||
mkOption {
|
||||
description = ''
|
||||
Request part of the backup contract.
|
||||
|
||||
|
|
@ -23,15 +31,23 @@ in
|
|||
'';
|
||||
|
||||
default = {
|
||||
inherit user backupName backupCmd restoreCmd;
|
||||
inherit
|
||||
user
|
||||
backupName
|
||||
backupCmd
|
||||
restoreCmd
|
||||
;
|
||||
};
|
||||
|
||||
defaultText = optionalString (anyNotNull [
|
||||
defaultText =
|
||||
optionalString
|
||||
(anyNotNull [
|
||||
userText
|
||||
backupNameText
|
||||
backupCmdText
|
||||
restoreCmdText
|
||||
]) (literalMD ''
|
||||
])
|
||||
(literalMD ''
|
||||
{
|
||||
user = ${if userText != null then userText else user};
|
||||
backupName = ${if backupNameText != null then backupNameText else backupName};
|
||||
|
|
@ -42,7 +58,8 @@ in
|
|||
|
||||
type = submodule {
|
||||
options = {
|
||||
user = mkOption {
|
||||
user =
|
||||
mkOption {
|
||||
description = ''
|
||||
Unix user doing the backups.
|
||||
|
||||
|
|
@ -51,51 +68,59 @@ in
|
|||
type = str;
|
||||
example = "postgres";
|
||||
default = user;
|
||||
} // optionalAttrs (userText != null) {
|
||||
}
|
||||
// optionalAttrs (userText != null) {
|
||||
defaultText = literalMD userText;
|
||||
};
|
||||
|
||||
backupName = mkOption {
|
||||
backupName =
|
||||
mkOption {
|
||||
description = "Name of the backup in the repository.";
|
||||
type = str;
|
||||
example = "postgresql.sql";
|
||||
default = backupName;
|
||||
} // optionalAttrs (backupNameText != null) {
|
||||
}
|
||||
// optionalAttrs (backupNameText != null) {
|
||||
defaultText = literalMD backupNameText;
|
||||
};
|
||||
|
||||
backupCmd = mkOption {
|
||||
backupCmd =
|
||||
mkOption {
|
||||
description = "Command that produces the database dump on stdout.";
|
||||
type = str;
|
||||
example = literalExpression ''
|
||||
''${pkgs.postgresql}/bin/pg_dumpall | ''${pkgs.gzip}/bin/gzip --rsyncable
|
||||
'';
|
||||
default = backupCmd;
|
||||
} // optionalAttrs (backupCmdText != null) {
|
||||
}
|
||||
// optionalAttrs (backupCmdText != null) {
|
||||
defaultText = literalMD backupCmdText;
|
||||
};
|
||||
|
||||
restoreCmd = mkOption {
|
||||
restoreCmd =
|
||||
mkOption {
|
||||
description = "Command that reads the database dump on stdin and restores the database.";
|
||||
type = str;
|
||||
example = literalExpression ''
|
||||
''${pkgs.gzip}/bin/gunzip | ''${pkgs.postgresql}/bin/psql postgres
|
||||
'';
|
||||
default = restoreCmd;
|
||||
} // optionalAttrs (restoreCmdText != null) {
|
||||
}
|
||||
// optionalAttrs (restoreCmdText != null) {
|
||||
defaultText = literalMD restoreCmdText;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
mkResult = {
|
||||
mkResult =
|
||||
{
|
||||
restoreScript ? "restore",
|
||||
restoreScriptText ? null,
|
||||
backupService ? "backup.service",
|
||||
backupServiceText ? null,
|
||||
}: mkOption {
|
||||
}:
|
||||
mkOption {
|
||||
description = ''
|
||||
Result part of the backup contract.
|
||||
|
||||
|
|
@ -105,10 +130,13 @@ in
|
|||
inherit restoreScript backupService;
|
||||
};
|
||||
|
||||
defaultText = optionalString (anyNotNull [
|
||||
defaultText =
|
||||
optionalString
|
||||
(anyNotNull [
|
||||
restoreScriptText
|
||||
backupServiceText
|
||||
]) (literalMD ''
|
||||
])
|
||||
(literalMD ''
|
||||
{
|
||||
restoreScript = ${if restoreScriptText != null then restoreScriptText else restoreScript};
|
||||
backupService = ${if backupServiceText != null then backupServiceText else backupService};
|
||||
|
|
@ -117,7 +145,8 @@ in
|
|||
|
||||
type = submodule {
|
||||
options = {
|
||||
restoreScript = mkOption {
|
||||
restoreScript =
|
||||
mkOption {
|
||||
description = ''
|
||||
Name of script that can restore the database.
|
||||
One can then list snapshots with:
|
||||
|
|
@ -134,11 +163,13 @@ in
|
|||
'';
|
||||
type = str;
|
||||
default = restoreScript;
|
||||
} // optionalAttrs (restoreScriptText != null) {
|
||||
}
|
||||
// optionalAttrs (restoreScriptText != null) {
|
||||
defaultText = literalMD restoreScriptText;
|
||||
};
|
||||
|
||||
backupService = mkOption {
|
||||
backupService =
|
||||
mkOption {
|
||||
description = ''
|
||||
Name of service backing up the database.
|
||||
|
||||
|
|
@ -150,7 +181,8 @@ in
|
|||
'';
|
||||
type = str;
|
||||
default = backupService;
|
||||
} // optionalAttrs (backupServiceText != null) {
|
||||
}
|
||||
// optionalAttrs (backupServiceText != null) {
|
||||
defaultText = literalMD backupServiceText;
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{ pkgs, lib, ... }:
|
||||
let
|
||||
contracts = pkgs.callPackage ../. {};
|
||||
contracts = pkgs.callPackage ../. { };
|
||||
|
||||
inherit (lib) mkOption;
|
||||
inherit (lib.types) submodule;
|
||||
|
|
|
|||
|
|
@ -1,18 +1,27 @@
|
|||
{ pkgs, lib }:
|
||||
let
|
||||
inherit (lib) getAttrFromPath mkIf optionalAttrs setAttrByPath;
|
||||
inherit (lib)
|
||||
getAttrFromPath
|
||||
mkIf
|
||||
optionalAttrs
|
||||
setAttrByPath
|
||||
;
|
||||
in
|
||||
{ name,
|
||||
{
|
||||
name,
|
||||
requesterRoot,
|
||||
providerRoot,
|
||||
extraConfig ? null, # { config, database } -> attrset
|
||||
modules ? [],
|
||||
modules ? [ ],
|
||||
database ? "me",
|
||||
settings, # { repository, config } -> attrset
|
||||
}: lib.shb.runNixOSTest {
|
||||
}:
|
||||
lib.shb.runNixOSTest {
|
||||
inherit name;
|
||||
|
||||
nodes.machine = { config, ... }: {
|
||||
nodes.machine =
|
||||
{ config, ... }:
|
||||
{
|
||||
imports = [ lib.shb.baseImports ] ++ modules;
|
||||
config = lib.mkMerge [
|
||||
(setAttrByPath providerRoot {
|
||||
|
|
@ -29,13 +38,18 @@ in
|
|||
group = "root";
|
||||
};
|
||||
})
|
||||
(optionalAttrs (extraConfig != null) (extraConfig { inherit config database; }))
|
||||
(optionalAttrs (extraConfig != null) (extraConfig {
|
||||
inherit config database;
|
||||
}))
|
||||
];
|
||||
};
|
||||
|
||||
testScript = { nodes, ... }: let
|
||||
testScript =
|
||||
{ nodes, ... }:
|
||||
let
|
||||
provider = getAttrFromPath providerRoot nodes.machine;
|
||||
in ''
|
||||
in
|
||||
''
|
||||
import csv
|
||||
|
||||
start_all()
|
||||
|
|
|
|||
|
|
@ -4,28 +4,33 @@ let
|
|||
inherit (lib.types) anything;
|
||||
|
||||
mkContractFunctions =
|
||||
{ mkRequest,
|
||||
{
|
||||
mkRequest,
|
||||
mkResult,
|
||||
}: {
|
||||
}:
|
||||
{
|
||||
mkRequester = requestCfg: {
|
||||
request = mkRequest requestCfg;
|
||||
|
||||
result = mkResult {};
|
||||
result = mkResult { };
|
||||
};
|
||||
|
||||
mkProvider =
|
||||
{ resultCfg,
|
||||
settings ? {},
|
||||
}: {
|
||||
request = mkRequest {};
|
||||
{
|
||||
resultCfg,
|
||||
settings ? { },
|
||||
}:
|
||||
{
|
||||
request = mkRequest { };
|
||||
|
||||
result = mkResult resultCfg;
|
||||
} // optionalAttrs (settings != {}) { inherit settings; };
|
||||
}
|
||||
// optionalAttrs (settings != { }) { inherit settings; };
|
||||
|
||||
contract = {
|
||||
request = mkRequest {};
|
||||
request = mkRequest { };
|
||||
|
||||
result = mkResult {};
|
||||
result = mkResult { };
|
||||
|
||||
settings = mkOption {
|
||||
description = ''
|
||||
|
|
@ -36,9 +41,10 @@ let
|
|||
};
|
||||
};
|
||||
|
||||
importContract = module:
|
||||
importContract =
|
||||
module:
|
||||
let
|
||||
importedModule = pkgs.callPackage module {};
|
||||
importedModule = pkgs.callPackage module { };
|
||||
in
|
||||
mkContractFunctions {
|
||||
inherit (importedModule) mkRequest mkResult;
|
||||
|
|
@ -47,12 +53,12 @@ in
|
|||
{
|
||||
databasebackup = importContract ./databasebackup.nix;
|
||||
backup = importContract ./backup.nix;
|
||||
mount = pkgs.callPackage ./mount.nix {};
|
||||
mount = pkgs.callPackage ./mount.nix { };
|
||||
secret = importContract ./secret.nix;
|
||||
ssl = pkgs.callPackage ./ssl.nix {};
|
||||
ssl = pkgs.callPackage ./ssl.nix { };
|
||||
test = {
|
||||
secret = pkgs.callPackage ./secret/test.nix {};
|
||||
databasebackup = pkgs.callPackage ./databasebackup/test.nix {};
|
||||
backup = pkgs.callPackage ./backup/test.nix {};
|
||||
secret = pkgs.callPackage ./secret/test.nix { };
|
||||
databasebackup = pkgs.callPackage ./databasebackup/test.nix { };
|
||||
backup = pkgs.callPackage ./backup/test.nix { };
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,20 +1,28 @@
|
|||
{ lib, ... }:
|
||||
let
|
||||
inherit (lib) concatStringsSep literalMD mkOption optionalAttrs optionalString;
|
||||
inherit (lib)
|
||||
concatStringsSep
|
||||
literalMD
|
||||
mkOption
|
||||
optionalAttrs
|
||||
optionalString
|
||||
;
|
||||
inherit (lib.types) listOf submodule str;
|
||||
inherit (lib.shb) anyNotNull;
|
||||
in
|
||||
{
|
||||
mkRequest =
|
||||
{ mode ? "0400",
|
||||
{
|
||||
mode ? "0400",
|
||||
modeText ? null,
|
||||
owner ? "root",
|
||||
ownerText ? null,
|
||||
group ? "root",
|
||||
groupText ? null,
|
||||
restartUnits ? [],
|
||||
restartUnits ? [ ],
|
||||
restartUnitsText ? null,
|
||||
}: mkOption {
|
||||
}:
|
||||
mkOption {
|
||||
description = ''
|
||||
Request part of the secret contract.
|
||||
|
||||
|
|
@ -23,64 +31,87 @@ in
|
|||
'';
|
||||
|
||||
default = {
|
||||
inherit mode owner group restartUnits;
|
||||
inherit
|
||||
mode
|
||||
owner
|
||||
group
|
||||
restartUnits
|
||||
;
|
||||
};
|
||||
|
||||
defaultText = optionalString (anyNotNull [
|
||||
defaultText =
|
||||
optionalString
|
||||
(anyNotNull [
|
||||
modeText
|
||||
ownerText
|
||||
groupText
|
||||
restartUnitsText
|
||||
]) (literalMD ''
|
||||
])
|
||||
(literalMD ''
|
||||
{
|
||||
mode = ${if modeText != null then modeText else mode};
|
||||
owner = ${if ownerText != null then ownerText else owner};
|
||||
group = ${if groupText != null then groupText else group};
|
||||
restartUnits = ${if restartUnitsText != null then restartUnitsText else "[ " + concatStringsSep " " restartUnits + " ]"};
|
||||
restartUnits = ${
|
||||
if restartUnitsText != null then
|
||||
restartUnitsText
|
||||
else
|
||||
"[ " + concatStringsSep " " restartUnits + " ]"
|
||||
};
|
||||
}
|
||||
'');
|
||||
|
||||
type = submodule {
|
||||
options = {
|
||||
mode = mkOption {
|
||||
mode =
|
||||
mkOption {
|
||||
description = ''
|
||||
Mode of the secret file.
|
||||
'';
|
||||
type = str;
|
||||
default = mode;
|
||||
} // optionalAttrs (modeText != null) {
|
||||
}
|
||||
// optionalAttrs (modeText != null) {
|
||||
defaultText = literalMD modeText;
|
||||
};
|
||||
|
||||
owner = mkOption ({
|
||||
owner = mkOption (
|
||||
{
|
||||
description = ''
|
||||
Linux user owning the secret file.
|
||||
'';
|
||||
type = str;
|
||||
default = owner;
|
||||
} // optionalAttrs (ownerText != null) {
|
||||
}
|
||||
// optionalAttrs (ownerText != null) {
|
||||
defaultText = literalMD ownerText;
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
group = mkOption {
|
||||
group =
|
||||
mkOption {
|
||||
description = ''
|
||||
Linux group owning the secret file.
|
||||
'';
|
||||
type = str;
|
||||
default = group;
|
||||
} // optionalAttrs (groupText != null) {
|
||||
}
|
||||
// optionalAttrs (groupText != null) {
|
||||
defaultText = literalMD groupText;
|
||||
};
|
||||
|
||||
restartUnits = mkOption ({
|
||||
restartUnits = mkOption (
|
||||
{
|
||||
description = ''
|
||||
Systemd units to restart after the secret is updated.
|
||||
'';
|
||||
type = listOf str;
|
||||
default = restartUnits;
|
||||
} // optionalAttrs (restartUnitsText != null) {
|
||||
}
|
||||
// optionalAttrs (restartUnitsText != null) {
|
||||
defaultText = literalMD restartUnitsText;
|
||||
});
|
||||
}
|
||||
);
|
||||
};
|
||||
};
|
||||
};
|
||||
|
|
@ -90,7 +121,8 @@ in
|
|||
path ? "/run/secrets/secret",
|
||||
pathText ? null,
|
||||
}:
|
||||
mkOption ({
|
||||
mkOption (
|
||||
{
|
||||
description = ''
|
||||
Result part of the secret contract.
|
||||
|
||||
|
|
@ -101,7 +133,8 @@ in
|
|||
};
|
||||
type = submodule {
|
||||
options = {
|
||||
path = mkOption {
|
||||
path =
|
||||
mkOption {
|
||||
type = lib.types.path;
|
||||
description = ''
|
||||
Path to the file containing the secret generated out of band.
|
||||
|
|
@ -110,14 +143,17 @@ in
|
|||
it is not available through the nix store.
|
||||
'';
|
||||
default = path;
|
||||
} // optionalAttrs (pathText != null) {
|
||||
}
|
||||
// optionalAttrs (pathText != null) {
|
||||
defaultText = pathText;
|
||||
};
|
||||
};
|
||||
};
|
||||
} // optionalAttrs (pathText != null) {
|
||||
}
|
||||
// optionalAttrs (pathText != null) {
|
||||
defaultText = {
|
||||
path = pathText;
|
||||
};
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{ pkgs, lib, ... }:
|
||||
let
|
||||
contracts = pkgs.callPackage ../. {};
|
||||
contracts = pkgs.callPackage ../. { };
|
||||
|
||||
inherit (lib) mkOption;
|
||||
inherit (lib.types) submodule;
|
||||
|
|
|
|||
|
|
@ -3,24 +3,33 @@ let
|
|||
inherit (lib) getAttrFromPath setAttrByPath;
|
||||
inherit (lib) mkIf;
|
||||
in
|
||||
{ name,
|
||||
{
|
||||
name,
|
||||
configRoot,
|
||||
settingsCfg, # str -> attrset
|
||||
modules ? [],
|
||||
modules ? [ ],
|
||||
owner ? "root",
|
||||
group ? "root",
|
||||
mode ? "0400",
|
||||
restartUnits ? [ "myunit.service" ],
|
||||
}: lib.shb.runNixOSTest {
|
||||
}:
|
||||
lib.shb.runNixOSTest {
|
||||
name = "secret_${name}_${owner}_${group}_${mode}";
|
||||
|
||||
nodes.machine = { config, ... }: {
|
||||
nodes.machine =
|
||||
{ config, ... }:
|
||||
{
|
||||
imports = [ lib.shb.baseImports ] ++ modules;
|
||||
config = lib.mkMerge [
|
||||
(setAttrByPath configRoot {
|
||||
A = {
|
||||
request = {
|
||||
inherit owner group mode restartUnits;
|
||||
inherit
|
||||
owner
|
||||
group
|
||||
mode
|
||||
restartUnits
|
||||
;
|
||||
};
|
||||
settings = settingsCfg "secretA";
|
||||
};
|
||||
|
|
@ -29,12 +38,13 @@ in
|
|||
users.users.${owner}.isNormalUser = true;
|
||||
})
|
||||
(mkIf (group != "root") {
|
||||
users.groups.${group} = {};
|
||||
users.groups.${group} = { };
|
||||
})
|
||||
];
|
||||
};
|
||||
|
||||
testScript = { nodes, ... }:
|
||||
testScript =
|
||||
{ nodes, ... }:
|
||||
let
|
||||
result = (getAttrFromPath configRoot nodes.machine)."A".result;
|
||||
in
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{ pkgs, lib, ... }:
|
||||
let
|
||||
contracts = pkgs.callPackage ../. {};
|
||||
contracts = pkgs.callPackage ../. { };
|
||||
in
|
||||
{
|
||||
options.shb.contracts.ssl = lib.mkOption {
|
||||
|
|
|
|||
|
|
@ -1,9 +1,14 @@
|
|||
{ config, pkgs, lib, ... }:
|
||||
{
|
||||
config,
|
||||
pkgs,
|
||||
lib,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
cfg = config.shb.arr;
|
||||
|
||||
contracts = pkgs.callPackage ../contracts {};
|
||||
contracts = pkgs.callPackage ../contracts { };
|
||||
|
||||
apps = {
|
||||
radarr = {
|
||||
|
|
@ -11,7 +16,7 @@ let
|
|||
moreOptions = {
|
||||
settings = lib.mkOption {
|
||||
description = "Specific options for radarr.";
|
||||
default = {};
|
||||
default = { };
|
||||
type = lib.types.submodule {
|
||||
freeformType = apps.radarr.settingsFormat.type;
|
||||
options = {
|
||||
|
|
@ -20,7 +25,10 @@ let
|
|||
description = "Path to api key secret file.";
|
||||
};
|
||||
LogLevel = lib.mkOption {
|
||||
type = lib.types.enum ["debug" "info"];
|
||||
type = lib.types.enum [
|
||||
"debug"
|
||||
"info"
|
||||
];
|
||||
description = "Log level.";
|
||||
default = "info";
|
||||
};
|
||||
|
|
@ -69,7 +77,7 @@ let
|
|||
moreOptions = {
|
||||
settings = lib.mkOption {
|
||||
description = "Specific options for sonarr.";
|
||||
default = {};
|
||||
default = { };
|
||||
type = lib.types.submodule {
|
||||
freeformType = apps.sonarr.settingsFormat.type;
|
||||
options = {
|
||||
|
|
@ -78,7 +86,10 @@ let
|
|||
description = "Path to api key secret file.";
|
||||
};
|
||||
LogLevel = lib.mkOption {
|
||||
type = lib.types.enum ["debug" "info"];
|
||||
type = lib.types.enum [
|
||||
"debug"
|
||||
"info"
|
||||
];
|
||||
description = "Log level.";
|
||||
default = "info";
|
||||
};
|
||||
|
|
@ -122,12 +133,15 @@ let
|
|||
moreOptions = {
|
||||
settings = lib.mkOption {
|
||||
description = "Specific options for bazarr.";
|
||||
default = {};
|
||||
default = { };
|
||||
type = lib.types.submodule {
|
||||
freeformType = apps.bazarr.settingsFormat.type;
|
||||
options = {
|
||||
LogLevel = lib.mkOption {
|
||||
type = lib.types.enum ["debug" "info"];
|
||||
type = lib.types.enum [
|
||||
"debug"
|
||||
"info"
|
||||
];
|
||||
description = "Log level.";
|
||||
default = "info";
|
||||
};
|
||||
|
|
@ -147,12 +161,15 @@ let
|
|||
moreOptions = {
|
||||
settings = lib.mkOption {
|
||||
description = "Specific options for readarr.";
|
||||
default = {};
|
||||
default = { };
|
||||
type = lib.types.submodule {
|
||||
freeformType = apps.readarr.settingsFormat.type;
|
||||
options = {
|
||||
LogLevel = lib.mkOption {
|
||||
type = lib.types.enum ["debug" "info"];
|
||||
type = lib.types.enum [
|
||||
"debug"
|
||||
"info"
|
||||
];
|
||||
description = "Log level.";
|
||||
default = "info";
|
||||
};
|
||||
|
|
@ -171,12 +188,15 @@ let
|
|||
moreOptions = {
|
||||
settings = lib.mkOption {
|
||||
description = "Specific options for lidarr.";
|
||||
default = {};
|
||||
default = { };
|
||||
type = lib.types.submodule {
|
||||
freeformType = apps.lidarr.settingsFormat.type;
|
||||
options = {
|
||||
LogLevel = lib.mkOption {
|
||||
type = lib.types.enum ["debug" "info"];
|
||||
type = lib.types.enum [
|
||||
"debug"
|
||||
"info"
|
||||
];
|
||||
description = "Log level.";
|
||||
default = "info";
|
||||
};
|
||||
|
|
@ -191,11 +211,11 @@ let
|
|||
};
|
||||
};
|
||||
jackett = {
|
||||
settingsFormat = pkgs.formats.json {};
|
||||
settingsFormat = pkgs.formats.json { };
|
||||
moreOptions = {
|
||||
settings = lib.mkOption {
|
||||
description = "Specific options for jackett.";
|
||||
default = {};
|
||||
default = { };
|
||||
type = lib.types.submodule {
|
||||
freeformType = apps.jackett.settingsFormat.type;
|
||||
options = {
|
||||
|
|
@ -214,7 +234,12 @@ let
|
|||
default = null;
|
||||
};
|
||||
ProxyType = lib.mkOption {
|
||||
type = lib.types.enum [ "-1" "0" "1" "2" ];
|
||||
type = lib.types.enum [
|
||||
"-1"
|
||||
"0"
|
||||
"1"
|
||||
"2"
|
||||
];
|
||||
default = "-1";
|
||||
description = ''
|
||||
-1 = disabled
|
||||
|
|
@ -256,8 +281,17 @@ let
|
|||
};
|
||||
};
|
||||
|
||||
vhosts = { extraBypassResources ? [] }: c: {
|
||||
inherit (c) subdomain domain authEndpoint ssl;
|
||||
vhosts =
|
||||
{
|
||||
extraBypassResources ? [ ],
|
||||
}:
|
||||
c: {
|
||||
inherit (c)
|
||||
subdomain
|
||||
domain
|
||||
authEndpoint
|
||||
ssl
|
||||
;
|
||||
|
||||
upstream = "http://127.0.0.1:${toString c.settings.Port}";
|
||||
autheliaRules = lib.optionals (!(isNull c.authEndpoint)) [
|
||||
|
|
@ -272,14 +306,17 @@ let
|
|||
{
|
||||
domain = "${c.subdomain}.${c.domain}";
|
||||
policy = "two_factor";
|
||||
subject = ["group:arr_user"];
|
||||
subject = [ "group:arr_user" ];
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
appOption = name: c: lib.nameValuePair name (lib.mkOption {
|
||||
appOption =
|
||||
name: c:
|
||||
lib.nameValuePair name (
|
||||
lib.mkOption {
|
||||
description = "Configuration for ${name}";
|
||||
default = {};
|
||||
default = { };
|
||||
type = lib.types.submodule {
|
||||
options = {
|
||||
enable = lib.mkEnableOption name;
|
||||
|
|
@ -319,20 +356,26 @@ let
|
|||
description = ''
|
||||
Backup configuration.
|
||||
'';
|
||||
default = {};
|
||||
default = { };
|
||||
type = lib.types.submodule {
|
||||
options = contracts.backup.mkRequester {
|
||||
user = name;
|
||||
sourceDirectories = [
|
||||
cfg.${name}.dataDir
|
||||
];
|
||||
excludePatterns = [".db-shm" ".db-wal" ".mono"];
|
||||
excludePatterns = [
|
||||
".db-shm"
|
||||
".db-wal"
|
||||
".mono"
|
||||
];
|
||||
};
|
||||
};
|
||||
};
|
||||
} // (c.moreOptions or {});
|
||||
}
|
||||
// (c.moreOptions or { });
|
||||
};
|
||||
});
|
||||
}
|
||||
);
|
||||
in
|
||||
{
|
||||
imports = [
|
||||
|
|
@ -356,7 +399,8 @@ in
|
|||
};
|
||||
|
||||
systemd.services.radarr.preStart = lib.shb.replaceSecrets {
|
||||
userConfig = cfg'.settings
|
||||
userConfig =
|
||||
cfg'.settings
|
||||
// (lib.optionalAttrs isSSOEnabled {
|
||||
AuthenticationRequired = "DisabledForLocalAddresses";
|
||||
AuthenticationMethod = "External";
|
||||
|
|
@ -365,8 +409,9 @@ in
|
|||
generator = lib.shb.replaceSecretsFormatAdapter apps.radarr.settingsFormat;
|
||||
};
|
||||
|
||||
shb.nginx.vhosts = [ (vhosts {} cfg') ];
|
||||
}))
|
||||
shb.nginx.vhosts = [ (vhosts { } cfg') ];
|
||||
}
|
||||
))
|
||||
|
||||
(lib.mkIf cfg.sonarr.enable (
|
||||
let
|
||||
|
|
@ -385,7 +430,8 @@ in
|
|||
};
|
||||
|
||||
systemd.services.sonarr.preStart = lib.shb.replaceSecrets {
|
||||
userConfig = cfg'.settings
|
||||
userConfig =
|
||||
cfg'.settings
|
||||
// (lib.optionalAttrs isSSOEnabled {
|
||||
AuthenticationRequired = "DisabledForLocalAddresses";
|
||||
AuthenticationMethod = "External";
|
||||
|
|
@ -394,8 +440,9 @@ in
|
|||
generator = apps.sonarr.settingsFormat.generate;
|
||||
};
|
||||
|
||||
shb.nginx.vhosts = [ (vhosts {} cfg') ];
|
||||
}))
|
||||
shb.nginx.vhosts = [ (vhosts { } cfg') ];
|
||||
}
|
||||
))
|
||||
|
||||
(lib.mkIf cfg.bazarr.enable (
|
||||
let
|
||||
|
|
@ -411,7 +458,8 @@ in
|
|||
extraGroups = [ "media" ];
|
||||
};
|
||||
systemd.services.bazarr.preStart = lib.shb.replaceSecrets {
|
||||
userConfig = cfg'.settings
|
||||
userConfig =
|
||||
cfg'.settings
|
||||
// (lib.optionalAttrs isSSOEnabled {
|
||||
AuthenticationRequired = "DisabledForLocalAddresses";
|
||||
AuthenticationMethod = "External";
|
||||
|
|
@ -420,8 +468,9 @@ in
|
|||
generator = apps.bazarr.settingsFormat.generate;
|
||||
};
|
||||
|
||||
shb.nginx.vhosts = [ (vhosts {} cfg') ];
|
||||
}))
|
||||
shb.nginx.vhosts = [ (vhosts { } cfg') ];
|
||||
}
|
||||
))
|
||||
|
||||
(lib.mkIf cfg.readarr.enable (
|
||||
let
|
||||
|
|
@ -441,8 +490,9 @@ in
|
|||
generator = apps.readarr.settingsFormat.generate;
|
||||
};
|
||||
|
||||
shb.nginx.vhosts = [ (vhosts {} cfg') ];
|
||||
}))
|
||||
shb.nginx.vhosts = [ (vhosts { } cfg') ];
|
||||
}
|
||||
))
|
||||
|
||||
(lib.mkIf cfg.lidarr.enable (
|
||||
let
|
||||
|
|
@ -458,7 +508,8 @@ in
|
|||
extraGroups = [ "media" ];
|
||||
};
|
||||
systemd.services.lidarr.preStart = lib.shb.replaceSecrets {
|
||||
userConfig = cfg'.settings
|
||||
userConfig =
|
||||
cfg'.settings
|
||||
// (lib.optionalAttrs isSSOEnabled {
|
||||
AuthenticationRequired = "DisabledForLocalAddresses";
|
||||
AuthenticationMethod = "External";
|
||||
|
|
@ -467,8 +518,9 @@ in
|
|||
generator = apps.lidarr.settingsFormat.generate;
|
||||
};
|
||||
|
||||
shb.nginx.vhosts = [ (vhosts {} cfg') ];
|
||||
}))
|
||||
shb.nginx.vhosts = [ (vhosts { } cfg') ];
|
||||
}
|
||||
))
|
||||
|
||||
(lib.mkIf cfg.jackett.enable (
|
||||
let
|
||||
|
|
@ -489,9 +541,12 @@ in
|
|||
generator = apps.jackett.settingsFormat.generate;
|
||||
};
|
||||
|
||||
shb.nginx.vhosts = [ (vhosts {
|
||||
shb.nginx.vhosts = [
|
||||
(vhosts {
|
||||
extraBypassResources = [ "^/dl.*" ];
|
||||
} cfg') ];
|
||||
}))
|
||||
} cfg')
|
||||
];
|
||||
}
|
||||
))
|
||||
];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,14 @@
|
|||
{ config, pkgs, lib, ... }:
|
||||
{
|
||||
config,
|
||||
pkgs,
|
||||
lib,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
cfg = config.shb.audiobookshelf;
|
||||
|
||||
contracts = pkgs.callPackage ../contracts {};
|
||||
contracts = pkgs.callPackage ../contracts { };
|
||||
|
||||
fqdn = "${cfg.subdomain}.${cfg.domain}";
|
||||
|
||||
|
|
@ -40,7 +45,7 @@ in
|
|||
extraServiceConfig = lib.mkOption {
|
||||
type = lib.types.attrsOf lib.types.str;
|
||||
description = "Extra configuration given to the systemd service file.";
|
||||
default = {};
|
||||
default = { };
|
||||
example = lib.literalExpression ''
|
||||
{
|
||||
MemoryHigh = "512M";
|
||||
|
|
@ -51,7 +56,7 @@ in
|
|||
|
||||
sso = lib.mkOption {
|
||||
description = "SSO configuration.";
|
||||
default = {};
|
||||
default = { };
|
||||
type = lib.types.submodule {
|
||||
options = {
|
||||
enable = lib.mkEnableOption "SSO";
|
||||
|
|
@ -87,7 +92,10 @@ in
|
|||
};
|
||||
|
||||
authorization_policy = lib.mkOption {
|
||||
type = lib.types.enum [ "one_factor" "two_factor" ];
|
||||
type = lib.types.enum [
|
||||
"one_factor"
|
||||
"two_factor"
|
||||
];
|
||||
description = "Require one factor (password) or two factor (device) authentication.";
|
||||
default = "one_factor";
|
||||
};
|
||||
|
|
@ -133,14 +141,24 @@ in
|
|||
};
|
||||
|
||||
logLevel = lib.mkOption {
|
||||
type = lib.types.nullOr (lib.types.enum ["critical" "error" "warning" "info" "debug"]);
|
||||
type = lib.types.nullOr (
|
||||
lib.types.enum [
|
||||
"critical"
|
||||
"error"
|
||||
"warning"
|
||||
"info"
|
||||
"debug"
|
||||
]
|
||||
);
|
||||
description = "Enable logging.";
|
||||
default = false;
|
||||
example = true;
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf cfg.enable (lib.mkMerge [{
|
||||
config = lib.mkIf cfg.enable (
|
||||
lib.mkMerge [
|
||||
{
|
||||
|
||||
services.audiobookshelf = {
|
||||
enable = true;
|
||||
|
|
@ -175,7 +193,6 @@ in
|
|||
'';
|
||||
};
|
||||
|
||||
|
||||
shb.authelia.extraDefinitions = {
|
||||
user_attributes.${roleClaim}.expression =
|
||||
''"${cfg.sso.adminUserGroup}" in groups ? ["admin"] : ("${cfg.sso.userGroup}" in groups ? ["user"] : [""])'';
|
||||
|
|
@ -183,7 +200,7 @@ in
|
|||
|
||||
shb.authelia.extraOidcClaimsPolicies.${roleClaim} = {
|
||||
custom_claims = {
|
||||
"${roleClaim}" = {};
|
||||
"${roleClaim}" = { };
|
||||
};
|
||||
};
|
||||
|
||||
|
|
@ -203,14 +220,23 @@ in
|
|||
"https://${cfg.subdomain}.${cfg.domain}/auth/openid/callback"
|
||||
"https://${cfg.subdomain}.${cfg.domain}/auth/openid/mobile-redirect"
|
||||
];
|
||||
scopes = [ "openid" "profile" "email" "groups" "${roleClaim}" ];
|
||||
scopes = [
|
||||
"openid"
|
||||
"profile"
|
||||
"email"
|
||||
"groups"
|
||||
"${roleClaim}"
|
||||
];
|
||||
require_pkce = true;
|
||||
pkce_challenge_method = "S256";
|
||||
userinfo_signed_response_alg = "none";
|
||||
token_endpoint_auth_method = "client_secret_basic";
|
||||
}
|
||||
];
|
||||
} {
|
||||
}
|
||||
{
|
||||
systemd.services.audiobookshelfd.serviceConfig = cfg.extraServiceConfig;
|
||||
}]);
|
||||
}
|
||||
]
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,15 +1,26 @@
|
|||
{ config, pkgs, lib, ... }:
|
||||
{
|
||||
config,
|
||||
pkgs,
|
||||
lib,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
cfg = config.shb.deluge;
|
||||
|
||||
contracts = pkgs.callPackage ../contracts {};
|
||||
contracts = pkgs.callPackage ../contracts { };
|
||||
|
||||
fqdn = "${cfg.subdomain}.${cfg.domain}";
|
||||
|
||||
authGenerator = users:
|
||||
authGenerator =
|
||||
users:
|
||||
let
|
||||
genLine = name: { password, priority ? 10 }:
|
||||
genLine =
|
||||
name:
|
||||
{
|
||||
password,
|
||||
priority ? 10,
|
||||
}:
|
||||
"${name}:${password}:${toString priority}";
|
||||
|
||||
lines = lib.mapAttrsToList genLine users;
|
||||
|
|
@ -57,7 +68,10 @@ in
|
|||
daemonListenPorts = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.int;
|
||||
description = "Deluge daemon listen ports";
|
||||
default = [ 6881 6889 ];
|
||||
default = [
|
||||
6881
|
||||
6889
|
||||
];
|
||||
};
|
||||
|
||||
webPort = lib.mkOption {
|
||||
|
|
@ -158,7 +172,7 @@ in
|
|||
extraServiceConfig = lib.mkOption {
|
||||
type = lib.types.attrsOf lib.types.str;
|
||||
description = "Extra configuration given to the systemd service file.";
|
||||
default = {};
|
||||
default = { };
|
||||
example = lib.literalExpression ''
|
||||
{
|
||||
MemoryHigh = "512M";
|
||||
|
|
@ -176,14 +190,16 @@ in
|
|||
|
||||
extraUsers = lib.mkOption {
|
||||
description = "Users having access to this deluge instance. Attrset of username to user options.";
|
||||
type = lib.types.attrsOf (lib.types.submodule {
|
||||
type = lib.types.attrsOf (
|
||||
lib.types.submodule {
|
||||
options = {
|
||||
password = lib.mkOption {
|
||||
type = lib.shb.secretFileType;
|
||||
description = "File containing the user password.";
|
||||
};
|
||||
};
|
||||
});
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
localclientPassword = lib.mkOption {
|
||||
|
|
@ -198,12 +214,17 @@ in
|
|||
|
||||
prometheusScraperPassword = lib.mkOption {
|
||||
description = "Password for prometheus scraper. Setting this option will activate the prometheus deluge exporter.";
|
||||
type = lib.types.nullOr (lib.types.submodule {
|
||||
type = lib.types.nullOr (
|
||||
lib.types.submodule {
|
||||
options = contracts.secret.mkRequester {
|
||||
owner = "deluge";
|
||||
restartUnits = [ "deluged.service" "prometheus.service" ];
|
||||
restartUnits = [
|
||||
"deluged.service"
|
||||
"prometheus.service"
|
||||
];
|
||||
};
|
||||
});
|
||||
}
|
||||
);
|
||||
default = null;
|
||||
};
|
||||
|
||||
|
|
@ -214,14 +235,14 @@ in
|
|||
|
||||
Label is automatically enabled if any of the `shb.arr.*` service is enabled.
|
||||
'';
|
||||
example = ["Label"];
|
||||
default = [];
|
||||
example = [ "Label" ];
|
||||
default = [ ];
|
||||
};
|
||||
|
||||
additionalPlugins = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.path;
|
||||
description = "Location of additional plugins. Each item in the list must be the path to the directory containing the plugin .egg file.";
|
||||
default = [];
|
||||
default = [ ];
|
||||
example = lib.literalExpression ''
|
||||
additionalPlugins = [
|
||||
(pkgs.callPackage ({ python3, fetchFromGitHub }: python3.pkgs.buildPythonPackage {
|
||||
|
|
@ -250,7 +271,7 @@ in
|
|||
description = ''
|
||||
Backup configuration.
|
||||
'';
|
||||
default = {};
|
||||
default = { };
|
||||
type = lib.types.submodule {
|
||||
options = contracts.backup.mkRequester {
|
||||
user = "deluge";
|
||||
|
|
@ -262,14 +283,24 @@ in
|
|||
};
|
||||
|
||||
logLevel = lib.mkOption {
|
||||
type = lib.types.nullOr (lib.types.enum ["critical" "error" "warning" "info" "debug"]);
|
||||
type = lib.types.nullOr (
|
||||
lib.types.enum [
|
||||
"critical"
|
||||
"error"
|
||||
"warning"
|
||||
"info"
|
||||
"debug"
|
||||
]
|
||||
);
|
||||
description = "Enable logging.";
|
||||
default = null;
|
||||
example = "info";
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf cfg.enable (lib.mkMerge [{
|
||||
config = lib.mkIf cfg.enable (
|
||||
lib.mkMerge [
|
||||
{
|
||||
services.deluge = {
|
||||
enable = true;
|
||||
declarative = true;
|
||||
|
|
@ -292,7 +323,8 @@ in
|
|||
};
|
||||
outgoing_interface = cfg.outgoingInterface;
|
||||
|
||||
enabled_plugins = cfg.enabledPlugins
|
||||
enabled_plugins =
|
||||
cfg.enabledPlugins
|
||||
++ lib.optional (lib.any (x: x.enable) [
|
||||
config.services.radarr
|
||||
config.services.sonarr
|
||||
|
|
@ -316,7 +348,8 @@ in
|
|||
max_upload_speed
|
||||
max_upload_speed_per_torrent
|
||||
|
||||
dont_count_slow_torrents;
|
||||
dont_count_slow_torrents
|
||||
;
|
||||
|
||||
new_release_check = false;
|
||||
};
|
||||
|
|
@ -327,22 +360,31 @@ in
|
|||
web.port = cfg.webPort;
|
||||
};
|
||||
|
||||
systemd.services.deluged.preStart = lib.mkBefore (lib.shb.replaceSecrets {
|
||||
userConfig = cfg.extraUsers // {
|
||||
systemd.services.deluged.preStart = lib.mkBefore (
|
||||
lib.shb.replaceSecrets {
|
||||
userConfig =
|
||||
cfg.extraUsers
|
||||
// {
|
||||
localclient.password.source = config.shb.deluge.localclientPassword.result.path;
|
||||
} // (lib.optionalAttrs (config.shb.deluge.prometheusScraperPassword != null) {
|
||||
}
|
||||
// (lib.optionalAttrs (config.shb.deluge.prometheusScraperPassword != null) {
|
||||
prometheus_scraper.password.source = config.shb.deluge.prometheusScraperPassword.result.path;
|
||||
});
|
||||
resultPath = "${cfg.dataDir}/.config/deluge/authTemplate";
|
||||
generator = name: value: pkgs.writeText "delugeAuth" (authGenerator value);
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
systemd.services.deluged.serviceConfig.ExecStart = lib.mkForce (lib.concatStringsSep " \\\n " ([
|
||||
systemd.services.deluged.serviceConfig.ExecStart = lib.mkForce (
|
||||
lib.concatStringsSep " \\\n " (
|
||||
[
|
||||
"${config.services.deluge.package}/bin/deluged"
|
||||
"--do-not-daemonize"
|
||||
"--config ${cfg.dataDir}/.config/deluge"
|
||||
] ++ (lib.optional (!(isNull cfg.logLevel)) "-L ${cfg.logLevel}")
|
||||
));
|
||||
]
|
||||
++ (lib.optional (!(isNull cfg.logLevel)) "-L ${cfg.logLevel}")
|
||||
)
|
||||
);
|
||||
|
||||
systemd.tmpfiles.rules =
|
||||
let
|
||||
|
|
@ -356,7 +398,8 @@ in
|
|||
];
|
||||
|
||||
shb.nginx.vhosts = [
|
||||
({
|
||||
(
|
||||
{
|
||||
inherit (cfg) subdomain domain ssl;
|
||||
upstream = "http://127.0.0.1:${toString config.services.deluge.web.port}";
|
||||
autheliaRules = lib.mkIf (cfg.authEndpoint != null) [
|
||||
|
|
@ -370,16 +413,20 @@ in
|
|||
{
|
||||
domain = fqdn;
|
||||
policy = "two_factor";
|
||||
subject = ["group:deluge_user"];
|
||||
subject = [ "group:deluge_user" ];
|
||||
}
|
||||
];
|
||||
} // (lib.optionalAttrs (cfg.authEndpoint != null) {
|
||||
}
|
||||
// (lib.optionalAttrs (cfg.authEndpoint != null) {
|
||||
inherit (cfg) authEndpoint;
|
||||
}))
|
||||
})
|
||||
)
|
||||
];
|
||||
} {
|
||||
}
|
||||
{
|
||||
systemd.services.deluged.serviceConfig = cfg.extraServiceConfig;
|
||||
} (lib.mkIf (config.shb.deluge.prometheusScraperPassword != null) {
|
||||
}
|
||||
(lib.mkIf (config.shb.deluge.prometheusScraperPassword != null) {
|
||||
services.prometheus.exporters.deluge = {
|
||||
enable = true;
|
||||
|
||||
|
|
@ -393,15 +440,18 @@ in
|
|||
services.prometheus.scrapeConfigs = [
|
||||
{
|
||||
job_name = "deluge";
|
||||
static_configs = [{
|
||||
targets = ["127.0.0.1:${toString config.services.prometheus.exporters.deluge.port}"];
|
||||
static_configs = [
|
||||
{
|
||||
targets = [ "127.0.0.1:${toString config.services.prometheus.exporters.deluge.port}" ];
|
||||
labels = {
|
||||
"hostname" = config.networking.hostName;
|
||||
"domain" = cfg.domain;
|
||||
};
|
||||
}];
|
||||
}
|
||||
];
|
||||
}
|
||||
];
|
||||
})
|
||||
]);
|
||||
]
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,18 +1,53 @@
|
|||
{ config, options, pkgs, lib, ... }:
|
||||
{
|
||||
config,
|
||||
options,
|
||||
pkgs,
|
||||
lib,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
cfg = config.shb.forgejo;
|
||||
|
||||
contracts = pkgs.callPackage ../contracts {};
|
||||
contracts = pkgs.callPackage ../contracts { };
|
||||
|
||||
inherit (lib) all attrNames concatMapStringsSep getExe lists literalExpression mapAttrsToList mkBefore mkEnableOption mkForce mkIf mkMerge mkOption mkOverride nameValuePair optionalString optionals;
|
||||
inherit (lib.types) attrsOf bool enum listOf nullOr package port submodule str;
|
||||
inherit (lib)
|
||||
all
|
||||
attrNames
|
||||
concatMapStringsSep
|
||||
getExe
|
||||
lists
|
||||
literalExpression
|
||||
mapAttrsToList
|
||||
mkBefore
|
||||
mkEnableOption
|
||||
mkForce
|
||||
mkIf
|
||||
mkMerge
|
||||
mkOption
|
||||
mkOverride
|
||||
nameValuePair
|
||||
optionalString
|
||||
optionals
|
||||
;
|
||||
inherit (lib.types)
|
||||
attrsOf
|
||||
bool
|
||||
enum
|
||||
listOf
|
||||
nullOr
|
||||
package
|
||||
port
|
||||
submodule
|
||||
str
|
||||
;
|
||||
in
|
||||
{
|
||||
imports = [
|
||||
../blocks/nginx.nix
|
||||
|
||||
(lib.mkRemovedOptionModule [ "shb" "forgejo" "adminPassword" ] ''Instead, define an admin user in shb.forgejo.users and give it the same password, like so:
|
||||
(lib.mkRemovedOptionModule [ "shb" "forgejo" "adminPassword" ] ''
|
||||
Instead, define an admin user in shb.forgejo.users and give it the same password, like so:
|
||||
shb.forgejo.users = {
|
||||
"forgejoadmin" = {
|
||||
isAdmin = true;
|
||||
|
|
@ -60,7 +95,7 @@ in
|
|||
description = ''
|
||||
LDAP Integration.
|
||||
'';
|
||||
default = {};
|
||||
default = { };
|
||||
type = nullOr (submodule {
|
||||
options = {
|
||||
enable = mkEnableOption "LDAP integration.";
|
||||
|
|
@ -125,7 +160,7 @@ in
|
|||
|
||||
waitForSystemdServices = mkOption {
|
||||
type = listOf str;
|
||||
default = [];
|
||||
default = [ ];
|
||||
description = ''
|
||||
List of systemd services to wait on before starting.
|
||||
This is needed because forgejo will try a lookup on the LDAP instance
|
||||
|
|
@ -140,7 +175,7 @@ in
|
|||
description = ''
|
||||
Setup SSO integration.
|
||||
'';
|
||||
default = {};
|
||||
default = { };
|
||||
type = submodule {
|
||||
options = {
|
||||
enable = mkEnableOption "SSO integration.";
|
||||
|
|
@ -164,7 +199,10 @@ in
|
|||
};
|
||||
|
||||
authorization_policy = mkOption {
|
||||
type = enum [ "one_factor" "two_factor" ];
|
||||
type = enum [
|
||||
"one_factor"
|
||||
"two_factor"
|
||||
];
|
||||
description = "Require one factor (password) or two factor (device) authentication.";
|
||||
default = "one_factor";
|
||||
};
|
||||
|
|
@ -205,7 +243,8 @@ in
|
|||
};
|
||||
|
||||
email = mkOption {
|
||||
description = ''Email of user.
|
||||
description = ''
|
||||
Email of user.
|
||||
|
||||
This is only set when the user is created, changing this later on will have no effect.
|
||||
'';
|
||||
|
|
@ -254,7 +293,6 @@ in
|
|||
'';
|
||||
};
|
||||
|
||||
|
||||
hostPackages = mkOption {
|
||||
type = listOf package;
|
||||
default = with pkgs; [
|
||||
|
|
@ -289,13 +327,14 @@ in
|
|||
description = ''
|
||||
Backup configuration.
|
||||
'';
|
||||
default = {};
|
||||
default = { };
|
||||
type = lib.types.submodule {
|
||||
options = contracts.backup.mkRequester {
|
||||
user = options.services.forgejo.user.value;
|
||||
sourceDirectories = [
|
||||
options.services.forgejo.dump.backupDir.value
|
||||
] ++ optionals (cfg.repositoryRoot != null) [
|
||||
]
|
||||
++ optionals (cfg.repositoryRoot != null) [
|
||||
cfg.repositoryRoot
|
||||
];
|
||||
};
|
||||
|
|
@ -317,7 +356,9 @@ in
|
|||
```
|
||||
'';
|
||||
readOnly = true;
|
||||
default = { path = config.services.forgejo.stateDir; };
|
||||
default = {
|
||||
path = config.services.forgejo.stateDir;
|
||||
};
|
||||
};
|
||||
|
||||
smtp = mkOption {
|
||||
|
|
@ -390,10 +431,12 @@ in
|
|||
# https://github.com/NixOS/nixpkgs/issues/258371#issuecomment-2271967113
|
||||
systemd.services.forgejo.serviceConfig.Type = mkForce "exec";
|
||||
|
||||
shb.nginx.vhosts = [{
|
||||
shb.nginx.vhosts = [
|
||||
{
|
||||
inherit (cfg) domain subdomain ssl;
|
||||
upstream = "http://unix:${config.services.forgejo.settings.server.HTTP_ADDR}";
|
||||
}];
|
||||
}
|
||||
];
|
||||
})
|
||||
|
||||
(mkIf cfg.enable {
|
||||
|
|
@ -420,9 +463,11 @@ in
|
|||
systemd.services.forgejo.wants = cfg.ldap.waitForSystemdServices;
|
||||
systemd.services.forgejo.after = cfg.ldap.waitForSystemdServices;
|
||||
# The delimiter in the `cut` command is a TAB!
|
||||
systemd.services.forgejo.preStart = let
|
||||
systemd.services.forgejo.preStart =
|
||||
let
|
||||
provider = "SHB-${cfg.ldap.provider}";
|
||||
in ''
|
||||
in
|
||||
''
|
||||
auth="${getExe config.services.forgejo.package} admin auth"
|
||||
|
||||
echo "Trying to find existing ldap configuration for ${provider}"...
|
||||
|
|
@ -497,9 +542,11 @@ in
|
|||
};
|
||||
|
||||
# The delimiter in the `cut` command is a TAB!
|
||||
systemd.services.forgejo.preStart = let
|
||||
systemd.services.forgejo.preStart =
|
||||
let
|
||||
provider = "SHB-${cfg.sso.provider}";
|
||||
in ''
|
||||
in
|
||||
''
|
||||
auth="${getExe config.services.forgejo.package} admin auth"
|
||||
|
||||
echo "Trying to find existing sso configuration for ${provider}"...
|
||||
|
|
@ -529,16 +576,19 @@ in
|
|||
'';
|
||||
|
||||
shb.authelia.oidcClients = lists.optionals (!(isNull cfg.sso)) [
|
||||
(let
|
||||
(
|
||||
let
|
||||
provider = "SHB-${cfg.sso.provider}";
|
||||
in {
|
||||
in
|
||||
{
|
||||
client_id = cfg.sso.clientID;
|
||||
client_name = "Forgejo";
|
||||
client_secret.source = cfg.sso.sharedSecretForAuthelia.result.path;
|
||||
public = false;
|
||||
authorization_policy = cfg.sso.authorization_policy;
|
||||
redirect_uris = [ "https://${cfg.subdomain}.${cfg.domain}/user/oauth2/${provider}/callback" ];
|
||||
})
|
||||
}
|
||||
)
|
||||
];
|
||||
})
|
||||
|
||||
|
|
@ -552,13 +602,14 @@ in
|
|||
|
||||
systemd.services.forgejo.preStart = ''
|
||||
admin="${getExe config.services.forgejo.package} admin user"
|
||||
'' + concatMapStringsSep "\n" (u: ''
|
||||
''
|
||||
+ concatMapStringsSep "\n" (u: ''
|
||||
if ! $admin list | grep "${u.name}"; then
|
||||
$admin create ${optionalString u.value.isAdmin "--admin"} --email "${u.value.email}" --must-change-password=false --username "${u.name}" --password "$(tr -d '\n' < ${u.value.password.result.path})"
|
||||
else
|
||||
$admin change-password --must-change-password=false --username "${u.name}" --password "$(tr -d '\n' < ${u.value.password.result.path})"
|
||||
fi
|
||||
'') (mapAttrsToList nameValuePair cfg.users);
|
||||
'') (mapAttrsToList nameValuePair cfg.users);
|
||||
})
|
||||
|
||||
(mkIf (cfg.enable && cfg.smtp != null) {
|
||||
|
|
@ -584,9 +635,11 @@ in
|
|||
instances.local = {
|
||||
enable = true;
|
||||
name = "local";
|
||||
url = let
|
||||
url =
|
||||
let
|
||||
protocol = if cfg.ssl != null then "https" else "http";
|
||||
in "${protocol}://${cfg.subdomain}.${cfg.domain}";
|
||||
in
|
||||
"${protocol}://${cfg.subdomain}.${cfg.domain}";
|
||||
tokenFile = ""; # Empty variable to satisfy an assertion.
|
||||
labels = [
|
||||
# "ubuntu-latest:docker://node:16-bullseye"
|
||||
|
|
|
|||
|
|
@ -1,9 +1,14 @@
|
|||
{ config, pkgs, lib, ... }:
|
||||
{
|
||||
config,
|
||||
pkgs,
|
||||
lib,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
cfg = config.shb.grocy;
|
||||
|
||||
contracts = pkgs.callPackage ../contracts {};
|
||||
contracts = pkgs.callPackage ../contracts { };
|
||||
|
||||
fqdn = "${cfg.subdomain}.${cfg.domain}";
|
||||
in
|
||||
|
|
@ -37,7 +42,24 @@ in
|
|||
};
|
||||
|
||||
culture = lib.mkOption {
|
||||
type = lib.types.enum [ "de" "en" "da" "en_GB" "es" "fr" "hu" "it" "nl" "no" "pl" "pt_BR" "ru" "sk_SK" "sv_SE" "tr" ];
|
||||
type = lib.types.enum [
|
||||
"de"
|
||||
"en"
|
||||
"da"
|
||||
"en_GB"
|
||||
"es"
|
||||
"fr"
|
||||
"hu"
|
||||
"it"
|
||||
"nl"
|
||||
"no"
|
||||
"pl"
|
||||
"pt_BR"
|
||||
"ru"
|
||||
"sk_SK"
|
||||
"sv_SE"
|
||||
"tr"
|
||||
];
|
||||
default = "en";
|
||||
description = ''
|
||||
Display language of the frontend.
|
||||
|
|
@ -53,7 +75,7 @@ in
|
|||
extraServiceConfig = lib.mkOption {
|
||||
type = lib.types.attrsOf lib.types.str;
|
||||
description = "Extra configuration given to the systemd service file.";
|
||||
default = {};
|
||||
default = { };
|
||||
example = lib.literalExpression ''
|
||||
{
|
||||
MemoryHigh = "512M";
|
||||
|
|
@ -78,14 +100,24 @@ in
|
|||
};
|
||||
|
||||
logLevel = lib.mkOption {
|
||||
type = lib.types.nullOr (lib.types.enum ["critical" "error" "warning" "info" "debug"]);
|
||||
type = lib.types.nullOr (
|
||||
lib.types.enum [
|
||||
"critical"
|
||||
"error"
|
||||
"warning"
|
||||
"info"
|
||||
"debug"
|
||||
]
|
||||
);
|
||||
description = "Enable logging.";
|
||||
default = false;
|
||||
example = true;
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf cfg.enable (lib.mkMerge [{
|
||||
config = lib.mkIf cfg.enable (
|
||||
lib.mkMerge [
|
||||
{
|
||||
services.grocy = {
|
||||
enable = true;
|
||||
hostName = fqdn;
|
||||
|
|
@ -97,7 +129,7 @@ in
|
|||
|
||||
services.phpfpm.pools.grocy.group = lib.mkForce "grocy";
|
||||
|
||||
users.groups.grocy = {};
|
||||
users.groups.grocy = { };
|
||||
users.users.grocy.group = lib.mkForce "grocy";
|
||||
|
||||
services.nginx.virtualHosts."${fqdn}" = {
|
||||
|
|
@ -105,7 +137,10 @@ in
|
|||
sslCertificate = lib.mkIf (!(isNull cfg.ssl)) cfg.ssl.paths.cert;
|
||||
sslCertificateKey = lib.mkIf (!(isNull cfg.ssl)) cfg.ssl.paths.key;
|
||||
};
|
||||
} {
|
||||
}
|
||||
{
|
||||
systemd.services.grocyd.serviceConfig = cfg.extraServiceConfig;
|
||||
}]);
|
||||
}
|
||||
]
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,14 @@
|
|||
{ config, pkgs, lib, ... }:
|
||||
{
|
||||
config,
|
||||
pkgs,
|
||||
lib,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
cfg = config.shb.hledger;
|
||||
|
||||
contracts = pkgs.callPackage ../contracts {};
|
||||
contracts = pkgs.callPackage ../contracts { };
|
||||
|
||||
fqdn = "${cfg.subdomain}.${cfg.domain}";
|
||||
in
|
||||
|
|
@ -63,7 +68,7 @@ in
|
|||
description = ''
|
||||
Backup configuration.
|
||||
'';
|
||||
default = {};
|
||||
default = { };
|
||||
type = lib.types.submodule {
|
||||
options = contracts.backup.mkRequester {
|
||||
user = "hledger";
|
||||
|
|
@ -76,7 +81,7 @@ in
|
|||
|
||||
extraArguments = lib.mkOption {
|
||||
description = "Extra arguments append to the hledger command.";
|
||||
default = ["--forecast"];
|
||||
default = [ "--forecast" ];
|
||||
type = lib.types.listOf lib.types.str;
|
||||
};
|
||||
};
|
||||
|
|
@ -88,7 +93,7 @@ in
|
|||
baseUrl = "";
|
||||
|
||||
stateDir = cfg.dataDir;
|
||||
journalFiles = ["hledger.journal"];
|
||||
journalFiles = [ "hledger.journal" ];
|
||||
|
||||
host = "127.0.0.1";
|
||||
port = cfg.port;
|
||||
|
|
@ -108,13 +113,20 @@ in
|
|||
|
||||
shb.nginx.vhosts = [
|
||||
{
|
||||
inherit (cfg) subdomain domain authEndpoint ssl;
|
||||
inherit (cfg)
|
||||
subdomain
|
||||
domain
|
||||
authEndpoint
|
||||
ssl
|
||||
;
|
||||
upstream = "http://${toString config.services.hledger-web.host}:${toString config.services.hledger-web.port}";
|
||||
autheliaRules = [{
|
||||
autheliaRules = [
|
||||
{
|
||||
domain = fqdn;
|
||||
policy = "two_factor";
|
||||
subject = ["group:hledger_user"];
|
||||
}];
|
||||
subject = [ "group:hledger_user" ];
|
||||
}
|
||||
];
|
||||
}
|
||||
];
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,9 +1,14 @@
|
|||
{ config, pkgs, lib, ... }:
|
||||
{
|
||||
config,
|
||||
pkgs,
|
||||
lib,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
cfg = config.shb.home-assistant;
|
||||
|
||||
contracts = pkgs.callPackage ../contracts {};
|
||||
contracts = pkgs.callPackage ../contracts { };
|
||||
|
||||
fqdn = "${cfg.subdomain}.${cfg.domain}";
|
||||
|
||||
|
|
@ -24,9 +29,7 @@ let
|
|||
|
||||
nonSecrets = (lib.attrsets.filterAttrs (k: v: !(builtins.isAttrs v)) cfg.config);
|
||||
|
||||
configWithSecretsIncludes =
|
||||
nonSecrets
|
||||
// (lib.attrsets.mapAttrs (k: v: "!secret ${k}") secrets);
|
||||
configWithSecretsIncludes = nonSecrets // (lib.attrsets.mapAttrs (k: v: "!secret ${k}") secrets);
|
||||
in
|
||||
{
|
||||
options.shb.home-assistant = {
|
||||
|
|
@ -56,28 +59,49 @@ in
|
|||
freeformType = lib.types.attrsOf lib.types.str;
|
||||
options = {
|
||||
name = lib.mkOption {
|
||||
type = lib.types.oneOf [ lib.types.str lib.shb.secretFileType ];
|
||||
type = lib.types.oneOf [
|
||||
lib.types.str
|
||||
lib.shb.secretFileType
|
||||
];
|
||||
description = "Name of the Home Assistant instance.";
|
||||
};
|
||||
country = lib.mkOption {
|
||||
type = lib.types.oneOf [ lib.types.str lib.shb.secretFileType ];
|
||||
type = lib.types.oneOf [
|
||||
lib.types.str
|
||||
lib.shb.secretFileType
|
||||
];
|
||||
description = "Two letter country code where this instance is located.";
|
||||
};
|
||||
latitude = lib.mkOption {
|
||||
type = lib.types.oneOf [ lib.types.str lib.shb.secretFileType ];
|
||||
type = lib.types.oneOf [
|
||||
lib.types.str
|
||||
lib.shb.secretFileType
|
||||
];
|
||||
description = "Latitude where this instance is located.";
|
||||
};
|
||||
longitude = lib.mkOption {
|
||||
type = lib.types.oneOf [ lib.types.str lib.shb.secretFileType ];
|
||||
type = lib.types.oneOf [
|
||||
lib.types.str
|
||||
lib.shb.secretFileType
|
||||
];
|
||||
description = "Longitude where this instance is located.";
|
||||
};
|
||||
time_zone = lib.mkOption {
|
||||
type = lib.types.oneOf [ lib.types.str lib.shb.secretFileType ];
|
||||
type = lib.types.oneOf [
|
||||
lib.types.str
|
||||
lib.shb.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" ]) ];
|
||||
type = lib.types.oneOf [
|
||||
lib.types.str
|
||||
(lib.types.enum [
|
||||
"metric"
|
||||
"us_customary"
|
||||
])
|
||||
];
|
||||
description = "Unit system of this instance.";
|
||||
example = "metric";
|
||||
};
|
||||
|
|
@ -95,7 +119,7 @@ in
|
|||
Also, enabling LDAP will skip onboarding
|
||||
otherwise Home Assistant gets into a cyclic lock.
|
||||
'';
|
||||
default = {};
|
||||
default = { };
|
||||
type = lib.types.submodule {
|
||||
options = {
|
||||
enable = lib.mkEnableOption "LDAP app.";
|
||||
|
|
@ -140,7 +164,7 @@ in
|
|||
|
||||
voice = lib.mkOption {
|
||||
description = "Options related to voice service.";
|
||||
default = {};
|
||||
default = { };
|
||||
type = lib.types.submodule {
|
||||
options = {
|
||||
speech-to-text = lib.mkOption {
|
||||
|
|
@ -150,7 +174,7 @@ in
|
|||
https://search.nixos.org/options?channel=23.11&from=0&size=50&sort=relevance&type=packages&query=services.wyoming.piper.servers
|
||||
'';
|
||||
type = lib.types.attrsOf lib.types.anything;
|
||||
default = {};
|
||||
default = { };
|
||||
};
|
||||
text-to-speech = lib.mkOption {
|
||||
description = ''
|
||||
|
|
@ -159,7 +183,7 @@ in
|
|||
https://search.nixos.org/options?channel=23.11&from=0&size=50&sort=relevance&type=packages&query=services.wyoming.faster-whisper.servers
|
||||
'';
|
||||
type = lib.types.attrsOf lib.types.anything;
|
||||
default = {};
|
||||
default = { };
|
||||
};
|
||||
wakeword = lib.mkOption {
|
||||
description = ''
|
||||
|
|
@ -168,7 +192,9 @@ in
|
|||
https://search.nixos.org/options?channel=23.11&from=0&size=50&sort=relevance&type=packages&query=services.wyoming.openwakeword
|
||||
'';
|
||||
type = lib.types.anything;
|
||||
default = { enable = false; };
|
||||
default = {
|
||||
enable = false;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
|
@ -178,7 +204,7 @@ in
|
|||
description = ''
|
||||
Backup configuration.
|
||||
'';
|
||||
default = {};
|
||||
default = { };
|
||||
type = lib.types.submodule {
|
||||
options = contracts.backup.mkRequester {
|
||||
user = "hass";
|
||||
|
|
@ -219,7 +245,7 @@ in
|
|||
config = {
|
||||
# Includes dependencies for a basic setup
|
||||
# https://www.home-assistant.io/integrations/default_config/
|
||||
default_config = {};
|
||||
default_config = { };
|
||||
http = {
|
||||
use_x_forwarded_for = true;
|
||||
server_host = "127.0.0.1";
|
||||
|
|
@ -240,7 +266,10 @@ in
|
|||
{
|
||||
type = "command_line";
|
||||
command = ldap_auth_script + "/bin/ldap_auth.sh";
|
||||
args = [ "http://${cfg.ldap.host}:${toString cfg.ldap.port}" cfg.ldap.userGroup ];
|
||||
args = [
|
||||
"http://${cfg.ldap.host}:${toString cfg.ldap.port}"
|
||||
cfg.ldap.userGroup
|
||||
];
|
||||
meta = true;
|
||||
}
|
||||
]);
|
||||
|
|
@ -261,11 +290,11 @@ in
|
|||
action = [
|
||||
{
|
||||
service = "shell_command.delete_backups";
|
||||
data = {};
|
||||
data = { };
|
||||
}
|
||||
{
|
||||
service = "backup.create";
|
||||
data = {};
|
||||
data = { };
|
||||
}
|
||||
];
|
||||
mode = "single";
|
||||
|
|
@ -286,7 +315,11 @@ in
|
|||
{
|
||||
name = "random_joke";
|
||||
platform = "rest";
|
||||
json_attributes = ["joke" "id" "status"];
|
||||
json_attributes = [
|
||||
"joke"
|
||||
"id"
|
||||
"status"
|
||||
];
|
||||
value_template = "{{ value_json.joke }}";
|
||||
resource = "https://icanhazdadjoke.com/";
|
||||
scan_interval = "3600";
|
||||
|
|
@ -324,7 +357,8 @@ in
|
|||
};
|
||||
|
||||
systemd.services.home-assistant.preStart =
|
||||
(let
|
||||
(
|
||||
let
|
||||
# TODO: this probably does not work anymore
|
||||
onboarding = pkgs.writeText "onboarding" ''
|
||||
{
|
||||
|
|
@ -342,15 +376,17 @@ in
|
|||
'';
|
||||
storage = "${config.services.home-assistant.configDir}";
|
||||
file = "${storage}/.storage/onboarding";
|
||||
in ''
|
||||
in
|
||||
''
|
||||
if [ ! -f ${file} ]; then
|
||||
mkdir -p ''$(dirname ${file}) && cp ${onboarding} ${file}
|
||||
fi
|
||||
'')
|
||||
''
|
||||
)
|
||||
+ (lib.shb.replaceSecrets {
|
||||
userConfig = cfg.config;
|
||||
resultPath = "${config.services.home-assistant.configDir}/secrets.yaml";
|
||||
generator = lib.shb.replaceSecretsGeneratorAdapter (lib.generators.toYAML {});
|
||||
generator = lib.shb.replaceSecretsGeneratorAdapter (lib.generators.toYAML { });
|
||||
});
|
||||
|
||||
systemd.tmpfiles.rules = [
|
||||
|
|
|
|||
|
|
@ -1,9 +1,14 @@
|
|||
{ config, pkgs, lib, ... }:
|
||||
{
|
||||
config,
|
||||
pkgs,
|
||||
lib,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
cfg = config.shb.immich;
|
||||
|
||||
contracts = pkgs.callPackage ../contracts {};
|
||||
contracts = pkgs.callPackage ../contracts { };
|
||||
|
||||
fqdn = "${cfg.subdomain}.${cfg.domain}";
|
||||
protocol = if !(isNull cfg.ssl) then "https" else "http";
|
||||
|
|
@ -12,21 +17,29 @@ let
|
|||
|
||||
# TODO: Quota management, see https://github.com/ibizaman/selfhostblocks/pull/523#discussion_r2309421694
|
||||
#quotaClaim = "immich_quota";
|
||||
scopes = [ "openid" "email" "profile" "groups" "immich_scope"];
|
||||
scopes = [
|
||||
"openid"
|
||||
"email"
|
||||
"profile"
|
||||
"groups"
|
||||
"immich_scope"
|
||||
];
|
||||
|
||||
dataFolder = cfg.mediaLocation;
|
||||
ssoFqdnWithPort = if isNull cfg.sso.port
|
||||
then cfg.sso.endpoint
|
||||
else "${cfg.sso.endpoint}:${toString cfg.sso.port}";
|
||||
ssoFqdnWithPort =
|
||||
if isNull cfg.sso.port then cfg.sso.endpoint else "${cfg.sso.endpoint}:${toString cfg.sso.port}";
|
||||
# Generate Immich configuration file only for SHB-managed settings
|
||||
shbManagedSettings = lib.optionalAttrs (cfg.settings != {}) cfg.settings
|
||||
shbManagedSettings =
|
||||
lib.optionalAttrs (cfg.settings != { }) cfg.settings
|
||||
// lib.optionalAttrs (cfg.sso.enable) {
|
||||
oauth = {
|
||||
enabled = true;
|
||||
issuerUrl = "${ssoFqdnWithPort}";
|
||||
clientId = cfg.sso.clientID;
|
||||
roleClaim = roleClaim;
|
||||
clientSecret = { source = cfg.sso.sharedSecret.result.path; };
|
||||
clientSecret = {
|
||||
source = cfg.sso.sharedSecret.result.path;
|
||||
};
|
||||
scope = builtins.concatStringsSep " " scopes;
|
||||
storageLabelClaim = cfg.sso.storageLabelClaim;
|
||||
#storageQuotaClaim = quotaClaim; # TODO (commented out, otherwise defaults to 0 bytes!)
|
||||
|
|
@ -49,7 +62,9 @@ let
|
|||
host = cfg.smtp.host;
|
||||
port = cfg.smtp.port;
|
||||
username = cfg.smtp.username;
|
||||
password = { source = cfg.smtp.password.result.path; };
|
||||
password = {
|
||||
source = cfg.smtp.password.result.path;
|
||||
};
|
||||
ignoreTLS = cfg.smtp.ignoreTLS;
|
||||
secure = cfg.smtp.secure;
|
||||
};
|
||||
|
|
@ -64,13 +79,30 @@ let
|
|||
lib.shb.replaceSecrets {
|
||||
userConfig = shbManagedSettings;
|
||||
resultPath = configFile;
|
||||
generator = lib.shb.replaceSecretsFormatAdapter (pkgs.formats.json {});
|
||||
generator = lib.shb.replaceSecretsFormatAdapter (pkgs.formats.json { });
|
||||
user = "immich";
|
||||
permissions = "u=r,g=,o=";
|
||||
}
|
||||
);
|
||||
inherit (lib) mkEnableOption mkIf lists mkOption optionals;
|
||||
inherit (lib.types) attrs attrsOf bool enum listOf nullOr port submodule str path;
|
||||
inherit (lib)
|
||||
mkEnableOption
|
||||
mkIf
|
||||
lists
|
||||
mkOption
|
||||
optionals
|
||||
;
|
||||
inherit (lib.types)
|
||||
attrs
|
||||
attrsOf
|
||||
bool
|
||||
enum
|
||||
listOf
|
||||
nullOr
|
||||
port
|
||||
submodule
|
||||
str
|
||||
path
|
||||
;
|
||||
in
|
||||
{
|
||||
imports = [
|
||||
|
|
@ -154,14 +186,16 @@ in
|
|||
```
|
||||
'';
|
||||
readOnly = true;
|
||||
default = { path = dataFolder; };
|
||||
default = {
|
||||
path = dataFolder;
|
||||
};
|
||||
};
|
||||
|
||||
backup = mkOption {
|
||||
description = ''
|
||||
Backup configuration for Immich media files and database.
|
||||
'';
|
||||
default = {};
|
||||
default = { };
|
||||
type = submodule {
|
||||
options = contracts.backup.mkRequester {
|
||||
user = "immich";
|
||||
|
|
@ -190,7 +224,7 @@ in
|
|||
|
||||
machineLearning = mkOption {
|
||||
description = "Machine learning configuration.";
|
||||
default = {};
|
||||
default = { };
|
||||
type = submodule {
|
||||
options = {
|
||||
enable = mkOption {
|
||||
|
|
@ -202,7 +236,7 @@ in
|
|||
environment = mkOption {
|
||||
description = "Extra environment variables for machine learning service.";
|
||||
type = attrsOf str;
|
||||
default = {};
|
||||
default = { };
|
||||
example = {
|
||||
MACHINE_LEARNING_WORKERS = "2";
|
||||
MACHINE_LEARNING_WORKER_TIMEOUT = "180";
|
||||
|
|
@ -216,13 +250,17 @@ in
|
|||
description = ''
|
||||
Setup SSO integration.
|
||||
'';
|
||||
default = {};
|
||||
default = { };
|
||||
type = submodule {
|
||||
options = {
|
||||
enable = mkEnableOption "SSO integration.";
|
||||
|
||||
provider = mkOption {
|
||||
type = enum [ "Authelia" "Keycloak" "Generic" ];
|
||||
type = enum [
|
||||
"Authelia"
|
||||
"Keycloak"
|
||||
"Generic"
|
||||
];
|
||||
description = "OIDC provider name, used for display.";
|
||||
default = "Authelia";
|
||||
};
|
||||
|
|
@ -311,7 +349,10 @@ in
|
|||
};
|
||||
|
||||
authorization_policy = mkOption {
|
||||
type = enum [ "one_factor" "two_factor" ];
|
||||
type = enum [
|
||||
"one_factor"
|
||||
"two_factor"
|
||||
];
|
||||
description = "Require one factor (password) or two factor (device) authentication.";
|
||||
default = "one_factor";
|
||||
};
|
||||
|
|
@ -328,7 +369,7 @@ in
|
|||
|
||||
See https://immich.app/docs/install/config-file/ for available options.
|
||||
'';
|
||||
default = {};
|
||||
default = { };
|
||||
example = {
|
||||
ffmpeg.crf = 23;
|
||||
job.backgroundTask.concurrency = 5;
|
||||
|
|
@ -452,9 +493,11 @@ in
|
|||
REDIS_HOSTNAME = "127.0.0.1";
|
||||
REDIS_PORT = "6379";
|
||||
REDIS_DBINDEX = "0";
|
||||
} // lib.optionalAttrs (cfg.jwtSecretFile != null) {
|
||||
}
|
||||
// lib.optionalAttrs (cfg.jwtSecretFile != null) {
|
||||
JWT_SECRET_FILE = cfg.jwtSecretFile.result.path;
|
||||
} // lib.optionalAttrs (cfg.settings != {} || cfg.sso.enable || cfg.smtp != null) {
|
||||
}
|
||||
// lib.optionalAttrs (cfg.settings != { } || cfg.sso.enable || cfg.smtp != null) {
|
||||
IMMICH_CONFIG_FILE = configFile;
|
||||
};
|
||||
};
|
||||
|
|
@ -465,7 +508,9 @@ in
|
|||
];
|
||||
|
||||
# Configuration setup service - generates config only for SHB-managed settings
|
||||
systemd.services.immich-setup-config = mkIf (cfg.enable && (cfg.settings != {} || cfg.sso.enable || cfg.smtp != null)) {
|
||||
systemd.services.immich-setup-config =
|
||||
mkIf (cfg.enable && (cfg.settings != { } || cfg.sso.enable || cfg.smtp != null))
|
||||
{
|
||||
description = "Setup Immich configuration for SHB-managed settings";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
before = [ "immich-server.service" ];
|
||||
|
|
@ -484,7 +529,10 @@ in
|
|||
};
|
||||
|
||||
# Add immich user to video and render groups for hardware acceleration
|
||||
users.users.immich.extraGroups = optionals (cfg.accelerationDevices != []) [ "video" "render" ];
|
||||
users.users.immich.extraGroups = optionals (cfg.accelerationDevices != [ ]) [
|
||||
"video"
|
||||
"render"
|
||||
];
|
||||
|
||||
# PostgreSQL extensions are automatically handled by the Immich service
|
||||
|
||||
|
|
@ -499,7 +547,10 @@ in
|
|||
{
|
||||
domain = fqdn;
|
||||
policy = cfg.sso.authorization_policy;
|
||||
subject = ["group:immich_user" "group:immich_admin"];
|
||||
subject = [
|
||||
"group:immich_user"
|
||||
"group:immich_admin"
|
||||
];
|
||||
}
|
||||
];
|
||||
authEndpoint = lib.mkIf (cfg.sso.enable) cfg.sso.endpoint;
|
||||
|
|
@ -515,10 +566,20 @@ in
|
|||
|
||||
# Ensure services start in correct order
|
||||
systemd.services.immich-server = {
|
||||
after = [ "postgresql.service" "redis-immich.service" ]
|
||||
++ optionals (cfg.settings != {} || cfg.sso.enable || cfg.smtp != null) [ "immich-setup-config.service" ];
|
||||
requires = [ "postgresql.service" "redis-immich.service" ]
|
||||
++ optionals (cfg.settings != {} || cfg.sso.enable || cfg.smtp != null) [ "immich-setup-config.service" ];
|
||||
after = [
|
||||
"postgresql.service"
|
||||
"redis-immich.service"
|
||||
]
|
||||
++ optionals (cfg.settings != { } || cfg.sso.enable || cfg.smtp != null) [
|
||||
"immich-setup-config.service"
|
||||
];
|
||||
requires = [
|
||||
"postgresql.service"
|
||||
"redis-immich.service"
|
||||
]
|
||||
++ optionals (cfg.settings != { } || cfg.sso.enable || cfg.smtp != null) [
|
||||
"immich-setup-config.service"
|
||||
];
|
||||
};
|
||||
|
||||
systemd.services.immich-machine-learning = mkIf cfg.machineLearning.enable {
|
||||
|
|
@ -530,18 +591,18 @@ in
|
|||
# Immich expects all users that get a token to be granted access. So users can either be part of the
|
||||
# "admin" group or the "user" group. Users that are not part of either should be blocked by
|
||||
# the ID provider (Authelia).
|
||||
user_attributes.${roleClaim}.expression = ''"${cfg.sso.adminUserGroup}" in groups ? "admin" : "user"'';
|
||||
user_attributes.${roleClaim}.expression =
|
||||
''"${cfg.sso.adminUserGroup}" in groups ? "admin" : "user"'';
|
||||
};
|
||||
shb.authelia.extraOidcClaimsPolicies.immich_policy = {
|
||||
custom_claims = {
|
||||
${roleClaim} = {};
|
||||
${roleClaim} = { };
|
||||
};
|
||||
};
|
||||
shb.authelia.extraOidcScopes.immich_scope = {
|
||||
claims = [ roleClaim ];
|
||||
};
|
||||
|
||||
|
||||
shb.authelia.oidcClients = lists.optionals (cfg.sso.enable && cfg.sso.provider == "Authelia") [
|
||||
{
|
||||
client_id = cfg.sso.clientID;
|
||||
|
|
|
|||
|
|
@ -1,11 +1,16 @@
|
|||
{ config, lib, pkgs, ...}:
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
inherit (lib) types;
|
||||
|
||||
cfg = config.shb.jellyfin;
|
||||
|
||||
contracts = pkgs.callPackage ../contracts {};
|
||||
contracts = pkgs.callPackage ../contracts { };
|
||||
|
||||
fqdn = "${cfg.subdomain}.${cfg.domain}";
|
||||
|
||||
|
|
@ -91,7 +96,8 @@ in
|
|||
admin = lib.mkOption {
|
||||
description = "Default admin user info. Only needed if LDAP or SSO is not configured.";
|
||||
default = null;
|
||||
type = types.nullOr (types.submodule {
|
||||
type = types.nullOr (
|
||||
types.submodule {
|
||||
options = {
|
||||
username = lib.mkOption {
|
||||
description = "Username of the default admin user.";
|
||||
|
|
@ -110,12 +116,13 @@ in
|
|||
};
|
||||
};
|
||||
};
|
||||
});
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
ldap = lib.mkOption {
|
||||
description = "LDAP configuration.";
|
||||
default = {};
|
||||
default = { };
|
||||
type = types.submodule {
|
||||
options = {
|
||||
enable = lib.mkEnableOption "LDAP";
|
||||
|
|
@ -167,7 +174,7 @@ in
|
|||
|
||||
sso = lib.mkOption {
|
||||
description = "SSO configuration.";
|
||||
default = {};
|
||||
default = { };
|
||||
type = types.submodule {
|
||||
options = {
|
||||
enable = lib.mkEnableOption "SSO";
|
||||
|
|
@ -203,7 +210,10 @@ in
|
|||
};
|
||||
|
||||
authorization_policy = lib.mkOption {
|
||||
type = types.enum [ "one_factor" "two_factor" ];
|
||||
type = types.enum [
|
||||
"one_factor"
|
||||
"two_factor"
|
||||
];
|
||||
description = "Require one factor (password) or two factor (device) authentication.";
|
||||
default = "one_factor";
|
||||
};
|
||||
|
|
@ -238,14 +248,15 @@ in
|
|||
description = ''
|
||||
Backup configuration.
|
||||
'';
|
||||
default = {};
|
||||
default = { };
|
||||
type = types.submodule {
|
||||
options = contracts.backup.mkRequester {
|
||||
user = "jellyfin";
|
||||
sourceDirectories = [
|
||||
config.services.jellyfin.dataDir
|
||||
];
|
||||
sourceDirectoriesText = ''[
|
||||
sourceDirectoriesText = ''
|
||||
[
|
||||
"services.jellyfin.dataDir"
|
||||
]'';
|
||||
};
|
||||
|
|
@ -254,7 +265,10 @@ in
|
|||
};
|
||||
|
||||
imports = [
|
||||
(lib.mkRenamedOptionModule [ "shb" "jellyfin" "adminPassword" ] [ "shb" "jellyfin" "admin" "password" ])
|
||||
(lib.mkRenamedOptionModule
|
||||
[ "shb" "jellyfin" "adminPassword" ]
|
||||
[ "shb" "jellyfin" "admin" "password" ]
|
||||
)
|
||||
];
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
|
|
@ -269,7 +283,10 @@ in
|
|||
|
||||
networking.firewall = {
|
||||
# from https://jellyfin.org/docs/general/networking/index.html, for auto-discovery
|
||||
allowedUDPPorts = [ 1900 7359 ];
|
||||
allowedUDPPorts = [
|
||||
1900
|
||||
7359
|
||||
];
|
||||
};
|
||||
|
||||
services.nginx.enable = true;
|
||||
|
|
@ -383,18 +400,20 @@ in
|
|||
'';
|
||||
};
|
||||
|
||||
services.prometheus.scrapeConfigs = [{
|
||||
services.prometheus.scrapeConfigs = [
|
||||
{
|
||||
job_name = "jellyfin";
|
||||
static_configs = [
|
||||
{
|
||||
targets = ["127.0.0.1:${toString cfg.port}"];
|
||||
targets = [ "127.0.0.1:${toString cfg.port}" ];
|
||||
labels = {
|
||||
"hostname" = config.networking.hostName;
|
||||
"domain" = cfg.domain;
|
||||
};
|
||||
}
|
||||
];
|
||||
}];
|
||||
}
|
||||
];
|
||||
|
||||
# LDAP config but you need to install the plugin by hand
|
||||
|
||||
|
|
@ -560,8 +579,7 @@ in
|
|||
</NetworkConfiguration>
|
||||
'';
|
||||
in
|
||||
lib.strings.optionalString cfg.debug
|
||||
''
|
||||
lib.strings.optionalString cfg.debug ''
|
||||
if [ -f "${config.services.jellyfin.configDir}/logging.json" ] && [ ! -L "${config.services.jellyfin.configDir}/logging.json" ]; then
|
||||
echo "A ${config.services.jellyfin.configDir}/logging.json file exists already, this indicates probably an existing installation. Please remove it before continuing."
|
||||
exit 1
|
||||
|
|
@ -576,7 +594,8 @@ in
|
|||
replacements = [
|
||||
];
|
||||
})
|
||||
+ lib.strings.optionalString cfg.ldap.enable (lib.shb.replaceSecretsScript {
|
||||
+ lib.strings.optionalString cfg.ldap.enable (
|
||||
lib.shb.replaceSecretsScript {
|
||||
file = ldapConfig;
|
||||
resultPath = "${config.services.jellyfin.dataDir}/plugins/configurations/LDAP-Auth.xml";
|
||||
replacements = [
|
||||
|
|
@ -585,8 +604,10 @@ in
|
|||
source = cfg.ldap.adminPassword.result.path;
|
||||
}
|
||||
];
|
||||
})
|
||||
+ lib.strings.optionalString cfg.sso.enable (lib.shb.replaceSecretsScript {
|
||||
}
|
||||
)
|
||||
+ lib.strings.optionalString cfg.sso.enable (
|
||||
lib.shb.replaceSecretsScript {
|
||||
file = ssoConfig;
|
||||
resultPath = "${config.services.jellyfin.dataDir}/plugins/configurations/SSO-Auth.xml";
|
||||
replacements = [
|
||||
|
|
@ -595,15 +616,19 @@ in
|
|||
source = cfg.sso.sharedSecret.result.path;
|
||||
}
|
||||
];
|
||||
})
|
||||
+ lib.strings.optionalString cfg.sso.enable (lib.shb.replaceSecretsScript {
|
||||
}
|
||||
)
|
||||
+ lib.strings.optionalString cfg.sso.enable (
|
||||
lib.shb.replaceSecretsScript {
|
||||
file = brandingConfig;
|
||||
resultPath = "${config.services.jellyfin.dataDir}/config/branding.xml";
|
||||
replacements = [
|
||||
];
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
systemd.services.jellyfin.serviceConfig.ExecStartPost = let
|
||||
systemd.services.jellyfin.serviceConfig.ExecStartPost =
|
||||
let
|
||||
# We must always wait for the service to be fully initialized,
|
||||
# even if we're planning on changing the config and restarting.
|
||||
waitForCurl = pkgs.writeShellApplication {
|
||||
|
|
@ -646,7 +671,7 @@ in
|
|||
#
|
||||
# If the file does not exist, write the config, create the file then restart.
|
||||
# If the file exists, do nothing and remove the file, resetting the state for the next time.
|
||||
restartedFile="${config.services.jellyfin.dataDir}/.jellyfin-restarted";
|
||||
restartedFile = "${config.services.jellyfin.dataDir}/.jellyfin-restarted";
|
||||
|
||||
writeConfig = pkgs.writeShellApplication {
|
||||
name = "writeConfig";
|
||||
|
|
|
|||
|
|
@ -1,8 +1,13 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
let
|
||||
cfg = config.shb.karakeep;
|
||||
|
||||
contracts = pkgs.callPackage ../contracts {};
|
||||
contracts = pkgs.callPackage ../contracts { };
|
||||
in
|
||||
{
|
||||
imports = [
|
||||
|
|
@ -37,7 +42,7 @@ in
|
|||
};
|
||||
|
||||
environment = lib.mkOption {
|
||||
default = {};
|
||||
default = { };
|
||||
type = lib.types.attrsOf lib.types.str;
|
||||
description = "Extra environment variables. See https://docs.karakeep.app/configuration/";
|
||||
example = ''
|
||||
|
|
@ -56,7 +61,7 @@ in
|
|||
description = ''
|
||||
Setup LDAP integration.
|
||||
'';
|
||||
default = {};
|
||||
default = { };
|
||||
type = lib.types.submodule {
|
||||
options = {
|
||||
userGroup = lib.mkOption {
|
||||
|
|
@ -72,7 +77,7 @@ in
|
|||
description = ''
|
||||
Setup SSO integration.
|
||||
'';
|
||||
default = {};
|
||||
default = { };
|
||||
type = lib.types.submodule {
|
||||
options = {
|
||||
enable = lib.mkEnableOption "SSO integration.";
|
||||
|
|
@ -91,7 +96,10 @@ in
|
|||
};
|
||||
|
||||
authorization_policy = lib.mkOption {
|
||||
type = lib.types.enum [ "one_factor" "two_factor" ];
|
||||
type = lib.types.enum [
|
||||
"one_factor"
|
||||
"two_factor"
|
||||
];
|
||||
description = "Require one factor (password) or two factor (device) authentication.";
|
||||
default = "one_factor";
|
||||
};
|
||||
|
|
@ -102,7 +110,11 @@ in
|
|||
options = contracts.secret.mkRequester {
|
||||
owner = "karakeep";
|
||||
# These services are the ones relying on the environment file containing the secrets.
|
||||
restartUnits = [ "karakeep-init.service" "karakeep-workers.service" "karakeep-workers.service" ];
|
||||
restartUnits = [
|
||||
"karakeep-init.service"
|
||||
"karakeep-workers.service"
|
||||
"karakeep-workers.service"
|
||||
];
|
||||
};
|
||||
};
|
||||
};
|
||||
|
|
@ -125,7 +137,7 @@ in
|
|||
description = ''
|
||||
Backup state directory.
|
||||
'';
|
||||
default = {};
|
||||
default = { };
|
||||
type = lib.types.submodule {
|
||||
options = contracts.backup.mkRequester {
|
||||
user = "karakeep";
|
||||
|
|
@ -142,7 +154,11 @@ in
|
|||
options = contracts.secret.mkRequester {
|
||||
owner = "karakeep";
|
||||
# These services are the ones relying on the environment file containing the secrets.
|
||||
restartUnits = [ "karakeep-init.service" "karakeep-workers.service" "karakeep-workers.service" ];
|
||||
restartUnits = [
|
||||
"karakeep-init.service"
|
||||
"karakeep-workers.service"
|
||||
"karakeep-workers.service"
|
||||
];
|
||||
};
|
||||
};
|
||||
};
|
||||
|
|
@ -153,13 +169,18 @@ in
|
|||
options = contracts.secret.mkRequester {
|
||||
owner = "karakeep";
|
||||
# These services are the ones relying on the environment file containing the secrets.
|
||||
restartUnits = [ "karakeep-init.service" "karakeep-workers.service" "karakeep-workers.service" ];
|
||||
restartUnits = [
|
||||
"karakeep-init.service"
|
||||
"karakeep-workers.service"
|
||||
"karakeep-workers.service"
|
||||
];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = (lib.mkMerge [
|
||||
config = (
|
||||
lib.mkMerge [
|
||||
(lib.mkIf cfg.enable {
|
||||
services.karakeep = {
|
||||
enable = true;
|
||||
|
|
@ -168,7 +189,8 @@ in
|
|||
extraEnvironment = {
|
||||
PORT = toString cfg.port;
|
||||
DISABLE_NEW_RELEASE_CHECK = "true"; # These are handled by NixOS
|
||||
} // cfg.environment;
|
||||
}
|
||||
// cfg.environment;
|
||||
};
|
||||
|
||||
shb.nginx.vhosts = [
|
||||
|
|
@ -182,19 +204,23 @@ in
|
|||
# This is needed otherwise the MEILI_MASTER_KEY is generated randomly on first start
|
||||
# instead of using the value from the cfg.meilisearchMasterKey option.
|
||||
systemd.services.karakeep-init = {
|
||||
script = lib.mkForce ((lib.shb.replaceSecrets {
|
||||
script = lib.mkForce (
|
||||
(lib.shb.replaceSecrets {
|
||||
userConfig = {
|
||||
MEILI_MASTER_KEY.source = cfg.meilisearchMasterKey.result.path;
|
||||
NEXTAUTH_SECRET.source = cfg.nextauthSecret.result.path;
|
||||
} // lib.optionalAttrs cfg.sso.enable {
|
||||
}
|
||||
// lib.optionalAttrs cfg.sso.enable {
|
||||
OAUTH_CLIENT_SECRET.source = cfg.sso.sharedSecret.result.path;
|
||||
};
|
||||
resultPath = "/var/lib/karakeep/settings.env";
|
||||
generator = lib.shb.toEnvVar;
|
||||
}) + ''
|
||||
})
|
||||
+ ''
|
||||
export DATA_DIR="$STATE_DIRECTORY"
|
||||
exec ${config.services.karakeep.package}/lib/karakeep/migrate
|
||||
'');
|
||||
''
|
||||
);
|
||||
};
|
||||
})
|
||||
(lib.mkIf cfg.enable {
|
||||
|
|
@ -206,7 +232,7 @@ in
|
|||
})
|
||||
(lib.mkIf (cfg.enable && cfg.sso.enable) {
|
||||
shb.lldap.ensureGroups = {
|
||||
${cfg.ldap.userGroup} = {};
|
||||
${cfg.ldap.userGroup} = { };
|
||||
};
|
||||
|
||||
shb.authelia.extraOidcAuthorizationPolicies.karakeep = {
|
||||
|
|
@ -222,7 +248,11 @@ in
|
|||
{
|
||||
client_id = cfg.sso.clientID;
|
||||
client_secret.source = cfg.sso.sharedSecretForAuthelia.result.path;
|
||||
scopes = [ "openid" "email" "profile" ];
|
||||
scopes = [
|
||||
"openid"
|
||||
"email"
|
||||
"profile"
|
||||
];
|
||||
authorization_policy = "karakeep";
|
||||
redirect_uris = [
|
||||
"https://${cfg.subdomain}.${cfg.domain}/api/auth/callback/custom"
|
||||
|
|
@ -241,5 +271,6 @@ in
|
|||
};
|
||||
};
|
||||
})
|
||||
]);
|
||||
]
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,9 @@
|
|||
{ config, pkgs, lib, ... }:
|
||||
{
|
||||
config,
|
||||
pkgs,
|
||||
lib,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
cfg = config.shb.nextcloud;
|
||||
|
|
@ -7,12 +12,17 @@ let
|
|||
fqdnWithPort = if isNull cfg.port then fqdn else "${fqdn}:${toString cfg.port}";
|
||||
protocol = if !(isNull cfg.ssl) then "https" else "http";
|
||||
|
||||
ssoFqdnWithPort = if isNull cfg.apps.sso.port then cfg.apps.sso.endpoint else "${cfg.apps.sso.endpoint}:${toString cfg.apps.sso.port}";
|
||||
ssoFqdnWithPort =
|
||||
if isNull cfg.apps.sso.port then
|
||||
cfg.apps.sso.endpoint
|
||||
else
|
||||
"${cfg.apps.sso.endpoint}:${toString cfg.apps.sso.port}";
|
||||
|
||||
contracts = pkgs.callPackage ../contracts {};
|
||||
contracts = pkgs.callPackage ../contracts { };
|
||||
|
||||
nextcloudPkg = builtins.getAttr ("nextcloud" + builtins.toString cfg.version) pkgs;
|
||||
nextcloudApps = (builtins.getAttr ("nextcloud" + builtins.toString cfg.version + "Packages") pkgs).apps;
|
||||
nextcloudApps =
|
||||
(builtins.getAttr ("nextcloud" + builtins.toString cfg.version + "Packages") pkgs).apps;
|
||||
|
||||
occ = "${config.services.nextcloud.occ}/bin/nextcloud-occ";
|
||||
in
|
||||
|
|
@ -71,7 +81,10 @@ in
|
|||
|
||||
version = lib.mkOption {
|
||||
description = "Nextcloud version to choose from.";
|
||||
type = lib.types.enum [ 31 32 ];
|
||||
type = lib.types.enum [
|
||||
31
|
||||
32
|
||||
];
|
||||
default = 31;
|
||||
};
|
||||
|
||||
|
|
@ -84,7 +97,7 @@ in
|
|||
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 = [];
|
||||
default = [ ];
|
||||
example = lib.literalExpression ''["var.mount"]'';
|
||||
};
|
||||
|
||||
|
|
@ -105,7 +118,6 @@ in
|
|||
};
|
||||
};
|
||||
|
||||
|
||||
maxUploadSize = lib.mkOption {
|
||||
default = "4G";
|
||||
type = lib.types.str;
|
||||
|
|
@ -188,7 +200,7 @@ in
|
|||
|
||||
phpFpmPrometheusExporter = lib.mkOption {
|
||||
description = "Settings for exporting";
|
||||
default = {};
|
||||
default = { };
|
||||
|
||||
type = lib.types.submodule {
|
||||
options = {
|
||||
|
|
@ -216,7 +228,7 @@ in
|
|||
through the UI. You can still make changes but they will be overridden on next deploy. You
|
||||
can still install and configure other apps through the UI.
|
||||
'';
|
||||
default = {};
|
||||
default = { };
|
||||
type = lib.types.submodule {
|
||||
options = {
|
||||
onlyoffice = lib.mkOption {
|
||||
|
|
@ -226,7 +238,7 @@ in
|
|||
Enabling this app will also start an OnlyOffice instance accessible at the given
|
||||
subdomain from the given network range.
|
||||
'';
|
||||
default = {};
|
||||
default = { };
|
||||
type = lib.types.submodule {
|
||||
options = {
|
||||
enable = lib.mkEnableOption "Nextcloud OnlyOffice App";
|
||||
|
|
@ -275,7 +287,7 @@ in
|
|||
nextcloud-occ -vvv preview:generate-all
|
||||
```
|
||||
'';
|
||||
default = {};
|
||||
default = { };
|
||||
type = lib.types.submodule {
|
||||
options = {
|
||||
enable = lib.mkEnableOption "Nextcloud Preview Generator App";
|
||||
|
|
@ -330,14 +342,15 @@ in
|
|||
other side, a spinning hard drive can store more data which is well suited for storing
|
||||
user data.
|
||||
'';
|
||||
default = {};
|
||||
default = { };
|
||||
type = lib.types.submodule {
|
||||
options = {
|
||||
enable = lib.mkEnableOption "Nextcloud External Storage App";
|
||||
userLocalMount = lib.mkOption {
|
||||
default = null;
|
||||
description = "If set, adds a local mount as external storage.";
|
||||
type = lib.types.nullOr (lib.types.submodule {
|
||||
type = lib.types.nullOr (
|
||||
lib.types.submodule {
|
||||
options = {
|
||||
directory = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
|
|
@ -354,10 +367,14 @@ in
|
|||
Path of the mount in Nextcloud. Use `/` to mount as the root.
|
||||
'';
|
||||
default = "";
|
||||
example = [ "home" "/" ];
|
||||
example = [
|
||||
"home"
|
||||
"/"
|
||||
];
|
||||
};
|
||||
};
|
||||
});
|
||||
}
|
||||
);
|
||||
};
|
||||
};
|
||||
};
|
||||
|
|
@ -370,8 +387,9 @@ in
|
|||
Enabling this app will create a new LDAP configuration or update one that exists with
|
||||
the given host.
|
||||
'';
|
||||
default = {};
|
||||
type = lib.types.nullOr (lib.types.submodule {
|
||||
default = { };
|
||||
type = lib.types.nullOr (
|
||||
lib.types.submodule {
|
||||
options = {
|
||||
enable = lib.mkEnableOption "LDAP app.";
|
||||
|
||||
|
|
@ -429,7 +447,8 @@ in
|
|||
default = 50;
|
||||
};
|
||||
};
|
||||
});
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
sso = lib.mkOption {
|
||||
|
|
@ -439,7 +458,7 @@ in
|
|||
Enabling this app will create a new LDAP configuration or update one that exists with
|
||||
the given host.
|
||||
'';
|
||||
default = {};
|
||||
default = { };
|
||||
type = lib.types.submodule {
|
||||
options = {
|
||||
enable = lib.mkEnableOption "SSO app.";
|
||||
|
|
@ -469,7 +488,10 @@ in
|
|||
};
|
||||
|
||||
authorization_policy = lib.mkOption {
|
||||
type = lib.types.enum [ "one_factor" "two_factor" ];
|
||||
type = lib.types.enum [
|
||||
"one_factor"
|
||||
"two_factor"
|
||||
];
|
||||
description = "Require one factor (password) or two factor (device) authentication.";
|
||||
default = "one_factor";
|
||||
};
|
||||
|
|
@ -491,7 +513,6 @@ in
|
|||
};
|
||||
};
|
||||
|
||||
|
||||
secretForAuthelia = lib.mkOption {
|
||||
description = "OIDC shared secret. Content must be the same as `secretFile` option.";
|
||||
type = lib.types.submodule {
|
||||
|
|
@ -502,7 +523,6 @@ in
|
|||
};
|
||||
};
|
||||
|
||||
|
||||
fallbackDefaultAuth = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
description = ''
|
||||
|
|
@ -528,7 +548,7 @@ in
|
|||
nextcloud-occ memories:index
|
||||
```
|
||||
'';
|
||||
default = {};
|
||||
default = { };
|
||||
type = lib.types.submodule {
|
||||
options = {
|
||||
enable = lib.mkEnableOption "Memories app.";
|
||||
|
|
@ -561,7 +581,7 @@ in
|
|||
|
||||
Enabling this app will set up the Recognize app and configure all its dependencies.
|
||||
'';
|
||||
default = {};
|
||||
default = { };
|
||||
type = lib.types.submodule {
|
||||
options = {
|
||||
enable = lib.mkEnableOption "Recognize app.";
|
||||
|
|
@ -599,19 +619,18 @@ in
|
|||
'';
|
||||
};
|
||||
|
||||
|
||||
backup = lib.mkOption {
|
||||
description = ''
|
||||
Backup configuration.
|
||||
'';
|
||||
default = {};
|
||||
default = { };
|
||||
type = lib.types.submodule {
|
||||
options = contracts.backup.mkRequester {
|
||||
user = "nextcloud";
|
||||
sourceDirectories = [
|
||||
cfg.dataDir
|
||||
];
|
||||
excludePatterns = [".rnd"];
|
||||
excludePatterns = [ ".rnd" ];
|
||||
};
|
||||
};
|
||||
};
|
||||
|
|
@ -722,13 +741,15 @@ in
|
|||
# Very important for a bunch of scripts to load correctly. Otherwise you get Content-Security-Policy errors. See https://docs.nextcloud.com/server/13/admin_manual/configuration_server/harden_server.html#enable-http-strict-transport-security
|
||||
https = !(isNull cfg.ssl);
|
||||
|
||||
extraApps = if isNull cfg.extraApps then {} else cfg.extraApps nextcloudApps;
|
||||
extraApps = if isNull cfg.extraApps then { } else cfg.extraApps nextcloudApps;
|
||||
extraAppsEnable = true;
|
||||
appstoreEnable = true;
|
||||
|
||||
settings = let
|
||||
settings =
|
||||
let
|
||||
protocol = if !(isNull cfg.ssl) then "https" else "http";
|
||||
in {
|
||||
in
|
||||
{
|
||||
"default_phone_region" = cfg.defaultPhoneRegion;
|
||||
|
||||
"overwrite.cli.url" = "${protocol}://${fqdn}";
|
||||
|
|
@ -773,7 +794,8 @@ in
|
|||
"redis.session.locking_enabled" = "1";
|
||||
"redis.session.lock_retries" = "-1";
|
||||
"redis.session.lock_wait_time" = "10000";
|
||||
} // lib.optionalAttrs (! (isNull cfg.tracing)) {
|
||||
}
|
||||
// lib.optionalAttrs (!(isNull cfg.tracing)) {
|
||||
# "xdebug.remote_enable" = "on";
|
||||
# "xdebug.remote_host" = "127.0.0.1";
|
||||
# "xdebug.remote_port" = "9000";
|
||||
|
|
@ -785,7 +807,7 @@ in
|
|||
"xdebug.start_with_request" = "trigger";
|
||||
};
|
||||
|
||||
poolSettings = lib.mkIf (! (isNull cfg.phpFpmPoolSettings)) cfg.phpFpmPoolSettings;
|
||||
poolSettings = lib.mkIf (!(isNull cfg.phpFpmPoolSettings)) cfg.phpFpmPoolSettings;
|
||||
|
||||
phpExtraExtensions = all: [ all.xdebug ];
|
||||
};
|
||||
|
|
@ -810,7 +832,7 @@ in
|
|||
pkgs.ffmpeg-headless
|
||||
];
|
||||
|
||||
services.postgresql.settings = lib.mkIf (! (isNull cfg.postgresSettings)) cfg.postgresSettings;
|
||||
services.postgresql.settings = lib.mkIf (!(isNull cfg.postgresSettings)) cfg.postgresSettings;
|
||||
|
||||
systemd.services.phpfpm-nextcloud.preStart = ''
|
||||
mkdir -p /var/log/xdebug; chown -R nextcloud: /var/log/xdebug
|
||||
|
|
@ -837,7 +859,9 @@ in
|
|||
port = cfg.phpFpmPrometheusExporter.port;
|
||||
listenAddress = "127.0.0.1";
|
||||
extraFlags = [
|
||||
"--phpfpm.scrape-uri=tcp://127.0.0.1:${toString (cfg.phpFpmPrometheusExporter.port -1)}/status?full"
|
||||
"--phpfpm.scrape-uri=tcp://127.0.0.1:${
|
||||
toString (cfg.phpFpmPrometheusExporter.port - 1)
|
||||
}/status?full"
|
||||
];
|
||||
};
|
||||
|
||||
|
|
@ -849,20 +873,22 @@ in
|
|||
#
|
||||
# I also tried to server the status page at /status.php
|
||||
# but fcgi doesn't like the returned headers.
|
||||
"pm.status_listen" = "127.0.0.1:${toString (cfg.phpFpmPrometheusExporter.port -1)}";
|
||||
"pm.status_listen" = "127.0.0.1:${toString (cfg.phpFpmPrometheusExporter.port - 1)}";
|
||||
};
|
||||
};
|
||||
|
||||
services.prometheus.scrapeConfigs = [
|
||||
{
|
||||
job_name = "phpfpm-nextcloud";
|
||||
static_configs = [{
|
||||
targets = ["127.0.0.1:${toString cfg.phpFpmPrometheusExporter.port}"];
|
||||
static_configs = [
|
||||
{
|
||||
targets = [ "127.0.0.1:${toString cfg.phpFpmPrometheusExporter.port}" ];
|
||||
labels = {
|
||||
"hostname" = config.networking.hostName;
|
||||
"domain" = cfg.domain;
|
||||
};
|
||||
}];
|
||||
}
|
||||
];
|
||||
}
|
||||
];
|
||||
})
|
||||
|
|
@ -964,7 +990,8 @@ in
|
|||
systemd.services.nextcloud-setup.script = ''
|
||||
${occ} app:install files_external || :
|
||||
${occ} app:enable files_external
|
||||
'' + lib.optionalString (cfg.apps.externalStorage.userLocalMount != null) (
|
||||
''
|
||||
+ lib.optionalString (cfg.apps.externalStorage.userLocalMount != null) (
|
||||
let
|
||||
cfg' = cfg.apps.externalStorage.userLocalMount;
|
||||
jq = "${pkgs.jq}/bin/jq";
|
||||
|
|
@ -979,7 +1006,8 @@ in
|
|||
null::null \
|
||||
--config datadir='${cfg'.directory}'
|
||||
fi
|
||||
'');
|
||||
''
|
||||
);
|
||||
})
|
||||
|
||||
(lib.mkIf (cfg.enable && cfg.apps.ldap.enable) {
|
||||
|
|
@ -988,7 +1016,8 @@ in
|
|||
let
|
||||
cfg' = cfg.apps.ldap;
|
||||
cID = "s" + toString cfg'.configID;
|
||||
in ''
|
||||
in
|
||||
''
|
||||
${occ} app:install user_ldap || :
|
||||
${occ} app:enable user_ldap
|
||||
|
||||
|
|
@ -1055,7 +1084,8 @@ in
|
|||
'';
|
||||
})
|
||||
|
||||
(let
|
||||
(
|
||||
let
|
||||
scopes = [
|
||||
"openid"
|
||||
"profile"
|
||||
|
|
@ -1063,7 +1093,8 @@ in
|
|||
"groups"
|
||||
"nextcloud_userinfo"
|
||||
];
|
||||
in lib.mkIf (cfg.enable && cfg.apps.sso.enable) {
|
||||
in
|
||||
lib.mkIf (cfg.enable && cfg.apps.sso.enable) {
|
||||
assertions = [
|
||||
{
|
||||
assertion = cfg.ssl != null;
|
||||
|
|
@ -1080,8 +1111,7 @@ in
|
|||
before = [ "nextcloud-setup.service" ];
|
||||
serviceConfig.Type = "oneshot";
|
||||
serviceConfig.User = "nextcloud";
|
||||
script =
|
||||
''
|
||||
script = ''
|
||||
mkdir -p ${cfg.dataDir}/config
|
||||
cat <<EOF > "${cfg.dataDir}/config/secretFile"
|
||||
{
|
||||
|
|
@ -1157,11 +1187,12 @@ in
|
|||
};
|
||||
|
||||
shb.authelia.extraDefinitions = {
|
||||
user_attributes."is_nextcloud_admin".expression = ''type(groups) == list && "${cfg.apps.sso.adminGroup}" in groups'';
|
||||
user_attributes."is_nextcloud_admin".expression =
|
||||
''type(groups) == list && "${cfg.apps.sso.adminGroup}" in groups'';
|
||||
};
|
||||
shb.authelia.extraOidcClaimsPolicies."nextcloud_userinfo" = {
|
||||
custom_claims = {
|
||||
is_nextcloud_admin = {};
|
||||
is_nextcloud_admin = { };
|
||||
};
|
||||
};
|
||||
shb.authelia.extraOidcScopes."nextcloud_userinfo" = {
|
||||
|
|
@ -1187,11 +1218,11 @@ in
|
|||
token_endpoint_auth_method = "client_secret_basic";
|
||||
}
|
||||
];
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
(lib.mkIf (cfg.enable && cfg.autoDisableMaintenanceModeOnStart) {
|
||||
systemd.services.nextcloud-setup.preStart =
|
||||
lib.mkBefore ''
|
||||
systemd.services.nextcloud-setup.preStart = lib.mkBefore ''
|
||||
if [[ -e /var/lib/nextcloud/config/config.php ]]; then
|
||||
${occ} maintenance:mode --no-interaction --quiet --off
|
||||
fi
|
||||
|
|
@ -1199,8 +1230,7 @@ in
|
|||
})
|
||||
|
||||
(lib.mkIf (cfg.enable && cfg.alwaysApplyExpensiveMigrations) {
|
||||
systemd.services.nextcloud-setup.script =
|
||||
''
|
||||
systemd.services.nextcloud-setup.script = ''
|
||||
if [[ -e /var/lib/nextcloud/config/config.php ]]; then
|
||||
${occ} maintenance:repair --include-expensive
|
||||
fi
|
||||
|
|
@ -1209,17 +1239,19 @@ in
|
|||
|
||||
# Great source of inspiration:
|
||||
# https://github.com/Shawn8901/nix-configuration/blob/538c18d9ecbf7c7e649b1540c0d40881bada6690/modules/nixos/private/nextcloud/memories.nix#L226
|
||||
(lib.mkIf cfg.apps.memories.enable
|
||||
(let
|
||||
(lib.mkIf cfg.apps.memories.enable (
|
||||
let
|
||||
cfg' = cfg.apps.memories;
|
||||
|
||||
exiftool = pkgs.exiftool.overrideAttrs (f: p: {
|
||||
exiftool = pkgs.exiftool.overrideAttrs (
|
||||
f: p: {
|
||||
version = "12.70";
|
||||
src = pkgs.fetchurl {
|
||||
url = "https://exiftool.org/Image-ExifTool-12.70.tar.gz";
|
||||
hash = "sha256-TLJSJEXMPj870TkExq6uraX8Wl4kmNerrSlX3LQsr/4=";
|
||||
};
|
||||
});
|
||||
}
|
||||
);
|
||||
in
|
||||
{
|
||||
assertions = [
|
||||
|
|
@ -1265,10 +1297,11 @@ in
|
|||
DeviceAllow = [ "/dev/dri/renderD128 rwm" ];
|
||||
PrivateDevices = lib.mkForce false;
|
||||
};
|
||||
}))
|
||||
}
|
||||
))
|
||||
|
||||
(lib.mkIf cfg.apps.recognize.enable
|
||||
(let
|
||||
(lib.mkIf cfg.apps.recognize.enable (
|
||||
let
|
||||
cfg' = cfg.apps.recognize;
|
||||
in
|
||||
{
|
||||
|
|
@ -1276,8 +1309,7 @@ in
|
|||
inherit (nextcloudApps) recognize;
|
||||
};
|
||||
|
||||
systemd.services.nextcloud-setup.script =
|
||||
''
|
||||
systemd.services.nextcloud-setup.script = ''
|
||||
${occ} config:app:set recognize nice_binary --value ${pkgs.coreutils}/bin/nice
|
||||
${occ} config:app:set recognize node_binary --value ${pkgs.nodejs}/bin/node
|
||||
${occ} config:app:set recognize faces.enabled --value true
|
||||
|
|
@ -1292,6 +1324,7 @@ in
|
|||
${occ} config:app:set recognize musicnn.enabled --value true
|
||||
${occ} config:app:set recognize musicnn.batchSize --value 100
|
||||
'';
|
||||
}))
|
||||
}
|
||||
))
|
||||
];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,22 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
let
|
||||
cfg = config.shb.open-webui;
|
||||
|
||||
contracts = pkgs.callPackage ../contracts {};
|
||||
contracts = pkgs.callPackage ../contracts { };
|
||||
|
||||
roleClaim = "openwebui_groups";
|
||||
oauthScopes = [ "openid" "email" "profile" "groups" "${roleClaim}" ];
|
||||
oauthScopes = [
|
||||
"openid"
|
||||
"email"
|
||||
"profile"
|
||||
"groups"
|
||||
"${roleClaim}"
|
||||
];
|
||||
in
|
||||
{
|
||||
imports = [
|
||||
|
|
@ -42,7 +53,7 @@ in
|
|||
environment = lib.mkOption {
|
||||
type = lib.types.attrsOf lib.types.str;
|
||||
description = "Extra environment variables. See https://docs.openwebui.com/getting-started/env-configuration";
|
||||
default = {};
|
||||
default = { };
|
||||
example = ''
|
||||
{
|
||||
WEBUI_NAME = "SelfHostBlocks";
|
||||
|
|
@ -62,7 +73,7 @@ in
|
|||
description = ''
|
||||
Setup LDAP integration.
|
||||
'';
|
||||
default = {};
|
||||
default = { };
|
||||
type = lib.types.submodule {
|
||||
options = {
|
||||
userGroup = lib.mkOption {
|
||||
|
|
@ -84,7 +95,7 @@ in
|
|||
description = ''
|
||||
Setup SSO integration.
|
||||
'';
|
||||
default = {};
|
||||
default = { };
|
||||
type = lib.types.submodule {
|
||||
options = {
|
||||
enable = lib.mkEnableOption "SSO integration.";
|
||||
|
|
@ -103,7 +114,10 @@ in
|
|||
};
|
||||
|
||||
authorization_policy = lib.mkOption {
|
||||
type = lib.types.enum [ "one_factor" "two_factor" ];
|
||||
type = lib.types.enum [
|
||||
"one_factor"
|
||||
"two_factor"
|
||||
];
|
||||
description = "Require one factor (password) or two factor (device) authentication.";
|
||||
default = "one_factor";
|
||||
};
|
||||
|
|
@ -136,7 +150,7 @@ in
|
|||
description = ''
|
||||
Backup state directory.
|
||||
'';
|
||||
default = {};
|
||||
default = { };
|
||||
type = lib.types.submodule {
|
||||
options = contracts.backup.mkRequester {
|
||||
user = "open-webui";
|
||||
|
|
@ -149,13 +163,14 @@ in
|
|||
};
|
||||
};
|
||||
|
||||
config = (lib.mkMerge [
|
||||
config = (
|
||||
lib.mkMerge [
|
||||
(lib.mkIf cfg.enable {
|
||||
users.users.open-webui = {
|
||||
isSystemUser = true;
|
||||
group = "open-webui";
|
||||
};
|
||||
users.groups.open-webui = {};
|
||||
users.groups.open-webui = { };
|
||||
|
||||
services.open-webui = {
|
||||
enable = true;
|
||||
|
|
@ -173,7 +188,8 @@ in
|
|||
SCARF_NO_ANALYTICS = "True";
|
||||
|
||||
ENABLE_VERSION_UPDATE_CHECK = "False";
|
||||
} // cfg.environment;
|
||||
}
|
||||
// cfg.environment;
|
||||
};
|
||||
|
||||
systemd.services.open-webui.path = [
|
||||
|
|
@ -193,8 +209,8 @@ in
|
|||
})
|
||||
(lib.mkIf (cfg.enable && cfg.sso.enable) {
|
||||
shb.lldap.ensureGroups = {
|
||||
${cfg.ldap.userGroup} = {};
|
||||
${cfg.ldap.adminGroup} = {};
|
||||
${cfg.ldap.userGroup} = { };
|
||||
${cfg.ldap.adminGroup} = { };
|
||||
};
|
||||
|
||||
services.open-webui = {
|
||||
|
|
@ -228,7 +244,7 @@ in
|
|||
};
|
||||
shb.authelia.extraOidcClaimsPolicies.${roleClaim} = {
|
||||
custom_claims = {
|
||||
"${roleClaim}" = {};
|
||||
"${roleClaim}" = { };
|
||||
};
|
||||
};
|
||||
shb.authelia.extraOidcScopes."${roleClaim}" = {
|
||||
|
|
@ -268,5 +284,6 @@ in
|
|||
requiredBy = [ "open-webui.service" ];
|
||||
};
|
||||
})
|
||||
]);
|
||||
]
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,15 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
let
|
||||
cfg = config.shb.pinchflat;
|
||||
|
||||
inherit (lib) types;
|
||||
|
||||
contracts = pkgs.callPackage ../contracts {};
|
||||
contracts = pkgs.callPackage ../contracts { };
|
||||
in
|
||||
{
|
||||
imports = [
|
||||
|
|
@ -57,7 +62,10 @@ in
|
|||
};
|
||||
|
||||
timeZone = lib.mkOption {
|
||||
type = lib.types.oneOf [ lib.types.str lib.shb.secretFileType ];
|
||||
type = lib.types.oneOf [
|
||||
lib.types.str
|
||||
lib.shb.secretFileType
|
||||
];
|
||||
description = "Timezone of this instance.";
|
||||
example = "America/Los_Angeles";
|
||||
};
|
||||
|
|
@ -66,7 +74,7 @@ in
|
|||
description = ''
|
||||
Setup LDAP integration.
|
||||
'';
|
||||
default = {};
|
||||
default = { };
|
||||
type = types.submodule {
|
||||
options = {
|
||||
enable = lib.mkEnableOption "LDAP integration." // {
|
||||
|
|
@ -86,7 +94,7 @@ in
|
|||
description = ''
|
||||
Setup SSO integration.
|
||||
'';
|
||||
default = {};
|
||||
default = { };
|
||||
type = types.submodule {
|
||||
options = {
|
||||
enable = lib.mkEnableOption "SSO integration.";
|
||||
|
|
@ -99,7 +107,10 @@ in
|
|||
};
|
||||
|
||||
authorization_policy = lib.mkOption {
|
||||
type = types.enum [ "one_factor" "two_factor" ];
|
||||
type = types.enum [
|
||||
"one_factor"
|
||||
"two_factor"
|
||||
];
|
||||
description = "Require one factor (password) or two factor (device) authentication.";
|
||||
default = "one_factor";
|
||||
};
|
||||
|
|
@ -111,7 +122,7 @@ in
|
|||
description = ''
|
||||
Backup media directory `shb.mediaDir`.
|
||||
'';
|
||||
default = {};
|
||||
default = { };
|
||||
type = lib.types.submodule {
|
||||
options = contracts.backup.mkRequester {
|
||||
user = "pinchflat";
|
||||
|
|
@ -142,7 +153,9 @@ in
|
|||
|
||||
# This should be using a contract instead of setting the option directly.
|
||||
shb.lldap = lib.mkIf config.shb.lldap.enable {
|
||||
ensureGroups = { ${cfg.ldap.userGroup} = {}; };
|
||||
ensureGroups = {
|
||||
${cfg.ldap.userGroup} = { };
|
||||
};
|
||||
};
|
||||
|
||||
systemd.services.pinchflat-pre = {
|
||||
|
|
@ -181,7 +194,7 @@ in
|
|||
job_name = "pinchflat";
|
||||
static_configs = [
|
||||
{
|
||||
targets = ["127.0.0.1:${toString cfg.port}"];
|
||||
targets = [ "127.0.0.1:${toString cfg.port}" ];
|
||||
labels = {
|
||||
"hostname" = config.networking.hostName;
|
||||
"domain" = cfg.domain;
|
||||
|
|
|
|||
|
|
@ -1,13 +1,22 @@
|
|||
{ config, pkgs, lib, ... }:
|
||||
{
|
||||
config,
|
||||
pkgs,
|
||||
lib,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
cfg = config.shb.vaultwarden;
|
||||
|
||||
contracts = pkgs.callPackage ../contracts {};
|
||||
contracts = pkgs.callPackage ../contracts { };
|
||||
|
||||
fqdn = "${cfg.subdomain}.${cfg.domain}";
|
||||
|
||||
dataFolder = if lib.versionOlder (config.system.stateVersion or "24.11") "24.11" then "/var/lib/bitwarden_rs" else "/var/lib/vaultwarden";
|
||||
dataFolder =
|
||||
if lib.versionOlder (config.system.stateVersion or "24.11") "24.11" then
|
||||
"/var/lib/bitwarden_rs"
|
||||
else
|
||||
"/var/lib/vaultwarden";
|
||||
in
|
||||
{
|
||||
imports = [
|
||||
|
|
@ -55,7 +64,10 @@ in
|
|||
mode = "0440";
|
||||
owner = "vaultwarden";
|
||||
group = "postgres";
|
||||
restartUnits = [ "vaultwarden.service" "postgresql.service" ];
|
||||
restartUnits = [
|
||||
"vaultwarden.service"
|
||||
"postgresql.service"
|
||||
];
|
||||
};
|
||||
};
|
||||
};
|
||||
|
|
@ -63,7 +75,8 @@ in
|
|||
smtp = lib.mkOption {
|
||||
description = "SMTP options.";
|
||||
default = null;
|
||||
type = lib.types.nullOr (lib.types.submodule {
|
||||
type = lib.types.nullOr (
|
||||
lib.types.submodule {
|
||||
options = {
|
||||
from_address = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
|
|
@ -80,7 +93,11 @@ in
|
|||
description = "SMTP host to send the emails to.";
|
||||
};
|
||||
security = lib.mkOption {
|
||||
type = lib.types.enum [ "starttls" "force_tls" "off" ];
|
||||
type = lib.types.enum [
|
||||
"starttls"
|
||||
"force_tls"
|
||||
"off"
|
||||
];
|
||||
description = "Security expected by SMTP host.";
|
||||
default = "starttls";
|
||||
};
|
||||
|
|
@ -109,7 +126,8 @@ in
|
|||
};
|
||||
};
|
||||
};
|
||||
});
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
mount = lib.mkOption {
|
||||
|
|
@ -127,14 +145,16 @@ in
|
|||
```
|
||||
'';
|
||||
readOnly = true;
|
||||
default = { path = dataFolder; };
|
||||
default = {
|
||||
path = dataFolder;
|
||||
};
|
||||
};
|
||||
|
||||
backup = lib.mkOption {
|
||||
description = ''
|
||||
Backup configuration.
|
||||
'';
|
||||
default = {};
|
||||
default = { };
|
||||
type = lib.types.submodule {
|
||||
options = contracts.backup.mkRequester {
|
||||
user = "vaultwarden";
|
||||
|
|
@ -170,7 +190,8 @@ in
|
|||
ROCKET_LOG = if cfg.debug then "trace" else "info";
|
||||
ROCKET_ADDRESS = "127.0.0.1";
|
||||
ROCKET_PORT = cfg.port;
|
||||
} // lib.optionalAttrs (cfg.smtp != null) {
|
||||
}
|
||||
// lib.optionalAttrs (cfg.smtp != null) {
|
||||
SMTP_FROM = cfg.smtp.from_address;
|
||||
SMTP_FROM_NAME = cfg.smtp.from_name;
|
||||
SMTP_HOST = cfg.smtp.host;
|
||||
|
|
@ -189,12 +210,12 @@ in
|
|||
];
|
||||
# Needed to be able to write template config.
|
||||
systemd.services.vaultwarden.serviceConfig.ProtectHome = lib.mkForce false;
|
||||
systemd.services.vaultwarden.preStart =
|
||||
lib.shb.replaceSecrets {
|
||||
systemd.services.vaultwarden.preStart = lib.shb.replaceSecrets {
|
||||
userConfig = {
|
||||
DATABASE_URL.source = cfg.databasePassword.result.path;
|
||||
DATABASE_URL.transform = v: "postgresql://vaultwarden:${v}@127.0.0.1:5432/vaultwarden";
|
||||
} // lib.optionalAttrs (cfg.smtp != null) {
|
||||
}
|
||||
// lib.optionalAttrs (cfg.smtp != null) {
|
||||
SMTP_PASSWORD.source = cfg.smtp.password.result.path;
|
||||
};
|
||||
resultPath = "${dataFolder}/vaultwarden.env";
|
||||
|
|
@ -203,13 +224,18 @@ in
|
|||
|
||||
shb.nginx.vhosts = [
|
||||
{
|
||||
inherit (cfg) subdomain domain authEndpoint ssl;
|
||||
inherit (cfg)
|
||||
subdomain
|
||||
domain
|
||||
authEndpoint
|
||||
ssl
|
||||
;
|
||||
upstream = "http://127.0.0.1:${toString config.services.vaultwarden.config.ROCKET_PORT}";
|
||||
autheliaRules = lib.mkIf (cfg.authEndpoint != null) [
|
||||
{
|
||||
domain = "${fqdn}";
|
||||
policy = "two_factor";
|
||||
subject = ["group:vaultwarden_admin"];
|
||||
subject = [ "group:vaultwarden_admin" ];
|
||||
resources = [
|
||||
"^/admin"
|
||||
];
|
||||
|
|
|
|||
|
|
@ -8,7 +8,9 @@ in
|
|||
basic = lib.shb.runNixOSTest {
|
||||
name = "authelia-basic";
|
||||
|
||||
nodes.machine = { config, pkgs, ... }: {
|
||||
nodes.machine =
|
||||
{ config, pkgs, ... }:
|
||||
{
|
||||
imports = [
|
||||
(pkgs'.path + "/nixos/modules/profiles/headless.nix")
|
||||
(pkgs'.path + "/nixos/modules/profiles/qemu-guest.nix")
|
||||
|
|
@ -56,8 +58,10 @@ in
|
|||
ldapAdminPassword.result = config.shb.hardcodedsecret.ldapAdminPassword.result;
|
||||
sessionSecret.result = config.shb.hardcodedsecret.sessionSecret.result;
|
||||
storageEncryptionKey.result = config.shb.hardcodedsecret.storageEncryptionKey.result;
|
||||
identityProvidersOIDCHMACSecret.result = config.shb.hardcodedsecret.identityProvidersOIDCHMACSecret.result;
|
||||
identityProvidersOIDCIssuerPrivateKey.result = config.shb.hardcodedsecret.identityProvidersOIDCIssuerPrivateKey.result;
|
||||
identityProvidersOIDCHMACSecret.result =
|
||||
config.shb.hardcodedsecret.identityProvidersOIDCHMACSecret.result;
|
||||
identityProvidersOIDCIssuerPrivateKey.result =
|
||||
config.shb.hardcodedsecret.identityProvidersOIDCIssuerPrivateKey.result;
|
||||
};
|
||||
|
||||
oidcClients = [
|
||||
|
|
@ -102,10 +106,12 @@ in
|
|||
};
|
||||
shb.hardcodedsecret.identityProvidersOIDCIssuerPrivateKey = {
|
||||
request = config.shb.authelia.secrets.identityProvidersOIDCIssuerPrivateKey.request;
|
||||
settings.source = (pkgs.runCommand "gen-private-key" {} ''
|
||||
settings.source =
|
||||
(pkgs.runCommand "gen-private-key" { } ''
|
||||
mkdir $out
|
||||
${pkgs.openssl}/bin/openssl genrsa -out $out/private.pem 4096
|
||||
'') + "/private.pem";
|
||||
'')
|
||||
+ "/private.pem";
|
||||
};
|
||||
|
||||
specialisation = {
|
||||
|
|
@ -115,7 +121,8 @@ in
|
|||
};
|
||||
};
|
||||
|
||||
testScript = { nodes, ... }:
|
||||
testScript =
|
||||
{ nodes, ... }:
|
||||
let
|
||||
specializations = "${nodes.machine.system.build.toplevel}/specialisation";
|
||||
in
|
||||
|
|
|
|||
|
|
@ -24,7 +24,9 @@ in
|
|||
|
||||
configWithTemplates = lib.shb.withReplacements userConfig;
|
||||
|
||||
nonSecretConfigFile = pkgs.writeText "config.yaml.template" (lib.generators.toJSON {} configWithTemplates);
|
||||
nonSecretConfigFile = pkgs.writeText "config.yaml.template" (
|
||||
lib.generators.toJSON { } configWithTemplates
|
||||
);
|
||||
|
||||
replacements = lib.shb.getReplacements userConfig;
|
||||
|
||||
|
|
@ -37,24 +39,25 @@ in
|
|||
replaceInTemplateJSON = lib.shb.replaceSecrets {
|
||||
inherit userConfig;
|
||||
resultPath = "/var/lib/config.json";
|
||||
generator = lib.shb.replaceSecretsFormatAdapter (pkgs.formats.json {});
|
||||
generator = lib.shb.replaceSecretsFormatAdapter (pkgs.formats.json { });
|
||||
};
|
||||
|
||||
replaceInTemplateJSONGen = lib.shb.replaceSecrets {
|
||||
inherit userConfig;
|
||||
resultPath = "/var/lib/config_gen.json";
|
||||
generator = lib.shb.replaceSecretsGeneratorAdapter (lib.generators.toJSON {});
|
||||
generator = lib.shb.replaceSecretsGeneratorAdapter (lib.generators.toJSON { });
|
||||
};
|
||||
|
||||
replaceInTemplateXML = lib.shb.replaceSecrets {
|
||||
inherit userConfig;
|
||||
resultPath = "/var/lib/config.xml";
|
||||
generator = lib.shb.replaceSecretsFormatAdapter (lib.shb.formatXML {enclosingRoot = "Root";});
|
||||
generator = lib.shb.replaceSecretsFormatAdapter (lib.shb.formatXML { enclosingRoot = "Root"; });
|
||||
};
|
||||
in
|
||||
lib.shb.runNixOSTest {
|
||||
name = "lib-template";
|
||||
nodes.machine = { config, pkgs, ... }:
|
||||
nodes.machine =
|
||||
{ config, pkgs, ... }:
|
||||
{
|
||||
imports = [
|
||||
(pkgs'.path + "/nixos/modules/profiles/headless.nix")
|
||||
|
|
@ -62,7 +65,12 @@ in
|
|||
{
|
||||
options = {
|
||||
libtest.config = lib.mkOption {
|
||||
type = lib.types.attrsOf (lib.types.oneOf [ lib.types.str lib.secretFileType ]);
|
||||
type = lib.types.attrsOf (
|
||||
lib.types.oneOf [
|
||||
lib.types.str
|
||||
lib.secretFileType
|
||||
]
|
||||
);
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
@ -76,7 +84,9 @@ in
|
|||
};
|
||||
};
|
||||
|
||||
testScript = { nodes, ... }: ''
|
||||
testScript =
|
||||
{ nodes, ... }:
|
||||
''
|
||||
import json
|
||||
from collections import ChainMap
|
||||
from xml.etree import ElementTree
|
||||
|
|
@ -95,7 +105,7 @@ in
|
|||
merged_dict = ChainMap(*map(xml_to_dict_recursive, all_descendants))
|
||||
return {root.tag: dict(merged_dict)}
|
||||
|
||||
wantedConfig = json.loads('${lib.generators.toJSON {} wantedConfig}')
|
||||
wantedConfig = json.loads('${lib.generators.toJSON { } wantedConfig}')
|
||||
|
||||
with subtest("config"):
|
||||
print(machine.succeed("cat ${pkgs.writeText "replaceInTemplate" replaceInTemplate}"))
|
||||
|
|
|
|||
|
|
@ -9,7 +9,9 @@ in
|
|||
auth = lib.shb.runNixOSTest {
|
||||
name = "ldap-auth";
|
||||
|
||||
nodes.server = { config, pkgs, ... }: {
|
||||
nodes.server =
|
||||
{ config, pkgs, ... }:
|
||||
{
|
||||
imports = [
|
||||
(pkgs'.path + "/nixos/modules/profiles/headless.nix")
|
||||
(pkgs'.path + "/nixos/modules/profiles/qemu-guest.nix")
|
||||
|
|
@ -38,7 +40,7 @@ in
|
|||
};
|
||||
|
||||
ensureGroups = {
|
||||
"family" = {};
|
||||
"family" = { };
|
||||
};
|
||||
};
|
||||
shb.hardcodedsecret.ldapUserPassword = {
|
||||
|
|
@ -65,10 +67,11 @@ in
|
|||
};
|
||||
};
|
||||
|
||||
nodes.client = {};
|
||||
nodes.client = { };
|
||||
|
||||
# Inspired from https://github.com/lldap/lldap/blob/33f50d13a2e2d24a3e6bb05a148246bc98090df0/example_configs/lldap-ha-auth.sh
|
||||
testScript = { nodes, ... }:
|
||||
testScript =
|
||||
{ nodes, ... }:
|
||||
let
|
||||
specializations = "${nodes.server.system.build.toplevel}/specialisation";
|
||||
in
|
||||
|
|
|
|||
|
|
@ -1,12 +1,17 @@
|
|||
{ pkgs, lib, ... }:
|
||||
let
|
||||
serve = port: text: lib.getExe (pkgs.writers.writePython3Bin "serve"
|
||||
serve =
|
||||
port: text:
|
||||
lib.getExe (
|
||||
pkgs.writers.writePython3Bin "serve"
|
||||
{
|
||||
libraries = [ pkgs.python3Packages.systemd ];
|
||||
}
|
||||
(let
|
||||
(
|
||||
let
|
||||
content = pkgs.writeText "content" text;
|
||||
in ''
|
||||
in
|
||||
''
|
||||
from http.server import BaseHTTPRequestHandler, HTTPServer
|
||||
from systemd.daemon import notify
|
||||
|
||||
|
|
@ -35,14 +40,17 @@ let
|
|||
print("Serving hardcoded page on http://127.0.0.1:${toString port}")
|
||||
notify('READY=1')
|
||||
httpd.serve_forever()
|
||||
'')
|
||||
''
|
||||
)
|
||||
);
|
||||
in
|
||||
{
|
||||
default = lib.shb.runNixOSTest {
|
||||
name = "mitmdump-default";
|
||||
|
||||
nodes.machine = { config, pkgs, ... }: {
|
||||
nodes.machine =
|
||||
{ config, pkgs, ... }:
|
||||
{
|
||||
imports = [
|
||||
../../modules/blocks/mitmdump.nix
|
||||
];
|
||||
|
|
@ -79,12 +87,15 @@ in
|
|||
after = [ "test2.service" ];
|
||||
enabledAddons = [ config.shb.mitmdump.addons.logger ];
|
||||
extraArgs = [
|
||||
"--set" "verbose_pattern=/verbose"
|
||||
"--set"
|
||||
"verbose_pattern=/verbose"
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
testScript = { nodes, ... }: ''
|
||||
testScript =
|
||||
{ nodes, ... }:
|
||||
''
|
||||
start_all()
|
||||
|
||||
machine.wait_for_unit("test1.service")
|
||||
|
|
|
|||
|
|
@ -6,7 +6,9 @@ in
|
|||
peerWithoutUser = lib.shb.runNixOSTest {
|
||||
name = "postgresql-peerWithoutUser";
|
||||
|
||||
nodes.machine = { config, pkgs, ... }: {
|
||||
nodes.machine =
|
||||
{ config, pkgs, ... }:
|
||||
{
|
||||
imports = [
|
||||
(pkgs'.path + "/nixos/modules/profiles/headless.nix")
|
||||
(pkgs'.path + "/nixos/modules/profiles/qemu-guest.nix")
|
||||
|
|
@ -21,7 +23,9 @@ in
|
|||
];
|
||||
};
|
||||
|
||||
testScript = { nodes, ... }: ''
|
||||
testScript =
|
||||
{ nodes, ... }:
|
||||
''
|
||||
start_all()
|
||||
machine.wait_for_unit("postgresql.service")
|
||||
machine.wait_for_open_port(5432)
|
||||
|
|
@ -43,7 +47,9 @@ in
|
|||
peerAuth = lib.shb.runNixOSTest {
|
||||
name = "postgresql-peerAuth";
|
||||
|
||||
nodes.machine = { config, pkgs, ... }: {
|
||||
nodes.machine =
|
||||
{ config, pkgs, ... }:
|
||||
{
|
||||
imports = [
|
||||
(pkgs'.path + "/nixos/modules/profiles/headless.nix")
|
||||
(pkgs'.path + "/nixos/modules/profiles/qemu-guest.nix")
|
||||
|
|
@ -55,7 +61,7 @@ in
|
|||
group = "me";
|
||||
extraGroups = [ "sudoers" ];
|
||||
};
|
||||
users.groups.me = {};
|
||||
users.groups.me = { };
|
||||
|
||||
shb.postgresql.ensures = [
|
||||
{
|
||||
|
|
@ -65,7 +71,9 @@ in
|
|||
];
|
||||
};
|
||||
|
||||
testScript = { nodes, ... }: ''
|
||||
testScript =
|
||||
{ nodes, ... }:
|
||||
''
|
||||
start_all()
|
||||
machine.wait_for_unit("postgresql.service")
|
||||
machine.wait_for_open_port(5432)
|
||||
|
|
@ -93,7 +101,9 @@ in
|
|||
tcpIPWithoutPasswordAuth = lib.shb.runNixOSTest {
|
||||
name = "postgresql-tcpIpWithoutPasswordAuth";
|
||||
|
||||
nodes.machine = { config, pkgs, ... }: {
|
||||
nodes.machine =
|
||||
{ config, pkgs, ... }:
|
||||
{
|
||||
imports = [
|
||||
(pkgs'.path + "/nixos/modules/profiles/headless.nix")
|
||||
(pkgs'.path + "/nixos/modules/profiles/qemu-guest.nix")
|
||||
|
|
@ -109,7 +119,9 @@ in
|
|||
];
|
||||
};
|
||||
|
||||
testScript = { nodes, ... }: ''
|
||||
testScript =
|
||||
{ nodes, ... }:
|
||||
''
|
||||
start_all()
|
||||
machine.wait_for_unit("postgresql.service")
|
||||
machine.wait_for_open_port(5432)
|
||||
|
|
@ -131,7 +143,9 @@ in
|
|||
tcpIPPasswordAuth = lib.shb.runNixOSTest {
|
||||
name = "postgresql-tcpIPPasswordAuth";
|
||||
|
||||
nodes.machine = { config, pkgs, ... }: {
|
||||
nodes.machine =
|
||||
{ config, pkgs, ... }:
|
||||
{
|
||||
imports = [
|
||||
(pkgs'.path + "/nixos/modules/profiles/headless.nix")
|
||||
(pkgs'.path + "/nixos/modules/profiles/qemu-guest.nix")
|
||||
|
|
@ -143,7 +157,7 @@ in
|
|||
group = "me";
|
||||
extraGroups = [ "sudoers" ];
|
||||
};
|
||||
users.groups.me = {};
|
||||
users.groups.me = { };
|
||||
|
||||
system.activationScripts.secret = ''
|
||||
echo secretpw > /run/dbsecret
|
||||
|
|
@ -158,7 +172,9 @@ in
|
|||
];
|
||||
};
|
||||
|
||||
testScript = { nodes, ... }: ''
|
||||
testScript =
|
||||
{ nodes, ... }:
|
||||
''
|
||||
start_all()
|
||||
machine.wait_for_unit("postgresql.service")
|
||||
machine.wait_for_open_port(5432)
|
||||
|
|
|
|||
|
|
@ -1,11 +1,15 @@
|
|||
{ pkgs, lib, ... }:
|
||||
let
|
||||
testLib = pkgs.callPackage ../common.nix {};
|
||||
testLib = pkgs.callPackage ../common.nix { };
|
||||
|
||||
commonTest = user: lib.shb.runNixOSTest {
|
||||
commonTest =
|
||||
user:
|
||||
lib.shb.runNixOSTest {
|
||||
name = "restic_backupAndRestore_${user}";
|
||||
|
||||
nodes.machine = { config, ... }: {
|
||||
nodes.machine =
|
||||
{ config, ... }:
|
||||
{
|
||||
imports = [
|
||||
testLib.baseImports
|
||||
|
||||
|
|
@ -64,7 +68,8 @@ let
|
|||
"/opt/files/B"
|
||||
];
|
||||
|
||||
hooks.beforeBackup = [''
|
||||
hooks.beforeBackup = [
|
||||
''
|
||||
echo $RUNTIME_DIRECTORY
|
||||
if [ "$RUNTIME_DIRECTORY" = /run/restic-backups-testinstance_opt_repos_A ]; then
|
||||
if ! [ -f /run/secrets_restic/restic-backups-testinstance_opt_repos_A ]; then
|
||||
|
|
@ -79,7 +84,8 @@ let
|
|||
exit 12
|
||||
fi
|
||||
fi
|
||||
''];
|
||||
''
|
||||
];
|
||||
};
|
||||
};
|
||||
};
|
||||
|
|
@ -87,11 +93,14 @@ let
|
|||
extraPythonPackages = p: [ p.dictdiffer ];
|
||||
skipTypeCheck = true;
|
||||
|
||||
testScript = { nodes, ... }: let
|
||||
testScript =
|
||||
{ nodes, ... }:
|
||||
let
|
||||
provider = nodes.machine.shb.restic.instances."testinstance";
|
||||
backupService = provider.result.backupService;
|
||||
restoreScript = provider.result.restoreScript;
|
||||
in ''
|
||||
in
|
||||
''
|
||||
from dictdiffer import diff
|
||||
|
||||
def list_files(dir):
|
||||
|
|
|
|||
|
|
@ -6,7 +6,9 @@ in
|
|||
test = lib.shb.runNixOSTest {
|
||||
name = "ssl-test";
|
||||
|
||||
nodes.server = { config, pkgs, ... }: {
|
||||
nodes.server =
|
||||
{ config, pkgs, ... }:
|
||||
{
|
||||
imports = [
|
||||
(pkgs'.path + "/nixos/modules/profiles/headless.nix")
|
||||
(pkgs'.path + "/nixos/modules/profiles/qemu-guest.nix")
|
||||
|
|
@ -24,8 +26,8 @@ in
|
|||
};
|
||||
};
|
||||
users.groups = {
|
||||
group1 = {};
|
||||
group2 = {};
|
||||
group1 = { };
|
||||
group2 = { };
|
||||
};
|
||||
|
||||
shb.certs = {
|
||||
|
|
@ -54,7 +56,10 @@ in
|
|||
ca = config.shb.certs.cas.selfsigned.myca;
|
||||
|
||||
domain = "multi1.example.com";
|
||||
extraDomains = [ "multi2.example.com" "multi3.example.com" ];
|
||||
extraDomains = [
|
||||
"multi2.example.com"
|
||||
"multi3.example.com"
|
||||
];
|
||||
group = "nginx";
|
||||
};
|
||||
|
||||
|
|
@ -121,7 +126,8 @@ in
|
|||
};
|
||||
|
||||
# Taken from https://github.com/NixOS/nixpkgs/blob/7f311dd9226bbd568a43632c977f4992cfb2b5c8/nixos/tests/custom-ca.nix
|
||||
testScript = { nodes, ... }:
|
||||
testScript =
|
||||
{ nodes, ... }:
|
||||
let
|
||||
myca = nodes.server.shb.certs.cas.selfsigned.myca;
|
||||
myotherca = nodes.server.shb.certs.cas.selfsigned.myotherca;
|
||||
|
|
|
|||
173
test/common.nix
173
test/common.nix
|
|
@ -1,7 +1,14 @@
|
|||
{ pkgs, lib }:
|
||||
let
|
||||
inherit (lib) hasAttr mkOption optionalString;
|
||||
inherit (lib.types) bool enum listOf nullOr submodule str;
|
||||
inherit (lib.types)
|
||||
bool
|
||||
enum
|
||||
listOf
|
||||
nullOr
|
||||
submodule
|
||||
str
|
||||
;
|
||||
|
||||
baseImports = {
|
||||
imports = [
|
||||
|
|
@ -10,15 +17,17 @@ let
|
|||
];
|
||||
};
|
||||
|
||||
accessScript = lib.makeOverridable ({
|
||||
hasSSL
|
||||
, waitForServices ? s: []
|
||||
, waitForPorts ? p: []
|
||||
, waitForUnixSocket ? u: []
|
||||
, waitForUrls ? u: []
|
||||
, extraScript ? {...}: ""
|
||||
, redirectSSO ? false
|
||||
}: { nodes, ... }:
|
||||
accessScript = lib.makeOverridable (
|
||||
{
|
||||
hasSSL,
|
||||
waitForServices ? s: [ ],
|
||||
waitForPorts ? p: [ ],
|
||||
waitForUnixSocket ? u: [ ],
|
||||
waitForUrls ? u: [ ],
|
||||
extraScript ? { ... }: "",
|
||||
redirectSSO ? false,
|
||||
}:
|
||||
{ nodes, ... }:
|
||||
let
|
||||
cfg = nodes.server.test;
|
||||
|
||||
|
|
@ -72,9 +81,13 @@ let
|
|||
+ lib.strings.concatMapStrings (p: ''server.wait_for_open_port(${toString p})'' + "\n") (
|
||||
waitForPorts args
|
||||
# TODO: when the SSO block exists, replace this hardcoded port.
|
||||
++ (lib.optionals autheliaEnabled [ 9091 /* nodes.server.services.authelia.instances."auth.${domain}".settings.server.port */ ])
|
||||
++ (lib.optionals autheliaEnabled [
|
||||
9091 # nodes.server.services.authelia.instances."auth.${domain}".settings.server.port
|
||||
])
|
||||
)
|
||||
+ lib.strings.concatMapStrings (u: ''server.wait_for_open_unix_socket("${u}")'' + "\n") (
|
||||
waitForUnixSocket args
|
||||
)
|
||||
+ lib.strings.concatMapStrings (u: ''server.wait_for_open_unix_socket("${u}")'' + "\n") (waitForUnixSocket args)
|
||||
+ ''
|
||||
if ${if hasSSL args then "True" else "False"}:
|
||||
server.copy_from_vm("/etc/ssl/certs/ca-certificates.crt")
|
||||
|
|
@ -84,7 +97,9 @@ let
|
|||
''
|
||||
# Making a curl request to an URL needs to happen after we copied the certificates over,
|
||||
# otherwise curl will not be able to verify the "legitimacy of the server".
|
||||
+ lib.strings.concatMapStrings (u: ''
|
||||
+ lib.strings.concatMapStrings (
|
||||
u:
|
||||
''
|
||||
import time
|
||||
|
||||
done = False
|
||||
|
|
@ -97,16 +112,20 @@ let
|
|||
done = response.get('code') == 200
|
||||
if not done:
|
||||
raise Exception(f"Response was never 200, got last: {response}")
|
||||
'' + "\n") (
|
||||
waitForUrls args
|
||||
)
|
||||
+ (if (! redirectSSO) then ''
|
||||
''
|
||||
+ "\n"
|
||||
) (waitForUrls args)
|
||||
+ (
|
||||
if (!redirectSSO) then
|
||||
''
|
||||
with subtest("access"):
|
||||
response = curl(client, """{"code":%{response_code}}""", "${proto_fqdn}")
|
||||
|
||||
if response['code'] != 200:
|
||||
raise Exception(f"Code is {response['code']}")
|
||||
'' else ''
|
||||
''
|
||||
else
|
||||
''
|
||||
with subtest("unauthenticated access is not granted"):
|
||||
response = curl(client, """{"code":%{response_code},"auth_host":"%{urle.host}","auth_query":"%{urle.query}","all":%{json}}""", "${proto_fqdn}")
|
||||
|
||||
|
|
@ -116,11 +135,14 @@ let
|
|||
raise Exception(f"auth host should be auth.${cfg.domain} but is {response['auth_host']}")
|
||||
if response['auth_query'] != "rd=${proto_fqdn}/":
|
||||
raise Exception(f"auth query should be rd=${proto_fqdn}/ but is {response['auth_query']}")
|
||||
'')
|
||||
+ (let
|
||||
''
|
||||
)
|
||||
+ (
|
||||
let
|
||||
script = extraScript args;
|
||||
in
|
||||
lib.optionalString (script != "") script)
|
||||
lib.optionalString (script != "") script
|
||||
)
|
||||
+ (optionalString (hasAttr "test" nodes.server && hasAttr "login" nodes.server.test) ''
|
||||
with subtest("Login from server"):
|
||||
code, logs = server.execute("login_playwright")
|
||||
|
|
@ -145,8 +167,12 @@ let
|
|||
'')
|
||||
);
|
||||
|
||||
backupScript = args: (accessScript args).override {
|
||||
extraScript = { proto_fqdn, ... }: ''
|
||||
backupScript =
|
||||
args:
|
||||
(accessScript args).override {
|
||||
extraScript =
|
||||
{ proto_fqdn, ... }:
|
||||
''
|
||||
with subtest("backup"):
|
||||
server.succeed("systemctl start restic-backups-testinstance_opt_repos_A")
|
||||
'';
|
||||
|
|
@ -155,17 +181,23 @@ in
|
|||
{
|
||||
inherit baseImports accessScript;
|
||||
|
||||
runNixOSTest = args: pkgs.testers.runNixOSTest ({
|
||||
interactive.sshBackdoor.enable = true;
|
||||
} // args);
|
||||
|
||||
mkScripts = args:
|
||||
runNixOSTest =
|
||||
args:
|
||||
pkgs.testers.runNixOSTest (
|
||||
{
|
||||
interactive.sshBackdoor.enable = true;
|
||||
}
|
||||
// args
|
||||
);
|
||||
|
||||
mkScripts = args: {
|
||||
access = accessScript args;
|
||||
backup = backupScript args;
|
||||
};
|
||||
|
||||
baseModule = { config, ... }: {
|
||||
baseModule =
|
||||
{ config, ... }:
|
||||
{
|
||||
options.test = {
|
||||
domain = mkOption {
|
||||
type = str;
|
||||
|
|
@ -204,21 +236,34 @@ in
|
|||
];
|
||||
config = {
|
||||
# HTTP(s) server port.
|
||||
networking.firewall.allowedTCPPorts = [ 80 443 ];
|
||||
networking.firewall.allowedTCPPorts = [
|
||||
80
|
||||
443
|
||||
];
|
||||
shb.nginx.accessLog = true;
|
||||
|
||||
networking.hosts = {
|
||||
"192.168.1.2" = [ config.test.fqdn "auth.${config.test.domain}" ];
|
||||
"192.168.1.2" = [
|
||||
config.test.fqdn
|
||||
"auth.${config.test.domain}"
|
||||
];
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
clientLoginModule = { config, pkgs, ... }: let
|
||||
clientLoginModule =
|
||||
{ config, pkgs, ... }:
|
||||
let
|
||||
cfg = config.test.login;
|
||||
in {
|
||||
in
|
||||
{
|
||||
options.test.login = {
|
||||
browser = mkOption {
|
||||
type = enum [ "firefox" "chromium" "webkit" ];
|
||||
type = enum [
|
||||
"firefox"
|
||||
"chromium"
|
||||
"webkit"
|
||||
];
|
||||
default = "firefox";
|
||||
};
|
||||
usernameFieldLabelRegex = mkOption {
|
||||
|
|
@ -269,7 +314,10 @@ in
|
|||
};
|
||||
config = {
|
||||
networking.hosts = {
|
||||
"192.168.1.2" = [ config.test.fqdn "auth.${config.test.domain}" ];
|
||||
"192.168.1.2" = [
|
||||
config.test.fqdn
|
||||
"auth.${config.test.domain}"
|
||||
];
|
||||
};
|
||||
|
||||
environment.variables = {
|
||||
|
|
@ -280,11 +328,16 @@ in
|
|||
(pkgs.writers.writePython3Bin "login_playwright"
|
||||
{
|
||||
libraries = [ pkgs.python3Packages.playwright ];
|
||||
flakeIgnore = [ "F401" "E501" ];
|
||||
flakeIgnore = [
|
||||
"F401"
|
||||
"E501"
|
||||
];
|
||||
}
|
||||
(let
|
||||
(
|
||||
let
|
||||
testCfg = pkgs.writeText "users.json" (builtins.toJSON cfg);
|
||||
in ''
|
||||
in
|
||||
''
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
|
|
@ -343,13 +396,17 @@ in
|
|||
context.tracing.stop(path=f"trace/{i}.zip")
|
||||
|
||||
browser.close()
|
||||
'')
|
||||
''
|
||||
)
|
||||
)
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
backup = backupOption: { config, ... }: {
|
||||
backup =
|
||||
backupOption:
|
||||
{ config, ... }:
|
||||
{
|
||||
imports = [
|
||||
../modules/blocks/restic.nix
|
||||
];
|
||||
|
|
@ -373,7 +430,9 @@ in
|
|||
};
|
||||
};
|
||||
|
||||
certs = { config, ... }: {
|
||||
certs =
|
||||
{ config, ... }:
|
||||
{
|
||||
imports = [
|
||||
../modules/blocks/ssl.nix
|
||||
];
|
||||
|
|
@ -395,7 +454,9 @@ in
|
|||
systemd.services.nginx.requires = [ config.shb.certs.certs.selfsigned.n.systemdService ];
|
||||
};
|
||||
|
||||
ldap = { config, pkgs, ... }: {
|
||||
ldap =
|
||||
{ config, pkgs, ... }:
|
||||
{
|
||||
imports = [
|
||||
../modules/blocks/lldap.nix
|
||||
];
|
||||
|
|
@ -432,7 +493,10 @@ in
|
|||
};
|
||||
bob = {
|
||||
email = "bob@example.com";
|
||||
groups = [ "user_group" "admin_group" ];
|
||||
groups = [
|
||||
"user_group"
|
||||
"admin_group"
|
||||
];
|
||||
password.result.path = pkgs.writeText "bobPassword" "BobPassword";
|
||||
};
|
||||
charlie = {
|
||||
|
|
@ -443,14 +507,17 @@ in
|
|||
};
|
||||
|
||||
ensureGroups = {
|
||||
user_group = {};
|
||||
admin_group = {};
|
||||
other_group = {};
|
||||
user_group = { };
|
||||
admin_group = { };
|
||||
other_group = { };
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
sso = ssl: { config, pkgs, ... }: {
|
||||
sso =
|
||||
ssl:
|
||||
{ config, pkgs, ... }:
|
||||
{
|
||||
imports = [
|
||||
../modules/blocks/authelia.nix
|
||||
];
|
||||
|
|
@ -475,8 +542,10 @@ in
|
|||
ldapAdminPassword.result = config.shb.hardcodedsecret.ldapAdminPassword.result;
|
||||
sessionSecret.result = config.shb.hardcodedsecret.sessionSecret.result;
|
||||
storageEncryptionKey.result = config.shb.hardcodedsecret.storageEncryptionKey.result;
|
||||
identityProvidersOIDCHMACSecret.result = config.shb.hardcodedsecret.identityProvidersOIDCHMACSecret.result;
|
||||
identityProvidersOIDCIssuerPrivateKey.result = config.shb.hardcodedsecret.identityProvidersOIDCIssuerPrivateKey.result;
|
||||
identityProvidersOIDCHMACSecret.result =
|
||||
config.shb.hardcodedsecret.identityProvidersOIDCHMACSecret.result;
|
||||
identityProvidersOIDCIssuerPrivateKey.result =
|
||||
config.shb.hardcodedsecret.identityProvidersOIDCIssuerPrivateKey.result;
|
||||
};
|
||||
};
|
||||
|
||||
|
|
@ -502,10 +571,12 @@ in
|
|||
};
|
||||
shb.hardcodedsecret.identityProvidersOIDCIssuerPrivateKey = {
|
||||
request = config.shb.authelia.secrets.identityProvidersOIDCIssuerPrivateKey.request;
|
||||
settings.source = (pkgs.runCommand "gen-private-key" {} ''
|
||||
settings.source =
|
||||
(pkgs.runCommand "gen-private-key" { } ''
|
||||
mkdir $out
|
||||
${pkgs.openssl}/bin/openssl genrsa -out $out/private.pem 4096
|
||||
'') + "/private.pem";
|
||||
'')
|
||||
+ "/private.pem";
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,17 +1,24 @@
|
|||
{ pkgs, ... }:
|
||||
let
|
||||
contracts = pkgs.callPackage ../../modules/contracts {};
|
||||
contracts = pkgs.callPackage ../../modules/contracts { };
|
||||
in
|
||||
{
|
||||
restic_root = contracts.test.backup {
|
||||
name = "restic_root";
|
||||
username = "root";
|
||||
providerRoot = [ "shb" "restic" "instances" "mytest" ];
|
||||
providerRoot = [
|
||||
"shb"
|
||||
"restic"
|
||||
"instances"
|
||||
"mytest"
|
||||
];
|
||||
modules = [
|
||||
../../modules/blocks/restic.nix
|
||||
../../modules/blocks/hardcodedsecret.nix
|
||||
];
|
||||
settings = { repository, config, ... }: {
|
||||
settings =
|
||||
{ repository, config, ... }:
|
||||
{
|
||||
enable = true;
|
||||
passphrase.result = config.shb.hardcodedsecret.passphrase.result;
|
||||
repository = {
|
||||
|
|
@ -21,7 +28,9 @@ in
|
|||
};
|
||||
};
|
||||
};
|
||||
extraConfig = { username, config, ... }: {
|
||||
extraConfig =
|
||||
{ username, config, ... }:
|
||||
{
|
||||
shb.hardcodedsecret.passphrase = {
|
||||
request = config.shb.restic.instances."mytest".settings.passphrase.request;
|
||||
settings.content = "passphrase";
|
||||
|
|
@ -32,12 +41,19 @@ in
|
|||
restic_nonroot = contracts.test.backup {
|
||||
name = "restic_nonroot";
|
||||
username = "me";
|
||||
providerRoot = [ "shb" "restic" "instances" "mytest" ];
|
||||
providerRoot = [
|
||||
"shb"
|
||||
"restic"
|
||||
"instances"
|
||||
"mytest"
|
||||
];
|
||||
modules = [
|
||||
../../modules/blocks/restic.nix
|
||||
../../modules/blocks/hardcodedsecret.nix
|
||||
];
|
||||
settings = { repository, config, ... }: {
|
||||
settings =
|
||||
{ repository, config, ... }:
|
||||
{
|
||||
enable = true;
|
||||
passphrase.result = config.shb.hardcodedsecret.passphrase.result;
|
||||
repository = {
|
||||
|
|
@ -47,7 +63,9 @@ in
|
|||
};
|
||||
};
|
||||
};
|
||||
extraConfig = { username, config, ... }: {
|
||||
extraConfig =
|
||||
{ username, config, ... }:
|
||||
{
|
||||
shb.hardcodedsecret.passphrase = {
|
||||
request = config.shb.restic.instances."mytest".settings.passphrase.request;
|
||||
settings.content = "passphrase";
|
||||
|
|
|
|||
|
|
@ -1,18 +1,29 @@
|
|||
{ pkgs, ... }:
|
||||
let
|
||||
contracts = pkgs.callPackage ../../modules/contracts {};
|
||||
contracts = pkgs.callPackage ../../modules/contracts { };
|
||||
in
|
||||
{
|
||||
restic_postgres = contracts.test.databasebackup {
|
||||
name = "restic_postgres";
|
||||
requesterRoot = [ "shb" "postgresql" "databasebackup" ];
|
||||
providerRoot = [ "shb" "restic" "databases" "postgresql" ];
|
||||
requesterRoot = [
|
||||
"shb"
|
||||
"postgresql"
|
||||
"databasebackup"
|
||||
];
|
||||
providerRoot = [
|
||||
"shb"
|
||||
"restic"
|
||||
"databases"
|
||||
"postgresql"
|
||||
];
|
||||
modules = [
|
||||
../../modules/blocks/postgresql.nix
|
||||
../../modules/blocks/restic.nix
|
||||
../../modules/blocks/hardcodedsecret.nix
|
||||
];
|
||||
settings = { repository, config, ... }: {
|
||||
settings =
|
||||
{ repository, config, ... }:
|
||||
{
|
||||
enable = true;
|
||||
passphrase.result = config.shb.hardcodedsecret.passphrase.result;
|
||||
repository = {
|
||||
|
|
@ -22,7 +33,9 @@ in
|
|||
};
|
||||
};
|
||||
};
|
||||
extraConfig = { config, database, ... }: {
|
||||
extraConfig =
|
||||
{ config, database, ... }:
|
||||
{
|
||||
shb.postgresql.ensures = [
|
||||
{
|
||||
inherit database;
|
||||
|
|
|
|||
|
|
@ -1,12 +1,15 @@
|
|||
{ pkgs, ... }:
|
||||
let
|
||||
contracts = pkgs.callPackage ../../modules/contracts {};
|
||||
contracts = pkgs.callPackage ../../modules/contracts { };
|
||||
in
|
||||
{
|
||||
hardcoded_root_root = contracts.test.secret {
|
||||
name = "hardcoded";
|
||||
modules = [ ../../modules/blocks/hardcodedsecret.nix ];
|
||||
configRoot = [ "shb" "hardcodedsecret" ];
|
||||
configRoot = [
|
||||
"shb"
|
||||
"hardcodedsecret"
|
||||
];
|
||||
settingsCfg = secret: {
|
||||
content = secret;
|
||||
};
|
||||
|
|
@ -15,7 +18,10 @@ in
|
|||
hardcoded_user_group = contracts.test.secret {
|
||||
name = "hardcoded";
|
||||
modules = [ ../../modules/blocks/hardcodedsecret.nix ];
|
||||
configRoot = [ "shb" "hardcodedsecret" ];
|
||||
configRoot = [
|
||||
"shb"
|
||||
"hardcodedsecret"
|
||||
];
|
||||
settingsCfg = secret: {
|
||||
content = secret;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,26 +1,31 @@
|
|||
{ pkgs, lib, ... }:
|
||||
let
|
||||
anyOpt = default: lib.mkOption {
|
||||
anyOpt =
|
||||
default:
|
||||
lib.mkOption {
|
||||
type = lib.types.anything;
|
||||
inherit default;
|
||||
};
|
||||
|
||||
testConfig = m:
|
||||
testConfig =
|
||||
m:
|
||||
let
|
||||
cfg = (lib.evalModules {
|
||||
cfg =
|
||||
(lib.evalModules {
|
||||
specialArgs = { inherit pkgs; };
|
||||
modules = [
|
||||
{
|
||||
options = {
|
||||
systemd = anyOpt {};
|
||||
services = anyOpt {};
|
||||
systemd = anyOpt { };
|
||||
services = anyOpt { };
|
||||
};
|
||||
}
|
||||
../../modules/blocks/davfs.nix
|
||||
m
|
||||
];
|
||||
}).config;
|
||||
in {
|
||||
in
|
||||
{
|
||||
inherit (cfg) systemd services;
|
||||
};
|
||||
in
|
||||
|
|
@ -28,8 +33,8 @@ in
|
|||
testDavfsNoOptions = {
|
||||
expected = {
|
||||
services.davfs2.enable = false;
|
||||
systemd.mounts = [];
|
||||
systemd.mounts = [ ];
|
||||
};
|
||||
expr = testConfig {};
|
||||
expr = testConfig { };
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,8 @@ in
|
|||
c = "%SECRET_${root}C%";
|
||||
};
|
||||
in
|
||||
(item "") // {
|
||||
(item "")
|
||||
// {
|
||||
nestedAttr = item "NESTEDATTR_";
|
||||
nestedList = [ (item "NESTEDLIST_0_") ];
|
||||
doubleNestedList = [ { n = (item "DOUBLENESTEDLIST_0_N_"); } ];
|
||||
|
|
@ -34,7 +35,8 @@ in
|
|||
};
|
||||
in
|
||||
lib.shb.withReplacements (
|
||||
item // {
|
||||
item
|
||||
// {
|
||||
nestedAttr = item;
|
||||
nestedList = [ item ];
|
||||
doubleNestedList = [ { n = item; } ];
|
||||
|
|
@ -84,10 +86,10 @@ in
|
|||
(nameValuePair "%SECRET_${root}C%" "prefix-$(cat /path/C)-suffix")
|
||||
];
|
||||
in
|
||||
(secrets "") ++
|
||||
(secrets "DOUBLENESTEDLIST_0_N_") ++
|
||||
(secrets "NESTEDATTR_") ++
|
||||
(secrets "NESTEDLIST_0_");
|
||||
(secrets "")
|
||||
++ (secrets "DOUBLENESTEDLIST_0_N_")
|
||||
++ (secrets "NESTEDATTR_")
|
||||
++ (secrets "NESTEDLIST_0_");
|
||||
expr =
|
||||
let
|
||||
item = {
|
||||
|
|
@ -99,13 +101,16 @@ in
|
|||
c.other = "other";
|
||||
};
|
||||
in
|
||||
map lib.shb.genReplacement (lib.shb.getReplacements (
|
||||
item // {
|
||||
map lib.shb.genReplacement (
|
||||
lib.shb.getReplacements (
|
||||
item
|
||||
// {
|
||||
nestedAttr = item;
|
||||
nestedList = [ item ];
|
||||
doubleNestedList = [ { n = item; } ];
|
||||
}
|
||||
));
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
testParseXML = {
|
||||
|
|
|
|||
|
|
@ -4,20 +4,34 @@ let
|
|||
loginUrl = "/UI/Login";
|
||||
|
||||
# TODO: Test login
|
||||
commonTestScript = appname: cfgPathFn: lib.shb.mkScripts {
|
||||
commonTestScript =
|
||||
appname: cfgPathFn:
|
||||
lib.shb.mkScripts {
|
||||
hasSSL = { node, ... }: !(isNull node.config.shb.arr.${appname}.ssl);
|
||||
waitForServices = { ... }: [
|
||||
waitForServices =
|
||||
{ ... }:
|
||||
[
|
||||
"${appname}.service"
|
||||
"nginx.service"
|
||||
];
|
||||
waitForPorts = { node, ... }: [
|
||||
waitForPorts =
|
||||
{ node, ... }:
|
||||
[
|
||||
node.config.shb.arr.${appname}.settings.Port
|
||||
];
|
||||
extraScript = { node, fqdn, proto_fqdn, ... }: let
|
||||
extraScript =
|
||||
{
|
||||
node,
|
||||
fqdn,
|
||||
proto_fqdn,
|
||||
...
|
||||
}:
|
||||
let
|
||||
shbapp = node.config.shb.arr.${appname};
|
||||
cfgPath = cfgPathFn shbapp;
|
||||
apiKey = if (shbapp.settings ? ApiKey) then "01234567890123456789" else null;
|
||||
in ''
|
||||
in
|
||||
''
|
||||
# These curl requests still return a 200 even with sso redirect.
|
||||
with subtest("health"):
|
||||
response = curl(client, """{"code":%{response_code}}""", "${fqdn}${healthUrl}")
|
||||
|
|
@ -31,7 +45,8 @@ let
|
|||
|
||||
if response['code'] != 200:
|
||||
raise Exception(f"Code is {response['code']}")
|
||||
'' + lib.optionalString (apiKey != null) ''
|
||||
''
|
||||
+ lib.optionalString (apiKey != null) ''
|
||||
|
||||
with subtest("apikey"):
|
||||
config = server.succeed("cat ${cfgPath}")
|
||||
|
|
@ -40,7 +55,10 @@ let
|
|||
'';
|
||||
};
|
||||
|
||||
basic = appname: { config, ... }: {
|
||||
basic =
|
||||
appname:
|
||||
{ config, ... }:
|
||||
{
|
||||
imports = [
|
||||
lib.shb.baseModule
|
||||
../../modules/services/arr.nix
|
||||
|
|
@ -58,7 +76,10 @@ let
|
|||
};
|
||||
};
|
||||
|
||||
clientLogin = appname: { config, ... }: {
|
||||
clientLogin =
|
||||
appname:
|
||||
{ config, ... }:
|
||||
{
|
||||
imports = [
|
||||
lib.shb.baseModule
|
||||
lib.shb.clientLoginModule
|
||||
|
|
@ -74,14 +95,18 @@ let
|
|||
passwordFieldLabelRegex = "^ *[Pp]assword";
|
||||
loginButtonNameRegex = "[Ll]og [Ii]n";
|
||||
testLoginWith = [
|
||||
{ nextPageExpect = [
|
||||
{
|
||||
nextPageExpect = [
|
||||
"expect(page).to_have_title(re.compile('${appname}', re.IGNORECASE))"
|
||||
]; }
|
||||
];
|
||||
}
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
basicTest = appname: cfgPathFn: lib.shb.runNixOSTest {
|
||||
basicTest =
|
||||
appname: cfgPathFn:
|
||||
lib.shb.runNixOSTest {
|
||||
name = "arr_${appname}_basic";
|
||||
|
||||
nodes.client = {
|
||||
|
|
@ -98,31 +123,42 @@ let
|
|||
testScript = (commonTestScript appname cfgPathFn).access;
|
||||
};
|
||||
|
||||
backupTest = appname: cfgPathFn: lib.shb.runNixOSTest {
|
||||
backupTest =
|
||||
appname: cfgPathFn:
|
||||
lib.shb.runNixOSTest {
|
||||
name = "arr_${appname}_backup";
|
||||
|
||||
nodes.server = { config, ... }: {
|
||||
nodes.server =
|
||||
{ config, ... }:
|
||||
{
|
||||
imports = [
|
||||
(basic appname)
|
||||
(lib.shb.backup config.shb.arr.${appname}.backup)
|
||||
];
|
||||
};
|
||||
|
||||
nodes.client = {};
|
||||
nodes.client = { };
|
||||
|
||||
testScript = (commonTestScript appname cfgPathFn).backup;
|
||||
};
|
||||
|
||||
https = appname: { config, ...}: {
|
||||
https =
|
||||
appname:
|
||||
{ config, ... }:
|
||||
{
|
||||
shb.arr.${appname} = {
|
||||
ssl = config.shb.certs.certs.selfsigned.n;
|
||||
};
|
||||
};
|
||||
|
||||
httpsTest = appname: cfgPathFn: lib.shb.runNixOSTest {
|
||||
httpsTest =
|
||||
appname: cfgPathFn:
|
||||
lib.shb.runNixOSTest {
|
||||
name = "arr_${appname}_https";
|
||||
|
||||
nodes.server = { config, pkgs, ... }: {
|
||||
nodes.server =
|
||||
{ config, pkgs, ... }:
|
||||
{
|
||||
imports = [
|
||||
(basic appname)
|
||||
lib.shb.certs
|
||||
|
|
@ -130,21 +166,28 @@ let
|
|||
];
|
||||
};
|
||||
|
||||
nodes.client = {};
|
||||
nodes.client = { };
|
||||
|
||||
testScript = (commonTestScript appname cfgPathFn).access;
|
||||
};
|
||||
|
||||
sso = appname: { config, ...}: {
|
||||
sso =
|
||||
appname:
|
||||
{ config, ... }:
|
||||
{
|
||||
shb.arr.${appname} = {
|
||||
authEndpoint = "https://${config.shb.authelia.subdomain}.${config.shb.authelia.domain}";
|
||||
};
|
||||
};
|
||||
|
||||
ssoTest = appname: cfgPathFn: lib.shb.runNixOSTest {
|
||||
ssoTest =
|
||||
appname: cfgPathFn:
|
||||
lib.shb.runNixOSTest {
|
||||
name = "arr_${appname}_sso";
|
||||
|
||||
nodes.server = { config, pkgs, ... }: {
|
||||
nodes.server =
|
||||
{ config, pkgs, ... }:
|
||||
{
|
||||
imports = [
|
||||
(basic appname)
|
||||
lib.shb.certs
|
||||
|
|
@ -155,7 +198,7 @@ let
|
|||
];
|
||||
};
|
||||
|
||||
nodes.client = {};
|
||||
nodes.client = { };
|
||||
|
||||
testScript = (commonTestScript appname cfgPathFn).access.override {
|
||||
redirectSSO = true;
|
||||
|
|
|
|||
|
|
@ -2,11 +2,15 @@
|
|||
let
|
||||
commonTestScript = lib.shb.accessScript {
|
||||
hasSSL = { node, ... }: !(isNull node.config.shb.audiobookshelf.ssl);
|
||||
waitForServices = { ... }: [
|
||||
waitForServices =
|
||||
{ ... }:
|
||||
[
|
||||
"audiobookshelf.service"
|
||||
"nginx.service"
|
||||
];
|
||||
waitForPorts = { node, ... }: [
|
||||
waitForPorts =
|
||||
{ node, ... }:
|
||||
[
|
||||
node.config.shb.audiobookshelf.webPort
|
||||
];
|
||||
# TODO: Test login
|
||||
|
|
@ -14,7 +18,9 @@ let
|
|||
# '';
|
||||
};
|
||||
|
||||
basic = { config, ... }: {
|
||||
basic =
|
||||
{ config, ... }:
|
||||
{
|
||||
imports = [
|
||||
lib.shb.baseModule
|
||||
../../modules/services/audiobookshelf.nix
|
||||
|
|
@ -29,7 +35,9 @@ let
|
|||
};
|
||||
};
|
||||
|
||||
clientLogin = { config, ... }: {
|
||||
clientLogin =
|
||||
{ config, ... }:
|
||||
{
|
||||
imports = [
|
||||
lib.shb.baseModule
|
||||
lib.shb.clientLoginModule
|
||||
|
|
@ -47,9 +55,13 @@ let
|
|||
loginButtonNameRegex = "[Ll]og [Ii]n";
|
||||
testLoginWith = [
|
||||
# Failure is after so we're not throttled too much.
|
||||
{ username = "root"; password = "rootpw"; nextPageExpect = [
|
||||
{
|
||||
username = "root";
|
||||
password = "rootpw";
|
||||
nextPageExpect = [
|
||||
"expect(page.get_by_text('Wrong username or password')).to_be_visible()"
|
||||
]; }
|
||||
];
|
||||
}
|
||||
# { username = adminUser; password = adminPass; nextPageExpect = [
|
||||
# "expect(page.get_by_text('Wrong username or password')).not_to_be_visible()"
|
||||
# "expect(page.get_by_role('button', name=re.compile('[Ll]og [Ii]n'))).not_to_be_visible()"
|
||||
|
|
@ -59,19 +71,24 @@ let
|
|||
};
|
||||
};
|
||||
|
||||
https = { config, ... }: {
|
||||
https =
|
||||
{ config, ... }:
|
||||
{
|
||||
shb.audiobookshelf = {
|
||||
ssl = config.shb.certs.certs.selfsigned.n;
|
||||
};
|
||||
};
|
||||
|
||||
sso = { config, ... }: {
|
||||
sso =
|
||||
{ config, ... }:
|
||||
{
|
||||
shb.audiobookshelf = {
|
||||
sso = {
|
||||
enable = true;
|
||||
endpoint = "https://${config.shb.authelia.subdomain}.${config.shb.authelia.domain}";
|
||||
sharedSecret.result = config.shb.hardcodedsecret.audiobookshelfSSOPassword.result;
|
||||
sharedSecretForAuthelia.result = config.shb.hardcodedsecret.audiobookshelfSSOPasswordAuthelia.result;
|
||||
sharedSecretForAuthelia.result =
|
||||
config.shb.hardcodedsecret.audiobookshelfSSOPasswordAuthelia.result;
|
||||
};
|
||||
};
|
||||
|
||||
|
|
@ -116,7 +133,7 @@ in
|
|||
];
|
||||
};
|
||||
|
||||
nodes.client = {};
|
||||
nodes.client = { };
|
||||
|
||||
testScript = commonTestScript;
|
||||
};
|
||||
|
|
@ -124,7 +141,9 @@ in
|
|||
sso = lib.shb.runNixOSTest {
|
||||
name = "audiobookshelf-sso";
|
||||
|
||||
nodes.server = { config, ... }: {
|
||||
nodes.server =
|
||||
{ config, ... }:
|
||||
{
|
||||
imports = [
|
||||
basic
|
||||
lib.shb.certs
|
||||
|
|
@ -135,7 +154,7 @@ in
|
|||
];
|
||||
};
|
||||
|
||||
nodes.client = {};
|
||||
nodes.client = { };
|
||||
|
||||
testScript = commonTestScript;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -2,16 +2,22 @@
|
|||
let
|
||||
commonTestScript = lib.shb.mkScripts {
|
||||
hasSSL = { node, ... }: !(isNull node.config.shb.deluge.ssl);
|
||||
waitForServices = { ... }: [
|
||||
waitForServices =
|
||||
{ ... }:
|
||||
[
|
||||
"nginx.service"
|
||||
"deluged.service"
|
||||
"delugeweb.service"
|
||||
];
|
||||
waitForPorts = { node, ... }: [
|
||||
waitForPorts =
|
||||
{ node, ... }:
|
||||
[
|
||||
node.config.shb.deluge.daemonPort
|
||||
node.config.shb.deluge.webPort
|
||||
];
|
||||
extraScript = { node, proto_fqdn, ... }: ''
|
||||
extraScript =
|
||||
{ node, proto_fqdn, ... }:
|
||||
''
|
||||
print(${node.name}.succeed('journalctl -n100 -u deluged'))
|
||||
print(${node.name}.succeed('systemctl status deluged'))
|
||||
print(${node.name}.succeed('systemctl status delugeweb'))
|
||||
|
|
@ -54,7 +60,8 @@ let
|
|||
'';
|
||||
};
|
||||
|
||||
prometheusTestScript = { nodes, ... }:
|
||||
prometheusTestScript =
|
||||
{ nodes, ... }:
|
||||
''
|
||||
server.wait_for_open_port(${toString nodes.server.services.prometheus.exporters.deluge.port})
|
||||
with subtest("prometheus"):
|
||||
|
|
@ -65,7 +72,9 @@ let
|
|||
print(response)
|
||||
'';
|
||||
|
||||
basic = { config, ... }: {
|
||||
basic =
|
||||
{ config, ... }:
|
||||
{
|
||||
imports = [
|
||||
lib.shb.baseModule
|
||||
../../modules/blocks/hardcodedsecret.nix
|
||||
|
|
@ -96,7 +105,9 @@ let
|
|||
};
|
||||
};
|
||||
|
||||
clientLogin = { config, ... }: {
|
||||
clientLogin =
|
||||
{ config, ... }:
|
||||
{
|
||||
imports = [
|
||||
lib.shb.baseModule
|
||||
lib.shb.clientLoginModule
|
||||
|
|
@ -109,19 +120,27 @@ let
|
|||
passwordFieldLabelRegex = "Password";
|
||||
loginButtonNameRegex = "Login";
|
||||
testLoginWith = [
|
||||
{ password = "deluge"; nextPageExpect = [
|
||||
{
|
||||
password = "deluge";
|
||||
nextPageExpect = [
|
||||
"expect(page.get_by_role('button', name='Login')).not_to_be_visible()"
|
||||
"expect(page.get_by_text('Login Failed')).not_to_be_visible()"
|
||||
]; }
|
||||
{ password = "other"; nextPageExpect = [
|
||||
];
|
||||
}
|
||||
{
|
||||
password = "other";
|
||||
nextPageExpect = [
|
||||
"expect(page.get_by_role('button', name='Login')).to_be_visible()"
|
||||
"expect(page.get_by_text('Login Failed')).to_be_visible()"
|
||||
]; }
|
||||
];
|
||||
}
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
prometheus = { config, ... }: {
|
||||
prometheus =
|
||||
{ config, ... }:
|
||||
{
|
||||
shb.deluge = {
|
||||
prometheusScraperPassword.result = config.shb.hardcodedsecret."scraper".result;
|
||||
};
|
||||
|
|
@ -131,13 +150,17 @@ let
|
|||
};
|
||||
};
|
||||
|
||||
https = { config, ...}: {
|
||||
https =
|
||||
{ config, ... }:
|
||||
{
|
||||
shb.deluge = {
|
||||
ssl = config.shb.certs.certs.selfsigned.n;
|
||||
};
|
||||
};
|
||||
|
||||
sso = { config, ... }: {
|
||||
sso =
|
||||
{ config, ... }:
|
||||
{
|
||||
shb.deluge = {
|
||||
authEndpoint = "https://${config.shb.authelia.subdomain}.${config.shb.authelia.domain}";
|
||||
};
|
||||
|
|
@ -164,14 +187,16 @@ in
|
|||
backup = lib.shb.runNixOSTest {
|
||||
name = "deluge_backup";
|
||||
|
||||
nodes.server = { config, ... }: {
|
||||
nodes.server =
|
||||
{ config, ... }:
|
||||
{
|
||||
imports = [
|
||||
basic
|
||||
(lib.shb.backup config.shb.deluge.backup)
|
||||
];
|
||||
};
|
||||
|
||||
nodes.client = {};
|
||||
nodes.client = { };
|
||||
|
||||
testScript = commonTestScript.backup;
|
||||
};
|
||||
|
|
@ -187,7 +212,7 @@ in
|
|||
];
|
||||
};
|
||||
|
||||
nodes.client = {};
|
||||
nodes.client = { };
|
||||
|
||||
testScript = commonTestScript.access;
|
||||
};
|
||||
|
|
@ -195,7 +220,9 @@ in
|
|||
sso = lib.shb.runNixOSTest {
|
||||
name = "deluge_sso";
|
||||
|
||||
nodes.server = { config, ... }: {
|
||||
nodes.server =
|
||||
{ config, ... }:
|
||||
{
|
||||
imports = [
|
||||
basic
|
||||
lib.shb.certs
|
||||
|
|
@ -206,7 +233,7 @@ in
|
|||
];
|
||||
};
|
||||
|
||||
nodes.client = {};
|
||||
nodes.client = { };
|
||||
|
||||
testScript = commonTestScript.access.override {
|
||||
redirectSSO = true;
|
||||
|
|
@ -225,10 +252,8 @@ in
|
|||
];
|
||||
};
|
||||
|
||||
nodes.client = {};
|
||||
nodes.client = { };
|
||||
|
||||
testScript = inputs:
|
||||
(commonTestScript.access inputs)
|
||||
+ (prometheusTestScript inputs);
|
||||
testScript = inputs: (commonTestScript.access inputs) + (prometheusTestScript inputs);
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,20 +4,28 @@ let
|
|||
|
||||
commonTestScript = lib.shb.mkScripts {
|
||||
hasSSL = { node, ... }: !(isNull node.config.shb.forgejo.ssl);
|
||||
waitForServices = { ... }: [
|
||||
waitForServices =
|
||||
{ ... }:
|
||||
[
|
||||
"forgejo.service"
|
||||
"nginx.service"
|
||||
];
|
||||
waitForUnixSocket = { node, ... }: [
|
||||
waitForUnixSocket =
|
||||
{ node, ... }:
|
||||
[
|
||||
node.config.services.forgejo.settings.server.HTTP_ADDR
|
||||
];
|
||||
extraScript = { node, ... }: ''
|
||||
extraScript =
|
||||
{ node, ... }:
|
||||
''
|
||||
server.wait_for_unit("gitea-runner-local.service", timeout=10)
|
||||
server.succeed("journalctl -o cat -u gitea-runner-local.service | grep -q 'Runner registered successfully'")
|
||||
'';
|
||||
};
|
||||
|
||||
basic = { config, ... }: {
|
||||
basic =
|
||||
{ config, ... }:
|
||||
{
|
||||
imports = [
|
||||
lib.shb.baseModule
|
||||
../../modules/blocks/hardcodedsecret.nix
|
||||
|
|
@ -67,7 +75,9 @@ let
|
|||
};
|
||||
};
|
||||
|
||||
clientLogin = { config, ... }: {
|
||||
clientLogin =
|
||||
{ config, ... }:
|
||||
{
|
||||
imports = [
|
||||
lib.shb.baseModule
|
||||
lib.shb.clientLoginModule
|
||||
|
|
@ -82,33 +92,53 @@ let
|
|||
passwordFieldLabelRegex = "Password";
|
||||
loginButtonNameRegex = "[sS]ign [iI]n";
|
||||
testLoginWith = [
|
||||
{ username = "theadmin"; password = adminPassword + "oops"; nextPageExpect = [
|
||||
{
|
||||
username = "theadmin";
|
||||
password = adminPassword + "oops";
|
||||
nextPageExpect = [
|
||||
"expect(page.get_by_text('Username or password is incorrect.')).to_be_visible()"
|
||||
]; }
|
||||
{ username = "theadmin"; password = adminPassword; nextPageExpect = [
|
||||
];
|
||||
}
|
||||
{
|
||||
username = "theadmin";
|
||||
password = adminPassword;
|
||||
nextPageExpect = [
|
||||
"expect(page.get_by_text('Username or password is incorrect.')).not_to_be_visible()"
|
||||
"expect(page.get_by_role('button', name=re.compile('Sign In'))).not_to_be_visible()"
|
||||
"expect(page).to_have_title(re.compile('Dashboard'))"
|
||||
]; }
|
||||
{ username = "theuser"; password = "userPasswordOops"; nextPageExpect = [
|
||||
];
|
||||
}
|
||||
{
|
||||
username = "theuser";
|
||||
password = "userPasswordOops";
|
||||
nextPageExpect = [
|
||||
"expect(page.get_by_text('Username or password is incorrect.')).to_be_visible()"
|
||||
]; }
|
||||
{ username = "theuser"; password = "userPassword"; nextPageExpect = [
|
||||
];
|
||||
}
|
||||
{
|
||||
username = "theuser";
|
||||
password = "userPassword";
|
||||
nextPageExpect = [
|
||||
"expect(page.get_by_text('Username or password is incorrect.')).not_to_be_visible()"
|
||||
"expect(page.get_by_role('button', name=re.compile('Sign In'))).not_to_be_visible()"
|
||||
"expect(page).to_have_title(re.compile('Dashboard'))"
|
||||
]; }
|
||||
];
|
||||
}
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
https = { config, ... }: {
|
||||
https =
|
||||
{ config, ... }:
|
||||
{
|
||||
shb.forgejo = {
|
||||
ssl = config.shb.certs.certs.selfsigned.n;
|
||||
};
|
||||
};
|
||||
|
||||
ldap = { config, ... }: {
|
||||
ldap =
|
||||
{ config, ... }:
|
||||
{
|
||||
shb.forgejo = {
|
||||
ldap = {
|
||||
enable = true;
|
||||
|
|
@ -128,7 +158,9 @@ let
|
|||
};
|
||||
};
|
||||
|
||||
sso = { config, ... }: {
|
||||
sso =
|
||||
{ config, ... }:
|
||||
{
|
||||
shb.forgejo = {
|
||||
sso = {
|
||||
enable = true;
|
||||
|
|
@ -170,14 +202,16 @@ in
|
|||
backup = lib.shb.runNixOSTest {
|
||||
name = "forgejo_backup";
|
||||
|
||||
nodes.server = { config, ... }: {
|
||||
nodes.server =
|
||||
{ config, ... }:
|
||||
{
|
||||
imports = [
|
||||
basic
|
||||
(lib.shb.backup config.shb.forgejo.backup)
|
||||
];
|
||||
};
|
||||
|
||||
nodes.client = {};
|
||||
nodes.client = { };
|
||||
|
||||
testScript = commonTestScript.backup;
|
||||
};
|
||||
|
|
@ -193,7 +227,7 @@ in
|
|||
];
|
||||
};
|
||||
|
||||
nodes.client = {};
|
||||
nodes.client = { };
|
||||
|
||||
testScript = commonTestScript.access;
|
||||
};
|
||||
|
|
@ -211,7 +245,9 @@ in
|
|||
|
||||
nodes.client = {
|
||||
imports = [
|
||||
({ config, ... }: {
|
||||
(
|
||||
{ config, ... }:
|
||||
{
|
||||
imports = [
|
||||
lib.shb.baseModule
|
||||
lib.shb.clientLoginModule
|
||||
|
|
@ -227,31 +263,56 @@ in
|
|||
passwordFieldLabelRegex = "Password";
|
||||
loginButtonNameRegex = "[sS]ign [iI]n";
|
||||
testLoginWith = [
|
||||
{ username = "alice"; password = "NotAlicePassword"; nextPageExpect = [
|
||||
{
|
||||
username = "alice";
|
||||
password = "NotAlicePassword";
|
||||
nextPageExpect = [
|
||||
"expect(page.get_by_text('Username or password is incorrect.')).to_be_visible()"
|
||||
]; }
|
||||
{ username = "alice"; password = "AlicePassword"; nextPageExpect = [
|
||||
];
|
||||
}
|
||||
{
|
||||
username = "alice";
|
||||
password = "AlicePassword";
|
||||
nextPageExpect = [
|
||||
"expect(page.get_by_text('Username or password is incorrect.')).not_to_be_visible()"
|
||||
"expect(page.get_by_role('button', name=re.compile('Sign In'))).not_to_be_visible()"
|
||||
"expect(page).to_have_title(re.compile('Dashboard'))"
|
||||
]; }
|
||||
{ username = "bob"; password = "NotBobPassword"; nextPageExpect = [
|
||||
];
|
||||
}
|
||||
{
|
||||
username = "bob";
|
||||
password = "NotBobPassword";
|
||||
nextPageExpect = [
|
||||
"expect(page.get_by_text('Username or password is incorrect.')).to_be_visible()"
|
||||
]; }
|
||||
{ username = "bob"; password = "BobPassword"; nextPageExpect = [
|
||||
];
|
||||
}
|
||||
{
|
||||
username = "bob";
|
||||
password = "BobPassword";
|
||||
nextPageExpect = [
|
||||
"expect(page.get_by_text('Username or password is incorrect.')).not_to_be_visible()"
|
||||
"expect(page.get_by_role('button', name=re.compile('Sign In'))).not_to_be_visible()"
|
||||
"expect(page).to_have_title(re.compile('Dashboard'))"
|
||||
]; }
|
||||
{ username = "charlie"; password = "NotCharliePassword"; nextPageExpect = [
|
||||
];
|
||||
}
|
||||
{
|
||||
username = "charlie";
|
||||
password = "NotCharliePassword";
|
||||
nextPageExpect = [
|
||||
"expect(page.get_by_text('Username or password is incorrect.')).to_be_visible()"
|
||||
]; }
|
||||
{ username = "charlie"; password = "CharliePassword"; nextPageExpect = [
|
||||
];
|
||||
}
|
||||
{
|
||||
username = "charlie";
|
||||
password = "CharliePassword";
|
||||
nextPageExpect = [
|
||||
"expect(page.get_by_text('Username or password is incorrect.')).to_be_visible()"
|
||||
]; }
|
||||
];
|
||||
}
|
||||
];
|
||||
};
|
||||
})
|
||||
}
|
||||
)
|
||||
];
|
||||
};
|
||||
|
||||
|
|
@ -261,7 +322,9 @@ in
|
|||
sso = lib.shb.runNixOSTest {
|
||||
name = "forgejo_sso";
|
||||
|
||||
nodes.server = { config, pkgs, ... }: {
|
||||
nodes.server =
|
||||
{ config, pkgs, ... }:
|
||||
{
|
||||
imports = [
|
||||
basic
|
||||
lib.shb.certs
|
||||
|
|
@ -272,7 +335,7 @@ in
|
|||
];
|
||||
};
|
||||
|
||||
nodes.client = {};
|
||||
nodes.client = { };
|
||||
|
||||
testScript = commonTestScript.access;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -2,16 +2,22 @@
|
|||
let
|
||||
commonTestScript = lib.shb.mkScripts {
|
||||
hasSSL = { node, ... }: !(isNull node.config.shb.grocy.ssl);
|
||||
waitForServices = { ... }: [
|
||||
waitForServices =
|
||||
{ ... }:
|
||||
[
|
||||
"phpfpm-grocy.service"
|
||||
"nginx.service"
|
||||
];
|
||||
waitForUnixSocket = { node, ... }: [
|
||||
waitForUnixSocket =
|
||||
{ node, ... }:
|
||||
[
|
||||
node.config.services.phpfpm.pools.grocy.socket
|
||||
];
|
||||
};
|
||||
|
||||
basic = { config, ... }: {
|
||||
basic =
|
||||
{ config, ... }:
|
||||
{
|
||||
imports = [
|
||||
lib.shb.baseModule
|
||||
../../modules/services/grocy.nix
|
||||
|
|
@ -27,7 +33,9 @@ let
|
|||
};
|
||||
};
|
||||
|
||||
clientLogin = { config, ... }: {
|
||||
clientLogin =
|
||||
{ config, ... }:
|
||||
{
|
||||
imports = [
|
||||
lib.shb.baseModule
|
||||
lib.shb.clientLoginModule
|
||||
|
|
@ -44,19 +52,29 @@ let
|
|||
passwordFieldLabelRegex = "Password";
|
||||
loginButtonNameRegex = "OK";
|
||||
testLoginWith = [
|
||||
{ username = "admin"; password = "admin oops"; nextPageExpect = [
|
||||
{
|
||||
username = "admin";
|
||||
password = "admin oops";
|
||||
nextPageExpect = [
|
||||
"expect(page.get_by_text('Invalid credentials, please try again')).to_be_visible()"
|
||||
]; }
|
||||
{ username = "admin"; password = "admin"; nextPageExpect = [
|
||||
];
|
||||
}
|
||||
{
|
||||
username = "admin";
|
||||
password = "admin";
|
||||
nextPageExpect = [
|
||||
"expect(page.get_by_text('Invalid credentials, please try again')).not_to_be_visible()"
|
||||
"expect(page.get_by_role('button', name=re.compile('OK'))).not_to_be_visible()"
|
||||
"expect(page).to_have_title(re.compile('Grocy'))"
|
||||
]; }
|
||||
];
|
||||
}
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
https = { config, ...}: {
|
||||
https =
|
||||
{ config, ... }:
|
||||
{
|
||||
shb.grocy = {
|
||||
ssl = config.shb.certs.certs.selfsigned.n;
|
||||
};
|
||||
|
|
@ -91,7 +109,7 @@ in
|
|||
];
|
||||
};
|
||||
|
||||
nodes.client = {};
|
||||
nodes.client = { };
|
||||
|
||||
testScript = commonTestScript.access;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -2,13 +2,17 @@
|
|||
let
|
||||
commonTestScript = lib.shb.mkScripts {
|
||||
hasSSL = { node, ... }: !(isNull node.config.shb.hledger.ssl);
|
||||
waitForServices = { ... }: [
|
||||
waitForServices =
|
||||
{ ... }:
|
||||
[
|
||||
"hledger-web.service"
|
||||
"nginx.service"
|
||||
];
|
||||
};
|
||||
|
||||
basic = { config, ... }: {
|
||||
basic =
|
||||
{ config, ... }:
|
||||
{
|
||||
imports = [
|
||||
lib.shb.baseModule
|
||||
../../modules/services/hledger.nix
|
||||
|
|
@ -24,7 +28,9 @@ let
|
|||
};
|
||||
};
|
||||
|
||||
clientLogin = { config, ... }: {
|
||||
clientLogin =
|
||||
{ config, ... }:
|
||||
{
|
||||
imports = [
|
||||
lib.shb.baseModule
|
||||
lib.shb.clientLoginModule
|
||||
|
|
@ -37,20 +43,26 @@ let
|
|||
test.login = {
|
||||
startUrl = "http://${config.test.fqdn}";
|
||||
testLoginWith = [
|
||||
{ nextPageExpect = [
|
||||
{
|
||||
nextPageExpect = [
|
||||
"expect(page).to_have_title('journal - hledger-web')"
|
||||
]; }
|
||||
];
|
||||
}
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
https = { config, ... }: {
|
||||
https =
|
||||
{ config, ... }:
|
||||
{
|
||||
shb.hledger = {
|
||||
ssl = config.shb.certs.certs.selfsigned.n;
|
||||
};
|
||||
};
|
||||
|
||||
sso = { config, ... }: {
|
||||
sso =
|
||||
{ config, ... }:
|
||||
{
|
||||
shb.hledger = {
|
||||
authEndpoint = "https://${config.shb.authelia.subdomain}.${config.shb.authelia.domain}";
|
||||
};
|
||||
|
|
@ -78,14 +90,16 @@ in
|
|||
backup = lib.shb.runNixOSTest {
|
||||
name = "hledger_backup";
|
||||
|
||||
nodes.server = { config, ... }: {
|
||||
nodes.server =
|
||||
{ config, ... }:
|
||||
{
|
||||
imports = [
|
||||
basic
|
||||
(lib.shb.backup config.shb.hledger.backup)
|
||||
];
|
||||
};
|
||||
|
||||
nodes.client = {};
|
||||
nodes.client = { };
|
||||
|
||||
testScript = commonTestScript.backup;
|
||||
};
|
||||
|
|
@ -101,7 +115,7 @@ in
|
|||
];
|
||||
};
|
||||
|
||||
nodes.client = {};
|
||||
nodes.client = { };
|
||||
|
||||
testScript = commonTestScript.access;
|
||||
};
|
||||
|
|
@ -109,7 +123,9 @@ in
|
|||
sso = lib.shb.runNixOSTest {
|
||||
name = "hledger_sso";
|
||||
|
||||
nodes.server = { config, pkgs, ... }: {
|
||||
nodes.server =
|
||||
{ config, pkgs, ... }:
|
||||
{
|
||||
imports = [
|
||||
basic
|
||||
lib.shb.certs
|
||||
|
|
@ -120,7 +136,7 @@ in
|
|||
];
|
||||
};
|
||||
|
||||
nodes.client = {};
|
||||
nodes.client = { };
|
||||
|
||||
testScript = commonTestScript.access.override {
|
||||
redirectSSO = true;
|
||||
|
|
|
|||
|
|
@ -2,16 +2,22 @@
|
|||
let
|
||||
commonTestScript = lib.shb.mkScripts {
|
||||
hasSSL = { node, ... }: !(isNull node.config.shb.home-assistant.ssl);
|
||||
waitForServices = { ... }: [
|
||||
waitForServices =
|
||||
{ ... }:
|
||||
[
|
||||
"home-assistant.service"
|
||||
"nginx.service"
|
||||
];
|
||||
waitForPorts = { node, ... }: [
|
||||
waitForPorts =
|
||||
{ node, ... }:
|
||||
[
|
||||
8123
|
||||
];
|
||||
};
|
||||
|
||||
basic = { config, ... }: {
|
||||
basic =
|
||||
{ config, ... }:
|
||||
{
|
||||
imports = [
|
||||
lib.shb.baseModule
|
||||
../../modules/services/home-assistant.nix
|
||||
|
|
@ -36,7 +42,9 @@ let
|
|||
};
|
||||
};
|
||||
|
||||
clientLogin = { config, ... }: {
|
||||
clientLogin =
|
||||
{ config, ... }:
|
||||
{
|
||||
imports = [
|
||||
lib.shb.baseModule
|
||||
lib.shb.clientLoginModule
|
||||
|
|
@ -50,7 +58,8 @@ let
|
|||
test.login = {
|
||||
startUrl = "http://${config.test.fqdn}";
|
||||
testLoginWith = [
|
||||
{ nextPageExpect = [
|
||||
{
|
||||
nextPageExpect = [
|
||||
"page.get_by_role('button', name=re.compile('Create my smart home')).click()"
|
||||
|
||||
"expect(page.get_by_text('Create user')).to_be_visible()"
|
||||
|
|
@ -64,18 +73,23 @@ let
|
|||
"page.get_by_role('button', name=re.compile('Finish')).click()"
|
||||
|
||||
"expect(page).to_have_title(re.compile('Overview'), timeout=15000)"
|
||||
]; }
|
||||
];
|
||||
}
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
https = { config, ...}: {
|
||||
https =
|
||||
{ config, ... }:
|
||||
{
|
||||
shb.home-assistant = {
|
||||
ssl = config.shb.certs.certs.selfsigned.n;
|
||||
};
|
||||
};
|
||||
|
||||
ldap = { config, ... }: {
|
||||
ldap =
|
||||
{ config, ... }:
|
||||
{
|
||||
shb.home-assistant = {
|
||||
ldap = {
|
||||
enable = true;
|
||||
|
|
@ -95,7 +109,9 @@ let
|
|||
# };
|
||||
# };
|
||||
|
||||
voice = { config, ... }: {
|
||||
voice =
|
||||
{ config, ... }:
|
||||
{
|
||||
# For now, verifying the packages can build is good enough.
|
||||
environment.systemPackages = [
|
||||
config.services.wyoming.piper.package
|
||||
|
|
@ -169,14 +185,16 @@ in
|
|||
backup = lib.shb.runNixOSTest {
|
||||
name = "homeassistant_backup";
|
||||
|
||||
nodes.server = { config, ... }: {
|
||||
nodes.server =
|
||||
{ config, ... }:
|
||||
{
|
||||
imports = [
|
||||
basic
|
||||
(lib.shb.backup config.shb.home-assistant.backup)
|
||||
];
|
||||
};
|
||||
|
||||
nodes.client = {};
|
||||
nodes.client = { };
|
||||
|
||||
testScript = commonTestScript.backup;
|
||||
};
|
||||
|
|
@ -192,7 +210,7 @@ in
|
|||
];
|
||||
};
|
||||
|
||||
nodes.client = {};
|
||||
nodes.client = { };
|
||||
|
||||
testScript = commonTestScript.access;
|
||||
};
|
||||
|
|
@ -208,7 +226,7 @@ in
|
|||
];
|
||||
};
|
||||
|
||||
nodes.client = {};
|
||||
nodes.client = { };
|
||||
|
||||
testScript = commonTestScript.access;
|
||||
};
|
||||
|
|
@ -243,7 +261,7 @@ in
|
|||
];
|
||||
};
|
||||
|
||||
nodes.client = {};
|
||||
nodes.client = { };
|
||||
|
||||
testScript = commonTestScript.access;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -5,12 +5,25 @@ let
|
|||
|
||||
commonTestScript = lib.shb.accessScript {
|
||||
hasSSL = { node, ... }: !(isNull node.config.shb.immich.ssl);
|
||||
waitForServices = { ... }: [ "immich-server.service" "postgresql.service" "nginx.service" ];
|
||||
waitForPorts = { ... }: [ 2283 80 ];
|
||||
waitForServices =
|
||||
{ ... }:
|
||||
[
|
||||
"immich-server.service"
|
||||
"postgresql.service"
|
||||
"nginx.service"
|
||||
];
|
||||
waitForPorts =
|
||||
{ ... }:
|
||||
[
|
||||
2283
|
||||
80
|
||||
];
|
||||
waitForUrls = { proto_fqdn, ... }: [ "${proto_fqdn}" ];
|
||||
};
|
||||
|
||||
base = { config, ... }: {
|
||||
base =
|
||||
{ config, ... }:
|
||||
{
|
||||
imports = [
|
||||
lib.shb.baseModule
|
||||
../../modules/services/immich.nix
|
||||
|
|
@ -34,13 +47,17 @@ let
|
|||
environment.systemPackages = [ pkgs.curl ];
|
||||
};
|
||||
|
||||
basic = { config, ... }: {
|
||||
basic =
|
||||
{ config, ... }:
|
||||
{
|
||||
imports = [ base ];
|
||||
|
||||
test.hasSSL = false;
|
||||
};
|
||||
|
||||
https = { config, ... }: {
|
||||
https =
|
||||
{ config, ... }:
|
||||
{
|
||||
imports = [
|
||||
base
|
||||
lib.shb.certs
|
||||
|
|
@ -50,14 +67,18 @@ let
|
|||
shb.immich.ssl = config.shb.certs.certs.selfsigned.n;
|
||||
};
|
||||
|
||||
backup = { config, ... }: {
|
||||
backup =
|
||||
{ config, ... }:
|
||||
{
|
||||
imports = [
|
||||
https
|
||||
(lib.shb.backup config.shb.immich.backup)
|
||||
];
|
||||
};
|
||||
|
||||
sso = { config, ... }: {
|
||||
sso =
|
||||
{ config, ... }:
|
||||
{
|
||||
imports = [
|
||||
https
|
||||
lib.shb.ldap
|
||||
|
|
@ -85,7 +106,7 @@ let
|
|||
};
|
||||
|
||||
# Configure LDAP groups for group-based access control
|
||||
shb.lldap.ensureGroups.immich_user = {};
|
||||
shb.lldap.ensureGroups.immich_user = { };
|
||||
|
||||
shb.lldap.ensureUsers.immich_test_user = {
|
||||
email = "immich_user@example.com";
|
||||
|
|
@ -115,7 +136,7 @@ in
|
|||
name = "immich-basic";
|
||||
|
||||
nodes.server = basic;
|
||||
nodes.client = {};
|
||||
nodes.client = { };
|
||||
|
||||
testScript = commonTestScript;
|
||||
};
|
||||
|
|
@ -124,7 +145,7 @@ in
|
|||
name = "immich-https";
|
||||
|
||||
nodes.server = https;
|
||||
nodes.client = {};
|
||||
nodes.client = { };
|
||||
|
||||
testScript = commonTestScript;
|
||||
};
|
||||
|
|
@ -133,12 +154,20 @@ in
|
|||
name = "immich-backup";
|
||||
|
||||
nodes.server = backup;
|
||||
nodes.client = {};
|
||||
nodes.client = { };
|
||||
|
||||
testScript = (lib.shb.mkScripts {
|
||||
testScript =
|
||||
(lib.shb.mkScripts {
|
||||
hasSSL = args: !(isNull args.node.config.shb.immich.ssl);
|
||||
waitForServices = args: [ "immich-server.service" "postgresql.service" "nginx.service" ];
|
||||
waitForPorts = args: [ 2283 80 ];
|
||||
waitForServices = args: [
|
||||
"immich-server.service"
|
||||
"postgresql.service"
|
||||
"nginx.service"
|
||||
];
|
||||
waitForPorts = args: [
|
||||
2283
|
||||
80
|
||||
];
|
||||
waitForUrls = args: [ "${args.proto_fqdn}" ];
|
||||
}).backup;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -4,17 +4,25 @@ let
|
|||
|
||||
commonTestScript = lib.shb.mkScripts {
|
||||
hasSSL = { node, ... }: !(isNull node.config.shb.jellyfin.ssl);
|
||||
waitForServices = { ... }: [
|
||||
waitForServices =
|
||||
{ ... }:
|
||||
[
|
||||
"jellyfin.service"
|
||||
"nginx.service"
|
||||
];
|
||||
waitForPorts = { node, ... }: [
|
||||
waitForPorts =
|
||||
{ node, ... }:
|
||||
[
|
||||
port
|
||||
];
|
||||
waitForUrls = { proto_fqdn, ... }: [
|
||||
waitForUrls =
|
||||
{ proto_fqdn, ... }:
|
||||
[
|
||||
"${proto_fqdn}/System/Info/Public"
|
||||
];
|
||||
extraScript = { node, ... }: ''
|
||||
extraScript =
|
||||
{ node, ... }:
|
||||
''
|
||||
headers = unline_with(" ", """
|
||||
-H 'Content-Type: application/json'
|
||||
-H 'Authorization: MediaBrowser Client="Android TV", Device="Nvidia Shield", DeviceId="ZQ9YQHHrUzk24vV", Version="0.15.3"'
|
||||
|
|
@ -35,7 +43,9 @@ let
|
|||
'';
|
||||
};
|
||||
|
||||
basic = { config, ... }: {
|
||||
basic =
|
||||
{ config, ... }:
|
||||
{
|
||||
imports = [
|
||||
lib.shb.baseModule
|
||||
../../modules/services/jellyfin.nix
|
||||
|
|
@ -65,7 +75,9 @@ let
|
|||
];
|
||||
};
|
||||
|
||||
clientLogin = { config, ... }: {
|
||||
clientLogin =
|
||||
{ config, ... }:
|
||||
{
|
||||
imports = [
|
||||
lib.shb.clientLoginModule
|
||||
];
|
||||
|
|
@ -103,7 +115,9 @@ let
|
|||
};
|
||||
};
|
||||
|
||||
https = { config, ... }: {
|
||||
https =
|
||||
{ config, ... }:
|
||||
{
|
||||
shb.jellyfin = {
|
||||
ssl = config.shb.certs.certs.selfsigned.n;
|
||||
};
|
||||
|
|
@ -112,7 +126,9 @@ let
|
|||
};
|
||||
};
|
||||
|
||||
ldap = { config, ... }: {
|
||||
ldap =
|
||||
{ config, ... }:
|
||||
{
|
||||
shb.jellyfin = {
|
||||
ldap = {
|
||||
enable = true;
|
||||
|
|
@ -129,7 +145,9 @@ let
|
|||
};
|
||||
};
|
||||
|
||||
sso = { config, ... }: {
|
||||
sso =
|
||||
{ config, ... }:
|
||||
{
|
||||
shb.jellyfin = {
|
||||
sso = {
|
||||
enable = true;
|
||||
|
|
@ -150,7 +168,10 @@ let
|
|||
};
|
||||
};
|
||||
|
||||
jellyfinTest = name: { nodes, testScript }: lib.shb.runNixOSTest {
|
||||
jellyfinTest =
|
||||
name:
|
||||
{ nodes, testScript }:
|
||||
lib.shb.runNixOSTest {
|
||||
name = "jellyfin_${name}";
|
||||
|
||||
interactive.nodes.server = {
|
||||
|
|
@ -174,20 +195,22 @@ in
|
|||
|
||||
# Client login does not work without SSL.
|
||||
# At least, I couldn't make it work.
|
||||
nodes.client = {};
|
||||
nodes.client = { };
|
||||
|
||||
testScript = commonTestScript.access;
|
||||
};
|
||||
|
||||
backup = jellyfinTest "backup" {
|
||||
nodes.server = { config, ... }: {
|
||||
nodes.server =
|
||||
{ config, ... }:
|
||||
{
|
||||
imports = [
|
||||
basic
|
||||
(lib.shb.backup config.shb.jellyfin.backup)
|
||||
];
|
||||
};
|
||||
|
||||
nodes.client = {};
|
||||
nodes.client = { };
|
||||
|
||||
testScript = commonTestScript.backup;
|
||||
};
|
||||
|
|
@ -201,7 +224,9 @@ in
|
|||
];
|
||||
};
|
||||
|
||||
nodes.client = { config, lib, ... }: {
|
||||
nodes.client =
|
||||
{ config, lib, ... }:
|
||||
{
|
||||
imports = [
|
||||
lib.shb.baseModule
|
||||
clientLogin
|
||||
|
|
@ -220,13 +245,15 @@ in
|
|||
];
|
||||
};
|
||||
|
||||
nodes.client = {};
|
||||
nodes.client = { };
|
||||
|
||||
testScript = commonTestScript.access;
|
||||
};
|
||||
|
||||
sso = jellyfinTest "sso" {
|
||||
nodes.server = { config, pkgs, ... }: {
|
||||
nodes.server =
|
||||
{ config, pkgs, ... }:
|
||||
{
|
||||
imports = [
|
||||
basic
|
||||
lib.shb.certs
|
||||
|
|
@ -237,7 +264,7 @@ in
|
|||
];
|
||||
};
|
||||
|
||||
nodes.client = {};
|
||||
nodes.client = { };
|
||||
|
||||
testScript = commonTestScript.access;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -3,23 +3,29 @@ let
|
|||
nextauthSecret = "nextauthSecret";
|
||||
oidcSecret = "oidcSecret";
|
||||
|
||||
testLib = pkgs.callPackage ../common.nix {};
|
||||
testLib = pkgs.callPackage ../common.nix { };
|
||||
|
||||
commonTestScript = testLib.mkScripts {
|
||||
hasSSL = { node, ... }: !(isNull node.config.shb.karakeep.ssl);
|
||||
waitForServices = { ... }: [
|
||||
waitForServices =
|
||||
{ ... }:
|
||||
[
|
||||
"karakeep-init.service"
|
||||
"karakeep-browser.service"
|
||||
"karakeep-web.service"
|
||||
"karakeep-workers.service"
|
||||
"nginx.service"
|
||||
];
|
||||
waitForPorts = { node, ... }: [
|
||||
waitForPorts =
|
||||
{ node, ... }:
|
||||
[
|
||||
node.config.shb.karakeep.port
|
||||
];
|
||||
};
|
||||
|
||||
basic = { config, ... }: {
|
||||
basic =
|
||||
{ config, ... }:
|
||||
{
|
||||
imports = [
|
||||
testLib.baseModule
|
||||
../../modules/blocks/hardcodedsecret.nix
|
||||
|
|
@ -53,13 +59,17 @@ let
|
|||
};
|
||||
};
|
||||
|
||||
https = { config, ... }: {
|
||||
https =
|
||||
{ config, ... }:
|
||||
{
|
||||
shb.karakeep = {
|
||||
ssl = config.shb.certs.certs.selfsigned.n;
|
||||
};
|
||||
};
|
||||
|
||||
ldap = { config, ... }: {
|
||||
ldap =
|
||||
{ config, ... }:
|
||||
{
|
||||
shb.karakeep = {
|
||||
ldap = {
|
||||
userGroup = "user_group";
|
||||
|
|
@ -67,7 +77,9 @@ let
|
|||
};
|
||||
};
|
||||
|
||||
clientLoginSso = { config, ... }: {
|
||||
clientLoginSso =
|
||||
{ config, ... }:
|
||||
{
|
||||
imports = [
|
||||
testLib.baseModule
|
||||
testLib.clientLoginModule
|
||||
|
|
@ -85,36 +97,62 @@ let
|
|||
passwordFieldLabelRegex = "Password";
|
||||
loginButtonNameRegex = "[sS]ign [iI]n";
|
||||
testLoginWith = [
|
||||
{ username = "alice"; password = "NotAlicePassword"; nextPageExpect = [
|
||||
{
|
||||
username = "alice";
|
||||
password = "NotAlicePassword";
|
||||
nextPageExpect = [
|
||||
"expect(page.get_by_text(re.compile('[Ii]ncorrect'))).to_be_visible(timeout=10000)"
|
||||
]; }
|
||||
{ username = "alice"; password = "AlicePassword"; nextPageExpect = [
|
||||
];
|
||||
}
|
||||
{
|
||||
username = "alice";
|
||||
password = "AlicePassword";
|
||||
nextPageExpect = [
|
||||
"page.get_by_role('button', name=re.compile('Accept')).click()"
|
||||
"expect(page.get_by_text(re.compile('[Ii]ncorrect'))).not_to_be_visible(timeout=10000)"
|
||||
"expect(page.get_by_role('button', name=re.compile('Sign In'))).not_to_be_visible()"
|
||||
"expect(page.get_by_text('new item')).to_be_visible()"
|
||||
]; }
|
||||
{ username = "bob"; password = "NotBobPassword"; nextPageExpect = [
|
||||
];
|
||||
}
|
||||
{
|
||||
username = "bob";
|
||||
password = "NotBobPassword";
|
||||
nextPageExpect = [
|
||||
"expect(page.get_by_text(re.compile('[Ii]ncorrect'))).to_be_visible(timeout=10000)"
|
||||
]; }
|
||||
{ username = "bob"; password = "BobPassword"; nextPageExpect = [
|
||||
];
|
||||
}
|
||||
{
|
||||
username = "bob";
|
||||
password = "BobPassword";
|
||||
nextPageExpect = [
|
||||
"page.get_by_role('button', name=re.compile('Accept')).click()"
|
||||
"expect(page.get_by_text(re.compile('[Ii]ncorrect'))).not_to_be_visible(timeout=10000)"
|
||||
"expect(page.get_by_role('button', name=re.compile('Sign In'))).not_to_be_visible()"
|
||||
"expect(page.get_by_text('new item')).to_be_visible()"
|
||||
]; }
|
||||
{ username = "charlie"; password = "NotCharliePassword"; nextPageExpect = [
|
||||
];
|
||||
}
|
||||
{
|
||||
username = "charlie";
|
||||
password = "NotCharliePassword";
|
||||
nextPageExpect = [
|
||||
"expect(page.get_by_text(re.compile('[Ii]ncorrect'))).to_be_visible(timeout=10000)"
|
||||
]; }
|
||||
{ username = "charlie"; password = "CharliePassword"; nextPageExpect = [
|
||||
];
|
||||
}
|
||||
{
|
||||
username = "charlie";
|
||||
password = "CharliePassword";
|
||||
nextPageExpect = [
|
||||
# "page.get_by_role('button', name=re.compile('Accept')).click()" # I don't understand why this is not needed. Maybe it keeps somewhere the previous token?
|
||||
"expect(page.get_by_text(re.compile('login failed'))).to_be_visible(timeout=10000)"
|
||||
]; }
|
||||
];
|
||||
}
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
sso = { config, ... }: {
|
||||
sso =
|
||||
{ config, ... }:
|
||||
{
|
||||
shb.karakeep = {
|
||||
sso = {
|
||||
enable = true;
|
||||
|
|
@ -140,7 +178,7 @@ in
|
|||
basic = pkgs.testers.runNixOSTest {
|
||||
name = "karakeep_basic";
|
||||
|
||||
nodes.client = {};
|
||||
nodes.client = { };
|
||||
nodes.server = {
|
||||
imports = [
|
||||
basic
|
||||
|
|
@ -153,14 +191,16 @@ in
|
|||
backup = pkgs.testers.runNixOSTest {
|
||||
name = "karakeep_backup";
|
||||
|
||||
nodes.server = { config, ... }: {
|
||||
nodes.server =
|
||||
{ config, ... }:
|
||||
{
|
||||
imports = [
|
||||
basic
|
||||
(testLib.backup config.shb.karakeep.backup)
|
||||
];
|
||||
};
|
||||
|
||||
nodes.client = {};
|
||||
nodes.client = { };
|
||||
|
||||
testScript = commonTestScript.backup;
|
||||
};
|
||||
|
|
@ -168,7 +208,7 @@ in
|
|||
https = pkgs.testers.runNixOSTest {
|
||||
name = "karakeep_https";
|
||||
|
||||
nodes.client = {};
|
||||
nodes.client = { };
|
||||
nodes.server = {
|
||||
imports = [
|
||||
basic
|
||||
|
|
@ -191,7 +231,9 @@ in
|
|||
|
||||
virtualisation.memorySize = 4096;
|
||||
};
|
||||
nodes.server = { config, pkgs, ... }: {
|
||||
nodes.server =
|
||||
{ config, pkgs, ... }:
|
||||
{
|
||||
imports = [
|
||||
basic
|
||||
testLib.certs
|
||||
|
|
|
|||
|
|
@ -4,15 +4,21 @@ let
|
|||
|
||||
commonTestScript = lib.shb.accessScript {
|
||||
hasSSL = { node, ... }: !(isNull node.config.shb.monitoring.ssl);
|
||||
waitForServices = { ... }: [
|
||||
waitForServices =
|
||||
{ ... }:
|
||||
[
|
||||
"grafana.service"
|
||||
];
|
||||
waitForPorts = { node, ... }: [
|
||||
waitForPorts =
|
||||
{ node, ... }:
|
||||
[
|
||||
node.config.shb.monitoring.grafanaPort
|
||||
];
|
||||
};
|
||||
|
||||
basic = { config, ... }: {
|
||||
basic =
|
||||
{ config, ... }:
|
||||
{
|
||||
test = {
|
||||
subdomain = "g";
|
||||
};
|
||||
|
|
@ -36,7 +42,9 @@ let
|
|||
};
|
||||
};
|
||||
|
||||
https = { config, ...}: {
|
||||
https =
|
||||
{ config, ... }:
|
||||
{
|
||||
shb.monitoring = {
|
||||
ssl = config.shb.certs.certs.selfsigned.n;
|
||||
};
|
||||
|
|
@ -54,7 +62,7 @@ in
|
|||
];
|
||||
};
|
||||
|
||||
nodes.client = {};
|
||||
nodes.client = { };
|
||||
|
||||
testScript = commonTestScript;
|
||||
};
|
||||
|
|
@ -72,7 +80,7 @@ in
|
|||
];
|
||||
};
|
||||
|
||||
nodes.client = {};
|
||||
nodes.client = { };
|
||||
|
||||
testScript = commonTestScript;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -6,14 +6,25 @@ let
|
|||
|
||||
commonTestScript = lib.shb.mkScripts {
|
||||
hasSSL = { node, ... }: !(isNull node.config.shb.nextcloud.ssl);
|
||||
waitForServices = { ... }: [
|
||||
waitForServices =
|
||||
{ ... }:
|
||||
[
|
||||
"phpfpm-nextcloud.service"
|
||||
"nginx.service"
|
||||
];
|
||||
waitForUnixSocket = { node, ... }: [
|
||||
waitForUnixSocket =
|
||||
{ node, ... }:
|
||||
[
|
||||
node.config.services.phpfpm.pools.nextcloud.socket
|
||||
];
|
||||
extraScript = { node, fqdn, proto_fqdn, ... }: ''
|
||||
extraScript =
|
||||
{
|
||||
node,
|
||||
fqdn,
|
||||
proto_fqdn,
|
||||
...
|
||||
}:
|
||||
''
|
||||
with subtest("fails with incorrect authentication"):
|
||||
client.fail(
|
||||
"curl -f -s --location -X PROPFIND"
|
||||
|
|
@ -87,7 +98,9 @@ let
|
|||
'';
|
||||
};
|
||||
|
||||
basic = { config, ... }: {
|
||||
basic =
|
||||
{ config, ... }:
|
||||
{
|
||||
imports = [
|
||||
lib.shb.baseModule
|
||||
../../modules/services/nextcloud-server.nix
|
||||
|
|
@ -119,7 +132,9 @@ let
|
|||
};
|
||||
};
|
||||
|
||||
clientLogin = { config, ... }: {
|
||||
clientLogin =
|
||||
{ config, ... }:
|
||||
{
|
||||
imports = [
|
||||
lib.shb.baseModule
|
||||
lib.shb.clientLoginModule
|
||||
|
|
@ -136,20 +151,30 @@ let
|
|||
passwordFieldLabelRegex = "^ *[Pp]assword";
|
||||
loginButtonNameRegex = "[Ll]og [Ii]n";
|
||||
testLoginWith = [
|
||||
{ username = adminUser; password = adminPass; nextPageExpect = [
|
||||
{
|
||||
username = adminUser;
|
||||
password = adminPass;
|
||||
nextPageExpect = [
|
||||
"expect(page.get_by_text('Wrong login or password')).not_to_be_visible()"
|
||||
"expect(page.get_by_role('button', name=re.compile('[Ll]og [Ii]n'))).not_to_be_visible()"
|
||||
"expect(page).to_have_title(re.compile('Dashboard'))"
|
||||
]; }
|
||||
];
|
||||
}
|
||||
# Failure is after so we're not throttled too much.
|
||||
{ username = adminUser; password = adminPass + "oops"; nextPageExpect = [
|
||||
{
|
||||
username = adminUser;
|
||||
password = adminPass + "oops";
|
||||
nextPageExpect = [
|
||||
"expect(page.get_by_text('Wrong login or password')).to_be_visible()"
|
||||
]; }
|
||||
];
|
||||
}
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
clientLdapLogin = { config, ... }: {
|
||||
clientLdapLogin =
|
||||
{ config, ... }:
|
||||
{
|
||||
imports = [
|
||||
lib.shb.baseModule
|
||||
lib.shb.clientLoginModule
|
||||
|
|
@ -166,27 +191,45 @@ let
|
|||
passwordFieldLabelRegex = "^ *[Pp]assword";
|
||||
loginButtonNameRegex = "[Ll]og [Ii]n";
|
||||
testLoginWith = [
|
||||
{ username = "alice"; password = "AlicePassword"; nextPageExpect = [
|
||||
{
|
||||
username = "alice";
|
||||
password = "AlicePassword";
|
||||
nextPageExpect = [
|
||||
"expect(page.get_by_text('Wrong login or password')).not_to_be_visible()"
|
||||
"expect(page.get_by_role('button', name=re.compile('[Ll]og [Ii]n'))).not_to_be_visible()"
|
||||
"expect(page).to_have_title(re.compile('Dashboard'))"
|
||||
]; }
|
||||
{ username = "alice"; password = "NotAlicePassword"; nextPageExpect = [
|
||||
];
|
||||
}
|
||||
{
|
||||
username = "alice";
|
||||
password = "NotAlicePassword";
|
||||
nextPageExpect = [
|
||||
"expect(page.get_by_text('Wrong login or password')).to_be_visible()"
|
||||
]; }
|
||||
{ username = "bob"; password = "BobPassword"; nextPageExpect = [
|
||||
];
|
||||
}
|
||||
{
|
||||
username = "bob";
|
||||
password = "BobPassword";
|
||||
nextPageExpect = [
|
||||
"expect(page.get_by_text('Wrong login or password')).not_to_be_visible()"
|
||||
"expect(page.get_by_role('button', name=re.compile('[Ll]og [Ii]n'))).not_to_be_visible()"
|
||||
"expect(page).to_have_title(re.compile('Dashboard'))"
|
||||
]; }
|
||||
{ username = "bob"; password = "NotBobPassword"; nextPageExpect = [
|
||||
];
|
||||
}
|
||||
{
|
||||
username = "bob";
|
||||
password = "NotBobPassword";
|
||||
nextPageExpect = [
|
||||
"expect(page.get_by_text('Wrong login or password')).to_be_visible()"
|
||||
]; }
|
||||
];
|
||||
}
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
clientSsoLogin = { config, ... }: {
|
||||
clientSsoLogin =
|
||||
{ config, ... }:
|
||||
{
|
||||
imports = [
|
||||
lib.shb.baseModule
|
||||
lib.shb.clientLoginModule
|
||||
|
|
@ -211,36 +254,62 @@ let
|
|||
passwordFieldSelector = "get_by_label(\"Password *\")";
|
||||
loginButtonNameRegex = "[sS]ign [iI]n";
|
||||
testLoginWith = [
|
||||
{ username = "alice"; password = "AlicePassword"; nextPageExpect = [
|
||||
{
|
||||
username = "alice";
|
||||
password = "AlicePassword";
|
||||
nextPageExpect = [
|
||||
"page.get_by_role('button', name=re.compile('Accept')).click()"
|
||||
"expect(page).to_have_title(re.compile('Dashboard'))"
|
||||
"page.goto('https://${config.test.fqdn}/settings/admin')"
|
||||
"expect(page.get_by_text('Access forbidden')).to_be_visible()"
|
||||
]; }
|
||||
{ username = "alice"; password = "NotAlicePassword"; nextPageExpect = [
|
||||
];
|
||||
}
|
||||
{
|
||||
username = "alice";
|
||||
password = "NotAlicePassword";
|
||||
nextPageExpect = [
|
||||
"expect(page.get_by_text('Incorrect username or password')).to_be_visible()"
|
||||
]; }
|
||||
{ username = "bob"; password = "BobPassword"; nextPageExpect = [
|
||||
];
|
||||
}
|
||||
{
|
||||
username = "bob";
|
||||
password = "BobPassword";
|
||||
nextPageExpect = [
|
||||
"page.get_by_role('button', name=re.compile('Accept')).click()"
|
||||
"expect(page).to_have_title(re.compile('Dashboard'))"
|
||||
"page.goto('https://${config.test.fqdn}/settings/admin')"
|
||||
"expect(page.get_by_text('Access forbidden')).not_to_be_visible()"
|
||||
]; }
|
||||
{ username = "bob"; password = "NotBobPassword"; nextPageExpect = [
|
||||
];
|
||||
}
|
||||
{
|
||||
username = "bob";
|
||||
password = "NotBobPassword";
|
||||
nextPageExpect = [
|
||||
"expect(page.get_by_text('Incorrect username or password')).to_be_visible()"
|
||||
]; }
|
||||
{ username = "charlie"; password = "NotCharliePassword"; nextPageExpect = [
|
||||
];
|
||||
}
|
||||
{
|
||||
username = "charlie";
|
||||
password = "NotCharliePassword";
|
||||
nextPageExpect = [
|
||||
"expect(page.get_by_text('Incorrect username or password')).to_be_visible()"
|
||||
]; }
|
||||
{ username = "charlie"; password = "CharliePassword"; nextPageExpect = [
|
||||
];
|
||||
}
|
||||
{
|
||||
username = "charlie";
|
||||
password = "CharliePassword";
|
||||
nextPageExpect = [
|
||||
"page.get_by_role('button', name=re.compile('Accept')).click()"
|
||||
"expect(page.get_by_text('not member of the allowed groups')).to_be_visible()"
|
||||
]; }
|
||||
];
|
||||
}
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
https = { config, ...}: {
|
||||
https =
|
||||
{ config, ... }:
|
||||
{
|
||||
shb.nextcloud = {
|
||||
ssl = config.shb.certs.certs.selfsigned.n;
|
||||
|
||||
|
|
@ -248,7 +317,9 @@ let
|
|||
};
|
||||
};
|
||||
|
||||
ldap = { config, ... }: {
|
||||
ldap =
|
||||
{ config, ... }:
|
||||
{
|
||||
shb.nextcloud = {
|
||||
apps.ldap = {
|
||||
enable = true;
|
||||
|
|
@ -266,7 +337,8 @@ let
|
|||
};
|
||||
};
|
||||
|
||||
sso = { config, ... }:
|
||||
sso =
|
||||
{ config, ... }:
|
||||
{
|
||||
shb.nextcloud = {
|
||||
apps.ldap = {
|
||||
|
|
@ -299,7 +371,9 @@ let
|
|||
};
|
||||
};
|
||||
|
||||
previewgenerator = { config, ...}: {
|
||||
previewgenerator =
|
||||
{ config, ... }:
|
||||
{
|
||||
systemd.tmpfiles.rules = [
|
||||
"d '/srv/nextcloud' 0750 nextcloud nextcloud - -"
|
||||
];
|
||||
|
|
@ -323,7 +397,9 @@ let
|
|||
};
|
||||
};
|
||||
|
||||
memories = { config, ...}: {
|
||||
memories =
|
||||
{ config, ... }:
|
||||
{
|
||||
systemd.tmpfiles.rules = [
|
||||
"d '/srv/nextcloud' 0750 nextcloud nextcloud - -"
|
||||
];
|
||||
|
|
@ -334,7 +410,9 @@ let
|
|||
};
|
||||
};
|
||||
|
||||
recognize = { config, ...}: {
|
||||
recognize =
|
||||
{ config, ... }:
|
||||
{
|
||||
systemd.tmpfiles.rules = [
|
||||
"d '/srv/nextcloud' 0750 nextcloud nextcloud - -"
|
||||
];
|
||||
|
|
@ -344,13 +422,16 @@ let
|
|||
};
|
||||
};
|
||||
|
||||
prometheus = { config, ... }: {
|
||||
prometheus =
|
||||
{ config, ... }:
|
||||
{
|
||||
shb.nextcloud = {
|
||||
phpFpmPrometheusExporter.enable = true;
|
||||
};
|
||||
};
|
||||
|
||||
prometheusTestScript = { nodes, ... }:
|
||||
prometheusTestScript =
|
||||
{ nodes, ... }:
|
||||
''
|
||||
server.wait_for_open_unix_socket("${nodes.server.services.phpfpm.pools.nextcloud.socket}")
|
||||
server.wait_for_open_port(${toString nodes.server.services.prometheus.exporters.php-fpm.port})
|
||||
|
|
@ -389,10 +470,17 @@ in
|
|||
];
|
||||
};
|
||||
|
||||
nodes.client = {};
|
||||
nodes.client = { };
|
||||
|
||||
testScript = commonTestScript.access.override {
|
||||
extraScript = { node, fqdn, proto_fqdn, ... }: ''
|
||||
extraScript =
|
||||
{
|
||||
node,
|
||||
fqdn,
|
||||
proto_fqdn,
|
||||
...
|
||||
}:
|
||||
''
|
||||
import time
|
||||
|
||||
def find_in_logs(unit, text):
|
||||
|
|
@ -419,14 +507,16 @@ in
|
|||
backup = lib.shb.runNixOSTest {
|
||||
name = "nextcloud_backup";
|
||||
|
||||
nodes.server = { config, ... }: {
|
||||
nodes.server =
|
||||
{ config, ... }:
|
||||
{
|
||||
imports = [
|
||||
basic
|
||||
(lib.shb.backup config.shb.nextcloud.backup)
|
||||
];
|
||||
};
|
||||
|
||||
nodes.client = {};
|
||||
nodes.client = { };
|
||||
|
||||
testScript = commonTestScript.backup;
|
||||
};
|
||||
|
|
@ -442,7 +532,7 @@ in
|
|||
];
|
||||
};
|
||||
|
||||
nodes.client = {};
|
||||
nodes.client = { };
|
||||
|
||||
# TODO: Test login
|
||||
testScript = commonTestScript.access;
|
||||
|
|
@ -460,7 +550,7 @@ in
|
|||
];
|
||||
};
|
||||
|
||||
nodes.client = {};
|
||||
nodes.client = { };
|
||||
|
||||
testScript = commonTestScript.access;
|
||||
};
|
||||
|
|
@ -477,7 +567,7 @@ in
|
|||
];
|
||||
};
|
||||
|
||||
nodes.client = {};
|
||||
nodes.client = { };
|
||||
|
||||
testScript = commonTestScript.access;
|
||||
};
|
||||
|
|
@ -514,7 +604,7 @@ in
|
|||
];
|
||||
};
|
||||
|
||||
nodes.client = {};
|
||||
nodes.client = { };
|
||||
|
||||
testScript = commonTestScript.access;
|
||||
};
|
||||
|
|
@ -522,7 +612,9 @@ in
|
|||
ldap = lib.shb.runNixOSTest {
|
||||
name = "nextcloud_ldap";
|
||||
|
||||
nodes.server = { config, ... }: {
|
||||
nodes.server =
|
||||
{ config, ... }:
|
||||
{
|
||||
imports = [
|
||||
basic
|
||||
lib.shb.certs
|
||||
|
|
@ -544,7 +636,9 @@ in
|
|||
sso = lib.shb.runNixOSTest {
|
||||
name = "nextcloud_sso";
|
||||
|
||||
nodes.server = { config, ... }: {
|
||||
nodes.server =
|
||||
{ config, ... }:
|
||||
{
|
||||
imports = [
|
||||
basic
|
||||
lib.shb.certs
|
||||
|
|
@ -552,22 +646,28 @@ in
|
|||
lib.shb.ldap
|
||||
(lib.shb.sso config.shb.certs.certs.selfsigned.n)
|
||||
sso
|
||||
({ config, ... }: {
|
||||
(
|
||||
{ config, ... }:
|
||||
{
|
||||
networking.hosts = {
|
||||
"127.0.0.1" = [ config.test.fqdn ];
|
||||
};
|
||||
})
|
||||
}
|
||||
)
|
||||
];
|
||||
};
|
||||
|
||||
nodes.client = {
|
||||
imports = [
|
||||
clientSsoLogin
|
||||
({ config, ... }: {
|
||||
(
|
||||
{ config, ... }:
|
||||
{
|
||||
networking.hosts = {
|
||||
"192.168.1.2" = [ config.test.fqdn ];
|
||||
};
|
||||
})
|
||||
}
|
||||
)
|
||||
];
|
||||
};
|
||||
|
||||
|
|
@ -577,14 +677,16 @@ in
|
|||
prometheus = lib.shb.runNixOSTest {
|
||||
name = "nextcloud_prometheus";
|
||||
|
||||
nodes.server = { config, ... }: {
|
||||
nodes.server =
|
||||
{ config, ... }:
|
||||
{
|
||||
imports = [
|
||||
basic
|
||||
prometheus
|
||||
];
|
||||
};
|
||||
|
||||
nodes.client = {};
|
||||
nodes.client = { };
|
||||
|
||||
testScript = prometheusTestScript;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -4,16 +4,22 @@ let
|
|||
|
||||
commonTestScript = lib.shb.mkScripts {
|
||||
hasSSL = { node, ... }: !(isNull node.config.shb.open-webui.ssl);
|
||||
waitForServices = { ... }: [
|
||||
waitForServices =
|
||||
{ ... }:
|
||||
[
|
||||
"open-webui.service"
|
||||
"nginx.service"
|
||||
];
|
||||
waitForPorts = { node, ... }: [
|
||||
waitForPorts =
|
||||
{ node, ... }:
|
||||
[
|
||||
node.config.shb.open-webui.port
|
||||
];
|
||||
};
|
||||
|
||||
basic = { config, ... }: {
|
||||
basic =
|
||||
{ config, ... }:
|
||||
{
|
||||
imports = [
|
||||
lib.shb.baseModule
|
||||
../../modules/blocks/hardcodedsecret.nix
|
||||
|
|
@ -34,7 +40,9 @@ let
|
|||
};
|
||||
};
|
||||
|
||||
https = { config, ... }: {
|
||||
https =
|
||||
{ config, ... }:
|
||||
{
|
||||
shb.open-webui = {
|
||||
ssl = config.shb.certs.certs.selfsigned.n;
|
||||
};
|
||||
|
|
@ -45,7 +53,9 @@ let
|
|||
};
|
||||
};
|
||||
|
||||
ldap = { config, ... }: {
|
||||
ldap =
|
||||
{ config, ... }:
|
||||
{
|
||||
shb.open-webui = {
|
||||
ldap = {
|
||||
userGroup = "user_group";
|
||||
|
|
@ -54,7 +64,9 @@ let
|
|||
};
|
||||
};
|
||||
|
||||
clientLoginSso = { config, ... }: {
|
||||
clientLoginSso =
|
||||
{ config, ... }:
|
||||
{
|
||||
imports = [
|
||||
lib.shb.baseModule
|
||||
lib.shb.clientLoginModule
|
||||
|
|
@ -73,36 +85,62 @@ let
|
|||
passwordFieldLabelRegex = "Password";
|
||||
loginButtonNameRegex = "[sS]ign [iI]n";
|
||||
testLoginWith = [
|
||||
{ username = "alice"; password = "NotAlicePassword"; nextPageExpect = [
|
||||
{
|
||||
username = "alice";
|
||||
password = "NotAlicePassword";
|
||||
nextPageExpect = [
|
||||
"expect(page.get_by_text(re.compile('[Ii]ncorrect'))).to_be_visible()"
|
||||
]; }
|
||||
{ username = "alice"; password = "AlicePassword"; nextPageExpect = [
|
||||
];
|
||||
}
|
||||
{
|
||||
username = "alice";
|
||||
password = "AlicePassword";
|
||||
nextPageExpect = [
|
||||
"page.get_by_role('button', name=re.compile('Accept')).click()"
|
||||
"expect(page.get_by_text(re.compile('[Ii]ncorrect'))).not_to_be_visible()"
|
||||
"expect(page.get_by_role('button', name=re.compile('Sign In'))).not_to_be_visible()"
|
||||
"expect(page.get_by_text('logged in')).to_be_visible()"
|
||||
]; }
|
||||
{ username = "bob"; password = "NotBobPassword"; nextPageExpect = [
|
||||
];
|
||||
}
|
||||
{
|
||||
username = "bob";
|
||||
password = "NotBobPassword";
|
||||
nextPageExpect = [
|
||||
"expect(page.get_by_text(re.compile('[Ii]ncorrect'))).to_be_visible()"
|
||||
]; }
|
||||
{ username = "bob"; password = "BobPassword"; nextPageExpect = [
|
||||
];
|
||||
}
|
||||
{
|
||||
username = "bob";
|
||||
password = "BobPassword";
|
||||
nextPageExpect = [
|
||||
"page.get_by_role('button', name=re.compile('Accept')).click()"
|
||||
"expect(page.get_by_text(re.compile('[Ii]ncorrect'))).not_to_be_visible()"
|
||||
"expect(page.get_by_role('button', name=re.compile('Sign In'))).not_to_be_visible()"
|
||||
"expect(page.get_by_text('logged in')).to_be_visible()"
|
||||
]; }
|
||||
{ username = "charlie"; password = "NotCharliePassword"; nextPageExpect = [
|
||||
];
|
||||
}
|
||||
{
|
||||
username = "charlie";
|
||||
password = "NotCharliePassword";
|
||||
nextPageExpect = [
|
||||
"expect(page.get_by_text(re.compile('[Ii]ncorrect'))).to_be_visible()"
|
||||
]; }
|
||||
{ username = "charlie"; password = "CharliePassword"; nextPageExpect = [
|
||||
];
|
||||
}
|
||||
{
|
||||
username = "charlie";
|
||||
password = "CharliePassword";
|
||||
nextPageExpect = [
|
||||
"page.get_by_role('button', name=re.compile('Accept')).click()"
|
||||
"expect(page.get_by_text('unauthorized')).to_be_visible()"
|
||||
]; }
|
||||
];
|
||||
}
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
sso = { config, ... }: {
|
||||
sso =
|
||||
{ config, ... }:
|
||||
{
|
||||
shb.open-webui = {
|
||||
sso = {
|
||||
enable = true;
|
||||
|
|
@ -128,7 +166,7 @@ in
|
|||
basic = lib.shb.runNixOSTest {
|
||||
name = "open-webui_basic";
|
||||
|
||||
nodes.client = {};
|
||||
nodes.client = { };
|
||||
nodes.server = {
|
||||
imports = [
|
||||
basic
|
||||
|
|
@ -141,14 +179,16 @@ in
|
|||
backup = lib.shb.runNixOSTest {
|
||||
name = "open-webui_backup";
|
||||
|
||||
nodes.server = { config, ... }: {
|
||||
nodes.server =
|
||||
{ config, ... }:
|
||||
{
|
||||
imports = [
|
||||
basic
|
||||
(lib.shb.backup config.shb.open-webui.backup)
|
||||
];
|
||||
};
|
||||
|
||||
nodes.client = {};
|
||||
nodes.client = { };
|
||||
|
||||
testScript = commonTestScript.backup;
|
||||
};
|
||||
|
|
@ -156,7 +196,7 @@ in
|
|||
https = lib.shb.runNixOSTest {
|
||||
name = "open-webui_https";
|
||||
|
||||
nodes.client = {};
|
||||
nodes.client = { };
|
||||
nodes.server = {
|
||||
imports = [
|
||||
basic
|
||||
|
|
@ -176,7 +216,9 @@ in
|
|||
clientLoginSso
|
||||
];
|
||||
};
|
||||
nodes.server = { config, pkgs, ... }: {
|
||||
nodes.server =
|
||||
{ config, pkgs, ... }:
|
||||
{
|
||||
imports = [
|
||||
basic
|
||||
lib.shb.certs
|
||||
|
|
|
|||
|
|
@ -2,16 +2,22 @@
|
|||
let
|
||||
commonTestScript = lib.shb.mkScripts {
|
||||
hasSSL = { node, ... }: !(isNull node.config.shb.pinchflat.ssl);
|
||||
waitForServices = { ... }: [
|
||||
waitForServices =
|
||||
{ ... }:
|
||||
[
|
||||
"pinchflat.service"
|
||||
"nginx.service"
|
||||
];
|
||||
waitForPorts = { node, ... }: [
|
||||
waitForPorts =
|
||||
{ node, ... }:
|
||||
[
|
||||
node.config.shb.pinchflat.port
|
||||
];
|
||||
};
|
||||
|
||||
basic = { config, ... }: {
|
||||
basic =
|
||||
{ config, ... }:
|
||||
{
|
||||
imports = [
|
||||
lib.shb.baseModule
|
||||
../../modules/blocks/hardcodedsecret.nix
|
||||
|
|
@ -45,7 +51,9 @@ let
|
|||
};
|
||||
};
|
||||
|
||||
clientLogin = { config, ... }: {
|
||||
clientLogin =
|
||||
{ config, ... }:
|
||||
{
|
||||
imports = [
|
||||
lib.shb.baseModule
|
||||
lib.shb.clientLoginModule
|
||||
|
|
@ -58,20 +66,28 @@ let
|
|||
startUrl = "http://${config.test.fqdn}";
|
||||
# There is no login without SSO integration.
|
||||
testLoginWith = [
|
||||
{ username = null; password = null; nextPageExpect = [
|
||||
{
|
||||
username = null;
|
||||
password = null;
|
||||
nextPageExpect = [
|
||||
"expect(page.get_by_text('Create a media profile')).to_be_visible()"
|
||||
]; }
|
||||
];
|
||||
}
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
https = { config, ... }: {
|
||||
https =
|
||||
{ config, ... }:
|
||||
{
|
||||
shb.pinchflat = {
|
||||
ssl = config.shb.certs.certs.selfsigned.n;
|
||||
};
|
||||
};
|
||||
|
||||
ldap = { config, ... }: {
|
||||
ldap =
|
||||
{ config, ... }:
|
||||
{
|
||||
shb.pinchflat = {
|
||||
ldap = {
|
||||
enable = true;
|
||||
|
|
@ -81,7 +97,9 @@ let
|
|||
};
|
||||
};
|
||||
|
||||
clientLoginSso = { config, ... }: {
|
||||
clientLoginSso =
|
||||
{ config, ... }:
|
||||
{
|
||||
imports = [
|
||||
lib.shb.baseModule
|
||||
lib.shb.clientLoginModule
|
||||
|
|
@ -96,33 +114,59 @@ let
|
|||
passwordFieldLabelRegex = "Password";
|
||||
loginButtonNameRegex = "[sS]ign [iI]n";
|
||||
testLoginWith = [
|
||||
{ username = "alice"; password = "NotAlicePassword"; nextPageExpect = [
|
||||
{
|
||||
username = "alice";
|
||||
password = "NotAlicePassword";
|
||||
nextPageExpect = [
|
||||
"expect(page.get_by_text(re.compile('[Ii]ncorrect'))).to_be_visible()"
|
||||
]; }
|
||||
{ username = "alice"; password = "AlicePassword"; nextPageExpect = [
|
||||
];
|
||||
}
|
||||
{
|
||||
username = "alice";
|
||||
password = "AlicePassword";
|
||||
nextPageExpect = [
|
||||
"expect(page.get_by_text(re.compile('[Ii]ncorrect'))).not_to_be_visible()"
|
||||
"expect(page.get_by_role('button', name=re.compile('Sign In'))).not_to_be_visible()"
|
||||
"expect(page.get_by_text('Create a media profile')).to_be_visible()"
|
||||
]; }
|
||||
{ username = "bob"; password = "NotBobPassword"; nextPageExpect = [
|
||||
];
|
||||
}
|
||||
{
|
||||
username = "bob";
|
||||
password = "NotBobPassword";
|
||||
nextPageExpect = [
|
||||
"expect(page.get_by_text(re.compile('[Ii]ncorrect'))).to_be_visible()"
|
||||
]; }
|
||||
{ username = "bob"; password = "BobPassword"; nextPageExpect = [
|
||||
];
|
||||
}
|
||||
{
|
||||
username = "bob";
|
||||
password = "BobPassword";
|
||||
nextPageExpect = [
|
||||
"expect(page.get_by_text(re.compile('[Ii]ncorrect'))).not_to_be_visible()"
|
||||
"expect(page.get_by_role('button', name=re.compile('Sign In'))).not_to_be_visible()"
|
||||
"expect(page.get_by_text('Create a media profile')).to_be_visible()"
|
||||
]; }
|
||||
{ username = "charlie"; password = "NotCharliePassword"; nextPageExpect = [
|
||||
];
|
||||
}
|
||||
{
|
||||
username = "charlie";
|
||||
password = "NotCharliePassword";
|
||||
nextPageExpect = [
|
||||
"expect(page.get_by_text(re.compile('[Ii]ncorrect'))).to_be_visible()"
|
||||
]; }
|
||||
{ username = "charlie"; password = "CharliePassword"; nextPageExpect = [
|
||||
];
|
||||
}
|
||||
{
|
||||
username = "charlie";
|
||||
password = "CharliePassword";
|
||||
nextPageExpect = [
|
||||
"expect(page).to_have_url(re.compile('.*/authenticated'))"
|
||||
]; }
|
||||
];
|
||||
}
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
sso = { config, ... }: {
|
||||
sso =
|
||||
{ config, ... }:
|
||||
{
|
||||
shb.pinchflat = {
|
||||
sso = {
|
||||
enable = true;
|
||||
|
|
@ -152,14 +196,16 @@ in
|
|||
backup = lib.shb.runNixOSTest {
|
||||
name = "pinchflat_backup";
|
||||
|
||||
nodes.server = { config, ... }: {
|
||||
nodes.server =
|
||||
{ config, ... }:
|
||||
{
|
||||
imports = [
|
||||
basic
|
||||
(lib.shb.backup config.shb.pinchflat.backup)
|
||||
];
|
||||
};
|
||||
|
||||
nodes.client = {};
|
||||
nodes.client = { };
|
||||
|
||||
testScript = commonTestScript.backup;
|
||||
};
|
||||
|
|
@ -191,7 +237,9 @@ in
|
|||
clientLoginSso
|
||||
];
|
||||
};
|
||||
nodes.server = { config, pkgs, ... }: {
|
||||
nodes.server =
|
||||
{ config, pkgs, ... }:
|
||||
{
|
||||
imports = [
|
||||
basic
|
||||
lib.shb.certs
|
||||
|
|
|
|||
|
|
@ -2,11 +2,15 @@
|
|||
let
|
||||
commonTestScript = lib.shb.mkScripts {
|
||||
hasSSL = { node, ... }: !(isNull node.config.shb.vaultwarden.ssl);
|
||||
waitForServices = { ... }: [
|
||||
waitForServices =
|
||||
{ ... }:
|
||||
[
|
||||
"vaultwarden.service"
|
||||
"nginx.service"
|
||||
];
|
||||
waitForPorts = { node, ... }: [
|
||||
waitForPorts =
|
||||
{ node, ... }:
|
||||
[
|
||||
8222
|
||||
5432
|
||||
];
|
||||
|
|
@ -17,7 +21,9 @@ let
|
|||
# 4. go to the Vaultwarden /admin endpoint
|
||||
# 5. create a Vaultwarden user
|
||||
# 6. now login with that new user to Vaultwarden
|
||||
extraScript = { node, proto_fqdn, ... }: ''
|
||||
extraScript =
|
||||
{ node, proto_fqdn, ... }:
|
||||
''
|
||||
with subtest("prelogin"):
|
||||
response = curl(client, "", "${proto_fqdn}/identity/accounts/prelogin", data=unline_with("", """
|
||||
{"email": "me@example.com"}
|
||||
|
|
@ -42,7 +48,9 @@ let
|
|||
'';
|
||||
};
|
||||
|
||||
basic = { config, ... }: {
|
||||
basic =
|
||||
{ config, ... }:
|
||||
{
|
||||
test = {
|
||||
subdomain = "v";
|
||||
};
|
||||
|
|
@ -64,7 +72,9 @@ let
|
|||
# };
|
||||
};
|
||||
|
||||
https = { config, ... }: {
|
||||
https =
|
||||
{ config, ... }:
|
||||
{
|
||||
shb.vaultwarden = {
|
||||
ssl = config.shb.certs.certs.selfsigned.n;
|
||||
};
|
||||
|
|
@ -78,7 +88,9 @@ let
|
|||
# # };
|
||||
# };
|
||||
|
||||
sso = { config, ... }: {
|
||||
sso =
|
||||
{ config, ... }:
|
||||
{
|
||||
shb.vaultwarden = {
|
||||
authEndpoint = "https://${config.shb.authelia.subdomain}.${config.shb.authelia.domain}";
|
||||
};
|
||||
|
|
@ -97,7 +109,7 @@ in
|
|||
];
|
||||
};
|
||||
|
||||
nodes.client = {};
|
||||
nodes.client = { };
|
||||
|
||||
testScript = commonTestScript.access;
|
||||
};
|
||||
|
|
@ -116,7 +128,7 @@ in
|
|||
];
|
||||
};
|
||||
|
||||
nodes.client = {};
|
||||
nodes.client = { };
|
||||
|
||||
testScript = commonTestScript.access;
|
||||
};
|
||||
|
|
@ -142,7 +154,9 @@ in
|
|||
sso = lib.shb.runNixOSTest {
|
||||
name = "vaultwarden_sso";
|
||||
|
||||
nodes.server = { config, ... }: {
|
||||
nodes.server =
|
||||
{ config, ... }:
|
||||
{
|
||||
imports = [
|
||||
lib.shb.baseModule
|
||||
../../modules/blocks/hardcodedsecret.nix
|
||||
|
|
@ -156,15 +170,19 @@ in
|
|||
];
|
||||
};
|
||||
|
||||
nodes.client = {};
|
||||
nodes.client = { };
|
||||
|
||||
testScript = commonTestScript.access.override {
|
||||
waitForPorts = { node, ... }: [
|
||||
waitForPorts =
|
||||
{ node, ... }:
|
||||
[
|
||||
8222
|
||||
5432
|
||||
9091
|
||||
];
|
||||
extraScript = { node, proto_fqdn, ... }: ''
|
||||
extraScript =
|
||||
{ node, proto_fqdn, ... }:
|
||||
''
|
||||
with subtest("unauthenticated access is not granted to /admin"):
|
||||
response = curl(client, """{"code":%{response_code},"auth_host":"%{urle.host}","auth_query":"%{urle.query}","all":%{json}}""", "${proto_fqdn}/admin")
|
||||
|
||||
|
|
@ -181,7 +199,9 @@ in
|
|||
backup = lib.shb.runNixOSTest {
|
||||
name = "vaultwarden_backup";
|
||||
|
||||
nodes.server = { config, ... }: {
|
||||
nodes.server =
|
||||
{ config, ... }:
|
||||
{
|
||||
imports = [
|
||||
lib.shb.baseModule
|
||||
../../modules/blocks/hardcodedsecret.nix
|
||||
|
|
@ -191,7 +211,7 @@ in
|
|||
];
|
||||
};
|
||||
|
||||
nodes.client = {};
|
||||
nodes.client = { };
|
||||
|
||||
testScript = commonTestScript.backup;
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in a new issue