diff --git a/.moon/workspace.yml b/.moon/workspace.yml index 7d413909..fa1c63b5 100644 --- a/.moon/workspace.yml +++ b/.moon/workspace.yml @@ -56,6 +56,11 @@ projects: # the CI sweep — that suite is what catches a build-arg or closure-root drift, # since no compass CI step can build the image itself today. runner-image: 'tools/runner-image' + # The agent-image pin the microVM guest rootfs derives from. Registered so + # its typecheck + pure-core unit tests ride the CI sweep — that suite is what + # catches a provenance or layer-order drift, and moon discovers projects only + # from this map, so unregistered the whole guard suite is silently inert. + guest-image: 'tools/guest-image' # The Compass native-app release bundle: a heavy nix build (realises the # WebKitGTK cc/pkg-config closure) that stages the versioned tarball. Same # affected-detection posture as compass-agent-image — registered here so the diff --git a/bun.lock b/bun.lock index 786a6194..fcea345e 100644 --- a/bun.lock +++ b/bun.lock @@ -169,6 +169,16 @@ "typescript": "catalog:", }, }, + "tools/guest-image": { + "name": "@compass/guest-image", + "bin": { + "guest-image-pin-agent": "./pin-agent-image.ts", + }, + "devDependencies": { + "@types/bun": "catalog:", + "typescript": "catalog:", + }, + }, "tools/inline-sql-gate": { "name": "@compass/inline-sql-gate", "bin": { @@ -487,6 +497,8 @@ "@compass/forge-linear-token": ["@compass/forge-linear-token@workspace:tools/forge-linear-token"], + "@compass/guest-image": ["@compass/guest-image@workspace:tools/guest-image"], + "@compass/inline-sql-gate": ["@compass/inline-sql-gate@workspace:tools/inline-sql-gate"], "@compass/macos-bundle": ["@compass/macos-bundle@workspace:tools/macos-bundle"], diff --git a/guest-image/agent-oci.lock b/guest-image/agent-oci.lock new file mode 100644 index 00000000..bb7afe76 --- /dev/null +++ b/guest-image/agent-oci.lock @@ -0,0 +1,127 @@ +{ + "repo": "ghcr.io/rigelbuild/compass-agent", + "tag": "git-7d22c69390cb", + "digest": "sha256:634aeee15954f43b9a009b2dd9fe2ad3b454f64dd09c9491957c03d0313a6823", + "layers": [ + "sha256:989a42bd3ddf0028fce105bf936074ae1cad375bbabf868ba358161d6eddea56", + "sha256:50173e59368b86248a8fc447563bc692eb54cea61522d423c562037b27273b1f", + "sha256:609f4904737a247c83c6f5e57210a99c625ee6fef0e1a2bab7c1368f5cb7da95", + "sha256:9b1563075c8b42c7e91675d2c44177dfdfd47087996152d063e04b2c838c8f57", + "sha256:a27e5e982aec83990632c6891f73050659a47e9425d2f8ff4ba27e3a60fd3a05", + "sha256:83e4a642b15fa357dd60c58e34dce2dbe8bca77866e602c3ddb457fb86ed3791", + "sha256:a28acb06d258001260b8879bc49bad34a40235752ec067ebafff194c015bab78", + "sha256:1eefdcdcb94a4c13c0bb56dcb22a00c4b07ebf09b2a13441d6feb4f774765592", + "sha256:2b835b65e2621850178daf39a8b51501a77e0141ab1997fa49959855de597200", + "sha256:b4939a5f31ab0f55db6a137ece82f2d1dd837fd9a75a335535a3cdf34ea4ed0c", + "sha256:88337f8b7d5b5415714dc5db31f95397e6c0d69797e977ae630a45c6fd7b1f70", + "sha256:ce855785a761c203e0b4992059938de647b375b3c99662ec9a7b9446a39b46b7", + "sha256:a65a251cbf515329613b066e7137ff1bb7e76605afff8b63f5a1eece0fad4814", + "sha256:5087d0bd2d08b75bccdb9d9e0cdf3fe643d36a420750efb68b9ccb7d7cbfaf42", + "sha256:97290c6058d9cdb192d4a844731078d54b76e17e01926866c8b002ca0a22cf81", + "sha256:12d9b9a35a002c04b40dfa8ce19f1930844c756132db2a0acc9114e6d3c77be2", + "sha256:c904b30222e4fb1b1dfd2bef268e7acd862d30a027688ad705fe0ca824aa25b7", + "sha256:befa4be2046b290e0f318c2bae8d0c6e0b60fcbbec0e8b9c4e523e0fa2920fab", + "sha256:1a5561045f0d6f709a5af083cc9229d7c2a107e71d41b33c1a8360fea3e88266", + "sha256:20d466aba19a30ed1f9baf7721d7f3dec6343e9c2e56cabe71d080a01158c059", + "sha256:fb54e13d029c33ad1ee18e64b535951a2a38fc7fbdf54c7d1cf9a4079e2ba5cf", + "sha256:5cb7cb4604ee7d5784b1eec9595ba61d4710ebe5144d88d564f2d866fc36f63d", + "sha256:dfc15617d544c97e54dd90b3aa4ae226ea154ea20b19225bb0c7a9ae813f2f81", + "sha256:06d8edbc5d4b45454ed65016ebd473df9a73d731a1a318c61fb339690b2b2a0a", + "sha256:0198a70f4b3c743a14950e34e5faad8a10e05279da9d1ee23d9c61784189c911", + "sha256:85b200ba6a9563b01210fb6db146d687216b4f1e4ead539686522d9b745c9da4", + "sha256:e8e32b46884a076249c1ba0d8f8009f044908801b07e428e83e369ca52281bb2", + "sha256:8e8004a82bd80ac730a45ff11144afd39237e36e622ae1eae305e421a4ed9023", + "sha256:a2784d4ed644739b1e62165bfe0dc7691fabcfe85e953256efa54eb227c70575", + "sha256:9a110d87dee34a853f538f53f50df90c23f74bb53db2c53d3a2107155004b0cd", + "sha256:13de35f6e89265878c1ae16ab4229918fa813ec44be7157d2ada66599bf6be5f", + "sha256:f3a08b5ecac5276869ebd366319e108122fc87163c05bc9675d0f028f790467f", + "sha256:df281230c7a1c6db680798e6f966ced09429944ff0c5712eaed9232d11fab644", + "sha256:4fada2aae32b9938dc63e1b2ed0598a3975324ed6736c707e52ceecaeebdf5e1", + "sha256:4244ec3726b6b1a71d3c4cd9f0302a1a152a24b3f028b0080e35496ee1ab1dab", + "sha256:4eac009357ae2dd5ceba989358f0fa6ee78b5f183cd67f056a99dd02345e870b", + "sha256:a829f97edb5088eff29dfebae5d3660958e7d975885fecc51ce70ead4833a66c", + "sha256:6c7632a46e4ff00f8169a235bdbcf13ba72c8cedceea9dd668554e5603818ecd", + "sha256:cb66e85841af9451aa00ab4fe86b4cd882e72a5773c077f37925306bcfa1d246", + "sha256:4ca9a76c303917d8c4ebde29863514bd6d76e24d787f5f0e2e9adf9036912683", + "sha256:00f060b0330fc649c90ff8aed0d03759f4cdee31b2be667c09ce2160a6458636", + "sha256:bdfff65a8c7c8621704da3d42c65121a6ea8523e258aa59d015b23beb0420502", + "sha256:0d5cfe1be61877a897aea120f0928e295bfc0a88958ffc641faa19f9983302e4", + "sha256:728eac9836a08e8de1694f02fc8c29eac6170b3ef9286ee64eaecc4cfa53df8e", + "sha256:5d5bbab83a69df879f9af7f9560249903b5e4cd3ad008e35b7961abce039cbdc", + "sha256:246ed1b833889333ed005b5c3a72dd39235d1ad8211d6c70f86b86ebb407ef85", + "sha256:775d60e8d947f0b14ca30badc625df4b7d80bf87ba5e1b172d764052697a10fb", + "sha256:c964d986e2041dc5918b96df2d84106893a0099661f0e677634a8984fe965259", + "sha256:12c3a013e98cc6b4336bcf4c050a4b74e1deca42fcc2bcc9a6166977a357e475", + "sha256:e2926a71d238fac16fb251b1f1a316399b4aa05bf606b6be7ef307c50833a0d8", + "sha256:74ead26ad73b3ccdf0366060c36bed040a3bc72e78a6676619ff5f92abbc6999", + "sha256:a754daf94f43269c51efba261e2a1f2a2fe334c394537dbac00dc3f8d8df4a59", + "sha256:2baf24046fe64dd59b1daa3412ad8d0311a1d49f6f658e876e129a462a968a05", + "sha256:0e213195a5c4a56ba80f8fbcfcfab538763f98911e7e8c72bd6d671362fb743f", + "sha256:f41f723d758771feea97eb49b70accb3f71eb584909635ef32332b7093e664b5", + "sha256:ab2beaf90a91a92a339fc96abb64fdb39ed513cd19fcfb492e4cd546bb25acf2", + "sha256:f0c6726cd51279b847ff36fa7ff0d687a7959900e7544171df2bb2e8e3caf2d0", + "sha256:c40dd490d44cdc6440cb2683092145444b8f42886d07b714c75c1c2aaf103e50", + "sha256:b516c2ee390cbc32fd7310cc7af36eb246125c694444742c73e520cecae0e4df", + "sha256:5b1711b70bea797e1ce3412fbcf6a08db518a7c6824e76979b21068a97bd2cf9", + "sha256:9f7536248f41ecdf13dacde207e17123c60da3a94bd69a6ef8f7bd4d139eb960", + "sha256:5bc8acf2dea1d01db0898633dfd1bde185ceb274b7585b948d0dd324e2f0e3f5", + "sha256:66bece8745d11e9544e544fcb28a11392715247cb7e20c3be437ec570c59ba44", + "sha256:e31da11550fa2b023bdc7b75deaef05a255cd60de8ab2ab3b6bfccb7c27ef592", + "sha256:25ed9f7c6766d6942a72491c4df4f5e6ece1800dfee40763900a0fa43a2be5b5", + "sha256:9d19949397a22dc46c92e39023bd819dbd9c97998f7ec1540bb4e7cf780962be", + "sha256:06220d2ab5c467b16cfd243be799ea404c8f27aa9915bdf7d519d11d92e50fdb", + "sha256:ff4a05359598d8e219aa9bcb50e84c0155c30befb4a60eb470ab0f2867f7c7f0", + "sha256:49829c965f4ae698d07769fdcebaa3c6c99f87c99df9c03e263ba3aed7d644df", + "sha256:f5c5ed6c6f2df8e23f17f0db989fb12516eb7626e08ac1e5a7cb12d58b6b3d45", + "sha256:ac4c11458e54c5a34854ba6d54ffc262d9c6450d261b16069302d9367cde5698", + "sha256:8f9d57a9adef65c895542b90d3235b272e6349d574e4644e07b1b0a7519e2b5e", + "sha256:6e2f7a0e9efa1a18ea3829f0393c89bc9761cc6ae96a6aa9107d087f2c621c74", + "sha256:6aaf42086be878b2d8547a0565c022249cc1d05fe9402982f77eb51c53697b61", + "sha256:65294ae739b115754167fa6e5e737721a1ae59a8f28a3750642c24dbfaedc162", + "sha256:0b60a919cb943e44ec9b862389f2c86b8d86eb4ad3b855d5346bf11df1c34550", + "sha256:715f8f27508def1aae7ab159b1ea75b5924364c5f21b5d494eaddc2fdb79fc7f", + "sha256:b65b930bbd24ab4b07c3fbdc7c57c209c1b9905fe628c83cbc933e0c30577445", + "sha256:97ad1a929ca4155ee00fdc2d4f6d2b57278ae06e0c395230ec7113d821b3527d", + "sha256:bc8a89708d25b218cd31000d8eac61f324d49562f2c330197f1af68fc9bc652c", + "sha256:46cab03582f2a2720809cbbe22b8d9b445cb22d19de646d77fa0446ded5b5802", + "sha256:99b2de06e987ead1a31fd34a7f62653e0b8048659419c705ffe4c9e9657d419d", + "sha256:ef475c65af727c6a13570a37f1ef362c242f43a5f1a90a8a55f3e2f7bbce2d70", + "sha256:3ef92c85b3f0bec77b09c39249546b7f91fc7c371918ab6ab9948e59bd917fa6", + "sha256:ca2641e89ea1c87034ed4a274c3dc991d33f9447bb68b6f22e4b7d4edcf068c2", + "sha256:96e4db8add2228b01419c910d8ebc2ba01e2f242a610d74f7bbf677eafb53a40", + "sha256:7e69e346d08c4457b0e63f09960dfe24f752c960a864c092d48763b62c49e564", + "sha256:6e35e26aa14caa1f1815b6371660534eae37889992c0132c195eb525231f27c2", + "sha256:7306b70d6767a21686f9fed6e16919ba392093fbd58368be9a02b39f62cd3461", + "sha256:65f85994228e6dde888ff7fa7b6d96d92da9b98c9501186586fc7e53daeac2c3", + "sha256:1f952f741c864361946ac2407d122b6fb9b3f123129b148de514efedd3235d12", + "sha256:c958165a9542d4bf4393b9604b9eb6be3c7d9bba5804951826dcb096e3b67b95", + "sha256:ecac70470eabab90c8bed7011dacbe94f194d71fc9f54a9b34872bd0f98f6b88", + "sha256:a02defd4db5cc2b025f7a1c5ce7993a870176448e46c0a927f0af0713a3b6314", + "sha256:401e76c82da6cceb5ad589a2d2f0c4147edfac4f8201beddd2aa6bb50e7a529b", + "sha256:754284604d7120599f55426b596d3059a5f165d0a0f6945ff0c8a0c7dbe78d7d", + "sha256:aaae4d6fac166dd8391dd6658edd2a3f301221b39e71326e5ea5f1d6adf1b064", + "sha256:5a9b6b2a22916fcb634f882a6db07eac6d3adc76091acd1b0bc4ebac2d3f8a93", + "sha256:0f7b3d9b68a4182a7a1a69210f185b79602d7c4af167a8e0ef3f8164cd38c31f", + "sha256:7de18bb60520597810e1a194bedfea095e6ef1e50e1ded8bae5932b5de912f28", + "sha256:fc47246e342a6eb0a98ba9de3c22935a3efb207145225c11618c80231c1bb0d4", + "sha256:ee0a9aa87179703abee4b1cb415e07824062f39c2b04ee4f60a76df6966f3d74", + "sha256:203eeea7b1eb8205b03425e85285b5da506690c259ab0c6fa04ca8e96324696b", + "sha256:6e73d91899a55c1f0675d38edd0c204464dc292e8a00c1098c0d35a21838562b", + "sha256:c0b09b3836b9186903c246773edc9f8f87a3a03b961b0caecbe9eb90d19959c1", + "sha256:f32d5c68675ebc9e082de3df1038c3ad1d89d201505e621a85ba3577c2b215af", + "sha256:c2beac798a2dbe5871128478b2a3f10f538c8b8261e5c38eaf31e66aaa5edfa9", + "sha256:52380eaa83367c693530b0a7090ae394d01921000edc44670afbabce927f6e7f", + "sha256:7a844f24a8e47aa904741a2df961abec6d4afacc165ebef763c4d29af2dc5df3", + "sha256:402112cc028033bdb988d0cd3ef05efdc03c69f00edfa283631154282f38a65a", + "sha256:1a27431e1302ba23b4bc81e831d1d8dc8b15b4865fab9597ee3e8820c75937e7", + "sha256:4f1af9cd0cd69eb34e1768141d28c910c60362948689abc3386cd89f7e0151a1", + "sha256:fe2778aea835fa21fa28f93259017b12a4374b776047abb8735200cfc94f56ff", + "sha256:d4cf521bbf13c6e071ac0175177694f462483fc293e6753680cae7e40535f603", + "sha256:d0b7a7f7092eaccf2be2d988d5ce399fc40c137ff7c51c55cdc81e6ac91add9e", + "sha256:1d8ff8656e5da54cde4f2fff6b262d6d99f54ea1ce119ad655aef28131c6e107", + "sha256:d7594f8803ea13a94c80a3345f2706b0cb69d07c428c77ba632230fcb4248e8b", + "sha256:ee18288abf12a859737dc7bacfef6506657ea5582e184745f1e433422df945eb", + "sha256:462cce1dc39d5d73bafee275866bf3de96ca4f25594fe84e13ba873eef95b953", + "sha256:7564bc82edf713cb15a3d5406a213a27cff25f83663e1954c3bcc35c35df011e" + ] +} diff --git a/guest-image/default.nix b/guest-image/default.nix index e06bb6ac..d0b7cac7 100644 --- a/guest-image/default.nix +++ b/guest-image/default.nix @@ -1,19 +1,11 @@ # The Compass microVM guest image: the three nix attrs V2a's cloud-hypervisor -# runtime consumes to boot a session guest — a direct-boot kernel, a packed erofs -# root filesystem, and a module initramfs. It reuses agent-image/'s toolchain -# closure, so the guest ships the same agent runtime as the container path — one -# closure, two artifact shapes (OCI layers there, a bootable erofs image here). -# -# Pin divergence (a parity note V2a honors): `agent-image/toolchain.nix` is -# called here with ROOT's `pkgs` (the root devenv.lock), NOT agent-image's own -# pin, so the whole rootfs closure resolves from the root pin. Deliberate: the -# guest-image moon gate's `inputs` track the root devenv.lock, so the gate -# reschedules on a root-pin move. agent-image's OCI build keeps its own pin; the -# two closures are the same shape through two nixpkgs revisions. +# runtime consumes to boot a session guest. The rootfs userland IS the published +# agent OCI image unpacked, fetched fixed-output against `agent-oci.lock`, so +# guest/container drift is not expressible. Only the boot layer is added on top. let # The root devenv.lock-pinned nixpkgs, resolved as the other plain nix gates do - # (read the lock, fetch that rev, import it). This is the "root's pkgs" the pin - # divergence turns on. + # (read the lock, fetch that rev, import it). Supplies the BOOT layer only: the + # agent userland comes from the OCI image, not from this pin. lock = builtins.fromJSON (builtins.readFile ../devenv.lock); node = lock.nodes.nixpkgs.locked; nixpkgsSrc = builtins.fetchTarball { @@ -23,24 +15,95 @@ let pkgs = import nixpkgsSrc { }; lib = pkgs.lib; - # The SAME bundled agent entrypoint and toolchain closure the agent image ships, - # imported unchanged and fed root's `pkgs`. Their own relative imports resolve - # against agent-image/, not this file, so importing them here is transparent. - compassAgent = import ../agent-image/entrypoint.nix { inherit pkgs lib; }; - toolchain = import ../agent-image/toolchain.nix { inherit pkgs compassAgent; }; - - # The real guest init (T2, go/cmd/compass-guestd): the guest-side supervisor — - # mounts the API filesystems, brings networking up (in-process DHCP), mounts the - # virtio-fs workspace, serves the vsock Health handshake as guest PID 1. - # buildGoModule of the backend module; static (CGO_ENABLED=0) so it needs no - # in-guest libc a switch_root'd PID 1 cannot assume. - # * src is renamed off `go` so buildGoModule's $GOPATH unpack does not collide - # ("go.mod file not found"). - # * proxyVendor is required: wails/secretspec //go:embed patterns reference - # darwin/windows-only files a vendor-tree build would fail on; proxyVendor - # touches only the packages actually compiled for linux/amd64. - # * vendorHash pins the fetched module set; recompute with `lib.fakeHash` on a - # go.mod/go.sum move. + # The pinned agent image, written only by tools/guest-image/pin-agent-image.ts. + # The shape is re-checked HERE as well as in the pin tool: the tool guards the + # write path, this guards the eval, and a hand-edited lock has to defeat both. + agentLock = builtins.fromJSON (builtins.readFile ./agent-oci.lock); + + # Split registry host from repository path: the lock stores the full + # reference, the v2 API needs the two separately. + agentRegistry = "ghcr.io"; + agentPath = "rigelbuild/compass-agent"; + agentRepo = "${agentRegistry}/${agentPath}"; + + # A digest is only a pin if it is a real sha256. `match` returns null on any + # deviation, so a truncated or hex-invalid digest fails eval instead of + # reaching fetchurl as an unenforceable hash. + isSha256 = s: builtins.isString s && builtins.match "sha256:[0-9a-f]{64}" s != null; + + # Fail at eval, naming the field, rather than letting a malformed lock surface + # as an opaque fetch or hash error deep in the build. + checkedLock = + let + bad = + if !builtins.isAttrs agentLock then + "lock must be a JSON object" + else if !builtins.isString (agentLock.repo or null) || agentLock.repo != agentRepo then + "repo must be ${agentRepo}, got ${toString (agentLock.repo or "")}" + else if + !builtins.isString (agentLock.tag or null) || builtins.match "git-[0-9a-f]{12}" agentLock.tag == null + then + "tag must match git-, got ${toString (agentLock.tag or "")}" + else if !isSha256 (agentLock.digest or "") then + "digest must be sha256:<64 hex>, got ${toString (agentLock.digest or "")}" + else if !builtins.isList (agentLock.layers or null) || agentLock.layers == [ ] then + "layers must be a non-empty list" + else if !builtins.all isSha256 agentLock.layers then + "every layer must be sha256:<64 hex>" + else + null; + in + if bad == null then + agentLock + else + throw "guest-image: agent-oci.lock is not a valid pin: ${bad}. Rewrite it with `bun tools/guest-image/pin-agent-image.ts --relock`, never by hand."; + + # The pinned manifest, fetched fixed-output against the lock's digest: a + # manifest digest IS the sha256 of its body, so nix's hash check authenticates + # it outright. Without this the digest would be decoration -- the layer + # descriptors alone decide what gets unpacked. + agentManifest = pkgs.fetchurl { + url = "https://${agentRegistry}/v2/${agentPath}/manifests/${checkedLock.digest}"; + curlOptsList = [ + "-H" + "Authorization: Bearer QQ==" + "-H" + "Accept: application/vnd.oci.image.manifest.v1+json" + ]; + hash = checkedLock.digest; + }; + + # The manifest's own ordered layer list. Reading it here rather than trusting + # the lock's copy binds the unpacked bytes to the pinned image: a hand-edited + # lock pairing this digest with another image's valid layers no longer builds. + manifestLayers = map (l: l.digest) (builtins.fromJSON (builtins.readFile agentManifest)).layers; + + lockedLayers = + if manifestLayers == checkedLock.layers then + checkedLock.layers + else + throw "guest-image: agent-oci.lock layers do not match the manifest it pins (${checkedLock.digest}). Rewrite it with `bun tools/guest-image/pin-agent-image.ts --relock`, never by hand."; + + # Each layer blob, fetched fixed-output against its descriptor digest -- the + # registry's own content address, where one archive's narHash would track + # skopeo's byte layout. The bearer is GHCR's literal anonymous public-read + # token, not a credential: an unauthenticated blob GET 401s demanding one. + agentLayers = map ( + digest: + pkgs.fetchurl { + url = "https://${agentRegistry}/v2/${agentPath}/blobs/${digest}"; + curlOptsList = [ + "-H" + "Authorization: Bearer QQ==" + ]; + hash = digest; + } + ) lockedLayers; + + # The guest-side supervisor, running as guest PID 1: mounts the API + # filesystems, brings networking up, serves the vsock Health handshake. Static + # since a switch_root'd PID 1 cannot assume a libc; src is renamed off `go` so + # buildGoModule's unpack cannot collide. guestd = pkgs.buildGoModule { pname = "compass-guestd"; version = "0-v2a"; @@ -56,8 +119,8 @@ let "-s" "-w" ]; - # This slice only packages the binary; guestd's logic is unit-tested under the - # backend gate and the real boot is T4's KVM-gated proof. + # This derivation only packages the binary; guestd's logic is unit-tested + # under the backend gate and the real boot is proved by the KVM-gated test. doCheck = false; }; @@ -76,16 +139,10 @@ let # the separate `modules` output, consumed by both the rootfs and the initrd. kernel = pkgs.linuxPackages.kernel; - # The module set the initramfs loads before switch_root, via kmod modprobe from - # the shrunk closure, because the guest has no udev/systemd-modules-load to - # autoload post-switch_root (guestd IS init). These loads persist across - # switch_root, so every driver the guest needs is bound by the time guestd - # starts: the boot-critical set mounts the root overlay (virtio transport + - # block, erofs, overlayfs); the runtime set covers guestd's net/workspace/vsock - # and af_packet (its in-process DHCP client's raw socket — without it the lease - # fails EAFNOSUPPORT). Every one is `=m` in the pinned kernel; the check below - # fails the build on a pin move that flips one to `=y` or drops it, rather than - # shipping a guest that boots but cannot reach network, workspace, or host. + # The initramfs modprobes these before switch_root: nothing autoloads + # afterwards (guestd IS init) and the loads persist across it. af_packet is + # load-bearing -- guestd's DHCP raw socket fails EAFNOSUPPORT without it. + # The check below breaks the build if a pin move flips one to `=y`. bootModules = [ "virtio_pci" "virtio_blk" @@ -121,7 +178,7 @@ let ${lib.concatMapStringsSep "\n" (sym: '' if ! grep -qx '${sym}=m' ${kernel.configfile}; then echo "guest-image: BUILD-BREAK — kernel .config lacks '${sym}=m'." >&2 - echo " The initramfs assumes ${sym} is a loadable module (record §(a))." >&2 + echo " The initramfs assumes ${sym} is a loadable module." >&2 echo " A kernel-pin move flipped it to =y or dropped it; the initrd would" >&2 echo " not boot. Re-audit guest-image/default.nix bootModules against the" >&2 echo " new kernel before proceeding." >&2 @@ -156,7 +213,7 @@ let fail() { echo "compass-guest-initrd: $1" >&2 # Give the console a moment to flush before PID 1 exits and the kernel - # panics, so the cause is visible in T4's captured serial log. + # panics, so the cause is visible in the captured serial log. exec sh -c 'echo "compass-guest-initrd: boot aborted"; exit 1' } @@ -183,13 +240,10 @@ let -o lowerdir=/mnt/lower,upperdir=/mnt/rw/upper,workdir=/mnt/rw/work \ /mnt/root || fail "mount whole-root overlay failed" - # No pre-switch_root existence check on /mnt/root/sbin/init: it is an - # ABSOLUTE store symlink (-> /nix/store/…-compass-guestd/bin/compass-guestd), - # so `test -x` would follow the symlink and resolve its absolute target - # against the CURRENT process root — still the initramfs, where guestd is - # absent — and fail-close on every correct image. switch_root below is the - # gate: it chroots into /mnt/root first, so /sbin/init resolves in the - # overlay where guestd exists, and it is itself `|| fail`-closed. + # No pre-switch_root check on /mnt/root/sbin/init: it is an ABSOLUTE store + # symlink, so `test -x` resolves it against the CURRENT root (still the + # initramfs, where guestd is absent) and fail-closes on every correct image. + # switch_root chroots first, and is itself `|| fail`-closed. # Hand off to the real guest init. switch_root tears down the initramfs and # execs /sbin/init as PID 1 in the overlay root. @@ -215,39 +269,12 @@ let ]; }; - # The rootfs contents tree: a store-path symlink farm + a real writable - # resolv.conf + the kernel's full /lib/modules tree; the erofs step below packs - # its store closure into the bootable image. Assembled by hand (not `buildEnv`) - # so resolv.conf lands as a real file and the closure references stay explicit. - rootfsTree = pkgs.runCommand "compass-guest-rootfs-tree" { } '' - mkdir -p $out/bin $out/sbin $out/etc $out/lib - - # The agent-image toolchain closure: its /bin and /etc, symlinked in. These - # point into the store closure the packed image ships — the same - # relocated-/etc + store-closure shape nix2container gives the OCI artifact. - for f in ${toolchain}/bin/*; do - ln -s "$f" "$out/bin/$(basename "$f")" - done - if [ -d ${toolchain}/etc ]; then - cp -a ${toolchain}/etc/. $out/etc/ - # cp -a preserves the store's read-only dir/file modes; make the staged - # /etc writable so the resolv.conf install below lands cleanly. - chmod -R u+w $out/etc - fi - - # The egress prerequisites (microvm-runner.md:446-449) plus /bin/sh. Already - # present via the toolchain closure above (agent-image/toolchain.nix:144-147), - # linked again here explicitly so the guest's contract does not depend on the - # toolchain's internal package list. `ln -sf` because the toolchain loop may - # already have created these names. /bin/sh is load-bearing under always-arm - # (record §(e)): every microVM Start spawns `/bin/sh -c