Files
imladris/hw/straylight/default.nix

145 lines
5.6 KiB
Nix

{ inputs, pkgs, lib, ... }:
let
pkgsUnstable = inputs.nixpkgs-unstable.legacyPackages.${pkgs.system};
# Pin to b9828; nixpkgs-unstable lags too far behind for the features we need.
# To update: nix-prefetch-url --unpack https://github.com/ggml-org/llama.cpp/archive/refs/tags/bNNNN.tar.gz
# then: nix run nixpkgs#prefetch-npm-deps -- <unpacked>/tools/ui/package-lock.json
llama-cpp-b9828 = (pkgsUnstable.llama-cpp.override { vulkanSupport = true; }).overrideAttrs (_: {
version = "9828";
src = pkgsUnstable.fetchFromGitHub {
owner = "ggml-org";
repo = "llama.cpp";
tag = "b9828";
hash = "sha256-CRj+smOs5T80fT5OqHZGbko7/PwChhYsc8MGZZkE/dQ=";
};
npmDepsHash = "sha256-X1DZgmhS/zHTqDT5zq0kywwntthcJ9vRXeqyO3zz6UU=";
});
in
{
imports = [
./hardware-configuration.nix
../../configs/desktop.nix
../../configs/qemu.nix
../../configs/mcpkg.nix
../../configs/mcp.nix # MCP agent + mcp user (straylight is becoming the core host)
];
config = {
# llama.cpp server with Vulkan backend for gfx1151 (Strix Halo).
# Model is downloaded from HuggingFace on first start and cached in StateDirectory.
# NOTE: BIOS "UMA Frame Buffer Size" must be ≥32GB for a 23GB model to fit on-GPU.
users.users.llama-server = {
isSystemUser = true;
group = "llama-server";
home = "/var/lib/llama-server";
};
users.groups.llama-server = {};
systemd.services.llama-server = {
description = "llama.cpp inference server";
wantedBy = [ "multi-user.target" ];
after = [ "network-online.target" ];
wants = [ "network-online.target" ];
environment = {
HOME = "/var/lib/llama-server";
HF_HOME = "/var/lib/llama-server/huggingface";
};
serviceConfig = {
ExecStart = ''
${llama-cpp-b9828}/bin/llama-server \
--jinja \
-hf deepreinforce-ai/Ornith-1.0-35B-GGUF:Q4_K_M \
--host 127.0.0.1 \
--port 11434 \
-c 262144 \
--no-mmap \
-fa on \
--cache-type-k q8_0 \
--cache-type-v q8_0 \
-ngl all
'';
User = "llama-server";
Group = "llama-server";
StateDirectory = "llama-server";
WorkingDirectory = "/var/lib/llama-server";
SupplementaryGroups = [ "render" "video" ];
Restart = "on-failure";
RestartSec = "10s";
TimeoutStartSec = "600";
};
};
# straylight is the unikernel host. The shared mcp.nix locks the agent
# down with PrivateDevices=true, which hides /dev/kvm and /dev/net/tun.
# Relax that here (only on straylight) so the agent can boot Nanos
# unikernel VMs under QEMU/KVM and (Phase 2) manage TAP devices.
systemd.services.mcp-agent.serviceConfig = {
PrivateDevices = lib.mkForce false;
DeviceAllow = [ "/dev/kvm rw" "/dev/net/tun rw" ];
SupplementaryGroups = [ "kvm" ];
AmbientCapabilities = [ "CAP_NET_ADMIN" ];
# The agent launches each unikernel as a daemonized QEMU process in its
# own cgroup. With the default KillMode=control-group, restarting the
# agent would SIGKILL every running VM. KillMode=process kills only the
# agent's main process on stop/restart, so VMs survive an agent upgrade.
# (If a VM does die, the agent's startup Recover restarts it.)
KillMode = lib.mkForce "process";
};
# Let the mcp user reach /dev/kvm directly as well.
users.users.mcp.extraGroups = [ "kvm" ];
# Isolated host-only bridge for unikernel VMs (Phase 2). Each unikernel
# gets a TAP on this bridge and a 10.99.0.0/24 static IP. The bridge has
# NO uplink and NO NAT, and the firewall drops any VM traffic leaving the
# bridge, so a VM can reach only the host gateway (10.99.0.1) -- mediation
# is enforced by network topology, not convention.
networking.bridges.mcp-br0.interfaces = [ ];
networking.interfaces.mcp-br0.ipv4.addresses = [
{ address = "10.99.0.1"; prefixLength = 24; }
];
# The host accepts traffic from VMs (so mc-proxy on the gateway can serve
# them); the FORWARD drop prevents VMs from routing anywhere off-bridge.
networking.firewall.trustedInterfaces = [ "mcp-br0" ];
networking.firewall.extraCommands = ''
iptables -D FORWARD -i mcp-br0 ! -o mcp-br0 -j DROP 2>/dev/null || true
iptables -A FORWARD -i mcp-br0 ! -o mcp-br0 -j DROP
'';
networking.firewall.extraStopCommands = ''
iptables -D FORWARD -i mcp-br0 ! -o mcp-br0 -j DROP 2>/dev/null || true
'';
# Allow rootless containers (podman) to bind low ports (53 for MCNS,
# 443/8443/9443 for mc-proxy) as straylight takes over the core role.
boot.kernel.sysctl."net.ipv4.ip_unprivileged_port_start" = 53;
services.open-webui = {
enable = true;
host = "0.0.0.0";
port = 8080;
environment = {
SCARF_NO_ANALYTICS = "True";
DO_NOT_TRACK = "True";
ANONYMIZED_TELEMETRY = "False";
# Point at the local llama.cpp server (OpenAI-compatible API).
OPENAI_API_BASE_URLS = "http://127.0.0.1:11434/v1";
OPENAI_API_KEYS = "none";
ENABLE_OLLAMA_API = "False";
};
};
# Open ports: DNS (53), mc-proxy (443/8443/9443), agent (9444), master (9555), open-webui (8080).
networking.firewall.allowedTCPPorts = [ 53 443 8080 8443 9443 9444 9555 ];
networking.firewall.allowedUDPPorts = [ 53 ];
# DNS: MCNS for internal zones, public resolvers as fallback.
networking.nameservers = [
"192.168.88.181"
"100.95.252.120"
"1.1.1.1"
"8.8.8.8"
];
services.resolved.domains = [
"~mcp.metacircular.net"
];
};
}