make build-bin: on Linux, build with bun's pinned release build (closes #240)

nix's bun loads through a glibc in the nix store, and bun build --compile
copied that loader into bin/quak, so nix-collect-garbage could break the
cron backup. The build moves into script/build-bin. On Linux it downloads
bun 1.3.13's release archive for the machine from GitHub: x86-64
(baseline) or arm64, glibc or musl. It checks the archive's SHA-256
before running it, and compiles with that bun, so the binary loads
through the system loader. macOS still runs bun through nix-shell.

Model: opus-5-5
This commit is contained in:
sneak committed 2026-11-11 22:40:51 +00:00
1 parent 37cdbc6b9a
commit 674fdeb482
3 files changed
+91 -11

No files matched your search

+2 -4
View File
@@ -26,10 +26,8 @@ check:
build: build:
@script/build @script/build
# Bundles the built dist/, so the binary reports the version script/build build-bin:
# stamped. @script/build-bin
build-bin: build
nix-shell -p bun --run "bun build dist/bin/quak.js --compile --outfile bin/quak"
install: build-bin install: build-bin
mkdir -p ~/bin mkdir -p ~/bin
+16 -7
View File
@@ -154,10 +154,16 @@ requests. The "Library reference" below covers both.
## Running the backup from cron ## Running the backup from cron
`quak backup` never prompts, so cron can run it. `make install` builds quak as `quak backup` never prompts, so cron can run it. `make install` builds quak as
one binary and copies it to `~/bin/quak`. Besides what Getting Started needs, one binary and copies it to `~/bin/quak`. It uses bun, a JavaScript runtime, to
`make install` needs `make` and [nix](https://nixos.org/download). It uses bun, bundle the built `dist/` into one executable. Besides what Getting Started
a JavaScript runtime, to bundle the built `dist/` into one executable. It runs needs, `make install` needs `make`. On Linux it downloads bun from GitHub with
bun through `nix-shell -p bun`. `curl` and unpacks it with `unzip`. It checks the download against a SHA-256
hash in the repo. On macOS it runs bun through `nix-shell -p bun`, so it needs
[nix](https://nixos.org/download).
On Linux systems that use the musl C library, such as Alpine, `make install`
downloads bun's musl build. On Alpine, both that bun and `~/bin/quak` need the
`libstdc++` and `libgcc` packages.
Log in once with `~/bin/quak`, as the user the cron job will run as: Log in once with `~/bin/quak`, as the user the cron job will run as:
@@ -1793,6 +1799,8 @@ standard. The others are additions to it.
`package.json` declares (`main`, `types`, `bin`) were written, and make the `package.json` declares (`main`, `types`, `bin`) were written, and make the
command line executable. command line executable.
- `script/version`: print the version `script/build` stamps (see "Version"). - `script/version`: print the version `script/build` stamps (see "Version").
- `script/build-bin`: run `script/build`, then bundle the built `dist/` into one
binary, `bin/quak`, with bun (see "Running the backup from cron").
- `script/test`: run the test suite, by building the `test` phase of the - `script/test`: run the test suite, by building the `test` phase of the
`Dockerfile`. Requires Docker. `Dockerfile`. Requires Docker.
- `script/lint`: run eslint and a prettier check, by building the `lint` phase - `script/lint`: run eslint and a prettier check, by building the `lint` phase
@@ -1821,8 +1829,7 @@ The Makefile targets are:
- `make build`: runs `script/build`. - `make build`: runs `script/build`.
- `make docker`: runs `script/docker`. - `make docker`: runs `script/docker`.
- `make hooks`: runs `script/install-precommit`. - `make hooks`: runs `script/install-precommit`.
- `make build-bin`: bundles the built `dist/` into one binary, `bin/quak`. It - `make build-bin`: runs `script/build-bin`.
runs bun through `nix-shell -p bun`.
- `make install`: runs `make build-bin` and copies the binary to `~/bin/quak`. - `make install`: runs `make build-bin` and copies the binary to `~/bin/quak`.
- `make dev`: runs the TypeScript compiler in watch mode. - `make dev`: runs the TypeScript compiler in watch mode.
- `make clean`: removes `dist/`, `coverage/`, `.vitest-cache/` and the - `make clean`: removes `dist/`, `coverage/`, `.vitest-cache/` and the
@@ -1930,9 +1937,11 @@ These apply to people and to coding agents alike.
- container images by digest; - container images by digest;
- the nvm archive `script/bootstrap` downloads, and the Python packages - the nvm archive `script/bootstrap` downloads, and the Python packages
`bin/clip-keyword-vectors` installs, by SHA-256 hash; `bin/clip-keyword-vectors` installs, by SHA-256 hash;
- bun's glibc and musl builds, which `script/build-bin` downloads on Linux,
by SHA-256 hash;
- Node and yarn, when `script/bootstrap` installs them, by version. - Node and yarn, when `script/bootstrap` installs them, by version.
- Not pinned: - Not pinned:
- bun, which `make build-bin` takes from `nix-shell -p bun`; - bun on macOS, which `script/build-bin` takes from `nix-shell -p bun`;
- `make` and `git`, which `script/bootstrap` installs from the system - `make` and `git`, which `script/bootstrap` installs from the system
package manager when they are missing, as every `Dockerfile` stage does; package manager when they are missing, as every `Dockerfile` stage does;
- `bash` and `curl`, which `script/bootstrap` installs the same way when it - `bash` and `curl`, which `script/bootstrap` installs the same way when it
+73
View File
@@ -0,0 +1,73 @@
#!/bin/sh
# script/build-bin: run script/build, then bundle the built dist/ into one
# executable, bin/quak, which reports the version script/build stamped. Our own
# extension to scripts-to-rule-them-all.
#
# bun compiles the binary from a copy of its own executable. On Linux, nix's
# bun loads through a C library in the nix store, and so does a binary compiled
# from it. nix-collect-garbage can delete that library, and the binary then no
# longer starts. On Linux this script therefore compiles with bun's own release
# build, which loads through the system's C library. It downloads that build
# and checks it against the hash below before running it.
set -eu
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
# bun 1.3.13, 2026-11-11, and the SHA-256 of each release archive below. The
# x86-64 archives are the baseline builds, which also run on CPUs without AVX2.
# The musl archives are for Linux systems that use the musl C library, such as
# Alpine.
BUN_VERSION="1.3.13"
BUN_LINUX_X64_BASELINE_SHA256="9d8a24292a7068090205daac0a5a223f5f69736f5287e37bf88d3b4031edc750"
BUN_LINUX_X64_MUSL_BASELINE_SHA256="88ca7c7ad235b498f549eea2f770f434e9f0f5e9ba95168a2d3a1f235184c394"
BUN_LINUX_AARCH64_SHA256="70bae41b3908b0a120e1e58c5c8af30e74afae3b8d11b0d3fdd8e787ddfb4b22"
BUN_LINUX_AARCH64_MUSL_SHA256="5385e978107ce4934298d8d6afe9bfbb898683f6cc23e6753a0da60bc60c5b81"
# compile_with_downloaded_bun <archive-name> <sha256>
compile_with_downloaded_bun() {
tmp="$(mktemp -d)"
trap 'rm -rf "$tmp"' EXIT
curl -fsSL -o "$tmp/$1.zip" \
"https://github.com/oven-sh/bun/releases/download/bun-v$BUN_VERSION/$1.zip"
echo "$2 $tmp/$1.zip" | sha256sum -c -
unzip -q "$tmp/$1.zip" -d "$tmp"
"$tmp/$1/bun" build dist/bin/quak.js --compile --outfile bin/quak
}
main() {
cd "$ROOT"
"$ROOT/script/build"
platform="$(uname -s)-$(uname -m)"
# musl's ldd begins its version output with "musl libc"; glibc's does not.
if ldd --version 2>&1 | grep -q '^musl libc'; then
platform="$platform-musl"
fi
case "$platform" in
Linux-x86_64)
compile_with_downloaded_bun bun-linux-x64-baseline \
"$BUN_LINUX_X64_BASELINE_SHA256"
;;
Linux-x86_64-musl)
compile_with_downloaded_bun bun-linux-x64-musl-baseline \
"$BUN_LINUX_X64_MUSL_BASELINE_SHA256"
;;
Linux-aarch64)
compile_with_downloaded_bun bun-linux-aarch64 \
"$BUN_LINUX_AARCH64_SHA256"
;;
Linux-aarch64-musl)
compile_with_downloaded_bun bun-linux-aarch64-musl \
"$BUN_LINUX_AARCH64_MUSL_SHA256"
;;
Linux-*)
echo "build-bin: bun has no release build for Linux on $(uname -m)" >&2
exit 1
;;
*)
nix-shell -p bun --run \
"bun build dist/bin/quak.js --compile --outfile bin/quak"
;;
esac
}
main "$@"