Migrated from multi repos to monorepo architecture.

This commit is contained in:
Raphaël Numbus
2026-05-02 12:52:08 +02:00
parent 72668492f5
commit 73adb395c0
218 changed files with 9639 additions and 57 deletions
+12
View File
@@ -0,0 +1,12 @@
{ ... }:
{
imports = [
# To test
./hardware/default.nix
./mail/default.nix
./misc/default.nix
./packages/default.nix
./global.nix
];
}
+42
View File
@@ -0,0 +1,42 @@
{ lib, deviceType, ... }:
with lib;
let
cfg = config.numbus;
country = "";
language = "";
in
{
options.numbus = {
owner = mkOption {
type = types.str;
example = "Alex";
default = "Numbus";
description = "The name of the person who owns this ${deviceType}.";
};
internationalization = {
country = mkOption {
type = types.str;
example = "FR";
default = country;
description = "The country where this ${deviceType} is located.";
};
language = mkOption {
type = types.str;
example = "fr";
default = language;
description = "The language for this ${deviceType}.";
};
locale = mkOption {
type = types.str;
example = "fr_FR";
default = "fr_FR";
description = "The locale for this ${deviceType}.";
};
};
};
}
+9
View File
@@ -0,0 +1,9 @@
{ config, ... }:
{
config = {
hardware.enableRedistributableFirmware = true;
hardware.cpu.intel.updateMicrocode = true;
hardware.cpu.amd.updateMicrocode = true;
};
}
+9
View File
@@ -0,0 +1,9 @@
{ ... }:
{
imports = [
# To test
./disks/default.nix
./cpu.nix
];
}
+192
View File
@@ -0,0 +1,192 @@
{ config, lib, ... }:
with lib;
let
cfg = config.numbus.hardware.disks.boot;
bootCount = builtins.length cfg.list;
singleDiskConfig = {
disko.devices.disk.main = {
type = "disk";
device = head cfg.list;
content = {
type = cfg.partitionTableScheme;
partitions = {
ESP = {
size = cfg.partition.boot.size;
type = cfg.partition.boot.esp;
content = {
type = "filesystem";
format = cfg.partition.boot.filesystem;
mountpoint = "/boot";
mountOptions = [ "umask=0077" ];
};
};
swap = {
size = cfg.partition.swap.size;
content = {
type = "swap";
randomEncryption = cfg.partition.swap.encrypt;
};
};
luks = {
size = cfg.partition.root.size;
content = {
type = "luks";
name = "boot";
settings.keyFile = "/run/secrets/disks/boot";
content = {
type = "filesystem";
format = cfg.partition.root.filesystem;
mountpoint = "/";
};
};
};
};
};
};
};
raid1DiskConfig = {
disko.devices.disk = lib.listToAttrs (lib.imap0 (i: device: {
name = "boot-${toString i}";
value = {
type = "disk";
inherit device;
content = {
type = cfg.partitionTableScheme;
partitions = {
ESP = { size = cfg.partition.boot.size; type = cfg.partition.boot.esp; content = { type = "mdraid"; name = "boot"; }; };
swap = { size = cfg.partition.swap.size; content = { type = "mdraid"; name = "swap"; }; };
mdadm = { size = cfg.partition.root.size; content = { type = "mdraid"; name = "raid1"; }; };
};
};
};
}) cfg.list);
disko.devices.mdadm = {
boot = {
type = "mdadm";
level = 1;
metadata = "1.0";
content = {
type = "filesystem";
format = cfg.partition.boot.filesystem;
mountpoint = "/boot";
mountOptions = [ "umask=0077" ];
};
};
swap = {
type = "mdadm";
level = 1;
content = {
type = "swap";
randomEncryption = cfg.partition.swap.encrypt;
};
};
raid1 = {
type = "mdadm";
level = 1;
content = {
type = "luks";
name = "boot";
settings.keyFile = "/run/secrets/disks/boot";
content = {
type = "filesystem";
format = cfg.partition.root.filesystem;
mountpoint = "/";
};
};
};
};
};
in
{
options.numbus.hardware.disks = {
boot = {
list = mkOption {
type = types.listOf types.str;
example = [ "/dev/disk/by-id/nvme_SAMSUNG_MZVPYEHCO_159Ejz224G0000" "/dev/disk/by-id/ata-San_Disk_159Ejz224G" ];
description = "A set of by-id path of disk(s) that will be used as boot disk(s). At least one disk must be set.";
};
partitionTableScheme = mkOption {
type = types.enum [ "gpt" "mbr" ];
default = "gpt";
example = "gpt";
description = "The scheme of the partition table. Use \"gpt\" for modern devices and \"mbr\" for legacy ones.";
};
partition = {
root = {
filesystem = mkOption {
type = types.enum [ "ext4" "btrfs" "xfs" ];
default = "ext4";
example = "ext4";
description = "The filesystem to use for the root partition of the boot disk(s).";
};
size = mkOption {
type = types.str;
default = "100%";
example = "100%";
description = "The size of the root partition. Use G for GBs and M for MBs.";
};
};
boot = {
filesystem = mkOption {
type = types.enum [ "vfat" ];
default = "vfat";
example = "vfat";
description = "The filesystem to use for the boot partition of the boot disk(s).";
};
esp = mkOption {
type = types.enum [ "EF00" "EF02" ];
default = "EF00";
example = "EF00";
description = "The ESP type to use for the boot partition. Use EF02 for UEFI and EF00 for BIOS.";
};
size = mkOption {
type = types.str;
default = "1G";
example = "1G";
description = "The size of the boot partition.";
};
};
swap = {
enable = mkOption {
type = types.bool;
default = true;
example = true;
description = "Wether to create a swap partition. Useful for servers that don't have a lot of RAM.";
};
encrypt = mkOption {
type = types.bool;
default = true;
example = true;
description = "Wether to encrypt randomly the swap partition. Disable if you need hibernation";
};
size = mkOption {
type = types.str;
default = "16G";
example = "16G";
description = "Size of the swap partition. Use G for GBs and M for MBs.";
};
};
};
};
};
config = mkMerge [
{
sops.secrets."disks/boot" = {
sopsFile = "/etc/nixos/secrets/disks/boot.yaml";
gid = "0";
uid = "0";
mode = "0400";
};
}
(mkIf (bootCount == 1) singleDiskConfig)
(mkIf (bootCount == 2) raid1DiskConfig)
];
}
+107
View File
@@ -0,0 +1,107 @@
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.numbus.hardware.disks.content;
contentCount = builtins.length cfg.list;
parityCount = builtins.length config.numbus.hardware.disks.parity.list;
contentDisks = lib.imap0 (i: device: {
name = "content-${toString i}";
value = {
type = "disk";
inherit device;
content = {
type = cfg.partitionTableScheme;
partitions.luks = {
size = cfg.partition.size;
content = {
type = "luks";
name = "content-${toString i}";
settings.keyFile = "/run/secrets/disks/content-${toString i}";
initrdUnlock = false;
content = {
type = "filesystem";
format = cfg.partition.filesystem;
mountpoint = "/mnt/content-${toString i}";
mountOptions = [ "noauto" "nofail" ];
};
};
};
};
};
}) cfg.list;
in
{
options.numbus.hardware.disks = {
content = {
list = mkOption {
type = types.listOf types.str;
example = [ "/dev/disk/by-id/ata_Hitachi_MZVPYEHCO_159Ejz224G0000" "/dev/disk/by-id/ata-WD_159Ejz224G" ];
default = [];
description = "A set of by-id path of disk(s) that will be used as content disk(s).";
};
partitionTableScheme = mkOption {
type = types.enum [ "gpt" "mbr" ];
default = "gpt";
example = "gpt";
description = "The scheme of the partition table. Use \"gpt\" for modern devices and \"mbr\" for legacy ones.";
};
partition = {
filesystem = mkOption {
type = types.enum [ "ext4" "btrfs" "xfs" ];
default = "xfs";
example = "xfs";
description = "The filesystem to use for the main partition of the content disk(s).";
};
size = mkOption {
type = types.str;
default = "100%";
example = "100%";
description = "The size of the main partition. Use G for GBs and M for MBs.";
};
};
};
};
config = mkIf (contentCount > 0 && (parityCount != 1 && contentCount != 1)) {
disko.devices.disk = builtins.listToAttrs contentDisks;
sops.secrets = listToAttrs (map (i:
nameValuePair "disks/content-${toString i}" {
sopsFile = "/etc/nixos/secrets/disks/content.yaml";
gid = "0";
uid = "0";
mode = "0400";
}
) (range 0 (contentCount - 1)));
systemd.services.mount-content-disks = {
description = "Mount content disks.";
before = [ "mnt-data.mount" ];
requiredBy = [ "mnt-data.mount" ];
requires = [ "sops-install-secrets.service" ];
path = [ pkgs.cryptsetup pkgs.util-linux ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
};
script = let
mountContentDisk = i: ''
if [ ! -e /dev/mapper/content-${toString i} ]; then
cryptsetup luksOpen --key-file /run/secrets/disks/content-${toString i} /dev/disk/by-partlabel/disk-content-${toString i}-luks content-${toString i}
fi
mkdir -p /mnt/content-${toString i}
if ! mountpoint -q /mnt/content-${toString i}; then
mount -t ${cfg.partition.filesystem} /dev/mapper/content-${toString i} /mnt/content-${toString i}
fi
'';
in ''
${concatMapStrings mountContentDisk (range 0 (contentCount - 1))}
'';
};
};
}
+12
View File
@@ -0,0 +1,12 @@
{ ... }:
{
imports = [
./boot.nix
./content.nix
./mergerfs-snapraid.nix
./mirror.nix
./parity.nix
./spindown.nix
];
}
@@ -0,0 +1,41 @@
{ config, lib, ... }:
with lib;
let
cfg = config.numbus.hardware.disks;
contentCount = builtins.length cfg.content.list;
parityCount = builtins.length cfg.parity.list;
in
{
config = mkIf (contentCount >= 2 && parityCount >= 1) {
services.snapraid = {
enable = true;
contentFiles = map (i: "/mnt/content-${toString i}/snapraid.content") (range 0 (contentCount - 1));
parityFiles = map (i: "/mnt/parity-${toString i}/snapraid.parity") (range 0 (parityCount - 1));
dataDisks = listToAttrs (imap0 (i: _: nameValuePair "d${toString i}" "/mnt/content-${toString i}") cfg.content.list);
};
fileSystems."/mnt/data" = {
device = concatStringsSep ":" (map (i: "/mnt/content-${toString i}") (range 0 (contentCount - 1)));
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"
];
};
};
}
+75
View File
@@ -0,0 +1,75 @@
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.numbus.hardware.disks;
contentCount = builtins.length cfg.content.list;
parityCount = builtins.length cfg.parity.list;
dataMirror = {
disko.devices.disk = listToAttrs (imap0 (i: device: {
name = "mirror-${toString i}";
value = {
type = "disk";
inherit device;
content = {
type = cfg.partitionTableScheme;
partitions.raid = {
size = cfg.content.partition.size;
content = {
type = "mdraid";
name = "mirror";
};
};
};
};
}) (cfg.content.list ++ cfg.parity.list));
disko.devices.mdadm.mirror = {
type = "mdadm";
level = 1;
content = {
type = "luks";
name = "mirror";
settings.keyFile = "/run/secrets/disks/mirror";
initrdUnlock = false;
content = {
type = "filesystem";
format = cfg.content.partition.filesystem;
mountpoint = "/mnt/data";
mountOptions = [ "noauto" "nofail" ];
};
};
};
};
in
{
config = mkIf (contentCount == 1 && parityCount == 1) (mkMerge [
dataMirror
{
systemd.services.mount-mirror = {
description = "Mount the disks mirror.";
before = [ "mnt-data.mount" ];
requiredBy = [ "mnt-data.mount" ];
requires = [ "sops-install-secrets.service" ];
path = [ pkgs.cryptsetup pkgs.util-linux ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
};
script = ''
if [ ! -e /dev/mapper/mirror ]; then
cryptsetup open /dev/md/mirror mirror --key-file /run/secrets/disks/mirror
fi
mkdir -p /mnt/data
if ! mountpoint -q /mnt/data; then
mount -t ${cfg.content.partition.filesystem} /dev/mapper/mirror /mnt/data
fi
'';
};
}
]);
}
+107
View File
@@ -0,0 +1,107 @@
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.numbus.hardware.disks.parity;
parityCount = builtins.length cfg.list;
parityDisks = lib.imap0 (i: device: {
name = "parity-${toString i}";
value = {
type = "disk";
inherit device;
content = {
type = cfg.partitionTableScheme;
partitions.luks = {
size = cfg.partition.size;
content = {
type = "luks";
name = "parity-${toString i}";
settings.keyFile = "/run/secrets/disks/parity-${toString i}";
initrdUnlock = false;
content = {
type = "filesystem";
format = cfg.partition.filesystem;
mountpoint = "/mnt/parity-${toString i}";
mountOptions = [ "noauto" "nofail" ];
};
};
};
};
};
}) cfg.list;
in
{
options.numbus.hardware.disks = {
parity = {
list = mkOption {
type = types.listOf types.str;
example = [ "/dev/disk/by-id/ata_WDC_MZVPYEHCO_159Ejz224G0000" "/dev/disk/by-id/ata-San_Disk_159Ejz224G" ];
default = [];
description = "A set of by-id path of disk(s) that will be used as parity disk(s).";
};
partitionTableScheme = mkOption {
type = types.enum [ "gpt" "mbr" ];
default = "gpt";
example = "gpt";
description = "The scheme of the partition table. Use \"gpt\" for modern devices and \"mbr\" for legacy ones.";
};
partition = {
filesystem = mkOption {
type = types.enum [ "ext4" "btrfs" "xfs" ];
default = "xfs";
example = "xfs";
description = "The filesystem to use for the main partition of the parity disk(s).";
};
size = mkOption {
type = types.str;
default = "100%";
example = "100%";
description = "The size of the main partition. Use G for GBs and M for MBs.";
};
};
};
};
config = mkIf (parityCount > 1) {
disko.devices.disk = builtins.listToAttrs parityDisks;
sops.secrets = listToAttrs (map (i:
nameValuePair "disks/parity-${toString i}" {
sopsFile = "/etc/nixos/secrets/disks/parity.yaml";
gid = "0";
uid = "0";
mode = "0400";
}
) (range 0 (parityCount - 1)));
systemd.services.mount-parity-disks = {
description = "Mount parity disks.";
before = [ "mnt-data.mount" ];
requiredBy = [ "mnt-data.mount" ];
requires = [ "sops-install-secrets.service" ];
path = [ pkgs.cryptsetup pkgs.util-linux ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
};
script = let
mountparityDisk = i: ''
if [ ! -e /dev/mapper/parity-${toString i} ]; then
cryptsetup luksOpen --key-file /run/secrets/disks/parity-${toString i} /dev/disk/by-partlabel/disk-parity-${toString i}-luks parity-${toString i}
fi
mkdir -p /mnt/parity-${toString i}
if ! mountpoint -q /mnt/parity-${toString i}; then
mount -t ${cfg.partition.filesystem} /dev/mapper/parity-${toString i} /mnt/parity-${toString i}
fi
'';
in
''
${concatMapStrings mountparityDisk (range 0 (parityCount - 1))}
'';
};
};
}
@@ -0,0 +1,46 @@
{ config, lib, ... }:
with lib;
let
hardDrives = config.numbus.hardware.spindown.list;
cfg = config.numbus.hardware;
in
{
config = mkIf (cfg.HddSpindown.enable == true) {
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}";
};
};
};
options.numbus = {
hardware = {
spindown = {
enable = mkEnableOption "hard drives spin down when inactive in order to save power.";
list = mkOption {
description = "The list of compatible hard drives that will spin down.";
type = types.listOf types.str;
default = [];
example = [ "/dev/disk/by-id/ata_Hitachi_MZVPYEHCO_159Ejz224G0000" "/dev/disk/by-id/ata-WD_159Ejz224G" ];
};
optimize = mkOption {
description = "Optimize services to reduce HDD wakeups when spindown is enabled. Can be set to \"compatible\" to optimize all compatible services, or a list of service names to optimize.";
type = types.nullOr (types.either (types.enum [ "compatible" ]) (types.listOf types.str));
default = "compatible";
example = "[ \"crafty\" \"gitea\" ]";
};
};
};
};
}
+10
View File
@@ -0,0 +1,10 @@
{ ... }:
{
imports = [
# To test
./disk-space.nix
./smart.nix
./smtp.nix
];
}
+130
View File
@@ -0,0 +1,130 @@
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.numbus-server.services.disk-space-checker;
disk_space_notifier = pkgs.writeScript "disk-space-notifier.sh" ''
#!${pkgs.bash}/bin/bash
ALERT_FILE="/var/lib/numbus-server/disk_alert.env"
if [ ! -f "$ALERT_FILE" ]; then
exit 0
fi
source "$ALERT_FILE"
rm "$ALERT_FILE"
# Update the timestamp for this specific path to prevent spamming
SAFE_PATH=$(echo "$DISK_ALERT_PATH" | tr '/' '_')
date +%s > "/var/lib/numbus-server/last_alert_$SAFE_PATH.ts"
ADMIN_EMAIL="${config.numbus-server.mail.adminAddress}"
USER_EMAIL="${config.numbus-server.mail.userAddress}"
OWNER_NAME="${config.numbus-server.owner}"
SUBJECT="Numbus Server Alert: Low Disk Space Detected"
TECH_BODY="
Disk Space Alert:
Server owner: $OWNER_NAME
The following mount point has exceeded the safety threshold:
Mount: $DISK_ALERT_PATH
Usage: $DISK_ALERT_USAGE%
Full partition details:
$(df -h "$DISK_ALERT_PATH")
Action required: Please investigate and clear space or expand the storage capacity.
"
FRIENDLY_BODY="Cher/Chère $OWNER_NAME,
L'espace de stockage de votre serveur Numbus est presque saturé.
Disque concerné : $DISK_ALERT_PATH ($DISK_ALERT_USAGE% utilisé)
Votre administrateur a été notifié avec les détails techniques.
Nous vous conseillons d'éviter d'ajouter des fichiers volumineux pour garantir le bon fonctionnement de vos services.
Contactez votre administrateur afin d'évoquer les possibilités d'expansion du stockage.
"
printf "Subject: [ADMIN] %s\n\n%s" "$SUBJECT" "$TECH_BODY" | /run/wrappers/bin/sendmail -t "$ADMIN_EMAIL"
printf "Subject: [Alerte] Espace disque presque saturé sur votre serveur Numbus\n\n%s\n\nMerci de votre confiance,\nL'équipe de support,\nNumbus-Server." "$FRIENDLY_BODY" | /run/wrappers/bin/sendmail -t "$USER_EMAIL"
'';
disk_space_checker = pkgs.writeScript "disk-space-checker.sh" ''
#!${pkgs.bash}/bin/bash
# Safety threshold in percentage
THRESHOLD=90
# Paths to monitor (Root and MergerFS data pool)
PATHS=("/" "/mnt/data")
ALERT_FILE="/var/lib/numbus-server/disk_alert.env"
for path in "''${PATHS[@]}"; do
# Skip if path does not exist (e.g. if mergerfs is not mounted yet)
if [ ! -d "$path" ]; then
continue
fi
# Anti-spam logic: Check if we alerted on this path recently (7 days = 604800 seconds)
SAFE_PATH=$(echo "$path" | tr '/' '_')
TS_FILE="/var/lib/numbus-server/last_alert_$SAFE_PATH.ts"
NOW=$(date +%s)
if [ -f "$TS_FILE" ]; then
LAST_SENT=$(cat "$TS_FILE")
DIFF=$((NOW - LAST_SENT))
if [ "$DIFF" -lt 604800 ]; then
echo "Alert for $path was sent recently. Skipping notification to avoid spam."
continue
fi
fi
# Extract usage percentage using df
USAGE=$(df -h "$path" | awk 'NR==2 {print $5}' | sed 's/%//')
if [ "$USAGE" -ge "$THRESHOLD" ]; then
echo "DISK_ALERT_PATH=$path" > "$ALERT_FILE"
echo "DISK_ALERT_USAGE=$USAGE" >> "$ALERT_FILE"
echo "Threshold exceeded for $path ($USAGE%). Triggering notification."
# Trigger the notification service
/run/current-system/sw/bin/systemctl start disk-space-notifier.service
# We exit after the first alert to avoid multiple overlapping emails in one run
exit 0
fi
done
'';
in
{
config = mkIf cfg.enable {
systemd.services.disk-space-notifier = {
description = "Email notification for low disk space";
serviceConfig = {
Type = "oneshot";
ExecStart = "${disk_space_notifier}";
};
};
systemd.services.disk-space-checker = {
description = "Check for low disk space";
serviceConfig = {
Type = "oneshot";
ExecStart = "${disk_space_checker}";
};
};
systemd.timers.disk-space-checker = {
description = "Run disk space check every day";
timerConfig = {
OnCalendar = "daily";
Persistent = true;
};
wantedBy = [ "timers.target" ];
};
};
}
+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-server.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-server.mail.userAddress}"
OWNER_NAME="${config.numbus-server.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-server.mail.fromAddress;
recipient = "${config.numbus-server.mail.userAddress},${config.numbus-server.mail.adminAddress}";
};
};
};
}
+99
View File
@@ -0,0 +1,99 @@
{ config, pkgs, lib, ... }:
with lib;
let
cfg = config.numbus.mail;
in
{
options.numbus.mail = {
enable = mkEnableOption "Email sending functionality";
userAddress = mkOption {
description = "The address of the user this server will send emails to";
type = types.str;
example = "user@your-domain.com";
};
adminAddress = mkOption {
description = "The address of the admin this server will send emails to";
type = types.str;
example = "admin@your-domain.com";
};
smtpUsername = mkOption {
description = "The username/email that will be use to authenticate to the SMTP server";
type = types.str;
example = "your-smtp-enabled-address@your-domain.com";
};
smtpPasswordPath = mkOption {
description = "The path to a file containing the password that will be use to authenticate to the SMTP server";
type = types.path;
example = /run/secrets/system/mail/smtpPassword;
};
fromAddress = mkOption {
description = "This server will send emails from this address";
type = types.str;
default = "numbus-server-noreply@${config.numbus.services.domain}";
example = "numbus-server-noreply@your-domain.com";
};
smtpServer = mkOption {
description = "The SMTP server address your server will use to send emails";
type = types.str;
default = "smtp.gmail.com";
example = "smtp.your-provider.com";
};
smtpPort = mkOption {
description = "The SMTP port your server will connect to to send emails";
type = types.port;
default = 587;
example = 587;
};
smtpEncryption = mkOption {
description = "The encryption method for SMTP : NONE (NOT RECOMMENDED), TLS (port 465, also called SSL), or STARTTLS (port 587). STARTTLS is recommended.";
type = types.enum [ "NONE" "TLS" "STARTTLS" ];
default = "STARTTLS";
example = "STARTTLS";
};
};
config = mkIf cfg.enable {
sops.secrets."smtpPassword" = {
sopsFile = /etc/nixos/secrets/system/mail.yaml;
owner = "numbus-admin";
mode = "0600";
};
environment.etc."aliases" ={
mode = "0440";
text = ''
root: ${cfg.userAddress}, ${cfg.adminAddress}
'';
};
programs.msmtp = {
enable = true;
defaults = {
aliases = "/etc/aliases";
timeout = 60;
syslog = "on";
};
accounts.default = {
auth = true;
host = cfg.smtpServer;
port = cfg.smtpPort;
from = cfg.fromAddress;
user = cfg.smtpUsername;
tls = true;
tls_starttls = true;
passwordeval = "${pkgs.coreutils}/bin/cat ${cfg.smtpPasswordPath}";
};
};
};
}
+15
View File
@@ -0,0 +1,15 @@
{ config, deviceType, ... }:
{
config = mkIf (deviceType == "computer" || deviceType == "tv" ) {
# Enable sound with pipewire.
services.pulseaudio.enable = false;
security.rtkit.enable = true;
services.pipewire = {
enable = true;
alsa.enable = true;
alsa.support32Bit = true;
pulse.enable = true;
};
};
}
+13
View File
@@ -0,0 +1,13 @@
{ ... }:
{
imports = [
# To test
./audio.nix
./internationalisation.nix
./power.nix
./printer.nix
./update.nix
./users.nix
];
}
@@ -0,0 +1,30 @@
{ config, lib, ... }:
with lib;
let
cfg = config.numbus.internationalization;
in
{
config = {
i18n.defaultLocale = "${cfg.locale}.UTF-8";
i18n.extraLocaleSettings = {
LC_ADDRESS = "${cfg.locale}.UTF-8";
LC_IDENTIFICATION = "${cfg.locale}.UTF-8";
LC_MEASUREMENT = "${cfg.locale}.UTF-8";
LC_MONETARY = "${cfg.locale}.UTF-8";
LC_NAME = "${cfg.locale}.UTF-8";
LC_NUMERIC = "${cfg.locale}.UTF-8";
LC_PAPER = "${cfg.locale}.UTF-8";
LC_TELEPHONE = "${cfg.locale}.UTF-8";
LC_TIME = "${cfg.locale}.UTF-8";
};
console.keyMap = toLower cfg.language;
services.xserver.xkb = {
layout = toLower cfg.language;
variant = "";
};
};
}
+12
View File
@@ -0,0 +1,12 @@
{ config, lib, pkgs, ... }:
{
config = {
services.autoaspm.enable = true;
powerManagement.powertop.enable = true;
boot.kernelParams = [
"pcie_aspm=force"
"consoleblank=60"
];
};
}
+8
View File
@@ -0,0 +1,8 @@
{ config, ... }:
{
config = mkIf (deviceType == "computer" || deviceType == "tv" ) {
# Enable CUPS to print documents.
services.printing.enable = true;
};
}
+23
View File
@@ -0,0 +1,23 @@
{ config, inputs, ... }:
{
config = {
system.autoUpgrade = {
enable = true;
allowReboot = false;
flake = inputs.self.outPath;
flags = [ "--print-build-logs" ];
dates = "02:00";
randomizedDelaySec = "45min";
};
nix.gc = {
automatic = true;
dates = "weekly";
options = "--delete-older-than 7d";
};
nix.settings.experimental-features = [ "nix-command" "flakes" ];
nix.settings.auto-optimise-store = true;
};
}
+20
View File
@@ -0,0 +1,20 @@
{ config, pkgs, ... }:
let
cfg = config.numbus;
in
{
users.users.numbus-admin = {
shell = pkgs.fish;
isNormalUser = true;
description = cfg.owner;
extraGroups = [ "wheel" ];
uid = 1000;
initialPassword = "changeMe!";
# required for auto start before user login
linger = true;
# required for rootless container with multiple users
autoSubUidGidRange = true;
};
}
+13
View File
@@ -0,0 +1,13 @@
{ ... }:
{
imports = [
# To test
./fail2ban.nix
./flatpaks.nix
./numbus-cli.nix
./ssh.nix
./terminal.nix
./updates.nix
];
}
+5
View File
@@ -0,0 +1,5 @@
{ config, ... }:
{
services.fail2ban.enable = true;
}
+16
View File
@@ -0,0 +1,16 @@
{ config, lib, ... }:
with lib;
{
config = mkIf (services.flatpak.packages != []) {
services.flatpak.enable = true;
services.flatpak.update.auto.enable = true;
services.flatpak.uninstallUnmanaged = true;
services.flatpak.remotes = mkOptionDefault [{
name = "flathub";
location = "https://dl.flathub.org/repo/flathub.flatpakrepo";
}];
};
}
+155
View File
@@ -0,0 +1,155 @@
{ pkgs, lib, ... }:
let
# Base script header and common setup for all device types
baseScriptHeader = ''
#!/usr/bin/env bash
set -euo pipefail
# The device type is baked into the script at build time
readonly NUMBUS_DEVICE_TYPE="${deviceType}"
# Common utility function for consistent output
numbus_echo() {
echo "[Numbus CLI - $NUMBUS_DEVICE_TYPE] $*"
}
'';
# --- Device-specific script definitions ---
serverScript = baseScriptHeader + ''
case "$1" in
test)
numbus_echo "Hello World! This is a Numbus Server."
;;
status)
numbus_echo "Checking system status for Server..."
numbus_echo "--- Podman Containers ---"
podman ps || numbus_echo "No Podman containers found or Podman not running."
systemctl list-units --type=service "numbus-*" --no-pager || numbus_echo "No Numbus services found."
;;
upgrade)
numbus_echo "Pulling latest configuration and upgrading for Server..."
# Add server-specific upgrade logic here (e.g., nixos-rebuild switch)
;;
*)
numbus_echo "Numbus CLI (Server edition)"
echo ""
echo "Usage: numbus <command>"
echo ""
echo "Commands:"
echo " test - Print a test message"
numbus_echo " status - Show status of Numbus services (Podman, systemd)"
numbus_echo " upgrade - Upgrade the server configuration"
;;
esac
'';
backupScript = baseScriptHeader + ''
case "$1" in
test)
numbus_echo "Hello World! This is a Numbus Backup Server."
;;
status)
numbus_echo "Checking system status for Backup Server..."
systemctl list-units --type=service "numbus-*" --no-pager || numbus_echo "No Numbus services found."
# Add backup-specific status checks here (e.g., SnapRAID status, rsync jobs)
;;
restore)
numbus_echo "Starting interactive restore wizard for Backup Server..."
# Add backup-specific restore logic here
;;
upgrade)
numbus_echo "Pulling latest configuration and upgrading for Backup Server..."
# Add backup-specific upgrade logic here
;;
*)
numbus_echo "Numbus CLI (Backup Server edition)"
echo ""
echo "Usage: numbus <command>"
echo ""
echo "Commands:"
numbus_echo " test - Print a test message"
numbus_echo " status - Show status of Numbus services"
numbus_echo " restore - Start interactive restore wizard"
numbus_echo " upgrade - Upgrade the backup server configuration"
;;
esac
'';
computerScript = baseScriptHeader + ''
case "$1" in
test)
numbus_echo "Hello World! This is a Numbus Computer."
;;
status)
numbus_echo "Checking system status for Computer..."
systemctl list-units --type=service "numbus-*" --no-pager || numbus_echo "No Numbus services found."
# Add computer-specific status checks (e.g., GPU status, Flatpak updates)
;;
upgrade)
numbus_echo "Pulling latest configuration and upgrading for Computer..."
# Add computer-specific upgrade logic here
;;
*)
numbus_echo "Numbus CLI (Computer edition)"
echo ""
echo "Usage: numbus <command>"
echo ""
echo "Commands:"
numbus_echo " test - Print a test message"
numbus_echo " status - Show status of Numbus services"
numbus_echo " upgrade - Upgrade the computer configuration"
;;
esac
'';
tvScript = baseScriptHeader + ''
case "$1" in
test)
numbus_echo "Hello World! This is a Numbus TV."
;;
status)
numbus_echo "Checking system status for TV..."
systemctl list-units --type=service "numbus-*" --no-pager || numbus_echo "No Numbus services found."
# Add TV-specific status checks (e.g., media server status, remote connectivity)
;;
remote)
numbus_echo "Pairing a new Bluetooth remote for TV..."
# Add TV-specific remote pairing logic here
;;
upgrade)
numbus_echo "Pulling latest configuration and upgrading for TV..."
# Add TV-specific upgrade logic here
;;
*)
numbus_echo "Numbus CLI (TV edition)"
echo ""
echo "Usage: numbus <command>"
echo ""
numbus_echo "Commands:"
numbus_echo " test - Print a test message"
numbus_echo " status - Show status of Numbus services"
numbus_echo " remote - Pair a new Bluetooth remote"
numbus_echo " upgrade - Upgrade the TV configuration"
;;
esac
'';
# Use lib.switch to select the correct script based on deviceType
selectedScript = lib.switch deviceType {
server = serverScript;
backup = backupScript;
computer = computerScript;
tv = tvScript;
} (throw "Unknown Numbus device type: ${deviceType}"); # Fail if an unknown deviceType is encountered
# Define the numbus-cli package using the selected script
numbus = pkgs.writeShellScriptBin "numbus" selectedScript;
in {
environment.systemPackages = [ numbus ];
# Add a useful alias so people can check the type via env
environment.variables.NUMBUS_DEVICE_TYPE = deviceType;
}
+21
View File
@@ -0,0 +1,21 @@
{ config, ... }:
{
config.services.openssh = {
enable = true;
settings = {
PasswordAuthentication = false;
KbdInteractiveAuthentication = false;
PermitRootLogin = "no";
};
AllowUsers = [ "numbus-admin" ];
ports = [ 245 ]
};
config.sops.secrets."authorizedSshPublicKeys" = {
sopsFile = /etc/nixos/secrets/system/ssh.yaml;
mode = "0440";
owner = "numbus-admin";
path = "/home/numbus-admin/.ssh/authorized_keys";
};
}
+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 Numbus !\n\n- This system is managed by NixOS\n- All changes are futile\n- Please consider buying support to get assistance\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 -";
};
};
}
+23
View File
@@ -0,0 +1,23 @@
{ config, inputs, ... }:
{
config = {
system.autoUpgrade = {
enable = true;
allowReboot = false;
flake = inputs.self.outPath;
flags = [ "--print-build-logs" ];
dates = "21:00";
randomizedDelaySec = "45min";
};
nix.gc = {
automatic = true;
dates = "weekly";
options = "--delete-older-than 7d";
};
nix.settings.experimental-features = [ "nix-command" "flakes" ];
nix.settings.auto-optimise-store = true;
};
}