481b998525
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>
144 lines
6.9 KiB
Markdown
144 lines
6.9 KiB
Markdown
# 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](https://gitea.apointless.space/bsncubed/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/latest`
|
|
returns **HTTP 200 anonymously**. No token needed.
|
|
- `.../repos/bsncubed/haul` also returns **200 anonymously**, and
|
|
`.../repos/bsncubed/haul/releases` returns `[]`. So haul's repo is public;
|
|
the 404 on `releases/latest` is simply "no releases exist yet", not a
|
|
permissions problem. The updater will work unauthenticated.
|
|
- Release JSON exposes `tag_name`, `draft`, `prerelease`, and an `assets` array
|
|
whose entries carry `name` and `browser_download_url`. ShippingTracker's
|
|
latest (`1.1.0.2`) attaches exactly two assets: `Shipping Tracker.exe` and
|
|
`checksums.txt`.
|
|
|
|
## What ShippingTracker's updater does
|
|
|
|
1. GET `/api/v1/repos/{owner}/{repo}/releases/latest`; take `tag_name`, match
|
|
assets by exact filename.
|
|
2. Parse the tag into a version — tolerates a leading `v`, pads missing
|
|
components with `0` (an unset `System.Version` component is `-1`, which
|
|
sorts wrong).
|
|
3. **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.
|
|
4. Prompts with current vs. available before doing anything.
|
|
5. **Pre-flights write permission** next to the executable, so a read-only
|
|
install directory fails early rather than half-applying an update.
|
|
6. Streams the download to `<exe>.new` behind a small progress window.
|
|
7. Verifies SHA256 against a `checksums.txt` asset in `sha256sum` format
|
|
(`<hash> <filename>`). If that asset is absent the update still installs,
|
|
unverified.
|
|
8. **Swaps by rename**: running exe → `.old`, `.new` → exe, rolling back if the
|
|
second rename fails. `.old` is deleted on the next launch.
|
|
9. 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
|
|
|
|
1. **haul has no version at all.** ShippingTracker compares against
|
|
`AssemblyVersion`; `cmd/haul` has no equivalent, which is why the Makefile
|
|
deliberately omits `-X main.version` (a `-X` against a non-existent symbol is
|
|
silently a no-op). Adding one is a prerequisite — without it every check
|
|
reports "up to date".
|
|
2. **One asset becomes many.** ShippingTracker is Windows-only, so its
|
|
`AssetName` is a single constant. haul must select an asset by
|
|
`runtime.GOOS`/`runtime.GOARCH` (`haul-linux-amd64`, `haul-darwin-arm64`,
|
|
`haul-windows-amd64.exe`).
|
|
3. **The executable bit.** On Unix the downloaded file needs `chmod 0755`.
|
|
Nothing in the VB version corresponds to this.
|
|
4. **Fyne threading.** `internal/ui` only touches widgets from the UI thread and
|
|
returns from I/O via `fyne.Do`. The updater must follow the same shape as
|
|
`session.transfer` and `pane.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
|
|
|
|
1. **Auto-check on startup, or manual only?** ShippingTracker does both. Silent
|
|
startup checks mean haul contacts gitea on every launch.
|
|
2. **Which platforms get auto-update?** Suggestion: exclude Linux at first, for
|
|
the dynamic-linking reason above.
|
|
3. **Checksums only, or add signatures?**
|
|
4. **Do phase 1 first, or write the updater against ShippingTracker's existing
|
|
releases as a test fixture?**
|