CachyOS Host Runbooks
AUR malware defenses — components and verification
Three standing guards, all deployed 2026-07-29, all user-level except the pacman hook:
1. Blocklist enforcement (pacman hook)
/etc/pacman.d/hooks/00-aur-malware-blocklist.hook— PreTransaction abort, 1,936 Target names.- Verify:
grep -c '^Target' /etc/pacman.d/hooks/00-aur-malware-blocklist.hook
2. Daily blocklist sync
aur-blocklist-sync.timer→~/.local/bin/aur-blocklist-sync, daily ~00:23,Persistent=true.- Sources (union-merged, removals retained):
https://raw.githubusercontent.com/lenucksi/aur-malware-check/master/data/campaigns/aur-infected/packages.txt(+packages-extra.txt)https://md.archlinux.org/s/SxbqukK6IA/download(official Arch list)https://cscs.pastes.sh/raw/aurvulnlist20260611.txt
- Also cross-checks installed packages against the list; a hit → critical desktop notification + FAIL line at next Claude session start.
- State + durable log:
~/.local/state/aur-blocklist/{blocklist.txt,last-report.txt}(journal does not retain prior-boot user-service runs —last-report.txtis the record). - Verify:
systemctl --user list-timers | grep aur-blocklistand readlast-report.txt.
3. Weekly maintainer watch
aur-watch.timer→~/.local/bin/aur-watch, Mon 09:00. Queries the AUR for every installed foreign package; alerts on maintainer change, AUR deletion, or orphaning (maintainer takeover is exactly how the 2026 campaign worked).- State:
~/.local/state/aur-watch/{state.json,last-report.txt}. - Current baseline (2026-07-29): 7 foreign packages — clion/clion-cmake/clion-gdb (Zrax), clips (aksr), jack (dvzrv), rclone-browser (FabioLolix), termcap (hildigerr).
4. Session-start IOC tripwire (inside the boot-health SessionStart hook)
- Checks the 2026 campaign IOCs every Claude session: eBPF rootkit maps
/sys/fs/bpf/hidden_*, fake~/.local/bin/sudopassword grabber,/tmp/.cachedropper. PrintsFAIL: malware IOC presentif any appear.
Manual IOC sweep (run if a tripwire ever fires)
ls /sys/fs/bpf/hidden_* 2>/dev/null # eBPF rootkit maps — must be empty
ls -la ~/.local/bin/sudo 2>/dev/null # fake sudo grabber — must not exist
ls -d /tmp/.cache 2>/dev/null # dropper dir — must not exist
pacman -Qm # foreign packages — compare to the 7-package baseline
If INFECTED: step one is credential rotation (GitHub/npm/SSH/API keys — the payload is an infostealer), then restore from a pre-infection snapper snapshot.
Upgrade workflow
- Automatic: Sun 03:00
incus-update-all --stopped(17 containers, parallel, package-manager autodetect) +host-update.service(pacman -Syu, pacnew report, nvidia-580xx pin guard). Logs:~/.local/state/incus-update/<timestamp>/. - Manual:
sudo pacman -Syuany time — official repos are outside the AUR attack surface (see decisions ADR-005).incus-update-allfor the fleet. - After kernel upgrades: confirm DKMS built for both kernels:
dkms statusshould show the nvidia module forlinux-cachyosANDlinux-cachyos-lts;nvidia-smimust see the Quadro P1000. - Rollback: snap-pac takes pre/post Btrfs snapshots around every transaction; limine has snapshot boot entries. LTS kernel is fallback boot entry 2.
Known-benign boot noise (do not chase)
- ACPI
AE_ALREADY_EXISTSBIOS errors, i915Failed to probe lspcon— firmware/hardware noise. - i915
Atomic update failure on pipe A— display-timing noise, cosmetic. tmpfs: Unknown parameter 'none'— one line per Incus OCI (application) container start: LXC mounts a tmpfs on the container's/runand passesnonewhere kernel ≥7.x parses it as an unknown mount parameter. Upstream lxc/incus#3340, maintainer-confirmed non-fatal ("optional mount, fails and logs but is non-fatal"), closed without a fix commit. The dmesg error maps to the LXC-log lineFailed to mount "none" onto ".../rootfs/run"; containers run fine. Count per boot should equal the number of autostarted APP containers (glr-/glrsvc-/gatekeeper-neo4j); a higher count means something new is doing bad tmpfs mounts.gkr-pam: unable to locate daemon control file— gnome-keyring PAM quirk under lightdm, harmless.
bluetoothd "Host is down" flood — FIXED 2026-08-03
An earlier revision of this page called bluetoothd's Unable to get Hands-Free Voice gateway SDP record: Host is down benign noise. WRONG: it was a 1-per-minute error loop (1,137 lines in one 19-hour boot) that tripped the boot-health FAIL.
- Root cause: blueman-applet's autoconnect plugin held
[('EC:66:D1:C5:1A:F4', '<all-services wildcard>')]— the Px7 S2e headphones — and host-polls that device every 60 s forever while it is powered off. Each attempt failsEHOSTDOWN; bluetoothd logs the failure against the HFP profile leg. - Red herring: the Echo Plus is the only paired device advertising the Handsfree Audio Gateway UUID, but untrusting it changed nothing — the initiator was blueman, not bluez trust-based policy.
- Fix:
gsettings set org.blueman.plugins.autoconnect services "[]". Nothing is lost: powered-on headphones initiate the connection themselves; blueman's host-side polling is redundant. - Regression check:
journalctl -b -u bluetooth | grep -c 'Host is down'should stay 0. If the flood returns, re-readgsettings get org.blueman.plugins.autoconnect services— pairing a device via blueman's dialog can silently re-add it.
doxygen 1.16.1 SIGABRT on CUDD sources (known, app-level)
4 identical coredumps 2026-08-02 came from doxygen Doxyfile inside the SWI-Prolog bddem pack build (~/.local/share/swi-prolog/pack/bddem/cudd-3.0.0/). Deterministic upstream bug: std::string_view::operator[] assertion (__pos < _M_len) in the comment scanner, triggered by CUDD's malformed nested comment (cudd/cuddCompose.c:1704), surfaced by Arch's _GLIBCXX_ASSERTIONS hardening. No functional impact — the pack's bddem.so built and loads. Do not chase new coredumps that match doxygen + this stack; docs for that pack simply don't build.
Package hygiene
- Orphan sweep:
pacman -Qdtq→sudo pacman -Rns $(pacman -Qdtq). 2026-08-03 removed: ffmpeg4.4, libdispatch, libjcat, litehtml0.9, python-pkg_resources (all zero reverse-deps). pacman -Qm(foreign) includes both AUR packages AND ex-official packages dropped from repos — checkPackager:before suspecting AUR origin.
orchestrata boot race — FIXED 2026-07-29, regressed, re-fixed 2026-08-11
The three orchestrata user units used to log Failed to start on every cold boot. Root cause was double: cachyos-settings ships DefaultTimeoutStartSec=15s for the user manager (/usr/lib/systemd/user.conf.d/00-timeout.conf), which killed the units' 180s ExecStartPre wait loop at 15s; and the wait loop only probed the bolt TCP port, which opens before the Memgraph cluster can actually route reads — so the kernel could start degraded (no audit trail / no KG injection, one-shot connect with no retry).
Fix (in the unit files + ~/.local/bin/orchgraph-ready): each unit sets TimeoutStartSec=240 (> the 180s wait budget), StartLimitIntervalSec/Burst moved to [Unit] where systemd ≥230 requires them, and ExecStartPre now calls orchgraph-ready, which performs a routed RETURN 1 via the neo4j driver — the unit starts only when the cluster can serve reads. A Failed to start from these units after a boot is now a REAL failure, not noise. The remaining app-level gap (no reconnect after a post-start blip) is in the orchestrata backlog.
2026-08-11 recurrence — the TimeoutStartSec was lost in a unit rewrite
All five orchestrata units failed again on the 2026-08-11 boot (start-pre operation timed out, recovered via Restart=on-failure on the second attempt). Cause: a later rewrite of the unit files renamed the probe orchgraph-ready → orchestrata-kg-ready and dropped the explicit TimeoutStartSec in the process, silently re-exposing the 15s cachyos default. This page kept claiming the 240s fix was in place — stale.
Re-fix: explicit TimeoutStartSec restored on every unit that runs the probe, sized to the probe budget (gateway gets more because it has a second start-pre):
| unit | TimeoutStartSec | start-pre budget |
|---|---|---|
| orchestrata-bot, orchestrata-kernel, orchestrata-ast-live-sync, orchestrata-conversation-consumer | 200 | kg-ready 180s |
| orchestrata-gateway | 260 | kg-ready 180s + CXP-port wait 60s |
| orchgraph-backup (timer) | 200 | kg-ready 180s |
Each unit file now carries a comment stating WHY the value exists and that it regressed once — do not remove it when rewriting a unit. Regression check after any boot: journalctl -b -p err | grep -c 'Failed to start orchestrata' must be 0.
gitlab-runner glr-* container orphans — FIXED 2026-08-11
Sibling failure class, found the same day: when the runner is SIGTERMed during host shutdown, cleanup.sh's incus delete -f races incusd going down and loses (user units cannot order against the system incus.service). The 2026-08-10 shutdown orphaned five glr-*/glrsvc-* containers, which auto-started next boot as zombies — one (glr-15815547501) became permanently unstartable because /run wiped its /etc/hosts bind-mount backing file. Fix: deploy/gitlab-runner/incus-executor/sweep-stale.sh runs as the runner unit's ExecStartPre and deletes any glr-*/glrsvc-* container plus stale glr-hosts-* dirs — safe because custom-executor jobs cannot survive a runner restart. Committed in orchestrata-ultimate-architecture 51c4e536.