Upgrading
This page shows how to move homebutler to the latest release on the local machine and on every server in your config, and how to upgrade installs that a package manager or a container owns. It ends with what the 1.0 compatibility promise means for an upgrade.
Upgrade with homebutler
homebutler upgrade
upgrade asks GitHub for the latest release, then handles the local binary and each remote server in turn. It updates only what is out of date. Progress goes to standard error:
checking latest version... v1.7.0
upgrading local... ─ already v1.7.0
1 already up-to-date
Pass --local to skip the remote servers and upgrade only the binary you are running. Add --json to print a report on standard output, as well as the progress on standard error:
{
"latest_version": "1.7.0",
"results": [
{
"target": "local",
"prev_version": "1.7.0",
"new_version": "1.7.0",
"status": "up-to-date",
"message": "already v1.7.0"
}
]
}
Each result has a status of upgraded, up-to-date, skipped or error. Every target is tried. If any fails, the command then exits 1, such as upgrade failed on 1 of 3 targets: edge. A local dev build is skipped, not failed, so it doesn't change the exit code.
How remote servers are upgraded
For each server without local: true, upgrade connects over SSH and:
- Reads the version of the
homebutlerfound on the server'sPATH. - Skips the server if it already runs the latest release or a newer one.
- Downloads the release build for the server's operating system and architecture, checks it against the release's
checksums.txt, and uploads it over the existing binary. - Runs
versionon the server to check the new binary.
A server that has no homebutler yet reports not installed — run 'homebutler deploy --server <name>' first. Use deploy for the first install. A server whose binary is owned by another user reports the Permission denied and suggests deploy, which replaces it through sudo when the account has it.
The output for a server that was upgraded and a server that was unreachable, shortened to those lines:
upgrading edge... ✓ v1.6.0 → v1.7.0 (linux/amd64)
upgrading dead... ✗ ssh connect: [dead] SSH connection failed (192.168.1.30:22): dial tcp 192.168.1.30:22: connect: connection refused
→ Check: server online? correct host/port? firewall rules?
→ Config: /home/you/.config/homebutler/config.yaml
How the local binary is upgraded
upgrade follows symlinks to the real executable, renames it to homebutler.bak, writes the new binary in its place, and deletes the backup. If the write fails, it restores the backup. When the directory isn't writable by your user, it fails with a hint to run sudo homebutler upgrade.
On Windows, upgrade downloads the release .zip and leaves the previous binary next to the new one as homebutler.exe.bak, because Windows doesn't delete a program while it runs. The next upgrade replaces it. When access is denied, the hint is to run homebutler upgrade in a terminal opened as administrator, after stopping any running homebutler.
It leaves two kinds of binary alone:
- A build that reports
devas its version, such as one fromgo installorgo build. The line is─ dev build, left as it is, the summary counts it as skipped, and the remote servers are still upgraded. Update it withgo install github.com/Higangssh/homebutler@latest, or rebuild it from your checkout. A remote server that runs adevbuild isn't skipped:upgradereplaces it with the release. - A build newer than the latest release, such as one made from a later commit. The result has the status
up-to-dateand a message that names both versions.
Upgrade an install that something else manages
upgrade replaces the file in place, so an install owned by a package manager is better updated by that manager. Use the command that matches how you installed homebutler:
| Installed with | Upgrade with |
|---|---|
| Homebrew | brew upgrade homebutler |
| Install script | Run the script again, or homebutler upgrade |
| Go | go install github.com/Higangssh/homebutler@latest |
| npm | npm install -g homebutler@latest |
| Container | Pull the new image, then recreate the container: docker pull ghcr.io/higangssh/homebutler:latest, then docker rm -f homebutler, then the same docker run command (or docker compose pull && docker compose up -d). |
A container keeps your config and snapshots in the mounted folder and volume, so recreating it keeps them. See Run in a container.
The binary on each remote server is separate from the one on the hub. After you upgrade the hub, run homebutler upgrade from a machine with SSH access to bring the servers level, or the commands the hub runs on them fail with does not have errors.
Check these when upgrading to 1.4, 1.5, 1.6 or 1.7
--allexit codes: from 1.7.0, a command run with--allexits1when any server couldn't be asked or its command failed there. A cron job or script over servers that are often off, or over a server without Docker fordocker list --all, now sees a failure. See Multi-server.upgradefrom adevbuild: from 1.7.0 it exits0again, and in--jsonthe local result'sstatusisskipped, where 1.6.0 reportederror. A script that matched onerrorshould acceptskipped.--serverand--allon commands that can't use them: from 1.6.0,install,inventory scan,config validate,watch,serveand the other local commands exit1where they used to run on this machine. Drop the flag.upgrade --serveris refused too. See Multi-server.--allon commands that change a machine: from 1.6.0,docker restart,docker stop,backup,restoreandwakeare refused with--all. Name one server with--server.deployandupgrade: from 1.6.0 they exit1when any server failed. A script that treated exit0as the only outcome now sees the failure.report --all: from 1.6.0 it also saves a snapshot on alocal: trueserver. Add--no-saveto compare without saving.GET /api/ports: from 1.6.0 a failed read returns500with the reason, where it returned200with an empty list.docker_topanddocker top --json: from 1.4.0,commandholds the kernel's process name (thecommvalue ofps), without arguments. A Node.js process showsMainThread. A script that read arguments from it gets only that name.- filebrowser: see the filebrowser note.
GET /api/report?server=<name>: from 1.5.0 it saves no snapshot on that server. A server that got its snapshots only from being polled needsPOST /api/report/snapshot?server=<name>orhomebutler reportfrom cron.- Containers on runtimes that write no marker file: from 1.5.0 the image sets
HOMEBUTLER_CONTAINER=1, so under containerd, such as Kubernetes without dockershim,serveneeds a token and local MCP calls are refused. servewithHOMEBUTLER_TOKENin its environment: from 1.5.0 aservewith no other token uses it, so a browser that had the dashboard open is asked for that token.
What stays the same across upgrades
From 1.0, the parts that scripts, agents and widgets build on don't change in a minor or patch release. You can upgrade without editing:
- MCP tool names, input schemas and risk classes.
- JSON field names and types in every command's
--jsonoutput, and the words fields such askind,categoryandstatuscan hold. - The documented config keys, with their names and meanings.
- The web dashboard's HTTP routes, and what each one asks the caller to send.
- The exit codes of
doctor. - Snapshots that earlier releases saved:
reportreads a snapshot written by any release from 0.18.0 on and compares against it.
New tools, optional fields, config keys, routes and kind words can appear in any release, each with a changelog entry, so a script should ignore fields it doesn't know. Terminal output, log lines, tool descriptions, the order of items in a list, and the dashboard's page markup can change at any time.
A frozen item is replaced in four steps: add the replacement, mark the old one deprecated in the changelog, keep both for at least one minor release, and remove the old one only in the next major release. JSON output and stability lists exactly what is frozen. Release notes are in the changelog.