Modelled on ShippingTracker's Updater.vb: gitea releases API, silent startup check vs. loud manual one, SHA256 verification, and the rename-swap with .old cleanup on next launch. Records the research so it doesn't need repeating — haul's repo is readable anonymously, so the updater needs no token, and the 404 on releases/latest is just an empty release list. Also records the two blockers: the release pipeline has to exist first (cgo means make dist can't cross-compile), and dynamically linked Linux binaries may not run on the machine that downloads them. Not started; four open decisions listed at the end of the doc. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
6.9 KiB
Auto-update for haul — plan
Status: planned, not started. Blocked on four decisions (bottom of this file). No code has been written.
The model is the self-updater in
ShippingTracker
(HelloWorld/Updater.vb), which haul should behave like.
Research already done — don't redo this
Verified 2026-07-29 against the live gitea instance:
https://gitea.apointless.space/api/v1/repos/bsncubed/ShippingTracker/releases/latestreturns HTTP 200 anonymously. No token needed..../repos/bsncubed/haulalso returns 200 anonymously, and.../repos/bsncubed/haul/releasesreturns[]. So haul's repo is public; the 404 onreleases/latestis simply "no releases exist yet", not a permissions problem. The updater will work unauthenticated.- Release JSON exposes
tag_name,draft,prerelease, and anassetsarray whose entries carrynameandbrowser_download_url. ShippingTracker's latest (1.1.0.2) attaches exactly two assets:Shipping Tracker.exeandchecksums.txt.
What ShippingTracker's updater does
- GET
/api/v1/repos/{owner}/{repo}/releases/latest; taketag_name, match assets by exact filename. - Parse the tag into a version — tolerates a leading
v, pads missing components with0(an unsetSystem.Versioncomponent is-1, which sorts wrong). - Silent vs. loud. A startup check swallows every failure (offline, DNS, timeout, non-200, malformed JSON) with no UI at all. A user-clicked check reports "you're up to date" and surfaces errors.
- Prompts with current vs. available before doing anything.
- Pre-flights write permission next to the executable, so a read-only install directory fails early rather than half-applying an update.
- Streams the download to
<exe>.newbehind a small progress window. - Verifies SHA256 against a
checksums.txtasset insha256sumformat (<hash> <filename>). If that asset is absent the update still installs, unverified. - Swaps by rename: running exe →
.old,.new→ exe, rolling back if the second rename fails..oldis deleted on the next launch. - On any failure, offers to open the releases page in a browser instead.
Ports over unchanged
The API shape, the silent/loud split, tag parsing, checksum verification, the
rename-swap with .old cleanup, and the manual-download fallback.
The rename dance exists because Windows won't delete a running binary but will rename one. Linux and macOS also allow renaming a running executable (the inode stays live), so a single code path covers all three platforms.
Has to change for haul
- haul has no version at all. ShippingTracker compares against
AssemblyVersion;cmd/haulhas no equivalent, which is why the Makefile deliberately omits-X main.version(a-Xagainst a non-existent symbol is silently a no-op). Adding one is a prerequisite — without it every check reports "up to date". - One asset becomes many. ShippingTracker is Windows-only, so its
AssetNameis a single constant. haul must select an asset byruntime.GOOS/runtime.GOARCH(haul-linux-amd64,haul-darwin-arm64,haul-windows-amd64.exe). - The executable bit. On Unix the downloaded file needs
chmod 0755. Nothing in the VB version corresponds to this. - Fyne threading.
internal/uionly touches widgets from the UI thread and returns from I/O viafyne.Do. The updater must follow the same shape assession.transferandpane.navigateKeepMarks.
Blockers — read before starting
The release assets can't be built yet. This gates everything. haul is cgo,
so make dist produces the native binary only; there is currently no way to
produce macOS or Windows assets from the Linux box. An updater with nothing to
download is pointless, so the release pipeline must come first
(fyne-cross over Docker, which is installed, or real machines).
Linux binaries don't travel. haul links dynamically against libGL,
libX11, libc and ten others. A binary built on Ubuntu 24.04 may not start on
a different or older distro — it would download, swap itself in, and then fail
to launch, which is the worst failure mode an updater has. ShippingTracker never
hit this (.NET Framework, stable Windows ABI). Options: build against the oldest
glibc worth supporting, ship Linux without auto-update, or accept it.
checksums.txt is not a signature. It lives on the same server as the
binary, so it catches corruption and truncation but not a compromised gitea —
anyone who can replace one can replace the other. ShippingTracker accepts this.
Closing the gap means a minisign/ed25519 signature with the public key compiled
into haul, roughly 40 extra lines.
Proposed shape
New package internal/selfupdate, UI-free so it can be tested against a fake
HTTP server:
| Piece | Responsibility |
|---|---|
Check(ctx) (*Release, error) |
Fetch latest, parse tag, pick this platform's asset |
Release.Newer(current) |
Version comparison |
Download(ctx, rel, progress) |
Stream to <exe>.new, report progress |
Verify(path, checksumURL) |
SHA256 against checksums.txt |
Apply(newPath) |
Rename dance, rollback, chmod |
CleanupOld() |
Delete <exe>.old; never errors |
Plus a thin internal/ui/update.go for dialogs, reusing confirmDialog from
dialogs.go and a progress dialog modelled on the existing transfer bar.
Integration points, mirroring ShippingTracker's Form1_Load and Form3:
CleanupOld()and a silent check when the connect window opens (newConnectWindow).- A manual "Check for updates" button. The connect window is the natural home — haul has no settings screen.
Release process: a make release VERSION=x.y.z target that builds, writes
checksums.txt, and prints the tag/upload steps. Tag as 1.2.0 or v1.2.0,
and don't mark it pre-release — /releases/latest skips drafts and
pre-releases.
Phasing
| Phase | Work |
|---|---|
| 0 | Version stamping — var version in cmd/haul, -X in the Makefile, haul --version |
| 1 | Release pipeline — cross-builds via fyne-cross, checksums, first tagged release |
| 2 | internal/selfupdate with tests against a fake HTTP server |
| 3 | UI wiring — startup silent check, manual button, progress and error dialogs |
| 4 | README: how updating works, and how to publish a release |
Phases 0 and 1 are worth doing regardless — there is currently no way to ship haul to anyone at all.
Open decisions — needed before building
- Auto-check on startup, or manual only? ShippingTracker does both. Silent startup checks mean haul contacts gitea on every launch.
- Which platforms get auto-update? Suggestion: exclude Linux at first, for the dynamic-linking reason above.
- Checksums only, or add signatures?
- Do phase 1 first, or write the updater against ShippingTracker's existing releases as a test fixture?