From 8d42faf707681f6f127b9e4d8754a28b26c14972 Mon Sep 17 00:00:00 2001 From: "Foluso ADEBISI (DevBox Aux)" Date: Tue, 6 Oct 2026 19:19:23 +0100 Subject: [PATCH] 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. --- .../.openspec.yaml | 2 + .../design.md | 72 ++++++++++++ .../proposal.md | 47 ++++++++ .../specs/fpagent-installer/spec.md | 103 ++++++++++++++++++ .../tasks.md | 27 +++++ openspec/specs/fpagent-installer/spec.md | 50 +++++++-- 6 files changed, 291 insertions(+), 10 deletions(-) create mode 100644 openspec/changes/archive/2026-10-06-fpagent-installer-multiarch/.openspec.yaml create mode 100644 openspec/changes/archive/2026-10-06-fpagent-installer-multiarch/design.md create mode 100644 openspec/changes/archive/2026-10-06-fpagent-installer-multiarch/proposal.md create mode 100644 openspec/changes/archive/2026-10-06-fpagent-installer-multiarch/specs/fpagent-installer/spec.md create mode 100644 openspec/changes/archive/2026-10-06-fpagent-installer-multiarch/tasks.md diff --git a/openspec/changes/archive/2026-10-06-fpagent-installer-multiarch/.openspec.yaml b/openspec/changes/archive/2026-10-06-fpagent-installer-multiarch/.openspec.yaml new file mode 100644 index 0000000..aa32566 --- /dev/null +++ b/openspec/changes/archive/2026-10-06-fpagent-installer-multiarch/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-10-06 diff --git a/openspec/changes/archive/2026-10-06-fpagent-installer-multiarch/design.md b/openspec/changes/archive/2026-10-06-fpagent-installer-multiarch/design.md new file mode 100644 index 0000000..6651d9c --- /dev/null +++ b/openspec/changes/archive/2026-10-06-fpagent-installer-multiarch/design.md @@ -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--linux-x86_64.tar.gz`, `fpagent--linux-i386.tar.gz` +- offline: `fpagent--ubuntu--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--.tar.gz` +2. `-linux-.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--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. diff --git a/openspec/changes/archive/2026-10-06-fpagent-installer-multiarch/proposal.md b/openspec/changes/archive/2026-10-06-fpagent-installer-multiarch/proposal.md new file mode 100644 index 0000000..63a3117 --- /dev/null +++ b/openspec/changes/archive/2026-10-06-fpagent-installer-multiarch/proposal.md @@ -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-` 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--linux-.tar.gz`, where `` is `x86_64` or + `i386` (v1.5.0 and newer). No change to `tools/server.js`: the server proxies + whatever assets the release lists. diff --git a/openspec/changes/archive/2026-10-06-fpagent-installer-multiarch/specs/fpagent-installer/spec.md b/openspec/changes/archive/2026-10-06-fpagent-installer-multiarch/specs/fpagent-installer/spec.md new file mode 100644 index 0000000..deccb8d --- /dev/null +++ b/openspec/changes/archive/2026-10-06-fpagent-installer-multiarch/specs/fpagent-installer/spec.md @@ -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--.tar.gz` +- **THEN** it uses `` as the version + +#### Scenario: Version is derived for a 32-bit bundle + +- **WHEN** the installer selects a generic bundle named + `fpagent--linux-i386.tar.gz` +- **THEN** it uses `` 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 diff --git a/openspec/changes/archive/2026-10-06-fpagent-installer-multiarch/tasks.md b/openspec/changes/archive/2026-10-06-fpagent-installer-multiarch/tasks.md new file mode 100644 index 0000000..53a30e8 --- /dev/null +++ b/openspec/changes/archive/2026-10-06-fpagent-installer-multiarch/tasks.md @@ -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--.tar.gz` first, then + `-linux-.tar.gz`, and make the "no compatible bundle" message name the + host architecture. Verify: with a release offering only + `fpagent--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--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`. diff --git a/openspec/specs/fpagent-installer/spec.md b/openspec/specs/fpagent-installer/spec.md index c2e2969..53ef3c1 100644 --- a/openspec/specs/fpagent-installer/spec.md +++ b/openspec/specs/fpagent-installer/spec.md @@ -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--.tar.gz` - **THEN** it uses `` as the version +#### Scenario: Version is derived for a 32-bit bundle + +- **WHEN** the installer selects a generic bundle named + `fpagent--linux-i386.tar.gz` +- **THEN** it uses `` as the version + #### Scenario: Upgrade is reported - **WHEN** the recorded version differs from the version being installed