Bootstrap an exact Harn release
scripts/bootstrap_harn.mjs is Harn's supported host-native bootstrap
interface for local development and CI systems outside GitHub Actions. It uses
only Node.js built-ins, runs on Linux, macOS, and Windows, and never requires a
POSIX shell. Node.js 20 or newer is required.
Pin the bootstrap script itself by checking out an immutable Harn release or commit, then provide either an exact version or a file containing one:
node path/to/harn/scripts/bootstrap_harn.mjs --version 0.10.31 --cache-dir path/to/cache --install-dir path/to/tools/harn
node path/to/harn/scripts/bootstrap_harn.mjs --version-file .harn-version --cache-dir path/to/cache --install-dir path/to/tools/harn
The effective request must resolve to one MAJOR.MINOR.PATCH value, with an
optional leading v. latest, version ranges, and prerelease selectors are
rejected. When neither option is written on the command line, --version-file
defaults to .harn-version in the working directory; a missing or invalid file
is a closed failure.
The command prints one JSON receipt to standard output:
{
"schema_version": "harn-bootstrap-v1",
"version": "0.10.31",
"target": "aarch64-apple-darwin",
"binary_path": "/ci/tools/harn/harn",
"source": "https://github.com/burin-labs/harn/releases/download/v0.10.31/harn-aarch64-apple-darwin.tar.gz",
"checksum": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
"cache_hit": false,
"binary_sha256": "abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789"
}
The first seven fields form the stable harn-bootstrap-v1 contract.
binary_sha256 provides additional local integrity evidence. The installed
directory also contains the integrity-only install-manifest.json used to
validate future reuse; the JSON printed for each invocation is its audit
receipt.
Verification and atomicity
Every online run downloads the exact release's SHA256SUMS, requires one
unambiguous entry for the selected platform archive, and verifies the archive
before extraction. A cached archive is only a candidate: the bootstrapper
hashes it again against the release metadata before reporting
cache_hit: true. If checksum metadata previously cached for an exact release
differs from newly published metadata, the run fails closed.
Archive files and install directories are prepared beside their destinations
and published atomically only after verification. Interrupted temporary paths
never become cache or install state. Concurrent processes installing the same
version may both extract work, but a <install-dir>.lock ownership record
serializes validation, corrupt-state quarantine, and final publication. A
process that loses publication validates the winner before returning.
The publication lock records its host, process, and a unique ownership token. Waiters give each unchanged owner 30 seconds before evicting it; an ownership change starts a fresh deadline rather than transferring stale wait time to the new owner. After at least two minutes of total contention, the next acquisition attempt revalidates the install: an exact valid peer publication wins, while a missing or invalid install fails closed. After acquiring the boundary, a waiter also revalidates any installation the prior owner may have published. A dead same-host process or a lock older than five minutes is reclaimed immediately. Filesystem mutations retry transient Windows sharing and antivirus errors while the boundary remains owned. Token checks fence publication and cleanup so an evicted process cannot later mutate state or delete its replacement's lock.
The default cache is under the runner tool cache, $XDG_CACHE_HOME, Windows
%LOCALAPPDATA%, or the user's .cache directory. The default installation
is versioned below that bootstrap cache. Use --cache-dir and --install-dir
in shared CI so Harn does not write to a package manager, Cargo home, or another
toolchain's directory.
Offline runs
After one successful online install, --offline requires both of these exact
version entries:
metadata/<version>/SHA256SUMSdownloads/<version>/<platform archive>
Offline mode performs no network request and re-hashes the archive against the cached checksum metadata. A missing or corrupt entry is a closed failure; the bootstrapper never selects another version or falls back to an unverified binary.
Proxies
The bootstrapper uses Node's built-in fetch. Node.js 22.21+, 24.5+, and newer
release lines can read proxy settings when NODE_USE_ENV_PROXY=1 is set before
Node starts. Configure HTTPS_PROXY, HTTP_PROXY, and NO_PROXY in the
process environment. Harn's setup action enables this support automatically
when the runner's Node version provides it. On older Node versions, use a
network environment that provides direct GitHub access or pre-populate the
exact cache on a connected machine and run with --offline.
Failure recovery and source builds
Rerun the same command after a network interruption. Unique
.harn-bootstrap-* leftovers are ignored and can be deleted after no bootstrap
processes are active. An <install-dir>.lock directory can likewise be removed
after confirming no bootstrap process is active; normal runs reclaim dead,
stale, or timed-out owners automatically. If an operator must rebuild one
entry, remove only that version's metadata/archive directory and its versioned
install directory, then rerun online.
The bootstrapper does not silently build from source. A source build has different provenance, prerequisites, and cache semantics, so it must be an explicit policy decision. The supported fallback is to install the same exact version with Rust:
cargo install --locked harn-cli --version 0.10.31
Alternatively, check out the matching v0.10.31 source tag and run
cargo install --locked --path crates/harn-cli. Neither source-build path
emits a harn-bootstrap-v1 receipt or counts as a release-archive cache hit.
GitHub Actions
The first-party setup action is a thin adapter over this same file. It resolves
the version and platform through the bootstrapper, restores the candidate
cache, invokes the bootstrapper for verification and installation, adds the
binary directory to PATH, and exposes the JSON as its receipt output:
- uses: burin-labs/harn/.github/actions/setup-harn@<full-commit-sha>
id: harn
with:
version: 0.10.31
- env:
HARN_RECEIPT: ${{ steps.harn.outputs.receipt }}
run: node -e 'process.stdout.write(process.env.HARN_RECEIPT)'