Commit graph

3 commits

Author SHA1 Message Date
emirhanmamak-blueforce
e28783e563 feat(offline-repo): build the APT index so installs work without network
The package pool was committed but the installer still stopped at "no local
package repository": it requires the compiled bundle (Packages, Packages.gz,
packages.lock.tsv, SHA256SUMS), and building that had never run.

The bundle
- 1321 packages, 884 MiB, Ubuntu 26.04 amd64, built with apt-ftparchive on the
  fleet's own release so the closure matches the devices.
- built/pool/ is gitignored: those .deb files are already tracked once under
  provisioning/offline-repo/packages/, and the same ~890 MB does not need a
  second path. What the installer reads from the bundle is the index.
- The output directory is chmod 0755: mktemp -d created it 0700 and left it
  so, which made a valid bundle unreadable to the installer's probe.

dpkg-deb output was parsed wrong in four places
- `dpkg-deb -f <file> Package Version` prints "<Field>: <value>", and the
  callers kept the whole line, so the value carried its label. build-index.sh
  therefore accepted no package at all and reported an empty pool;
  add-packages.sh rejected every real .deb as an invalid name, missed version
  conflicts, and copied a duplicate; build-offline-repo.sh never matched a pin.
- The test suite could not see any of this: its dpkg-deb stub emitted bare
  values. The stub now prints the labels exactly as the real tool does, which
  is what surfaced the remaining three sites.

Tests
- The secret scan covers the files we author, not apt-ftparchive's index over
  third-party packages: polkit's own description reads "...asked for their
  password:", which is prose in the packages, not a leak. Any secret reaching a
  manifest, README or tool is still caught.
- The "test build must not touch the bundle" assertion now compares the
  directory before and after instead of demanding it be empty, so a
  maintainer's real build is not reported as test spill.
2026-09-16 21:42:27 +03:00
emirhanmamak-blueforce
57fbccd4d0 feat(offline-repo): ship the reviewed package set so installs never download
The field installer needs no network at all, which means the packages have to
travel with the repository. This adds the resolved pool and pins every package
to the exact version the pool carries.

Package pool
- 1312 .deb files (884 MB), resolved on Ubuntu 26.04 in a container so the
  closure matches the release the fleet runs: a .deb built against another glibc
  cannot be installed on the devices.
- Tracked through Git LFS (*.deb), so history stays navigable while the working
  tree keeps the real files the installer reads from disk.
- Two RustDesk dependencies (libxdo3, libva-x11-2) were missing: the vendor .deb
  is installed with dpkg, so nothing else pulls them in and `dpkg -i rustdesk`
  would have left the package unconfigured on a device.
- Every file validated in the same release: 1312/1312 readable, 0 corrupt, 0
  foreign architecture, and the pool satisfies all 24 pins (PLAN READY).

Pins
- All 24 UNPINNED sentinels replaced with the versions read from the packages
  themselves (dpkg-deb), with requirements.tsv updated in the same change so the
  contract has no drift.

build-index.sh: stop destroying the shipped record
- The repository record next to this script is the reviewed contract template
  (state: UNBUILT, packages: []) that documents the schema. Its default record
  destination pointed at that same file, so any default-path run overwrote the
  template with BUILT rows -- which happened twice, once from a hand run and
  once while this change was being developed.
- The record now defaults to <output>/repo-manifest.yaml, and pointing it at the
  template is refused before any work starts. The template header documents this.

Tests
- The unpinned-pin and missing-pin cases read the sentinel out of the real
  manifests, so they became no-ops the moment the contract was filled instead of
  failing. They now set up the condition explicitly, and the fixture manifests
  carry the synthetic versions their pool is built from.
- check-provisioning-static required packages/.gitkeep; the pool is what keeps
  that directory in git now, so it asserts a populated pool directory instead.
2026-09-16 19:03:45 +03:00
emirhanmamak-blueforce
ef102b9657 feat(field-os): provisioning layer, real ISO build, offline contracts
Adds the Blueforce Field OS provisioning stack on top of the existing fleet
architecture, without rewriting the working systems (installer, diagnostics,
Ansible, WireGuard/SSH/xRDP/RustDesk, Docker policies, monitoring, Docusaurus).

ISO build (the previous builder was a skeleton that produced no medium):
- boot layout is read from the base ISO at build time via
  `xorriso -report_el_torito as_mkisofs` and rebuilt with `-as mkisofs`;
  `-boot_image any replay` cannot work on Ubuntu live ISOs because the GRUB2
  MBR lives in the source image's first 16 sectors and the EFI system partition
  is an appended partition, neither of which is a file in the ISO9660 tree
- preflight now asserts El Torito boot metadata positively: xorriso exits 0 on
  a non-ISO input, which previously let an arbitrary file pass --check
- artifact produced and verified: dist/Blueforce-Field-OS-1.0.0-amd64.iso,
  6 481 917 952 bytes, SHA256 cb0cc56f...d431296, BIOS+UEFI entries preserved,
  /autoinstall.yaml at the media root, casper/minimal.squashfs byte-identical
  to the base
- recorded baseline deviation: the verified medium on the build host is the
  Desktop flavour, so the manifest carries a baseline_deviation block instead
  of hiding it; Server remains one flag away

Offline and identity:
- offline APT contract with concrete package requirements, pin format and an
  audit table; builds fail closed while pins are UNPINNED
- autoinstall keeps interactive-sections: [storage]: the operator confirms the
  target disk, and no automatic wipe is ever performed
- firstboot terminal wizard for the 8-digit dealer id, three-state lifecycle
  (PROVISIONED_OFFLINE -> ENROLLED -> READY) with fail-closed gates, so a
  missing internet link or WireGuard handshake cannot fail provisioning but can
  never produce a false READY

Fleet and migration:
- five read-only Ansible playbooks for Field OS version, provisioning,
  enrollment, remote channels and offline readiness, all wave-scoped with a
  separate production override
- Windows pre-migration inventory and data export extended to the full brief
  scope, read-only, never collecting credentials

Tests: tests/run-all.sh -> 8/8 pass, exit 0. Boot acceptance (UEFI, Legacy
BIOS, Secure Boot), air-gapped install, real enrollment endpoint and Windows
rollback remain LAB work and are listed in IMPLEMENTATION-REPORT.md.
2026-09-16 11:14:42 +03:00