feat(cli): ship ftw as the native operator command - #1372
Conversation
A native install's version panel no longer opens the old updater. ftw update asks beta or stable, or takes --channel. The same command backs up, checks the running Core, writes a redacted support file, and prints a systemd snippet to paste. Starting Core on a port FTW already holds says that it is already running.
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f2df85d232
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| // Command ftwupdate is installed on PATH as `ftw`. It only runs updates. | ||
| // Starting Core is the long-running ftw binary, not this command. |
There was a problem hiding this comment.
Ship the CLI that the native UI invokes
The release path never builds or installs this command: scripts/build-core.sh still builds only cmd/ftw, cmd/ftw-backup, and cmd/ftw-launcher, while scripts/package-linux.py archives only those binaries and the installer creates no PATH entry. Consequently the advertised ftw CLI is absent from native releases; manually invoking the packaged Core binary only recognizes update, so backup, doctor, support, startup, and help also fail. Add this command to the build/package/install path and expose it as ftw.
AGENTS.md reference: AGENTS.md:L24-L25
Useful? React with 👍 / 👎.
| _modalHTML() { | ||
| const info = this._info || {}; | ||
| if (this._phase === "updating") return this._updatingModalHTML(); | ||
| if (info.native) return this._nativeVersionHTML(info); |
There was a problem hiding this comment.
Preserve native binary rollback in the replacement dialog
When info.native is true, this early return bypasses the existing binaryRollback action even when info.previous is available. The new CLI has no rollback command, so a native operator loses the only normal route to /api/version/binary-rollback; the existing native rollback case in web/update-dialog.test.mjs now fails as well. Keep the native rollback control reachable from this dialog.
AGENTS.md reference: AGENTS.md:L24-L25
Useful? React with 👍 / 👎.
| _modalHTML() { | ||
| const info = this._info || {}; | ||
| if (this._phase === "updating") return this._updatingModalHTML(); | ||
| if (info.native) return this._nativeVersionHTML(info); |
There was a problem hiding this comment.
Keep pending driver updates actionable on native installs
For a native install with only a signed driver update pending, _pendingUpdates() still counts that driver and renders a clickable “1 component update available” badge, but this branch opens a Core-only panel that says no newer release is published and omits _componentsSectionHTML(). The user therefore receives an update notification whose dialog neither identifies nor links to the pending driver; retain the driver section or direct the badge to the Devices update flow.
AGENTS.md reference: AGENTS.md:L22-L23
Useful? React with 👍 / 👎.
| slog.Error("load config", "err", err) | ||
| os.Exit(1) | ||
| } | ||
| if *apiPort != 0 { |
There was a problem hiding this comment.
Apply the port override before entering bootstrap
On a fresh host where the config file does not yet exist, execution calls runBootstrap and returns before reaching this override; that bootstrap server hardcodes :8080. Thus ftw -port 9090 initially serves setup on 8080 and can jump to 9090 only after setup writes the config and re-executes, contrary to the new flag's behavior. Validate and pass the override into bootstrap before the missing-config branch.
AGENTS.md reference: AGENTS.md:L24-L25
Useful? React with 👍 / 👎.
The owner runs native updates on the machine (ADR 0007 as amended in #1373). ftw-cli ships in the release package and the installer puts it on PATH as /usr/local/bin/ftw, owned by root. It talks only to the local Core API, asks nothing and never starts Core: - status: version, published release, last run, previous release, health and history, with the systemd commands to look next - update [--channel]: follows a run already in progress, waits through the restart, reports what runs and how health settled; already current exits 0; stops before a state-schema change - rollback: binary rollback to previous, followed the same way - backup [--output-dir]: verified archive, copy checked by SHA-256 - support: the redacted zip, rejected if the stream was cut Every request has a timeout and waiting is bounded by the trial deadline. doctor, startup, the channel prompt, --port and Core's -port flag are gone, and the Core binary no longer routes update. A native "restarting" status now stays in flight until the trial deadline instead of failing after five minutes. The bind error asks /api/health, so a starting Core is recognised, and names ftw status. The native web panel reports version, release, command and last run without controls; it shows a loading panel before the first check and fetches nothing for the old dialog. Setup on native names ftw update instead of offering Update now. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MuerPFZFG88kgu8sWVHeq7
Summary
Builds on the direction in #1373. On a native install the owner runs updates on the machine, by hand or from their own timer or agent. Core supplies the steps.
ftwon PATH.ftw-cliships in the release package.scripts/install.shinstalls it as/usr/local/bin/ftw, owned by root. It talks only to the local Core API, asks no questions and never starts Core.ftw status: version, published release, last update or rollback, previous release, health and history, plusjournalctl/systemctlfor the next step. Exit 1 when Core is down, still starting or not healthy.ftw update [--channel beta|stable]:ftw rollback: binary rollback toprevious, followed the same way.ftw backup [--output-dir DIR]: Core's verified archive, and a copy checked by size and SHA-256 before it gets its final name.ftw support [--output FILE]: the redacted zip, mode 0600, deleted if the stream was cut.doctor(nowstatus),startup(its unit overwrote the installer's), the channel prompt,--port, Core's-port, and theupdateroute in the Core binary (ftw doctorthere started a setup wizard).restartingstatus stays in flight until the trial deadline instead of failing after 5 minutes./api/health, so it recognises a starting Core, and points toftw status.ftw updateinstead of offering Update now.docs/self-update.mdanddocs/backup-and-restore.mddescribe the commands.minor, because updating needs a user step. Owner approval needed.Tests
go test ./internal/ftwcli(also with-race -count=3), against a fake Core. It covers:previous.part, the second run and a mismatchinternal/selfupdatehas a test for the native restart deadline, andcmd/ftwfor the bind message.make testpasses (69 packages). The rest ofmake verifypasses: the script suites plusgo vet ./...andgo build ./....LANG=C npm test, 654 pass. The native panel, loading panel, nativeopen()requests, header count and setup banner are covered.test_package_linux.pyandtest-install-native.shpass.ftw-cli.ftw rollbackv0.132.2-beta.1 → v0.132.1-beta.1 in 4 sftw updateback in 4 sftw updateagain reports already current (exit 0)ftw backup --output-dirwith a checked copyftw supportNot in this PR
In the order of #1373:
🤖 Generated with Claude Code
https://claude.ai/code/session_01MuerPFZFG88kgu8sWVHeq7