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:
@@ -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.
|
||||
+103
@@ -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`.
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user