Upgrading
Upgrading a beam host and the installations behind it
Two programs that speak to each other over a versioned protocol, on machines you restart independently. This page is the order to do that in, what the interlock between them actually is, and how to get back.
The one interlock
The tunnel carries a protocol version in its handshake. A host and an installation that disagree do not negotiate: the host closes the socket with BEAM_CLOSE_VERSION, and the installation parks. It does not retry, because redialing a host that cannot speak to this build would never start working.
Drawing the diagram…
From the installation's side that looks like:
beam host https://beam.example.com refused protocol version 1; standing downand the settings page reads Stood down (the beam host speaks a different tunnel protocol version). It is the one stand-down that a person has to clear, by upgrading one end or the other.
Everything else additive - a new hello field, a new control frame, a new feature name - is designed not to bump that version. An end that does not know a field ignores it, an unknown control frame falls through both switches, and features are negotiated by name. So in practice:
- Most upgrades need no coordination at all. Upgrade either end whenever.
- A protocol version bump is announced in the release notes and needs both ends moved, host first.
The order
Host first, then the installations. A newer host serving older installations is the state that works while you roll: it keeps answering the version they speak, and each one reconnects on its own as you get to it. The reverse leaves upgraded installations parked until you reach the host.
Drawing the diagram…
Within that, one installation at a time. They are independent; there is no quorum and nothing to drain.
Upgrading the beam host
It holds nothing that a restart loses, so this is a replace-and-start.
# Containers
docker compose pull && docker compose up -d beam-host
# The compiled binary
systemctl stop beam-host
install -m 0755 beam-host /usr/local/bin/beam-host
systemctl start beam-hostEvery tunnel drops and every installation re-dials on its own backoff, so expect a few seconds of 502 for anybody with a browser open. The offline page they get polls and goes back in by itself.
Afterwards, in order:
curl -s https://beam.example.com/__beam/health | jq .live
curl -s -H "Authorization: Bearer $BEAM_TOKEN" \
https://beam.example.com/__beam/slots | jq '.slots[].slot'live should come back to the number you issued within a minute. A slot that does not return is one whose installation cannot reach the host, and its own log says why.
Rolling back is the same operation with the older binary or image tag. The storage backend is unchanged between versions unless a release says otherwise.
Upgrading a containerised installation
The data directory is what makes this safe to repeat: the profile, the database, the harnesses under $HOME and the workspaces all outlive the image.
mise run docker update # build the image, recreate, wait for health
mise run docker update --pull # pull a published tag instead of buildingIt records the image digest before it starts, recreates the container from deploy.json - the same port, mounts, beam host, slot and token the last up used - and then waits up to a minute for /api/health to answer. It exits non-zero if it does not, so it can be the last line of a deploy script.
image 5e1c8a7b9d21 -> a04f2e6b118c
health {"status":"ok","mode":"serve",...}Getting back is one command, because that digest was kept:
mise run docker rollbackIt refuses rather than guesses when there is no recorded previous image, or when the recorded one is no longer on the machine. It puts the image back and leaves the recorded reference pointing at the tag, so a later update still targets what you meant.
By hand, the same shape:
docker pull mechabedo:latest
docker stop mechabedo && docker rm mechabedo
docker run -d --name mechabedo ... # the same run command, with the same volumeWhich is exactly the thing deploy.json exists to stop you from getting wrong.
Automatic updates
A release install of mechabedo updates itself. It reads a signed release feed every few hours, downloads only what changed in the newest release, rebuilds the new version beside the running one and checks every file, and only then swaps it in. The version it replaced is kept, and a new version that fails to start three times is put back on its own.
Which installs update. One that sits where its installer put it, in a directory the user running it can write:
| Install | Updates itself |
|---|---|
macOS, Mechabedo.app in ~/Applications or a writable /Applications | yes |
Windows, the per-user install under %LOCALAPPDATA%\Programs\Mechabedo | yes |
Linux desktop, installed by mise run ship:local-install-linux or by Install for updates | yes |
| Linux, an AppImage run from wherever it was downloaded | no; Settings > Updates offers Install for updates |
| Windows, the MSI under Program Files | no; use the newer installer |
A server under systemd, in /var/lib/mechabedo/app with the launcher (Server setup) | yes |
A server binary in /usr/local/bin | no, until moved into that layout |
| A container | no; mise run docker update, below |
A checkout, or a build made with mise run ship | never |
When it applies. Settings > Updates > Policy:
| Policy | Applies the update |
|---|---|
| Auto (default) | on a desktop, when you quit or at the next launch; on a server, when nothing has been busy for ten minutes |
| On quit | when you quit; the next launch is the new version |
| On launch | at the next launch, with one quick restart before the window loads |
| When idle | once no agent turn, Bedo turn, graph run or terminal command has been active for ten minutes |
| Ask | only when you click Restart to update |
| Manual | never; it only downloads |
Every policy but Manual also shows Restart to update once the download is ready, in Settings > Updates and as a dot in the status bar beside the version: click it once to arm, and again to restart. Check now reads the feed at once. To turn updates off entirely, switch them off there or set MECHABEDO_UPDATES=off in the environment.
Going back. Revert to the previous version, in Settings > Updates, puts the kept version back and restarts. That version is not offered again until a newer one appears. Data written since stays, because a release keeps its database readable by the one before it; a release that cannot says so, and then revert is refused. A version that never came up is put back without asking, and the backup taken before it touched the database is restored at the next start.
Backups. Each time a profile first starts on a new version, it takes an App data backup labelled before-<version>, and keeps the last three of them.
From another machine. An agent with the fleet tools calls fleet_update with that machine's slot as instance. The machine being updated must have granted that slot update under Settings > Beam > Inbound from other machines (the Operate preset includes it), and someone there approves it in its approvals tray, unless its unattended policy answers operations.
Upgrading the other shapes
systemd, over the compiled binary. In the managed layout it updates itself, above. To put a binary in by hand, stop the unit, drop it into a new version directory, point current at it, and start the unit. The config document is re-read and re-applied on every start, and the harnesses are under $HOME rather than in the binary, so they survive it.
version=$(./mechabedo --version | cut -d" " -f1)
sudo systemctl stop mechabedo
sudo -u mechabedo install -D -m 0755 mechabedo /var/lib/mechabedo/app/versions/$version/mechabedo
sudo -u mechabedo ln -sfn versions/$version /var/lib/mechabedo/app/current
sudo systemctl start mechabedo
journalctl -u mechabedo -n 50The desktop app. A release install updates itself, above. Otherwise replace it and launch it. The profile is under ~/.mechabedo/<profile>/ and is not in the bundle.
A Linux desktop that ships from its own checkout. One command in that checkout pulls, builds, installs and restarts, and works the same over ssh from another machine:
mise run ship:local-install-linux --pull --restart
ssh desktop 'cd ~/src/mechabedo && mise run ship:local-install-linux --pull --restart'--pull fast-forwards the checkout, runs prep and ships. It installs the AppImage's contents into ~/.local/share/mechabedo/desktop/versions/<version>/ with a launcher at ~/.local/bin/mechabedo-desktop, the same layout automatic updates use, and the version it replaced stays beside it as previous. --appimage keeps the older single-file install at ~/.local/bin/Mechabedo.AppImage, with Mechabedo.AppImage.previous as its rollback. --restart stops the running copy and starts the new one as a user systemd unit, so it opens on the desktop and outlives the ssh session. Run from a terminal inside the copy being replaced, the restart hands itself to a detached unit first, since stopping that copy would stop the script too.
Restarting without a shell. An installation picks up a changed config document or a replaced binary only when it restarts. Settings > App data > Restart does that from any browser signed in as an admin, through the beam too, and waits until the installation answers again. From another machine, an agent with the fleet tools can call fleet_restart with that machine's slot as instance. The machine being restarted must have granted that slot restart under Settings > Beam > Inbound from other machines, and someone at it approves the restart in its approvals tray. Installing a new release remotely is fleet_update, under Automatic updates.
Before an upgrade that could go badly
Take the backup first, and take it as one unit: the database and the settings directory together (Running mechabedo as a server). An image upgrade does not migrate the volume, but a database migration inside a release does, and those are one-way.
docker stop mechabedo
docker run --rm -v mechabedo-data:/data -v "$PWD":/out alpine \
tar -C /data -czf /out/before-upgrade.tgz .
docker start mechabedoThe whole volume, not settings alone: the database is under /data/home, and the two halves only mean anything together.
What a version mismatch looks like from each end
| End | Symptom |
|---|---|
| Installation log | beam host <url> refused protocol version <n>; standing down |
| Installation UI | Settings, Beam reads Stood down, with the reason |
| Host log | Nothing. The refusal happens before the tunnel is adopted |
Host /__beam/slots | That slot missing from slots, still present in configured |
| A browser on that slot | The offline page, waiting for an installation that will not come |
The asymmetry is deliberate: the installation knows why and says so, and the host has nothing useful to add about a peer it never accepted.
After any upgrade
/__beam/healthshows thelivecount you expect.- Each slot is back in
/__beam/slots, and itsagent.versionis the one you just deployed - which is the point of it being reported at all. - One browser reaches one instance and signs in.
- One chat session opens and stays open while idle.
Step 2 is the cheap one that catches a half-finished roll: a slot still reporting the old version is an installation you have not restarted yet.