Nightly maintenance (owl.sh)
owl.sh is BOA's nightly maintenance run — the successor to the daily.sh monolith. The
rename came with a decomposition: /var/xdrago/owl.sh is now a thin orchestrator that
freezes the run context, walks the Octopus accounts, and dispatches the real work to
worker scripts under /var/xdrago/night/. Per-account work runs as a subprocess per
account with its own log file, and can optionally fan out in parallel. The default remains
serial and behaviour-equivalent to the old inline run.
This page covers the orchestrator: layout, gates, the run-context freeze, the per-account
subprocess model, parallelism, and the daily.sh transition. The ghost/empty cleanup family
that runs inside the same nightly window is the companion leaf
Ghost & empty cleanup; Let's Encrypt renewal and the per-account
failure reports the night workers assemble are covered in
SSL operations.
Layout: orchestrator + night/ workers
| File | Role |
|---|---|
/var/xdrago/owl.sh |
Orchestrator: root/disk checks, skip gates, lock, global pre-steps, run-freeze, account loop, global post-steps. Source: aegir/tools/system/owl.sh. |
/var/xdrago/night/night.inc.sh |
Shared helper library: pure helpers, load + chattr helpers, drush8 wrappers, and the run-freeze (night_emit_run_env / night_load_run_env). Sourced by the orchestrator and every worker. |
/var/xdrago/night/10-account.sh |
Per-account worker — the whole per-account sequence (drush prep, octopus.cnf email sync, hostmaster vSet block, the per-site loop, platform GC, hostmaster LE, goaccess, final chattr relock). One subprocess per account; the unit of parallelism. |
/var/xdrago/night/20-sites.sh |
Per-site (per-vhost) maintenance family plus the per-site loop driver _daily_process. Sourced by 10-account.sh, not run directly. |
/var/xdrago/night/90-global-post.sh |
Once-per-run global post-steps: shared-codebase + ghost cleanup, empty-hostmaster-platform removal, weblog teardown, incident detection, Nginx forward-secrecy/DH-param refresh with a single reload, /data permission sweep, backup pruning, log archiving. Touches shared resources, so it must never run inside the per-account fan-out. |
owl.sh aborts with FATAL ERROR if night.inc.sh or 90-global-post.sh cannot be
sourced — it never runs half-defined. All five files are serial-gated _fetch_versioned
entries in BOA.sh.txt, so every box re-fetches the current copy automatically whenever one
changes; you do not pull updates by hand.
Two legacy names are kept by design: the run lock is still /run/daily-fix.pid, and the log
directory is still /var/log/boa/daily/.
Launch, gates and lock
The root crontab launches the run at 04:15, throttled below interactive work
(aegir/tools/system/cron/crontabs/root):
15 4 * * * /usr/bin/nice -n5 /usr/bin/ionice -c2 -n7 bash /var/xdrago/owl.sh >/dev/null 2>&1
Before doing anything, owl.sh:
- exits 1 if the root filesystem is over 90% full (
ERROR: Your disk space is almost full) — nightly maintenance does not run on a nearly-full disk; - exits 0 immediately if
/root/.proxy.cnfor/root/.pause_heavy_tasks_maint.cnfexists — proxy-only nodes and paused boxes run no nightly maintenance at all; - takes the run lock
/run/daily-fix.pid. If the lock already exists, it touches/var/log/boa/wait-for-dailyand exits 1; the lock is removed at the very end of the run, after the global post-steps.
To disable the nightly run on a box, the toggle is /var/xdrago/.owl.sh.off — xoct and
xcopy park the script there (mv owl.sh .owl.sh.off + pkill) during operations that must
not collide with nightly maintenance, and restore it afterwards. xmass does not manage this
toggle. A legacy .daily.sh.off left by the pre-rename tooling is carried over to
.owl.sh.off during self-update (see the transition section below).
Run order
Inside the lock, one run proceeds as:
- Global pre-steps — a once-per-upgrade permissions sweep over
distro/*/*/sites/all/{libraries,modules,themes}(stampedpermissions-fix-post-up-<serial>.infoso it reruns only after a BOA upgrade), then hostmaster housekeeping as theaegiruser:drush8 cc drush, disablingupdate/syslog/dblogon@hostmaster, hostmaster cron, two cache-clears, andutf8mb4-convert-databases. _daily_action— logged to/var/log/boa/daily/daily-<NOW>.log: weblogx preparation (when goaccess is enabled), the run-freeze, the per-account loop (below), and the global cleanup family from90-global-post.sh.- Post steps — incident detection, the Nginx forward-secrecy/DH-param refresh (single
service nginx reload), global cleanup, log archiving, lock release, and a finalINFO: Daily maintenance complete.
The run-context freeze: /run/night/run.env
Because each account now runs in its own subprocess, the run context must be inherited, not
re-derived. After the global pre-steps and before the account loop, the orchestrator calls
night_emit_run_env (night.inc.sh), which writes the exact per-run state as export
lines to /run/night/run.env, chmod 0600:
_NOW— the run keystone,date +%y%m%d-%H%M%S. It names every*-${_NOW}.infoidempotency guard and every per-account log file, so a worker must see the orchestrator's value, never compute its own;_DOW,_xSrl(the BOA serial),_FORCE_SITES_VERIFY,_CTRL_TPL_FORCE_UPDATE;- the module lists:
_O_CONTRIB*per core version and the_MODULES_ON_*/_MODULES_OFF_*/_MODULES_FORCEsets; - the cnf flags a worker acts on:
_APT_UPDATE,_PERMISSIONS_FIX,_MODULES_FIX,_CLEAR_BOOST,_ENABLE_GOACCESS,_hostedSys; - the reporting identity:
_ADMIN_EMAIL(from_MY_EMAIL),_INCIDENT_REPORT,_LE_CLIENT_NOTIFY,_GHOST_CLIENT_NOTIFY.
A worker loads context with night_load_run_env: it sources /root/.barracuda.cnf first
(for cnf flags and anything not in the freeze), then /run/night/run.env — the freeze is
authoritative for the frozen per-run set.
Per-account subprocess model
The orchestrator walks /data/disk/ and processes each eligible account — one that has
config/server_master/nginx/vhost.d and neither log/proxied.pid nor log/CANCELLED — by
running:
bash /var/xdrago/night/10-account.sh <account> >> /var/log/boa/daily/acct-<user>-<NOW>.log 2>&1
The log path comes from _acct_night_log (night.inc.sh) — derived identically on both
sides from the frozen _NOW plus the account basename, so the orchestrator that redirects
and the worker that later reads its own log back (to build the Let's Encrypt incident
report and the ghost-site client notice) always agree on the
file name. The orchestrator's own log for the run is
/var/log/boa/daily/daily-<NOW>.log.
Before each account, the orchestrator re-samples CPU count and load (_count_cpu +
_load_control); an account is only launched below the load ceiling
(_O_LOAD_MAX, derived from _CPU_TASK_RATIO — see Load control).
/var/log/boa/daily/ stays bounded without deleting anything: at the end of each run,
_archive_old_daily_logs (90-global-post.sh) moves top-level *.log files whose mtime
month differs from the current month into MM-YYYY/ subdirectories. The current run's
daily-*.log and acct-*.log stay in place, already-archived files are never re-scanned,
and nothing is ever deleted — the logs remain available for incident analysis.
Optional parallel fan-out
Two /root/.barracuda.cnf variables control per-account parallelism
(lib/settings/barracuda.sh.cnf):
| Variable | Default | Meaning |
|---|---|---|
_NIGHT_PARALLEL |
NO |
NO processes accounts one at a time — serial, behaviour-equivalent to the former inline call. YES fans accounts out concurrently. |
_NIGHT_MAX_PARALLEL |
empty | Max concurrent accounts when parallel. Empty defaults to the CPU core count; sanitised to a number, floored at 1. |
The admission gate differs between the modes in one operator-relevant way:
- Serial (default): an account whose turn arrives while load is over the ceiling is
logged (
...we have to wait...) and passed over for this run — the historicaldaily.shbehaviour. - Parallel: each launch waits in a 5-second retry loop until there is both a free
slot (fewer than
_NIGHT_MAX_PARALLELrunning workers) and load headroom, then fans out. Parallelism therefore does not raise the silent-skip rate — an overloaded moment delays an account instead of dropping it.
After the loop, a parallel run waits for all workers to join before the global post-steps
run — 90-global-post.sh touches shared resources (the master /var/aegir tree,
/data/all, /etc/ssl/private, one Nginx reload) and must run exactly once, after all
per-account work has finished.
Task-queue interplay
The Ægir task-queue runner defers to the nightly run: runner.sh refuses to drain the
queue while owl.sh is running (pgrep -fc owl.sh) or while a SQL dump is in flight
(mysql_backup.sh, mysql_cluster_backup.sh, or any mydumper process). It touches
/var/log/boa/wait-runner.pid, logs Another BOA task is running, we will try again later..., and exits — the queue resumes on a later tick. See
Tasks & queue for the runner's full back-off list.
Transition from daily.sh
The rename is designed so that no box loses its nightly run mid-transition
(BOA.sh.txt):
- The self-update path installs
owl.shand the fournight/workers via_fetch_versioned. - A leftover
/var/xdrago/.daily.sh.off(a box an operator orxoct/xcopyhad disabled under the old name) is converted:owl.shis parked as.owl.sh.offand the legacy marker removed — a disabled box stays disabled. - Legacy
/var/xdrago/daily.shand its ctrl stamps are deleted only once the live/var/spool/cron/crontabs/rootno longer invokesdaily.sh— i.e. after a full system upgrade has re-rendered the crontab to callowl.sh. Until then the deployed crontab still callsdaily.sh, so removing it early would silently stop nightly maintenance; gating on the live crontab keeps the retirement self-correcting and idempotent. Whether a given box has already crossed that gate is per-box state — check its live root crontab.
Related
- Ghost & empty cleanup — the opt-in, dry-run-by-default cleanup
family (
_GHOST_*_CLEANUP,_SHARED_CODEBASES_CLEANUP) that runs inside this nightly window. - SSL operations — Let's Encrypt renewal and
the per-account failure reports assembled from the
acct-*.lognight logs. - Tasks & queue —
runner.shand its back-off whileowl.shor a SQL dump runs. - Load control —
_load_controland the_CPU_TASK_RATIOceiling that gates each account launch. - Host control files & INI —
/root/.proxy.cnfand/root/.pause_heavy_tasks_maint.cnf, the box-level skip gates. - Reference appendix —
_NIGHT_PARALLEL/_NIGHT_MAX_PARALLELand the other/root/.barracuda.cnfvariables.