Files
gosentry/docs/AUTO_UPDATE_PLAN.md
T

12 KiB
Raw Blame History

Auto-update implementation plan

Working document for in-app updates from GitHub Releases. Retire this file and record the shipped behaviour in CHANGELOG.md, ARCHITECTURE.md, and STANDARDS.md when all phases are done.

Approach: design-first — build and agree the Settings UI on a stub before any network or install code. Backend work follows the UI contract.


1. Goal and non-goals

Goal: Let the user update the portable build (zip/tar.gz produced by .github/workflows/release.yml) to the latest GitHub Release in one confirmed action, with an application restart.

Non-goals:

  • macOS (stub returns a clear error; no self-replace path in v1).
  • Installers, package managers, or system-wide installs outside the app folder.
  • Downgrades, pre-release channels, delta updates, or silent install without confirmation.

2. Current state

Facts the design depends on:

Topic Detail
Version app.Version in src/app/version.go; release builds override via -ldflags -X in scripts/ci-build-release.sh.
Artifacts gosentry-<ver>-linux-{amd64,arm64}.tar.gz, gosentry-<ver>-windows-amd64.zip. Executable at archive root: gosentry / gosentry.exe.
Portable layout Config and jobs live beside the executable (src/storage/paths.go). Autostart shortcuts keep working after replace.
Single instance 127.0.0.1:37653 (src/ui/singleinstance.go). A second process that starts before the port is free sends "show" and exits — the main restart risk.
Hosting origin is Gitea (gitea.mixdep.ru/mix/gosentry). GitHub mixeme/gosentry is a pruning push mirror (git refs only). Release assets are attached by GitHub Actions, not mirrored. Update source: GitHub Releases API only.
Repository URL in app Today projectRepositoryURL lives in ui; move release coordinates to app (constant or build-time -X).

Mirror release ordering (DEVELOPMENT.md §Releasing through the GitHub push mirror):

  1. A release can appear on GitHub before the workflow attaches archives and checksum files (build takes several minutes). If releases/latest has no matching asset or no SHA256SUMS / .sig, treat as not yet available: no UI error, no “up to date”; retry on the next check.
  2. Deleting or moving a tag in Gitea removes or repoints the GitHub release. Re-fetch release metadata before download; HTTP 404 → treat as no update.

3. UI design (built first)

Implement the full user-facing surface on a stub (no network, no install). Capture screenshots for every state; Phase 2 does not start until the owner signs off on layout and copy in this document.

3.1 Elements

Location Control
Settings → Application Checkbox Check for updates (saved with Save, like other toggles).
Settings → About Row Updates: status label, one primary button (label depends on state), Release notes hyperlink, progress bar while downloading.
Modal Install and restart confirmation: target version, download size, text that GoSentry will restart and scheduled runs pause briefly.
Modal Refusal when a job is running (same policy as changing the jobs file).
Modal Error for manual check or install failure (dialog.ShowError).
Tray menu Update to X… when a newer version is known (tray enabled).
History Session line after successful restart: Updated from X to Y.

3.2 States (screenshot each)

  1. Auto-check off, never checked.
  2. Checking — primary button disabled.
  3. Up to date — includes last-check time.
  4. Available — Install and restart + Release notes.
  5. Downloading — progress + Cancel.
  6. Restarting — short transitional copy.
  7. Failed — status + Check now (or equivalent).
  8. Available, app directory not writable — Release notes link only (no Install).
  9. Unsupported — dev build (0.0.0-dev / unparseable version) or macOS.

A release that exists but has no assets yet does not get its own state; the UI matches Up to date until artifacts are present.

3.3 View model

  • updateView in ui: status text, primary button label/enabled/visible, release URL, progress 0–1, whether tray item shows.
  • Widgets render only from updateView.
  • Phase 2 adds updateViewFor(domain.UpdateState) updateView — the UI defines the contract the backend must satisfy.

3.4 Preview build

  • src/ui/update_preview.go with build tag updatepreview: cycle states on a timer and via buttons for screenshot work.
  • Production builds: no Updates row until Phase 2 wires real state (avoids shipping a non-functional row).

3.5 STANDARDS constraints

  • Add new captions to settingsCaptions for measured column width.
  • Widget updates from background goroutines via fyne.Do.

3.6 Design gate

Insert screenshots under §3.2 in this file (or docs/screenshots/update_*.PNG). Owner approves copy and layout → proceed to Phase 2.


4. Architecture

flowchart LR
    settingsUI["ui Settings About/Updates"] -->|"CheckForUpdates, InstallUpdate"| svc["app.Service UpdateState"]
    svc -->|"updater seam"| upd["src/update API download verify extract"]
    svc -->|"ReplaceExecutable, Relaunch"| plat["platform/selfupdate"]
    upd -->|"HTTPS"| gh["api.github.com"]
    svc -->|"UpdateStateChanged"| settingsUI
Layer Responsibility
domain UpdateState enum + data; CompareVersions (semver, pre-release rules). Dev/unparseable current version → never offer update.
src/update stdlib only: LatestRelease, SelectAsset(goos, goarch), Download, Verify (SHA256 + ed25519), ExtractBinary (zip-slip safe, single binary entry).
src/platform/selfupdate ReplaceExecutable, Relaunch, CleanupPrevious — _windows.go, _linux.go, _other.go like autostart.
app.Service CheckForUpdates(manual), InstallUpdate, UpdateState(), emit UpdateStateChanged. Network behind updater interface (tests use fake). No network I/O under mu.

Windows replace: rename running gosentry.exe → gosentry.exe.old, write new exe via temp + rename; rollback rename on failure.

Linux replace: temp file in app dir, chmod 0755, rename over executable.

Relaunch: new process with --updated-from=<oldVersion> and --start-in-tray when the window was hidden. New instance retries single-instance bind ~10s, deletes .old, records History.


5. Update flow (install and restart)

  1. User clicks Install and restart → confirmation dialog.
  2. If any job is running → error dialog (same rule as UpdateSettings jobs-file switch).
  3. Download, verify signature and hash, extract binary, replace executable.
  4. svc.Stop(), close single-instance listener, Relaunch, a.Quit().
  5. New process: bind port (retry), CleanupPrevious(), History event.
  6. If Relaunch fails → restore from .old, dialog.ShowError.

6. Integrity and security

  • CI produces SHA256SUMS and SHA256SUMS.sig (ed25519). Public key embedded in the binary; private key in GitHub Actions secret. No new Go dependencies.
  • Install refuses without valid signature and matching hash for the selected archive (checksum alone does not defend a compromised release upload).
  • HTTPS only; download size cap; allow redirects only to objects.githubusercontent.com (and same release asset URLs from the API).

7. Check behaviour and config

  • Config.CheckForUpdates bool — json:"check_for_updates,omitempty". Zero = off (legacy configs unchanged). Default in DefaultConfig(): see §12 (recommend off).
  • When enabled: check after startup (delayed, non-blocking) and every 24h; cache in Service. Automatic check errors are silent; manual check/install errors use dialog.ShowError.
  • Writable app directory probe in Service → UpdateState for “link only” UI.

8. Release pipeline changes

  • scripts/ci-build-release.sh: emit SHA256SUMS; sign with openssl pkeyutl or small cmd/signsums helper.
  • .github/workflows/release.yml: secret key; attach SHA256SUMS and SHA256SUMS.sig in action-gh-release files: (same job as build so assets and signature land together).
  • DEVELOPMENT.md: key generation, rotation, post-release checklist — five files per release: three archives + SHA256SUMS + .sig.

9. Phases (design-first order)

Phase Deliverable Shippable?
1 — UI design §3: updateView, Updates row, checkbox, dialogs, tray item, updatepreview, UI tests, screenshots, design gate Preview tag only
2 — Detection domain.UpdateState, CompareVersions, update.LatestRelease, config field, Service + event, updateViewFor; Updates row in release builds; Available opens release page until Phase 4 Yes (ROADMAP notification scope)
3 — Signed artifacts CI checksums + signature on every GitHub release Must ship before Phase 4
4 — Download and install Full update + selfupdate, handoff, --updated-from, .old rollback; Install and restart live Yes
5 — Landing README screenshot, ARCHITECTURE, STANDARDS intentional behaviour; remove updatepreview if unused; delete this plan from ROADMAP Yes

If Phase 2–4 needs a new UI state or field not in the signed-off §3, update screenshots and §3 first, then code.


10. Testing

Per TESTS.md:

Phase Package Coverage
1 ui Each updateView state via Fyne test driver; assert no Updates row in default build
2 ui Table tests for updateViewFor
2+ domain CompareVersions (0.12.0 > 0.9.0, pre-releases, garbage)
3–4 update httptest: draft/prerelease, missing asset, 403, bad hash/sig, size limit, zip-slip
4 platform/selfupdate Temp-dir replace; Windows rename + rollback
4 app Fake updater; install blocked when job running; events; config compat trio (STANDARDS)

Manual: N−1 → N on Windows and Linux; tray vs visible window; autostart enabled.


11. Docs to update when feature lands

  • STANDARDS §Intentional: no install while job running; brief scheduler gap on restart; opt-in silent auto-check.
  • ARCHITECTURE: package map, platform table, update flow diagram.
  • ROADMAP: remove auto-update item.
  • CHANGELOG: user-visible release notes.
  • Delete this file after Phase 5.

12. Open questions

  1. CheckForUpdates default in DefaultConfig(): off vs first-run prompt.
  2. Signature format: raw ed25519 (stdlib) vs minisign-compatible.
  3. Optional pre-release channel later?

Screenshot placeholders (Phase 1)

Add images here after the design gate.

State File
Auto-check off docs/screenshots/update_idle.PNG
Checking docs/screenshots/update_checking.PNG
Up to date docs/screenshots/update_uptodate.PNG
Available docs/screenshots/update_available.PNG
Downloading docs/screenshots/update_downloading.PNG
Restarting docs/screenshots/update_restarting.PNG
Failed docs/screenshots/update_failed.PNG
Not writable docs/screenshots/update_nowrite.PNG
Unsupported docs/screenshots/update_unsupported.PNG