Compare commits

..

131 Commits

Author SHA1 Message Date
0f0c7824bc fix: update compose submodule for backup OS compat
Points compose to fix/backup-compose-os-upgrade which:
- restic/restic -> restic/rest-server
- embergarage/restic-browser -> floibach/restic-browser
- adds persistent browser-data volume
- adds backup/.env with RESTIC_PASSWORD
2026-07-05 21:35:25 -04:00
f8502d0114 fix: update compose submodule to fix hermes user ownership 2026-07-04 23:50:48 -04:00
65543e3ce8 Re-encrypt secrets for actual SSH host keys (bypass rekey until master key on server) 2026-06-22 00:08:17 -04:00
5de646d018 Fix rekey block placement (was outside module braces) 2026-06-22 00:06:35 -04:00
24761b58b2 Add agenix-rekey module + rekey config back to lazyworkhorse
agenix-rekey auto-re-encrypts secrets per host at build time:
- Encrypted once in git with master identity
- Re-encrypted for each host using their SSH host key
- No manual multi-recipient encryption needed
2026-06-22 00:05:22 -04:00
9bf38f1077 Remove age.rekey from lazyworkhorse (module not available, host key handles decryption directly) 2026-06-22 00:02:53 -04:00
cbb016d409 Fix agenix-rekey: add real uConsole host key, re-encrypt secrets
- Update uConsole hostPubkey from dummy to real SSH host key
- Add uConsole host key to keys.nix
- Add age.rekey config to lazyworkhorse
- Add SSH host key to identityPaths on both hosts
- Re-encrypt builder_key.age and gortium_password.age for both hosts
2026-06-22 00:02:22 -04:00
7e7bb73b0d fix: restore uConsole display rotation (transform 3) in dotfiles
The rotation was lost in the dotfiles repo PR #16 (rename host/ -> hosts/)
which was based on a branch predating the transform commits.
Fixed directly in dotfiles submodule.
2026-06-21 23:55:57 -04:00
ea5eac2234 Comment foxtrotgps, viking, gpsbabel for cross-compile bootstrap 2026-06-21 23:37:42 -04:00
c9072e86da Temporarily comment native-only packages for cross-compile bootstrap
Comment out wsjtx, fldigi, sdrpp, gqrx, inspectrum, chirp, trustedqsl
- these need native compilation on uConsole but remote builder
  is not yet deployed. Will uncomment after bootstrap.
2026-06-21 23:36:09 -04:00
3a0fb70d26 Fix: remove ca-derivations (not supported in Lix 2.96) 2026-06-21 23:28:05 -04:00
a0e0b6e78e Update compose submodule to master (adds vpn stack) 2026-06-21 23:26:55 -04:00
e03f09c247 Merge feat/remote-builders into uconsole-cm5-incremental
Bidirectional remote building + rename uconsole-cm5 -> uConsole:
- Rename all uconsole-cm5 refs to uConsole (nixosSystem key, paths, module)
- Replace single-machine remote-builder module with multi-machine config
- Enable remote builder on lazyworkhorse -> uConsole (aarch64)
- Enable remote builder on uConsole -> lazyworkhorse (x86_64)
- Add builder system user + SSH key via agenix-rekey (secrets/builder_key.age)
- Add builder_key + gortium_password age secrets to all hosts
- Fix pre-existing bugs: missing modules in lazyworkhorse/cyt-pi,
  SSH config structure (hostKeys outside settings), cyt-pi inline user
- Remove obsolete openclaw-node service + module + age secrets
2026-06-21 23:14:14 -04:00
db2d540b3c feat: server-side remote builder config (lazyworkhorse dispatches aarch64 to uConsole)
Server only — uConsole-side config will be added in uconsole-cm5-incremental branch.
Includes reusable module, builder system user, and SSH key placeholder.
2026-06-21 16:22:50 -04:00
3778069baf fix: use configureFlags to set PYTHON path for john cross-compile 2026-06-21 15:18:14 -04:00
8ec9ffe6a9 fix: clean john overlay with python3 + preConfigure symlink for cross-compile 2026-06-21 15:15:01 -04:00
16ec40b237 fix: use python3 + preConfigure symlink for john cross-compile (python not in PATH) 2026-06-21 15:12:12 -04:00
3ec8bab25b fix: add python to john nativeBuildInputs for cross-compile (opencl_generate_dynamic_loader.py) 2026-06-21 15:09:53 -04:00
2a4856e447 fix: add python3 to aiov2_ctl buildInputs so patchShebangs fixes the shebang 2026-06-21 15:07:49 -04:00
01f8166529 feat: add uConsole as remote builder for aarch64-linux native builds 2026-06-20 20:04:04 -04:00
84ce9de5ca restore native-build packages in config (wsjtx, fldigi, chirp, sdrpp, gqrx, inspectrum, john, foxtrotgps, viking, gpsbabel, trustedqsl) 2026-06-20 20:02:56 -04:00
14755f1ea6 revert native-build packages — cross-compile still fails for Qt5/gpsbabel/john, install via nix profile instead 2026-06-20 20:01:36 -04:00
f51155823e temp: comment out Qt5 packages (wsjtx, fldigi, sdrpp, gqrx, inspectrum) — cross-compile fails, installed via nix profile instead 2026-06-20 19:59:36 -04:00
73c12abec2 feat: re-integrate native-build packages (wsjtx, fldigi, chirp, sdrpp, gqrx, inspectrum, hashcat, john, foxtrotgps, viking, gpsbabel, trustedqsl) 2026-06-20 19:00:39 -04:00
a97c68fd81 fix: add rtkit and pipewire service for uConsole audio 2026-06-20 18:16:43 -04:00
bf2500dce8 fix: add missing packages (swww, emacs, udiskie, hyprshade) and wallpapers for uConsole 2026-06-20 18:16:00 -04:00
f0fa8ac942 fix: update dotfiles for transform 3 2026-06-20 17:54:16 -04:00
7899bf28f2 fix: sync dotfiles submodule, home.nix paths to hosts/ 2026-06-20 17:51:50 -04:00
282b4bc229 chore: remove stale temp file 2026-06-20 17:50:18 -04:00
905998466c fix: update home.nix host/ paths after dotfiles rename 2026-06-20 17:50:13 -04:00
6c34b92186 fix: update dotfiles submodule for host/ rename 2026-06-20 17:48:15 -04:00
7b3a5802a5 fix: update dotfiles submodule 2026-06-20 17:35:01 -04:00
1ab11b76d6 chore: update dotfiles submodule for per-host 2026-06-20 17:01:59 -04:00
3ce550691c fix: use zsh.initExtra for dotfiles config (no home.file conflict) 2026-06-20 16:25:50 -04:00
256979e6e5 fix: update dotfiles submodule for uConsole keyboard docs 2026-06-20 16:25:21 -04:00
095fa4c200 fix: add .zshrc to home-manager dotfiles 2026-06-20 16:25:06 -04:00
c430427617 fix: update dotfiles submodule for uConsole transform+keyboard 2026-06-20 16:22:20 -04:00
eeb10db5ed fix: define dotfiles as local path in home.nix (via submodule), fix swaync name 2026-06-20 16:03:58 -04:00
60a0bcfdd6 fix: dotfiles path via self (submodule), fix Nix syntax 2026-06-20 16:02:34 -04:00
7827638c5a fix: dotfiles path from submodule, not flake input 2026-06-20 16:01:37 -04:00
ceca908457 fix: replace dotfiles flake input with self.submodules=true (git submodule) 2026-06-20 16:01:00 -04:00
f443e79c17 fix: dotfiles as flake input (not submodule path) so Nix gets actual files 2026-06-20 15:29:08 -04:00
d765ead020 fix: kismet --log-base -> --log-prefix (wrong flag), aiov2 pinctrl from raspberrypi-utils not libraspberrypi 2026-06-20 15:07:00 -04:00
da3363e894 fix: remove chirp for aarch64 cross-compile (wxPython fails with GTK3) 2026-06-20 14:40:37 -04:00
bfb521f684 chore: remove accidentally committed .bak files 2026-06-20 12:42:03 -04:00
e38908de74 fix: remove john for aarch64 cross-compile (configure needs python) 2026-06-20 12:41:50 -04:00
1b70f95038 fix: remove gpsbabel for aarch64 cross-compile (qmake can't find g++) 2026-06-20 11:56:31 -04:00
9f62dac106 uConsole: remove wsjtx + fldigi (qtbase/Qt5 linker fails) — add to removal comment 2026-06-19 22:38:08 -04:00
a8215ae441 uConsole: remove accidental .bak file from tracking 2026-06-19 22:22:16 -04:00
eeb345b7e0 uConsole: add neovim to cross-compile removal tracking comment 2026-06-19 22:22:13 -04:00
95833f4d28 uConsole: fix missing closing brace (sed cleanup mishap) 2026-06-19 22:14:46 -04:00
93dc4f1cac uConsole: consolidate removal tracking into single block 2026-06-19 22:13:18 -04:00
12af6bb643 uConsole: remove failing cross-compile packages (round 3)
Removed for aarch64 bootstrap:
  sdrpp     — glfw/wxPython cross-compile fails
  gqrx      — Qt5 cascade fails
  emacs-pgtk/nox — GTK3 + mailutils → gss → shishi chain
  viking    — GTK3 GPS
  foxtrotgps — GTK2 GPS

Leave remaining as native install after first switch.
Add consolidated removal tracking comment.
2026-06-19 22:11:39 -04:00
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
ecbf226b01 temp: remove nh to skip Haskell cross-compile 2026-06-17 20:24:22 -04:00
09add9f5e4 fix: remove duplicate extraSpecialArgs in gortium.nix 2026-06-17 08:30:01 -04:00
b4b928a985 fix: clean module and flake after merge 2026-06-17 08:29:24 -04:00
33e98f32d7 feat: add HackerGadgets AIO v2 board module + enable on uConsole CM5 2026-06-17 08:27:15 -04:00
332f1cca1a chore: update nixos-uconsole flake.lock to latest pr/dcs-panel-detection 2026-06-17 08:27:01 -04:00
102586d7e8 fix: switch nixos-uconsole to cm5_fix branch (patches OK)
- Remove local boot.kernelPatches (now in nixos-uconsole fork)
- Point to github:gortium/nixos-uconsole/cm5_fix instead of pr/dcs-panel-detection
2026-06-16 19:21:54 -04:00
43f8d8a61c fix: correct aiov2_ctl hash from actual build 2026-06-16 19:12:37 -04:00
6aca5466b6 fix: convert hash to proper SRI base64 format 2026-06-16 19:11:43 -04:00
f0ec375875 fix: set real hash for aiov2_ctl fetchFromGitHub 2026-06-16 19:11:00 -04:00
e05ef66b8f fix: correct secrets path in configuration.nix (../../secrets from hosts/uconsole-cm5/) 2026-06-16 19:09:24 -04:00
a2096efc3f fix: correct dotfiles path in home.nix (relative to repo root) 2026-06-16 19:08:49 -04:00
088a82d730 fix: pass hostName as extraSpecialArgs to home-manager
home.nix used config.networking.hostName but home-manager modules
don't have access to NixOS config. Fix by passing via extraSpecialArgs.
2026-06-16 19:08:15 -04:00
b9e89ce537 fix: use libraspberrypi instead of raspberrypi-tools for pinctrl 2026-06-16 19:07:34 -04:00
1d50b6455d fix: zsh conflicts for gortium home-manager on uConsole
- Remove duplicate .zshrc from home.file (managed by programs.zsh)
- Enable programs.zsh system-wide for gortium user
2026-06-16 19:06:44 -04:00
bcf924408b fix: remove programs.gitsign (not available in nixpkgs 25.11) 2026-06-16 19:06:11 -04:00
820de72c0f fix: remove duplicate ai-worker user definition in configuration.nix
ai-worker is now defined in users/ai-worker/ai-worker.nix module
2026-06-16 19:05:03 -04:00
6c08958730 fix: add ai-worker-restricted module to uConsole CM5 base modules
Required for services.aiWorkerAccess option used by users/ai-worker/ai-worker.nix
2026-06-16 19:04:35 -04:00
3f331e4bfb fix: add home-manager input for uConsole CM5 gortium user config
The remote branch added users/gortium/gortium.nix which uses
home-manager module option, but home-manager wasn't imported.
2026-06-16 19:03:59 -04:00
1550219e77 Merge remote changes + feat: AIO v2 board module
- Cross-compile overlays for Hyprland (libcamera, pipewire, gjs)
- Refactor uconsoleBaseModules into reusable list
- Add wireguard-client service module
- Restructure users into subdirectories
- New: hardware.uconsole-cm5-aio-v2 module (GPIO rails, aiov2_ctl, GPS UART)
- Update configuration.nix with Hyprland + AIO v2
- Add AIO v2 module to both toplevel and SD image config
2026-06-16 19:02:38 -04:00
2572f47e41 feat: add NixOS module for HackerGadgets AIO v2 board (uConsole CM5)
- New module: hardware.uconsole-cm5-aio-v2
  - GPIO rail control for GPS (27), LORA (16), SDR (7), USB (23)
  - Systemd oneshot service (aiov2-rails-boot) to apply states at boot
  - aiov2_ctl CLI tool packaged from GitHub source
  - GPS UART support (ttyAMA0, 9600 baud) with dialout group
  - Optional systemd user service for system tray GUI
- Wired into uconsole-cm5 NixOS config + SD image

All rails default OFF — activate on demand with:
  aiov2_ctl <GPS|LORA|SDR|USB> on
2026-06-16 19:00:50 -04:00
bd8b1c564e feat: add reusable wireguard-client NixOS module
- modules/nixos/services/wireguard-client.nix — optional module under
  gortium.wireguard-client namespace with enable, vpnIp, privateKeyFile,
  and presharedKeyFile options
- Added to lazyworkhorse, cyt-pi, and uconsoleBaseModules (covers both
  uconsole-cm5 toplevel and SD image)
- Migrated lazyworkhorse from inline networking.wireguard to module
- Split-tunnel: allowedIPs = [ "10.8.0.0/24" ]

Usage in a host config:
  gortium.wireguard-client = {
    enable = true;
    vpnIp = "10.8.0.X/24";
    privateKeyFile = config.age.secrets.wireguard_private_key.path;
    presharedKeyFile = config.age.secrets.wireguard_preshared_key.path;
  };
2026-06-15 10:55:40 -04:00
bd283de350 fix: place passwordFile at correct attrset level in gortium.nix 2026-06-14 21:58:57 -04:00
a6d88f2d41 Moved user ai-worker 2026-06-14 21:57:44 -04:00
6399196a2c fix: move gortium passwordFile to shared user module (applies to all hosts) 2026-06-14 21:55:48 -04:00
8651295b0a Fixed stuff maybe i guess not sure 2026-06-14 21:48:23 -04:00
e991359584 add gortium password age secret 2026-06-14 21:34:42 -04:00
fba52fa66d fix: use passwordFile instead of hashedPasswordFile (matches other secrets: plain text) 2026-06-14 21:09:10 -04:00
e95baddb96 rename users/gortium/default.nix -> gortium.nix, add to uconsole modules 2026-06-14 21:05:22 -04:00
eb3fe42542 refactor: extract shared uconsole modules to eliminate toplevel/image duplication 2026-06-14 20:56:17 -04:00
67aafe37db fix: add DSI_INIT0 lane config to old panel init seq + fix mode_flags 2026-06-13 18:59:29 -04:00
cb2b535fde fix: correct patch format — blank line between hunks 2026-06-13 18:51:39 -04:00
2c9136d1dc fix: add DSI_INIT0 lane config to old panel init_sequence + fix mode_flags 2026-06-13 18:49:36 -04:00
6fac886598 revert: remove failed CWU50 display fix patches 2026-06-13 18:04:06 -04:00
a4f4891236 try: no-burst-no-sync-pulse (VIDEO only) 2026-06-13 17:12:51 -04:00
3a809938c9 try: no-sync-pulse variant (keep BURST, remove SYNC_PULSE) 2026-06-13 16:38:24 -04:00
0f765d99cb feat: add CWU50 display patch (no-burst) + fix flake syntax
Remove extra '};' that broke flake.nix parsing.
Apply kernel patch '0008-panel-cwu50-no-burst.patch' to remove
MIPI_DSI_MODE_VIDEO_BURST flag in panel-cwu50.c.
Switch nixos-uconsole module to consolidated uconsole-cm5 module.
Keep patches/0008-panel-cwu50-remove-sync-pulse.patch as variant.
2026-06-13 16:27:32 -04:00
4d8087badf fix: apply DSI burst mode fix as kernel patch overlay 2026-06-13 13:47:35 -04:00
5aca97e057 Merge pull request 'feat: add uConsole CM5 host with Reticulum mesh network support' (#61) from feat/uconsole-cm5-v3 into master
Reviewed-on: #61
2026-06-06 13:19:19 +00:00
a51e095717 feat: enable aarch64 cross-build on lazyworkhorse (QEMU binfmt + extra-platforms) 2026-06-06 09:16:23 -04:00
9ebbb1c0c6 fix: bump nixos-raspberrypi to v1.20260517.0 (matches nixos-uconsole tested version) 2026-06-05 23:38:21 -04:00
7f11da1878 fix: let nixos-raspberrypi manage kernel version (patches incompatible with linuxPackages_latest) 2026-06-05 23:33:10 -04:00
29cc20bb04 fix: add wants=network-online.target to rnsd and kismet services to silence eval warnings 2026-06-05 22:58:09 -04:00
1617ac9149 fix: migrate from deprecated kernelboot to kernel bootloader for nixos-raspberrypi 2026-06-05 22:57:26 -04:00
24f15c98cd fix: add format=setuptools to all reticulum overlay python packages 2026-06-05 22:46:54 -04:00
bdd6d03739 fix: use mkForce for PermitRootLogin to override upstream module default 2026-06-05 22:45:59 -04:00
a0a6663793 fix: use mkForce for PasswordAuthentication to override upstream module default 2026-06-05 22:45:30 -04:00
b66ffadb79 fix: add missing 'keys' to uConsole module args 2026-06-05 22:43:53 -04:00
db2bd1d157 feat: add uConsole CM5 host configuration with Reticulum mesh stack
- New NixOS host 'uConsole' for ClockworkPi CM5 portable terminal
- flake.nix: add nixos-uconsole and nixos-raspberrypi inputs
- Imports: nixos-uconsole.nixosModules.uconsole-cm5,
  nixos-raspberrypi.nixosModules.raspberry-pi-5.base
- Full package list: base tools, HAM radio, SDR/RF, mesh/LoRa,
  security tools, GPS/maps
- Reticulum stack (rns 1.2.9, lxmf 0.9.8, nomadnet 1.1.1) built
  from PyPI via overlays/reticulum.nix
- systemd services: rnsd (Reticulum daemon), kismet (Wi-Fi IDS)
- Kernel modules for SDR (rtl-sdr, dvb) and USB WiFi
- Follows existing host config conventions (cyt-pi as template)
2026-05-20 14:34:15 -04:00
29 changed files with 1753 additions and 318 deletions

View File

@@ -1,106 +0,0 @@
# ollama-gfx906/Dockerfile
#
# Custom ollama image with ROCm 6.1 + gfx906 (MI50) support.
# The official ollama/rocm image ships ROCm 7.2 which dropped gfx906.
# This uses v0.23.2's native CMake build system with AMDGPU_TARGETS including gfx906.
#
# Build: docker build -t ollama/ollama:rocm-gfx906 ai/ollama
FROM rocm/dev-ubuntu-22.04:6.1.2-complete AS builder
# Build dependencies (CMake, Ninja, Go)
ARG CMAKEVERSION=3.31.2
ARG NINJAVERSION=1.12.1
ARG GOLANG_VERSION=1.22.0
RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y \
curl git ccache build-essential pkg-config unzip \
&& rm -rf /var/lib/apt/lists/*
# Install CMake from official binaries
RUN curl -fsSL https://github.com/Kitware/CMake/releases/download/v${CMAKEVERSION}/cmake-${CMAKEVERSION}-linux-x86_64.tar.gz \
| tar xz -C /usr/local --strip-components 1
# Install Ninja
RUN curl -fsSL -o /tmp/ninja.zip \
https://github.com/ninja-build/ninja/releases/download/v${NINJAVERSION}/ninja-linux.zip \
&& unzip /tmp/ninja.zip -d /usr/local/bin && rm /tmp/ninja.zip
# Install Go
RUN curl -fsSL https://go.dev/dl/go${GOLANG_VERSION}.linux-amd64.tar.gz \
| tar xz -C /usr/local
ENV PATH=/usr/local/go/bin:$PATH
ARG OLLAMA_VERSION=v0.23.2
RUN git clone --depth 1 --branch ${OLLAMA_VERSION} https://github.com/ollama/ollama.git /build
WORKDIR /build
# ROCm paths
ENV HIP_PATH=/opt/rocm
ENV ROCM_PATH=/opt/rocm
ENV CMAKE_GENERATOR=Ninja
ENV LDFLAGS=-s
# Step 1: Build CPU backends with GCC (no ROCm preset)
# Pre-set CMAKE_HIP_COMPILER="" to prevent check_language(HIP) from
# finding a HIP compiler (it searches /opt/rocm even without PATH).
# Remove /opt/rocm from PATH to prevent find_program from finding hipcc.
RUN mkdir -p build-cpu && \
PATH=/usr/local/go/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin \
cmake -B build-cpu -DCMAKE_BUILD_TYPE=Release \
-DCMAKE_HIP_COMPILER="" \
-DCMAKE_INSTALL_PREFIX=/build/dist && \
cmake --build build-cpu --target ggml-cpu -- -l $(nproc) && \
cmake --install build-cpu --component CPU --strip && \
echo "=== CPU install ===" && \
(find /build/dist/lib/ollama -type f -o -type l 2>&1 | head -20 || echo "empty")
# Step 2: Build HIP backend with ROCm preset + gfx906 target only
# The ROCm 6 preset enables HIP language detection (enable_language(HIP))
# which ensures GPU kernels are properly compiled for gfx906.
# OLLAMA_RUNNER_DIR=rocm from the preset, so HIP goes to lib/ollama/rocm/
# Need CMAKE_PREFIX_PATH so find_package(hip) finds hip-config.cmake
# at /opt/rocm/lib/cmake/hip/hip-config.cmake.
RUN mkdir -p build-hip && \
cmake -B build-hip \
--preset 'ROCm 6' \
-DAMDGPU_TARGETS="gfx906:xnack-" \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_PREFIX_PATH="/opt/rocm" && \
cmake --build build-hip --target ggml-hip -- -l $(nproc) && \
cmake --install build-hip --component HIP --strip && \
echo "=== HIP install ===" && \
find /build/dist/lib/ollama -type f -o -type l | head -20
# Step 3: Build Go binary (GCC for CGo linking)
ENV CGO_ENABLED=1
RUN go build -trimpath -ldflags="-X=github.com/ollama/ollama/version.Version=${OLLAMA_VERSION}" -o /build/dist/ollama .
# ---------- Runtime image ----------
FROM ubuntu:24.04
RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y \
ca-certificates curl libstdc++6 libgomp1 libvulkan1 libopenblas0 \
&& rm -rf /var/lib/apt/lists/*
# Copy ROCm 6.1 runtime libraries
# These are needed at runtime by ggml-hip via LD_LIBRARY_PATH
COPY --from=builder /opt/rocm/lib/ /opt/rocm/lib/
COPY --from=builder /opt/rocm/share/ /opt/rocm/share/
# Copy ollama binary + all backends (CPU + HIP)
# CPU install: /build/dist/lib/ollama/libggml-*.so
# HIP install: /build/dist/lib/ollama/rocm/libggml-hip.so
COPY --from=builder /build/dist/ollama /usr/bin/ollama
COPY --from=builder /build/dist/lib/ollama/ /usr/lib/ollama/
RUN ldconfig
ENV LD_LIBRARY_PATH=/opt/rocm/lib:/usr/lib/ollama/rocm:/usr/lib/ollama
ENV HSA_OVERRIDE_GFX_VERSION=9.0.6
ENV HCC_AMDGPU_TARGET=gfx906
ENV HSA_ENABLE_SDMA=0
EXPOSE 11434
ENTRYPOINT ["/bin/ollama"]
CMD ["serve"]

179
flake.lock generated
View File

@@ -23,6 +23,30 @@
"type": "github" "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": { "argononed": {
"flake": false, "flake": false,
"locked": { "locked": {
@@ -39,7 +63,44 @@
"type": "github" "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-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, "flake": false,
"locked": { "locked": {
"lastModified": 1751685974, "lastModified": 1751685974,
@@ -53,7 +114,7 @@
"url": "https://git.lix.systems/lix-project/flake-compat/archive/main.tar.gz" "url": "https://git.lix.systems/lix-project/flake-compat/archive/main.tar.gz"
} }
}, },
"flake-compat_2": { "flake-compat_3": {
"locked": { "locked": {
"lastModified": 1767039857, "lastModified": 1767039857,
"narHash": "sha256-vNpUSpF5Nuw8xvDLj2KCwwksIbjua2LZCqhV1LNRDns=", "narHash": "sha256-vNpUSpF5Nuw8xvDLj2KCwwksIbjua2LZCqhV1LNRDns=",
@@ -68,6 +129,49 @@
"type": "github" "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": { "home-manager": {
"inputs": { "inputs": {
"nixpkgs": [ "nixpkgs": [
@@ -89,16 +193,37 @@
"type": "github" "type": "github"
} }
}, },
"home-manager_2": {
"inputs": {
"nixpkgs": [
"nixpkgs-uconsole"
]
},
"locked": {
"lastModified": 1779506708,
"narHash": "sha256-QOD/CNm196nCJRheux/URi4/HE66fthdOMqCJoPP1Y0=",
"owner": "nix-community",
"repo": "home-manager",
"rev": "3ee51fbdac8c8bdfe1e7e1fcaba6520a563f394f",
"type": "github"
},
"original": {
"owner": "nix-community",
"ref": "release-25.11",
"repo": "home-manager",
"type": "github"
}
},
"lix": { "lix": {
"inputs": { "inputs": {
"flake-compat": "flake-compat", "flake-compat": "flake-compat_2",
"nix2container": "nix2container", "nix2container": "nix2container",
"nix_2_18": "nix_2_18", "nix_2_18": "nix_2_18",
"nixpkgs": [ "nixpkgs": [
"nixpkgs" "nixpkgs"
], ],
"nixpkgs-regression": "nixpkgs-regression", "nixpkgs-regression": "nixpkgs-regression",
"pre-commit-hooks": "pre-commit-hooks" "pre-commit-hooks": "pre-commit-hooks_2"
}, },
"locked": { "locked": {
"lastModified": 1774721317, "lastModified": 1774721317,
@@ -204,7 +329,7 @@
"nixos-raspberrypi": { "nixos-raspberrypi": {
"inputs": { "inputs": {
"argononed": "argononed", "argononed": "argononed",
"flake-compat": "flake-compat_2", "flake-compat": "flake-compat_3",
"nixos-images": "nixos-images", "nixos-images": "nixos-images",
"nixpkgs": [ "nixpkgs": [
"nixpkgs-uconsole" "nixpkgs-uconsole"
@@ -314,6 +439,29 @@
} }
}, },
"pre-commit-hooks": { "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, "flake": false,
"locked": { "locked": {
"lastModified": 1769939035, "lastModified": 1769939035,
@@ -332,6 +480,8 @@
"root": { "root": {
"inputs": { "inputs": {
"agenix": "agenix", "agenix": "agenix",
"agenix-rekey": "agenix-rekey",
"home-manager": "home-manager_2",
"lix": "lix", "lix": "lix",
"nixos-raspberrypi": "nixos-raspberrypi", "nixos-raspberrypi": "nixos-raspberrypi",
"nixos-uconsole": "nixos-uconsole", "nixos-uconsole": "nixos-uconsole",
@@ -353,6 +503,27 @@
"repo": "default", "repo": "default",
"type": "github" "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", "root": "root",

179
flake.nix
View File

@@ -8,6 +8,10 @@
inputs.darwin.follows = ""; inputs.darwin.follows = "";
inputs.nixpkgs.follows = "nixpkgs"; inputs.nixpkgs.follows = "nixpkgs";
}; };
agenix-rekey = {
url = "github:oddlama/agenix-rekey";
inputs.nixpkgs.follows = "nixpkgs";
};
lix = { lix = {
url = "git+https://git.lix.systems/lix-project/lix?ref=main"; url = "git+https://git.lix.systems/lix-project/lix?ref=main";
inputs.nixpkgs.follows = "nixpkgs"; inputs.nixpkgs.follows = "nixpkgs";
@@ -22,10 +26,16 @@
url = "github:gortium/nixos-raspberrypi/cm5-cross-v1"; url = "github:gortium/nixos-raspberrypi/cm5-cross-v1";
inputs.nixpkgs.follows = "nixpkgs-uconsole"; inputs.nixpkgs.follows = "nixpkgs-uconsole";
}; };
self.submodules = true;
home-manager = {
url = "github:nix-community/home-manager/release-25.11";
inputs.nixpkgs.follows = "nixpkgs-uconsole";
};
}; };
outputs = { self, nixpkgs, agenix, lix outputs = { self, nixpkgs, agenix, agenix-rekey, lix
, nixpkgs-uconsole, nixos-uconsole, nixos-raspberrypi , nixpkgs-uconsole, nixos-uconsole, nixos-raspberrypi
, home-manager
, ... }@inputs: , ... }@inputs:
let let
system = "x86_64-linux"; system = "x86_64-linux";
@@ -37,7 +47,7 @@
"/etc/ssh/ssh_host_ed25519_key" "/etc/ssh/ssh_host_ed25519_key"
"/root/.age/bootstrap.key" ]; "/root/.age/bootstrap.key" ];
}; };
overlays = [ agenix.overlays.default ]; overlays = [ agenix.overlays.default (import ./overlays/reticulum.nix) ];
pkgs = import nixpkgs { pkgs = import nixpkgs {
inherit system overlays; inherit system overlays;
config.allowUnfree = true; config.allowUnfree = true;
@@ -47,7 +57,109 @@
inherit pkgs system agenix; 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: { uconsoleCrossOverlay = final: prev: {
libcamera = prev.libcamera.overrideAttrs (_: { meta.platforms = []; }); libcamera = prev.libcamera.overrideAttrs (_: { meta.platforms = []; });
libcamera-rpi = prev.libcamera-rpi.overrideAttrs (_: { meta.platforms = []; }); libcamera-rpi = prev.libcamera-rpi.overrideAttrs (_: { meta.platforms = []; });
@@ -64,15 +176,31 @@
mesonFlags = (old.mesonFlags or []) ++ [ "-Dskip_gtk_tests=true" ]; mesonFlags = (old.mesonFlags or []) ++ [ "-Dskip_gtk_tests=true" ];
}); });
hyprland = prev.hyprland.override { wrapRuntimeDeps = false; }; 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: { xdg-desktop-portal-hyprland = prev.xdg-desktop-portal-hyprland.overrideAttrs (old: {
preConfigure = (old.preConfigure or "") + '' preConfigure = (old.preConfigure or "") + ''
cmakeFlags="$cmakeFlags -Dhyprwayland-scanner_DIR=${prev.buildPackages.hyprwayland-scanner}/lib/cmake/hyprwayland-scanner" 2>/dev/null || true 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" 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: {
nativeBuildInputs = (old.nativeBuildInputs or []) ++ [
prev.buildPackages.python3 # python3 for opencl_generate_dynamic_loader.py
];
configureFlags = (old.configureFlags or []) ++ [
"ac_cv_prog_PYTHON=${prev.buildPackages.python3}/bin/python3"
];
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: { uconsoleRpiPipewireOverlay = final: prev: {
pipewire = prev.pipewire.overrideAttrs (old: { pipewire = prev.pipewire.overrideAttrs (old: {
buildInputs = builtins.filter buildInputs = builtins.filter
@@ -84,14 +212,13 @@
}); });
}; };
# Shared uConsole CM5 module set — used by both toplevel and SD image
uconsoleBaseModules = [ uconsoleBaseModules = [
{ {
nixpkgs.buildPlatform = "x86_64-linux"; nixpkgs.buildPlatform = "x86_64-linux";
nixpkgs.hostPlatform = "aarch64-linux"; nixpkgs.hostPlatform = "aarch64-linux";
nixpkgs.config.allowUnfree = true; nixpkgs.config.allowUnfree = true;
boot.loader.raspberry-pi.bootloader = "kernel"; boot.loader.raspberry-pi.bootloader = "kernel";
nixpkgs.overlays = [ uconsoleCrossOverlay ]; nixpkgs.overlays = [ uconsoleCrossOverlay (import ./overlays/reticulum.nix) ];
} }
nixos-raspberrypi.nixosModules.nixpkgs-rpi nixos-raspberrypi.nixosModules.nixpkgs-rpi
({ config, lib, pkgs, ... }: { ({ config, lib, pkgs, ... }: {
@@ -101,7 +228,7 @@
nixos-raspberrypi.lib.inject-overlays nixos-raspberrypi.lib.inject-overlays
nixos-raspberrypi.lib.inject-overlays-global nixos-raspberrypi.lib.inject-overlays-global
nixos-uconsole.nixosModules.uconsole-cm5 nixos-uconsole.nixosModules.uconsole-cm5
# Cross-compiled Lix for uConsole ./modules/nixos/hardware/uConsole-aio-v2.nix
({ config, lib, pkgs, inputs, ... }: let ({ config, lib, pkgs, inputs, ... }: let
lixCross = import inputs.nixpkgs-uconsole { lixCross = import inputs.nixpkgs-uconsole {
localSystem = { system = "x86_64-linux"; }; localSystem = { system = "x86_64-linux"; };
@@ -109,12 +236,18 @@
overlays = [ inputs.lix.overlays.default ]; overlays = [ inputs.lix.overlays.default ];
}; };
in { nix.package = lixCross.lix; }) in { nix.package = lixCross.lix; })
inputs.home-manager.nixosModules.home-manager
agenix.nixosModules.default agenix.nixosModules.default
./hosts/uconsole-cm5/configuration.nix agenix-rekey.nixosModules.default
./hosts/uconsole-cm5/hardware-configuration.nix ./hosts/uConsole/configuration.nix
./hosts/uConsole/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/gortium/gortium.nix
./users/ai-worker/ai-worker.nix
]; ];
in { in {
nixosConfigurations = { nixosConfigurations = {
lazyworkhorse = nixpkgs.lib.nixosSystem { lazyworkhorse = nixpkgs.lib.nixosSystem {
@@ -127,17 +260,23 @@
nixpkgs.config.permittedInsecurePackages = [ "openclaw-2026.3.12" ]; nixpkgs.config.permittedInsecurePackages = [ "openclaw-2026.3.12" ];
nix.package = lix.packages.${system}.default; nix.package = lix.packages.${system}.default;
} }
inputs.home-manager.nixosModules.home-manager
agenix.nixosModules.default agenix.nixosModules.default
agenix-rekey.nixosModules.default
./hosts/lazyworkhorse/configuration.nix ./hosts/lazyworkhorse/configuration.nix
./hosts/lazyworkhorse/hardware-configuration.nix ./hosts/lazyworkhorse/hardware-configuration.nix
./modules/nixos/filesystem/hoardingcow-mount.nix ./modules/nixos/filesystem/hoardingcow-mount.nix
./modules/nixos/services/docker_manager.nix ./modules/nixos/services/docker_manager.nix
./modules/nixos/services/open_code_server.nix ./modules/nixos/filesystem/poup-16t-disk.nix
./modules/nixos/services/ollama_init_custom_models.nix ./modules/nixos/services/ollama_init_custom_models.nix
./modules/nixos/services/openclaw_node.nix ./modules/nixos/services/open_code_server.nix
./modules/nixos/services/clamav.nix
./modules/nixos/services/wireguard-client.nix
./modules/nixos/services/remote-builder.nix
./modules/nixos/security/ai-worker-restricted.nix ./modules/nixos/security/ai-worker-restricted.nix
./users/gortium/gortium.nix ./users/gortium/gortium.nix
./users/ai-worker.nix ./users/ai-worker/ai-worker.nix
./users/builder.nix
]; ];
}; };
@@ -150,12 +289,17 @@
nixpkgs.hostPlatform = "aarch64-linux"; nixpkgs.hostPlatform = "aarch64-linux";
nix.package = lix.packages."aarch64-linux".default; nix.package = lix.packages."aarch64-linux".default;
} }
inputs.home-manager.nixosModules.home-manager
agenix.nixosModules.default
./hosts/cyt-pi/configuration.nix ./hosts/cyt-pi/configuration.nix
./hosts/cyt-pi/hardware-configuration.nix ./hosts/cyt-pi/hardware-configuration.nix
./modules/nixos/services/remote-builder.nix
./modules/nixos/services/wireguard-client.nix
./users/gortium/gortium.nix
]; ];
}; };
uconsole-cm5 = nixpkgs-uconsole.lib.nixosSystem { uConsole = nixpkgs-uconsole.lib.nixosSystem {
system = "aarch64-linux"; system = "aarch64-linux";
specialArgs = { specialArgs = {
inherit self keys paths inputs; inherit self keys paths inputs;
@@ -166,10 +310,15 @@
}; };
}; };
agenix-rekey = agenix-rekey.configure {
userFlake = self;
nixosConfigurations = self.nixosConfigurations;
};
devShells.${system}.default = devShell; devShells.${system}.default = devShell;
packages.${system} = { packages.${system} = {
uconsole-cm5-image = (nixos-raspberrypi.lib.nixosSystem { uConsole-image = (nixos-raspberrypi.lib.nixosSystem {
system = "aarch64-linux"; system = "aarch64-linux";
specialArgs = { specialArgs = {
inherit self keys inputs; inherit self keys inputs;

View File

@@ -21,14 +21,7 @@
settings.PermitRootLogin = "prohibit-password"; settings.PermitRootLogin = "prohibit-password";
}; };
# User # User (defined in users/gortium/gortium.nix)
users.users.gortium = {
isNormalUser = true;
extraGroups = [ "wheel" "networkmanager" "kismet" ];
openssh.authorizedKeys.keys = [
# Populate with your public key
];
};
# CYT Project Dependencies (Headless) # CYT Project Dependencies (Headless)
environment.systemPackages = with pkgs; [ environment.systemPackages = with pkgs; [
@@ -50,7 +43,7 @@
User = "gortium"; User = "gortium";
Group = "kismet"; Group = "kismet";
ExecStart = '' ExecStart = ''
${pkgs.kismet}/bin/kismet -c panda --log-base=/home/gortium/kismet_logs --no-nc-ui ${pkgs.kismet}/bin/kismet -c panda --log-prefix=/home/gortium/kismet_logs --no-nc-ui
''; '';
Restart = "always"; Restart = "always";
RestartSec = "10s"; RestartSec = "10s";
@@ -77,6 +70,12 @@
}; };
}; };
# Age secrets
age.secrets.gortium_password = {
file = ../../secrets/gortium_password.age;
};
# CYT Application Service # CYT Application Service
systemd.services.cyt-app = { systemd.services.cyt-app = {
description = "Chasing Your Tail - Target Detector"; description = "Chasing Your Tail - Target Detector";

View File

@@ -9,8 +9,12 @@
hoardingcow-mount.enable = true; hoardingcow-mount.enable = true;
# Flakesss # Flakesss
nix.settings.experimental-features = [ "nix-command" "flakes" "flake-self-attrs" "ca-derivations" ]; nix.settings.experimental-features = [ "nix-command" "flakes" "flake-self-attrs" ];
nix.settings.trusted-users = [ "root" "gortium" ]; nix.settings.trusted-users = [ "root" "gortium" ];
nix.settings.extra-platforms = [ "aarch64-linux" ];
# QEMU binfmt for cross-building aarch64 NixOS targets
boot.binfmt.emulatedSystems = [ "aarch64-linux" ];
# Garbage collection # Garbage collection
nix.gc = { nix.gc = {
@@ -49,24 +53,12 @@
networking.networkmanager.enable = true; # Easiest to use and most distros use this by default. networking.networkmanager.enable = true; # Easiest to use and most distros use this by default.
networking.hostId = "deadbeef"; networking.hostId = "deadbeef";
# WireGuard VPN client -- always up, connects to wg-easy server # WireGuard VPN client -- module, always up, connects to wg-easy server
# Create age-encrypted secrets before deploying (run on the host): gortium.wireguard-client = {
# echo -n "<private_key>" | agenix -e secrets/wireguard_private_key.age enable = true;
# echo -n "<preshared_key>" | agenix -e secrets/wireguard_preshared_key.age vpnIp = "10.8.0.3/24";
networking.wireguard.interfaces = { privateKeyFile = config.age.secrets.wireguard_private_key.path;
wg0 = { presharedKeyFile = config.age.secrets.wireguard_preshared_key.path;
ips = [ "10.8.0.3/24" ];
privateKeyFile = config.age.secrets.wireguard_private_key.path;
peers = [
{
publicKey = "rY9zII3AOm8rog2rv02PyA3Bq7zdvTOGkZapfCV1DkE=";
presharedKeyFile = config.age.secrets.wireguard_preshared_key.path;
allowedIPs = [ "10.8.0.0/24" ];
endpoint = "vpn.lazyworkhorse.net:51820";
persistentKeepalive = 25;
}
];
};
}; };
# Set your time zone. # Set your time zone.
@@ -178,7 +170,6 @@
settings = { settings = {
PasswordAuthentication = false; PasswordAuthentication = false;
KbdInteractiveAuthentication = false; KbdInteractiveAuthentication = false;
# Additional hardening settings below in SERVER HARDENING section
}; };
hostKeys = [ hostKeys = [
{ {
@@ -260,7 +251,9 @@
# Private host ssh key managed by agenix # Private host ssh key managed by agenix
age = { age = {
identityPaths = paths.identities; identityPaths = paths.identities ++ [
"/etc/ssh/ssh_host_ed25519_key"
];
secrets = { secrets = {
containers_env = { containers_env = {
file = ../../secrets/containers.env.age; file = ../../secrets/containers.env.age;
@@ -283,13 +276,6 @@
mode = "0600"; mode = "0600";
path = "/home/ai-worker/.ssh/ai_ssh_key"; path = "/home/ai-worker/.ssh/ai_ssh_key";
}; };
openclaw_gateway_token = {
file = ../../secrets/openclaw_gateway_token.age;
owner = "root";
group = "ai-worker";
mode = "0440";
path = "/run/secrets/openclaw_gateway_token";
};
wireguard_private_key = { wireguard_private_key = {
file = ../../secrets/wireguard_private_key.age; file = ../../secrets/wireguard_private_key.age;
owner = "root"; owner = "root";
@@ -303,20 +289,24 @@
group = "root"; group = "root";
mode = "0400"; mode = "0400";
path = "/run/secrets/wireguard_preshared_key"; path = "/run/secrets/wireguard_preshared_key";
};
builder_key = {
file = ../../secrets/builder_key.age;
owner = "root";
group = "root";
mode = "0600";
path = "/etc/ssh/builder_key";
};
gortium_password = {
file = ../../secrets/gortium_password.age;
owner = "gortium";
group = "gortium";
mode = "0400";
path = "/run/secrets/gortium_password";
}; };
}; };
}; };
# OpenClaw Node service (host-side execution for Docker gateway)
services.openclaw-node = {
enable = true;
user = "ai-worker";
gatewayHost = "127.0.0.1";
gatewayPort = 18789;
gatewayTokenFile = "/run/secrets/openclaw_gateway_token";
displayName = "lazyworkhorse-host";
};
# Public host ssh key (kept in sync with the private one) # Public host ssh key (kept in sync with the private one)
environment.etc."ssh/ssh_host_ed25519_key.pub".text = environment.etc."ssh/ssh_host_ed25519_key.pub".text =
"${keys.hosts.lazyworkhorse.main}"; "${keys.hosts.lazyworkhorse.main}";
@@ -349,6 +339,16 @@
# networking.firewall.enable = false; # 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 # SERVER HARDENING - Firewall, Fail2ban, SSH, Kernel
# ============================================================================= # =============================================================================
@@ -569,5 +569,29 @@
# For more information, see `man configuration.nix` or https://nixos.org/manual/nixos/stable/options#opt-system.stateVersion . # For more information, see `man configuration.nix` or https://nixos.org/manual/nixos/stable/options#opt-system.stateVersion .
system.stateVersion = "25.05"; # Did you read the comment? system.stateVersion = "25.05"; # Did you read the comment?
}
# ============================================================
# Remote builder — dispatches aarch64-linux builds to uConsole
# ============================================================
services.remoteBuilder = {
enable = true;
machines = [
{
hostName = "192.168.1.120";
port = 22;
sshUser = "builder";
sshKey = config.age.secrets.builder_key.path;
systems = [ "aarch64-linux" ];
maxJobs = 4;
}
];
};
# agenix-rekey — automatic re-encryption for each target host
age.rekey = {
masterIdentities = [
"/home/gortium/.ssh/gortium_ssh_key"
];
};
}

View File

@@ -0,0 +1,240 @@
{ config, lib, pkgs, keys, ... }:
{
networking.hostName = "uConsole";
time.timeZone = "America/Montreal";
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;
settings = {
PermitRootLogin = lib.mkForce "prohibit-password";
PasswordAuthentication = lib.mkForce false;
};
};
users.users.root.openssh.authorizedKeys.keys = with keys; [
users.gortium.main
users.ai-worker.main
];
# Age secret for gortium password (file created by user)
age.secrets.gortium_password = {
file = ../../secrets/gortium_password.age;
};
# WiFi via NetworkManager
networking.networkmanager.enable = true;
# Firmware
hardware.enableRedistributableFirmware = true;
# RealtimeKit for PipeWire audio
security.rtkit.enable = true;
services.pipewire = {
enable = true;
alsa.enable = true;
alsa.support32Bit = true;
pulse.enable = true;
};
# Hyprland Wayland compositor (manual start — no SDDM)
programs.hyprland = {
enable = true;
xwayland.enable = true;
};
# HackerGadgets AIO v2 board
hardware.uconsole-cm5-aio-v2 = {
enable = true;
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
# ============================================================
# ============================================================
# CROSS-COMPILE REMOVALS — packages removed for aarch64 bootstrap
# ============================================================
# These packages fail to cross-compile for aarch64.
# Install them natively AFTER the first successful switch.
#
# Removed: Reason:
# inspectrum — Qt5 cross-compile cascade fails (qtsvg mismatched qtbase deps)
# hashcat — Makefile calls gcc directly (cross-compiler not used)
# neovim — Same as hashcat: Makefile calls gcc directly (cross-compiler not used)
# clamav — cmake try_run + Rust proc-macro linker for aarch64
# sdrpp — glfw/wxPython cross-compile fails
# gqrx — Qt5 cross-compile cascade fails
# emacs-pgtk → emacs-nox — GTK3 + mailutils → gss → shishi chain
# viking — GTK3 GPS map editor
# foxtrotgps — GTK2 GPS app
# js8call — QtQuick3D dep
# wsjtx — qtbase/Qt5 linker fails (collect2: ld returned 1)
# fldigi — same: qtbase/Qt5 linker fails
# gpsbabel — qmake can't find cross-compiler g++
# john — configure script needs python (not in PATH during cross-compile)
# trustedqsl — needs wxWidgets (unavailable in cross-compile)
# chirp — depends on wxPython (fails cross-compile: GTK3 + wx build)
# ============================================================
environment.systemPackages = with pkgs; [
# ===== Base =====
# emacs-pgtk — removed for bootstrap (GTK3 cross-compile fails)
# emacs-nox — removed for bootstrap (depends on mailutils -> gss -> shishi, cross-compile fails)
git
ripgrep
fd
htop
tmux
# ===== HAM Radio =====
# wsjtx # removed for bootstrap - now native (native-only, needs remote build)
# fldigi # removed for bootstrap - now native (native-only, needs remote build)
pat # Winlink client
direwolf # AX.25 packet modem
# chirp # Radio programming tool - now native (native-only, needs remote build)
hamlib # Ham radio control libraries
# trustedqsl # Logbook of the World (LoTW) - now native (native-only, needs remote build)
# ===== SDR / RF =====
# sdrpp # removed for bootstrap - now native (native-only, needs remote build)
# gqrx # removed for bootstrap - now native (native-only, needs remote build)
rtl-sdr # RTL-SDR drivers & utilities
# inspectrum # removed for bootstrap - now native (native-only, needs remote build)
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 - now native
sqlmap # SQL injection tool
# ===== GPS / Maps =====
#foxtrotgps # removed for bootstrap - now native (native-only, needs remote build)
#viking # removed for bootstrap - now native (native-only, needs remote build)
#gpsbabel # GPS data conversion - now native (native-only, needs remote build)
];
# ============================================================
# 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-prefix=/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 = [ ];
# ============================================================
# Remote builder — dispatches x86_64-linux builds to lazyworkhorse
# ============================================================
services.remoteBuilder = {
enable = true;
machines = [
{
hostName = "lazyworkhorse.net";
port = 2424;
sshUser = "builder";
sshKey = config.age.secrets.builder_key.path;
systems = [ "x86_64-linux" ];
maxJobs = 36;
supportedFeatures = [ "benchmark" "big-parallel" "nixos-test" ];
}
];
};
# Age secret for builder SSH key (remote building)
age.secrets.builder_key = {
file = ../../secrets/builder_key.age;
};
# ============================================================
age.identityPaths = [
"/etc/ssh/ssh_host_ed25519_key"
];
# 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 = "age1un8td4jzkhg5fm4jja7z3aznskc8hlcm8ky0j2f70tv74rulwgaqtpemn2"; # uConsole CM5 SSH host key
};
# Pipewire overlay: drop libcamera (fixes aarch64 cross-compile — rpi-pisp blocks)
nixpkgs.overlays = [
(final: prev: {
pipewire = prev.pipewire.override { libcamera = null; };
})
];
}

View File

@@ -1,51 +0,0 @@
{ config, lib, pkgs, keys, ... }:
{
networking.hostName = "uConsole";
time.timeZone = "America/Montreal";
i18n.defaultLocale = "en_CA.UTF-8";
system.stateVersion = "25.11";
# SSH — root access avec clés gortium + ai-worker
services.openssh = {
enable = true;
settings = {
PermitRootLogin = lib.mkForce "prohibit-password";
PasswordAuthentication = lib.mkForce false;
};
};
users.users.root.openssh.authorizedKeys.keys = with keys; [
users.gortium.main
users.ai-worker.main
];
# AI worker user (Hermes SSH access)
users.users.ai-worker = {
isNormalUser = false;
shell = pkgs.bash;
openssh.authorizedKeys.keys = with keys; [
users.ai-worker.main
];
};
# 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)
users.users.gortium.passwordFile = config.age.secrets.gortium_password.path;
# WiFi via NetworkManager + secret agenix
networking.networkmanager.enable = true;
# Firmware
hardware.enableRedistributableFirmware = true;
# Hyprland Wayland compositor (manual start — no SDDM)
programs.hyprland = {
enable = true;
xwayland.enable = true;
};
}

View File

@@ -9,6 +9,13 @@
ai-worker = { ai-worker = {
main = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAXeGtPPcsP2IYRQNvII41NVWhJsarEk8c4qxs/a5sXf"; main = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAXeGtPPcsP2IYRQNvII41NVWhJsarEk8c4qxs/a5sXf";
}; };
builder = {
# Same key on both hosts for bidirectional remote building.
# Generate with: ssh-keygen -t ed25519 -f /etc/ssh/builder_key -N ""
# Replace the placeholder below with the public key (builder_key.pub).
main = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIByG79NM/rk13q3MTow9Ezb8pe3RWoQ76Mv8atASp3Ri";
};
}; };
hosts = { hosts = {
@@ -18,5 +25,9 @@
gitea = ""; gitea = "";
bootstrap = "age1r796v2uldtspawyh863pks74sd2pwcan8j4e4pjzsvkmr3vjja9qpz5ste"; bootstrap = "age1r796v2uldtspawyh863pks74sd2pwcan8j4e4pjzsvkmr3vjja9qpz5ste";
}; };
uConsole = {
main = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICasvsmaHuNVaWnjwuBfoMXyY/6PIqGL8yxstPBb46u4";
bootstrap = "age1un8td4jzkhg5fm4jja7z3aznskc8hlcm8ky0j2f70tv74rulwgaqtpemn2";
};
}; };
} }

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,170 @@
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.hardware.uconsole-cm5-aio-v2;
# GPIO pin map matching the AIO v2 board hardware
# SDR (RTL-SDR): GPIO 7
# LoRa (SX1262) : GPIO 16
# USB Hub Interne: GPIO 23
# GPS (GNSS) : GPIO 27
gpioMap = {
GPS = 27;
LORA = 16;
SDR = 7;
USB = 23;
};
# Generate a script that applies boot rail states via pinctrl
applyRailsScript = pkgs.writeShellScript "apply-aio-v2-rails" (
''
set -e
PINCTRL=${pkgs.raspberrypi-utils}/bin/pinctrl
''
+ concatStringsSep "" (mapAttrsToList (name: pin: ''
if [ "${if cfg.bootRails.${name} then "1" else "0"}" = "1" ]; then
echo "AIO v2: ${name} (GPIO${toString pin}) -> ON"
$PINCTRL set ${toString pin} op dh
else
echo "AIO v2: ${name} (GPIO${toString pin}) -> OFF"
$PINCTRL set ${toString pin} op dl
fi
'') gpioMap)
);
# aiov2_ctl CLI tool -- fetched from GitHub, available as `aiov2_ctl`
aiov2CtlPkg = pkgs.stdenv.mkDerivation rec {
pname = "aiov2_ctl";
version = "0-unstable-2026-06-16";
src = pkgs.fetchFromGitHub {
owner = "hackergadgets";
repo = "aiov2_ctl";
rev = "main";
hash = "sha256-hqOvS1K5pDVXAroUE50i5R9YqRgC2U3fzby6uuB67K0=";
};
dontUnpack = true;
buildInputs = [ pkgs.python3 ];
installPhase = ''
mkdir -p $out/bin $out/share/aiov2_ctl/img
cp $src/aiov2_ctl.py $out/bin/aiov2_ctl
chmod +x $out/bin/aiov2_ctl
patchShebangs $out/bin/aiov2_ctl
substituteInPlace $out/bin/aiov2_ctl \
--replace-fail '"/usr/local/share/aiov2_ctl/img/' '"'$out'/share/aiov2_ctl/img/'
cp -r $src/img/* $out/share/aiov2_ctl/img/
'';
meta = {
description = "HackerGadgets uConsole AIO v2 GPIO control and telemetry tool";
homepage = "https://github.com/hackergadgets/aiov2_ctl";
license = lib.licenses.mit;
maintainers = with lib.maintainers; [ ];
platforms = [ "aarch64-linux" ];
};
};
in {
options.hardware.uconsole-cm5-aio-v2 = {
enable = mkEnableOption "HackerGadgets uConsole AIO v2 board support";
bootRails = {
GPS = mkOption {
type = types.bool;
default = false;
description = "Enable GPS module at boot (GPIO 27)";
};
LORA = mkOption {
type = types.bool;
default = false;
description = "Enable LoRa module at boot (GPIO 16)";
};
SDR = mkOption {
type = types.bool;
default = false;
description = "Enable SDR module at boot (GPIO 7)";
};
USB = mkOption {
type = types.bool;
default = false;
description = "Enable internal USB hub at boot (GPIO 23)";
};
};
package = mkOption {
type = types.package;
default = aiov2CtlPkg;
defaultText = literalExpression "aiov2CtlPkg";
description = "aiov2_ctl package to use";
};
enableGPS = mkOption {
type = types.bool;
default = false;
description = ''
Enable GPS UART (/dev/ttyAMA0 at 9600 baud).
Requires enabling UART on the CM5 via boot.kernelParams.
'';
};
enableGUI = mkOption {
type = types.bool;
default = false;
description = ''
Enable the system tray GUI for aiov2_ctl.
Requires a desktop environment with system tray support.
'';
};
};
config = mkIf cfg.enable {
# Package the aiov2_ctl tool + pinctrl
environment.systemPackages = with pkgs; [
cfg.package
raspberrypi-utils # provides pinctrl
];
# Boot rail systemd oneshot service
systemd.services.aiov2-rails-boot = {
description = "Apply AIO v2 GPIO rail boot states";
after = [ "local-fs.target" ];
wants = [ "local-fs.target" ];
before = [ "multi-user.target" ];
wantedBy = [ "multi-user.target" ];
serviceConfig = {
Type = "oneshot";
ExecStart = "${applyRailsScript}";
RemainAfterExit = true;
};
};
# GPS configuration
boot.kernelParams = mkIf cfg.enableGPS [ "uart0=on" ];
users.users = mkIf cfg.enableGPS {
gortium = {
extraGroups = [ "dialout" ];
};
};
# GUI autostart (XDG)
systemd.user.services.aiov2-ctl-gui = mkIf cfg.enableGUI {
description = "AIO v2 System Tray Controller";
after = [ "graphical-session.target" ];
wants = [ "graphical-session.target" ];
wantedBy = [ "graphical-session.target" ];
serviceConfig = {
Type = "simple";
ExecStart = "${cfg.package}/bin/aiov2_ctl --gui";
Restart = "on-failure";
RestartSec = 5;
};
environment = {
AIOV2_CTL_DEBUG = "0";
};
};
};
}

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

@@ -1,64 +0,0 @@
{ config, lib, pkgs, ... }:
let
cfg = config.services.openclaw-node;
openclawPkg = pkgs.openclaw;
in {
options.services.openclaw-node = {
enable = lib.mkEnableOption "OpenClaw Node service";
user = lib.mkOption {
type = lib.types.str;
default = "ai-worker";
description = "User to run the OpenClaw headless node as.";
};
gatewayHost = lib.mkOption {
type = lib.types.str;
default = "127.0.0.1";
description = "Gateway host (IP or hostname).";
};
gatewayPort = lib.mkOption {
type = lib.types.int;
default = 18789;
description = "Gateway WebSocket port.";
};
gatewayTokenFile = lib.mkOption {
type = lib.types.str;
default = "";
description = "Path to file containing the gateway auth token.";
};
displayName = lib.mkOption {
type = lib.types.str;
default = "lazyworkhorse-host";
description = "Display name for this node (shown in pairing).";
};
};
config = lib.mkIf cfg.enable {
systemd.services.openclaw-node = {
description = "OpenClaw Headless Node Service";
after = [ "network.target" ];
wantedBy = [ "multi-user.target" ];
serviceConfig = {
Type = "exec";
User = cfg.user;
Group = cfg.user;
WorkingDirectory = "/home/${cfg.user}";
ExecStart = ''
${pkgs.bash}/bin/bash -c 'export OPENCLAW_GATEWAY_TOKEN=$(cat ${cfg.gatewayTokenFile}) && exec ${openclawPkg}/bin/openclaw node run --host ${cfg.gatewayHost} --port ${toString cfg.gatewayPort} --display-name "${cfg.displayName}"'
'';
Restart = "always";
RestartSec = 5;
};
environment = {
NODE_ENV = "production";
};
};
};
}

View File

@@ -0,0 +1,73 @@
{ config, lib, pkgs, ... }:
let
cfg = config.services.remoteBuilder;
in {
options.services.remoteBuilder = {
enable = lib.mkEnableOption "remote Nix build machine";
machines = lib.mkOption {
type = lib.types.listOf (lib.types.submodule {
options = {
hostName = lib.mkOption {
type = lib.types.str;
description = "Hostname or IP of the remote build machine.";
};
port = lib.mkOption {
type = lib.types.port;
default = 22;
description = "SSH port.";
};
sshUser = lib.mkOption {
type = lib.types.str;
default = "builder";
description = "SSH user on the remote build machine.";
};
sshKey = lib.mkOption {
type = lib.types.str;
description = "Path to SSH private key for the builder.";
};
systems = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ "aarch64-linux" ];
description = "System types the remote builder can build for.";
};
maxJobs = lib.mkOption {
type = lib.types.int;
default = 4;
description = "Max parallel jobs on the remote builder.";
};
supportedFeatures = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ "benchmark" "big-parallel" "nixos-test" ];
description = "Features the remote builder supports.";
};
};
});
default = [];
description = "List of remote Nix build machines.";
};
};
config = lib.mkIf cfg.enable {
nix.distributedBuilds = true;
nix.buildMachines = map (m: {
hostName = m.hostName;
sshUser = m.sshUser;
sshKey = m.sshKey;
systems = m.systems;
maxJobs = m.maxJobs;
supportedFeatures = m.supportedFeatures;
}) cfg.machines;
# SSH config for port + key (nix.buildMachines has no port option)
programs.ssh.extraConfig = lib.concatStringsSep "\n" (map (m: ''
Host ${m.hostName}
HostName ${m.hostName}
Port ${toString m.port}
User ${m.sshUser}
IdentityFile ${m.sshKey}
StrictHostKeyChecking no
UserKnownHostsFile /dev/null
'') cfg.machines);
};
}

View File

@@ -0,0 +1,275 @@
{ config, pkgs, lib, ... }:
with lib;
let
cfg = config.services.stagingVm;
in
{
options.services.stagingVm = {
enable = mkOption {
type = types.bool;
default = false;
description = "Enable KVM/libvirt staging VM for compose PR testing";
};
vmName = mkOption {
type = types.str;
default = "compose-test-vm";
description = "Name of the staging VM";
};
memory = mkOption {
type = types.str;
default = "4096";
description = "RAM allocated to the staging VM (MB)";
};
vcpus = mkOption {
type = types.int;
default = 2;
description = "Number of vCPUs for the staging VM";
};
storagePath = mkOption {
type = types.str;
default = "/var/lib/libvirt/images";
description = "Path for libvirt storage pool";
};
dataPath = mkOption {
type = types.str;
default = "/var/lib/staging-vm";
description = "Path for compose test data (PR checkouts, test results)";
};
};
config = mkIf cfg.enable {
# Enable libvirt daemon
virtualisation.libvirtd = {
enable = true;
qemu = {
package = pkgs.qemu_kvm;
runAsRoot = true;
swtpm.enable = true;
ovmf = {
enable = true;
packages = [ pkgs.OVMFFull.fd ];
};
};
};
# Kernel modules + groups already handled in configuration.nix
# libvirt NAT network (192.168.122.0/24)
environment.etc."libvirt/qemu/networks/default.xml" = {
text = ''
<network>
<name>default</name>
<uuid>2b8f7a3c-9e5d-4a1f-bc3d-6e7a8f9b0c1d</uuid>
<forward mode='nat'>
<nat>
<port start='1024' end='65535'/>
</nat>
</forward>
<bridge name='virbr0' stp='on' delay='0'/>
<mac address='52:54:00:12:34:56'/>
<ip address='192.168.122.1' netmask='255.255.255.0'>
<dhcp>
<range start='192.168.122.2' end='192.168.122.254'/>
</dhcp>
</ip>
</network>
'';
# Autostart the network so it comes up on boot
mode = "0644";
};
# Ensure the default network is defined and autostarted
systemd.services.libvirtd = {
postStart = ''
${pkgs.libvirt}/bin/virsh net-define /etc/libvirt/qemu/networks/default.xml 2>/dev/null || true
${pkgs.libvirt}/bin/virsh net-autostart default 2>/dev/null || true
${pkgs.libvirt}/bin/virsh net-start default 2>/dev/null || true
'';
};
# Storage directory for VM images
systemd.tmpfiles.rules = [
"d ${cfg.storagePath} 0755 root root -"
"d ${cfg.dataPath} 0755 root root -"
];
# Ensure storage pool exists in libvirt
systemd.services.libvirtd.postStart = mkAfter ''
${pkgs.libvirt}/bin/virsh pool-define-as default dir --target "${cfg.storagePath}" 2>/dev/null || true
${pkgs.libvirt}/bin/virsh pool-autostart default 2>/dev/null || true
${pkgs.libvirt}/bin/virsh pool-start default 2>/dev/null || true
'';
# Firewall: allow traffic from virbr0 to host and outbound NAT
networking.firewall = {
extraCommands = ''
# Allow inbound DHCP/DNS from libvirt guests
iptables -I INPUT -i virbr0 -p udp --dport 67:68 -j ACCEPT
iptables -I INPUT -i virbr0 -p tcp --dport 53 -j ACCEPT
iptables -I INPUT -i virbr0 -p udp --dport 53 -j ACCEPT
# Allow established/related traffic back to guests
iptables -I FORWARD -i virbr0 -o virbr0 -j ACCEPT
iptables -I FORWARD -o virbr0 -j ACCEPT
iptables -I FORWARD -i virbr0 -j ACCEPT
'';
};
# Packages needed for VM management
environment.systemPackages = with pkgs; [
libvirt
qemu_kvm
virt-manager # optional GUI for manual management
OVMFFull
swtpm
];
# Enable docker in the host (already enabled, but ensure for compose testing)
virtualisation.docker.enable = true;
# Helper script: pr-test-vm
# Usage:
# pr-test-vm build — build the staging VM derivation
# pr-test-vm start — boot the VM with a compose PR branch
# pr-test-vm stop — graceful shutdown
# pr-test-vm destroy — force stop + delete VM
# pr-test-vm ssh — SSH into the running VM
systemd.tmpfiles.rules = mkAfter [
"d ${cfg.dataPath}/scripts 0755 root root -"
];
environment.systemPackages = [ (pkgs.writeShellScriptBin "pr-test-vm" ''
set -euo pipefail
DATA="${cfg.dataPath}"
VM_NAME="${cfg.vmName}"
VM_IMAGE="''${DATA}/''${VM_NAME}.qcow2"
VM_PORT=2223
build_vm() {
echo "==> Building NixOS staging VM for compose testing..."
# Build the VM config inline a minimal NixOS with Docker + SSH
cat > /tmp/staging-vm-config.nix << 'NIXEOF'
{ config, pkgs, lib, ... }: {
boot.loader.grub.devices = [ "/dev/vda" ];
boot.loader.timeout = 0;
# Minimal kernel
boot.kernelParams = [ "console=ttyS0" ];
boot.initrd.kernelModules = [ "virtio_blk" "virtio_net" "virtio_pci" ];
# SSH access
services.openssh = {
enable = true;
settings.PasswordAuthentication = false;
settings.PermitRootLogin = "prohibit-password";
};
# Docker for compose testing
virtualisation.docker.enable = true;
# Network (DHCP via virbr0)
networking.useDHCP = true;
networking.firewall.enable = false;
# Users
users.users.root.openssh.authorizedKeys.keys = [
"$(cat /root/.ssh/authorized_keys 2>/dev/null || echo 'ssh-ed25519 AAAAC3... placeholder')"
];
users.users.testrunner = {
isNormalUser = true;
extraGroups = [ "docker" ];
openssh.authorizedKeys.keys = [
"$(cat /root/.ssh/authorized_keys 2>/dev/null || echo 'ssh-ed25519 AAAAC3... placeholder')"
];
};
# Git + compose tools
environment.systemPackages = with pkgs; [ git docker-compose curl ];
system.stateVersion = "24.11";
}
NIXEOF
nixos-rebuild build-vm -I nixpkgs=channel:nixos-unstable \
--arg configuration 'import /tmp/staging-vm-config.nix' \
--out-link "''${DATA}/vm-result"
echo "==> VM built. Run 'pr-test-vm start' to boot."
}
start_vm() {
if [ -f "''${VM_IMAGE}" ]; then
echo "==> Booting existing VM..."
else
echo "==> Creating VM image..."
${pkgs.qemu_kvm}/bin/qemu-img create -f qcow2 "''${VM_IMAGE}" 20G
fi
# Check if already running
if ${pkgs.libvirt}/bin/virsh list --name 2>/dev/null | grep -q "''${VM_NAME}"; then
echo "==> VM already running."
exit 0
fi
${pkgs.qemu_kvm}/bin/qemu-system-x86_64 \
-name "''${VM_NAME}" \
-machine q35,accel=kvm \
-cpu host \
-smp ${toString cfg.vcpus} \
-m ${cfg.memory} \
-drive file="''${VM_IMAGE}",if=virtio,format=qcow2 \
-netdev user,id=net0,hostfwd=tcp::''${VM_PORT}-:22 \
-device virtio-net-pci,netdev=net0 \
-nographic \
-serial mon:stdio \
-pidfile "''${DATA}/''${VM_NAME}.pid" \
-daemonize
echo "==> VM booting... SSH on port ''${VM_PORT}"
echo "==> Wait for it: ssh -p ''${VM_PORT} testrunner@localhost"
}
stop_vm() {
PIDFILE="''${DATA}/''${VM_NAME}.pid"
if [ -f "''${PIDFILE}" ]; then
PID=$(cat "''${PIDFILE}")
kill "''${PID}" 2>/dev/null || true
rm -f "''${PIDFILE}"
echo "==> VM stopped."
else
${pkgs.libvirt}/bin/virsh destroy "''${VM_NAME}" 2>/dev/null || true
echo "==> VM destroyed."
fi
}
ssh_vm() {
exec ssh -p "''${VM_PORT}" -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null "testrunner@localhost" "$@"
}
# Main dispatch
case "''${1:-help}" in
build) build_vm ;;
start) start_vm ;;
stop) stop_vm ;;
destroy) stop_vm; rm -f "''${VM_IMAGE}"; echo "==> VM deleted." ;;
ssh) shift; ssh_vm "$@" ;;
*)
echo "Usage: pr-test-vm {build|start|stop|destroy|ssh}"
echo ""
echo " build build the NixOS VM derivation"
echo " start boot the VM (create image if needed)"
echo " stop graceful VM shutdown"
echo " destroy stop + delete VM image"
echo " ssh SSH into the running VM"
;;
esac
'') ];
};
}

View File

@@ -0,0 +1,54 @@
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.gortium.wireguard-client;
in
{
##### Options #####
options.gortium.wireguard-client = {
enable = mkEnableOption "WireGuard VPN client to lazyworkhorse VPN server";
vpnIp = mkOption {
type = types.str;
description = "Assigned VPN IP with CIDR, e.g. \"10.8.0.4/24\"";
example = "10.8.0.4/24";
};
privateKeyFile = mkOption {
type = types.path;
description = "Path to the WireGuard private key (age-encrypted, via agenix)";
};
presharedKeyFile = mkOption {
type = types.nullOr types.path;
default = null;
description = "Path to the WireGuard preshared key (optional, age-encrypted)";
};
};
##### Config #####
config = mkIf cfg.enable {
networking.wireguard.interfaces = {
wg0 = {
ips = [ cfg.vpnIp ];
privateKeyFile = cfg.privateKeyFile;
peers = [
{
# Server public key (lazyworkhorse wg-easy)
publicKey = "rY9zII3AOm8rog2rv02PyA3Bq7zdvTOGkZapfCV1DkE=";
presharedKeyFile = cfg.presharedKeyFile;
# Split-tunnel: only route the VPN subnet
allowedIPs = [ "10.8.0.0/24" ];
endpoint = "vpn.lazyworkhorse.net:51820";
persistentKeepalive = 25;
}
];
};
};
environment.systemPackages = with pkgs; [ wireguard-tools ];
};
}

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;
};
};
}

View File

@@ -0,0 +1,19 @@
--- a/drivers/gpu/drm/panel/panel-cwu50.c
+++ b/drivers/gpu/drm/panel/panel-cwu50.c
@@ -58,5 +58,8 @@
dcs_write_seq(0x72,0x06);
dcs_write_seq(0x75,0x03);
+ /* DSI_INIT0: set 4 lanes (bits[1:0]=11) */
+ dcs_write_seq(0x80,0x03);
dcs_write_seq(0xE0,0x01);
+
dcs_write_seq(0x00,0x00);
dcs_write_seq(0x01,0x47);//VCOM0x47
@@ -721,6 +723,6 @@
dsi->lanes = 4;
dsi->format = MIPI_DSI_FMT_RGB888;
- dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST | MIPI_DSI_MODE_VIDEO_SYNC_PULSE;
+ dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_SYNC_PULSE;
ctx->id_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_IN);
if (IS_ERR(ctx->id_gpio)) {

1
result
View File

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

BIN
secrets/builder_key.age Normal file

Binary file not shown.

Binary file not shown.

View File

@@ -1,11 +0,0 @@
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1lZDI1NTE5IEdoTUQ4QSBCWEpO
cG9yNnFpcHFqTkNzTngxU1MxN0NYK0hrZFhUTjVORWFrK3JNd2tZCmtMTGpwQk1E
WlUwL3N6SGRWblpnNEkrWkkyU2hQMkRIK0M3R0pOVEREV3MKLT4gY2osLWdyZWFz
ZSBacSozVVQgUCAxRS1OQSAuKXxDPCoKbStWNW1BZjBZQzNDaTlDbU5EZkxsRWxM
cXJ3dDU1RDNpOXRlV0tzdEp2NUo3S1lhRG5Md0RHTGlJdkFSYmt5YQo4R1hiQWRG
V2VxekJKZwotLS0geG1XSi9VbkhXZHQzcEFVS3hKNzVueXFLa2xnZTc3Q2tJTVZ5
eXJabWk5Ywp6bJCP3s0xxzjE+eTR+cv7ZUnkoliT/n7uIprq1BTn/LIRLkUTUqs3
NiDwrXcoq4/QKd0Dt+8ap3vFAuusjGxRlnYMaRrZie2AGtTV8U7Q7durm9o2K+/4
QzRQ/MtumIQm
-----END AGE ENCRYPTED FILE-----

View File

@@ -12,5 +12,7 @@ in
"home_wifi.age".publicKeys = authorizedKeys; "home_wifi.age".publicKeys = authorizedKeys;
"lazyworkhorse_host_ssh_key.age".publicKeys = authorizedKeys; "lazyworkhorse_host_ssh_key.age".publicKeys = authorizedKeys;
"n8n_ssh_key.age".publicKeys = authorizedKeys; "n8n_ssh_key.age".publicKeys = authorizedKeys;
"openclaw_gateway_token.age".publicKeys = authorizedKeys; "builder_key.age".publicKeys = authorizedKeys ++ [
keys.hosts.uConsole.bootstrap
];
} }

View File

@@ -22,7 +22,6 @@
# - NO access to infra repo (no bind mount) # - NO access to infra repo (no bind mount)
# - NO sudo access (no nh, nixos-rebuild, nixpkgs-fmt, nix) # - NO sudo access (no nh, nixos-rebuild, nixpkgs-fmt, nix)
# WORKFLOW: SSH from Hermes container, run docker benchmarks, return and save results to /opt/data/ai-optimizer/ # WORKFLOW: SSH from Hermes container, run docker benchmarks, return and save results to /opt/data/ai-optimizer/
services.aiWorkerAccess = true;
# Restricted sudo for ai-worker - security checks only # Restricted sudo for ai-worker - security checks only
security.sudo.extraRules = [ security.sudo.extraRules = [

13
users/builder.nix Normal file
View File

@@ -0,0 +1,13 @@
{ config, lib, pkgs, keys, ... }: {
users.users.builder = {
isSystemUser = true;
group = "builder";
home = "/var/empty";
createHome = false;
shell = "${pkgs.shadow}/bin/nologin";
openssh.authorizedKeys.keys = with keys; [
users.builder.main
];
};
users.groups.builder = {};
}

View File

@@ -1,4 +1,7 @@
{ pkgs, inputs, config, keys, ... }: { { pkgs, inputs, config, keys, ... }: {
home-manager.extraSpecialArgs = {
inherit (config.networking) hostName;
};
home-manager.users.gortium = import ./home.nix; home-manager.users.gortium = import ./home.nix;
users.users.gortium = { users.users.gortium = {
isNormalUser = true; isNormalUser = true;
@@ -7,9 +10,10 @@
packages = with pkgs; [ packages = with pkgs; [
tree tree
btop btop
nh
]; ];
shell = pkgs.zsh; shell = pkgs.zsh;
passwordFile = config.age.secrets.gortium_password.path;
ignoreShellProgramCheck = true;
openssh.authorizedKeys.keys = [ openssh.authorizedKeys.keys = [
keys.users.gortium.main keys.users.gortium.main
]; ];

View File

@@ -1,8 +1,8 @@
{ pkgs, lib, config, inputs, ... }: { pkgs, lib, config, inputs, hostName, ... }:
let let
dotfiles = ./assets/dotfiles; isUconsole = hostName == "uConsole";
isUconsole = config.networking.hostName == "uConsole"; dotfiles = ../../assets/dotfiles;
in { in {
home.username = "gortium"; home.username = "gortium";
home.homeDirectory = "/home/gortium"; home.homeDirectory = "/home/gortium";
@@ -10,9 +10,6 @@ in {
programs.home-manager.enable = true; programs.home-manager.enable = true;
home.file = { home.file = {
# zsh
".zshrc".source = "${dotfiles}/zsh/.zshrc";
# tmux # tmux
".tmux.conf".source = "${dotfiles}/tmux/.tmux.conf"; ".tmux.conf".source = "${dotfiles}/tmux/.tmux.conf";
@@ -37,6 +34,9 @@ in {
".config/wofi/config".source = "${dotfiles}/wofi/.config/wofi/config"; ".config/wofi/config".source = "${dotfiles}/wofi/.config/wofi/config";
# yazi # yazi
# wallpapers
".config/wallpapers".source = "${dotfiles}/wallpapers/.config/wallpapers";
".config/yazi/yazi.toml".source = "${dotfiles}/yazi/.config/yazi/yazi.toml"; ".config/yazi/yazi.toml".source = "${dotfiles}/yazi/.config/yazi/yazi.toml";
# hyprland — common config # hyprland — common config
@@ -47,22 +47,45 @@ in {
".config/hypr/mocha.conf".source = "${dotfiles}/hypr/.config/hypr/mocha.conf"; ".config/hypr/mocha.conf".source = "${dotfiles}/hypr/.config/hypr/mocha.conf";
# hyprland — host-specific monitor config # hyprland — host-specific monitor config
".config/hypr/host/monitors.conf".source = ".config/hypr/hosts/monitors.conf".source =
if isUconsole if isUconsole
then "${dotfiles}/hypr/.config/hypr/hosts/uconsole.conf" then "${dotfiles}/hypr/.config/hypr/hosts/uconsole.conf"
else "${dotfiles}/hypr/.config/hypr/hosts/laptop.conf"; else "${dotfiles}/hypr/.config/hypr/hosts/laptop.conf";
}; };
programs.bash.enable = true;
programs.zsh = {
enable = true;
initExtra = builtins.readFile "${dotfiles}/zsh/.zshrc";
};
home.packages = with pkgs; [ home.packages = with pkgs; [
git zsh tmux starship git zsh tmux starship
neovim kitty neovim kitty
btop yazi ripgrep fd fzf btop yazi ripgrep fd fzf
] ++ lib.optionals (!isUconsole) [ htop unzip wget jq
waybar wofi swww hyprshot hyprland hyprlock hypridle hyprpaper
] ++ lib.optionals isUconsole [ waybar wofi dunst
brightnessctl libnotify mako
swaynotificationcenter
swww
emacs
udiskie
hyprshade
networkmanagerapplet
pavucontrol
]; ];
programs.zsh.enable = true; xdg.userDirs = {
programs.starship.enable = true; enable = true;
createDirectories = true;
desktop = "$HOME/desktop";
documents = "$HOME/documents";
download = "$HOME/downloads";
music = "$HOME/music";
pictures = "$HOME/pictures";
publicShare = "$HOME/public";
templates = "$HOME/templates";
videos = "$HOME/videos";
};
} }