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.
This commit is contained in:
2026-10-06 19:19:23 +01:00
parent e432585f04
commit 8d42faf707
6 changed files with 291 additions and 10 deletions
@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-10-06
@@ -0,0 +1,72 @@
# Design: fpagent-installer-multiarch
## Context
The hosted installer (`tools/public/fpagent/install.sh`) runs on the field
laptop and proxies the private release through `tools.yrka.app`. It resolves
the release's asset names with a small `python3` snippet and hardcodes the
x86_64 suffix. fingerprint-agent v1.5.0 added an i386 build:
- online: `fpagent-<version>-linux-x86_64.tar.gz`, `fpagent-<version>-linux-i386.tar.gz`
- offline: `fpagent-<version>-ubuntu-<release>-x86_64.tar.gz` (x86_64 only)
The bundle's own installer already refuses a binary whose ELF class does not
match the host, so the hosted installer only has to pick the right asset.
## Goals / Non-goals
- Goal: on any supported host (x86_64, i386) pick the release bundle built for
that architecture, and record its version.
- Non-goal: changing `tools/server.js`. It proxies whatever the release lists,
and asset filtering/naming is unchanged.
- Non-goal: i386 offline (codename) bundles. fingerprint-agent ships those
x86_64-only; i386 hosts fall back to the generic online bundle whose
installer apt-get's libfprint.
- Non-goal: adding a production environment knob for the architecture. The
host's own `uname -m` is the source of truth.
## Decisions
### Architecture from `uname -m`, resolved once
Map `x86_64|amd64` → `x86_64`, `i386|i486|i586|i686` → `i386`, anything else →
fail before any download. This mirrors `deploy/install.sh` in fingerprint-agent
so the hosted and bundled installers agree on what is supported.
### Selection order: codename bundle, then generic, both arch-scoped
With `codename` from `/etc/os-release`:
1. `-ubuntu-<codename>-<arch>.tar.gz`
2. `-linux-<arch>.tar.gz`
The codename attempt is skipped when the host has no codename. Because offline
bundles are x86_64-only, an i386 host simply finds no codename asset and falls
to step 2. The suffix match keeps one architecture's bundle from ever matching
another's (`-linux-i386` does not end with `-linux-x86_64`).
### Version regex derived from the architecture
The version is the git hash embedded in the asset name. The existing regex only
listed the x86_64 endings, so it produced an empty version for an i386 bundle
and nothing was recorded. The regex must accept the same set of endings the
selection can produce: `linux-x86_64`, `linux-i386` and
`ubuntu-<codename>-x86_64`.
### Tests stub `uname`
The installer takes the architecture from the host, so a test cannot assert the
i386 path on an x86_64 CI box by running the real `uname`. A test prepends a
temp directory holding a fake `uname` to `PATH`, which is contained and needs no
production-only override. Assertions are made on the gateway requests the script
makes (which asset it asks for), the way the existing `--tag` test does.
## Risks / trade-offs
- A 64-bit userland on a 32-bit kernel is exotic; `uname -m` is the documented
choice for the bundled installer, so the hosted one follows it rather than
trying a second heuristic. `ponytail:` if such hosts appear, add a
`getconf LONG_BIT` fallback in both installers at once.
- The generated `install.sh --help` text duplicates the bundle installer's
flags list. It can drift again; keeping it short and pointing at the bundle
installer's own help limits the cost.
@@ -0,0 +1,47 @@
# Proposal: fpagent-installer-multiarch
## Why
The hosted installer only ever picks the x86_64 bundle. fingerprint-agent
v1.5.0 publishes a 32-bit `-linux-i386` bundle and its own installer accepts
i386 hosts, so a 32-bit field laptop running the public one-liner fails with
"no compatible bundle" and installs nothing, even though a matching release
asset exists.
## What Changes
- The installer detects the host architecture (`uname -m` → `x86_64` or
`i386`) and selects the matching bundle: the distro-codename bundle when one
exists for that architecture, else the generic `-linux-<arch>` bundle.
- An unsupported architecture fails with a message that names the host machine,
and installs nothing.
- The version parsed from the asset name accepts the `-linux-i386` suffix, so a
32-bit install records and reports its version the same way x86_64 does.
- The installer's help text matches the bundle installer: the systemd service
and the self-update timer are on by default, so `--no-service`,
`--no-updates` and `--update-base` are documented instead of
`--enable-service` being described as required to get a service.
## Capabilities
### New Capabilities
(none)
### Modified Capabilities
- `fpagent-installer`: bundle selection becomes architecture-aware (not just
distro-codename aware), the no-compatible-bundle failure names the host
architecture, and the version is derived for every published architecture.
## Impact
- `tools/public/fpagent/install.sh`: architecture detection, bundle pick,
version regex, help text.
- `tools/test/fpagent-install.test.js`: architecture selection and version
parsing tests.
- `README.md`: the installer section (flags and architectures).
- Depends on the `fingerprint-agent` bundle naming
`fpagent-<version>-linux-<arch>.tar.gz`, where `<arch>` is `x86_64` or
`i386` (v1.5.0 and newer). No change to `tools/server.js`: the server proxies
whatever assets the release lists.
@@ -0,0 +1,103 @@
# Spec Delta
## MODIFIED 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: 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
@@ -0,0 +1,27 @@
# Tasks
- [x] 1. Hosted installer (`tools/public/fpagent/install.sh`): resolve the host
architecture from `uname -m` (`x86_64`/`i386`, else die) before any download,
and pass it to the asset picker. Verify: `bash -n install.sh`; a stubbed
`uname` printing `i686` reaches the metadata fetch, and one printing `aarch64`
dies without fetching.
- [x] 2. Hosted installer: pick `-ubuntu-<codename>-<arch>.tar.gz` first, then
`-linux-<arch>.tar.gz`, and make the "no compatible bundle" message name the
host architecture. Verify: with a release offering only
`fpagent-<hash>-linux-i386.tar.gz`, a stubbed i686 host requests that asset;
with only `-linux-x86_64`, the same host fails and installs nothing.
- [x] 3. Hosted installer: derive the version from the asset name for every
supported architecture (`linux-x86_64`, `linux-i386`,
`ubuntu-<codename>-x86_64`). Verify: an i386 asset name yields the git hash,
not an empty string.
- [x] 4. Hosted installer: correct the help text — service and self-update are
on by default; list `--no-service`, `--no-updates`, `--update-base`, and keep
the note that options are forwarded to the bundle installer.
Verify: `install.sh --help` mentions the three flags and no longer implies
`--enable-service` is required.
- [x] 5. Tests (`tools/test/fpagent-install.test.js`): stub `uname` on `PATH`
and assert i386 selection, cross-architecture rejection, and i386 version
parsing. Verify: `node --test tools/test/fpagent-install.test.js` passes.
- [x] 6. Docs (`README.md`): installer section notes x86_64 and i386 bundles
and the on-by-default service/self-update. Verify: reading the section, no
flag claims contradict `install.sh --help`.
+40 -10
View File
@@ -11,10 +11,13 @@ proxies the private release and serves the installer.
### 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 fetch the latest
release metadata, choose a bundle for the host, verify the bundle's published
sha256 checksum, extract it, and hand off to the extracted bundle's own
installer. The script SHALL be served uncached.
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
@@ -33,6 +36,19 @@ installer. The script SHALL be served uncached.
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
@@ -41,14 +57,21 @@ installer. The script SHALL be served uncached.
#### Scenario: Options are forwarded to the bundled installer
- **WHEN** the installer is invoked with options such as `--enable-service` or
- **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 and installs nothing
- **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
@@ -175,16 +198,23 @@ tag that is not a valid release tag 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.
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