Ghost & empty-artifact cleanup
Long-lived BOA boxes accumulate ghosts: orphaned Composer trees with no working docroot,
legacy shared codebases no platform links to any more, platform aliases whose platform is
gone, nginx vhosts with no matching site, stale alias copies for sites deleted long ago.
The nightly owl run and the every-3-minutes manage_ltd_users.sh
cron detect all of these — and, only when explicitly enabled, move them aside.
Three rules govern every mover on this page:
- Opt-in, dry-run by default. Every flag defaults to
NO: out of the box each check only logs what it would move and changes nothing. You flip one_VAR=YESper check, per box or per account, after reviewing the dry-run output. - Moves, never deletes. An enabled check relocates the item to a backup or
undo/path; recovery is moving it back. - Safeguards sit above the flags. The Provision interlock and the fail-closed liveness tests below apply on every run, enabled or not; the nightly per-site reapers additionally demand ghost state across consecutive nights before acting.
The reaper family
| Check | Ghost definition | Cadence | Flag | Destination when enabled |
|---|---|---|---|---|
| Ghost codebases | tree under distro/ with a vendor/ dir but no detectable docroot |
nightly, once globally | _GHOST_CODEBASES_CLEANUP |
/var/backups/ghost-codebases-cleanup/<original path>/<timestamp>/ |
| Shared codebases | legacy D6/D7 shared codebase under /data/all that no platform symlinks to |
nightly, once globally | _SHARED_CODEBASES_CLEANUP |
/var/backups/codebases-cleanup/… — or /data/disk/codebases-cleanup/… when /data/all is a symlink to attached storage |
| Ghost / empty platforms | platform alias (and platform dir) whose root has no detectable docroot | nightly, per account + Hostmaster | _GHOST_PLATFORMS_CLEANUP |
the account's undo/ dir; /var/aegir/undo/ for Hostmaster |
| Ghost vhosts | nginx vhost with no matching site Drush alias | nightly, per account | _GHOST_VHOSTS_CLEANUP |
the account's undo/ dir |
| Ghost sites | site registration (alias + vhost) whose site dir has no drushrc.php |
nightly, per account | _GHOST_SITES_CLEANUP |
the account's undo/ dir |
| Ghost site files | that ghost site's leftover files directory — data, not registration | with the previous check | _GHOST_SITE_FILES_CLEANUP |
undo/ghost-site-<domain> |
| Ghost ltd aliases | site alias whose site dir has no drushrc.php, caught by the ltd-shell (FTPS) alias mirror pass |
every 3 min, manage_ltd_users.sh |
_GHOST_ALIASES_CLEANUP |
the account's undo/ dir; the mirrored ~/.ftp/.drush copy is deleted |
The six nightly checks belong to the owl run (04:15 by default): the per-account workers
handle platforms, vhosts, and sites for their account; after all accounts finish, the two
codebase checks and the Hostmaster platform sweep run once globally. The ltd-alias reaper
lives in manage_ltd_users.sh (see
lshell + manage_ltd_users) because that script
already owns the tenant alias copies it inspects.
Version-agnostic detection
Whether a tree is "real" is judged by _detect_real_docroot (night/night.inc.sh): an
index.php at the platform root or under docroot/, html/, or web/. That is the
front controller present in every Drupal docroot from D6 to D11, regardless of build
recipe, so Composer-based D8+ platforms — whose web root differs from the application
root and which no longer ship sites/all — are never misread as ghosts. Ghost detection
never keys on sites/all. The -f test follows symlinks and is false for a dangling
one, so a decoy like web -> /etc cannot register as a docroot.
For the marker-gated platform reapers a vendor/ tree alone also counts as live: a
codebase mid-composer-build has no docroot yet, but it is not a ghost.
Always-on safeguards
These apply before any move, regardless of flags:
- Provision interlock. While any Aegir/Provision task runs (install, clone, migrate,
verify, backup, restore) the site and platform trees are transiently inconsistent —
files mid-repoint, aliases mid-rewrite, vhosts staged under a leading dot. Every
cleanup checks
pgrep -f provisionfirst (_provision_running) and skips entirely, logging a notice, when one is found. It retries on the next pass once the box is quiet. - Consecutive-night markers. The nightly site, vhost, and platform reapers never
act on a single snapshot:
_ghost_seen_enoughrequires the item to look ghost on 2 consecutive nightly runs, tracked in counter files under<account>/log/ctrl/(ghost-*.seen). The moment an item looks valid the counter is reset (_ghost_seen_reset), so a recovered item never carries a stale count into a later reap. A transient state — a momentarily-unmounted store, a symlink mid-repoint — gets at least one grace run to recover. - Symlink-aware liveness. A site is judged live on
drushrc.phpalone, present as a file or a symlink.files/andprivate/are deliberately not required: under native files-symlinking those point into a static store whose target can be transiently absent, and that must never count as gone. - Fail-closed parsing. A degraded or mid-rewrite alias that parses to an empty or
non-
/data/disksite_pathkeeps the site and resets its marker — a bad parse never reaps. - Freshness and staging skips. Vhosts younger than 24 h (mid-import/activation) reset
their marker; leading-dot companion vhosts (
.example.com— staged or rollback originals fromxoct/xcopy) are always skipped; the vhost sweep skips the whole account whilelog/exported.pidmarks a migrate/export in flight; the ltd reaper skips aliases written under 60 minutes ago and re-checks the Provision interlock inline on its 3-minute cadence. - No instant re-bake. On any
manage_ltd_users.shpass that reaped, the tenant Drush yml site-cache rebuild is skipped, so a deregistration is never immediately baked into the tenant's Drush state.
One platform check predates the markers: the legacy empty-platform sweep in the
per-account worker (and its Hostmaster twin) carries no consecutive-night marker — it is
age-gated instead, examining only platform_* alias files older than
_DEL_OLD_EMPTY_PLATFORMS days (see the
octopus.cnf reference). Only its
ghost-alias branch belongs to the flag family above; the sweep's original job — deleting
an old legacy (sites/all) platform no site uses, through a Hostmaster task — is that
variable's own feature, not gated by any _GHOST_* flag. The marker-gated platform
reapers run independently of the age gate.
Control flags
| Flag | Settable in |
|---|---|
_SHARED_CODEBASES_CLEANUP |
/root/.barracuda.cnf (system-wide) |
_GHOST_CODEBASES_CLEANUP |
/root/.barracuda.cnf (system-wide) |
_GHOST_PLATFORMS_CLEANUP |
/root/.barracuda.cnf and per account in /root/.<account>.octopus.cnf |
_GHOST_VHOSTS_CLEANUP |
both, as above |
_GHOST_SITES_CLEANUP |
both, as above |
_GHOST_SITE_FILES_CLEANUP |
both, as above |
_GHOST_ALIASES_CLEANUP |
both, as above |
All default NO. The two codebase checks walk the whole /data tree, so their flags are
system-wide only. Semantics for the dual-scope flags: YES in either file enables
the move for that account — the per-account file lets you opt in one account at a time,
but a per-account NO cannot veto a system-wide YES (only YES enables, anything else
is ignored). The Hostmaster platform sweep reads /root/.barracuda.cnf only.
Flags are read at act time by grepping the control file (case-insensitive YES; a
missing file or unset variable reads as disabled) — no reload or restart is involved, and
the next nightly pass simply picks up the current value. BOA is designed to seed the NO
defaults into the live control files on install and to append any still-absent flag on
the next barracuda up-* / octopus up-* upgrade, never overwriting a value you have set; a box whose
control files predate the flags behaves identically, since unset reads as disabled.
Reading the dry-run output
Where each check logs:
| Source | Log |
|---|---|
| Per-account night worker (platforms, vhosts, sites) | /var/log/boa/daily/acct-<account>-<timestamp>.log |
| Global post-account pass (codebases, Hostmaster platforms) | /var/log/boa/daily/daily-<timestamp>.log |
manage_ltd_users.sh (ltd aliases) |
/var/backups/ltd/log/users-<timestamp>.log |
Typical dry-run lines, verbatim:
GHOST platform /data/disk/o1/static/foo detected (dry-run; set _GHOST_PLATFORMS_CLEANUP=YES in /root/.o1.octopus.cnf or /root/.barracuda.cnf to move)
Ghost /data/disk/o1/distro/002/foo detected (dry-run; set _GHOST_CODEBASES_CLEANUP=YES in /root/.barracuda.cnf to move)
Unused /data/all/001/foo detected (dry-run; set _SHARED_CODEBASES_CLEANUP=YES in /root/.barracuda.cnf to move)
GHOST vhost for foo.example.com with no drushrc detected (dry-run/grace; set _GHOST_VHOSTS_CLEANUP=YES to move)
GHOST drushrc for foo.example.com detected (grace run, not moved)
GHOST foo.example.com.alias detected (dry-run; set _GHOST_ALIASES_CLEANUP=YES to move)
(grace run, not moved) means the consecutive-night marker is still counting — the item
looked ghost tonight but has not yet been ghost long enough to act on even with the flag
enabled. (dry-run/grace) covers both states in one line: flag off, or marker not yet
satisfied.
Recovering a moved item
Nothing is deleted, so recovery is moving the item back from its destination:
- ghost codebases — mirrored under
/var/backups/ghost-codebases-cleanup/at their original path, in a<timestamp>/subdirectory per move; - shared codebases —
/var/backups/codebases-cleanup/…(or/data/disk/codebases-cleanup/…on symlinked/data/all); - platform aliases, platform dirs, vhosts, site aliases — the owning account's
undo/dir (/var/aegir/undo/for Hostmaster); a reaped site's vhost lands asundo/ghost-vhost-<domain>and its files dir asundo/ghost-site-<domain>. The~/.ftp/.drushmirror copy the ltd reaper deletes is derived data: restore the master alias and a live site's copy is re-created on the next 3-minute pass.
One retention caveat: the nightly run prunes the shared-codebase backup dirs after
7 days. Review what landed there within that window; the ghost-codebase mirror and the
undo/ dirs are not aged out by the night family.
Enabling on production
The codebase and platform checks are the older, long-exercised pair. The per-site
reapers (_GHOST_VHOSTS_CLEANUP, _GHOST_SITES_CLEANUP, _GHOST_SITE_FILES_CLEANUP,
_GHOST_ALIASES_CLEANUP) act on live serving artifacts and are newer — exercise them in
dry-run, then on a disposable VM, before enabling on production. A sane rollout:
- Watch the dry-run output across at least two nights (the markers need two consecutive runs anyway) and confirm every listed path really is a ghost.
- Enable per account in that account's
octopus.cnffirst; go system-wide inbarracuda.cnfonly once per-account runs are clean. - Leave
_GHOST_SITE_FILES_CLEANUPfor last — it is split out precisely because it moves site data, not just registrations.
Related
- Nightly owl maintenance — the 04:15 run that hosts the six nightly checks: per-account fan-out, load gating, and the global post-account pass.
- lshell + manage_ltd_users — the orchestrator
that owns the tenant alias copies and carries the
_GHOST_ALIASES_CLEANUPreaper. - barracuda.cnf and
octopus.cnf — the control files the flags
live in, and
_DEL_OLD_EMPTY_PLATFORMS. - Reference appendix — the catalogued
_GHOST_*/_SHARED_CODEBASES_CLEANUPvariables.