max / alloy
- Co-Authored-By
- Claude Opus 5 (1M context) <noreply@anthropic.com>
7 files changed,
+86 insertions,
-43 deletions
| @@ -2173,19 +2173,23 @@ | |||
| 2173 | 2173 | ||
| 2174 | 2174 | [[patch.unused]] | |
| 2175 | 2175 | name = "quasi-axum" | |
| 2176 | - | version = "0.4.0" | |
| 2176 | + | version = "0.8.0" | |
| 2177 | + | ||
| 2178 | + | [[patch.unused]] | |
| 2179 | + | name = "quasi-basics" | |
| 2180 | + | version = "0.8.0" | |
| 2177 | 2181 | ||
| 2178 | 2182 | [[patch.unused]] | |
| 2179 | 2183 | name = "quasi-http" | |
| 2180 | - | version = "0.4.0" | |
| 2184 | + | version = "0.8.0" | |
| 2181 | 2185 | ||
| 2182 | 2186 | [[patch.unused]] | |
| 2183 | 2187 | name = "quasi-immediate" | |
| 2184 | - | version = "0.4.0" | |
| 2188 | + | version = "0.8.0" | |
| 2185 | 2189 | ||
| 2186 | 2190 | [[patch.unused]] | |
| 2187 | 2191 | name = "quasi-router" | |
| 2188 | - | version = "0.4.0" | |
| 2192 | + | version = "0.8.0" | |
| 2189 | 2193 | ||
| 2190 | 2194 | [[patch.unused]] | |
| 2191 | 2195 | name = "quasi-store" | |
| @@ -2193,23 +2197,11 @@ | |||
| 2193 | 2197 | ||
| 2194 | 2198 | [[patch.unused]] | |
| 2195 | 2199 | name = "quasi-tauri" | |
| 2196 | - | version = "0.4.0" | |
| 2197 | - | ||
| 2198 | - | [[patch.unused]] | |
| 2199 | - | name = "quasi-webview" | |
| 2200 | - | version = "0.4.0" | |
| 2201 | - | ||
| 2202 | - | [[patch.unused]] | |
| 2203 | - | name = "synckit-client" | |
| 2204 | 2200 | version = "0.8.0" | |
| 2205 | 2201 | ||
| 2206 | 2202 | [[patch.unused]] | |
| 2207 | - | name = "synckit-config" | |
| 2208 | - | version = "0.2.0" | |
| 2209 | - | ||
| 2210 | - | [[patch.unused]] | |
| 2211 | - | name = "docengine" | |
| 2212 | - | version = "0.7.0" | |
| 2203 | + | name = "quasi-webview" | |
| 2204 | + | version = "0.8.0" | |
| 2213 | 2205 | ||
| 2214 | 2206 | [[patch.unused]] | |
| 2215 | 2207 | name = "kberg" | |
| @@ -2226,3 +2218,15 @@ | |||
| 2226 | 2218 | [[patch.unused]] | |
| 2227 | 2219 | name = "tagtree" | |
| 2228 | 2220 | version = "0.4.0" | |
| 2221 | + | ||
| 2222 | + | [[patch.unused]] | |
| 2223 | + | name = "synckit-client" | |
| 2224 | + | version = "0.8.0" | |
| 2225 | + | ||
| 2226 | + | [[patch.unused]] | |
| 2227 | + | name = "synckit-config" | |
| 2228 | + | version = "0.2.0" | |
| 2229 | + | ||
| 2230 | + | [[patch.unused]] | |
| 2231 | + | name = "docengine" | |
| 2232 | + | version = "0.7.0" |
| @@ -536,13 +536,28 @@ | |||
| 536 | 536 | # carries are unreachable from any Alloy install however the machine is used: | |
| 537 | 537 | # python3-botocore (an AWS SDK, with boto3 and s3transfer behind it), | |
| 538 | 538 | # qemu-user-static for eighteen foreign architectures, and toolbox, whose job | |
| 539 | - | # distrobox already does here. Measured 2026-08-15 against this image: 297 MiB | |
| 540 | - | # across 22 packages, and nothing else in the image requires any of them. | |
| 539 | + | # distrobox already does here. 22 packages, and nothing else in the image | |
| 540 | + | # requires any of them. | |
| 541 | 541 | # | |
| 542 | - | # It is a build-speed choice as much as a size one. Everything downstream of | |
| 543 | - | # the image is proportional to its size — mkfs and the squashfs on the way to | |
| 544 | - | # an ISO, the dd to a stick, the install onto a disk — so the base's dead | |
| 545 | - | # weight is paid four times by whoever builds and installs. | |
| 542 | + | # WHAT IT IS WORTH, MEASURED RATHER THAN ASSUMED (2026-08-15, both profiles | |
| 543 | + | # built the same hour so the package versions match). dnf reports 297 MiB | |
| 544 | + | # freed, and `/usr` does shrink by 304 MiB: 170 MiB out of /usr/bin, 123 MiB | |
| 545 | + | # out of /usr/lib. **The image on disk shrinks by 3.8 MiB.** | |
| 546 | + | # | |
| 547 | + | # The difference is how the base is composed. Every file in the base's /usr is | |
| 548 | + | # a hardlink into /sysroot/ostree/repo/objects — `stat` reports nlink 2 and the | |
| 549 | + | # object is findable with `find -samefile` — so removing the /usr path drops a | |
| 550 | + | # link and frees no blocks, because the repo still holds the other one. dnf is | |
| 551 | + | # accounting for the rpm database, which is not the same question as what the | |
| 552 | + | # medium carries. Nothing here can prune the repo either: those objects belong | |
| 553 | + | # to the base commit, which is still referenced. | |
| 554 | + | # | |
| 555 | + | # So it is not the build-speed lever it looks like. What it should still buy is | |
| 556 | + | # the *installed* system, whose deployment is built from /usr rather than from | |
| 557 | + | # the image's own repo, and that has not been measured yet — believe it when an | |
| 558 | + | # install has been weighed both ways. Kept because the packages are genuinely | |
| 559 | + | # unreachable and because whoever measures that wants the switch to already | |
| 560 | + | # exist, not because the medium got smaller. | |
| 546 | 561 | # | |
| 547 | 562 | # WHAT THIS NEVER TOUCHES IS FIRMWARE, and that is a decision rather than an | |
| 548 | 563 | # oversight. `nvidia-gpu-firmware` alone is 101 MiB and is the largest single |
| @@ -17,8 +17,16 @@ | |||
| 17 | 17 | # Why a registry and not a file. bootc reads several transports, and the | |
| 18 | 18 | # obvious ones move the whole image every time: `containers-storage` is not | |
| 19 | 19 | # reachable from another machine at all, and an archive is 3 GB down the wire | |
| 20 | - | # per rebuild. A registry moves layers, and a config-only rebuild changes the | |
| 21 | - | # tail of the image, so the second push and every push after it is megabytes. | |
| 20 | + | # per rebuild. A registry stores layers, so the target pulls only the ones it | |
| 21 | + | # does not have, and a config-only rebuild changes the tail of the image. | |
| 22 | + | # | |
| 23 | + | # The push itself is NOT incremental, and measuring it is the only way anyone | |
| 24 | + | # would know. skopeo re-copies all 125 blobs on every run — 22s, twice in a | |
| 25 | + | # row, with no "Skipping fetch of repeat blob" — because a containers-storage | |
| 26 | + | # source carries no compression metadata, so each layer has to be recompressed | |
| 27 | + | # before its digest is even known. 22s is the fixed price of a push here. What | |
| 28 | + | # the registry buys is the wire to the target, not this end. | |
| 29 | + | # | |
| 22 | 30 | # The registry is a dev tool that runs on this box and is torn down with | |
| 23 | 31 | # `--stop`; nothing about it is the distribution story, which stays "no image | |
| 24 | 32 | # is published anywhere" (docs/IMAGE.md, "Registry"). | |
| @@ -44,6 +52,13 @@ | |||
| 44 | 52 | # drop-in file, it is a development machine talking to a registry on the same | |
| 45 | 53 | # desk, and `build/vmtest` is the intended target. Do not carry it onto a | |
| 46 | 54 | # machine that matters. | |
| 55 | + | # | |
| 56 | + | # The registry itself binds every interface, because a VM reaches it at | |
| 57 | + | # 10.0.2.2 and a real target reaches it by tailnet name, and it takes no | |
| 58 | + | # authentication: while it is up, anyone who can reach this box can read the | |
| 59 | + | # image and push one. That is the same trust boundary as the tailnet and it is | |
| 60 | + | # still a reason to run it only while a push is in flight. `--stop` is one | |
| 61 | + | # command and the next push starts it again. | |
| 47 | 62 | ||
| 48 | 63 | set -euo pipefail | |
| 49 | 64 | ||
| @@ -57,9 +72,9 @@ | |||
| 57 | 72 | IMAGE="localhost/alloy:local" | |
| 58 | 73 | REGISTRY_IMAGE="docker.io/library/registry:2" | |
| 59 | 74 | CONTAINER="alloy-dev-registry" | |
| 60 | - | # A named volume rather than a tmpfs, so the blobs of the last push survive a | |
| 61 | - | # restart. That is the whole economy of this script: what makes the second | |
| 62 | - | # push cheap is the registry already holding the layers that did not change. | |
| 75 | + | # A named volume rather than a tmpfs, so the blobs survive a restart of the | |
| 76 | + | # registry. What that protects is the target's next pull: with the layers still | |
| 77 | + | # here, a machine that already fetched them fetches only what changed. | |
| 63 | 78 | VOLUME="alloy-dev-registry" | |
| 64 | 79 | PORT=5000 | |
| 65 | 80 | ADDRESS="" | |
| @@ -85,7 +100,7 @@ | |||
| 85 | 100 | say "stopping $CONTAINER" | |
| 86 | 101 | privc podman rm -f "$CONTAINER" >/dev/null 2>&1 || true | |
| 87 | 102 | privc podman volume rm "$VOLUME" >/dev/null 2>&1 || true | |
| 88 | - | say "stopped. The next push starts from an empty registry and moves the whole image." | |
| 103 | + | say "stopped. The next target to switch to it pulls the whole image again." | |
| 89 | 104 | exit 0 | |
| 90 | 105 | fi | |
| 91 | 106 | ||
| @@ -110,7 +125,7 @@ | |||
| 110 | 125 | # keep current. --network host so 127.0.0.1 means this box rather than the | |
| 111 | 126 | # skopeo container's own loopback, which is the failure that looks like the | |
| 112 | 127 | # registry being down. | |
| 113 | - | say "pushing $IMAGE (first push moves the image; later ones move what changed)" | |
| 128 | + | say "pushing $IMAGE (about 20s; skopeo recompresses every layer, see the header)" | |
| 114 | 129 | priv podman run --rm --privileged --network host \ | |
| 115 | 130 | --security-opt label=type:unconfined_t \ | |
| 116 | 131 | -v /var/lib/containers/storage:/var/lib/containers/storage \ |
| @@ -86,7 +86,7 @@ | |||
| 86 | 86 | ||
| 87 | 87 | That is A/B staged with rollback, the same mechanism a registry-fed update would use, with the image coming off local container storage instead of the network. bootc 1.16.3 accepts registry, oci, oci-archive, docker-daemon and containers-storage. | |
| 88 | 88 | ||
| 89 | - | That is the same machine adopting its own build. For a *different* machine, and above all for a VM under `build/vmtest`, the loop that matters is `build/dev-push.sh`: it runs a registry on the dev box, pushes `localhost/alloy:local` into it, and prints the `bootc switch` line to run on the target. A registry rather than an archive because a registry moves layers, so a config-only rebuild is megabytes rather than three gigabytes down the wire. It is a development tool that lives and dies with a `--stop`, and it changes nothing about the position above: no image is published anywhere. | |
| 89 | + | That is the same machine adopting its own build. For a *different* machine, and above all for a VM under `build/vmtest`, the loop that matters is `build/dev-push.sh`: it runs a registry on the dev box, pushes `localhost/alloy:local` into it, and prints the `bootc switch` line to run on the target. A registry rather than an archive because the target then pulls only the layers it lacks, so a config-only rebuild is megabytes rather than three gigabytes down the wire. The push out of local storage is a flat ~20s either way: skopeo recompresses every layer because containers-storage records no compression metadata, so it cannot know a blob's digest before it has made it. It is a development tool that lives and dies with a `--stop`, and it changes nothing about the position above: no image is published anywhere. | |
| 90 | 90 | ||
| 91 | 91 | The reason it exists is the measured shape of the loop. A config-only change is fifteen seconds of `podman build` and then four minutes of ISO assembly, and the ISO is installer media nobody is going to install from when the question is whether a sway binding works. Building one to see a config change is the single most expensive habit available here. | |
| 92 | 92 |
| @@ -88,14 +88,19 @@ | |||
| 88 | 88 | The Fedora base is a general-purpose server image, and three of the things it | |
| 89 | 89 | carries cannot be reached from an Alloy install: an AWS SDK, eighteen | |
| 90 | 90 | architectures of `qemu-user-static`, and toolbox, whose job distrobox already | |
| 91 | - | does. The build drops them, which is 297 MB off the image and therefore off | |
| 92 | - | the squashfs, the stick and the install. | |
| 91 | + | does. The build drops them. | |
| 93 | 92 | ||
| 94 | 93 | build/build-iso.sh --build-arg TRIM=keep | |
| 95 | 94 | ||
| 96 | 95 | keeps them, and the one capability that comes back with them is running | |
| 97 | 96 | containers built for a foreign architecture. | |
| 98 | 97 | ||
| 98 | + | Do not expect a smaller ISO from this. Removing a base package takes 304 MB | |
| 99 | + | out of `/usr` and 3.8 MB off the image, because the base hardlinks its `/usr` | |
| 100 | + | into an ostree repo that ships in the image and that the removal cannot prune. | |
| 101 | + | The packages are gone from the installed system, which is where the saving | |
| 102 | + | should land; the medium you write is the same size either way. | |
| 103 | + | ||
| 99 | 104 | Firmware is never trimmed. A medium you build has to boot hardware nobody | |
| 100 | 105 | asked about when it was built, so every firmware package the base ships stays | |
| 101 | 106 | in, and the build fails rather than producing an image that lost one. |
| @@ -275,9 +275,10 @@ | |||
| 275 | 275 | /// which anything in Alloy reaches; the Containerfile's `ARG TRIM` argues the | |
| 276 | 276 | /// list and holds the measurement. | |
| 277 | 277 | /// | |
| 278 | - | /// Size here is build time everywhere else: the ext4 populate, the squashfs, | |
| 279 | - | /// the write to a stick and the install all scale with it, so a base package | |
| 280 | - | /// nobody can reach is paid for four times. | |
| 278 | + | /// Read the measurement before quoting a number at anyone. It takes 304 MiB | |
| 279 | + | /// out of `/usr` and 3.8 MiB off the image, because the base hardlinks its | |
| 280 | + | /// `/usr` into an ostree repo the removal cannot prune. The installed system | |
| 281 | + | /// is where it should pay, and that has not been weighed yet. | |
| 281 | 282 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] | |
| 282 | 283 | pub(crate) enum Trim { | |
| 283 | 284 | /// Drop them. The default, because the capability lost with them is | |
| @@ -300,7 +301,7 @@ | |||
| 300 | 301 | ||
| 301 | 302 | const fn label(self) -> &'static str { | |
| 302 | 303 | match self { | |
| 303 | - | Trim::Unused => "drop what nothing here reaches (297 MiB)", | |
| 304 | + | Trim::Unused => "drop what nothing here reaches", | |
| 304 | 305 | Trim::Keep => "keep the base as it ships", | |
| 305 | 306 | } | |
| 306 | 307 | } |
| @@ -1,10 +1,13 @@ | |||
| 1 | 1 | //! `TRIM` may never reach firmware, and the Containerfile is where that holds. | |
| 2 | 2 | //! | |
| 3 | - | //! The trim exists because size in the image is build time everywhere after it: | |
| 4 | - | //! the ext4 populate, the squashfs, the write to a stick, the install. That | |
| 5 | - | //! makes the list of packages it removes a thing people will want to extend, | |
| 6 | - | //! and the largest single candidate a naive extension finds is | |
| 7 | - | //! `nvidia-gpu-firmware` at 101 MiB, with the rest of `*-firmware` behind it. | |
| 3 | + | //! The trim removes base packages nothing in Alloy can reach. It looks like a | |
| 4 | + | //! size lever and mostly is not one — the base hardlinks its `/usr` into an | |
| 5 | + | //! ostree repo the removal cannot prune, so the image loses 3.8 MiB for 304 MiB | |
| 6 | + | //! of files (the Containerfile's `ARG TRIM` holds the measurement). That is | |
| 7 | + | //! exactly why this file exists: a list that looks like it buys hundreds of | |
| 8 | + | //! megabytes is a list people extend, and the largest single candidate a naive | |
| 9 | + | //! extension finds is `nvidia-gpu-firmware` at 101 MiB, with the rest of | |
| 10 | + | //! `*-firmware` behind it. | |
| 8 | 11 | //! | |
| 9 | 12 | //! Taking any of it would be wrong in a way no build catches. A medium built | |
| 10 | 13 | //! here has to boot hardware nobody was asked about at build time, and astra |