{ lib, config, pkgs }: with lib; { mkPodmanService = { name, description, defaultPort ? "0", defaultSubdomain ? name, pod ? name, reverseProxied ? true, composeFile ? "podman/${name}/compose.yaml", composeText, scheme ? "http", middlewares ? [ "secureHeaders" ], dependencies ? [], extraOptions ? {}, extraConfig ? {}, configDir ? true, dataDir ? true, delaySec ? 180 }: let cfg = config.numbus.services.${name}; Deps = dependencies; in { 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; description = "Whether to create a Traefik reverse proxy configuration for this service."; }; } // (optionalAttrs configDir { 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 dataDir { 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."${composeFile}".text = composeText; 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 = Deps; after = Deps; wantedBy = [ "multi-user.target" ]; path = [ pkgs.podman pkgs.podman-compose pkgs.coreutils pkgs.sudo ]; serviceConfig = { Type = "exec"; ExecStartPre = "bash -c 'sleep $((RANDOM % ${delaySec}))'"; ExecStart = "sudo -u numbus-admin podman-compose --in-pod ${pod} -f /etc/${composeFile} up --remove-orphans"; ExecStop = "sudo -u numbus-admin podman-compose --in-pod ${pod} -f /etc/${composeFile} down"; Restart = "on-failure"; RestartSec = "1m"; StartLimitBurst = "5"; }; }; systemd.services."update-${name}" = { description = "Update ${name} container"; path = [ pkgs.podman pkgs.podman-compose pkgs.sudo pkgs.systemd ]; serviceConfig = { Type = "oneshot"; ExecStart = [ "sudo -u numbus-admin podman-compose --in-pod ${pod} -f /etc/${composeFile} pull" "${pkgs.systemd}/bin/systemctl restart ${name}.service" ]; }; }; systemd.timers."update-${name}" = { timerConfig = { OnCalendar = "02:00"; RandomizedDelaySec = "60m"; Unit = "update-${name}.service"; }; wantedBy = [ "timers.target" ]; }; } extraConfig ]); }; }