Added mail notifications on failure. Needs more work on lib.nix and the services/*.nix.

This commit is contained in:
Raphaël Numbus
2026-02-23 16:36:40 +01:00
parent f445bd8659
commit 944ffcea85
14 changed files with 460 additions and 147 deletions
+6
View File
@@ -4,6 +4,12 @@ with lib;
{ {
options.numbus = { options.numbus = {
owner = {
type = type.str;
example = "Alex";
default = "Numbus";
description = "The name of the person who owns this server";
};
services = { services = {
domain = mkOption { domain = mkOption {
+2
View File
@@ -2,6 +2,8 @@
{ {
imports = [ imports = [
./smart.nix
./systemd-failures.nix
./smtp.nix ./smtp.nix
]; ];
} }
+61
View File
@@ -0,0 +1,61 @@
{ config, pkgs, ... }:
let
smartd_notifier = pkgs.writeScript "smartd-notify.sh" ''
#!${pkgs.bash}/bin/bash
# 1. Send Technical Email to Admin
ADMIN_EMAIL="${config.numbus.mail.adminAddress}"
SUBJECT="Numbus Server Alert: $SMARTD_FAILTYPE on $SMARTD_DEVICE"
TECH_BODY="
SMARTD Alert Details:
Server owner: $OWNER_NAME
Device: $SMARTD_DEVICE
Type: $SMARTD_DEVICETYPE
Failure Type: $SMARTD_FAILTYPE
Message: $SMARTD_MESSAGE
Full Message:
$SMARTD_FULLMESSAGE
"
printf "Subject: [ADMIN] $SUBJECT\n\n$TECH_BODY" | /run/wrappers/bin/sendmail -t "$ADMIN_EMAIL"
# 2. Send Friendly Email to Owner
USER_EMAIL="${config.numbus.mail.userAddress}"
OWNER_NAME="${config.numbus.owner}"
FRIENDLY_BODY="Cher/Chère $OWNER_NAME,
Votre serveur a automatiquement détecté une panne matérielle de disque dur.
Ce genre de panne est tout à fait normal selon l'âge de votre matériel et n'entraîne
dans la grande majorité des cas aucune perte de données grâce au système de
stockage redondant préventif.
Votre administrateur a été notifié de cette panne. Il vous recontactera dans de très
brefs délais afin de procéder au remplacement, si nécessaire, du disque dur défaillant.
Merci de votre confiance,
L'équipe de support,
Numbus-Server."
printf "Subject: [Alerte] Défaillance matérielle sur votre serveur Numbus\n\n$FRIENDLY_BODY" | /run/wrappers/bin/sendmail -t "$USER_EMAIL"
'';
in
{
services.smartd = {
enable = true;
defaults.autodetected = "-a -o on -S on -s (S/../.././00|L/../../6/01) -n standby,q -M exec ${smartd_notifier}";
notifications = {
wall = {
enable = true;
};
mail = {
enable = true;
sender = config.numbus.mail.fromAddress;
recipient = "${config.numbus.mail.userAddress},${config.numbus.mail.adminAddress}";
};
};
};
}
+3 -3
View File
@@ -3,11 +3,11 @@
with lib; with lib;
let let
cfg = config.numbus.services.mail; cfg = config.numbus.mail;
in in
{ {
options.numbus.services.mail = { options.numbus.mail = {
enable = mkEnableOption "Email sending functionality"; enable = mkEnableOption "Email sending functionality";
userAddress = mkOption { userAddress = mkOption {
@@ -31,7 +31,7 @@ in
smtpPasswordPath = mkOption { smtpPasswordPath = mkOption {
description = "The path to a file containing the password that will be use to authenticate to the SMTP server"; description = "The path to a file containing the password that will be use to authenticate to the SMTP server";
type = types.path; type = types.path;
example = "/run/secrets/smtp-password"; example = /run/secrets/smtp-password;
}; };
fromAddress = mkOption { fromAddress = mkOption {
+55
View File
@@ -0,0 +1,55 @@
{ config, pkgs, ... }:
let
systemd_notifier = pkgs.writeScript "systemd-email-notify.sh" ''
#!${pkgs.bash}/bin/bash
# The failing service name is passed as the first argument
UNIT=$1
# 1. Send Technical Email to Admin
ADMIN_EMAIL="${config.numbus.mail.adminAddress}"
SUBJECT="Numbus Server Alert: Service $UNIT Failed"
# Retrieve recent logs for context
LOGS=$(journalctl -u "$UNIT" -n 20 --no-pager)
TECH_BODY="
Systemd Service Failure Alert:
Server owner: ${config.numbus.owner}
Service: $UNIT
Recent Logs:
$LOGS
"
printf "Subject: [ADMIN] $SUBJECT\n\n$TECH_BODY" | /run/wrappers/bin/sendmail -t "$ADMIN_EMAIL"
# 2. Send Friendly Email to Owner
USER_EMAIL="${config.numbus.mail.userAddress}"
OWNER_NAME="${config.numbus.owner}"
FRIENDLY_BODY="Cher/Chère $OWNER_NAME,
Votre serveur a détecté une défaillance du service $UNIT.
Le système a tenté de gérer l'erreur, mais une intervention peut être nécessaire.
Votre administrateur a été notifié de cet incident avec les détails techniques nécessaires.
Il interviendra si une action manuelle est requise.
Merci de votre confiance,
L'équipe de support,
Numbus-Server."
printf "Subject: [Alerte] Erreur sur votre serveur Numbus\n\n$FRIENDLY_BODY" | /run/wrappers/bin/sendmail -t "$USER_EMAIL"
'';
in
{
systemd.services."service-failure-notify@" = {
description = "Email notification for failed service %i";
onFailure = [ ];
serviceConfig = {
Type = "oneshot";
ExecStart = "${systemd_notifier} %i";
};
};
}
+2 -2
View File
@@ -3,11 +3,11 @@
with lib; with lib;
let let
cfg = config.numbus.services.networking; cfg = config.numbus.networking;
in in
{ {
options.numbus.services.networking = { options.numbus.networking = {
ipAddress = mkOption { ipAddress = mkOption {
description = "The IP address that this server will use"; description = "The IP address that this server will use";
type = types.str; type = types.str;
+1
View File
@@ -5,5 +5,6 @@
./packages.nix ./packages.nix
./podman.nix ./podman.nix
./ssh.nix ./ssh.nix
./terminal.nix
]; ];
} }
+24
View File
@@ -0,0 +1,24 @@
{ config, pkgs, ... }:
{
environment.systemPackages = with pkgs; [
fish
fishPlugins.fzf-fish
fishPlugins.grc
grc
fzf
];
programs.fish = {
enable = true;
interactiveShellInit = ''
set fish_greeting # Disable greeting
fastfetch
echo -e "\n\nWelcome to your Numbus-Server !\n\n- This system is managed by NixOS\n- All changes are futile\n- Please consider buying support if you can't get your server running\n- Have a nice day and enjoy !"
'';
shellAliases = {
nixup = "cd /etc/nixos/ && sudo nix flake update && sudo nixos-rebuild --flake . switch --upgrade && cd -";
nixwitch = "cd /etc/nixos/ && sudo nix flake update && sudo nixos-rebuild --flake . switch && cd -";
};
};
}
+5 -2
View File
@@ -3,7 +3,9 @@
with lib; with lib;
let let
# Version tagging
frigateVersion = "0.16.4"; frigateVersion = "0.16.4";
# Helper
helper = import ./lib.nix { inherit config pkgs lib; }; helper = import ./lib.nix { inherit config pkgs lib; };
cfg = config.numbus.services.frigate; cfg = config.numbus.services.frigate;
in in
@@ -15,6 +17,7 @@ helper.mkPodmanService {
defaultPort = "8971"; defaultPort = "8971";
scheme = "https"; scheme = "https";
dependencies = [ "traefik.service" "${config.numbus.services.dns}.service" "home-assistant.service" ]; dependencies = [ "traefik.service" "${config.numbus.services.dns}.service" "home-assistant.service" ];
envFile = "/var/lib/numbus-server/home-assistant/.env";
extraOptions = { extraOptions = {
devices = mkOption { devices = mkOption {
@@ -45,8 +48,8 @@ helper.mkPodmanService {
tmpfs: tmpfs:
size: 1000000000 size: 1000000000
environment: environment:
- FRIGATE_MQTT_USER=$FRIGATE_MQTT_USER - FRIGATE_MQTT_USER=$HOME_ASSISTANT_MQTT_USER
- FRIGATE_MQTT_PASSWORD=$FRIGATE_MQTT_PASSWORD - FRIGATE_MQTT_PASSWORD=$HOME_ASSISTANT_MQTT_PASSWORD
${lib.optionalString (cfg.devices != []) '' ${lib.optionalString (cfg.devices != []) ''
devices: devices:
${lib.concatStringsSep "\n" (map (d: " - \"${d}\"") cfg.devices)} ${lib.concatStringsSep "\n" (map (d: " - \"${d}\"") cfg.devices)}
+17 -1
View File
@@ -3,8 +3,10 @@
with lib; with lib;
let let
# Version tagging
giteaVersion = "1.25.4-rootless"; giteaVersion = "1.25.4-rootless";
databaseVersion = "18-alpine"; databaseVersion = "18-alpine";
# Helper
helper = import ./lib.nix { inherit config pkgs lib; }; helper = import ./lib.nix { inherit config pkgs lib; };
cfg = config.numbus.services.gitea; cfg = config.numbus.services.gitea;
in in
@@ -14,6 +16,20 @@ helper.mkPodmanService {
name = "gitea"; name = "gitea";
pod = "gitea"; pod = "gitea";
defaultPort = "3000"; defaultPort = "3000";
dataDirEnabled = false;
generatedSecrets = {
DB_NAME = "xkcdpass -n 2 -d -";
DB_USERNAME = "xkcdpass -n 2 -d -";
DB_PASSWORD = "xkcdpass -n 8 -d -";
};
importedSecrets = {
DOMAIN_NAME = "${config.numbus.services.domain}";
POSTGRES_HOST="gitea-database";
POSTGRES_PORT=5432;
};
dirPermissions = [
"100999:users ${cfg.configDir}"
];
composeText = '' composeText = ''
services: services:
@@ -49,7 +65,7 @@ helper.mkPodmanService {
image: docker.io/library/postgres:${databaseVersion} image: docker.io/library/postgres:${databaseVersion}
container_name: gitea-database container_name: gitea-database
hostname: gitea-database hostname: gitea-database
user: '999:999' user: '1000:1000'
networks: networks:
gitea: gitea:
volumes: volumes:
+79 -2
View File
@@ -14,7 +14,15 @@ helper.mkPodmanService {
name = "home-assistant"; name = "home-assistant";
pod = "home-assistant"; pod = "home-assistant";
defaultPort = "8123"; defaultPort = "8123";
dataDir = false; dataDirEnabled = false;
generatedSecrets = {
HOME_ASSISTANT_MQTT_USER = "xkcdpass -n 2 -d -";
HOME_ASSISTANT_MQTT_PASSWORD = "xkcdpass -n 8 -d -";
};
dirPermissions = [
"numbus-admin:users ${cfg.configDir}/home-assistant"
"100999:users ${cfg.configDir}/mqtt"
];
extraOptions = { extraOptions = {
devices = mkOption { devices = mkOption {
@@ -25,6 +33,75 @@ helper.mkPodmanService {
}; };
}; };
extraConfig = {
systemd.services."${name}-quirk-1" = {
description = "Podman container quirk 1 : ${name}";
wantedBy = [ "multi-user.target" ];
after = [ "${name}.service" ];
onFailure = [ "service-failure-notify@%n.service" ];
startLimitBurst = 5;
startLimitIntervalSec = 600;
path = [ pkgs.coreutils pkgs.systemd ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
};
script = ''
mkdir -p /var/lib/numbus-server/${name}
if [[ -e /var/lib/numbus-server/${name}/quirk-1.true ]]; then
exit 0
fi
until [[ -e ${cfg.configDir}/home-assistant/configuration.yaml ]]; do
sleep 15
done
cat << 'EOF' >> ${cfg.configDir}/home-assistant/configuration.yaml
http:
use_x_forwarded_for: true
trusted_proxies: ${config.numbus.networking.ipAddress}/24
zha:
EOF
systemctl restart ${name}.service
touch /var/lib/numbus-server/${name}/quirk-1.true
'';
};
};
systemd.services."${name}-quirk-2" = {
description = "Podman container quirk 2 : ${name}";
wantedBy = [ "multi-user.target" "${name}.service" ];
after = [ "${name}-secrets.service" ];
before = [ "${name}.service" "${name}-permissions.service" ];
onFailure = [ "service-failure-notify@%n.service" ];
startLimitBurst = 5;
startLimitIntervalSec = 600;
path = [ pkgs.coreutils pkgs.mosquitto ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
};
script = ''
mkdir -p /var/lib/numbus-server/${name}
if [[ -e /var/lib/numbus-server/${name}/quirk.true ]]; then
exit 0
fi
cat << EOF >> ${cfg.configDir}/mqtt/mosquitto.conf
persistence true
persistence_location /mosquitto/data/
log_dest file /mosquitto/log/mosquitto.log
listener 1883
## Authentication ##
allow_anonymous false
password_file /mosquitto/password.txt
EOF
source /var/lib/numbus-server/${name}/.env
mosquitto_passwd -b ${cfg.configDir}/mqtt/password.txt "$HOME_ASSISTANT_MQTT_USER" "$HOME_ASSISTANT_MQTT_PASSWORD"
chmod 600 ${cfg.configDir}/mqtt/password.txt
touch /var/lib/numbus-server/${name}/quirk.true
'';
};
# Compose file good # Compose file good
composeText = '' composeText = ''
services: services:
@@ -37,7 +114,7 @@ helper.mkPodmanService {
ports: ports:
- "${cfg.port}:8123/tcp" - "${cfg.port}:8123/tcp"
volumes: volumes:
- ${cfg.configDir}/config:/config - ${cfg.configDir}/home-assistant:/config
- /etc/localtime:/etc/localtime:ro - /etc/localtime:/etc/localtime:ro
- /run/dbus:/run/dbus:ro - /run/dbus:/run/dbus:ro
${lib.optionalString (cfg.devices != []) '' ${lib.optionalString (cfg.devices != []) ''
+26 -13
View File
@@ -3,26 +3,39 @@
with lib; with lib;
let let
# Version tagging
immichVersion = "v2.5.6"; immichVersion = "v2.5.6";
redisVersion = "9@sha256:546304417feac0874c3dd576e0952c6bb8f06bb4093ea0c9ca303c73cf458f63"; redisVersion = "9@sha256:546304417feac0874c3dd576e0952c6bb8f06bb4093ea0c9ca303c73cf458f63";
databaseVersion = "14-vectorchord0.4.3-pgvectors0.2.0@sha256:bcf63357191b76a916ae5eb93464d65c07511da41e3bf7a8416db519b40b1c23"; databaseVersion = "14-vectorchord0.4.3-pgvectors0.2.0@sha256:bcf63357191b76a916ae5eb93464d65c07511da41e3bf7a8416db519b40b1c23";
# Helper
helper = import ./lib.nix { inherit config pkgs lib; }; helper = import ./lib.nix { inherit config pkgs lib; };
cfg = config.numbus.services.immich; cfg = config.numbus.services.${name};
# Container configuration
name = "immich";
in in
helper.mkPodmanService { helper.mkPodmanService {
description = "Immich, Google Photos but better"; description = "Immich, Google Photos but better";
name = "immich"; inherit name;
pod = "immich"; pod = "immich";
defaultPort = "2283"; defaultPort = "2283";
useSopsSecrets = true; generatedSecrets = {
DB_NAME = "xkcdpass -n 2 -d -";
extraConfig = { DB_USERNAME = "xkcdpass -n 2 -d -";
numbus.services.immich.secretMapping = { DB_PASSWORD = "xkcdpass -n 8 -d -";
DB_PASSWORD = "db_password";
DB_USERNAME = "db_username"; # Assuming you add this to schema
};
}; };
importedSecrets = {
DOMAIN_NAME = "${config.numbus.services.domain}";
REDIS_HOSTNAME = "immich-redis";
DB_HOSTNAME = "immich-database";
UPLOAD_LOCATION = "${cfg.dataDir}";
DB_DATA_LOCATION = "${cfg.configDir}/database";
TZ = "${time.timeZone}";
};
dirPermissions = [
"100999:users ${cfg.dataDir}"
"100999:users ${cfg.configDir}"
];
# Compose file good # Compose file good
composeText = '' composeText = ''
@@ -37,7 +50,7 @@ helper.mkPodmanService {
ports: ports:
- "${cfg.port}:2283/tcp" - "${cfg.port}:2283/tcp"
volumes: volumes:
- $UPLOAD_LOCATION:/data - ${cfg.dataDir}:/data
- /etc/localtime:/etc/localtime:ro - /etc/localtime:/etc/localtime:ro
env_file: env_file:
- .env - .env
@@ -85,16 +98,16 @@ helper.mkPodmanService {
container_name: immich-database container_name: immich-database
hostname: immich-database hostname: immich-database
image: ghcr.io/immich-app/postgres:${databaseVersion} image: ghcr.io/immich-app/postgres:${databaseVersion}
user: '999:999' user: '1000:1000'
networks: networks:
immich: immich:
environment: environment:
POSTGRES_PASSWORD: $DB_PASSWORD POSTGRES_PASSWORD: $DB_PASSWORD
POSTGRES_USER: $DB_USERNAME POSTGRES_USER: $DB_USERNAME
POSTGRES_DB: $DB_DATABASE_NAME POSTGRES_DB: $DB_NAME
POSTGRES_INITDB_ARGS: '--data-checksums' POSTGRES_INITDB_ARGS: '--data-checksums'
volumes: volumes:
- $DB_DATA_LOCATION:/var/lib/postgresql/data - ${cfg.configDir}/database:/var/lib/postgresql/data
shm_size: 128mb shm_size: 128mb
healthcheck: healthcheck:
disable: false disable: false
+157 -110
View File
@@ -4,147 +4,194 @@ with lib;
{ {
mkPodmanService = { mkPodmanService = {
description,
name, name,
secondName ? null, description,
thirdName ? null, defaultPort ? "0",
defaultSubdomain ? name, defaultSubdomain ? name,
secondDefaultSubdomain ? secondName, pod ? name,
thirdDefaultSubdomain ? thirdName,
defaultPort ? "",
secondDefaultPort ? "",
thirdDefaultPort ? "",
scheme ? "http",
secondScheme ? "http",
thirdScheme ? "http",
reverseProxied ? true, reverseProxied ? true,
secondReverseProxied ? false,
thirdReverseProxied ? false,
configDirEnabled ? true,
secondConfigDirEnabled ? false,
thirdConfigDirEnabled ? false,
dataDirEnabled ? true,
secondDataDirEnabled ? false,
thirdDataDirEnabled ? false,
pod ? "false",
composeText, composeText,
extraOptions ? {}, scheme ? "http",
extraConfig ? {},
delaySec ? 180,
useSopsSecrets ? false, # New argument to enable sops integration
middlewares ? [ "secureHeaders" ], middlewares ? [ "secureHeaders" ],
dependencies ? [ "traefik.service" "${config.numbus.services.dns}.service" ], dependencies ? [ "traefik.service" "${config.numbus.services.dns}.service" ],
extraOptions ? {},
extraConfig ? {},
configDirEnabled ? true,
dataDirEnabled ? true,
startDelay ? 180,
dirPermissions ? [],
generatedSecrets ? {},
importedSecrets ? {},
envFile ? null,
}: }:
let 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}; cfg = config.numbus.services.${name};
cfg2 = if secondName != null then config.numbus.services.${secondName} else {}; hasSecrets = (generatedSecrets != {}) || (importedSecrets != {});
cfg3 = if thirdName != null then config.numbus.services.${thirdName} else {}; envFilePath = if envFile == null then "/var/lib/numbus-server/${name}/.env" else envFile;
envFileArg = if hasSecrets || envFile != null then "--env-file ${envFilePath}" 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}"
'';
};
in in
{ {
options = mkMerge [ options.numbus.services.${name} = recursiveUpdate ({
(mkServiceOpts name description defaultPort defaultSubdomain reverseProxied configDirEnabled dataDirEnabled) enable = mkEnableOption description;
(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)) subdomain = mkOption {
{ numbus.services.${name} = extraOptions; } 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 [ config = mkIf cfg.enable (mkMerge [
{ {
environment.etc."podman/${name}/compose.yaml".text = composeText; environment.etc."podman/${name}/compose.yaml".text = composeText;
environment.etc = mkMerge [ environment.etc."${config.numbus.traefikDynamicConfigDir}/${name}.yaml" = mkIf cfg.reverseProxied {
{ "${config.numbus.traefikDynamicConfigDir}/${name}.yaml" = mkTraefikConfig name cfg scheme; } text = ''
(mkIf (secondName != null) { http:
"${config.numbus.traefikDynamicConfigDir}/${secondName}.yaml" = mkTraefikConfig secondName cfg2 secondScheme; routers:
}) ${name}:
(mkIf (thirdName != null) { rule: "Host(`${cfg.subdomain}.${config.numbus.services.domain}`)"
"${config.numbus.traefikDynamicConfigDir}/${thirdName}.yaml" = mkTraefikConfig thirdName cfg3 thirdScheme; 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}" = { systemd.services."${name}" = {
description = "Podman container : ${name}"; description = "Podman container : ${name}";
requires = dependencies; requires = dependencies;
after = dependencies; after = dependencies;
wantedBy = [ "multi-user.target" ]; wantedBy = [ "multi-user.target" ];
onFailure = [ "service-failure-notify@%n.service" ];
startLimitBurst = 5;
startLimitIntervalSec = 600;
path = [ pkgs.podman pkgs.podman-compose pkgs.coreutils pkgs.sudo ]; path = [ pkgs.podman pkgs.podman-compose pkgs.coreutils pkgs.sudo ];
serviceConfig = { serviceConfig = {
Type = "exec"; Type = "exec";
ExecStartPre = [ ExecStartPre = [
"bash -c 'sleep $((RANDOM % ${toString delaySec}))'" "bash -c 'sleep $((RANDOM % ${toString startDelay}))'"
"- sudo -u numbus-admin podman-compose -f /etc/podman/${name}/compose.yaml pull" "- 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"; 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 --in-pod ${toString pod} -f /etc/podman/${name}/compose.yaml down"; ExecStop = "sudo -u numbus-admin podman-compose ${envFileArg} --in-pod ${toString pod} -f /etc/podman/${name}/compose.yaml down";
Restart = "on-failure"; Restart = "on-failure";
RestartSec = "1m"; 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 extraConfig
]); ]);
+22 -14
View File
@@ -18,17 +18,25 @@ helper.mkPodmanService {
description = "Nextcloud, your own online office suite"; description = "Nextcloud, your own online office suite";
name = "nextcloud"; name = "nextcloud";
pod = "nextcloud"; pod = "nextcloud";
secondName = "onlyoffice";
thirdName = "whiteboard";
defaultPort = "11000"; defaultPort = "11000";
secondDefaultPort = "9980"; generatedSecrets = {
thirdDefaultPort = "3002"; DB_NAME = "xkcdpass -n 2 -d -";
secondReverseProxied = true; DB_USERNAME = "xkcdpass -n 2 -d -";
thirdReverseProxied = true; DB_PASSWORD = "xkcdpass -n 8 -d -";
secondConfigDirEnabled = true; REDIS_PASSWORD = "xkcdpass -n 8 -d -";
thirdConfigDirEnabled = false; };
secondDataDirEnabled = false; importedSecrets = {
thirdDataDirEnabled = false; DOMAIN_NAME = "${config.numbus.services.domain}";
REDIS_HOSTNAME = "immich-redis";
DB_HOSTNAME = "immich-database";
UPLOAD_LOCATION = "${cfg.dataDir}";
DB_DATA_LOCATION = "${cfg.configDir}/database";
TZ = "${time.timeZone}";
};
dirPermissions = [
"100999:users ${cfg.dataDir}"
"100999:users ${cfg.configDir}"
];
# Compose file good # Compose file good
composeText = '' composeText = ''
@@ -46,11 +54,11 @@ helper.mkPodmanService {
- ${cfg.dataDir}:/mnt/ncdata - ${cfg.dataDir}:/mnt/ncdata
environment: environment:
MYSQL_HOST: nextcloud-database MYSQL_HOST: nextcloud-database
MYSQL_DATABASE: $MYSQL_DATABASE MYSQL_DATABASE: $DB_NAME
MYSQL_USER: $MYSQL_USER MYSQL_USER: $DB_USERNAME
MYSQL_PASSWORD: $MYSQL_PASSWORD MYSQL_PASSWORD: $DB_PASSWORD
REDIS_HOST: nextcloud-redis REDIS_HOST: nextcloud-redis
REDIS_HOST_PASSWORD: $REDIS_HOST_PASSWORD REDIS_HOST_PASSWORD: $REDIS_PASSWORD
NEXTCLOUD_TRUSTED_DOMAINS: ${cfg.subdomain}.${config.numbus.services.domain} NEXTCLOUD_TRUSTED_DOMAINS: ${cfg.subdomain}.${config.numbus.services.domain}
NEXTCLOUD_DATA_DIR: /mnt/ncdata NEXTCLOUD_DATA_DIR: /mnt/ncdata
SMTP_HOST: $SMTP_HOST SMTP_HOST: $SMTP_HOST