#!/usr/bin/env bash
# scripts/prereqs/install-apparmor-libvirt.sh [--check|--dry-run]
#
# Prereq: let qemu read/write the VR1 libvirt pool parent under AppArmor.
#
# WHY THIS EXISTS (learned the hard way, 2026-07-12): libvirt's stock
# `abstractions/libvirt-qemu` profile only grants qemu access to 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 when the POSIX permissions are
# perfect. The symptom is nasty: the domain DEFINES fine, then fails to start with
# a bare qemu "Permission denied", and nothing in the libvirt error names AppArmor.
# This GATES EVERY VR1 VM BOOT (Office1 edge, DC edges, node VMs).
#
# It had been applied by hand on the vcloud host and existed ONLY as host state --
# so a rebuild, or a second host, walked straight back into the same wall. That is
# what this script closes.
#
# Grants: `<pool-parent>/** rwk,` in /etc/apparmor.d/local/abstractions/libvirt-qemu
# (the `local/` include is the vendor-sanctioned override point -- it survives
# package upgrades, which editing the abstraction itself would not).
#
# --check report whether the rule is present; exit 0 ok / 1 missing
# --dry-run print what it would do; mutate nothing
# (no flag) append the rule (if absent), reload apparmor, restart libvirtd (sudo)
#
# Idempotent: if the rule is already present it exits 0 having touched NOTHING --
# in particular it does NOT reload apparmor or bounce libvirtd on an already-good
# host. 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 -- hence it is gated behind an actual change.
#
# Pool parent defaults to /var/lib/libvirt/vr1; override with VR1_POOL_PARENT.
#
# Exit: 0 ok/installed | 1 check-failed | 2 bad args | 3 unsupported OS |
# 4 install failed. ASCII + LF.
set -uo pipefail
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
. "$HERE/lib-prereq.sh"
POOL_PARENT="${VR1_POOL_PARENT:-/var/lib/libvirt/vr1}"
AA_DIR="/etc/apparmor.d/local/abstractions"
AA_FILE="$AA_DIR/libvirt-qemu"
RULE="$POOL_PARENT/** rwk,"
pq_parse_mode "$@"; rc=$?
[ "$rc" = "10" ] && { echo "usage: bash scripts/prereqs/install-apparmor-libvirt.sh [--check|--dry-run]"; exit 0; }
[ "$rc" = "0" ] || exit "$rc"
# Is AppArmor actually the LSM in play? On a host without it (or with it disabled)
# the rule is unnecessary, not failed -- report N/A and succeed. Do not manufacture
# a failure on a system this does not apply to.
aa_in_use() {
[ -d /etc/apparmor.d ] || return 1
if pq_have aa-enabled; then aa-enabled --quiet 2>/dev/null && return 0 || return 1; fi
[ -e /sys/module/apparmor/parameters/enabled ] || return 1
grep -qi '^y' /sys/module/apparmor/parameters/enabled 2>/dev/null
}
rule_present() { [ -r "$AA_FILE" ] && grep -qxF "$RULE" "$AA_FILE"; }
report() {
if ! aa_in_use; then
echo "N/A: AppArmor is not in use on this host -- no libvirt-qemu override needed"
return 0
fi
if rule_present; then
echo "OK: qemu is granted '$POOL_PARENT/**' in $AA_FILE"
return 0
fi
echo "ABSENT: no AppArmor grant for '$POOL_PARENT/**' in $AA_FILE"
echo " Every VR1 VM will DEFINE but FAIL TO START (qemu: Permission denied)."
return 1
}
report; st=$?
[ "$PQ_CHECK" = "1" ] && exit "$st"
[ "$st" = "0" ] && { echo "-- apparmor/libvirt prereq already satisfied --"; exit 0; }
pq_require_apt "the apparmor/libvirt override" || exit 3
pq_run "create $AA_DIR" -- sudo mkdir -p "$AA_DIR" \
|| { echo "FAIL: could not create $AA_DIR" >&2; exit 4; }
pq_run "grant qemu access to $POOL_PARENT/**" -- \
sudo sh -c "printf '%s\n' '$RULE' >> '$AA_FILE'" \
|| { echo "FAIL: could not append the rule to $AA_FILE" >&2; exit 4; }
pq_run "reload apparmor" -- sudo systemctl reload apparmor \
|| { echo "FAIL: apparmor reload" >&2; exit 4; }
pq_run "restart libvirtd (picks up the new profile for NEW domain starts)" -- \
sudo systemctl restart libvirtd \
|| { echo "FAIL: libvirtd restart" >&2; exit 4; }
[ "$PQ_DRYRUN" = "1" ] && { echo " (dry-run: no changes made)"; exit 0; }
rule_present || { echo "FAIL: rule still absent after append -- inspect $AA_FILE" >&2; exit 4; }
echo "INSTALLED: qemu may now open disks under $POOL_PARENT/. Re-run --check to confirm."
exit 0