Files
numbus-server-module/modules/services/lib.nix
T
2026-02-25 21:42:49 +01:00

200 lines
6.9 KiB
Nix

{ lib, config, pkgs }:
with lib;
{
mkPodmanService = {
name,
description,
defaultPort ? "0",
defaultSubdomain ? name,
pod ? name,
reverseProxied ? true,
composeText,
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
cfg = config.numbus.services.${name};
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.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."${config.numbus.services.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.bash pkgs.podman pkgs.podman-compose pkgs.coreutils pkgs.sudo ];
serviceConfig = {
Type = "exec";
ExecStartPre = [
"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 ${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";
};
};
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
]);
};
}