Skip to content

Build prebuilt releases, installable with one command - #74

Merged
ParallelEntrepreneur merged 1 commit into
mainfrom
feat/prebuilt-releases
Sep 17, 2026
Merged

ParallelEntrepreneur merged 1 commit into
mainfrom
feat/prebuilt-releases

Conversation

@ParallelEntrepreneur

Copy link
Copy Markdown
Collaborator

A version tag now publishes a GitHub release with a prebuilt app for Linux x86_64
and one for Apple Silicon, plus an installer:

curl -fsSL https://colonizer.dev/install.sh | sh

Nothing on the machine needs Rust or Node.js to install it.

.github/workflows/release.yml

  • linux-binaries: builds colonizer-agentd and rtk for both colony architectures,
    and the Linux harness, as static musl binaries. They are built inside
    rust:1-alpine with docker run, because GitHub's JavaScript actions don't run
    in Alpine containers. A static harness starts on any glibc; one built on the
    Ubuntu 24.04 runner would not start on Debian 12 or Ubuntu 22.04.
  • bundle: runs scripts/install.sh --bundle on ubuntu-24.04 and macos-15, then a
    smoke test. The app starts from the unpacked archive, finds its assets there,
    reports agentd, the web UI and the claude-code module, and serves the UI.
  • release: runs for tags only. The tag must match crates/colonizer's version. It
    writes SHA256SUMS and publishes the archives with install.sh.

A release contains no Anthropic code. The Claude Agent SDK is "all rights reserved",
and its optional platform packages are Claude Code itself (224 MB).

  • The bundle installs the module with --omit=optional. The runner already passes
    pathToClaudeCodeExecutable, so those packages were never used.
  • scripts/record-fetch-at-install.mjs takes the SDK out and writes its lockfile
    tarball URL and sha256, after checking the tarball against package-lock.json's
    integrity.
  • scripts/install-release.sh fetches the SDK from the npm registry against that
    record. On a Mac it also fetches the linux-arm64 Claude Code build from
    Anthropic's stable channel, checked against the manifest. macOS's own plutil
    reads the manifest, so no Node.js is needed. The Workflow's Package step fails
    if the SDK is found in a bundle.

scripts/install-release.sh (published as install.sh)

  • Refuses platforms it can't serve, including Linux without read-write /dev/kvm.
  • Checks the archive against SHA256SUMS.
  • Swaps ~/.local/share/colonizer/app in whole, and links ~/.local/bin/colonizer.
  • Running it again updates in place. It reuses a Claude Code binary that still
    matches the manifest, and never touches settings or colonies.
  • Accepts COLONIZER_VERSION, COLONIZER_APP and --pull-image.
  • Everything runs inside main(), so a truncated download runs nothing.

scripts/install.sh --bundle skips the KVM check and the Claude Code fetch, and
uses binaries from $COLONIZER_PREBUILT instead of building them.
build-agentd.sh and build-rtk.sh accept COLONIZER_BUILD_HERE=1, to build inside
an existing rust:1-alpine instead of a microVM. rtk's source is then unpacked
outside the repository, because cargo refused to build it as a stray member of
the harness workspace (the local test below caught that).

Tested on an Apple Silicon Mac:

  • In a rust:1-alpine microVM with COLONIZER_BUILD_HERE=1: agentd, rtk and the
    harness built as static aarch64 musl binaries. The static harness started in a
    Debian (node:24-bookworm) microVM and served /api/status.
  • install.sh --bundle with those guest binaries: a 129 MB app, a 44 MB archive,
    and no Agent SDK inside.
  • curl … | sh against the archive served locally, into an empty HOME, took 41 s.
    It fetched the SDK (sha256 checked) and Claude Code 2.1.267 (manifest checked).
  • The installed app reported its assets in the new location: agentd, web,
    claude-code, the guest Claude binary, and msb 0.6.18 from the relocated vendor
    directory. It served the UI.
  • Running the installer again reused Claude Code, left no app.new or app.old,
    and kept the data directories.
  • The installed colonizer-agentd, rtk and claude-guest each ran --version
    inside node:24-bookworm.
  • The runner's 58 tests passed against an SDK restored from the npm tarball
    without optional packages.

Not tested yet:

  • The workflow itself; this pull request is its first run.
  • The linux-x86_64 bundle end to end.
  • Launching a colony from an installed release.
  • colonizer.dev/install.sh, which the website adds once a release exists. Until
    then, install from the release URL directly.

A version tag now publishes a GitHub release with a prebuilt app for Linux x86_64
and one for Apple Silicon, plus an installer:

    curl -fsSL https://colonizer.dev/install.sh | sh

Nothing on the machine needs Rust or Node.js to install it.

.github/workflows/release.yml
- linux-binaries: builds colonizer-agentd and rtk for both colony architectures,
  and the Linux harness, as static musl binaries. They are built inside
  rust:1-alpine with `docker run`, because GitHub's JavaScript actions don't run
  in Alpine containers. A static harness starts on any glibc; one built on the
  Ubuntu 24.04 runner would not start on Debian 12 or Ubuntu 22.04.
- bundle: runs scripts/install.sh --bundle on ubuntu-24.04 and macos-15, then a
  smoke test. The app starts from the unpacked archive, finds its assets there,
  reports agentd, the web UI and the claude-code module, and serves the UI.
- release: runs for tags only. The tag must match crates/colonizer's version. It
  writes SHA256SUMS and publishes the archives with install.sh.

A release contains no Anthropic code. The Claude Agent SDK is "all rights reserved",
and its optional platform packages are Claude Code itself (224 MB).
- The bundle installs the module with --omit=optional. The runner already passes
  pathToClaudeCodeExecutable, so those packages were never used.
- scripts/record-fetch-at-install.mjs takes the SDK out and writes its lockfile
  tarball URL and sha256, after checking the tarball against package-lock.json's
  integrity.
- scripts/install-release.sh fetches the SDK from the npm registry against that
  record. On a Mac it also fetches the linux-arm64 Claude Code build from
  Anthropic's stable channel, checked against the manifest. macOS's own plutil
  reads the manifest, so no Node.js is needed. The Workflow's Package step fails
  if the SDK is found in a bundle.

scripts/install-release.sh (published as install.sh)
- Refuses platforms it can't serve, including Linux without read-write /dev/kvm.
- Checks the archive against SHA256SUMS.
- Swaps ~/.local/share/colonizer/app in whole, and links ~/.local/bin/colonizer.
- Running it again updates in place. It reuses a Claude Code binary that still
  matches the manifest, and never touches settings or colonies.
- Accepts COLONIZER_VERSION, COLONIZER_APP and --pull-image.
- Everything runs inside main(), so a truncated download runs nothing.

scripts/install.sh --bundle skips the KVM check and the Claude Code fetch, and
uses binaries from $COLONIZER_PREBUILT instead of building them.
build-agentd.sh and build-rtk.sh accept COLONIZER_BUILD_HERE=1, to build inside
an existing rust:1-alpine instead of a microVM. rtk's source is then unpacked
outside the repository, because cargo refused to build it as a stray member of
the harness workspace (the local test below caught that).

Tested on an Apple Silicon Mac:
- In a rust:1-alpine microVM with COLONIZER_BUILD_HERE=1: agentd, rtk and the
  harness built as static aarch64 musl binaries. The static harness started in a
  Debian (node:24-bookworm) microVM and served /api/status.
- `install.sh --bundle` with those guest binaries: a 129 MB app, a 44 MB archive,
  and no Agent SDK inside.
- `curl … | sh` against the archive served locally, into an empty HOME, took 41 s.
  It fetched the SDK (sha256 checked) and Claude Code 2.1.267 (manifest checked).
- The installed app reported its assets in the new location: agentd, web,
  claude-code, the guest Claude binary, and msb 0.6.18 from the relocated vendor
  directory. It served the UI.
- Running the installer again reused Claude Code, left no app.new or app.old,
  and kept the data directories.
- The installed colonizer-agentd, rtk and claude-guest each ran `--version`
  inside node:24-bookworm.
- The runner's 58 tests passed against an SDK restored from the npm tarball
  without optional packages.

Not tested yet:
- The workflow itself; this pull request is its first run.
- The linux-x86_64 bundle end to end.
- Launching a colony from an installed release.
- colonizer.dev/install.sh, which the website adds once a release exists. Until
  then, install from the release URL directly.
@ParallelEntrepreneur
ParallelEntrepreneur merged commit 1f28de3 into main Sep 17, 2026
5 checks passed
@ParallelEntrepreneur
ParallelEntrepreneur deleted the feat/prebuilt-releases branch September 17, 2026 08:10
ParallelEntrepreneur added a commit that referenced this pull request Sep 18, 2026
)

* Update in place, without taking plugins away from running colonies

Closes #45. #110 did the check; this applies it.

It runs scripts/install-release.sh from inside the app rather than
reimplementing the download, the checksum and the swap. That installer already
unpacks beside the running app and moves the symlink with one rename, so a
failure part-way leaves the running version exactly as it was — which is the
"a build that fails leaves the running version untouched" line, for free.

The proposal said to fetch the tag into a clone the mothership owns and build
it. Prebuilt releases landed since (#74), so there is a verified archive to
install and no cargo, npm or node needed on the machine. install.sh now ships
the installer into dist/ so an installed mothership has a local copy, instead of
downloading a script to execute.

The part that would have lost work: colonies mount vendored plugins straight out
of the app directory the mothership started from, because resolve_assets
canonicalises the symlink away — and the installer deletes the slot it replaces.
Applying an update under a running colony would take its plugins with it. So the
installer learned COLONIZER_KEEP_PREVIOUS=1, each session records the directory
its mounts resolved through, and a kept directory is swept at the next start
once no live colony still names it. A person running the installer by hand keeps
today's behaviour.

Publishing colonies hold the update, and are named in the refusal: the microVM
is already gone and the host is committing and pushing, so interrupting it
leaves the colony failed with its pull request unopened. A colony that is only
working does not hold it — it is detached, and recover reconnects it, which is
the whole reason this can be done at all.

The restart is an exec of the app symlink's binary, after the live map is told
this mothership is going and the mesh children are killed so the new process can
take their ports.

Checked by driving the real installer on this machine with fake releases: with
KEEP_PREVIOUS the previous slot survives an update and its plugin file is still
readable, and without it the slot is removed exactly as before. The UI was
driven against the mock: Update to v0.1.4 goes to Installing, then Restarting
with "colonies keep their microVMs and reconnect" and a line per colony.

Not exercised: a real release installed by a running mothership, and the exec
itself. Both need two real releases and an installed app.

* Say how far behind the running build is, and which commit it is

The pane showed a version and a date. It now also says how old the build is
relative to the release being offered — "Released 9/18/2026, 12 days after the
build you are running" — because a version number alone does not tell you
whether you are a day or a season behind.

The commit is shown beside the build time, and a development build says which
release it came after.

The gap is computed from the build time and the release date, so it is honest
about the build rather than about the tag: someone running a build from a
checkout sees how far that checkout has fallen behind.

---------

Co-authored-by: Nick <40026523+Nick-CHI@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant