Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions docs/linux-vm.md
Original file line number Diff line number Diff line change
Expand Up @@ -208,3 +208,19 @@ VM gate.
References: [Debian Cloud images](https://wiki.debian.org/Cloud),
[Debian cloud image comparison](https://wiki.debian.org/Cloud/SystemsComparison),
and the [QEMU invocation manual](https://www.qemu.org/docs/master/system/invocation.html).

## Package-only pinned image

The source-provisioned VM above remains the development and diagnostic gate.
The first package-only image recipe is in
[`images/debian-13/README.md`](../images/debian-13/README.md). It builds five
native Debian packages outside the image, verifies the already-evidenced Debian
13.6 ARM64 generic image by filename, byte size, SHA-256, and SHA-512, resolves
runtime dependencies only against an immutable Debian snapshot, and installs
the packages into a fresh QCOW2 without copying the repository or a compiler.

This is currently an implemented, statically checked recipe rather than a
completed image gate. It must not replace the source-provisioned VM evidence
until a Debian 13 builder has produced the packages, the exact assembled image
has booted through `sos-session.target`, and the package lifecycle and
boot-session verifiers have passed against that artifact.
156 changes: 156 additions & 0 deletions docs/progress.md
Original file line number Diff line number Diff line change
Expand Up @@ -8997,3 +8997,159 @@ SHA-256
run its separate one-sideload Core 1 no-Zygote readiness, exact Pi authority,
credential-clear, leak/crash/AVC, manifest, and soak gate. No Core hardware
claim is made here.

## 2026-08-18 — Native Debian packages and pinned reference-image recipe

**Goal:** Replace source-tree mutation as the Linux release boundary with
versioned Debian packages, then define an immutable-input Debian 13 image that
contains no source checkout or compiler.

**Changed:** Added deterministic builders for `sos-runtime`, `sos-agent`,
`sos-desktop-session`, `sos-appliance-session`, and `sos-image-config`.
The agent package carries the already pinned Node 24.18.0 runtime privately;
shared-library dependencies come from `dpkg-shlibdeps`; package timestamps use
`SOURCE_DATE_EPOCH`; and one layout renderer converts the development
`/usr/local` installation into package-owned `/usr` paths. Added first-boot
secret/revision initialization and a Debian 13 image recipe locked to the
previously evidenced ARM64 generic image and the `20260809T000000Z` Debian
archive snapshot. The image assembler installs only local SOS packages,
resolves dependencies from that snapshot, removes cloned identity, and emits an
adjacent input/output hash manifest. Added a Podman/Docker builder based on the
multi-architecture Debian 13.6 slim manifest digest
`sha256:3a39a0592364683e6bab97937b72cad5a8fa6dcbbee90edb3bb48c7f8e94f258`;
it uses the same Debian snapshot, Rust 1.95.0, and verified Node 24.18.0 while
isolating Cargo targets, npm caches, and Linux agent staging from the host
checkout.

**Evidence:** `bash -n` and ShellCheck passed over the new builders,
initializer, renderer, host test, and modified login launchers;
`./tests/linux-packaging-host-test.sh` returned
`linux_packaging_host_test_passed architecture=arm64
snapshot=20260809T000000Z`; and `git diff --check` passed. These static checks
cover rendered paths, all five package boundaries, lock syntax, and the
prohibition on source installer/Cargo/npm use inside the image. The Debian
builder manifest digest was queried from Docker Registry for
`debian:13.6-slim`; its recorded platform manifests are
`sha256:38a76d01668772e381ad2826d876627c89e7133e2f8a0f5d567306798b0f2a16`
for AMD64 and
`sha256:c94f5ddd41327aa2d4a7cfba7889056c02936182fd76a513fec6160c97181fc0`
for ARM64.

Podman 6.1.0 built final ARM64 builder image
`f7a4b4c86aa9f9671f50ec2bc4d79260e3efef30851d95657bc50759b45533b1`
in an AppleHV VM configured with 8 CPUs, 12 GiB RAM, and a 100 GiB disk.
The package build used source revision
`e05f91bb6f0b0a9299b914138d6cd0966b9c82d5-dirty`, source epoch
`1787036064`, and two Cargo jobs. The locked release build completed in
3 minutes 52 seconds; npm installed/audited 102 packages with zero
vulnerabilities; and `artifacts/linux-packages/` contains 46,185,773 logical
bytes:

- `sos-runtime_0.1.0_arm64.deb`: 7,716,860 bytes, SHA-256
`be708f2c486ba35e10bd1f894920b7719f2931018bbeb09f4a8614a596e5ec78`;
- `sos-agent_0.1.0_arm64.deb`: 38,459,692 bytes, SHA-256
`5e47d86a734d8f2695b99924e95195a8d6569b5bd2c0fa5b7427f7ee709bfba6`;
- `sos-desktop-session_0.1.0_all.deb`: 3,824 bytes, SHA-256
`a76db9f14efe276d9e540af8489768cb7a2fbe6476a8f185337e3895b8734999`;
- `sos-appliance-session_0.1.0_all.deb`: 2,832 bytes, SHA-256
`63055a8b9164b5da7b026278244debd48cecd3fe7d73025f895e5a632bfaa767`;
- `sos-image-config_0.1.0_all.deb`: 2,080 bytes, SHA-256
`20ac338e4afa0715a4dc0bb20595d225654c17cb0e11f184012a3cc229247527`;
- `SHA256SUMS`: 485 bytes, SHA-256
`2f98fa9ca9d9887017f5695a3448beb422b24efb09f48d13c3371bbfaf67535c`.

Host `shasum -a 256 -c` independently accepted every package. A fresh
digest-pinned Debian 13.6 slim container resolved and installed the package
closure from snapshot `20260809T000000Z`; all six native executables had no
missing `ldd` dependency; `systemd-analyze verify` accepted the session, agent,
target, and image-initializer units; package ownership resolved to the intended
five packages; and private Node reported `v24.18.0`. The first-boot initializer
created revision
`31f8e1d31b6e2c91a8a0b0829e5f29934440c64ed8f535bb86d81a5a836c49e5`,
a 64-byte shell token, and retained the same revision on a second invocation.
A second same-input package build was byte-for-byte identical for all five
`.deb` files and `SHA256SUMS`; its duplicate output was removed. This proves
same-builder package reproducibility, not yet independent-builder
reproducibility. The clean install and initialization procedure is retained as
`SOS_CONTAINER_ENGINE=podman ./tools/test-linux-packages-container`.

Two useful failures preceded PASS. The first container run could not resolve
the Delta worktree's external Git administration directory; the wrapper now
captures revision, dirty state, and source epoch on the host. The first Rust
attempt then ran in the user's initial 2 GiB Podman VM and `rustc` compiling
`wayland-protocols` terminated with signal 9. Kernel OOM evidence was
unavailable after the VM stopped, but resizing that same VM to 12 GiB and
limiting Cargo to two jobs eliminated the failure. Neither failed attempt left
partial packages.

**Decision:** Accept the ARM64 container build, package ownership/dependency
closure, clean install, initializer idempotence, and same-builder package
reproducibility. Continue with the package-only image architecture. This is a
package PASS, not an image boot, physical-hardware, update, or rollback claim;
keep the source-provisioned Debian VM as the accepted runtime gate until the
new image passes independently.

**Open risks / next gate:** Build the exact ARM64 QCOW2 from these package
hashes, record its byte size and SHA-256, boot it, and adapt the boot-session
verifier so it proves package ownership, machine-identity regeneration,
first-boot initialization, upgrade/reinstall behavior, and SOS readiness
without a guest source tree. Independent-builder package reproduction,
byte-for-byte QCOW2 reproducibility, rollback/A-B layout, signed repository
metadata, AMD64 base-image identity, physical Linux hardware, and installer
media remain open.

## 2026-08-18 — Pinned ARM64 live-ISO assembly attempt

**Goal:** Assemble the five verified Debian packages into ARM64 UEFI live media
that the owner can boot in a VM, while retaining the package hashes and pinned
`20260809T000000Z` Debian snapshot as the only software inputs.

**Changed:** Added a digest-pinned Debian 13.6 live-build container, a
package-only live-build recipe, and a Podman/Docker host wrapper. The recipe
selects an ARM64 GRUB EFI hybrid ISO, installs the SOS package set, enables the
SOS system session, removes cloned machine identity, and emits an ISO structure
inspection plus an atomic input/output manifest. Snapshot APT calls now disable
only `Valid-Until` enforcement, disable the absent `trixie-updates` suite, use
bounded 30-second HTTP/HTTPS attempts with five retries and no HTTP pipelining,
and omit live-build's generic hardware-firmware bundles because virtio VM
devices do not require them.

**Evidence:** Syntax checks, ShellCheck,
`./tests/linux-packaging-host-test.sh`, and `git diff --check` pass. The first
full build reached chroot archive setup but rejected the pinned security and
updates metadata after its `Valid-Until` timestamp. Passing
`Acquire::Check-Valid-Until=false` through live-build's own `--apt-options` and
disabling updates allowed the next run to pass that phase, validate all five
local package hashes, and begin package installation. That run was interrupted
before an artifact was produced. A subsequent owner-run attempt selected 163
packages and requested 568 MB, including 522 MB from the snapshot. Repeated
runtime samples showed the container alive but unchanged at 127.9 MB network
input and 787.8 MB block output, with APT's HTTP method waiting and no counter
movement over the sampled intervals. Dependency inspection showed live-build's
default firmware handling had expanded the VM image with broad AMD, Intel,
Nvidia, Wi-Fi, and device firmware; that default is now disabled.

The revised detached build used builder image
`92ffa6cbdef931bfef2921f3d1fffd3a1cc7b900533fa52bf5480964f2a273c5`,
validated the five package hashes, and completed live-build's binary stage.
`xorriso` reported a successfully written 236,153-sector hybrid ISO in the
temporary build volume, followed by `P: Build completed successfully`. The
container and recorded host wrapper had already exited when inspected, however,
and neither the container entrypoint's `linux_live_iso_built` marker nor the host
wrapper's `linux_iso_container_build_passed` marker appears in the closed log.
The host output directory is empty and the temporary volume has been removed,
so the built bytes, size, and SHA-256 were not retained. No bootable evidence
artifact exists from these attempts.

**Decision:** Continue with package-only ARM64 live media, bounded snapshot
fetches, and no generic firmware bundle. The expiry failure is fixed, but ISO
assembly remains unproven until a clean build completes and its manifest and
structure inspection pass.

**Open risks / next gate:** Diagnose why the successful `lb build` did not
continue through the entrypoint's output-copy and inspection statements, then
rerun the revised recipe. Record the final ISO path, byte size, SHA-256, builder
identity, package closure, and elapsed build time; then boot it as ARM64 UEFI
media and verify SOS readiness, graphics/input, package ownership, first-boot
identity and secret generation, and absence of relevant service failures. A
container build or ISO structure check alone is not boot evidence.
129 changes: 129 additions & 0 deletions images/debian-13/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
# Pinned Debian 13 SOS reference image

This recipe creates a package-only development appliance image. It is a
reference image, not yet the SOS production distribution or an installer ISO.
The image assembler never copies a source tree, runs Cargo/npm, or invokes the
source installer inside the image.

## Inputs

- the exact official Debian 13 ARM64 `generic` QCOW2 named and hashed in
`base-images.lock`;
- one same-version build of each SOS package produced by
`tools/build-linux-packages`;
- the immutable Debian archive timestamp in `base-images.lock`;
- this recipe revision.

The first lock deliberately contains only the Debian 13.6 ARM64 artifact that
has existing VM evidence. Package building also supports Debian `amd64`, but an
AMD64 image must not be claimed as pinned until its official filename, size,
SHA-256, and SHA-512 are added and independently checked.

## Build packages

The preferred host entry point uses Podman or Docker:

```sh
./tools/build-linux-packages-container 0.1.0
```

It builds the environment from the digest-pinned Debian 13.6 slim image,
switches APT to the same immutable Debian snapshot as the image recipe,
installs Rust 1.95.0 and Node 24.18.0, bind-mounts the repository, and invokes
the native package builder as the host UID. Cargo targets and package-manager
caches stay below ignored `.cache/linux-package-builder`; Linux `node_modules`
are built in a temporary staging tree rather than the host source directory.

Set `SOS_CONTAINER_ENGINE=podman` or `SOS_CONTAINER_ENGINE=docker` to select an
engine explicitly. `SOS_LINUX_PACKAGE_PLATFORM=linux/arm64` or
`linux/amd64` selects the package architecture when the engine supports that
platform; cross-architecture builds may require emulation and are not a
substitute for the architecture's boot gate.

The wrapper defaults to two Cargo jobs because release LTO exceeded the
12 GiB Podman VM when Cargo used unrestricted parallelism. Override this only
when the builder has measured headroom:

```sh
SOS_CARGO_BUILD_JOBS=1 \
./tools/build-linux-packages-container 0.1.0
```

The lower-level command remains available when already running inside a clean
Debian 13 builder:

```sh
./tools/build-linux-packages 0.1.0
```

The command creates five `.deb` files and `SHA256SUMS` under the ignored
`artifacts/linux-packages` directory:

- `sos-runtime`;
- `sos-agent`, including Node 24.18.0 as a private runtime;
- `sos-desktop-session`;
- `sos-appliance-session`;
- `sos-image-config`.

The native package builder rejects non-Debian-13 environments, derives native
shared-library dependencies with `dpkg-shlibdeps`, verifies the Node archive
digest, and normalizes package timestamps with `SOURCE_DATE_EPOCH`.

Verify the package dependency closure, installed paths, systemd units, private
Node runtime, package ownership, and idempotent first-boot initialization in a
fresh pinned Debian container:

```sh
SOS_CONTAINER_ENGINE=podman ./tools/test-linux-packages-container
```

## Assemble a live ISO

For a VM-visible boot gate, build an ARM64 Debian Live hybrid ISO from the same
five packages:

```sh
SOS_CONTAINER_ENGINE=podman ./tools/build-linux-iso-container
```

The live builder runs privileged inside the Podman VM because Debian
`live-build` uses chroots and mounts. Its build tree is an ephemeral Podman
volume; only the ISO, El Torito/filesystem inspection report, and deterministic
input/output manifest are copied to ignored `artifacts/linux-iso`. The ISO uses
GRUB EFI, boots the live system into `sos-session.target`, carries no source
tree or compiler, and generates its machine identity, shell token, and initial
experience revision at boot.

## Assemble the image

On a Linux host with QEMU tools and libguestfs:

```sh
./images/debian-13/build \
/path/to/debian-13-generic-arm64.qcow2 \
artifacts/linux-packages
```

The builder verifies both base-image digests and its byte size before writing
anything. `virt-customize` switches APT to the immutable Debian snapshot,
installs only the five local SOS packages plus dependencies from that snapshot,
selects `sos-session.target`, removes package caches and device identity, and
closes the image. The adjacent `.manifest` records the recipe revision, package
hashes, base identity, output byte size, and output SHA-256.

The first boot generates a unique shell token and machine identity, creates the
service-owned state directories, and installs the stock immutable experience
revision before the compositor session starts. Authentication credentials and
SSH host keys are never cloned into the image.

## Current confidence boundary

The recipe is functionally pinned: its base, archive timestamp, SOS package
payloads, and policy are immutable inputs. Byte-for-byte image reproduction is
not yet claimed because QCOW2/libguestfs allocation and filesystem metadata
have not been normalized and compared across two independent builders.

The ARM64 package build, clean-container install, first-boot initializer, and
same-builder byte reproducibility checks have passed. The next gate is to
assemble the image, boot the exact artifact in QEMU, and adapt the boot-session
verifier to consume the installed packages without a guest source tree.
9 changes: 9 additions & 0 deletions images/debian-13/base-images.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Immutable base accepted by the first SOS Debian 13 image recipe.
# Add another architecture only after recording the official artifact's exact
# filename and both independently verified digests.
SOS_IMAGE_ARCH=arm64
SOS_IMAGE_BASE_FILENAME=debian-13-generic-arm64.qcow2
SOS_IMAGE_BASE_SIZE=428736512
SOS_IMAGE_BASE_SHA256=0e68f071dec0215f5d8c7e6f51898213951a6c1a4859f1b980fb4d479255e2bc
SOS_IMAGE_BASE_SHA512=e8ed94e83edded072c66b8871beff8243e0b846ac53980847e2ae44c6d47a8a55579181390b6c85939e85e2a821014ae87e9684930c0509a045212753c8d7916
SOS_IMAGE_DEBIAN_SNAPSHOT=20260809T000000Z
Loading