Compare commits

..

30 Commits

Author SHA1 Message Date
e734102104 uConsole: remove clamav (cross-compile failure — cmake try_run + Rust proc-macro linker) 2026-06-19 12:20:44 -04:00
e54812c3c5 fix: add native gcc for clamav Rust build scripts in cross-compile 2026-06-19 12:13:37 -04:00
42949532a3 fix: set CC=gcc for clamav Rust proc-macro builds in cross-compile 2026-06-19 12:09:33 -04:00
a0875e9e0a fix: add clamav cross-compile workaround (cmake try_run for FD passing, uname POSIX, struct packing, SAR) 2026-06-19 07:54:30 -04:00
016cf4aa53 uConsole: remove hashcat (cross-compile failure — Makefile calls gcc directly, same issue as neovim) 2026-06-19 07:37:59 -04:00
a114cd859c docs: correct maxJobs in remote builder notes (uConsole=4, server=36) 2026-06-18 23:36:04 -04:00
317e908ab5 docs: add bidirectional remote builder setup notes to flake 2026-06-18 23:15:55 -04:00
ef8c92f05e docs: note neovim cross-compile failure in overlay comments 2026-06-18 23:02:13 -04:00
8874f6ff66 feat: add gortium.clamav NixOS module
- New module at modules/nixos/services/clamav.nix
- Options: enable (CLI-only), enableDaemon (full services),
  onAccessScanning (clamonacc), scanPaths, dailyScanTime
- All scans are logging-only — no auto-quarantine or deletion
- uConsole: CLI tools only (enableDaemon=false)
- lazyworkhorse: full setup with on-access scanning, daily 3 AM scan

Also: remove neovim from uConsole (fails cross-compile, emacs available)
2026-06-18 21:53:33 -04:00
f14c74f50f feat: add ClamAV antivirus with daily auto-scan 2026-06-18 21:26:14 -04:00
570ab16243 docs: add comprehensive cross-compile workaround tracker in overlay comment 2026-06-18 21:16:56 -04:00
b072e2052f fix: remove js8call + switch wireshark to CLI to drop qtquick3d dep 2026-06-18 21:13:31 -04:00
16b9b1c866 fix: use dontUseCmakeInstall + stub for qtquick3d cross-compile 2026-06-18 21:07:37 -04:00
4acd98c689 fix: stub qtquick3d install for aarch64 cross-compile (Qt::Quick unavailable) 2026-06-18 21:04:23 -04:00
c8eb80b7f8 fix: disable mailutils in emacs-pgtk to avoid broken gss cross-compile 2026-06-18 20:59:39 -04:00
e6d1b1bdab fix: remove broken perl-ldap hacks, keep only john perl-ldap filter 2026-06-18 20:40:29 -04:00
e5188eb5b0 fix: strip perl-ldap from john deps in cross-compile overlay 2026-06-18 20:39:50 -04:00
9be5583750 fix: try final.buildPackages.perl for perl.mini cross-compile fix 2026-06-18 20:39:50 -04:00
533de87069 fix: replace perl.mini with native build perl for cross-compile (fixes all perl modules) 2026-06-18 20:39:50 -04:00
0772daf3ed fix: try null perl for perl-ldap cross-compile 2026-06-18 17:25:39 -04:00
bf9b3a7890 fix: proper perl-ldap cross-compile override 2026-06-18 17:25:06 -04:00
d9e56e8958 fix: force perl-ldap to use buildPackages perl for cross-compile 2026-06-18 17:23:59 -04:00
c6fd58123e fix: remove sshPort from buildMachines (use SSH config instead) 2026-06-18 17:20:38 -04:00
932de1752d fix: place agenix-rekey config inside module (was outside closing brace) 2026-06-18 17:19:29 -04:00
050f2d4761 feat: add agenix-rekey config + remote builder to uConsole 2026-06-18 17:17:29 -04:00
da691f0b4d feat: add agenix-rekey + remote-builder module for distributed builds 2026-06-18 17:17:03 -04:00
ef3ad6bbcf fix: disable Boost MPI for aarch64 cross-compile (no b2 alternatives) 2026-06-18 17:09:59 -04:00
7e148791fb remove meshtastic (not in nixpkgs) 2026-06-18 16:58:56 -04:00
65241113cc fix: add reticulum overlay to uconsole nixpkgs 2026-06-18 15:23:59 -04:00
4989f9898c feat: merge Reticulum overlay, poup-16t-disk, open_code_server, merged uConsole config 2026-06-18 15:23:06 -04:00
9 changed files with 971 additions and 27 deletions

157
flake.lock generated
View File

@@ -23,6 +23,30 @@
"type": "github"
}
},
"agenix-rekey": {
"inputs": {
"devshell": "devshell",
"flake-parts": "flake-parts",
"nixpkgs": [
"nixpkgs"
],
"pre-commit-hooks": "pre-commit-hooks",
"treefmt-nix": "treefmt-nix"
},
"locked": {
"lastModified": 1774522439,
"narHash": "sha256-GvINrdGznE7mGlDNjW0/PMgOJlC+Nl9MkfxALB4QvWs=",
"owner": "oddlama",
"repo": "agenix-rekey",
"rev": "8b9c179bc1300ab130c90f2d25426bf0e7a2b58d",
"type": "github"
},
"original": {
"owner": "oddlama",
"repo": "agenix-rekey",
"type": "github"
}
},
"argononed": {
"flake": false,
"locked": {
@@ -39,7 +63,44 @@
"type": "github"
}
},
"devshell": {
"inputs": {
"nixpkgs": [
"agenix-rekey",
"nixpkgs"
]
},
"locked": {
"lastModified": 1728330715,
"narHash": "sha256-xRJ2nPOXb//u1jaBnDP56M7v5ldavjbtR6lfGqSvcKg=",
"owner": "numtide",
"repo": "devshell",
"rev": "dd6b80932022cea34a019e2bb32f6fa9e494dfef",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "devshell",
"type": "github"
}
},
"flake-compat": {
"flake": false,
"locked": {
"lastModified": 1696426674,
"narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
"owner": "edolstra",
"repo": "flake-compat",
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
"type": "github"
},
"original": {
"owner": "edolstra",
"repo": "flake-compat",
"type": "github"
}
},
"flake-compat_2": {
"flake": false,
"locked": {
"lastModified": 1751685974,
@@ -53,7 +114,7 @@
"url": "https://git.lix.systems/lix-project/flake-compat/archive/main.tar.gz"
}
},
"flake-compat_2": {
"flake-compat_3": {
"locked": {
"lastModified": 1767039857,
"narHash": "sha256-vNpUSpF5Nuw8xvDLj2KCwwksIbjua2LZCqhV1LNRDns=",
@@ -68,6 +129,49 @@
"type": "github"
}
},
"flake-parts": {
"inputs": {
"nixpkgs-lib": [
"agenix-rekey",
"nixpkgs"
]
},
"locked": {
"lastModified": 1733312601,
"narHash": "sha256-4pDvzqnegAfRkPwO3wmwBhVi/Sye1mzps0zHWYnP88c=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "205b12d8b7cd4802fbcb8e8ef6a0f1408781a4f9",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "flake-parts",
"type": "github"
}
},
"gitignore": {
"inputs": {
"nixpkgs": [
"agenix-rekey",
"pre-commit-hooks",
"nixpkgs"
]
},
"locked": {
"lastModified": 1709087332,
"narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=",
"owner": "hercules-ci",
"repo": "gitignore.nix",
"rev": "637db329424fd7e46cf4185293b9cc8c88c95394",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "gitignore.nix",
"type": "github"
}
},
"home-manager": {
"inputs": {
"nixpkgs": [
@@ -112,14 +216,14 @@
},
"lix": {
"inputs": {
"flake-compat": "flake-compat",
"flake-compat": "flake-compat_2",
"nix2container": "nix2container",
"nix_2_18": "nix_2_18",
"nixpkgs": [
"nixpkgs"
],
"nixpkgs-regression": "nixpkgs-regression",
"pre-commit-hooks": "pre-commit-hooks"
"pre-commit-hooks": "pre-commit-hooks_2"
},
"locked": {
"lastModified": 1774721317,
@@ -225,7 +329,7 @@
"nixos-raspberrypi": {
"inputs": {
"argononed": "argononed",
"flake-compat": "flake-compat_2",
"flake-compat": "flake-compat_3",
"nixos-images": "nixos-images",
"nixpkgs": [
"nixpkgs-uconsole"
@@ -335,6 +439,29 @@
}
},
"pre-commit-hooks": {
"inputs": {
"flake-compat": "flake-compat",
"gitignore": "gitignore",
"nixpkgs": [
"agenix-rekey",
"nixpkgs"
]
},
"locked": {
"lastModified": 1735882644,
"narHash": "sha256-3FZAG+pGt3OElQjesCAWeMkQ7C/nB1oTHLRQ8ceP110=",
"owner": "cachix",
"repo": "pre-commit-hooks.nix",
"rev": "a5a961387e75ae44cc20f0a57ae463da5e959656",
"type": "github"
},
"original": {
"owner": "cachix",
"repo": "pre-commit-hooks.nix",
"type": "github"
}
},
"pre-commit-hooks_2": {
"flake": false,
"locked": {
"lastModified": 1769939035,
@@ -353,6 +480,7 @@
"root": {
"inputs": {
"agenix": "agenix",
"agenix-rekey": "agenix-rekey",
"home-manager": "home-manager_2",
"lix": "lix",
"nixos-raspberrypi": "nixos-raspberrypi",
@@ -375,6 +503,27 @@
"repo": "default",
"type": "github"
}
},
"treefmt-nix": {
"inputs": {
"nixpkgs": [
"agenix-rekey",
"nixpkgs"
]
},
"locked": {
"lastModified": 1735135567,
"narHash": "sha256-8T3K5amndEavxnludPyfj3Z1IkcFdRpR23q+T0BVeZE=",
"owner": "numtide",
"repo": "treefmt-nix",
"rev": "9e09d30a644c57257715902efbb3adc56c79cf28",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "treefmt-nix",
"type": "github"
}
}
},
"root": "root",

144
flake.nix
View File

@@ -8,6 +8,10 @@
inputs.darwin.follows = "";
inputs.nixpkgs.follows = "nixpkgs";
};
agenix-rekey = {
url = "github:oddlama/agenix-rekey";
inputs.nixpkgs.follows = "nixpkgs";
};
lix = {
url = "git+https://git.lix.systems/lix-project/lix?ref=main";
inputs.nixpkgs.follows = "nixpkgs";
@@ -28,7 +32,7 @@
};
};
outputs = { self, nixpkgs, agenix, lix
outputs = { self, nixpkgs, agenix, agenix-rekey, lix
, nixpkgs-uconsole, nixos-uconsole, nixos-raspberrypi
, home-manager
, ... }@inputs:
@@ -42,7 +46,7 @@
"/etc/ssh/ssh_host_ed25519_key"
"/root/.age/bootstrap.key" ];
};
overlays = [ agenix.overlays.default ];
overlays = [ agenix.overlays.default (import ./overlays/reticulum.nix) ];
pkgs = import nixpkgs {
inherit system overlays;
config.allowUnfree = true;
@@ -52,7 +56,108 @@
inherit pkgs system agenix;
};
# Cross-compile overlay fixes for Hyprland and deps on aarch64
##############################################################################
# CROSS-COMPILE WORKAROUNDS — packages that fail aarch64 cross-compile
#
# These packages need NATIVE COMPILATION on the uConsole itself (aarch64).
# They cannot cross-compile from x86_64 for various reasons listed below.
# We work around them in the overlay until we set up distributed builds
# with the uConsole as a native aarch64 builder.
#
# ==== Cross-compile failures ====
#
# libcamera / libcamera-rpi / libpisp:
# meta.platforms excludes aarch64. pipewire hard-depends on them in nixos-25.11.
# Fix: empty meta.platforms + strip from pipewire buildInputs.
#
# gjs:
# Need native display (GTK3/4 tests) for cross-compile configure.
# Fix: meson -Dskip_gtk_tests=true.
#
# hyprland:
# Qt6Quick missing from aarch64 qtdeclarative, breaks hyprland-qt-support.
# Fix: wrapRuntimeDeps=false (Qt UI components disabled, WM still works).
#
# boost.mpi:
# Boost.Build has no b2 architecture alternatives for ARM.
# Fix: useMpi=false.
#
# perl-ldap (perlPackages.perlldap):
# Module::Install requires Perl dynamic loading (Fcntl) which is
# unavailable in cross-compiled Perl.
# Fix: stripped from john.s propagatedBuildInputs.
#
# john (John the Ripper):
# Indirectly affected — depends on perl-ldap for perl utility scripts.
# Fix: perl-ldap stripped from propagatedBuildInputs (john still works,
# just loses sha-dump.pl etc. LDAP support).
#
# gss (GNU Generic Security Service):
# autogen.sh fails cross-compile. Pulled by mailutils → emacs-pgtk.
# Fix: emacs withMailutils=false.
#
# emacs-pgtk:
# Indirectly affected — depends on mailutils which depends on gss.
# Fix: withMailutils=false (no mail/IMAP within emacs).
#
# qtquick3d (Qt6):
# Qt::Quick not available in aarch64 cross-compile qtdeclarative.
# cmake skips build, ninja has no install target.
# Fix: removed js8call, switched wireshark → wireshark-cli.
#
# js8call:
# REMOVED from system packages. Depends on Qt6 multimedia → qtquick3d.
#
# wireshark-qt:
# SWITCHED to wireshark-cli. Same Qt6 multimedia → qtquick3d chain.
#
# neovim:
# `libnlua0.so` built for aarch64, luajit (x86_64) tries to load it
# during codegen (preload_nlua.lua). No clean override option.
# Fix: remove from system packages + install via native build
# once uConsole is set up as remote builder.
#
# clamav:
# cmake try_run() + Rust proc-macro can't find native linker in
# cross-compile (cc crate uses cross CC, no cc in PATH for build
# scripts). Chain: clamav → system-path → etc → dbus → polkit.
# Fix: remove from system packages; clamscan available from server.
#
# ==== Remote builder setup (bidirectional) — TODO ====
# To eliminate cross-compile exceptions, set up distributed builds:
# 1. Create a dedicated `builder` user on both hosts (no shell, home=/var/empty)
# 2. Add the same SSH key to both hosts (symmetric)
# 3. On lazyworkhorse — `nix.buildMachines` pointing to uConsole for aarch64-linux
# 4. On uConsole — `nix.buildMachines` pointing to lazyworkhorse for x86_64-linux
# 5. Remove the uconsoleCrossOverlay workarounds above
# 6. Nix auto-dispatches derivations by `system` — no per-package exceptions needed
# Example buildMachines config:
# Server dispatches aarch64 builds to uConsole (4 cores, less power):
# nix.buildMachines = [{
# hostName = "uConsole.local";
# systems = ["aarch64-linux"];
# maxJobs = 4;
# sshUser = "builder";
# sshKey = "/etc/ssh/builder_key";
# }];
# uConsole dispatches x86_64 builds to server (36 cores, 256GB RAM):
# nix.buildMachines = [{
# hostName = "lazyworkhorse.net";
# port = 2424;
# systems = ["x86_64-linux"];
# maxJobs = 36;
# sshUser = "builder";
# sshKey = "/etc/ssh/builder_key";
# }];
# ==== How to build natively on uConsole ====
# To native-compile these on the uConsole:
# 1. Add uConsole as a remote builder (nix.buildMachines)
# 2. Set nix.extra-platforms = [ "aarch64-linux" ] on server
# 3. Remove the overlay workarounds below
# 4. Packages will auto-dispatch to uConsole for native builds
##############################################################################
uconsoleCrossOverlay = final: prev: {
libcamera = prev.libcamera.overrideAttrs (_: { meta.platforms = []; });
libcamera-rpi = prev.libcamera-rpi.overrideAttrs (_: { meta.platforms = []; });
@@ -69,15 +174,25 @@
mesonFlags = (old.mesonFlags or []) ++ [ "-Dskip_gtk_tests=true" ];
});
hyprland = prev.hyprland.override { wrapRuntimeDeps = false; };
boost = prev.boost.override { useMpi = false; };
# perl-ldap cannot cross-compile (Module::Install needs dynamic loading)
xdg-desktop-portal-hyprland = prev.xdg-desktop-portal-hyprland.overrideAttrs (old: {
preConfigure = (old.preConfigure or "") + ''
cmakeFlags="$cmakeFlags -Dhyprwayland-scanner_DIR=${prev.buildPackages.hyprwayland-scanner}/lib/cmake/hyprwayland-scanner" 2>/dev/null || true
export PKG_CONFIG_PATH="${prev.buildPackages.hyprwayland-scanner}/lib/pkgconfig:$PKG_CONFIG_PATH"
'';
});
emacs-pgtk = prev.emacs-pgtk.override { withMailutils = false; };
# perl-ldap fails cross-compile (Module::Install needs dynamic loading)
# Strip it from john deps -- the perl scripts that need it are not critical
john = prev.john.overrideAttrs (old: {
propagatedBuildInputs = builtins.filter
(x: x?pname && x.pname != "perl-ldap")
(old.propagatedBuildInputs or []);
});
# clamav: removed from system packages (see note above).
};
# RPI-specific pipewire libcamera fix (separate nixpkgs instance)
uconsoleRpiPipewireOverlay = final: prev: {
pipewire = prev.pipewire.overrideAttrs (old: {
buildInputs = builtins.filter
@@ -89,14 +204,13 @@
});
};
# Shared uConsole CM5 module set — used by both toplevel and SD image
uconsoleBaseModules = [
{
nixpkgs.buildPlatform = "x86_64-linux";
nixpkgs.hostPlatform = "aarch64-linux";
nixpkgs.config.allowUnfree = true;
boot.loader.raspberry-pi.bootloader = "kernel";
nixpkgs.overlays = [ uconsoleCrossOverlay ];
nixpkgs.overlays = [ uconsoleCrossOverlay (import ./overlays/reticulum.nix) ];
}
nixos-raspberrypi.nixosModules.nixpkgs-rpi
({ config, lib, pkgs, ... }: {
@@ -107,7 +221,6 @@
nixos-raspberrypi.lib.inject-overlays-global
nixos-uconsole.nixosModules.uconsole-cm5
./modules/nixos/hardware/uconsole-cm5-aio-v2.nix
# Cross-compiled Lix for uConsole
({ config, lib, pkgs, inputs, ... }: let
lixCross = import inputs.nixpkgs-uconsole {
localSystem = { system = "x86_64-linux"; };
@@ -117,9 +230,12 @@
in { nix.package = lixCross.lix; })
inputs.home-manager.nixosModules.home-manager
agenix.nixosModules.default
agenix-rekey.nixosModules.default
./hosts/uconsole-cm5/configuration.nix
./hosts/uconsole-cm5/hardware-configuration.nix
./modules/nixos/services/remote-builder.nix
./modules/nixos/services/wireguard-client.nix
./modules/nixos/services/clamav.nix
./modules/nixos/security/ai-worker-restricted.nix
./users/gortium/gortium.nix
./users/ai-worker/ai-worker.nix
@@ -136,14 +252,16 @@
nixpkgs.config.permittedInsecurePackages = [ "openclaw-2026.3.12" ];
nix.package = lix.packages.${system}.default;
}
inputs.home-manager.nixosModules.home-manager
inputs.home-manager.nixosModules.home-manager
agenix.nixosModules.default
./hosts/lazyworkhorse/configuration.nix
./hosts/lazyworkhorse/hardware-configuration.nix
./modules/nixos/filesystem/hoardingcow-mount.nix
./modules/nixos/services/docker_manager.nix
./modules/nixos/services/wireguard-client.nix
./modules/nixos/filesystem/poup-16t-disk.nix
./modules/nixos/services/ollama_init_custom_models.nix
./modules/nixos/services/open_code_server.nix
./modules/nixos/services/clamav.nix
./modules/nixos/security/ai-worker-restricted.nix
./users/gortium/gortium.nix
./users/ai-worker/ai-worker.nix
@@ -161,7 +279,8 @@
}
./hosts/cyt-pi/configuration.nix
./hosts/cyt-pi/hardware-configuration.nix
./modules/nixos/services/wireguard-client.nix
./modules/nixos/services/remote-builder.nix
./modules/nixos/services/wireguard-client.nix
./users/gortium/gortium.nix
];
};
@@ -177,6 +296,11 @@
};
};
agenix-rekey = agenix-rekey.configure {
userFlake = self;
nixosConfigurations = self.nixosConfigurations;
};
devShells.${system}.default = devShell;
packages.${system} = {

View File

@@ -166,9 +166,9 @@
settings = {
PasswordAuthentication = false;
KbdInteractiveAuthentication = false;
# Additional hardening settings below in SERVER HARDENING section
};
hostKeys = [
# ============================================================
# ClamAV antivirus — daemon, hourly updates, daily scan, on-access
# ============================================================
{
path = "/etc/ssh/ssh_host_ed25519_key";
type = "ed25519";
@@ -337,6 +337,16 @@
# networking.firewall.enable = false;
# =============================================================================
# ============================================================
# ClamAV antivirus — daemon, hourly updates, daily scan, on-access
# ============================================================
gortium.clamav = {
enable = true;
enableDaemon = true;
onAccessScanning = true;
dailyScanTime = "03:00";
};
# SERVER HARDENING - Firewall, Fail2ban, SSH, Kernel
# =============================================================================

View File

@@ -6,6 +6,9 @@
i18n.defaultLocale = "en_CA.UTF-8";
system.stateVersion = "25.11";
# Boot & Hardware
boot.loader.raspberry-pi.bootloader = "kernel";
# SSH — root access avec clés gortium + ai-worker
services.openssh = {
enable = true;
@@ -20,16 +23,12 @@
users.ai-worker.main
];
# AI worker user (Hermes SSH access)
# Age secret for gortium password (file created by user)
age.secrets.gortium_password = {
file = ../../secrets/gortium_password.age;
};
# Password file for gortium (merges with users/gortium/default.nix)
# WiFi via NetworkManager + secret agenix
# WiFi via NetworkManager
networking.networkmanager.enable = true;
# Firmware
@@ -40,19 +39,169 @@
enable = true;
xwayland.enable = true;
};
# HackerGadgets AIO v2 board
hardware.uconsole-cm5-aio-v2 = {
enable = true;
# Rails actifs au boot
bootRails = {
GPS = false;
LORA = false;
SDR = false;
USB = false;
};
enableGPS = false;
};
# User
users.users.gortium = {
isNormalUser = true;
extraGroups = [ "wheel" "networkmanager" "video" "dialout" "kismet" ];
hashedPasswordFile = config.age.secrets.gortium_password.path;
openssh.authorizedKeys.keys = [
keys.users.gortium.main
keys.users.gortium.gitea
];
};
security.sudo.extraRules = [
{
users = [ "gortium" ];
commands = [{
command = "ALL";
options = [ "NOPASSWD" ];
}];
}
];
# ============================================================
# Package groups
# ============================================================
environment.systemPackages = with pkgs; [
# ===== Base =====
emacs-pgtk
git
ripgrep
fd
htop
tmux
# ===== HAM Radio =====
wsjtx
fldigi
pat # Winlink client
direwolf # AX.25 packet modem
chirp # Radio programming tool
hamlib # Ham radio control libraries
trustedqsl # Logbook of the World (LoTW)
# ===== SDR / RF =====
sdrpp # SDR++ spectrum analyzer
gqrx # SDR receiver GUI
rtl-sdr # RTL-SDR drivers & utilities
inspectrum # Offline signal analysis
soapysdr-with-plugins # SoapySDR + hardware support plugins
# ===== Mesh / LoRa =====
reticulumStack # Reticulum Network Stack
lxmf # LXMF messaging protocol
nomadnet # Nomad Network client
# ===== Security =====
nmap
aircrack-ng
kismet # Wi-Fi monitor / IDS
bettercap # MITM/network attack framework
wireshark-cli # Packet analyzer
john # John the Ripper
sqlmap # SQL injection tool
# ===== GPS / Maps =====
foxtrotgps
viking # GPS map editor
gpsbabel # GPS data conversion
];
# ============================================================
# Reticulum Service (rnsd)
# ============================================================
systemd.services.rnsd = {
description = "Reticulum Network Stack Daemon";
wants = [ "network-online.target" ];
after = [ "network-online.target" ];
wantedBy = [ "multi-user.target" ];
serviceConfig = {
User = "gortium";
Group = "gortium";
ExecStart = "${pkgs.reticulumStack}/bin/rnsd";
Restart = "always";
RestartSec = "10s";
LimitNOFILE = 65536;
};
};
# ============================================================
# Kismet Service (Wi-Fi monitoring / mesh node)
# ============================================================
systemd.services.kismet = {
description = "Kismet Wi-Fi Monitor & IDS";
wants = [ "network-online.target" ];
after = [ "network-online.target" ];
wantedBy = [ "multi-user.target" ];
serviceConfig = {
User = "gortium";
Group = "kismet";
ExecStart = "${pkgs.kismet}/bin/kismet -c wlan0 --log-base=/home/gortium/kismet_logs --no-nc-ui";
Restart = "always";
RestartSec = "10s";
};
};
# ============================================================
# Kernel modules for SDR and radio
# ============================================================
boot.kernelModules = [
"88x2bu" # Realtek 8812/8821BU USB WiFi
"rtl8xxxu" # RTL8188/8192/8723 USB WiFi
"rtl2832_sdr" # RTL-SDR kernel module
"dvb_usb_rtl28xxu" # RTL-SDR DVB-T
];
# ============================================================
# Extra udev rules for SDR and HAM radio devices
# ============================================================
services.udev.packages = with pkgs; [ rtl-sdr ];
# ============================================================
# Enable IPv6 for Reticulum mesh
# ============================================================
networking.enableIPv6 = true;
# ============================================================
# Firewall
# ============================================================
networking.firewall.allowedTCPPorts = [ 22 ];
networking.firewall.allowedUDPPorts = [ ];
# ============================================================
# agenix-rekey — automatic secret re-encryption at deploy time
# ============================================================
age.rekey = {
# Master identities for encrypting secrets (on Thierry's laptop)
masterIdentities = [
"/home/gortium/.ssh/gortium_ssh_key"
];
# uConsole SSH host pubkey — for automatic rekey at build time
# Once uConsole is deployed, replace with actual pubkey from:
# ssh-keyscan uConsole.local | ssh-to-age
hostPubkey = "age1qyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqs3290gq"; # dummy — replace after bootstrap
};
# Enable remote builder (distributed build via lazyworkhorse server)
services.remoteBuilder.enable = true;
# ClamAV REMOVED — cross-compile failure (try_run + Rust linker)
# clamscan available from server when needed.
# gortium.clamav = {
# enable = true;
# enableDaemon = false;
# };
}

View File

@@ -0,0 +1,121 @@
{ config, lib, pkgs, ... }:
let
cfg = config.gortium.poup16t;
luksName = cfg.luksName;
in
with lib;
{
options.gortium.poup16t = {
enable = mkEnableOption "Poup_16T storage disk (btrfs + LUKS + btrbk snapshots)";
luksUuid = mkOption {
type = types.str;
description = ''
UUID of the LUKS partition on the 16TB disk (WD Red Pro).
Find this by running as root when the disk is connected:
blkid /dev/sdb # or wherever the disk appears
lsblk -o NAME,SIZE,FSTYPE,UUID
Since btrfs is inside LUKS, the FS UUID is hidden use the
LUKS partition UUID from blkid (it'll show TYPE=\"crypto_LUKS\").
'';
example = "00000000-0000-0000-0000-000000000000";
};
luksName = mkOption {
type = types.str;
default = "poup_16t";
description = "Name for the LUKS /dev/mapper/ mapping";
};
mountPoint = mkOption {
type = types.str;
default = "/mnt/Poup_16T";
description = "Mount point for the 16TB data disk";
};
btrfsOptions = mkOption {
type = types.listOf types.str;
default = [ "defaults" "noatime" "compress=zstd:3" "nofail" ];
description = "Mount options for the btrfs filesystem. 'nofail' ensures boot succeeds when disk is disconnected.";
};
btrbk = {
enable = mkOption {
type = types.bool;
default = true;
description = "Enable btrbk snapshot management on this volume";
};
schedule = mkOption {
type = types.str;
default = "daily";
description = "systemd calendar event for btrbk (e.g. 'daily', 'hourly', '*-*-* 00:00:00')";
};
preserveMin = mkOption {
type = types.str;
default = "2d";
description = "btrbk snapshot_preserve_min minimum age before pruning";
};
preserve = mkOption {
type = types.str;
default = "14d 4w 3m";
description = "btrbk snapshot_preserve retention policy (daily, weekly, monthly)";
};
snapshotDir = mkOption {
type = types.str;
default = ".snapshots";
description = "Directory name for snapshots relative to volume root";
};
};
};
config = mkIf cfg.enable {
# Enable btrfs kernel support (no DKMS needed — it's in-tree)
boot.supportedFilesystems = [ "btrfs" ];
# Install btrfs administration tools
environment.systemPackages = with pkgs; [
btrfs-progs # mkfs.btrfs, btrfs, fsck, balance, scrub
btrbk # Snapshot management + rotation
];
# LUKS2 unlock at boot (uses keyfile or prompts if unavailable)
# Since the disk may be disconnected, initrd times out gracefully (~30s)
boot.initrd.luks.devices.${luksName} = {
device = "/dev/disk/by-uuid/${cfg.luksUuid}";
preLVM = false;
allowDiscards = true;
};
# Mount the unlocked mapper device as btrfs
fileSystems.${cfg.mountPoint} = {
device = "/dev/mapper/${luksName}";
fsType = "btrfs";
options = cfg.btrfsOptions;
};
# btrbk — automated snapshot creation and rotation
services.btrbk = mkIf cfg.btrbk.enable {
instances.poup16t = {
onCalendar = cfg.btrbk.schedule;
settings = {
snapshot_preserve_min = cfg.btrbk.preserveMin;
snapshot_preserve = cfg.btrbk.preserve;
volume.${cfg.mountPoint} = {
snapshot_create = "always";
snapshot_dir = cfg.btrbk.snapshotDir;
subvolume = ".";
};
};
};
};
};
}

View File

@@ -0,0 +1,240 @@
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.gortium.clamav;
clamavPkg = pkgs.clamav;
clamdConfig = pkgs.writeText "clamd.conf" ''
LogFile /var/log/clamav/clamd.log
LogTime yes
LogVerbose yes
LogSyslog yes
LocalSocket /run/clamav/clamd.sock
TCPSocket 3310
TCPAddr 127.0.0.1
User clamav
AllowSupplementaryGroups yes
${cfg.clamdExtraConfig}
'';
freshclamConfig = pkgs.writeText "freshclam.conf" ''
DatabaseDirectory /var/lib/clamav
LogFile /var/log/clamav/freshclam.log
LogTime yes
LogVerbose yes
LogSyslog yes
User clamav
AllowSupplementaryGroups yes
${cfg.freshclamExtraConfig}
'';
# Daily scan — logging only, no auto-quarantine/delete
scanScript = pkgs.writeShellScript "clamav-daily-scan" ''
set -e
PATHS="${concatStringsSep " " cfg.scanPaths}"
if [ -z "$PATHS" ]; then
echo "No paths configured for daily scan"
exit 0
fi
echo "=== ClamAV daily scan started: $(date) ==="
${clamavPkg}/bin/clamdscan --fdpass --log=/var/log/clamav/daily-scan.log --no-summary $PATHS
echo "=== ClamAV daily scan finished: $(date) ==="
'';
in
{
##### Options #####
options.gortium.clamav = {
enable = mkEnableOption "ClamAV antivirus installs clamav CLI tools";
enableDaemon = mkOption {
type = types.bool;
default = true;
description = ''
Run clamd daemon + freshclam updater + daily scheduled scan.
Set to false on machines where you only want the CLI tools
(clamscan, clamdscan) for manual on-demand scanning.
'';
};
onAccessScanning = mkOption {
type = types.bool;
default = false;
description = ''
Enable on-access scanning via clamonacc (fanotify-based).
Resource-heavy; server use only. Requires enableDaemon = true.
'';
};
scanPaths = mkOption {
type = types.listOf types.str;
default = [
"/home"
"/nix/store"
"/var/lib"
"/etc"
"/tmp"
"/var/tmp"
];
description = "Paths for the daily scheduled scan.";
};
dailyScanTime = mkOption {
type = types.str;
default = "daily";
description = ''
When to run the daily scan. systemd calendar expression
or shortcuts like "daily", "weekly", "04:00".
'';
};
clamdExtraConfig = mkOption {
type = types.lines;
default = "";
description = "Extra lines appended to clamd.conf";
};
freshclamExtraConfig = mkOption {
type = types.lines;
default = "";
description = "Extra lines appended to freshclam.conf";
};
};
##### Implementation #####
config = mkIf cfg.enable {
# 1. Package — always installed when enable = true
environment.systemPackages = [ clamavPkg ];
# Everything below uses mkIf cfg.enableDaemon — conditionalized per attribute
# 2. Users/groups (only if daemon runs)
users.users.clamav = mkIf cfg.enableDaemon {
isSystemUser = true;
group = "clamav";
home = "/var/lib/clamav";
createHome = true;
description = "ClamAV daemon user";
};
users.groups.clamav = mkIf cfg.enableDaemon {};
# 3. Directories (only if daemon runs)
systemd.tmpfiles.rules = mkIf cfg.enableDaemon [
"d /var/lib/clamav 0750 clamav clamav -"
"d /var/log/clamav 0750 clamav clamav -"
"d /run/clamav 0755 clamav clamav -"
];
# 4. ClamAV daemon (clamd)
systemd.services.clamav-daemon = mkIf cfg.enableDaemon {
description = "ClamAV Anti-Virus Daemon";
after = [ "network.target" ];
wantedBy = [ "multi-user.target" ];
path = [ clamavPkg ];
preStart = ''
mkdir -p /var/lib/clamav /var/log/clamav /run/clamav
chown clamav:clamav /var/lib/clamav /var/log/clamav /run/clamav
'';
serviceConfig = {
Type = "simple";
ExecStart = "${clamavPkg}/bin/clamd --config-file=${clamdConfig}";
ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
Restart = "on-failure";
RestartSec = "10s";
User = "clamav";
Group = "clamav";
RuntimeDirectory = "clamav";
RuntimeDirectoryMode = "0755";
StateDirectory = "clamav";
StateDirectoryMode = "0750";
LogsDirectory = "clamav";
LogsDirectoryMode = "0750";
PrivateTmp = true;
ProtectSystem = "strict";
ProtectHome = true;
ReadWritePaths = [
"/var/lib/clamav"
"/var/log/clamav"
"/run/clamav"
];
NoNewPrivileges = true;
};
};
# 5. freshclam (database updater) — hourly via timer
systemd.services.clamav-freshclam = mkIf cfg.enableDaemon {
description = "ClamAV Virus Database Updater";
after = [ "network.target" ];
wantedBy = [ "multi-user.target" ];
path = [ clamavPkg pkgs.curl ];
serviceConfig = {
Type = "oneshot";
ExecStart = "${clamavPkg}/bin/freshclam --config-file=${freshclamConfig} --daemon-notify=${clamdConfig}";
User = "clamav";
Group = "clamav";
PrivateTmp = true;
ProtectSystem = "full";
NoNewPrivileges = true;
};
};
systemd.timers.clamav-freshclam = mkIf cfg.enableDaemon {
description = "ClamAV database update timer";
wantedBy = [ "timers.target" ];
timerConfig = {
OnCalendar = "hourly";
Persistent = true;
RandomizedDelaySec = "1800";
};
};
# 6. Daily scan — logging only, no auto-quarantine
systemd.services.clamav-daily-scan = mkIf cfg.enableDaemon {
description = "ClamAV Daily Scheduled Scan";
after = [ "clamav-daemon.service" ];
requires = [ "clamav-daemon.service" ];
path = [ clamavPkg ];
serviceConfig = {
Type = "oneshot";
ExecStart = "${scanScript}";
User = "clamav";
Group = "clamav";
PrivateTmp = true;
ProtectSystem = "strict";
ReadWritePaths = [ "/var/log/clamav" ];
};
};
systemd.timers.clamav-daily-scan = mkIf cfg.enableDaemon {
description = "ClamAV daily scan timer";
wantedBy = [ "timers.target" ];
timerConfig = {
OnCalendar = cfg.dailyScanTime;
Persistent = true;
};
};
# 7. On-access scanning (clamonacc) — needs enableDaemon
systemd.services.clamav-onaccess = mkIf (cfg.enableDaemon && cfg.onAccessScanning) {
description = "ClamAV On-Access Scanner (clamonacc)";
after = [ "clamav-daemon.service" ];
requires = [ "clamav-daemon.service" ];
wantedBy = [ "multi-user.target" ];
path = [ clamavPkg ];
serviceConfig = {
Type = "simple";
ExecStart = "${clamavPkg}/bin/clamonacc --config-file=${clamdConfig} --fdpass --log=/var/log/clamav/clamonacc.log";
Restart = "on-failure";
RestartSec = "10s";
User = "root"; # clamonacc needs root for fanotify
Group = "root";
PrivateTmp = true;
NoNewPrivileges = true;
};
};
};
}

View File

@@ -0,0 +1,71 @@
{ config, lib, pkgs, ... }:
let
cfg = config.services.remoteBuilder;
in {
options.services.remoteBuilder = {
enable = lib.mkEnableOption "remote Nix build machine (lazyworkhorse server)";
buildMachine = {
host = lib.mkOption {
type = lib.types.str;
default = "lazyworkhorse.net";
description = "Hostname or IP of the remote build machine.";
};
sshUser = lib.mkOption {
type = lib.types.str;
default = "ai-worker";
description = "SSH user on the remote build machine.";
};
port = lib.mkOption {
type = lib.types.port;
default = 2424;
description = "SSH port added via ~root/.ssh/config since nix.buildMachines has no sshPort option.";
};
systems = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ "aarch64-linux" "x86_64-linux" ];
description = "System types the remote builder can build for.";
};
maxJobs = lib.mkOption {
type = lib.types.int;
default = 16;
description = "Max parallel jobs on the remote builder.";
};
supportedFeatures = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ "big-parallel" "nixos-test" "benchmark" ];
description = "Features the remote builder supports.";
};
};
fallbackLocal = lib.mkOption {
type = lib.types.bool;
default = true;
description = "Fall back to local build when remote builder is unreachable.";
};
};
config = lib.mkIf cfg.enable {
nix.distributedBuilds = true;
nix.buildMachines = [{
hostName = cfg.buildMachine.host;
sshUser = cfg.buildMachine.sshUser;
systems = cfg.buildMachine.systems;
maxJobs = cfg.buildMachine.maxJobs;
supportedFeatures = cfg.buildMachine.supportedFeatures;
}];
nix.extraOptions = lib.optionalString cfg.fallbackLocal ''
builders-use-substitutes = true
fallback = true
'';
# SSH config for the remote builder (since nix.buildMachines has no port option)
programs.ssh.extraConfig = ''
Host ${cfg.buildMachine.host}
HostName ${cfg.buildMachine.host}
Port ${toString cfg.buildMachine.port}
User ${cfg.buildMachine.sshUser}
'';
};
}

81
overlays/reticulum.nix Normal file
View File

@@ -0,0 +1,81 @@
final: prev: let
python3 = final.python3;
pyPkgs = python3.pkgs;
in {
reticulumStack = python3.pkgs.buildPythonApplication rec {
pname = "reticulum";
version = "1.2.9";
format = "setuptools";
src = pyPkgs.fetchPypi {
pname = "rns";
inherit version;
sha256 = "554814231c237b9caacf8df669312e57dd7d3f84b6d4810125087d1a79a75d75";
};
propagatedBuildInputs = with pyPkgs; [ cryptography pyserial ];
doCheck = false;
pythonImportsCheck = [ "RNS" ];
meta = with final.lib; {
description = "Self-configuring, encrypted and resilient mesh networking stack";
homepage = "https://reticulum.network/";
license = licenses.mit;
platforms = platforms.linux;
};
};
lxmf = python3.pkgs.buildPythonApplication rec {
pname = "lxmf";
version = "0.9.8";
format = "setuptools";
src = pyPkgs.fetchPypi {
inherit pname version;
sha256 = "30f39f3a975a049c12ee2cfceb3261d24cb5adec881c6821f7354464b3f3650c";
};
propagatedBuildInputs = [ final.reticulumStack ];
doCheck = false;
pythonImportsCheck = [ "LXMF" ];
meta = with final.lib; {
description = "Lightweight Extensible Message Format for Reticulum";
homepage = "https://github.com/markqvist/lxmf";
license = licenses.mit;
platforms = platforms.linux;
};
};
nomadnet = python3.pkgs.buildPythonApplication rec {
pname = "nomadnet";
version = "1.1.1";
format = "setuptools";
src = pyPkgs.fetchPypi {
inherit pname version;
sha256 = "fa13b64a10e75b705a58024815ab72451700aa726af96d415ba99dec28dfc40a";
};
propagatedBuildInputs = with pyPkgs; [ final.reticulumStack final.lxmf urwid qrcode ];
doCheck = false;
pythonImportsCheck = [ "nomadnet" ];
meta = with final.lib; {
description = "Nomad Network resilient mesh communications platform";
homepage = "https://github.com/markqvist/NomadNet";
license = licenses.mit;
platforms = platforms.linux;
};
};
rnsh = python3.pkgs.buildPythonApplication rec {
pname = "rnsh";
version = "0.1.7";
format = "setuptools";
src = pyPkgs.fetchPypi {
inherit pname version;
sha256 = "9cb72f25abb1c6d300f8014b264184ff78f592fe88e36094938012990b797c93";
};
propagatedBuildInputs = [ final.reticulumStack ];
doCheck = false;
pythonImportsCheck = [ "rnsh" ];
meta = with final.lib; {
description = "Remote shell over Reticulum";
homepage = "https://github.com/acehoss/rnsh";
license = licenses.mit;
platforms = platforms.linux;
};
};
}

1
result
View File

@@ -1 +0,0 @@
/nix/store/z86r4awsbrc5q9qhwwi757wxixcqgn31-nixos-system-uConsole-25.11.20260608.e820eb4