diff --git a/Makefile b/Makefile index a71e286..5b04bad 100644 --- a/Makefile +++ b/Makefile @@ -26,10 +26,8 @@ check: build: @script/build -# Bundles the built dist/, so the binary reports the version script/build -# stamped. -build-bin: build - nix-shell -p bun --run "bun build dist/bin/quak.js --compile --outfile bin/quak" +build-bin: + @script/build-bin install: build-bin mkdir -p ~/bin diff --git a/README.md b/README.md index 5a4ff1e..f2e2b7b 100644 --- a/README.md +++ b/README.md @@ -135,10 +135,12 @@ requests. The "Library reference" below covers both. ## Running the backup from cron `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, -`make install` needs `make` and [nix](https://nixos.org/download). It uses bun, -a JavaScript runtime, to bundle the built `dist/` into one executable. It runs -bun through `nix-shell -p bun`. +one binary and copies it to `~/bin/quak`. It uses bun, a JavaScript runtime, to +bundle the built `dist/` into one executable. Besides what Getting Started +needs, `make install` needs `make`. On Linux it downloads bun from GitHub with +`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). Log in once with `~/bin/quak`, as the user the cron job will run as: @@ -1771,6 +1773,8 @@ standard. The others are additions to it. `package.json` declares (`main`, `types`, `bin`) were written, and make the command line executable. - `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 `Dockerfile`. Requires Docker. - `script/lint`: run eslint and a prettier check, by building the `lint` phase @@ -1799,8 +1803,7 @@ The Makefile targets are: - `make build`: runs `script/build`. - `make docker`: runs `script/docker`. - `make hooks`: runs `script/install-precommit`. -- `make build-bin`: bundles the built `dist/` into one binary, `bin/quak`. It - runs bun through `nix-shell -p bun`. +- `make build-bin`: runs `script/build-bin`. - `make install`: runs `make build-bin` and copies the binary to `~/bin/quak`. - `make dev`: runs the TypeScript compiler in watch mode. - `make clean`: removes `dist/`, `coverage/`, `.vitest-cache/` and the @@ -1908,9 +1911,10 @@ These apply to people and to coding agents alike. - container images by digest; - the nvm archive `script/bootstrap` downloads, and the Python packages `bin/clip-keyword-vectors` installs, by SHA-256 hash; + - bun, which `script/build-bin` downloads on Linux, by SHA-256 hash; - Node and yarn, when `script/bootstrap` installs them, by version. - 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 package manager when they are missing, as every `Dockerfile` stage does; - `bash` and `curl`, which `script/bootstrap` installs the same way when it diff --git a/script/build-bin b/script/build-bin new file mode 100755 index 0000000..5c40f89 --- /dev/null +++ b/script/build-bin @@ -0,0 +1,56 @@ +#!/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 archive is the baseline build, which also runs on CPUs without AVX2. +BUN_VERSION="1.3.13" +BUN_LINUX_X64_BASELINE_SHA256="9d8a24292a7068090205daac0a5a223f5f69736f5287e37bf88d3b4031edc750" +BUN_LINUX_AARCH64_SHA256="70bae41b3908b0a120e1e58c5c8af30e74afae3b8d11b0d3fdd8e787ddfb4b22" + +# compile_with_downloaded_bun +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" + case "$(uname -s)-$(uname -m)" in + Linux-x86_64) + compile_with_downloaded_bun bun-linux-x64-baseline \ + "$BUN_LINUX_X64_BASELINE_SHA256" + ;; + Linux-aarch64) + compile_with_downloaded_bun bun-linux-aarch64 \ + "$BUN_LINUX_AARCH64_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 "$@"