Files
tools-legacy/openspec/specs/fpagent-installer/spec.md
T
adebisi-fa 8d42faf707 docs(openspec): archive fpagent-installer-multiarch and sync specs
Bundle selection is now architecture-aware, not just codename-aware, and the
no-compatible-bundle failure names the host architecture. Merged into the
fpagent-installer spec and archived the change.
2026-10-06 19:19:23 +01:00

256 lines
9.4 KiB
Markdown

# fpagent-installer Specification
## Purpose
Lets a field laptop install the latest private `fpagent` release with a single
public command, without ever handling the forge credential — the tools app
proxies the private release and serves the installer.
## Requirements
### Requirement: Public installer endpoint
The app SHALL serve an installer script at `/fpagent/install.sh` that is
reachable without authentication. When run, the script SHALL determine the
host's architecture, fetch the latest release metadata, choose the bundle for
the host architecture, verify the bundle's published sha256 checksum, extract
it, and hand off to the extracted bundle's own installer. It SHALL prefer a
bundle built for the host's distro codename when one exists for that
architecture, and otherwise use the generic bundle for that architecture. The
script SHALL be served uncached.
#### Scenario: Installer is fetched anonymously
- **WHEN** a client requests `/fpagent/install.sh` without a session
- **THEN** the app returns the installer script and does not redirect to login
#### Scenario: Host-appropriate bundle is chosen
- **WHEN** the latest release offers both a bundle for the host's Ubuntu
codename and a generic bundle
- **THEN** the installer uses the codename bundle
#### Scenario: Generic bundle is the fallback
- **WHEN** the latest release offers no bundle matching the host's Ubuntu
codename
- **THEN** the installer uses the generic bundle
#### Scenario: Bundle matches the host architecture
- **WHEN** the latest release offers a bundle for the host architecture and a
bundle for another architecture
- **THEN** the installer chooses the bundle for the host architecture
- **AND** it never chooses a bundle for another architecture
#### Scenario: 32-bit host uses the 32-bit bundle
- **WHEN** the installer runs on an i386 host and the release offers only a
generic 32-bit bundle
- **THEN** the installer uses that generic 32-bit bundle
#### Scenario: Checksum is verified before extraction
- **WHEN** the installer downloads a bundle and its published checksum
- **THEN** it verifies the checksum before extracting and refuses to install
when the checksum does not match
#### Scenario: Options are forwarded to the bundled installer
- **WHEN** the installer is invoked with options such as `--no-service` or
`--check`
- **THEN** it passes those options through to the extracted bundle's installer
#### Scenario: No compatible bundle
- **WHEN** the latest release contains no bundle for the host architecture
- **THEN** the installer exits with a failure message that names the host
architecture and installs nothing
#### Scenario: Unsupported architecture
- **WHEN** the installer runs on a machine whose architecture is neither
x86_64 nor i386
- **THEN** it exits with a failure message and installs nothing
### Requirement: Release metadata endpoint
The app SHALL expose a same-origin endpoint that returns the latest release's
tag and the names of its downloadable assets as same-origin URLs. The app SHALL
authenticate to the private forge using the configured release key, SHALL omit
asset names that are not valid asset names, and SHALL NOT reveal the release
key or the forge host to clients.
#### Scenario: Latest release is returned
- **WHEN** a client requests the latest-release endpoint and the forge returns
a release
- **THEN** the response carries the release tag and each asset's name and a
same-origin URL under `/fpagent/releases/download/`
#### Scenario: Unsafe asset names are omitted
- **WHEN** the forge release lists an asset whose name is not a valid asset
name
- **THEN** that asset does not appear in the response
#### Scenario: Release key is not exposed
- **WHEN** any client fetches the latest-release endpoint
- **THEN** the response body contains neither the release key nor the forge host
#### Scenario: Installer not configured
- **WHEN** the release key is not configured
- **THEN** the endpoint answers that the installer is not configured
#### Scenario: Forge unavailable
- **WHEN** the forge cannot be reached or returns an error for the latest
release
- **THEN** the endpoint answers with a gateway error
### Requirement: Release asset download proxy
The app SHALL stream a requested release asset to the client through a
same-origin route, authenticating the upstream request with the release key so
the client sends no credentials. It SHALL reject asset names that are not valid
asset names and SHALL report a missing asset as not found.
#### Scenario: Asset is streamed without client credentials
- **WHEN** a client requests a release asset by tag and name without any
credential
- **THEN** the app fetches it from the forge with the release key and streams
its bytes to the client
#### Scenario: Invalid asset name is rejected
- **WHEN** a client requests an asset whose tag or name is not a valid asset
name
- **THEN** the app rejects the request without contacting the forge
#### Scenario: Missing asset
- **WHEN** the forge reports the requested asset as not found
- **THEN** the app answers with not found
#### Scenario: Installer not configured
- **WHEN** the release key is not configured
- **THEN** the endpoint answers that the installer is not configured
### Requirement: Installer credential stays server-side
The app SHALL read the private-forge release key from the
`GITEA_RELEASE_INSTALL_KEY` environment variable. The key SHALL NOT appear in
the installer script, in any release metadata response, or in any streamed
asset.
#### Scenario: Served installer carries no credential
- **WHEN** a client fetches `/fpagent/install.sh`
- **THEN** the response contains no release key
#### Scenario: Credential supplied by the environment
- **WHEN** the release key is supplied in the environment
- **THEN** the installer and proxy endpoints use it without any change to the
hardcoded configuration
### 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, for every architecture it can install. 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: Version is derived for a 32-bit bundle
- **WHEN** the installer selects a generic bundle named
`fpagent-<hash>-linux-i386.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