lets actually commit for once...
Some checks failed
Test action / kaas (push) Failing after 1s

This commit is contained in:
Chris Kruining 2025-11-20 00:05:34 +01:00
parent 169b62e6f3
commit 2d3da197ee
No known key found for this signature in database
GPG key ID: EB894A3560CCCAD2
13 changed files with 711 additions and 74 deletions

View file

@ -52,7 +52,7 @@ in {
};
emoji = {
package = pkgs.noto-fonts-emoji;
package = pkgs.noto-fonts-color-emoji;
name = "Noto Color Emoji";
};
};

View file

@ -1,6 +1,6 @@
{ config, lib, pkgs, namespace, system, inputs, ... }:
let
inherit (lib) mkIf mkEnableOption mkOption types toUpper toSentenceCase nameValuePair mapAttrs mapAttrs' concatMapAttrs filterAttrsRecursive listToAttrs imap0 head drop length literalExpression attrNames;
inherit (lib) mkIf mkEnableOption mkOption types toUpper toSentenceCase nameValuePair mapAttrs mapAttrs' concatMapAttrs concatMapStringsSep filterAttrsRecursive listToAttrs imap0 head drop length literalExpression attrNames;
inherit (lib.${namespace}.strings) toSnakeCase;
cfg = config.${namespace}.services.authentication.zitadel;
@ -334,6 +334,16 @@ in
concatMapAttrs (k: v: select (drop 1 keys) (callback k) (v.${key} or {})) set
;
append = attrList: set: set // (listToAttrs attrList);
forEach = src: key: set:
let
_key = concatMapStringsSep "_" (k: "\${item.${k}}") key;
in
{
forEach = "{ for item in ${src} : \"${_key}\" => item }";
}
// set;
config' = config;
# this is a nix package, the generated json file to be exact
@ -418,7 +428,7 @@ in
# Users
zitadel_human_user =
(cfg.organization
cfg.organization
|> select [ "user" ] (org: name: { email, userName, firstName, lastName, ... }:
{
inherit email userName firstName lastName;
@ -427,24 +437,20 @@ in
}
|> withRef "org" org
|> toResource "${org}_${name}"
))
// {
"extra_users" = {
for_each = lib.tfRef ''{
for user in local.extra_users :
"''${user.org}_''${user.name}" => user
}'';
org_id = lib.tfRef "local.orgs[each.value.org]";
user_name = lib.tfRef "each.value.name";
)
|> append
[
(forEach "local.extra_users" [ "org" "name" ] {
orgId = lib.tfRef "local.orgs[each.value.org]";
userName = lib.tfRef "each.value.name";
email = lib.tfRef "each.value.email";
first_name = lib.tfRef "each.value.firstName";
last_name = lib.tfRef "each.value.lastName";
firstName = lib.tfRef "each.value.firstName";
lastName = lib.tfRef "each.value.lastName";
is_email_verified = true;
};
}
isEmailVerified = true;
}
|> toResource "extraUsers")
]
;
# Global user roles
@ -708,6 +714,12 @@ in
restartUnits = [ "zitadelApplyTerraform.service" ];
};
};
templates = {
"users.yml" = {
};
};
};
};
}

View file

@ -10,13 +10,22 @@ in
};
config = mkIf cfg.enable {
programs.ssh.extraConfig = ''
Host beheer.hazelhof.nl
Port 222
User chris
AddressFamily inet
IdentityFile /home/chris/.ssh/id_ed25519
'';
services = {
borgbackup.jobs = {
media = {
paths = "/var/media/test";
encryption.mode = "none";
environment.BORG_SSH = "ssh -i /home/chris/.ssh/id_ed25519 -4";
repo = "ssh://chris@beheer.hazelhof.nl:222/media";
# environment.BORG_SSH = "ssh -4 -i /home/chris/.ssh/id_ed25519";
environment.BORG_UNKNOWN_UNENCRYPTED_REPO_ACCESS_IS_OK = "yes";
repo = "ssh://beheer.hazelhof.nl//media";
compression = "auto,zstd";
startAt = "daily";
};

View file

@ -1,9 +1,11 @@
{ pkgs, lib, namespace, config, ... }:
{ pkgs, lib, namespace, config, inputs, system, ... }:
let
inherit (lib) mkIf mkEnableOption mkOption;
inherit (lib.types) str;
cfg = config.${namespace}.services.media;
arr = ["radarr" ];
in
{
options.${namespace}.services.media = {
@ -60,47 +62,48 @@ in
"d '${cfg.path}/reiverr/config' 0700 ${cfg.user} ${cfg.group} - -"
"d '${cfg.path}/downloads/incomplete' 0700 ${cfg.user} ${cfg.group} - -"
"d '${cfg.path}/downloads/done' 0700 ${cfg.user} ${cfg.group} - -"
"d /var/lib/radarrApplyTerraform 0755 ${cfg.user} ${cfg.group} -"
];
#=========================================================================
# Services
#=========================================================================
services = let
arrService = {
enable = true;
openFirewall = true;
arr-services =
arr
|> lib.imap (i: service: {
name = service;
value = {
enable = true;
openFirewall = true;
settings = {
auth.AuthenticationMethod = "External";
};
};
environmentFiles = [
config.sops.templates."${service}/config.env".path
];
withPort = port: service: service // { settings.server.Port = builtins.toString port; };
settings = {
auth.authenticationMethod = "External";
withUserAndGroup = service: service // {
user = cfg.user;
group = cfg.group;
};
in {
radarr =
arrService
|> withPort 2001
|> withUserAndGroup;
sonarr =
arrService
|> withPort 2002
|> withUserAndGroup;
lidarr =
arrService
|> withPort 2003
|> withUserAndGroup;
prowlarr =
arrService
|> withPort 2004;
server = {
bindaddress = "0.0.0.0";
port = 2000 + i;
};
postgres = {
host = "localhost";
port = "5432";
user = service;
maindb = service;
logdb = service;
};
};
}
// (if service != "prowlarr" then { user = cfg.user; group = cfg.group; } else {});
})
|> lib.listToAttrs
;
in
arr-services // {
bazarr = {
enable = true;
openFirewall = true;
@ -146,6 +149,19 @@ in
group = cfg.group;
};
postgresql =
let
databases = arr |> lib.concatMap (s: [ s "${s}-log" ]);
in
{
enable = true;
ensureDatabases = arr;
ensureUsers = arr |> lib.map (service: {
name = service;
ensureDBOwnership = true;
});
};
caddy = {
enable = true;
virtualHosts = {
@ -156,6 +172,136 @@ in
};
};
systemd.services.radarrApplyTerraform =
let
# this is a nix package, the generated json file to be exact
terraformConfiguration = inputs.terranix.lib.terranixConfiguration {
inherit system;
modules = [
({ config, lib, ... }: {
config = {
variable = {
api_key = {
type = "string";
description = "Radarr api key";
};
};
terraform.required_providers.radarr = {
source = "devopsarr/radarr";
version = "2.2.0";
};
provider.radarr = {
url = "http://127.0.0.1:2001";
api_key = lib.tfRef "var.api_key";
};
resource = {
radarr_root_folder.local = {
path = "/var/media/movies";
};
};
};
})
];
};
in
{
description = "Radarr terraform apply";
wantedBy = [ "multi-user.target" ];
wants = [ "radarr.service" ];
script = ''
#!/usr/bin/env bash
if [ "$(systemctl is-active radarr)" != "active" ]; then
echo "Radarr is not running"
exit 1
fi
# Sleep for a bit to give radarr the chance to start up
sleep 5s
# Print the path to the source for easier debugging
echo "config location: ${terraformConfiguration}"
# Copy infra code into workspace
cp -f ${terraformConfiguration} config.tf.json
# Initialize OpenTofu
${lib.getExe pkgs.opentofu} init
# Run the infrastructure code
# ${lib.getExe pkgs.opentofu} plan -var-file='${config.sops.templates."radarr/config.tfvars".path}'
${lib.getExe pkgs.opentofu} apply -auto-approve -var-file='${config.sops.templates."radarr/config.tfvars".path}'
'';
serviceConfig = {
Type = "oneshot";
User = cfg.user;
Group = cfg.group;
WorkingDirectory = "/var/lib/radarrApplyTerraform";
EnvironmentFile = [
config.sops.templates."radarr/config.env".path
];
};
};
systemd.services.jellyfin.serviceConfig.killSignal = lib.mkForce "SIGKILL";
sops = {
secrets =
arr
|> lib.map (service: {
name = "${service}/apikey";
value = {
owner = cfg.user;
group = cfg.group;
restartUnits = [ "${service}.service" ];
};
})
|> lib.listToAttrs
;
templates =
let
apikeys =
arr
|> lib.map (service: {
name = "${service}/config.env";
value = {
owner = cfg.user;
group = cfg.group;
restartUnits = [ "${service}.service" ];
content = ''
${lib.toUpper service}__AUTH__APIKEY="${config.sops.placeholder."${service}/apikey"}"
'';
};
})
|> lib.listToAttrs;
tfvars =
arr
|> lib.map(service: {
name = "${service}/config.tfvars";
value = {
owner = cfg.user;
group = cfg.group;
restartUnits = [ "${service}ApplyTerraform.service" ];
content = ''
api_key = "${config.sops.placeholder."${service}/apikey"}"
'';
};
})
|> lib.listToAttrs;
in
apikeys // tfvars
;
};
};
}

View file

@ -103,7 +103,7 @@ in
type = "Radarr";
logo = "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/radarr.svg";
tag = "app";
url = "http://${config.networking.hostName}:${builtins.toString config.services.radarr.settings.server.port}";
url = "http://${config.networking.hostName}:2001";
target = "_blank";
}
@ -112,7 +112,7 @@ in
type = "Sonarr";
logo = "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/sonarr.svg";
tag = "app";
url = "http://${config.networking.hostName}:${builtins.toString config.services.sonarr.settings.server.port}";
url = "http://${config.networking.hostName}:2002";
target = "_blank";
}
@ -121,7 +121,7 @@ in
type = "Lidarr";
logo = "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/lidarr.svg";
tag = "app";
url = "http://${config.networking.hostName}:${builtins.toString config.services.lidarr.settings.server.port}";
url = "http://${config.networking.hostName}:2003";
target = "_blank";
}
@ -130,7 +130,7 @@ in
type = "Prowlarr";
logo = "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/prowlarr.svg";
tag = "app";
url = "http://${config.networking.hostName}:${builtins.toString config.services.prowlarr.settings.server.port}";
url = "http://${config.networking.hostName}:2004";
target = "_blank";
}

View file

@ -0,0 +1,214 @@
{ pkgs, config, lib, namespace, inputs, system, ... }:
let
inherit (builtins) toString;
inherit (lib) mkIf mkEnableOption mkOption types;
cfg = config.${namespace}.services.media.servarr;
in
{
options.${namespace}.services.media = {
servarr = mkOption {
type = types.attrsOf (types.submodule ({ name, ... }: {
options = {
enable = mkEnableOption "Enable ${name}";
debug = mkEnableOption "Use tofu plan instead of tofu apply for ${name} ";
port = mkOption {
type = types.port;
};
rootFolders = mkOption {
type = types.listOf types.str;
default = [];
};
};
}));
default = {};
};
};
config = {
services =
cfg
|> lib.mapAttrsToList (service: { enable, port, ... }: (mkIf enable {
"${service}" = {
enable = true;
openFirewall = true;
environmentFiles = [
config.sops.templates."${service}/config.env".path
];
settings = {
auth.authenticationMethod = "External";
server = {
bindaddress = "0.0.0.0";
port = port;
};
postgres = {
host = "localhost";
port = "5432";
user = service;
maindb = service;
logdb = service;
};
};
};
}))
|> lib.mergeAttrsList
|> (set: set // {
postgres = {
ensureDatabases = cfg |> lib.attrNames;
ensureUsers = cfg |> lib.attrNames |> lib.map (service: {
name = service;
ensureDBOwnership = true;
});
};
})
;
systemd =
cfg
|> lib.mapAttrsToList (service: { enable, debug, port, rootFolders, ... }: (mkIf enable {
tmpfiles.rules = [
"d /var/lib/${service}ApplyTerraform 0755 ${service} ${service} -"
];
services."${service}ApplyTerraform" =
let
terraformConfiguration = inputs.terranix.lib.terranixConfiguration {
inherit system;
modules = [
({ config, lib, ... }: {
config = {
variable = {
api_key = {
type = "string";
description = "${service} api key";
};
};
terraform.required_providers.${service} = {
source = "devopsarr/${service}";
version = "2.2.0";
};
provider.${service} = {
url = "http://127.0.0.1:${toString port}";
api_key = lib.tfRef "var.api_key";
};
resource = {
"${service}_root_folder" =
rootFolders
|> lib.imap (i: f: lib.nameValuePair "local${toString i}" { path = f; })
|> lib.listToAttrs
;
};
};
})
];
};
in
{
description = "${service} terraform apply";
wantedBy = [ "multi-user.target" ];
wants = [ "${service}.service" ];
script = ''
#!/usr/bin/env bash
# Sleep for a bit to give the service a chance to start up
sleep 5s
if [ "$(systemctl is-active ${service})" != "active" ]; then
echo "${service} is not running"
exit 1
fi
# Print the path to the source for easier debugging
echo "config location: ${terraformConfiguration}"
# Copy infra code into workspace
cp -f ${terraformConfiguration} config.tf.json
# Initialize OpenTofu
${lib.getExe pkgs.opentofu} init
# Run the infrastructure code
${lib.getExe pkgs.opentofu} \
${if debug then "plan" else "apply -auto-approve"} \
-var-file='${config.sops.templates."${service}/config.tfvars".path}'
'';
serviceConfig = {
Type = "oneshot";
User = service;
Group = service;
WorkingDirectory = "/var/lib/${service}ApplyTerraform";
EnvironmentFile = [
config.sops.templates."${service}/config.env".path
];
};
};
}))
|> lib.mergeAttrsList
;
users.users =
cfg
|> lib.mapAttrsToList (service: { enable, ... }: (mkIf enable {
"${service}".extraGroups = [ "media" ];
}))
|> lib.mergeAttrsList
;
sops =
cfg
|> lib.mapAttrsToList (service: { enable, ... }: (mkIf enable {
secrets."${service}/apikey" = {
owner = service;
group = service;
restartUnits = [ "${service}.service" ];
};
templates = {
"${service}/config.env" = {
owner = service;
group = service;
restartUnits = [ "${service}.service" ];
content = ''
${lib.toUpper service}__AUTH__APIKEY="${config.sops.placeholder."${service}/apikey"}"
'';
};
"${service}/config.tfvars" = {
owner = service;
group = service;
restartUnits = [ "${service}.service" ];
content = ''
api_key = "${config.sops.placeholder."${service}/apikey"}"
'';
};
};
}))
|> lib.mergeAttrsList
;
};
# cfg
# |> lib.mapAttrsToList (service: { enable, debug, port, rootFolders, ... }: (mkIf enable {
# # sops = {
# # };
# }))
# |> lib.mergeAttrsList
# ;
}

View file

@ -0,0 +1,25 @@
{ pkgs, config, lib, namespace, ... }:
let
inherit (builtins) toString;
inherit (lib) mkIf mkEnableOption;
cfg = config.${namespace}.services.observability.uptime-kuma;
in
{
options.${namespace}.services.observability.uptime-kuma = {
enable = mkEnableOption "enable uptime kuma";
};
config = mkIf cfg.enable {
services.uptime-kuma = {
enable = true;
settings = {
PORT = toString 9006;
HOST = "0.0.0.0";
};
};
networking.firewall.allowedTCPPorts = [ 9006 ];
};
}

View file

@ -1,13 +1,87 @@
{ pkgs, config, lib, namespace, ... }:
let
inherit (builtins) toString;
inherit (lib) mkIf mkEnableOption;
inherit (lib) mkIf mkEnableOption mkOption types getAttrs toUpper concatMapAttrsStringSep;
cfg = config.${namespace}.services.security.vaultwarden;
databaseProviderSqlite = types.submodule ({ ... }: {
options = {
type = mkOption {
type = types.enum [ "sqlite" ];
};
file = mkOption {
type = types.str;
description = '''';
};
};
});
databaseProviderPostgresql = types.submodule ({ ... }:
let
urlOptions = lib.${namespace}.options.mkUrlOptions {
host = {
description = ''
Hostname of the postgresql server
'';
};
port = {
default = 5432;
example = "5432";
description = ''
Port of the postgresql server
'';
};
protocol = mkOption {
default = "postgres";
example = "postgres";
};
};
in
{
options = {
type = mkOption {
type = types.enum [ "postgresql" ];
};
sslMode = mkOption {
type = types.enum [ "verify-ca" "verify-full" "require" "prefer" "allow" "disabled" ];
default = "verify-full";
example = "verify-ca";
description = ''
How to verify the server's ssl
| mode | eavesdropping protection | MITM protection | Statement |
|-------------|--------------------------|----------------------|---------------------------------------------------------------------------------------------------------------------------------------------|
| disable | No | No | I don't care about security, and I don't want to pay the overhead of encryption. |
| allow | Maybe | No | I don't care about security, but I will pay the overhead of encryption if the server insists on it. |
| prefer | Maybe | No | I don't care about encryption, but I wish to pay the overhead of encryption if the server supports it. |
| require | Yes | No | I want my data to be encrypted, and I accept the overhead. I trust that the network will make sure I always connect to the server I want. |
| verify-ca | Yes | Depends on CA policy | I want my data encrypted, and I accept the overhead. I want to be sure that I connect to a server that I trust. |
| verify-full | Yes | Yes | I want my data encrypted, and I accept the overhead. I want to be sure that I connect to a server I trust, and that it's the one I specify. |
[Source](https://www.postgresql.org/docs/current/libpq-ssl.html#LIBPQ-SSL-SSLMODE-STATEMENTS)
'';
};
} // (urlOptions |> getAttrs [ "protocol" "host" "port" ]);
});
in
{
options.${namespace}.services.security.vaultwarden = {
enable = mkEnableOption "enable vaultwarden";
database = mkOption {
type = types.oneOf [
(types.addCheck databaseProviderSqlite (x: x ? type && x.type == "sqlite"))
(types.addCheck databaseProviderPostgresql (x: x ? type && x.type == "postgresql"))
null
];
default = null;
description = '''';
};
};
config = mkIf cfg.enable {
@ -15,6 +89,8 @@ in
"d '/var/lib/vaultwarden' 0700 vaultwarden vaultwarden - -"
];
# systemd.services.vaultwarden.wants = [ "zitadelApplyTerraform.service" ];
services = {
vaultwarden = {
enable = true;
@ -26,8 +102,6 @@ in
SIGNUPS_ALLOWED = false;
DOMAIN = "https://vault.kruining.eu";
ADMIN_TOKEN = "";
DATABASE_URL = "postgres://localhost:5432/vaultwarden?sslmode=disable";
WEB_VAULT_ENABLED = true;
@ -41,9 +115,6 @@ in
SSO_ORGANIZATIONS_REVOCATION = true;
SSO_AUTHORITY = "https://auth.kruining.eu/";
SSO_SCOPES = "email profile offline_access";
SSO_AUDIENCE_TRUSTED = "^333297815511892227$";
SSO_CLIENT_ID = "335178854421299459";
SSO_CLIENT_SECRET = "";
ROCKET_ADDRESS = "::1";
ROCKET_PORT = 8222;
@ -53,10 +124,14 @@ in
SMTP_PORT = 587;
SMTP_SECURITY = "starttls";
SMTP_USERNAME = "chris@kruining.eu";
SMTP_PASSWORD = "";
SMTP_FROM = "chris@kruining.eu";
SMTP_FROM_NAME = "Chris' Vaultwarden";
};
environmentFile = [
"/var/lib/zitadel/clients/nix_ulmo_vaultwarden"
config.sops.templates."vaultwarden/config.env".path
];
};
postgresql = {
@ -89,5 +164,54 @@ in
};
};
};
sops = {
secrets = {
"vaultwarden/email" = {
owner = config.users.users.vaultwarden.name;
group = config.users.users.vaultwarden.name;
key = "email/chris_kruining_eu";
restartUnits = [ "vaultwarden.service" ];
};
};
templates = {
"vaultwarden/config.env" = {
content = ''
SMTP_PASSWORD='${config.sops.placeholder."vaultwarden/email"}';
'';
owner = config.users.users.vaultwarden.name;
group = config.users.groups.vaultwarden.name;
};
temp-db-output.content =
let
config =
cfg.database
|> ({ type, ... }@db:
if type == "sqlite" then
{ inherit (db) type file; }
else if type == "postgresql" then
{
inherit (db) type;
url = lib.${namespace}.strings.toUrl {
inherit (db) protocol host port;
path = "vaultwarden";
query = {
sslmode = db.sslMode;
};
};
}
else
{}
)
|> concatMapAttrsStringSep "\n" (n: v: "${toUpper n}=${v}")
;
in
''
# GENERATED VALUES
${config}
'';
};
};
};
}