Skip to content

Update in place, without taking plugins away from running colonies - #112

Merged
ParallelEntrepreneur merged 2 commits into
mainfrom
feat/update-in-place
Sep 18, 2026
Merged

ParallelEntrepreneur merged 2 commits into
mainfrom
feat/update-in-place

Conversation

@ParallelEntrepreneur

Copy link
Copy Markdown
Collaborator

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

It reuses the installer rather than reimplementing it

scripts/install-release.sh, from inside the app. 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 — one of the acceptance lines, for free.

The proposal said to fetch the tag into a clone and build it. Prebuilt releases landed since (#74), so there is a verified archive to install and no cargo/npm/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 fromresolve_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.

COLONIZER_KEEP_PREVIOUS=1 The installer keeps the slot it replaced
Session.app_slot Each colony records the directory its mounts resolved through
update::sweep_slots At the next start, once recovery has settled, a kept slot goes only if no live colony still names it

A person running the installer by hand keeps today's behaviour.

What holds an update, and what does not

Publishing colonies hold it, and are named in the refusal: the microVM is already gone and the host is committing and pushing, so interrupting leaves the colony failed with its PR unopened.

A working colony does not. It is detached, and recover reconnects it — which is the whole reason this is possible. Blocking on it would mean never updating while anything runs.

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.

Verified

The real installer, on this machine, with fake releases:

with COLONIZER_KEEP_PREVIOUS=1:
  app -> app-b, slots: app app-a app-b
  old slot's plugin still readable: "skill for v1.0.0"
without it (a hand-run install):
  app -> app-a, slots: app app-a        # previous removed, as before

The UI, against the mock: Update to v0.1.4Installing…Restarting… with "colonies keep their microVMs and reconnect" and a line per colony. The pane also now says how far behind the build is — "Released 9/18/2026, 12 days after the build you are running" — and shows the commit.

cargo test -p colonizer-harness    132 passed, 0 failed
cargo check                        0 warnings
npx tsc --noEmit                   clean
sh -n on both installers           clean

Not exercised: a real release installed by a running mothership, and the exec itself. Both need two real releases and an installed app, which I do not have here.

Acceptance in #45

  • UI shows the installed version and "update available" with notes
  • A fresh install checks by default
  • With checking off, no request to GitHub
  • Updating while a colony is publishing is refused, naming it
  • A failed build leaves the running version untouched
  • Mounted plugins stay readable through an update — the reason for KEEP_PREVIOUS and the sweep
  • Two colonies mid-turn, verified on real hardware — needs two real releases

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.
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.
@ParallelEntrepreneur
ParallelEntrepreneur merged commit ca1cd09 into main Sep 18, 2026
3 checks passed
@ParallelEntrepreneur
ParallelEntrepreneur deleted the feat/update-in-place branch September 18, 2026 03:47
ParallelEntrepreneur added a commit that referenced this pull request Sep 23, 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.

Tell the operator when a newer Colonizer is out, and update without losing colonies

1 participant