PAF becomes saf/device (history kept), STATE.md dissolves into saf/state.md with the dated era archived, the substrate SAF moves up from souveraine, and every agreement points at saf/INDEX.md and nowhere else. one map, nothing to remember
266 lines
14 KiB
Markdown
266 lines
14 KiB
Markdown
# TASK 25 — One gitea repo for every SouveraineOS package
|
|
|
|
**Status:** 2026-08-13 — Pixel3Arch now has one package workflow. Its device
|
|
and kernel jobs publish signed artifacts into the additive `edge` release and
|
|
validate every emitted artifact against the central distribution contract. The
|
|
former raw `kernel-latest` release path is retired; its first shared-archive CI
|
|
proof remains required before calling the kernel delivery complete.
|
|
|
|
## The problem in one line
|
|
|
|
`souveraine`'s CI is the only pipeline that does the last mile — `repo-add
|
|
--sign` into a per-arch pacman database. Every other repo stops at "here is
|
|
a file," so those packages have to be copied to the phone and `pacman -U`'d
|
|
by hand.
|
|
|
|
| Repo | CI | Publishes | `pacman -Syu`-able |
|
|
|---|---|---|---|
|
|
| souveraine | `ci.yml` (primary/public) | signed pkgs **+ `souveraine-{arch}.db`** | **yes** |
|
|
| Pixel3Arch | `kernel.yml` (main) | raw `.pkg.tar.zst`, no db, no sig | no |
|
|
| firefox-arm | `build.yml` | nothing published yet | no |
|
|
| culver | `ci.yml` | tests only | no |
|
|
| souveraine-player | none | — | no |
|
|
| upower | none — rides souveraine as a submodule | (into souveraine's db) | yes |
|
|
|
|
upower is the proof the pattern generalises: a **separate repo**, built by
|
|
souveraine's CI, folded into the same database. That is the shape everything
|
|
else should take.
|
|
|
|
## The design — one database, many producers
|
|
|
|
Do NOT add a fourth pipeline. Make `souveraine-{arch}.db` the single archive
|
|
and have every producer publish into it.
|
|
|
|
1. **Pixel3Arch `kernel.yml` gains the last mile.** It already builds a valid
|
|
package on the archdev runner and asserts the built hash against the gitea
|
|
tip. Add the `repo-add --include-sigs --sign --key "$ARCHIVE_KEY"` step and
|
|
publish into the shared `edge` release — `souveraine/.gitea/workflows/ci.yml`
|
|
lines ~248-256 are the reference implementation. Kernel updates then arrive
|
|
by `pacman -Syu`; flashing becomes a recovery tool, not a release channel.
|
|
|
|
**`repo-add --sign` is NOT the first blocker** (verified 2026-07-25;
|
|
parked by Casey the same day). Three things sit in front of it:
|
|
|
|
- **The job has never once passed.** 11 runs, 11 failures, none since
|
|
Jul 14; `kernel-latest` is a 404 and has never been published. The Jul 14
|
|
chain (`b1d3226` → `9c51f3f`) was successive gitea-credential fixes to
|
|
`build.sh` and **the last fix was never re-run** — it may already work.
|
|
- **`pkgver` is static.** `kernel/PKGBUILD:12-13` is `pkgver=7.1.1` /
|
|
`pkgrel=1`; the commit lives only in the kernelrelease, so every build is
|
|
`7.1.1-1` and `-Syu` can never see an upgrade. `build.sh:27-28` hardcodes
|
|
both artifact filenames — they must become dynamic in the same change.
|
|
- **The obvious fix is non-monotonic.** `vercmp 7.1.1.g588929024
|
|
7.1.1.gaaa111222` → `-1`: a bare `.g<sha>` sorts as hex, so a newer
|
|
kernel can sort *below* an older one. Use `7.1.1.r<count>.g<sha>`
|
|
(verified monotonic), matching souveraine's own scheme. Never `+`.
|
|
|
|
**Archive shape — Casey chose "additive" 2026-07-25.** Publishing into the
|
|
shared `edge` release as written above does not work today: `ci.yml`
|
|
**deletes the release and tag and recreates it**, uploading only its own
|
|
`pacman-repo` (which it `rm -rf`s first), so anything another producer
|
|
published there is erased on the next souveraine push. ci.yml must stop
|
|
delete-and-recreate; each producer then does read-modify-write on
|
|
`souveraine-<arch>.db` under a shared archdev flock (`repo-add` replaces
|
|
the same-pkgname entry, so old versions drop out), uploading its own
|
|
packages plus the refreshed db and deleting only its own superseded
|
|
assets. Both jobs are `runs-on: archdev`, so one flock serialises them.
|
|
|
|
Two things that make it easier than expected: the archive key
|
|
`3CD9E99E…C8E911` is **already a secret key in casey's gpg keyring on
|
|
archdev** and souveraine's CI signs with it there, so kernel.yml needs no
|
|
new signing secret; and `~/.git-credentials` on archdev holds both
|
|
`oauth2:` and `casey:` gitea entries. Pixel3Arch CI does still need a
|
|
token that can write to `Fimeg/souveraine`.
|
|
2. **squeekboard + stevia** are Pixel3Arch packages — once the kernel job is
|
|
generalised into a "build these packages, sign, publish" job, they are extra
|
|
entries in it, not new pipelines.
|
|
3. **firefox-arm** publishes into the same db instead of standing alone.
|
|
4. **Phone wiring lives in `rootfs-overlay/`**, never only on the device — see
|
|
"What bit us" below.
|
|
|
|
## Signing key
|
|
|
|
One archive key signs the whole database; a pacman db cannot coherently mix
|
|
signers. Pixel3Arch's CI needs the same `$ARCHIVE_KEY` secret souveraine's CI
|
|
uses. The key asserts "SouveraineOS published this," which is true regardless
|
|
of which repo built it — so sharing it is correct, not a compromise.
|
|
|
|
This is the *package archive* key and is deliberately shared across machines.
|
|
It is NOT the machine/agent identity key: those stay unique per device and are
|
|
never generated implicitly (`SouveraineOS/docs/FEDERATION.md`, identity tiers).
|
|
Different keyring, different trust domain — do not conflate them.
|
|
|
|
## Phone-side wiring (done 2026-07-24, live)
|
|
|
|
- `/etc/pacman.d/souveraine-edge.conf` — `[souveraine-aarch64]`,
|
|
`SigLevel = Required DatabaseRequired`. Template from
|
|
`souveraine/packaging/arch/souveraine-edge.conf`, `__ARCH__` → `aarch64`.
|
|
- `/usr/local/libexec/souveraine-pacman-fetch` — XferCommand shim; attaches the
|
|
gitea netrc credential for `10.10.20.120` only, every other mirror unchanged.
|
|
- `/etc/pacman.d/souveraine-gitea.netrc` — `root:alpm 0640`, token, NOT in git.
|
|
- `pacman-key --add` + `--lsign-key 3CD9E99E222C2A174986FC9AFF4949AA20C8E911`.
|
|
- `/etc/pacman.conf` — `XferCommand` under `[options]`, `Include` at the end.
|
|
|
|
### Two traps found doing it
|
|
|
|
- **`DownloadUser` + a custom `XferCommand` are incompatible.** Pacman execs the
|
|
external command as `DownloadUser` but hands it a path inside the root-owned
|
|
sync dir; it only creates the alpm-owned `download-*` staging dir for its
|
|
INTERNAL downloader. Result: `EACCES` on every repo, failing on upstream
|
|
`core.db` long before the souveraine repo is reached. Unset `DownloadUser`.
|
|
- **`DisableSandboxFilesystem` / `DisableSandboxSyscalls` are not pacman
|
|
options.** They were present in the phone's `pacman.conf`, silently ignored,
|
|
and left `pacman -Sy` broken since ~2026-07-23. pacman 7.1 has exactly one:
|
|
`DisableSandbox` — and it governs landlock, not the privilege drop, so it is
|
|
not the fix here either.
|
|
|
|
## What bit us — why the overlay matters
|
|
|
|
The phone wiring existed once, was configured live on the device, and was lost
|
|
at the next reflash. `provision-rootfs.sh` copies `rootfs-overlay/` wholesale
|
|
into a freshly extracted rootfs; anything not represented there does not
|
|
survive. **The phone is Casey's daily driver — a reprovision costs him his live
|
|
system, which is exactly why the repo pipeline matters: it replaces reflashing
|
|
with in-place updates.**
|
|
|
|
## Next stage — the shell surfaces, settings included
|
|
|
|
Casey 2026-07-24: souveraine-settings should become a versioned app, and the
|
|
state-manager settings should live in it.
|
|
|
|
**Settings is QML, not a binary** — `surfaces/quickshell/settings.qml`, run via
|
|
`qs -p`. On the phone it sits in `~/.config/quickshell/souveraine/`, unowned,
|
|
delivered by rsync + `deploy.sh`. So "version the settings app" is really
|
|
**package the shell surfaces**, and it is harder than the binaries were:
|
|
|
|
- A package cannot own `$HOME`. The tree would install to a system path
|
|
(`/usr/share/quickshell/souveraine/`) and the shell's resolution + the
|
|
composed-tree symlinks (`souveraine/` → stock `ii/`) must survive the move.
|
|
See `unify-shell-trees-laptop-phone.md`, which is the same tree problem.
|
|
- It is all-or-nothing: splitting settings out alone puts one QML tree behind
|
|
two delivery mechanisms.
|
|
- `deploy.sh` becomes the dev loop, not the delivery path.
|
|
|
|
**Ordering, and why.** TASK-19's rule is that every control is a view over the
|
|
owning service — "no success-shaped switches". The device state machine is
|
|
still *observational* (it logs transitions and confidence, drives nothing;
|
|
idle timeouts remain hypridle's 300s/600s). Settings pages over it would be
|
|
lying until TASK-15/08 give it actuators. But this is not a hard block on all
|
|
of it: **charge control is real now** (TASK-08b, unblocked tonight) and has a
|
|
live sysfs + D-Bus backend, so a battery/charge page can be honest today.
|
|
|
|
So: package the surfaces → expose what already actuates (charge, existing
|
|
pages) → add state-machine controls as TASK-15/08 wire them up.
|
|
|
|
### 2026-08-11 — an update is offered to a body, not pushed through its glass
|
|
|
|
Observed on the Pixel daily driver: `deploy.sh --phone` passed its lock check,
|
|
snapshotted the tree, and still replaced the live QuickShell composition while
|
|
Casey was using it. The reload killed an active Souveraine conversation. A
|
|
rollback makes the files recoverable; it does not make destroying live session
|
|
state an acceptable delivery protocol.
|
|
|
|
Casey's required development shape is now explicit:
|
|
|
|
1. CI publishes a signed, immutable surface candidate. The laptop never rsyncs
|
|
that candidate directly over a live body's active tree.
|
|
2. A connected-device channel may announce that a development update is
|
|
available and safe to evaluate. USB Signaller is a possible transport for
|
|
that offer; it is not the update authority and attachment is not consent.
|
|
3. Each body answers independently. Its local updater acquires the durable
|
|
maintenance lease, verifies the package and target profile, stages beside
|
|
the running candidate, and reports what would change.
|
|
4. Activation is an explicit local transition with conversation/session state
|
|
preserved or deliberately quiesced, a health check, and an automatic return
|
|
to the last working candidate. One connected device accepting must not make
|
|
every connected device switch.
|
|
|
|
`deploy.sh --phone` remains a recovery/developer instrument for an explicitly
|
|
quiesced target. It is no longer the ordinary update path. TASK-08 owns the
|
|
lease and state projection; the updater owns retrieval and candidate
|
|
activation; USB Signaller can carry presence and offers only.
|
|
|
|
## The actual inventory (measured 2026-07-25)
|
|
|
|
379,153 files under `/usr /etc /opt /boot`; **5,624 owned by nothing**. The
|
|
same disease as the eleven binaries, just not yet colliding: 16
|
|
`/usr/bin/blueline-*` scripts, the `gadget-*` set, `clatd`, `mmsdtng`,
|
|
`qmic`, `qrtr-*`, `rmtfs`, a hand-installed `Qt6Contacts` cmake tree,
|
|
`libcmatrix.so.0u` (the `ldconfig` warning on every transaction), and stale
|
|
`chatty.orig-pacman` / `chatty.pacman-orig` leftovers.
|
|
|
|
One of them, `blueline-proximity-lock`, is not merely unpackaged — it is a
|
|
**running second authority** over proximity. Resolved 2026-07-25: it reports
|
|
to sessiond and decides nothing. Only its packaging remains, under TASK-27.
|
|
|
|
**`pacman -Syu` had been blocked for days by exactly one of these files.**
|
|
Intersecting the unowned set against the 112,051 files shipped by the 199
|
|
pending packages gave a conflict set of ONE:
|
|
`/usr/include/aquamarine/backend/FrameScheduler.hpp`, hand-dropped Jul 9 into
|
|
a directory whose every other header belongs to `aquamarine 0.12.1-1`, and
|
|
shipped by `extra/aquamarine 0.13.0-2`. It was also a version-mismatched
|
|
header sitting in the include path the whole time. Backed up to
|
|
`/var/backups/unowned-preupgrade/` and removed.
|
|
|
|
**Compute conflicts, do not guess at them:**
|
|
|
|
pacman -Fy
|
|
pacman -Ql | awk '{print $2}' | sed 's#/$##' | sort -u > owned
|
|
find /usr /etc /opt /boot -xdev \( -type f -o -type l \) | sort -u > ondisk
|
|
comm -13 owned ondisk > unowned
|
|
pacman -Qu | awk '{print $1}' > pending
|
|
xargs -a pending pacman -Fl | awk '{print "/" $2}' | sed 's#/$##' | sort -u > newfiles
|
|
comm -12 unowned newfiles # <- exactly what will abort -Syu
|
|
|
|
The phone's login shell is **zsh** — no `mapfile`; run this under `bash -s`
|
|
over ssh.
|
|
|
|
## Acceptance
|
|
|
|
- Pushing to any SouveraineOS repo produces a signed package in
|
|
`souveraine-{arch}` without hand-carrying anything. **DONE for every device
|
|
package except the kernel** (2026-07-30; Pixel3Arch `packages.yml`).
|
|
- The phone installs kernel, upower, squeekboard, stevia and souveraine from
|
|
`pacman -Syu`. **DONE except the kernel** — squeekboard/stevia publish
|
|
alongside the rest; the first real upgrade the pipeline delivered was
|
|
`usb-signaller 0.3.1-2 → 0.3.1-3`.
|
|
- A reprovision restores the repo wiring from `rootfs-overlay/` with no manual
|
|
steps.
|
|
|
|
## Cross-build shapes (landed 2026-07-30)
|
|
|
|
Every aarch64 package cross-compiles on the archdev runner against
|
|
`$HOME/aarch64-sysroot`. Four distinct shapes live in `packages.yml`, each its
|
|
own block because the incantation differs:
|
|
|
|
- **arch=any** (pulse-native-provider, osk-switch): plain native `makepkg`.
|
|
- **meson C** (hexagonrpc, tqftpserv): a generated `--cross-file` + sysroot
|
|
pkg-config. Both PKGBUILDs honour `MESON_CROSS_FILE` in `build()`.
|
|
- **Makefile C** (blueline-edge-sense): `CC="aarch64-linux-gnu-gcc
|
|
--sysroot=… -L…/usr/lib"`; the Makefile honours `CC ?=`.
|
|
- **meson+Rust / cargo** (stevia, squeekboard via each `build-cross.sh`;
|
|
usb-signaller via `CARGO_BUILD_TARGET` + a per-target cross linker).
|
|
|
|
Two traps worth not re-deriving: (1) `--sysroot` alone does NOT link on this
|
|
toolchain — ld resolves the sysroot `libc.so` script's `=` paths against the
|
|
wrong prefix and falls back to the host `/lib/libc.so.6` ("file in wrong
|
|
format"); an explicit `-L$SYSROOT/usr/lib` makes ld find the right script
|
|
first. (2) the C cross-file is generated inline in the workflow; stevia/
|
|
squeekboard's own cross-files (`~/build/*.ini`) are pets-state, failed loud
|
|
if missing.
|
|
|
|
## What broke CI and how it was fixed
|
|
|
|
First green run was 993. Run 992 failed because **stale bare VCS clones and
|
|
built `*.pkg.tar.zst` binaries had been committed** (`pkgs/stevia/stevia/`,
|
|
`pkgs/squeekboard/squeekboard/`, 9 package files incl. a 1.7 MB packfile) —
|
|
makepkg rejected the clone as "not a clone of <url>". Fix: `git rm`'d the junk,
|
|
gitignored `*.pkg.tar.zst` + the VCS checkout dirs, and set `SRCDEST`
|
|
out-of-tree in CI so clones never land in a package dir. `usb-signaller`'s
|
|
PKGBUILD was also made cross-aware (it built for the host tuple before).
|
|
|
|
## Connects to
|
|
|
|
TASK-24 (keyboard: squeekboard/stevia are two of the packages), the
|
|
kernel flash flow in `saf/device/build.md`, and
|
|
`souveraine/packaging/arch/` which already holds the canonical configs.
|