Temporarily remove automatic printer discovery

cups-browsed is the daemon that watches the network and creates print queues by itself. Hardening it took a root daemon with a predictable cache down to a confined service account, but a daemon that turns anything advertising itself on the network into a print queue is a lot of exposure for a convenience, so it comes out of the default install while that is reworked. Only the discovery half: CUPS itself stays and printing keeps working, with each printer added by hand in Print Settings.

The migration disables the unit before removing the package because that is the only order that works: pacman deletes the unit file but not the enable symlink, and once the unit is gone systemd can no longer resolve it by name to clean that up.

It then removes the queues discovery generated. cups-browsed keeps those when it stops, since KeepGeneratedQueuesOnShutdown defaults to Yes, and they route through its own implicitclass backend, which goes with the package, so they cannot print again. Idle ones go. A queue with jobs on it is left alone and named: implicitclass only needs cups-browsed to choose a destination, so a job already past that point finishes on its own, and deleting the queue would abort it. One printer's job does not hold up the removal. A printer added by hand has an ipp:// or usb:// device and is left where it is.

A queue whose jobs cannot be asked about is left alone rather than assumed idle, including one named so that lpstat would misread it -- "all" is its word for every destination, and a leading dash or a comma reads as another option or a list.

Where CUPS does not answer at all, or a queue will not delete, discovery is still stopped but the package stays and no marker is written. omarchy-migrate records a migration for the user as soon as it exits zero, so that is where the machine stays until someone removes the package by hand, and the message says so rather than implying a retry.

The queue list is read under LC_ALL=C because lpstat translates "device for", and captured rather than piped, so a cupsd it cannot reach is reported instead of reading like a machine with nothing to clean up.

It removes with plain pacman -R rather than omarchy-pkg-drop, which passes -n and would discard /etc/cups/cups-browsed.conf instead of keeping it as a .pacsave. A removal meant to be temporary should not delete the machine's copy of its own configuration. Without -s either, so it only ever removes the package it names: sweeping newly unneeded dependencies is nothing today, but it is not a promise a rolling dependency graph can keep.

Queue names come off the network, since cups-browsed names its queues after what the printer advertised. CUPS allows every printable character but space, tab, / and #, and lpstat and lpadmin take a destination as an option value, so a name with a leading dash or a comma is reported rather than passed to them and guessed at.

Migration state is per user, so a machine-wide marker records the one removal. Without it, an account whose first migration run came after someone deliberately reinstalled discovery would quietly take it back out again.

The install-time override for cups-browsed.conf now waits for cups-browsed rather than for CUPS. Guarding it on a file CUPS still ships would write a configuration file for a package nothing installed, and pacman would later land the package's own copy beside it as a .pacnew.

The hardened configuration stays in the tree. omarchy-settings still ships the cups-browsed.conf override, the sysusers account and the service drop-in, so they are what discovery returns onto.

Co-Authored-By: Codex XHigh <noreply@openai.com>
This commit is contained in:
David Heinemeier Hansson
2026-08-29 17:13:11 +02:00
co-authored by Codex XHigh
parent 0b3f1b7ead
commit 24c18df5b7
9 changed files with 563 additions and 67 deletions
+15 -52
View File
@@ -47,7 +47,7 @@ verify_services() {
local unit
for unit in \
avahi-daemon.service cups.service cups-browsed.service docker.socket \
avahi-daemon.service cups.service docker.socket \
NetworkManager.service power-profiles-daemon.service sddm.service \
systemd-resolved.service ufw.service; do
systemctl is-enabled --quiet "$unit" || fail "core system services are enabled" "$unit is not enabled"
@@ -65,29 +65,24 @@ verify_services() {
}
verify_printing_security() {
local cups_browsed_pid lpinfo_output printer_name printer_process printer_tmp
local lpinfo_output
! pacman -Q cups-pdf >/dev/null 2>&1 || fail "CUPS-PDF is absent"
pass "the root CUPS-PDF backend is not installed"
getent passwd cups-browsed >/dev/null || fail "the cups-browsed service account exists"
[[ $(systemctl show -P User cups-browsed.service) == "cups-browsed" ]] ||
fail "cups-browsed runs as its service account"
[[ $(systemctl show -P Group cups-browsed.service) == "cups-browsed" ]] ||
fail "cups-browsed runs as its service group"
systemctl is-active --quiet cups-browsed.service || fail "cups-browsed is running"
# Automatic discovery is temporarily out of the default install: a daemon that
# turns anything advertising itself on the network into a print queue is more
# exposure than the convenience is worth while it is reworked. CUPS itself
# stays, so what a stock machine proves here is that printing runs and that
# the desktop user still cannot administer it without authenticating.
! pacman -Q cups-browsed >/dev/null 2>&1 || fail "automatic printer discovery is not installed"
! systemctl is-enabled --quiet cups-browsed.service 2>/dev/null ||
fail "no discovery service is enabled"
! systemctl is-active --quiet cups-browsed.service 2>/dev/null ||
fail "no discovery service is running"
pass "automatic printer discovery is absent from a stock install"
cups_browsed_pid=$(systemctl show -P MainPID cups-browsed.service)
[[ -r /proc/$cups_browsed_pid/status ]] || fail "cups-browsed has a readable process status"
[[ $(awk '/^Uid:/{print $2}' "/proc/$cups_browsed_pid/status") != 0 ]] ||
fail "cups-browsed does not run with root UID"
[[ $(awk '/^CapEff:/{print $2}' "/proc/$cups_browsed_pid/status") == "0000000000000000" ]] ||
fail "cups-browsed has no effective Linux capabilities"
[[ $(stat -c '%a %U:%G' /var/cache/cups-browsed) == "750 cups-browsed:cups-browsed" ]] ||
fail "cups-browsed has an isolated cache" "$(stat -c '%a %U:%G' /var/cache/cups-browsed)"
[[ " $(id -nG cups-browsed) " != *" cups "* ]] ||
fail "cups-browsed is separate from the print-filter group"
systemctl is-active --quiet cups.service || fail "CUPS is running"
if lpinfo_output=$(LC_ALL=C timeout 10 lpinfo -v </dev/null 2>&1); then
fail "the desktop user cannot administer CUPS without authentication"
@@ -95,39 +90,7 @@ verify_printing_security() {
fail "CUPS explicitly denies unauthenticated desktop administration" "$lpinfo_output"
fi
pass "CUPS discovery is isolated from root, filters, and passwordless desktop administration"
# A live driverless printer proves the non-root daemon can still discover and
# create queues without the CAP_NET_BIND_SERVICE Ubuntu carries downstream.
printer_name="OmarchyAcceptancePrinter"
printer_tmp=$(mktemp -d)
printf '#!/bin/bash\nexit 0\n' >"$printer_tmp/command"
chmod 0700 "$printer_tmp/command"
mkdir -m 0700 "$printer_tmp/spool"
ippeveprinter -p 18631 -d "$printer_tmp/spool" -c "$printer_tmp/command" "$printer_name" \
>"$printer_tmp/ippeveprinter.log" 2>&1 &
printer_process=$!
printing_test_cleanup() {
kill "$printer_process" >/dev/null 2>&1 || true
wait "$printer_process" >/dev/null 2>&1 || true
rm -rf "$printer_tmp"
}
trap printing_test_cleanup EXIT
for _ in {1..30}; do
lpstat -v "$printer_name" 2>/dev/null | grep -q "implicitclass://$printer_name/" && break
sleep 1
done
lpstat -v "$printer_name" 2>/dev/null | grep -q "implicitclass://$printer_name/" ||
fail "non-root cups-browsed discovers a driverless IPP printer" "$(<"$printer_tmp/ippeveprinter.log")"
printing_test_cleanup
trap - EXIT
pass "non-root cups-browsed still creates driverless IPP queues without capabilities"
pass "CUPS runs with passwordless desktop administration still denied"
}
verify_runtime_tools() {
@@ -0,0 +1,364 @@
#!/bin/bash
set -euo pipefail
source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh"
migration="$ROOT/migrations/1788009111.sh"
test_tmp=$(mktemp -d)
trap 'rm -rf "$test_tmp"' EXIT
mock_bin="$test_tmp/bin"
mkdir -p "$mock_bin" "$test_tmp/var/lib/omarchy/migrations"
# Every privileged step is stubbed: a real run here would take printer discovery
# off the developer's own machine.
cat >"$mock_bin/omarchy-pkg-present" <<'SH'
#!/bin/bash
[[ " $BROWSED_INSTALLED " == *" $1 "* ]]
SH
# pacman answers the removal preflight from the state each case sets up, and
# logs the removal itself so the flags it is called with are visible.
cat >"$mock_bin/pacman" <<'SH'
#!/bin/bash
printf 'pacman\t%s\n' "$*" >>"$BROWSED_LOG"
[[ $* == *--print* ]] || exit 0
[[ -z $BROWSED_REMOVAL_BLOCKED ]]
SH
cat >"$mock_bin/systemctl" <<'SH'
#!/bin/bash
printf 'systemctl\t%s\n' "$*" >>"$BROWSED_LOG"
unit=${*: -1}
case $1 in
is-enabled) [[ " $BROWSED_ENABLED " == *" $unit "* ]] ;;
is-active) [[ " $BROWSED_ACTIVE " == *" $unit "* ]] ;;
*) : ;;
esac
SH
cat >"$mock_bin/sudo" <<'SH'
#!/bin/bash
printf 'sudo\t%s\n' "$*" >>"$BROWSED_LOG"
exec "$@"
SH
cat >"$mock_bin/install" <<'SH'
#!/bin/bash
printf 'install\t%s\n' "$*" >>"$BROWSED_LOG"
exec /usr/bin/install "$@"
SH
# lpstat reports the queues cups-browsed generated. Only those route through its
# implicitclass backend; the ipp:// and usb:// entries are printers a person
# added and must survive.
cat >"$mock_bin/lpstat" <<'SH'
#!/bin/bash
printf 'lpstat\t%s\n' "$*" >>"$BROWSED_LOG"
case $1 in
-v)
[[ -z $BROWSED_LPSTAT_FAILS ]] || exit 1
# lpstat translates "device for". Only a caller that pinned the locale gets
# the string the migration parses.
if [[ ${LC_ALL:-} == "C" ]]; then
printf '%s\n' "$BROWSED_QUEUES"
else
printf '%s\n' "$BROWSED_QUEUES" | sed 's|^device for |Gerät für |'
fi
;;
-o)
[[ -z $BROWSED_LPSTAT_O_FAILS ]] || exit 1
# Jobs are listed per queue: "<queue>-<id> <user> <size>".
[[ " $BROWSED_BUSY_QUEUES " == *" $2 "* ]] && printf '%s-7 alice 1024\n' "$2"
;;
esac
exit 0
SH
cat >"$mock_bin/lpadmin" <<'SH'
#!/bin/bash
printf 'lpadmin\t%s\n' "$*" >>"$BROWSED_LOG"
[[ -z $BROWSED_LPADMIN_FAILS ]]
SH
chmod +x "$mock_bin"/*
log="$test_tmp/actions.log"
output="$test_tmp/migration.out"
marker="$test_tmp/var/lib/omarchy/migrations/1788009111"
run_migration() {
: >"$log"
: >"$output"
# No LC_ALL in the environment, so the mock above sees "C" only when the
# migration pinned it for the call itself -- which is what a non-English
# desktop depends on.
env -u LC_ALL -u LANGUAGE \
BROWSED_LOG="$log" \
BROWSED_INSTALLED="${installed:-}" \
BROWSED_ENABLED="${enabled:-}" \
BROWSED_ACTIVE="${active:-}" \
BROWSED_REMOVAL_BLOCKED="${blocked:-}" \
BROWSED_QUEUES="${queues:-}" \
BROWSED_BUSY_QUEUES="${busy:-}" \
BROWSED_LPSTAT_FAILS="${lpstat_fails:-}" \
BROWSED_LPADMIN_FAILS="${lpadmin_fails:-}" \
BROWSED_LPSTAT_O_FAILS="${lpstat_o_fails:-}" \
PATH="$mock_bin:$PATH" \
OMARCHY_PATH="$ROOT" \
OMARCHY_CUPS_BROWSED_REMOVAL_MARKER="${use_marker:-$marker}" \
bash -euo pipefail "$migration" >"$output"
}
# ------------------------------------------------ a machine that has discovery
installed="cups-browsed"
enabled="cups-browsed.service"
active="cups-browsed.service cups.service"
blocked=""
# CUPS allows a colon in a destination name but never a space, so "Front:Desk"
# is a legal queue and the separator is the last ": implicitclass://".
queues=$'device for Office: implicitclass://Office/\ndevice for Front:Desk: implicitclass://Front:Desk/\ndevice for -p: implicitclass://-p/\ndevice for a,b: implicitclass://a,b/\ndevice for all: implicitclass://all/\ndevice for Desk: ipp://192.168.1.9/ipp/print\ndevice for Attic: usb://HP/LaserJet%20P1102'
rm -f "$marker"
run_migration
grep -qxF $'sudo\tsystemctl disable --now cups-browsed.service' "$log" ||
fail "the removal disables and stops the discovery service" "$(cat "$log")"
pass "the removal disables and stops the discovery service"
# -Rns would discard /etc/cups/cups-browsed.conf rather than keep it as a
# .pacsave, and a removal meant to be temporary must not delete configuration.
grep -qxF $'pacman\t-R --noconfirm cups-browsed' "$log" ||
fail "the removal keeps configuration pacman marked as a backup" "$(cat "$log")"
if grep -qE -- '-Rns|-Rn ' "$log"; then
fail "the removal never passes -n" "$(cat "$log")"
fi
# -s would also sweep dependencies that became unneeded, which is a promise a
# rolling dependency graph cannot keep.
if grep -q -- '-Rs --noconfirm' "$log"; then
fail "the removal names only the package it means to remove" "$(cat "$log")"
fi
pass "the removal keeps the machine's own cups-browsed.conf and touches nothing else"
# pacman deletes the unit file but not the enable symlink, and once the unit is
# gone systemd cannot resolve it by name to clean that up.
disable_line=$(grep -n 'disable --now' "$log" | tail -1 | cut -d: -f1 || true)
removal_line=$(grep -n $'^pacman\t-R --noconfirm' "$log" | head -1 | cut -d: -f1 || true)
[[ -n $disable_line && -n $removal_line ]] ||
fail "the removal both disables the unit and removes the package" "$(cat "$log")"
(( disable_line < removal_line )) ||
fail "the removal disables before the unit file goes" "$(cat "$log")"
pass "the removal disables before the unit file goes"
# cups-browsed keeps its generated queues on shutdown, and they print through
# the implicitclass backend that goes with the package.
grep -qxF $'sudo\tlpadmin -x Office' "$log" ||
fail "the queues discovery generated are removed with it" "$(cat "$log")"
grep -q 'lpadmin -x Desk' "$log" &&
fail "a printer added by hand over ipp survives" "$(cat "$log")"
grep -q 'lpadmin -x Attic' "$log" &&
fail "a printer added by hand over usb survives" "$(cat "$log")"
grep -qxF $'sudo\tlpadmin -x Front:Desk' "$log" ||
fail "a queue whose name contains a colon is still matched" "$(cat "$log")"
# cups-browsed names queues after what the printer advertised, so the name came
# off the network. lpstat and lpadmin take a destination as an option value: a
# leading dash reads as another option and a comma separates a list.
if grep -qF -- $'lpstat\t-o -p' "$log"; then
fail "a queue named like an option is never passed to lpstat" "$(cat "$log")"
fi
if grep -q -- 'lpadmin -x -p' "$log"; then
fail "a queue named like an option is never passed to lpadmin" "$(cat "$log")"
fi
if grep -q 'lpadmin -x a,b' "$log"; then
fail "a queue name holding a comma is never passed as a destination list" "$(cat "$log")"
fi
if grep -qF -- $'lpstat\t-o all' "$log"; then
fail "a queue named all is never asked about, since lpstat reads it as every destination" "$(cat "$log")"
fi
grep -qF -- "Cannot safely ask about jobs on the queue named '-p'" "$output" ||
fail "a queue that cannot be asked about safely is reported" "$(cat "$output")"
pass "generated queues go, hand-added printers stay, unaddressable names are reported"
# The queues have to go while cups-browsed's backend is still installed.
cleanup_line=$(grep -n 'lpadmin -x' "$log" | tail -1 | cut -d: -f1 || true)
[[ -n $cleanup_line ]] || fail "the removal cleans up generated queues" "$(cat "$log")"
(( cleanup_line < removal_line )) ||
fail "generated queues go before the backend that serves them" "$(cat "$log")"
pass "generated queues go before the backend that serves them"
[[ -f $marker ]] || fail "the removal records machine-wide completion"
pass "the removal records machine-wide completion"
# ------------------------------------ a second user, after a deliberate reinstall
# Migration state is per user. The account that runs this after someone put
# discovery back on purpose must not quietly take it away again.
installed="cups-browsed"
enabled="cups-browsed.service"
active="cups-browsed.service cups.service"
run_migration
[[ ! -s $log ]] || fail "a machine that already had its removal is left alone" "$(cat "$log")"
pass "a second user does not undo a deliberate reinstall"
# ----------------------------------------- a machine that never had discovery
installed=""
enabled=""
active="cups.service"
use_marker="$test_tmp/var/lib/omarchy/migrations/never-had-it"
run_migration
[[ ! -s $log ]] || fail "a machine without discovery is left alone" "$(cat "$log")"
[[ ! -e $use_marker ]] ||
fail "a machine with nothing to remove is not made to pay for a marker" "$(cat "$log")"
pass "a machine without discovery does no privileged work and gets no password prompt"
# ------------------------------------------------- a blocked package removal
# Something depending on cups-browsed makes pacman refuse. Stopping the service
# first and only then discovering that would leave discovery broken rather than
# removed.
installed="cups-browsed"
enabled="cups-browsed.service"
active="cups-browsed.service cups.service"
blocked="1"
use_marker="$test_tmp/var/lib/omarchy/migrations/blocked"
run_migration
grep -q 'disable --now' "$log" &&
fail "a refused removal never stops the service" "$(cat "$log")"
if grep -q $'^pacman\t-R --noconfirm' "$log"; then
fail "a refused removal does not go on to remove anything" "$(cat "$log")"
fi
[[ ! -e $use_marker ]] || fail "a refused removal is not recorded as done"
# The preflight has to ask about the same command the removal will run.
grep -qxF $'pacman\t-R --print cups-browsed' "$log" ||
fail "the preflight asks pacman about the removal it will actually run" "$(cat "$log")"
pass "a removal pacman would refuse changes nothing at all"
# --------------------------------------------------- a queue that is printing
# lpadmin -x cancels the jobs on the queue it removes. A stale queue can be
# deleted whenever someone notices it; an aborted print cannot come back.
installed="cups-browsed"
enabled="cups-browsed.service"
active="cups-browsed.service cups.service"
blocked=""
queues=$'device for Office: implicitclass://Office/\ndevice for Spare: implicitclass://Spare/'
busy="Office"
use_marker="$test_tmp/var/lib/omarchy/migrations/printing"
run_migration
# The implicitclass backend only needs cups-browsed to choose a destination, so
# a job already past that point finishes even though the daemon has stopped.
# Deleting the queue would abort it.
if grep -q 'lpadmin -x Office' "$log"; then
fail "a queue with jobs on it is left for them to finish" "$(cat "$log")"
fi
grep -qxF $'sudo\tlpadmin -x Spare' "$log" ||
fail "an idle generated queue is still removed" "$(cat "$log")"
grep -q 'Office still has jobs' "$output" ||
fail "a queue left alone is named so it can be removed later" "$(cat "$output")"
# One printer's job must not keep discovery on the machine.
grep -qxF $'sudo\tpacman -R --noconfirm cups-browsed' "$log" ||
fail "a busy queue does not hold up the removal" "$(cat "$log")"
pass "a queue with jobs is left for them to finish, and does not hold up the removal"
# ------------------------------------------------- a job query that fails
# Treating a failed query as an idle queue would delete it and abort whatever
# was on it, which is the one outcome this is trying to avoid.
lpstat_o_fails="1"
use_marker="$test_tmp/var/lib/omarchy/migrations/nojobs"
run_migration
if grep -q 'lpadmin -x' "$log"; then
fail "a queue whose jobs could not be checked is left alone" "$(cat "$log")"
fi
grep -q 'Could not check for jobs' "$output" ||
fail "a job query that failed is said out loud" "$(cat "$output")"
pass "a queue whose jobs cannot be checked is left alone, not assumed idle"
lpstat_o_fails=""
# ------------------------------------------------------ CUPS out of reach
# Failing to reach cupsd must not read like a machine with no queues to clean.
installed="cups-browsed"
enabled="cups-browsed.service"
active="cups-browsed.service"
busy=""
lpstat_fails="1"
use_marker="$test_tmp/var/lib/omarchy/migrations/nocups"
run_migration
if grep -q 'lpadmin -x' "$log"; then
fail "nothing is removed when the queue list could not be read" "$(cat "$log")"
fi
# Removing the backend without having read the queue list would strand every
# generated queue permanently, and the marker would stop anyone retrying.
if grep -q $'^pacman\t-R --noconfirm' "$log"; then
fail "the package waits until the queue list can be read" "$(cat "$log")"
fi
[[ ! -e $use_marker ]] ||
fail "an unread queue list is not recorded as a finished removal"
grep -qi 'could not ask cups' "$output" ||
fail "a queue list that could not be read is said out loud" "$(cat "$output")"
# Stopping discovery is the half that mattered, and it is idempotent.
grep -qxF $'sudo\tsystemctl disable --now cups-browsed.service' "$log" ||
fail "discovery is still stopped when the queue list cannot be read" "$(cat "$log")"
pass "an unreadable queue list stops discovery but finalizes nothing"
lpstat_fails=""
# ------------------------------------------------ a queue that will not go
# A queue left behind would route through a backend the removal is about to
# delete, so the package waits rather than stranding it for good.
installed="cups-browsed"
enabled="cups-browsed.service"
active="cups-browsed.service cups.service"
blocked=""
queues=$'device for Office: implicitclass://Office/'
busy=""
lpstat_fails=""
lpadmin_fails="1"
use_marker="$test_tmp/var/lib/omarchy/migrations/stuck"
run_migration
if grep -q $'^pacman\t-R --noconfirm' "$log"; then
fail "the package waits while a generated queue is still there" "$(cat "$log")"
fi
[[ ! -e $use_marker ]] || fail "a half-done cleanup is not recorded as finished"
grep -q 'Could not remove the queue Office' "$output" ||
fail "a queue that would not go is named" "$(cat "$output")"
pass "a queue that will not go keeps the package and the marker back"
lpadmin_fails=""
# -------------------------------------------------- a masked but running daemon
installed="cups-browsed"
enabled=""
active="cups-browsed.service"
blocked=""
queues=""
use_marker="$test_tmp/var/lib/omarchy/migrations/masked"
run_migration
grep -qxF $'sudo\tsystemctl stop cups-browsed.service' "$log" ||
fail "a running unit is stopped even when it is not enabled" "$(cat "$log")"
grep -q 'disable --now' "$log" &&
fail "a masked unit is not disabled" "$(cat "$log")"
pass "a masked but running daemon is stopped without being disabled"
# A machine whose discovery never created a queue has nothing to clean up, and
# no reason to reach for CUPS administration.
grep -q 'lpadmin' "$log" &&
fail "no queues means no CUPS administration" "$(cat "$log")"
pass "a machine with no generated queues does not touch CUPS administration"
+29 -2
View File
@@ -10,11 +10,38 @@ cups_files_conf="$ROOT/etc/cups/cups-files.conf"
sysusers_conf="$ROOT/etc/sysusers.d/omarchy-cups-browsed.conf"
service_dropin="$ROOT/etc/systemd/system/cups-browsed.service.d/10-omarchy.conf"
grep -qxF cups-browsed "$packages" || fail "cups-browsed remains in the base package set"
# Only discovery goes. Everything else printing needs stays, or this stops
# being a removal of one daemon and becomes a removal of printing.
grep -qxF cups "$packages" || fail "CUPS itself remains in the base package set"
grep -qxF cups-filters "$packages" || fail "the CUPS filters remain in the base package set"
grep -qxF system-config-printer "$packages" || fail "Print Settings remains in the base package set"
grep -qxF cups-pk-helper "$packages" || fail "Polkit printer administration is installed"
! grep -qxF cups-pdf "$packages" || fail "the root CUPS-PDF backend is removed"
pass "the base install keeps discovery and replaces CUPS-PDF with Polkit administration"
# Automatic discovery is temporarily out of the default install while it is
# reworked. The hardened configuration below still ships: it is what a
# hand-installed cups-browsed gets, and what discovery comes back onto.
! grep -qxF cups-browsed "$packages" || fail "automatic printer discovery is out of the base package set"
! grep -q 'cups-browsed' "$ROOT/install/config/enable-services.sh" ||
fail "a fresh install does not enable a discovery service it no longer installs"
! grep -q 'enable_system_service cups-browsed' "$ROOT/bin/omarchy-upgrade-to-quattro" ||
fail "the Quattro upgrade does not enable a discovery service it no longer installs"
pass "the base install keeps CUPS and Polkit administration, without automatic discovery"
# The install-time override for a file has to wait for the package that owns
# it. CUPS still ships /etc/cups/cups-files.conf, so a guard on that file no
# longer says anything about cups-browsed: writing its override on a machine
# without the package leaves a configuration file for a package nothing
# installed, and pacman lands its own copy beside it as a .pacnew later.
post_install_pacman="$ROOT/install/post-install/pacman.sh"
grep -q 'cups-cups-browsed.conf && -f /etc/cups/cups-browsed.conf' "$post_install_pacman" ||
fail "the discovery override waits for the package that owns the file it replaces"
grep -q 'cups-cups-files.conf && -f /etc/cups/cups-files.conf' "$post_install_pacman" ||
fail "the CUPS authorization override waits for the file it replaces"
pass "install-time overrides wait for the packages that own their files"
grep -qxF 'CacheDir /var/cache/cups-browsed' "$cups_browsed_conf" ||
fail "cups-browsed keeps state outside the print-filter cache"