Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
685f1d584c | ||
|
|
674fdeb482 |
No files matched your search
@@ -156,9 +156,10 @@ requests. The "Library reference" below covers both.
|
|||||||
`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`. It uses bun, a JavaScript runtime, to
|
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
|
bundle the built `dist/` into one executable. Besides what Getting Started
|
||||||
needs, `make install` needs `make`. It downloads bun from GitHub with `curl` and
|
needs, `make install` needs `make`. On Linux it downloads bun from GitHub with
|
||||||
unpacks it with `unzip`. It checks the download against a SHA-256 hash in the
|
`curl` and unpacks it with `unzip`. It checks the download against a SHA-256
|
||||||
repo. `make install` works on Linux and macOS, on x86-64 and arm64.
|
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`
|
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
|
downloads bun's musl build. On Alpine, both that bun and `~/bin/quak` need the
|
||||||
@@ -1936,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;
|
||||||
- the bun builds `script/build-bin` downloads, 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 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
|
||||||
|
|||||||
+11
-23
@@ -3,13 +3,12 @@
|
|||||||
# executable, bin/quak, which reports the version script/build stamped. Our own
|
# executable, bin/quak, which reports the version script/build stamped. Our own
|
||||||
# extension to scripts-to-rule-them-all.
|
# extension to scripts-to-rule-them-all.
|
||||||
#
|
#
|
||||||
# bun compiles the binary from a copy of its own executable, so the binary
|
# bun compiles the binary from a copy of its own executable. On Linux, nix's
|
||||||
# loads the same libraries as the bun that built it. nix's bun loads libraries
|
# bun loads through a C library in the nix store, and so does a binary compiled
|
||||||
# from the nix store: on Linux its C library, on macOS its ICU. A binary
|
# from it. nix-collect-garbage can delete that library, and the binary then no
|
||||||
# compiled from it loads them too. nix-collect-garbage can delete them, and the
|
# longer starts. On Linux this script therefore compiles with bun's own release
|
||||||
# binary then no longer starts. This script therefore compiles with bun's own
|
# build, which loads through the system's C library. It downloads that build
|
||||||
# release build, which loads only the system's libraries. It downloads that
|
# and checks it against the hash below before running it.
|
||||||
# build and checks it against the hash below before running it.
|
|
||||||
set -eu
|
set -eu
|
||||||
|
|
||||||
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
|
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
|
||||||
@@ -23,8 +22,6 @@ BUN_LINUX_X64_BASELINE_SHA256="9d8a24292a7068090205daac0a5a223f5f69736f5287e37bf
|
|||||||
BUN_LINUX_X64_MUSL_BASELINE_SHA256="88ca7c7ad235b498f549eea2f770f434e9f0f5e9ba95168a2d3a1f235184c394"
|
BUN_LINUX_X64_MUSL_BASELINE_SHA256="88ca7c7ad235b498f549eea2f770f434e9f0f5e9ba95168a2d3a1f235184c394"
|
||||||
BUN_LINUX_AARCH64_SHA256="70bae41b3908b0a120e1e58c5c8af30e74afae3b8d11b0d3fdd8e787ddfb4b22"
|
BUN_LINUX_AARCH64_SHA256="70bae41b3908b0a120e1e58c5c8af30e74afae3b8d11b0d3fdd8e787ddfb4b22"
|
||||||
BUN_LINUX_AARCH64_MUSL_SHA256="5385e978107ce4934298d8d6afe9bfbb898683f6cc23e6753a0da60bc60c5b81"
|
BUN_LINUX_AARCH64_MUSL_SHA256="5385e978107ce4934298d8d6afe9bfbb898683f6cc23e6753a0da60bc60c5b81"
|
||||||
BUN_DARWIN_X64_BASELINE_SHA256="a98ba6a480f22fda9b343626b906a4e26aa53618bf85d2bc5928ecf2ba45f0ed"
|
|
||||||
BUN_DARWIN_AARCH64_SHA256="5467e3f65dba526b9fea98f0cce04efafc0c63e169733ec27b876a3ad32da190"
|
|
||||||
|
|
||||||
# compile_with_downloaded_bun <archive-name> <sha256>
|
# compile_with_downloaded_bun <archive-name> <sha256>
|
||||||
compile_with_downloaded_bun() {
|
compile_with_downloaded_bun() {
|
||||||
@@ -32,12 +29,7 @@ compile_with_downloaded_bun() {
|
|||||||
trap 'rm -rf "$tmp"' EXIT
|
trap 'rm -rf "$tmp"' EXIT
|
||||||
curl -fsSL -o "$tmp/$1.zip" \
|
curl -fsSL -o "$tmp/$1.zip" \
|
||||||
"https://github.com/oven-sh/bun/releases/download/bun-v$BUN_VERSION/$1.zip"
|
"https://github.com/oven-sh/bun/releases/download/bun-v$BUN_VERSION/$1.zip"
|
||||||
# macOS always has shasum, but not every macOS has sha256sum.
|
|
||||||
if command -v sha256sum >/dev/null 2>&1; then
|
|
||||||
echo "$2 $tmp/$1.zip" | sha256sum -c -
|
echo "$2 $tmp/$1.zip" | sha256sum -c -
|
||||||
else
|
|
||||||
echo "$2 $tmp/$1.zip" | shasum -a 256 -c -
|
|
||||||
fi
|
|
||||||
unzip -q "$tmp/$1.zip" -d "$tmp"
|
unzip -q "$tmp/$1.zip" -d "$tmp"
|
||||||
"$tmp/$1/bun" build dist/bin/quak.js --compile --outfile bin/quak
|
"$tmp/$1/bun" build dist/bin/quak.js --compile --outfile bin/quak
|
||||||
}
|
}
|
||||||
@@ -67,17 +59,13 @@ main() {
|
|||||||
compile_with_downloaded_bun bun-linux-aarch64-musl \
|
compile_with_downloaded_bun bun-linux-aarch64-musl \
|
||||||
"$BUN_LINUX_AARCH64_MUSL_SHA256"
|
"$BUN_LINUX_AARCH64_MUSL_SHA256"
|
||||||
;;
|
;;
|
||||||
Darwin-x86_64)
|
Linux-*)
|
||||||
compile_with_downloaded_bun bun-darwin-x64-baseline \
|
echo "build-bin: bun has no release build for Linux on $(uname -m)" >&2
|
||||||
"$BUN_DARWIN_X64_BASELINE_SHA256"
|
exit 1
|
||||||
;;
|
|
||||||
Darwin-arm64)
|
|
||||||
compile_with_downloaded_bun bun-darwin-aarch64 \
|
|
||||||
"$BUN_DARWIN_AARCH64_SHA256"
|
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
echo "build-bin: bun has no release build for $(uname -s) on $(uname -m)" >&2
|
nix-shell -p bun --run \
|
||||||
exit 1
|
"bun build dist/bin/quak.js --compile --outfile bin/quak"
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,29 +1,30 @@
|
|||||||
// `script/build-bin` compiles `bin/quak`, the single binary that the README's
|
// `script/build-bin` compiles `bin/quak`, the single binary that the README's
|
||||||
// cron setup runs. bun compiles it from a copy of its own executable, so the
|
// cron setup runs. bun compiles it from a copy of its own executable, so the
|
||||||
// binary loads the same libraries as the bun that built it. nix's bun loads
|
// binary loads through the same C library as the bun that built it. nix's bun
|
||||||
// libraries from the nix store, which garbage collection can delete: on Linux
|
// loads through a C library in the nix store, which garbage collection can
|
||||||
// its C library, on macOS its ICU. The script therefore downloads bun's own
|
// delete. On Linux the script therefore downloads bun's own release build,
|
||||||
// release build, which loads only the system's libraries.
|
// which loads through the system's C library.
|
||||||
//
|
//
|
||||||
// What the script promises, and why each test matters:
|
// What the script promises, and why each test matters:
|
||||||
//
|
//
|
||||||
// - Each system, machine and C library that bun publishes a build for gets its
|
// - On Linux, each machine and C library that bun publishes a build for gets
|
||||||
// own archive: Linux on x86-64 or arm64, with glibc or with musl, as on
|
// its own archive: x86-64 or arm64, with glibc or with musl, as on Alpine. A
|
||||||
// Alpine, and macOS on Intel or Apple silicon. A bun built for another
|
// bun built for another machine or C library does not start, and
|
||||||
// system, machine or C library does not start, and `make install` fails.
|
// `make install` fails.
|
||||||
// - The archive's SHA-256 must match the one in the script before anything in
|
// - The archive's SHA-256 must match the one in the script before anything in
|
||||||
// the archive runs. Without that check, a tampered download would build the
|
// the archive runs. Without that check, a tampered download would build the
|
||||||
// binary that cron runs.
|
// binary that cron runs.
|
||||||
// - Any other system or machine stops with an error, and never falls back to
|
// - Linux on any other machine stops with an error. Building there with nix's
|
||||||
// nix's bun. Building with nix's bun would bring back the dependency on the
|
// bun would bring back the dependency on the nix store.
|
||||||
// nix store.
|
// - Other systems, such as macOS, compile with nix's bun as before, and
|
||||||
|
// download nothing.
|
||||||
//
|
//
|
||||||
// Each test copies the script into a fresh directory, which the script then
|
// Each test copies the script into a fresh directory, which the script then
|
||||||
// treats as the checkout, and runs it there. Stubs first on PATH stand in for
|
// treats as the checkout, and runs it there. Stubs first on PATH stand in for
|
||||||
// `uname`, `ldd`, `curl`, `unzip`, `nix-shell` and `shasum`, so no test
|
// `uname`, `ldd`, `curl`, `unzip` and `nix-shell`, so no test downloads
|
||||||
// downloads anything or runs a real bun.
|
// anything or runs a real bun.
|
||||||
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
||||||
import { execFileSync, spawnSync } from "node:child_process";
|
import { spawnSync } from "node:child_process";
|
||||||
import {
|
import {
|
||||||
chmodSync,
|
chmodSync,
|
||||||
copyFileSync,
|
copyFileSync,
|
||||||
@@ -32,7 +33,6 @@ import {
|
|||||||
mkdtempSync,
|
mkdtempSync,
|
||||||
readFileSync,
|
readFileSync,
|
||||||
rmSync,
|
rmSync,
|
||||||
symlinkSync,
|
|
||||||
writeFileSync,
|
writeFileSync,
|
||||||
} from "node:fs";
|
} from "node:fs";
|
||||||
import { tmpdir } from "node:os";
|
import { tmpdir } from "node:os";
|
||||||
@@ -42,10 +42,9 @@ import { fileURLToPath } from "node:url";
|
|||||||
const repoRoot = fileURLToPath(new URL("../../", import.meta.url));
|
const repoRoot = fileURLToPath(new URL("../../", import.meta.url));
|
||||||
|
|
||||||
// The stubs, keyed by the command each replaces. `uname` and `ldd` describe the
|
// The stubs, keyed by the command each replaces. `uname` and `ldd` describe the
|
||||||
// system that runBuildBin passes in the environment. `curl`, `unzip`,
|
// system that runBuildBin passes in the environment. `curl`, `unzip` and
|
||||||
// `nix-shell` and `shasum` write the arguments they were given, one per line,
|
// `nix-shell` write the arguments they were given, one per line, to a file
|
||||||
// to a file named after the command in $STUB_LOG_DIR, which also shows that
|
// named after the command in $STUB_LOG_DIR, which also shows that they ran.
|
||||||
// they ran.
|
|
||||||
const stubs: Record<string, string> = {
|
const stubs: Record<string, string> = {
|
||||||
uname: `
|
uname: `
|
||||||
case "$1" in
|
case "$1" in
|
||||||
@@ -83,11 +82,6 @@ const stubs: Record<string, string> = {
|
|||||||
chmod +x "$bun"`,
|
chmod +x "$bun"`,
|
||||||
"nix-shell": `
|
"nix-shell": `
|
||||||
printf '%s\\n' "$@" > "$STUB_LOG_DIR/nix-shell"`,
|
printf '%s\\n' "$@" > "$STUB_LOG_DIR/nix-shell"`,
|
||||||
// The script checks the hash with `shasum` where there is no `sha256sum`.
|
|
||||||
// The stub fails, as `shasum` does when the hash does not match.
|
|
||||||
shasum: `
|
|
||||||
printf '%s\\n' "$@" > "$STUB_LOG_DIR/shasum"
|
|
||||||
exit 1`,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let checkout = "";
|
let checkout = "";
|
||||||
@@ -120,21 +114,13 @@ afterEach(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Runs the script with nothing in its environment but the stubs' settings and a
|
// Runs the script with nothing in its environment but the stubs' settings and a
|
||||||
// PATH. Unless the caller passes one, the PATH finds the stubs first and then
|
// PATH that finds the stubs first.
|
||||||
// the host's commands.
|
const runBuildBin = (system: string, machine: string, libc = "glibc") =>
|
||||||
const runBuildBin = (
|
|
||||||
system: string,
|
|
||||||
machine: string,
|
|
||||||
{
|
|
||||||
libc = "glibc",
|
|
||||||
path = `${join(checkout, "stubs")}:${process.env.PATH}`,
|
|
||||||
} = {},
|
|
||||||
) =>
|
|
||||||
spawnSync(join(checkout, "script/build-bin"), {
|
spawnSync(join(checkout, "script/build-bin"), {
|
||||||
cwd: checkout,
|
cwd: checkout,
|
||||||
encoding: "utf-8",
|
encoding: "utf-8",
|
||||||
env: {
|
env: {
|
||||||
PATH: path,
|
PATH: `${join(checkout, "stubs")}:${process.env.PATH}`,
|
||||||
STUB_UNAME_S: system,
|
STUB_UNAME_S: system,
|
||||||
STUB_UNAME_M: machine,
|
STUB_UNAME_M: machine,
|
||||||
STUB_LIBC: libc,
|
STUB_LIBC: libc,
|
||||||
@@ -142,31 +128,6 @@ const runBuildBin = (
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// The real commands that the script and the stubs run.
|
|
||||||
const hostCommands = [
|
|
||||||
"basename",
|
|
||||||
"chmod",
|
|
||||||
"dirname",
|
|
||||||
"grep",
|
|
||||||
"mkdir",
|
|
||||||
"mktemp",
|
|
||||||
"rm",
|
|
||||||
];
|
|
||||||
|
|
||||||
// A Mac has no `ldd`, and not every Mac has `sha256sum`. This makes a PATH that
|
|
||||||
// finds neither: the stubs other than `ldd`, then links to hostCommands.
|
|
||||||
const makeMacPath = (): string => {
|
|
||||||
rmSync(join(checkout, "stubs", "ldd"));
|
|
||||||
mkdirSync(join(checkout, "host-commands"));
|
|
||||||
for (const command of hostCommands) {
|
|
||||||
const target = execFileSync("sh", ["-c", `command -v ${command}`], {
|
|
||||||
encoding: "utf-8",
|
|
||||||
}).trim();
|
|
||||||
symlinkSync(target, join(checkout, "host-commands", command));
|
|
||||||
}
|
|
||||||
return `${join(checkout, "stubs")}:${join(checkout, "host-commands")}`;
|
|
||||||
};
|
|
||||||
|
|
||||||
const ran = (command: string): boolean =>
|
const ran = (command: string): boolean =>
|
||||||
existsSync(join(checkout, "log", command));
|
existsSync(join(checkout, "log", command));
|
||||||
|
|
||||||
@@ -180,60 +141,40 @@ describe("script/build-bin", () => {
|
|||||||
// must stop the script before unzip runs. A script without the check would
|
// must stop the script before unzip runs. A script without the check would
|
||||||
// run unzip, and the stub bun it unpacks would write bin/quak.
|
// run unzip, and the stub bun it unpacks would write bin/quak.
|
||||||
it.each([
|
it.each([
|
||||||
["Linux", "x86_64", "glibc", "bun-linux-x64-baseline.zip"],
|
["x86_64", "glibc", "bun-linux-x64-baseline.zip"],
|
||||||
["Linux", "x86_64", "musl", "bun-linux-x64-musl-baseline.zip"],
|
["x86_64", "musl", "bun-linux-x64-musl-baseline.zip"],
|
||||||
["Linux", "aarch64", "glibc", "bun-linux-aarch64.zip"],
|
["aarch64", "glibc", "bun-linux-aarch64.zip"],
|
||||||
["Linux", "aarch64", "musl", "bun-linux-aarch64-musl.zip"],
|
["aarch64", "musl", "bun-linux-aarch64-musl.zip"],
|
||||||
])(
|
])(
|
||||||
"on %s %s with %s, downloads %s and stops when its hash does not match",
|
"on Linux %s with %s, downloads %s and stops when its hash does not match",
|
||||||
(system, machine, libc, archive) => {
|
(machine, libc, archive) => {
|
||||||
const result = runBuildBin(system, machine, { libc });
|
const result = runBuildBin("Linux", machine, libc);
|
||||||
expect(argumentsOf("curl")).toContain(
|
expect(argumentsOf("curl")).toContain(
|
||||||
"https://github.com/oven-sh/bun/releases/download/bun-v1.3.13/" +
|
"https://github.com/oven-sh/bun/releases/download/bun-v1.3.13/" +
|
||||||
archive,
|
archive,
|
||||||
);
|
);
|
||||||
expect(result.status).not.toBe(0);
|
expect(result.status).not.toBe(0);
|
||||||
expect(ran("unzip")).toBe(false);
|
expect(ran("unzip")).toBe(false);
|
||||||
expect(ran("nix-shell")).toBe(false);
|
|
||||||
expect(existsSync(join(checkout, "bin/quak"))).toBe(false);
|
expect(existsSync(join(checkout, "bin/quak"))).toBe(false);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
// On the PATH from makeMacPath the script finds no `sha256sum` and checks
|
it("stops on a Linux machine that bun publishes no build for", () => {
|
||||||
// the hash with the stub `shasum`. That stub fails, so the script must stop
|
const result = runBuildBin("Linux", "riscv64");
|
||||||
// before unzip runs.
|
|
||||||
// On Apple silicon `uname -m` says arm64, where bun's archive says aarch64.
|
|
||||||
it.each([
|
|
||||||
["Darwin", "x86_64", "bun-darwin-x64-baseline.zip"],
|
|
||||||
["Darwin", "arm64", "bun-darwin-aarch64.zip"],
|
|
||||||
])(
|
|
||||||
"on %s %s, downloads %s and stops when shasum rejects its hash",
|
|
||||||
(system, machine, archive) => {
|
|
||||||
const result = runBuildBin(system, machine, {
|
|
||||||
path: makeMacPath(),
|
|
||||||
});
|
|
||||||
expect(argumentsOf("curl")).toContain(
|
|
||||||
"https://github.com/oven-sh/bun/releases/download/bun-v1.3.13/" +
|
|
||||||
archive,
|
|
||||||
);
|
|
||||||
expect(argumentsOf("shasum")).toEqual(["-a", "256", "-c", "-"]);
|
|
||||||
expect(result.status).not.toBe(0);
|
|
||||||
expect(ran("unzip")).toBe(false);
|
|
||||||
expect(ran("nix-shell")).toBe(false);
|
|
||||||
expect(existsSync(join(checkout, "bin/quak"))).toBe(false);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
it.each([
|
|
||||||
["Linux", "riscv64"],
|
|
||||||
["FreeBSD", "amd64"],
|
|
||||||
])(
|
|
||||||
"stops on %s %s, which bun publishes no build for",
|
|
||||||
(system, machine) => {
|
|
||||||
const result = runBuildBin(system, machine);
|
|
||||||
expect(result.status).not.toBe(0);
|
expect(result.status).not.toBe(0);
|
||||||
expect(ran("curl")).toBe(false);
|
expect(ran("curl")).toBe(false);
|
||||||
expect(ran("nix-shell")).toBe(false);
|
expect(ran("nix-shell")).toBe(false);
|
||||||
},
|
});
|
||||||
);
|
|
||||||
|
it("compiles with nix's bun on macOS", () => {
|
||||||
|
const result = runBuildBin("Darwin", "arm64");
|
||||||
|
expect(result.status).toBe(0);
|
||||||
|
expect(ran("curl")).toBe(false);
|
||||||
|
expect(argumentsOf("nix-shell")).toEqual([
|
||||||
|
"-p",
|
||||||
|
"bun",
|
||||||
|
"--run",
|
||||||
|
"bun build dist/bin/quak.js --compile --outfile bin/quak",
|
||||||
|
]);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
Reference in new issue
Block a user