{ lib, config, pkgs }: with lib; { mkPodmanService = { name, description, defaultPort ? "0", defaultSubdomain ? name, pod ? "false", reverseProxied ? true, composeText, scheme ? "http", middlewares ? null, dependencies ? [ "sops-install-secrets.service" "traefik.service" "authelia.service" "${config.numbus-server.services.dns}.service" ], extraOptions ? {}, extraConfig ? {}, configDirEnabled ? true, dataDirEnabled ? true, startDelay ? 180, dirPermissions ? [], secrets ? [], envFile ? null, ... }: let cfg = config.numbus-server.services.${name}; in { options.numbus-server.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 basic Traefik reverse proxy configuration for this service. You might need to set it to false for custom configurations or services that don't need to be reverse proxied."; }; } // (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 [ { # Compose file sops.templates."podman/${name}" = { gid = "100"; uid = "1000"; mode = "0400"; content = composeText; path = "/etc/podman/${name}/compose.yaml"; }; # Traefik config sops.templates."traefik/rules/${name}" = mkIf cfg.reverseProxied { gid = "100"; uid = "100999"; mode = "0400"; content = '' http: routers: ${name}: rule: "Host(`${cfg.subdomain}.${config.numbus-server.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}" ''; path = "/etc/traefik/rules/${name}.yaml"; }; # Secrets config sops.secrets = genAttrs secrets (secretPath: { sopsFile = "/etc/nixos/secrets/podman/${name}.yaml"; gid = "100"; uid = "1000"; mode = "0400"; }); # SystemD config systemd.services."${name}" = { description = "Podman container : ${name}"; after = dependencies; wantedBy = [ "multi-user.target" ]; onFailure = [ "service-failure-notify@%n.service" ]; startLimitBurst = 5; startLimitIntervalSec = 600; path = [ pkgs.podman pkgs.podman-compose pkgs.slirp4netns pkgs.su pkgs.sudo pkgs.coreutils ]; serviceConfig = { Type = "exec"; TimeoutStartSec = "1000"; ExecStartPre = [ "${pkgs.bash}/bin/bash -c 'sleep $((RANDOM % ${toString startDelay}))'" "${pkgs.bash}/bin/bash -c 'export PATH=/run/wrappers/bin:$PATH; exec ${pkgs.sudo}/bin/sudo -u numbus-admin podman-compose -f /etc/podman/${name}/compose.yaml pull'" ]; ExecStart = "${pkgs.bash}/bin/bash -c 'export PATH=/run/wrappers/bin:$PATH; exec ${pkgs.sudo}/bin/sudo -u numbus-admin podman-compose --in-pod ${toString pod} -f /etc/podman/${name}/compose.yaml up --remove-orphans'"; ExecStop = "${pkgs.bash}/bin/bash -c 'export PATH=/run/wrappers/bin:$PATH; exec ${pkgs.sudo}/bin/sudo -u numbus-admin podman-compose --in-pod ${toString pod} -f /etc/podman/${name}/compose.yaml down'"; Restart = "on-failure"; RestartSec = "3m"; }; }; # Permissions config 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 = "5m"; }; script = '' ${concatStringsSep "\n" (map (perm: '' set -- ${perm} WANTED_PERMISSIONS=$1 FOLDER_PATH=$2 if [[ ! -e "$FOLDER_PATH" ]]; then mkdir -p "$FOLDER_PATH" elif [[ ! -d "$FOLDER_PATH" ]]; then rm "$FOLDER_PATH" mkdir -p "$FOLDER_PATH" fi ACTUAL_PERMISSIONS=$(stat -c '%u:%g' "$FOLDER_PATH") if [[ "$ACTUAL_PERMISSIONS" != "$WANTED_PERMISSIONS" ]]; then chown -R "$WANTED_PERMISSIONS" "$FOLDER_PATH" fi '') dirPermissions)} exit 0 ''; }; } extraConfig ]); }; }