Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -703,6 +703,43 @@

# The RAW out-of-store dotfiles are never checked at switch, so check them here.
checks.${system} = {
# DF-5: the vendored Cargo.lock must equal upstream's at the pinned rev.
# A build-time comparison, not an evaluation-time read, so evaluation
# never needs the fetched source (no import-from-derivation).
zenbook-duo-daemon-lock =
let
daemon = self.nixosConfigurations.client.config.services.zenbook-duo-daemon.package;
in
pkgs.runCommand "zenbook-duo-daemon-lock-check" { } ''
cmp ${daemon.src}/Cargo.lock ${./pkgs/zenbook-duo-daemon.Cargo.lock}
touch "$out"
'';

# G1: modules/gvisor.nix is imported on both twins with the gate OFF,
# puts nothing on PATH while off, and puts exactly pkgs.gvisor there
# when a host flips it (evaluated through extendModules, never switched).
gvisor-module =
let
hasGvisor = c: builtins.any (p: (p.pname or "") == "gvisor") c.environment.systemPackages;
coordinator = self.nixosConfigurations.coordinator.config;
worker = self.nixosConfigurations.worker.config;
workerOn =
(self.nixosConfigurations.worker.extendModules {
modules = [ { myGvisor.enable = nixpkgs.lib.mkForce true; } ];
}).config;
in
assert !coordinator.myGvisor.enable;
assert !worker.myGvisor.enable;
assert !(hasGvisor coordinator);
assert !(hasGvisor worker);
assert hasGvisor workerOn;
assert builtins.all (a: a.assertion) workerOn.assertions;
pkgs.runCommand "gvisor-module-check" { } ''
test -x ${workerOn.myGvisor.package}/bin/runsc
test -x ${workerOn.myGvisor.package}/bin/containerd-shim-runsc-v1
touch "$out"
'';

qwen-speech =
pkgs.runCommand "qwen-speech-tests"
{
Expand Down
22 changes: 20 additions & 2 deletions home/dot_local/bin/runtime-test
Original file line number Diff line number Diff line change
@@ -1,12 +1,30 @@
#!/usr/bin/env bash
# Run tests with private user-runtime sockets. This is runtime isolation, not
# a filesystem sandbox: the checkout, home, /tmp and network remain available.
#
# --allow-kvm (#453) adds exactly one device node, /dev/kvm, to the otherwise
# minimal /dev, so a KVM guest (a microvm.nix declaredRunner) can start. It
# widens nothing else: /run/user stays private, the PID and IPC namespaces and
# every --unsetenv below are unchanged, and the checkout, $HOME, /tmp and the
# network stay as accessible as they already are. It is one flag for one
# device on purpose; there is no general --dev-bind passthrough.
set -euo pipefail
if [ "${1:-}" = --help ] || [ "$#" -eq 0 ]; then
echo 'usage: runtime-test [--] command [args...]'
echo 'usage: runtime-test [--allow-kvm] [--] command [args...]'
echo 'Private /run/user and PID/IPC namespaces; source files remain writable.'
echo '--allow-kvm also binds /dev/kvm (and nothing else) for KVM guests.'
exit 0
fi
devices=()
if [ "${1:-}" = --allow-kvm ]; then
shift
# Fail loudly rather than run the guest without the device it asked for.
if [ ! -c /dev/kvm ] || [ ! -r /dev/kvm ] || [ ! -w /dev/kvm ]; then
echo 'runtime-test: --allow-kvm given but /dev/kvm is absent or not read-write for this user' >&2
exit 1
fi
devices=(--dev-bind /dev/kvm /dev/kvm)
fi
[ "${1:-}" != -- ] || shift
[ "$#" -gt 0 ] || { echo 'runtime-test: missing command' >&2; exit 2; }
command -v bwrap >/dev/null || { echo 'runtime-test: bubblewrap is required' >&2; exit 1; }
Expand All @@ -16,7 +34,7 @@ command -v bwrap >/dev/null || { echo 'runtime-test: bubblewrap is required' >&2
# Never derive a cleanup target from the inherited XDG_RUNTIME_DIR.
runtime="/run/user/$(id -u)"
exec bwrap --die-with-parent --unshare-user --unshare-pid --unshare-ipc \
--bind / / --proc /proc --dev /dev --tmpfs /run/user --dir "$runtime" \
--bind / / --proc /proc --dev /dev "${devices[@]}" --tmpfs /run/user --dir "$runtime" \
--chmod 0700 "$runtime" --setenv XDG_RUNTIME_DIR "$runtime" \
--unsetenv DBUS_SESSION_BUS_ADDRESS --unsetenv DBUS_SESSION_BUS_PID \
--unsetenv NOTIFY_SOCKET --unsetenv WATCHDOG_PID --unsetenv WATCHDOG_USEC \
Expand Down
5 changes: 5 additions & 0 deletions hosts/coordinator/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
../../modules/handwriting-annotation.nix
../../modules/qwen-tts.nix
../../modules/strix.nix
../../modules/gvisor.nix # runsc on PATH; gate below (G1, 2026-09-23)
# TWINS ONLY: kills the stock 127.0.0.2 self-mapping and points both twins'
# names at their static LAN addresses (#273). Without it gethostname()
# resolves to loopback, which every distributed library happily binds — the
Expand All @@ -83,6 +84,10 @@

networking.hostName = "coordinator";

# gVisor runsc for direct rootless `runsc run` jobs (modules/gvisor.nix).
# OFF until Tom flips it; the lane recommends the worker first.
myGvisor.enable = false;

# Primary physical seat again (2026-09-16); Zenbook remains a second seat.
# Agent services stay independent of either compositor.
myDisplay.enable = true;
Expand Down
5 changes: 5 additions & 0 deletions hosts/worker/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@
./journal-upload.nix # sender half of the #135 substrate — Strix boxes only
../../modules/cli-anything.nix
../../modules/strix.nix
../../modules/gvisor.nix # runsc on PATH; gate below (G1, 2026-09-23)
# TWINS ONLY: kills the stock 127.0.0.2 self-mapping and points both twins'
# names at their static LAN addresses (#273). Without it gethostname()
# resolves to loopback, which every distributed library happily binds — the
Expand All @@ -83,6 +84,10 @@

networking.hostName = "worker";

# gVisor runsc for direct rootless `runsc run` jobs (modules/gvisor.nix).
# OFF until Tom flips it; the lane recommends the worker first.
myGvisor.enable = false;

# ── no display, no compositor ──────────────────────────────────────────────
# One line, not two forces: myDisplay.enable (modules/display.nix) is the
# fleet's "is there a seat here" option, and modules/common.nix derives
Expand Down
45 changes: 45 additions & 0 deletions modules/gvisor.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
{
config,
lib,
pkgs,
...
}:
# gvisor.nix: gVisor's `runsc` on the host PATH, and nothing else (G1,
# 2026-09-23 evaluation). GATE OFF on every host that imports it.
#
# WHY THIS FILE EXISTS APART FROM #447
# The 2026-09-23 runtime lane MEASURED that rootless `runsc run` on a
# generated OCI bundle works on the twins (rc propagated, rw worktree bind,
# $HOME hidden, `claude --version` ran), and that runsc is installed on no
# host: the only dotfiles carrier is draft #447 (`modules/k3s-fleet.nix`),
# where gVisor arrives as a containerd shim behind a k3s gate. The direct
# path needs neither k3s nor containerd, so it gets its own gate and can
# land, and be flipped, independently of the cluster decision.
#
# WHAT ENABLING IT DOES
# * adds `pkgs.gvisor` (runsc and containerd-shim-runsc-v1) to
# environment.systemPackages, which also roots the store path in the
# system profile (the spike's copies were unrooted and collectable).
# WHAT IT DELIBERATELY DOES NOT DO
# * no containerd, no podman runtime entry, no k3s RuntimeClass (#447 owns
# those, and uses the same `pkgs.gvisor`, so the two cannot drift);
# * no state directory: runsc's `--root` must be passed by the caller and
# must point under ~/.local/state, never under $XDG_RUNTIME_DIR (the
# rootless default), per the house rule on /run/user;
# * no network policy: `--network=host` jobs reach whatever the host
# reaches. An egress fence is a separate, open decision.
# Worker first is the lane's recommendation; the gate line is explicit on
# both twins so the flip is a one-line, host-scoped edit.
let
cfg = config.myGvisor;
in
{
options.myGvisor = {
enable = lib.mkEnableOption "gVisor's runsc on PATH for rootless, direct `runsc run` jobs (no k3s, no containerd)";
package = lib.mkPackageOption pkgs "gvisor" { };
};

config = lib.mkIf cfg.enable {
environment.systemPackages = [ cfg.package ];
};
}
32 changes: 31 additions & 1 deletion modules/halogen.nix
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,33 @@ in
description = "The only interface the unauthenticated API is admitted on.";
};

openLanPort = lib.mkOption {
type = lib.types.bool;
default = cfg.autoStart;
defaultText = lib.literalExpression "config.services.halogen.autoStart";
description = ''
Whether to admit `port` on `lanInterface`. Declaring the server and
SERVING the fleet are different things, and this is the second one
(dotfiles#460).

The API has no authentication, so the door belongs only to the host
that is the fleet's endpoint. That host is the one that keeps a model
resident from boot, which is why this follows `autoStart`: the worker
(autoStart = true) opens it, the coordinator (autoStart = false, an
operator-driven `halogen-switch` box) does not. Before this option the
line keyed off `enable`, so the 2026-09-16 change that declared the
server on both twins also opened :8731 on the coordinator's WIFI
uplink — a segment the "every client is a pinned house device"
argument above was never making a claim about.

Turning it on is how a host declares itself a fleet endpoint. It is
deliberately separate from `enable` so that is a visible choice and not
a side effect. Loopback is never filtered, so a host with this off
still serves `http://localhost:${toString cfg.port}` to its own
clients after `halogen-switch`.
'';
};

contextPositions = lib.mkOption {
type = lib.types.nullOr lib.types.ints.positive;
default = null;
Expand Down Expand Up @@ -515,7 +542,10 @@ in

environment.systemPackages = [ halogenSwitch ];

networking.firewall.interfaces.${cfg.lanInterface}.allowedTCPPorts = [ cfg.port ];
# Gated on openLanPort, NOT on enable: see that option (dotfiles#460).
networking.firewall.interfaces = lib.mkIf cfg.openLanPort {
${cfg.lanInterface}.allowedTCPPorts = [ cfg.port ];
};

# GTT sized to the box. This is the half of upstream's reference boot
# line that is a SIZE rather than a flag: GTT is where every allocation
Expand Down
Loading