diff --git a/.gitmodules b/.gitmodules index bd90853..66f7a80 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,7 @@ [submodule "assets/compose"] path = assets/compose url = ssh://git@code.lazyworkhorse.net:2222/gortium/compose.git +[submodule "assets/dotfiles"] + path = assets/dotfiles + url = ssh://git@code.lazyworkhorse.net:2222/gortium/dotfiles.git + branch = master diff --git a/assets/compose b/assets/compose index 3c92d93..dab158d 160000 --- a/assets/compose +++ b/assets/compose @@ -1 +1 @@ -Subproject commit 3c92d93366bcf301878f83bcdec6b6de7246d652 +Subproject commit dab158da0a869262f227d4d35451960ae11b6642 diff --git a/assets/dotfiles b/assets/dotfiles new file mode 160000 index 0000000..f453874 --- /dev/null +++ b/assets/dotfiles @@ -0,0 +1 @@ +Subproject commit f45387456b4d1f594e2d623366248ece64f50b25 diff --git a/assets/ollama/Dockerfile b/assets/ollama/Dockerfile deleted file mode 100644 index 438e607..0000000 --- a/assets/ollama/Dockerfile +++ /dev/null @@ -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"] diff --git a/flake.lock b/flake.lock index e78ed64..2f54d5c 100644 --- a/flake.lock +++ b/flake.lock @@ -23,6 +23,22 @@ "type": "github" } }, + "argononed": { + "flake": false, + "locked": { + "lastModified": 1729566243, + "narHash": "sha256-DPNI0Dpk5aym3Baf5UbEe5GENDrSmmXVdriRSWE+rgk=", + "owner": "nvmd", + "repo": "argononed", + "rev": "16dbee54d49b66d5654d228d1061246b440ef7cf", + "type": "github" + }, + "original": { + "owner": "nvmd", + "repo": "argononed", + "type": "github" + } + }, "flake-compat": { "flake": false, "locked": { @@ -37,6 +53,21 @@ "url": "https://git.lix.systems/lix-project/flake-compat/archive/main.tar.gz" } }, + "flake-compat_2": { + "locked": { + "lastModified": 1767039857, + "narHash": "sha256-vNpUSpF5Nuw8xvDLj2KCwwksIbjua2LZCqhV1LNRDns=", + "owner": "edolstra", + "repo": "flake-compat", + "rev": "5edf11c44bc78a0d334f6334cdaf7d60d732daab", + "type": "github" + }, + "original": { + "owner": "edolstra", + "repo": "flake-compat", + "type": "github" + } + }, "home-manager": { "inputs": { "nixpkgs": [ @@ -144,6 +175,80 @@ "type": "github" } }, + "nixos-images": { + "inputs": { + "nixos-stable": [ + "nixos-raspberrypi", + "nixpkgs" + ], + "nixos-unstable": [ + "nixos-raspberrypi", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1747747741, + "narHash": "sha256-LUOH27unNWbGTvZFitHonraNx0JF/55h30r9WxqrznM=", + "owner": "nvmd", + "repo": "nixos-images", + "rev": "cbbd6db325775096680b65e2a32fb6187c09bbb4", + "type": "github" + }, + "original": { + "owner": "nvmd", + "ref": "sdimage-installer", + "repo": "nixos-images", + "type": "github" + } + }, + "nixos-raspberrypi": { + "inputs": { + "argononed": "argononed", + "flake-compat": "flake-compat_2", + "nixos-images": "nixos-images", + "nixpkgs": [ + "nixpkgs-uconsole" + ] + }, + "locked": { + "lastModified": 1781324200, + "narHash": "sha256-JWqxN2Yle86+4Q+GFh12SvB92ZyLeqalVsN9lfMh6eQ=", + "owner": "gortium", + "repo": "nixos-raspberrypi", + "rev": "721a6e9e67dca3a23133db650b87018646bca3e6", + "type": "github" + }, + "original": { + "owner": "gortium", + "ref": "cm5-cross-v1", + "repo": "nixos-raspberrypi", + "type": "github" + } + }, + "nixos-uconsole": { + "inputs": { + "nixos-raspberrypi": [ + "nixos-raspberrypi" + ], + "nixpkgs": [ + "nixpkgs-uconsole" + ] + }, + "locked": { + "lastModified": 1781476310, + "narHash": "sha256-jY6ujqLXNAWJGvt+pAuw1Wg/OiHRGd1B1Z7Czhiq7Q4=", + "owner": "gortium", + "repo": "nixos-uconsole", + "rev": "38a7fcbffbf2d2e122bc1e1c634fe25f66ecda13", + "type": "github" + }, + "original": { + "owner": "gortium", + "ref": "pr/dcs-panel-detection", + "repo": "nixos-uconsole", + "type": "github" + } + }, "nixpkgs": { "locked": { "lastModified": 1705033721, @@ -176,6 +281,22 @@ "type": "github" } }, + "nixpkgs-uconsole": { + "locked": { + "lastModified": 1780952837, + "narHash": "sha256-Fwd1+spDtQ0hDyBwme6ufG3n4mY0UrjjFdYHv+G/Hds=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "e820eb4a444b46a19b2e03e8dfd2359439ff30fe", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-25.11", + "repo": "nixpkgs", + "type": "github" + } + }, "nixpkgs_2": { "locked": { "lastModified": 1774386573, @@ -212,7 +333,10 @@ "inputs": { "agenix": "agenix", "lix": "lix", - "nixpkgs": "nixpkgs_2" + "nixos-raspberrypi": "nixos-raspberrypi", + "nixos-uconsole": "nixos-uconsole", + "nixpkgs": "nixpkgs_2", + "nixpkgs-uconsole": "nixpkgs-uconsole" } }, "systems": { diff --git a/flake.nix b/flake.nix index f0971ef..4246e8b 100644 --- a/flake.nix +++ b/flake.nix @@ -14,7 +14,7 @@ }; nixpkgs-uconsole.url = "github:NixOS/nixpkgs/nixos-25.11"; nixos-uconsole = { - url = "github:nixos-uconsole/nixos-uconsole/v1.1.0"; + url = "github:gortium/nixos-uconsole/pr/dcs-panel-detection"; inputs.nixpkgs.follows = "nixpkgs-uconsole"; inputs.nixos-raspberrypi.follows = "nixos-raspberrypi"; }; @@ -46,6 +46,84 @@ devShell = import ./shells/nix_dev.nix { inherit pkgs system agenix; }; + + # Cross-compile overlay fixes for Hyprland and deps on aarch64 + uconsoleCrossOverlay = final: prev: { + libcamera = prev.libcamera.overrideAttrs (_: { meta.platforms = []; }); + libcamera-rpi = prev.libcamera-rpi.overrideAttrs (_: { meta.platforms = []; }); + libpisp = prev.libpisp.overrideAttrs (_: { meta.platforms = []; }); + pipewire = prev.pipewire.overrideAttrs (old: { + buildInputs = builtins.filter + (x: !(x?pname && x.pname == "libcamera")) + (old.buildInputs or []); + mesonFlags = builtins.filter + (flag: !(builtins.isString flag && builtins.match ".*libcamera.*" flag != null)) + (old.mesonFlags or []) ++ [ "-Dlibcamera=disabled" ]; + }); + gjs = prev.gjs.overrideAttrs (old: { + mesonFlags = (old.mesonFlags or []) ++ [ "-Dskip_gtk_tests=true" ]; + }); + hyprland = prev.hyprland.override { wrapRuntimeDeps = false; }; + xdg-desktop-portal-hyprland = prev.xdg-desktop-portal-hyprland.overrideAttrs (old: { + preConfigure = (old.preConfigure or "") + '' + cmakeFlags="$cmakeFlags -Dhyprwayland-scanner_DIR=${prev.buildPackages.hyprwayland-scanner}/lib/cmake/hyprwayland-scanner" 2>/dev/null || true + export PKG_CONFIG_PATH="${prev.buildPackages.hyprwayland-scanner}/lib/pkgconfig:$PKG_CONFIG_PATH" + ''; + }); + }; + + # RPI-specific pipewire libcamera fix (separate nixpkgs instance) + uconsoleRpiPipewireOverlay = final: prev: { + pipewire = prev.pipewire.overrideAttrs (old: { + buildInputs = builtins.filter + (x: !(x?pname && x.pname == "libcamera")) + (old.buildInputs or []); + mesonFlags = builtins.filter + (flag: !(builtins.isString flag && builtins.match ".*libcamera.*" flag != null)) + (old.mesonFlags or []) ++ [ "-Dlibcamera=disabled" ]; + }); + }; + + # Shared uConsole CM5 module set — used by both toplevel and SD image + uconsoleBaseModules = [ + { + nixpkgs.buildPlatform = "x86_64-linux"; + nixpkgs.hostPlatform = "aarch64-linux"; + nixpkgs.config.allowUnfree = true; + boot.loader.raspberry-pi.bootloader = "kernel"; + nixpkgs.overlays = [ uconsoleCrossOverlay ]; + } + nixos-raspberrypi.nixosModules.nixpkgs-rpi + ({ config, lib, pkgs, ... }: { + nixpkgs.overlays = [ uconsoleRpiPipewireOverlay ]; + }) + # Fix old panel init_sequence: DCS read + DSI_INIT0 lane config + ({ lib, ... }: { + boot.kernelPatches = [{ + name = "panel-cwu50-fix-lanes"; + patch = ./patches/0008-panel-cwu50-fix-init-seq1.patch; + }]; + }) + nixos-raspberrypi.nixosModules.raspberry-pi-5.base + nixos-raspberrypi.lib.inject-overlays + nixos-raspberrypi.lib.inject-overlays-global + nixos-uconsole.nixosModules.uconsole-cm5 + # Cross-compiled Lix for uConsole + ({ config, lib, pkgs, inputs, ... }: let + lixCross = import inputs.nixpkgs-uconsole { + localSystem = { system = "x86_64-linux"; }; + crossSystem = { system = "aarch64-linux"; }; + overlays = [ inputs.lix.overlays.default ]; + }; + in { nix.package = lixCross.lix; }) + agenix.nixosModules.default + ./hosts/uconsole-cm5/configuration.nix + ./hosts/uconsole-cm5/hardware-configuration.nix + ./modules/nixos/services/wireguard-client.nix + ./modules/nixos/hardware/uconsole-cm5-aio-v2.nix + ./users/gortium/gortium.nix + ./users/ai-worker/ai-worker.nix + ]; in { nixosConfigurations = { lazyworkhorse = nixpkgs.lib.nixosSystem { @@ -63,12 +141,11 @@ ./hosts/lazyworkhorse/hardware-configuration.nix ./modules/nixos/filesystem/hoardingcow-mount.nix ./modules/nixos/services/docker_manager.nix - ./modules/nixos/services/open_code_server.nix + ./modules/nixos/services/wireguard-client.nix ./modules/nixos/services/ollama_init_custom_models.nix - ./modules/nixos/services/openclaw_node.nix ./modules/nixos/security/ai-worker-restricted.nix - ./users/gortium.nix - ./users/ai-worker.nix + ./users/gortium/gortium.nix + ./users/ai-worker/ai-worker.nix ]; }; @@ -83,6 +160,8 @@ } ./hosts/cyt-pi/configuration.nix ./hosts/cyt-pi/hardware-configuration.nix + ./modules/nixos/services/wireguard-client.nix + ./users/gortium/gortium.nix ]; }; @@ -93,37 +172,7 @@ nixos-raspberrypi = nixos-raspberrypi; isCM4 = false; }; - modules = [ - { - nixpkgs.buildPlatform = "x86_64-linux"; - nixpkgs.hostPlatform = "aarch64-linux"; - nixpkgs.config.allowUnfree = true; - boot.loader.raspberry-pi.bootloader = "kernel"; - } - nixos-raspberrypi.nixosModules.nixpkgs-rpi - # Fix old panel init_sequence: add DSI_INIT0 lane config, remove contradictory BURST flag - ({ lib, ... }: { - boot.kernelPatches = [{ - name = "panel-cwu50-fix-lanes"; - patch = ./patches/0008-panel-cwu50-fix-init-seq1.patch; - }]; - }) - nixos-raspberrypi.nixosModules.raspberry-pi-5.base - nixos-raspberrypi.lib.inject-overlays - nixos-raspberrypi.lib.inject-overlays-global - nixos-uconsole.nixosModules.uconsole-cm5 - ({ config, lib, pkgs, inputs, ... }: let - lix-cross = import inputs.nixpkgs-uconsole { - localSystem = { system = "x86_64-linux"; }; - crossSystem = { system = "aarch64-linux"; }; - overlays = [ inputs.lix.overlays.default ]; - }; - in { nix.package = lix-cross.lix; }) - agenix.nixosModules.default - ./modules/nixos/hardware/uconsole-cm5-aio-v2.nix - ./hosts/uconsole-cm5/configuration.nix - ./hosts/uconsole-cm5/hardware-configuration.nix - ]; + modules = uconsoleBaseModules; }; }; @@ -137,19 +186,8 @@ nixos-raspberrypi = nixos-raspberrypi; isCM4 = false; }; - modules = [ - { - nixpkgs.buildPlatform = system; - nixpkgs.hostPlatform = "aarch64-linux"; - } - nixos-raspberrypi.nixosModules.nixpkgs-rpi - nixos-raspberrypi.nixosModules.raspberry-pi-5.base - nixos-raspberrypi.lib.inject-overlays-global + modules = uconsoleBaseModules ++ [ nixos-raspberrypi.nixosModules.sd-image - nixos-uconsole.nixosModules.uconsole-cm5 - agenix.nixosModules.default - ./modules/nixos/hardware/uconsole-cm5-aio-v2.nix - ./hosts/uconsole-cm5/configuration.nix ]; }).config.system.build.sdImage; }; diff --git a/hosts/lazyworkhorse/configuration.nix b/hosts/lazyworkhorse/configuration.nix index fe7737a..a5e61e2 100644 --- a/hosts/lazyworkhorse/configuration.nix +++ b/hosts/lazyworkhorse/configuration.nix @@ -49,24 +49,12 @@ networking.networkmanager.enable = true; # Easiest to use and most distros use this by default. networking.hostId = "deadbeef"; - # WireGuard VPN client -- always up, connects to wg-easy server - # Create age-encrypted secrets before deploying (run on the host): - # echo -n "" | agenix -e secrets/wireguard_private_key.age - # echo -n "" | agenix -e secrets/wireguard_preshared_key.age - networking.wireguard.interfaces = { - wg0 = { - 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; - } - ]; - }; + # WireGuard VPN client -- module, always up, connects to wg-easy server + gortium.wireguard-client = { + enable = true; + vpnIp = "10.8.0.3/24"; + privateKeyFile = config.age.secrets.wireguard_private_key.path; + presharedKeyFile = config.age.secrets.wireguard_preshared_key.path; }; # Set your time zone. diff --git a/hosts/lazyworkhorse/hyperspace-commit-msg.txt b/hosts/lazyworkhorse/hyperspace-commit-msg.txt deleted file mode 100755 index 6916f2e..0000000 --- a/hosts/lazyworkhorse/hyperspace-commit-msg.txt +++ /dev/null @@ -1,12 +0,0 @@ -feat: add Hyperspace Pods NixOS module - -Create modules/nixos/services/hyperspace.nix for the Hyperspace Pods -P2P AI cluster agent. Registered in flake.nix under lazyworkhorse. - -- Fetches CLI binary v5.45.30 via fetchurl with SRI hash verification -- Systemd system service: auto profile, configurable api port 8080, - ai-worker user, GPU device access (kfd+dri), SupplementaryGroups - for video+render groups, service hardening -- Firewall: TCP 4001 libp2p, 30301 chain, 8080 API; UDP 4001 libp2p -- AMD MI50 ROCm via HSA_OVERRIDE_GFX_VERSION=9.0.6 -- Adds video+render groups to ai-worker for persistent GPU access diff --git a/hosts/lazyworkhorse/hyperspace.nix b/hosts/lazyworkhorse/hyperspace.nix deleted file mode 100755 index 0c2a39f..0000000 --- a/hosts/lazyworkhorse/hyperspace.nix +++ /dev/null @@ -1,134 +0,0 @@ -{ config, lib, pkgs, ... }: - -let - cfg = config.services.hyperspace; - - hyperspacePkg = pkgs.stdenv.mkDerivation { - name = "hyperspace-pods-${cfg.version}"; - src = pkgs.fetchurl { - url = "https://github.com/hyperspaceai/aios-cli/releases/download/v${cfg.version}/aios-cli-x86_64-unknown-linux-gnu.tar.gz"; - hash = cfg.packageHash; - }; - sourceRoot = "."; - installPhase = '' - mkdir -p $out/libexec $out/bin - cp -r * $out/libexec/ - chmod +x $out/libexec/aios-cli - ln -s $out/libexec/aios-cli $out/bin/hyperspace - ''; - }; -in { - options.services.hyperspace = { - enable = lib.mkEnableOption "Hyperspace Pods P2P AI cluster agent"; - - version = lib.mkOption { - type = lib.types.str; - default = "5.45.30"; - description = "Hyperspace CLI version to download."; - }; - - packageHash = lib.mkOption { - type = lib.types.str; - default = "sha256-f6fJ8t3exqtYwUD5j+WvD+Hm0oN/Eef0X+R9Rj23dE0="; - description = '' - SRI hash of the hyperspace release tarball (sha256-). - Must be updated when version changes. Generate with: - nix store prefetch-file --hash-algo sha256 \\ - https://github.com/hyperspaceai/aios-cli/releases/download/v{version}/aios-cli-x86_64-unknown-linux-gnu.tar.gz - ''; - }; - - user = lib.mkOption { - type = lib.types.str; - default = "ai-worker"; - description = "System user to run the Hyperspace agent."; - }; - - apiPort = lib.mkOption { - type = lib.types.port; - default = 8080; - description = "OpenAI-compatible API port (configurable via --api-port)."; - }; - - profile = lib.mkOption { - type = lib.types.str; - default = "auto"; - description = '' - Agent profile. Options: auto (auto-detect hardware), full (all capabilities), - inference (GPU inference only), embedding (CPU embedding only), - relay (lightweight relay), storage (storage + memory). - ''; - }; - - autoStart = lib.mkOption { - type = lib.types.bool; - default = true; - description = "Start the agent automatically on boot."; - }; - - openFirewall = lib.mkOption { - type = lib.types.bool; - default = true; - description = "Open P2P mesh (4001 TCP+UDP, 30301 TCP) and API port in the firewall."; - }; - - extraArgs = lib.mkOption { - type = lib.types.listOf lib.types.str; - default = [ ]; - description = "Extra arguments to pass to 'hyperspace start'."; - }; - }; - - config = lib.mkIf cfg.enable { - systemd.services.hyperspace = { - description = "Hyperspace Pods P2P AI Cluster Agent"; - after = [ "network.target" "network-online.target" ]; - wants = [ "network-online.target" ]; - wantedBy = lib.mkIf cfg.autoStart [ "multi-user.target" ]; - - path = with pkgs; [ bash coreutils ]; - - serviceConfig = { - Type = "simple"; - User = cfg.user; - Group = cfg.user; - WorkingDirectory = "${hyperspacePkg}/libexec"; - ExecStart = "${hyperspacePkg}/bin/hyperspace start --profile ${cfg.profile} --api-port ${toString cfg.apiPort} ${lib.escapeShellArgs cfg.extraArgs}"; - Restart = "on-failure"; - RestartSec = 5; - - # AMD MI50 (ROCm) device access - DeviceAllow = [ "/dev/kfd rw" "/dev/dri rw" ]; - - # Supplementary groups for GPU/accelerator access - SupplementaryGroups = [ "video" "render" ]; - - # Hardening - NoNewPrivileges = true; - ProtectHome = "tmpfs"; - ProtectSystem = "strict"; - PrivateTmp = true; - PrivateDevices = false; # Needs /dev/kfd and /dev/dri - }; - - environment = { - HSA_OVERRIDE_GFX_VERSION = "9.0.6"; - HOME = "/home/${cfg.user}"; - }; - }; - - # Firewall ports for P2P mesh (libp2p 4001, chain 30301) and API - networking.firewall.allowedTCPPorts = lib.mkIf cfg.openFirewall [ 4001 30301 cfg.apiPort ]; - networking.firewall.allowedUDPPorts = lib.mkIf cfg.openFirewall [ 4001 ]; - - # Add GPU/accelerator groups to the service user (persistent beyond service restarts) - users.users = lib.mkIf (cfg.user == "ai-worker") { - ai-worker = { - extraGroups = [ "video" "render" ]; - }; - }; - - # ROCm override for AMD MI50 (gfx906) compatibility - environment.variables.HSA_OVERRIDE_GFX_VERSION = "9.0.6"; - }; -} diff --git a/hosts/uconsole-cm5/configuration.nix b/hosts/uconsole-cm5/configuration.nix index 19e7166..83c7bed 100644 --- a/hosts/uconsole-cm5/configuration.nix +++ b/hosts/uconsole-cm5/configuration.nix @@ -20,12 +20,32 @@ 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; + }; + # 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; + }; + # HackerGadgets AIO v2 board hardware.uconsole-cm5-aio-v2 = { enable = true; diff --git a/modules/nixos/services/default.nix b/modules/nixos/services/default.nix new file mode 100644 index 0000000..f741778 --- /dev/null +++ b/modules/nixos/services/default.nix @@ -0,0 +1,5 @@ +{ + imports = [ + ./systemd + ]; +} diff --git a/modules/nixos/services/wireguard-client.nix b/modules/nixos/services/wireguard-client.nix new file mode 100644 index 0000000..0ed3951 --- /dev/null +++ b/modules/nixos/services/wireguard-client.nix @@ -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 ]; + }; +} diff --git a/secrets/gortium_password.age b/secrets/gortium_password.age new file mode 100644 index 0000000..3594b19 --- /dev/null +++ b/secrets/gortium_password.age @@ -0,0 +1,10 @@ +-----BEGIN AGE ENCRYPTED FILE----- +YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1lZDI1NTE5IEdoTUQ4QSA4MlFz +SHFjYjJMVHRlTWNGVGI2bHQxc0xRd2tlaExlM0NFMWhlbkR2bVg0CkxxenVTaXkr +eWxybDdCeUM0ejRvZWI4cFZCWm5VczRvZkNnT0d5Y1oyYmsKLT4gK1NmRzVtLWdy +ZWFzZSB3UDI6TyNaCnF4Ylk0QWduaXZxRFBFbDBOZ0dxeGxiWTVCYjRtZTJBRkFC +YU5qaytYWWI4OWl1K1FSdXNlY2JXZjkzak9tTHkKVFlCRlRqY1FVSzFmNS9yZmxF +aEUxelUwNEpKN3VXYi9KUWN4bXFscm5oUEFOajhRZDlERWVYcFgvQQotLS0gK1JI +VERTQjB6d1k3NDQwbjNveXBqcFk1WE96cHlaTTVkTWRMZENPamFJZwpcT1CP/KvU +CsunvfX9RBlSSKuw4eem9N9s3JqJNj4FRQizNx6QzlE1vSME +-----END AGE ENCRYPTED FILE----- diff --git a/secrets/secrets.nix b/secrets/secrets.nix index 612ce18..cf42b6c 100644 --- a/secrets/secrets.nix +++ b/secrets/secrets.nix @@ -8,8 +8,9 @@ let in { "containers.env.age".publicKeys = authorizedKeys; + "gortium_password.age".publicKeys = authorizedKeys; + "home_wifi.age".publicKeys = authorizedKeys; "lazyworkhorse_host_ssh_key.age".publicKeys = authorizedKeys; "n8n_ssh_key.age".publicKeys = authorizedKeys; "openclaw_gateway_token.age".publicKeys = authorizedKeys; - "home_wifi.age".publicKeys = authorizedKeys; } diff --git a/users/ai-worker.nix b/users/ai-worker/ai-worker.nix similarity index 100% rename from users/ai-worker.nix rename to users/ai-worker/ai-worker.nix diff --git a/users/gortium.nix b/users/gortium/gortium.nix similarity index 81% rename from users/gortium.nix rename to users/gortium/gortium.nix index 20df0ea..b94375b 100644 --- a/users/gortium.nix +++ b/users/gortium/gortium.nix @@ -1,4 +1,5 @@ { pkgs, inputs, config, keys, ... }: { + home-manager.users.gortium = import ./home.nix; users.users.gortium = { isNormalUser = true; extraGroups = [ "wheel" "docker" "video" "render"]; @@ -9,6 +10,7 @@ nh ]; shell = pkgs.zsh; + passwordFile = config.age.secrets.gortium_password.path; openssh.authorizedKeys.keys = [ keys.users.gortium.main ]; diff --git a/users/gortium/home.nix b/users/gortium/home.nix new file mode 100644 index 0000000..ce7902f --- /dev/null +++ b/users/gortium/home.nix @@ -0,0 +1,68 @@ +{ pkgs, lib, config, inputs, ... }: + +let + dotfiles = ./assets/dotfiles; + isUconsole = config.networking.hostName == "uConsole"; +in { + home.username = "gortium"; + home.homeDirectory = "/home/gortium"; + home.stateVersion = "23.11"; + programs.home-manager.enable = true; + + home.file = { + # zsh + ".zshrc".source = "${dotfiles}/zsh/.zshrc"; + + # tmux + ".tmux.conf".source = "${dotfiles}/tmux/.tmux.conf"; + + # kitty + ".config/kitty/kitty.conf".source = "${dotfiles}/kitty/.config/kitty/kitty.conf"; + + # nvim + ".config/nvim/init.lua".source = "${dotfiles}/nvim/.config/nvim/init.lua"; + + # starship + ".config/starship.toml".source = "${dotfiles}/starship/.config/starship.toml"; + + # btop + ".config/btop/btop.conf".source = "${dotfiles}/btop/.config/btop/btop.conf"; + + # waybar + ".config/waybar/style.css".source = "${dotfiles}/waybar/.config/waybar/style.css"; + ".config/waybar/config.jsonc".source = "${dotfiles}/waybar/.config/waybar/config.jsonc"; + + # wofi + ".config/wofi/style.css".source = "${dotfiles}/wofi/.config/wofi/style.css"; + ".config/wofi/config".source = "${dotfiles}/wofi/.config/wofi/config"; + + # yazi + ".config/yazi/yazi.toml".source = "${dotfiles}/yazi/.config/yazi/yazi.toml"; + + # hyprland — common config + ".config/hypr/hyprland.conf".source = "${dotfiles}/hypr/.config/hypr/hyprland.conf"; + ".config/hypr/hypridle.conf".source = "${dotfiles}/hypr/.config/hypr/hypridle.conf"; + ".config/hypr/hyprlock.conf".source = "${dotfiles}/hypr/.config/hypr/hyprlock.conf"; + ".config/hypr/hyprpaper.conf".source = "${dotfiles}/hypr/.config/hypr/hyprpaper.conf"; + ".config/hypr/mocha.conf".source = "${dotfiles}/hypr/.config/hypr/mocha.conf"; + + # hyprland — host-specific monitor config + ".config/hypr/host/monitors.conf".source = + if isUconsole + then "${dotfiles}/hypr/.config/hypr/hosts/uconsole.conf" + else "${dotfiles}/hypr/.config/hypr/hosts/laptop.conf"; + }; + + home.packages = with pkgs; [ + git zsh tmux starship + neovim kitty + btop yazi ripgrep fd fzf + ] ++ lib.optionals (!isUconsole) [ + waybar wofi swww hyprshot + ] ++ lib.optionals isUconsole [ + brightnessctl + ]; + + programs.zsh.enable = true; + programs.starship.enable = true; +}