Compare commits

..

135 Commits

Author SHA1 Message Date
numbus df3c3d1c08 Update Whiteboard 2026-06-08 12:45:17 +02:00
numbus e98263b100 Update Immich 2026-06-08 12:42:04 +02:00
numbus 0500445003 Update Traefik 2026-06-08 12:40:06 +02:00
numbus ce4fa00b77 Update NixOS 2026-06-08 12:38:50 +02:00
numbus ec9b8f7d55 Update Traefik 2026-05-14 11:48:55 +02:00
numbus 0cc60dcd29 Update Nextcloud, Whiteboard and OnlyOffice 2026-05-14 11:48:03 +02:00
numbus 3b432d8bd4 Update Nextcloud 2026-04-13 09:21:29 +02:00
numbus 19b2459f65 Up the amount of RAM usable by Nextcloud 2026-04-12 15:12:01 +02:00
numbus 76fbcd86db Added screen package 2026-03-25 09:13:09 +01:00
numbus d1e511bfc0 Typo : 1 data disk 2026-03-15 12:34:23 +01:00
numbus 090cb2a7e4 Remove the /mnt/content-0 if more than 2 data disks 2026-03-15 12:30:20 +01:00
numbus c994337e1f Change onlyoffice headers. 2026-03-09 00:35:21 +01:00
numbus f1e24678b9 Remove security option to make Nextcloud onlyoffice work. 2026-03-09 00:28:25 +01:00
Raphaël Numbus dcde9fad01 Changed trusted proxy address. 2026-03-05 22:09:01 +01:00
Raphaël Numbus 3c41c307ee Fixed Nextcloud headers. OnlyOffice now works with Nextcloud. 2026-03-05 22:05:32 +01:00
Raphaël Numbus 7e4ef7b679 Get nextcloud-onlyoffice to work. 2026-03-05 13:04:24 +01:00
Raphaël Numbus 3e927af8f9 Get nextcloud-onlyoffice to work. 2026-03-05 12:58:05 +01:00
Raphaël Numbus 6de5f0cd28 Get gitea to work. 2026-03-05 12:48:25 +01:00
Raphaël Numbus 5394287b3a Home-assistant bug. Get nextcloud-onlyoffice to work. 2026-03-05 12:42:26 +01:00
Raphaël Numbus a4c0c2b051 Fixed home-assistant 400: bad request. Fixed Nextcloud-Quirk failing. Fixed Nextcloud-Onlyoffice mkdir: permission denied. 2026-03-05 09:24:51 +01:00
Raphaël Numbus 7933a3aa57 Added slirp4netns 2026-03-04 21:54:46 +01:00
Raphaël Numbus b5bece34ed Moved coral tpu config to a single file. Added slirp4netns. 2026-03-04 21:22:33 +01:00
Raphaël Numbus 4ab54cae0a Added AdGuard (NEEDS TESTING). Fixed bad indentation for middlewares. Switched from every 2 month periodic scan to every 3 months. 2026-03-03 22:27:24 +01:00
Raphaël Numbus e6907ddd0a Try to fix newuidmap exec not found 2026-03-03 22:08:21 +01:00
Raphaël Numbus 5bf87a1f83 Try to fix newuidmap exec not found 2026-03-03 22:04:58 +01:00
Raphaël Numbus cca3e0d42b Try to fix newuidmap exec not found 2026-03-03 21:46:15 +01:00
Raphaël Numbus f190eb2040 Try to fix newuidmap exec not found 2026-03-03 21:14:06 +01:00
Raphaël Numbus 96d049d486 Try to fix newuidmap exec not found 2026-03-03 20:49:21 +01:00
Raphaël Numbus e09301c493 Try to fix newuidmap exec not found 2026-03-03 16:30:21 +01:00
Raphaël Numbus 3721e41e94 Try to fix newuidmap exec not found 2026-03-03 16:00:48 +01:00
Raphaël Numbus 5b604fac08 Try to fix newuidmap exec not found 2026-03-03 15:43:21 +01:00
Raphaël Numbus e1ddf88300 Try to fix newuidmap exec not found 2026-03-03 15:35:18 +01:00
Raphaël Numbus 07e7084b1b Try to fix Traefik not launching on startup 2026-03-03 15:07:39 +01:00
Raphaël Numbus e46ee8495c Fixed Home-assistant script. 2026-03-03 14:45:23 +01:00
Raphaël Numbus 5cd7f661c0 Fixed passbolt error. 2026-03-03 14:38:25 +01:00
Raphaël Numbus 4d1448189c Added coral TPU driver compile files. Try to fix passbolt YAML error. 2026-03-03 14:23:53 +01:00
Raphaël Numbus 501383bc8d Get periodic scan to work. 2026-03-02 14:45:59 +01:00
Raphaël Numbus 331b686bae Get periodic scan to work. 2026-03-01 19:11:39 +01:00
Raphaël Numbus ca8e8c967e Remove unnecessary input 2026-03-01 17:58:26 +01:00
Raphaël Numbus 487889e3c6 Forgot with lib 2026-03-01 17:58:04 +01:00
Raphaël Numbus de6c80d7b4 Fixed bad option in it-tools, pi-hole and traefik. Added virtualization file. 2026-03-01 17:57:01 +01:00
Raphaël Numbus d384fe1f7c Fix clamAV onacc service. 2026-03-01 17:39:30 +01:00
Raphaël Numbus 65e1ba4ed2 Enable clamAV mail alerts. 2026-03-01 15:06:36 +01:00
Raphaël Numbus 6705329887 Debugging 2026-03-01 15:02:58 +01:00
Raphaël Numbus ae00cb69de Fix nextcloud-quirk systemD service failures. Also get clamAV configuration to work. 2026-03-01 14:49:28 +01:00
Raphaël Numbus 19b2ac7426 Fix nextcloud-quirk systemD service failures. 2026-03-01 14:25:19 +01:00
Raphaël Numbus b12f081bc3 Malformed if statement. 2026-03-01 14:10:38 +01:00
Raphaël Numbus 0ff3ec0e1b Fix nextcloud-quirk systemD service failures. 2026-03-01 14:08:03 +01:00
Raphaël Numbus 45495b114f Fix nextcloud-quirk systemD service failures. 2026-03-01 13:46:18 +01:00
Raphaël Numbus 3bfaf5fa6f Added periodic clamAV scan. Added mail alert on virus detection. 2026-03-01 13:18:35 +01:00
Raphaël Numbus bbe269bfcd Fix systemD service failures. 2026-03-01 12:25:12 +01:00
Raphaël Numbus af4f384797 Fixed syntax error. 2026-03-01 12:10:03 +01:00
Raphaël Numbus 68949dc81b Forgot to add lib. 2026-03-01 12:06:57 +01:00
Raphaël Numbus fad1f51323 Updated clamAV configuration. Fixed nextcloud-quirk typo. 2026-03-01 12:05:30 +01:00
Raphaël Numbus ab886a8a0b Fixed error. 2026-03-01 11:29:26 +01:00
Raphaël Numbus 87b857071d Add records for onlyoffice and whiteboard. 2026-03-01 11:22:16 +01:00
Raphaël Numbus ea86da660d Improved Nextcloud systemD services to avoid failures. Added middleware for carddav/caldav 2026-02-28 22:41:11 +01:00
Raphaël Numbus 41f8298078 Set slirp4netns as default bridge backend. 2026-02-28 18:24:21 +01:00
Raphaël Numbus 60bf000973 Fix missing else. 2026-02-28 15:14:44 +01:00
Raphaël Numbus 384f8cf1ce Fix indentation on multiple services. 2026-02-28 15:11:52 +01:00
Raphaël Numbus d753c34e37 Remove empty space when no domain. 2026-02-28 15:10:02 +01:00
Raphaël Numbus e3e6cac8a3 Fix pi-hole's compose file indentation. 2026-02-28 15:07:22 +01:00
Raphaël Numbus 25820b8578 Typo 2026-02-28 14:59:35 +01:00
Raphaël Numbus 33766aaded Back to normal firewall. Updated systemD container start service. 2026-02-28 14:56:02 +01:00
Raphaël Numbus 98f727c904 Updated firewall rules. 2026-02-28 12:30:14 +01:00
Raphaël Numbus 3558fc5d5c TEST 2026-02-27 17:39:45 +01:00
Raphaël Numbus af86328ec1 Try to get nextcloud to be able to connect to itself. 2026-02-27 17:36:17 +01:00
Raphaël Numbus d7a28f93f1 Forgot pkgs input 2026-02-27 17:25:41 +01:00
Raphaël Numbus d9ee1967fd Added some ClamAV configuration, needs more research. Try to get nextcloud to be able to connect to itself. 2026-02-27 17:24:16 +01:00
Raphaël Numbus 346c2094a9 Removed TimeOut. 2026-02-27 16:06:10 +01:00
Raphaël Numbus 42f463152f Closed firewall port. Fixed middlewares not defined error. 2026-02-27 16:03:59 +01:00
Raphaël Numbus bf753471ba Fixed indentation. Fixed Immich not working correctly behind Traefik (too restrictive headers). 2026-02-27 14:49:25 +01:00
Raphaël Numbus c2b49d7d67 Temporarily open port on firewall. 2026-02-27 12:00:02 +01:00
Raphaël Numbus df165acd1c Removed unnecessary variable 2026-02-27 00:33:01 +01:00
Raphaël Numbus 88bfa9d1ec Added redis persistent storage 2026-02-27 00:13:40 +01:00
Raphaël Numbus d98fd8cebf Added back immich_version variable. 2026-02-26 23:41:42 +01:00
Raphaël Numbus 8bd9f05621 Try to fix immich database not found 2026-02-26 23:40:53 +01:00
Raphaël Numbus acbc0ce72d Fixed bad variable assignment. 2026-02-26 22:26:47 +01:00
Raphaël Numbus 260807ea82 Try to get nextcloud database to initialize properly. 2026-02-26 21:41:43 +01:00
Raphaël Numbus bd91b1e8bf Added port to mysql_host variable nextcloud. 2026-02-26 21:05:43 +01:00
Raphaël Numbus 5220822812 Bunch of bugfixes. Made quirk and permissions fixing scripts more reliable. 2026-02-26 19:45:36 +01:00
Raphaël Numbus 1697952fc4 Bunch of bugfixes. Made quirk and permissions fixing scripts more reliable. 2026-02-26 19:40:31 +01:00
Raphaël Numbus 329355d6c0 Bunch of bugfixes. Made quirk and permissions fixing scripts more reliable. 2026-02-26 19:34:47 +01:00
Raphaël Numbus a98e25abe5 Added back code. 2026-02-26 18:15:38 +01:00
Raphaël Numbus 38154bbd0d Added back code. 2026-02-26 18:13:52 +01:00
Raphaël Numbus 16c1517d95 Removed unnecessary code. Fixed immich env not found. Fixed nextcloud-quirks by quoting .env variable values. 2026-02-26 18:07:57 +01:00
Raphaël Numbus b68deed2e7 Try to break systemD start cycle to work. 2026-02-26 17:31:00 +01:00
Raphaël Numbus 169ead06a4 Get cp to no copy symlinks but actual files. 2026-02-26 17:14:53 +01:00
Raphaël Numbus 6e9ea3fde3 Fixed sudo executable not found. 2026-02-26 16:57:40 +01:00
Raphaël Numbus 30e4291089 Fixed folder not created. 2026-02-26 16:52:50 +01:00
Raphaël Numbus 75c5775369 Bugfixes 2026-02-26 16:40:38 +01:00
Raphaël Numbus f377b0a4ec Fixed Traefik folders situation. Configuration is defined in /etc/traefik and then copied to Traefik'sconfigDir. 2026-02-26 16:28:51 +01:00
Raphaël Numbus 3385f6551f Try to fix Traefik no such file or dir error. 2026-02-26 11:44:32 +01:00
Raphaël Numbus 47db93e61b Removed Traefik rules location modularity (why would anyone change it ?) and fixed Traefik no such file or dir error. 2026-02-26 11:36:31 +01:00
Raphaël Numbus ca3f766975 Try to fix Traefik no such directory 2026-02-26 11:02:02 +01:00
Raphaël Numbus 6e188590ce Added quotes. 2026-02-25 23:23:03 +01:00
Raphaël Numbus d8056117de Bugfixes. 2026-02-25 23:13:27 +01:00
Raphaël Numbus 2a3a2f3832 Fixed startDelay 2026-02-25 22:58:00 +01:00
Raphaël Numbus f95cdf5d26 Fix can't find sudo error. 2026-02-25 22:53:30 +01:00
Raphaël Numbus c4a83ff303 Fix can't find sleep error. 2026-02-25 22:44:00 +01:00
Raphaël Numbus 33a00771e5 Fix can't find sleep error. 2026-02-25 22:39:50 +01:00
Raphaël Numbus 3375f3566e Fix can't find bash error. 2026-02-25 22:32:09 +01:00
Raphaël Numbus 71eb11efe0 Fixed bad path for Traefik configuration files. 2026-02-25 22:14:12 +01:00
Raphaël Numbus a2d8fcb929 Fixed bad path for Traefik configuration files. 2026-02-25 22:03:23 +01:00
Raphaël Numbus 6063267e46 Fixed bad path for Traefik configuration files. 2026-02-25 22:01:03 +01:00
Raphaël Numbus 81da85cd91 Moved Traefik static file to config dir to avoid systemD error. 2026-02-25 21:51:58 +01:00
Raphaël Numbus b673f94b07 Moved Traefik static file to config dir to avoid systemD error. 2026-02-25 21:50:28 +01:00
Raphaël Numbus eaf7aa32ed Moved traefikDynamicConfigDir under config.numbus.services 2026-02-25 21:47:28 +01:00
Raphaël Numbus 555751aa7a Fixed typo service--> services 2026-02-25 21:42:49 +01:00
Raphaël Numbus 9b0c350219 Bugfixes. 2026-02-25 21:39:07 +01:00
Raphaël Numbus 70acd769d6 Wrong variable. 2026-02-25 14:03:40 +01:00
Raphaël Numbus 4a5bef02fc Added config. to `time.timeZone 2026-02-25 14:00:12 +01:00
Raphaël Numbus 966e3288dd Added toString transformation for smtpPort variable. 2026-02-25 13:29:14 +01:00
Raphaël Numbus bc3de08c2b Added top-level config. 2026-02-25 13:13:46 +01:00
Raphaël Numbus dd47755f74 Fixed syntax error. 2026-02-25 13:00:11 +01:00
Raphaël Numbus ae18febf6d Fixed undefined variable : name when addind extraConfig. 2026-02-25 12:18:13 +01:00
Raphaël Numbus 475e37d559 Fixed wrongly defined options. 2026-02-25 12:00:58 +01:00
Raphaël Numbus 10611475a6 Fixed typo type --> types. 2026-02-25 11:56:30 +01:00
Raphaël Numbus e678e08634 Added spindownDisksList list to provide the HDD spindown capable list. Tried to fix mkPodmanService error. 2026-02-25 10:53:24 +01:00
Raphaël Numbus f0e304507b Added missing config attribute. 2026-02-24 22:39:35 +01:00
Raphaël Numbus f301f48227 Fixed typo. 2026-02-24 22:35:44 +01:00
Raphaël Numbus 0d80044380 Updated inputs. 2026-02-24 22:31:50 +01:00
Raphaël Numbus 596c03a800 Added missing variable. 2026-02-24 22:13:14 +01:00
Raphaël Numbus 12150fa8a8 Update flake. 2026-02-24 21:48:12 +01:00
Raphaël Numbus a3906244f0 Update flake. 2026-02-24 21:43:03 +01:00
Raphaël Numbus 5b5dbd1c0a Updated misc. 2026-02-23 23:15:36 +01:00
Raphaël Numbus 4bbd62a93e Services are ready 2026-02-23 23:05:54 +01:00
Raphaël Numbus 944ffcea85 Added mail notifications on failure. Needs more work on lib.nix and the services/*.nix. 2026-02-23 16:36:40 +01:00
Raphaël Numbus f445bd8659 Added the rest of the configuration. Still some things to add. 2026-02-22 20:34:44 +01:00
Raphaël Numbus 2e16ac3711 Services are ready. 2026-02-22 12:04:19 +01:00
Raphaël Numbus 40265e8c81 Updated container networking. 2026-02-20 11:04:09 +01:00
Raphaël Numbus 61d0fbd339 Added the correct options to multiple containers. 2026-02-20 10:43:11 +01:00
Raphaël Numbus c90169f242 Created a helper to create the container configurations. Updated all containers files to use it. 2026-02-20 10:23:24 +01:00
Raphaël Numbus 3b130bc2e9 Traefik and Frigate updated to new module format. Frigate needs some more testing (hardening, devices). 2026-02-19 14:03:03 +01:00
Raphaël Numbus 583963c7dc First commit 2026-02-18 22:22:31 +01:00
43 changed files with 2748 additions and 1 deletions
+3
View File
@@ -0,0 +1,3 @@
.DS_STORE
.env
*test*
+3
View File
@@ -0,0 +1,3 @@
.DS_STORE
.env
*test*
+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.
+11
View File
@@ -0,0 +1,11 @@
{
description = "Numbus Server Module";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-26.05";
};
outputs = { self, nixpkgs }: {
nixosModules.numbus = import ./modules/default.nix;
};
}
+13
View File
@@ -0,0 +1,13 @@
{ ... }:
{
imports = [
./hardware/default.nix
./mail/default.nix
./misc/default.nix
./networking/default.nix
./packages/default.nix
./services/default.nix
./global.nix
];
}
+53
View File
@@ -0,0 +1,53 @@
{ lib, ... }:
with lib;
{
options.numbus = {
owner = mkOption {
type = types.str;
example = "Alex";
default = "Numbus";
description = "The name of the person who owns this server";
};
language = mkOption {
type = types.str;
example = "FR";
default = "FR";
description = "The language for this server";
};
locale = mkOption {
type = types.str;
example = "fr_FR";
default = "fr_FR";
description = "The default locale for this server";
};
services = {
domain = mkOption {
type = types.str;
example = "numbus.eu";
description = "The root domain name (i.e. example.com) that your services will use";
};
dns = mkOption {
type = types.enum [ "pi-hole" "adguard" ];
default = "pi-hole";
example = "pi-hole";
description = "The preferred DNS resolver service (pi-hole or adguard) that other services should depend on";
};
};
email = {
administratorEmail = mkOption {
type = types.str;
example = "admin@your-domain.com";
description = "The email that will be used to send critical notifications such as hardware failures, services errors, ACME updates, etc";
};
userEmail = mkOption {
type = types.str;
example = "user@your-domain.com";
description = "The email that will be used by services to send notifications";
};
};
};
}
+13
View File
@@ -0,0 +1,13 @@
{ config, ... }:
{
config = {
boot.initrd.systemd.enable = true;
boot.loader.systemd-boot.enable = true;
boot.loader.efi.canTouchEfiVariables = true;
boot.kernel.sysctl = {
"vm.overcommit_memory" = 1;
};
};
}
+9
View File
@@ -0,0 +1,9 @@
{ config, ... }:
{
config = {
hardware.enableRedistributableFirmware = true;
hardware.cpu.intel.updateMicrocode = true;
hardware.cpu.amd.updateMicrocode = true;
};
}
+10
View File
@@ -0,0 +1,10 @@
{ ... }:
{
imports = [
./boot.nix
./cpu.nix
./disks.nix
./pcie-coral.nix
];
}
+328
View File
@@ -0,0 +1,328 @@
{ 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";
};
spindownDisksList = 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 to spindown when inactive to save power (HDD only)";
};
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 = (optionals (length cfg.dataDisksList == 1) [ "/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))}
'';
};
};
}
+111
View File
@@ -0,0 +1,111 @@
{ config, lib, pkgs, ... }:
let
cfg = config.numbus.hardware.pcie-coral;
gasket-driver = { 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 = [ maintainers.kylehendricks ];
platforms = platforms.linux;
};
};
libedgetpu-pkg = { 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
'';
};
gasket = config.boot.kernelPackages.callPackage gasket-driver {};
libedgetpu = pkgs.callPackage libedgetpu-pkg {};
in
{
options.numbus.hardware.pcie-coral = lib.mkEnableOption "PCIe Coral TPU support";
config = lib.mkIf cfg {
services.udev.packages = [ libedgetpu ];
users.groups.plugdev = {};
boot.extraModulePackages = [ gasket ];
};
}
+89
View File
@@ -0,0 +1,89 @@
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.numbus.services.clamav;
clamav_notifier = pkgs.writeScript "clamav-notify.sh" ''
#!${pkgs.bash}/bin/bash
# Check if triggered by Real-time event (file exists)
if [ -f /var/lib/clamav/virus_event.env ]; then
source /var/lib/clamav/virus_event.env
rm /var/lib/clamav/virus_event.env
fi
ADMIN_EMAIL="${config.numbus.mail.adminAddress}"
USER_EMAIL="${config.numbus.mail.userAddress}"
OWNER_NAME="${config.numbus.owner}"
if [ -n "$CLAM_VIRUSEVENT_VIRUSNAME" ]; then
# --- Real-time / VirusEvent Mode ---
SUBJECT="Numbus Server Alert: Virus Detected (Real-time)"
# Retrieve logs from clamav-daemon
LOGS=$(journalctl -u clamav-daemon.service -n 50 --no-pager | grep "FOUND")
TECH_BODY="
ClamAV Real-time Alert:
Server owner: $OWNER_NAME
Virus detected: $CLAM_VIRUSEVENT_VIRUSNAME
File: $CLAM_VIRUSEVENT_FILENAME
Logs:
$LOGS
Action taken: Access blocked (OnAccessPrevention).
Please investigate manually.
"
FRIENDLY_BODY="Cher/Chère $OWNER_NAME,
L'antivirus de votre serveur a détecté et bloqué une menace en temps réel.
Fichier : $CLAM_VIRUSEVENT_FILENAME
Votre administrateur a été notifié.
"
else
# --- Scheduled Scan Summary Mode ---
SUBJECT="Numbus Server Alert: Virus Detected during Scheduled Scan"
# Retrieve logs (clamdscan prints FOUND when a virus is detected)
LOGS=$(journalctl -u clamav-periodic-scan.service -n 100 --no-pager | grep "FOUND")
TECH_BODY="
ClamAV Scan Alert:
Server owner: $OWNER_NAME
Viruses detected:
$LOGS
Action taken: Detection only.
Please investigate manually.
"
FRIENDLY_BODY="Cher/Chère $OWNER_NAME,
L'antivirus de votre serveur a détecté une menace potentielle lors de l'analyse périodique.
Votre administrateur a été notifié avec les détails techniques.
Nous vous conseillons d'être prudent avec vos fichiers récents.
"
fi
printf "Subject: [ADMIN] %s\n\n%s" "$SUBJECT" "$TECH_BODY" | /run/wrappers/bin/sendmail -t "$ADMIN_EMAIL"
printf "Subject: [Alerte] Menace détectée 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"
'';
in
{
config = mkIf cfg.enable {
systemd.services.clamav-virus-notify = {
description = "Email notification for ClamAV virus detection";
serviceConfig = {
Type = "oneshot";
ExecStart = "${clamav_notifier}";
};
};
};
}
+10
View File
@@ -0,0 +1,10 @@
{ ... }:
{
imports = [
./clamav.nix
./smart.nix
./systemd.nix
./smtp.nix
];
}
+61
View File
@@ -0,0 +1,61 @@
{ config, pkgs, ... }:
let
smartd_notifier = pkgs.writeScript "smartd-notify.sh" ''
#!${pkgs.bash}/bin/bash
# 1. Send Technical Email to Admin
ADMIN_EMAIL="${config.numbus.mail.adminAddress}"
SUBJECT="Numbus Server Alert: $SMARTD_FAILTYPE on $SMARTD_DEVICE"
TECH_BODY="
SMARTD Alert Details:
Server owner: $OWNER_NAME
Device: $SMARTD_DEVICE
Type: $SMARTD_DEVICETYPE
Failure Type: $SMARTD_FAILTYPE
Message: $SMARTD_MESSAGE
Full Message:
$SMARTD_FULLMESSAGE
"
printf "Subject: [ADMIN] $SUBJECT\n\n$TECH_BODY" | /run/wrappers/bin/sendmail -t "$ADMIN_EMAIL"
# 2. Send Friendly Email to Owner
USER_EMAIL="${config.numbus.mail.userAddress}"
OWNER_NAME="${config.numbus.owner}"
FRIENDLY_BODY="Cher/Chère $OWNER_NAME,
Votre serveur a automatiquement détecté une panne matérielle de disque dur.
Ce genre de panne est tout à fait normal selon l'âge de votre matériel et n'entraîne
dans la grande majorité des cas aucune perte de données grâce au système de
stockage redondant préventif.
Votre administrateur a été notifié de cette panne. Il vous recontactera dans de très
brefs délais afin de procéder au remplacement, si nécessaire, du disque dur défaillant.
Merci de votre confiance,
L'équipe de support,
Numbus-Server."
printf "Subject: [Alerte] Défaillance matérielle sur votre serveur Numbus\n\n$FRIENDLY_BODY" | /run/wrappers/bin/sendmail -t "$USER_EMAIL"
'';
in
{
services.smartd = {
enable = true;
defaults.autodetected = "-a -o on -S on -s (S/../.././00|L/../../6/01) -n standby,q -M exec ${smartd_notifier}";
notifications = {
wall = {
enable = true;
};
mail = {
enable = true;
sender = config.numbus.mail.fromAddress;
recipient = "${config.numbus.mail.userAddress},${config.numbus.mail.adminAddress}";
};
};
};
}
+84
View File
@@ -0,0 +1,84 @@
{ 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/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}";
};
};
};
}
+55
View File
@@ -0,0 +1,55 @@
{ config, pkgs, ... }:
let
systemd_notifier = pkgs.writeScript "systemd-email-notify.sh" ''
#!${pkgs.bash}/bin/bash
# The failing service name is passed as the first argument
UNIT=$1
# 1. Send Technical Email to Admin
ADMIN_EMAIL="${config.numbus.mail.adminAddress}"
SUBJECT="Numbus Server Alert: Service $UNIT Failed"
# Retrieve recent logs for context
LOGS=$(journalctl -u "$UNIT" -n 20 --no-pager)
TECH_BODY="
Systemd Service Failure Alert:
Server owner: ${config.numbus.owner}
Service: $UNIT
Recent Logs:
$LOGS
"
printf "Subject: [ADMIN] $SUBJECT\n\n$TECH_BODY" | /run/wrappers/bin/sendmail -t "$ADMIN_EMAIL"
# 2. Send Friendly Email to Owner
USER_EMAIL="${config.numbus.mail.userAddress}"
OWNER_NAME="${config.numbus.owner}"
FRIENDLY_BODY="Cher/Chère $OWNER_NAME,
Votre serveur a détecté une défaillance du service $UNIT.
Le système a tenté de gérer l'erreur, mais une intervention peut être nécessaire.
Votre administrateur a été notifié de cet incident avec les détails techniques nécessaires.
Il interviendra si une action manuelle est requise.
Merci de votre confiance,
L'équipe de support,
Numbus-Server."
printf "Subject: [Alerte] Erreur sur votre serveur Numbus\n\n$FRIENDLY_BODY" | /run/wrappers/bin/sendmail -t "$USER_EMAIL"
'';
in
{
systemd.services."service-failure-notify@" = {
description = "Email notification for failed service %i";
onFailure = [ ];
serviceConfig = {
Type = "oneshot";
ExecStart = "${systemd_notifier} %i";
};
};
}
+10
View File
@@ -0,0 +1,10 @@
{ ... }:
{
imports = [
./internationalisation.nix
./power.nix
./update.nix
./users.nix
];
}
+24
View File
@@ -0,0 +1,24 @@
{ config, lib, ... }:
{
config = {
i18n.defaultLocale = "${config.numbus.locale}.UTF-8";
i18n.extraLocaleSettings = {
LC_ADDRESS = "${config.numbus.locale}.UTF-8";
LC_IDENTIFICATION = "${config.numbus.locale}.UTF-8";
LC_MEASUREMENT = "${config.numbus.locale}.UTF-8";
LC_MONETARY = "${config.numbus.locale}.UTF-8";
LC_NAME = "${config.numbus.locale}.UTF-8";
LC_NUMERIC = "${config.numbus.locale}.UTF-8";
LC_PAPER = "${config.numbus.locale}.UTF-8";
LC_TELEPHONE = "${config.numbus.locale}.UTF-8";
LC_TIME = "${config.numbus.locale}.UTF-8";
};
console.keyMap = lib.toLower config.numbus.language;
services.xserver.xkb = {
layout = lib.toLower config.numbus.language;
variant = "";
};
};
}
+30
View File
@@ -0,0 +1,30 @@
{ config, lib, pkgs, ... }:
let
hardDrives = config.numbus.hardware.spindownDisksList;
in
{
config = {
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"
];
};
}
+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;
};
}
+16
View File
@@ -0,0 +1,16 @@
{ config, pkgs, ... }:
{
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
];
}
+13
View File
@@ -0,0 +1,13 @@
{ config, pkgs, lib, ... }:
{
config = {
networking.nftables.enable = true;
networking.firewall = {
enable = true;
allowPing = true;
allowedTCPPorts = [ 53 80 443 ];
allowedUDPPorts = [ 53 443 ];
};
};
}
+60
View File
@@ -0,0 +1,60 @@
{ config, pkgs, lib, ... }:
with lib;
let
cfg = config.numbus.networking;
in
{
options.numbus.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";
};
networkSubnet = mkOption {
description = "The subnet of your network";
type = types.str;
default = "";
example = "192.168.1.0/24";
};
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" ];
};
};
config = {
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";
};
};
}
+10
View File
@@ -0,0 +1,10 @@
{ ... }:
{
imports = [
./packages.nix
./podman.nix
./ssh.nix
./terminal.nix
];
}
+26
View File
@@ -0,0 +1,26 @@
{ config, pkgs, ... }:
{
nixpkgs.config.allowUnfree = true;
environment.systemPackages = with pkgs; [
git
screen
ncdu
fastfetch
tpm2-tss
sops
age
powertop
pciutils
hdparm
hd-idle
hddtemp
smartmontools
cpufrequtils
intel-gpu-tools
snapraid
mergerfs
mergerfs-tools
];
}
+16
View File
@@ -0,0 +1,16 @@
{ pkgs, ... }:
{
virtualisation.podman.enable = true;
virtualisation.podman.defaultNetwork.settings.dns_enabled = true;
virtualisation.containers.containersConf.settings = {
network.default_rootless_network_cmd = "slirp4netns";
};
environment.systemPackages = with pkgs; [
podman-compose
podman-tui
slirp4netns
];
}
+5
View File
@@ -0,0 +1,5 @@
{ config, ... }:
{
services.openssh.enable = true;
}
+24
View File
@@ -0,0 +1,24 @@
{ config, pkgs, ... }:
{
environment.systemPackages = with pkgs; [
fish
fishPlugins.fzf-fish
fishPlugins.grc
grc
fzf
];
programs.fish = {
enable = true;
interactiveShellInit = ''
set fish_greeting # Disable greeting
fastfetch
echo -e "\n\nWelcome to your Numbus-Server !\n\n- This system is managed by NixOS\n- All changes are futile\n- Please consider buying support if you can't get your server running\n- Have a nice day and enjoy !"
'';
shellAliases = {
nixup = "cd /etc/nixos/ && sudo nix flake update && sudo nixos-rebuild --flake . switch --upgrade && cd -";
nixwitch = "cd /etc/nixos/ && sudo nix flake update && sudo nixos-rebuild --flake . switch && cd -";
};
};
}
+51
View File
@@ -0,0 +1,51 @@
{ config, pkgs, lib, ... }:
with lib;
let
# Version tagging
adguardVersion = "latest";
# Helper
helper = import ./lib.nix { inherit config pkgs lib; };
cfg = config.numbus.services.adguard;
# Container config
name = "adguard";
in
helper.mkPodmanService {
inherit name;
description = "AdGuard, feature-rich DNS service";
pod = "false";
defaultPort = "3000";
scheme = "http";
dependencies = [ "network.target" ];
dataDirEnabled = false;
startDelay = 10;
middlewares = [ "secureHeaders" ];
dirPermissions = [
"100999:100 ${cfg.configDir}"
];
# Compose file good
composeText = ''
services:
adguardhome:
image: adguard/adguardhome:${adguardVersion}
container_name: adguard
hostname: adguard
network_mode: pasta
user: '1000:1000'
ports:
- "3000:3000/tcp"
- "53:53/tcp"
- "53:53/udp"
volumes:
- ${cfg.configDir}/work:/opt/adguardhome/work
- ${cfg.configDir}/config:/opt/adguardhome/conf
cap_add:
- SYS_NICE
security_opt:
- no-new-privileges:true
restart: unless-stopped
'';
}
+91
View File
@@ -0,0 +1,91 @@
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.numbus.services.clamav;
onAccessPaths = lib.mapAttrsToList (n: v: v.dataDir) (lib.filterAttrs (n: v:
v ? enable && v.enable && v ? dataDir && v.dataDir != null && v.dataDir != false
) config.numbus.services);
clamonacc_virus_notifier = pkgs.writeScript "clamonacc_virus_notifier.sh" ''
#!${pkgs.bash}/bin/bash
echo "CLAM_VIRUSEVENT_VIRUSNAME=\"$CLAM_VIRUSEVENT_VIRUSNAME\"" > /var/lib/clamav/virus_event.env
echo "CLAM_VIRUSEVENT_FILENAME=\"$CLAM_VIRUSEVENT_FILENAME\"" >> /var/lib/clamav/virus_event.env
/run/wrappers/bin/sudo /run/current-system/sw/bin/systemctl start clamav-virus-notify.service
'';
in
{
options.numbus.services.clamav = {
enable = mkEnableOption "ClamAV open-source anti-virus software";
};
config = mkIf cfg.enable {
environment.systemPackages = [ pkgs.clamav pkgs.curl ];
system.activationScripts.clamav-quarantine = ''
mkdir -p /quarantine
chown clamav:clamav /quarantine
chmod 440 /quarantine
'';
security.sudo.extraRules = [{
users = [ "clamav" ];
commands = [{
command = "/run/current-system/sw/bin/systemctl start clamav-virus-notify.service";
options = [ "NOPASSWD" ];
}];
}];
services.clamav = {
updater.enable = true;
clamonacc.enable = true;
scanner = {
enable = true;
interval = "*-*-* 04:00:00"; # Everyday at 4am
scanDirectories = [
"/etc"
"/home"
"/var/lib"
"/var/tmp"
"/tmp"
];
};
daemon = {
enable = true;
settings = {
OnAccessPrevention = true;
OnAccessIncludePath = onAccessPaths;
VirusEvent = "${clamonacc_virus_notifier}";
};
};
};
systemd.services.clamav-periodic-scan = mkIf (onAccessPaths != []) {
description = "Periodic ClamAV virus scan";
after = [ "clamav-daemon.service" "clamav-freshclam.service" ];
requires = [ "clamav-daemon.service" ];
wants = [ "clamav-freshclam.service" ];
onFailure = [ "clamav-virus-notify.service" ];
serviceConfig = {
Type = "oneshot";
ExecStart = "${pkgs.clamav}/bin/clamdscan --multiscan --fdpass --infected --allmatch --move=/quarantine ${lib.escapeShellArgs onAccessPaths}";
Slice = "system-clamav.slice";
};
};
systemd.timers.clamav-periodic-scan = mkIf (onAccessPaths != []) {
description = "Timer for ClamAV periodic scan";
wantedBy = [ "timers.target" ];
timerConfig = {
OnCalendar = "*-1/3-01 04:00:00";
Persistent = true;
Unit = "clamav-periodic-scan.service";
};
};
};
}
+18
View File
@@ -0,0 +1,18 @@
{ ... }:
{
imports = [
# ./adguard.nix
./clamav.nix
./frigate.nix
./gitea.nix
./home-assistant.nix
./immich.nix
./it-tools.nix
./nextcloud.nix
./passbolt.nix
./pi-hole.nix
./traefik.nix
./virtualization.nix
];
}
+74
View File
@@ -0,0 +1,74 @@
{ config, pkgs, lib, ... }:
with lib;
let
# Version tagging
frigateVersion = "0.16.4";
# Helper
helper = import ./lib.nix { inherit config pkgs lib; };
cfg = config.numbus.services.frigate;
# Container config
name = "frigate";
in
helper.mkPodmanService {
inherit name;
description = "Frigate, your fully-local NVR (Network Video Recorder)";
pod = "home-assistant";
defaultPort = "8971";
scheme = "https";
envFile = "/var/lib/numbus-server/home-assistant/.env";
dependencies = [ "traefik.service" "${config.numbus.services.dns}.service" "home-assistant.service" ];
middlewares = [ "secureHeaders" ];
dirPermissions = [
"1000:100 ${cfg.configDir}"
"1000:100 ${cfg.dataDir}"
];
extraOptions = {
devices = mkOption {
type = types.listOf types.str;
default = [];
example = [ "/dev/dri:/dev/dri" "/dev/bus/usb:/dev/bus/usb" "/dev/apex_0:/dev/apex_0" ];
description = "List of devices to map into the container. /dev/dri is used for graphics acceleration, /dev/bus/usb for USB Coral TPUs, and /dev/apex_0 for PCI coral TPUs";
};
};
composeText = ''
services:
frigate:
image: ghcr.io/blakeblackshear/frigate:${frigateVersion}
container_name: frigate
hostname: frigate
shm_size: "256mb"
networks:
home-assistant:
ports:
- "${cfg.port}:8971/tcp"
volumes:
- ${cfg.configDir}:/config
- ${cfg.dataDir}:/media/frigate
- /etc/localtime:/etc/localtime:ro
- type: tmpfs
target: /tmp/cache
tmpfs:
size: 1000000000
environment:
- FRIGATE_MQTT_USER=$HOME_ASSISTANT_MQTT_USER
- FRIGATE_MQTT_PASSWORD=$HOME_ASSISTANT_MQTT_PASSWORD
${lib.optionalString (cfg.devices != []) ''
devices:
${lib.concatStringsSep "\n" (map (d: " - \"${d}\"") cfg.devices)}
''}
security_opt:
- no-new-privileges:true
cap_drop:
- NET_RAW
stop_grace_period: 30s
restart: unless-stopped
networks:
home-assistant:
external: true
'';
}
+90
View File
@@ -0,0 +1,90 @@
{ config, pkgs, lib, ... }:
with lib;
let
# Version tagging
giteaVersion = "1.25.4-rootless";
databaseVersion = "18-alpine";
# Helper
helper = import ./lib.nix { inherit config pkgs lib; };
cfg = config.numbus.services.gitea;
# Container config
name = "gitea";
in
helper.mkPodmanService {
inherit name;
description = "Gitea, your own self-hosted git platform";
defaultPort = "3000";
dataDirEnabled = false;
generatedSecrets = {
DB_NAME = "xkcdpass -n 2 -d -";
DB_USERNAME = "xkcdpass -n 2 -d -";
DB_PASSWORD = "xkcdpass -n 8 -d -";
};
middlewares = [ "secureHeaders" ];
dirPermissions = [
"100999:100 ${cfg.configDir}"
"100999:100 ${cfg.configDir}/data"
"100999:100 ${cfg.configDir}/config"
"100999:100 ${cfg.configDir}/database"
];
composeText = ''
services:
gitea-server:
image: docker.gitea.com/gitea:${giteaVersion}
container_name: gitea-server
hostname: gitea-server
user: '1000:1000'
networks:
gitea:
ports:
- "${cfg.port}:3000/tcp"
volumes:
- ${cfg.configDir}/data:/var/lib/gitea
- ${cfg.configDir}/config:/etc/gitea
- /etc/localtime:/etc/localtime:ro
environment:
- GITEA__database__DB_TYPE=postgres
- GITEA__database__HOST=gitea-database:5432
- GITEA__database__NAME=$DB_NAME
- GITEA__database__USER=$DB_USERNAME
- GITEA__database__PASSWD=$DB_PASSWORD
- GITEA__server__SSH_PORT=2424
- GITEA__server__ROOT_URL=https://${cfg.subdomain}.${config.numbus.services.domain}
depends_on:
- gitea-database
security_opt:
- no-new-privileges:true
cap_drop:
- NET_RAW
restart: unless-stopped
gitea-database:
image: docker.io/library/postgres:${databaseVersion}
container_name: gitea-database
hostname: gitea-database
user: '1000:1000'
networks:
gitea:
volumes:
- ${cfg.configDir}/database:/var/lib/postgresql
environment:
- POSTGRES_USER=$DB_USERNAME
- POSTGRES_PASSWORD=$DB_PASSWORD
- POSTGRES_DB=$DB_NAME
security_opt:
- no-new-privileges:true
cap_drop:
- NET_RAW
restart: unless-stopped
volumes:
gitea_database:
name: gitea_database
networks:
gitea:
name: gitea
driver: bridge
'';
}
+165
View File
@@ -0,0 +1,165 @@
{ config, pkgs, lib, ... }:
with lib;
let
# Version tagging
homeAssistantVersion = "2026.2.3";
mqttVersion = "2.1-alpine";
# Helper
helper = import ./lib.nix { inherit config pkgs lib; };
cfg = config.numbus.services.home-assistant;
# Container config
name = "home-assistant";
in
helper.mkPodmanService {
inherit name;
description = "Home Assistant, libre house control and much more";
defaultPort = "8123";
dataDirEnabled = false;
generatedSecrets = {
HOME_ASSISTANT_MQTT_USER = "xkcdpass -n 2 -d -";
HOME_ASSISTANT_MQTT_PASSWORD = "xkcdpass -n 8 -d -";
};
middlewares = [ "secureHeaders" ];
dirPermissions = [
"1000:100 ${cfg.configDir}"
"1000:100 ${cfg.configDir}/config"
"100999:100 ${cfg.configDir}/mqtt"
];
# Compose file good
composeText = ''
services:
home-assistant:
image: ghcr.io/home-assistant/home-assistant:${homeAssistantVersion}
container_name: home-assistant
hostname: home-assistant
networks:
home-assistant:
ports:
- "${cfg.port}:8123/tcp"
volumes:
- ${cfg.configDir}/config:/config
- /etc/localtime:/etc/localtime:ro
- /run/dbus:/run/dbus:ro
${lib.optionalString (cfg.devices != []) ''
devices:
${lib.concatStringsSep "\n" (map (d: " - \"${d}\"") cfg.devices)}
''}
security_opt:
- no-new-privileges:true
cap_drop:
- NET_RAW
restart: unless-stopped
home-assistant-mqtt:
image: docker.io/library/eclipse-mosquitto:${mqttVersion}
container_name: home-assistant-mqtt
hostname: home-assistant-mqtt
user: '1000:1000'
networks:
home-assistant:
volumes:
- ${cfg.configDir}/mqtt:/mosquitto
security_opt:
- no-new-privileges:true
cap_drop:
- NET_RAW
restart: unless-stopped
networks:
home-assistant:
name: home-assistant
driver: bridge
'';
extraOptions = {
devices = mkOption {
type = types.listOf types.str;
default = [];
example = [ "/dev/serial/by-id/Sonoff_Zigbee_3.0-id-port0:/dev/ttyUSB0" ];
description = "List of devices to map into the container. /dev/ttyUSB0 is used for Zigbee dongles";
};
};
extraConfig = {
systemd.services."${name}-quirk" = {
description = "Podman container quirk : ${name}";
wantedBy = [ "multi-user.target" ];
after = [ "${name}.service" ];
onFailure = [ "service-failure-notify@%n.service" ];
startLimitBurst = 5;
startLimitIntervalSec = 600;
path = [ pkgs.coreutils pkgs.systemd ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
};
script = ''
mkdir -p /var/lib/numbus-server/${name}
if [[ -e ${cfg.configDir}/config/configuration.yaml ]]; then
if grep -qF "${config.numbus.networking.ipAddress}/24" ${cfg.configDir}/config/configuration.yaml; then
exit 0
elif grep -qF "use_x_forwarded_for" ${cfg.configDir}/config/configuration.yaml && ! grep -qF "${config.numbus.networking.ipAddress}/24" ${cfg.configDir}/config/configuration.yaml; then
tmp=$(mktemp)
head -n -6 ${cfg.configDir}/config/configuration.yaml > "$tmp"
mv "$tmp" ${cfg.configDir}/config/configuration.yaml
fi
fi
until [[ -e ${cfg.configDir}/config/configuration.yaml ]]; do
sleep 15
done
cat << 'EOF' >> ${cfg.configDir}/config/configuration.yaml
http:
use_x_forwarded_for: true
trusted_proxies: 10.89.0.0/16
zha:
EOF
systemctl restart ${name}.service
'';
};
};
systemd.services."mqtt-quirk" = {
description = "Podman container quirk : Home-assistant MQTT";
wantedBy = [ "multi-user.target" "mqtt.service" ];
after = [ "mqtt-secrets.service" ];
before = [ "mqtt.service" "mqtt-permissions.service" ];
onFailure = [ "service-failure-notify@%n.service" ];
startLimitBurst = 5;
startLimitIntervalSec = 600;
path = [ pkgs.coreutils pkgs.mosquitto ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
};
script = ''
if [[ -e ${cfg.configDir}/mqtt/mosquitto.conf && ${cfg.configDir}/mqtt/password.txt ]]; then
if grep -qF "listener 1883" ${cfg.configDir}/mqtt/mosquitto.conf; then
exit 0
else
rm ${cfg.configDir}/mqtt/mosquitto.conf
rm ${cfg.configDir}/mqtt/password.txt
touch ${cfg.configDir}/mqtt/mosquitto.conf
touch ${cfg.configDir}/mqtt/password.txt
fi
fi
cat << EOF >> ${cfg.configDir}/mqtt/mosquitto.conf
persistence true
persistence_location /mosquitto/data/
log_dest file /mosquitto/log/mosquitto.log
listener 1883
## Authentication ##
allow_anonymous false
password_file /mosquitto/password.txt
EOF
source /var/lib/numbus-server/mqtt/.env
mosquitto_passwd -b ${cfg.configDir}/mqtt/password.txt "$HOME_ASSISTANT_MQTT_USER" "$HOME_ASSISTANT_MQTT_PASSWORD"
chmod 600 ${cfg.configDir}/mqtt/password.txt
'';
};
}
+165
View File
@@ -0,0 +1,165 @@
{ config, pkgs, lib, ... }:
with lib;
let
# Version tagging
immichVersion = "v2.7.5";
redisVersion = "9@sha256:546304417feac0874c3dd576e0952c6bb8f06bb4093ea0c9ca303c73cf458f63";
databaseVersion = "14-vectorchord0.4.3-pgvectors0.2.0@sha256:bcf63357191b76a916ae5eb93464d65c07511da41e3bf7a8416db519b40b1c23";
# Helper
helper = import ./lib.nix { inherit config pkgs lib; };
cfg = config.numbus.services.immich;
# Container configuration
name = "immich";
in
helper.mkPodmanService {
inherit name;
description = "Immich, Google Photos but better";
defaultPort = "2283";
generatedSecrets = {
DB_DATABASE_NAME = "xkcdpass -n 2 -d -";
DB_USERNAME = "xkcdpass -n 2 -d -";
DB_PASSWORD = "xkcdpass -n 8 -d -";
};
importedSecrets = {
REDIS_HOSTNAME = "immich-redis";
DB_HOSTNAME = "immich-database";
UPLOAD_LOCATION = "${cfg.dataDir}";
DB_DATA_LOCATION = "${cfg.configDir}/database";
TZ = "${config.time.timeZone}";
IMMICH_VERSION = "v2.7.5";
};
middlewares = [ "immichSecureHeaders" ];
dirPermissions = [
"100999:100 ${cfg.configDir}"
"100999:100 ${cfg.configDir}/redis"
"100999:100 ${cfg.configDir}/model-cache"
"100999:100 ${cfg.configDir}/machine-learning-cache"
"100999:100 ${cfg.configDir}/machine-learning-config"
"100999:100 ${cfg.configDir}/database"
"100999:100 ${cfg.dataDir}"
];
# Compose file good
composeText = ''
services:
immich-server:
container_name: immich-server
hostname: immich-server
image: ghcr.io/immich-app/immich-server:${immichVersion}
user: '1000:1000'
networks:
immich:
ports:
- "${cfg.port}:2283/tcp"
volumes:
- $UPLOAD_LOCATION:/data
- /etc/localtime:/etc/localtime:ro
env_file:
- /var/lib/numbus-server/immich/.env
environment:
TZ: $TZ
REDIS_HOSTNAME: $REDIS_HOSTNAME
DB_HOSTNAME: $DB_HOSTNAME
DB_DATABASE_NAME: $DB_DATABASE_NAME
DB_USERNAME: $DB_USERNAME
DB_PASSWORD: $DB_PASSWORD
IMMICH_TRUSTED_PROXIES: ${config.numbus.networking.ipAddress}
depends_on:
- immich-redis
- immich-database
healthcheck:
disable: false
security_opt:
- no-new-privileges:true
cap_drop:
- NET_RAW
restart: unless-stopped
immich-machine-learning:
container_name: immich-machine-learning
hostname: immich-machine-learning
image: ghcr.io/immich-app/immich-machine-learning:${immichVersion}
user: '1000:1000'
networks:
immich:
volumes:
- ${cfg.configDir}/model-cache:/cache
- ${cfg.configDir}/machine-learning-config:/usr/src/.config
- ${cfg.configDir}/machine-learning-cache:/usr/src/.cache/
env_file:
- /var/lib/numbus-server/immich/.env
healthcheck:
disable: false
security_opt:
- no-new-privileges:true
cap_drop:
- NET_RAW
restart: unless-stopped
immich-redis:
container_name: immich-redis
hostname: immich-redis
image: docker.io/valkey/valkey:${redisVersion}
user: '1000:1000'
networks:
immich:
volumes:
- ${cfg.configDir}/redis:/data
healthcheck:
test: redis-cli ping || exit 1
restart: unless-stopped
immich-database:
container_name: immich-database
hostname: immich-database
image: ghcr.io/immich-app/postgres:${databaseVersion}
user: '1000:1000'
networks:
immich:
environment:
POSTGRES_PASSWORD: $DB_PASSWORD
POSTGRES_USER: $DB_USERNAME
POSTGRES_DB: $DB_DATABASE_NAME
POSTGRES_INITDB_ARGS: '--data-checksums'
volumes:
- $DB_DATA_LOCATION:/var/lib/postgresql/data
shm_size: 128mb
healthcheck:
disable: false
security_opt:
- no-new-privileges:true
cap_drop:
- NET_RAW
restart: unless-stopped
networks:
immich:
name: immich
driver: bridge
'';
extraConfig = {
environment.etc."traefik/rules/immichSecureHeaders.yaml".text = ''
http:
middlewares:
immichSecureHeaders:
headers:
FrameDeny: true
AccessControlAllowMethods: 'GET,POST,PUT,DELETE,OPTIONS'
AccessControlAllowOriginList:
- https://${cfg.subdomain}.${config.numbus.services.domain}
- origin-list-or-null
AccessControlMaxAge: 100
AddVaryHeader: true
BrowserXssFilter: true
ContentTypeNosniff: true
ForceSTSHeader: true
STSIncludeSubdomains: true
STSPreload: true
ContentSecurityPolicy: "default-src 'self'; base-uri 'self'; img-src 'self' https://static.immich.cloud https://tiles.immich.cloud data: blob:; connect-src 'self' https://${cfg.subdomain}.${config.numbus.services.domain} wss://${cfg.subdomain}.${config.numbus.services.domain} https://static.immich.cloud https://tiles.immich.cloud; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; script-src 'self' 'unsafe-inline' 'unsafe-eval'; worker-src 'self' blob: https://${cfg.subdomain}.${config.numbus.services.domain}; frame-ancestors 'self';"
CustomFrameOptionsValue: SAMEORIGIN
ReferrerPolicy: same-origin
PermissionsPolicy: vibrate 'self'
STSSeconds: 315360000
'';
};
}
+41
View File
@@ -0,0 +1,41 @@
{ config, pkgs, lib, ... }:
with lib;
let
# Version tagging
it-toolsVersion = "2024.10.22-7ca5933";
# Helper
helper = import ./lib.nix { inherit config pkgs lib; };
cfg = config.numbus.services.it-tools;
# Container config
name = "it-tools";
in
helper.mkPodmanService {
inherit name;
description = "IT-tools, useful tools when doing IT";
pod = "false";
defaultPort = "8880";
configDirEnabled = false;
dataDirEnabled = false;
middlewares = [ "secureHeaders" ];
# Compose file good
composeText = ''
services:
it-tools:
image: docker.io/corentinth/it-tools:${it-toolsVersion}
container_name: it-tools
hostname: it-tools
networks:
it-tools:
ports:
- "${cfg.port}:80/tcp"
restart: unless-stopped
networks:
it-tools:
name: it-tools
driver: bridge
'';
}
+206
View File
@@ -0,0 +1,206 @@
{ lib, config, pkgs }:
with lib;
{
mkPodmanService = {
name,
description,
defaultPort ? "0",
defaultSubdomain ? name,
pod ? name,
reverseProxied ? true,
composeText,
scheme ? "http",
middlewares ? null,
dependencies ? [ "traefik.service" "${config.numbus.services.dns}.service" ],
extraOptions ? {},
extraConfig ? {},
configDirEnabled ? true,
dataDirEnabled ? true,
startDelay ? 180,
dirPermissions ? [],
generatedSecrets ? {},
importedSecrets ? {},
envFile ? null,
...
}:
let
cfg = config.numbus.services.${name};
hasSecrets = (generatedSecrets != {}) || (importedSecrets != {});
envFilePath = if envFile == null then "/var/lib/numbus-server/${name}/.env" else envFile;
envFileArg = if hasSecrets || envFile != null then "--env-file ${envFilePath}" else "";
in
{
options.numbus.services.${name} = recursiveUpdate ({
enable = mkEnableOption description;
subdomain = mkOption {
type = types.str;
default = defaultSubdomain;
example = defaultSubdomain;
description = "The subdomain that ${name} will use";
};
port = mkOption {
type = types.str;
default = defaultPort;
example = defaultPort;
description = "The port that ${name} will use.";
};
reverseProxied = mkOption {
type = types.bool;
default = reverseProxied;
example = reverseProxied;
description = "Whether to create a Traefik reverse proxy configuration for this service.";
};
} // (optionalAttrs configDirEnabled {
configDir = mkOption {
type = types.str;
default = "/mnt/config/${name}";
example = "/mnt/config/${name}";
description = "The directory where ${name}'s configuration files will be stored";
};
}) // (optionalAttrs dataDirEnabled {
dataDir = mkOption {
type = types.str;
default = "/mnt/data/${name}";
example = "/mnt/data/${name}";
description = "The directory where ${name}'s data will be stored";
};
})) extraOptions;
config = mkIf cfg.enable (mkMerge [
{
environment.etc."podman/${name}/compose.yaml".text = composeText;
environment.etc."traefik/rules/${name}.yaml" = mkIf cfg.reverseProxied {
text = ''
http:
routers:
${name}:
rule: "Host(`${cfg.subdomain}.${config.numbus.services.domain}`)"
entrypoints:
- "websecure"
service: ${name}
middlewares:
${concatStringsSep "\n" (map (m: " - ${m}") middlewares)}
tls:
certresolver: "cloudflare"
options: "secureTLS"
services:
${name}:
loadBalancer:
servers:
- url: "${scheme}://host.containers.internal:${cfg.port}"
'';
};
systemd.services."${name}" = {
description = "Podman container : ${name}";
after = dependencies;
wantedBy = [ "multi-user.target" ];
onFailure = [ "service-failure-notify@%n.service" ];
startLimitBurst = 5;
startLimitIntervalSec = 600;
path = [ pkgs.podman pkgs.podman-compose pkgs.slirp4netns pkgs.su pkgs.sudo pkgs.coreutils ];
serviceConfig = {
Type = "exec";
TimeoutStartSec = "1000";
ExecStartPre = [
"${pkgs.bash}/bin/bash -c 'sleep $((RANDOM % ${toString startDelay}))'"
"${pkgs.bash}/bin/bash -c 'export PATH=/run/wrappers/bin:$PATH; exec ${pkgs.sudo}/bin/sudo -u numbus-admin podman-compose -f /etc/podman/${name}/compose.yaml pull'"
];
ExecStart = "${pkgs.bash}/bin/bash -c 'export PATH=/run/wrappers/bin:$PATH; exec ${pkgs.sudo}/bin/sudo -u numbus-admin podman-compose ${envFileArg} --in-pod ${toString pod} -f /etc/podman/${name}/compose.yaml up --remove-orphans'";
ExecStop = "${pkgs.bash}/bin/bash -c 'export PATH=/run/wrappers/bin:$PATH; exec ${pkgs.sudo}/bin/sudo -u numbus-admin podman-compose ${envFileArg} --in-pod ${toString pod} -f /etc/podman/${name}/compose.yaml down'";
Restart = "on-failure";
RestartSec = "3m";
};
};
systemd.services."${name}-permissions" = mkIf (dirPermissions != []) {
description = "Podman container : ${name} : check and fix permissions";
before = [ "${name}.service" ];
wantedBy = [ "multi-user.target" "${name}.service" ];
onFailure = [ "service-failure-notify@%n.service" ];
startLimitBurst = 5;
startLimitIntervalSec = 600;
path = [ pkgs.coreutils ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
Restart = "on-failure";
RestartSec = "5m";
};
script = ''
${concatStringsSep "\n" (map (perm: ''
set -- ${perm}
WANTED_PERMISSIONS=$1
FOLDER_PATH=$2
if [[ ! -e "$FOLDER_PATH" ]]; then
mkdir -p "$FOLDER_PATH"
elif [[ ! -d "$FOLDER_PATH" ]]; then
rm "$FOLDER_PATH"
mkdir -p "$FOLDER_PATH"
fi
ACTUAL_PERMISSIONS=$(stat -c '%u:%g' "$FOLDER_PATH")
if [[ "$ACTUAL_PERMISSIONS" != "$WANTED_PERMISSIONS" ]]; then
chown -R "$WANTED_PERMISSIONS" "$FOLDER_PATH"
fi
'') dirPermissions)}
exit 0
'';
};
systemd.services."${name}-secrets" = mkIf hasSecrets {
description = "Podman container create secrets : ${name}";
before = [ "${name}.service" ];
wantedBy = [ "multi-user.target" "${name}.service" ];
onFailure = [ "service-failure-notify@%n.service" ];
startLimitBurst = 5;
startLimitIntervalSec = 600;
path = [ pkgs.coreutils pkgs.xkcdpass pkgs.gnugrep ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
Restart = "on-failure";
RestartSec = "5m";
};
script = ''
mkdir -p /var/lib/numbus-server/${name}
SECRETS_FILE="${envFilePath}"
if [[ ! -f "$SECRETS_FILE" ]]; then
touch "$SECRETS_FILE"
fi
# Generated Secrets (only if missing)
${concatStringsSep "\n" (mapAttrsToList (k: v: ''
if ! grep -q "^${k}=" "$SECRETS_FILE"; then
echo "${k}=\"$(${v})\"" >> "$SECRETS_FILE"
fi
'') generatedSecrets)}
# Imported Secrets (update or append)
${concatStringsSep "\n" (mapAttrsToList (k: v: ''
if grep -q "^${k}=" "$SECRETS_FILE"; then
grep -v "^${k}=" "$SECRETS_FILE" > "$SECRETS_FILE.tmp"
mv "$SECRETS_FILE.tmp" "$SECRETS_FILE"
fi
echo "${k}=\"${lib.escapeShellArg v}\"" >> "$SECRETS_FILE"
'') importedSecrets)}
chown numbus-admin:users "$SECRETS_FILE"
chmod 600 "$SECRETS_FILE"
'';
};
}
extraConfig
]);
};
}
+335
View File
@@ -0,0 +1,335 @@
{ config, pkgs, lib, ... }:
with lib;
let
# Version tagging
nextcloudVersion = "33.0.5-apache";
redisVersion = "8.6-alpine";
databaseVersion = "11.8";
onlyofficeVersion = "9.4.0";
whiteboardVersion = "v1.5.9";
# Helper
helper = import ./lib.nix { inherit config pkgs lib; };
cfg = config.numbus.services.nextcloud;
# Container config
name = "nextcloud";
in
helper.mkPodmanService {
inherit name;
description = "Nextcloud, your own online office suite";
defaultPort = "1100";
generatedSecrets = {
DB_NAME = "xkcdpass -n 2 -d -";
DB_USERNAME = "xkcdpass -n 2 -d -";
DB_PASSWORD = "xkcdpass -n 10 -d -";
REDIS_PASSWORD = "xkcdpass -n 10 -d -";
ONLYOFFICE_PASSWORD = "xkcdpass -n 10 -d -";
WHITEBOARD_PASSWORD = "xkcdpass -n 10 -d -";
SMTP_PASSWORD = "cat ${config.numbus.mail.smtpPasswordPath}";
};
middlewares = [ "nextcloudSecureHeaders" ];
dirPermissions = [
"100032:100 ${cfg.dataDir}"
"100032:100 ${cfg.configDir}"
"100032:100 ${cfg.configDir}/web"
"100999:100 ${cfg.configDir}/redis"
"100999:100 ${cfg.configDir}/database"
"1000:100 ${cfg.configDir}/onlyoffice"
"1000:100 ${cfg.configDir}/onlyoffice/log"
"1000:100 ${cfg.configDir}/onlyoffice/cache"
"1000:100 ${cfg.configDir}/onlyoffice/data"
"1000:100 ${cfg.configDir}/onlyoffice/database"
];
# Compose file good
composeText = ''
services:
nextcloud-server:
image: docker.io/library/nextcloud:${nextcloudVersion}
container_name: nextcloud-server
hostname: nextcloud-server
networks:
nextcloud:
ports:
- "${cfg.port}:80/tcp"
volumes:
- ${cfg.configDir}/web:/var/www/html
- ${cfg.dataDir}:/mnt/ncdata
environment:
MYSQL_HOST: nextcloud-database:3306
MYSQL_DATABASE: $DB_NAME
MYSQL_USER: $DB_USERNAME
MYSQL_PASSWORD: $DB_PASSWORD
REDIS_HOST: nextcloud-redis
REDIS_HOST_PASSWORD: $REDIS_PASSWORD
NEXTCLOUD_TRUSTED_DOMAINS: ${cfg.subdomain}.${config.numbus.services.domain}
NEXTCLOUD_DATA_DIR: /mnt/ncdata
SMTP_SECURE: tls
SMTP_HOST: ${config.numbus.mail.smtpServer}
SMTP_PORT: ${toString config.numbus.mail.smtpPort}
SMTP_NAME: ${config.numbus.mail.smtpUsername}
SMTP_PASSWORD: $SMTP_PASSWORD
MAIL_FROM_ADDRESS: nextcloud-noreply
MAIL_DOMAIN: ${config.numbus.services.domain}
APACHE_DISABLE_REWRITE_IP: 1
OVERWRITEPROTOCOL: https
TRUSTED_PROXIES: 10.89.0.0/16
NC_default_phone_region: "${config.numbus.language}"
NC_default_language: "${config.numbus.language}"
NC_default_locale: "${config.numbus.locale}"
NC_default_timezone: "${config.time.timeZone}"
NC_maintenance_window_start: "1"
PHP_MEMORY_LIMIT: 1024M
PHP_OPCACHE_MEMORY_CONSUMPTION: 256
depends_on:
- nextcloud-database
security_opt:
- no-new-privileges:true
cap_drop:
- NET_RAW
restart: unless-stopped
nextcloud-redis:
image: docker.io/library/redis:${redisVersion}
container_name: nextcloud-redis
hostname: nextcloud-redis
user: '1000:1000'
networks:
nextcloud:
volumes:
- ${cfg.configDir}/redis:/data
command: redis-server --requirepass $REDIS_PASSWORD --save 60 1 --loglevel warning
security_opt:
- no-new-privileges:true
cap_drop:
- NET_RAW
restart: unless-stopped
nextcloud-database:
image: docker.io/library/mariadb:${databaseVersion}
container_name: nextcloud-database
hostname: nextcloud-database
user: '1000:1000'
networks:
nextcloud:
volumes:
- ${cfg.configDir}/database:/var/lib/mysql
environment:
MARIADB_DATABASE: $DB_NAME
MARIADB_USER: $DB_USERNAME
MARIADB_PASSWORD: $DB_PASSWORD
MARIADB_RANDOM_ROOT_PASSWORD: true
security_opt:
- no-new-privileges:true
cap_drop:
- NET_RAW
command:
- "--transaction-isolation=READ-COMMITTED"
- "--binlog-format=ROW"
restart: unless-stopped
nextcloud-onlyoffice:
container_name: nextcloud-onlyoffice
hostname: nextcloud-onlyoffice
image: docker.io/onlyoffice/documentserver:${onlyofficeVersion}
environment:
- JWT_SECRET=$ONLYOFFICE_PASSWORD
- REDIS_SERVER_HOST=nextcloud-redis
- REDIS_SERVER_PORT=6379
- REDIS_SERVER_PASS=$REDIS_PASSWORD
- ADMINPANEL_ENABLED=false
- EXAMPLE_ENABLED=false
- METRICS_ENABLED=false
ports:
- "9980:80/tcp"
volumes:
- ${cfg.configDir}/onlyoffice/log:/var/log/onlyoffice
- ${cfg.configDir}/onlyoffice/cache:/var/lib/onlyoffice
- ${cfg.configDir}/onlyoffice/data:/var/www/onlyoffice/Data
- ${cfg.configDir}/onlyoffice/database:/var/lib/postgresql
cap_drop:
- NET_RAW
restart: unless-stopped
nextcloud-whiteboard:
image: ghcr.io/nextcloud-releases/whiteboard:${whiteboardVersion}
container_name: nextcloud-whiteboard
hostname: nextcloud-whiteboard
user: '1000:1000'
ports:
- "3002:3002/tcp"
environment:
NEXTCLOUD_URL: https://${cfg.subdomain}.${config.numbus.services.domain}
JWT_SECRET_KEY: $WHITEBOARD_PASSWORD
security_opt:
- no-new-privileges:true
cap_drop:
- NET_RAW
restart: unless-stopped
networks:
nextcloud:
name: nextcloud
driver: bridge
'';
extraConfig = {
environment.etc."traefik/rules/nextcloud-onlyoffice.yaml".text = ''
http:
routers:
nextcloud-onlyoffice:
rule: "Host(`onlyoffice.${config.numbus.services.domain}`)"
entrypoints:
- "websecure"
service: nextcloud-onlyoffice
middlewares:
- "nextcloudSecureHeaders"
tls:
certresolver: "cloudflare"
options: "secureTLS"
services:
nextcloud-onlyoffice:
loadBalancer:
servers:
- url: "http://host.containers.internal:9980"
'';
environment.etc."traefik/rules/nextcloud-whiteboard.yaml".text = ''
http:
routers:
nextcloud-whiteboard:
rule: "Host(`whiteboard.${config.numbus.services.domain}`)"
entrypoints:
- "websecure"
service: nextcloud-whiteboard
middlewares:
- "secureHeaders"
tls:
certresolver: "cloudflare"
options: "secureTLS"
services:
nextcloud-whiteboard:
loadBalancer:
servers:
- url: "http://host.containers.internal:3002"
'';
environment.etc."traefik/rules/nextcloudSecureHeaders.yaml".text = ''
http:
middlewares:
nextcloudSecureHeaders:
headers:
FrameDeny: false
CustomFrameOptionsValue: "SAMEORIGIN"
AddVaryHeader: true
BrowserXssFilter: true
ContentTypeNosniff: true
ForceSTSHeader: true
STSSeconds: 315360000
STSIncludeSubdomains: true
STSPreload: true
AccessControlAllowMethods: "GET,OPTIONS,PUT"
AccessControlAllowOriginList:
- origin-list-or-null
AccessControlMaxAge: 100
ReferrerPolicy: same-origin
PermissionsPolicy: "vibrate=()"
ContentSecurityPolicy: >-
default-src https://onlyoffice.${config.numbus.services.domain} 'self';
script-src https://onlyoffice.${config.numbus.services.domain} 'self' 'unsafe-inline';
style-src 'self' 'unsafe-inline';
connect-src 'self';
img-src 'self' data:;
font-src 'self' data:;
frame-src https://onlyoffice.${config.numbus.services.domain} 'self';
frame-ancestors https://onlyoffice.${config.numbus.services.domain} 'self';
object-src 'none';
base-uri 'self';
'';
systemd.services."${name}-quirk" = {
description = "Podman container quirk : ${name}";
wantedBy = [ "multi-user.target" ];
after = [ "${name}.service" "${name}-secrets.service" ];
onFailure = [ "service-failure-notify@%n.service" ];
startLimitBurst = 5;
startLimitIntervalSec = 600;
path = [ pkgs.coreutils pkgs.sudo pkgs.podman pkgs.systemd pkgs.gnugrep ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
};
script = ''
OCC="sudo -u numbus-admin podman exec --user www-data nextcloud-server php occ"
[[ ! -e /var/lib/numbus-server/${name}/.env ]] && systemctl start ${name}-secrets.service
until [[ -e /var/lib/numbus-server/${name}/.env ]]; do
echo "Waiting for secrets generation..."
sleep 5
done
source /var/lib/numbus-server/${name}/.env
until $OCC status | grep -iq "installed: true" >/dev/null 2>&1; do
echo "Waiting for Nextcloud to be up and running..."
sleep 60
done
$OCC db:add-missing-indices
$OCC maintenance:repair --include-expensive
INSTALL_APPS_LIST=( "calendar" "contacts" "mail" "notes" "onlyoffice" "cookbook" "whiteboard" )
DISABLE_APPS_LIST=( "activity" "federation" "webhook_listeners" "photos" "recommendations" "sharebymail" "teams" "support" "richdocumentscode" )
for app in ''${INSTALL_APPS_LIST[@]}; do
if ! $OCC --no-warnings app:list | grep -iq "$app:"; then
$OCC --no-warnings app:install "$app"
fi
if $OCC --no-warnings app:list --disabled | grep -iq "$app:"; then
$OCC --no-warnings app:enable "$app"
fi
done
for app in ''${DISABLE_APPS_LIST[@]}; do
if $OCC --no-warnings app:list --enabled | grep -iq "$app:"; then
$OCC --no-warnings app:disable "$app"
fi
done
$OCC --no-warnings config:system:set onlyoffice DocumentServerInternalUrl --value="https://onlyoffice.${config.numbus.services.domain}/"
$OCC --no-warnings config:system:set onlyoffice DocumentServerUrl --value="https://onlyoffice.${config.numbus.services.domain}/"
$OCC --no-warnings config:system:set onlyoffice jwt_secret --value="$ONLYOFFICE_PASSWORD"
$OCC --no-warnings config:app:set whiteboard collabBackendUrl --value="https://whiteboard.${config.numbus.services.domain}"
$OCC --no-warnings config:app:set whiteboard jwt_secret_key --value="$WHITEBOARD_PASSWORD"
if [[ ! -f /var/lib/numbus-server/${name}/croned.true ]]; then
$OCC background:cron
sudo -u numbus-admin podman exec --user www-data nextcloud-server php -f /var/www/html/cron.php
touch /var/lib/numbus-server/${name}/croned.true
fi
if [[ ! -f /var/lib/numbus-server/${name}/scanned.true ]]; then
$OCC files:scan --all
$OCC files:repair-tree
touch /var/lib/numbus-server/${name}/scanned.true
fi
'';
};
systemd.services."${name}-cron" = {
description = "Podman container crontab : ${name}";
after = [ "${name}.service" "${name}-quirk.service" ];
onFailure = [ "service-failure-notify@%n.service" ];
path = [ pkgs.sudo pkgs.podman ];
serviceConfig = {
Type = "oneshot";
ExecCondition = ''${pkgs.sudo}/bin/sudo -u numbus-admin podman exec --user www-data nextcloud-server php occ status'';
ExecStart = "${pkgs.sudo}/bin/sudo -u numbus-admin podman exec --user www-data nextcloud-server php -f /var/www/html/cron.php";
};
};
systemd.timers."${name}-cron" = {
description = "Timer for Nextcloud cron";
wantedBy = [ "timers.target" ];
timerConfig = {
OnBootSec = "5m";
OnUnitActiveSec = "5m";
Unit = "${name}-cron.service";
};
};
};
}
+106
View File
@@ -0,0 +1,106 @@
{ config, pkgs, lib, ... }:
with lib;
let
# Version tagging
passboltVersion = "5.9.0-1-ce-non-root";
databaseVersion = "12.2";
# Helper
helper = import ./lib.nix { inherit config pkgs lib; };
cfg = config.numbus.services.passbolt;
# Container config
name = "passbolt";
in
helper.mkPodmanService {
inherit name;
description = "Passbolt, your password manager";
defaultPort = "4433";
scheme = "https";
dataDirEnabled = false;
generatedSecrets = {
DB_NAME = "xkcdpass -n 2 -d -";
DB_USERNAME = "xkcdpass -n 2 -d -";
DB_PASSWORD = "xkcdpass -n 10 -d -";
SMTP_PASSWORD = "cat ${config.numbus.mail.smtpPasswordPath}";
};
middlewares = [ "secureHeaders" ];
dirPermissions = [
"100032:100 ${cfg.configDir}"
"100032:100 ${cfg.configDir}/gpg"
"100032:100 ${cfg.configDir}/jwt"
"100999:100 ${cfg.configDir}/database"
];
# Compose file good
composeText = ''
services:
passbolt-server:
image: docker.io/passbolt/passbolt:${passboltVersion}
container_name: passbolt-server
hostname: passbolt-server
user: '33:33'
networks:
passbolt:
ports:
- "${cfg.port}:4433/tcp"
volumes:
- ${cfg.configDir}/gpg:/etc/passbolt/gpg
- ${cfg.configDir}/jwt:/etc/passbolt/jwt
environment:
APP_DEFAULT_TIMEZONE: ${config.time.timeZone}
APP_FULL_BASE_URL: https://${cfg.subdomain}.${config.numbus.services.domain}
DATASOURCES_DEFAULT_HOST: "passbolt-database"
DATASOURCES_DEFAULT_USERNAME: $DB_USERNAME
DATASOURCES_DEFAULT_PASSWORD: $DB_PASSWORD
DATASOURCES_DEFAULT_DATABASE: $DB_NAME
EMAIL_DEFAULT_FROM_NAME: "Passbolt"
EMAIL_TRANSPORT_DEFAULT_HOST: ${config.numbus.mail.smtpServer}
EMAIL_TRANSPORT_DEFAULT_PORT: ${toString config.numbus.mail.smtpPort}
EMAIL_TRANSPORT_DEFAULT_USERNAME: ${config.numbus.mail.smtpUsername}
EMAIL_TRANSPORT_DEFAULT_PASSWORD: $EMAIL_TRANSPORT_DEFAULT_PASSWORD
EMAIL_TRANSPORT_DEFAULT_TLS: true
EMAIL_DEFAULT_FROM: passbolt-noreply@${config.numbus.services.domain}
PASSBOLT_SSL_FORCE: true
command:
[
"/usr/bin/wait-for.sh",
"-t",
"0",
"passbolt-database:3306",
"--",
"/docker-entrypoint.sh"
]
depends_on:
- passbolt-database
security_opt:
- no-new-privileges:true
cap_drop:
- NET_RAW
restart: unless-stopped
passbolt-database:
image: docker.io/library/mariadb:${databaseVersion}
container_name: passbolt-database
hostname: passbolt-database
user: '1000:1000'
networks:
passbolt:
volumes:
- ${cfg.configDir}/database:/var/lib/mysql
environment:
MYSQL_RANDOM_ROOT_PASSWORD: "true"
MYSQL_DATABASE: $DB_NAME
MYSQL_USER: $DB_USERNAME
MYSQL_PASSWORD: $DB_PASSWORD
security_opt:
- no-new-privileges:true
cap_drop:
- NET_RAW
restart: unless-stopped
networks:
passbolt:
name: passbolt
driver: bridge
'';
}
+74
View File
@@ -0,0 +1,74 @@
{ config, pkgs, lib, ... }:
with lib;
let
# Version tagging
piholeVersion = "2026.02.0";
# Helper
helper = import ./lib.nix { inherit config pkgs lib; };
cfg = config.numbus.services.pi-hole;
# Container config
name = "pi-hole";
in
helper.mkPodmanService {
inherit name;
description = "Pi-Hole, the ads black hole";
pod = "false";
defaultPort = "4443";
scheme = "https";
dependencies = [ "network.target" ];
dataDirEnabled = false;
startDelay = 10;
generatedSecrets = {
PIHOLE_PASSWORD = "xkcdpass -n 10 -d -";
};
middlewares = [ "secureHeaders" ];
dirPermissions = [
"100999:100 ${cfg.configDir}"
];
# Compose file good
composeText = ''
services:
pi-hole:
image: docker.io/pihole/pihole:${piholeVersion}
container_name: pi-hole
hostname: pi-hole
network_mode: pasta
ports:
- "${cfg.port}:443/tcp"
- "53:53/tcp"
- "53:53/udp"
volumes:
- ${cfg.configDir}:/etc/pihole
environment:
PIHOLE_UID: '1000'
PIHOLE_GID: '1000'
TZ: ${config.time.timeZone}
FTLCONF_webserver_api_password: $PIHOLE_PASSWORD
FTLCONF_webserver_domain: ${cfg.subdomain}.${config.numbus.services.domain}
FTLCONF_dns_upstreams: 9.9.9.9;149.112.112.112
FTLCONF_dns_hosts: |
${lib.concatStringsSep "" (lib.mapAttrsToList (name: service:
if builtins.isAttrs service && service ? enable && service.enable && service ? subdomain then
" ${config.numbus.networking.ipAddress} ${service.subdomain}.${config.numbus.services.domain}\n" +
(if name == "nextcloud" then
" ${config.numbus.networking.ipAddress} onlyoffice.${config.numbus.services.domain}\n" +
" ${config.numbus.networking.ipAddress} whiteboard.${config.numbus.services.domain}\n"
else "")
else ""
) config.numbus.services)}
FTLCONF_dns_listeningMode: "BIND"
FTLCONF_dns_domain_name: "${config.numbus.services.domain}"
FTLCONF_dns_domain_local: "true"
FTLCONF_dhcp_active: "false"
FTLCONF_ntp_ipv4_active: "false"
FTLCONF_ntp_ipv6_active: "false"
FTLCONF_ntp_sync_active: "false"
cap_add:
- SYS_NICE
restart: unless-stopped
'';
}
+168
View File
@@ -0,0 +1,168 @@
{ config, pkgs, lib, ... }:
with lib;
let
# Version tagging
traefikVersion = "v3.7.4";
# Helper
helper = import ./lib.nix { inherit config pkgs lib; };
cfg = config.numbus.services.traefik;
# Container config
name = "traefik";
in
helper.mkPodmanService {
inherit name;
description = "Traefik reverse proxy, one to rule them all";
pod = "false";
dataDirEnabled = false;
dependencies = [ "network.target" ];
startDelay = 10;
generatedSecrets = {
CLOUDFLARE_DNS_API_TOKEN = "cat ${config.sops.secrets."cloudflareDnsApiToken".path}";
};
dirPermissions = [
"100999:100 ${cfg.configDir}"
"100999:100 ${cfg.configDir}/rules"
"100999:100 ${cfg.configDir}/certs"
];
reverseProxied = false;
# Compose file good
composeText = ''
services:
traefik:
image: docker.io/library/traefik:${traefikVersion}
container_name: traefik
hostname: traefik
user: '1000:1000'
network_mode: pasta
ports:
- "80:80/tcp"
- "443:443/tcp"
volumes:
- ${cfg.configDir}/traefik.yaml:/etc/traefik/traefik.yaml:ro
- ${cfg.configDir}/rules:/etc/traefik/rules:ro
- ${cfg.configDir}/certs:/var/traefik/certs:rw
environment:
- CF_DNS_API_TOKEN=$CLOUDFLARE_DNS_API_TOKEN
cap_add:
- NET_BIND_SERVICE
security_opt:
- no-new-privileges:true
restart: unless-stopped
'';
extraConfig = {
environment.etc."traefik/traefik.yaml".text = ''
global:
checkNewVersion: false
sendAnonymousUsage: false
log:
level: ${cfg.logLevel}
accesslog: {}
api:
dashboard: false
insecure: false
entryPoints:
web:
address: :80
http:
redirections:
entryPoint:
to: websecure
scheme: https
websecure:
address: :443
forwardedHeaders:
trustedIPs:
- "127.0.0.1/32"
- "10.0.0.0/8"
- "192.168.0.0/16"
- "172.16.0.0/12"
certificatesResolvers:
cloudflare:
acme:
email: ${config.numbus.mail.adminAddress}
storage: /var/traefik/certs/cloudflare-acme.json
caServer: "https://acme-v02.api.letsencrypt.org/directory"
dnsChallenge:
provider: cloudflare
resolvers:
- "1.1.1.1:53"
- "9.9.9.9:53"
serversTransport:
insecureSkipVerify: true
providers:
file:
directory: "/etc/traefik/rules"
watch: true
'';
environment.etc."traefik/rules/secureHeaders.yaml".text = ''
http:
middlewares:
secureHeaders:
headers:
FrameDeny: true
AccessControlAllowMethods: 'GET,OPTIONS,PUT'
AccessControlAllowOriginList:
- origin-list-or-null
AccessControlMaxAge: 100
AddVaryHeader: true
BrowserXssFilter: true
ContentTypeNosniff: true
ForceSTSHeader: true
STSIncludeSubdomains: true
STSPreload: true
ContentSecurityPolicy: default-src 'self' 'unsafe-inline'
CustomFrameOptionsValue: SAMEORIGIN
ReferrerPolicy: same-origin
PermissionsPolicy: vibrate 'self'
STSSeconds: 315360000
'';
environment.etc."traefik/rules/secureTLS.yaml".text = ''
tls:
options:
secureTLS:
minVersion: VersionTLS12
sniStrict: true
curvePreferences:
- CurveP521
- CurveP384
cipherSuites:
- TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
- TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
- TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256
'';
systemd.services."${name}-quirk" = {
description = "Podman container quirk : ${name}";
wantedBy = [ "multi-user.target" ];
after = [ "${name}.service" "${name}-secrets.service" ];
onFailure = [ "service-failure-notify@%n.service" ];
startLimitBurst = 5;
startLimitIntervalSec = 600;
path = [ pkgs.coreutils ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
};
script = ''
cp -Lurv /etc/traefik/* ${cfg.configDir}
'';
};
};
extraOptions = {
enable.default = true;
logLevel = mkOption {
type = types.enum [ "TRACE" "DEBUG" "INFO" "WARN" "ERROR" "FATAL" ];
default = "ERROR";
example = "ERROR";
description = "The level of detail Traefik should print in the logs.";
};
};
}
+17
View File
@@ -0,0 +1,17 @@
{ config, lib, ... }:
with lib;
let
cfg = config.numbus.services.virtualization;
in
{
options.numbus.services.virtualization = {
enable = mkEnableOption "QEMU/KVM virtualization software";
};
config = mkIf cfg.enable {
virtualisation.libvirtd.enable = true;
};
}