From 2d3da197ee8e549b46a52266af22271a817127fd Mon Sep 17 00:00:00 2001 From: Chris Kruining Date: Thu, 20 Nov 2025 00:05:34 +0100 Subject: [PATCH] lets actually commit for once... --- .just/vars.just | 18 +- lib/options/default.nix | 38 ++++ lib/strings/default.nix | 26 ++- modules/home/themes/default.nix | 2 +- .../authentication/zitadel/default.nix | 48 ++-- .../nixos/services/backup/borg/default.nix | 13 +- modules/nixos/services/media/default.nix | 210 ++++++++++++++--- .../nixos/services/media/homer/default.nix | 8 +- .../nixos/services/media/servarr/default.nix | 214 ++++++++++++++++++ .../observability/uptime-kuma/default.nix | 25 ++ .../services/security/vaultwarden/default.nix | 138 ++++++++++- shells/default/default.nix | 2 + systems/x86_64-linux/ulmo/default.nix | 43 +++- 13 files changed, 711 insertions(+), 74 deletions(-) create mode 100644 lib/options/default.nix create mode 100644 modules/nixos/services/media/servarr/default.nix create mode 100644 modules/nixos/services/observability/uptime-kuma/default.nix diff --git a/.just/vars.just b/.just/vars.just index 944d7cf..d8bd181 100644 --- a/.just/vars.just +++ b/.just/vars.just @@ -1,19 +1,23 @@ +set unstable + base_path := invocation_directory() / "systems/x86_64-linux" -sops := "nix shell nixpkgs#sops --command sops" -yq := "nix shell nixpkgs#yq --command yq" +# sops := "nix shell nixpkgs#sops --command sops" +# yq := "nix shell nixpkgs#yq --command yq" +sops := "sops" +yq := "yq" @_default: just --list [doc('list all vars of the target machine')] list machine: - {{ sops }} decrypt {{ base_path }}/{{ machine }}/secrets.yml + sops decrypt {{ base_path }}/{{ machine }}/secrets.yml @edit machine: - {{ sops }} edit {{ base_path }}/{{ machine }}/secrets.yml + sops edit {{ base_path }}/{{ machine }}/secrets.yml @set machine key value: - {{ sops }} set {{ base_path }}/{{ machine }}/secrets.yml "$(printf '%s\n' '["{{ key }}"]' | sed -E 's#/#"]["#g; s/\["([0-9]+)"\]/[\1]/g')" "\"$(echo '{{ value }}' | sed 's/\"/\\"/g')\"" + sops set {{ base_path }}/{{ machine }}/secrets.yml "$(printf '%s\n' '["{{ key }}"]' | sed -E 's#/#"]["#g; s/\["([0-9]+)"\]/[\1]/g')" "\"$(echo '{{ value }}' | sed 's/\"/\\\"/g')\"" git add {{ base_path }}/{{ machine }}/secrets.yml git commit -m 'chore(secrets): set secret "{{ key }}" for machine "{{ machine}}"' -- {{ base_path }}/{{ machine }}/secrets.yml > /dev/null @@ -21,10 +25,10 @@ list machine: echo "Done" @get machine key: - {{ sops }} decrypt {{ base_path }}/{{ machine }}/secrets.yml | {{ yq }} ".$(echo "{{ key }}" | sed -E 's/\//./g')" + sops decrypt {{ base_path }}/{{ machine }}/secrets.yml | yq ".$(echo "{{ key }}" | sed -E 's/\//./g')" @remove machine key: - {{ sops }} unset {{ base_path }}/{{ machine }}/secrets.yml "$(printf '%s\n' '["{{ key }}"]' | sed -E 's#/#"]["#g; s/\["([0-9]+)"\]/[\1]/g')" + sops unset {{ base_path }}/{{ machine }}/secrets.yml "$(printf '%s\n' '["{{ key }}"]' | sed -E 's#/#"]["#g; s/\["([0-9]+)"\]/[\1]/g')" git add {{ base_path }}/{{ machine }}/secrets.yml git commit -m 'chore(secrets): removed secret "{{ key }}" from machine "{{ machine}}"' -- {{ base_path }}/{{ machine }}/secrets.yml > /dev/null diff --git a/lib/options/default.nix b/lib/options/default.nix new file mode 100644 index 0000000..72e8621 --- /dev/null +++ b/lib/options/default.nix @@ -0,0 +1,38 @@ +{ lib, ...}: +let + inherit (builtins) isString typeOf; + inherit (lib) mkOption types throwIfNot concatStringsSep splitStringBy toLower map; +in +{ + options = { + mkUrlOptions = + defaults: + { + host = mkOption { + type = types.str; + example = "host.tld"; + description = '' + Hostname + ''; + } // (defaults.host or {}); + + port = mkOption { + type = types.port; + default = 1234; + example = "1234"; + description = '' + Port + ''; + } // (defaults.port or {}); + + protocol = mkOption { + type = types.str; + default = "https"; + example = "https"; + description = '' + Which protocol to use when creating a url string + ''; + } // (defaults.protocol or {}); + }; + }; +} \ No newline at end of file diff --git a/lib/strings/default.nix b/lib/strings/default.nix index 52b05e3..0c15699 100644 --- a/lib/strings/default.nix +++ b/lib/strings/default.nix @@ -1,10 +1,15 @@ { lib, ...}: let - inherit (builtins) isString typeOf; - inherit (lib) throwIfNot concatStringsSep splitStringBy toLower map; + inherit (builtins) isString typeOf match toString head; + inherit (lib) throwIfNot concatStringsSep splitStringBy toLower map concatMapAttrsStringSep; in { strings = { + #======================================================================================== + # Converts a string to snake case + # + # simply replaces any uppercase letter to its lowercase variant preceeded by an underscore + #======================================================================================== toSnakeCase = str: throwIfNot (isString str) "toSnakeCase only accepts string values, but got ${typeOf str}" ( @@ -13,5 +18,22 @@ in |> map (p: toLower p) |> concatStringsSep "_" ); + + #======================================================================================== + # Converts a set of url parts to a string + #======================================================================================== + toUrl = + { protocol ? null, host, port ? null, path ? null, query ? null, hash ? null }: + let + trim_slashes = str: str |> match "^\/*(.+?)\/*$" |> head; + encode_to_str = set: concatMapAttrsStringSep "&" (n: v: "${n}=${v}") set; + + _protocol = if protocol != null then "${protocol}://" else ""; + _port = if port != null then ":${toString port}" else ""; + _path = if path != null then "/${path |> trim_slashes}" else ""; + _query = if query != null then "?${query |> encode_to_str}" else ""; + _hash = if hash != null then "#${hash |> encode_to_str}" else ""; + in + "${_protocol}${host}${_port}${_path}${_query}${_hash}"; }; } \ No newline at end of file diff --git a/modules/home/themes/default.nix b/modules/home/themes/default.nix index 3fa74b9..3fb8f15 100644 --- a/modules/home/themes/default.nix +++ b/modules/home/themes/default.nix @@ -52,7 +52,7 @@ in { }; emoji = { - package = pkgs.noto-fonts-emoji; + package = pkgs.noto-fonts-color-emoji; name = "Noto Color Emoji"; }; }; diff --git a/modules/nixos/services/authentication/zitadel/default.nix b/modules/nixos/services/authentication/zitadel/default.nix index bd74ca2..9a02f01 100644 --- a/modules/nixos/services/authentication/zitadel/default.nix +++ b/modules/nixos/services/authentication/zitadel/default.nix @@ -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" = { + + }; + }; }; }; } diff --git a/modules/nixos/services/backup/borg/default.nix b/modules/nixos/services/backup/borg/default.nix index e200505..9cbbea0 100644 --- a/modules/nixos/services/backup/borg/default.nix +++ b/modules/nixos/services/backup/borg/default.nix @@ -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"; }; diff --git a/modules/nixos/services/media/default.nix b/modules/nixos/services/media/default.nix index 9d915da..1950bf0 100644 --- a/modules/nixos/services/media/default.nix +++ b/modules/nixos/services/media/default.nix @@ -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 + ; + }; }; } diff --git a/modules/nixos/services/media/homer/default.nix b/modules/nixos/services/media/homer/default.nix index 41535cd..79633ab 100644 --- a/modules/nixos/services/media/homer/default.nix +++ b/modules/nixos/services/media/homer/default.nix @@ -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"; } diff --git a/modules/nixos/services/media/servarr/default.nix b/modules/nixos/services/media/servarr/default.nix new file mode 100644 index 0000000..097a36b --- /dev/null +++ b/modules/nixos/services/media/servarr/default.nix @@ -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 + # ; +} diff --git a/modules/nixos/services/observability/uptime-kuma/default.nix b/modules/nixos/services/observability/uptime-kuma/default.nix new file mode 100644 index 0000000..c23977b --- /dev/null +++ b/modules/nixos/services/observability/uptime-kuma/default.nix @@ -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 ]; + }; +} diff --git a/modules/nixos/services/security/vaultwarden/default.nix b/modules/nixos/services/security/vaultwarden/default.nix index de50be7..abab566 100644 --- a/modules/nixos/services/security/vaultwarden/default.nix +++ b/modules/nixos/services/security/vaultwarden/default.nix @@ -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} + ''; + }; + }; }; } diff --git a/shells/default/default.nix b/shells/default/default.nix index 0361f88..1749c48 100644 --- a/shells/default/default.nix +++ b/shells/default/default.nix @@ -5,6 +5,8 @@ mkShell { bash sops just + yq + pwgen inputs.clan-core.packages.x86_64-linux.clan-cli ]; } \ No newline at end of file diff --git a/systems/x86_64-linux/ulmo/default.nix b/systems/x86_64-linux/ulmo/default.nix index 8bb5cea..0310818 100644 --- a/systems/x86_64-linux/ulmo/default.nix +++ b/systems/x86_64-linux/ulmo/default.nix @@ -147,15 +147,56 @@ media.enable = true; media.homer.enable = true; media.nfs.enable = true; + media.servarr = { + # radarr = { + # port = 2001; + # }; + + sonarr = { + enable = true; + # debug = true; + port = 2002; + rootFolders = [ + "/var/media/series" + ]; + }; + + lidarr = { + enable = true; + debug = true; + port = 2003; + rootFolders = [ + "/var/media/music" + ]; + }; + + prowlarr = { + enable = true; + debug = true; + port = 2004; + }; + }; observability = { grafana.enable = true; prometheus.enable = true; loki.enable = true; promtail.enable = true; + # uptime-kuma.enable = true; }; - security.vaultwarden.enable = true; + security.vaultwarden = { + enable = true; + database = { + # type = "sqlite"; + # file = "/var/lib/vaultwarden/state.db"; + + type = "postgresql"; + host = "localhost"; + port = 5432; + sslMode = "disabled"; + }; + }; }; editor = {