Added the rest of the configuration. Still some things to add.

This commit is contained in:
Raphaël Numbus
2026-02-22 20:34:44 +01:00
parent 2e16ac3711
commit f445bd8659
23 changed files with 705 additions and 3 deletions
+29 -1
View File
@@ -1,2 +1,30 @@
# numbus-server-module
# ☁️ Numbus Server: Your Personal Cloud, Simplified 🚀
Welcome to the **Numbus Server** project!
⚠️ This repository contains the NixOS module that configures the numbus server. **Please head to https://gittea.dev/numbus/numbus-server to get more information and installation instructions.**
This repository provides a complete NixOS configuration to deploy a personal home server with a rich set of services in minutes. Our goal is to make self-hosting accessible to everyone, allowing you to take back control of your data with a solution that is easy to manage and highly reliable.
## 🛠️ Key Technologies
- **NixOS:** A declarative Linux distribution that makes system management a breeze.
- **Nix Flakes:** For reproducible builds and dependency management.
- **Numbus Server NixOS module:** To make user's configuration simpler.
- **Podman:** To run containerized services with ease.
- **Traefik:** A modern reverse proxy to access services securely.
- **Sops-nix:** For a secure and convenient way of managing secrets.
- **NixOS-anywhere:** For a seamless initial deployment to any machine.
- **Disko:** For a declarative and predictable disk partitioning.
## 🔧 Numbus Server NixOS Module
This repository contains the code of this module. This modules contains the **complex NixOS configuration** : networking, user creation, container management, secrets provisioning, power settings, and much more. Since all the **complexity** of the configuration is contained in this module, this allows the end user configuration to be very **simple and clean**.
## 🤝 Contributing
Contributions are welcome! If you have any ideas, suggestions, or bug reports, please open an issue or submit a pull request.
## 📄 License
This project is licensed under the AGPLv3. See the [LICENSE](LICENSE) file for details.
+6 -1
View File
@@ -2,7 +2,12 @@
{
imports = [
./global.nix
./hardware/default.nix
./mail/default.nix
./misc/default.nix
./networking/default.nix
./packages/default.nix
./services/default.nix
./global.nix
];
}
+11
View File
@@ -0,0 +1,11 @@
{ config, ... }:
{
boot.initrd.systemd.enable = true;
boot.loader.systemd-boot.enable = true;
boot.loader.efi.canTouchEfiVariables = true;
boot.kernel.sysctl = {
"vm.overcommit_memory" = 1;
};
}
+7
View File
@@ -0,0 +1,7 @@
{ config, ... }:
{
hardware.enableRedistributableFirmware = true;
hardware.cpu.intel.updateMicrocode = true;
hardware.cpu.amd.updateMicrocode = true;
}
+9
View File
@@ -0,0 +1,9 @@
{ ... }:
{
imports = [
./boot.nix
./cpu.nix
./disks.nix
];
}
+322
View File
@@ -0,0 +1,322 @@
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.numbus.hardware;
mkDataDisk = idx: device: {
name = "content-${toString idx}";
value = {
type = "disk";
device = device;
content = {
type = "gpt";
partitions = {
luks = {
size = "100%";
content = {
type = "luks";
name = "crypted-content-${toString idx}";
initrdUnlock = false;
settings = {
keyFile = "/etc/secrets/disks/content-${toString idx}";
allowDiscards = true;
crypttabExtraOpts = [ "nofail" ];
};
content = {
type = "filesystem";
format = cfg.dataDisksFilesystem;
mountpoint = "/mnt/content-${toString idx}";
mountOptions = [ "nofail" "noauto" ];
};
};
};
};
};
};
};
mkParityDisk = idx: device: {
name = "parity-${toString idx}";
value = {
type = "disk";
device = device;
content = {
type = "gpt";
partitions = {
luks = {
size = "100%";
content = {
type = "luks";
name = "crypted-parity-${toString idx}";
initrdUnlock = false;
settings = {
keyFile = "/etc/secrets/disks/parity-${toString idx}";
allowDiscards = true;
crypttabExtraOpts = [ "nofail" ];
};
content = {
type = "filesystem";
format = cfg.parityDisksFilesystem;
mountpoint = "/mnt/parity-${toString idx}";
mountOptions = [ "nofail" "noauto" ];
};
};
};
};
};
};
};
isMirror = length cfg.bootDisksList > 1;
bootDisksConfig = if isMirror then {
mdadm = {
boot = {
type = "mdadm";
level = 1;
metadata = "1.2";
content = {
type = "filesystem";
format = "vfat";
mountpoint = "/boot";
mountOptions = [ "umask=0077" ];
};
};
};
disk = listToAttrs (imap1 (i: device: {
name = "boot-${toString i}";
value = {
type = "disk";
device = device;
content = {
type = "gpt";
partitions = {
ESP = {
size = "1G";
type = "EF00";
content = {
type = "mdraid";
name = "boot";
};
};
luks = {
size = "100%";
content = {
type = "luks";
name = "crypted-boot-${toString i}";
settings = {
allowDiscards = true;
keyFile = "/etc/secrets/disks/boot-${toString i}";
};
content = {
type = "lvm_pv";
vg = "pool";
};
};
};
};
};
};
}) cfg.bootDisksList);
} else {
disk = {
"boot-1" = {
type = "disk";
device = head cfg.bootDisksList;
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";
};
};
};
};
};
};
};
};
lvmConfig = {
lvm_vg = {
pool = {
type = "lvm_vg";
lvs = {
swap = {
size = cfg.swapSize;
content = {
type = "swap";
};
} // optionalAttrs isMirror { lvm_type = "mirror"; };
snapraid = {
size = "1G";
content = {
type = "filesystem";
format = "btrfs";
mountpoint = "/mnt/content-0";
};
} // optionalAttrs isMirror { lvm_type = "mirror"; };
root = {
size = "100%";
content = {
type = "btrfs";
extraArgs = [ "-f" ];
subvolumes = {
"/rootfs" = {
mountpoint = "/";
mountOptions = [ "compress=zstd" "noatime" ];
};
"/home" = {
mountpoint = "/home";
mountOptions = [ "compress=zstd" ];
};
"/nix" = {
mountpoint = "/nix";
mountOptions = [ "compress=zstd" "noatime" ];
};
};
};
} // optionalAttrs isMirror { lvm_type = "mirror"; };
};
};
};
};
in
{
options.numbus.hardware = {
dataDisksList = mkOption {
type = types.listOf types.str;
default = [];
example = [ "/dev/disk/by-id/WD_Blue_ATO431_159Ejz224G0000382b" "/dev/disk/by-id/Seagate_Barracuda_159Ejz224G" ];
description = "List by-id path of devices for data disks";
};
parityDisksList = mkOption {
type = types.listOf types.str;
default = [];
example = [ "/dev/disk/by-id/WD_Blue_ATO431_159Ejz224G0000382b" "/dev/disk/by-id/Seagate_Barracuda_159Ejz224G" ];
description = "List of by-id path of devices for parity disks";
};
bootDisksList = mkOption {
type = types.listOf types.str;
default = [];
example = [ "/dev/disk/by-id/nvme_SAMSUNG_MZVPYEHCO_159Ejz224G0000" "/dev/disk/by-id/ata-San_Disk_159Ejz224G" ];
description = "List of by-id path of devices for boot disks";
};
swapSize = mkOption {
type = types.str;
default = "16G";
example = "16G";
description = "Size of the swap partition";
};
dataDisksFilesystem = mkOption {
type = types.enum [ "xfs" "ext4" "btrfs" ];
default = "xfs";
example = "xfs";
description = "Filesystem for data disks. Available filesystem options : xfs, ext4, btrfs";
};
parityDisksFilesystem = mkOption {
type = types.enum [ "xfs" "ext4" "btrfs" ];
default = "xfs";
example = "xfs";
description = "Filesystem for parity disks. Available filesystem options : xfs, ext4, btrfs";
};
};
config = mkIf (cfg.bootDisksList != []) {
disko.devices = mkMerge [
bootDisksConfig
lvmConfig
{
disk = listToAttrs (imap1 mkDataDisk cfg.dataDisksList);
}
{
disk = listToAttrs (imap1 mkParityDisk cfg.parityDisksList);
}
];
services.snapraid = {
enable = true;
contentFiles = [ "/mnt/content-0/snapraid.content" ] ++
(map (i: "/mnt/content-${toString i}/snapraid.content") (range 1 (length cfg.dataDisksList)));
parityFiles = map (i: "/mnt/parity-${toString i}/snapraid.parity") (range 1 (length cfg.parityDisksList));
dataDisks = listToAttrs (imap1 (i: _: nameValuePair "d${toString i}" "/mnt/content-${toString i}") cfg.dataDisksList);
};
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"
];
};
systemd.services.mount-disks = {
description = "Mount data and parity disks";
before = [ "mnt-data.mount" ];
requiredBy = [ "mnt-data.mount" ];
path = [ pkgs.cryptsetup pkgs.util-linux ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
};
script = let
mountDataDisk = i: ''
if [ ! -e /dev/mapper/crypted-content-${toString i} ]; then
cryptsetup luksOpen --key-file /etc/secrets/disks/content-${toString i} /dev/disk/by-partlabel/disk-content-${toString i}-luks crypted-content-${toString i}
fi
mkdir -p /mnt/content-${toString i}
if ! mountpoint -q /mnt/content-${toString i}; then
mount -t ${cfg.dataDisksFilesystem} /dev/mapper/crypted-content-${toString i} /mnt/content-${toString i}
fi
'';
mountParityDisk = i: ''
if [ ! -e /dev/mapper/crypted-parity-${toString i} ]; then
cryptsetup luksOpen --key-file /etc/secrets/disks/parity-${toString i} /dev/disk/by-partlabel/disk-parity-${toString i}-luks crypted-parity-${toString i}
fi
mkdir -p /mnt/parity-${toString i}
if ! mountpoint -q /mnt/parity-${toString i}; then
mount -t ${cfg.parityDisksFilesystem} /dev/mapper/crypted-parity-${toString i} /mnt/parity-${toString i}
fi
'';
in ''
${concatMapStrings mountDataDisk (range 1 (length cfg.dataDisksList))}
${concatMapStrings mountParityDisk (range 1 (length cfg.parityDisksList))}
'';
};
};
}
+7
View File
@@ -0,0 +1,7 @@
{ ... }:
{
imports = [
./smtp.nix
];
}
+84
View File
@@ -0,0 +1,84 @@
{ config, pkgs, lib, ... }:
with lib;
let
cfg = config.numbus.services.mail;
in
{
options.numbus.services.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/smtp-password";
};
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;
};
};
config = mkIf cfg.enable {
environment.etc."aliases".text = ''
root: ${cfg.userAddress}, ${cfg.adminAddress}
default: ${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}";
};
};
};
}
+10
View File
@@ -0,0 +1,10 @@
{ ... }:
{
imports = [
./internationalisation.nix
./power.nix
./update.nix
./users.nix
];
}
+22
View File
@@ -0,0 +1,22 @@
{ config, ... }:
{
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";
};
console.keyMap = "fr";
services.xserver.xkb = {
layout = "fr";
variant = "";
};
}
+28
View File
@@ -0,0 +1,28 @@
{ config, lib, pkgs, ... }:
let
hardDrives = config.numbus.hardware.dataDisksList ++ config.numbus.hardware.parityDisksList;
in
{
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}";
};
};
services.autoaspm.enable = true;
powerManagement.powertop.enable = true;
boot.kernelParams = [
"pcie_aspm=force"
"consoleblank=60"
];
}
+21
View File
@@ -0,0 +1,21 @@
{ 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;
}
+16
View File
@@ -0,0 +1,16 @@
{ config, ... }:
{
users.users.numbus-admin = {
shell = pkgs.fish;
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;
};
}
+8
View File
@@ -0,0 +1,8 @@
{ ... }:
{
imports = [
./firewall.nix
./networking.nix
];
}
+11
View File
@@ -0,0 +1,11 @@
{ config, ... }:
{
networking.nftables.enable = true;
networking.firewall = {
enable = true;
allowPing = true;
allowedTCPPorts = [ 53 80 443 ];
allowedUDPPorts = [ 53 443 ];
};
}
+52
View File
@@ -0,0 +1,52 @@
{ config, pkgs, lib, ... }:
with lib;
let
cfg = config.numbus.services.networking;
in
{
options.numbus.services.networking = {
ipAddress = mkOption {
description = "The IP address that this server will use";
type = types.str;
example = "192.168.1.100";
};
interface = mkOption {
description = "The interface that this server will use to connect to the network";
type = types.str;
example = "enp1s0";
};
routerIpAddress = mkOption {
description = "The IP address of the router of your network";
type = types.str;
example = "192.168.1.1";
};
dnsServers = mkOption {
description = "The list of DNS servers that this server will use";
type = types.listOf types.str;
default = [ "${cfg.ipAddress}" "9.9.9.9" ];
example = [ "${cfg.ipAddress}" "9.9.9.9" ];
};
};
networking.hostName = "numbus-server";
networking.networkmanager.enable = false;
# Allow rootless containers to bind to port 53 and up
boot.kernel.sysctl."net.ipv4.ip_unprivileged_port_start" = 53;
networking.bridges.br0.interfaces = [ "${cfg.interface}" ];
networking.interfaces."${cfg.interface}".useDHCP = false;
networking.interfaces.br0.useDHCP = false;
networking.nameservers = ${cfg.dnsServers};
networking.interfaces.br0.ipv4.addresses = [{
address = "${cfg.ipAddress}";
prefixLength = 24;
}];
networking.defaultGateway = {
address = "${cfg.routerIpAddress}";
interface = "br0";
};
}
+9
View File
@@ -0,0 +1,9 @@
{ ... }:
{
imports = [
./packages.nix
./podman.nix
./ssh.nix
];
}
+25
View File
@@ -0,0 +1,25 @@
{ config, pkgs, ... }:
{
nixpkgs.config.allowUnfree = true;
environment.systemPackages = with pkgs; [
git
ncdu
fastfetch
tpm2-tss
sops
age
powertop
pciutils
hdparm
hd-idle
hddtemp
smartmontools
cpufrequtils
intel-gpu-tools
snapraid
mergerfs
mergerfs-tools
];
}
+13
View File
@@ -0,0 +1,13 @@
{ config, pkgs, ...}:
{
virtualisation.podman.enable = true;
virtualisation.podman.defaultNetwork.settings.dns_enabled = true;
environment.systemPackages = with pkgs; [
podman
podman-compose
podman-tui
slirp4netns
];
}
+5
View File
@@ -0,0 +1,5 @@
{ config, ... }:
{
services.openssh.enable = true;
}
View File
+8
View File
@@ -15,6 +15,14 @@ helper.mkPodmanService {
name = "immich";
pod = "immich";
defaultPort = "2283";
useSopsSecrets = true;
extraConfig = {
numbus.services.immich.secretMapping = {
DB_PASSWORD = "db_password";
DB_USERNAME = "db_username"; # Assuming you add this to schema
};
};
# Compose file good
composeText = ''
+2 -1
View File
@@ -31,6 +31,7 @@ with lib;
extraOptions ? {},
extraConfig ? {},
delaySec ? 180,
useSopsSecrets ? false, # New argument to enable sops integration
middlewares ? [ "secureHeaders" ],
dependencies ? [ "traefik.service" "${config.numbus.services.dns}.service" ],
}:
@@ -135,7 +136,7 @@ ${concatStringsSep "\n" (map (m: " - ${m}") middlewares)}
Type = "exec";
ExecStartPre = [
"bash -c 'sleep $((RANDOM % ${toString delaySec}))'"
"-sudo -u numbus-admin podman-compose -f /etc/podman/${name}/compose.yaml pull"
"- sudo -u numbus-admin podman-compose -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";
ExecStop = "sudo -u numbus-admin podman-compose --in-pod ${toString pod} -f /etc/podman/${name}/compose.yaml down";