# 0021: wopkg

> One package tool for every source, with a stable library behind it.

Source: https://wolos.pages.dev/manual/adr-0021

## Context

apps reach a WolOS machine from pacman repos (`[wolos]`, `[wolos-cachy]`, Arch), Flathub, AppImages, the AUR and `.wol` files. Each has its own tool, search, update command and trust model, and AppImages have no updater at all. Changing the system needs root on a sealed system (layers, pending slot), but the person should not need sudo to install an app.
## Decision

- Split: `libwopkg` (Rust, `crates/wopkg`) holds what must be fast and safe: the search index, the downloader, signature and hash checks, AppImage delta updates, the transaction journal (atomic swap, rollback) and the bridge to wold. `wopkg` (Crystal, `wopkg/`) is the CLI and the orchestration: Flatpak and AUR drivers, recipes, the build sandbox. Crystal binds the library through `lib LibWopkg`; the App Store (Rust) links the rlib.
- ABI: ADR 0020's rules with prefix `wopkg_`: opaque handles, `_create`/`_release`, UTF-8 with lengths, `wopkg_status` + optional `wopkg_error`, panics stopped at the boundary, callbacks with `user_data` on the calling thread. Requests and results are JSON documents (`archicture/modules/wopkg.md`); new fields are only added. `libwopkg.so.1`, symbols versioned by `abi/wopkg.map` (WOPKG_1.0), `tests/abi.rs` fails on any removed or changed symbol or declaration.
- Index: one segment file per source (`~/.cache/wopkg/index/<source>.seg`), memory-mapped, rebuilt only when its input's size, mtime or ETag changed. Search is a substring scan over a lowercase haystack, ranked (exact name, prefix, word, summary; then source order `wolos` > `wolos-cachy` > Arch > `.wol` > Flathub > AppImage > recipes > AUR) and grouped into one result per app listing every source that has it. No SQLite: the index is a cache rebuilt from the sources, so it needs no schema, migrations or locking.
- Downloads: HTTPS only (loopback HTTP only when a test sets it), parallel, resumed with `Range` into `<dest>.part`, checked against the expected size and SHA-256 before the rename.
- Trust per source, never weaker than the source's own tool: pacman packages by pacman's keyring (pacman installs them); Flatpak remotes must have `gpg-verify=true`; `.wol` by `wol_core::package::verify` (Ed25519, ADR 0011); AppImages by the embedded signature against the key pinned at first install, otherwise by the zsync SHA-1 fetched over TLS, marked `unsigned`; recipes by an Ed25519-signed recipe index and pinned SHA-256 sources; AUR is marked `unreviewed`.
- Privilege: wopkg runs as the person. User installs (Flatpak `--user`, AppImages in `~/Applications`, recipes in `~/.local`) need nothing. A system change is one wold portal call `package.change` (verb, names) checked against the consent grant for the verb (`packages-install`, `packages-remove`, `packages-update`; Settings > Privacy & Security). wold runs it through the sealed-system pacman (`controller::pkg`: live layer, pending slot for kernel/initramfs/init). A locally built package (AUR, recipe) needs `packages-unreviewed`, which is a separate grant; wold copies the file into root-only staging before `pacman -U`.
- AUR: the PKGBUILD and its diff against the last reviewed copy are shown before a build; sources are fetched in a bubblewrap sandbox with network, `build()`/`package()` run in a second one without network, never as root.
- Recipes: `recipe.toml` (name, version, sources with SHA-256, optional lockfile `fetch` step, `build` and `install` steps). Built in the offline sandbox, cached by the recipe's hash, installed into `~/.local`. Official recipes live in `wopkg/recipes/` and are published with a signed index.
- History: every change is a journal line (`~/.local/state/wopkg/history.jsonl`); AppImages keep the previous file, Flatpak the previous commit, the system its restore slot.
## Consequences

`.wol` payloads are pacman packages (ADR 0011), so a `.wol` install is a system change through wold, not a user install. Grants name the executable, so `packages-install` for `/usr/bin/wopkg` covers every script that runs wopkg (ADR 0020). Crystal is a build dependency of `wopkg` only.

AUR hardening (every PKGBUILD and `.install` is hostile until read; `wopkg/src/wopkg/aur_guard.cr`, `scan.cr`, `aur_hook.cr`). No scanner catches everything: these checks raise flags in plain words and wopkg never says "safe".
- Sandbox: AUR steps use the strict bubblewrap profile: a throwaway user namespace where the build is uid 65534 (never root, never you), `--cap-drop ALL`, no home, no session bus, no ssh agent, and only the few `/etc` files a build needs (no shadow, no keys). Sources are fetched with network (`makepkg --verifysource`, which also checks upstream PGP signatures when `validpgpkeys` is given); `build()` and `package()` run with no network, and a build that needs one fails with a sentence saying so. `.SRCINFO` is regenerated inside the sandbox; the one the AUR serves is not used.
- Pins: the reviewed commit is recorded (`aur-provenance/<pkg>.json`) and the checkout must still be exactly that commit, unedited, before the fetch and before the build. Checkouts with symbolic links are refused. Package metadata (maintainer, first submitted, votes) comes from the AUR RPC over TLS, never from the search dump.
- Checksums: `SKIP` on a downloaded (non-VCS) source is refused unless the person types `yes`; md5/sha1-only sums, plain http and signing keys with no signature file are flagged.
- Signals shown before the question: first-seen or young package, orphan, few votes, maintainer change (needs `yes`), a source host unrelated to the project's page, link shorteners, paste sites, numeric addresses, `curl | sh`, base64, eval, writes outside `$pkgdir`, `sudo`, home folder access, systemd units, setuid, and `.install` scriptlets that run as the administrator.
- Updates: `wopkg aur update` shows the diff of `PKGBUILD` and `*.install` since the installed commit; a changed maintainer or a new scriptlet needs the word `yes`. `wopkg update` never builds AUR packages and `-y` never answers an AUR question.
- Built package: `bsdtar -tv` before pacman sees it: setuid/setgid, device files and files outside `/usr`, `/etc`, `/opt`, `/var` are refused; hooks and units are flagged; the `.INSTALL` script is shown and needs `yes`. One hook, `AurHook.check(dir, artifact, trace)`, hands the package folder, the built file and a system-call trace (strace in the sandbox) to the `wopkg-aurlaws` analyser and returns pass, flag or block with evidence; without it the result is "not checked", never "pass". Provenance (commit, source checksums, maintainer, build date, files) is written next to history.
- Malware scan: AUR sources and built packages, AppImages, recipes and third-party `.wol` go through `clamscan` as a separate optional process (GPL, never linked); the `xguard` engine replaces it when it lands. Signed repository packages are not scanned. A hit stops the install with one sentence and needs a typed `yes` to override; a missing or old (more than 7 days) signature database says so and is never reported as clean. Cost: about 7 s per scan to load signatures.
- Consent: installing still goes through wold's `packages-unreviewed` grant and the same password as other admin actions. The App Store has AUR off by default.

Measured on a box nobody else used (c7i.4xlarge, 16 vCPU, Arch core/extra/multilib plus a 123k-package AUR dump and the AppImage/Flathub indexes; warm cache; Frankfurt mirror):
- `wopkg search` 7-9 ms vs `pacman -Ss` 206-207 ms (firefox, "python numpy", gcc; mean of 30); `info firefox` 7.7 ms vs 199.6 ms.
- Index: 142,075 records from 6 sources in 1.7 s cold, about 0.31 s when nothing changed.
- `wopkg update --check` 0.51-0.56 s (index refresh, release plan, `pacman -Qu`) vs `pacman -Sy` + `-Qu` 0.69-0.71 s, three runs each.
- 60 packages (57 MB), cold cache, three rounds: `wopkg` parallel fetch 1.6 s; `pacman -Sw` 14.3 s with `ParallelDownloads=1`, 5.1-5.5 s with 5 (the default), 2.8-2.9 s with 16. pacman's time includes its signature checks; the fetch checks size and SHA-256 only, so the pacman install that follows still verifies signatures.
Not measured: paru (its install was refused by the sandbox policy), and `install` and `update` of apps end to end (system installs need wold on a sealed image; AppImage and Flatpak installs are covered by tests, not benchmarks).
