298bebbf
docs: the release is one tarball per OS
a73x 2026-09-04 12:50
Commit message
CLAUDE.md
| Old | New | ||
|---|---|---|---|
| @@ -29,6 +29,8 @@ make provision-mac # clone, boot and key a macOS guest on a tart host | |||
| 29 | make install # the one user binary -> ~/.local/bin, ReleaseSafe | 29 | make install # the one user binary -> ~/.local/bin, ReleaseSafe |
| 30 | # MUX_TARGET follows the HOST: a Mac installs a Mac | 30 | # MUX_TARGET follows the HOST: a Mac installs a Mac |
| 31 | # binary, because that is the only one it can link | 31 | # binary, because that is the only one it can link |
| 32 | make release release-mac # one tarball per OS into dist/, cut on that OS; | ||
| 33 | # release-mac runs `make release` on $MAC_BUILDER | ||
| 32 | make xversion-build xversion # cross-version gate; XVER_OLD_WORKTREE points at the old checkout (default ..) | 34 | make xversion-build xversion # cross-version gate; XVER_OLD_WORKTREE points at the old checkout (default ..) |
| 33 | ``` | 35 | ``` |
| 34 | 36 | ||
README.md
| Old | New | ||
|---|---|---|---|
| @@ -25,9 +25,9 @@ that runs on any x86_64 Linux: | |||
| 25 | ~/Downloads/zig-x86_64-linux-0.15.2/zig build -Dtarget=x86_64-linux-musl | 25 | ~/Downloads/zig-x86_64-linux-0.15.2/zig build -Dtarget=x86_64-linux-musl |
| 26 | ``` | 26 | ``` |
| 27 | 27 | ||
| 28 | `make release` does the whole release build instead — static musl, | 28 | `make release` does the whole release build instead — static musl on |
| 29 | `ReleaseSafe`, stripped, checked against `build.zig`'s version, and tarred | 29 | Linux, `ReleaseSafe`, stripped, checked against `build.zig`'s version, and |
| 30 | into `dist/`. The tarball is what to hand another machine, because it | 30 | tarred into `dist/`. The tarball is what to hand another machine, because it |
| 31 | carries the exec bit that a bare downloaded binary does not: | 31 | carries the exec bit that a bare downloaded binary does not: |
| 32 | 32 | ||
| 33 | ```sh | 33 | ```sh |
| @@ -35,6 +35,19 @@ make release | |||
| 35 | ssh HOST 'mkdir -p ~/.local/bin && tar xzf - -C ~/.local/bin' < dist/mux-vVERSION-x86_64-linux-musl.tar.gz | 35 | ssh HOST 'mkdir -p ~/.local/bin && tar xzf - -C ~/.local/bin' < dist/mux-vVERSION-x86_64-linux-musl.tar.gz |
| 36 | ``` | 36 | ``` |
| 37 | 37 | ||
| 38 | A release is one tarball per OS, named for the target it holds: | ||
| 39 | `mux-vVERSION-x86_64-linux-musl.tar.gz` and | ||
| 40 | `mux-vVERSION-aarch64-macos.tar.gz`. Each is cut on that OS — `make release` | ||
| 41 | builds for the host it runs on — and both are published under | ||
| 42 | `https://git.a73x.sh/mux/releases/vVERSION/`, so installing a published | ||
| 43 | build is one line: | ||
| 44 | |||
| 45 | ```sh | ||
| 46 | mkdir -p ~/.local/bin | ||
| 47 | curl -fsSL https://git.a73x.sh/mux/releases/vVERSION/mux-vVERSION-x86_64-linux-musl.tar.gz | tar xzf - -C ~/.local/bin | ||
| 48 | curl -fsSL https://git.a73x.sh/mux/releases/vVERSION/mux-vVERSION-aarch64-macos.tar.gz | tar xzf - -C ~/.local/bin | ||
| 49 | ``` | ||
| 50 | |||
| 38 | On an Apple-silicon Mac the same build and install commands work, with two | 51 | On an Apple-silicon Mac the same build and install commands work, with two |
| 39 | things to install first and one binary you cannot cross-compile — see | 52 | things to install first and one binary you cannot cross-compile — see |
| 40 | [macOS](#macos). | 53 | [macOS](#macos). |
| @@ -728,6 +741,10 @@ make build # first build fetches + compiles the QUIC deps, as on Linu | |||
| 728 | make install # an aarch64 binary in ~/.local/bin | 741 | make install # an aarch64 binary in ~/.local/bin |
| 729 | ``` | 742 | ``` |
| 730 | 743 | ||
| 744 | The Mac tarball of a release is cut by `make release-mac`, which runs | ||
| 745 | `make release` on the Mac named by `MAC_BUILDER` — the same box, and the | ||
| 746 | same variable, the macOS gates build on — and publishes it from there. | ||
| 747 | |||
| 731 | `make build` runs `deps/mac-sdk.sh` first, which may build a shadow SDK | 748 | `make build` runs `deps/mac-sdk.sh` first, which may build a shadow SDK |
| 732 | under `deps/mac-sdk/`. Xcode 26.4 and later ship a `libSystem` stub that | 749 | under `deps/mac-sdk/`. Xcode 26.4 and later ship a `libSystem` stub that |
| 733 | lists only the `arm64e` slice, and the pinned Zig 0.15.2 matches the bare | 750 | lists only the `arm64e` slice, and the pinned Zig 0.15.2 matches the bare |
docs/decisions.md
| Old | New | ||
|---|---|---|---|
| @@ -8833,3 +8833,67 @@ clone-and-boot leg left with the VM lifecycle, and what replaced it is the | |||
| 8833 | preflight line, which is not an `ok`. The user's real Mac is no longer a | 8833 | preflight line, which is not an `ok`. The user's real Mac is no longer a |
| 8834 | gate target at all; it can still be `MAC_BUILDER`, which is the one role | 8834 | gate target at all; it can still be `MAC_BUILDER`, which is the one role |
| 8835 | that touches nothing. | 8835 | that touches nothing. |
| 8836 | |||
| 8837 | ## 2026-09-04 — a release is one tarball per OS, and a Mac cuts the Mac one | ||
| 8838 | |||
| 8839 | `RELEASE_TARGET` now follows the host, the way `MUX_TARGET` already did. | ||
| 8840 | That reverses the rule the Makefile carried until today, which was that what | ||
| 8841 | a release IS must not follow the host that cuts it, and the reversal is | ||
| 8842 | worth stating because the old rule was right when it was written. | ||
| 8843 | |||
| 8844 | It was written when there was one release, a Linux one, and it guarded | ||
| 8845 | against one accident: `MUX_TARGET` had just been hoisted to the top of the | ||
| 8846 | Makefile behind a `uname -s` switch, which put `make release` on a Mac one | ||
| 8847 | `?=` away from building a Mach-O binary and shipping it as the Linux | ||
| 8848 | tarball. What removes that danger is not the rule but the NAME. The target | ||
| 8849 | is in the filename, so a Mac cuts `mux-vN-aarch64-macos.tar.gz` and a Linux | ||
| 8850 | box cuts `mux-vN-x86_64-linux-musl.tar.gz`, and no step downstream — the | ||
| 8851 | publish, the release listing, the README's install line, `mux d upgrade`'s | ||
| 8852 | stream — can take one for the other. The old rule's second argument, that | ||
| 8853 | macOS ships no `sha256sum`, was never an argument about what a release is; | ||
| 8854 | it is a spelling, and `SHA256` is `shasum -a 256` on Darwin now. | ||
| 8855 | |||
| 8856 | The reason to reverse it is that mux runs on two operating systems, and | ||
| 8857 | there is no cross-compiling the Mac binary: zig 0.15.2 asks `xcrun` for the | ||
| 8858 | SDK only for a NATIVE target, so `-Dtarget=aarch64-macos` gets no system | ||
| 8859 | include path and `@cImport` of `<util.h>` fails. So the Mac release exists | ||
| 8860 | only if a Mac cuts it. `RELEASE_BUILD_TARGET` is the second variable that | ||
| 8861 | falls out of this: `native` is the only `-Dtarget=` word that builds on a | ||
| 8862 | Mac, and `aarch64-macos` is the only name the artifact may carry, so the | ||
| 8863 | name and the build word are separate variables that happen to be the same | ||
| 8864 | string on Linux. `make release` on Linux is byte-for-byte the recipe it was. | ||
| 8865 | |||
| 8866 | `make release-mac` (tools/release-mac.sh) is how the Mac tarball gets cut | ||
| 8867 | from a Linux box. It refuses unless the tag exists locally AND points at | ||
| 8868 | HEAD — a release is cut from a tag, never a branch tip — pushes exactly that | ||
| 8869 | one tag (`--no-follow-tags` and an explicit refspec: a developer with | ||
| 8870 | `push.followTags = true`, which is a common global setting, otherwise sends | ||
| 8871 | every reachable annotated tag, measured as five extra tags in the hand | ||
| 8872 | check), and then hands ONE remote shell script to `MAC_BUILDER`. That | ||
| 8873 | script fetches the tag as a tag, detaches onto it, runs `make release` and | ||
| 8874 | publishes with the builder's own `git-collab`. The publish is the builder's | ||
| 8875 | because the builder is the box holding the bytes; nothing here runs | ||
| 8876 | git-collab locally, and nothing here touches main. | ||
| 8877 | |||
| 8878 | `MAC_BUILDER` has no default, and the refusal says so. A release published | ||
| 8879 | to whatever machine happened to be in someone's ssh config is worse than a | ||
| 8880 | refusal, and the variable is the same one the macOS gates take, so the | ||
| 8881 | builder is a box that is already known to build this repo. | ||
| 8882 | |||
| 8883 | The builder needs three one-time arrangements beyond what the gates need, | ||
| 8884 | all of them on the builder itself: its checkout's `origin` must be the real | ||
| 8885 | remote (a gate-only checkout can have been cloned from anywhere), and it | ||
| 8886 | needs `git-collab init` and `git-collab init-key` so it has an identity to | ||
| 8887 | publish under. That key is then trusted from the developer's own box with | ||
| 8888 | `git-collab key add --global --label BUILDER`; without the trust step the | ||
| 8889 | builder's publish is refused for the identity, which reads nothing like a | ||
| 8890 | build failure. | ||
| 8891 | |||
| 8892 | One outcome to expect rather than debug: a publish of a version that | ||
| 8893 | already exists is REFUSED, and that is correct. The answer is to download | ||
| 8894 | the published tarball from `https://HOST/mux/releases/vN/FILE`, extract it | ||
| 8895 | and `cmp` the BINARY against the local one — the gzip wrapper never matches, | ||
| 8896 | because it carries an mtime — and never `--force` on the strength of a | ||
| 8897 | matching sha alone. That rule was established at v0.0.1-16 (2026-08-29), | ||
| 8898 | where a publish was refused against an identical binary built 76 seconds | ||
| 8899 | earlier and the download-and-cmp is what established it was identical. | ||