Added mail notifications on failure. Needs more work on lib.nix and the services/*.nix.
This commit is contained in:
@@ -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,6 +2,8 @@
|
|||||||
|
|
||||||
{
|
{
|
||||||
imports = [
|
imports = [
|
||||||
|
./smart.nix
|
||||||
|
./systemd-failures.nix
|
||||||
./smtp.nix
|
./smtp.nix
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@@ -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,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 {
|
||||||
|
|||||||
@@ -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";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
|||||||
@@ -5,5 +5,6 @@
|
|||||||
./packages.nix
|
./packages.nix
|
||||||
./podman.nix
|
./podman.nix
|
||||||
./ssh.nix
|
./ssh.nix
|
||||||
|
./terminal.nix
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@@ -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 -";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -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)}
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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
|
||||||
]);
|
]);
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user