Added mail notifications on failure. Needs more work on lib.nix and the services/*.nix.
This commit is contained in:
+157
-110
@@ -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
|
||||
]);
|
||||
|
||||
Reference in New Issue
Block a user