chore: format all files
This commit is contained in:
parent
16fcb94897
commit
48802553de
77 changed files with 10265 additions and 8177 deletions
|
|
@ -5,8 +5,8 @@ let
|
||||||
targetPort = 2222;
|
targetPort = 2222;
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
imports =
|
imports = [
|
||||||
[ # Include the results of the hardware scan.
|
# Include the results of the hardware scan.
|
||||||
./hardware-configuration.nix
|
./hardware-configuration.nix
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
@ -26,7 +26,10 @@ in
|
||||||
|
|
||||||
# Options above are needed to deploy in a VM.
|
# 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.
|
# We need to create the user we will deploy with.
|
||||||
users.users.${targetUser} = {
|
users.users.${targetUser} = {
|
||||||
|
|
@ -41,9 +44,11 @@ in
|
||||||
|
|
||||||
# The user we're deploying with must be able to run sudo without password.
|
# The user we're deploying with must be able to run sudo without password.
|
||||||
security.sudo.extraRules = [
|
security.sudo.extraRules = [
|
||||||
{ users = [ targetUser ];
|
{
|
||||||
|
users = [ targetUser ];
|
||||||
commands = [
|
commands = [
|
||||||
{ command = "ALL";
|
{
|
||||||
|
command = "ALL";
|
||||||
options = [ "NOPASSWD" ];
|
options = [ "NOPASSWD" ];
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
|
||||||
|
|
@ -6,13 +6,20 @@
|
||||||
sops-nix.url = "github:Mic92/sops-nix";
|
sops-nix.url = "github:Mic92/sops-nix";
|
||||||
};
|
};
|
||||||
|
|
||||||
outputs = inputs@{ self, selfhostblocks, sops-nix }:
|
outputs =
|
||||||
|
inputs@{
|
||||||
|
self,
|
||||||
|
selfhostblocks,
|
||||||
|
sops-nix,
|
||||||
|
}:
|
||||||
let
|
let
|
||||||
system = "x86_64-linux";
|
system = "x86_64-linux";
|
||||||
nixpkgs' = selfhostblocks.lib.${system}.patchedNixpkgs;
|
nixpkgs' = selfhostblocks.lib.${system}.patchedNixpkgs;
|
||||||
inherit (selfhostblocks.lib.${system}) pkgs;
|
inherit (selfhostblocks.lib.${system}) pkgs;
|
||||||
|
|
||||||
basic = { config, ... }: {
|
basic =
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
imports = [
|
imports = [
|
||||||
./configuration.nix
|
./configuration.nix
|
||||||
selfhostblocks.nixosModules.authelia
|
selfhostblocks.nixosModules.authelia
|
||||||
|
|
@ -67,7 +74,9 @@
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
ldap = { config, ... }: {
|
ldap =
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
shb.lldap = {
|
shb.lldap = {
|
||||||
enable = true;
|
enable = true;
|
||||||
domain = "example.com";
|
domain = "example.com";
|
||||||
|
|
@ -122,7 +131,9 @@
|
||||||
specialArgs = inputs;
|
specialArgs = inputs;
|
||||||
};
|
};
|
||||||
|
|
||||||
basic = { config, ... }: {
|
basic =
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
imports = [
|
imports = [
|
||||||
basic
|
basic
|
||||||
];
|
];
|
||||||
|
|
@ -135,7 +146,9 @@
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
ldap = { config, ... }: {
|
ldap =
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
imports = [
|
imports = [
|
||||||
basic
|
basic
|
||||||
ldap
|
ldap
|
||||||
|
|
|
||||||
|
|
@ -3,50 +3,63 @@
|
||||||
# Do not modify this file! It was generated by ‘nixos-generate-config’
|
# Do not modify this file! It was generated by ‘nixos-generate-config’
|
||||||
# and may be overwritten by future invocations. Please make changes
|
# and may be overwritten by future invocations. Please make changes
|
||||||
# to /etc/nixos/configuration.nix instead.
|
# to /etc/nixos/configuration.nix instead.
|
||||||
{ config, lib, pkgs, modulesPath, ... }:
|
{
|
||||||
|
config,
|
||||||
|
lib,
|
||||||
|
pkgs,
|
||||||
|
modulesPath,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
|
||||||
{
|
{
|
||||||
imports =
|
imports = [
|
||||||
[ (modulesPath + "/profiles/qemu-guest.nix")
|
(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.initrd.kernelModules = [ ];
|
||||||
boot.kernelModules = [ "kvm-intel" ];
|
boot.kernelModules = [ "kvm-intel" ];
|
||||||
boot.extraModulePackages = [ ];
|
boot.extraModulePackages = [ ];
|
||||||
|
|
||||||
fileSystems."/" =
|
fileSystems."/" = {
|
||||||
{ device = "/dev/vda";
|
device = "/dev/vda";
|
||||||
fsType = "ext4";
|
fsType = "ext4";
|
||||||
};
|
};
|
||||||
|
|
||||||
fileSystems."/nix/.ro-store" =
|
fileSystems."/nix/.ro-store" = {
|
||||||
{ device = "nix-store";
|
device = "nix-store";
|
||||||
fsType = "9p";
|
fsType = "9p";
|
||||||
};
|
};
|
||||||
|
|
||||||
fileSystems."/nix/.rw-store" =
|
fileSystems."/nix/.rw-store" = {
|
||||||
{ device = "tmpfs";
|
device = "tmpfs";
|
||||||
fsType = "tmpfs";
|
fsType = "tmpfs";
|
||||||
};
|
};
|
||||||
|
|
||||||
fileSystems."/tmp/shared" =
|
fileSystems."/tmp/shared" = {
|
||||||
{ device = "shared";
|
device = "shared";
|
||||||
fsType = "9p";
|
fsType = "9p";
|
||||||
};
|
};
|
||||||
|
|
||||||
fileSystems."/tmp/xchg" =
|
fileSystems."/tmp/xchg" = {
|
||||||
{ device = "xchg";
|
device = "xchg";
|
||||||
fsType = "9p";
|
fsType = "9p";
|
||||||
};
|
};
|
||||||
|
|
||||||
fileSystems."/nix/store" =
|
fileSystems."/nix/store" = {
|
||||||
{ device = "overlay";
|
device = "overlay";
|
||||||
fsType = "overlay";
|
fsType = "overlay";
|
||||||
};
|
};
|
||||||
|
|
||||||
fileSystems."/boot" =
|
fileSystems."/boot" = {
|
||||||
{ device = "/dev/vdb2";
|
device = "/dev/vdb2";
|
||||||
fsType = "vfat";
|
fsType = "vfat";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,8 @@ let
|
||||||
targetPort = 2222;
|
targetPort = 2222;
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
imports =
|
imports = [
|
||||||
[ # Include the results of the hardware scan.
|
# Include the results of the hardware scan.
|
||||||
./hardware-configuration.nix
|
./hardware-configuration.nix
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
@ -27,7 +27,10 @@ in
|
||||||
|
|
||||||
# Options above are needed to deploy in a VM.
|
# 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.
|
# We need to create the user we will deploy with.
|
||||||
users.users.${targetUser} = {
|
users.users.${targetUser} = {
|
||||||
|
|
@ -42,9 +45,11 @@ in
|
||||||
|
|
||||||
# The user we're deploying with must be able to run sudo without password.
|
# The user we're deploying with must be able to run sudo without password.
|
||||||
security.sudo.extraRules = [
|
security.sudo.extraRules = [
|
||||||
{ users = [ targetUser ];
|
{
|
||||||
|
users = [ targetUser ];
|
||||||
commands = [
|
commands = [
|
||||||
{ command = "ALL";
|
{
|
||||||
|
command = "ALL";
|
||||||
options = [ "NOPASSWD" ];
|
options = [ "NOPASSWD" ];
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
|
||||||
|
|
@ -6,13 +6,20 @@
|
||||||
sops-nix.url = "github:Mic92/sops-nix";
|
sops-nix.url = "github:Mic92/sops-nix";
|
||||||
};
|
};
|
||||||
|
|
||||||
outputs = inputs@{ self, selfhostblocks, sops-nix }:
|
outputs =
|
||||||
|
inputs@{
|
||||||
|
self,
|
||||||
|
selfhostblocks,
|
||||||
|
sops-nix,
|
||||||
|
}:
|
||||||
let
|
let
|
||||||
system = "x86_64-linux";
|
system = "x86_64-linux";
|
||||||
nixpkgs' = selfhostblocks.lib.${system}.patchedNixpkgs;
|
nixpkgs' = selfhostblocks.lib.${system}.patchedNixpkgs;
|
||||||
inherit (selfhostblocks.lib.${system}) pkgs;
|
inherit (selfhostblocks.lib.${system}) pkgs;
|
||||||
|
|
||||||
basic = { config, ... }: {
|
basic =
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
imports = [
|
imports = [
|
||||||
./configuration.nix
|
./configuration.nix
|
||||||
selfhostblocks.nixosModules.authelia
|
selfhostblocks.nixosModules.authelia
|
||||||
|
|
@ -49,7 +56,9 @@
|
||||||
shb.nginx.debugLog = false;
|
shb.nginx.debugLog = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
ldap = { config, ... }: {
|
ldap =
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
shb.lldap = {
|
shb.lldap = {
|
||||||
enable = true;
|
enable = true;
|
||||||
domain = "example.com";
|
domain = "example.com";
|
||||||
|
|
@ -78,7 +87,9 @@
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
sso = { config, lib, ... }: {
|
sso =
|
||||||
|
{ config, lib, ... }:
|
||||||
|
{
|
||||||
shb.certs = {
|
shb.certs = {
|
||||||
cas.selfsigned.myca = {
|
cas.selfsigned.myca = {
|
||||||
name = "My CA";
|
name = "My CA";
|
||||||
|
|
@ -103,8 +114,7 @@
|
||||||
domain-needed = true;
|
domain-needed = true;
|
||||||
# no-resolv = true;
|
# no-resolv = true;
|
||||||
bogus-priv = true;
|
bogus-priv = true;
|
||||||
address =
|
address = map (hostname: "/${hostname}/127.0.0.1") [
|
||||||
map (hostname: "/${hostname}/127.0.0.1") [
|
|
||||||
"example.com"
|
"example.com"
|
||||||
"n.example.com"
|
"n.example.com"
|
||||||
"ldap.example.com"
|
"ldap.example.com"
|
||||||
|
|
@ -136,10 +146,14 @@
|
||||||
request = config.shb.authelia.secrets.ldapAdminPassword.request;
|
request = config.shb.authelia.secrets.ldapAdminPassword.request;
|
||||||
settings.key = "lldap/user_password";
|
settings.key = "lldap/user_password";
|
||||||
};
|
};
|
||||||
shb.sops.secret."authelia/session_secret".request = config.shb.authelia.secrets.sessionSecret.request;
|
shb.sops.secret."authelia/session_secret".request =
|
||||||
shb.sops.secret."authelia/storage_encryption_key".request = config.shb.authelia.secrets.storageEncryptionKey.request;
|
config.shb.authelia.secrets.sessionSecret.request;
|
||||||
shb.sops.secret."authelia/hmac_secret".request = config.shb.authelia.secrets.identityProvidersOIDCHMACSecret.request;
|
shb.sops.secret."authelia/storage_encryption_key".request =
|
||||||
shb.sops.secret."authelia/private_key".request = config.shb.authelia.secrets.identityProvidersOIDCIssuerPrivateKey.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 = {
|
shb.nextcloud.apps.sso = {
|
||||||
enable = true;
|
enable = true;
|
||||||
|
|
@ -198,7 +212,9 @@
|
||||||
specialArgs = inputs;
|
specialArgs = inputs;
|
||||||
};
|
};
|
||||||
|
|
||||||
basic = { config, ... }: {
|
basic =
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
imports = [
|
imports = [
|
||||||
basic
|
basic
|
||||||
];
|
];
|
||||||
|
|
@ -210,7 +226,9 @@
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
ldap = { config, ... }: {
|
ldap =
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
imports = [
|
imports = [
|
||||||
basic
|
basic
|
||||||
ldap
|
ldap
|
||||||
|
|
@ -223,7 +241,9 @@
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
sso = { config, ... }: {
|
sso =
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
imports = [
|
imports = [
|
||||||
basic
|
basic
|
||||||
ldap
|
ldap
|
||||||
|
|
|
||||||
|
|
@ -3,50 +3,63 @@
|
||||||
# Do not modify this file! It was generated by ‘nixos-generate-config’
|
# Do not modify this file! It was generated by ‘nixos-generate-config’
|
||||||
# and may be overwritten by future invocations. Please make changes
|
# and may be overwritten by future invocations. Please make changes
|
||||||
# to /etc/nixos/configuration.nix instead.
|
# to /etc/nixos/configuration.nix instead.
|
||||||
{ config, lib, pkgs, modulesPath, ... }:
|
{
|
||||||
|
config,
|
||||||
|
lib,
|
||||||
|
pkgs,
|
||||||
|
modulesPath,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
|
||||||
{
|
{
|
||||||
imports =
|
imports = [
|
||||||
[ (modulesPath + "/profiles/qemu-guest.nix")
|
(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.initrd.kernelModules = [ ];
|
||||||
boot.kernelModules = [ "kvm-intel" ];
|
boot.kernelModules = [ "kvm-intel" ];
|
||||||
boot.extraModulePackages = [ ];
|
boot.extraModulePackages = [ ];
|
||||||
|
|
||||||
fileSystems."/" =
|
fileSystems."/" = {
|
||||||
{ device = "/dev/vda";
|
device = "/dev/vda";
|
||||||
fsType = "ext4";
|
fsType = "ext4";
|
||||||
};
|
};
|
||||||
|
|
||||||
fileSystems."/nix/.ro-store" =
|
fileSystems."/nix/.ro-store" = {
|
||||||
{ device = "nix-store";
|
device = "nix-store";
|
||||||
fsType = "9p";
|
fsType = "9p";
|
||||||
};
|
};
|
||||||
|
|
||||||
fileSystems."/nix/.rw-store" =
|
fileSystems."/nix/.rw-store" = {
|
||||||
{ device = "tmpfs";
|
device = "tmpfs";
|
||||||
fsType = "tmpfs";
|
fsType = "tmpfs";
|
||||||
};
|
};
|
||||||
|
|
||||||
fileSystems."/tmp/shared" =
|
fileSystems."/tmp/shared" = {
|
||||||
{ device = "shared";
|
device = "shared";
|
||||||
fsType = "9p";
|
fsType = "9p";
|
||||||
};
|
};
|
||||||
|
|
||||||
fileSystems."/tmp/xchg" =
|
fileSystems."/tmp/xchg" = {
|
||||||
{ device = "xchg";
|
device = "xchg";
|
||||||
fsType = "9p";
|
fsType = "9p";
|
||||||
};
|
};
|
||||||
|
|
||||||
fileSystems."/nix/store" =
|
fileSystems."/nix/store" = {
|
||||||
{ device = "overlay";
|
device = "overlay";
|
||||||
fsType = "overlay";
|
fsType = "overlay";
|
||||||
};
|
};
|
||||||
|
|
||||||
fileSystems."/boot" =
|
fileSystems."/boot" = {
|
||||||
{ device = "/dev/vdb2";
|
device = "/dev/vdb2";
|
||||||
fsType = "vfat";
|
fsType = "vfat";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
113
docs/default.nix
113
docs/default.nix
|
|
@ -1,36 +1,45 @@
|
||||||
# Taken nearly verbatim from https://github.com/nix-community/home-manager/pull/4673
|
# Taken nearly verbatim from https://github.com/nix-community/home-manager/pull/4673
|
||||||
# Read these docs online at https://shb.skarabox.com.
|
# Read these docs online at https://shb.skarabox.com.
|
||||||
{ pkgs
|
{
|
||||||
, buildPackages
|
pkgs,
|
||||||
, lib
|
buildPackages,
|
||||||
, nmdsrc
|
lib,
|
||||||
, stdenv
|
nmdsrc,
|
||||||
, documentation-highlighter
|
stdenv,
|
||||||
, nixos-render-docs
|
documentation-highlighter,
|
||||||
|
nixos-render-docs,
|
||||||
|
|
||||||
, release
|
release,
|
||||||
, allModules
|
allModules,
|
||||||
|
|
||||||
, version ? builtins.readFile ../VERSION
|
version ? builtins.readFile ../VERSION,
|
||||||
, substituteVersionIn
|
substituteVersionIn,
|
||||||
|
|
||||||
, modules
|
modules,
|
||||||
}:
|
}:
|
||||||
|
|
||||||
let
|
let
|
||||||
shbPath = toString ./..;
|
shbPath = toString ./..;
|
||||||
|
|
||||||
gitHubDeclaration = user: repo: subpath:
|
gitHubDeclaration =
|
||||||
let urlRef = "main";
|
user: repo: subpath:
|
||||||
|
let
|
||||||
|
urlRef = "main";
|
||||||
end = if subpath == "" then "" else "/" + subpath;
|
end = if subpath == "" then "" else "/" + subpath;
|
||||||
in {
|
in
|
||||||
|
{
|
||||||
url = "https://github.com/${user}/${repo}/blob/${urlRef}${end}";
|
url = "https://github.com/${user}/${repo}/blob/${urlRef}${end}";
|
||||||
name = "<${repo}${end}>";
|
name = "<${repo}${end}>";
|
||||||
};
|
};
|
||||||
|
|
||||||
ghRoot = (gitHubDeclaration "ibizaman" "selfhostblocks" "").url;
|
ghRoot = (gitHubDeclaration "ibizaman" "selfhostblocks" "").url;
|
||||||
|
|
||||||
buildOptionsDocs = { modules, filterOptionPath ? null }: args:
|
buildOptionsDocs =
|
||||||
|
{
|
||||||
|
modules,
|
||||||
|
filterOptionPath ? null,
|
||||||
|
}:
|
||||||
|
args:
|
||||||
let
|
let
|
||||||
config = {
|
config = {
|
||||||
_module.check = false;
|
_module.check = false;
|
||||||
|
|
@ -52,41 +61,56 @@ let
|
||||||
};
|
};
|
||||||
|
|
||||||
options = lib.setAttrByPath filterOptionPath (lib.getAttrFromPath filterOptionPath eval.options);
|
options = lib.setAttrByPath filterOptionPath (lib.getAttrFromPath filterOptionPath eval.options);
|
||||||
in buildPackages.nixosOptionsDoc ({
|
in
|
||||||
|
buildPackages.nixosOptionsDoc (
|
||||||
|
{
|
||||||
inherit options;
|
inherit options;
|
||||||
|
|
||||||
transformOptions = opt:
|
transformOptions =
|
||||||
opt // {
|
opt:
|
||||||
|
opt
|
||||||
|
// {
|
||||||
# Clean up declaration sites to not refer to the Home Manager
|
# Clean up declaration sites to not refer to the Home Manager
|
||||||
# source tree.
|
# source tree.
|
||||||
declarations = map (decl:
|
declarations = map (
|
||||||
gitHubDeclaration "ibizaman" "selfhostblocks"
|
decl:
|
||||||
(lib.removePrefix "/" (lib.removePrefix shbPath (toString decl)))) opt.declarations;
|
gitHubDeclaration "ibizaman" "selfhostblocks" (
|
||||||
|
lib.removePrefix "/" (lib.removePrefix shbPath (toString decl))
|
||||||
|
)
|
||||||
|
) opt.declarations;
|
||||||
};
|
};
|
||||||
} // builtins.removeAttrs args [ "includeModuleSystemOptions" ]);
|
}
|
||||||
|
// builtins.removeAttrs args [ "includeModuleSystemOptions" ]
|
||||||
|
);
|
||||||
|
|
||||||
scrubbedModule = {
|
scrubbedModule = {
|
||||||
_module.args.pkgs = lib.mkForce (nmd.scrubDerivations "pkgs" pkgs);
|
_module.args.pkgs = lib.mkForce (nmd.scrubDerivations "pkgs" pkgs);
|
||||||
_module.check = false;
|
_module.check = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
allOptionsDocs = paths: (buildOptionsDocs
|
allOptionsDocs =
|
||||||
|
paths:
|
||||||
|
(buildOptionsDocs
|
||||||
{
|
{
|
||||||
modules = paths ++ allModules ++ [ scrubbedModule ];
|
modules = paths ++ allModules ++ [ scrubbedModule ];
|
||||||
filterOptionPath = [ "shb" ];
|
filterOptionPath = [ "shb" ];
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
variablelistId = "selfhostblocks-options";
|
variablelistId = "selfhostblocks-options";
|
||||||
}).optionsJSON;
|
}
|
||||||
|
).optionsJSON;
|
||||||
|
|
||||||
individualModuleOptionsDocs = filterOptionPath: paths: (buildOptionsDocs
|
individualModuleOptionsDocs =
|
||||||
|
filterOptionPath: paths:
|
||||||
|
(buildOptionsDocs
|
||||||
{
|
{
|
||||||
modules = paths ++ [ scrubbedModule ];
|
modules = paths ++ [ scrubbedModule ];
|
||||||
inherit filterOptionPath;
|
inherit filterOptionPath;
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
variablelistId = "selfhostblocks-options";
|
variablelistId = "selfhostblocks-options";
|
||||||
}).optionsJSON;
|
}
|
||||||
|
).optionsJSON;
|
||||||
|
|
||||||
nmd = import nmdsrc {
|
nmd = import nmdsrc {
|
||||||
inherit lib;
|
inherit lib;
|
||||||
|
|
@ -94,15 +118,15 @@ let
|
||||||
# `nmd` uses to work around the broken stylesheets in
|
# `nmd` uses to work around the broken stylesheets in
|
||||||
# `docbook-xsl-ns`, so we restore the patched version here.
|
# `docbook-xsl-ns`, so we restore the patched version here.
|
||||||
pkgs = pkgs // {
|
pkgs = pkgs // {
|
||||||
docbook-xsl-ns =
|
docbook-xsl-ns = pkgs.docbook-xsl-ns.override { withManOptDedupPatch = true; };
|
||||||
pkgs.docbook-xsl-ns.override { withManOptDedupPatch = true; };
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
outputPath = "share/doc/selfhostblocks";
|
outputPath = "share/doc/selfhostblocks";
|
||||||
|
|
||||||
manpage-urls = pkgs.writeText "manpage-urls.json" ''{}'';
|
manpage-urls = pkgs.writeText "manpage-urls.json" ''{}'';
|
||||||
in stdenv.mkDerivation {
|
in
|
||||||
|
stdenv.mkDerivation {
|
||||||
name = "self-host-blocks-manual";
|
name = "self-host-blocks-manual";
|
||||||
|
|
||||||
nativeBuildInputs = [ nixos-render-docs ];
|
nativeBuildInputs = [ nixos-render-docs ];
|
||||||
|
|
@ -135,28 +159,41 @@ in stdenv.mkDerivation {
|
||||||
${nmdsrc}/static/highlightjs/highlight.load.js
|
${nmdsrc}/static/highlightjs/highlight.load.js
|
||||||
|
|
||||||
''
|
''
|
||||||
+ lib.concatStringsSep "\n" (map (m: ''
|
+ lib.concatStringsSep "\n" (
|
||||||
|
map (m: ''
|
||||||
substituteInPlace ${m} --replace '@VERSION@' ${version}
|
substituteInPlace ${m} --replace '@VERSION@' ${version}
|
||||||
'') substituteVersionIn)
|
'') substituteVersionIn
|
||||||
|
)
|
||||||
+ ''
|
+ ''
|
||||||
substituteInPlace ./options.md \
|
substituteInPlace ./options.md \
|
||||||
--replace \
|
--replace \
|
||||||
'@OPTIONS_JSON@' \
|
'@OPTIONS_JSON@' \
|
||||||
${allOptionsDocs [
|
${
|
||||||
|
allOptionsDocs [
|
||||||
(pkgs.path + "/nixos/modules/services/misc/forgejo.nix")
|
(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
|
let
|
||||||
cfg = if builtins.isAttrs cfg' then cfg' else { module = cfg'; };
|
cfg = if builtins.isAttrs cfg' then cfg' else { module = cfg'; };
|
||||||
module = if builtins.isList cfg.module then cfg.module else [ cfg.module ];
|
module = if builtins.isList cfg.module then cfg.module else [ cfg.module ];
|
||||||
optionRoot = cfg.optionRoot or [ "shb" (lib.last (lib.splitString "/" name)) ];
|
optionRoot =
|
||||||
in ''
|
cfg.optionRoot or [
|
||||||
|
"shb"
|
||||||
|
(lib.last (lib.splitString "/" name))
|
||||||
|
];
|
||||||
|
in
|
||||||
|
''
|
||||||
substituteInPlace ./modules/${name}/docs/default.md \
|
substituteInPlace ./modules/${name}/docs/default.md \
|
||||||
--replace-fail \
|
--replace-fail \
|
||||||
'@OPTIONS_JSON@' \
|
'@OPTIONS_JSON@' \
|
||||||
${individualModuleOptionsDocs optionRoot module}/share/doc/nixos/options.json
|
${individualModuleOptionsDocs optionRoot module}/share/doc/nixos/options.json
|
||||||
'') modules)
|
''
|
||||||
|
) modules
|
||||||
|
)
|
||||||
+ ''
|
+ ''
|
||||||
find . -name "*.md" -print0 | \
|
find . -name "*.md" -print0 | \
|
||||||
while IFS= read -r -d ''' f; do
|
while IFS= read -r -d ''' f; do
|
||||||
|
|
|
||||||
143
flake.nix
143
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
|
let
|
||||||
originPkgs = nixpkgs.legacyPackages.${system};
|
originPkgs = nixpkgs.legacyPackages.${system};
|
||||||
shbPatches = originPkgs.lib.optionals (system == "x86_64-linux") [
|
shbPatches = originPkgs.lib.optionals (system == "x86_64-linux") [
|
||||||
|
|
@ -24,20 +34,24 @@
|
||||||
# hash = "sha256-hoLrqV7XtR1hP/m0rV9hjYUBtrSjay0qcPUYlKKuVWk=";
|
# hash = "sha256-hoLrqV7XtR1hP/m0rV9hjYUBtrSjay0qcPUYlKKuVWk=";
|
||||||
# })
|
# })
|
||||||
];
|
];
|
||||||
patchNixpkgs = {
|
patchNixpkgs =
|
||||||
|
{
|
||||||
nixpkgs,
|
nixpkgs,
|
||||||
patches,
|
patches,
|
||||||
system,
|
system,
|
||||||
}: nixpkgs.legacyPackages.${system}.applyPatches {
|
}:
|
||||||
|
nixpkgs.legacyPackages.${system}.applyPatches {
|
||||||
name = "nixpkgs-patched";
|
name = "nixpkgs-patched";
|
||||||
src = nixpkgs;
|
src = nixpkgs;
|
||||||
inherit patches;
|
inherit patches;
|
||||||
};
|
};
|
||||||
patchedNixpkgs = (patchNixpkgs {
|
patchedNixpkgs = (
|
||||||
|
patchNixpkgs {
|
||||||
nixpkgs = inputs.nixpkgs;
|
nixpkgs = inputs.nixpkgs;
|
||||||
patches = shbPatches;
|
patches = shbPatches;
|
||||||
inherit system;
|
inherit system;
|
||||||
});
|
}
|
||||||
|
);
|
||||||
pkgs = import patchedNixpkgs {
|
pkgs = import patchedNixpkgs {
|
||||||
inherit system;
|
inherit system;
|
||||||
config.allowUnfree = true;
|
config.allowUnfree = true;
|
||||||
|
|
@ -45,11 +59,18 @@
|
||||||
(final: prev: {
|
(final: prev: {
|
||||||
lib = prev.lib // {
|
lib = prev.lib // {
|
||||||
shb = self.lib.${system};
|
shb = self.lib.${system};
|
||||||
evalModules = args: ((prev.lib.makeOverridable prev.lib.evalModules) args).override (prevAttrs: {
|
evalModules =
|
||||||
specialArgs = (prevAttrs.specialArgs or {}) // { inherit (pkgs) lib; };
|
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;
|
inherit (pkgs) lib;
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
|
|
@ -67,10 +88,12 @@
|
||||||
|
|
||||||
packages.manualHtml = pkgs.callPackage ./docs {
|
packages.manualHtml = pkgs.callPackage ./docs {
|
||||||
inherit nmdsrc;
|
inherit nmdsrc;
|
||||||
allModules = self.nixosModules.default.imports
|
allModules =
|
||||||
|
self.nixosModules.default.imports
|
||||||
++ [
|
++ [
|
||||||
self.nixosModules.sops
|
self.nixosModules.sops
|
||||||
] ++ contractDummyModules;
|
]
|
||||||
|
++ contractDummyModules;
|
||||||
release = builtins.readFile ./VERSION;
|
release = builtins.readFile ./VERSION;
|
||||||
|
|
||||||
substituteVersionIn = [
|
substituteVersionIn = [
|
||||||
|
|
@ -82,7 +105,10 @@
|
||||||
"blocks/lldap" = ./modules/blocks/lldap.nix;
|
"blocks/lldap" = ./modules/blocks/lldap.nix;
|
||||||
"blocks/ssl" = {
|
"blocks/ssl" = {
|
||||||
module = ./modules/blocks/ssl.nix;
|
module = ./modules/blocks/ssl.nix;
|
||||||
optionRoot = [ "shb" "certs" ];
|
optionRoot = [
|
||||||
|
"shb"
|
||||||
|
"certs"
|
||||||
|
];
|
||||||
};
|
};
|
||||||
"blocks/mitmdump" = ./modules/blocks/mitmdump.nix;
|
"blocks/mitmdump" = ./modules/blocks/mitmdump.nix;
|
||||||
"blocks/monitoring" = ./modules/blocks/monitoring.nix;
|
"blocks/monitoring" = ./modules/blocks/monitoring.nix;
|
||||||
|
|
@ -99,26 +125,45 @@
|
||||||
"services/karakeep" = ./modules/services/karakeep.nix;
|
"services/karakeep" = ./modules/services/karakeep.nix;
|
||||||
"services/nextcloud-server" = {
|
"services/nextcloud-server" = {
|
||||||
module = ./modules/services/nextcloud-server.nix;
|
module = ./modules/services/nextcloud-server.nix;
|
||||||
optionRoot = [ "shb" "nextcloud" ];
|
optionRoot = [
|
||||||
|
"shb"
|
||||||
|
"nextcloud"
|
||||||
|
];
|
||||||
};
|
};
|
||||||
"services/open-webui" = ./modules/services/open-webui.nix;
|
"services/open-webui" = ./modules/services/open-webui.nix;
|
||||||
"services/pinchflat" = ./modules/services/pinchflat.nix;
|
"services/pinchflat" = ./modules/services/pinchflat.nix;
|
||||||
"services/vaultwarden" = ./modules/services/vaultwarden.nix;
|
"services/vaultwarden" = ./modules/services/vaultwarden.nix;
|
||||||
"contracts/backup" = {
|
"contracts/backup" = {
|
||||||
module = ./modules/contracts/backup/dummyModule.nix;
|
module = ./modules/contracts/backup/dummyModule.nix;
|
||||||
optionRoot = [ "shb" "contracts" "backup" ];
|
optionRoot = [
|
||||||
|
"shb"
|
||||||
|
"contracts"
|
||||||
|
"backup"
|
||||||
|
];
|
||||||
};
|
};
|
||||||
"contracts/databasebackup" = {
|
"contracts/databasebackup" = {
|
||||||
module = ./modules/contracts/databasebackup/dummyModule.nix;
|
module = ./modules/contracts/databasebackup/dummyModule.nix;
|
||||||
optionRoot = [ "shb" "contracts" "databasebackup" ];
|
optionRoot = [
|
||||||
|
"shb"
|
||||||
|
"contracts"
|
||||||
|
"databasebackup"
|
||||||
|
];
|
||||||
};
|
};
|
||||||
"contracts/secret" = {
|
"contracts/secret" = {
|
||||||
module = ./modules/contracts/secret/dummyModule.nix;
|
module = ./modules/contracts/secret/dummyModule.nix;
|
||||||
optionRoot = [ "shb" "contracts" "secret" ];
|
optionRoot = [
|
||||||
|
"shb"
|
||||||
|
"contracts"
|
||||||
|
"secret"
|
||||||
|
];
|
||||||
};
|
};
|
||||||
"contracts/ssl" = {
|
"contracts/ssl" = {
|
||||||
module = ./modules/contracts/ssl/dummyModule.nix;
|
module = ./modules/contracts/ssl/dummyModule.nix;
|
||||||
optionRoot = [ "shb" "contracts" "ssl" ];
|
optionRoot = [
|
||||||
|
"shb"
|
||||||
|
"contracts"
|
||||||
|
"ssl"
|
||||||
|
];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
@ -165,7 +210,8 @@
|
||||||
in
|
in
|
||||||
(self.packages.${system}.manualHtml.override {
|
(self.packages.${system}.manualHtml.override {
|
||||||
nixos-render-docs = nixos-render-docs-patched;
|
nixos-render-docs = nixos-render-docs-patched;
|
||||||
}).overrideAttrs (old: {
|
}).overrideAttrs
|
||||||
|
(old: {
|
||||||
installPhase = ''
|
installPhase = ''
|
||||||
${old.installPhase}
|
${old.installPhase}
|
||||||
ln -sf share/doc/selfhostblocks/redirects.json $out/redirects.json
|
ln -sf share/doc/selfhostblocks/redirects.json $out/redirects.json
|
||||||
|
|
@ -183,25 +229,42 @@
|
||||||
|
|
||||||
checks =
|
checks =
|
||||||
let
|
let
|
||||||
inherit (pkgs.lib) foldl foldlAttrs removeAttrs mergeAttrs optionalAttrs;
|
inherit (pkgs.lib)
|
||||||
|
foldl
|
||||||
|
foldlAttrs
|
||||||
|
removeAttrs
|
||||||
|
mergeAttrs
|
||||||
|
optionalAttrs
|
||||||
|
;
|
||||||
|
|
||||||
importFiles = files:
|
importFiles = files: map (m: pkgs.callPackage m { }) 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;
|
"${root}_${name}" = value;
|
||||||
}) {} attrset;
|
}
|
||||||
|
) { } attrset;
|
||||||
|
|
||||||
vm_test = name: path: flattenAttrs "vm_${name}" (
|
vm_test =
|
||||||
removeAttrs (pkgs.callPackage path {}) [ "override" "overrideDerivation" ]
|
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 {
|
modules = pkgs.lib.shb.check {
|
||||||
inherit pkgs;
|
inherit pkgs;
|
||||||
tests =
|
tests = mergeTests (importFiles [
|
||||||
mergeTests (importFiles [
|
|
||||||
./test/modules/davfs.nix
|
./test/modules/davfs.nix
|
||||||
# TODO: Make this not use IFD
|
# TODO: Make this not use IFD
|
||||||
./test/modules/lib.nix
|
./test/modules/lib.nix
|
||||||
|
|
@ -245,8 +308,13 @@
|
||||||
|
|
||||||
# To see the traces, run:
|
# To see the traces, run:
|
||||||
# nix run .#playwright -- show-trace $(nix eval .#checks.x86_64-linux.vm_grocy_basic --raw)/trace/0.zip
|
# nix run .#playwright -- show-trace $(nix eval .#checks.x86_64-linux.vm_grocy_basic --raw)/trace/0.zip
|
||||||
packages.playwright =
|
packages.playwright = pkgs.callPackage (
|
||||||
pkgs.callPackage ({ stdenvNoCC, makeWrapper, playwright }: stdenvNoCC.mkDerivation {
|
{
|
||||||
|
stdenvNoCC,
|
||||||
|
makeWrapper,
|
||||||
|
playwright,
|
||||||
|
}:
|
||||||
|
stdenvNoCC.mkDerivation {
|
||||||
name = "playwright";
|
name = "playwright";
|
||||||
|
|
||||||
src = playwright;
|
src = playwright;
|
||||||
|
|
@ -261,14 +329,19 @@
|
||||||
--set PLAYWRIGHT_BROWSERS_PATH ${pkgs.playwright-driver.browsers} \
|
--set PLAYWRIGHT_BROWSERS_PATH ${pkgs.playwright-driver.browsers} \
|
||||||
--set PLAYWRIGHT_SKIP_VALIDATE_HOST_REQUIREMENTS true
|
--set PLAYWRIGHT_SKIP_VALIDATE_HOST_REQUIREMENTS true
|
||||||
'';
|
'';
|
||||||
}) {};
|
}
|
||||||
|
) { };
|
||||||
|
|
||||||
# Run "nix run .#update-redirects" to regenerate docs/redirects.json
|
# Run "nix run .#update-redirects" to regenerate docs/redirects.json
|
||||||
apps.update-redirects = {
|
apps.update-redirects = {
|
||||||
type = "app";
|
type = "app";
|
||||||
program = "${pkgs.writeShellApplication {
|
program = "${
|
||||||
|
pkgs.writeShellApplication {
|
||||||
name = "update-redirects";
|
name = "update-redirects";
|
||||||
runtimeInputs = [ pkgs.nix pkgs.jq ];
|
runtimeInputs = [
|
||||||
|
pkgs.nix
|
||||||
|
pkgs.jq
|
||||||
|
];
|
||||||
text = ''
|
text = ''
|
||||||
echo "=== SelfHostBlocks Redirects Updater ==="
|
echo "=== SelfHostBlocks Redirects Updater ==="
|
||||||
echo "Generating fresh ./docs/redirects.json..."
|
echo "Generating fresh ./docs/redirects.json..."
|
||||||
|
|
@ -283,10 +356,12 @@
|
||||||
echo " Updated docs/redirects.json"
|
echo " Updated docs/redirects.json"
|
||||||
echo "To verify: nix build .#manualHtml"
|
echo "To verify: nix build .#manualHtml"
|
||||||
'';
|
'';
|
||||||
}}/bin/update-redirects";
|
}
|
||||||
|
}/bin/update-redirects";
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
) // {
|
)
|
||||||
|
// {
|
||||||
herculesCI.ciSystems = [ "x86_64-linux" ];
|
herculesCI.ciSystems = [ "x86_64-linux" ];
|
||||||
|
|
||||||
nixosModules.default = {
|
nixosModules.default = {
|
||||||
|
|
|
||||||
248
lib/default.nix
248
lib/default.nix
|
|
@ -9,7 +9,14 @@ rec {
|
||||||
# - resultPath is the location the config file should have on the filesystem.
|
# - 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
|
# - generator is a function taking two arguments name and value and returning path in the nix
|
||||||
# nix store where the
|
# 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
|
let
|
||||||
configWithTemplates = withReplacements userConfig;
|
configWithTemplates = withReplacements userConfig;
|
||||||
|
|
||||||
|
|
@ -24,21 +31,40 @@ rec {
|
||||||
};
|
};
|
||||||
|
|
||||||
replaceSecretsFormatAdapter = format: format.generate;
|
replaceSecretsFormatAdapter = format: format.generate;
|
||||||
replaceSecretsGeneratorAdapter = generator: name: value: pkgs.writeText "generator " (generator value);
|
replaceSecretsGeneratorAdapter =
|
||||||
toEnvVar = replaceSecretsGeneratorAdapter (v: (lib.generators.toINIWithGlobalSection {} { globalSection = v; }));
|
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;
|
inherit file replacements;
|
||||||
resultPath = newPath;
|
resultPath = newPath;
|
||||||
};
|
};
|
||||||
|
|
||||||
genReplacement = secret:
|
genReplacement =
|
||||||
|
secret:
|
||||||
let
|
let
|
||||||
t = { transform ? null, ... }: if isNull transform then x: x else transform;
|
t =
|
||||||
|
{
|
||||||
|
transform ? null,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
if isNull transform then x: x else transform;
|
||||||
in
|
in
|
||||||
lib.attrsets.nameValuePair (secretName secret.name) ((t secret) "$(cat ${toString secret.source})");
|
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
|
let
|
||||||
templatePath = resultPath + ".template";
|
templatePath = resultPath + ".template";
|
||||||
|
|
||||||
|
|
@ -47,13 +73,15 @@ rec {
|
||||||
# step. Otherwise, the $(cat ...) commands inside the sed
|
# step. Otherwise, the $(cat ...) commands inside the sed
|
||||||
# replacements could fail but not fail individually but
|
# replacements could fail but not fail individually but
|
||||||
# not fail the whole script.
|
# 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 == []
|
sedCmd = if replacements == [ ] then "cat" else "${pkgs.gnused}/bin/sed ${sedPatterns}";
|
||||||
then "cat"
|
|
||||||
else "${pkgs.gnused}/bin/sed ${sedPatterns}";
|
|
||||||
in
|
in
|
||||||
''
|
''
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
@ -64,9 +92,11 @@ rec {
|
||||||
ln -fs ${file} ${templatePath}
|
ln -fs ${file} ${templatePath}
|
||||||
rm -f ${resultPath}
|
rm -f ${resultPath}
|
||||||
touch ${resultPath}
|
touch ${resultPath}
|
||||||
'' + (lib.optionalString (user != null) ''
|
''
|
||||||
|
+ (lib.optionalString (user != null) ''
|
||||||
chown ${user} ${resultPath}
|
chown ${user} ${resultPath}
|
||||||
'') + ''
|
'')
|
||||||
|
+ ''
|
||||||
${sedCmd} ${templatePath} > ${resultPath}
|
${sedCmd} ${templatePath} > ${resultPath}
|
||||||
chmod ${permissions} ${resultPath}
|
chmod ${permissions} ${resultPath}
|
||||||
'';
|
'';
|
||||||
|
|
@ -89,24 +119,23 @@ rec {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
secretName = names:
|
secretName =
|
||||||
"%SECRET${lib.strings.toUpper (lib.strings.concatMapStrings (s: "_" + s) names)}%";
|
names: "%SECRET${lib.strings.toUpper (lib.strings.concatMapStrings (s: "_" + s) names)}%";
|
||||||
|
|
||||||
withReplacements = attrs:
|
withReplacements =
|
||||||
|
attrs:
|
||||||
let
|
let
|
||||||
valueOrReplacement = name: value:
|
valueOrReplacement =
|
||||||
if !(builtins.isAttrs value && value ? "source")
|
name: value: if !(builtins.isAttrs value && value ? "source") then value else secretName name;
|
||||||
then value
|
|
||||||
else secretName name;
|
|
||||||
in
|
in
|
||||||
mapAttrsRecursiveCond (v: !v ? "source") valueOrReplacement attrs;
|
mapAttrsRecursiveCond (v: !v ? "source") valueOrReplacement attrs;
|
||||||
|
|
||||||
getReplacements = attrs:
|
getReplacements =
|
||||||
|
attrs:
|
||||||
let
|
let
|
||||||
addNameField = name: value:
|
addNameField =
|
||||||
if !(builtins.isAttrs value && value ? "source")
|
name: value:
|
||||||
then value
|
if !(builtins.isAttrs value && value ? "source") then value else value // { name = name; };
|
||||||
else value // { name = name; };
|
|
||||||
|
|
||||||
secretsWithName = mapAttrsRecursiveCond (v: !v ? "source") addNameField attrs;
|
secretsWithName = mapAttrsRecursiveCond (v: !v ? "source") addNameField attrs;
|
||||||
in
|
in
|
||||||
|
|
@ -121,13 +150,16 @@ rec {
|
||||||
# Attribute set or list to recursively map over.
|
# Attribute set or list to recursively map over.
|
||||||
set:
|
set:
|
||||||
let
|
let
|
||||||
recurse = path: val:
|
recurse =
|
||||||
if builtins.isAttrs val && cond val
|
path: val:
|
||||||
then lib.attrsets.mapAttrs (n: v: recurse (path ++ [n]) v) val
|
if builtins.isAttrs val && cond val then
|
||||||
else if builtins.isList val && cond val
|
lib.attrsets.mapAttrs (n: v: recurse (path ++ [ n ]) v) val
|
||||||
then lib.lists.imap0 (i: v: recurse (path ++ [(builtins.toString i)]) v) val
|
else if builtins.isList val && cond val then
|
||||||
else f path val;
|
lib.lists.imap0 (i: v: recurse (path ++ [ (builtins.toString i) ]) v) val
|
||||||
in recurse [] set;
|
else
|
||||||
|
f path val;
|
||||||
|
in
|
||||||
|
recurse [ ] set;
|
||||||
|
|
||||||
# Like lib.attrsets.collect but also recurses on lists.
|
# Like lib.attrsets.collect but also recurses on lists.
|
||||||
collect =
|
collect =
|
||||||
|
|
@ -144,14 +176,21 @@ rec {
|
||||||
else
|
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
|
# Generator for XML
|
||||||
formatXML = {
|
formatXML =
|
||||||
enclosingRoot ? null
|
{
|
||||||
}: {
|
enclosingRoot ? null,
|
||||||
type = with lib.types; let
|
}:
|
||||||
valueType = nullOr (oneOf [
|
{
|
||||||
|
type =
|
||||||
|
with lib.types;
|
||||||
|
let
|
||||||
|
valueType =
|
||||||
|
nullOr (oneOf [
|
||||||
bool
|
bool
|
||||||
int
|
int
|
||||||
float
|
float
|
||||||
|
|
@ -159,21 +198,31 @@ rec {
|
||||||
path
|
path
|
||||||
(attrsOf valueType)
|
(attrsOf valueType)
|
||||||
(listOf valueType)
|
(listOf valueType)
|
||||||
]) // {
|
])
|
||||||
|
// {
|
||||||
description = "XML value";
|
description = "XML value";
|
||||||
};
|
};
|
||||||
in valueType;
|
in
|
||||||
|
valueType;
|
||||||
|
|
||||||
generate = name: value: pkgs.callPackage ({ runCommand, python3 }: runCommand "config" {
|
generate =
|
||||||
value = builtins.toJSON (
|
name: value:
|
||||||
if enclosingRoot == null then
|
pkgs.callPackage (
|
||||||
value
|
{ runCommand, python3 }:
|
||||||
else
|
runCommand "config"
|
||||||
{ ${enclosingRoot} = value; });
|
{
|
||||||
|
value = builtins.toJSON (if enclosingRoot == null then value else { ${enclosingRoot} = value; });
|
||||||
passAsFile = [ "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 os
|
||||||
import json
|
import json
|
||||||
from dict2xml import dict2xml
|
from dict2xml import dict2xml
|
||||||
|
|
@ -185,18 +234,28 @@ rec {
|
||||||
os.exit(2)
|
os.exit(2)
|
||||||
with open(os.environ["out"], "w") as out:
|
with open(os.environ["out"], "w") as out:
|
||||||
out.write(dict2xml(content))
|
out.write(dict2xml(content))
|
||||||
'')) {};
|
''
|
||||||
|
)
|
||||||
|
) { };
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
parseXML = xml:
|
parseXML =
|
||||||
|
xml:
|
||||||
let
|
let
|
||||||
xmlToJsonFile = pkgs.callPackage ({ runCommand, python3 }: runCommand "config" {
|
xmlToJsonFile = pkgs.callPackage (
|
||||||
|
{ runCommand, python3 }:
|
||||||
|
runCommand "config"
|
||||||
|
{
|
||||||
inherit xml;
|
inherit xml;
|
||||||
passAsFile = [ "xml" ];
|
passAsFile = [ "xml" ];
|
||||||
} (pkgs.writers.writePython3 "xml2json" {
|
}
|
||||||
|
(
|
||||||
|
pkgs.writers.writePython3 "xml2json"
|
||||||
|
{
|
||||||
libraries = with python3.pkgs; [ python ];
|
libraries = with python3.pkgs; [ python ];
|
||||||
} ''
|
}
|
||||||
|
''
|
||||||
import os
|
import os
|
||||||
import json
|
import json
|
||||||
from collections import ChainMap
|
from collections import ChainMap
|
||||||
|
|
@ -219,34 +278,54 @@ rec {
|
||||||
|
|
||||||
with open(os.environ["out"], "w") as out:
|
with open(os.environ["out"], "w") as out:
|
||||||
out.write(j)
|
out.write(j)
|
||||||
'')) {};
|
''
|
||||||
|
)
|
||||||
|
) { };
|
||||||
in
|
in
|
||||||
builtins.fromJSON (builtins.readFile xmlToJsonFile);
|
builtins.fromJSON (builtins.readFile xmlToJsonFile);
|
||||||
|
|
||||||
renameAttrName = attrset: from: to:
|
renameAttrName =
|
||||||
(lib.attrsets.filterAttrs (name: v: name == from) attrset) // {
|
attrset: from: to:
|
||||||
|
(lib.attrsets.filterAttrs (name: v: name == from) attrset)
|
||||||
|
// {
|
||||||
${to} = attrset.${from};
|
${to} = attrset.${from};
|
||||||
};
|
};
|
||||||
|
|
||||||
# Taken from https://github.com/antifuchs/nix-flake-tests/blob/main/default.nix
|
# Taken from https://github.com/antifuchs/nix-flake-tests/blob/main/default.nix
|
||||||
# with a nicer diff display function.
|
# with a nicer diff display function.
|
||||||
check = { pkgs, tests }:
|
check =
|
||||||
|
{ pkgs, tests }:
|
||||||
let
|
let
|
||||||
formatValue = val:
|
formatValue =
|
||||||
if (builtins.isList val || builtins.isAttrs val) then builtins.toJSON val
|
val:
|
||||||
else builtins.toString val;
|
if (builtins.isList val || builtins.isAttrs val) then
|
||||||
|
builtins.toJSON val
|
||||||
|
else
|
||||||
|
builtins.toString val;
|
||||||
|
|
||||||
resultToString = { name, expected, result }:
|
resultToString =
|
||||||
builtins.readFile (pkgs.runCommand "nix-flake-tests-error" {
|
{
|
||||||
|
name,
|
||||||
|
expected,
|
||||||
|
result,
|
||||||
|
}:
|
||||||
|
builtins.readFile (
|
||||||
|
pkgs.runCommand "nix-flake-tests-error"
|
||||||
|
{
|
||||||
expected = formatValue expected;
|
expected = formatValue expected;
|
||||||
result = formatValue result;
|
result = formatValue result;
|
||||||
passAsFile = [ "expected" "result" ];
|
passAsFile = [
|
||||||
} ''
|
"expected"
|
||||||
|
"result"
|
||||||
|
];
|
||||||
|
}
|
||||||
|
''
|
||||||
echo "${name} failed (- expected, + result)" > $out
|
echo "${name} failed (- expected, + result)" > $out
|
||||||
cp ''${expectedPath} ''${expectedPath}.json
|
cp ''${expectedPath} ''${expectedPath}.json
|
||||||
cp ''${resultPath} ''${resultPath}.json
|
cp ''${resultPath} ''${resultPath}.json
|
||||||
${pkgs.deepdiff}/bin/deep diff ''${expectedPath}.json ''${resultPath}.json >> $out
|
${pkgs.deepdiff}/bin/deep diff ''${expectedPath}.json ''${resultPath}.json >> $out
|
||||||
'');
|
''
|
||||||
|
);
|
||||||
|
|
||||||
results = pkgs.lib.runTests tests;
|
results = pkgs.lib.runTests tests;
|
||||||
in
|
in
|
||||||
|
|
@ -255,8 +334,14 @@ rec {
|
||||||
else
|
else
|
||||||
pkgs.runCommand "nix-flake-tests-success" { } "echo > $out";
|
pkgs.runCommand "nix-flake-tests-success" { } "echo > $out";
|
||||||
|
|
||||||
|
genConfigOutOfBandSystemd =
|
||||||
genConfigOutOfBandSystemd = { config, configLocation, generator, user ? null, permissions ? "u=r,g=r,o=" }:
|
{
|
||||||
|
config,
|
||||||
|
configLocation,
|
||||||
|
generator,
|
||||||
|
user ? null,
|
||||||
|
permissions ? "u=r,g=r,o=",
|
||||||
|
}:
|
||||||
{
|
{
|
||||||
loadCredentials = getLoadCredentials "source" config;
|
loadCredentials = getLoadCredentials "source" config;
|
||||||
preStart = lib.mkBefore (replaceSecrets {
|
preStart = lib.mkBefore (replaceSecrets {
|
||||||
|
|
@ -267,32 +352,33 @@ rec {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
updateToLoadCredentials = sourceField: rootDir: attrs:
|
updateToLoadCredentials =
|
||||||
|
sourceField: rootDir: attrs:
|
||||||
let
|
let
|
||||||
hasPlaceholderField = v: isAttrs v && hasAttr sourceField v;
|
hasPlaceholderField = v: isAttrs v && hasAttr sourceField v;
|
||||||
|
|
||||||
valueOrLoadCredential = path: value:
|
valueOrLoadCredential =
|
||||||
if ! (hasPlaceholderField value)
|
path: value:
|
||||||
then value
|
if !(hasPlaceholderField value) then
|
||||||
else value // { ${sourceField} = rootDir + "/" + concatStringsSep "_" path; };
|
value
|
||||||
|
else
|
||||||
|
value // { ${sourceField} = rootDir + "/" + concatStringsSep "_" path; };
|
||||||
in
|
in
|
||||||
mapAttrsRecursiveCond (v: !(hasPlaceholderField v)) valueOrLoadCredential attrs;
|
mapAttrsRecursiveCond (v: !(hasPlaceholderField v)) valueOrLoadCredential attrs;
|
||||||
|
|
||||||
getLoadCredentials = sourceField: attrs:
|
getLoadCredentials =
|
||||||
|
sourceField: attrs:
|
||||||
let
|
let
|
||||||
hasPlaceholderField = v: isAttrs v && hasAttr sourceField v;
|
hasPlaceholderField = v: isAttrs v && hasAttr sourceField v;
|
||||||
|
|
||||||
addPathField = path: value:
|
addPathField =
|
||||||
if ! (hasPlaceholderField value)
|
path: value: if !(hasPlaceholderField value) then value else value // { inherit path; };
|
||||||
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;
|
allSecrets = collect (v: hasPlaceholderField v) secretsWithPath;
|
||||||
|
|
||||||
genLoadCredentials = secret:
|
genLoadCredentials = secret: "${concatStringsSep "_" secret.path}:${secret.${sourceField}}";
|
||||||
"${concatStringsSep "_" secret.path}:${secret.${sourceField}}";
|
|
||||||
in
|
in
|
||||||
map genLoadCredentials allSecrets;
|
map genLoadCredentials allSecrets;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,10 @@
|
||||||
{ config, options, pkgs, lib, ... }:
|
{
|
||||||
|
config,
|
||||||
|
options,
|
||||||
|
pkgs,
|
||||||
|
lib,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
|
||||||
let
|
let
|
||||||
cfg = config.shb.authelia;
|
cfg = config.shb.authelia;
|
||||||
|
|
@ -181,7 +187,8 @@ in
|
||||||
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;
|
freeformType = lib.types.attrsOf lib.types.anything;
|
||||||
|
|
||||||
options = {
|
options = {
|
||||||
|
|
@ -222,7 +229,13 @@ in
|
||||||
};
|
};
|
||||||
|
|
||||||
authorization_policy = lib.mkOption {
|
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.";
|
description = "Require one factor (password) or two factor (device) authentication.";
|
||||||
default = "one_factor";
|
default = "one_factor";
|
||||||
};
|
};
|
||||||
|
|
@ -235,7 +248,12 @@ in
|
||||||
scopes = lib.mkOption {
|
scopes = lib.mkOption {
|
||||||
type = lib.types.listOf lib.types.str;
|
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";
|
description = "Scopes to ask for. See https://www.authelia.com/integration/openid-connect/openid-connect-1.0-claims";
|
||||||
example = [ "openid" "profile" "email" "groups" ];
|
example = [
|
||||||
|
"openid"
|
||||||
|
"profile"
|
||||||
|
"email"
|
||||||
|
"groups"
|
||||||
|
];
|
||||||
default = [ ];
|
default = [ ];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -250,7 +268,8 @@ in
|
||||||
default = "default";
|
default = "default";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
});
|
}
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
smtp = lib.mkOption {
|
smtp = lib.mkOption {
|
||||||
|
|
@ -263,7 +282,8 @@ in
|
||||||
default = "/tmp/authelia-notifications";
|
default = "/tmp/authelia-notifications";
|
||||||
type = lib.types.oneOf [
|
type = lib.types.oneOf [
|
||||||
lib.types.str
|
lib.types.str
|
||||||
(lib.types.nullOr (lib.types.submodule {
|
(lib.types.nullOr (
|
||||||
|
lib.types.submodule {
|
||||||
options = {
|
options = {
|
||||||
from_address = lib.mkOption {
|
from_address = lib.mkOption {
|
||||||
type = lib.types.str;
|
type = lib.types.str;
|
||||||
|
|
@ -299,7 +319,8 @@ in
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}))
|
}
|
||||||
|
))
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -324,8 +345,12 @@ in
|
||||||
```
|
```
|
||||||
'';
|
'';
|
||||||
readOnly = true;
|
readOnly = true;
|
||||||
default = { path = "/var/lib/authelia-authelia.${cfg.domain}"; };
|
default = {
|
||||||
defaultText = { path = "/var/lib/authelia-authelia.example.com"; };
|
path = "/var/lib/authelia-authelia.${cfg.domain}";
|
||||||
|
};
|
||||||
|
defaultText = {
|
||||||
|
path = "/var/lib/authelia-authelia.example.com";
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
mountRedis = lib.mkOption {
|
mountRedis = lib.mkOption {
|
||||||
|
|
@ -343,7 +368,9 @@ in
|
||||||
```
|
```
|
||||||
'';
|
'';
|
||||||
readOnly = true;
|
readOnly = true;
|
||||||
default = { path = "/var/lib/redis-authelia"; };
|
default = {
|
||||||
|
path = "/var/lib/redis-authelia";
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
debug = lib.mkOption {
|
debug = lib.mkOption {
|
||||||
|
|
@ -395,7 +422,9 @@ in
|
||||||
AUTHELIA_IDENTITY_PROVIDERS_OIDC_HMAC_SECRET_FILE = toString cfg.secrets.identityProvidersOIDCHMACSecret.result.path;
|
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_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 = {
|
settings = {
|
||||||
server.address = "tcp://127.0.0.1:${toString listenPort}";
|
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
|
# Inspired from https://www.authelia.com/configuration/session/introduction/ and https://www.authelia.com/configuration/session/redis
|
||||||
session = {
|
session = {
|
||||||
name = "authelia_session";
|
name = "authelia_session";
|
||||||
cookies = [{
|
cookies = [
|
||||||
|
{
|
||||||
domain = if isNull cfg.port then cfg.domain else "${cfg.domain}:${toString cfg.port}";
|
domain = if isNull cfg.port then cfg.domain else "${cfg.domain}:${toString cfg.port}";
|
||||||
authelia_url = "https://${cfg.subdomain}.${cfg.domain}";
|
authelia_url = "https://${cfg.subdomain}.${cfg.domain}";
|
||||||
}];
|
}
|
||||||
|
];
|
||||||
same_site = "lax";
|
same_site = "lax";
|
||||||
expiration = "1h";
|
expiration = "1h";
|
||||||
inactivity = "5m";
|
inactivity = "5m";
|
||||||
|
|
@ -468,7 +499,11 @@ in
|
||||||
networks = [
|
networks = [
|
||||||
{
|
{
|
||||||
name = "internal";
|
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 = [
|
rules = [
|
||||||
|
|
@ -479,7 +514,8 @@ in
|
||||||
"^/api/.*"
|
"^/api/.*"
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
] ++ cfg.rules;
|
]
|
||||||
|
++ cfg.rules;
|
||||||
};
|
};
|
||||||
telemetry = {
|
telemetry = {
|
||||||
metrics = {
|
metrics = {
|
||||||
|
|
@ -489,17 +525,25 @@ in
|
||||||
};
|
};
|
||||||
|
|
||||||
log.level = if cfg.debug then "debug" else "info";
|
log.level = if cfg.debug then "debug" else "info";
|
||||||
} // {
|
}
|
||||||
|
// {
|
||||||
identity_providers.oidc = {
|
identity_providers.oidc = {
|
||||||
claims_policies = {
|
claims_policies = {
|
||||||
# This default claim should go away at some point.
|
# 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
|
# 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" ];
|
default.id_token = [
|
||||||
} // cfg.extraOidcClaimsPolicies;
|
"email"
|
||||||
|
"preferred_username"
|
||||||
|
"name"
|
||||||
|
"groups"
|
||||||
|
];
|
||||||
|
}
|
||||||
|
// cfg.extraOidcClaimsPolicies;
|
||||||
scopes = cfg.extraOidcScopes;
|
scopes = cfg.extraOidcScopes;
|
||||||
authorization_policies = cfg.extraOidcAuthorizationPolicies;
|
authorization_policies = cfg.extraOidcAuthorizationPolicies;
|
||||||
};
|
};
|
||||||
} // lib.optionalAttrs (cfg.extraDefinitions != {}) {
|
}
|
||||||
|
// lib.optionalAttrs (cfg.extraDefinitions != { }) {
|
||||||
definitions = cfg.extraDefinitions;
|
definitions = cfg.extraDefinitions;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -508,7 +552,8 @@ in
|
||||||
|
|
||||||
systemd.services."authelia-${fqdn}".preStart =
|
systemd.services."authelia-${fqdn}".preStart =
|
||||||
let
|
let
|
||||||
mkCfg = clients:
|
mkCfg =
|
||||||
|
clients:
|
||||||
lib.shb.replaceSecrets {
|
lib.shb.replaceSecrets {
|
||||||
userConfig = {
|
userConfig = {
|
||||||
identity_providers.oidc.clients = clients;
|
identity_providers.oidc.clients = clients;
|
||||||
|
|
@ -517,9 +562,12 @@ in
|
||||||
generator = lib.shb.replaceSecretsGeneratorAdapter (lib.generators.toYAML { });
|
generator = lib.shb.replaceSecretsGeneratorAdapter (lib.generators.toYAML { });
|
||||||
};
|
};
|
||||||
in
|
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'
|
${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} = {
|
services.nginx.virtualHosts.${fqdn} = {
|
||||||
forceSSL = !(isNull cfg.ssl);
|
forceSSL = !(isNull cfg.ssl);
|
||||||
|
|
@ -579,7 +627,8 @@ in
|
||||||
after = [ "authelia-${fqdn}.service" ];
|
after = [ "authelia-${fqdn}.service" ];
|
||||||
enabledAddons = [ config.shb.mitmdump.addons.logger ];
|
enabledAddons = [ config.shb.mitmdump.addons.logger ];
|
||||||
extraArgs = [
|
extraArgs = [
|
||||||
"--set" "verbose_pattern=/api"
|
"--set"
|
||||||
|
"verbose_pattern=/api"
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -610,13 +659,16 @@ in
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
systemd.targets."authelia-${fqdn}" = let
|
systemd.targets."authelia-${fqdn}" =
|
||||||
|
let
|
||||||
services = [
|
services = [
|
||||||
"authelia-${fqdn}.service"
|
"authelia-${fqdn}.service"
|
||||||
] ++ lib.optionals cfg.debug [
|
]
|
||||||
|
++ lib.optionals cfg.debug [
|
||||||
config.shb.mitmdump.instances."authelia-${fqdn}".serviceName
|
config.shb.mitmdump.instances."authelia-${fqdn}".serviceName
|
||||||
];
|
];
|
||||||
in {
|
in
|
||||||
|
{
|
||||||
after = services;
|
after = services;
|
||||||
requires = services;
|
requires = services;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,10 @@
|
||||||
{ config, pkgs, lib, utils, ... }:
|
{
|
||||||
|
config,
|
||||||
|
pkgs,
|
||||||
|
lib,
|
||||||
|
utils,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
|
||||||
let
|
let
|
||||||
cfg = config.shb.borgbackup;
|
cfg = config.shb.borgbackup;
|
||||||
|
|
@ -17,7 +23,9 @@ let
|
||||||
type = lib.types.path;
|
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";
|
borg_keys_directory = "/run/secrets/borgmatic/keys";
|
||||||
|
|
||||||
sourceDirectories = lib.mkOption {
|
sourceDirectories = lib.mkOption {
|
||||||
|
|
@ -39,7 +47,8 @@ let
|
||||||
|
|
||||||
repositories = lib.mkOption {
|
repositories = lib.mkOption {
|
||||||
description = "Repositories to back this instance to.";
|
description = "Repositories to back this instance to.";
|
||||||
type = lib.types.nonEmptyListOf (lib.types.submodule {
|
type = lib.types.nonEmptyListOf (
|
||||||
|
lib.types.submodule {
|
||||||
options = {
|
options = {
|
||||||
path = lib.mkOption {
|
path = lib.mkOption {
|
||||||
type = lib.types.str;
|
type = lib.types.str;
|
||||||
|
|
@ -60,12 +69,18 @@ let
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
});
|
}
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
retention = lib.mkOption {
|
retention = lib.mkOption {
|
||||||
description = "Retention options.";
|
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 = {
|
default = {
|
||||||
keep_within = "1d";
|
keep_within = "1d";
|
||||||
keep_hourly = 24;
|
keep_hourly = 24;
|
||||||
|
|
@ -113,7 +128,8 @@ let
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
repoSlugName = name: builtins.replaceStrings ["/" ":"] ["_" "_"] (lib.strings.removePrefix "/" name);
|
repoSlugName =
|
||||||
|
name: builtins.replaceStrings [ "/" ":" ] [ "_" "_" ] (lib.strings.removePrefix "/" name);
|
||||||
|
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
|
|
@ -133,9 +149,11 @@ in
|
||||||
instances = lib.mkOption {
|
instances = lib.mkOption {
|
||||||
description = "Each instance is a backup setting";
|
description = "Each instance is a backup setting";
|
||||||
default = { };
|
default = { };
|
||||||
type = lib.types.attrsOf (lib.types.submodule {
|
type = lib.types.attrsOf (
|
||||||
|
lib.types.submodule {
|
||||||
options = instanceOptions;
|
options = instanceOptions;
|
||||||
});
|
}
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
borgServer = lib.mkOption {
|
borgServer = lib.mkOption {
|
||||||
|
|
@ -157,7 +175,11 @@ in
|
||||||
default = 15;
|
default = 15;
|
||||||
};
|
};
|
||||||
ioSchedulingClass = lib.mkOption {
|
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.";
|
description = "ionice scheduling class, defaults to best-effort IO.";
|
||||||
default = "best-effort";
|
default = "best-effort";
|
||||||
};
|
};
|
||||||
|
|
@ -174,7 +196,8 @@ in
|
||||||
config = lib.mkIf (cfg.instances != { }) (
|
config = lib.mkIf (cfg.instances != { }) (
|
||||||
let
|
let
|
||||||
enabledInstances = lib.attrsets.filterAttrs (k: i: i.enable) cfg.instances;
|
enabledInstances = lib.attrsets.filterAttrs (k: i: i.enable) cfg.instances;
|
||||||
in lib.mkMerge [
|
in
|
||||||
|
lib.mkMerge [
|
||||||
# Secrets configuration
|
# Secrets configuration
|
||||||
{
|
{
|
||||||
users.users = {
|
users.users = {
|
||||||
|
|
@ -195,32 +218,54 @@ in
|
||||||
|
|
||||||
sops.secrets =
|
sops.secrets =
|
||||||
let
|
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;
|
sopsFile = instance.keySopsFile;
|
||||||
mode = "0440";
|
mode = "0440";
|
||||||
owner = cfg.user;
|
owner = cfg.user;
|
||||||
group = cfg.group;
|
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;
|
sopsFile = instance.keySopsFile;
|
||||||
mode = "0440";
|
mode = "0440";
|
||||||
owner = cfg.user;
|
owner = cfg.user;
|
||||||
group = cfg.group;
|
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}" = {
|
"${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;
|
sopsFile = instance.keySopsFile;
|
||||||
mode = "0440";
|
mode = "0440";
|
||||||
owner = cfg.user;
|
owner = cfg.user;
|
||||||
group = cfg.group;
|
group = cfg.group;
|
||||||
};
|
};
|
||||||
}) instance.repositories))
|
}
|
||||||
|
) instance.repositories
|
||||||
|
)
|
||||||
|
)
|
||||||
);
|
);
|
||||||
in
|
in
|
||||||
lib.mkMerge (lib.flatten (lib.attrsets.mapAttrsToList mkSopsSecret enabledInstances));
|
lib.mkMerge (lib.flatten (lib.attrsets.mapAttrsToList mkSopsSecret enabledInstances));
|
||||||
|
|
@ -238,7 +283,10 @@ in
|
||||||
User = cfg.user;
|
User = cfg.user;
|
||||||
Group = cfg.group;
|
Group = cfg.group;
|
||||||
ExecStartPre = [ "" ]; # Do not sleep before starting.
|
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
|
# For borgmatic, since we have only one service, we need to merge all environmentFile
|
||||||
# from all instances.
|
# from all instances.
|
||||||
EnvironmentFile = lib.mapAttrsToList (name: value: value.environmentFile) enabledInstances;
|
EnvironmentFile = lib.mapAttrsToList (name: value: value.environmentFile) enabledInstances;
|
||||||
|
|
@ -248,15 +296,17 @@ in
|
||||||
systemd.packages = lib.mkIf (enabledInstances != { }) [ pkgs.borgmatic ];
|
systemd.packages = lib.mkIf (enabledInstances != { }) [ pkgs.borgmatic ];
|
||||||
environment.systemPackages = (
|
environment.systemPackages = (
|
||||||
lib.optionals cfg.borgServer [ pkgs.borgbackup ]
|
lib.optionals cfg.borgServer [ pkgs.borgbackup ]
|
||||||
++ lib.optionals (enabledInstances != {}) [ pkgs.borgbackup pkgs.borgmatic ]
|
++ lib.optionals (enabledInstances != { }) [
|
||||||
|
pkgs.borgbackup
|
||||||
|
pkgs.borgmatic
|
||||||
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
environment.etc =
|
environment.etc =
|
||||||
let
|
let
|
||||||
mkSettings = name: instance: {
|
mkSettings = name: instance: {
|
||||||
"borgmatic.d/${name}.yaml".text = lib.generators.toYAML { } {
|
"borgmatic.d/${name}.yaml".text = lib.generators.toYAML { } {
|
||||||
location =
|
location = {
|
||||||
{
|
|
||||||
source_directories = instance.sourceDirectories;
|
source_directories = instance.sourceDirectories;
|
||||||
repositories = map ({ path, ... }: path) instance.repositories;
|
repositories = map ({ path, ... }: path) instance.repositories;
|
||||||
}
|
}
|
||||||
|
|
@ -291,5 +341,6 @@ in
|
||||||
in
|
in
|
||||||
lib.mkMerge (lib.attrsets.mapAttrsToList mkSettings enabledInstances);
|
lib.mkMerge (lib.attrsets.mapAttrsToList mkSettings enabledInstances);
|
||||||
}
|
}
|
||||||
]);
|
]
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,8 @@ in
|
||||||
mounts = lib.mkOption {
|
mounts = lib.mkOption {
|
||||||
description = "List of mounts.";
|
description = "List of mounts.";
|
||||||
default = [ ];
|
default = [ ];
|
||||||
type = lib.types.listOf (lib.types.submodule {
|
type = lib.types.listOf (
|
||||||
|
lib.types.submodule {
|
||||||
options = {
|
options = {
|
||||||
remoteUrl = lib.mkOption {
|
remoteUrl = lib.mkOption {
|
||||||
type = lib.types.str;
|
type = lib.types.str;
|
||||||
|
|
@ -66,7 +67,8 @@ in
|
||||||
default = true;
|
default = true;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
});
|
}
|
||||||
|
);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,21 @@
|
||||||
{ config, lib, pkgs, ... }:
|
{
|
||||||
|
config,
|
||||||
|
lib,
|
||||||
|
pkgs,
|
||||||
|
...
|
||||||
|
}:
|
||||||
let
|
let
|
||||||
cfg = config.shb.hardcodedsecret;
|
cfg = config.shb.hardcodedsecret;
|
||||||
|
|
||||||
contracts = pkgs.callPackage ../contracts { };
|
contracts = pkgs.callPackage ../contracts { };
|
||||||
|
|
||||||
inherit (lib) mapAttrs' mkOption nameValuePair;
|
inherit (lib) mapAttrs' mkOption nameValuePair;
|
||||||
inherit (lib.types) attrsOf nullOr str submodule;
|
inherit (lib.types)
|
||||||
|
attrsOf
|
||||||
|
nullOr
|
||||||
|
str
|
||||||
|
submodule
|
||||||
|
;
|
||||||
inherit (pkgs) writeText;
|
inherit (pkgs) writeText;
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
|
|
@ -26,7 +36,10 @@ in
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
'';
|
'';
|
||||||
type = attrsOf (submodule ({ name, ... }: {
|
type = attrsOf (
|
||||||
|
submodule (
|
||||||
|
{ name, ... }:
|
||||||
|
{
|
||||||
options = contracts.secret.mkProvider {
|
options = contracts.secret.mkProvider {
|
||||||
settings = mkOption {
|
settings = mkOption {
|
||||||
description = ''
|
description = ''
|
||||||
|
|
@ -62,15 +75,20 @@ in
|
||||||
path = "/run/hardcodedsecrets/hardcodedsecret_${name}";
|
path = "/run/hardcodedsecrets/hardcodedsecret_${name}";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}));
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
config = {
|
config = {
|
||||||
system.activationScripts = mapAttrs' (n: cfg':
|
system.activationScripts = mapAttrs' (
|
||||||
|
n: cfg':
|
||||||
let
|
let
|
||||||
source = if cfg'.settings.source != null
|
source =
|
||||||
then cfg'.settings.source
|
if cfg'.settings.source != null then
|
||||||
else writeText "hardcodedsecret_${n}_content" cfg'.settings.content;
|
cfg'.settings.source
|
||||||
|
else
|
||||||
|
writeText "hardcodedsecret_${n}_content" cfg'.settings.content;
|
||||||
in
|
in
|
||||||
nameValuePair "hardcodedsecret_${n}" ''
|
nameValuePair "hardcodedsecret_${n}" ''
|
||||||
mkdir -p "$(dirname "${cfg'.result.path}")"
|
mkdir -p "$(dirname "${cfg'.result.path}")"
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,9 @@
|
||||||
{ config, pkgs, lib, ... }:
|
{
|
||||||
|
config,
|
||||||
|
pkgs,
|
||||||
|
lib,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
|
||||||
let
|
let
|
||||||
cfg = config.shb.lldap;
|
cfg = config.shb.lldap;
|
||||||
|
|
@ -145,7 +150,9 @@ in
|
||||||
```
|
```
|
||||||
'';
|
'';
|
||||||
readOnly = true;
|
readOnly = true;
|
||||||
default = { path = "/var/lib/lldap"; };
|
default = {
|
||||||
|
path = "/var/lib/lldap";
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
backup = lib.mkOption {
|
backup = lib.mkOption {
|
||||||
|
|
@ -340,10 +347,16 @@ in
|
||||||
locations."/" = {
|
locations."/" = {
|
||||||
extraConfig = ''
|
extraConfig = ''
|
||||||
proxy_set_header Host $host;
|
proxy_set_header Host $host;
|
||||||
'' + (if isNull cfg.restrictAccessIPRange then "" else ''
|
''
|
||||||
|
+ (
|
||||||
|
if isNull cfg.restrictAccessIPRange then
|
||||||
|
""
|
||||||
|
else
|
||||||
|
''
|
||||||
allow ${cfg.restrictAccessIPRange};
|
allow ${cfg.restrictAccessIPRange};
|
||||||
deny all;
|
deny all;
|
||||||
'');
|
''
|
||||||
|
);
|
||||||
proxyPass = "http://${toString config.services.lldap.settings.http_host}:${toString config.shb.lldap.webUIListenPort}/";
|
proxyPass = "http://${toString config.services.lldap.settings.http_host}:${toString config.shb.lldap.webUIListenPort}/";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
@ -382,9 +395,13 @@ in
|
||||||
};
|
};
|
||||||
|
|
||||||
inherit (cfg) ensureGroups ensureUserFields ensureGroupFields;
|
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;
|
"password_file" = toString v.password.result.path;
|
||||||
}) cfg.ensureUsers;
|
}
|
||||||
|
) cfg.ensureUsers;
|
||||||
};
|
};
|
||||||
|
|
||||||
shb.mitmdump.instances."lldap-web" = lib.mkIf cfg.debug {
|
shb.mitmdump.instances."lldap-web" = lib.mkIf cfg.debug {
|
||||||
|
|
@ -393,7 +410,8 @@ in
|
||||||
after = [ "lldap.service" ];
|
after = [ "lldap.service" ];
|
||||||
enabledAddons = [ config.shb.mitmdump.addons.logger ];
|
enabledAddons = [ config.shb.mitmdump.addons.logger ];
|
||||||
extraArgs = [
|
extraArgs = [
|
||||||
"--set" "verbose_pattern=/api"
|
"--set"
|
||||||
|
"verbose_pattern=/api"
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,34 @@
|
||||||
{ config, lib, pkgs, ... }:
|
{
|
||||||
|
config,
|
||||||
|
lib,
|
||||||
|
pkgs,
|
||||||
|
...
|
||||||
|
}:
|
||||||
let
|
let
|
||||||
inherit (lib) mapAttrs' mkOption nameValuePair types;
|
inherit (lib)
|
||||||
inherit (types) attrsOf listOf port submodule str;
|
mapAttrs'
|
||||||
|
mkOption
|
||||||
|
nameValuePair
|
||||||
|
types
|
||||||
|
;
|
||||||
|
inherit (types)
|
||||||
|
attrsOf
|
||||||
|
listOf
|
||||||
|
port
|
||||||
|
submodule
|
||||||
|
str
|
||||||
|
;
|
||||||
|
|
||||||
cfg = config.shb.mitmdump;
|
cfg = config.shb.mitmdump;
|
||||||
|
|
||||||
mitmdumpScript = pkgs.writers.writePython3Bin "mitmdump"
|
mitmdumpScript =
|
||||||
|
pkgs.writers.writePython3Bin "mitmdump"
|
||||||
{
|
{
|
||||||
libraries = let
|
libraries =
|
||||||
|
let
|
||||||
p = pkgs.python3Packages;
|
p = pkgs.python3Packages;
|
||||||
in [
|
in
|
||||||
|
[
|
||||||
p.systemd
|
p.systemd
|
||||||
p.mitmproxy
|
p.mitmproxy
|
||||||
];
|
];
|
||||||
|
|
@ -81,8 +100,8 @@ let
|
||||||
proc.wait()
|
proc.wait()
|
||||||
'';
|
'';
|
||||||
|
|
||||||
logger = toString (pkgs.writers.writeText "loggerAddon.py"
|
logger = toString (
|
||||||
''
|
pkgs.writers.writeText "loggerAddon.py" ''
|
||||||
import logging
|
import logging
|
||||||
from collections.abc import Sequence
|
from collections.abc import Sequence
|
||||||
from mitmproxy import ctx, http
|
from mitmproxy import ctx, http
|
||||||
|
|
@ -131,7 +150,8 @@ let
|
||||||
|
|
||||||
|
|
||||||
addons = [RegexLogger()]
|
addons = [RegexLogger()]
|
||||||
'');
|
''
|
||||||
|
);
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
options.shb.mitmdump = {
|
options.shb.mitmdump = {
|
||||||
|
|
@ -148,7 +168,10 @@ in
|
||||||
instances = mkOption {
|
instances = mkOption {
|
||||||
default = { };
|
default = { };
|
||||||
description = "Mitmdump instance.";
|
description = "Mitmdump instance.";
|
||||||
type = attrsOf (submodule ({ name, ... }: {
|
type = attrsOf (
|
||||||
|
submodule (
|
||||||
|
{ name, ... }:
|
||||||
|
{
|
||||||
options = {
|
options = {
|
||||||
package = lib.mkPackageOption pkgs "mitmproxy" { };
|
package = lib.mkPackageOption pkgs "mitmproxy" { };
|
||||||
|
|
||||||
|
|
@ -230,12 +253,16 @@ in
|
||||||
example = lib.literalExpression ''[ "--set" "verbose_pattern=/api" ]'';
|
example = lib.literalExpression ''[ "--set" "verbose_pattern=/api" ]'';
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}));
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
config = {
|
config = {
|
||||||
systemd.services = mapAttrs' (name: cfg': nameValuePair "mitmdump-${name}" {
|
systemd.services = mapAttrs' (
|
||||||
|
name: cfg':
|
||||||
|
nameValuePair "mitmdump-${name}" {
|
||||||
environment = {
|
environment = {
|
||||||
"HOME" = "/var/lib/private/mitmdump-${name}";
|
"HOME" = "/var/lib/private/mitmdump-${name}";
|
||||||
"MITMDUMP_BIN" = "${cfg'.package}/bin/mitmdump";
|
"MITMDUMP_BIN" = "${cfg'.package}/bin/mitmdump";
|
||||||
|
|
@ -250,7 +277,8 @@ in
|
||||||
WorkingDirectory = "/var/lib/mitmdump-${name}";
|
WorkingDirectory = "/var/lib/mitmdump-${name}";
|
||||||
StateDirectory = "mitmdump-${name}";
|
StateDirectory = "mitmdump-${name}";
|
||||||
|
|
||||||
ExecStart = let
|
ExecStart =
|
||||||
|
let
|
||||||
addons = lib.concatMapStringsSep " " (addon: "-s ${addon}") cfg'.enabledAddons;
|
addons = lib.concatMapStringsSep " " (addon: "-s ${addon}") cfg'.enabledAddons;
|
||||||
extraArgs = lib.concatStringsSep " " cfg'.extraArgs;
|
extraArgs = lib.concatStringsSep " " cfg'.extraArgs;
|
||||||
in
|
in
|
||||||
|
|
@ -259,7 +287,8 @@ in
|
||||||
requires = cfg'.after;
|
requires = cfg'.after;
|
||||||
after = cfg'.after;
|
after = cfg'.after;
|
||||||
wantedBy = [ "multi-user.target" ];
|
wantedBy = [ "multi-user.target" ];
|
||||||
}) cfg.instances;
|
}
|
||||||
|
) cfg.instances;
|
||||||
|
|
||||||
shb.mitmdump.addons = {
|
shb.mitmdump.addons = {
|
||||||
inherit logger;
|
inherit logger;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,9 @@
|
||||||
{ config, pkgs, lib, ... }:
|
{
|
||||||
|
config,
|
||||||
|
pkgs,
|
||||||
|
lib,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
|
||||||
let
|
let
|
||||||
cfg = config.shb.monitoring;
|
cfg = config.shb.monitoring;
|
||||||
|
|
@ -53,7 +58,10 @@ in
|
||||||
};
|
};
|
||||||
|
|
||||||
lokiMajorVersion = lib.mkOption {
|
lokiMajorVersion = lib.mkOption {
|
||||||
type = lib.types.enum [ 2 3 ];
|
type = lib.types.enum [
|
||||||
|
2
|
||||||
|
3
|
||||||
|
];
|
||||||
description = ''
|
description = ''
|
||||||
Switching from version 2 to 3 requires manual intervention
|
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
|
https://grafana.com/docs/loki/latest/setup/upgrade/#main--unreleased. So this let's the user
|
||||||
|
|
@ -114,7 +122,8 @@ in
|
||||||
smtp = lib.mkOption {
|
smtp = lib.mkOption {
|
||||||
description = "SMTP options.";
|
description = "SMTP options.";
|
||||||
default = null;
|
default = null;
|
||||||
type = lib.types.nullOr (lib.types.submodule {
|
type = lib.types.nullOr (
|
||||||
|
lib.types.submodule {
|
||||||
options = {
|
options = {
|
||||||
from_address = lib.mkOption {
|
from_address = lib.mkOption {
|
||||||
type = lib.types.str;
|
type = lib.types.str;
|
||||||
|
|
@ -144,7 +153,8 @@ in
|
||||||
description = "File containing the password to connect to the SMTP host.";
|
description = "File containing the password to connect to the SMTP host.";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
});
|
}
|
||||||
|
);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -204,12 +214,14 @@ in
|
||||||
services.grafana.provision = {
|
services.grafana.provision = {
|
||||||
dashboards.settings = lib.mkIf cfg.provisionDashboards {
|
dashboards.settings = lib.mkIf cfg.provisionDashboards {
|
||||||
apiVersion = 1;
|
apiVersion = 1;
|
||||||
providers = [{
|
providers = [
|
||||||
|
{
|
||||||
folder = "Self Host Blocks";
|
folder = "Self Host Blocks";
|
||||||
options.path = ./monitoring/dashboards;
|
options.path = ./monitoring/dashboards;
|
||||||
allowUiUpdates = true;
|
allowUiUpdates = true;
|
||||||
disableDeletion = true;
|
disableDeletion = true;
|
||||||
}];
|
}
|
||||||
|
];
|
||||||
};
|
};
|
||||||
datasources.settings = {
|
datasources.settings = {
|
||||||
apiVersion = 1;
|
apiVersion = 1;
|
||||||
|
|
@ -245,26 +257,35 @@ in
|
||||||
};
|
};
|
||||||
alerting.contactPoints.settings = {
|
alerting.contactPoints.settings = {
|
||||||
apiVersion = 1;
|
apiVersion = 1;
|
||||||
contactPoints = [{
|
contactPoints = [
|
||||||
|
{
|
||||||
inherit (cfg) orgId;
|
inherit (cfg) orgId;
|
||||||
name = "grafana-default-email";
|
name = "grafana-default-email";
|
||||||
receivers = lib.optionals ((builtins.length cfg.contactPoints) > 0) [{
|
receivers = lib.optionals ((builtins.length cfg.contactPoints) > 0) [
|
||||||
|
{
|
||||||
uid = "sysadmin";
|
uid = "sysadmin";
|
||||||
type = "email";
|
type = "email";
|
||||||
settings.addresses = lib.concatStringsSep ";" cfg.contactPoints;
|
settings.addresses = lib.concatStringsSep ";" cfg.contactPoints;
|
||||||
}];
|
}
|
||||||
}];
|
];
|
||||||
|
}
|
||||||
|
];
|
||||||
};
|
};
|
||||||
alerting.policies.settings = {
|
alerting.policies.settings = {
|
||||||
apiVersion = 1;
|
apiVersion = 1;
|
||||||
policies = [{
|
policies = [
|
||||||
|
{
|
||||||
inherit (cfg) orgId;
|
inherit (cfg) orgId;
|
||||||
receiver = "grafana-default-email";
|
receiver = "grafana-default-email";
|
||||||
group_by = [ "grafana_folder" "alertname" ];
|
group_by = [
|
||||||
|
"grafana_folder"
|
||||||
|
"alertname"
|
||||||
|
];
|
||||||
group_wait = "30s";
|
group_wait = "30s";
|
||||||
group_interval = "5m";
|
group_interval = "5m";
|
||||||
repeat_interval = "4h";
|
repeat_interval = "4h";
|
||||||
}];
|
}
|
||||||
|
];
|
||||||
# resetPolicies seems to happen after setting the above policies, effectively rolling back
|
# resetPolicies seems to happen after setting the above policies, effectively rolling back
|
||||||
# any updates.
|
# any updates.
|
||||||
};
|
};
|
||||||
|
|
@ -275,13 +296,15 @@ in
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
apiVersion = 1;
|
apiVersion = 1;
|
||||||
groups = [{
|
groups = [
|
||||||
|
{
|
||||||
inherit (cfg) orgId;
|
inherit (cfg) orgId;
|
||||||
name = "SysAdmin";
|
name = "SysAdmin";
|
||||||
folder = "Self Host Blocks";
|
folder = "Self Host Blocks";
|
||||||
interval = "10m";
|
interval = "10m";
|
||||||
inherit rules;
|
inherit rules;
|
||||||
}];
|
}
|
||||||
|
];
|
||||||
# deleteRules seems to happen after creating the above rules, effectively rolling back
|
# deleteRules seems to happen after creating the above rules, effectively rolling back
|
||||||
# any updates.
|
# any updates.
|
||||||
};
|
};
|
||||||
|
|
@ -295,9 +318,13 @@ in
|
||||||
services.loki = {
|
services.loki = {
|
||||||
enable = true;
|
enable = true;
|
||||||
dataDir = "/var/lib/loki";
|
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
|
# 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";
|
version = "2.9.6";
|
||||||
|
|
||||||
src = pkgs.fetchFromGitHub {
|
src = pkgs.fetchFromGitHub {
|
||||||
|
|
@ -315,7 +342,11 @@ in
|
||||||
# Removes "cmd/lokitool"
|
# 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"
|
"-s"
|
||||||
"-w"
|
"-w"
|
||||||
"-X ${t}.Version=${version}"
|
"-X ${t}.Version=${version}"
|
||||||
|
|
@ -324,7 +355,8 @@ in
|
||||||
"-X ${t}.Branch=unknown"
|
"-X ${t}.Branch=unknown"
|
||||||
"-X ${t}.Revision=unknown"
|
"-X ${t}.Revision=unknown"
|
||||||
];
|
];
|
||||||
}));
|
}
|
||||||
|
));
|
||||||
configuration = {
|
configuration = {
|
||||||
auth_enabled = false;
|
auth_enabled = false;
|
||||||
|
|
||||||
|
|
@ -448,41 +480,52 @@ in
|
||||||
services.prometheus.scrapeConfigs = [
|
services.prometheus.scrapeConfigs = [
|
||||||
{
|
{
|
||||||
job_name = "node";
|
job_name = "node";
|
||||||
static_configs = [{
|
static_configs = [
|
||||||
|
{
|
||||||
targets = [ "127.0.0.1:${toString config.services.prometheus.exporters.node.port}" ];
|
targets = [ "127.0.0.1:${toString config.services.prometheus.exporters.node.port}" ];
|
||||||
labels = commonLabels;
|
labels = commonLabels;
|
||||||
}];
|
}
|
||||||
|
];
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
job_name = "netdata";
|
job_name = "netdata";
|
||||||
metrics_path = "/api/v1/allmetrics";
|
metrics_path = "/api/v1/allmetrics";
|
||||||
params.format = [ "prometheus" ];
|
params.format = [ "prometheus" ];
|
||||||
honor_labels = true;
|
honor_labels = true;
|
||||||
static_configs = [{
|
static_configs = [
|
||||||
|
{
|
||||||
targets = [ "127.0.0.1:19999" ];
|
targets = [ "127.0.0.1:19999" ];
|
||||||
labels = commonLabels;
|
labels = commonLabels;
|
||||||
}];
|
}
|
||||||
|
];
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
job_name = "smartctl";
|
job_name = "smartctl";
|
||||||
static_configs = [{
|
static_configs = [
|
||||||
|
{
|
||||||
targets = [ "127.0.0.1:${toString config.services.prometheus.exporters.smartctl.port}" ];
|
targets = [ "127.0.0.1:${toString config.services.prometheus.exporters.smartctl.port}" ];
|
||||||
labels = commonLabels;
|
labels = commonLabels;
|
||||||
}];
|
}
|
||||||
|
];
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
job_name = "prometheus_internal";
|
job_name = "prometheus_internal";
|
||||||
static_configs = [{
|
static_configs = [
|
||||||
|
{
|
||||||
targets = [ "127.0.0.1:${toString config.services.prometheus.port}" ];
|
targets = [ "127.0.0.1:${toString config.services.prometheus.port}" ];
|
||||||
labels = commonLabels;
|
labels = commonLabels;
|
||||||
}];
|
|
||||||
}
|
}
|
||||||
] ++ (lib.lists.optional config.services.nginx.enable {
|
];
|
||||||
|
}
|
||||||
|
]
|
||||||
|
++ (lib.lists.optional config.services.nginx.enable {
|
||||||
job_name = "nginx";
|
job_name = "nginx";
|
||||||
static_configs = [{
|
static_configs = [
|
||||||
|
{
|
||||||
targets = [ "127.0.0.1:${toString config.services.prometheus.exporters.nginx.port}" ];
|
targets = [ "127.0.0.1:${toString config.services.prometheus.exporters.nginx.port}" ];
|
||||||
labels = commonLabels;
|
labels = commonLabels;
|
||||||
}];
|
}
|
||||||
|
];
|
||||||
# }) ++ (lib.optional (builtins.length (lib.attrNames config.services.redis.servers) > 0) {
|
# }) ++ (lib.optional (builtins.length (lib.attrNames config.services.redis.servers) > 0) {
|
||||||
# job_name = "redis";
|
# job_name = "redis";
|
||||||
# static_configs = [
|
# static_configs = [
|
||||||
|
|
@ -497,12 +540,15 @@ in
|
||||||
# targets = ["127.0.0.1:${toString config.services.prometheus.exporters.openvpn.port}"];
|
# 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";
|
job_name = "dnsmasq";
|
||||||
static_configs = [{
|
static_configs = [
|
||||||
|
{
|
||||||
targets = [ "127.0.0.1:${toString config.services.prometheus.exporters.dnsmasq.port}" ];
|
targets = [ "127.0.0.1:${toString config.services.prometheus.exporters.dnsmasq.port}" ];
|
||||||
labels = commonLabels;
|
labels = commonLabels;
|
||||||
}];
|
}
|
||||||
|
];
|
||||||
});
|
});
|
||||||
services.prometheus.exporters.nginx = lib.mkIf config.services.nginx.enable {
|
services.prometheus.exporters.nginx = lib.mkIf config.services.nginx.enable {
|
||||||
enable = true;
|
enable = true;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,9 @@
|
||||||
{ config, pkgs, lib, ... }:
|
{
|
||||||
|
config,
|
||||||
|
pkgs,
|
||||||
|
lib,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
|
||||||
let
|
let
|
||||||
cfg = config.shb.nginx;
|
cfg = config.shb.nginx;
|
||||||
|
|
@ -44,7 +49,8 @@ let
|
||||||
type = lib.types.listOf (lib.types.attrsOf lib.types.anything);
|
type = lib.types.listOf (lib.types.attrsOf lib.types.anything);
|
||||||
default = [ ];
|
default = [ ];
|
||||||
description = "Authelia rule configuration";
|
description = "Authelia rule configuration";
|
||||||
example = lib.literalExpression ''[{
|
example = lib.literalExpression ''
|
||||||
|
[{
|
||||||
policy = "two_factor";
|
policy = "two_factor";
|
||||||
subject = ["group:service_user"];
|
subject = ["group:service_user"];
|
||||||
}]'';
|
}]'';
|
||||||
|
|
@ -82,7 +88,10 @@ in
|
||||||
};
|
};
|
||||||
|
|
||||||
config = {
|
config = {
|
||||||
networking.firewall.allowedTCPPorts = [ 80 443 ];
|
networking.firewall.allowedTCPPorts = [
|
||||||
|
80
|
||||||
|
443
|
||||||
|
];
|
||||||
|
|
||||||
services.nginx.enable = true;
|
services.nginx.enable = true;
|
||||||
services.nginx.logError = lib.mkIf cfg.debugLog "stderr warn";
|
services.nginx.logError = lib.mkIf cfg.debugLog "stderr warn";
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,15 @@
|
||||||
{ config, lib, pkgs, ... }:
|
{
|
||||||
|
config,
|
||||||
|
lib,
|
||||||
|
pkgs,
|
||||||
|
...
|
||||||
|
}:
|
||||||
let
|
let
|
||||||
cfg = config.shb.postgresql;
|
cfg = config.shb.postgresql;
|
||||||
contracts = pkgs.callPackage ../contracts { };
|
contracts = pkgs.callPackage ../contracts { };
|
||||||
|
|
||||||
upgrade-script = old: new:
|
upgrade-script =
|
||||||
|
old: new:
|
||||||
let
|
let
|
||||||
oldStr = builtins.toString old;
|
oldStr = builtins.toString old;
|
||||||
newStr = builtins.toString new;
|
newStr = builtins.toString new;
|
||||||
|
|
@ -75,7 +81,8 @@ in
|
||||||
|
|
||||||
ensures = lib.mkOption {
|
ensures = lib.mkOption {
|
||||||
description = "List of username, database and/or passwords that should be created.";
|
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 = {
|
options = {
|
||||||
username = lib.mkOption {
|
username = lib.mkOption {
|
||||||
type = lib.types.str;
|
type = lib.types.str;
|
||||||
|
|
@ -94,7 +101,8 @@ in
|
||||||
example = "/run/secrets/postgresql/password";
|
example = "/run/secrets/postgresql/password";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
});
|
}
|
||||||
|
);
|
||||||
default = [ ];
|
default = [ ];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
@ -123,16 +131,19 @@ in
|
||||||
dbConfig = ensureCfgs: {
|
dbConfig = ensureCfgs: {
|
||||||
services.postgresql.enable = lib.mkDefault ((builtins.length ensureCfgs) > 0);
|
services.postgresql.enable = lib.mkDefault ((builtins.length ensureCfgs) > 0);
|
||||||
services.postgresql.ensureDatabases = map ({ database, ... }: database) ensureCfgs;
|
services.postgresql.ensureDatabases = map ({ database, ... }: database) ensureCfgs;
|
||||||
services.postgresql.ensureUsers = map ({ username, database, ... }: {
|
services.postgresql.ensureUsers = map (
|
||||||
|
{ username, database, ... }:
|
||||||
|
{
|
||||||
name = username;
|
name = username;
|
||||||
ensureDBOwnership = true;
|
ensureDBOwnership = true;
|
||||||
ensureClauses.login = true;
|
ensureClauses.login = true;
|
||||||
}) ensureCfgs;
|
}
|
||||||
|
) ensureCfgs;
|
||||||
};
|
};
|
||||||
|
|
||||||
pwdConfig = ensureCfgs: {
|
pwdConfig = ensureCfgs: {
|
||||||
systemd.services.postgresql-setup.script = lib.mkAfter
|
systemd.services.postgresql-setup.script = lib.mkAfter (
|
||||||
(let
|
let
|
||||||
prefix = ''
|
prefix = ''
|
||||||
psql -tA <<'EOF'
|
psql -tA <<'EOF'
|
||||||
DO $$
|
DO $$
|
||||||
|
|
@ -143,17 +154,24 @@ in
|
||||||
END $$;
|
END $$;
|
||||||
EOF
|
EOF
|
||||||
'';
|
'';
|
||||||
exec = { username, passwordFile, ... }: ''
|
exec =
|
||||||
|
{ username, passwordFile, ... }:
|
||||||
|
''
|
||||||
password := trim(both from replace(pg_read_file('${passwordFile}'), E'\n', '''));
|
password := trim(both from replace(pg_read_file('${passwordFile}'), E'\n', '''));
|
||||||
EXECUTE format('ALTER ROLE ${username} WITH PASSWORD '''%s''';', password);
|
EXECUTE format('ALTER ROLE ${username} WITH PASSWORD '''%s''';', password);
|
||||||
'';
|
'';
|
||||||
cfgsWithPasswords = builtins.filter (cfg: cfg.passwordFile != null) ensureCfgs;
|
cfgsWithPasswords = builtins.filter (cfg: cfg.passwordFile != null) ensureCfgs;
|
||||||
in
|
in
|
||||||
if (builtins.length cfgsWithPasswords) == 0 then "" else
|
if (builtins.length cfgsWithPasswords) == 0 then
|
||||||
prefix + (lib.concatStrings (map exec cfgsWithPasswords)) + suffix);
|
""
|
||||||
|
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";
|
services.postgresql.settings.shared_preload_libraries = "auto_explain, pg_stat_statements";
|
||||||
};
|
};
|
||||||
in
|
in
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,55 @@
|
||||||
{ config, pkgs, lib, utils, ... }:
|
{
|
||||||
|
config,
|
||||||
|
pkgs,
|
||||||
|
lib,
|
||||||
|
utils,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
|
||||||
let
|
let
|
||||||
cfg = config.shb.restic;
|
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)
|
||||||
inherit (lib) hasPrefix mkIf nameValuePair optionalAttrs removePrefix;
|
concatStringsSep
|
||||||
inherit (lib.types) attrsOf enum int ints oneOf nonEmptyStr nullOr str submodule;
|
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 ''
|
enable = mkEnableOption ''
|
||||||
this backup intance.
|
this backup intance.
|
||||||
|
|
||||||
|
|
@ -76,7 +116,10 @@ let
|
||||||
|
|
||||||
retention = mkOption {
|
retention = mkOption {
|
||||||
description = "For how long to keep backup files.";
|
description = "For how long to keep backup files.";
|
||||||
type = attrsOf (oneOf [ int nonEmptyStr ]);
|
type = attrsOf (oneOf [
|
||||||
|
int
|
||||||
|
nonEmptyStr
|
||||||
|
]);
|
||||||
default = {
|
default = {
|
||||||
keep_within = "1d";
|
keep_within = "1d";
|
||||||
keep_hourly = 24;
|
keep_hourly = 24;
|
||||||
|
|
@ -109,7 +152,10 @@ in
|
||||||
instances = mkOption {
|
instances = mkOption {
|
||||||
description = "Files to backup following the [backup contract](./contracts-backup.html).";
|
description = "Files to backup following the [backup contract](./contracts-backup.html).";
|
||||||
default = { };
|
default = { };
|
||||||
type = attrsOf (submodule ({ name, config, ... }: {
|
type = attrsOf (
|
||||||
|
submodule (
|
||||||
|
{ name, config, ... }:
|
||||||
|
{
|
||||||
options = contracts.backup.mkProvider {
|
options = contracts.backup.mkProvider {
|
||||||
settings = mkOption {
|
settings = mkOption {
|
||||||
description = ''
|
description = ''
|
||||||
|
|
@ -117,7 +163,10 @@ in
|
||||||
'';
|
'';
|
||||||
|
|
||||||
type = submodule {
|
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";
|
backupServiceText = "${fullName "<name>" { path = "path/to/repository"; }}.service";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}));
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
databases = mkOption {
|
databases = mkOption {
|
||||||
description = "Databases to backup following the [database backup contract](./contracts-databasebackup.html).";
|
description = "Databases to backup following the [database backup contract](./contracts-databasebackup.html).";
|
||||||
default = { };
|
default = { };
|
||||||
type = attrsOf (submodule ({ name, config, ... }: {
|
type = attrsOf (
|
||||||
|
submodule (
|
||||||
|
{ name, config, ... }:
|
||||||
|
{
|
||||||
options = contracts.databasebackup.mkProvider {
|
options = contracts.databasebackup.mkProvider {
|
||||||
settings = mkOption {
|
settings = mkOption {
|
||||||
description = ''
|
description = ''
|
||||||
|
|
@ -143,7 +197,10 @@ in
|
||||||
'';
|
'';
|
||||||
|
|
||||||
type = submodule {
|
type = submodule {
|
||||||
options = commonOptions { inherit name config; prefix = "databases"; };
|
options = commonOptions {
|
||||||
|
inherit name config;
|
||||||
|
prefix = "databases";
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -155,7 +212,9 @@ in
|
||||||
backupServiceText = "${fullName "<name>" { path = "path/to/repository"; }}.service";
|
backupServiceText = "${fullName "<name>" { path = "path/to/repository"; }}.service";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}));
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
# Taken from https://github.com/HubbeKing/restic-kubernetes/blob/73bfbdb0ba76939a4c52173fa2dbd52070710008/README.md?plain=1#L23
|
# Taken from https://github.com/HubbeKing/restic-kubernetes/blob/73bfbdb0ba76939a4c52173fa2dbd52070710008/README.md?plain=1#L23
|
||||||
|
|
@ -170,7 +229,11 @@ in
|
||||||
default = 15;
|
default = 15;
|
||||||
};
|
};
|
||||||
ioSchedulingClass = mkOption {
|
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.";
|
description = "ionice scheduling class, defaults to best-effort IO. Only used for `restic backup`, `restic forget` and `restic check` commands.";
|
||||||
default = "best-effort";
|
default = "best-effort";
|
||||||
};
|
};
|
||||||
|
|
@ -188,15 +251,20 @@ in
|
||||||
let
|
let
|
||||||
enabledInstances = filterAttrs (k: i: i.settings.enable) cfg.instances;
|
enabledInstances = filterAttrs (k: i: i.settings.enable) cfg.instances;
|
||||||
enabledDatabases = filterAttrs (k: i: i.settings.enable) cfg.databases;
|
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.
|
# Create repository if it is a local path.
|
||||||
systemd.tmpfiles.rules =
|
systemd.tmpfiles.rules =
|
||||||
let
|
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 - -"
|
"d '${instance.settings.repository.path}' 0750 ${instance.request.user} root - -"
|
||||||
];
|
];
|
||||||
in
|
in
|
||||||
|
|
@ -219,8 +287,8 @@ in
|
||||||
|
|
||||||
inherit (instance.settings.repository) timerConfig;
|
inherit (instance.settings.repository) timerConfig;
|
||||||
|
|
||||||
pruneOpts = mapAttrsToList (name: value:
|
pruneOpts = mapAttrsToList (
|
||||||
"--${builtins.replaceStrings ["_"] ["-"] name} ${builtins.toString value}"
|
name: value: "--${builtins.replaceStrings [ "_" ] [ "-" ] name} ${builtins.toString value}"
|
||||||
) instance.settings.retention;
|
) instance.settings.retention;
|
||||||
|
|
||||||
backupPrepareCommand = concatStringsSep "\n" instance.request.hooks.beforeBackup;
|
backupPrepareCommand = concatStringsSep "\n" instance.request.hooks.beforeBackup;
|
||||||
|
|
@ -234,7 +302,8 @@ in
|
||||||
++ (optionals (instance.settings.limitDownloadKiBs != null) [
|
++ (optionals (instance.settings.limitDownloadKiBs != null) [
|
||||||
"--limit-download=${toString instance.settings.limitDownloadKiBs}"
|
"--limit-download=${toString instance.settings.limitDownloadKiBs}"
|
||||||
]);
|
]);
|
||||||
} // optionalAttrs (builtins.length instance.request.excludePatterns > 0) {
|
}
|
||||||
|
// optionalAttrs (builtins.length instance.request.excludePatterns > 0) {
|
||||||
exclude = instance.request.excludePatterns;
|
exclude = instance.request.excludePatterns;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
@ -258,8 +327,8 @@ in
|
||||||
|
|
||||||
inherit (instance.settings.repository) timerConfig;
|
inherit (instance.settings.repository) timerConfig;
|
||||||
|
|
||||||
pruneOpts = mapAttrsToList (name: value:
|
pruneOpts = mapAttrsToList (
|
||||||
"--${builtins.replaceStrings ["_"] ["-"] name} ${builtins.toString value}"
|
name: value: "--${builtins.replaceStrings [ "_" ] [ "-" ] name} ${builtins.toString value}"
|
||||||
) instance.settings.retention;
|
) instance.settings.retention;
|
||||||
|
|
||||||
extraBackupArgs =
|
extraBackupArgs =
|
||||||
|
|
@ -269,13 +338,14 @@ in
|
||||||
++ (optionals (instance.settings.limitDownloadKiBs != null) [
|
++ (optionals (instance.settings.limitDownloadKiBs != null) [
|
||||||
"--limit-download=${toString instance.settings.limitDownloadKiBs}"
|
"--limit-download=${toString instance.settings.limitDownloadKiBs}"
|
||||||
])
|
])
|
||||||
++
|
++ (
|
||||||
(let
|
let
|
||||||
cmd = pkgs.writeShellScriptBin "dump.sh" instance.request.backupCmd;
|
cmd = pkgs.writeShellScriptBin "dump.sh" instance.request.backupCmd;
|
||||||
in
|
in
|
||||||
[
|
[
|
||||||
"--stdin-filename ${instance.request.backupName} --stdin-from-command -- ${cmd}/bin/dump.sh"
|
"--stdin-filename ${instance.request.backupName} --stdin-from-command -- ${cmd}/bin/dump.sh"
|
||||||
]);
|
]
|
||||||
|
);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
in
|
in
|
||||||
|
|
@ -284,7 +354,8 @@ in
|
||||||
{
|
{
|
||||||
systemd.services =
|
systemd.services =
|
||||||
let
|
let
|
||||||
mkSettings = name: instance:
|
mkSettings =
|
||||||
|
name: instance:
|
||||||
let
|
let
|
||||||
serviceName = fullName name instance.settings.repository;
|
serviceName = fullName name instance.settings.repository;
|
||||||
in
|
in
|
||||||
|
|
@ -298,8 +369,7 @@ in
|
||||||
# BindReadOnlyPaths = instance.sourceDirectories;
|
# BindReadOnlyPaths = instance.sourceDirectories;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
(optionalAttrs (instance.settings.repository.secrets != {})
|
(optionalAttrs (instance.settings.repository.secrets != { }) {
|
||||||
{
|
|
||||||
serviceConfig.EnvironmentFile = [
|
serviceConfig.EnvironmentFile = [
|
||||||
"/run/secrets_restic/${serviceName}"
|
"/run/secrets_restic/${serviceName}"
|
||||||
];
|
];
|
||||||
|
|
@ -308,8 +378,8 @@ in
|
||||||
})
|
})
|
||||||
];
|
];
|
||||||
|
|
||||||
"${serviceName}-pre" = mkIf (instance.settings.repository.secrets != {})
|
"${serviceName}-pre" = mkIf (instance.settings.repository.secrets != { }) (
|
||||||
(let
|
let
|
||||||
script = lib.shb.genConfigOutOfBandSystemd {
|
script = lib.shb.genConfigOutOfBandSystemd {
|
||||||
config = instance.settings.repository.secrets;
|
config = instance.settings.repository.secrets;
|
||||||
configLocation = "/run/secrets_restic/${serviceName}";
|
configLocation = "/run/secrets_restic/${serviceName}";
|
||||||
|
|
@ -321,19 +391,23 @@ in
|
||||||
script = script.preStart;
|
script = script.preStart;
|
||||||
serviceConfig.Type = "oneshot";
|
serviceConfig.Type = "oneshot";
|
||||||
serviceConfig.LoadCredential = script.loadCredentials;
|
serviceConfig.LoadCredential = script.loadCredentials;
|
||||||
});
|
}
|
||||||
|
);
|
||||||
};
|
};
|
||||||
in
|
in
|
||||||
mkMerge (flatten (mapAttrsToList mkSettings (enabledInstances // enabledDatabases)));
|
mkMerge (flatten (mapAttrsToList mkSettings (enabledInstances // enabledDatabases)));
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
systemd.services = let
|
systemd.services =
|
||||||
mkEnv = name: instance:
|
let
|
||||||
|
mkEnv =
|
||||||
|
name: instance:
|
||||||
nameValuePair "${fullName name instance.settings.repository}_restore_gen" {
|
nameValuePair "${fullName name instance.settings.repository}_restore_gen" {
|
||||||
enable = true;
|
enable = true;
|
||||||
wantedBy = [ "multi-user.target" ];
|
wantedBy = [ "multi-user.target" ];
|
||||||
serviceConfig.Type = "oneshot";
|
serviceConfig.Type = "oneshot";
|
||||||
script = (lib.shb.replaceSecrets {
|
script = (
|
||||||
|
lib.shb.replaceSecrets {
|
||||||
userConfig = instance.settings.repository.secrets // {
|
userConfig = instance.settings.repository.secrets // {
|
||||||
RESTIC_PASSWORD_FILE = toString instance.settings.passphrase.result.path;
|
RESTIC_PASSWORD_FILE = toString instance.settings.passphrase.result.path;
|
||||||
RESTIC_REPOSITORY = instance.settings.repository.path;
|
RESTIC_REPOSITORY = instance.settings.repository.path;
|
||||||
|
|
@ -341,14 +415,17 @@ in
|
||||||
resultPath = "/run/secrets_restic_env/${fullName name instance.settings.repository}";
|
resultPath = "/run/secrets_restic_env/${fullName name instance.settings.repository}";
|
||||||
generator = lib.shb.toEnvVar;
|
generator = lib.shb.toEnvVar;
|
||||||
user = instance.request.user;
|
user = instance.request.user;
|
||||||
});
|
}
|
||||||
|
);
|
||||||
};
|
};
|
||||||
in
|
in
|
||||||
listToAttrs (flatten (mapAttrsToList mkEnv (cfg.instances // cfg.databases)));
|
listToAttrs (flatten (mapAttrsToList mkEnv (cfg.instances // cfg.databases)));
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
environment.systemPackages = let
|
environment.systemPackages =
|
||||||
mkResticBinary = name: instance:
|
let
|
||||||
|
mkResticBinary =
|
||||||
|
name: instance:
|
||||||
pkgs.writeShellScriptBin (fullName name instance.settings.repository) ''
|
pkgs.writeShellScriptBin (fullName name instance.settings.repository) ''
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
|
|
@ -366,8 +443,10 @@ in
|
||||||
flatten (mapAttrsToList mkResticBinary cfg.instances);
|
flatten (mapAttrsToList mkResticBinary cfg.instances);
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
environment.systemPackages = let
|
environment.systemPackages =
|
||||||
mkResticBinary = name: instance:
|
let
|
||||||
|
mkResticBinary =
|
||||||
|
name: instance:
|
||||||
pkgs.writeShellScriptBin (fullName name instance.settings.repository) ''
|
pkgs.writeShellScriptBin (fullName name instance.settings.repository) ''
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
|
|
@ -384,5 +463,6 @@ in
|
||||||
in
|
in
|
||||||
flatten (mapAttrsToList mkResticBinary cfg.databases);
|
flatten (mapAttrsToList mkResticBinary cfg.databases);
|
||||||
}
|
}
|
||||||
]);
|
]
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,9 @@
|
||||||
{ config, lib, pkgs, ... }:
|
{
|
||||||
|
config,
|
||||||
|
lib,
|
||||||
|
pkgs,
|
||||||
|
...
|
||||||
|
}:
|
||||||
let
|
let
|
||||||
inherit (lib) mapAttrs mkOption;
|
inherit (lib) mapAttrs mkOption;
|
||||||
inherit (lib.types) attrsOf anything submodule;
|
inherit (lib.types) attrsOf anything submodule;
|
||||||
|
|
@ -12,7 +17,10 @@ in
|
||||||
secret = mkOption {
|
secret = mkOption {
|
||||||
description = "Secret following the [secret contract](./contracts-secret.html).";
|
description = "Secret following the [secret contract](./contracts-secret.html).";
|
||||||
default = { };
|
default = { };
|
||||||
type = attrsOf (submodule ({ name, options, ... }: {
|
type = attrsOf (
|
||||||
|
submodule (
|
||||||
|
{ name, options, ... }:
|
||||||
|
{
|
||||||
options = contracts.secret.mkProvider {
|
options = contracts.secret.mkProvider {
|
||||||
settings = mkOption {
|
settings = mkOption {
|
||||||
description = ''
|
description = ''
|
||||||
|
|
@ -33,13 +41,17 @@ in
|
||||||
pathText = "/run/secrets/<name>";
|
pathText = "/run/secrets/<name>";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}));
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
config = {
|
config = {
|
||||||
sops.secrets = let
|
sops.secrets =
|
||||||
|
let
|
||||||
mkSecret = n: secretCfg: secretCfg.request // secretCfg.settings;
|
mkSecret = n: secretCfg: secretCfg.request // secretCfg.settings;
|
||||||
in mapAttrs mkSecret cfg.secret;
|
in
|
||||||
|
mapAttrs mkSecret cfg.secret;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,9 @@
|
||||||
{ config, pkgs, lib, ... }:
|
{
|
||||||
|
config,
|
||||||
|
pkgs,
|
||||||
|
lib,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
|
||||||
let
|
let
|
||||||
cfg = config.shb.certs;
|
cfg = config.shb.certs;
|
||||||
|
|
@ -6,7 +11,13 @@ let
|
||||||
contracts = pkgs.callPackage ../contracts { };
|
contracts = pkgs.callPackage ../contracts { };
|
||||||
|
|
||||||
inherit (builtins) dirOf;
|
inherit (builtins) dirOf;
|
||||||
inherit (lib) flatten mapAttrsToList optionalAttrs optionals unique;
|
inherit (lib)
|
||||||
|
flatten
|
||||||
|
mapAttrsToList
|
||||||
|
optionalAttrs
|
||||||
|
optionals
|
||||||
|
unique
|
||||||
|
;
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
options.shb.certs = {
|
options.shb.certs = {
|
||||||
|
|
@ -20,7 +31,10 @@ in
|
||||||
cas.selfsigned = lib.mkOption {
|
cas.selfsigned = lib.mkOption {
|
||||||
description = "Generate a self-signed Certificate Authority.";
|
description = "Generate a self-signed Certificate Authority.";
|
||||||
default = { };
|
default = { };
|
||||||
type = lib.types.attrsOf (lib.types.submodule ({ config, ...}: {
|
type = lib.types.attrsOf (
|
||||||
|
lib.types.submodule (
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
options = {
|
options = {
|
||||||
name = lib.mkOption {
|
name = lib.mkOption {
|
||||||
type = lib.types.str;
|
type = lib.types.str;
|
||||||
|
|
@ -54,12 +68,17 @@ in
|
||||||
default = "shb-certs-ca-${config._module.args.name}.service";
|
default = "shb-certs-ca-${config._module.args.name}.service";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}));
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
};
|
};
|
||||||
certs.selfsigned = lib.mkOption {
|
certs.selfsigned = lib.mkOption {
|
||||||
description = "Generate self-signed certificates signed by a Certificate Authority.";
|
description = "Generate self-signed certificates signed by a Certificate Authority.";
|
||||||
default = { };
|
default = { };
|
||||||
type = lib.types.attrsOf (lib.types.submodule ({ config, ... }: {
|
type = lib.types.attrsOf (
|
||||||
|
lib.types.submodule (
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
options = {
|
options = {
|
||||||
ca = lib.mkOption {
|
ca = lib.mkOption {
|
||||||
type = lib.types.nullOr contracts.ssl.cas;
|
type = lib.types.nullOr contracts.ssl.cas;
|
||||||
|
|
@ -135,13 +154,18 @@ in
|
||||||
example = [ "nginx.service" ];
|
example = [ "nginx.service" ];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}));
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
certs.letsencrypt = lib.mkOption {
|
certs.letsencrypt = lib.mkOption {
|
||||||
description = "Generate certificates signed by [Let's Encrypt](https://letsencrypt.org/).";
|
description = "Generate certificates signed by [Let's Encrypt](https://letsencrypt.org/).";
|
||||||
default = { };
|
default = { };
|
||||||
type = lib.types.attrsOf (lib.types.submodule ({ config, ... }: {
|
type = lib.types.attrsOf (
|
||||||
|
lib.types.submodule (
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
options = {
|
options = {
|
||||||
domain = lib.mkOption {
|
domain = lib.mkOption {
|
||||||
type = lib.types.str;
|
type = lib.types.str;
|
||||||
|
|
@ -300,7 +324,9 @@ in
|
||||||
default = false;
|
default = false;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}));
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -311,7 +337,8 @@ in
|
||||||
lib.mkMerge [
|
lib.mkMerge [
|
||||||
# Config for self-signed CA.
|
# Config for self-signed CA.
|
||||||
{
|
{
|
||||||
systemd.services = lib.mapAttrs' (_name: caCfg:
|
systemd.services = lib.mapAttrs' (
|
||||||
|
_name: caCfg:
|
||||||
lib.nameValuePair (serviceName caCfg.systemdService) {
|
lib.nameValuePair (serviceName caCfg.systemdService) {
|
||||||
wantedBy = [ "multi-user.target" ];
|
wantedBy = [ "multi-user.target" ];
|
||||||
wants = [ config.shb.certs.systemdService ];
|
wants = [ config.shb.certs.systemdService ];
|
||||||
|
|
@ -352,7 +379,8 @@ in
|
||||||
}
|
}
|
||||||
# Config for self-signed CA bundle.
|
# 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" ];
|
wantedBy = [ "multi-user.target" ];
|
||||||
serviceConfig.Type = "oneshot";
|
serviceConfig.Type = "oneshot";
|
||||||
script = ''
|
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-bundle.crt
|
||||||
cat /etc/static/ssl/certs/ca-bundle.crt > /etc/ssl/certs/ca-certificates.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-bundle.crt
|
||||||
cat "$file" >> /etc/ssl/certs/ca-certificates.crt
|
cat "$file" >> /etc/ssl/certs/ca-certificates.crt
|
||||||
done
|
done
|
||||||
'';
|
'';
|
||||||
});
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
# Config for self-signed cert.
|
# Config for self-signed cert.
|
||||||
{
|
{
|
||||||
systemd.services = lib.mapAttrs' (_name: certCfg:
|
systemd.services = lib.mapAttrs' (
|
||||||
|
_name: certCfg:
|
||||||
lib.nameValuePair (serviceName certCfg.systemdService) {
|
lib.nameValuePair (serviceName certCfg.systemdService) {
|
||||||
after = [ certCfg.ca.systemdService ];
|
after = [ certCfg.ca.systemdService ];
|
||||||
requires = [ certCfg.ca.systemdService ];
|
requires = [ certCfg.ca.systemdService ];
|
||||||
|
|
@ -382,8 +414,7 @@ in
|
||||||
script =
|
script =
|
||||||
let
|
let
|
||||||
extraDnsNames = lib.strings.concatStringsSep "\n" (map (n: "dns_name = ${n}") certCfg.extraDomains);
|
extraDnsNames = lib.strings.concatStringsSep "\n" (map (n: "dns_name = ${n}") certCfg.extraDomains);
|
||||||
chmod = cert:
|
chmod = cert: ''
|
||||||
''
|
|
||||||
chown root:${certCfg.group} ${cert}
|
chown root:${certCfg.group} ${cert}
|
||||||
chmod 640 ${cert}
|
chmod 640 ${cert}
|
||||||
'';
|
'';
|
||||||
|
|
@ -432,89 +463,140 @@ in
|
||||||
}
|
}
|
||||||
# Config for Let's Encrypt cert.
|
# 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)) [
|
${certCfg.makeAvailableToUser}.extraGroups = lib.mkIf (!(isNull certCfg.makeAvailableToUser)) [
|
||||||
config.security.acme.defaults.group
|
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
|
security.acme.certs =
|
||||||
extraDomainsCfg = certCfg: map (name: {
|
let
|
||||||
|
extraDomainsCfg =
|
||||||
|
certCfg:
|
||||||
|
map (name: {
|
||||||
"${name}" = {
|
"${name}" = {
|
||||||
email = certCfg.adminEmail;
|
email = certCfg.adminEmail;
|
||||||
enableDebugLogs = certCfg.debug;
|
enableDebugLogs = certCfg.debug;
|
||||||
server = lib.mkIf certCfg.stagingServer "https://acme-staging-v02.api.letsencrypt.org/directory";
|
server = lib.mkIf certCfg.stagingServer "https://acme-staging-v02.api.letsencrypt.org/directory";
|
||||||
};
|
};
|
||||||
}) certCfg.extraDomains;
|
}) certCfg.extraDomains;
|
||||||
in lib.mkMerge (flatten (mapAttrsToList (name: certCfg:
|
in
|
||||||
[{
|
lib.mkMerge (
|
||||||
|
flatten (
|
||||||
|
mapAttrsToList (
|
||||||
|
name: certCfg:
|
||||||
|
[
|
||||||
|
{
|
||||||
"${name}" = {
|
"${name}" = {
|
||||||
extraDomainNames = [ certCfg.domain ] ++ certCfg.extraDomains;
|
extraDomainNames = [ certCfg.domain ] ++ certCfg.extraDomains;
|
||||||
email = certCfg.adminEmail;
|
email = certCfg.adminEmail;
|
||||||
enableDebugLogs = certCfg.debug;
|
enableDebugLogs = certCfg.debug;
|
||||||
server = lib.mkIf certCfg.stagingServer "https://acme-staging-v02.api.letsencrypt.org/directory";
|
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) dnsProvider dnsResolver;
|
||||||
inherit (certCfg) group reloadServices;
|
inherit (certCfg) group reloadServices;
|
||||||
credentialsFile = certCfg.credentialsFile;
|
credentialsFile = certCfg.credentialsFile;
|
||||||
};
|
};
|
||||||
}]
|
}
|
||||||
|
]
|
||||||
++ lib.optionals (certCfg.dnsProvider == null) (extraDomainsCfg certCfg)
|
++ lib.optionals (certCfg.dnsProvider == null) (extraDomainsCfg certCfg)
|
||||||
) cfg.certs.letsencrypt));
|
) cfg.certs.letsencrypt
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
services.nginx = let
|
services.nginx =
|
||||||
extraDomainsCfg = extraDomains: map (name: {
|
let
|
||||||
|
extraDomainsCfg =
|
||||||
|
extraDomains:
|
||||||
|
map (name: {
|
||||||
virtualHosts."${name}" = {
|
virtualHosts."${name}" = {
|
||||||
# addSSL = true;
|
# addSSL = true;
|
||||||
enableACME = true;
|
enableACME = true;
|
||||||
};
|
};
|
||||||
}) extraDomains;
|
}) extraDomains;
|
||||||
in lib.mkMerge (flatten (mapAttrsToList (name: certCfg:
|
in
|
||||||
|
lib.mkMerge (
|
||||||
|
flatten (
|
||||||
|
mapAttrsToList (
|
||||||
|
name: certCfg:
|
||||||
lib.optionals (certCfg.dnsProvider == null) (
|
lib.optionals (certCfg.dnsProvider == null) (
|
||||||
[{
|
[
|
||||||
|
{
|
||||||
virtualHosts."${name}" = {
|
virtualHosts."${name}" = {
|
||||||
# addSSL = true;
|
# addSSL = true;
|
||||||
enableACME = true;
|
enableACME = true;
|
||||||
};
|
};
|
||||||
}]
|
}
|
||||||
|
]
|
||||||
++ extraDomainsCfg certCfg.extraDomains
|
++ extraDomainsCfg certCfg.extraDomains
|
||||||
)) cfg.certs.letsencrypt));
|
)
|
||||||
|
) cfg.certs.letsencrypt
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
systemd.services = let
|
systemd.services =
|
||||||
extraDomainsCfg = certCfg: flatten (map (name:
|
let
|
||||||
lib.optionals (certCfg.additionalEnvironment != {} && certCfg.dnsProvider == null) [{
|
extraDomainsCfg =
|
||||||
|
certCfg:
|
||||||
|
flatten (
|
||||||
|
map (
|
||||||
|
name:
|
||||||
|
lib.optionals (certCfg.additionalEnvironment != { } && certCfg.dnsProvider == null) [
|
||||||
|
{
|
||||||
"acme-${name}".environment = certCfg.additionalEnvironment;
|
"acme-${name}".environment = certCfg.additionalEnvironment;
|
||||||
}]
|
}
|
||||||
++ lib.optionals (certCfg.afterAndWants != [] && certCfg.dnsProvider == null) [{
|
]
|
||||||
|
++ lib.optionals (certCfg.afterAndWants != [ ] && certCfg.dnsProvider == null) [
|
||||||
|
{
|
||||||
"acme-${name}" = {
|
"acme-${name}" = {
|
||||||
after = certCfg.afterAndWants;
|
after = certCfg.afterAndWants;
|
||||||
wants = certCfg.afterAndWants;
|
wants = certCfg.afterAndWants;
|
||||||
};
|
};
|
||||||
}]
|
}
|
||||||
) certCfg.extraDomains);
|
]
|
||||||
in lib.mkMerge (flatten (mapAttrsToList (name: certCfg:
|
) certCfg.extraDomains
|
||||||
lib.optionals (certCfg.additionalEnvironment != {} && certCfg.dnsProvider == null) [{
|
);
|
||||||
|
in
|
||||||
|
lib.mkMerge (
|
||||||
|
flatten (
|
||||||
|
mapAttrsToList (
|
||||||
|
name: certCfg:
|
||||||
|
lib.optionals (certCfg.additionalEnvironment != { } && certCfg.dnsProvider == null) [
|
||||||
|
{
|
||||||
"acme-${certCfg.domain}".environment = certCfg.additionalEnvironment;
|
"acme-${certCfg.domain}".environment = certCfg.additionalEnvironment;
|
||||||
}]
|
}
|
||||||
++ lib.optionals (certCfg.afterAndWants != [] && certCfg.dnsProvider == null) [{
|
]
|
||||||
|
++ lib.optionals (certCfg.afterAndWants != [ ] && certCfg.dnsProvider == null) [
|
||||||
|
{
|
||||||
"acme-${certCfg.domain}" = {
|
"acme-${certCfg.domain}" = {
|
||||||
after = certCfg.afterAndWants;
|
after = certCfg.afterAndWants;
|
||||||
wants = certCfg.afterAndWants;
|
wants = certCfg.afterAndWants;
|
||||||
};
|
};
|
||||||
}]
|
}
|
||||||
|
]
|
||||||
++ lib.optionals (certCfg.dnsProvider == null) (extraDomainsCfg certCfg)
|
++ 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;
|
enable = true;
|
||||||
listenAddress = "127.0.0.1";
|
listenAddress = "127.0.0.1";
|
||||||
user = "acme";
|
user = "acme";
|
||||||
paths = let
|
paths =
|
||||||
pathCfg = name: certCfg:
|
|
||||||
let
|
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.
|
# Not sure this will work for all cases.
|
||||||
mainPath = dirOf (dirOf certCfg.paths.cert);
|
mainPath = dirOf (dirOf certCfg.paths.cert);
|
||||||
extraDomainsPath = map (x: "${mainPath}/${x}") certCfg.extraDomains;
|
extraDomainsPath = map (x: "${mainPath}/${x}") certCfg.extraDomains;
|
||||||
|
|
@ -524,17 +606,22 @@ in
|
||||||
unique (flatten (mapAttrsToList pathCfg cfg.certs.letsencrypt));
|
unique (flatten (mapAttrsToList pathCfg cfg.certs.letsencrypt));
|
||||||
};
|
};
|
||||||
|
|
||||||
services.prometheus.scrapeConfigs = let
|
services.prometheus.scrapeConfigs =
|
||||||
scrapeCfg = name: certCfg: [{
|
let
|
||||||
|
scrapeCfg = name: certCfg: [
|
||||||
|
{
|
||||||
job_name = "node-cert-${name}";
|
job_name = "node-cert-${name}";
|
||||||
static_configs = [{
|
static_configs = [
|
||||||
|
{
|
||||||
targets = [ "127.0.0.1:${toString config.services.prometheus.exporters.node-cert.port}" ];
|
targets = [ "127.0.0.1:${toString config.services.prometheus.exporters.node-cert.port}" ];
|
||||||
labels = {
|
labels = {
|
||||||
"hostname" = config.networking.hostName;
|
"hostname" = config.networking.hostName;
|
||||||
"domain" = certCfg.domain;
|
"domain" = certCfg.domain;
|
||||||
};
|
};
|
||||||
}];
|
}
|
||||||
}];
|
];
|
||||||
|
}
|
||||||
|
];
|
||||||
in
|
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.
|
# 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;
|
with lib;
|
||||||
|
|
||||||
let
|
let
|
||||||
cfg = config.shb.tinyproxy;
|
cfg = config.shb.tinyproxy;
|
||||||
|
|
||||||
mkValueStringTinyproxy = with lib; v:
|
mkValueStringTinyproxy =
|
||||||
if true == v then "yes"
|
with lib;
|
||||||
else if false == v then "no"
|
v:
|
||||||
else generators.mkValueStringDefault {} v;
|
if true == v then
|
||||||
|
"yes"
|
||||||
|
else if false == v then
|
||||||
|
"no"
|
||||||
|
else
|
||||||
|
generators.mkValueStringDefault { } v;
|
||||||
|
|
||||||
mkKeyValueTinyproxy = {
|
mkKeyValueTinyproxy =
|
||||||
mkValueString ? mkValueStringDefault {}
|
{
|
||||||
}: sep: k: v:
|
mkValueString ? mkValueStringDefault { },
|
||||||
if null == v then ""
|
}:
|
||||||
else "${lib.strings.escape [sep] k}${sep}${mkValueString v}";
|
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 {
|
mkKeyValue = mkKeyValueTinyproxy {
|
||||||
mkValueString = mkValueStringTinyproxy;
|
mkValueString = mkValueStringTinyproxy;
|
||||||
} " ";
|
} " ";
|
||||||
listsAsDuplicateKeys = true;
|
listsAsDuplicateKeys = true;
|
||||||
});
|
}
|
||||||
|
);
|
||||||
|
|
||||||
configFile = name: cfg: settingsFormat.generate "tinyproxy-${name}.conf" cfg.settings;
|
configFile = name: cfg: settingsFormat.generate "tinyproxy-${name}.conf" cfg.settings;
|
||||||
in
|
in
|
||||||
|
|
@ -47,7 +60,8 @@ in
|
||||||
Configuration for [tinyproxy](https://tinyproxy.github.io/).
|
Configuration for [tinyproxy](https://tinyproxy.github.io/).
|
||||||
'';
|
'';
|
||||||
default = { };
|
default = { };
|
||||||
example = literalExpression ''{
|
example = literalExpression ''
|
||||||
|
{
|
||||||
Port 8888;
|
Port 8888;
|
||||||
Listen 127.0.0.1;
|
Listen 127.0.0.1;
|
||||||
Timeout 600;
|
Timeout 600;
|
||||||
|
|
@ -55,7 +69,9 @@ in
|
||||||
Anonymous = ['"Host"' '"Authorization"'];
|
Anonymous = ['"Host"' '"Authorization"'];
|
||||||
ReversePath = '"/example/" "http://www.example.com/"';
|
ReversePath = '"/example/" "http://www.example.com/"';
|
||||||
}'';
|
}'';
|
||||||
type = types.submodule ({name, ...}: {
|
type = types.submodule (
|
||||||
|
{ name, ... }:
|
||||||
|
{
|
||||||
freeformType = settingsFormat.type;
|
freeformType = settingsFormat.type;
|
||||||
options = {
|
options = {
|
||||||
Listen = mkOption {
|
Listen = mkOption {
|
||||||
|
|
@ -91,7 +107,8 @@ in
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
});
|
}
|
||||||
|
);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
@ -107,7 +124,9 @@ in
|
||||||
config = {
|
config = {
|
||||||
systemd.services =
|
systemd.services =
|
||||||
let
|
let
|
||||||
instanceConfig = name: c: mkIf c.enable {
|
instanceConfig =
|
||||||
|
name: c:
|
||||||
|
mkIf c.enable {
|
||||||
"tinyproxy-${name}" = {
|
"tinyproxy-${name}" = {
|
||||||
description = "TinyProxy daemon - instance ${name}";
|
description = "TinyProxy daemon - instance ${name}";
|
||||||
after = [ "network.target" ];
|
after = [ "network.target" ];
|
||||||
|
|
@ -126,12 +145,15 @@ in
|
||||||
RestartMaxDelaySec = "10s";
|
RestartMaxDelaySec = "10s";
|
||||||
ConfigurationDirectory = "tinyproxy";
|
ConfigurationDirectory = "tinyproxy";
|
||||||
};
|
};
|
||||||
preStart = concatStringsSep "\n" ([
|
preStart = concatStringsSep "\n" (
|
||||||
|
[
|
||||||
"cat ${configFile name c} > /etc/tinyproxy/${name}.conf"
|
"cat ${configFile name c} > /etc/tinyproxy/${name}.conf"
|
||||||
] ++ optionals (c.dynamicBindFile != "") [
|
]
|
||||||
|
++ optionals (c.dynamicBindFile != "") [
|
||||||
"echo -n 'Bind ' >> /etc/tinyproxy/${name}.conf"
|
"echo -n 'Bind ' >> /etc/tinyproxy/${name}.conf"
|
||||||
"cat ${c.dynamicBindFile} >> /etc/tinyproxy/${name}.conf"
|
"cat ${c.dynamicBindFile} >> /etc/tinyproxy/${name}.conf"
|
||||||
]);
|
]
|
||||||
|
);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
in
|
in
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,9 @@
|
||||||
{ config, pkgs, lib, ... }:
|
{
|
||||||
|
config,
|
||||||
|
pkgs,
|
||||||
|
lib,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
|
||||||
let
|
let
|
||||||
cfg = config.shb.vpn;
|
cfg = config.shb.vpn;
|
||||||
|
|
@ -6,12 +11,14 @@ let
|
||||||
quoteEach = lib.concatMapStrings (x: ''"${x}"'');
|
quoteEach = lib.concatMapStrings (x: ''"${x}"'');
|
||||||
|
|
||||||
nordvpnConfig =
|
nordvpnConfig =
|
||||||
{ name
|
{
|
||||||
, dev
|
name,
|
||||||
, authFile
|
dev,
|
||||||
, remoteServerIP
|
authFile,
|
||||||
, dependentServices ? []
|
remoteServerIP,
|
||||||
}: ''
|
dependentServices ? [ ],
|
||||||
|
}:
|
||||||
|
''
|
||||||
client
|
client
|
||||||
dev ${dev}
|
dev ${dev}
|
||||||
proto tcp
|
proto tcp
|
||||||
|
|
@ -103,10 +110,16 @@ let
|
||||||
</tls-auth>
|
</tls-auth>
|
||||||
'';
|
'';
|
||||||
|
|
||||||
routeUp = name: dependentServices: pkgs.writeShellApplication {
|
routeUp =
|
||||||
|
name: dependentServices:
|
||||||
|
pkgs.writeShellApplication {
|
||||||
name = "routeUp.sh";
|
name = "routeUp.sh";
|
||||||
|
|
||||||
runtimeInputs = [ pkgs.iproute2 pkgs.systemd pkgs.nettools ];
|
runtimeInputs = [
|
||||||
|
pkgs.iproute2
|
||||||
|
pkgs.systemd
|
||||||
|
pkgs.nettools
|
||||||
|
];
|
||||||
|
|
||||||
text = ''
|
text = ''
|
||||||
echo "Running route-up..."
|
echo "Running route-up..."
|
||||||
|
|
@ -150,10 +163,17 @@ let
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
routeDown = name: dependentServices: pkgs.writeShellApplication {
|
routeDown =
|
||||||
|
name: dependentServices:
|
||||||
|
pkgs.writeShellApplication {
|
||||||
name = "routeDown.sh";
|
name = "routeDown.sh";
|
||||||
|
|
||||||
runtimeInputs = [ pkgs.iproute2 pkgs.systemd pkgs.nettools pkgs.coreutils ];
|
runtimeInputs = [
|
||||||
|
pkgs.iproute2
|
||||||
|
pkgs.systemd
|
||||||
|
pkgs.nettools
|
||||||
|
pkgs.coreutils
|
||||||
|
];
|
||||||
|
|
||||||
text = ''
|
text = ''
|
||||||
echo "Running route-down..."
|
echo "Running route-down..."
|
||||||
|
|
@ -254,7 +274,9 @@ in
|
||||||
config = {
|
config = {
|
||||||
services.openvpn.servers =
|
services.openvpn.servers =
|
||||||
let
|
let
|
||||||
instanceConfig = name: c: lib.mkIf c.enable {
|
instanceConfig =
|
||||||
|
name: c:
|
||||||
|
lib.mkIf c.enable {
|
||||||
${name} = {
|
${name} = {
|
||||||
autoStart = true;
|
autoStart = true;
|
||||||
|
|
||||||
|
|
@ -270,17 +292,20 @@ in
|
||||||
in
|
in
|
||||||
lib.mkMerge (lib.mapAttrsToList instanceConfig cfg);
|
lib.mkMerge (lib.mapAttrsToList instanceConfig cfg);
|
||||||
|
|
||||||
systemd.tmpfiles.rules = map (name:
|
systemd.tmpfiles.rules = map (name: "d /tmp/openvpn/${name}.status 0700 root root") (
|
||||||
"d /tmp/openvpn/${name}.status 0700 root root"
|
lib.attrNames cfg
|
||||||
) (lib.attrNames cfg);
|
);
|
||||||
|
|
||||||
networking.iproute2.enable = true;
|
networking.iproute2.enable = true;
|
||||||
networking.iproute2.rttablesExtraConfig =
|
networking.iproute2.rttablesExtraConfig = lib.concatStringsSep "\n" (
|
||||||
lib.concatStringsSep "\n" (lib.mapAttrsToList (name: c: "${toString c.routingNumber} ${name}") cfg);
|
lib.mapAttrsToList (name: c: "${toString c.routingNumber} ${name}") cfg
|
||||||
|
);
|
||||||
|
|
||||||
shb.tinyproxy =
|
shb.tinyproxy =
|
||||||
let
|
let
|
||||||
instanceConfig = name: c: lib.mkIf (c.enable && c.proxyPort != null) {
|
instanceConfig =
|
||||||
|
name: c:
|
||||||
|
lib.mkIf (c.enable && c.proxyPort != null) {
|
||||||
${name} = {
|
${name} = {
|
||||||
enable = true;
|
enable = true;
|
||||||
# package = pkgs.tinyproxy.overrideAttrs (old: {
|
# package = pkgs.tinyproxy.overrideAttrs (old: {
|
||||||
|
|
@ -299,7 +324,10 @@ in
|
||||||
Listen = "127.0.0.1";
|
Listen = "127.0.0.1";
|
||||||
Syslog = "On";
|
Syslog = "On";
|
||||||
LogLevel = "Info";
|
LogLevel = "Info";
|
||||||
Allow = [ "127.0.0.1" "::1" ];
|
Allow = [
|
||||||
|
"127.0.0.1"
|
||||||
|
"::1"
|
||||||
|
];
|
||||||
ViaProxyName = ''"tinyproxy"'';
|
ViaProxyName = ''"tinyproxy"'';
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,9 @@
|
||||||
{ config, pkgs, lib, ... }:
|
{
|
||||||
|
config,
|
||||||
|
pkgs,
|
||||||
|
lib,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
|
||||||
let
|
let
|
||||||
cfg = config.shb.zfs;
|
cfg = config.shb.zfs;
|
||||||
|
|
@ -25,7 +30,8 @@ in
|
||||||
example = lib.literalExpression ''
|
example = lib.literalExpression ''
|
||||||
shb.zfs."safe/postgresql".path = "/var/lib/postgresql";
|
shb.zfs."safe/postgresql".path = "/var/lib/postgresql";
|
||||||
'';
|
'';
|
||||||
type = lib.types.attrsOf (lib.types.submodule {
|
type = lib.types.attrsOf (
|
||||||
|
lib.types.submodule {
|
||||||
options = {
|
options = {
|
||||||
enable = lib.mkEnableOption "shb.zfs.datasets";
|
enable = lib.mkEnableOption "shb.zfs.datasets";
|
||||||
|
|
||||||
|
|
@ -40,19 +46,23 @@ in
|
||||||
description = "Path this dataset should be mounted on.";
|
description = "Path this dataset should be mounted on.";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
});
|
}
|
||||||
|
);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
config = {
|
config = {
|
||||||
assertions = [
|
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";
|
message = "Cannot have both datasets.poolName and defaultPoolName set to null";
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
system.activationScripts = lib.mapAttrs' (name: cfg':
|
system.activationScripts = lib.mapAttrs' (
|
||||||
|
name: cfg':
|
||||||
let
|
let
|
||||||
dataset = (if cfg'.poolName != null then cfg'.poolName else cfg.defaultPoolName) + "/" + name;
|
dataset = (if cfg'.poolName != null then cfg'.poolName else cfg.defaultPoolName) + "/" + name;
|
||||||
in
|
in
|
||||||
|
|
@ -68,6 +78,7 @@ in
|
||||||
mountpoint=${cfg'.path} \
|
mountpoint=${cfg'.path} \
|
||||||
${dataset}
|
${dataset}
|
||||||
'';
|
'';
|
||||||
}) cfg.datasets;
|
}
|
||||||
|
) cfg.datasets;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,24 @@
|
||||||
{ lib, ... }:
|
{ lib, ... }:
|
||||||
let
|
let
|
||||||
inherit (lib) concatStringsSep literalMD mkOption optionalAttrs optionalString;
|
inherit (lib)
|
||||||
inherit (lib.types) listOf nonEmptyListOf submodule str;
|
concatStringsSep
|
||||||
|
literalMD
|
||||||
|
mkOption
|
||||||
|
optionalAttrs
|
||||||
|
optionalString
|
||||||
|
;
|
||||||
|
inherit (lib.types)
|
||||||
|
listOf
|
||||||
|
nonEmptyListOf
|
||||||
|
submodule
|
||||||
|
str
|
||||||
|
;
|
||||||
inherit (lib.shb) anyNotNull;
|
inherit (lib.shb) anyNotNull;
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
mkRequest =
|
mkRequest =
|
||||||
{ user ? "",
|
{
|
||||||
|
user ? "",
|
||||||
userText ? null,
|
userText ? null,
|
||||||
sourceDirectories ? [ "/var/lib/example" ],
|
sourceDirectories ? [ "/var/lib/example" ],
|
||||||
sourceDirectoriesText ? null,
|
sourceDirectoriesText ? null,
|
||||||
|
|
@ -16,7 +28,8 @@ in
|
||||||
beforeBackupText ? null,
|
beforeBackupText ? null,
|
||||||
afterBackup ? [ ],
|
afterBackup ? [ ],
|
||||||
afterBackupText ? null,
|
afterBackupText ? null,
|
||||||
}: mkOption {
|
}:
|
||||||
|
mkOption {
|
||||||
description = ''
|
description = ''
|
||||||
Request part of the backup contract.
|
Request part of the backup contract.
|
||||||
|
|
||||||
|
|
@ -31,49 +44,75 @@ in
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
defaultText = optionalString (anyNotNull [
|
defaultText =
|
||||||
|
optionalString
|
||||||
|
(anyNotNull [
|
||||||
userText
|
userText
|
||||||
sourceDirectoriesText
|
sourceDirectoriesText
|
||||||
excludePatternsText
|
excludePatternsText
|
||||||
beforeBackupText
|
beforeBackupText
|
||||||
afterBackupText
|
afterBackupText
|
||||||
]) (literalMD ''
|
])
|
||||||
|
(literalMD ''
|
||||||
{
|
{
|
||||||
user = ${if userText != null then userText else user};
|
user = ${if userText != null then userText else user};
|
||||||
sourceDirectories = ${if sourceDirectoriesText != null then sourceDirectoriesText else "[ " + concatStringsSep " " sourceDirectories + " ]"};
|
sourceDirectories = ${
|
||||||
excludePatterns = ${if excludePatternsText != null then excludePatternsText else "[ " + concatStringsSep " " excludePatterns + " ]"};
|
if sourceDirectoriesText != null then
|
||||||
hooks.beforeBackup = ${if beforeBackupText != null then beforeBackupText else "[ " + concatStringsSep " " beforeBackup + " ]"};
|
sourceDirectoriesText
|
||||||
hooks.afterBackup = ${if afterBackupText != null then afterBackupText else "[ " + concatStringsSep " " afterBackup + " ]"};
|
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 {
|
type = submodule {
|
||||||
options = {
|
options = {
|
||||||
user = mkOption {
|
user =
|
||||||
|
mkOption {
|
||||||
description = ''
|
description = ''
|
||||||
Unix user doing the backups.
|
Unix user doing the backups.
|
||||||
'';
|
'';
|
||||||
type = str;
|
type = str;
|
||||||
example = "vaultwarden";
|
example = "vaultwarden";
|
||||||
default = user;
|
default = user;
|
||||||
} // optionalAttrs (userText != null) {
|
}
|
||||||
|
// optionalAttrs (userText != null) {
|
||||||
defaultText = literalMD userText;
|
defaultText = literalMD userText;
|
||||||
};
|
};
|
||||||
|
|
||||||
sourceDirectories = mkOption {
|
sourceDirectories =
|
||||||
|
mkOption {
|
||||||
description = "Directories to backup.";
|
description = "Directories to backup.";
|
||||||
type = nonEmptyListOf str;
|
type = nonEmptyListOf str;
|
||||||
example = "/var/lib/vaultwarden";
|
example = "/var/lib/vaultwarden";
|
||||||
default = sourceDirectories;
|
default = sourceDirectories;
|
||||||
} // optionalAttrs (sourceDirectoriesText != null) {
|
}
|
||||||
|
// optionalAttrs (sourceDirectoriesText != null) {
|
||||||
defaultText = literalMD sourceDirectoriesText;
|
defaultText = literalMD sourceDirectoriesText;
|
||||||
};
|
};
|
||||||
|
|
||||||
excludePatterns = mkOption {
|
excludePatterns =
|
||||||
|
mkOption {
|
||||||
description = "File patterns to exclude.";
|
description = "File patterns to exclude.";
|
||||||
type = listOf str;
|
type = listOf str;
|
||||||
default = excludePatterns;
|
default = excludePatterns;
|
||||||
} // optionalAttrs (excludePatternsText != null) {
|
}
|
||||||
|
// optionalAttrs (excludePatternsText != null) {
|
||||||
defaultText = literalMD excludePatternsText;
|
defaultText = literalMD excludePatternsText;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -82,19 +121,23 @@ in
|
||||||
default = { };
|
default = { };
|
||||||
type = submodule {
|
type = submodule {
|
||||||
options = {
|
options = {
|
||||||
beforeBackup = mkOption {
|
beforeBackup =
|
||||||
|
mkOption {
|
||||||
description = "Hooks to run before backup.";
|
description = "Hooks to run before backup.";
|
||||||
type = listOf str;
|
type = listOf str;
|
||||||
default = beforeBackup;
|
default = beforeBackup;
|
||||||
} // optionalAttrs (beforeBackupText != null) {
|
}
|
||||||
|
// optionalAttrs (beforeBackupText != null) {
|
||||||
defaultText = literalMD beforeBackupText;
|
defaultText = literalMD beforeBackupText;
|
||||||
};
|
};
|
||||||
|
|
||||||
afterBackup = mkOption {
|
afterBackup =
|
||||||
|
mkOption {
|
||||||
description = "Hooks to run after backup.";
|
description = "Hooks to run after backup.";
|
||||||
type = listOf str;
|
type = listOf str;
|
||||||
default = afterBackup;
|
default = afterBackup;
|
||||||
} // optionalAttrs (afterBackupText != null) {
|
}
|
||||||
|
// optionalAttrs (afterBackupText != null) {
|
||||||
defaultText = literalMD afterBackupText;
|
defaultText = literalMD afterBackupText;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
@ -104,12 +147,14 @@ in
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
mkResult = {
|
mkResult =
|
||||||
|
{
|
||||||
restoreScript ? "restore",
|
restoreScript ? "restore",
|
||||||
restoreScriptText ? null,
|
restoreScriptText ? null,
|
||||||
backupService ? "backup.service",
|
backupService ? "backup.service",
|
||||||
backupServiceText ? null,
|
backupServiceText ? null,
|
||||||
}: mkOption {
|
}:
|
||||||
|
mkOption {
|
||||||
description = ''
|
description = ''
|
||||||
Result part of the backup contract.
|
Result part of the backup contract.
|
||||||
|
|
||||||
|
|
@ -119,10 +164,13 @@ in
|
||||||
inherit restoreScript backupService;
|
inherit restoreScript backupService;
|
||||||
};
|
};
|
||||||
|
|
||||||
defaultText = optionalString (anyNotNull [
|
defaultText =
|
||||||
|
optionalString
|
||||||
|
(anyNotNull [
|
||||||
restoreScriptText
|
restoreScriptText
|
||||||
backupServiceText
|
backupServiceText
|
||||||
]) (literalMD ''
|
])
|
||||||
|
(literalMD ''
|
||||||
{
|
{
|
||||||
restoreScript = ${if restoreScriptText != null then restoreScriptText else restoreScript};
|
restoreScript = ${if restoreScriptText != null then restoreScriptText else restoreScript};
|
||||||
backupService = ${if backupServiceText != null then backupServiceText else backupService};
|
backupService = ${if backupServiceText != null then backupServiceText else backupService};
|
||||||
|
|
@ -131,7 +179,8 @@ in
|
||||||
|
|
||||||
type = submodule {
|
type = submodule {
|
||||||
options = {
|
options = {
|
||||||
restoreScript = mkOption {
|
restoreScript =
|
||||||
|
mkOption {
|
||||||
description = ''
|
description = ''
|
||||||
Name of script that can restore the database.
|
Name of script that can restore the database.
|
||||||
One can then list snapshots with:
|
One can then list snapshots with:
|
||||||
|
|
@ -148,11 +197,13 @@ in
|
||||||
'';
|
'';
|
||||||
type = str;
|
type = str;
|
||||||
default = restoreScript;
|
default = restoreScript;
|
||||||
} // optionalAttrs (restoreScriptText != null) {
|
}
|
||||||
|
// optionalAttrs (restoreScriptText != null) {
|
||||||
defaultText = literalMD restoreScriptText;
|
defaultText = literalMD restoreScriptText;
|
||||||
};
|
};
|
||||||
|
|
||||||
backupService = mkOption {
|
backupService =
|
||||||
|
mkOption {
|
||||||
description = ''
|
description = ''
|
||||||
Name of service backing up the database.
|
Name of service backing up the database.
|
||||||
|
|
||||||
|
|
@ -164,7 +215,8 @@ in
|
||||||
'';
|
'';
|
||||||
type = str;
|
type = str;
|
||||||
default = backupService;
|
default = backupService;
|
||||||
} // optionalAttrs (backupServiceText != null) {
|
}
|
||||||
|
// optionalAttrs (backupServiceText != null) {
|
||||||
defaultText = literalMD backupServiceText;
|
defaultText = literalMD backupServiceText;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,15 @@
|
||||||
{ pkgs, lib }:
|
{ pkgs, lib }:
|
||||||
let
|
let
|
||||||
inherit (lib) concatMapStringsSep getAttrFromPath mkIf optionalAttrs setAttrByPath;
|
inherit (lib)
|
||||||
|
concatMapStringsSep
|
||||||
|
getAttrFromPath
|
||||||
|
mkIf
|
||||||
|
optionalAttrs
|
||||||
|
setAttrByPath
|
||||||
|
;
|
||||||
in
|
in
|
||||||
{ name,
|
{
|
||||||
|
name,
|
||||||
providerRoot,
|
providerRoot,
|
||||||
modules ? [ ],
|
modules ? [ ],
|
||||||
username ? "me",
|
username ? "me",
|
||||||
|
|
@ -12,10 +19,13 @@ in
|
||||||
],
|
],
|
||||||
settings, # { repository, config } -> attrset
|
settings, # { repository, config } -> attrset
|
||||||
extraConfig ? null, # { username, config } -> attrset
|
extraConfig ? null, # { username, config } -> attrset
|
||||||
}: lib.shb.runNixOSTest {
|
}:
|
||||||
|
lib.shb.runNixOSTest {
|
||||||
inherit name;
|
inherit name;
|
||||||
|
|
||||||
nodes.machine = { config, ... }: {
|
nodes.machine =
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
imports = [ lib.shb.baseImports ] ++ modules;
|
imports = [ lib.shb.baseImports ] ++ modules;
|
||||||
|
|
||||||
config = lib.mkMerge [
|
config = lib.mkMerge [
|
||||||
|
|
@ -36,16 +46,21 @@ in
|
||||||
group = "root";
|
group = "root";
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
(optionalAttrs (extraConfig != null) (extraConfig { inherit username config; }))
|
(optionalAttrs (extraConfig != null) (extraConfig {
|
||||||
|
inherit username config;
|
||||||
|
}))
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
extraPythonPackages = p: [ p.dictdiffer ];
|
extraPythonPackages = p: [ p.dictdiffer ];
|
||||||
skipTypeCheck = true;
|
skipTypeCheck = true;
|
||||||
|
|
||||||
testScript = { nodes, ... }: let
|
testScript =
|
||||||
|
{ nodes, ... }:
|
||||||
|
let
|
||||||
provider = (getAttrFromPath providerRoot nodes.machine).result;
|
provider = (getAttrFromPath providerRoot nodes.machine).result;
|
||||||
in ''
|
in
|
||||||
|
''
|
||||||
from dictdiffer import diff
|
from dictdiffer import diff
|
||||||
|
|
||||||
username = "${username}"
|
username = "${username}"
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,19 @@
|
||||||
{ lib, ... }:
|
{ lib, ... }:
|
||||||
let
|
let
|
||||||
inherit (lib) mkOption literalExpression literalMD optionalAttrs optionalString;
|
inherit (lib)
|
||||||
|
mkOption
|
||||||
|
literalExpression
|
||||||
|
literalMD
|
||||||
|
optionalAttrs
|
||||||
|
optionalString
|
||||||
|
;
|
||||||
inherit (lib.types) submodule str;
|
inherit (lib.types) submodule str;
|
||||||
inherit (lib.shb) anyNotNull;
|
inherit (lib.shb) anyNotNull;
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
mkRequest =
|
mkRequest =
|
||||||
{ user ? "root",
|
{
|
||||||
|
user ? "root",
|
||||||
userText ? null,
|
userText ? null,
|
||||||
backupName ? "dump",
|
backupName ? "dump",
|
||||||
backupNameText ? null,
|
backupNameText ? null,
|
||||||
|
|
@ -14,7 +21,8 @@ in
|
||||||
backupCmdText ? null,
|
backupCmdText ? null,
|
||||||
restoreCmd ? "",
|
restoreCmd ? "",
|
||||||
restoreCmdText ? null,
|
restoreCmdText ? null,
|
||||||
}: mkOption {
|
}:
|
||||||
|
mkOption {
|
||||||
description = ''
|
description = ''
|
||||||
Request part of the backup contract.
|
Request part of the backup contract.
|
||||||
|
|
||||||
|
|
@ -23,15 +31,23 @@ in
|
||||||
'';
|
'';
|
||||||
|
|
||||||
default = {
|
default = {
|
||||||
inherit user backupName backupCmd restoreCmd;
|
inherit
|
||||||
|
user
|
||||||
|
backupName
|
||||||
|
backupCmd
|
||||||
|
restoreCmd
|
||||||
|
;
|
||||||
};
|
};
|
||||||
|
|
||||||
defaultText = optionalString (anyNotNull [
|
defaultText =
|
||||||
|
optionalString
|
||||||
|
(anyNotNull [
|
||||||
userText
|
userText
|
||||||
backupNameText
|
backupNameText
|
||||||
backupCmdText
|
backupCmdText
|
||||||
restoreCmdText
|
restoreCmdText
|
||||||
]) (literalMD ''
|
])
|
||||||
|
(literalMD ''
|
||||||
{
|
{
|
||||||
user = ${if userText != null then userText else user};
|
user = ${if userText != null then userText else user};
|
||||||
backupName = ${if backupNameText != null then backupNameText else backupName};
|
backupName = ${if backupNameText != null then backupNameText else backupName};
|
||||||
|
|
@ -42,7 +58,8 @@ in
|
||||||
|
|
||||||
type = submodule {
|
type = submodule {
|
||||||
options = {
|
options = {
|
||||||
user = mkOption {
|
user =
|
||||||
|
mkOption {
|
||||||
description = ''
|
description = ''
|
||||||
Unix user doing the backups.
|
Unix user doing the backups.
|
||||||
|
|
||||||
|
|
@ -51,51 +68,59 @@ in
|
||||||
type = str;
|
type = str;
|
||||||
example = "postgres";
|
example = "postgres";
|
||||||
default = user;
|
default = user;
|
||||||
} // optionalAttrs (userText != null) {
|
}
|
||||||
|
// optionalAttrs (userText != null) {
|
||||||
defaultText = literalMD userText;
|
defaultText = literalMD userText;
|
||||||
};
|
};
|
||||||
|
|
||||||
backupName = mkOption {
|
backupName =
|
||||||
|
mkOption {
|
||||||
description = "Name of the backup in the repository.";
|
description = "Name of the backup in the repository.";
|
||||||
type = str;
|
type = str;
|
||||||
example = "postgresql.sql";
|
example = "postgresql.sql";
|
||||||
default = backupName;
|
default = backupName;
|
||||||
} // optionalAttrs (backupNameText != null) {
|
}
|
||||||
|
// optionalAttrs (backupNameText != null) {
|
||||||
defaultText = literalMD backupNameText;
|
defaultText = literalMD backupNameText;
|
||||||
};
|
};
|
||||||
|
|
||||||
backupCmd = mkOption {
|
backupCmd =
|
||||||
|
mkOption {
|
||||||
description = "Command that produces the database dump on stdout.";
|
description = "Command that produces the database dump on stdout.";
|
||||||
type = str;
|
type = str;
|
||||||
example = literalExpression ''
|
example = literalExpression ''
|
||||||
''${pkgs.postgresql}/bin/pg_dumpall | ''${pkgs.gzip}/bin/gzip --rsyncable
|
''${pkgs.postgresql}/bin/pg_dumpall | ''${pkgs.gzip}/bin/gzip --rsyncable
|
||||||
'';
|
'';
|
||||||
default = backupCmd;
|
default = backupCmd;
|
||||||
} // optionalAttrs (backupCmdText != null) {
|
}
|
||||||
|
// optionalAttrs (backupCmdText != null) {
|
||||||
defaultText = literalMD backupCmdText;
|
defaultText = literalMD backupCmdText;
|
||||||
};
|
};
|
||||||
|
|
||||||
restoreCmd = mkOption {
|
restoreCmd =
|
||||||
|
mkOption {
|
||||||
description = "Command that reads the database dump on stdin and restores the database.";
|
description = "Command that reads the database dump on stdin and restores the database.";
|
||||||
type = str;
|
type = str;
|
||||||
example = literalExpression ''
|
example = literalExpression ''
|
||||||
''${pkgs.gzip}/bin/gunzip | ''${pkgs.postgresql}/bin/psql postgres
|
''${pkgs.gzip}/bin/gunzip | ''${pkgs.postgresql}/bin/psql postgres
|
||||||
'';
|
'';
|
||||||
default = restoreCmd;
|
default = restoreCmd;
|
||||||
} // optionalAttrs (restoreCmdText != null) {
|
}
|
||||||
|
// optionalAttrs (restoreCmdText != null) {
|
||||||
defaultText = literalMD restoreCmdText;
|
defaultText = literalMD restoreCmdText;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
mkResult =
|
||||||
mkResult = {
|
{
|
||||||
restoreScript ? "restore",
|
restoreScript ? "restore",
|
||||||
restoreScriptText ? null,
|
restoreScriptText ? null,
|
||||||
backupService ? "backup.service",
|
backupService ? "backup.service",
|
||||||
backupServiceText ? null,
|
backupServiceText ? null,
|
||||||
}: mkOption {
|
}:
|
||||||
|
mkOption {
|
||||||
description = ''
|
description = ''
|
||||||
Result part of the backup contract.
|
Result part of the backup contract.
|
||||||
|
|
||||||
|
|
@ -105,10 +130,13 @@ in
|
||||||
inherit restoreScript backupService;
|
inherit restoreScript backupService;
|
||||||
};
|
};
|
||||||
|
|
||||||
defaultText = optionalString (anyNotNull [
|
defaultText =
|
||||||
|
optionalString
|
||||||
|
(anyNotNull [
|
||||||
restoreScriptText
|
restoreScriptText
|
||||||
backupServiceText
|
backupServiceText
|
||||||
]) (literalMD ''
|
])
|
||||||
|
(literalMD ''
|
||||||
{
|
{
|
||||||
restoreScript = ${if restoreScriptText != null then restoreScriptText else restoreScript};
|
restoreScript = ${if restoreScriptText != null then restoreScriptText else restoreScript};
|
||||||
backupService = ${if backupServiceText != null then backupServiceText else backupService};
|
backupService = ${if backupServiceText != null then backupServiceText else backupService};
|
||||||
|
|
@ -117,7 +145,8 @@ in
|
||||||
|
|
||||||
type = submodule {
|
type = submodule {
|
||||||
options = {
|
options = {
|
||||||
restoreScript = mkOption {
|
restoreScript =
|
||||||
|
mkOption {
|
||||||
description = ''
|
description = ''
|
||||||
Name of script that can restore the database.
|
Name of script that can restore the database.
|
||||||
One can then list snapshots with:
|
One can then list snapshots with:
|
||||||
|
|
@ -134,11 +163,13 @@ in
|
||||||
'';
|
'';
|
||||||
type = str;
|
type = str;
|
||||||
default = restoreScript;
|
default = restoreScript;
|
||||||
} // optionalAttrs (restoreScriptText != null) {
|
}
|
||||||
|
// optionalAttrs (restoreScriptText != null) {
|
||||||
defaultText = literalMD restoreScriptText;
|
defaultText = literalMD restoreScriptText;
|
||||||
};
|
};
|
||||||
|
|
||||||
backupService = mkOption {
|
backupService =
|
||||||
|
mkOption {
|
||||||
description = ''
|
description = ''
|
||||||
Name of service backing up the database.
|
Name of service backing up the database.
|
||||||
|
|
||||||
|
|
@ -150,7 +181,8 @@ in
|
||||||
'';
|
'';
|
||||||
type = str;
|
type = str;
|
||||||
default = backupService;
|
default = backupService;
|
||||||
} // optionalAttrs (backupServiceText != null) {
|
}
|
||||||
|
// optionalAttrs (backupServiceText != null) {
|
||||||
defaultText = literalMD backupServiceText;
|
defaultText = literalMD backupServiceText;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,27 @@
|
||||||
{ pkgs, lib }:
|
{ pkgs, lib }:
|
||||||
let
|
let
|
||||||
inherit (lib) getAttrFromPath mkIf optionalAttrs setAttrByPath;
|
inherit (lib)
|
||||||
|
getAttrFromPath
|
||||||
|
mkIf
|
||||||
|
optionalAttrs
|
||||||
|
setAttrByPath
|
||||||
|
;
|
||||||
in
|
in
|
||||||
{ name,
|
{
|
||||||
|
name,
|
||||||
requesterRoot,
|
requesterRoot,
|
||||||
providerRoot,
|
providerRoot,
|
||||||
extraConfig ? null, # { config, database } -> attrset
|
extraConfig ? null, # { config, database } -> attrset
|
||||||
modules ? [ ],
|
modules ? [ ],
|
||||||
database ? "me",
|
database ? "me",
|
||||||
settings, # { repository, config } -> attrset
|
settings, # { repository, config } -> attrset
|
||||||
}: lib.shb.runNixOSTest {
|
}:
|
||||||
|
lib.shb.runNixOSTest {
|
||||||
inherit name;
|
inherit name;
|
||||||
|
|
||||||
nodes.machine = { config, ... }: {
|
nodes.machine =
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
imports = [ lib.shb.baseImports ] ++ modules;
|
imports = [ lib.shb.baseImports ] ++ modules;
|
||||||
config = lib.mkMerge [
|
config = lib.mkMerge [
|
||||||
(setAttrByPath providerRoot {
|
(setAttrByPath providerRoot {
|
||||||
|
|
@ -29,13 +38,18 @@ in
|
||||||
group = "root";
|
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;
|
provider = getAttrFromPath providerRoot nodes.machine;
|
||||||
in ''
|
in
|
||||||
|
''
|
||||||
import csv
|
import csv
|
||||||
|
|
||||||
start_all()
|
start_all()
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,11 @@ let
|
||||||
inherit (lib.types) anything;
|
inherit (lib.types) anything;
|
||||||
|
|
||||||
mkContractFunctions =
|
mkContractFunctions =
|
||||||
{ mkRequest,
|
{
|
||||||
|
mkRequest,
|
||||||
mkResult,
|
mkResult,
|
||||||
}: {
|
}:
|
||||||
|
{
|
||||||
mkRequester = requestCfg: {
|
mkRequester = requestCfg: {
|
||||||
request = mkRequest requestCfg;
|
request = mkRequest requestCfg;
|
||||||
|
|
||||||
|
|
@ -14,13 +16,16 @@ let
|
||||||
};
|
};
|
||||||
|
|
||||||
mkProvider =
|
mkProvider =
|
||||||
{ resultCfg,
|
{
|
||||||
|
resultCfg,
|
||||||
settings ? { },
|
settings ? { },
|
||||||
}: {
|
}:
|
||||||
|
{
|
||||||
request = mkRequest { };
|
request = mkRequest { };
|
||||||
|
|
||||||
result = mkResult resultCfg;
|
result = mkResult resultCfg;
|
||||||
} // optionalAttrs (settings != {}) { inherit settings; };
|
}
|
||||||
|
// optionalAttrs (settings != { }) { inherit settings; };
|
||||||
|
|
||||||
contract = {
|
contract = {
|
||||||
request = mkRequest { };
|
request = mkRequest { };
|
||||||
|
|
@ -36,7 +41,8 @@ let
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
importContract = module:
|
importContract =
|
||||||
|
module:
|
||||||
let
|
let
|
||||||
importedModule = pkgs.callPackage module { };
|
importedModule = pkgs.callPackage module { };
|
||||||
in
|
in
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,19 @@
|
||||||
{ lib, ... }:
|
{ lib, ... }:
|
||||||
let
|
let
|
||||||
inherit (lib) concatStringsSep literalMD mkOption optionalAttrs optionalString;
|
inherit (lib)
|
||||||
|
concatStringsSep
|
||||||
|
literalMD
|
||||||
|
mkOption
|
||||||
|
optionalAttrs
|
||||||
|
optionalString
|
||||||
|
;
|
||||||
inherit (lib.types) listOf submodule str;
|
inherit (lib.types) listOf submodule str;
|
||||||
inherit (lib.shb) anyNotNull;
|
inherit (lib.shb) anyNotNull;
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
mkRequest =
|
mkRequest =
|
||||||
{ mode ? "0400",
|
{
|
||||||
|
mode ? "0400",
|
||||||
modeText ? null,
|
modeText ? null,
|
||||||
owner ? "root",
|
owner ? "root",
|
||||||
ownerText ? null,
|
ownerText ? null,
|
||||||
|
|
@ -14,7 +21,8 @@ in
|
||||||
groupText ? null,
|
groupText ? null,
|
||||||
restartUnits ? [ ],
|
restartUnits ? [ ],
|
||||||
restartUnitsText ? null,
|
restartUnitsText ? null,
|
||||||
}: mkOption {
|
}:
|
||||||
|
mkOption {
|
||||||
description = ''
|
description = ''
|
||||||
Request part of the secret contract.
|
Request part of the secret contract.
|
||||||
|
|
||||||
|
|
@ -23,64 +31,87 @@ in
|
||||||
'';
|
'';
|
||||||
|
|
||||||
default = {
|
default = {
|
||||||
inherit mode owner group restartUnits;
|
inherit
|
||||||
|
mode
|
||||||
|
owner
|
||||||
|
group
|
||||||
|
restartUnits
|
||||||
|
;
|
||||||
};
|
};
|
||||||
|
|
||||||
defaultText = optionalString (anyNotNull [
|
defaultText =
|
||||||
|
optionalString
|
||||||
|
(anyNotNull [
|
||||||
modeText
|
modeText
|
||||||
ownerText
|
ownerText
|
||||||
groupText
|
groupText
|
||||||
restartUnitsText
|
restartUnitsText
|
||||||
]) (literalMD ''
|
])
|
||||||
|
(literalMD ''
|
||||||
{
|
{
|
||||||
mode = ${if modeText != null then modeText else mode};
|
mode = ${if modeText != null then modeText else mode};
|
||||||
owner = ${if ownerText != null then ownerText else owner};
|
owner = ${if ownerText != null then ownerText else owner};
|
||||||
group = ${if groupText != null then groupText else group};
|
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 {
|
type = submodule {
|
||||||
options = {
|
options = {
|
||||||
mode = mkOption {
|
mode =
|
||||||
|
mkOption {
|
||||||
description = ''
|
description = ''
|
||||||
Mode of the secret file.
|
Mode of the secret file.
|
||||||
'';
|
'';
|
||||||
type = str;
|
type = str;
|
||||||
default = mode;
|
default = mode;
|
||||||
} // optionalAttrs (modeText != null) {
|
}
|
||||||
|
// optionalAttrs (modeText != null) {
|
||||||
defaultText = literalMD modeText;
|
defaultText = literalMD modeText;
|
||||||
};
|
};
|
||||||
|
|
||||||
owner = mkOption ({
|
owner = mkOption (
|
||||||
|
{
|
||||||
description = ''
|
description = ''
|
||||||
Linux user owning the secret file.
|
Linux user owning the secret file.
|
||||||
'';
|
'';
|
||||||
type = str;
|
type = str;
|
||||||
default = owner;
|
default = owner;
|
||||||
} // optionalAttrs (ownerText != null) {
|
}
|
||||||
|
// optionalAttrs (ownerText != null) {
|
||||||
defaultText = literalMD ownerText;
|
defaultText = literalMD ownerText;
|
||||||
});
|
}
|
||||||
|
);
|
||||||
|
|
||||||
group = mkOption {
|
group =
|
||||||
|
mkOption {
|
||||||
description = ''
|
description = ''
|
||||||
Linux group owning the secret file.
|
Linux group owning the secret file.
|
||||||
'';
|
'';
|
||||||
type = str;
|
type = str;
|
||||||
default = group;
|
default = group;
|
||||||
} // optionalAttrs (groupText != null) {
|
}
|
||||||
|
// optionalAttrs (groupText != null) {
|
||||||
defaultText = literalMD groupText;
|
defaultText = literalMD groupText;
|
||||||
};
|
};
|
||||||
|
|
||||||
restartUnits = mkOption ({
|
restartUnits = mkOption (
|
||||||
|
{
|
||||||
description = ''
|
description = ''
|
||||||
Systemd units to restart after the secret is updated.
|
Systemd units to restart after the secret is updated.
|
||||||
'';
|
'';
|
||||||
type = listOf str;
|
type = listOf str;
|
||||||
default = restartUnits;
|
default = restartUnits;
|
||||||
} // optionalAttrs (restartUnitsText != null) {
|
}
|
||||||
|
// optionalAttrs (restartUnitsText != null) {
|
||||||
defaultText = literalMD restartUnitsText;
|
defaultText = literalMD restartUnitsText;
|
||||||
});
|
}
|
||||||
|
);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
@ -90,7 +121,8 @@ in
|
||||||
path ? "/run/secrets/secret",
|
path ? "/run/secrets/secret",
|
||||||
pathText ? null,
|
pathText ? null,
|
||||||
}:
|
}:
|
||||||
mkOption ({
|
mkOption (
|
||||||
|
{
|
||||||
description = ''
|
description = ''
|
||||||
Result part of the secret contract.
|
Result part of the secret contract.
|
||||||
|
|
||||||
|
|
@ -101,7 +133,8 @@ in
|
||||||
};
|
};
|
||||||
type = submodule {
|
type = submodule {
|
||||||
options = {
|
options = {
|
||||||
path = mkOption {
|
path =
|
||||||
|
mkOption {
|
||||||
type = lib.types.path;
|
type = lib.types.path;
|
||||||
description = ''
|
description = ''
|
||||||
Path to the file containing the secret generated out of band.
|
Path to the file containing the secret generated out of band.
|
||||||
|
|
@ -110,14 +143,17 @@ in
|
||||||
it is not available through the nix store.
|
it is not available through the nix store.
|
||||||
'';
|
'';
|
||||||
default = path;
|
default = path;
|
||||||
} // optionalAttrs (pathText != null) {
|
}
|
||||||
|
// optionalAttrs (pathText != null) {
|
||||||
defaultText = pathText;
|
defaultText = pathText;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
} // optionalAttrs (pathText != null) {
|
}
|
||||||
|
// optionalAttrs (pathText != null) {
|
||||||
defaultText = {
|
defaultText = {
|
||||||
path = pathText;
|
path = pathText;
|
||||||
};
|
};
|
||||||
});
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,8 @@ let
|
||||||
inherit (lib) getAttrFromPath setAttrByPath;
|
inherit (lib) getAttrFromPath setAttrByPath;
|
||||||
inherit (lib) mkIf;
|
inherit (lib) mkIf;
|
||||||
in
|
in
|
||||||
{ name,
|
{
|
||||||
|
name,
|
||||||
configRoot,
|
configRoot,
|
||||||
settingsCfg, # str -> attrset
|
settingsCfg, # str -> attrset
|
||||||
modules ? [ ],
|
modules ? [ ],
|
||||||
|
|
@ -11,16 +12,24 @@ in
|
||||||
group ? "root",
|
group ? "root",
|
||||||
mode ? "0400",
|
mode ? "0400",
|
||||||
restartUnits ? [ "myunit.service" ],
|
restartUnits ? [ "myunit.service" ],
|
||||||
}: lib.shb.runNixOSTest {
|
}:
|
||||||
|
lib.shb.runNixOSTest {
|
||||||
name = "secret_${name}_${owner}_${group}_${mode}";
|
name = "secret_${name}_${owner}_${group}_${mode}";
|
||||||
|
|
||||||
nodes.machine = { config, ... }: {
|
nodes.machine =
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
imports = [ lib.shb.baseImports ] ++ modules;
|
imports = [ lib.shb.baseImports ] ++ modules;
|
||||||
config = lib.mkMerge [
|
config = lib.mkMerge [
|
||||||
(setAttrByPath configRoot {
|
(setAttrByPath configRoot {
|
||||||
A = {
|
A = {
|
||||||
request = {
|
request = {
|
||||||
inherit owner group mode restartUnits;
|
inherit
|
||||||
|
owner
|
||||||
|
group
|
||||||
|
mode
|
||||||
|
restartUnits
|
||||||
|
;
|
||||||
};
|
};
|
||||||
settings = settingsCfg "secretA";
|
settings = settingsCfg "secretA";
|
||||||
};
|
};
|
||||||
|
|
@ -34,7 +43,8 @@ in
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
testScript = { nodes, ... }:
|
testScript =
|
||||||
|
{ nodes, ... }:
|
||||||
let
|
let
|
||||||
result = (getAttrFromPath configRoot nodes.machine)."A".result;
|
result = (getAttrFromPath configRoot nodes.machine)."A".result;
|
||||||
in
|
in
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,9 @@
|
||||||
{ config, pkgs, lib, ... }:
|
{
|
||||||
|
config,
|
||||||
|
pkgs,
|
||||||
|
lib,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
|
||||||
let
|
let
|
||||||
cfg = config.shb.arr;
|
cfg = config.shb.arr;
|
||||||
|
|
@ -20,7 +25,10 @@ let
|
||||||
description = "Path to api key secret file.";
|
description = "Path to api key secret file.";
|
||||||
};
|
};
|
||||||
LogLevel = lib.mkOption {
|
LogLevel = lib.mkOption {
|
||||||
type = lib.types.enum ["debug" "info"];
|
type = lib.types.enum [
|
||||||
|
"debug"
|
||||||
|
"info"
|
||||||
|
];
|
||||||
description = "Log level.";
|
description = "Log level.";
|
||||||
default = "info";
|
default = "info";
|
||||||
};
|
};
|
||||||
|
|
@ -78,7 +86,10 @@ let
|
||||||
description = "Path to api key secret file.";
|
description = "Path to api key secret file.";
|
||||||
};
|
};
|
||||||
LogLevel = lib.mkOption {
|
LogLevel = lib.mkOption {
|
||||||
type = lib.types.enum ["debug" "info"];
|
type = lib.types.enum [
|
||||||
|
"debug"
|
||||||
|
"info"
|
||||||
|
];
|
||||||
description = "Log level.";
|
description = "Log level.";
|
||||||
default = "info";
|
default = "info";
|
||||||
};
|
};
|
||||||
|
|
@ -127,7 +138,10 @@ let
|
||||||
freeformType = apps.bazarr.settingsFormat.type;
|
freeformType = apps.bazarr.settingsFormat.type;
|
||||||
options = {
|
options = {
|
||||||
LogLevel = lib.mkOption {
|
LogLevel = lib.mkOption {
|
||||||
type = lib.types.enum ["debug" "info"];
|
type = lib.types.enum [
|
||||||
|
"debug"
|
||||||
|
"info"
|
||||||
|
];
|
||||||
description = "Log level.";
|
description = "Log level.";
|
||||||
default = "info";
|
default = "info";
|
||||||
};
|
};
|
||||||
|
|
@ -152,7 +166,10 @@ let
|
||||||
freeformType = apps.readarr.settingsFormat.type;
|
freeformType = apps.readarr.settingsFormat.type;
|
||||||
options = {
|
options = {
|
||||||
LogLevel = lib.mkOption {
|
LogLevel = lib.mkOption {
|
||||||
type = lib.types.enum ["debug" "info"];
|
type = lib.types.enum [
|
||||||
|
"debug"
|
||||||
|
"info"
|
||||||
|
];
|
||||||
description = "Log level.";
|
description = "Log level.";
|
||||||
default = "info";
|
default = "info";
|
||||||
};
|
};
|
||||||
|
|
@ -176,7 +193,10 @@ let
|
||||||
freeformType = apps.lidarr.settingsFormat.type;
|
freeformType = apps.lidarr.settingsFormat.type;
|
||||||
options = {
|
options = {
|
||||||
LogLevel = lib.mkOption {
|
LogLevel = lib.mkOption {
|
||||||
type = lib.types.enum ["debug" "info"];
|
type = lib.types.enum [
|
||||||
|
"debug"
|
||||||
|
"info"
|
||||||
|
];
|
||||||
description = "Log level.";
|
description = "Log level.";
|
||||||
default = "info";
|
default = "info";
|
||||||
};
|
};
|
||||||
|
|
@ -214,7 +234,12 @@ let
|
||||||
default = null;
|
default = null;
|
||||||
};
|
};
|
||||||
ProxyType = lib.mkOption {
|
ProxyType = lib.mkOption {
|
||||||
type = lib.types.enum [ "-1" "0" "1" "2" ];
|
type = lib.types.enum [
|
||||||
|
"-1"
|
||||||
|
"0"
|
||||||
|
"1"
|
||||||
|
"2"
|
||||||
|
];
|
||||||
default = "-1";
|
default = "-1";
|
||||||
description = ''
|
description = ''
|
||||||
-1 = disabled
|
-1 = disabled
|
||||||
|
|
@ -256,8 +281,17 @@ let
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
vhosts = { extraBypassResources ? [] }: c: {
|
vhosts =
|
||||||
inherit (c) subdomain domain authEndpoint ssl;
|
{
|
||||||
|
extraBypassResources ? [ ],
|
||||||
|
}:
|
||||||
|
c: {
|
||||||
|
inherit (c)
|
||||||
|
subdomain
|
||||||
|
domain
|
||||||
|
authEndpoint
|
||||||
|
ssl
|
||||||
|
;
|
||||||
|
|
||||||
upstream = "http://127.0.0.1:${toString c.settings.Port}";
|
upstream = "http://127.0.0.1:${toString c.settings.Port}";
|
||||||
autheliaRules = lib.optionals (!(isNull c.authEndpoint)) [
|
autheliaRules = lib.optionals (!(isNull c.authEndpoint)) [
|
||||||
|
|
@ -277,7 +311,10 @@ let
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
appOption = name: c: lib.nameValuePair name (lib.mkOption {
|
appOption =
|
||||||
|
name: c:
|
||||||
|
lib.nameValuePair name (
|
||||||
|
lib.mkOption {
|
||||||
description = "Configuration for ${name}";
|
description = "Configuration for ${name}";
|
||||||
default = { };
|
default = { };
|
||||||
type = lib.types.submodule {
|
type = lib.types.submodule {
|
||||||
|
|
@ -326,13 +363,19 @@ let
|
||||||
sourceDirectories = [
|
sourceDirectories = [
|
||||||
cfg.${name}.dataDir
|
cfg.${name}.dataDir
|
||||||
];
|
];
|
||||||
excludePatterns = [".db-shm" ".db-wal" ".mono"];
|
excludePatterns = [
|
||||||
|
".db-shm"
|
||||||
|
".db-wal"
|
||||||
|
".mono"
|
||||||
|
];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
} // (c.moreOptions or {});
|
}
|
||||||
|
// (c.moreOptions or { });
|
||||||
};
|
};
|
||||||
});
|
}
|
||||||
|
);
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
imports = [
|
imports = [
|
||||||
|
|
@ -356,7 +399,8 @@ in
|
||||||
};
|
};
|
||||||
|
|
||||||
systemd.services.radarr.preStart = lib.shb.replaceSecrets {
|
systemd.services.radarr.preStart = lib.shb.replaceSecrets {
|
||||||
userConfig = cfg'.settings
|
userConfig =
|
||||||
|
cfg'.settings
|
||||||
// (lib.optionalAttrs isSSOEnabled {
|
// (lib.optionalAttrs isSSOEnabled {
|
||||||
AuthenticationRequired = "DisabledForLocalAddresses";
|
AuthenticationRequired = "DisabledForLocalAddresses";
|
||||||
AuthenticationMethod = "External";
|
AuthenticationMethod = "External";
|
||||||
|
|
@ -366,7 +410,8 @@ in
|
||||||
};
|
};
|
||||||
|
|
||||||
shb.nginx.vhosts = [ (vhosts { } cfg') ];
|
shb.nginx.vhosts = [ (vhosts { } cfg') ];
|
||||||
}))
|
}
|
||||||
|
))
|
||||||
|
|
||||||
(lib.mkIf cfg.sonarr.enable (
|
(lib.mkIf cfg.sonarr.enable (
|
||||||
let
|
let
|
||||||
|
|
@ -385,7 +430,8 @@ in
|
||||||
};
|
};
|
||||||
|
|
||||||
systemd.services.sonarr.preStart = lib.shb.replaceSecrets {
|
systemd.services.sonarr.preStart = lib.shb.replaceSecrets {
|
||||||
userConfig = cfg'.settings
|
userConfig =
|
||||||
|
cfg'.settings
|
||||||
// (lib.optionalAttrs isSSOEnabled {
|
// (lib.optionalAttrs isSSOEnabled {
|
||||||
AuthenticationRequired = "DisabledForLocalAddresses";
|
AuthenticationRequired = "DisabledForLocalAddresses";
|
||||||
AuthenticationMethod = "External";
|
AuthenticationMethod = "External";
|
||||||
|
|
@ -395,7 +441,8 @@ in
|
||||||
};
|
};
|
||||||
|
|
||||||
shb.nginx.vhosts = [ (vhosts { } cfg') ];
|
shb.nginx.vhosts = [ (vhosts { } cfg') ];
|
||||||
}))
|
}
|
||||||
|
))
|
||||||
|
|
||||||
(lib.mkIf cfg.bazarr.enable (
|
(lib.mkIf cfg.bazarr.enable (
|
||||||
let
|
let
|
||||||
|
|
@ -411,7 +458,8 @@ in
|
||||||
extraGroups = [ "media" ];
|
extraGroups = [ "media" ];
|
||||||
};
|
};
|
||||||
systemd.services.bazarr.preStart = lib.shb.replaceSecrets {
|
systemd.services.bazarr.preStart = lib.shb.replaceSecrets {
|
||||||
userConfig = cfg'.settings
|
userConfig =
|
||||||
|
cfg'.settings
|
||||||
// (lib.optionalAttrs isSSOEnabled {
|
// (lib.optionalAttrs isSSOEnabled {
|
||||||
AuthenticationRequired = "DisabledForLocalAddresses";
|
AuthenticationRequired = "DisabledForLocalAddresses";
|
||||||
AuthenticationMethod = "External";
|
AuthenticationMethod = "External";
|
||||||
|
|
@ -421,7 +469,8 @@ in
|
||||||
};
|
};
|
||||||
|
|
||||||
shb.nginx.vhosts = [ (vhosts { } cfg') ];
|
shb.nginx.vhosts = [ (vhosts { } cfg') ];
|
||||||
}))
|
}
|
||||||
|
))
|
||||||
|
|
||||||
(lib.mkIf cfg.readarr.enable (
|
(lib.mkIf cfg.readarr.enable (
|
||||||
let
|
let
|
||||||
|
|
@ -442,7 +491,8 @@ in
|
||||||
};
|
};
|
||||||
|
|
||||||
shb.nginx.vhosts = [ (vhosts { } cfg') ];
|
shb.nginx.vhosts = [ (vhosts { } cfg') ];
|
||||||
}))
|
}
|
||||||
|
))
|
||||||
|
|
||||||
(lib.mkIf cfg.lidarr.enable (
|
(lib.mkIf cfg.lidarr.enable (
|
||||||
let
|
let
|
||||||
|
|
@ -458,7 +508,8 @@ in
|
||||||
extraGroups = [ "media" ];
|
extraGroups = [ "media" ];
|
||||||
};
|
};
|
||||||
systemd.services.lidarr.preStart = lib.shb.replaceSecrets {
|
systemd.services.lidarr.preStart = lib.shb.replaceSecrets {
|
||||||
userConfig = cfg'.settings
|
userConfig =
|
||||||
|
cfg'.settings
|
||||||
// (lib.optionalAttrs isSSOEnabled {
|
// (lib.optionalAttrs isSSOEnabled {
|
||||||
AuthenticationRequired = "DisabledForLocalAddresses";
|
AuthenticationRequired = "DisabledForLocalAddresses";
|
||||||
AuthenticationMethod = "External";
|
AuthenticationMethod = "External";
|
||||||
|
|
@ -468,7 +519,8 @@ in
|
||||||
};
|
};
|
||||||
|
|
||||||
shb.nginx.vhosts = [ (vhosts { } cfg') ];
|
shb.nginx.vhosts = [ (vhosts { } cfg') ];
|
||||||
}))
|
}
|
||||||
|
))
|
||||||
|
|
||||||
(lib.mkIf cfg.jackett.enable (
|
(lib.mkIf cfg.jackett.enable (
|
||||||
let
|
let
|
||||||
|
|
@ -489,9 +541,12 @@ in
|
||||||
generator = apps.jackett.settingsFormat.generate;
|
generator = apps.jackett.settingsFormat.generate;
|
||||||
};
|
};
|
||||||
|
|
||||||
shb.nginx.vhosts = [ (vhosts {
|
shb.nginx.vhosts = [
|
||||||
|
(vhosts {
|
||||||
extraBypassResources = [ "^/dl.*" ];
|
extraBypassResources = [ "^/dl.*" ];
|
||||||
} cfg') ];
|
} cfg')
|
||||||
}))
|
];
|
||||||
|
}
|
||||||
|
))
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,9 @@
|
||||||
{ config, pkgs, lib, ... }:
|
{
|
||||||
|
config,
|
||||||
|
pkgs,
|
||||||
|
lib,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
|
||||||
let
|
let
|
||||||
cfg = config.shb.audiobookshelf;
|
cfg = config.shb.audiobookshelf;
|
||||||
|
|
@ -87,7 +92,10 @@ in
|
||||||
};
|
};
|
||||||
|
|
||||||
authorization_policy = lib.mkOption {
|
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.";
|
description = "Require one factor (password) or two factor (device) authentication.";
|
||||||
default = "one_factor";
|
default = "one_factor";
|
||||||
};
|
};
|
||||||
|
|
@ -133,14 +141,24 @@ in
|
||||||
};
|
};
|
||||||
|
|
||||||
logLevel = lib.mkOption {
|
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.";
|
description = "Enable logging.";
|
||||||
default = false;
|
default = false;
|
||||||
example = true;
|
example = true;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
config = lib.mkIf cfg.enable (lib.mkMerge [{
|
config = lib.mkIf cfg.enable (
|
||||||
|
lib.mkMerge [
|
||||||
|
{
|
||||||
|
|
||||||
services.audiobookshelf = {
|
services.audiobookshelf = {
|
||||||
enable = true;
|
enable = true;
|
||||||
|
|
@ -175,7 +193,6 @@ in
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
shb.authelia.extraDefinitions = {
|
shb.authelia.extraDefinitions = {
|
||||||
user_attributes.${roleClaim}.expression =
|
user_attributes.${roleClaim}.expression =
|
||||||
''"${cfg.sso.adminUserGroup}" in groups ? ["admin"] : ("${cfg.sso.userGroup}" in groups ? ["user"] : [""])'';
|
''"${cfg.sso.adminUserGroup}" in groups ? ["admin"] : ("${cfg.sso.userGroup}" in groups ? ["user"] : [""])'';
|
||||||
|
|
@ -203,14 +220,23 @@ in
|
||||||
"https://${cfg.subdomain}.${cfg.domain}/auth/openid/callback"
|
"https://${cfg.subdomain}.${cfg.domain}/auth/openid/callback"
|
||||||
"https://${cfg.subdomain}.${cfg.domain}/auth/openid/mobile-redirect"
|
"https://${cfg.subdomain}.${cfg.domain}/auth/openid/mobile-redirect"
|
||||||
];
|
];
|
||||||
scopes = [ "openid" "profile" "email" "groups" "${roleClaim}" ];
|
scopes = [
|
||||||
|
"openid"
|
||||||
|
"profile"
|
||||||
|
"email"
|
||||||
|
"groups"
|
||||||
|
"${roleClaim}"
|
||||||
|
];
|
||||||
require_pkce = true;
|
require_pkce = true;
|
||||||
pkce_challenge_method = "S256";
|
pkce_challenge_method = "S256";
|
||||||
userinfo_signed_response_alg = "none";
|
userinfo_signed_response_alg = "none";
|
||||||
token_endpoint_auth_method = "client_secret_basic";
|
token_endpoint_auth_method = "client_secret_basic";
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
} {
|
}
|
||||||
systemd.services.audiobookshelfd.serviceConfig = cfg.extraServiceConfig;
|
{
|
||||||
}]);
|
systemd.services.audiobookshelfd.serviceConfig = cfg.extraServiceConfig;
|
||||||
|
}
|
||||||
|
]
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,9 @@
|
||||||
{ config, pkgs, lib, ... }:
|
{
|
||||||
|
config,
|
||||||
|
pkgs,
|
||||||
|
lib,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
|
||||||
let
|
let
|
||||||
cfg = config.shb.deluge;
|
cfg = config.shb.deluge;
|
||||||
|
|
@ -7,9 +12,15 @@ let
|
||||||
|
|
||||||
fqdn = "${cfg.subdomain}.${cfg.domain}";
|
fqdn = "${cfg.subdomain}.${cfg.domain}";
|
||||||
|
|
||||||
authGenerator = users:
|
authGenerator =
|
||||||
|
users:
|
||||||
let
|
let
|
||||||
genLine = name: { password, priority ? 10 }:
|
genLine =
|
||||||
|
name:
|
||||||
|
{
|
||||||
|
password,
|
||||||
|
priority ? 10,
|
||||||
|
}:
|
||||||
"${name}:${password}:${toString priority}";
|
"${name}:${password}:${toString priority}";
|
||||||
|
|
||||||
lines = lib.mapAttrsToList genLine users;
|
lines = lib.mapAttrsToList genLine users;
|
||||||
|
|
@ -57,7 +68,10 @@ in
|
||||||
daemonListenPorts = lib.mkOption {
|
daemonListenPorts = lib.mkOption {
|
||||||
type = lib.types.listOf lib.types.int;
|
type = lib.types.listOf lib.types.int;
|
||||||
description = "Deluge daemon listen ports";
|
description = "Deluge daemon listen ports";
|
||||||
default = [ 6881 6889 ];
|
default = [
|
||||||
|
6881
|
||||||
|
6889
|
||||||
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
webPort = lib.mkOption {
|
webPort = lib.mkOption {
|
||||||
|
|
@ -176,14 +190,16 @@ in
|
||||||
|
|
||||||
extraUsers = lib.mkOption {
|
extraUsers = lib.mkOption {
|
||||||
description = "Users having access to this deluge instance. Attrset of username to user options.";
|
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 = {
|
options = {
|
||||||
password = lib.mkOption {
|
password = lib.mkOption {
|
||||||
type = lib.shb.secretFileType;
|
type = lib.shb.secretFileType;
|
||||||
description = "File containing the user password.";
|
description = "File containing the user password.";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
});
|
}
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
localclientPassword = lib.mkOption {
|
localclientPassword = lib.mkOption {
|
||||||
|
|
@ -198,12 +214,17 @@ in
|
||||||
|
|
||||||
prometheusScraperPassword = lib.mkOption {
|
prometheusScraperPassword = lib.mkOption {
|
||||||
description = "Password for prometheus scraper. Setting this option will activate the prometheus deluge exporter.";
|
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 {
|
options = contracts.secret.mkRequester {
|
||||||
owner = "deluge";
|
owner = "deluge";
|
||||||
restartUnits = [ "deluged.service" "prometheus.service" ];
|
restartUnits = [
|
||||||
|
"deluged.service"
|
||||||
|
"prometheus.service"
|
||||||
|
];
|
||||||
};
|
};
|
||||||
});
|
}
|
||||||
|
);
|
||||||
default = null;
|
default = null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -262,14 +283,24 @@ in
|
||||||
};
|
};
|
||||||
|
|
||||||
logLevel = lib.mkOption {
|
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.";
|
description = "Enable logging.";
|
||||||
default = null;
|
default = null;
|
||||||
example = "info";
|
example = "info";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
config = lib.mkIf cfg.enable (lib.mkMerge [{
|
config = lib.mkIf cfg.enable (
|
||||||
|
lib.mkMerge [
|
||||||
|
{
|
||||||
services.deluge = {
|
services.deluge = {
|
||||||
enable = true;
|
enable = true;
|
||||||
declarative = true;
|
declarative = true;
|
||||||
|
|
@ -292,7 +323,8 @@ in
|
||||||
};
|
};
|
||||||
outgoing_interface = cfg.outgoingInterface;
|
outgoing_interface = cfg.outgoingInterface;
|
||||||
|
|
||||||
enabled_plugins = cfg.enabledPlugins
|
enabled_plugins =
|
||||||
|
cfg.enabledPlugins
|
||||||
++ lib.optional (lib.any (x: x.enable) [
|
++ lib.optional (lib.any (x: x.enable) [
|
||||||
config.services.radarr
|
config.services.radarr
|
||||||
config.services.sonarr
|
config.services.sonarr
|
||||||
|
|
@ -316,7 +348,8 @@ in
|
||||||
max_upload_speed
|
max_upload_speed
|
||||||
max_upload_speed_per_torrent
|
max_upload_speed_per_torrent
|
||||||
|
|
||||||
dont_count_slow_torrents;
|
dont_count_slow_torrents
|
||||||
|
;
|
||||||
|
|
||||||
new_release_check = false;
|
new_release_check = false;
|
||||||
};
|
};
|
||||||
|
|
@ -327,22 +360,31 @@ in
|
||||||
web.port = cfg.webPort;
|
web.port = cfg.webPort;
|
||||||
};
|
};
|
||||||
|
|
||||||
systemd.services.deluged.preStart = lib.mkBefore (lib.shb.replaceSecrets {
|
systemd.services.deluged.preStart = lib.mkBefore (
|
||||||
userConfig = cfg.extraUsers // {
|
lib.shb.replaceSecrets {
|
||||||
|
userConfig =
|
||||||
|
cfg.extraUsers
|
||||||
|
// {
|
||||||
localclient.password.source = config.shb.deluge.localclientPassword.result.path;
|
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;
|
prometheus_scraper.password.source = config.shb.deluge.prometheusScraperPassword.result.path;
|
||||||
});
|
});
|
||||||
resultPath = "${cfg.dataDir}/.config/deluge/authTemplate";
|
resultPath = "${cfg.dataDir}/.config/deluge/authTemplate";
|
||||||
generator = name: value: pkgs.writeText "delugeAuth" (authGenerator value);
|
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"
|
"${config.services.deluge.package}/bin/deluged"
|
||||||
"--do-not-daemonize"
|
"--do-not-daemonize"
|
||||||
"--config ${cfg.dataDir}/.config/deluge"
|
"--config ${cfg.dataDir}/.config/deluge"
|
||||||
] ++ (lib.optional (!(isNull cfg.logLevel)) "-L ${cfg.logLevel}")
|
]
|
||||||
));
|
++ (lib.optional (!(isNull cfg.logLevel)) "-L ${cfg.logLevel}")
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
systemd.tmpfiles.rules =
|
systemd.tmpfiles.rules =
|
||||||
let
|
let
|
||||||
|
|
@ -356,7 +398,8 @@ in
|
||||||
];
|
];
|
||||||
|
|
||||||
shb.nginx.vhosts = [
|
shb.nginx.vhosts = [
|
||||||
({
|
(
|
||||||
|
{
|
||||||
inherit (cfg) subdomain domain ssl;
|
inherit (cfg) subdomain domain ssl;
|
||||||
upstream = "http://127.0.0.1:${toString config.services.deluge.web.port}";
|
upstream = "http://127.0.0.1:${toString config.services.deluge.web.port}";
|
||||||
autheliaRules = lib.mkIf (cfg.authEndpoint != null) [
|
autheliaRules = lib.mkIf (cfg.authEndpoint != null) [
|
||||||
|
|
@ -373,13 +416,17 @@ in
|
||||||
subject = [ "group:deluge_user" ];
|
subject = [ "group:deluge_user" ];
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
} // (lib.optionalAttrs (cfg.authEndpoint != null) {
|
}
|
||||||
|
// (lib.optionalAttrs (cfg.authEndpoint != null) {
|
||||||
inherit (cfg) authEndpoint;
|
inherit (cfg) authEndpoint;
|
||||||
}))
|
})
|
||||||
|
)
|
||||||
];
|
];
|
||||||
} {
|
}
|
||||||
|
{
|
||||||
systemd.services.deluged.serviceConfig = cfg.extraServiceConfig;
|
systemd.services.deluged.serviceConfig = cfg.extraServiceConfig;
|
||||||
} (lib.mkIf (config.shb.deluge.prometheusScraperPassword != null) {
|
}
|
||||||
|
(lib.mkIf (config.shb.deluge.prometheusScraperPassword != null) {
|
||||||
services.prometheus.exporters.deluge = {
|
services.prometheus.exporters.deluge = {
|
||||||
enable = true;
|
enable = true;
|
||||||
|
|
||||||
|
|
@ -393,15 +440,18 @@ in
|
||||||
services.prometheus.scrapeConfigs = [
|
services.prometheus.scrapeConfigs = [
|
||||||
{
|
{
|
||||||
job_name = "deluge";
|
job_name = "deluge";
|
||||||
static_configs = [{
|
static_configs = [
|
||||||
|
{
|
||||||
targets = [ "127.0.0.1:${toString config.services.prometheus.exporters.deluge.port}" ];
|
targets = [ "127.0.0.1:${toString config.services.prometheus.exporters.deluge.port}" ];
|
||||||
labels = {
|
labels = {
|
||||||
"hostname" = config.networking.hostName;
|
"hostname" = config.networking.hostName;
|
||||||
"domain" = cfg.domain;
|
"domain" = cfg.domain;
|
||||||
};
|
};
|
||||||
}];
|
}
|
||||||
|
];
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
})
|
})
|
||||||
]);
|
]
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,53 @@
|
||||||
{ config, options, pkgs, lib, ... }:
|
{
|
||||||
|
config,
|
||||||
|
options,
|
||||||
|
pkgs,
|
||||||
|
lib,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
|
||||||
let
|
let
|
||||||
cfg = config.shb.forgejo;
|
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)
|
||||||
inherit (lib.types) attrsOf bool enum listOf nullOr package port submodule str;
|
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
|
in
|
||||||
{
|
{
|
||||||
imports = [
|
imports = [
|
||||||
../blocks/nginx.nix
|
../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 = {
|
shb.forgejo.users = {
|
||||||
"forgejoadmin" = {
|
"forgejoadmin" = {
|
||||||
isAdmin = true;
|
isAdmin = true;
|
||||||
|
|
@ -164,7 +199,10 @@ in
|
||||||
};
|
};
|
||||||
|
|
||||||
authorization_policy = mkOption {
|
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.";
|
description = "Require one factor (password) or two factor (device) authentication.";
|
||||||
default = "one_factor";
|
default = "one_factor";
|
||||||
};
|
};
|
||||||
|
|
@ -205,7 +243,8 @@ in
|
||||||
};
|
};
|
||||||
|
|
||||||
email = mkOption {
|
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.
|
This is only set when the user is created, changing this later on will have no effect.
|
||||||
'';
|
'';
|
||||||
|
|
@ -254,7 +293,6 @@ in
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
hostPackages = mkOption {
|
hostPackages = mkOption {
|
||||||
type = listOf package;
|
type = listOf package;
|
||||||
default = with pkgs; [
|
default = with pkgs; [
|
||||||
|
|
@ -295,7 +333,8 @@ in
|
||||||
user = options.services.forgejo.user.value;
|
user = options.services.forgejo.user.value;
|
||||||
sourceDirectories = [
|
sourceDirectories = [
|
||||||
options.services.forgejo.dump.backupDir.value
|
options.services.forgejo.dump.backupDir.value
|
||||||
] ++ optionals (cfg.repositoryRoot != null) [
|
]
|
||||||
|
++ optionals (cfg.repositoryRoot != null) [
|
||||||
cfg.repositoryRoot
|
cfg.repositoryRoot
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
@ -317,7 +356,9 @@ in
|
||||||
```
|
```
|
||||||
'';
|
'';
|
||||||
readOnly = true;
|
readOnly = true;
|
||||||
default = { path = config.services.forgejo.stateDir; };
|
default = {
|
||||||
|
path = config.services.forgejo.stateDir;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
smtp = mkOption {
|
smtp = mkOption {
|
||||||
|
|
@ -390,10 +431,12 @@ in
|
||||||
# https://github.com/NixOS/nixpkgs/issues/258371#issuecomment-2271967113
|
# https://github.com/NixOS/nixpkgs/issues/258371#issuecomment-2271967113
|
||||||
systemd.services.forgejo.serviceConfig.Type = mkForce "exec";
|
systemd.services.forgejo.serviceConfig.Type = mkForce "exec";
|
||||||
|
|
||||||
shb.nginx.vhosts = [{
|
shb.nginx.vhosts = [
|
||||||
|
{
|
||||||
inherit (cfg) domain subdomain ssl;
|
inherit (cfg) domain subdomain ssl;
|
||||||
upstream = "http://unix:${config.services.forgejo.settings.server.HTTP_ADDR}";
|
upstream = "http://unix:${config.services.forgejo.settings.server.HTTP_ADDR}";
|
||||||
}];
|
}
|
||||||
|
];
|
||||||
})
|
})
|
||||||
|
|
||||||
(mkIf cfg.enable {
|
(mkIf cfg.enable {
|
||||||
|
|
@ -420,9 +463,11 @@ in
|
||||||
systemd.services.forgejo.wants = cfg.ldap.waitForSystemdServices;
|
systemd.services.forgejo.wants = cfg.ldap.waitForSystemdServices;
|
||||||
systemd.services.forgejo.after = cfg.ldap.waitForSystemdServices;
|
systemd.services.forgejo.after = cfg.ldap.waitForSystemdServices;
|
||||||
# The delimiter in the `cut` command is a TAB!
|
# The delimiter in the `cut` command is a TAB!
|
||||||
systemd.services.forgejo.preStart = let
|
systemd.services.forgejo.preStart =
|
||||||
|
let
|
||||||
provider = "SHB-${cfg.ldap.provider}";
|
provider = "SHB-${cfg.ldap.provider}";
|
||||||
in ''
|
in
|
||||||
|
''
|
||||||
auth="${getExe config.services.forgejo.package} admin auth"
|
auth="${getExe config.services.forgejo.package} admin auth"
|
||||||
|
|
||||||
echo "Trying to find existing ldap configuration for ${provider}"...
|
echo "Trying to find existing ldap configuration for ${provider}"...
|
||||||
|
|
@ -497,9 +542,11 @@ in
|
||||||
};
|
};
|
||||||
|
|
||||||
# The delimiter in the `cut` command is a TAB!
|
# The delimiter in the `cut` command is a TAB!
|
||||||
systemd.services.forgejo.preStart = let
|
systemd.services.forgejo.preStart =
|
||||||
|
let
|
||||||
provider = "SHB-${cfg.sso.provider}";
|
provider = "SHB-${cfg.sso.provider}";
|
||||||
in ''
|
in
|
||||||
|
''
|
||||||
auth="${getExe config.services.forgejo.package} admin auth"
|
auth="${getExe config.services.forgejo.package} admin auth"
|
||||||
|
|
||||||
echo "Trying to find existing sso configuration for ${provider}"...
|
echo "Trying to find existing sso configuration for ${provider}"...
|
||||||
|
|
@ -529,16 +576,19 @@ in
|
||||||
'';
|
'';
|
||||||
|
|
||||||
shb.authelia.oidcClients = lists.optionals (!(isNull cfg.sso)) [
|
shb.authelia.oidcClients = lists.optionals (!(isNull cfg.sso)) [
|
||||||
(let
|
(
|
||||||
|
let
|
||||||
provider = "SHB-${cfg.sso.provider}";
|
provider = "SHB-${cfg.sso.provider}";
|
||||||
in {
|
in
|
||||||
|
{
|
||||||
client_id = cfg.sso.clientID;
|
client_id = cfg.sso.clientID;
|
||||||
client_name = "Forgejo";
|
client_name = "Forgejo";
|
||||||
client_secret.source = cfg.sso.sharedSecretForAuthelia.result.path;
|
client_secret.source = cfg.sso.sharedSecretForAuthelia.result.path;
|
||||||
public = false;
|
public = false;
|
||||||
authorization_policy = cfg.sso.authorization_policy;
|
authorization_policy = cfg.sso.authorization_policy;
|
||||||
redirect_uris = [ "https://${cfg.subdomain}.${cfg.domain}/user/oauth2/${provider}/callback" ];
|
redirect_uris = [ "https://${cfg.subdomain}.${cfg.domain}/user/oauth2/${provider}/callback" ];
|
||||||
})
|
}
|
||||||
|
)
|
||||||
];
|
];
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -552,7 +602,8 @@ in
|
||||||
|
|
||||||
systemd.services.forgejo.preStart = ''
|
systemd.services.forgejo.preStart = ''
|
||||||
admin="${getExe config.services.forgejo.package} admin user"
|
admin="${getExe config.services.forgejo.package} admin user"
|
||||||
'' + concatMapStringsSep "\n" (u: ''
|
''
|
||||||
|
+ concatMapStringsSep "\n" (u: ''
|
||||||
if ! $admin list | grep "${u.name}"; then
|
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})"
|
$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
|
else
|
||||||
|
|
@ -584,9 +635,11 @@ in
|
||||||
instances.local = {
|
instances.local = {
|
||||||
enable = true;
|
enable = true;
|
||||||
name = "local";
|
name = "local";
|
||||||
url = let
|
url =
|
||||||
|
let
|
||||||
protocol = if cfg.ssl != null then "https" else "http";
|
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.
|
tokenFile = ""; # Empty variable to satisfy an assertion.
|
||||||
labels = [
|
labels = [
|
||||||
# "ubuntu-latest:docker://node:16-bullseye"
|
# "ubuntu-latest:docker://node:16-bullseye"
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,9 @@
|
||||||
{ config, pkgs, lib, ... }:
|
{
|
||||||
|
config,
|
||||||
|
pkgs,
|
||||||
|
lib,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
|
||||||
let
|
let
|
||||||
cfg = config.shb.grocy;
|
cfg = config.shb.grocy;
|
||||||
|
|
@ -37,7 +42,24 @@ in
|
||||||
};
|
};
|
||||||
|
|
||||||
culture = lib.mkOption {
|
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";
|
default = "en";
|
||||||
description = ''
|
description = ''
|
||||||
Display language of the frontend.
|
Display language of the frontend.
|
||||||
|
|
@ -78,14 +100,24 @@ in
|
||||||
};
|
};
|
||||||
|
|
||||||
logLevel = lib.mkOption {
|
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.";
|
description = "Enable logging.";
|
||||||
default = false;
|
default = false;
|
||||||
example = true;
|
example = true;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
config = lib.mkIf cfg.enable (lib.mkMerge [{
|
config = lib.mkIf cfg.enable (
|
||||||
|
lib.mkMerge [
|
||||||
|
{
|
||||||
services.grocy = {
|
services.grocy = {
|
||||||
enable = true;
|
enable = true;
|
||||||
hostName = fqdn;
|
hostName = fqdn;
|
||||||
|
|
@ -105,7 +137,10 @@ in
|
||||||
sslCertificate = lib.mkIf (!(isNull cfg.ssl)) cfg.ssl.paths.cert;
|
sslCertificate = lib.mkIf (!(isNull cfg.ssl)) cfg.ssl.paths.cert;
|
||||||
sslCertificateKey = lib.mkIf (!(isNull cfg.ssl)) cfg.ssl.paths.key;
|
sslCertificateKey = lib.mkIf (!(isNull cfg.ssl)) cfg.ssl.paths.key;
|
||||||
};
|
};
|
||||||
} {
|
}
|
||||||
systemd.services.grocyd.serviceConfig = cfg.extraServiceConfig;
|
{
|
||||||
}]);
|
systemd.services.grocyd.serviceConfig = cfg.extraServiceConfig;
|
||||||
|
}
|
||||||
|
]
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,9 @@
|
||||||
{ config, pkgs, lib, ... }:
|
{
|
||||||
|
config,
|
||||||
|
pkgs,
|
||||||
|
lib,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
|
||||||
let
|
let
|
||||||
cfg = config.shb.hledger;
|
cfg = config.shb.hledger;
|
||||||
|
|
@ -108,13 +113,20 @@ in
|
||||||
|
|
||||||
shb.nginx.vhosts = [
|
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}";
|
upstream = "http://${toString config.services.hledger-web.host}:${toString config.services.hledger-web.port}";
|
||||||
autheliaRules = [{
|
autheliaRules = [
|
||||||
|
{
|
||||||
domain = fqdn;
|
domain = fqdn;
|
||||||
policy = "two_factor";
|
policy = "two_factor";
|
||||||
subject = [ "group:hledger_user" ];
|
subject = [ "group:hledger_user" ];
|
||||||
}];
|
}
|
||||||
|
];
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,9 @@
|
||||||
{ config, pkgs, lib, ... }:
|
{
|
||||||
|
config,
|
||||||
|
pkgs,
|
||||||
|
lib,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
|
||||||
let
|
let
|
||||||
cfg = config.shb.home-assistant;
|
cfg = config.shb.home-assistant;
|
||||||
|
|
@ -24,9 +29,7 @@ let
|
||||||
|
|
||||||
nonSecrets = (lib.attrsets.filterAttrs (k: v: !(builtins.isAttrs v)) cfg.config);
|
nonSecrets = (lib.attrsets.filterAttrs (k: v: !(builtins.isAttrs v)) cfg.config);
|
||||||
|
|
||||||
configWithSecretsIncludes =
|
configWithSecretsIncludes = nonSecrets // (lib.attrsets.mapAttrs (k: v: "!secret ${k}") secrets);
|
||||||
nonSecrets
|
|
||||||
// (lib.attrsets.mapAttrs (k: v: "!secret ${k}") secrets);
|
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
options.shb.home-assistant = {
|
options.shb.home-assistant = {
|
||||||
|
|
@ -56,28 +59,49 @@ in
|
||||||
freeformType = lib.types.attrsOf lib.types.str;
|
freeformType = lib.types.attrsOf lib.types.str;
|
||||||
options = {
|
options = {
|
||||||
name = lib.mkOption {
|
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.";
|
description = "Name of the Home Assistant instance.";
|
||||||
};
|
};
|
||||||
country = lib.mkOption {
|
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.";
|
description = "Two letter country code where this instance is located.";
|
||||||
};
|
};
|
||||||
latitude = lib.mkOption {
|
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.";
|
description = "Latitude where this instance is located.";
|
||||||
};
|
};
|
||||||
longitude = lib.mkOption {
|
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.";
|
description = "Longitude where this instance is located.";
|
||||||
};
|
};
|
||||||
time_zone = lib.mkOption {
|
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.";
|
description = "Timezone of this instance.";
|
||||||
example = "America/Los_Angeles";
|
example = "America/Los_Angeles";
|
||||||
};
|
};
|
||||||
unit_system = lib.mkOption {
|
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.";
|
description = "Unit system of this instance.";
|
||||||
example = "metric";
|
example = "metric";
|
||||||
};
|
};
|
||||||
|
|
@ -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
|
https://search.nixos.org/options?channel=23.11&from=0&size=50&sort=relevance&type=packages&query=services.wyoming.openwakeword
|
||||||
'';
|
'';
|
||||||
type = lib.types.anything;
|
type = lib.types.anything;
|
||||||
default = { enable = false; };
|
default = {
|
||||||
|
enable = false;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
@ -240,7 +266,10 @@ in
|
||||||
{
|
{
|
||||||
type = "command_line";
|
type = "command_line";
|
||||||
command = ldap_auth_script + "/bin/ldap_auth.sh";
|
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;
|
meta = true;
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
|
|
@ -286,7 +315,11 @@ in
|
||||||
{
|
{
|
||||||
name = "random_joke";
|
name = "random_joke";
|
||||||
platform = "rest";
|
platform = "rest";
|
||||||
json_attributes = ["joke" "id" "status"];
|
json_attributes = [
|
||||||
|
"joke"
|
||||||
|
"id"
|
||||||
|
"status"
|
||||||
|
];
|
||||||
value_template = "{{ value_json.joke }}";
|
value_template = "{{ value_json.joke }}";
|
||||||
resource = "https://icanhazdadjoke.com/";
|
resource = "https://icanhazdadjoke.com/";
|
||||||
scan_interval = "3600";
|
scan_interval = "3600";
|
||||||
|
|
@ -324,7 +357,8 @@ in
|
||||||
};
|
};
|
||||||
|
|
||||||
systemd.services.home-assistant.preStart =
|
systemd.services.home-assistant.preStart =
|
||||||
(let
|
(
|
||||||
|
let
|
||||||
# TODO: this probably does not work anymore
|
# TODO: this probably does not work anymore
|
||||||
onboarding = pkgs.writeText "onboarding" ''
|
onboarding = pkgs.writeText "onboarding" ''
|
||||||
{
|
{
|
||||||
|
|
@ -342,11 +376,13 @@ in
|
||||||
'';
|
'';
|
||||||
storage = "${config.services.home-assistant.configDir}";
|
storage = "${config.services.home-assistant.configDir}";
|
||||||
file = "${storage}/.storage/onboarding";
|
file = "${storage}/.storage/onboarding";
|
||||||
in ''
|
in
|
||||||
|
''
|
||||||
if [ ! -f ${file} ]; then
|
if [ ! -f ${file} ]; then
|
||||||
mkdir -p ''$(dirname ${file}) && cp ${onboarding} ${file}
|
mkdir -p ''$(dirname ${file}) && cp ${onboarding} ${file}
|
||||||
fi
|
fi
|
||||||
'')
|
''
|
||||||
|
)
|
||||||
+ (lib.shb.replaceSecrets {
|
+ (lib.shb.replaceSecrets {
|
||||||
userConfig = cfg.config;
|
userConfig = cfg.config;
|
||||||
resultPath = "${config.services.home-assistant.configDir}/secrets.yaml";
|
resultPath = "${config.services.home-assistant.configDir}/secrets.yaml";
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,9 @@
|
||||||
{ config, pkgs, lib, ... }:
|
{
|
||||||
|
config,
|
||||||
|
pkgs,
|
||||||
|
lib,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
|
||||||
let
|
let
|
||||||
cfg = config.shb.immich;
|
cfg = config.shb.immich;
|
||||||
|
|
@ -12,21 +17,29 @@ let
|
||||||
|
|
||||||
# TODO: Quota management, see https://github.com/ibizaman/selfhostblocks/pull/523#discussion_r2309421694
|
# TODO: Quota management, see https://github.com/ibizaman/selfhostblocks/pull/523#discussion_r2309421694
|
||||||
#quotaClaim = "immich_quota";
|
#quotaClaim = "immich_quota";
|
||||||
scopes = [ "openid" "email" "profile" "groups" "immich_scope"];
|
scopes = [
|
||||||
|
"openid"
|
||||||
|
"email"
|
||||||
|
"profile"
|
||||||
|
"groups"
|
||||||
|
"immich_scope"
|
||||||
|
];
|
||||||
|
|
||||||
dataFolder = cfg.mediaLocation;
|
dataFolder = cfg.mediaLocation;
|
||||||
ssoFqdnWithPort = if isNull cfg.sso.port
|
ssoFqdnWithPort =
|
||||||
then cfg.sso.endpoint
|
if isNull cfg.sso.port then cfg.sso.endpoint else "${cfg.sso.endpoint}:${toString cfg.sso.port}";
|
||||||
else "${cfg.sso.endpoint}:${toString cfg.sso.port}";
|
|
||||||
# Generate Immich configuration file only for SHB-managed settings
|
# 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) {
|
// lib.optionalAttrs (cfg.sso.enable) {
|
||||||
oauth = {
|
oauth = {
|
||||||
enabled = true;
|
enabled = true;
|
||||||
issuerUrl = "${ssoFqdnWithPort}";
|
issuerUrl = "${ssoFqdnWithPort}";
|
||||||
clientId = cfg.sso.clientID;
|
clientId = cfg.sso.clientID;
|
||||||
roleClaim = roleClaim;
|
roleClaim = roleClaim;
|
||||||
clientSecret = { source = cfg.sso.sharedSecret.result.path; };
|
clientSecret = {
|
||||||
|
source = cfg.sso.sharedSecret.result.path;
|
||||||
|
};
|
||||||
scope = builtins.concatStringsSep " " scopes;
|
scope = builtins.concatStringsSep " " scopes;
|
||||||
storageLabelClaim = cfg.sso.storageLabelClaim;
|
storageLabelClaim = cfg.sso.storageLabelClaim;
|
||||||
#storageQuotaClaim = quotaClaim; # TODO (commented out, otherwise defaults to 0 bytes!)
|
#storageQuotaClaim = quotaClaim; # TODO (commented out, otherwise defaults to 0 bytes!)
|
||||||
|
|
@ -49,7 +62,9 @@ let
|
||||||
host = cfg.smtp.host;
|
host = cfg.smtp.host;
|
||||||
port = cfg.smtp.port;
|
port = cfg.smtp.port;
|
||||||
username = cfg.smtp.username;
|
username = cfg.smtp.username;
|
||||||
password = { source = cfg.smtp.password.result.path; };
|
password = {
|
||||||
|
source = cfg.smtp.password.result.path;
|
||||||
|
};
|
||||||
ignoreTLS = cfg.smtp.ignoreTLS;
|
ignoreTLS = cfg.smtp.ignoreTLS;
|
||||||
secure = cfg.smtp.secure;
|
secure = cfg.smtp.secure;
|
||||||
};
|
};
|
||||||
|
|
@ -69,8 +84,25 @@ let
|
||||||
permissions = "u=r,g=,o=";
|
permissions = "u=r,g=,o=";
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
inherit (lib) mkEnableOption mkIf lists mkOption optionals;
|
inherit (lib)
|
||||||
inherit (lib.types) attrs attrsOf bool enum listOf nullOr port submodule str path;
|
mkEnableOption
|
||||||
|
mkIf
|
||||||
|
lists
|
||||||
|
mkOption
|
||||||
|
optionals
|
||||||
|
;
|
||||||
|
inherit (lib.types)
|
||||||
|
attrs
|
||||||
|
attrsOf
|
||||||
|
bool
|
||||||
|
enum
|
||||||
|
listOf
|
||||||
|
nullOr
|
||||||
|
port
|
||||||
|
submodule
|
||||||
|
str
|
||||||
|
path
|
||||||
|
;
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
imports = [
|
imports = [
|
||||||
|
|
@ -154,7 +186,9 @@ in
|
||||||
```
|
```
|
||||||
'';
|
'';
|
||||||
readOnly = true;
|
readOnly = true;
|
||||||
default = { path = dataFolder; };
|
default = {
|
||||||
|
path = dataFolder;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
backup = mkOption {
|
backup = mkOption {
|
||||||
|
|
@ -222,7 +256,11 @@ in
|
||||||
enable = mkEnableOption "SSO integration.";
|
enable = mkEnableOption "SSO integration.";
|
||||||
|
|
||||||
provider = mkOption {
|
provider = mkOption {
|
||||||
type = enum [ "Authelia" "Keycloak" "Generic" ];
|
type = enum [
|
||||||
|
"Authelia"
|
||||||
|
"Keycloak"
|
||||||
|
"Generic"
|
||||||
|
];
|
||||||
description = "OIDC provider name, used for display.";
|
description = "OIDC provider name, used for display.";
|
||||||
default = "Authelia";
|
default = "Authelia";
|
||||||
};
|
};
|
||||||
|
|
@ -311,7 +349,10 @@ in
|
||||||
};
|
};
|
||||||
|
|
||||||
authorization_policy = mkOption {
|
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.";
|
description = "Require one factor (password) or two factor (device) authentication.";
|
||||||
default = "one_factor";
|
default = "one_factor";
|
||||||
};
|
};
|
||||||
|
|
@ -452,9 +493,11 @@ in
|
||||||
REDIS_HOSTNAME = "127.0.0.1";
|
REDIS_HOSTNAME = "127.0.0.1";
|
||||||
REDIS_PORT = "6379";
|
REDIS_PORT = "6379";
|
||||||
REDIS_DBINDEX = "0";
|
REDIS_DBINDEX = "0";
|
||||||
} // lib.optionalAttrs (cfg.jwtSecretFile != null) {
|
}
|
||||||
|
// lib.optionalAttrs (cfg.jwtSecretFile != null) {
|
||||||
JWT_SECRET_FILE = cfg.jwtSecretFile.result.path;
|
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;
|
IMMICH_CONFIG_FILE = configFile;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
@ -465,7 +508,9 @@ in
|
||||||
];
|
];
|
||||||
|
|
||||||
# Configuration setup service - generates config only for SHB-managed settings
|
# 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";
|
description = "Setup Immich configuration for SHB-managed settings";
|
||||||
wantedBy = [ "multi-user.target" ];
|
wantedBy = [ "multi-user.target" ];
|
||||||
before = [ "immich-server.service" ];
|
before = [ "immich-server.service" ];
|
||||||
|
|
@ -484,7 +529,10 @@ in
|
||||||
};
|
};
|
||||||
|
|
||||||
# Add immich user to video and render groups for hardware acceleration
|
# 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
|
# PostgreSQL extensions are automatically handled by the Immich service
|
||||||
|
|
||||||
|
|
@ -499,7 +547,10 @@ in
|
||||||
{
|
{
|
||||||
domain = fqdn;
|
domain = fqdn;
|
||||||
policy = cfg.sso.authorization_policy;
|
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;
|
authEndpoint = lib.mkIf (cfg.sso.enable) cfg.sso.endpoint;
|
||||||
|
|
@ -515,10 +566,20 @@ in
|
||||||
|
|
||||||
# Ensure services start in correct order
|
# Ensure services start in correct order
|
||||||
systemd.services.immich-server = {
|
systemd.services.immich-server = {
|
||||||
after = [ "postgresql.service" "redis-immich.service" ]
|
after = [
|
||||||
++ optionals (cfg.settings != {} || cfg.sso.enable || cfg.smtp != null) [ "immich-setup-config.service" ];
|
"postgresql.service"
|
||||||
requires = [ "postgresql.service" "redis-immich.service" ]
|
"redis-immich.service"
|
||||||
++ optionals (cfg.settings != {} || cfg.sso.enable || cfg.smtp != null) [ "immich-setup-config.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 {
|
systemd.services.immich-machine-learning = mkIf cfg.machineLearning.enable {
|
||||||
|
|
@ -530,7 +591,8 @@ in
|
||||||
# Immich expects all users that get a token to be granted access. So users can either be part of the
|
# 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
|
# "admin" group or the "user" group. Users that are not part of either should be blocked by
|
||||||
# the ID provider (Authelia).
|
# 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 = {
|
shb.authelia.extraOidcClaimsPolicies.immich_policy = {
|
||||||
custom_claims = {
|
custom_claims = {
|
||||||
|
|
@ -541,7 +603,6 @@ in
|
||||||
claims = [ roleClaim ];
|
claims = [ roleClaim ];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
shb.authelia.oidcClients = lists.optionals (cfg.sso.enable && cfg.sso.provider == "Authelia") [
|
shb.authelia.oidcClients = lists.optionals (cfg.sso.enable && cfg.sso.provider == "Authelia") [
|
||||||
{
|
{
|
||||||
client_id = cfg.sso.clientID;
|
client_id = cfg.sso.clientID;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,9 @@
|
||||||
{ config, lib, pkgs, ...}:
|
{
|
||||||
|
config,
|
||||||
|
lib,
|
||||||
|
pkgs,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
|
||||||
let
|
let
|
||||||
inherit (lib) types;
|
inherit (lib) types;
|
||||||
|
|
@ -91,7 +96,8 @@ in
|
||||||
admin = lib.mkOption {
|
admin = lib.mkOption {
|
||||||
description = "Default admin user info. Only needed if LDAP or SSO is not configured.";
|
description = "Default admin user info. Only needed if LDAP or SSO is not configured.";
|
||||||
default = null;
|
default = null;
|
||||||
type = types.nullOr (types.submodule {
|
type = types.nullOr (
|
||||||
|
types.submodule {
|
||||||
options = {
|
options = {
|
||||||
username = lib.mkOption {
|
username = lib.mkOption {
|
||||||
description = "Username of the default admin user.";
|
description = "Username of the default admin user.";
|
||||||
|
|
@ -110,7 +116,8 @@ in
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
});
|
}
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
ldap = lib.mkOption {
|
ldap = lib.mkOption {
|
||||||
|
|
@ -203,7 +210,10 @@ in
|
||||||
};
|
};
|
||||||
|
|
||||||
authorization_policy = lib.mkOption {
|
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.";
|
description = "Require one factor (password) or two factor (device) authentication.";
|
||||||
default = "one_factor";
|
default = "one_factor";
|
||||||
};
|
};
|
||||||
|
|
@ -245,7 +255,8 @@ in
|
||||||
sourceDirectories = [
|
sourceDirectories = [
|
||||||
config.services.jellyfin.dataDir
|
config.services.jellyfin.dataDir
|
||||||
];
|
];
|
||||||
sourceDirectoriesText = ''[
|
sourceDirectoriesText = ''
|
||||||
|
[
|
||||||
"services.jellyfin.dataDir"
|
"services.jellyfin.dataDir"
|
||||||
]'';
|
]'';
|
||||||
};
|
};
|
||||||
|
|
@ -254,7 +265,10 @@ in
|
||||||
};
|
};
|
||||||
|
|
||||||
imports = [
|
imports = [
|
||||||
(lib.mkRenamedOptionModule [ "shb" "jellyfin" "adminPassword" ] [ "shb" "jellyfin" "admin" "password" ])
|
(lib.mkRenamedOptionModule
|
||||||
|
[ "shb" "jellyfin" "adminPassword" ]
|
||||||
|
[ "shb" "jellyfin" "admin" "password" ]
|
||||||
|
)
|
||||||
];
|
];
|
||||||
|
|
||||||
config = lib.mkIf cfg.enable {
|
config = lib.mkIf cfg.enable {
|
||||||
|
|
@ -269,7 +283,10 @@ in
|
||||||
|
|
||||||
networking.firewall = {
|
networking.firewall = {
|
||||||
# from https://jellyfin.org/docs/general/networking/index.html, for auto-discovery
|
# from https://jellyfin.org/docs/general/networking/index.html, for auto-discovery
|
||||||
allowedUDPPorts = [ 1900 7359 ];
|
allowedUDPPorts = [
|
||||||
|
1900
|
||||||
|
7359
|
||||||
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
services.nginx.enable = true;
|
services.nginx.enable = true;
|
||||||
|
|
@ -383,7 +400,8 @@ in
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
services.prometheus.scrapeConfigs = [{
|
services.prometheus.scrapeConfigs = [
|
||||||
|
{
|
||||||
job_name = "jellyfin";
|
job_name = "jellyfin";
|
||||||
static_configs = [
|
static_configs = [
|
||||||
{
|
{
|
||||||
|
|
@ -394,7 +412,8 @@ in
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
}];
|
}
|
||||||
|
];
|
||||||
|
|
||||||
# LDAP config but you need to install the plugin by hand
|
# LDAP config but you need to install the plugin by hand
|
||||||
|
|
||||||
|
|
@ -560,8 +579,7 @@ in
|
||||||
</NetworkConfiguration>
|
</NetworkConfiguration>
|
||||||
'';
|
'';
|
||||||
in
|
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
|
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."
|
echo "A ${config.services.jellyfin.configDir}/logging.json file exists already, this indicates probably an existing installation. Please remove it before continuing."
|
||||||
exit 1
|
exit 1
|
||||||
|
|
@ -576,7 +594,8 @@ in
|
||||||
replacements = [
|
replacements = [
|
||||||
];
|
];
|
||||||
})
|
})
|
||||||
+ lib.strings.optionalString cfg.ldap.enable (lib.shb.replaceSecretsScript {
|
+ lib.strings.optionalString cfg.ldap.enable (
|
||||||
|
lib.shb.replaceSecretsScript {
|
||||||
file = ldapConfig;
|
file = ldapConfig;
|
||||||
resultPath = "${config.services.jellyfin.dataDir}/plugins/configurations/LDAP-Auth.xml";
|
resultPath = "${config.services.jellyfin.dataDir}/plugins/configurations/LDAP-Auth.xml";
|
||||||
replacements = [
|
replacements = [
|
||||||
|
|
@ -585,8 +604,10 @@ in
|
||||||
source = cfg.ldap.adminPassword.result.path;
|
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;
|
file = ssoConfig;
|
||||||
resultPath = "${config.services.jellyfin.dataDir}/plugins/configurations/SSO-Auth.xml";
|
resultPath = "${config.services.jellyfin.dataDir}/plugins/configurations/SSO-Auth.xml";
|
||||||
replacements = [
|
replacements = [
|
||||||
|
|
@ -595,15 +616,19 @@ in
|
||||||
source = cfg.sso.sharedSecret.result.path;
|
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;
|
file = brandingConfig;
|
||||||
resultPath = "${config.services.jellyfin.dataDir}/config/branding.xml";
|
resultPath = "${config.services.jellyfin.dataDir}/config/branding.xml";
|
||||||
replacements = [
|
replacements = [
|
||||||
];
|
];
|
||||||
});
|
}
|
||||||
|
);
|
||||||
|
|
||||||
systemd.services.jellyfin.serviceConfig.ExecStartPost = let
|
systemd.services.jellyfin.serviceConfig.ExecStartPost =
|
||||||
|
let
|
||||||
# We must always wait for the service to be fully initialized,
|
# We must always wait for the service to be fully initialized,
|
||||||
# even if we're planning on changing the config and restarting.
|
# even if we're planning on changing the config and restarting.
|
||||||
waitForCurl = pkgs.writeShellApplication {
|
waitForCurl = pkgs.writeShellApplication {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,9 @@
|
||||||
{ config, lib, pkgs, ... }:
|
{
|
||||||
|
config,
|
||||||
|
lib,
|
||||||
|
pkgs,
|
||||||
|
...
|
||||||
|
}:
|
||||||
let
|
let
|
||||||
cfg = config.shb.karakeep;
|
cfg = config.shb.karakeep;
|
||||||
|
|
||||||
|
|
@ -91,7 +96,10 @@ in
|
||||||
};
|
};
|
||||||
|
|
||||||
authorization_policy = lib.mkOption {
|
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.";
|
description = "Require one factor (password) or two factor (device) authentication.";
|
||||||
default = "one_factor";
|
default = "one_factor";
|
||||||
};
|
};
|
||||||
|
|
@ -102,7 +110,11 @@ in
|
||||||
options = contracts.secret.mkRequester {
|
options = contracts.secret.mkRequester {
|
||||||
owner = "karakeep";
|
owner = "karakeep";
|
||||||
# These services are the ones relying on the environment file containing the secrets.
|
# 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"
|
||||||
|
];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
@ -142,7 +154,11 @@ in
|
||||||
options = contracts.secret.mkRequester {
|
options = contracts.secret.mkRequester {
|
||||||
owner = "karakeep";
|
owner = "karakeep";
|
||||||
# These services are the ones relying on the environment file containing the secrets.
|
# 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 {
|
options = contracts.secret.mkRequester {
|
||||||
owner = "karakeep";
|
owner = "karakeep";
|
||||||
# These services are the ones relying on the environment file containing the secrets.
|
# 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 {
|
(lib.mkIf cfg.enable {
|
||||||
services.karakeep = {
|
services.karakeep = {
|
||||||
enable = true;
|
enable = true;
|
||||||
|
|
@ -168,7 +189,8 @@ in
|
||||||
extraEnvironment = {
|
extraEnvironment = {
|
||||||
PORT = toString cfg.port;
|
PORT = toString cfg.port;
|
||||||
DISABLE_NEW_RELEASE_CHECK = "true"; # These are handled by NixOS
|
DISABLE_NEW_RELEASE_CHECK = "true"; # These are handled by NixOS
|
||||||
} // cfg.environment;
|
}
|
||||||
|
// cfg.environment;
|
||||||
};
|
};
|
||||||
|
|
||||||
shb.nginx.vhosts = [
|
shb.nginx.vhosts = [
|
||||||
|
|
@ -182,19 +204,23 @@ in
|
||||||
# This is needed otherwise the MEILI_MASTER_KEY is generated randomly on first start
|
# This is needed otherwise the MEILI_MASTER_KEY is generated randomly on first start
|
||||||
# instead of using the value from the cfg.meilisearchMasterKey option.
|
# instead of using the value from the cfg.meilisearchMasterKey option.
|
||||||
systemd.services.karakeep-init = {
|
systemd.services.karakeep-init = {
|
||||||
script = lib.mkForce ((lib.shb.replaceSecrets {
|
script = lib.mkForce (
|
||||||
|
(lib.shb.replaceSecrets {
|
||||||
userConfig = {
|
userConfig = {
|
||||||
MEILI_MASTER_KEY.source = cfg.meilisearchMasterKey.result.path;
|
MEILI_MASTER_KEY.source = cfg.meilisearchMasterKey.result.path;
|
||||||
NEXTAUTH_SECRET.source = cfg.nextauthSecret.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;
|
OAUTH_CLIENT_SECRET.source = cfg.sso.sharedSecret.result.path;
|
||||||
};
|
};
|
||||||
resultPath = "/var/lib/karakeep/settings.env";
|
resultPath = "/var/lib/karakeep/settings.env";
|
||||||
generator = lib.shb.toEnvVar;
|
generator = lib.shb.toEnvVar;
|
||||||
}) + ''
|
})
|
||||||
|
+ ''
|
||||||
export DATA_DIR="$STATE_DIRECTORY"
|
export DATA_DIR="$STATE_DIRECTORY"
|
||||||
exec ${config.services.karakeep.package}/lib/karakeep/migrate
|
exec ${config.services.karakeep.package}/lib/karakeep/migrate
|
||||||
'');
|
''
|
||||||
|
);
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
(lib.mkIf cfg.enable {
|
(lib.mkIf cfg.enable {
|
||||||
|
|
@ -222,7 +248,11 @@ in
|
||||||
{
|
{
|
||||||
client_id = cfg.sso.clientID;
|
client_id = cfg.sso.clientID;
|
||||||
client_secret.source = cfg.sso.sharedSecretForAuthelia.result.path;
|
client_secret.source = cfg.sso.sharedSecretForAuthelia.result.path;
|
||||||
scopes = [ "openid" "email" "profile" ];
|
scopes = [
|
||||||
|
"openid"
|
||||||
|
"email"
|
||||||
|
"profile"
|
||||||
|
];
|
||||||
authorization_policy = "karakeep";
|
authorization_policy = "karakeep";
|
||||||
redirect_uris = [
|
redirect_uris = [
|
||||||
"https://${cfg.subdomain}.${cfg.domain}/api/auth/callback/custom"
|
"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
|
let
|
||||||
cfg = config.shb.nextcloud;
|
cfg = config.shb.nextcloud;
|
||||||
|
|
@ -7,12 +12,17 @@ let
|
||||||
fqdnWithPort = if isNull cfg.port then fqdn else "${fqdn}:${toString cfg.port}";
|
fqdnWithPort = if isNull cfg.port then fqdn else "${fqdn}:${toString cfg.port}";
|
||||||
protocol = if !(isNull cfg.ssl) then "https" else "http";
|
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;
|
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";
|
occ = "${config.services.nextcloud.occ}/bin/nextcloud-occ";
|
||||||
in
|
in
|
||||||
|
|
@ -71,7 +81,10 @@ in
|
||||||
|
|
||||||
version = lib.mkOption {
|
version = lib.mkOption {
|
||||||
description = "Nextcloud version to choose from.";
|
description = "Nextcloud version to choose from.";
|
||||||
type = lib.types.enum [ 31 32 ];
|
type = lib.types.enum [
|
||||||
|
31
|
||||||
|
32
|
||||||
|
];
|
||||||
default = 31;
|
default = 31;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -105,7 +118,6 @@ in
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
maxUploadSize = lib.mkOption {
|
maxUploadSize = lib.mkOption {
|
||||||
default = "4G";
|
default = "4G";
|
||||||
type = lib.types.str;
|
type = lib.types.str;
|
||||||
|
|
@ -337,7 +349,8 @@ in
|
||||||
userLocalMount = lib.mkOption {
|
userLocalMount = lib.mkOption {
|
||||||
default = null;
|
default = null;
|
||||||
description = "If set, adds a local mount as external storage.";
|
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 = {
|
options = {
|
||||||
directory = lib.mkOption {
|
directory = lib.mkOption {
|
||||||
type = lib.types.str;
|
type = lib.types.str;
|
||||||
|
|
@ -354,10 +367,14 @@ in
|
||||||
Path of the mount in Nextcloud. Use `/` to mount as the root.
|
Path of the mount in Nextcloud. Use `/` to mount as the root.
|
||||||
'';
|
'';
|
||||||
default = "";
|
default = "";
|
||||||
example = [ "home" "/" ];
|
example = [
|
||||||
|
"home"
|
||||||
|
"/"
|
||||||
|
];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
});
|
}
|
||||||
|
);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
@ -371,7 +388,8 @@ in
|
||||||
the given host.
|
the given host.
|
||||||
'';
|
'';
|
||||||
default = { };
|
default = { };
|
||||||
type = lib.types.nullOr (lib.types.submodule {
|
type = lib.types.nullOr (
|
||||||
|
lib.types.submodule {
|
||||||
options = {
|
options = {
|
||||||
enable = lib.mkEnableOption "LDAP app.";
|
enable = lib.mkEnableOption "LDAP app.";
|
||||||
|
|
||||||
|
|
@ -429,7 +447,8 @@ in
|
||||||
default = 50;
|
default = 50;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
});
|
}
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
sso = lib.mkOption {
|
sso = lib.mkOption {
|
||||||
|
|
@ -469,7 +488,10 @@ in
|
||||||
};
|
};
|
||||||
|
|
||||||
authorization_policy = lib.mkOption {
|
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.";
|
description = "Require one factor (password) or two factor (device) authentication.";
|
||||||
default = "one_factor";
|
default = "one_factor";
|
||||||
};
|
};
|
||||||
|
|
@ -491,7 +513,6 @@ in
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
secretForAuthelia = lib.mkOption {
|
secretForAuthelia = lib.mkOption {
|
||||||
description = "OIDC shared secret. Content must be the same as `secretFile` option.";
|
description = "OIDC shared secret. Content must be the same as `secretFile` option.";
|
||||||
type = lib.types.submodule {
|
type = lib.types.submodule {
|
||||||
|
|
@ -502,7 +523,6 @@ in
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
fallbackDefaultAuth = lib.mkOption {
|
fallbackDefaultAuth = lib.mkOption {
|
||||||
type = lib.types.bool;
|
type = lib.types.bool;
|
||||||
description = ''
|
description = ''
|
||||||
|
|
@ -599,7 +619,6 @@ in
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
backup = lib.mkOption {
|
backup = lib.mkOption {
|
||||||
description = ''
|
description = ''
|
||||||
Backup configuration.
|
Backup configuration.
|
||||||
|
|
@ -726,9 +745,11 @@ in
|
||||||
extraAppsEnable = true;
|
extraAppsEnable = true;
|
||||||
appstoreEnable = true;
|
appstoreEnable = true;
|
||||||
|
|
||||||
settings = let
|
settings =
|
||||||
|
let
|
||||||
protocol = if !(isNull cfg.ssl) then "https" else "http";
|
protocol = if !(isNull cfg.ssl) then "https" else "http";
|
||||||
in {
|
in
|
||||||
|
{
|
||||||
"default_phone_region" = cfg.defaultPhoneRegion;
|
"default_phone_region" = cfg.defaultPhoneRegion;
|
||||||
|
|
||||||
"overwrite.cli.url" = "${protocol}://${fqdn}";
|
"overwrite.cli.url" = "${protocol}://${fqdn}";
|
||||||
|
|
@ -773,7 +794,8 @@ in
|
||||||
"redis.session.locking_enabled" = "1";
|
"redis.session.locking_enabled" = "1";
|
||||||
"redis.session.lock_retries" = "-1";
|
"redis.session.lock_retries" = "-1";
|
||||||
"redis.session.lock_wait_time" = "10000";
|
"redis.session.lock_wait_time" = "10000";
|
||||||
} // lib.optionalAttrs (! (isNull cfg.tracing)) {
|
}
|
||||||
|
// lib.optionalAttrs (!(isNull cfg.tracing)) {
|
||||||
# "xdebug.remote_enable" = "on";
|
# "xdebug.remote_enable" = "on";
|
||||||
# "xdebug.remote_host" = "127.0.0.1";
|
# "xdebug.remote_host" = "127.0.0.1";
|
||||||
# "xdebug.remote_port" = "9000";
|
# "xdebug.remote_port" = "9000";
|
||||||
|
|
@ -837,7 +859,9 @@ in
|
||||||
port = cfg.phpFpmPrometheusExporter.port;
|
port = cfg.phpFpmPrometheusExporter.port;
|
||||||
listenAddress = "127.0.0.1";
|
listenAddress = "127.0.0.1";
|
||||||
extraFlags = [
|
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"
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -856,13 +880,15 @@ in
|
||||||
services.prometheus.scrapeConfigs = [
|
services.prometheus.scrapeConfigs = [
|
||||||
{
|
{
|
||||||
job_name = "phpfpm-nextcloud";
|
job_name = "phpfpm-nextcloud";
|
||||||
static_configs = [{
|
static_configs = [
|
||||||
|
{
|
||||||
targets = [ "127.0.0.1:${toString cfg.phpFpmPrometheusExporter.port}" ];
|
targets = [ "127.0.0.1:${toString cfg.phpFpmPrometheusExporter.port}" ];
|
||||||
labels = {
|
labels = {
|
||||||
"hostname" = config.networking.hostName;
|
"hostname" = config.networking.hostName;
|
||||||
"domain" = cfg.domain;
|
"domain" = cfg.domain;
|
||||||
};
|
};
|
||||||
}];
|
}
|
||||||
|
];
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
})
|
})
|
||||||
|
|
@ -964,7 +990,8 @@ in
|
||||||
systemd.services.nextcloud-setup.script = ''
|
systemd.services.nextcloud-setup.script = ''
|
||||||
${occ} app:install files_external || :
|
${occ} app:install files_external || :
|
||||||
${occ} app:enable files_external
|
${occ} app:enable files_external
|
||||||
'' + lib.optionalString (cfg.apps.externalStorage.userLocalMount != null) (
|
''
|
||||||
|
+ lib.optionalString (cfg.apps.externalStorage.userLocalMount != null) (
|
||||||
let
|
let
|
||||||
cfg' = cfg.apps.externalStorage.userLocalMount;
|
cfg' = cfg.apps.externalStorage.userLocalMount;
|
||||||
jq = "${pkgs.jq}/bin/jq";
|
jq = "${pkgs.jq}/bin/jq";
|
||||||
|
|
@ -979,7 +1006,8 @@ in
|
||||||
null::null \
|
null::null \
|
||||||
--config datadir='${cfg'.directory}'
|
--config datadir='${cfg'.directory}'
|
||||||
fi
|
fi
|
||||||
'');
|
''
|
||||||
|
);
|
||||||
})
|
})
|
||||||
|
|
||||||
(lib.mkIf (cfg.enable && cfg.apps.ldap.enable) {
|
(lib.mkIf (cfg.enable && cfg.apps.ldap.enable) {
|
||||||
|
|
@ -988,7 +1016,8 @@ in
|
||||||
let
|
let
|
||||||
cfg' = cfg.apps.ldap;
|
cfg' = cfg.apps.ldap;
|
||||||
cID = "s" + toString cfg'.configID;
|
cID = "s" + toString cfg'.configID;
|
||||||
in ''
|
in
|
||||||
|
''
|
||||||
${occ} app:install user_ldap || :
|
${occ} app:install user_ldap || :
|
||||||
${occ} app:enable user_ldap
|
${occ} app:enable user_ldap
|
||||||
|
|
||||||
|
|
@ -1055,7 +1084,8 @@ in
|
||||||
'';
|
'';
|
||||||
})
|
})
|
||||||
|
|
||||||
(let
|
(
|
||||||
|
let
|
||||||
scopes = [
|
scopes = [
|
||||||
"openid"
|
"openid"
|
||||||
"profile"
|
"profile"
|
||||||
|
|
@ -1063,7 +1093,8 @@ in
|
||||||
"groups"
|
"groups"
|
||||||
"nextcloud_userinfo"
|
"nextcloud_userinfo"
|
||||||
];
|
];
|
||||||
in lib.mkIf (cfg.enable && cfg.apps.sso.enable) {
|
in
|
||||||
|
lib.mkIf (cfg.enable && cfg.apps.sso.enable) {
|
||||||
assertions = [
|
assertions = [
|
||||||
{
|
{
|
||||||
assertion = cfg.ssl != null;
|
assertion = cfg.ssl != null;
|
||||||
|
|
@ -1080,8 +1111,7 @@ in
|
||||||
before = [ "nextcloud-setup.service" ];
|
before = [ "nextcloud-setup.service" ];
|
||||||
serviceConfig.Type = "oneshot";
|
serviceConfig.Type = "oneshot";
|
||||||
serviceConfig.User = "nextcloud";
|
serviceConfig.User = "nextcloud";
|
||||||
script =
|
script = ''
|
||||||
''
|
|
||||||
mkdir -p ${cfg.dataDir}/config
|
mkdir -p ${cfg.dataDir}/config
|
||||||
cat <<EOF > "${cfg.dataDir}/config/secretFile"
|
cat <<EOF > "${cfg.dataDir}/config/secretFile"
|
||||||
{
|
{
|
||||||
|
|
@ -1157,7 +1187,8 @@ in
|
||||||
};
|
};
|
||||||
|
|
||||||
shb.authelia.extraDefinitions = {
|
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" = {
|
shb.authelia.extraOidcClaimsPolicies."nextcloud_userinfo" = {
|
||||||
custom_claims = {
|
custom_claims = {
|
||||||
|
|
@ -1187,11 +1218,11 @@ in
|
||||||
token_endpoint_auth_method = "client_secret_basic";
|
token_endpoint_auth_method = "client_secret_basic";
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
|
||||||
(lib.mkIf (cfg.enable && cfg.autoDisableMaintenanceModeOnStart) {
|
(lib.mkIf (cfg.enable && cfg.autoDisableMaintenanceModeOnStart) {
|
||||||
systemd.services.nextcloud-setup.preStart =
|
systemd.services.nextcloud-setup.preStart = lib.mkBefore ''
|
||||||
lib.mkBefore ''
|
|
||||||
if [[ -e /var/lib/nextcloud/config/config.php ]]; then
|
if [[ -e /var/lib/nextcloud/config/config.php ]]; then
|
||||||
${occ} maintenance:mode --no-interaction --quiet --off
|
${occ} maintenance:mode --no-interaction --quiet --off
|
||||||
fi
|
fi
|
||||||
|
|
@ -1199,8 +1230,7 @@ in
|
||||||
})
|
})
|
||||||
|
|
||||||
(lib.mkIf (cfg.enable && cfg.alwaysApplyExpensiveMigrations) {
|
(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
|
if [[ -e /var/lib/nextcloud/config/config.php ]]; then
|
||||||
${occ} maintenance:repair --include-expensive
|
${occ} maintenance:repair --include-expensive
|
||||||
fi
|
fi
|
||||||
|
|
@ -1209,17 +1239,19 @@ in
|
||||||
|
|
||||||
# Great source of inspiration:
|
# Great source of inspiration:
|
||||||
# https://github.com/Shawn8901/nix-configuration/blob/538c18d9ecbf7c7e649b1540c0d40881bada6690/modules/nixos/private/nextcloud/memories.nix#L226
|
# https://github.com/Shawn8901/nix-configuration/blob/538c18d9ecbf7c7e649b1540c0d40881bada6690/modules/nixos/private/nextcloud/memories.nix#L226
|
||||||
(lib.mkIf cfg.apps.memories.enable
|
(lib.mkIf cfg.apps.memories.enable (
|
||||||
(let
|
let
|
||||||
cfg' = cfg.apps.memories;
|
cfg' = cfg.apps.memories;
|
||||||
|
|
||||||
exiftool = pkgs.exiftool.overrideAttrs (f: p: {
|
exiftool = pkgs.exiftool.overrideAttrs (
|
||||||
|
f: p: {
|
||||||
version = "12.70";
|
version = "12.70";
|
||||||
src = pkgs.fetchurl {
|
src = pkgs.fetchurl {
|
||||||
url = "https://exiftool.org/Image-ExifTool-12.70.tar.gz";
|
url = "https://exiftool.org/Image-ExifTool-12.70.tar.gz";
|
||||||
hash = "sha256-TLJSJEXMPj870TkExq6uraX8Wl4kmNerrSlX3LQsr/4=";
|
hash = "sha256-TLJSJEXMPj870TkExq6uraX8Wl4kmNerrSlX3LQsr/4=";
|
||||||
};
|
};
|
||||||
});
|
}
|
||||||
|
);
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
assertions = [
|
assertions = [
|
||||||
|
|
@ -1265,10 +1297,11 @@ in
|
||||||
DeviceAllow = [ "/dev/dri/renderD128 rwm" ];
|
DeviceAllow = [ "/dev/dri/renderD128 rwm" ];
|
||||||
PrivateDevices = lib.mkForce false;
|
PrivateDevices = lib.mkForce false;
|
||||||
};
|
};
|
||||||
}))
|
}
|
||||||
|
))
|
||||||
|
|
||||||
(lib.mkIf cfg.apps.recognize.enable
|
(lib.mkIf cfg.apps.recognize.enable (
|
||||||
(let
|
let
|
||||||
cfg' = cfg.apps.recognize;
|
cfg' = cfg.apps.recognize;
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
|
|
@ -1276,8 +1309,7 @@ in
|
||||||
inherit (nextcloudApps) recognize;
|
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 nice_binary --value ${pkgs.coreutils}/bin/nice
|
||||||
${occ} config:app:set recognize node_binary --value ${pkgs.nodejs}/bin/node
|
${occ} config:app:set recognize node_binary --value ${pkgs.nodejs}/bin/node
|
||||||
${occ} config:app:set recognize faces.enabled --value true
|
${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.enabled --value true
|
||||||
${occ} config:app:set recognize musicnn.batchSize --value 100
|
${occ} config:app:set recognize musicnn.batchSize --value 100
|
||||||
'';
|
'';
|
||||||
}))
|
}
|
||||||
|
))
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,22 @@
|
||||||
{ config, lib, pkgs, ... }:
|
{
|
||||||
|
config,
|
||||||
|
lib,
|
||||||
|
pkgs,
|
||||||
|
...
|
||||||
|
}:
|
||||||
let
|
let
|
||||||
cfg = config.shb.open-webui;
|
cfg = config.shb.open-webui;
|
||||||
|
|
||||||
contracts = pkgs.callPackage ../contracts { };
|
contracts = pkgs.callPackage ../contracts { };
|
||||||
|
|
||||||
roleClaim = "openwebui_groups";
|
roleClaim = "openwebui_groups";
|
||||||
oauthScopes = [ "openid" "email" "profile" "groups" "${roleClaim}" ];
|
oauthScopes = [
|
||||||
|
"openid"
|
||||||
|
"email"
|
||||||
|
"profile"
|
||||||
|
"groups"
|
||||||
|
"${roleClaim}"
|
||||||
|
];
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
imports = [
|
imports = [
|
||||||
|
|
@ -103,7 +114,10 @@ in
|
||||||
};
|
};
|
||||||
|
|
||||||
authorization_policy = lib.mkOption {
|
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.";
|
description = "Require one factor (password) or two factor (device) authentication.";
|
||||||
default = "one_factor";
|
default = "one_factor";
|
||||||
};
|
};
|
||||||
|
|
@ -149,7 +163,8 @@ in
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
config = (lib.mkMerge [
|
config = (
|
||||||
|
lib.mkMerge [
|
||||||
(lib.mkIf cfg.enable {
|
(lib.mkIf cfg.enable {
|
||||||
users.users.open-webui = {
|
users.users.open-webui = {
|
||||||
isSystemUser = true;
|
isSystemUser = true;
|
||||||
|
|
@ -173,7 +188,8 @@ in
|
||||||
SCARF_NO_ANALYTICS = "True";
|
SCARF_NO_ANALYTICS = "True";
|
||||||
|
|
||||||
ENABLE_VERSION_UPDATE_CHECK = "False";
|
ENABLE_VERSION_UPDATE_CHECK = "False";
|
||||||
} // cfg.environment;
|
}
|
||||||
|
// cfg.environment;
|
||||||
};
|
};
|
||||||
|
|
||||||
systemd.services.open-webui.path = [
|
systemd.services.open-webui.path = [
|
||||||
|
|
@ -268,5 +284,6 @@ in
|
||||||
requiredBy = [ "open-webui.service" ];
|
requiredBy = [ "open-webui.service" ];
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
]);
|
]
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,9 @@
|
||||||
{ config, lib, pkgs, ... }:
|
{
|
||||||
|
config,
|
||||||
|
lib,
|
||||||
|
pkgs,
|
||||||
|
...
|
||||||
|
}:
|
||||||
let
|
let
|
||||||
cfg = config.shb.pinchflat;
|
cfg = config.shb.pinchflat;
|
||||||
|
|
||||||
|
|
@ -57,7 +62,10 @@ in
|
||||||
};
|
};
|
||||||
|
|
||||||
timeZone = lib.mkOption {
|
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.";
|
description = "Timezone of this instance.";
|
||||||
example = "America/Los_Angeles";
|
example = "America/Los_Angeles";
|
||||||
};
|
};
|
||||||
|
|
@ -99,7 +107,10 @@ in
|
||||||
};
|
};
|
||||||
|
|
||||||
authorization_policy = lib.mkOption {
|
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.";
|
description = "Require one factor (password) or two factor (device) authentication.";
|
||||||
default = "one_factor";
|
default = "one_factor";
|
||||||
};
|
};
|
||||||
|
|
@ -142,7 +153,9 @@ in
|
||||||
|
|
||||||
# This should be using a contract instead of setting the option directly.
|
# This should be using a contract instead of setting the option directly.
|
||||||
shb.lldap = lib.mkIf config.shb.lldap.enable {
|
shb.lldap = lib.mkIf config.shb.lldap.enable {
|
||||||
ensureGroups = { ${cfg.ldap.userGroup} = {}; };
|
ensureGroups = {
|
||||||
|
${cfg.ldap.userGroup} = { };
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
systemd.services.pinchflat-pre = {
|
systemd.services.pinchflat-pre = {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,9 @@
|
||||||
{ config, pkgs, lib, ... }:
|
{
|
||||||
|
config,
|
||||||
|
pkgs,
|
||||||
|
lib,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
|
||||||
let
|
let
|
||||||
cfg = config.shb.vaultwarden;
|
cfg = config.shb.vaultwarden;
|
||||||
|
|
@ -7,7 +12,11 @@ let
|
||||||
|
|
||||||
fqdn = "${cfg.subdomain}.${cfg.domain}";
|
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
|
in
|
||||||
{
|
{
|
||||||
imports = [
|
imports = [
|
||||||
|
|
@ -55,7 +64,10 @@ in
|
||||||
mode = "0440";
|
mode = "0440";
|
||||||
owner = "vaultwarden";
|
owner = "vaultwarden";
|
||||||
group = "postgres";
|
group = "postgres";
|
||||||
restartUnits = [ "vaultwarden.service" "postgresql.service" ];
|
restartUnits = [
|
||||||
|
"vaultwarden.service"
|
||||||
|
"postgresql.service"
|
||||||
|
];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
@ -63,7 +75,8 @@ in
|
||||||
smtp = lib.mkOption {
|
smtp = lib.mkOption {
|
||||||
description = "SMTP options.";
|
description = "SMTP options.";
|
||||||
default = null;
|
default = null;
|
||||||
type = lib.types.nullOr (lib.types.submodule {
|
type = lib.types.nullOr (
|
||||||
|
lib.types.submodule {
|
||||||
options = {
|
options = {
|
||||||
from_address = lib.mkOption {
|
from_address = lib.mkOption {
|
||||||
type = lib.types.str;
|
type = lib.types.str;
|
||||||
|
|
@ -80,7 +93,11 @@ in
|
||||||
description = "SMTP host to send the emails to.";
|
description = "SMTP host to send the emails to.";
|
||||||
};
|
};
|
||||||
security = lib.mkOption {
|
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.";
|
description = "Security expected by SMTP host.";
|
||||||
default = "starttls";
|
default = "starttls";
|
||||||
};
|
};
|
||||||
|
|
@ -109,7 +126,8 @@ in
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
});
|
}
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
mount = lib.mkOption {
|
mount = lib.mkOption {
|
||||||
|
|
@ -127,7 +145,9 @@ in
|
||||||
```
|
```
|
||||||
'';
|
'';
|
||||||
readOnly = true;
|
readOnly = true;
|
||||||
default = { path = dataFolder; };
|
default = {
|
||||||
|
path = dataFolder;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
backup = lib.mkOption {
|
backup = lib.mkOption {
|
||||||
|
|
@ -170,7 +190,8 @@ in
|
||||||
ROCKET_LOG = if cfg.debug then "trace" else "info";
|
ROCKET_LOG = if cfg.debug then "trace" else "info";
|
||||||
ROCKET_ADDRESS = "127.0.0.1";
|
ROCKET_ADDRESS = "127.0.0.1";
|
||||||
ROCKET_PORT = cfg.port;
|
ROCKET_PORT = cfg.port;
|
||||||
} // lib.optionalAttrs (cfg.smtp != null) {
|
}
|
||||||
|
// lib.optionalAttrs (cfg.smtp != null) {
|
||||||
SMTP_FROM = cfg.smtp.from_address;
|
SMTP_FROM = cfg.smtp.from_address;
|
||||||
SMTP_FROM_NAME = cfg.smtp.from_name;
|
SMTP_FROM_NAME = cfg.smtp.from_name;
|
||||||
SMTP_HOST = cfg.smtp.host;
|
SMTP_HOST = cfg.smtp.host;
|
||||||
|
|
@ -189,12 +210,12 @@ in
|
||||||
];
|
];
|
||||||
# Needed to be able to write template config.
|
# Needed to be able to write template config.
|
||||||
systemd.services.vaultwarden.serviceConfig.ProtectHome = lib.mkForce false;
|
systemd.services.vaultwarden.serviceConfig.ProtectHome = lib.mkForce false;
|
||||||
systemd.services.vaultwarden.preStart =
|
systemd.services.vaultwarden.preStart = lib.shb.replaceSecrets {
|
||||||
lib.shb.replaceSecrets {
|
|
||||||
userConfig = {
|
userConfig = {
|
||||||
DATABASE_URL.source = cfg.databasePassword.result.path;
|
DATABASE_URL.source = cfg.databasePassword.result.path;
|
||||||
DATABASE_URL.transform = v: "postgresql://vaultwarden:${v}@127.0.0.1:5432/vaultwarden";
|
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;
|
SMTP_PASSWORD.source = cfg.smtp.password.result.path;
|
||||||
};
|
};
|
||||||
resultPath = "${dataFolder}/vaultwarden.env";
|
resultPath = "${dataFolder}/vaultwarden.env";
|
||||||
|
|
@ -203,7 +224,12 @@ in
|
||||||
|
|
||||||
shb.nginx.vhosts = [
|
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}";
|
upstream = "http://127.0.0.1:${toString config.services.vaultwarden.config.ROCKET_PORT}";
|
||||||
autheliaRules = lib.mkIf (cfg.authEndpoint != null) [
|
autheliaRules = lib.mkIf (cfg.authEndpoint != null) [
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,9 @@ in
|
||||||
basic = lib.shb.runNixOSTest {
|
basic = lib.shb.runNixOSTest {
|
||||||
name = "authelia-basic";
|
name = "authelia-basic";
|
||||||
|
|
||||||
nodes.machine = { config, pkgs, ... }: {
|
nodes.machine =
|
||||||
|
{ config, pkgs, ... }:
|
||||||
|
{
|
||||||
imports = [
|
imports = [
|
||||||
(pkgs'.path + "/nixos/modules/profiles/headless.nix")
|
(pkgs'.path + "/nixos/modules/profiles/headless.nix")
|
||||||
(pkgs'.path + "/nixos/modules/profiles/qemu-guest.nix")
|
(pkgs'.path + "/nixos/modules/profiles/qemu-guest.nix")
|
||||||
|
|
@ -56,8 +58,10 @@ in
|
||||||
ldapAdminPassword.result = config.shb.hardcodedsecret.ldapAdminPassword.result;
|
ldapAdminPassword.result = config.shb.hardcodedsecret.ldapAdminPassword.result;
|
||||||
sessionSecret.result = config.shb.hardcodedsecret.sessionSecret.result;
|
sessionSecret.result = config.shb.hardcodedsecret.sessionSecret.result;
|
||||||
storageEncryptionKey.result = config.shb.hardcodedsecret.storageEncryptionKey.result;
|
storageEncryptionKey.result = config.shb.hardcodedsecret.storageEncryptionKey.result;
|
||||||
identityProvidersOIDCHMACSecret.result = config.shb.hardcodedsecret.identityProvidersOIDCHMACSecret.result;
|
identityProvidersOIDCHMACSecret.result =
|
||||||
identityProvidersOIDCIssuerPrivateKey.result = config.shb.hardcodedsecret.identityProvidersOIDCIssuerPrivateKey.result;
|
config.shb.hardcodedsecret.identityProvidersOIDCHMACSecret.result;
|
||||||
|
identityProvidersOIDCIssuerPrivateKey.result =
|
||||||
|
config.shb.hardcodedsecret.identityProvidersOIDCIssuerPrivateKey.result;
|
||||||
};
|
};
|
||||||
|
|
||||||
oidcClients = [
|
oidcClients = [
|
||||||
|
|
@ -102,10 +106,12 @@ in
|
||||||
};
|
};
|
||||||
shb.hardcodedsecret.identityProvidersOIDCIssuerPrivateKey = {
|
shb.hardcodedsecret.identityProvidersOIDCIssuerPrivateKey = {
|
||||||
request = config.shb.authelia.secrets.identityProvidersOIDCIssuerPrivateKey.request;
|
request = config.shb.authelia.secrets.identityProvidersOIDCIssuerPrivateKey.request;
|
||||||
settings.source = (pkgs.runCommand "gen-private-key" {} ''
|
settings.source =
|
||||||
|
(pkgs.runCommand "gen-private-key" { } ''
|
||||||
mkdir $out
|
mkdir $out
|
||||||
${pkgs.openssl}/bin/openssl genrsa -out $out/private.pem 4096
|
${pkgs.openssl}/bin/openssl genrsa -out $out/private.pem 4096
|
||||||
'') + "/private.pem";
|
'')
|
||||||
|
+ "/private.pem";
|
||||||
};
|
};
|
||||||
|
|
||||||
specialisation = {
|
specialisation = {
|
||||||
|
|
@ -115,7 +121,8 @@ in
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
testScript = { nodes, ... }:
|
testScript =
|
||||||
|
{ nodes, ... }:
|
||||||
let
|
let
|
||||||
specializations = "${nodes.machine.system.build.toplevel}/specialisation";
|
specializations = "${nodes.machine.system.build.toplevel}/specialisation";
|
||||||
in
|
in
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,9 @@ in
|
||||||
|
|
||||||
configWithTemplates = lib.shb.withReplacements userConfig;
|
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;
|
replacements = lib.shb.getReplacements userConfig;
|
||||||
|
|
||||||
|
|
@ -54,7 +56,8 @@ in
|
||||||
in
|
in
|
||||||
lib.shb.runNixOSTest {
|
lib.shb.runNixOSTest {
|
||||||
name = "lib-template";
|
name = "lib-template";
|
||||||
nodes.machine = { config, pkgs, ... }:
|
nodes.machine =
|
||||||
|
{ config, pkgs, ... }:
|
||||||
{
|
{
|
||||||
imports = [
|
imports = [
|
||||||
(pkgs'.path + "/nixos/modules/profiles/headless.nix")
|
(pkgs'.path + "/nixos/modules/profiles/headless.nix")
|
||||||
|
|
@ -62,7 +65,12 @@ in
|
||||||
{
|
{
|
||||||
options = {
|
options = {
|
||||||
libtest.config = lib.mkOption {
|
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
|
import json
|
||||||
from collections import ChainMap
|
from collections import ChainMap
|
||||||
from xml.etree import ElementTree
|
from xml.etree import ElementTree
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,9 @@ in
|
||||||
auth = lib.shb.runNixOSTest {
|
auth = lib.shb.runNixOSTest {
|
||||||
name = "ldap-auth";
|
name = "ldap-auth";
|
||||||
|
|
||||||
nodes.server = { config, pkgs, ... }: {
|
nodes.server =
|
||||||
|
{ config, pkgs, ... }:
|
||||||
|
{
|
||||||
imports = [
|
imports = [
|
||||||
(pkgs'.path + "/nixos/modules/profiles/headless.nix")
|
(pkgs'.path + "/nixos/modules/profiles/headless.nix")
|
||||||
(pkgs'.path + "/nixos/modules/profiles/qemu-guest.nix")
|
(pkgs'.path + "/nixos/modules/profiles/qemu-guest.nix")
|
||||||
|
|
@ -68,7 +70,8 @@ in
|
||||||
nodes.client = { };
|
nodes.client = { };
|
||||||
|
|
||||||
# Inspired from https://github.com/lldap/lldap/blob/33f50d13a2e2d24a3e6bb05a148246bc98090df0/example_configs/lldap-ha-auth.sh
|
# Inspired from https://github.com/lldap/lldap/blob/33f50d13a2e2d24a3e6bb05a148246bc98090df0/example_configs/lldap-ha-auth.sh
|
||||||
testScript = { nodes, ... }:
|
testScript =
|
||||||
|
{ nodes, ... }:
|
||||||
let
|
let
|
||||||
specializations = "${nodes.server.system.build.toplevel}/specialisation";
|
specializations = "${nodes.server.system.build.toplevel}/specialisation";
|
||||||
in
|
in
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,17 @@
|
||||||
{ pkgs, lib, ... }:
|
{ pkgs, lib, ... }:
|
||||||
let
|
let
|
||||||
serve = port: text: lib.getExe (pkgs.writers.writePython3Bin "serve"
|
serve =
|
||||||
|
port: text:
|
||||||
|
lib.getExe (
|
||||||
|
pkgs.writers.writePython3Bin "serve"
|
||||||
{
|
{
|
||||||
libraries = [ pkgs.python3Packages.systemd ];
|
libraries = [ pkgs.python3Packages.systemd ];
|
||||||
}
|
}
|
||||||
(let
|
(
|
||||||
|
let
|
||||||
content = pkgs.writeText "content" text;
|
content = pkgs.writeText "content" text;
|
||||||
in ''
|
in
|
||||||
|
''
|
||||||
from http.server import BaseHTTPRequestHandler, HTTPServer
|
from http.server import BaseHTTPRequestHandler, HTTPServer
|
||||||
from systemd.daemon import notify
|
from systemd.daemon import notify
|
||||||
|
|
||||||
|
|
@ -35,14 +40,17 @@ let
|
||||||
print("Serving hardcoded page on http://127.0.0.1:${toString port}")
|
print("Serving hardcoded page on http://127.0.0.1:${toString port}")
|
||||||
notify('READY=1')
|
notify('READY=1')
|
||||||
httpd.serve_forever()
|
httpd.serve_forever()
|
||||||
'')
|
''
|
||||||
|
)
|
||||||
);
|
);
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
default = lib.shb.runNixOSTest {
|
default = lib.shb.runNixOSTest {
|
||||||
name = "mitmdump-default";
|
name = "mitmdump-default";
|
||||||
|
|
||||||
nodes.machine = { config, pkgs, ... }: {
|
nodes.machine =
|
||||||
|
{ config, pkgs, ... }:
|
||||||
|
{
|
||||||
imports = [
|
imports = [
|
||||||
../../modules/blocks/mitmdump.nix
|
../../modules/blocks/mitmdump.nix
|
||||||
];
|
];
|
||||||
|
|
@ -79,12 +87,15 @@ in
|
||||||
after = [ "test2.service" ];
|
after = [ "test2.service" ];
|
||||||
enabledAddons = [ config.shb.mitmdump.addons.logger ];
|
enabledAddons = [ config.shb.mitmdump.addons.logger ];
|
||||||
extraArgs = [
|
extraArgs = [
|
||||||
"--set" "verbose_pattern=/verbose"
|
"--set"
|
||||||
|
"verbose_pattern=/verbose"
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
testScript = { nodes, ... }: ''
|
testScript =
|
||||||
|
{ nodes, ... }:
|
||||||
|
''
|
||||||
start_all()
|
start_all()
|
||||||
|
|
||||||
machine.wait_for_unit("test1.service")
|
machine.wait_for_unit("test1.service")
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,9 @@ in
|
||||||
peerWithoutUser = lib.shb.runNixOSTest {
|
peerWithoutUser = lib.shb.runNixOSTest {
|
||||||
name = "postgresql-peerWithoutUser";
|
name = "postgresql-peerWithoutUser";
|
||||||
|
|
||||||
nodes.machine = { config, pkgs, ... }: {
|
nodes.machine =
|
||||||
|
{ config, pkgs, ... }:
|
||||||
|
{
|
||||||
imports = [
|
imports = [
|
||||||
(pkgs'.path + "/nixos/modules/profiles/headless.nix")
|
(pkgs'.path + "/nixos/modules/profiles/headless.nix")
|
||||||
(pkgs'.path + "/nixos/modules/profiles/qemu-guest.nix")
|
(pkgs'.path + "/nixos/modules/profiles/qemu-guest.nix")
|
||||||
|
|
@ -21,7 +23,9 @@ in
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
testScript = { nodes, ... }: ''
|
testScript =
|
||||||
|
{ nodes, ... }:
|
||||||
|
''
|
||||||
start_all()
|
start_all()
|
||||||
machine.wait_for_unit("postgresql.service")
|
machine.wait_for_unit("postgresql.service")
|
||||||
machine.wait_for_open_port(5432)
|
machine.wait_for_open_port(5432)
|
||||||
|
|
@ -43,7 +47,9 @@ in
|
||||||
peerAuth = lib.shb.runNixOSTest {
|
peerAuth = lib.shb.runNixOSTest {
|
||||||
name = "postgresql-peerAuth";
|
name = "postgresql-peerAuth";
|
||||||
|
|
||||||
nodes.machine = { config, pkgs, ... }: {
|
nodes.machine =
|
||||||
|
{ config, pkgs, ... }:
|
||||||
|
{
|
||||||
imports = [
|
imports = [
|
||||||
(pkgs'.path + "/nixos/modules/profiles/headless.nix")
|
(pkgs'.path + "/nixos/modules/profiles/headless.nix")
|
||||||
(pkgs'.path + "/nixos/modules/profiles/qemu-guest.nix")
|
(pkgs'.path + "/nixos/modules/profiles/qemu-guest.nix")
|
||||||
|
|
@ -65,7 +71,9 @@ in
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
testScript = { nodes, ... }: ''
|
testScript =
|
||||||
|
{ nodes, ... }:
|
||||||
|
''
|
||||||
start_all()
|
start_all()
|
||||||
machine.wait_for_unit("postgresql.service")
|
machine.wait_for_unit("postgresql.service")
|
||||||
machine.wait_for_open_port(5432)
|
machine.wait_for_open_port(5432)
|
||||||
|
|
@ -93,7 +101,9 @@ in
|
||||||
tcpIPWithoutPasswordAuth = lib.shb.runNixOSTest {
|
tcpIPWithoutPasswordAuth = lib.shb.runNixOSTest {
|
||||||
name = "postgresql-tcpIpWithoutPasswordAuth";
|
name = "postgresql-tcpIpWithoutPasswordAuth";
|
||||||
|
|
||||||
nodes.machine = { config, pkgs, ... }: {
|
nodes.machine =
|
||||||
|
{ config, pkgs, ... }:
|
||||||
|
{
|
||||||
imports = [
|
imports = [
|
||||||
(pkgs'.path + "/nixos/modules/profiles/headless.nix")
|
(pkgs'.path + "/nixos/modules/profiles/headless.nix")
|
||||||
(pkgs'.path + "/nixos/modules/profiles/qemu-guest.nix")
|
(pkgs'.path + "/nixos/modules/profiles/qemu-guest.nix")
|
||||||
|
|
@ -109,7 +119,9 @@ in
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
testScript = { nodes, ... }: ''
|
testScript =
|
||||||
|
{ nodes, ... }:
|
||||||
|
''
|
||||||
start_all()
|
start_all()
|
||||||
machine.wait_for_unit("postgresql.service")
|
machine.wait_for_unit("postgresql.service")
|
||||||
machine.wait_for_open_port(5432)
|
machine.wait_for_open_port(5432)
|
||||||
|
|
@ -131,7 +143,9 @@ in
|
||||||
tcpIPPasswordAuth = lib.shb.runNixOSTest {
|
tcpIPPasswordAuth = lib.shb.runNixOSTest {
|
||||||
name = "postgresql-tcpIPPasswordAuth";
|
name = "postgresql-tcpIPPasswordAuth";
|
||||||
|
|
||||||
nodes.machine = { config, pkgs, ... }: {
|
nodes.machine =
|
||||||
|
{ config, pkgs, ... }:
|
||||||
|
{
|
||||||
imports = [
|
imports = [
|
||||||
(pkgs'.path + "/nixos/modules/profiles/headless.nix")
|
(pkgs'.path + "/nixos/modules/profiles/headless.nix")
|
||||||
(pkgs'.path + "/nixos/modules/profiles/qemu-guest.nix")
|
(pkgs'.path + "/nixos/modules/profiles/qemu-guest.nix")
|
||||||
|
|
@ -158,7 +172,9 @@ in
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
testScript = { nodes, ... }: ''
|
testScript =
|
||||||
|
{ nodes, ... }:
|
||||||
|
''
|
||||||
start_all()
|
start_all()
|
||||||
machine.wait_for_unit("postgresql.service")
|
machine.wait_for_unit("postgresql.service")
|
||||||
machine.wait_for_open_port(5432)
|
machine.wait_for_open_port(5432)
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,14 @@
|
||||||
let
|
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}";
|
name = "restic_backupAndRestore_${user}";
|
||||||
|
|
||||||
nodes.machine = { config, ... }: {
|
nodes.machine =
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
imports = [
|
imports = [
|
||||||
testLib.baseImports
|
testLib.baseImports
|
||||||
|
|
||||||
|
|
@ -64,7 +68,8 @@ let
|
||||||
"/opt/files/B"
|
"/opt/files/B"
|
||||||
];
|
];
|
||||||
|
|
||||||
hooks.beforeBackup = [''
|
hooks.beforeBackup = [
|
||||||
|
''
|
||||||
echo $RUNTIME_DIRECTORY
|
echo $RUNTIME_DIRECTORY
|
||||||
if [ "$RUNTIME_DIRECTORY" = /run/restic-backups-testinstance_opt_repos_A ]; then
|
if [ "$RUNTIME_DIRECTORY" = /run/restic-backups-testinstance_opt_repos_A ]; then
|
||||||
if ! [ -f /run/secrets_restic/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
|
exit 12
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
''];
|
''
|
||||||
|
];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
@ -87,11 +93,14 @@ let
|
||||||
extraPythonPackages = p: [ p.dictdiffer ];
|
extraPythonPackages = p: [ p.dictdiffer ];
|
||||||
skipTypeCheck = true;
|
skipTypeCheck = true;
|
||||||
|
|
||||||
testScript = { nodes, ... }: let
|
testScript =
|
||||||
|
{ nodes, ... }:
|
||||||
|
let
|
||||||
provider = nodes.machine.shb.restic.instances."testinstance";
|
provider = nodes.machine.shb.restic.instances."testinstance";
|
||||||
backupService = provider.result.backupService;
|
backupService = provider.result.backupService;
|
||||||
restoreScript = provider.result.restoreScript;
|
restoreScript = provider.result.restoreScript;
|
||||||
in ''
|
in
|
||||||
|
''
|
||||||
from dictdiffer import diff
|
from dictdiffer import diff
|
||||||
|
|
||||||
def list_files(dir):
|
def list_files(dir):
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,9 @@ in
|
||||||
test = lib.shb.runNixOSTest {
|
test = lib.shb.runNixOSTest {
|
||||||
name = "ssl-test";
|
name = "ssl-test";
|
||||||
|
|
||||||
nodes.server = { config, pkgs, ... }: {
|
nodes.server =
|
||||||
|
{ config, pkgs, ... }:
|
||||||
|
{
|
||||||
imports = [
|
imports = [
|
||||||
(pkgs'.path + "/nixos/modules/profiles/headless.nix")
|
(pkgs'.path + "/nixos/modules/profiles/headless.nix")
|
||||||
(pkgs'.path + "/nixos/modules/profiles/qemu-guest.nix")
|
(pkgs'.path + "/nixos/modules/profiles/qemu-guest.nix")
|
||||||
|
|
@ -54,7 +56,10 @@ in
|
||||||
ca = config.shb.certs.cas.selfsigned.myca;
|
ca = config.shb.certs.cas.selfsigned.myca;
|
||||||
|
|
||||||
domain = "multi1.example.com";
|
domain = "multi1.example.com";
|
||||||
extraDomains = [ "multi2.example.com" "multi3.example.com" ];
|
extraDomains = [
|
||||||
|
"multi2.example.com"
|
||||||
|
"multi3.example.com"
|
||||||
|
];
|
||||||
group = "nginx";
|
group = "nginx";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -121,7 +126,8 @@ in
|
||||||
};
|
};
|
||||||
|
|
||||||
# Taken from https://github.com/NixOS/nixpkgs/blob/7f311dd9226bbd568a43632c977f4992cfb2b5c8/nixos/tests/custom-ca.nix
|
# Taken from https://github.com/NixOS/nixpkgs/blob/7f311dd9226bbd568a43632c977f4992cfb2b5c8/nixos/tests/custom-ca.nix
|
||||||
testScript = { nodes, ... }:
|
testScript =
|
||||||
|
{ nodes, ... }:
|
||||||
let
|
let
|
||||||
myca = nodes.server.shb.certs.cas.selfsigned.myca;
|
myca = nodes.server.shb.certs.cas.selfsigned.myca;
|
||||||
myotherca = nodes.server.shb.certs.cas.selfsigned.myotherca;
|
myotherca = nodes.server.shb.certs.cas.selfsigned.myotherca;
|
||||||
|
|
|
||||||
167
test/common.nix
167
test/common.nix
|
|
@ -1,7 +1,14 @@
|
||||||
{ pkgs, lib }:
|
{ pkgs, lib }:
|
||||||
let
|
let
|
||||||
inherit (lib) hasAttr mkOption optionalString;
|
inherit (lib) hasAttr mkOption optionalString;
|
||||||
inherit (lib.types) bool enum listOf nullOr submodule str;
|
inherit (lib.types)
|
||||||
|
bool
|
||||||
|
enum
|
||||||
|
listOf
|
||||||
|
nullOr
|
||||||
|
submodule
|
||||||
|
str
|
||||||
|
;
|
||||||
|
|
||||||
baseImports = {
|
baseImports = {
|
||||||
imports = [
|
imports = [
|
||||||
|
|
@ -10,15 +17,17 @@ let
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
accessScript = lib.makeOverridable ({
|
accessScript = lib.makeOverridable (
|
||||||
hasSSL
|
{
|
||||||
, waitForServices ? s: []
|
hasSSL,
|
||||||
, waitForPorts ? p: []
|
waitForServices ? s: [ ],
|
||||||
, waitForUnixSocket ? u: []
|
waitForPorts ? p: [ ],
|
||||||
, waitForUrls ? u: []
|
waitForUnixSocket ? u: [ ],
|
||||||
, extraScript ? {...}: ""
|
waitForUrls ? u: [ ],
|
||||||
, redirectSSO ? false
|
extraScript ? { ... }: "",
|
||||||
}: { nodes, ... }:
|
redirectSSO ? false,
|
||||||
|
}:
|
||||||
|
{ nodes, ... }:
|
||||||
let
|
let
|
||||||
cfg = nodes.server.test;
|
cfg = nodes.server.test;
|
||||||
|
|
||||||
|
|
@ -72,9 +81,13 @@ let
|
||||||
+ lib.strings.concatMapStrings (p: ''server.wait_for_open_port(${toString p})'' + "\n") (
|
+ lib.strings.concatMapStrings (p: ''server.wait_for_open_port(${toString p})'' + "\n") (
|
||||||
waitForPorts args
|
waitForPorts args
|
||||||
# TODO: when the SSO block exists, replace this hardcoded port.
|
# 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"}:
|
if ${if hasSSL args then "True" else "False"}:
|
||||||
server.copy_from_vm("/etc/ssl/certs/ca-certificates.crt")
|
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,
|
# 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".
|
# otherwise curl will not be able to verify the "legitimacy of the server".
|
||||||
+ lib.strings.concatMapStrings (u: ''
|
+ lib.strings.concatMapStrings (
|
||||||
|
u:
|
||||||
|
''
|
||||||
import time
|
import time
|
||||||
|
|
||||||
done = False
|
done = False
|
||||||
|
|
@ -97,16 +112,20 @@ let
|
||||||
done = response.get('code') == 200
|
done = response.get('code') == 200
|
||||||
if not done:
|
if not done:
|
||||||
raise Exception(f"Response was never 200, got last: {response}")
|
raise Exception(f"Response was never 200, got last: {response}")
|
||||||
'' + "\n") (
|
''
|
||||||
waitForUrls args
|
+ "\n"
|
||||||
)
|
) (waitForUrls args)
|
||||||
+ (if (! redirectSSO) then ''
|
+ (
|
||||||
|
if (!redirectSSO) then
|
||||||
|
''
|
||||||
with subtest("access"):
|
with subtest("access"):
|
||||||
response = curl(client, """{"code":%{response_code}}""", "${proto_fqdn}")
|
response = curl(client, """{"code":%{response_code}}""", "${proto_fqdn}")
|
||||||
|
|
||||||
if response['code'] != 200:
|
if response['code'] != 200:
|
||||||
raise Exception(f"Code is {response['code']}")
|
raise Exception(f"Code is {response['code']}")
|
||||||
'' else ''
|
''
|
||||||
|
else
|
||||||
|
''
|
||||||
with subtest("unauthenticated access is not granted"):
|
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}")
|
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']}")
|
raise Exception(f"auth host should be auth.${cfg.domain} but is {response['auth_host']}")
|
||||||
if response['auth_query'] != "rd=${proto_fqdn}/":
|
if response['auth_query'] != "rd=${proto_fqdn}/":
|
||||||
raise Exception(f"auth query should be rd=${proto_fqdn}/ but is {response['auth_query']}")
|
raise Exception(f"auth query should be rd=${proto_fqdn}/ but is {response['auth_query']}")
|
||||||
'')
|
''
|
||||||
+ (let
|
)
|
||||||
|
+ (
|
||||||
|
let
|
||||||
script = extraScript args;
|
script = extraScript args;
|
||||||
in
|
in
|
||||||
lib.optionalString (script != "") script)
|
lib.optionalString (script != "") script
|
||||||
|
)
|
||||||
+ (optionalString (hasAttr "test" nodes.server && hasAttr "login" nodes.server.test) ''
|
+ (optionalString (hasAttr "test" nodes.server && hasAttr "login" nodes.server.test) ''
|
||||||
with subtest("Login from server"):
|
with subtest("Login from server"):
|
||||||
code, logs = server.execute("login_playwright")
|
code, logs = server.execute("login_playwright")
|
||||||
|
|
@ -145,8 +167,12 @@ let
|
||||||
'')
|
'')
|
||||||
);
|
);
|
||||||
|
|
||||||
backupScript = args: (accessScript args).override {
|
backupScript =
|
||||||
extraScript = { proto_fqdn, ... }: ''
|
args:
|
||||||
|
(accessScript args).override {
|
||||||
|
extraScript =
|
||||||
|
{ proto_fqdn, ... }:
|
||||||
|
''
|
||||||
with subtest("backup"):
|
with subtest("backup"):
|
||||||
server.succeed("systemctl start restic-backups-testinstance_opt_repos_A")
|
server.succeed("systemctl start restic-backups-testinstance_opt_repos_A")
|
||||||
'';
|
'';
|
||||||
|
|
@ -155,17 +181,23 @@ in
|
||||||
{
|
{
|
||||||
inherit baseImports accessScript;
|
inherit baseImports accessScript;
|
||||||
|
|
||||||
runNixOSTest = args: pkgs.testers.runNixOSTest ({
|
runNixOSTest =
|
||||||
interactive.sshBackdoor.enable = true;
|
args:
|
||||||
} // args);
|
pkgs.testers.runNixOSTest (
|
||||||
|
|
||||||
mkScripts = args:
|
|
||||||
{
|
{
|
||||||
|
interactive.sshBackdoor.enable = true;
|
||||||
|
}
|
||||||
|
// args
|
||||||
|
);
|
||||||
|
|
||||||
|
mkScripts = args: {
|
||||||
access = accessScript args;
|
access = accessScript args;
|
||||||
backup = backupScript args;
|
backup = backupScript args;
|
||||||
};
|
};
|
||||||
|
|
||||||
baseModule = { config, ... }: {
|
baseModule =
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
options.test = {
|
options.test = {
|
||||||
domain = mkOption {
|
domain = mkOption {
|
||||||
type = str;
|
type = str;
|
||||||
|
|
@ -204,21 +236,34 @@ in
|
||||||
];
|
];
|
||||||
config = {
|
config = {
|
||||||
# HTTP(s) server port.
|
# HTTP(s) server port.
|
||||||
networking.firewall.allowedTCPPorts = [ 80 443 ];
|
networking.firewall.allowedTCPPorts = [
|
||||||
|
80
|
||||||
|
443
|
||||||
|
];
|
||||||
shb.nginx.accessLog = true;
|
shb.nginx.accessLog = true;
|
||||||
|
|
||||||
networking.hosts = {
|
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;
|
cfg = config.test.login;
|
||||||
in {
|
in
|
||||||
|
{
|
||||||
options.test.login = {
|
options.test.login = {
|
||||||
browser = mkOption {
|
browser = mkOption {
|
||||||
type = enum [ "firefox" "chromium" "webkit" ];
|
type = enum [
|
||||||
|
"firefox"
|
||||||
|
"chromium"
|
||||||
|
"webkit"
|
||||||
|
];
|
||||||
default = "firefox";
|
default = "firefox";
|
||||||
};
|
};
|
||||||
usernameFieldLabelRegex = mkOption {
|
usernameFieldLabelRegex = mkOption {
|
||||||
|
|
@ -269,7 +314,10 @@ in
|
||||||
};
|
};
|
||||||
config = {
|
config = {
|
||||||
networking.hosts = {
|
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 = {
|
environment.variables = {
|
||||||
|
|
@ -280,11 +328,16 @@ in
|
||||||
(pkgs.writers.writePython3Bin "login_playwright"
|
(pkgs.writers.writePython3Bin "login_playwright"
|
||||||
{
|
{
|
||||||
libraries = [ pkgs.python3Packages.playwright ];
|
libraries = [ pkgs.python3Packages.playwright ];
|
||||||
flakeIgnore = [ "F401" "E501" ];
|
flakeIgnore = [
|
||||||
|
"F401"
|
||||||
|
"E501"
|
||||||
|
];
|
||||||
}
|
}
|
||||||
(let
|
(
|
||||||
|
let
|
||||||
testCfg = pkgs.writeText "users.json" (builtins.toJSON cfg);
|
testCfg = pkgs.writeText "users.json" (builtins.toJSON cfg);
|
||||||
in ''
|
in
|
||||||
|
''
|
||||||
import json
|
import json
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
|
|
@ -343,13 +396,17 @@ in
|
||||||
context.tracing.stop(path=f"trace/{i}.zip")
|
context.tracing.stop(path=f"trace/{i}.zip")
|
||||||
|
|
||||||
browser.close()
|
browser.close()
|
||||||
'')
|
''
|
||||||
|
)
|
||||||
)
|
)
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
backup = backupOption: { config, ... }: {
|
backup =
|
||||||
|
backupOption:
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
imports = [
|
imports = [
|
||||||
../modules/blocks/restic.nix
|
../modules/blocks/restic.nix
|
||||||
];
|
];
|
||||||
|
|
@ -373,7 +430,9 @@ in
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
certs = { config, ... }: {
|
certs =
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
imports = [
|
imports = [
|
||||||
../modules/blocks/ssl.nix
|
../modules/blocks/ssl.nix
|
||||||
];
|
];
|
||||||
|
|
@ -395,7 +454,9 @@ in
|
||||||
systemd.services.nginx.requires = [ config.shb.certs.certs.selfsigned.n.systemdService ];
|
systemd.services.nginx.requires = [ config.shb.certs.certs.selfsigned.n.systemdService ];
|
||||||
};
|
};
|
||||||
|
|
||||||
ldap = { config, pkgs, ... }: {
|
ldap =
|
||||||
|
{ config, pkgs, ... }:
|
||||||
|
{
|
||||||
imports = [
|
imports = [
|
||||||
../modules/blocks/lldap.nix
|
../modules/blocks/lldap.nix
|
||||||
];
|
];
|
||||||
|
|
@ -432,7 +493,10 @@ in
|
||||||
};
|
};
|
||||||
bob = {
|
bob = {
|
||||||
email = "bob@example.com";
|
email = "bob@example.com";
|
||||||
groups = [ "user_group" "admin_group" ];
|
groups = [
|
||||||
|
"user_group"
|
||||||
|
"admin_group"
|
||||||
|
];
|
||||||
password.result.path = pkgs.writeText "bobPassword" "BobPassword";
|
password.result.path = pkgs.writeText "bobPassword" "BobPassword";
|
||||||
};
|
};
|
||||||
charlie = {
|
charlie = {
|
||||||
|
|
@ -450,7 +514,10 @@ in
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
sso = ssl: { config, pkgs, ... }: {
|
sso =
|
||||||
|
ssl:
|
||||||
|
{ config, pkgs, ... }:
|
||||||
|
{
|
||||||
imports = [
|
imports = [
|
||||||
../modules/blocks/authelia.nix
|
../modules/blocks/authelia.nix
|
||||||
];
|
];
|
||||||
|
|
@ -475,8 +542,10 @@ in
|
||||||
ldapAdminPassword.result = config.shb.hardcodedsecret.ldapAdminPassword.result;
|
ldapAdminPassword.result = config.shb.hardcodedsecret.ldapAdminPassword.result;
|
||||||
sessionSecret.result = config.shb.hardcodedsecret.sessionSecret.result;
|
sessionSecret.result = config.shb.hardcodedsecret.sessionSecret.result;
|
||||||
storageEncryptionKey.result = config.shb.hardcodedsecret.storageEncryptionKey.result;
|
storageEncryptionKey.result = config.shb.hardcodedsecret.storageEncryptionKey.result;
|
||||||
identityProvidersOIDCHMACSecret.result = config.shb.hardcodedsecret.identityProvidersOIDCHMACSecret.result;
|
identityProvidersOIDCHMACSecret.result =
|
||||||
identityProvidersOIDCIssuerPrivateKey.result = config.shb.hardcodedsecret.identityProvidersOIDCIssuerPrivateKey.result;
|
config.shb.hardcodedsecret.identityProvidersOIDCHMACSecret.result;
|
||||||
|
identityProvidersOIDCIssuerPrivateKey.result =
|
||||||
|
config.shb.hardcodedsecret.identityProvidersOIDCIssuerPrivateKey.result;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -502,10 +571,12 @@ in
|
||||||
};
|
};
|
||||||
shb.hardcodedsecret.identityProvidersOIDCIssuerPrivateKey = {
|
shb.hardcodedsecret.identityProvidersOIDCIssuerPrivateKey = {
|
||||||
request = config.shb.authelia.secrets.identityProvidersOIDCIssuerPrivateKey.request;
|
request = config.shb.authelia.secrets.identityProvidersOIDCIssuerPrivateKey.request;
|
||||||
settings.source = (pkgs.runCommand "gen-private-key" {} ''
|
settings.source =
|
||||||
|
(pkgs.runCommand "gen-private-key" { } ''
|
||||||
mkdir $out
|
mkdir $out
|
||||||
${pkgs.openssl}/bin/openssl genrsa -out $out/private.pem 4096
|
${pkgs.openssl}/bin/openssl genrsa -out $out/private.pem 4096
|
||||||
'') + "/private.pem";
|
'')
|
||||||
|
+ "/private.pem";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,12 +6,19 @@ in
|
||||||
restic_root = contracts.test.backup {
|
restic_root = contracts.test.backup {
|
||||||
name = "restic_root";
|
name = "restic_root";
|
||||||
username = "root";
|
username = "root";
|
||||||
providerRoot = [ "shb" "restic" "instances" "mytest" ];
|
providerRoot = [
|
||||||
|
"shb"
|
||||||
|
"restic"
|
||||||
|
"instances"
|
||||||
|
"mytest"
|
||||||
|
];
|
||||||
modules = [
|
modules = [
|
||||||
../../modules/blocks/restic.nix
|
../../modules/blocks/restic.nix
|
||||||
../../modules/blocks/hardcodedsecret.nix
|
../../modules/blocks/hardcodedsecret.nix
|
||||||
];
|
];
|
||||||
settings = { repository, config, ... }: {
|
settings =
|
||||||
|
{ repository, config, ... }:
|
||||||
|
{
|
||||||
enable = true;
|
enable = true;
|
||||||
passphrase.result = config.shb.hardcodedsecret.passphrase.result;
|
passphrase.result = config.shb.hardcodedsecret.passphrase.result;
|
||||||
repository = {
|
repository = {
|
||||||
|
|
@ -21,7 +28,9 @@ in
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
extraConfig = { username, config, ... }: {
|
extraConfig =
|
||||||
|
{ username, config, ... }:
|
||||||
|
{
|
||||||
shb.hardcodedsecret.passphrase = {
|
shb.hardcodedsecret.passphrase = {
|
||||||
request = config.shb.restic.instances."mytest".settings.passphrase.request;
|
request = config.shb.restic.instances."mytest".settings.passphrase.request;
|
||||||
settings.content = "passphrase";
|
settings.content = "passphrase";
|
||||||
|
|
@ -32,12 +41,19 @@ in
|
||||||
restic_nonroot = contracts.test.backup {
|
restic_nonroot = contracts.test.backup {
|
||||||
name = "restic_nonroot";
|
name = "restic_nonroot";
|
||||||
username = "me";
|
username = "me";
|
||||||
providerRoot = [ "shb" "restic" "instances" "mytest" ];
|
providerRoot = [
|
||||||
|
"shb"
|
||||||
|
"restic"
|
||||||
|
"instances"
|
||||||
|
"mytest"
|
||||||
|
];
|
||||||
modules = [
|
modules = [
|
||||||
../../modules/blocks/restic.nix
|
../../modules/blocks/restic.nix
|
||||||
../../modules/blocks/hardcodedsecret.nix
|
../../modules/blocks/hardcodedsecret.nix
|
||||||
];
|
];
|
||||||
settings = { repository, config, ... }: {
|
settings =
|
||||||
|
{ repository, config, ... }:
|
||||||
|
{
|
||||||
enable = true;
|
enable = true;
|
||||||
passphrase.result = config.shb.hardcodedsecret.passphrase.result;
|
passphrase.result = config.shb.hardcodedsecret.passphrase.result;
|
||||||
repository = {
|
repository = {
|
||||||
|
|
@ -47,7 +63,9 @@ in
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
extraConfig = { username, config, ... }: {
|
extraConfig =
|
||||||
|
{ username, config, ... }:
|
||||||
|
{
|
||||||
shb.hardcodedsecret.passphrase = {
|
shb.hardcodedsecret.passphrase = {
|
||||||
request = config.shb.restic.instances."mytest".settings.passphrase.request;
|
request = config.shb.restic.instances."mytest".settings.passphrase.request;
|
||||||
settings.content = "passphrase";
|
settings.content = "passphrase";
|
||||||
|
|
|
||||||
|
|
@ -5,14 +5,25 @@ in
|
||||||
{
|
{
|
||||||
restic_postgres = contracts.test.databasebackup {
|
restic_postgres = contracts.test.databasebackup {
|
||||||
name = "restic_postgres";
|
name = "restic_postgres";
|
||||||
requesterRoot = [ "shb" "postgresql" "databasebackup" ];
|
requesterRoot = [
|
||||||
providerRoot = [ "shb" "restic" "databases" "postgresql" ];
|
"shb"
|
||||||
|
"postgresql"
|
||||||
|
"databasebackup"
|
||||||
|
];
|
||||||
|
providerRoot = [
|
||||||
|
"shb"
|
||||||
|
"restic"
|
||||||
|
"databases"
|
||||||
|
"postgresql"
|
||||||
|
];
|
||||||
modules = [
|
modules = [
|
||||||
../../modules/blocks/postgresql.nix
|
../../modules/blocks/postgresql.nix
|
||||||
../../modules/blocks/restic.nix
|
../../modules/blocks/restic.nix
|
||||||
../../modules/blocks/hardcodedsecret.nix
|
../../modules/blocks/hardcodedsecret.nix
|
||||||
];
|
];
|
||||||
settings = { repository, config, ... }: {
|
settings =
|
||||||
|
{ repository, config, ... }:
|
||||||
|
{
|
||||||
enable = true;
|
enable = true;
|
||||||
passphrase.result = config.shb.hardcodedsecret.passphrase.result;
|
passphrase.result = config.shb.hardcodedsecret.passphrase.result;
|
||||||
repository = {
|
repository = {
|
||||||
|
|
@ -22,7 +33,9 @@ in
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
extraConfig = { config, database, ... }: {
|
extraConfig =
|
||||||
|
{ config, database, ... }:
|
||||||
|
{
|
||||||
shb.postgresql.ensures = [
|
shb.postgresql.ensures = [
|
||||||
{
|
{
|
||||||
inherit database;
|
inherit database;
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,10 @@ in
|
||||||
hardcoded_root_root = contracts.test.secret {
|
hardcoded_root_root = contracts.test.secret {
|
||||||
name = "hardcoded";
|
name = "hardcoded";
|
||||||
modules = [ ../../modules/blocks/hardcodedsecret.nix ];
|
modules = [ ../../modules/blocks/hardcodedsecret.nix ];
|
||||||
configRoot = [ "shb" "hardcodedsecret" ];
|
configRoot = [
|
||||||
|
"shb"
|
||||||
|
"hardcodedsecret"
|
||||||
|
];
|
||||||
settingsCfg = secret: {
|
settingsCfg = secret: {
|
||||||
content = secret;
|
content = secret;
|
||||||
};
|
};
|
||||||
|
|
@ -15,7 +18,10 @@ in
|
||||||
hardcoded_user_group = contracts.test.secret {
|
hardcoded_user_group = contracts.test.secret {
|
||||||
name = "hardcoded";
|
name = "hardcoded";
|
||||||
modules = [ ../../modules/blocks/hardcodedsecret.nix ];
|
modules = [ ../../modules/blocks/hardcodedsecret.nix ];
|
||||||
configRoot = [ "shb" "hardcodedsecret" ];
|
configRoot = [
|
||||||
|
"shb"
|
||||||
|
"hardcodedsecret"
|
||||||
|
];
|
||||||
settingsCfg = secret: {
|
settingsCfg = secret: {
|
||||||
content = secret;
|
content = secret;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,17 @@
|
||||||
{ pkgs, lib, ... }:
|
{ pkgs, lib, ... }:
|
||||||
let
|
let
|
||||||
anyOpt = default: lib.mkOption {
|
anyOpt =
|
||||||
|
default:
|
||||||
|
lib.mkOption {
|
||||||
type = lib.types.anything;
|
type = lib.types.anything;
|
||||||
inherit default;
|
inherit default;
|
||||||
};
|
};
|
||||||
|
|
||||||
testConfig = m:
|
testConfig =
|
||||||
|
m:
|
||||||
let
|
let
|
||||||
cfg = (lib.evalModules {
|
cfg =
|
||||||
|
(lib.evalModules {
|
||||||
specialArgs = { inherit pkgs; };
|
specialArgs = { inherit pkgs; };
|
||||||
modules = [
|
modules = [
|
||||||
{
|
{
|
||||||
|
|
@ -20,7 +24,8 @@ let
|
||||||
m
|
m
|
||||||
];
|
];
|
||||||
}).config;
|
}).config;
|
||||||
in {
|
in
|
||||||
|
{
|
||||||
inherit (cfg) systemd services;
|
inherit (cfg) systemd services;
|
||||||
};
|
};
|
||||||
in
|
in
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,8 @@ in
|
||||||
c = "%SECRET_${root}C%";
|
c = "%SECRET_${root}C%";
|
||||||
};
|
};
|
||||||
in
|
in
|
||||||
(item "") // {
|
(item "")
|
||||||
|
// {
|
||||||
nestedAttr = item "NESTEDATTR_";
|
nestedAttr = item "NESTEDATTR_";
|
||||||
nestedList = [ (item "NESTEDLIST_0_") ];
|
nestedList = [ (item "NESTEDLIST_0_") ];
|
||||||
doubleNestedList = [ { n = (item "DOUBLENESTEDLIST_0_N_"); } ];
|
doubleNestedList = [ { n = (item "DOUBLENESTEDLIST_0_N_"); } ];
|
||||||
|
|
@ -34,7 +35,8 @@ in
|
||||||
};
|
};
|
||||||
in
|
in
|
||||||
lib.shb.withReplacements (
|
lib.shb.withReplacements (
|
||||||
item // {
|
item
|
||||||
|
// {
|
||||||
nestedAttr = item;
|
nestedAttr = item;
|
||||||
nestedList = [ item ];
|
nestedList = [ item ];
|
||||||
doubleNestedList = [ { n = item; } ];
|
doubleNestedList = [ { n = item; } ];
|
||||||
|
|
@ -84,10 +86,10 @@ in
|
||||||
(nameValuePair "%SECRET_${root}C%" "prefix-$(cat /path/C)-suffix")
|
(nameValuePair "%SECRET_${root}C%" "prefix-$(cat /path/C)-suffix")
|
||||||
];
|
];
|
||||||
in
|
in
|
||||||
(secrets "") ++
|
(secrets "")
|
||||||
(secrets "DOUBLENESTEDLIST_0_N_") ++
|
++ (secrets "DOUBLENESTEDLIST_0_N_")
|
||||||
(secrets "NESTEDATTR_") ++
|
++ (secrets "NESTEDATTR_")
|
||||||
(secrets "NESTEDLIST_0_");
|
++ (secrets "NESTEDLIST_0_");
|
||||||
expr =
|
expr =
|
||||||
let
|
let
|
||||||
item = {
|
item = {
|
||||||
|
|
@ -99,13 +101,16 @@ in
|
||||||
c.other = "other";
|
c.other = "other";
|
||||||
};
|
};
|
||||||
in
|
in
|
||||||
map lib.shb.genReplacement (lib.shb.getReplacements (
|
map lib.shb.genReplacement (
|
||||||
item // {
|
lib.shb.getReplacements (
|
||||||
|
item
|
||||||
|
// {
|
||||||
nestedAttr = item;
|
nestedAttr = item;
|
||||||
nestedList = [ item ];
|
nestedList = [ item ];
|
||||||
doubleNestedList = [ { n = item; } ];
|
doubleNestedList = [ { n = item; } ];
|
||||||
}
|
}
|
||||||
));
|
)
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
testParseXML = {
|
testParseXML = {
|
||||||
|
|
|
||||||
|
|
@ -4,20 +4,34 @@ let
|
||||||
loginUrl = "/UI/Login";
|
loginUrl = "/UI/Login";
|
||||||
|
|
||||||
# TODO: Test 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);
|
hasSSL = { node, ... }: !(isNull node.config.shb.arr.${appname}.ssl);
|
||||||
waitForServices = { ... }: [
|
waitForServices =
|
||||||
|
{ ... }:
|
||||||
|
[
|
||||||
"${appname}.service"
|
"${appname}.service"
|
||||||
"nginx.service"
|
"nginx.service"
|
||||||
];
|
];
|
||||||
waitForPorts = { node, ... }: [
|
waitForPorts =
|
||||||
|
{ node, ... }:
|
||||||
|
[
|
||||||
node.config.shb.arr.${appname}.settings.Port
|
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};
|
shbapp = node.config.shb.arr.${appname};
|
||||||
cfgPath = cfgPathFn shbapp;
|
cfgPath = cfgPathFn shbapp;
|
||||||
apiKey = if (shbapp.settings ? ApiKey) then "01234567890123456789" else null;
|
apiKey = if (shbapp.settings ? ApiKey) then "01234567890123456789" else null;
|
||||||
in ''
|
in
|
||||||
|
''
|
||||||
# These curl requests still return a 200 even with sso redirect.
|
# These curl requests still return a 200 even with sso redirect.
|
||||||
with subtest("health"):
|
with subtest("health"):
|
||||||
response = curl(client, """{"code":%{response_code}}""", "${fqdn}${healthUrl}")
|
response = curl(client, """{"code":%{response_code}}""", "${fqdn}${healthUrl}")
|
||||||
|
|
@ -31,7 +45,8 @@ let
|
||||||
|
|
||||||
if response['code'] != 200:
|
if response['code'] != 200:
|
||||||
raise Exception(f"Code is {response['code']}")
|
raise Exception(f"Code is {response['code']}")
|
||||||
'' + lib.optionalString (apiKey != null) ''
|
''
|
||||||
|
+ lib.optionalString (apiKey != null) ''
|
||||||
|
|
||||||
with subtest("apikey"):
|
with subtest("apikey"):
|
||||||
config = server.succeed("cat ${cfgPath}")
|
config = server.succeed("cat ${cfgPath}")
|
||||||
|
|
@ -40,7 +55,10 @@ let
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
basic = appname: { config, ... }: {
|
basic =
|
||||||
|
appname:
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
imports = [
|
imports = [
|
||||||
lib.shb.baseModule
|
lib.shb.baseModule
|
||||||
../../modules/services/arr.nix
|
../../modules/services/arr.nix
|
||||||
|
|
@ -58,7 +76,10 @@ let
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
clientLogin = appname: { config, ... }: {
|
clientLogin =
|
||||||
|
appname:
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
imports = [
|
imports = [
|
||||||
lib.shb.baseModule
|
lib.shb.baseModule
|
||||||
lib.shb.clientLoginModule
|
lib.shb.clientLoginModule
|
||||||
|
|
@ -74,14 +95,18 @@ let
|
||||||
passwordFieldLabelRegex = "^ *[Pp]assword";
|
passwordFieldLabelRegex = "^ *[Pp]assword";
|
||||||
loginButtonNameRegex = "[Ll]og [Ii]n";
|
loginButtonNameRegex = "[Ll]og [Ii]n";
|
||||||
testLoginWith = [
|
testLoginWith = [
|
||||||
{ nextPageExpect = [
|
{
|
||||||
|
nextPageExpect = [
|
||||||
"expect(page).to_have_title(re.compile('${appname}', re.IGNORECASE))"
|
"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";
|
name = "arr_${appname}_basic";
|
||||||
|
|
||||||
nodes.client = {
|
nodes.client = {
|
||||||
|
|
@ -98,10 +123,14 @@ let
|
||||||
testScript = (commonTestScript appname cfgPathFn).access;
|
testScript = (commonTestScript appname cfgPathFn).access;
|
||||||
};
|
};
|
||||||
|
|
||||||
backupTest = appname: cfgPathFn: lib.shb.runNixOSTest {
|
backupTest =
|
||||||
|
appname: cfgPathFn:
|
||||||
|
lib.shb.runNixOSTest {
|
||||||
name = "arr_${appname}_backup";
|
name = "arr_${appname}_backup";
|
||||||
|
|
||||||
nodes.server = { config, ... }: {
|
nodes.server =
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
imports = [
|
imports = [
|
||||||
(basic appname)
|
(basic appname)
|
||||||
(lib.shb.backup config.shb.arr.${appname}.backup)
|
(lib.shb.backup config.shb.arr.${appname}.backup)
|
||||||
|
|
@ -113,16 +142,23 @@ let
|
||||||
testScript = (commonTestScript appname cfgPathFn).backup;
|
testScript = (commonTestScript appname cfgPathFn).backup;
|
||||||
};
|
};
|
||||||
|
|
||||||
https = appname: { config, ...}: {
|
https =
|
||||||
|
appname:
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
shb.arr.${appname} = {
|
shb.arr.${appname} = {
|
||||||
ssl = config.shb.certs.certs.selfsigned.n;
|
ssl = config.shb.certs.certs.selfsigned.n;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
httpsTest = appname: cfgPathFn: lib.shb.runNixOSTest {
|
httpsTest =
|
||||||
|
appname: cfgPathFn:
|
||||||
|
lib.shb.runNixOSTest {
|
||||||
name = "arr_${appname}_https";
|
name = "arr_${appname}_https";
|
||||||
|
|
||||||
nodes.server = { config, pkgs, ... }: {
|
nodes.server =
|
||||||
|
{ config, pkgs, ... }:
|
||||||
|
{
|
||||||
imports = [
|
imports = [
|
||||||
(basic appname)
|
(basic appname)
|
||||||
lib.shb.certs
|
lib.shb.certs
|
||||||
|
|
@ -135,16 +171,23 @@ let
|
||||||
testScript = (commonTestScript appname cfgPathFn).access;
|
testScript = (commonTestScript appname cfgPathFn).access;
|
||||||
};
|
};
|
||||||
|
|
||||||
sso = appname: { config, ...}: {
|
sso =
|
||||||
|
appname:
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
shb.arr.${appname} = {
|
shb.arr.${appname} = {
|
||||||
authEndpoint = "https://${config.shb.authelia.subdomain}.${config.shb.authelia.domain}";
|
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";
|
name = "arr_${appname}_sso";
|
||||||
|
|
||||||
nodes.server = { config, pkgs, ... }: {
|
nodes.server =
|
||||||
|
{ config, pkgs, ... }:
|
||||||
|
{
|
||||||
imports = [
|
imports = [
|
||||||
(basic appname)
|
(basic appname)
|
||||||
lib.shb.certs
|
lib.shb.certs
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,15 @@
|
||||||
let
|
let
|
||||||
commonTestScript = lib.shb.accessScript {
|
commonTestScript = lib.shb.accessScript {
|
||||||
hasSSL = { node, ... }: !(isNull node.config.shb.audiobookshelf.ssl);
|
hasSSL = { node, ... }: !(isNull node.config.shb.audiobookshelf.ssl);
|
||||||
waitForServices = { ... }: [
|
waitForServices =
|
||||||
|
{ ... }:
|
||||||
|
[
|
||||||
"audiobookshelf.service"
|
"audiobookshelf.service"
|
||||||
"nginx.service"
|
"nginx.service"
|
||||||
];
|
];
|
||||||
waitForPorts = { node, ... }: [
|
waitForPorts =
|
||||||
|
{ node, ... }:
|
||||||
|
[
|
||||||
node.config.shb.audiobookshelf.webPort
|
node.config.shb.audiobookshelf.webPort
|
||||||
];
|
];
|
||||||
# TODO: Test login
|
# TODO: Test login
|
||||||
|
|
@ -14,7 +18,9 @@ let
|
||||||
# '';
|
# '';
|
||||||
};
|
};
|
||||||
|
|
||||||
basic = { config, ... }: {
|
basic =
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
imports = [
|
imports = [
|
||||||
lib.shb.baseModule
|
lib.shb.baseModule
|
||||||
../../modules/services/audiobookshelf.nix
|
../../modules/services/audiobookshelf.nix
|
||||||
|
|
@ -29,7 +35,9 @@ let
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
clientLogin = { config, ... }: {
|
clientLogin =
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
imports = [
|
imports = [
|
||||||
lib.shb.baseModule
|
lib.shb.baseModule
|
||||||
lib.shb.clientLoginModule
|
lib.shb.clientLoginModule
|
||||||
|
|
@ -47,9 +55,13 @@ let
|
||||||
loginButtonNameRegex = "[Ll]og [Ii]n";
|
loginButtonNameRegex = "[Ll]og [Ii]n";
|
||||||
testLoginWith = [
|
testLoginWith = [
|
||||||
# Failure is after so we're not throttled too much.
|
# 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()"
|
"expect(page.get_by_text('Wrong username or password')).to_be_visible()"
|
||||||
]; }
|
];
|
||||||
|
}
|
||||||
# { username = adminUser; password = adminPass; nextPageExpect = [
|
# { username = adminUser; password = adminPass; nextPageExpect = [
|
||||||
# "expect(page.get_by_text('Wrong username or password')).not_to_be_visible()"
|
# "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()"
|
# "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 = {
|
shb.audiobookshelf = {
|
||||||
ssl = config.shb.certs.certs.selfsigned.n;
|
ssl = config.shb.certs.certs.selfsigned.n;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
sso = { config, ... }: {
|
sso =
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
shb.audiobookshelf = {
|
shb.audiobookshelf = {
|
||||||
sso = {
|
sso = {
|
||||||
enable = true;
|
enable = true;
|
||||||
endpoint = "https://${config.shb.authelia.subdomain}.${config.shb.authelia.domain}";
|
endpoint = "https://${config.shb.authelia.subdomain}.${config.shb.authelia.domain}";
|
||||||
sharedSecret.result = config.shb.hardcodedsecret.audiobookshelfSSOPassword.result;
|
sharedSecret.result = config.shb.hardcodedsecret.audiobookshelfSSOPassword.result;
|
||||||
sharedSecretForAuthelia.result = config.shb.hardcodedsecret.audiobookshelfSSOPasswordAuthelia.result;
|
sharedSecretForAuthelia.result =
|
||||||
|
config.shb.hardcodedsecret.audiobookshelfSSOPasswordAuthelia.result;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -124,7 +141,9 @@ in
|
||||||
sso = lib.shb.runNixOSTest {
|
sso = lib.shb.runNixOSTest {
|
||||||
name = "audiobookshelf-sso";
|
name = "audiobookshelf-sso";
|
||||||
|
|
||||||
nodes.server = { config, ... }: {
|
nodes.server =
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
imports = [
|
imports = [
|
||||||
basic
|
basic
|
||||||
lib.shb.certs
|
lib.shb.certs
|
||||||
|
|
|
||||||
|
|
@ -2,16 +2,22 @@
|
||||||
let
|
let
|
||||||
commonTestScript = lib.shb.mkScripts {
|
commonTestScript = lib.shb.mkScripts {
|
||||||
hasSSL = { node, ... }: !(isNull node.config.shb.deluge.ssl);
|
hasSSL = { node, ... }: !(isNull node.config.shb.deluge.ssl);
|
||||||
waitForServices = { ... }: [
|
waitForServices =
|
||||||
|
{ ... }:
|
||||||
|
[
|
||||||
"nginx.service"
|
"nginx.service"
|
||||||
"deluged.service"
|
"deluged.service"
|
||||||
"delugeweb.service"
|
"delugeweb.service"
|
||||||
];
|
];
|
||||||
waitForPorts = { node, ... }: [
|
waitForPorts =
|
||||||
|
{ node, ... }:
|
||||||
|
[
|
||||||
node.config.shb.deluge.daemonPort
|
node.config.shb.deluge.daemonPort
|
||||||
node.config.shb.deluge.webPort
|
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('journalctl -n100 -u deluged'))
|
||||||
print(${node.name}.succeed('systemctl status deluged'))
|
print(${node.name}.succeed('systemctl status deluged'))
|
||||||
print(${node.name}.succeed('systemctl status delugeweb'))
|
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})
|
server.wait_for_open_port(${toString nodes.server.services.prometheus.exporters.deluge.port})
|
||||||
with subtest("prometheus"):
|
with subtest("prometheus"):
|
||||||
|
|
@ -65,7 +72,9 @@ let
|
||||||
print(response)
|
print(response)
|
||||||
'';
|
'';
|
||||||
|
|
||||||
basic = { config, ... }: {
|
basic =
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
imports = [
|
imports = [
|
||||||
lib.shb.baseModule
|
lib.shb.baseModule
|
||||||
../../modules/blocks/hardcodedsecret.nix
|
../../modules/blocks/hardcodedsecret.nix
|
||||||
|
|
@ -96,7 +105,9 @@ let
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
clientLogin = { config, ... }: {
|
clientLogin =
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
imports = [
|
imports = [
|
||||||
lib.shb.baseModule
|
lib.shb.baseModule
|
||||||
lib.shb.clientLoginModule
|
lib.shb.clientLoginModule
|
||||||
|
|
@ -109,19 +120,27 @@ let
|
||||||
passwordFieldLabelRegex = "Password";
|
passwordFieldLabelRegex = "Password";
|
||||||
loginButtonNameRegex = "Login";
|
loginButtonNameRegex = "Login";
|
||||||
testLoginWith = [
|
testLoginWith = [
|
||||||
{ password = "deluge"; nextPageExpect = [
|
{
|
||||||
|
password = "deluge";
|
||||||
|
nextPageExpect = [
|
||||||
"expect(page.get_by_role('button', name='Login')).not_to_be_visible()"
|
"expect(page.get_by_role('button', name='Login')).not_to_be_visible()"
|
||||||
"expect(page.get_by_text('Login Failed')).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_role('button', name='Login')).to_be_visible()"
|
||||||
"expect(page.get_by_text('Login Failed')).to_be_visible()"
|
"expect(page.get_by_text('Login Failed')).to_be_visible()"
|
||||||
]; }
|
];
|
||||||
|
}
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
prometheus = { config, ... }: {
|
prometheus =
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
shb.deluge = {
|
shb.deluge = {
|
||||||
prometheusScraperPassword.result = config.shb.hardcodedsecret."scraper".result;
|
prometheusScraperPassword.result = config.shb.hardcodedsecret."scraper".result;
|
||||||
};
|
};
|
||||||
|
|
@ -131,13 +150,17 @@ let
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
https = { config, ...}: {
|
https =
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
shb.deluge = {
|
shb.deluge = {
|
||||||
ssl = config.shb.certs.certs.selfsigned.n;
|
ssl = config.shb.certs.certs.selfsigned.n;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
sso = { config, ... }: {
|
sso =
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
shb.deluge = {
|
shb.deluge = {
|
||||||
authEndpoint = "https://${config.shb.authelia.subdomain}.${config.shb.authelia.domain}";
|
authEndpoint = "https://${config.shb.authelia.subdomain}.${config.shb.authelia.domain}";
|
||||||
};
|
};
|
||||||
|
|
@ -164,7 +187,9 @@ in
|
||||||
backup = lib.shb.runNixOSTest {
|
backup = lib.shb.runNixOSTest {
|
||||||
name = "deluge_backup";
|
name = "deluge_backup";
|
||||||
|
|
||||||
nodes.server = { config, ... }: {
|
nodes.server =
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
imports = [
|
imports = [
|
||||||
basic
|
basic
|
||||||
(lib.shb.backup config.shb.deluge.backup)
|
(lib.shb.backup config.shb.deluge.backup)
|
||||||
|
|
@ -195,7 +220,9 @@ in
|
||||||
sso = lib.shb.runNixOSTest {
|
sso = lib.shb.runNixOSTest {
|
||||||
name = "deluge_sso";
|
name = "deluge_sso";
|
||||||
|
|
||||||
nodes.server = { config, ... }: {
|
nodes.server =
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
imports = [
|
imports = [
|
||||||
basic
|
basic
|
||||||
lib.shb.certs
|
lib.shb.certs
|
||||||
|
|
@ -227,8 +254,6 @@ in
|
||||||
|
|
||||||
nodes.client = { };
|
nodes.client = { };
|
||||||
|
|
||||||
testScript = inputs:
|
testScript = inputs: (commonTestScript.access inputs) + (prometheusTestScript inputs);
|
||||||
(commonTestScript.access inputs)
|
|
||||||
+ (prometheusTestScript inputs);
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,20 +4,28 @@ let
|
||||||
|
|
||||||
commonTestScript = lib.shb.mkScripts {
|
commonTestScript = lib.shb.mkScripts {
|
||||||
hasSSL = { node, ... }: !(isNull node.config.shb.forgejo.ssl);
|
hasSSL = { node, ... }: !(isNull node.config.shb.forgejo.ssl);
|
||||||
waitForServices = { ... }: [
|
waitForServices =
|
||||||
|
{ ... }:
|
||||||
|
[
|
||||||
"forgejo.service"
|
"forgejo.service"
|
||||||
"nginx.service"
|
"nginx.service"
|
||||||
];
|
];
|
||||||
waitForUnixSocket = { node, ... }: [
|
waitForUnixSocket =
|
||||||
|
{ node, ... }:
|
||||||
|
[
|
||||||
node.config.services.forgejo.settings.server.HTTP_ADDR
|
node.config.services.forgejo.settings.server.HTTP_ADDR
|
||||||
];
|
];
|
||||||
extraScript = { node, ... }: ''
|
extraScript =
|
||||||
|
{ node, ... }:
|
||||||
|
''
|
||||||
server.wait_for_unit("gitea-runner-local.service", timeout=10)
|
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'")
|
server.succeed("journalctl -o cat -u gitea-runner-local.service | grep -q 'Runner registered successfully'")
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
basic = { config, ... }: {
|
basic =
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
imports = [
|
imports = [
|
||||||
lib.shb.baseModule
|
lib.shb.baseModule
|
||||||
../../modules/blocks/hardcodedsecret.nix
|
../../modules/blocks/hardcodedsecret.nix
|
||||||
|
|
@ -67,7 +75,9 @@ let
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
clientLogin = { config, ... }: {
|
clientLogin =
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
imports = [
|
imports = [
|
||||||
lib.shb.baseModule
|
lib.shb.baseModule
|
||||||
lib.shb.clientLoginModule
|
lib.shb.clientLoginModule
|
||||||
|
|
@ -82,33 +92,53 @@ let
|
||||||
passwordFieldLabelRegex = "Password";
|
passwordFieldLabelRegex = "Password";
|
||||||
loginButtonNameRegex = "[sS]ign [iI]n";
|
loginButtonNameRegex = "[sS]ign [iI]n";
|
||||||
testLoginWith = [
|
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()"
|
"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_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.get_by_role('button', name=re.compile('Sign In'))).not_to_be_visible()"
|
||||||
"expect(page).to_have_title(re.compile('Dashboard'))"
|
"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()"
|
"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_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.get_by_role('button', name=re.compile('Sign In'))).not_to_be_visible()"
|
||||||
"expect(page).to_have_title(re.compile('Dashboard'))"
|
"expect(page).to_have_title(re.compile('Dashboard'))"
|
||||||
]; }
|
];
|
||||||
|
}
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
https = { config, ... }: {
|
https =
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
shb.forgejo = {
|
shb.forgejo = {
|
||||||
ssl = config.shb.certs.certs.selfsigned.n;
|
ssl = config.shb.certs.certs.selfsigned.n;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
ldap = { config, ... }: {
|
ldap =
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
shb.forgejo = {
|
shb.forgejo = {
|
||||||
ldap = {
|
ldap = {
|
||||||
enable = true;
|
enable = true;
|
||||||
|
|
@ -128,7 +158,9 @@ let
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
sso = { config, ... }: {
|
sso =
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
shb.forgejo = {
|
shb.forgejo = {
|
||||||
sso = {
|
sso = {
|
||||||
enable = true;
|
enable = true;
|
||||||
|
|
@ -170,7 +202,9 @@ in
|
||||||
backup = lib.shb.runNixOSTest {
|
backup = lib.shb.runNixOSTest {
|
||||||
name = "forgejo_backup";
|
name = "forgejo_backup";
|
||||||
|
|
||||||
nodes.server = { config, ... }: {
|
nodes.server =
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
imports = [
|
imports = [
|
||||||
basic
|
basic
|
||||||
(lib.shb.backup config.shb.forgejo.backup)
|
(lib.shb.backup config.shb.forgejo.backup)
|
||||||
|
|
@ -211,7 +245,9 @@ in
|
||||||
|
|
||||||
nodes.client = {
|
nodes.client = {
|
||||||
imports = [
|
imports = [
|
||||||
({ config, ... }: {
|
(
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
imports = [
|
imports = [
|
||||||
lib.shb.baseModule
|
lib.shb.baseModule
|
||||||
lib.shb.clientLoginModule
|
lib.shb.clientLoginModule
|
||||||
|
|
@ -227,31 +263,56 @@ in
|
||||||
passwordFieldLabelRegex = "Password";
|
passwordFieldLabelRegex = "Password";
|
||||||
loginButtonNameRegex = "[sS]ign [iI]n";
|
loginButtonNameRegex = "[sS]ign [iI]n";
|
||||||
testLoginWith = [
|
testLoginWith = [
|
||||||
{ username = "alice"; password = "NotAlicePassword"; nextPageExpect = [
|
{
|
||||||
|
username = "alice";
|
||||||
|
password = "NotAlicePassword";
|
||||||
|
nextPageExpect = [
|
||||||
"expect(page.get_by_text('Username or password is incorrect.')).to_be_visible()"
|
"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_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.get_by_role('button', name=re.compile('Sign In'))).not_to_be_visible()"
|
||||||
"expect(page).to_have_title(re.compile('Dashboard'))"
|
"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()"
|
"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_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.get_by_role('button', name=re.compile('Sign In'))).not_to_be_visible()"
|
||||||
"expect(page).to_have_title(re.compile('Dashboard'))"
|
"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()"
|
"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()"
|
"expect(page.get_by_text('Username or password is incorrect.')).to_be_visible()"
|
||||||
]; }
|
];
|
||||||
|
}
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
})
|
}
|
||||||
|
)
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -261,7 +322,9 @@ in
|
||||||
sso = lib.shb.runNixOSTest {
|
sso = lib.shb.runNixOSTest {
|
||||||
name = "forgejo_sso";
|
name = "forgejo_sso";
|
||||||
|
|
||||||
nodes.server = { config, pkgs, ... }: {
|
nodes.server =
|
||||||
|
{ config, pkgs, ... }:
|
||||||
|
{
|
||||||
imports = [
|
imports = [
|
||||||
basic
|
basic
|
||||||
lib.shb.certs
|
lib.shb.certs
|
||||||
|
|
|
||||||
|
|
@ -2,16 +2,22 @@
|
||||||
let
|
let
|
||||||
commonTestScript = lib.shb.mkScripts {
|
commonTestScript = lib.shb.mkScripts {
|
||||||
hasSSL = { node, ... }: !(isNull node.config.shb.grocy.ssl);
|
hasSSL = { node, ... }: !(isNull node.config.shb.grocy.ssl);
|
||||||
waitForServices = { ... }: [
|
waitForServices =
|
||||||
|
{ ... }:
|
||||||
|
[
|
||||||
"phpfpm-grocy.service"
|
"phpfpm-grocy.service"
|
||||||
"nginx.service"
|
"nginx.service"
|
||||||
];
|
];
|
||||||
waitForUnixSocket = { node, ... }: [
|
waitForUnixSocket =
|
||||||
|
{ node, ... }:
|
||||||
|
[
|
||||||
node.config.services.phpfpm.pools.grocy.socket
|
node.config.services.phpfpm.pools.grocy.socket
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
basic = { config, ... }: {
|
basic =
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
imports = [
|
imports = [
|
||||||
lib.shb.baseModule
|
lib.shb.baseModule
|
||||||
../../modules/services/grocy.nix
|
../../modules/services/grocy.nix
|
||||||
|
|
@ -27,7 +33,9 @@ let
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
clientLogin = { config, ... }: {
|
clientLogin =
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
imports = [
|
imports = [
|
||||||
lib.shb.baseModule
|
lib.shb.baseModule
|
||||||
lib.shb.clientLoginModule
|
lib.shb.clientLoginModule
|
||||||
|
|
@ -44,19 +52,29 @@ let
|
||||||
passwordFieldLabelRegex = "Password";
|
passwordFieldLabelRegex = "Password";
|
||||||
loginButtonNameRegex = "OK";
|
loginButtonNameRegex = "OK";
|
||||||
testLoginWith = [
|
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()"
|
"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_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.get_by_role('button', name=re.compile('OK'))).not_to_be_visible()"
|
||||||
"expect(page).to_have_title(re.compile('Grocy'))"
|
"expect(page).to_have_title(re.compile('Grocy'))"
|
||||||
]; }
|
];
|
||||||
|
}
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
https = { config, ...}: {
|
https =
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
shb.grocy = {
|
shb.grocy = {
|
||||||
ssl = config.shb.certs.certs.selfsigned.n;
|
ssl = config.shb.certs.certs.selfsigned.n;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,17 @@
|
||||||
let
|
let
|
||||||
commonTestScript = lib.shb.mkScripts {
|
commonTestScript = lib.shb.mkScripts {
|
||||||
hasSSL = { node, ... }: !(isNull node.config.shb.hledger.ssl);
|
hasSSL = { node, ... }: !(isNull node.config.shb.hledger.ssl);
|
||||||
waitForServices = { ... }: [
|
waitForServices =
|
||||||
|
{ ... }:
|
||||||
|
[
|
||||||
"hledger-web.service"
|
"hledger-web.service"
|
||||||
"nginx.service"
|
"nginx.service"
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
basic = { config, ... }: {
|
basic =
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
imports = [
|
imports = [
|
||||||
lib.shb.baseModule
|
lib.shb.baseModule
|
||||||
../../modules/services/hledger.nix
|
../../modules/services/hledger.nix
|
||||||
|
|
@ -24,7 +28,9 @@ let
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
clientLogin = { config, ... }: {
|
clientLogin =
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
imports = [
|
imports = [
|
||||||
lib.shb.baseModule
|
lib.shb.baseModule
|
||||||
lib.shb.clientLoginModule
|
lib.shb.clientLoginModule
|
||||||
|
|
@ -37,20 +43,26 @@ let
|
||||||
test.login = {
|
test.login = {
|
||||||
startUrl = "http://${config.test.fqdn}";
|
startUrl = "http://${config.test.fqdn}";
|
||||||
testLoginWith = [
|
testLoginWith = [
|
||||||
{ nextPageExpect = [
|
{
|
||||||
|
nextPageExpect = [
|
||||||
"expect(page).to_have_title('journal - hledger-web')"
|
"expect(page).to_have_title('journal - hledger-web')"
|
||||||
]; }
|
];
|
||||||
|
}
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
https = { config, ... }: {
|
https =
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
shb.hledger = {
|
shb.hledger = {
|
||||||
ssl = config.shb.certs.certs.selfsigned.n;
|
ssl = config.shb.certs.certs.selfsigned.n;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
sso = { config, ... }: {
|
sso =
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
shb.hledger = {
|
shb.hledger = {
|
||||||
authEndpoint = "https://${config.shb.authelia.subdomain}.${config.shb.authelia.domain}";
|
authEndpoint = "https://${config.shb.authelia.subdomain}.${config.shb.authelia.domain}";
|
||||||
};
|
};
|
||||||
|
|
@ -78,7 +90,9 @@ in
|
||||||
backup = lib.shb.runNixOSTest {
|
backup = lib.shb.runNixOSTest {
|
||||||
name = "hledger_backup";
|
name = "hledger_backup";
|
||||||
|
|
||||||
nodes.server = { config, ... }: {
|
nodes.server =
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
imports = [
|
imports = [
|
||||||
basic
|
basic
|
||||||
(lib.shb.backup config.shb.hledger.backup)
|
(lib.shb.backup config.shb.hledger.backup)
|
||||||
|
|
@ -109,7 +123,9 @@ in
|
||||||
sso = lib.shb.runNixOSTest {
|
sso = lib.shb.runNixOSTest {
|
||||||
name = "hledger_sso";
|
name = "hledger_sso";
|
||||||
|
|
||||||
nodes.server = { config, pkgs, ... }: {
|
nodes.server =
|
||||||
|
{ config, pkgs, ... }:
|
||||||
|
{
|
||||||
imports = [
|
imports = [
|
||||||
basic
|
basic
|
||||||
lib.shb.certs
|
lib.shb.certs
|
||||||
|
|
|
||||||
|
|
@ -2,16 +2,22 @@
|
||||||
let
|
let
|
||||||
commonTestScript = lib.shb.mkScripts {
|
commonTestScript = lib.shb.mkScripts {
|
||||||
hasSSL = { node, ... }: !(isNull node.config.shb.home-assistant.ssl);
|
hasSSL = { node, ... }: !(isNull node.config.shb.home-assistant.ssl);
|
||||||
waitForServices = { ... }: [
|
waitForServices =
|
||||||
|
{ ... }:
|
||||||
|
[
|
||||||
"home-assistant.service"
|
"home-assistant.service"
|
||||||
"nginx.service"
|
"nginx.service"
|
||||||
];
|
];
|
||||||
waitForPorts = { node, ... }: [
|
waitForPorts =
|
||||||
|
{ node, ... }:
|
||||||
|
[
|
||||||
8123
|
8123
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
basic = { config, ... }: {
|
basic =
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
imports = [
|
imports = [
|
||||||
lib.shb.baseModule
|
lib.shb.baseModule
|
||||||
../../modules/services/home-assistant.nix
|
../../modules/services/home-assistant.nix
|
||||||
|
|
@ -36,7 +42,9 @@ let
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
clientLogin = { config, ... }: {
|
clientLogin =
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
imports = [
|
imports = [
|
||||||
lib.shb.baseModule
|
lib.shb.baseModule
|
||||||
lib.shb.clientLoginModule
|
lib.shb.clientLoginModule
|
||||||
|
|
@ -50,7 +58,8 @@ let
|
||||||
test.login = {
|
test.login = {
|
||||||
startUrl = "http://${config.test.fqdn}";
|
startUrl = "http://${config.test.fqdn}";
|
||||||
testLoginWith = [
|
testLoginWith = [
|
||||||
{ nextPageExpect = [
|
{
|
||||||
|
nextPageExpect = [
|
||||||
"page.get_by_role('button', name=re.compile('Create my smart home')).click()"
|
"page.get_by_role('button', name=re.compile('Create my smart home')).click()"
|
||||||
|
|
||||||
"expect(page.get_by_text('Create user')).to_be_visible()"
|
"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()"
|
"page.get_by_role('button', name=re.compile('Finish')).click()"
|
||||||
|
|
||||||
"expect(page).to_have_title(re.compile('Overview'), timeout=15000)"
|
"expect(page).to_have_title(re.compile('Overview'), timeout=15000)"
|
||||||
]; }
|
];
|
||||||
|
}
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
https = { config, ...}: {
|
https =
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
shb.home-assistant = {
|
shb.home-assistant = {
|
||||||
ssl = config.shb.certs.certs.selfsigned.n;
|
ssl = config.shb.certs.certs.selfsigned.n;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
ldap = { config, ... }: {
|
ldap =
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
shb.home-assistant = {
|
shb.home-assistant = {
|
||||||
ldap = {
|
ldap = {
|
||||||
enable = true;
|
enable = true;
|
||||||
|
|
@ -95,7 +109,9 @@ let
|
||||||
# };
|
# };
|
||||||
# };
|
# };
|
||||||
|
|
||||||
voice = { config, ... }: {
|
voice =
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
# For now, verifying the packages can build is good enough.
|
# For now, verifying the packages can build is good enough.
|
||||||
environment.systemPackages = [
|
environment.systemPackages = [
|
||||||
config.services.wyoming.piper.package
|
config.services.wyoming.piper.package
|
||||||
|
|
@ -169,7 +185,9 @@ in
|
||||||
backup = lib.shb.runNixOSTest {
|
backup = lib.shb.runNixOSTest {
|
||||||
name = "homeassistant_backup";
|
name = "homeassistant_backup";
|
||||||
|
|
||||||
nodes.server = { config, ... }: {
|
nodes.server =
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
imports = [
|
imports = [
|
||||||
basic
|
basic
|
||||||
(lib.shb.backup config.shb.home-assistant.backup)
|
(lib.shb.backup config.shb.home-assistant.backup)
|
||||||
|
|
|
||||||
|
|
@ -5,12 +5,25 @@ let
|
||||||
|
|
||||||
commonTestScript = lib.shb.accessScript {
|
commonTestScript = lib.shb.accessScript {
|
||||||
hasSSL = { node, ... }: !(isNull node.config.shb.immich.ssl);
|
hasSSL = { node, ... }: !(isNull node.config.shb.immich.ssl);
|
||||||
waitForServices = { ... }: [ "immich-server.service" "postgresql.service" "nginx.service" ];
|
waitForServices =
|
||||||
waitForPorts = { ... }: [ 2283 80 ];
|
{ ... }:
|
||||||
|
[
|
||||||
|
"immich-server.service"
|
||||||
|
"postgresql.service"
|
||||||
|
"nginx.service"
|
||||||
|
];
|
||||||
|
waitForPorts =
|
||||||
|
{ ... }:
|
||||||
|
[
|
||||||
|
2283
|
||||||
|
80
|
||||||
|
];
|
||||||
waitForUrls = { proto_fqdn, ... }: [ "${proto_fqdn}" ];
|
waitForUrls = { proto_fqdn, ... }: [ "${proto_fqdn}" ];
|
||||||
};
|
};
|
||||||
|
|
||||||
base = { config, ... }: {
|
base =
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
imports = [
|
imports = [
|
||||||
lib.shb.baseModule
|
lib.shb.baseModule
|
||||||
../../modules/services/immich.nix
|
../../modules/services/immich.nix
|
||||||
|
|
@ -34,13 +47,17 @@ let
|
||||||
environment.systemPackages = [ pkgs.curl ];
|
environment.systemPackages = [ pkgs.curl ];
|
||||||
};
|
};
|
||||||
|
|
||||||
basic = { config, ... }: {
|
basic =
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
imports = [ base ];
|
imports = [ base ];
|
||||||
|
|
||||||
test.hasSSL = false;
|
test.hasSSL = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
https = { config, ... }: {
|
https =
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
imports = [
|
imports = [
|
||||||
base
|
base
|
||||||
lib.shb.certs
|
lib.shb.certs
|
||||||
|
|
@ -50,14 +67,18 @@ let
|
||||||
shb.immich.ssl = config.shb.certs.certs.selfsigned.n;
|
shb.immich.ssl = config.shb.certs.certs.selfsigned.n;
|
||||||
};
|
};
|
||||||
|
|
||||||
backup = { config, ... }: {
|
backup =
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
imports = [
|
imports = [
|
||||||
https
|
https
|
||||||
(lib.shb.backup config.shb.immich.backup)
|
(lib.shb.backup config.shb.immich.backup)
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
sso = { config, ... }: {
|
sso =
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
imports = [
|
imports = [
|
||||||
https
|
https
|
||||||
lib.shb.ldap
|
lib.shb.ldap
|
||||||
|
|
@ -135,10 +156,18 @@ in
|
||||||
nodes.server = 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);
|
hasSSL = args: !(isNull args.node.config.shb.immich.ssl);
|
||||||
waitForServices = args: [ "immich-server.service" "postgresql.service" "nginx.service" ];
|
waitForServices = args: [
|
||||||
waitForPorts = args: [ 2283 80 ];
|
"immich-server.service"
|
||||||
|
"postgresql.service"
|
||||||
|
"nginx.service"
|
||||||
|
];
|
||||||
|
waitForPorts = args: [
|
||||||
|
2283
|
||||||
|
80
|
||||||
|
];
|
||||||
waitForUrls = args: [ "${args.proto_fqdn}" ];
|
waitForUrls = args: [ "${args.proto_fqdn}" ];
|
||||||
}).backup;
|
}).backup;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -4,17 +4,25 @@ let
|
||||||
|
|
||||||
commonTestScript = lib.shb.mkScripts {
|
commonTestScript = lib.shb.mkScripts {
|
||||||
hasSSL = { node, ... }: !(isNull node.config.shb.jellyfin.ssl);
|
hasSSL = { node, ... }: !(isNull node.config.shb.jellyfin.ssl);
|
||||||
waitForServices = { ... }: [
|
waitForServices =
|
||||||
|
{ ... }:
|
||||||
|
[
|
||||||
"jellyfin.service"
|
"jellyfin.service"
|
||||||
"nginx.service"
|
"nginx.service"
|
||||||
];
|
];
|
||||||
waitForPorts = { node, ... }: [
|
waitForPorts =
|
||||||
|
{ node, ... }:
|
||||||
|
[
|
||||||
port
|
port
|
||||||
];
|
];
|
||||||
waitForUrls = { proto_fqdn, ... }: [
|
waitForUrls =
|
||||||
|
{ proto_fqdn, ... }:
|
||||||
|
[
|
||||||
"${proto_fqdn}/System/Info/Public"
|
"${proto_fqdn}/System/Info/Public"
|
||||||
];
|
];
|
||||||
extraScript = { node, ... }: ''
|
extraScript =
|
||||||
|
{ node, ... }:
|
||||||
|
''
|
||||||
headers = unline_with(" ", """
|
headers = unline_with(" ", """
|
||||||
-H 'Content-Type: application/json'
|
-H 'Content-Type: application/json'
|
||||||
-H 'Authorization: MediaBrowser Client="Android TV", Device="Nvidia Shield", DeviceId="ZQ9YQHHrUzk24vV", Version="0.15.3"'
|
-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 = [
|
imports = [
|
||||||
lib.shb.baseModule
|
lib.shb.baseModule
|
||||||
../../modules/services/jellyfin.nix
|
../../modules/services/jellyfin.nix
|
||||||
|
|
@ -65,7 +75,9 @@ let
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
clientLogin = { config, ... }: {
|
clientLogin =
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
imports = [
|
imports = [
|
||||||
lib.shb.clientLoginModule
|
lib.shb.clientLoginModule
|
||||||
];
|
];
|
||||||
|
|
@ -103,7 +115,9 @@ let
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
https = { config, ... }: {
|
https =
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
shb.jellyfin = {
|
shb.jellyfin = {
|
||||||
ssl = config.shb.certs.certs.selfsigned.n;
|
ssl = config.shb.certs.certs.selfsigned.n;
|
||||||
};
|
};
|
||||||
|
|
@ -112,7 +126,9 @@ let
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
ldap = { config, ... }: {
|
ldap =
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
shb.jellyfin = {
|
shb.jellyfin = {
|
||||||
ldap = {
|
ldap = {
|
||||||
enable = true;
|
enable = true;
|
||||||
|
|
@ -129,7 +145,9 @@ let
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
sso = { config, ... }: {
|
sso =
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
shb.jellyfin = {
|
shb.jellyfin = {
|
||||||
sso = {
|
sso = {
|
||||||
enable = true;
|
enable = true;
|
||||||
|
|
@ -150,7 +168,10 @@ let
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
jellyfinTest = name: { nodes, testScript }: lib.shb.runNixOSTest {
|
jellyfinTest =
|
||||||
|
name:
|
||||||
|
{ nodes, testScript }:
|
||||||
|
lib.shb.runNixOSTest {
|
||||||
name = "jellyfin_${name}";
|
name = "jellyfin_${name}";
|
||||||
|
|
||||||
interactive.nodes.server = {
|
interactive.nodes.server = {
|
||||||
|
|
@ -180,7 +201,9 @@ in
|
||||||
};
|
};
|
||||||
|
|
||||||
backup = jellyfinTest "backup" {
|
backup = jellyfinTest "backup" {
|
||||||
nodes.server = { config, ... }: {
|
nodes.server =
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
imports = [
|
imports = [
|
||||||
basic
|
basic
|
||||||
(lib.shb.backup config.shb.jellyfin.backup)
|
(lib.shb.backup config.shb.jellyfin.backup)
|
||||||
|
|
@ -201,7 +224,9 @@ in
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
nodes.client = { config, lib, ... }: {
|
nodes.client =
|
||||||
|
{ config, lib, ... }:
|
||||||
|
{
|
||||||
imports = [
|
imports = [
|
||||||
lib.shb.baseModule
|
lib.shb.baseModule
|
||||||
clientLogin
|
clientLogin
|
||||||
|
|
@ -226,7 +251,9 @@ in
|
||||||
};
|
};
|
||||||
|
|
||||||
sso = jellyfinTest "sso" {
|
sso = jellyfinTest "sso" {
|
||||||
nodes.server = { config, pkgs, ... }: {
|
nodes.server =
|
||||||
|
{ config, pkgs, ... }:
|
||||||
|
{
|
||||||
imports = [
|
imports = [
|
||||||
basic
|
basic
|
||||||
lib.shb.certs
|
lib.shb.certs
|
||||||
|
|
|
||||||
|
|
@ -7,19 +7,25 @@ let
|
||||||
|
|
||||||
commonTestScript = testLib.mkScripts {
|
commonTestScript = testLib.mkScripts {
|
||||||
hasSSL = { node, ... }: !(isNull node.config.shb.karakeep.ssl);
|
hasSSL = { node, ... }: !(isNull node.config.shb.karakeep.ssl);
|
||||||
waitForServices = { ... }: [
|
waitForServices =
|
||||||
|
{ ... }:
|
||||||
|
[
|
||||||
"karakeep-init.service"
|
"karakeep-init.service"
|
||||||
"karakeep-browser.service"
|
"karakeep-browser.service"
|
||||||
"karakeep-web.service"
|
"karakeep-web.service"
|
||||||
"karakeep-workers.service"
|
"karakeep-workers.service"
|
||||||
"nginx.service"
|
"nginx.service"
|
||||||
];
|
];
|
||||||
waitForPorts = { node, ... }: [
|
waitForPorts =
|
||||||
|
{ node, ... }:
|
||||||
|
[
|
||||||
node.config.shb.karakeep.port
|
node.config.shb.karakeep.port
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
basic = { config, ... }: {
|
basic =
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
imports = [
|
imports = [
|
||||||
testLib.baseModule
|
testLib.baseModule
|
||||||
../../modules/blocks/hardcodedsecret.nix
|
../../modules/blocks/hardcodedsecret.nix
|
||||||
|
|
@ -53,13 +59,17 @@ let
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
https = { config, ... }: {
|
https =
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
shb.karakeep = {
|
shb.karakeep = {
|
||||||
ssl = config.shb.certs.certs.selfsigned.n;
|
ssl = config.shb.certs.certs.selfsigned.n;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
ldap = { config, ... }: {
|
ldap =
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
shb.karakeep = {
|
shb.karakeep = {
|
||||||
ldap = {
|
ldap = {
|
||||||
userGroup = "user_group";
|
userGroup = "user_group";
|
||||||
|
|
@ -67,7 +77,9 @@ let
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
clientLoginSso = { config, ... }: {
|
clientLoginSso =
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
imports = [
|
imports = [
|
||||||
testLib.baseModule
|
testLib.baseModule
|
||||||
testLib.clientLoginModule
|
testLib.clientLoginModule
|
||||||
|
|
@ -85,36 +97,62 @@ let
|
||||||
passwordFieldLabelRegex = "Password";
|
passwordFieldLabelRegex = "Password";
|
||||||
loginButtonNameRegex = "[sS]ign [iI]n";
|
loginButtonNameRegex = "[sS]ign [iI]n";
|
||||||
testLoginWith = [
|
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)"
|
"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()"
|
"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_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_role('button', name=re.compile('Sign In'))).not_to_be_visible()"
|
||||||
"expect(page.get_by_text('new item')).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)"
|
"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()"
|
"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_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_role('button', name=re.compile('Sign In'))).not_to_be_visible()"
|
||||||
"expect(page.get_by_text('new item')).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)"
|
"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?
|
# "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)"
|
"expect(page.get_by_text(re.compile('login failed'))).to_be_visible(timeout=10000)"
|
||||||
]; }
|
];
|
||||||
|
}
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
sso = { config, ... }: {
|
sso =
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
shb.karakeep = {
|
shb.karakeep = {
|
||||||
sso = {
|
sso = {
|
||||||
enable = true;
|
enable = true;
|
||||||
|
|
@ -153,7 +191,9 @@ in
|
||||||
backup = pkgs.testers.runNixOSTest {
|
backup = pkgs.testers.runNixOSTest {
|
||||||
name = "karakeep_backup";
|
name = "karakeep_backup";
|
||||||
|
|
||||||
nodes.server = { config, ... }: {
|
nodes.server =
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
imports = [
|
imports = [
|
||||||
basic
|
basic
|
||||||
(testLib.backup config.shb.karakeep.backup)
|
(testLib.backup config.shb.karakeep.backup)
|
||||||
|
|
@ -191,7 +231,9 @@ in
|
||||||
|
|
||||||
virtualisation.memorySize = 4096;
|
virtualisation.memorySize = 4096;
|
||||||
};
|
};
|
||||||
nodes.server = { config, pkgs, ... }: {
|
nodes.server =
|
||||||
|
{ config, pkgs, ... }:
|
||||||
|
{
|
||||||
imports = [
|
imports = [
|
||||||
basic
|
basic
|
||||||
testLib.certs
|
testLib.certs
|
||||||
|
|
|
||||||
|
|
@ -4,15 +4,21 @@ let
|
||||||
|
|
||||||
commonTestScript = lib.shb.accessScript {
|
commonTestScript = lib.shb.accessScript {
|
||||||
hasSSL = { node, ... }: !(isNull node.config.shb.monitoring.ssl);
|
hasSSL = { node, ... }: !(isNull node.config.shb.monitoring.ssl);
|
||||||
waitForServices = { ... }: [
|
waitForServices =
|
||||||
|
{ ... }:
|
||||||
|
[
|
||||||
"grafana.service"
|
"grafana.service"
|
||||||
];
|
];
|
||||||
waitForPorts = { node, ... }: [
|
waitForPorts =
|
||||||
|
{ node, ... }:
|
||||||
|
[
|
||||||
node.config.shb.monitoring.grafanaPort
|
node.config.shb.monitoring.grafanaPort
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
basic = { config, ... }: {
|
basic =
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
test = {
|
test = {
|
||||||
subdomain = "g";
|
subdomain = "g";
|
||||||
};
|
};
|
||||||
|
|
@ -36,7 +42,9 @@ let
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
https = { config, ...}: {
|
https =
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
shb.monitoring = {
|
shb.monitoring = {
|
||||||
ssl = config.shb.certs.certs.selfsigned.n;
|
ssl = config.shb.certs.certs.selfsigned.n;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -6,14 +6,25 @@ let
|
||||||
|
|
||||||
commonTestScript = lib.shb.mkScripts {
|
commonTestScript = lib.shb.mkScripts {
|
||||||
hasSSL = { node, ... }: !(isNull node.config.shb.nextcloud.ssl);
|
hasSSL = { node, ... }: !(isNull node.config.shb.nextcloud.ssl);
|
||||||
waitForServices = { ... }: [
|
waitForServices =
|
||||||
|
{ ... }:
|
||||||
|
[
|
||||||
"phpfpm-nextcloud.service"
|
"phpfpm-nextcloud.service"
|
||||||
"nginx.service"
|
"nginx.service"
|
||||||
];
|
];
|
||||||
waitForUnixSocket = { node, ... }: [
|
waitForUnixSocket =
|
||||||
|
{ node, ... }:
|
||||||
|
[
|
||||||
node.config.services.phpfpm.pools.nextcloud.socket
|
node.config.services.phpfpm.pools.nextcloud.socket
|
||||||
];
|
];
|
||||||
extraScript = { node, fqdn, proto_fqdn, ... }: ''
|
extraScript =
|
||||||
|
{
|
||||||
|
node,
|
||||||
|
fqdn,
|
||||||
|
proto_fqdn,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
''
|
||||||
with subtest("fails with incorrect authentication"):
|
with subtest("fails with incorrect authentication"):
|
||||||
client.fail(
|
client.fail(
|
||||||
"curl -f -s --location -X PROPFIND"
|
"curl -f -s --location -X PROPFIND"
|
||||||
|
|
@ -87,7 +98,9 @@ let
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
basic = { config, ... }: {
|
basic =
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
imports = [
|
imports = [
|
||||||
lib.shb.baseModule
|
lib.shb.baseModule
|
||||||
../../modules/services/nextcloud-server.nix
|
../../modules/services/nextcloud-server.nix
|
||||||
|
|
@ -119,7 +132,9 @@ let
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
clientLogin = { config, ... }: {
|
clientLogin =
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
imports = [
|
imports = [
|
||||||
lib.shb.baseModule
|
lib.shb.baseModule
|
||||||
lib.shb.clientLoginModule
|
lib.shb.clientLoginModule
|
||||||
|
|
@ -136,20 +151,30 @@ let
|
||||||
passwordFieldLabelRegex = "^ *[Pp]assword";
|
passwordFieldLabelRegex = "^ *[Pp]assword";
|
||||||
loginButtonNameRegex = "[Ll]og [Ii]n";
|
loginButtonNameRegex = "[Ll]og [Ii]n";
|
||||||
testLoginWith = [
|
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_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.get_by_role('button', name=re.compile('[Ll]og [Ii]n'))).not_to_be_visible()"
|
||||||
"expect(page).to_have_title(re.compile('Dashboard'))"
|
"expect(page).to_have_title(re.compile('Dashboard'))"
|
||||||
]; }
|
];
|
||||||
|
}
|
||||||
# Failure is after so we're not throttled too much.
|
# 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()"
|
"expect(page.get_by_text('Wrong login or password')).to_be_visible()"
|
||||||
]; }
|
];
|
||||||
|
}
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
clientLdapLogin = { config, ... }: {
|
clientLdapLogin =
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
imports = [
|
imports = [
|
||||||
lib.shb.baseModule
|
lib.shb.baseModule
|
||||||
lib.shb.clientLoginModule
|
lib.shb.clientLoginModule
|
||||||
|
|
@ -166,27 +191,45 @@ let
|
||||||
passwordFieldLabelRegex = "^ *[Pp]assword";
|
passwordFieldLabelRegex = "^ *[Pp]assword";
|
||||||
loginButtonNameRegex = "[Ll]og [Ii]n";
|
loginButtonNameRegex = "[Ll]og [Ii]n";
|
||||||
testLoginWith = [
|
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_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.get_by_role('button', name=re.compile('[Ll]og [Ii]n'))).not_to_be_visible()"
|
||||||
"expect(page).to_have_title(re.compile('Dashboard'))"
|
"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()"
|
"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_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.get_by_role('button', name=re.compile('[Ll]og [Ii]n'))).not_to_be_visible()"
|
||||||
"expect(page).to_have_title(re.compile('Dashboard'))"
|
"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()"
|
"expect(page.get_by_text('Wrong login or password')).to_be_visible()"
|
||||||
]; }
|
];
|
||||||
|
}
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
clientSsoLogin = { config, ... }: {
|
clientSsoLogin =
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
imports = [
|
imports = [
|
||||||
lib.shb.baseModule
|
lib.shb.baseModule
|
||||||
lib.shb.clientLoginModule
|
lib.shb.clientLoginModule
|
||||||
|
|
@ -211,36 +254,62 @@ let
|
||||||
passwordFieldSelector = "get_by_label(\"Password *\")";
|
passwordFieldSelector = "get_by_label(\"Password *\")";
|
||||||
loginButtonNameRegex = "[sS]ign [iI]n";
|
loginButtonNameRegex = "[sS]ign [iI]n";
|
||||||
testLoginWith = [
|
testLoginWith = [
|
||||||
{ username = "alice"; password = "AlicePassword"; nextPageExpect = [
|
{
|
||||||
|
username = "alice";
|
||||||
|
password = "AlicePassword";
|
||||||
|
nextPageExpect = [
|
||||||
"page.get_by_role('button', name=re.compile('Accept')).click()"
|
"page.get_by_role('button', name=re.compile('Accept')).click()"
|
||||||
"expect(page).to_have_title(re.compile('Dashboard'))"
|
"expect(page).to_have_title(re.compile('Dashboard'))"
|
||||||
"page.goto('https://${config.test.fqdn}/settings/admin')"
|
"page.goto('https://${config.test.fqdn}/settings/admin')"
|
||||||
"expect(page.get_by_text('Access forbidden')).to_be_visible()"
|
"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()"
|
"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()"
|
"page.get_by_role('button', name=re.compile('Accept')).click()"
|
||||||
"expect(page).to_have_title(re.compile('Dashboard'))"
|
"expect(page).to_have_title(re.compile('Dashboard'))"
|
||||||
"page.goto('https://${config.test.fqdn}/settings/admin')"
|
"page.goto('https://${config.test.fqdn}/settings/admin')"
|
||||||
"expect(page.get_by_text('Access forbidden')).not_to_be_visible()"
|
"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()"
|
"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()"
|
"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()"
|
"page.get_by_role('button', name=re.compile('Accept')).click()"
|
||||||
"expect(page.get_by_text('not member of the allowed groups')).to_be_visible()"
|
"expect(page.get_by_text('not member of the allowed groups')).to_be_visible()"
|
||||||
]; }
|
];
|
||||||
|
}
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
https = { config, ...}: {
|
https =
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
shb.nextcloud = {
|
shb.nextcloud = {
|
||||||
ssl = config.shb.certs.certs.selfsigned.n;
|
ssl = config.shb.certs.certs.selfsigned.n;
|
||||||
|
|
||||||
|
|
@ -248,7 +317,9 @@ let
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
ldap = { config, ... }: {
|
ldap =
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
shb.nextcloud = {
|
shb.nextcloud = {
|
||||||
apps.ldap = {
|
apps.ldap = {
|
||||||
enable = true;
|
enable = true;
|
||||||
|
|
@ -266,7 +337,8 @@ let
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
sso = { config, ... }:
|
sso =
|
||||||
|
{ config, ... }:
|
||||||
{
|
{
|
||||||
shb.nextcloud = {
|
shb.nextcloud = {
|
||||||
apps.ldap = {
|
apps.ldap = {
|
||||||
|
|
@ -299,7 +371,9 @@ let
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
previewgenerator = { config, ...}: {
|
previewgenerator =
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
systemd.tmpfiles.rules = [
|
systemd.tmpfiles.rules = [
|
||||||
"d '/srv/nextcloud' 0750 nextcloud nextcloud - -"
|
"d '/srv/nextcloud' 0750 nextcloud nextcloud - -"
|
||||||
];
|
];
|
||||||
|
|
@ -323,7 +397,9 @@ let
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
memories = { config, ...}: {
|
memories =
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
systemd.tmpfiles.rules = [
|
systemd.tmpfiles.rules = [
|
||||||
"d '/srv/nextcloud' 0750 nextcloud nextcloud - -"
|
"d '/srv/nextcloud' 0750 nextcloud nextcloud - -"
|
||||||
];
|
];
|
||||||
|
|
@ -334,7 +410,9 @@ let
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
recognize = { config, ...}: {
|
recognize =
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
systemd.tmpfiles.rules = [
|
systemd.tmpfiles.rules = [
|
||||||
"d '/srv/nextcloud' 0750 nextcloud nextcloud - -"
|
"d '/srv/nextcloud' 0750 nextcloud nextcloud - -"
|
||||||
];
|
];
|
||||||
|
|
@ -344,13 +422,16 @@ let
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
prometheus = { config, ... }: {
|
prometheus =
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
shb.nextcloud = {
|
shb.nextcloud = {
|
||||||
phpFpmPrometheusExporter.enable = true;
|
phpFpmPrometheusExporter.enable = true;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
prometheusTestScript = { nodes, ... }:
|
prometheusTestScript =
|
||||||
|
{ nodes, ... }:
|
||||||
''
|
''
|
||||||
server.wait_for_open_unix_socket("${nodes.server.services.phpfpm.pools.nextcloud.socket}")
|
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})
|
server.wait_for_open_port(${toString nodes.server.services.prometheus.exporters.php-fpm.port})
|
||||||
|
|
@ -392,7 +473,14 @@ in
|
||||||
nodes.client = { };
|
nodes.client = { };
|
||||||
|
|
||||||
testScript = commonTestScript.access.override {
|
testScript = commonTestScript.access.override {
|
||||||
extraScript = { node, fqdn, proto_fqdn, ... }: ''
|
extraScript =
|
||||||
|
{
|
||||||
|
node,
|
||||||
|
fqdn,
|
||||||
|
proto_fqdn,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
''
|
||||||
import time
|
import time
|
||||||
|
|
||||||
def find_in_logs(unit, text):
|
def find_in_logs(unit, text):
|
||||||
|
|
@ -419,7 +507,9 @@ in
|
||||||
backup = lib.shb.runNixOSTest {
|
backup = lib.shb.runNixOSTest {
|
||||||
name = "nextcloud_backup";
|
name = "nextcloud_backup";
|
||||||
|
|
||||||
nodes.server = { config, ... }: {
|
nodes.server =
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
imports = [
|
imports = [
|
||||||
basic
|
basic
|
||||||
(lib.shb.backup config.shb.nextcloud.backup)
|
(lib.shb.backup config.shb.nextcloud.backup)
|
||||||
|
|
@ -522,7 +612,9 @@ in
|
||||||
ldap = lib.shb.runNixOSTest {
|
ldap = lib.shb.runNixOSTest {
|
||||||
name = "nextcloud_ldap";
|
name = "nextcloud_ldap";
|
||||||
|
|
||||||
nodes.server = { config, ... }: {
|
nodes.server =
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
imports = [
|
imports = [
|
||||||
basic
|
basic
|
||||||
lib.shb.certs
|
lib.shb.certs
|
||||||
|
|
@ -544,7 +636,9 @@ in
|
||||||
sso = lib.shb.runNixOSTest {
|
sso = lib.shb.runNixOSTest {
|
||||||
name = "nextcloud_sso";
|
name = "nextcloud_sso";
|
||||||
|
|
||||||
nodes.server = { config, ... }: {
|
nodes.server =
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
imports = [
|
imports = [
|
||||||
basic
|
basic
|
||||||
lib.shb.certs
|
lib.shb.certs
|
||||||
|
|
@ -552,22 +646,28 @@ in
|
||||||
lib.shb.ldap
|
lib.shb.ldap
|
||||||
(lib.shb.sso config.shb.certs.certs.selfsigned.n)
|
(lib.shb.sso config.shb.certs.certs.selfsigned.n)
|
||||||
sso
|
sso
|
||||||
({ config, ... }: {
|
(
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
networking.hosts = {
|
networking.hosts = {
|
||||||
"127.0.0.1" = [ config.test.fqdn ];
|
"127.0.0.1" = [ config.test.fqdn ];
|
||||||
};
|
};
|
||||||
})
|
}
|
||||||
|
)
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
nodes.client = {
|
nodes.client = {
|
||||||
imports = [
|
imports = [
|
||||||
clientSsoLogin
|
clientSsoLogin
|
||||||
({ config, ... }: {
|
(
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
networking.hosts = {
|
networking.hosts = {
|
||||||
"192.168.1.2" = [ config.test.fqdn ];
|
"192.168.1.2" = [ config.test.fqdn ];
|
||||||
};
|
};
|
||||||
})
|
}
|
||||||
|
)
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -577,7 +677,9 @@ in
|
||||||
prometheus = lib.shb.runNixOSTest {
|
prometheus = lib.shb.runNixOSTest {
|
||||||
name = "nextcloud_prometheus";
|
name = "nextcloud_prometheus";
|
||||||
|
|
||||||
nodes.server = { config, ... }: {
|
nodes.server =
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
imports = [
|
imports = [
|
||||||
basic
|
basic
|
||||||
prometheus
|
prometheus
|
||||||
|
|
|
||||||
|
|
@ -4,16 +4,22 @@ let
|
||||||
|
|
||||||
commonTestScript = lib.shb.mkScripts {
|
commonTestScript = lib.shb.mkScripts {
|
||||||
hasSSL = { node, ... }: !(isNull node.config.shb.open-webui.ssl);
|
hasSSL = { node, ... }: !(isNull node.config.shb.open-webui.ssl);
|
||||||
waitForServices = { ... }: [
|
waitForServices =
|
||||||
|
{ ... }:
|
||||||
|
[
|
||||||
"open-webui.service"
|
"open-webui.service"
|
||||||
"nginx.service"
|
"nginx.service"
|
||||||
];
|
];
|
||||||
waitForPorts = { node, ... }: [
|
waitForPorts =
|
||||||
|
{ node, ... }:
|
||||||
|
[
|
||||||
node.config.shb.open-webui.port
|
node.config.shb.open-webui.port
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
basic = { config, ... }: {
|
basic =
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
imports = [
|
imports = [
|
||||||
lib.shb.baseModule
|
lib.shb.baseModule
|
||||||
../../modules/blocks/hardcodedsecret.nix
|
../../modules/blocks/hardcodedsecret.nix
|
||||||
|
|
@ -34,7 +40,9 @@ let
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
https = { config, ... }: {
|
https =
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
shb.open-webui = {
|
shb.open-webui = {
|
||||||
ssl = config.shb.certs.certs.selfsigned.n;
|
ssl = config.shb.certs.certs.selfsigned.n;
|
||||||
};
|
};
|
||||||
|
|
@ -45,7 +53,9 @@ let
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
ldap = { config, ... }: {
|
ldap =
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
shb.open-webui = {
|
shb.open-webui = {
|
||||||
ldap = {
|
ldap = {
|
||||||
userGroup = "user_group";
|
userGroup = "user_group";
|
||||||
|
|
@ -54,7 +64,9 @@ let
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
clientLoginSso = { config, ... }: {
|
clientLoginSso =
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
imports = [
|
imports = [
|
||||||
lib.shb.baseModule
|
lib.shb.baseModule
|
||||||
lib.shb.clientLoginModule
|
lib.shb.clientLoginModule
|
||||||
|
|
@ -73,36 +85,62 @@ let
|
||||||
passwordFieldLabelRegex = "Password";
|
passwordFieldLabelRegex = "Password";
|
||||||
loginButtonNameRegex = "[sS]ign [iI]n";
|
loginButtonNameRegex = "[sS]ign [iI]n";
|
||||||
testLoginWith = [
|
testLoginWith = [
|
||||||
{ username = "alice"; password = "NotAlicePassword"; nextPageExpect = [
|
{
|
||||||
|
username = "alice";
|
||||||
|
password = "NotAlicePassword";
|
||||||
|
nextPageExpect = [
|
||||||
"expect(page.get_by_text(re.compile('[Ii]ncorrect'))).to_be_visible()"
|
"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()"
|
"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_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_role('button', name=re.compile('Sign In'))).not_to_be_visible()"
|
||||||
"expect(page.get_by_text('logged in')).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()"
|
"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()"
|
"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_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_role('button', name=re.compile('Sign In'))).not_to_be_visible()"
|
||||||
"expect(page.get_by_text('logged in')).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()"
|
"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()"
|
"page.get_by_role('button', name=re.compile('Accept')).click()"
|
||||||
"expect(page.get_by_text('unauthorized')).to_be_visible()"
|
"expect(page.get_by_text('unauthorized')).to_be_visible()"
|
||||||
]; }
|
];
|
||||||
|
}
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
sso = { config, ... }: {
|
sso =
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
shb.open-webui = {
|
shb.open-webui = {
|
||||||
sso = {
|
sso = {
|
||||||
enable = true;
|
enable = true;
|
||||||
|
|
@ -141,7 +179,9 @@ in
|
||||||
backup = lib.shb.runNixOSTest {
|
backup = lib.shb.runNixOSTest {
|
||||||
name = "open-webui_backup";
|
name = "open-webui_backup";
|
||||||
|
|
||||||
nodes.server = { config, ... }: {
|
nodes.server =
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
imports = [
|
imports = [
|
||||||
basic
|
basic
|
||||||
(lib.shb.backup config.shb.open-webui.backup)
|
(lib.shb.backup config.shb.open-webui.backup)
|
||||||
|
|
@ -176,7 +216,9 @@ in
|
||||||
clientLoginSso
|
clientLoginSso
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
nodes.server = { config, pkgs, ... }: {
|
nodes.server =
|
||||||
|
{ config, pkgs, ... }:
|
||||||
|
{
|
||||||
imports = [
|
imports = [
|
||||||
basic
|
basic
|
||||||
lib.shb.certs
|
lib.shb.certs
|
||||||
|
|
|
||||||
|
|
@ -2,16 +2,22 @@
|
||||||
let
|
let
|
||||||
commonTestScript = lib.shb.mkScripts {
|
commonTestScript = lib.shb.mkScripts {
|
||||||
hasSSL = { node, ... }: !(isNull node.config.shb.pinchflat.ssl);
|
hasSSL = { node, ... }: !(isNull node.config.shb.pinchflat.ssl);
|
||||||
waitForServices = { ... }: [
|
waitForServices =
|
||||||
|
{ ... }:
|
||||||
|
[
|
||||||
"pinchflat.service"
|
"pinchflat.service"
|
||||||
"nginx.service"
|
"nginx.service"
|
||||||
];
|
];
|
||||||
waitForPorts = { node, ... }: [
|
waitForPorts =
|
||||||
|
{ node, ... }:
|
||||||
|
[
|
||||||
node.config.shb.pinchflat.port
|
node.config.shb.pinchflat.port
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
basic = { config, ... }: {
|
basic =
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
imports = [
|
imports = [
|
||||||
lib.shb.baseModule
|
lib.shb.baseModule
|
||||||
../../modules/blocks/hardcodedsecret.nix
|
../../modules/blocks/hardcodedsecret.nix
|
||||||
|
|
@ -45,7 +51,9 @@ let
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
clientLogin = { config, ... }: {
|
clientLogin =
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
imports = [
|
imports = [
|
||||||
lib.shb.baseModule
|
lib.shb.baseModule
|
||||||
lib.shb.clientLoginModule
|
lib.shb.clientLoginModule
|
||||||
|
|
@ -58,20 +66,28 @@ let
|
||||||
startUrl = "http://${config.test.fqdn}";
|
startUrl = "http://${config.test.fqdn}";
|
||||||
# There is no login without SSO integration.
|
# There is no login without SSO integration.
|
||||||
testLoginWith = [
|
testLoginWith = [
|
||||||
{ username = null; password = null; nextPageExpect = [
|
{
|
||||||
|
username = null;
|
||||||
|
password = null;
|
||||||
|
nextPageExpect = [
|
||||||
"expect(page.get_by_text('Create a media profile')).to_be_visible()"
|
"expect(page.get_by_text('Create a media profile')).to_be_visible()"
|
||||||
]; }
|
];
|
||||||
|
}
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
https = { config, ... }: {
|
https =
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
shb.pinchflat = {
|
shb.pinchflat = {
|
||||||
ssl = config.shb.certs.certs.selfsigned.n;
|
ssl = config.shb.certs.certs.selfsigned.n;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
ldap = { config, ... }: {
|
ldap =
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
shb.pinchflat = {
|
shb.pinchflat = {
|
||||||
ldap = {
|
ldap = {
|
||||||
enable = true;
|
enable = true;
|
||||||
|
|
@ -81,7 +97,9 @@ let
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
clientLoginSso = { config, ... }: {
|
clientLoginSso =
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
imports = [
|
imports = [
|
||||||
lib.shb.baseModule
|
lib.shb.baseModule
|
||||||
lib.shb.clientLoginModule
|
lib.shb.clientLoginModule
|
||||||
|
|
@ -96,33 +114,59 @@ let
|
||||||
passwordFieldLabelRegex = "Password";
|
passwordFieldLabelRegex = "Password";
|
||||||
loginButtonNameRegex = "[sS]ign [iI]n";
|
loginButtonNameRegex = "[sS]ign [iI]n";
|
||||||
testLoginWith = [
|
testLoginWith = [
|
||||||
{ username = "alice"; password = "NotAlicePassword"; nextPageExpect = [
|
{
|
||||||
|
username = "alice";
|
||||||
|
password = "NotAlicePassword";
|
||||||
|
nextPageExpect = [
|
||||||
"expect(page.get_by_text(re.compile('[Ii]ncorrect'))).to_be_visible()"
|
"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_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_role('button', name=re.compile('Sign In'))).not_to_be_visible()"
|
||||||
"expect(page.get_by_text('Create a media profile')).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()"
|
"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_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_role('button', name=re.compile('Sign In'))).not_to_be_visible()"
|
||||||
"expect(page.get_by_text('Create a media profile')).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()"
|
"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'))"
|
"expect(page).to_have_url(re.compile('.*/authenticated'))"
|
||||||
]; }
|
];
|
||||||
|
}
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
sso = { config, ... }: {
|
sso =
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
shb.pinchflat = {
|
shb.pinchflat = {
|
||||||
sso = {
|
sso = {
|
||||||
enable = true;
|
enable = true;
|
||||||
|
|
@ -152,7 +196,9 @@ in
|
||||||
backup = lib.shb.runNixOSTest {
|
backup = lib.shb.runNixOSTest {
|
||||||
name = "pinchflat_backup";
|
name = "pinchflat_backup";
|
||||||
|
|
||||||
nodes.server = { config, ... }: {
|
nodes.server =
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
imports = [
|
imports = [
|
||||||
basic
|
basic
|
||||||
(lib.shb.backup config.shb.pinchflat.backup)
|
(lib.shb.backup config.shb.pinchflat.backup)
|
||||||
|
|
@ -191,7 +237,9 @@ in
|
||||||
clientLoginSso
|
clientLoginSso
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
nodes.server = { config, pkgs, ... }: {
|
nodes.server =
|
||||||
|
{ config, pkgs, ... }:
|
||||||
|
{
|
||||||
imports = [
|
imports = [
|
||||||
basic
|
basic
|
||||||
lib.shb.certs
|
lib.shb.certs
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,15 @@
|
||||||
let
|
let
|
||||||
commonTestScript = lib.shb.mkScripts {
|
commonTestScript = lib.shb.mkScripts {
|
||||||
hasSSL = { node, ... }: !(isNull node.config.shb.vaultwarden.ssl);
|
hasSSL = { node, ... }: !(isNull node.config.shb.vaultwarden.ssl);
|
||||||
waitForServices = { ... }: [
|
waitForServices =
|
||||||
|
{ ... }:
|
||||||
|
[
|
||||||
"vaultwarden.service"
|
"vaultwarden.service"
|
||||||
"nginx.service"
|
"nginx.service"
|
||||||
];
|
];
|
||||||
waitForPorts = { node, ... }: [
|
waitForPorts =
|
||||||
|
{ node, ... }:
|
||||||
|
[
|
||||||
8222
|
8222
|
||||||
5432
|
5432
|
||||||
];
|
];
|
||||||
|
|
@ -17,7 +21,9 @@ let
|
||||||
# 4. go to the Vaultwarden /admin endpoint
|
# 4. go to the Vaultwarden /admin endpoint
|
||||||
# 5. create a Vaultwarden user
|
# 5. create a Vaultwarden user
|
||||||
# 6. now login with that new user to Vaultwarden
|
# 6. now login with that new user to Vaultwarden
|
||||||
extraScript = { node, proto_fqdn, ... }: ''
|
extraScript =
|
||||||
|
{ node, proto_fqdn, ... }:
|
||||||
|
''
|
||||||
with subtest("prelogin"):
|
with subtest("prelogin"):
|
||||||
response = curl(client, "", "${proto_fqdn}/identity/accounts/prelogin", data=unline_with("", """
|
response = curl(client, "", "${proto_fqdn}/identity/accounts/prelogin", data=unline_with("", """
|
||||||
{"email": "me@example.com"}
|
{"email": "me@example.com"}
|
||||||
|
|
@ -42,7 +48,9 @@ let
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
basic = { config, ... }: {
|
basic =
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
test = {
|
test = {
|
||||||
subdomain = "v";
|
subdomain = "v";
|
||||||
};
|
};
|
||||||
|
|
@ -64,7 +72,9 @@ let
|
||||||
# };
|
# };
|
||||||
};
|
};
|
||||||
|
|
||||||
https = { config, ... }: {
|
https =
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
shb.vaultwarden = {
|
shb.vaultwarden = {
|
||||||
ssl = config.shb.certs.certs.selfsigned.n;
|
ssl = config.shb.certs.certs.selfsigned.n;
|
||||||
};
|
};
|
||||||
|
|
@ -78,7 +88,9 @@ let
|
||||||
# # };
|
# # };
|
||||||
# };
|
# };
|
||||||
|
|
||||||
sso = { config, ... }: {
|
sso =
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
shb.vaultwarden = {
|
shb.vaultwarden = {
|
||||||
authEndpoint = "https://${config.shb.authelia.subdomain}.${config.shb.authelia.domain}";
|
authEndpoint = "https://${config.shb.authelia.subdomain}.${config.shb.authelia.domain}";
|
||||||
};
|
};
|
||||||
|
|
@ -142,7 +154,9 @@ in
|
||||||
sso = lib.shb.runNixOSTest {
|
sso = lib.shb.runNixOSTest {
|
||||||
name = "vaultwarden_sso";
|
name = "vaultwarden_sso";
|
||||||
|
|
||||||
nodes.server = { config, ... }: {
|
nodes.server =
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
imports = [
|
imports = [
|
||||||
lib.shb.baseModule
|
lib.shb.baseModule
|
||||||
../../modules/blocks/hardcodedsecret.nix
|
../../modules/blocks/hardcodedsecret.nix
|
||||||
|
|
@ -159,12 +173,16 @@ in
|
||||||
nodes.client = { };
|
nodes.client = { };
|
||||||
|
|
||||||
testScript = commonTestScript.access.override {
|
testScript = commonTestScript.access.override {
|
||||||
waitForPorts = { node, ... }: [
|
waitForPorts =
|
||||||
|
{ node, ... }:
|
||||||
|
[
|
||||||
8222
|
8222
|
||||||
5432
|
5432
|
||||||
9091
|
9091
|
||||||
];
|
];
|
||||||
extraScript = { node, proto_fqdn, ... }: ''
|
extraScript =
|
||||||
|
{ node, proto_fqdn, ... }:
|
||||||
|
''
|
||||||
with subtest("unauthenticated access is not granted to /admin"):
|
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")
|
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 {
|
backup = lib.shb.runNixOSTest {
|
||||||
name = "vaultwarden_backup";
|
name = "vaultwarden_backup";
|
||||||
|
|
||||||
nodes.server = { config, ... }: {
|
nodes.server =
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
imports = [
|
imports = [
|
||||||
lib.shb.baseModule
|
lib.shb.baseModule
|
||||||
../../modules/blocks/hardcodedsecret.nix
|
../../modules/blocks/hardcodedsecret.nix
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue