Files organization update

This commit is contained in:
Raphaël Numbus
2026-01-25 09:06:31 +01:00
parent eb17551aec
commit d4ea4ea1a8
39 changed files with 236 additions and 89 deletions
+176
View File
@@ -0,0 +1,176 @@
{ modulesPath, config, lib, pkgs, inputs, ... }:
{
imports = [
(modulesPath + "/installer/scan/not-detected.nix")
(modulesPath + "/profiles/qemu-guest.nix")
inputs.sops-nix.nixosModules.sops
./disks/disko.nix
./misc/activation.nix
./misc/mail.nix
./misc/networking.nix
./misc/smart.nix
# ./disks/pcr-check.nix
# ./disks/snapraid.nix
# ./pcie-coral/coral.nix
];
# Enable email notifications
email.enable = true;
# Hardware settings
hardware.enableRedistributableFirmware = true;
hardware.cpu.intel.updateMicrocode = true;
hardware.cpu.amd.updateMicrocode = true;
# Secrets management
sops.defaultSopsFile = ./secrets/secrets.yaml;
sops.age.sshKeyPaths = [ "/home/numbus-admin/.ssh/id_ed25519" ];
sops.age.keyFile = "/var/lib/sops-nix/key.txt";
sops.age.generateKey = true;
sops.secrets."ssh_public_keys" = { owner = "numbus-admin"; path = "/etc/ssh/authorized_keys.d/numbus-admin"; };
sops.secrets."sender_email_address_password" = {};
sops.secrets."podman/frigate" = { owner = "numbus-admin"; path = "/etc/podman/frigate/.env"; };
sops.secrets."podman/gitea" = { owner = "numbus-admin"; path = "/etc/podman/gitea/.env"; };
sops.secrets."podman/home_assistant" = { owner = "numbus-admin"; path = "/etc/podman/home-assistant/.env"; };
sops.secrets."podman/immich" = { owner = "numbus-admin"; path = "/etc/podman/immich/.env"; };
sops.secrets."podman/it_tools" = { owner = "numbus-admin"; path = "/etc/podman/it-tools/.env"; };
sops.secrets."podman/nextcloud" = { owner = "numbus-admin"; path = "/etc/podman/nextcloud/.env"; };
sops.secrets."podman/passbolt" = { owner = "numbus-admin"; path = "/etc/podman/passbolt/.env"; };
sops.secrets."podman/pi_hole" = { owner = "numbus-admin"; path = "/etc/podman/pi-hole/.env"; };
sops.secrets."podman/traefik" = { owner = "numbus-admin"; path = "/etc/podman/traefik/.env"; };
# Bootloader options
boot.initrd.systemd.enable = true;
boot.loader.systemd-boot.enable = true;
boot.loader.efi.canTouchEfiVariables = true;
boot.swraid.mdadmConf = "MAILADDR ${config.email.userAddress},${config.email.adminAddress}";
# boot.initrd.systemd.tpm2.enable = true;
# TPM2 PCR check
# systemIdentity.enable = true;
# systemIdentity.pcr15 = "PCR_HASH";
# Timezone
time.timeZone = "Europe/Paris";
# Internationalisation properties.
i18n.defaultLocale = "fr_FR.UTF-8";
i18n.extraLocaleSettings = {
LC_ADDRESS = "fr_FR.UTF-8";
LC_IDENTIFICATION = "fr_FR.UTF-8";
LC_MEASUREMENT = "fr_FR.UTF-8";
LC_MONETARY = "fr_FR.UTF-8";
LC_NAME = "fr_FR.UTF-8";
LC_NUMERIC = "fr_FR.UTF-8";
LC_PAPER = "fr_FR.UTF-8";
LC_TELEPHONE = "fr_FR.UTF-8";
LC_TIME = "fr_FR.UTF-8";
};
# Keyboard mapping
console.keyMap = "fr";
services.xserver.xkb = {
layout = "fr";
variant = "";
};
# Enable SSH
services.openssh.enable = true;
# Allow unfree packages
nixpkgs.config.allowUnfree = true;
# Install packages
environment.systemPackages = with pkgs; [
git
ncdu
fastfetch
tpm2-tss
sops
age
powertop
pciutils
hdparm
hd-idle
hddtemp
smartmontools
cpufrequtils
intel-gpu-tools
podman
podman-compose
podman-tui
snapraid
mergerfs
mergerfs-tools
];
# Power savings
services.autoaspm.enable = true;
powerManagement.powertop.enable = true;
boot.kernelParams = [
"pcie_aspm=force"
"consoleblank=60"
];
# Enable cron service
services.cron = {
enable = true;
systemCronJobs = [
];
};
# Enable Podman
virtualisation.podman.enable = true;
virtualisation.podman.defaultNetwork.settings.dns_enabled = true;
# Enable libvirt
# virtualisation.libvirtd.enable = true;
# programs.virt-manager.enable = true;
# User account
users.users.numbus-admin = {
isNormalUser = true;
description = "Numbus Admin";
extraGroups = [ "wheel" ];
uid = 1000;
initialPassword = "changeMe!";
# required for auto start before user login
linger = true;
# required for rootless container with multiple users
autoSubUidGidRange = true;
};
# Login message
environment.loginShellInit = ''
if [ "$(id -u)" -eq 1000 ]; then
if [ -n "$SSH_TTY" ]; then
fastfetch
echo -e "\n\nWelcome to numbus.eu 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 !"
fi
fi
'';
# Enable auto updates
system.autoUpgrade = {
enable = true;
allowReboot = true;
flake = inputs.self.outPath;
flags = [ "--print-build-logs" ];
dates = "02:00";
randomizedDelaySec = "45min";
};
nix.gc = {
automatic = true;
dates = "weekly";
options = "--delete-older-than 7d";
};
# Enable NixOS flakes
nix.settings.experimental-features = [ "nix-command" "flakes" ];
# Enable auto nix-store optimization
nix.settings.auto-optimise-store = true;
system.stateVersion = "25.05";
}
+57
View File
@@ -0,0 +1,57 @@
{ lib, ... }:
{
disko.devices = {
# Boot disk LVM configuration
lvm_vg = {
pool = {
type = "lvm_vg";
lvs = {
root = {
size = "100%FREE";
content.type = "filesystem";
content.format = "btrfs";
content.mountpoint = "/";
};
swap = {
size = "16G";
content.type = "swap";
};
};
};
};
disk = {
# Boot disk
"boot-1" = {
type = "disk";
device = "${BOOT_DISK_1_ID}";
content = {
type = "gpt";
partitions = {
ESP = {
size = "1G";
type = "EF00";
content = {
type = "filesystem";
format = "vfat";
mountpoint = "/boot";
mountOptions = [ "umask=0077" ];
};
};
luks = {
size = "100%";
content = {
type = "luks";
name = "crypted-boot-1";
settings = {
keyFile = "/etc/secrets/disks/boot-1";
allowDiscards = true;
};
content = {
type = "lvm_pv";
vg = "pool";
};
};
};
};
};
};
+75
View File
@@ -0,0 +1,75 @@
{ lib, ... }:
{
disko.devices = {
disk = {
"boot-1" = {
type = "disk";
device = "${BOOT_DISK_1_ID}";
content = {
type = "gpt";
partitions = {
ESP = {
size = "1G";
type = "EF00";
content = {
type = "filesystem";
format = "vfat";
mountpoint = "/boot";
mountOptions = [ "umask=0077" ];
};
};
crypt_p1 = {
size = "100%";
content = {
type = "luks";
name = "crypted-boot-1";
settings = {
keyFile = "/etc/secrets/disks/boot-1";
allowDiscards = true;
};
};
};
};
};
};
"boot-2" = {
type = "disk";
device = "${BOOT_DISK_2_ID}";
content = {
type = "gpt";
partitions = {
crypt_p2 = {
size = "100%";
content = {
type = "luks";
name = "crypted-boot-2";
settings = {
keyFile = "/etc/secrets/disks/boot-2";
allowDiscards = true;
};
content = {
type = "btrfs";
extraArgs = [
"-d raid1"
"/dev/mapper/crypted-boot-1"
];
subvolumes = {
"/root" = {
mountpoint = "/";
mountOptions = [
"rw"
"relatime"
"ssd"
];
};
"/swap" = {
mountpoint = "none";
swap.size = "16G";
};
};
};
};
};
};
};
};
+28
View File
@@ -0,0 +1,28 @@
"content-${j}" = {
type = "disk";
device = "${CONTENT_DISK_ID}";
content = {
type = "gpt";
partitions = {
luks = {
size = "100%";
content = {
type = "luks";
name = "crypted-content-${j}";
initrdUnlock = false;
settings = {
keyFile = "/etc/secrets/disks/content-${j}";
allowDiscards = ${ALLOW_DISCARDS:-false};
crypttabExtraOpts = [ "nofail" "noauto" ];
};
content = {
type = "filesystem";
format = "xfs";
mountpoint = "/mnt/content-${j}";
mountOptions = [ "nofail" "noauto" ];
};
};
};
};
};
};
+56
View File
@@ -0,0 +1,56 @@
# Data mirror disks
"content-1" = {
type = "disk";
device = "/dev/disk/by-id/ata-FUJITSU_MHW2120BH_NZ2TT772DCN5";
content = {
type = "gpt";
partitions = {
"data-1" = {
size = "$PARTITION_SIZE";
content = {
type = "luks";
name = "crypted-content-1";
settings.keyFile = "/etc/secrets/disks/content-1";
content = {
type = "mdraid";
name = "data-storage";
};
};
};
};
};
};
"parity-1" = {
type = "disk";
device = "/dev/disk/by-id/ata-TOSHIBA_MQ04ABF100_X7CXT0D8T";
content = {
type = "gpt";
partitions = {
"parity-1" = {
size = "$PARTITION_SIZE";
content = {
type = "luks";
name = "crypted-parity-1";
settings.keyFile = "/etc/secrets/disks/parity-1";
content = {
type = "mdraid";
name = "data-storage";
};
};
};
};
};
};
};
# RAID 1 Configuration
mdadm = {
"data-storage" = {
type = "mdadm";
level = 1;
content = {
type = "filesystem";
format = "xfs";
mountpoint = "/mnt/data-storage";
};
};
+28
View File
@@ -0,0 +1,28 @@
"parity-${j}" = {
type = "disk";
device = "${PARITY_DISK_ID}";
content = {
type = "gpt";
partitions = {
luks = {
size = "100%";
content = {
type = "luks";
name = "crypted-parity-${j}";
initrdUnlock = false;
settings = {
keyFile = "/etc/secrets/disks/parity-${j}";
allowDiscards = ${ALLOW_DISCARDS:-false};
crypttabExtraOpts = [ "nofail" "noauto" ];
};
content = {
type = "filesystem";
format = "xfs";
mountpoint = "/mnt/parity-${j}";
mountOptions = [ "nofail" "noauto" ];
};
};
};
};
};
};
+124
View File
@@ -0,0 +1,124 @@
{ lib, utils, config, ... }:
let
inherit (lib)
head
optional
foldl'
nameValuePair
listToAttrs
optionals
concatStringsSep
sortOn
mkIf
mkEnableOption
mkOption
types
;
in
{
options = {
systemIdentity = {
enable = mkEnableOption "hashing of Luks values into PCR 15 and subsequent checks";
pcr15 = mkOption {
type = types.nullOr types.str;
default = null;
description = ''
The expected value of PCR 15 after all luks partitions have been unlocked
Should be a 64 character hex string as ouput by the sha256 field of
'systemd-analyze pcrs 15 --json=short'
If set to null (the default) it will not check the value.
If the check fails the boot will abort and you will be dropped into an emergency shell, if enabled.
In ermergency shell type:
'systemctl disable check-pcrs'
'systemctl default'
to continue booting
'';
};
};
boot.initrd.luks.devices = lib.mkOption {
type =
with lib.types;
attrsOf (submodule {
config.crypttabExtraOpts = optionals config.systemIdentity.enable [
"tpm2-device=auto"
"tpm2-measure-pcr=yes"
];
});
};
};
config = mkIf config.systemIdentity.enable {
boot.kernelParams = [
"rd.luks=no"
];
boot.initrd.systemd.services =
{
check-pcrs = mkIf (config.systemIdentity.pcr15 != null) {
script = ''
echo "Checking PCR 15 value"
if [[ $(systemd-analyze pcrs 15 --json=short | jq -r ".[0].sha256") != "${config.systemIdentity.pcr15}" ]] ; then
echo "PCR 15 check failed"
exit 1
else
echo "PCR 15 check succeeded"
fi
'';
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
};
unitConfig.DefaultDependencies = "no";
after = [ "cryptsetup.target" ];
before = [ "sysroot.mount" ];
requiredBy = [ "sysroot.mount" ];
};
}
// (listToAttrs (
foldl' (
acc: attrs:
let
extraOpts = attrs.value.crypttabExtraOpts ++ (optional attrs.value.allowDiscards "discard");
cfg = config.boot.initrd.systemd;
in
[
(nameValuePair "cryptsetup-${attrs.name}" {
unitConfig = {
Description = "Cryptography setup for ${attrs.name}";
DefaultDependencies = "no";
IgnoreOnIsolate = true;
Conflicts = [ "umount.target" ];
BindsTo = "${utils.escapeSystemdPath attrs.value.device}.device";
};
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
TimeoutSec = "infinity";
KeyringMode = "shared";
OOMScoreAdjust = 500;
ImportCredential = "cryptsetup.*";
ExecStart = ''${cfg.package}/bin/systemd-cryptsetup attach '${attrs.name}' '${attrs.value.device}' '-' '${concatStringsSep "," extraOpts}' '';
ExecStop = ''${cfg.package}/bin/systemd-cryptsetup detach '${attrs.name}' '';
};
after =
[
"cryptsetup-pre.target"
"systemd-udevd-kernel.socket"
"${utils.escapeSystemdPath attrs.value.device}.device"
]
++ (optional cfg.tpm2.enable "systemd-tpm2-setup-early.service")
++ optional (acc != [ ]) "${(head acc).name}.service";
before = [
"blockdev@dev-mapper-${attrs.name}.target"
"cryptsetup.target"
"umount.target"
];
wants = [ "blockdev@dev-mapper-${attrs.name}.target" ];
requiredBy = [ "sysroot.mount" ];
})
]
++ acc
) [ ] (sortOn (x: x.name) (lib.attrsets.attrsToList config.boot.initrd.luks.devices))
));
};
}
+60
View File
@@ -0,0 +1,60 @@
{ config, lib, pkgs, ... }:
{
### --> MergerFS setup
fileSystems."/mnt/data" = {
device = "/mnt/content-*";
fsType = "fuse.mergerfs";
options = [
"category.create=ff"
"cache.files=partial"
"dropcacheonclose=true"
"defaults"
"noauto"
"nofail"
"allow_other"
"moveonenospc=1"
"minfreespace=50G"
"func.getattr=newest"
"fsname=mergerfs_data"
"x-mount.mkdir"
"x-systemd.automount"
"x-systemd.requires=mount-dependencies.service"
];
};
### MergerFS setup <--
systemd.services.mount-dependencies = {
description = "This service will mount the encrypted disks for mergerFS";
after = [ "network.target" ];
wantedBy = [ "multi-user.target" ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
# Bring the service up
ExecStart = pkgs.writeShellScript "mount-disks" ''
$MOUNT_DEPENDENCIES_START
'';
# Take it down gracefully
ExecStop = pkgs.writeShellScript "unmount-disks" ''
$MOUNT_DEPENDENCIES_STOP
'';
Restart = "on-failure";
};
};
### --> SnapRAID setup
services.snapraid = {
enable = true;
contentFiles = [
$SNAPRAID_CONTENT_FILES
];
parityFiles = [
$SNAPRAID_PARITY_FILES
];
dataDisks = {
$SNAPRAID_DATA_DISKS
};
};
### SnapRAID setup <--
}
+25
View File
@@ -0,0 +1,25 @@
{ config, pkgs, lib, ... }:
let
hardDrives = [
DISK_LIST
];
in
{
### --> Disk spindown
systemd.services.hd-idle = {
description = "External HD spin down daemon";
wantedBy = [ "multi-user.target" ];
serviceConfig = {
Type = "simple";
ExecStart =
let
idleTime = toString 1800;
hardDriveParameter = lib.strings.concatMapStringsSep " " (x: "-a ${x} -i ${idleTime}") hardDrives;
in
"${pkgs.hd-idle}/bin/hd-idle -i 0 ${hardDriveParameter}";
};
};
### Disk spindown <--
}
+51
View File
@@ -0,0 +1,51 @@
{
inputs = {
# Core Nixpkgs
nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.11";
# Diskpartitioning helper
disko.url = "github:nix-community/disko";
disko.inputs.nixpkgs.follows = "nixpkgs";
# Secrets handling
sops-nix.url = "github:Mic92/sops-nix";
sops-nix.inputs.nixpkgs.follows = "nixpkgs";
# Power savings
autoaspm.url = "git+https://git.notthebe.ee/notthebee/AutoASPM";
autoaspm.inputs.nixpkgs.follows = "nixpkgs";
};
outputs = { self, nixpkgs, disko, sops-nix, autoaspm, ... }@inputs: let
# System definition
system = "x86_64-linux";
pkgs = import nixpkgs {
inherit system;
config.allowUnfree = true;
};
# Helper: collect every *.nix file inside ./podman as a list
podmanModules = let
dir = ./podman;
entries = builtins.readDir dir;
names = builtins.attrNames entries;
nixNames = builtins.filter (n: builtins.match ".*\\.nix" n != null) names;
in map (name: "${dir}/${name}") nixNames;
in {
nixosConfigurations = {
numbus-server = nixpkgs.lib.nixosSystem {
inherit system;
specialArgs = { inherit inputs; };
modules = [
# Diskpartitioning helper
disko.nixosModules.disko
# Secrets handling
sops-nix.nixosModules.sops
# Power savings
inputs.autoaspm.nixosModules.autoaspm
# Core host configuration
./configuration.nix
./hardware-configuration.nix
# Podman services automatically added from ./podman/*.nix
] ++ podmanModules;
};
};
};
}
+57
View File
@@ -0,0 +1,57 @@
{ config, pkgs, ... }:
{
systemd.services.numbus-activation-chowned = {
description = "Numbus-Server activation : Correct permissions";
wantedBy = [ "multi-user.target" "traefik.service" ];
after = [ "network.target" "local-fs.target" ];
path = [ pkgs.coreutils ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
};
script = ''
#!/usr/bin/env bash
if [[ -e /home/numbus-admin/.numbus-server/chowned.true ]]; then
exit 0
fi
mkdir -p /mnt/config/ /mnt/data/ /mnt/data/nextcloud/
mkdir -p /home/numbus-admin/.numbus-server/
chown -R numbus-admin:users /mnt/config/
chown -R numbus-admin:users /mnt/data/
chown -R 100032:users /mnt/data/nextcloud/
chown -R numbus-admin:users /home/numbus-admin/.numbus-server/
touch /home/numbus-admin/.numbus-server/chowned.true
'';
};
systemd.services.numbus-activation-networked = {
description = "Numbus-Server activation : Create podman networks";
wantedBy = [ "multi-user.target" "traefik.service" ];
before = [ "traefik.service" ];
after = [ "network.target" "local-fs.target" "numbus-activation-chowned.service" ];
path = [ pkgs.podman pkgs.coreutils ];
serviceConfig = {
User = "numbus-admin";
Environment = [ "XDG_RUNTIME_DIR=/run/user/1000" ];
Type = "oneshot";
RemainAfterExit = true;
};
script = ''
#!/usr/bin/env bash
export PATH=$PATH:/run/wrappers/bin
if [[ -e /home/numbus-admin/.numbus-server/networked.true ]]; then
exit 0
fi
PODMAN_NETWORKS
mkdir -p /home/numbus-admin/.numbus-server/
touch /home/numbus-admin/.numbus-server/networked.true
'';
};
}
+74
View File
@@ -0,0 +1,74 @@
{ config, pkgs, lib, ... }:
let
cfg = config.email;
in
### --> Mail notifications configuration
{
options.email = {
enable = lib.mkEnableOption "Email sending functionality";
fromAddress = lib.mkOption {
description = "The 'from' address";
type = lib.types.str;
default = "no-reply@DOMAIN_NAME";
};
userAddress = lib.mkOption {
description = "The 'to' address";
type = lib.types.str;
default = "EMAIL_ADDRESS";
};
adminAddress = lib.mkOption {
description = "The admin email address to receive alerts in copy";
type = lib.types.str;
default = "admin@numbus.eu";
};
smtpServer = lib.mkOption {
description = "The SMTP server address";
type = lib.types.str;
default = "SENDER_MAIL_DOMAIN";
};
smtpPort = lib.mkOption {
description = "The SMTP port";
type = lib.types.port;
default = 465;
};
smtpUsername = lib.mkOption {
description = "The SMTP username";
type = lib.types.str;
default = "SENDER_MAIL_ADDRESS";
};
smtpPasswordPath = lib.mkOption {
description = "Path to the secret containing SMTP password";
type = lib.types.path;
default = config.sops.secrets.sender_email_address_password.path;
};
};
config = lib.mkIf cfg.enable {
environment.etc."aliases".text = ''
root: ${config.email.userAddress}, ${config.email.adminAddress}
default: ${config.email.userAddress}, ${config.email.adminAddress}
'';
programs.msmtp = {
enable = true;
defaults = {
aliases = "/etc/aliases";
timeout = 60;
syslog = "on";
};
accounts.default = {
auth = true;
host = config.email.smtpServer;
port = config.email.smtpPort;
from = config.email.fromAddress;
user = config.email.smtpUsername;
tls = true;
tls_starttls = false;
passwordeval = "${pkgs.coreutils}/bin/cat ${config.email.smtpPasswordPath}";
};
};
};
### Mail notifications configuration <--
}
+33
View File
@@ -0,0 +1,33 @@
{ config, pkgs, lib, ... }:
{
# Hostname
networking.hostName = "numbus-server";
networking.networkmanager.enable = false;
networking.resolvconf.enable = false;
networking.nftables.enable = true;
networking.firewall.enable = true;
# Allow rootless containers to bind to port 53 and up
boot.kernel.sysctl."net.ipv4.ip_unprivileged_port_start" = 53;
# Bridge configuration for VMs
networking.bridges.br0.interfaces = [ "TARGET_INTERFACE" ];
networking.interfaces.br0.useDHCP = false;
networking.nameservers = [ "127.0.0.1" "9.9.9.9" ];
networking.interfaces.br0.ipv4.addresses = [{
address = "HOME_SERVER_IP";
prefixLength = 24;
}];
networking.defaultGateway = {
address = "HOME_ROUTER_IP";
interface = "br0";
};
# Open ports in the firewall
networking.firewall.allowPing = true;
networking.firewall.allowedTCPPorts = [ 53 80 443 ];
networking.firewall.allowedUDPPorts = [ 53 443 ];
}
+62
View File
@@ -0,0 +1,62 @@
{ config, pkgs, ... }:
let
smartd_notifier = pkgs.writeScript "smartd-notify.sh" ''
#!${pkgs.bash}/bin/bash
# 1. Send Technical Email to Admin
ADMIN_EMAIL="${config.email.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.email.userAddress}"
OWNER_NAME=$(cat /etc/numbus-server/owner 2>/dev/null || echo "User")
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
{
### --> SMART disk heath
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.email.fromAddress;
recipient = "${config.email.userAddress},${config.email.adminAddress}";
};
};
};
### SMART disk heath <--
}
+12
View File
@@ -0,0 +1,12 @@
{ config, pkgs, ... }:
let
libedgetpu = pkgs.callPackage ./libedgetpu.nix {};
gasket = config.boot.kernelPackages.callPackage ./gasket.nix {};
in
{
services.udev.packages = [ libedgetpu ];
users.groups.plugdev = {};
boot.extraModulePackages = [ gasket ];
}
@@ -0,0 +1,35 @@
{ stdenv, lib, fetchFromGitHub, kernel }:
stdenv.mkDerivation rec {
pname = "gasket";
version = "1.0-18";
src = fetchFromGitHub {
owner = "google";
repo = "gasket-driver";
rev = "97aeba584efd18983850c36dcf7384b0185284b3";
sha256 = "pJwrrI7jVKFts4+bl2xmPIAD01VKFta2SRuElerQnTo=";
};
makeFlags = [
"-C"
"${kernel.dev}/lib/modules/${kernel.modDirVersion}/build"
"M=$(PWD)"
];
buildFlags = [ "modules" ];
installFlags = [ "INSTALL_MOD_PATH=${placeholder "out"}" ];
installTargets = [ "modules_install" ];
sourceRoot = "source/src";
hardeningDisable = [ "pic" "format" ];
nativeBuildInputs = kernel.moduleBuildDependencies;
meta = with lib; {
description = "The Coral Gasket Driver allows usage of the Coral EdgeTPU on Linux systems.";
homepage = "https://github.com/google/gasket-driver";
license = licenses.gpl2;
maintainers = [ lib.maintainers.kylehendricks ];
platforms = platforms.linux;
};
}
@@ -0,0 +1,59 @@
{ stdenv, lib, fetchFromGitHub, libusb1, abseil-cpp, flatbuffers, xxd }:
let
flatbuffers_1_12 = flatbuffers.overrideAttrs (oldAttrs: rec {
version = "1.12.0";
NIX_CFLAGS_COMPILE = "-Wno-error=class-memaccess -Wno-error=maybe-uninitialized";
cmakeFlags = (oldAttrs.cmakeFlags or []) ++ ["-DFLATBUFFERS_BUILD_SHAREDLIB=ON"];
NIX_CXXSTDLIB_COMPILE = "-std=c++17";
configureFlags = (oldAttrs.configureFlags or []) ++ ["--enable-shared"];
src = fetchFromGitHub {
owner = "google";
repo = "flatbuffers";
rev = "v${version}";
sha256 = "sha256-L1B5Y/c897Jg9fGwT2J3+vaXsZ+lfXnskp8Gto1p/Tg=";
};
});
in stdenv.mkDerivation rec {
pname = "libedgetpu";
version = "grouper";
src = fetchFromGitHub {
owner = "google-coral";
repo = pname;
rev = "release-${version}";
sha256 = "sha256-73hwItimf88Iqnb40lk4ul/PzmCNIfdt6Afi+xjNiBE=";
};
makeFlags = ["-f" "makefile_build/Makefile" "libedgetpu" ];
buildInputs = [
libusb1
abseil-cpp
flatbuffers_1_12
];
nativeBuildInputs = [
xxd
];
NIX_CXXSTDLIB_COMPILE = "-std=c++17";
TFROOT = "${fetchFromGitHub {
owner = "tensorflow";
repo = "tensorflow";
rev = "v2.7.4";
sha256 = "sha256-liDbUAdaVllB0b74aBeqNxkYNu/zPy7k3CevzRF5dk0=";
}}";
enableParallelBuilding = false;
installPhase = ''
mkdir -p $out/lib
cp out/direct/k8/libedgetpu.so.1.0 $out/lib
ln -s $out/lib/libedgetpu.so.1.0 $out/lib/libedgetpu.so.1
mkdir -p $out/lib/udev/rules.d
cp debian/edgetpu-accelerator.rules $out/lib/udev/rules.d/99-edgetpu-accelerator.rules
'';
}
@@ -0,0 +1,41 @@
{ config, pkgs, ... }:
let
container_name = "adguard";
compose_file = "podman/adguard/compose.yaml";
config_dir = "/mnt/config/adguard";
in
{
config = {
environment.etc."${compose_file}".text =
/*
yaml
*/
''
'';
systemd.services.${container_name} = {
description = "Podman container : ${container_name}";
after = [ "network.target" "traefik.service" ];
wantedBy = [ "multi-user.target" ];
path = [ pkgs.podman-compose pkgs.podman ];
serviceConfig = {
User = "numbus-admin";
Environment = [ "XDG_RUNTIME_DIR=/run/user/1000" ];
Type = "exec";
# Pull the latest image before running
ExecStartPre = "${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} pull";
# Bring the service up
ExecStart = "${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} up --remove-orphans";
# Take it down gracefully
ExecStop = "${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} down";
Restart = "on-failure";
RestartSec = "10m";
StartLimitBurst = "3";
StartLimitIntervalSec = "30s";
};
};
};
}
+79
View File
@@ -0,0 +1,79 @@
{ config, pkgs, ... }:
let
container_name = "frigate";
compose_file = "podman/frigate/compose.yaml";
config_dir = "/mnt/config/frigate";
data_dir = "/mnt/data/frigate";
in
{
config = {
environment.etc."${compose_file}".text =
/*
yaml
*/
''
services:
frigate:
image: ghcr.io/blakeblackshear/frigate:stable
container_name: frigate
shm_size: "512MB"
networks:
hass_frontend:
hass_backend:
volumes:
- ${config_dir}:/config
- ${data_dir}/clips:/media/frigate/clips
- ${data_dir}/recordings:/media/frigate/recordings
- ${data_dir}/exports:/media/frigate/exports
- /etc/localtime:/etc/localtime:ro
- type: tmpfs
target: /tmp/cache
tmpfs:
size: 2000000000
environment:
FRIGATE_MQTT_USER: $FRIGATE_MQTT_USER
FRIGATE_MQTT_PASSWORD: $FRIGATE_MQTT_PASSWORD
# --- frigate devices --- #
labels:
- traefik.enable=true
- traefik.http.services.frigate.loadbalancer.server.port=8971
- traefik.http.services.frigate.loadbalancer.server.scheme=http
- traefik.http.routers.frigate-https.entrypoints=websecure
- traefik.http.routers.frigate-https.rule=Host(`frigate.$DOMAIN_NAME`)
- traefik.http.routers.frigate-https.tls=true
- traefik.http.routers.frigate-https.tls.certresolver=cloudflare
restart: unless-stopped
networks:
hass_backend:
external: true
hass_frontend:
external: true
'';
systemd.services.${container_name} = {
description = "Podman container : ${container_name}";
after = [ "network.target" ];
requires = [ "traefik.service" ];
wantedBy = [ "multi-user.target" ];
path = [ pkgs.podman ];
serviceConfig = {
User = "numbus-admin";
Environment = [ "XDG_RUNTIME_DIR=/run/user/1000" ];
Type = "exec";
# Pull the latest image before running
ExecStartPre = "${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} pull";
# Bring the service up
ExecStart = "${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} up --remove-orphans";
# Take it down gracefully
ExecStop = "${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} down";
Restart = "on-failure";
RestartSec = "5m";
StartLimitBurst = "3";
};
};
};
}
+91
View File
@@ -0,0 +1,91 @@
{ config, pkgs, ... }:
let
container_name = "gitea";
compose_file = "podman/gitea/compose.yaml";
config_dir = "/mnt/config/gitea";
in
{
config = {
environment.etc."${compose_file}".text =
/*
yaml
*/
''
services:
gitea:
image: docker.io/gitea/gitea:latest
container_name: gitea
networks:
gitea_frontend:
gitea_backend:
volumes:
- ${config_dir}:/data
- /etc/localtime:/etc/localtime:ro
environment:
- USER_UID=1000
- USER_GID=1000
- GITEA__database__DB_TYPE=postgres
- GITEA__database__HOST=$POSTGRES_HOST:$POSTGRES_PORT
- GITEA__database__NAME=$DB_NAME
- GITEA__database__USER=$DB_USERNAME
- GITEA__database__PASSWD=$DB_PASSWORD
- GITEA__server__SSH_PORT=2424
- GITEA__server__ROOT_URL=gitea.$DOMAIN_NAME
labels:
- traefik.enable=true
- traefik.http.services.gitea.loadbalancer.server.port=3000
- traefik.http.services.gitea.loadbalancer.server.scheme=http
- traefik.http.routers.gitea-https.entrypoints=websecure
- traefik.http.routers.gitea-https.rule=Host(`gitea.$DOMAIN_NAME`)
- traefik.http.routers.gitea-https.tls=true
- traefik.http.routers.gitea-https.tls.certresolver=cloudflare
depends_on:
- gitea-database
restart: unless-stopped
gitea-database:
image: docker.io/library/postgres:17.5
container_name: gitea-database
environment:
- POSTGRES_USER=$DB_USERNAME
- POSTGRES_PASSWORD=$DB_PASSWORD
- POSTGRES_DB=$DB_NAME
networks:
gitea_backend:
volumes:
- ${config_dir}:/var/lib/postgresql/data
restart: unless-stopped
networks:
gitea_frontend:
external: true
gitea_backend:
external: true
'';
systemd.services.${container_name} = {
description = "Podman container : ${container_name}";
after = [ "network.target" ];
requires = [ "traefik.service" ];
wantedBy = [ "multi-user.target" ];
path = [ pkgs.podman ];
serviceConfig = {
User = "numbus-admin";
Environment = [ "XDG_RUNTIME_DIR=/run/user/1000" ];
Type = "exec";
# Pull the latest image before running
ExecStartPre = "${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} pull";
# Bring the service up
ExecStart = "${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} up --remove-orphans";
# Take it down gracefully
ExecStop = "${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} down";
Restart = "on-failure";
RestartSec = "5m";
StartLimitBurst = "3";
};
};
};
}
@@ -0,0 +1,78 @@
{ config, pkgs, ... }:
let
container_name = "home-assistant";
compose_file = "podman/home-assistant/compose.yaml";
config_dir_1 = "/mnt/config/home-assistant";
config_dir_2 = "/mnt/config/mqtt";
in
{
config = {
environment.etc."${compose_file}".text =
/*
yaml
*/
''
services:
home-assistant:
image: ghcr.io/home-assistant/home-assistant:latest
container_name: home-assistant
networks:
home-assistant_frontend:
home-assistant_backend:
volumes:
- ${config_dir_1}:/config
- /etc/localtime:/etc/localtime:ro
- /run/dbus:/run/dbus:ro
# --- home-assistant devices --- #
labels:
- traefik.enable=true
- traefik.http.services.home-assistant.loadbalancer.server.port=8123
- traefik.http.services.home-assistant.loadbalancer.server.scheme=http
- traefik.http.routers.home-assistant-https.entrypoints=websecure
- traefik.http.routers.home-assistant-https.rule=Host(`home-assistant.$DOMAIN_NAME`)
- traefik.http.routers.home-assistant-https.tls=true
- traefik.http.routers.home-assistant-https.tls.certresolver=cloudflare
restart: unless-stopped
frigate-mqtt:
image: eclipse-mosquitto
container_name: mqtt
user: 1000:1000
networks:
home-assistant_backend:
volumes:
- ${config_dir_2}:/mosquitto
restart: unless-stopped
networks:
home-assistant_backend:
external: true
home-assistant_frontend:
external: true
'';
systemd.services.${container_name} = {
description = "Podman container : ${container_name}";
after = [ "network.target" ];
requires = [ "traefik.service" ];
wantedBy = [ "multi-user.target" ];
path = [ pkgs.podman ];
serviceConfig = {
User = "numbus-admin";
Environment = [ "XDG_RUNTIME_DIR=/run/user/1000" ];
Type = "exec";
# Pull the latest image before running
ExecStartPre = "${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} pull";
# Bring the service up
ExecStart = "${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} up --remove-orphans";
# Take it down gracefully
ExecStop = "${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} down";
Restart = "on-failure";
RestartSec = "5m";
StartLimitBurst = "3";
};
};
};
}
+115
View File
@@ -0,0 +1,115 @@
{ config, pkgs, ... }:
let
container_name = "immich";
compose_file = "podman/immich/compose.yaml";
config_dir = "/mnt/config/immich";
data_dir = "/mnt/data/immich";
in
{
config = {
environment.etc."${compose_file}".text =
/*
yaml
*/
''
services:
immich-server:
image: ghcr.io/immich-app/immich-server:$IMMICH_VERSION
container_name: immich-server
networks:
immich_frontend:
immich_backend:
volumes:
- $UPLOAD_LOCATION:/data
- /etc/localtime:/etc/localtime:ro
# --- immich devices --- #
labels:
- traefik.enable=true
- traefik.http.services.immich.loadbalancer.server.port=2283
- traefik.http.services.immich.loadbalancer.server.scheme=http
- traefik.http.routers.immich-https.entrypoints=websecure
- traefik.http.routers.immich-https.rule=Host(`immich.$DOMAIN_NAME`)
- traefik.http.routers.immich-https.tls=true
- traefik.http.routers.immich-https.tls.certresolver=cloudflare
env_file:
- .env
depends_on:
- immich-redis
- immich-database
restart: always
healthcheck:
disable: false
immich-machine-learning:
container_name: immich-machine-learning
image: ghcr.io/immich-app/immich-machine-learning:$IMMICH_VERSION
networks:
immich_backend:
volumes:
- ${config_dir}/models:/cache
env_file:
- .env
restart: always
healthcheck:
disable: false
immich-redis:
container_name: immich-redis
image: docker.io/valkey/valkey:8-bookworm@sha256:a137a2b60aca1a75130022d6bb96af423fefae4eb55faf395732db3544803280
networks:
immich_backend:
healthcheck:
test: redis-cli ping || exit 1
restart: always
immich-database:
container_name: immich-database
image: ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0@sha256:32324a2f41df5de9efe1af166b7008c3f55646f8d0e00d9550c16c9822366b4a
networks:
immich_backend:
shm_size: 128mb
volumes:
# Do not edit the next line. If you want to change the database storage location on your system, edit the value of DB_DATA_LOCATION in the .env file
- $DB_DATA_LOCATION:/var/lib/postgresql/data
environment:
POSTGRES_PASSWORD: $DB_PASSWORD
POSTGRES_USER: $DB_USERNAME
POSTGRES_DB: $DB_DATABASE_NAME
POSTGRES_INITDB_ARGS: '--data-checksums'
restart: always
healthcheck:
disable: false
networks:
immich_backend:
external: true
immich_frontend:
external: true
'';
systemd.services.${container_name} = {
description = "Podman container : ${container_name}";
after = [ "network.target" ];
requires = [ "traefik.service" ];
wantedBy = [ "multi-user.target" ];
path = [ pkgs.podman ];
serviceConfig = {
User = "numbus-admin";
Environment = [ "XDG_RUNTIME_DIR=/run/user/1000" ];
Type = "exec";
# Pull the latest image before running
ExecStartPre = "${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} pull";
# Bring the service up
ExecStart = "${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} up --remove-orphans";
# Take it down gracefully
ExecStop = "${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} down";
Restart = "on-failure";
RestartSec = "5m";
StartLimitBurst = "3";
};
};
};
}
+58
View File
@@ -0,0 +1,58 @@
{ config, pkgs, ... }:
let
container_name = "it-tools";
compose_file = "podman/it-tools/compose.yaml";
in
{
config = {
environment.etc."${compose_file}".text =
/*
yaml
*/
''
services:
it-tools:
container_name: it-tools
image: corentinth/it-tools
networks:
it-tools_frontend:
labels:
- traefik.enable=true
- traefik.http.services.it-tools.loadbalancer.server.port=80
- traefik.http.services.it-tools.loadbalancer.server.scheme=http
- traefik.http.routers.it-tools-https.entrypoints=websecure
- traefik.http.routers.it-tools-https.rule=Host(`it-tools.$DOMAIN_NAME`)
- traefik.http.routers.it-tools-https.tls=true
- traefik.http.routers.it-tools-https.tls.certresolver=cloudflare
restart: unless-stopped
networks:
it-tools_frontend:
external: true
'';
systemd.services.${container_name} = {
description = "Podman container : ${container_name}";
after = [ "network.target" ];
requires = [ "traefik.service" ];
wantedBy = [ "multi-user.target" ];
path = [ pkgs.podman ];
serviceConfig = {
User = "numbus-admin";
Environment = [ "XDG_RUNTIME_DIR=/run/user/1000" ];
Type = "exec";
# Pull the latest image before running
ExecStartPre = "${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} pull";
# Bring the service up
ExecStart = "${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} up --remove-orphans";
# Take it down gracefully
ExecStop = "${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} down";
Restart = "on-failure";
RestartSec = "5m";
StartLimitBurst = "3";
};
};
};
}
+81
View File
@@ -0,0 +1,81 @@
{ config, pkgs, ... }:
let
container_name = "nextcloud";
compose_file = "podman/nextcloud/compose.yaml";
data_dir = "/mnt/data/nextcloud";
in
{
config = {
environment.etc."${compose_file}".text =
/*
yaml
*/
''
services:
nextcloud-aio-mastercontainer:
image: ghcr.io/nextcloud-releases/all-in-one:latest
container_name: nextcloud-aio-mastercontainer
networks:
nextcloud-aio:
volumes:
- nextcloud_aio_mastercontainer:/mnt/docker-aio-config
- /run/user/1000/podman/podman.sock:/var/run/docker.sock:ro
environment:
APACHE_PORT: 11000
NEXTCLOUD_TRUSTED_DOMAINS: nextcloud.$DOMAIN_NAME nextcloud-aio.$DOMAIN_NAME
TRUSTED_PROXIES: 172.16.1.253
APACHE_IP_BINDING: 127.0.0.1
NEXTCLOUD_DATADIR: ${data_dir}
NEXTCLOUD_ENABLE_DRI_DEVICE: $NEXTCLOUD_ENABLE_DRI_DEVICE
NEXTCLOUD_UPLOAD_LIMIT: 16G
NEXTCLOUD_MAX_TIME: 3600
NEXTCLOUD_MEMORY_LIMIT: 2048M
NEXTCLOUD_ADDITIONAL_APKS: imagemagick
NEXTCLOUD_ADDITIONAL_PHP_EXTENSIONS: imagick
WATCHTOWER_DOCKER_SOCKET_PATH: /run/user/1000/podman/podman.sock
labels:
- traefik.enable=true
- traefik.http.services.nextcloud-aio.loadbalancer.server.port=8080
- traefik.http.services.nextcloud-aio.loadbalancer.server.scheme=https
- traefik.http.routers.nextcloud-aio-https.entrypoints=websecure
- traefik.http.routers.nextcloud-aio-https.rule=Host(`nextcloud-aio.$DOMAIN_NAME`)
- traefik.http.routers.nextcloud-aio-https.tls=true
- traefik.http.routers.nextcloud-aio-https.tls.certresolver=cloudflare
init: true
restart: always
networks:
nextcloud-aio:
external: true
volumes:
nextcloud_aio_mastercontainer:
name: nextcloud_aio_mastercontainer
'';
systemd.services.${container_name} = {
description = "Podman container : ${container_name}";
after = [ "network.target" ];
requires = [ "traefik.service" ];
wantedBy = [ "multi-user.target" ];
path = [ pkgs.podman ];
serviceConfig = {
User = "numbus-admin";
Environment = [ "XDG_RUNTIME_DIR=/run/user/1000" ];
Type = "exec";
# Pull the latest image before running
ExecStartPre = "${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} pull";
# Bring the service up
ExecStart = "${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} up --remove-orphans";
# Take it down gracefully
ExecStop = "${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} down";
Restart = "on-failure";
RestartSec = "5m";
StartLimitBurst = "3";
};
};
};
}
+110
View File
@@ -0,0 +1,110 @@
{ config, pkgs, ... }:
let
container_name = "passbolt";
compose_file = "podman/passbolt/compose.yaml";
in
{
config = {
environment.etc."${compose_file}".text =
/*
yaml
*/
''
services:
passbolt:
image: passbolt/passbolt:latest-ce-non-root
container_name: passbolt
networks:
passbolt_frontend:
passbolt_backend:
volumes:
- passbolt-gpg:/etc/passbolt/gpg
- passbolt-jwt:/etc/passbolt/jwt
environment:
APP_DEFAULT_TIMEZONE: $TZ
APP_FULL_BASE_URL: https://passbolt.$DOMAIN_NAME
DATASOURCES_DEFAULT_HOST: "passbolt-database"
DATASOURCES_DEFAULT_USERNAME: $PASSBOLT_MYSQL_USER
DATASOURCES_DEFAULT_PASSWORD: $PASSBOLT_MYSQL_PASSWORD
DATASOURCES_DEFAULT_DATABASE: $PASSBOLT_MYSQL_DATABASE
EMAIL_DEFAULT_FROM_NAME: "Passbolt"
EMAIL_TRANSPORT_DEFAULT_HOST: $SENDER_EMAIL_DOMAIN
EMAIL_TRANSPORT_DEFAULT_PORT: $SENDER_EMAIL_PORT
EMAIL_TRANSPORT_DEFAULT_USERNAME: $SENDER_EMAIL_ADDRESS
EMAIL_TRANSPORT_DEFAULT_PASSWORD: $SENDER_EMAIL_ADDRESS_PASSWORD
EMAIL_TRANSPORT_DEFAULT_TLS: true
EMAIL_DEFAULT_FROM: $EMAIL_ADDRESS
PASSBOLT_SSL_FORCE: true
labels:
- traefik.enable=true
- traefik.http.services.passbolt.loadbalancer.server.port=4433
- traefik.http.services.passbolt.loadbalancer.server.scheme=https
- traefik.http.routers.passbolt-https.entrypoints=websecure
- traefik.http.routers.passbolt-https.rule=Host(`passbolt.$DOMAIN_NAME`)
- traefik.http.routers.passbolt-https.tls=true
- traefik.http.routers.passbolt-https.tls.certresolver=cloudflare
command:
[
"/usr/bin/wait-for.sh",
"-t",
"0",
"passbolt-database:3306",
"--",
"/docker-entrypoint.sh",
]
depends_on:
- passbolt-database
restart: unless-stopped
passbolt-database:
image: mariadb:11.3
container_name: passbolt-database
networks:
passbolt_backend:
volumes:
- passbolt-database:/var/lib/mysql
environment:
MYSQL_RANDOM_ROOT_PASSWORD: "true"
MYSQL_DATABASE: $PASSBOLT_MYSQL_DATABASE
MYSQL_USER: $PASSBOLT_MYSQL_USER
MYSQL_PASSWORD: $PASSBOLT_MYSQL_PASSWORD
restart: unless-stopped
networks:
passbolt_backend:
external: true
passbolt_frontend:
external: true
volumes:
passbolt-database:
passbolt-gpg:
passbolt-jwt:
'';
systemd.services.${container_name} = {
description = "Podman container : ${container_name}";
after = [ "network.target" ];
requires = [ "traefik.service" ];
wantedBy = [ "multi-user.target" ];
path = [ pkgs.podman ];
serviceConfig = {
User = "numbus-admin";
Environment = [ "XDG_RUNTIME_DIR=/run/user/1000" ];
Type = "exec";
# Pull the latest image before running
ExecStartPre = "${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} pull";
# Bring the service up
ExecStart = "${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} up --remove-orphans";
# Take it down gracefully
ExecStop = "${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} down";
Restart = "on-failure";
RestartSec = "5m";
StartLimitBurst = "3";
};
};
};
}
+92
View File
@@ -0,0 +1,92 @@
{ config, pkgs, ... }:
let
container_name = "pi-hole";
compose_file = "podman/pi-hole/compose.yaml";
config_dir = "/mnt/config/pi-hole";
in
{
config = {
environment.etc."${compose_file}".text =
/*
yaml
*/
''
services:
pihole:
image: pihole/pihole:latest
container_name: pi-hole
networks:
pi-hole_frontend:
ports:
# DNS Ports
- "53:53/tcp"
- "53:53/udp"
environment:
TZ: $TZ
FTLCONF_webserver_api_password: $FTLCONF_webserver_api_password
FTLCONF_dns_listeningMode: "all"
FTLCONF_dns_revServers: true,$HOME_ROUTER_SUBNET,$HOME_ROUTER_IP,home
FTLCONF_dns_domain_name: "home"
FTLCONF_dns_domain_local: "true"
FTLCONF_dns_hosts: |
$HOME_SERVER_IP frigate.$DOMAIN_NAME
$HOME_SERVER_IP gitea.$DOMAIN_NAME
$HOME_SERVER_IP home-assistant.$DOMAIN_NAME
$HOME_SERVER_IP immich.$DOMAIN_NAME
$HOME_SERVER_IP it-tools.$DOMAIN_NAME
$HOME_SERVER_IP nextcloud.$DOMAIN_NAME
$HOME_SERVER_IP nextcloud-aio.$DOMAIN_NAME
$HOME_SERVER_IP passbolt.$DOMAIN_NAME
$HOME_SERVER_IP pi-hole.$DOMAIN_NAME
$HOME_SERVER_IP traefik.$DOMAIN_NAME
FTLCONF_dhcp_active: "false"
FTLCONF_dns_upstreams: 9.9.9.9;149.112.112.112
FTLCONF_ntp_ipv4_active: "false"
FTLCONF_ntp_ipv6_active: "false"
FTLCONF_ntp_sync_active: "false"
volumes:
- ${config_dir}:/etc/pihole
cap_add:
- SYS_NICE
labels:
- traefik.enable=true
- traefik.http.services.pihole.loadbalancer.server.port=80
- traefik.http.services.pihole.loadbalancer.server.scheme=http
- traefik.http.routers.pihole-https.entrypoints=websecure
- traefik.http.routers.pihole-https.rule=Host(`pi-hole.$DOMAIN_NAME`)
- traefik.http.routers.pihole-https.tls=true
- traefik.http.routers.pihole-https.tls.certresolver=cloudflare
restart: unless-stopped
networks:
pi-hole_frontend:
external: true
'';
systemd.services.${container_name} = {
description = "Podman container : ${container_name}";
after = [ "network.target" ];
requires = [ "traefik.service" ];
wantedBy = [ "multi-user.target" ];
path = [ pkgs.podman ];
serviceConfig = {
User = "numbus-admin";
Environment = [ "XDG_RUNTIME_DIR=/run/user/1000" ];
AmbientCapabilities = "CAP_SYS_NICE";
LimitNICE = -20;
Type = "exec";
# Pull the latest image before running
ExecStartPre = "${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} pull";
# Bring the service up
ExecStart = "${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} up --remove-orphans";
# Take it down gracefully
ExecStop = "${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} down";
Restart = "on-failure";
RestartSec = "5m";
StartLimitBurst = "3";
};
};
};
}
+67
View File
@@ -0,0 +1,67 @@
{ config, pkgs, ... }:
let
container_name = "traefik";
compose_file = "podman/traefik/compose.yaml";
config_dir = "/mnt/config/traefik";
in
{
config = {
environment.etc."${compose_file}".text =
/*
yaml
*/
''
services:
traefik:
image: docker.io/library/traefik:latest
container_name: traefik
networks:
TRAEFIK_NETWORKS
ports:
- "80:80"
- "443:443"
volumes:
- /run/user/1000/podman/podman.sock:/run/docker.sock:ro
- ${config_dir}/rules/:/etc/traefik/conf/:ro
- ${config_dir}/traefik.yaml:/etc/traefik/traefik.yaml:ro
- ${config_dir}/certs/:/var/traefik/certs/:rw
environment:
- CF_DNS_API_TOKEN=$CF_DNS_API_TOKEN
labels:
- traefik.enable=true
- traefik.http.services.traefik.loadbalancer.server.port=8080
- traefik.http.services.traefik.loadbalancer.server.scheme=http
- traefik.http.routers.traefik-https.entrypoints=websecure
- traefik.http.routers.traefik-https.rule=Host(`traefik.$DOMAIN_NAME`)
- traefik.http.routers.traefik-https.tls=true
- traefik.http.routers.traefik-https.tls.certresolver=cloudflare
restart: always
networks:
TRAEFIK_REF_NETWORKS
'';
systemd.services.traefik = {
description = "Podman container : ${container_name}";
after = [ "numbus-activation-networked.service" "numbus-activation-chowned.service" ];
wantedBy = [ "multi-user.target" ];
path = [ pkgs.podman ];
serviceConfig = {
User = "numbus-admin";
Environment = [ "XDG_RUNTIME_DIR=/run/user/1000" ];
Type = "exec";
# Pull the latest image before running
ExecStartPre = "${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} pull";
# Bring the service up
ExecStart = "${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} up --remove-orphans";
# Take it down gracefully
ExecStop = "${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} down";
Restart = "on-failure";
RestartSec = "5m";
StartLimitBurst = "3";
};
};
};
}
+9
View File
@@ -0,0 +1,9 @@
# .sops.yaml
keys:
- &primary $SOPS_PUBLIC_KEY
creation_rules:
- path_regex: secrets/secrets.yaml$
key_groups:
- age:
- *primary
@@ -0,0 +1,68 @@
ssh_public_keys: "$SSH_PUBLIC_KEY"
sender_email_address_password: "$SENDER_EMAIL_ADDRESS_PASSWORD"
podman:
frigate: |
DOMAIN_NAME="$DOMAIN_NAME"
FRIGATE_MQTT_USER="$HOME_ASSISTANT_MQTT_USER"
FRIGATE_MQTT_PASSWORD="$HOME_ASSISTANT_MQTT_PASSWORD"
gitea: |
DOMAIN_NAME="$DOMAIN_NAME"
DB_NAME="$GITEA_DB_NAME"
DB_USERNAME="$GITEA_DB_USERNAME"
DB_PASSWORD="$GITEA_DB_PASSWORD"
POSTGRES_HOST="gitea-database"
POSTGRES_PORT=5432
home_assistant: |
DOMAIN_NAME="$DOMAIN_NAME"
HOME_ASSISTANT_MQTT_USER="$HOME_ASSISTANT_MQTT_USER"
HOME_ASSISTANT_MQTT_PASSWORD="$HOME_ASSISTANT_MQTT_PASSWORD"
immich: |
DOMAIN_NAME="$DOMAIN_NAME"
DB_DATABASE_NAME="$IMMICH_DB_NAME"
DB_USERNAME="$IMMICH_DB_USERNAME"
DB_PASSWORD="$IMMICH_DB_PASSWORD"
IMMICH_VERSION="release"
IMMICH_TRUSTED_PROXIES=172.16.50.253
REDIS_HOSTNAME="immich-redis"
DB_HOSTNAME="immich-database"
UPLOAD_LOCATION=/mnt/data/immich
DB_DATA_LOCATION=/mnt/config/immich/database
TZ="Europe/Paris"
it_tools: |
DOMAIN_NAME="$DOMAIN_NAME"
nextcloud: |
DOMAIN_NAME="$DOMAIN_NAME"
NEXTCLOUD_ENABLE_DRI_DEVICE=$TARGET_GRAPHICS
passbolt: |
DOMAIN_NAME="$DOMAIN_NAME"
PASSBOLT_MYSQL_DATABASE="$PASSBOLT_DB_NAME"
PASSBOLT_MYSQL_USER="$PASSBOLT_DB_USERNAME"
PASSBOLT_MYSQL_PASSWORD="$PASSBOLT_DB_PASSWORD"
SENDER_EMAIL_ADDRESS="$SENDER_EMAIL_ADDRESS"
SENDER_EMAIL_ADDRESS_PASSWORD="$SENDER_EMAIL_ADDRESS_PASSWORD"
SENDER_EMAIL_DOMAIN="$SENDER_EMAIL_DOMAIN"
SENDER_EMAIL_PORT="$SENDER_EMAIL_PORT"
EMAIL_ADDRESS="$EMAIL_ADDRESS"
TZ="Europe/Paris"
pi_hole: |
DOMAIN_NAME="$DOMAIN_NAME"
HOME_ROUTER_SUBNET=$HOME_ROUTER_SUBNET
HOME_ROUTER_IP=$HOME_ROUTER_IP
HOME_SERVER_IP=$HOME_SERVER_IP
FTLCONF_webserver_api_password="$FTLCONF_WEBSERVER_PASSWORD"
TZ="Europe/Paris"
traefik: |
DOMAIN_NAME="$DOMAIN_NAME"
CF_DNS_API_TOKEN="$CF_DNS_API_TOKEN"
disks:
content-disk-1: "$CONTENT_DISK_1_KEY"
content-disk-2: "$CONTENT_DISK_2_KEY"
content-disk-3: "$CONTENT_DISK_3_KEY"
content-disk-4: "$CONTENT_DISK_4_KEY"
content-disk-5: "$CONTENT_DISK_5_KEY"
content-disk-6: "$CONTENT_DISK_6_KEY"
parity-disk-1: "$PARITY_DISK_1_KEY"
parity-disk-2: "$PARITY_DISK_2_KEY"
parity-disk-3: "$PARITY_DISK_3_KEY"