Compare commits

..

1 Commits

Author SHA1 Message Date
8b9a144254 fix: update compose submodule for Matrix bridge deps + persistent venv
Updates assets/compose submodule to 8adbbf0 (compose fix/matrix-bridge-deps).

Compose commit adds:
- uv pip install openai mautrix[encryption] to hermes entrypoint
- Persistent venv volume at /opt/hermes/.venv
- Empty-volume first-boot handling (venv recreation)

This ensures Matrix bridge dependencies survive container recreation.
2026-05-20 14:36:42 -04:00
7 changed files with 91 additions and 296 deletions

30
flake.nix Executable file → Normal file
View File

@@ -2,34 +2,28 @@
description = "Gortium infra flake"; description = "Gortium infra flake";
inputs = { inputs = {
nixpkgs.url = "github:nixos/nixpkgs?ref=25.11"; nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";
agenix = { agenix = {
url = "github:ryantm/agenix"; url = "github:ryantm/agenix";
inputs.darwin.follows = ""; inputs.darwin.follows = "";
inputs.nixpkgs.follows = "nixpkgs"; inputs.nixpkgs.follows = "nixpkgs";
}; };
disko = {
url = "github:nix-community/disko";
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";
}; };
nixpkgs-uconsole = {
url = "github:nixos/nixpkgs/54170c54449ea4d6725efd30d719c5e505f1c10e";
};
nixos-uconsole = { nixos-uconsole = {
url = "github:nixos-uconsole/nixos-uconsole/v1.1.0"; url = "github:nixos-uconsole/nixos-uconsole";
inputs.nixpkgs.follows = "nixpkgs-uconsole"; inputs.nixpkgs.follows = "nixpkgs";
}; };
nixos-raspberrypi = { nixos-raspberrypi = {
url = "github:nvmd/nixos-raspberrypi/v1.20260317.0"; url = "github:nvmd/nixos-raspberrypi/v1.20260317.0";
inputs.nixpkgs.follows = "nixos-uconsole/nixpkgs"; inputs.nixpkgs.follows = "nixpkgs";
}; };
self.submodules = true;
}; };
outputs = { self, nixpkgs, agenix, disko, lix, nixos-uconsole, nixos-raspberrypi, ... }@inputs: outputs = { self, nixpkgs, agenix, lix, nixos-uconsole, nixos-raspberrypi, ... }@inputs:
let let
system = "x86_64-linux"; system = "x86_64-linux";
keys = import ./lib/keys.nix; keys = import ./lib/keys.nix;
@@ -95,21 +89,19 @@
]; ];
}; };
uConsole = nixos-uconsole.lib.mkUConsoleSystem { uConsole = nixos-raspberrypi.lib.nixosSystem {
variant = "cm5";
specialArgs = { inherit self keys paths inputs nixos-raspberrypi; }; specialArgs = { inherit self keys paths inputs nixos-raspberrypi; };
modules = [ modules = [
{ {
nixpkgs.overlays = overlays; nixpkgs.overlays = overlays;
nixpkgs.config.allowUnfree = true; nixpkgs.config.allowUnfree = true;
nixpkgs.config.permittedInsecurePackages = [ nixpkgs.hostPlatform = "aarch64-linux";
"openclaw-2026.3.12" nix.package = lix.packages."aarch64-linux".default;
];
} }
disko.nixosModules.disko nixos-raspberrypi.nixosModules.raspberry-pi-5.base
nixos-uconsole.nixosModules.uconsole-cm5
./hosts/uConsole/configuration.nix ./hosts/uConsole/configuration.nix
./hosts/uConsole/hardware-configuration.nix ./hosts/uConsole/hardware-configuration.nix
./hosts/uConsole/disko-config.nix
]; ];
}; };
}; };

View File

@@ -11,10 +11,6 @@
# Flakesss # Flakesss
nix.settings.experimental-features = [ "nix-command" "flakes" "flake-self-attrs" ]; 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 = {

135
hosts/uConsole/configuration.nix Executable file → Normal file
View File

@@ -1,18 +1,4 @@
{ config, lib, pkgs, paths, self, keys, ... }: { config, lib, pkgs, paths, self, ... }:
let
# Backlight fallback for CM5 display quirk
# The kernel driver usually handles this, but some boots need a kick
backlightFixScript = pkgs.writeShellScript "backlight-fix" ''
# Try sysfs backlight control
for bl in /sys/class/backlight/*/brightness; do
if [ -f "$bl" ]; then
max=$(cat "$(dirname "$bl")/max_brightness" 2>/dev/null || echo 100)
echo "$max" > "$bl" 2>/dev/null || true
fi
done
'';
in
{ {
# Basic Host Info # Basic Host Info
@@ -21,37 +7,17 @@ in
i18n.defaultLocale = "en_CA.UTF-8"; i18n.defaultLocale = "en_CA.UTF-8";
# System State # System State
system.stateVersion = "25.11"; system.stateVersion = "25.05";
# Boot & Hardware (migrated to kernel bootloader per nixos-raspberrypi deprecation notice) # Boot & Hardware (uconsole-cm5 module handles boot.loader)
boot.loader.raspberry-pi.bootloader = "kernel"; boot.kernelPackages = pkgs.linuxPackages_latest;
# kernel managed by nixos-raspberrypi module — don't override, patches are version-specific
# boot.kernelPackages = pkgs.linuxPackages_latest;
# Kernel parameters matching nixos-uconsole CM5 module
# console=tty1 is critical — without it, console output goes to ttyAMA0 not fb0
boot.kernelParams = [
"8250.nr_uarts=1"
"console=tty1"
];
# Enable Mesa GPU drivers — REQUIRED for VC4 display pipeline to initialize
hardware.graphics.enable = true;
# Console font sized for the 5" 720x1280 display (from nixos-uconsole base module)
console = {
earlySetup = true;
font = "ter-v24n";
packages = with pkgs; [ terminus_font ];
};
# Networking # Networking
networking.networkmanager.enable = true; networking.networkmanager.enable = true;
services.openssh = { services.openssh = {
enable = true; enable = true;
# TODO: lock down after first deployment settings.PermitRootLogin = "prohibit-password";
settings.PermitRootLogin = lib.mkForce "yes"; settings.PasswordAuthentication = false;
settings.PasswordAuthentication = lib.mkForce true;
}; };
# User # User
@@ -88,7 +54,6 @@ in
htop htop
tmux tmux
neovim neovim
libgpiod # GPIO control (for internal USB hub, AIO modules)
# ===== HAM Radio ===== # ===== HAM Radio =====
js8call js8call
@@ -108,8 +73,7 @@ in
soapysdr-with-plugins # SoapySDR + hardware support plugins soapysdr-with-plugins # SoapySDR + hardware support plugins
# ===== Mesh / LoRa ===== # ===== Mesh / LoRa =====
# meshtastic not available in nixpkgs 25.11 stable; install manually: meshtastic # Python CLI for Meshtastic devices
# nix shell nixpkgs#meshtastic -c meshtastic
reticulumStack # Reticulum Network Stack (rnsd, rnsh, rncp, rnx, rnpath, etc.) reticulumStack # Reticulum Network Stack (rnsd, rnsh, rncp, rnx, rnpath, etc.)
lxmf # LXMF messaging protocol lxmf # LXMF messaging protocol
nomadnet # Nomad Network client nomadnet # Nomad Network client
@@ -142,7 +106,6 @@ in
# ============================================================ # ============================================================
systemd.services.rnsd = { systemd.services.rnsd = {
description = "Reticulum Network Stack Daemon"; description = "Reticulum Network Stack Daemon";
wants = [ "network-online.target" ];
after = [ "network-online.target" ]; after = [ "network-online.target" ];
wantedBy = [ "multi-user.target" ]; wantedBy = [ "multi-user.target" ];
serviceConfig = { serviceConfig = {
@@ -160,7 +123,6 @@ in
# ============================================================ # ============================================================
systemd.services.kismet = { systemd.services.kismet = {
description = "Kismet Wi-Fi Monitor & IDS"; description = "Kismet Wi-Fi Monitor & IDS";
wants = [ "network-online.target" ];
after = [ "network-online.target" ]; after = [ "network-online.target" ];
wantedBy = [ "multi-user.target" ]; wantedBy = [ "multi-user.target" ];
serviceConfig = { serviceConfig = {
@@ -173,33 +135,22 @@ in
}; };
# ============================================================ # ============================================================
# Kernel modules for SDR, radio, and WiFi # Kernel modules for SDR and radio
# ============================================================ # ============================================================
boot.kernelModules = [ boot.kernelModules = [
"mt7921u" # MediaTek MT7921 USB WiFi (uConsole AC1200)
"88x2bu" # Realtek 8812/8821BU USB WiFi (common adapter) "88x2bu" # Realtek 8812/8821BU USB WiFi (common adapter)
"rtl8xxxu" # RTL8188/8192/8723 USB WiFi "rtl8xxxu" # RTL8188/8192/8723 USB WiFi
"rtl2832_sdr" # RTL-SDR kernel module "rtl2832_sdr" # RTL-SDR kernel module
"dvb_usb_rtl28xxu" # RTL-SDR DVB-T "dvb_usb_rtl28xxu" # RTL-SDR DVB-T
# Display drivers — loaded AFTER RP1 PCIe southbridge init (~12s)
# NOTHING in initrd — ALL RP1 hardware is behind PCIe
"panel_cwu50" # uConsole DSI panel driver
"vc4" # VideoCore 4 KMS GPU driver
"rp1_dsi" # RP1 DSI bridge driver
]; ];
boot.blacklistedKernelModules = [ ]; boot.blacklistedKernelModules = [ ];
# Rien dans initrd pour le display — tout RP1 est derrière PCIe
boot.initrd.kernelModules = lib.mkForce [ ];
# ============================================================ # ============================================================
# Extra udev rules for SDR and HAM radio devices # Extra udev rules for SDR and HAM radio devices
# ============================================================ # ============================================================
services.udev.packages = with pkgs; [ rtl-sdr ]; services.udev.packages = with pkgs; [ rtl-sdr ];
# ============================================================ # ============================================================
# Enable IPv6 for Reticulum mesh # Enable IPv6 for Reticulum mesh
# ============================================================ # ============================================================
@@ -213,74 +164,4 @@ in
# Reticulum uses its own encryption and doesn't need open ports # Reticulum uses its own encryption and doesn't need open ports
# for basic mesh operations (peer-to-peer discovery). # for basic mesh operations (peer-to-peer discovery).
# For TCP interfaces, open additional ports as needed. # For TCP interfaces, open additional ports as needed.
# ============================================================
# Hyprland Wayland compositor (manual start)
# No SDDM — boot to console, user starts Hyprland with command
# Display modules (vc4/panel_cwu50) load late after RP1 PCIe init
# ============================================================
programs.hyprland = {
enable = true;
xwayland.enable = true;
};
# SDDM disabled — was blocking boot when display isn't ready
# services.displayManager.sddm = {
# enable = true;
# wayland.enable = true;
# };
# ============================================================
# CM5 Config.txt Fix: use [pi5] section (not [cm5])
# Rex's images use [pi5], the CM5 firmware may not detect [cm5]
# ============================================================
# Merge nixos-uconsole GPIO config with our [pi5] overrides
# GPIO 10/11 are from nixos-uconsole configtxt.nix (audio amplifier)
# [pi5] section fixes the CM5 detection issue — firmware matches [pi5] not [cm5]
hardware.raspberry-pi.extra-config = ''
[all]
gpio=10=ip,np
gpio=11=op,dh
[pi5]
dtparam=pciex1=off
dtoverlay=clockworkpi-uconsole-cm5
dtoverlay=dwc2,dr_mode=host
dtoverlay=vc4-kms-v3d-pi5,cma-384
dtparam=nohdmi1=off
'';
# ============================================================
# CM5 Display Backlight Fix
# The kernel driver initializes backlight, but some boots fail.
# This service kicks it after boot as a reliable fallback.
# ============================================================
systemd.services.cm5-backlight-fix = {
description = "CM5 Display Backlight Fix";
after = [ "multi-user.target" ];
wantedBy = [ "multi-user.target" ];
serviceConfig = {
Type = "oneshot";
ExecStart = "${backlightFixScript}";
};
};
# ============================================================
# Internal USB Hub Enable (GPIO 23) — DISABLED
# This service freeze the CM5 because gpioset 0 23=1 writes
# to the wrong GPIO chip (BCM2712 native, not RP1).
# Enable manually after boot once the correct chip is confirmed:
# gpioset 0 23=1 # on chip 0 (BCM2712, CORE_VOLT or critical)
# gpioset 512 23=1 # on chip 512 (RP1, likely correct)
# ============================================================
# systemd.services.enable-gpio23-usb-hub = {
# description = "Enable Internal USB Hub (GPIO 23)";
# before = [ "network.target" ];
# wantedBy = [ "multi-user.target" ];
# serviceConfig = {
# Type = "oneshot";
# RemainAfterExit = true;
# ExecStart = "${pkgs.libgpiod}/bin/gpioset 0 23=1";
# ExecStop = "${pkgs.libgpiod}/bin/gpioset 0 23=0";
# };
# };
} }

View File

@@ -1,46 +0,0 @@
{ lib, ... }:
{
disko.devices.disk.main = {
type = "disk";
device = "/dev/mmcblk0";
content = {
type = "gpt";
partitions = {
boot = {
name = "FIRMWARE";
size = "1G";
type = "EF00";
content = {
type = "filesystem";
format = "vfat";
mountpoint = "/boot/firmware";
mountOptions = [
"fmask=0022"
"dmask=0022"
];
};
};
root = {
name = "NIXOS_UCM5";
size = "30G";
content = {
type = "filesystem";
format = "ext4";
mountpoint = "/";
mountOptions = [ "noatime" ];
};
};
home = {
name = "NIXOS_HOME";
size = "100%";
content = {
type = "filesystem";
format = "ext4";
mountpoint = "/home";
mountOptions = [ "noatime" ];
};
};
};
};
};
}

View File

@@ -9,28 +9,15 @@
boot.initrd.kernelModules = [ ]; boot.initrd.kernelModules = [ ];
boot.extraModulePackages = [ ]; boot.extraModulePackages = [ ];
# Filesystems for NixOS install. # uConsole CM5 uses NVMe or eMMC for boot storage
# mkForce overrides disko's auto-generated paths so we can use # The uconsole-cm5 module sets up /boot/firmware and default /
# filesystem labels (by-label) which work with loop device installs. # Override device label here if using different storage
# Disko will set its own paths when nixos-anywhere is used. fileSystems."/" = lib.mkDefault {
fileSystems."/" = lib.mkForce {
device = "/dev/disk/by-label/NIXOS_UCM5"; device = "/dev/disk/by-label/NIXOS_UCM5";
fsType = "ext4"; fsType = "ext4";
options = [ "noatime" ]; options = [ "noatime" ];
}; };
fileSystems."/boot/firmware" = lib.mkForce {
device = "/dev/disk/by-label/FIRMWARE";
fsType = "vfat";
options = [ "fmask=0022" "dmask=0022" ];
};
fileSystems."/home" = lib.mkForce {
device = "/dev/disk/by-label/NIXOS_HOME";
fsType = "ext4";
options = [ "noatime" ];
};
swapDevices = [ ]; swapDevices = [ ];
nixpkgs.hostPlatform = lib.mkDefault "aarch64-linux"; nixpkgs.hostPlatform = lib.mkDefault "aarch64-linux";

149
overlays/reticulum.nix Executable file → Normal file
View File

@@ -1,92 +1,77 @@
final: prev: let final: prev: let
python3 = final.python3; python3 = final.python3;
pyPkgs = python3.pkgs; pyPkgs = python3.pkgs;
in in {
{ reticulumStack = python3.pkgs.buildPythonApplication rec {
reticulumStack = python3.pkgs.buildPythonApplication rec { pname = "reticulum";
pname = "reticulum"; version = "1.2.9";
version = "1.2.9"; src = pyPkgs.fetchPypi {
format = "setuptools"; pname = "rns";
src = pyPkgs.fetchPypi { inherit version;
pname = "rns"; sha256 = "554814231c237b9caacf8df669312e57dd7d3f84b6d4810125087d1a79a75d75";
inherit version;
sha256 = "554814231c237b9caacf8df669312e57dd7d3f84b6d4810125087d1a79a75d75";
};
patchPhase = ''
# Fix license_files syntax: ("LICENSE") is a string not tuple
# Newer setuptools iterates over it char by char, fails on 'S'
substituteInPlace setup.py \
--replace-fail 'license_files = ("LICENSE")' 'license_files = ("LICENSE",)'
'';
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;
};
}; };
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 { lxmf = python3.pkgs.buildPythonApplication rec {
pname = "lxmf"; pname = "lxmf";
version = "0.9.8"; version = "0.9.8";
format = "setuptools"; src = pyPkgs.fetchPypi {
src = pyPkgs.fetchPypi { inherit pname version;
inherit pname version; sha256 = "30f39f3a975a049c12ee2cfceb3261d24cb5adec881c6821f7354464b3f3650c";
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;
};
}; };
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 { nomadnet = python3.pkgs.buildPythonApplication rec {
pname = "nomadnet"; pname = "nomadnet";
version = "1.1.1"; version = "1.1.1";
format = "setuptools"; src = pyPkgs.fetchPypi {
src = pyPkgs.fetchPypi { inherit pname version;
inherit pname version; sha256 = "fa13b64a10e75b705a58024815ab72451700aa726af96d415ba99dec28dfc40a";
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;
};
}; };
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 { rnsh = python3.pkgs.buildPythonApplication rec {
pname = "rnsh"; pname = "rnsh";
version = "0.1.7"; version = "0.1.7";
format = "setuptools"; src = pyPkgs.fetchPypi {
src = pyPkgs.fetchPypi { inherit pname version;
inherit pname version; sha256 = "9cb72f25abb1c6d300f8014b264184ff78f592fe88e36094938012990b797c93";
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;
};
}; };
} propagatedBuildInputs = [ final.reticulumStack ];
# meshtastic may not exist in all nixpkgs versions (e.g. not in 25.11) doCheck = false;
// prev.lib.optionalAttrs (prev ? meshtastic) { pythonImportsCheck = [ "rnsh" ];
inherit (prev) meshtastic; meta = with final.lib; {
} description = "Remote shell over Reticulum";
homepage = "https://github.com/acehoss/rnsh";
license = licenses.mit;
platforms = platforms.linux;
};
};
}