diff --git a/modules/global.nix b/modules/global.nix index 5222703..1b856f5 100644 --- a/modules/global.nix +++ b/modules/global.nix @@ -4,6 +4,12 @@ with lib; { options.numbus = { + owner = { + type = type.str; + example = "Alex"; + default = "Numbus"; + description = "The name of the person who owns this server"; + }; services = { domain = mkOption { diff --git a/modules/mail/default.nix b/modules/mail/default.nix index 2ba32c3..5539e1f 100644 --- a/modules/mail/default.nix +++ b/modules/mail/default.nix @@ -2,6 +2,8 @@ { imports = [ + ./smart.nix + ./systemd-failures.nix ./smtp.nix ]; } \ No newline at end of file diff --git a/modules/mail/smart.nix b/modules/mail/smart.nix new file mode 100644 index 0000000..c84e0ab --- /dev/null +++ b/modules/mail/smart.nix @@ -0,0 +1,61 @@ +{ config, pkgs, ... }: + +let + smartd_notifier = pkgs.writeScript "smartd-notify.sh" '' + #!${pkgs.bash}/bin/bash + + # 1. Send Technical Email to Admin + ADMIN_EMAIL="${config.numbus.mail.adminAddress}" + SUBJECT="Numbus Server Alert: $SMARTD_FAILTYPE on $SMARTD_DEVICE" + + TECH_BODY=" + SMARTD Alert Details: + Server owner: $OWNER_NAME + Device: $SMARTD_DEVICE + Type: $SMARTD_DEVICETYPE + Failure Type: $SMARTD_FAILTYPE + Message: $SMARTD_MESSAGE + + Full Message: + $SMARTD_FULLMESSAGE + " + printf "Subject: [ADMIN] $SUBJECT\n\n$TECH_BODY" | /run/wrappers/bin/sendmail -t "$ADMIN_EMAIL" + + # 2. Send Friendly Email to Owner + USER_EMAIL="${config.numbus.mail.userAddress}" + OWNER_NAME="${config.numbus.owner}" + + FRIENDLY_BODY="Cher/Chère $OWNER_NAME, + + Votre serveur a automatiquement détecté une panne matérielle de disque dur. + Ce genre de panne est tout à fait normal selon l'âge de votre matériel et n'entraîne + dans la grande majorité des cas aucune perte de données grâce au système de + stockage redondant préventif. + + Votre administrateur a été notifié de cette panne. Il vous recontactera dans de très + brefs délais afin de procéder au remplacement, si nécessaire, du disque dur défaillant. + + Merci de votre confiance, + L'équipe de support, + Numbus-Server." + + printf "Subject: [Alerte] Défaillance matérielle sur votre serveur Numbus\n\n$FRIENDLY_BODY" | /run/wrappers/bin/sendmail -t "$USER_EMAIL" + ''; +in + +{ + services.smartd = { + enable = true; + defaults.autodetected = "-a -o on -S on -s (S/../.././00|L/../../6/01) -n standby,q -M exec ${smartd_notifier}"; + notifications = { + wall = { + enable = true; + }; + mail = { + enable = true; + sender = config.numbus.mail.fromAddress; + recipient = "${config.numbus.mail.userAddress},${config.numbus.mail.adminAddress}"; + }; + }; + }; +} \ No newline at end of file diff --git a/modules/mail/smtp.nix b/modules/mail/smtp.nix index c6ee718..55b84f5 100644 --- a/modules/mail/smtp.nix +++ b/modules/mail/smtp.nix @@ -3,11 +3,11 @@ with lib; let - cfg = config.numbus.services.mail; + cfg = config.numbus.mail; in { - options.numbus.services.mail = { + options.numbus.mail = { enable = mkEnableOption "Email sending functionality"; userAddress = mkOption { @@ -31,7 +31,7 @@ in smtpPasswordPath = mkOption { description = "The path to a file containing the password that will be use to authenticate to the SMTP server"; type = types.path; - example = "/run/secrets/smtp-password"; + example = /run/secrets/smtp-password; }; fromAddress = mkOption { diff --git a/modules/mail/systemd.nix b/modules/mail/systemd.nix new file mode 100644 index 0000000..0b7a566 --- /dev/null +++ b/modules/mail/systemd.nix @@ -0,0 +1,55 @@ +{ config, pkgs, ... }: + +let + systemd_notifier = pkgs.writeScript "systemd-email-notify.sh" '' + #!${pkgs.bash}/bin/bash + + # The failing service name is passed as the first argument + UNIT=$1 + + # 1. Send Technical Email to Admin + ADMIN_EMAIL="${config.numbus.mail.adminAddress}" + SUBJECT="Numbus Server Alert: Service $UNIT Failed" + + # Retrieve recent logs for context + LOGS=$(journalctl -u "$UNIT" -n 20 --no-pager) + + TECH_BODY=" + Systemd Service Failure Alert: + Server owner: ${config.numbus.owner} + Service: $UNIT + + Recent Logs: + $LOGS + " + printf "Subject: [ADMIN] $SUBJECT\n\n$TECH_BODY" | /run/wrappers/bin/sendmail -t "$ADMIN_EMAIL" + + # 2. Send Friendly Email to Owner + USER_EMAIL="${config.numbus.mail.userAddress}" + OWNER_NAME="${config.numbus.owner}" + + FRIENDLY_BODY="Cher/Chère $OWNER_NAME, + + Votre serveur a détecté une défaillance du service $UNIT. + Le système a tenté de gérer l'erreur, mais une intervention peut être nécessaire. + + Votre administrateur a été notifié de cet incident avec les détails techniques nécessaires. + Il interviendra si une action manuelle est requise. + + Merci de votre confiance, + L'équipe de support, + Numbus-Server." + + printf "Subject: [Alerte] Erreur sur votre serveur Numbus\n\n$FRIENDLY_BODY" | /run/wrappers/bin/sendmail -t "$USER_EMAIL" + ''; +in +{ + systemd.services."service-failure-notify@" = { + description = "Email notification for failed service %i"; + onFailure = [ ]; + serviceConfig = { + Type = "oneshot"; + ExecStart = "${systemd_notifier} %i"; + }; + }; +} \ No newline at end of file diff --git a/modules/networking/networking.nix b/modules/networking/networking.nix index 5f120c5..cc4cef6 100644 --- a/modules/networking/networking.nix +++ b/modules/networking/networking.nix @@ -3,11 +3,11 @@ with lib; let - cfg = config.numbus.services.networking; + cfg = config.numbus.networking; in { - options.numbus.services.networking = { + options.numbus.networking = { ipAddress = mkOption { description = "The IP address that this server will use"; type = types.str; diff --git a/modules/packages/default.nix b/modules/packages/default.nix index 401b24c..66c45e1 100644 --- a/modules/packages/default.nix +++ b/modules/packages/default.nix @@ -5,5 +5,6 @@ ./packages.nix ./podman.nix ./ssh.nix + ./terminal.nix ]; } \ No newline at end of file diff --git a/modules/packages/terminal.nix b/modules/packages/terminal.nix new file mode 100644 index 0000000..88312a3 --- /dev/null +++ b/modules/packages/terminal.nix @@ -0,0 +1,24 @@ +{ config, pkgs, ... }: + +{ + environment.systemPackages = with pkgs; [ + fish + fishPlugins.fzf-fish + fishPlugins.grc + grc + fzf + ]; + + programs.fish = { + enable = true; + interactiveShellInit = '' + set fish_greeting # Disable greeting + fastfetch + echo -e "\n\nWelcome to your Numbus-Server !\n\n- This system is managed by NixOS\n- All changes are futile\n- Please consider buying support if you can't get your server running\n- Have a nice day and enjoy !" + ''; + shellAliases = { + nixup = "cd /etc/nixos/ && sudo nix flake update && sudo nixos-rebuild --flake . switch --upgrade && cd -"; + nixwitch = "cd /etc/nixos/ && sudo nix flake update && sudo nixos-rebuild --flake . switch && cd -"; + }; + }; +} \ No newline at end of file diff --git a/modules/services/frigate.nix b/modules/services/frigate.nix index 6c5c52d..0e2d455 100644 --- a/modules/services/frigate.nix +++ b/modules/services/frigate.nix @@ -3,7 +3,9 @@ with lib; let + # Version tagging frigateVersion = "0.16.4"; + # Helper helper = import ./lib.nix { inherit config pkgs lib; }; cfg = config.numbus.services.frigate; in @@ -15,6 +17,7 @@ helper.mkPodmanService { defaultPort = "8971"; scheme = "https"; dependencies = [ "traefik.service" "${config.numbus.services.dns}.service" "home-assistant.service" ]; + envFile = "/var/lib/numbus-server/home-assistant/.env"; extraOptions = { devices = mkOption { @@ -45,8 +48,8 @@ helper.mkPodmanService { tmpfs: size: 1000000000 environment: - - FRIGATE_MQTT_USER=$FRIGATE_MQTT_USER - - FRIGATE_MQTT_PASSWORD=$FRIGATE_MQTT_PASSWORD + - FRIGATE_MQTT_USER=$HOME_ASSISTANT_MQTT_USER + - FRIGATE_MQTT_PASSWORD=$HOME_ASSISTANT_MQTT_PASSWORD ${lib.optionalString (cfg.devices != []) '' devices: ${lib.concatStringsSep "\n" (map (d: " - \"${d}\"") cfg.devices)} diff --git a/modules/services/gitea.nix b/modules/services/gitea.nix index ec8c6d1..22b07a4 100644 --- a/modules/services/gitea.nix +++ b/modules/services/gitea.nix @@ -3,8 +3,10 @@ with lib; let + # Version tagging giteaVersion = "1.25.4-rootless"; databaseVersion = "18-alpine"; + # Helper helper = import ./lib.nix { inherit config pkgs lib; }; cfg = config.numbus.services.gitea; in @@ -14,6 +16,20 @@ helper.mkPodmanService { name = "gitea"; pod = "gitea"; defaultPort = "3000"; + dataDirEnabled = false; + generatedSecrets = { + DB_NAME = "xkcdpass -n 2 -d -"; + DB_USERNAME = "xkcdpass -n 2 -d -"; + DB_PASSWORD = "xkcdpass -n 8 -d -"; + }; + importedSecrets = { + DOMAIN_NAME = "${config.numbus.services.domain}"; + POSTGRES_HOST="gitea-database"; + POSTGRES_PORT=5432; + }; + dirPermissions = [ + "100999:users ${cfg.configDir}" + ]; composeText = '' services: @@ -49,7 +65,7 @@ helper.mkPodmanService { image: docker.io/library/postgres:${databaseVersion} container_name: gitea-database hostname: gitea-database - user: '999:999' + user: '1000:1000' networks: gitea: volumes: diff --git a/modules/services/home-assistant.nix b/modules/services/home-assistant.nix index da17850..2e0a7ba 100644 --- a/modules/services/home-assistant.nix +++ b/modules/services/home-assistant.nix @@ -14,7 +14,15 @@ helper.mkPodmanService { name = "home-assistant"; pod = "home-assistant"; defaultPort = "8123"; - dataDir = false; + dataDirEnabled = false; + generatedSecrets = { + HOME_ASSISTANT_MQTT_USER = "xkcdpass -n 2 -d -"; + HOME_ASSISTANT_MQTT_PASSWORD = "xkcdpass -n 8 -d -"; + }; + dirPermissions = [ + "numbus-admin:users ${cfg.configDir}/home-assistant" + "100999:users ${cfg.configDir}/mqtt" + ]; extraOptions = { devices = mkOption { @@ -25,6 +33,75 @@ helper.mkPodmanService { }; }; + extraConfig = { + systemd.services."${name}-quirk-1" = { + description = "Podman container quirk 1 : ${name}"; + wantedBy = [ "multi-user.target" ]; + after = [ "${name}.service" ]; + onFailure = [ "service-failure-notify@%n.service" ]; + startLimitBurst = 5; + startLimitIntervalSec = 600; + path = [ pkgs.coreutils pkgs.systemd ]; + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + }; + script = '' + mkdir -p /var/lib/numbus-server/${name} + if [[ -e /var/lib/numbus-server/${name}/quirk-1.true ]]; then + exit 0 + fi + until [[ -e ${cfg.configDir}/home-assistant/configuration.yaml ]]; do + sleep 15 + done + cat << 'EOF' >> ${cfg.configDir}/home-assistant/configuration.yaml + +http: + use_x_forwarded_for: true + trusted_proxies: ${config.numbus.networking.ipAddress}/24 + +zha: +EOF + systemctl restart ${name}.service + touch /var/lib/numbus-server/${name}/quirk-1.true + ''; + }; + }; + + systemd.services."${name}-quirk-2" = { + description = "Podman container quirk 2 : ${name}"; + wantedBy = [ "multi-user.target" "${name}.service" ]; + after = [ "${name}-secrets.service" ]; + before = [ "${name}.service" "${name}-permissions.service" ]; + onFailure = [ "service-failure-notify@%n.service" ]; + startLimitBurst = 5; + startLimitIntervalSec = 600; + path = [ pkgs.coreutils pkgs.mosquitto ]; + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + }; + script = '' + mkdir -p /var/lib/numbus-server/${name} + if [[ -e /var/lib/numbus-server/${name}/quirk.true ]]; then + exit 0 + fi + cat << EOF >> ${cfg.configDir}/mqtt/mosquitto.conf +persistence true +persistence_location /mosquitto/data/ +log_dest file /mosquitto/log/mosquitto.log +listener 1883 +## Authentication ## +allow_anonymous false +password_file /mosquitto/password.txt +EOF + source /var/lib/numbus-server/${name}/.env + mosquitto_passwd -b ${cfg.configDir}/mqtt/password.txt "$HOME_ASSISTANT_MQTT_USER" "$HOME_ASSISTANT_MQTT_PASSWORD" + chmod 600 ${cfg.configDir}/mqtt/password.txt + touch /var/lib/numbus-server/${name}/quirk.true + ''; + }; + # Compose file good composeText = '' services: @@ -37,7 +114,7 @@ helper.mkPodmanService { ports: - "${cfg.port}:8123/tcp" volumes: - - ${cfg.configDir}/config:/config + - ${cfg.configDir}/home-assistant:/config - /etc/localtime:/etc/localtime:ro - /run/dbus:/run/dbus:ro ${lib.optionalString (cfg.devices != []) '' diff --git a/modules/services/immich.nix b/modules/services/immich.nix index 88612ea..631dd3b 100644 --- a/modules/services/immich.nix +++ b/modules/services/immich.nix @@ -3,26 +3,39 @@ with lib; let + # Version tagging immichVersion = "v2.5.6"; redisVersion = "9@sha256:546304417feac0874c3dd576e0952c6bb8f06bb4093ea0c9ca303c73cf458f63"; databaseVersion = "14-vectorchord0.4.3-pgvectors0.2.0@sha256:bcf63357191b76a916ae5eb93464d65c07511da41e3bf7a8416db519b40b1c23"; + # Helper helper = import ./lib.nix { inherit config pkgs lib; }; - cfg = config.numbus.services.immich; + cfg = config.numbus.services.${name}; + # Container configuration + name = "immich"; in helper.mkPodmanService { description = "Immich, Google Photos but better"; - name = "immich"; + inherit name; pod = "immich"; defaultPort = "2283"; - useSopsSecrets = true; - - extraConfig = { - numbus.services.immich.secretMapping = { - DB_PASSWORD = "db_password"; - DB_USERNAME = "db_username"; # Assuming you add this to schema - }; + generatedSecrets = { + DB_NAME = "xkcdpass -n 2 -d -"; + DB_USERNAME = "xkcdpass -n 2 -d -"; + DB_PASSWORD = "xkcdpass -n 8 -d -"; }; + importedSecrets = { + DOMAIN_NAME = "${config.numbus.services.domain}"; + REDIS_HOSTNAME = "immich-redis"; + DB_HOSTNAME = "immich-database"; + UPLOAD_LOCATION = "${cfg.dataDir}"; + DB_DATA_LOCATION = "${cfg.configDir}/database"; + TZ = "${time.timeZone}"; + }; + dirPermissions = [ + "100999:users ${cfg.dataDir}" + "100999:users ${cfg.configDir}" + ]; # Compose file good composeText = '' @@ -37,7 +50,7 @@ helper.mkPodmanService { ports: - "${cfg.port}:2283/tcp" volumes: - - $UPLOAD_LOCATION:/data + - ${cfg.dataDir}:/data - /etc/localtime:/etc/localtime:ro env_file: - .env @@ -85,16 +98,16 @@ helper.mkPodmanService { container_name: immich-database hostname: immich-database image: ghcr.io/immich-app/postgres:${databaseVersion} - user: '999:999' + user: '1000:1000' networks: immich: environment: POSTGRES_PASSWORD: $DB_PASSWORD POSTGRES_USER: $DB_USERNAME - POSTGRES_DB: $DB_DATABASE_NAME + POSTGRES_DB: $DB_NAME POSTGRES_INITDB_ARGS: '--data-checksums' volumes: - - $DB_DATA_LOCATION:/var/lib/postgresql/data + - ${cfg.configDir}/database:/var/lib/postgresql/data shm_size: 128mb healthcheck: disable: false diff --git a/modules/services/lib.nix b/modules/services/lib.nix index 22482be..be8f098 100644 --- a/modules/services/lib.nix +++ b/modules/services/lib.nix @@ -4,147 +4,194 @@ with lib; { mkPodmanService = { - description, name, - secondName ? null, - thirdName ? null, + description, + defaultPort ? "0", defaultSubdomain ? name, - secondDefaultSubdomain ? secondName, - thirdDefaultSubdomain ? thirdName, - defaultPort ? "", - secondDefaultPort ? "", - thirdDefaultPort ? "", - scheme ? "http", - secondScheme ? "http", - thirdScheme ? "http", + pod ? name, reverseProxied ? true, - secondReverseProxied ? false, - thirdReverseProxied ? false, - configDirEnabled ? true, - secondConfigDirEnabled ? false, - thirdConfigDirEnabled ? false, - dataDirEnabled ? true, - secondDataDirEnabled ? false, - thirdDataDirEnabled ? false, - pod ? "false", composeText, - extraOptions ? {}, - extraConfig ? {}, - delaySec ? 180, - useSopsSecrets ? false, # New argument to enable sops integration + scheme ? "http", middlewares ? [ "secureHeaders" ], dependencies ? [ "traefik.service" "${config.numbus.services.dns}.service" ], + extraOptions ? {}, + extraConfig ? {}, + configDirEnabled ? true, + dataDirEnabled ? true, + startDelay ? 180, + dirPermissions ? [], + generatedSecrets ? {}, + importedSecrets ? {}, + envFile ? null, }: + let - mkServiceOpts = svcName: svcDesc: svcPort: svcSubdomain: svcReverseProxied: svcConfigDir: svcDataDir: - { - numbus.services.${svcName} = { - enable = mkEnableOption svcDesc; - - subdomain = mkOption { - type = types.str; - default = svcSubdomain; - example = svcSubdomain; - description = "The subdomain that ${svcName} will use"; - }; - - port = mkOption { - type = types.str; - default = svcPort; - example = svcPort; - description = "The port that ${svcName} will use."; - }; - - reverseProxied = mkOption { - type = types.bool; - default = svcReverseProxied; - description = "Whether to create a Traefik reverse proxy configuration for this service."; - }; - } // (optionalAttrs svcConfigDir { - configDir = mkOption { - type = types.str; - default = "/mnt/config/${svcName}"; - example = "/mnt/config/${svcName}"; - description = "The directory where ${svcName}'s configuration files will be stored"; - }; - }) // (optionalAttrs svcDataDir { - dataDir = mkOption { - type = types.str; - default = "/mnt/data/${svcName}"; - example = "/mnt/data/${svcName}"; - description = "The directory where ${svcName}'s data will be stored"; - }; - }); - }; - cfg = config.numbus.services.${name}; - cfg2 = if secondName != null then config.numbus.services.${secondName} else {}; - cfg3 = if thirdName != null then config.numbus.services.${thirdName} else {}; - - mkTraefikConfig = svcName: svcCfg: svcScheme: - mkIf (cfg.enable && svcCfg.reverseProxied) { - text = '' - http: - routers: - ${svcName}: - rule: "Host(`${svcCfg.subdomain}.${config.numbus.services.domain}`)" - entrypoints: - - "websecure" - service: ${svcName} - middlewares: -${concatStringsSep "\n" (map (m: " - ${m}") middlewares)} - tls: - certresolver: "cloudflare" - options: "secureTLS" - services: - ${svcName}: - loadBalancer: - servers: - - url: "${svcScheme}://host.containers.internal:${svcCfg.port}" - ''; - }; + hasSecrets = (generatedSecrets != {}) || (importedSecrets != {}); + envFilePath = if envFile == null then "/var/lib/numbus-server/${name}/.env" else envFile; + envFileArg = if hasSecrets || envFile != null then "--env-file ${envFilePath}" else ""; in + { - options = mkMerge [ - (mkServiceOpts name description defaultPort defaultSubdomain reverseProxied configDirEnabled dataDirEnabled) - (optionalAttrs (secondName != null) (mkServiceOpts secondName "Secondary service for ${name}" secondDefaultPort secondDefaultSubdomain secondReverseProxied secondConfigDirEnabled secondDataDirEnabled)) - (optionalAttrs (thirdName != null) (mkServiceOpts thirdName "Tertiary service for ${name}" thirdDefaultPort thirdDefaultSubdomain thirdReverseProxied thirdConfigDirEnabled thirdDataDirEnabled)) - { numbus.services.${name} = extraOptions; } - ]; + options.numbus.services.${name} = recursiveUpdate ({ + enable = mkEnableOption description; + + subdomain = mkOption { + type = types.str; + default = defaultSubdomain; + example = defaultSubdomain; + description = "The subdomain that ${name} will use"; + }; + + port = mkOption { + type = types.str; + default = defaultPort; + example = defaultPort; + description = "The port that ${name} will use."; + }; + + reverseProxied = mkOption { + type = types.bool; + default = reverseProxied; + example = reverseProxied; + description = "Whether to create a Traefik reverse proxy configuration for this service."; + }; + } // (optionalAttrs configDirEnabled { + configDir = mkOption { + type = types.str; + default = "/mnt/config/${name}"; + example = "/mnt/config/${name}"; + description = "The directory where ${name}'s configuration files will be stored"; + }; + }) // (optionalAttrs dataDirEnabled { + dataDir = mkOption { + type = types.str; + default = "/mnt/data/${name}"; + example = "/mnt/data/${name}"; + description = "The directory where ${name}'s data will be stored"; + }; + })) extraOptions; config = mkIf cfg.enable (mkMerge [ { environment.etc."podman/${name}/compose.yaml".text = composeText; - environment.etc = mkMerge [ - { "${config.numbus.traefikDynamicConfigDir}/${name}.yaml" = mkTraefikConfig name cfg scheme; } - (mkIf (secondName != null) { - "${config.numbus.traefikDynamicConfigDir}/${secondName}.yaml" = mkTraefikConfig secondName cfg2 secondScheme; - }) - (mkIf (thirdName != null) { - "${config.numbus.traefikDynamicConfigDir}/${thirdName}.yaml" = mkTraefikConfig thirdName cfg3 thirdScheme; - }) - ]; + environment.etc."${config.numbus.traefikDynamicConfigDir}/${name}.yaml" = mkIf cfg.reverseProxied { + text = '' + http: + routers: + ${name}: + rule: "Host(`${cfg.subdomain}.${config.numbus.services.domain}`)" + entrypoints: + - "websecure" + service: ${name} + middlewares: +${concatStringsSep "\n" (map (m: " - ${m}") middlewares)} + tls: + certresolver: "cloudflare" + options: "secureTLS" + services: + ${name}: + loadBalancer: + servers: + - url: "${scheme}://host.containers.internal:${cfg.port}" + ''; + }; systemd.services."${name}" = { description = "Podman container : ${name}"; requires = dependencies; after = dependencies; wantedBy = [ "multi-user.target" ]; + onFailure = [ "service-failure-notify@%n.service" ]; + startLimitBurst = 5; + startLimitIntervalSec = 600; path = [ pkgs.podman pkgs.podman-compose pkgs.coreutils pkgs.sudo ]; serviceConfig = { Type = "exec"; ExecStartPre = [ - "bash -c 'sleep $((RANDOM % ${toString delaySec}))'" - "- sudo -u numbus-admin podman-compose -f /etc/podman/${name}/compose.yaml pull" + "bash -c 'sleep $((RANDOM % ${toString startDelay}))'" + "- sudo -u numbus-admin podman-compose ${envFileArg} -f /etc/podman/${name}/compose.yaml pull" ]; - ExecStart = "sudo -u numbus-admin podman-compose --in-pod ${toString pod} -f /etc/podman/${name}/compose.yaml up --remove-orphans"; - ExecStop = "sudo -u numbus-admin podman-compose --in-pod ${toString pod} -f /etc/podman/${name}/compose.yaml down"; + ExecStart = "sudo -u numbus-admin podman-compose ${envFileArg} --in-pod ${toString pod} -f /etc/podman/${name}/compose.yaml up --remove-orphans"; + ExecStop = "sudo -u numbus-admin podman-compose ${envFileArg} --in-pod ${toString pod} -f /etc/podman/${name}/compose.yaml down"; Restart = "on-failure"; RestartSec = "1m"; - StartLimitBurst = "5"; }; }; + + systemd.services."${name}-permissions" = mkIf dirPermissions != [] { + description = "Podman container : ${name} : check and fix permissions"; + before = [ "${name}.service" ]; + wantedBy = [ "multi-user.target" "${name}.service" ]; + onFailure = [ "service-failure-notify@%n.service" ]; + startLimitBurst = 5; + startLimitIntervalSec = 600; + path = [ pkgs.coreutils ]; + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + Restart = "on-failure"; + RestartSec = "1m"; + }; + script = '' + mkdir -p /var/lib/numbus-server/${name} + ${concatStringsSep "\n" (map (perm: '' + set -- ${perm} + MARKER="/var/lib/numbus-server/${name}/.perm-fixed-$(echo "$1:$2" | md5sum | cut -d' ' -f1)" + if [ ! -f "$MARKER" ]; then + rm -f /var/lib/numbus-server/${name}/.perm-fixed-* + mkdir -p "$2" + chown -R "$1" "$2" + touch "$MARKER" + fi + '') dirPermissions)} + exit 0 + ''; + }; + + systemd.services."${name}-secrets" = mkIf hasSecrets { + description = "Podman container create secrets : ${name}"; + before = [ "${name}.service" ]; + wantedBy = [ "multi-user.target" "${name}.service" ]; + onFailure = [ "service-failure-notify@%n.service" ]; + startLimitBurst = 5; + startLimitIntervalSec = 600; + path = [ pkgs.coreutils pkgs.xkcdpass pkgs.gnugrep ]; + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + Restart = "on-failure"; + RestartSec = "1m"; + }; + script = '' + mkdir -p /var/lib/numbus-server/${name} + SECRETS_FILE="${envFilePath}" + + if [[ ! -f "$SECRETS_FILE" ]]; then + touch "$SECRETS_FILE" + fi + + # Generated Secrets (only if missing) + ${concatStringsSep "\n" (mapAttrsToList (k: v: '' + if ! grep -q "^${k}=" "$SECRETS_FILE"; then + echo "${k}=$(${v})" >> "$SECRETS_FILE" + fi + '') generatedSecrets)} + + # Imported Secrets (update or append) + ${concatStringsSep "\n" (mapAttrsToList (k: v: '' + if grep -q "^${k}=" "$SECRETS_FILE"; then + grep -v "^${k}=" "$SECRETS_FILE" > "$SECRETS_FILE.tmp" + mv "$SECRETS_FILE.tmp" "$SECRETS_FILE" + fi + echo "${k}=${lib.escapeShellArg v}" >> "$SECRETS_FILE" + '') importedSecrets)} + + chown numbus-admin:users "$SECRETS_FILE" + chmod 600 "$SECRETS_FILE" + ''; + }; } extraConfig ]); diff --git a/modules/services/nextcloud.nix b/modules/services/nextcloud.nix index 949832c..1995b2a 100644 --- a/modules/services/nextcloud.nix +++ b/modules/services/nextcloud.nix @@ -18,17 +18,25 @@ helper.mkPodmanService { description = "Nextcloud, your own online office suite"; name = "nextcloud"; pod = "nextcloud"; - secondName = "onlyoffice"; - thirdName = "whiteboard"; defaultPort = "11000"; - secondDefaultPort = "9980"; - thirdDefaultPort = "3002"; - secondReverseProxied = true; - thirdReverseProxied = true; - secondConfigDirEnabled = true; - thirdConfigDirEnabled = false; - secondDataDirEnabled = false; - thirdDataDirEnabled = false; + generatedSecrets = { + DB_NAME = "xkcdpass -n 2 -d -"; + DB_USERNAME = "xkcdpass -n 2 -d -"; + DB_PASSWORD = "xkcdpass -n 8 -d -"; + REDIS_PASSWORD = "xkcdpass -n 8 -d -"; + }; + importedSecrets = { + DOMAIN_NAME = "${config.numbus.services.domain}"; + REDIS_HOSTNAME = "immich-redis"; + DB_HOSTNAME = "immich-database"; + UPLOAD_LOCATION = "${cfg.dataDir}"; + DB_DATA_LOCATION = "${cfg.configDir}/database"; + TZ = "${time.timeZone}"; + }; + dirPermissions = [ + "100999:users ${cfg.dataDir}" + "100999:users ${cfg.configDir}" + ]; # Compose file good composeText = '' @@ -46,11 +54,11 @@ helper.mkPodmanService { - ${cfg.dataDir}:/mnt/ncdata environment: MYSQL_HOST: nextcloud-database - MYSQL_DATABASE: $MYSQL_DATABASE - MYSQL_USER: $MYSQL_USER - MYSQL_PASSWORD: $MYSQL_PASSWORD + MYSQL_DATABASE: $DB_NAME + MYSQL_USER: $DB_USERNAME + MYSQL_PASSWORD: $DB_PASSWORD REDIS_HOST: nextcloud-redis - REDIS_HOST_PASSWORD: $REDIS_HOST_PASSWORD + REDIS_HOST_PASSWORD: $REDIS_PASSWORD NEXTCLOUD_TRUSTED_DOMAINS: ${cfg.subdomain}.${config.numbus.services.domain} NEXTCLOUD_DATA_DIR: /mnt/ncdata SMTP_HOST: $SMTP_HOST