Services are ready.

This commit is contained in:
Raphaël Numbus
2026-02-22 12:04:19 +01:00
parent 40265e8c81
commit 2e16ac3711
10 changed files with 335 additions and 267 deletions
+107 -90
View File
@@ -4,129 +4,146 @@ with lib;
{
mkPodmanService = {
name,
description,
defaultPort ? "0",
name,
secondName ? null,
thirdName ? null,
defaultSubdomain ? name,
pod ? name,
reverseProxied ? true,
composeFile ? "podman/${name}/compose.yaml",
composeText,
secondDefaultSubdomain ? secondName,
thirdDefaultSubdomain ? thirdName,
defaultPort ? "",
secondDefaultPort ? "",
thirdDefaultPort ? "",
scheme ? "http",
middlewares ? [ "secureHeaders" ],
dependencies ? [ "traefik.service" "${config.numbus.services.dns}.service" ],
secondScheme ? "http",
thirdScheme ? "http",
reverseProxied ? true,
secondReverseProxied ? false,
thirdReverseProxied ? false,
configDirEnabled ? true,
secondConfigDirEnabled ? false,
thirdConfigDirEnabled ? false,
dataDirEnabled ? true,
secondDataDirEnabled ? false,
thirdDataDirEnabled ? false,
pod ? "false",
composeText,
extraOptions ? {},
extraConfig ? {},
configDir ? true,
dataDir ? true,
delaySec ? 180
delaySec ? 180,
middlewares ? [ "secureHeaders" ],
dependencies ? [ "traefik.service" "${config.numbus.services.dns}.service" ],
}:
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 [
mkServiceOpts = svcName: svcDesc: svcPort: svcSubdomain: svcReverseProxied: svcConfigDir: svcDataDir:
{
environment.etc."${composeFile}".text = composeText;
numbus.services.${svcName} = {
enable = mkEnableOption svcDesc;
environment.etc."${config.numbus.traefikDynamicConfigDir}/${name}.yaml" = mkIf cfg.reverseProxied {
text = ''
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:
${name}:
rule: "Host(`${cfg.subdomain}.${config.numbus.services.domain}`)"
${svcName}:
rule: "Host(`${svcCfg.subdomain}.${config.numbus.services.domain}`)"
entrypoints:
- "websecure"
service: ${name}
service: ${svcName}
middlewares:
${concatStringsSep "\n" (map (m: " - ${m}") middlewares)}
${concatStringsSep "\n" (map (m: " - ${m}") middlewares)}
tls:
certresolver: "cloudflare"
options: "secureTLS"
services:
${name}:
${svcName}:
loadBalancer:
servers:
- url: "${scheme}://host.containers.internal:${cfg.port}"
- url: "${svcScheme}://host.containers.internal:${svcCfg.port}"
'';
};
};
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; }
];
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;
})
];
systemd.services."${name}" = {
description = "Podman container : ${name}";
requires = Deps;
after = Deps;
requires = dependencies;
after = dependencies;
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";
ExecStartPre = [
"bash -c 'sleep $((RANDOM % ${toString delaySec}))'"
"-sudo -u numbus-admin podman-compose -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";
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
]);