# changelog 2026-07-13 -- fold the AppArmor/libvirt rule into scripts/prereqs

## What

New prereq installer `scripts/prereqs/install-apparmor-libvirt.sh`, wired into
`install-all.sh` (right after `install-virtualization`, since it edits libvirt's own
profile directory) and reported by `check-prereqs.sh`. Harness extended
`tests/prereqs/run-tests.sh` 24 -> 32 PASS. README updated.

## Why

The rule `/var/lib/libvirt/vr1/** rwk,` in
`/etc/apparmor.d/local/abstractions/libvirt-qemu` **gates every VR1 VM boot** -- the
Office1 edge, the DC edges, and every node VM. libvirt's stock
`abstractions/libvirt-qemu` grants qemu only the DEFAULT pool path
(`/var/lib/libvirt/images`); VR1 uses a custom pool parent, which is not in that
abstraction, so AppArmor blocks qemu even with perfect POSIX permissions. The failure
is nasty and non-obvious: the domain DEFINES fine, then fails to start with a bare
qemu "Permission denied" and **nothing in the libvirt error names AppArmor**. It cost
a session on 2026-07-12 (DOCFIX-186).

It had been applied BY HAND on the vcloud host and existed ONLY as host state. Nothing
in the repo carried it -- so a rebuild, or a second host, walked straight back into the
identical wall. This closes that gap. It was logged as a PREREQ candidate at the time
and never actioned; the ledger collapse surfaced it again.

## Design notes

- **Idempotent, and deliberately quiet on a good host.** If the rule is already present
  the script exits 0 having touched nothing -- in particular it does NOT reload apparmor
  and does NOT bounce libvirtd. The reload/restart happens ONLY on the run that actually
  adds the rule. (Restarting libvirtd does not stop running domains, but it is a real
  service action and is gated behind a real change rather than run on every pass.)
- **Writes to `local/`, not the abstraction itself.** `local/abstractions/libvirt-qemu`
  is the vendor-sanctioned override include -- it survives package upgrades, which an
  edit to the shipped abstraction would not.
- **Pool parent is a variable, not a literal.** Defaults to `/var/lib/libvirt/vr1`,
  overridable via `VR1_POOL_PARENT` -- per the repo's prefer-dynamic-over-hardcoded rule.
- **Non-AppArmor hosts report N/A and SUCCEED.** The rule is unnecessary there, not
  failed; the script does not manufacture a failure on a system it does not apply to.

## Verification

- `tests/prereqs/run-tests.sh`: **32/32 PASS** (was 24/24). The new cases drive the
  DETECTION off a pool parent guaranteed absent from the profile, so the negative path
  is deterministic regardless of what the host has applied: `--check` must FAIL for an
  ungranted parent (a false OK here is the whole bug), `--dry-run` must plan the fix and
  mutate nothing, and the planned rule must be exactly `<parent>/** rwk,`.
- Full gauntlet: **ALL GREEN (52 harnesses)**. `repo-lint`: 0 fail (1 documented legacy warn).
- Run live on vcloud: `--check` reports OK (rule already present), exit 0, and the
  profile file is unchanged -- confirming the already-satisfied path is a true no-op.

## Revert

```
git revert <this commit>
```
Removes the installer, its harness cases, the `install-all`/`check-prereqs` wiring, and
the README rows. Reverting touches NO host state: the rule already live on vcloud was
applied by hand long before this commit and is not managed by it.
