docs(openspec): archive fpagent-install-upgrade and sync specs
Documents release pinning (--tag), the pinned-tag metadata route, installed-version tracking with upgrade reporting, and restart of a managed running service on upgrade; syncs the four requirements into the fpagent-installer main spec.
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-10-02
|
||||
@@ -0,0 +1,64 @@
|
||||
# Design
|
||||
|
||||
## Context
|
||||
|
||||
See proposal.md — Why. The `fpagent-installer` capability (change
|
||||
`fpagent-release-installer`) installs the newest release and hands off to the
|
||||
bundle's `deploy/install.sh`, which replaces the binary but only warns when a
|
||||
daemon is running. The bundle name already embeds the git hash
|
||||
(`fpagent-<hash>-<platform>.tar.gz`) and `bundle.sh` already builds the binary
|
||||
with that hash as its embedded `Version`.
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:** install a pinned release; make a rerun a real upgrade that reports
|
||||
what changed and takes effect; keep both install paths (hosted and direct
|
||||
bundle) consistent.
|
||||
|
||||
**Non-Goals:** a separate version service or a new `--version` CLI flag; signing
|
||||
or provenance beyond sha256; a downgrade-unsafe rollback.
|
||||
|
||||
## Decisions
|
||||
|
||||
1. **The version is the asset-name git hash.** It is already the binary's
|
||||
embedded `Version` and needs no new endpoint or flag. The hosted installer
|
||||
parses it, and also writes it as `VERSION` in the extracted bundle so the
|
||||
bundle installer has it even for bundles built before this change.
|
||||
|
||||
2. **The bundle installer owns the upgrade reporting and the restart.** It
|
||||
knows `INSTALL_PATH` and the service; the hosted installer only fetches,
|
||||
verifies and hands off. `bundle.sh` ships `VERSION` so direct bundle installs
|
||||
behave the same. The stamp lives at `${INSTALL_PATH}.version`, so a custom
|
||||
`--install-path` keeps its own record, and uninstall removes it.
|
||||
|
||||
3. **Restart only on a managed, already-running service.** `enable --now` starts
|
||||
a stopped unit; for an upgrade the installer checks `is-active` before
|
||||
enabling and restarts only if it was active, avoiding a redundant restart on
|
||||
a fresh install.
|
||||
|
||||
4. **`--tag` reuses the release handler.** A second same-origin metadata route
|
||||
(`/fpagent/releases/tags/:tag`) shares the latest route's handler, so the
|
||||
filtering and non-disclosure rules are identical; the installer switches only
|
||||
the metadata URL, then downloads by the resolved tag.
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
- [Bundles built before this change carry no VERSION and do not restart] → the
|
||||
hosted installer writes VERSION from the asset name (fixes reporting), but the
|
||||
restart is in the bundle and appears only after a new `fingerprint-agent`
|
||||
release. Until then upgrades still need a manual restart.
|
||||
- [Upgrade without `--enable-service` leaves a running daemon on the old binary]
|
||||
→ the installer warns; the documented install command uses `--enable-service`.
|
||||
- [A pinned tag whose assets differ] → the tag's own metadata drives asset
|
||||
selection, the same as latest.
|
||||
|
||||
## Migration Plan
|
||||
|
||||
Release a new `fingerprint-agent` bundle (ships VERSION and restarts on
|
||||
upgrade), then deploy the app. The hosted installer is backward compatible: with
|
||||
an older bundle it reports the version but the bundle's older installer ignores
|
||||
the stamp. Rollback = revert the app; `--tag` can pin the previous release.
|
||||
|
||||
## Open Questions
|
||||
|
||||
None.
|
||||
@@ -0,0 +1,42 @@
|
||||
# Proposal: fpagent-install-upgrade
|
||||
|
||||
## Why
|
||||
|
||||
The installer always took the newest release and left any running `fpagent`
|
||||
daemon on the old binary, so there was no way to hold a known-good version and
|
||||
an "upgrade" did not take effect until a reboot. Operators need a controlled
|
||||
version and a rerun that actually upgrades.
|
||||
|
||||
## What Changes
|
||||
|
||||
- The installer accepts `--tag TAG` to install a specific release instead of
|
||||
`latest`, and the app proxies that tag's metadata through a same-origin route.
|
||||
- The installed version is the git hash carried in the bundle's asset name. The
|
||||
installer passes it to the bundle installer, which records it beside the
|
||||
binary and reports `upgrading <old> -> <new>` on the next run.
|
||||
- When the installer manages the systemd service and the service was already
|
||||
running, an upgrade restarts it so the new binary takes effect. The version
|
||||
stamp is removed on uninstall.
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
|
||||
(none)
|
||||
|
||||
### Modified Capabilities
|
||||
|
||||
- `fpagent-installer`: adds release pinning, a pinned-tag metadata endpoint,
|
||||
installed-version tracking with upgrade reporting, and restart of a managed
|
||||
service on upgrade.
|
||||
|
||||
## Impact
|
||||
|
||||
- `tools/server.js`: `/fpagent/releases/tags/:tag` route (shares the release
|
||||
metadata handler with `/fpagent/releases/latest`).
|
||||
- `tools/public/fpagent/install.sh`: `--tag`, version parse from the asset name,
|
||||
and handoff of the version to the bundle installer.
|
||||
- `fingerprint-agent` `deploy/install.sh` and `deploy/bundle.sh`: read/stamp the
|
||||
version and restart an already-running service; `bundle.sh` ships `VERSION`.
|
||||
- Depends on the `fingerprint-agent` bundle naming, which already embeds the
|
||||
git hash (`fpagent-<hash>-<platform>.tar.gz`).
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
# Spec Delta
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Pinned release selection
|
||||
|
||||
The installer SHALL accept a release tag and install that release instead of the
|
||||
latest. Without a tag it SHALL install the latest release.
|
||||
|
||||
#### Scenario: A pinned release is installed
|
||||
|
||||
- **WHEN** the installer is invoked with a release tag
|
||||
- **THEN** it fetches that tag's metadata and installs its bundle
|
||||
|
||||
#### Scenario: Default is the latest release
|
||||
|
||||
- **WHEN** the installer is invoked without a release tag
|
||||
- **THEN** it installs the latest release
|
||||
|
||||
#### Scenario: Unknown pinned release
|
||||
|
||||
- **WHEN** the pinned tag does not exist
|
||||
- **THEN** the installer exits with a failure message and installs nothing
|
||||
|
||||
### Requirement: Pinned release metadata endpoint
|
||||
|
||||
The app SHALL expose a same-origin endpoint that returns a specific release's
|
||||
tag and asset names as same-origin URLs, with the same authentication, filtering
|
||||
and non-disclosure guarantees as the latest-release endpoint. It SHALL reject a
|
||||
tag that is not a valid release tag without contacting the forge.
|
||||
|
||||
#### Scenario: Tagged release is returned
|
||||
|
||||
- **WHEN** a client requests the metadata for an existing tag
|
||||
- **THEN** the response carries that release's tag and its assets as same-origin
|
||||
URLs
|
||||
|
||||
#### Scenario: Invalid tag is rejected
|
||||
|
||||
- **WHEN** a client requests metadata for a tag that is not a valid release tag
|
||||
- **THEN** the app rejects the request without contacting the forge
|
||||
|
||||
### Requirement: Installed version is tracked and upgrades are reported
|
||||
|
||||
The installer SHALL treat the git hash in the bundle's asset name as the version.
|
||||
It SHALL hand that version to the bundle installer, which SHALL record it beside
|
||||
the installed binary and SHALL report an upgrade with the previous and new
|
||||
versions on the next run. Uninstalling SHALL remove the recorded version.
|
||||
|
||||
#### Scenario: Version is derived from the asset name
|
||||
|
||||
- **WHEN** the installer selects a bundle named `fpagent-<hash>-<platform>.tar.gz`
|
||||
- **THEN** it uses `<hash>` as the version
|
||||
|
||||
#### Scenario: Upgrade is reported
|
||||
|
||||
- **WHEN** the recorded version differs from the version being installed
|
||||
- **THEN** the bundle installer reports the change from the old to the new
|
||||
version
|
||||
|
||||
#### Scenario: First install records the version
|
||||
|
||||
- **WHEN** no version has been recorded yet
|
||||
- **THEN** the bundle installer records the new version and reports no upgrade
|
||||
|
||||
#### Scenario: Uninstall removes the recorded version
|
||||
|
||||
- **WHEN** the installer is asked to uninstall
|
||||
- **THEN** the recorded version is removed
|
||||
|
||||
### Requirement: A managed running service is restarted on upgrade
|
||||
|
||||
When the installer is asked to manage the systemd service and a service is
|
||||
already running, the installer SHALL restart it after installing the new binary
|
||||
so the new binary takes effect. A fresh install SHALL NOT perform a redundant
|
||||
restart, and the installer SHALL NOT restart the service when service management
|
||||
is not requested.
|
||||
|
||||
#### Scenario: Upgrade restarts the running service
|
||||
|
||||
- **WHEN** the running service is managed and a new binary is installed
|
||||
- **THEN** the service is restarted on the new binary
|
||||
|
||||
#### Scenario: Fresh install starts the service
|
||||
|
||||
- **WHEN** the service is not running and service management is requested
|
||||
- **THEN** the service is started and no separate restart is performed
|
||||
|
||||
#### Scenario: Service management not requested
|
||||
|
||||
- **WHEN** service management is not requested
|
||||
- **THEN** the installer does not restart the service
|
||||
@@ -0,0 +1,19 @@
|
||||
# Tasks
|
||||
|
||||
- [x] 1. App: add `/fpagent/releases/tags/:tag`, sharing the release-metadata
|
||||
handler with `/fpagent/releases/latest`; reject invalid tags without contacting
|
||||
the forge.
|
||||
- [x] 2. Hosted installer: parse `--tag`, fetch the pinned tag's metadata,
|
||||
derive the git hash from the asset name, and write it as `VERSION` into the
|
||||
extracted bundle before handoff.
|
||||
- [x] 3. Bundle installer (`fingerprint-agent`): read `VERSION` beside the script
|
||||
or the binary, report `upgrading <old> -> <new>` from `${INSTALL_PATH}.version`,
|
||||
stamp the new version, remove it on uninstall.
|
||||
- [x] 4. Bundle installer: restart an already-running `fpagent.service` after
|
||||
`enable --now` (via `is-active`, so a fresh install does not restart).
|
||||
- [x] 5. `bundle.sh`: ship `VERSION` in the bundle.
|
||||
- [x] 6. Tests: tag-route metadata and invalid-tag rejection; `--tag` makes the
|
||||
installer request the tag endpoint, not latest. Verified 59 tests pass.
|
||||
- [x] 7. Verified end to end: a real patched bundle served through a fake gateway
|
||||
→ hosted installer → bundle installer reported `upgrading fpagent deadbeef ->
|
||||
cafebabe`, version `cafebabe`, with options forwarded.
|
||||
Reference in New Issue
Block a user