From 7d72c5d4ed5cdc46757adf7fd88ddff1748f099f Mon Sep 17 00:00:00 2001 From: mecattaf Date: Wed, 23 Sep 2026 07:39:19 +0200 Subject: [PATCH 1/4] flake check: remove the two store-dependent evaluation sites (DF-5, #455) `nix flake check --no-build` passed or failed on store state: two sites needed a store path during evaluation. 1. pkgs/zenbook-duo-daemon.nix read `${src}/Cargo.lock`, an import-from-derivation. Vendor the lock (112 packages, no git sources) as pkgs/zenbook-duo-daemon.Cargo.lock and add checks.zenbook-duo-daemon-lock, a build-time `cmp` against upstream's file at the pinned rev, so a rev bump cannot silently drift. 2. tests/tailscale-personal imported "${pkgs.path}/nixos/...", which forces a store copy of nixpkgs that --no-build does not guarantee is valid. Use path arithmetic; the check's drvPath is unchanged (jlz5imr3...). Evidence (evaluation, 2026-09-23): client toplevel with allow-import-from-derivation=false went from rc 1 (cannot build 61p9hcv4...-source.drv) to rc 0; the package builds with the vendored lock. Co-Authored-By: Claude Opus 5.5 --- flake.nix | 12 + pkgs/zenbook-duo-daemon.Cargo.lock | 1004 ++++++++++++++++++++++++++ pkgs/zenbook-duo-daemon.nix | 8 +- tests/tailscale-personal/default.nix | 6 +- 4 files changed, 1028 insertions(+), 2 deletions(-) create mode 100644 pkgs/zenbook-duo-daemon.Cargo.lock diff --git a/flake.nix b/flake.nix index fcae9c295..aa435e746 100644 --- a/flake.nix +++ b/flake.nix @@ -703,6 +703,18 @@ # 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" + ''; + qwen-speech = pkgs.runCommand "qwen-speech-tests" { diff --git a/pkgs/zenbook-duo-daemon.Cargo.lock b/pkgs/zenbook-duo-daemon.Cargo.lock new file mode 100644 index 000000000..1416f0c06 --- /dev/null +++ b/pkgs/zenbook-duo-daemon.Cargo.lock @@ -0,0 +1,1004 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "anstream" +version = "0.6.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" + +[[package]] +name = "anstyle-parse" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.61.2", +] + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "bitflags" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" + +[[package]] +name = "cc" +version = "1.2.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90583009037521a116abf44494efecd645ba48b6622457080f080b85544e2215" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "clap" +version = "4.5.53" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9e340e012a1bf4935f5282ed1436d1489548e8f72308207ea5df0e23d2d03f8" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.53" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d76b5d13eaa18c901fd2f7fca939fefe3a0727a953561fefdf3b2922b8569d00" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" + +[[package]] +name = "colorchoice" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" + +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "enum-primitive-derive" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba7795da175654fe16979af73f81f26a8ea27638d8d9823d317016888a63dc4c" +dependencies = [ + "num-traits", + "quote", + "syn", +] + +[[package]] +name = "env_filter" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bf3c259d255ca70051b30e2e95b5446cdb8949ac4cd22c0d7fd634d89f568e2" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "env_logger" +version = "0.11.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "jiff", + "log", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "evdev-rs" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d28ab5638ed883532ae91b8f0e8b5ffa6e7296c0127855d6f8f9c0a1f468889a" +dependencies = [ + "bitflags", + "evdev-sys", + "libc", + "log", + "serde", +] + +[[package]] +name = "evdev-sys" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdcf0d489f4d9a80ac2b3b35b92fdd8fcf68d33bb67f947afe5cd36e482de576" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" + +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "indexmap" +version = "2.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "inotify" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f37dccff2791ab604f9babef0ba14fbe0be30bd368dc541e2b08d07c8aa908f3" +dependencies = [ + "bitflags", + "futures-core", + "inotify-sys", + "libc", + "tokio", +] + +[[package]] +name = "inotify-sys" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb" +dependencies = [ + "libc", +] + +[[package]] +name = "io-kit-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "617ee6cf8e3f66f3b4ea67a4058564628cde41901316e19f559e14c7c72c5e7b" +dependencies = [ + "core-foundation-sys", + "mach2", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + +[[package]] +name = "jiff" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49cce2b81f2098e7e3efc35bc2e0a6b7abec9d34128283d7a26fa8f32a6dbb35" +dependencies = [ + "jiff-static", + "log", + "portable-atomic", + "portable-atomic-util", + "serde_core", +] + +[[package]] +name = "jiff-static" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "980af8b43c3ad5d8d349ace167ec8170839f753a42d233ba19e08afe1850fa69" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "libc" +version = "0.2.178" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" + +[[package]] +name = "linux-raw-sys" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" + +[[package]] +name = "linux-raw-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "mach2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d640282b302c0bb0a2a8e0233ead9035e3bed871f0b7e81fe4a1ec829765db44" +dependencies = [ + "libc", +] + +[[package]] +name = "memchr" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" + +[[package]] +name = "mio" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "nix" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" +dependencies = [ + "bitflags", + "cfg-if", + "cfg_aliases", + "libc", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "nusb" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0226f4db3ee78f820747cf713767722877f6449d7a0fcfbf2ec3b840969763f" +dependencies = [ + "core-foundation", + "core-foundation-sys", + "futures-core", + "io-kit-sys", + "linux-raw-sys 0.9.4", + "log", + "once_cell", + "rustix", + "slab", + "tokio", + "windows-sys 0.60.2", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "portable-atomic" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" + +[[package]] +name = "portable-atomic-util" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "proc-macro2" +version = "1.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "pulseaudio" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d70623bd7967a9ca4c2ae0e807fc380b291f98480fc037042305ec643a4d3373" +dependencies = [ + "bitflags", + "byteorder", + "enum-primitive-derive", + "futures", + "log", + "mio", + "num-traits", + "thiserror", +] + +[[package]] +name = "quote" +version = "1.0.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" + +[[package]] +name = "rustix" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys 0.11.0", + "windows-sys 0.61.2", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_spanned" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e24345aa0fe688594e73770a5f6d1b216508b4f93484c0026d521acd30134392" +dependencies = [ + "serde_core", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook-registry" +version = "1.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7664a098b8e616bdfcc2dc0e9ac44eb231eedf41db4e9fe95d8d32ec728dedad" +dependencies = [ + "libc", +] + +[[package]] +name = "slab" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "socket2" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" +dependencies = [ + "libc", + "windows-sys 0.60.2", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "syn" +version = "2.0.111" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio" +version = "1.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" +dependencies = [ + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-macros" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "toml" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0dc8b1fb61449e27716ec0e1bdf0f6b8f3e8f6b05391e8497b8b6d7804ea6d8" +dependencies = [ + "indexmap", + "serde_core", + "serde_spanned", + "toml_datetime", + "toml_parser", + "toml_writer", + "winnow", +] + +[[package]] +name = "toml_datetime" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_parser" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e" +dependencies = [ + "winnow", +] + +[[package]] +name = "toml_writer" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df8b2b54733674ad286d16267dcfc7a71ed5c776e4ac7aa3c3e2561f7c637bf2" + +[[package]] +name = "unicode-ident" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" + +[[package]] +name = "users" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24cc0f6d6f267b73e5a2cadf007ba8f9bc39c6a6f9666f8cf25ea809a153b032" +dependencies = [ + "libc", + "log", +] + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + +[[package]] +name = "winnow" +version = "0.7.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" + +[[package]] +name = "zenbook-duo-daemon" +version = "1.2.0" +dependencies = [ + "clap", + "env_logger", + "evdev-rs", + "futures", + "inotify", + "log", + "nix", + "nusb", + "pulseaudio", + "serde", + "tokio", + "toml", + "users", +] diff --git a/pkgs/zenbook-duo-daemon.nix b/pkgs/zenbook-duo-daemon.nix index 747af536b..e94a53ce0 100644 --- a/pkgs/zenbook-duo-daemon.nix +++ b/pkgs/zenbook-duo-daemon.nix @@ -26,7 +26,13 @@ rustPlatform.buildRustPackage rec { hash = "sha256-ucyjhbF/qA8/J81mwqZfC0CLTQoU0eBwO0qSScVmuRY="; }; - cargoLock.lockFile = "${src}/Cargo.lock"; + # Vendored copy of ${src}/Cargo.lock at the pinned rev (DF-5, 2026-09-23). + # Reading "${src}/Cargo.lock" was an import-from-derivation: evaluation had to + # fetch src first, so `nix flake check --no-build` passed or failed on whether + # the source happened to be in the store (#455). The copy keeps evaluation + # store-independent; checks.zenbook-duo-daemon-lock fails at build time if a + # rev bump leaves this file behind upstream's. + cargoLock.lockFile = ./zenbook-duo-daemon.Cargo.lock; nativeBuildInputs = [ pkg-config ]; buildInputs = [ libevdev ]; diff --git a/tests/tailscale-personal/default.nix b/tests/tailscale-personal/default.nix index d8db0faf8..02e47dc23 100644 --- a/tests/tailscale-personal/default.nix +++ b/tests/tailscale-personal/default.nix @@ -3,7 +3,11 @@ let lib = pkgs.lib; make = extra: - (import "${pkgs.path}/nixos/lib/eval-config.nix" { + # Path arithmetic, not string interpolation (DF-5, 2026-09-23): "${pkgs.path}" + # forces a store copy of the whole nixpkgs source, and under + # `nix flake check --no-build` that copy is not guaranteed to be valid, so + # the check failed with "path '...-source' is not valid" after a GC. + (import (pkgs.path + "/nixos/lib/eval-config.nix") { system = pkgs.stdenv.hostPlatform.system; modules = [ ../../hosts/nas/tailscale-personal.nix From 1d5fd526d55c415ccaa614d3602d190bd5e776b2 Mon Sep 17 00:00:00 2001 From: mecattaf Date: Wed, 23 Sep 2026 07:39:19 +0200 Subject: [PATCH 2/4] gvisor: a runsc-only module, imported on the twins, gate OFF (G1) modules/gvisor.nix adds pkgs.gvisor (runsc, containerd-shim-runsc-v1) to systemPackages behind myGvisor.enable, independent of the k3s gate in #447, which keeps owning containerd and the RuntimeClass and uses the same pkgs.gvisor. No state dir (callers pass --root under ~/.local/state), no network policy. Both twins import it with an explicit `false`. checks.gvisor-module asserts the gate is off and runsc absent on both twins, and that an extendModules flip on the worker puts runsc and the shim on PATH with no failed assertion. Co-Authored-By: Claude Opus 5.5 --- flake.nix | 25 +++++++++++++++++++ hosts/coordinator/default.nix | 5 ++++ hosts/worker/default.nix | 5 ++++ modules/gvisor.nix | 45 +++++++++++++++++++++++++++++++++++ 4 files changed, 80 insertions(+) create mode 100644 modules/gvisor.nix diff --git a/flake.nix b/flake.nix index aa435e746..0308a6c03 100644 --- a/flake.nix +++ b/flake.nix @@ -715,6 +715,31 @@ 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" { diff --git a/hosts/coordinator/default.nix b/hosts/coordinator/default.nix index 9bc36001e..9467e4440 100644 --- a/hosts/coordinator/default.nix +++ b/hosts/coordinator/default.nix @@ -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 @@ -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; diff --git a/hosts/worker/default.nix b/hosts/worker/default.nix index c18052618..8afa63dbb 100644 --- a/hosts/worker/default.nix +++ b/hosts/worker/default.nix @@ -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 @@ -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 diff --git a/modules/gvisor.nix b/modules/gvisor.nix new file mode 100644 index 000000000..05dd66e7c --- /dev/null +++ b/modules/gvisor.nix @@ -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 ]; + }; +} From 0ed74d7e51ba0894a62e047a0247dc79b0cc54c9 Mon Sep 17 00:00:00 2001 From: mecattaf Date: Wed, 23 Sep 2026 07:40:02 +0200 Subject: [PATCH 3/4] runtime-test: opt-in --allow-kvm binds /dev/kvm and nothing else (#453) One flag, parsed before `--`, appends `--dev-bind /dev/kvm /dev/kvm` to the bwrap line. Default off, so existing callers get the same bwrap argv. With the flag and no usable /dev/kvm the wrapper exits 1 with a message before starting anything. No general --dev-bind passthrough; /run/user, the PID/IPC namespaces and every --unsetenv are unchanged. tests/runtime-test/test_allow_kvm.py (10 cases, red before, green after) creates nothing under the host /run/user: default unchanged, the device opens with the flag, only `kvm` is added to /dev, the private runtime dir stays empty, the nested no-kvm case fails loudly, usage errors, no passthrough, --help documents the flag. Co-Authored-By: Claude Opus 5.5 --- home/dot_local/bin/runtime-test | 22 ++++++- tests/runtime-test/test_allow_kvm.py | 90 ++++++++++++++++++++++++++++ 2 files changed, 110 insertions(+), 2 deletions(-) create mode 100644 tests/runtime-test/test_allow_kvm.py diff --git a/home/dot_local/bin/runtime-test b/home/dot_local/bin/runtime-test index 0ea29db5d..1fc20c7d0 100755 --- a/home/dot_local/bin/runtime-test +++ b/home/dot_local/bin/runtime-test @@ -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; } @@ -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 \ diff --git a/tests/runtime-test/test_allow_kvm.py b/tests/runtime-test/test_allow_kvm.py new file mode 100644 index 000000000..fbf79a400 --- /dev/null +++ b/tests/runtime-test/test_allow_kvm.py @@ -0,0 +1,90 @@ +"""runtime-test --allow-kvm (#453). Run on a host with unprivileged user +namespaces, outside the Nix build sandbox: + + python3 tests/runtime-test/test_allow_kvm.py + +Unlike test_isolation.py this file creates nothing under the host's +/run/user: every probe runs inside the wrapper's own private tree. +""" +import os +from pathlib import Path +import shutil +import subprocess +import sys +import unittest + +RUNNER = str(Path(__file__).resolve().parents[2] / 'home/dot_local/bin/runtime-test') +HOST_KVM = Path('/dev/kvm') +HOST_KVM_USABLE = HOST_KVM.is_char_device() and os.access(HOST_KVM, os.R_OK | os.W_OK) +OPEN_KVM = "import os; os.close(os.open('/dev/kvm', os.O_RDWR))" + + +def run(*argv, timeout=15): + return subprocess.run([RUNNER, *argv], capture_output=True, text=True, timeout=timeout) + + +@unittest.skipUnless(shutil.which('bwrap'), 'bubblewrap is required') +class AllowKvm(unittest.TestCase): + def test_default_is_unchanged_kvm_absent(self): + p = run('--', 'test', '-e', '/dev/kvm') + self.assertEqual(p.returncode, 1, p.stderr) + + def test_default_still_passes_exit_code(self): + p = run('--', sys.executable, '-c', 'raise SystemExit(23)') + self.assertEqual(p.returncode, 23, p.stderr) + + def test_bare_command_without_separator_still_works(self): + p = run(sys.executable, '-c', 'raise SystemExit(7)') + self.assertEqual(p.returncode, 7, p.stderr) + + @unittest.skipUnless(HOST_KVM_USABLE, 'host has no usable /dev/kvm') + def test_flag_binds_kvm_and_it_opens(self): + p = run('--allow-kvm', '--', sys.executable, '-c', OPEN_KVM) + self.assertEqual(p.returncode, 0, p.stderr) + + @unittest.skipUnless(HOST_KVM_USABLE, 'host has no usable /dev/kvm') + def test_flag_widens_only_kvm(self): + # The rest of /dev is still bubblewrap's minimal devtmpfs. + probe = "import os; print(' '.join(sorted(os.listdir('/dev'))))" + base = run('--', sys.executable, '-c', probe) + kvm = run('--allow-kvm', '--', sys.executable, '-c', probe) + self.assertEqual(base.returncode, 0, base.stderr) + self.assertEqual(kvm.returncode, 0, kvm.stderr) + self.assertEqual(set(kvm.stdout.split()) - set(base.stdout.split()), {'kvm'}) + + @unittest.skipUnless(HOST_KVM_USABLE, 'host has no usable /dev/kvm') + def test_flag_keeps_runtime_isolation(self): + probe = ( + "import os, pathlib; r = pathlib.Path(os.environ['XDG_RUNTIME_DIR']);" + "assert r == pathlib.Path('/run/user') / str(os.getuid());" + "assert list(r.iterdir()) == [], 'private runtime dir is not empty';" + "assert 'DBUS_SESSION_BUS_ADDRESS' not in os.environ" + ) + p = run('--allow-kvm', '--', sys.executable, '-c', probe) + self.assertEqual(p.returncode, 0, p.stderr) + + def test_flag_fails_loudly_when_kvm_missing(self): + # The outer wrapper has no /dev/kvm, so the inner --allow-kvm must refuse + # before starting anything rather than run the command without the device. + p = run('--', RUNNER, '--allow-kvm', '--', 'true') + self.assertEqual(p.returncode, 1, p.stderr) + self.assertIn('runtime-test: --allow-kvm', p.stderr) + self.assertIn('/dev/kvm', p.stderr) + + def test_flag_without_command_is_usage_error(self): + self.assertEqual(run('--allow-kvm').returncode, 2) + self.assertEqual(run('--allow-kvm', '--').returncode, 2) + + def test_no_general_device_passthrough(self): + # Only the one named flag is parsed; anything else is the command. + p = run('--dev-bind', '/dev/null', '/dev/kvm', '--', 'true') + self.assertNotEqual(p.returncode, 0) + + def test_help_documents_the_flag(self): + p = run('--help') + self.assertEqual(p.returncode, 0) + self.assertIn('--allow-kvm', p.stdout) + + +if __name__ == '__main__': + unittest.main(verbosity=2) From 204c96a00b11ac0aea5ac397f9f89c40ccfb096d Mon Sep 17 00:00:00 2001 From: mecattaf Date: Wed, 23 Sep 2026 06:10:46 +0200 Subject: [PATCH 4/4] halogen: the LAN door follows autoStart, not enable (#460) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit modules/halogen.nix opened `port` on `lanInterface` for every host that set `services.halogen.enable`. modules/strix.nix declares the server on BOTH twins and splits only `autoStart`, so the coordinator — which by design serves no model — was admitting :8731 on wlp192s0, its wifi uplink. The live box had the rule (`-A nixos-fw -i wlp192s0 -p tcp --dport 8731 -j nixos-fw-accept`) with nothing listening behind it: an open door to an empty room, which `halogen-switch flash` would have furnished with an unauthenticated inference endpoint. The "no authentication is fine, it is admitted on the LAN interface only; every client on that segment is a pinned house device" argument at the top of this module is true of the worker's wired enp191s0. It was never a claim about the coordinator's wifi segment. New `openLanPort`, defaulting to `autoStart`, now gates the firewall line. Declaring the server and serving the fleet become separate statements: the worker keeps its door, the coordinator loses one it was never meant to have, and a host that genuinely wants to serve sets one visible option instead of inheriting it from `enable`. Coordinator-local `halogen-switch` use is unaffected — loopback is never filtered. flake.nix:1695 already asserted exactly this and had been failing since 2026-09-16, taking `checks.nas-topology` — and everything evaluated after it — down with it. `nix flake check --offline --no-build` is green again. No change to the assert; it was right all along. Co-Authored-By: Claude Opus 5 (cherry picked from commit 334e59640f0141a1555b27d236a9508e92e5ca5b) --- modules/halogen.nix | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/modules/halogen.nix b/modules/halogen.nix index d3bc6b3d3..04d91a680 100644 --- a/modules/halogen.nix +++ b/modules/halogen.nix @@ -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; @@ -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