Compare commits

..

4 Commits

Author SHA1 Message Date
f4b666284a feat: add Hyperspace Pods NixOS module and enable on lazyworkhorse
Hyperspace Pods let multiple machines pool their GPUs into one private
P2P mesh AI cluster. Models are split across all connected GPUs —
e.g. two machines with 16GB VRAM each can run Qwen 3.5 32B together.

Changes:
- Add modules/nixos/services/hyperspace.nix — NixOS module that:
  * Fetches the Hyperspace CLI binary (v5.45.30) via fetchurl
  * Sets up systemd service for the agent
  * Opens firewall ports (libp2p 4001, chain 30301, API 8080)
  * Configures GPU passthrough for AMD MI50 (ROCm)
- Register module in flake.nix for lazyworkhorse
- Enable hyperspace service on lazyworkhorse (ai-worker user, port 8080)

Usage after deployment:
  hyperspace pod create "tdnde-lab"   # create pod
  hyperspace pod invite                # share invite with cyt-pi
  curl http://localhost:8080/v1/chat/completions  # OpenAI API

See skill: nixos-hyperspace-pods
2026-05-02 15:36:15 +00:00
815ca3afa6 chore: update compose submodule to traefik logging branch 2026-05-02 15:30:28 +00:00
e983775c04 docs: add merge priority order with security hardening as #1 priority
- Updated roadmap phase status (Phase 4 complete)
- Added merge priority table with PR #28 (security) at top
- Documented that security must merge before new services exposed
- Added deployment command reference
2026-05-02 15:30:28 +00:00
Robert
bcf5cadaa0 olllama template fix to remove currenttime 2026-04-30 21:54:47 -04:00
9 changed files with 290 additions and 246 deletions

View File

@@ -13,7 +13,9 @@ None
-**Phase 1: Foundation Setup** - Establish core NixOS configuration with flakes
-**Phase 2: Docker Service Integration** - Integrate Docker Compose services
-**Phase 3: AI Assistant Integration** - Enable AI-assisted infrastructure management
- [ ] **Phase 4: Internet Access & MCP** - MCP server for web access
- **Phase 4: Internet Access & MCP** - MCP server for web access
- 🚨 **Security Hardening** - CRITICAL: Firewall, fail2ban, SSH hardening (PR #28)
- [ ] **Phase 5: TAK Server** - Research, implementation, and validation
## Phase Details
@@ -133,8 +135,25 @@ Plans:
## Progress
**Merge Priority Order** (CRITICAL - merge in this order):
| Priority | PR | Description | Status | Notes |
|----------|-----|-------------|--------|-------|
| 🚨 1 | #28 | **Security hardening** (firewall, fail2ban, SSH) | Open | **MERGE FIRST** - protects all other services |
| 2 | #22 | Matrix bridge dependency fix | Open | Blocks Hermes functionality |
| 3 | #21 | Backup network creation fix | Open | Infrastructure fix |
| 4 | #25 | Hermes voice GPU support | Open | Feature enhancement |
| 5 | #24 | uConsole CM5 host | Open | New hardware support |
| 6 | #23 | NixOS deployment infrastructure | Open | Deployment tooling |
| 7 | #1 | AI worker restricted access | Open | Legacy PR (superseded by hardening) |
**Execution Order:**
Phases execute in numeric order: 1 → 2 → 3 → 4 → 5 → 6 → 7
Phases execute in numeric order: 1 → 2 → 3 → 4 → Security → 5 → 6 → 7
**Merge vs Phase Execution:**
- PRs can merge independently (no strict phase ordering for merges)
- **EXCEPTION:** Security hardening (#28) must merge before any new services are exposed
- After security merge, deploy with: `nh os switch --flake .#lazyworkhorse`
| Phase | Milestone | Plans Complete | Status | Completed |
|-------|-----------|----------------|--------|-----------|

View File

@@ -12,10 +12,6 @@
url = "git+https://git.lix.systems/lix-project/lix?ref=main";
inputs.nixpkgs.follows = "nixpkgs";
};
# uConsole CM5 hardware support
nixos-uconsole.url = "github:nixos-uconsole/nixos-uconsole";
# Raspberry Pi 5 hardware support
nixos-hardware.url = "github:nixos/nixos-hardware/master";
self.submodules = true;
};
@@ -65,6 +61,7 @@
./modules/nixos/services/open_code_server.nix
./modules/nixos/services/ollama_init_custom_models.nix
./modules/nixos/services/openclaw_node.nix
./modules/nixos/services/hyperspace.nix
./users/gortium.nix
./users/ai-worker.nix
];
@@ -83,20 +80,6 @@
./hosts/cyt-pi/hardware-configuration.nix
];
};
uConsole = nixpkgs.lib.nixosSystem {
specialArgs = { inherit self keys paths inputs; };
modules = [
{
nixpkgs.overlays = overlays;
nixpkgs.config.allowUnfree = true;
nixpkgs.hostPlatform = "aarch64-linux";
nix.package = lix.packages."aarch64-linux".default;
}
./hosts/uconsole/configuration.nix
./hosts/uconsole/hardware-configuration.nix
];
};
};
devShells.${system}.default = devShell;
};

View File

@@ -277,6 +277,16 @@
displayName = "lazyworkhorse-host";
};
# Hyperspace Pods — P2P mesh AI cluster (combine GPUs across machines)
services.hyperspace = {
enable = true;
user = "ai-worker";
apiPort = 8080;
profile = "auto";
openFirewall = true;
extraArgs = [ "--verbose" ];
};
# Public host ssh key (kept in sync with the private one)
environment.etc."ssh/ssh_host_ed25519_key.pub".text =
"${keys.hosts.lazyworkhorse.main}";

View File

@@ -1,191 +0,0 @@
{ config, lib, pkgs, paths, self, keys, inputs, ... }:
let
# Reticulum Network Stack - build from PyPI
reticulum = pkgs.python3Packages.buildPythonPackage {
pname = "reticulum";
version = "0.7.0";
format = "pyproject";
src = pkgs.python3Packages.fetchPypi {
pname = "reticulum";
version = "0.7.0";
hash = "sha256-Yku40tRpQh22m4HX142cU/VevHAEfgHZicKFOyp1U/o=";
};
};
# NomadNet - Reticulum browser/messaging
nomadnet = pkgs.python3Packages.buildPythonPackage {
pname = "nomadnet";
version = "0.5.2";
format = "pyproject";
src = pkgs.python3Packages.fetchPypi {
pname = "nomadnet";
version = "0.5.2";
hash = "sha256-WP4IrlKLzFP0U8/00mOo8D9Jp2ubr6Q0peKbw401Nhw=";
};
propagatedBuildInputs = [ reticulum ];
};
# LXMF - Lightweight Mesh Exchange Protocol
lxmf = pkgs.python3Packages.buildPythonPackage {
pname = "lxmf";
version = "0.5.1";
format = "pyproject";
src = pkgs.python3Packages.fetchPypi {
pname = "lxmf";
version = "0.5.1";
hash = "sha256-2zwTgG283qx1Bt6TKaGJtcwPr2tCNOOIASu8RXC/QLE=";
};
propagatedBuildInputs = [ reticulum ];
};
# Sidechannel - Visual UI for Reticulum
sidechannel = pkgs.python3Packages.buildPythonPackage {
pname = "sidechannel";
version = "0.1.0";
format = "pyproject";
src = pkgs.python3Packages.fetchPypi {
pname = "sidechannel";
version = "0.1.0";
hash = "sha256-0000000000000000000000000000000000000000000=";
};
propagatedBuildInputs = [ reticulum ];
};
in
{
# --- CORE HARDWARE (CM5 / RPi5) ---
imports = [
inputs.nixos-uconsole.nixosModules.uconsole
inputs.nixos-hardware.nixosModules.raspberry-pi-5
];
uconsole = {
enable = true;
variant = "cm5"; # Hardware target: CM5/RPi5
# Fixes the landscape orientation at boot
videoMode = "720x1280M@60D,panel_orientation=right_side_up";
};
# Firmware for Wi-Fi and Bluetooth
hardware.enableRedistributableFirmware = true;
hardware.raspberry-pi."5".apply-overlays-dtmerge.enable = true;
# Enable GPU acceleration (VideoCore VII)
hardware.graphics.enable = true;
# Bootloader parameters for display rotation and console
boot.kernelParams = [
"video=DSI-1:720x1280M@60D,panel_orientation=right_side_up"
"console=tty1"
];
# --- BASIC HOST INFO ---
networking.hostName = "uConsole";
networking.networkmanager.enable = true;
time.timeZone = "America/Montreal";
i18n.defaultLocale = "en_CA.UTF-8";
# --- GPS DAEMON ---
services.gpsd = {
enable = true;
devices = [ "/dev/ttyAMA0" ]; # Default port for RPi5/CM5 GPS
nowait = true;
};
# --- USER CONFIGURATION ---
users.users.thierry = {
isNormalUser = true;
description = "Thierry";
extraGroups = [
"wheel" # Sudo
"dialout" # Access to serial/HAM rigs
"plugdev" # Access to USB SDRs
"wireshark" # Packet capture without root
"video" # Hardware acceleration access
"networkmanager"
];
openssh.authorizedKeys.keys = [
keys.users.gortium.main
keys.users.gortium.gitea
];
};
# --- INTERFACE (WAYLAND/SWAY) ---
# Sway is recommended for the uConsole's low resources
programs.sway = {
enable = true;
extraOptions = [ "--unsupported-gpu" ]; # Often needed for RPi
};
# --- SOFTWARE TOOLKITS ---
environment.systemPackages = with pkgs; [
# Base Tools (for your Doom Emacs environment)
emacs-pgtk # Emacs with Wayland support
git # Required for Doom Emacs / Flakes
ripgrep # Fast searching for Emacs/CLI
fd # Better find for Emacs
htop # Resource monitor
tmux # Terminal multiplexer
neovim # Alternative editor
# HAM RADIO (Digital Modes)
js8call # Weak-signal keyboard messaging
wsjtx # FT8, JT65, etc.
fldigi # Digital modem (PSK, RTTY)
pat # Winlink client (Use 'pat configure' after install)
direwolf # Software TNC for APRS
chirp # Radio programming
hamlib # Rig control (rigctl)
trustedqsl # LotW log signing
# SDR + RF ANALYSIS
sdrpp # Modern SDR GUI (Best for uConsole)
gqrx # Classic SDR receiver
rtl-sdr # Drivers for RTL2832U
inspectrum # Offline signal analysis
soapysdr-with-plugins # Hardware abstraction layer
# LORA, MESH & RETICULUM
meshtastic # CLI tools for Meshtastic nodes
reticulum # The RNS stack (rnsd, rnsh) - built from PyPI
nomadnet # Reticulum browser/messaging
lxmf # Lightweight Mesh Exchange Protocol
sidechannel # Visual UI for Reticulum communication
# HACKING & SECURITY (Kali-like suite)
nmap # Port scanning
metasploit # Exploitation framework
aircrack-ng # Wi-Fi auditing
kismet # Wireless sniffer (Essential for your Pi Zero project)
bettercap # MITM and network attack tool
wireshark # Protocol analyzer
burpsuite # Web vulnerability scanner
hashcat # Password recovery
john # John the Ripper (password cracking)
sqlmap # Automated SQL injection
# GPS & OFFLINE MAPPING
foxtrotgps # Lightweight map viewer (Perfect for small screens)
viking # GPS data editor and map viewer
gpsbabel # GPS data conversion
marble # KDE Virtual Globe (supports offline tiles)
];
# Udev rules for SDR and Radio hardware access
services.udev.packages = [
pkgs.rtl-sdr
pkgs.librtlsdr
];
# Enable Wireshark privilege separation
programs.wireshark.enable = true;
# Enable OpenSSH
services.openssh = {
enable = true;
settings.PermitRootLogin = "prohibit-password";
};
# System state version
system.stateVersion = "23.11";
}

View File

@@ -1,30 +0,0 @@
{ config, lib, pkgs, modulesPath, ... }:
{
imports =
[ (modulesPath + "/installer/scan/not-detected.nix")
];
boot.initrd.availableKernelModules = [ "xhci_pci" "usbhid" "sdhci_pci" ];
boot.initrd.kernelModules = [ ];
boot.kernelModules = [ ];
boot.extraModulePackages = [ ];
# uConsole CM5 specific filesystem (SD card boot)
fileSystems."/" =
{ device = "/dev/disk/by-label/NIXOS_SD";
fsType = "ext4";
options = [ "noatime" ];
};
fileSystems."/boot" =
{ device = "/dev/disk/by-label/FIRMWARE";
fsType = "vfat";
};
swapDevices = [ ];
# uConsole CM5 is ARM64
nixpkgs.hostPlatform = lib.mkDefault "aarch64-linux";
hardware.enableRedistributableFirmware = true;
}

View File

@@ -18,9 +18,5 @@
gitea = "";
bootstrap = "age1r796v2uldtspawyh863pks74sd2pwcan8j4e4pjzsvkmr3vjja9qpz5ste";
};
# uConsole CM5 - key to be generated on first boot
uconsole = {
main = "";
};
};
}

View File

@@ -0,0 +1,235 @@
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.services.hyperspace;
# Hyperspace CLI release from github.com/hyperspaceai/aios-cli
# The binary bundles Node.js runtime + llama.cpp + sidecars (~914MB)
# It auto-updates via `hyperspace update` post-install
hyperspacePkg = pkgs.stdenv.mkDerivation rec {
pname = "hyperspace";
version = cfg.release;
src = pkgs.fetchurl {
url = "https://github.com/hyperspaceai/aios-cli/releases/download/v${version}/aios-cli-x86_64-unknown-linux-gnu.tar.gz";
hash = "sha256-f6fJ8t3exqtYwUD5j+WvD+Hm0oN/Eef0X+R9Rj23dE0=";
};
sourceRoot = ".";
installPhase = ''
mkdir -p $out/bin $out/lib/hyperspace
# Main CLI binary
cp aios-cli $out/bin/hyperspace
chmod +x $out/bin/hyperspace
# Sidecar binaries
for f in _aios-cli pod-raft hyperspace-*; do
[ -f "$f" ] && install -m755 "$f" $out/lib/hyperspace/ || true
done
# WASM, native modules, Python shards
cp -r *.wasm $out/lib/hyperspace/ 2>/dev/null || true
cp -r *.node $out/lib/hyperspace/ 2>/dev/null || true
mkdir -p $out/lib/hyperspace/python
cp -r python/* $out/lib/hyperspace/python/ 2>/dev/null || true
# Skills directory
mkdir -p $out/share/hyperspace
cp -r skills $out/share/hyperspace/ 2>/dev/null || true
# Set HYPERSPACE_PATH so the binary finds sidecars
wrapProgram $out/bin/hyperspace \
--set HYPERSPACE_PATH "$out/lib/hyperspace" \
--set HYPERSPACE_SKILLS_DIR "$out/share/hyperspace/skills"
'';
nativeBuildInputs = with pkgs; [ makeWrapper ];
meta = {
description = "Hyperspace CLI P2P mesh AI inference network (Pods)";
longDescription = ''
Hyperspace Pods let multiple machines pool their GPUs into one private
AI cluster. Install the CLI, create a pod, share an invite link your
machines form a P2P mesh and can run models split across all connected
GPUs. Exposes an OpenAI-compatible API for use with Cursor, Claude Code,
Aider, etc.
'';
homepage = "https://hyperspace.sh";
sourceProvenance = with lib; [ sourceTypes.binaryNativeCode ];
license = lib.licenses.unfree;
platforms = [ "x86_64-linux" ];
maintainers = [ ];
};
};
in {
options.services.hyperspace = {
enable = mkEnableOption "Hyperspace P2P AI agent (Pods)";
release = mkOption {
type = types.str;
default = "5.45.30";
description = "Hyperspace CLI release version (from GitHub releases).";
};
user = mkOption {
type = types.str;
default = "ai-worker";
description = "System user to run the Hyperspace agent.";
};
apiPort = mkOption {
type = types.port;
default = 8080;
description = "Port for the OpenAI-compatible API server.";
};
autoStart = mkOption {
type = types.bool;
default = true;
description = "Auto-start the Hyperspace agent on boot.";
};
openFirewall = mkOption {
type = types.bool;
default = true;
description = "Open firewall ports for P2P traffic (libp2p 4001, chain 30301, API).";
};
profile = mkOption {
type = types.enum [ "auto" "full" "inference" "embedding" "relay" "storage" ];
default = "auto";
description = ''
Agent profile:
- auto: auto-detect hardware
- full: all 9 capabilities
- inference: GPU inference only
- embedding: CPU embedding only
- relay: lightweight relay
- storage: storage + memory
'';
};
extraArgs = mkOption {
type = types.listOf types.str;
default = [ ];
description = "Extra arguments passed to `hyperspace start`.";
};
dataDir = mkOption {
type = types.str;
default = "/var/lib/hyperspace";
description = "Data directory for agent state (models, config, logs).";
};
};
config = mkIf cfg.enable {
# Ensure the service user exists
users.users.${cfg.user} = {
isSystemUser = true;
group = cfg.user;
home = "/home/${cfg.user}";
createHome = true;
shell = pkgs.bash;
};
users.groups.${cfg.user} = { };
# Install the hyperspace binary
environment.systemPackages = [ hyperspacePkg ];
# Data directories
systemd.tmpfiles.rules = [
"d ${cfg.dataDir} 0755 ${cfg.user} ${cfg.user} -"
"d ${cfg.dataDir}/models 0755 ${cfg.user} ${cfg.user} -"
"d ${cfg.dataDir}/data 0755 ${cfg.user} ${cfg.user} -"
];
# Systemd service: runs the Hyperspace agent as a system daemon
systemd.services.hyperspace = {
description = "Hyperspace P2P AI Agent Pods mesh cluster";
documentation = [ "https://hyperspace.sh" "https://github.com/hyperspaceai/aios-cli" ];
after = [ "network-online.target" ];
wants = [ "network-online.target" ];
wantedBy = mkIf cfg.autoStart [ "multi-user.target" ];
environment = {
HYPERSPACE_HOME = cfg.dataDir;
HYPERSPACE_API_PORT = toString cfg.apiPort;
HYPERSPACE_PATH = "${hyperspacePkg}/lib/hyperspace";
};
path = with pkgs; [ bash curl nodejs ];
script = ''
# Wait for network connectivity before starting
${pkgs.bash}/bin/bash -c '
for i in $(seq 1 30); do
ping -c 1 -W 1 8.8.8.8 >/dev/null 2>&1 && break
sleep 2
done
' || true
exec ${hyperspacePkg}/bin/hyperspace start \
--profile ${cfg.profile} \
--api-port ${toString cfg.apiPort} \
${lib.escapeShellArgs cfg.extraArgs}
'';
serviceConfig = {
Type = "exec";
User = cfg.user;
Group = cfg.user;
WorkingDirectory = cfg.dataDir;
Restart = "always";
RestartSec = 10;
TimeoutStartSec = 180;
TimeoutStopSec = 30;
KillMode = "mixed";
# File limits for network-heavy P2P agent
LimitNOFILE = 65536;
LimitNPROC = 4096;
# GPU access — AMD MI50 (ROCm) through /dev/kfd and /dev/dri
DeviceAllow = [
"/dev/kfd" "rw"
"/dev/dri" "rw"
];
SupplementaryGroups = [ "video" "render" ];
# Security hardening
NoNewPrivileges = true;
ProtectSystem = "strict";
ProtectHome = true;
PrivateTmp = true;
PrivateDevices = false; # needs GPU access
ReadWritePaths = [
cfg.dataDir
"/tmp"
];
BindPaths = [
# GPU devices for AMD MI50
"/dev/kfd"
"/dev/dri"
];
};
};
# Firewall: open P2P ports for the mesh network
networking.firewall = mkIf cfg.openFirewall {
allowedTCPPorts = [
4001 # libp2p P2P (agent gossip, DHT, circuits)
30301 # Chain P2P (blockchain consensus)
cfg.apiPort # OpenAI-compatible API
];
allowedUDPPorts = [
4001 # libp2p QUIC transport
30301 # Chain UDP discovery
];
};
};
}

View File

@@ -14,8 +14,25 @@
local base_model=$2
if ! ${pkgs.docker}/bin/docker exec ollama ollama list | grep -q "$model_name"; then
echo "$model_name not found, creating from $base_model..."
# We use a custom TEMPLATE block to strip the 'currentDate' function
# which is unsupported in Ollama 0.5.7 but present in Devstral's default manifest.
${pkgs.docker}/bin/docker exec ollama sh -c "cat <<EOF > /root/.ollama/$model_name.modelfile
FROM $base_model
TEMPLATE \"\"\"{{- if .System }}
[SYSTEM_PROMPT]
{{ .System }}
[/SYSTEM_PROMPT]
{{- end }}
{{- range .Messages }}
{{- if eq .Role \"user\" }}
[INST]
{{ .Content }}
[/INST]
{{- else if eq .Role \"assistant\" }}
{{ .Content }}
{{- end }}
{{- end }}\"\"\"
PARAMETER num_ctx 131072
PARAMETER num_predict 4096
PARAMETER num_keep 1024
@@ -26,6 +43,7 @@ PARAMETER stop \"[/INST]\"
PARAMETER stop \"</s>\"
EOF"
${pkgs.docker}/bin/docker exec ollama ollama create "$model_name" -f "/root/.ollama/$model_name.modelfile"
${pkgs.docker}/bin/docker exec ollama rm "/root/.ollama/$model_name.modelfile"
else
echo "$model_name already exists, skipping."
fi
@@ -36,6 +54,10 @@ EOF"
# Create Devstral
create_model_if_missing "devstral-small-2:24b-128k" "devstral-small-2:24b"
# create_model_if_missing "qwen2.5-coder:32b-128k" "qwen2.5-coder:32b"
# create_model_if_missing "mistral-large-planner:123b" "mistral-large:123b-instruct-v2407-q4_K_S"
'';
serviceConfig = {
Type = "oneshot";