feat(tpagectl): add self-update #622

Merged
fuzzy merged 1 commit from feat/tpagectl-update into main 2026-08-09 21:43:12 +00:00
Owner

What

Implements Phase 9 auto-update (roadmap #306).

  • New internal/tpagectl/update package with a tpagectl update command:
    • #304 — check latest from Forgejo releases — a small net/http client queries the public API GET {host}/api/v1/repos/{owner}/{repo}/releases/latest and compares semantic versions. update --check reports the latest release and whether an update is available.
    • #303update command — selects the platform-specific release asset (tpagectl_<ver>_<os>_<arch>.tar.gz/.zip) for the runtime GOOS/GOARCH, and reports "already up to date" when current ≥ latest.
    • #305 — download + replace with rollback — downloads the archive, verifies its sha256 against the release's tarballs.sha256, extracts the binary, backs up the current executable (.bak), stages and atomically renames the new binary (0755), restoring the backup on any failure. getExecutable is injectable for tests.
  • UX: prompts for confirmation before replacing (--yes/-y to skip) and refuses to self-update a development build (non-semver version).
  • Config: new update.host/update.owner/update.repo keys (defaults git.lan.thwap.org/thwap/thwap-pagesd, TPAGECTL_UPDATE_* env) plus --host/--owner/--repo flags.
  • README updated and docs/commands/tpagectl-update.* + root-help golden regenerated.

Why

Roadmap task #306, the last item in Phase 9.

Testing

  • Unit: semver parse/compare, per-OS/arch asset selection, latest-release fetch, checksum verify, tar.gz/zip extraction, replace-success and rollback (backup restored, .bak removed on success)
  • Command: --check reports latest; already-up-to-date; dev-build refusal on update + note on --check; end-to-end update --yes replaces the binary and removes the backup (via injected executable path + httptest fake Forgejo)
  • Config: TPAGECTL_UPDATE_* env mapped
  • go test -race ./... passes (31 packages); make coverage = 84.7% (≥80% gate)
  • golangci-lint run clean; pre-commit hooks pass

Breaking Changes

None. Adds an update command and a new update config section.

Notes

No thwap-actions action exists for self-update (this is a CLI feature), so it's implemented in-repo, consuming the releases/tarballs.sha256 that the release workflow publishes. Windows self-replace of a running .exe may be restricted by the OS; rollback restores the backup if the rename fails.

Closes #303
Closes #304
Closes #305
Closes #306

## What Implements Phase 9 auto-update (roadmap #306). - **New `internal/tpagectl/update` package** with a `tpagectl update` command: - **#304 — check latest from Forgejo releases** — a small `net/http` client queries the public API `GET {host}/api/v1/repos/{owner}/{repo}/releases/latest` and compares semantic versions. `update --check` reports the latest release and whether an update is available. - **#303 — `update` command** — selects the platform-specific release asset (`tpagectl_<ver>_<os>_<arch>.tar.gz`/`.zip`) for the runtime `GOOS`/`GOARCH`, and reports "already up to date" when current ≥ latest. - **#305 — download + replace with rollback** — downloads the archive, verifies its sha256 against the release's `tarballs.sha256`, extracts the binary, backs up the current executable (`.bak`), stages and atomically renames the new binary (0755), restoring the backup on any failure. `getExecutable` is injectable for tests. - **UX:** prompts for confirmation before replacing (`--yes`/`-y` to skip) and refuses to self-update a development build (non-semver `version`). - **Config:** new `update.host`/`update.owner`/`update.repo` keys (defaults `git.lan.thwap.org`/`thwap`/`thwap-pagesd`, `TPAGECTL_UPDATE_*` env) plus `--host`/`--owner`/`--repo` flags. - README updated and `docs/commands/tpagectl-update.*` + root-help golden regenerated. ## Why Roadmap task #306, the last item in Phase 9. ## Testing - [x] Unit: semver parse/compare, per-OS/arch asset selection, latest-release fetch, checksum verify, tar.gz/zip extraction, replace-success and rollback (backup restored, `.bak` removed on success) - [x] Command: `--check` reports latest; already-up-to-date; dev-build refusal on `update` + note on `--check`; end-to-end `update --yes` replaces the binary and removes the backup (via injected executable path + httptest fake Forgejo) - [x] Config: `TPAGECTL_UPDATE_*` env mapped - [x] `go test -race ./...` passes (31 packages); `make coverage` = 84.7% (≥80% gate) - [x] `golangci-lint run` clean; pre-commit hooks pass ## Breaking Changes None. Adds an `update` command and a new `update` config section. ## Notes No `thwap-actions` action exists for self-update (this is a CLI feature), so it's implemented in-repo, consuming the releases/`tarballs.sha256` that the release workflow publishes. Windows self-replace of a running `.exe` may be restricted by the OS; rollback restores the backup if the rename fails. Closes #303 Closes #304 Closes #305 Closes #306
feat(tpagectl): add self-update
All checks were successful
CI / test (pull_request) Successful in 5m33s
CI / docker (pull_request) Successful in 2m52s
CI / build (pull_request) Successful in 5m49s
CI / lint (pull_request) Successful in 5m46s
CI / build (push) Successful in 8m43s
CI / test (push) Successful in 12m51s
CI / lint (push) Successful in 13m28s
CI / docker (push) Successful in 26m23s
065bf20523
Add an `update` command backed by the Forgejo releases API:

- update --check reports the latest release and whether an update is
  available, comparing semantic versions (#304).
- update downloads the platform-specific release tarball, verifies its
  sha256 against the release's tarballs.sha256, extracts the binary, and
  replaces the running executable atomically with a .bak backup restored
  on any failure (#303, #305).
- The update prompts for confirmation (skipped with --yes) and refuses to
  self-update a development build (non-semver version).
- New update.host/owner/repo config (TPAGECTL_UPDATE_*) with
  --host/--owner/--repo flags, defaulting to the thwap/thwap-pagesd
  releases on git.lan.thwap.org.

Documented in the README and regenerated command docs.

Closes #303
Closes #304
Closes #305
Closes #306
fuzzy scheduled this pull request to auto merge when all checks succeed 2026-08-09 21:28:02 +00:00
the.auditor left a comment

Solid self-update implementation. The flow is correctly ordered (checksum verify → extract → atomic replace with backup+rollback), extraction writes to a fixed destination (no zip-slip), getExecutable is injectable for tests, the semver/dev-build guards are right, and config/env/flag precedence is clean. Approving; one security finding filed.

Suggestions

  1. internal/tpagectl/update/update.go:317verifyChecksum pulls tarballs.sha256 from the same releases API as the archive, so it provides integrity but not authenticity. A compromised host, a taken-over release account, or a malicious update.host/owner/repo config ships matching checksums + binary → arbitrary code execution on update. Consider signing (minisign/cosign) or documenting the trust model. Filed as #623.

Notes

  • Windows update depends on golang-release naming Windows artifacts with .exe so the .zip branch in #618 fires; otherwise assetFor finds no .zip and update fails on Windows (the #619 coupling).
  • update still runs the root Before hook (daemon client + keychain bootstrap) — a malformed config or genuine keychain error blocks update (same pattern as #611).
  • Unbounded download/extract sizes and whole-archive-in-RAM checksum — acceptable given the host is already trusted, but worth noting.
  • Rollback restore errors are silently ignored (_ = os.Rename(backup, execPath)); on Windows a locked running binary can orphan a .bak.

Praise

  • Checksum verification happens before any file replacement, and rollback restores the original if any step after backup fails.
  • Archive names and checksum lookups are consistent with the release workflow's naming (tpagectl_<ver>_<os>_<arch>.tar.gz/.zip).
  • Dev-build refusal and --check handling are clean and user-friendly.
Solid self-update implementation. The flow is correctly ordered (checksum verify → extract → atomic replace with backup+rollback), extraction writes to a fixed destination (no zip-slip), `getExecutable` is injectable for tests, the semver/dev-build guards are right, and config/env/flag precedence is clean. Approving; one security finding filed. ## Suggestions 1. `internal/tpagectl/update/update.go:317` – `verifyChecksum` pulls `tarballs.sha256` from the same releases API as the archive, so it provides integrity but not authenticity. A compromised host, a taken-over release account, or a malicious `update.host/owner/repo` config ships matching checksums + binary → arbitrary code execution on update. Consider signing (minisign/cosign) or documenting the trust model. Filed as #623. ## Notes - Windows update depends on `golang-release` naming Windows artifacts with `.exe` so the `.zip` branch in #618 fires; otherwise `assetFor` finds no `.zip` and update fails on Windows (the #619 coupling). - `update` still runs the root `Before` hook (daemon client + keychain bootstrap) — a malformed config or genuine keychain error blocks `update` (same pattern as #611). - Unbounded download/extract sizes and whole-archive-in-RAM checksum — acceptable given the host is already trusted, but worth noting. - Rollback restore errors are silently ignored (`_ = os.Rename(backup, execPath)`); on Windows a locked running binary can orphan a `.bak`. ## Praise - Checksum verification happens before any file replacement, and rollback restores the original if any step after backup fails. - Archive names and checksum lookups are consistent with the release workflow's naming (`tpagectl_<ver>_<os>_<arch>.tar.gz/.zip`). - Dev-build refusal and `--check` handling are clean and user-friendly.
fuzzy merged commit 065bf20523 into main 2026-08-09 21:43:12 +00:00
fuzzy deleted branch feat/tpagectl-update 2026-08-09 21:43:12 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
thwap/thwap-pagesd!622
No description provided.