Process guards & auth scanners
second.sh is one of the two per-minute monitor launchers (the other is minute.sh). Cron
starts it once a minute, but it self-loops ten times with sleep 5 to give roughly
5-second monitoring granularity across the whole minute. On each loop it samples system load
and reacts to it (the load auto-pause logic); on the heavy loops it
also fans out a set of short-lived service guards and security scanners. This page documents
that heavy fan-out — the service guard set spawned by _proc_control, the three auth /
shell-escape scanners, and the separate Aegir task-queue runner (runner.sh).
Source: aegir/tools/system/second.sh, aegir/tools/system/runner.sh, and the individual
guards under aegir/tools/system/monitor/check/. On a running box the launchers live at
/var/xdrago/second.sh etc., and the guards at /var/xdrago/monitor/check/.
Where this sits
cron (every minute)
│
├─ second.sh ── 10× loop, sleep 5 ──┐
│ ├─ _load_control (every loop, cheap — see Load control)
│ └─ HEAVY pass (every Nth loop):
│ ├─ _proc_control ── fan out service guards (monitor/check/*.sh)
│ ├─ hackcheck.sh ┐
│ ├─ hackftp.sh │ auth / shell-escape scanners
│ └─ escapecheck.sh ┘
│
├─ minute.sh ── auto-healing watchdogs (system, mysql, php, nginx,
│ nginx_guard → scan_nginx, …) [Auto-healing page]
│
└─ runner.sh ── Aegir task-queue runner (load-gated, separate)
The nginx Abuse Guard (scan_nginx.sh) is the security-facing member of this same
/var/xdrago/monitor/ machinery, but it is not launched from here. minute.sh spawns
nginx_guard.sh, which in turn runs scan_nginx.sh; that whole chain is on the minute.sh
side. It is documented in depth in the Abuse Guard topic and is
not duplicated on this page.
Why this matters for idle load. On a NORMAL box the heavy pass runs every loop, so
_proc_controliterates its 12 service guards (plus the three scanners) roughly every 5 s, each spawned child re-sourcing/root/.barracuda.cnfand runningpgrep. Combined with theminute.shauto-healing fan-out, that spawn churn is the dominant idle-load source on small boxes (load 3–4 on a 2 CPU / 4 GB box with no traffic). The box-class throttle exists to tame exactly this; see Cron cadence & idle-load throttle.
The heavy-pass cadence
second.sh classifies the box once at startup (_monitor_box_class) into CI, SLOW, or
NORMAL, and from that derives how often the heavy work runs (_HEAVY_EVERY):
| Box class | Signal (in precedence order) | _HEAVY_EVERY |
Heavy pass runs |
|---|---|---|---|
CI |
/etc/boa/.look.like.jenkins.cnf present |
10 | once per minute (loop 1 only) |
SLOW |
(/root/.slow.cron.cnf present or total RAM ≤ 4096 MB) and no /root/.force.queue.runner.cnf |
4 | every 4th loop (loops 1, 5, 9) |
NORMAL |
default, or /root/.force.queue.runner.cnf over-riding a small box |
1 | every loop (every ~5 s) |
.fast.cron.cnf is not consulted by _monitor_box_class (it only affects runner.sh's
queue-drain cadence); only .force.queue.runner.cnf over-rides the slow/RAM heuristic to force
NORMAL. This matches minute.sh exactly — both loops share an identical classifier.
The cheap part — _load_control, the load sampling and auto-pause — runs on every loop
regardless of class, so responsiveness to a load spike is unchanged. Only the expensive
fan-out (_proc_control plus the three scanners) is gated by _HEAVY_EVERY. The cadence is
overridable in /root/.barracuda.cnf via _MONITOR_HEAVY_EVERY (a non-numeric or empty
value is ignored and the class default stands; otherwise the value replaces the default and
is floored at 1). On NORMAL this is the historical every-pass behaviour, unchanged.
_proc_control — service guards
_proc_control is the successor to the legacy proc_num_ctrl.pl. Rather than one monolithic
Perl service monitor, it has been split into individual single-shot guards under
monitor/check/, and _proc_control simply fans them out, each detached via
_spawn_detached (nohup/setsid):
for _w in sendmail_guard convert_guard hostname_sync syslog_legacy \
bind9 proxysql droplet newrelic_daemon newrelic_sysmond \
collectd xinetd lsyncd; do
[ -e "${_monPath}/${_w}.sh" ] && _spawn_detached "bash ${_monPath}/${_w}.sh"
done
Each guard is presence-gated: a guard whose .sh file is not present (not fetched on
this box, or deliberately removed) is simply skipped. _proc_control itself is skipped on a
heavy pass when load limits were already exceeded (_skip_proc_control, set by
_load_control on a MAX/CRIT trip), since the auto-pause is already shedding work. Note that
the three scanners below are launched outside that gate, so they still run on every heavy
pass even when _proc_control is being skipped. After fanning out, _proc_control touches
/var/log/boa/proc_num_ctrl.done.pid as a heartbeat.
Every guard follows the same shape: _check_root, then _manage_single_lock (the shared
lock.inc single-instance lock with a legacy pgrep -fc … > 2 fallback — see
re-entrancy), then one short action, then exit. They are intentionally
single-shot: the every-~5 s cadence comes from second.sh, not from any internal loop.
The guards spawned, and what each one watches:
| Guard | Watches / acts on |
|---|---|
sendmail_guard |
Kills any root-owned process whose executable name contains sendmail. BOA delivers mail via postfix/msmtp, so a root sendmail MTA should never run. Excludes itself and its shell. |
convert_guard |
Watches runaway ImageMagick convert. Only acts when more than one is running; a hot one (>10% CPU, accumulated CPU time, R/Z state) is logged to convert.watch.log, or killed when more than 5 are running and it exceeds 50% CPU (logged to convert.kill.log). |
hostname_sync |
On DHCP-managed hosts (dhcpcd/dhclient running), restores the running hostname from a non-empty /etc/hostname after a lease renewal reset it. |
syslog_legacy |
Restarts a legacy syslog daemon (sysklogd or inetutils-syslogd) when it is down and its init script is present. rsyslog — the BOA default — is not handled here (it is watched by system.sh). Skipped while a DHCP client is active, so it never collides with hostname_sync. |
bind9 |
Restarts bind9 (named) when the daemon is down and /etc/init.d/bind9 exists. |
proxysql |
Restarts proxysql when the daemon is down and /etc/init.d/proxysql exists. |
droplet |
Restarts the DigitalOcean droplet-agent (serial/web-console access on DO VMs) when it is down or its pidfile is missing and /etc/init.d/droplet-agent exists. |
newrelic_daemon |
Restarts the New Relic APM newrelic-daemon when it is down and /etc/init.d/newrelic-daemon exists. |
newrelic_sysmond |
New Relic server monitor (nrsysmond), gated by the opt-in flag /etc/boa/.enable.newrelic.sysmond.cnf: restarts it when the flag is present and the daemon is down, stops it when the flag is absent and it is running. |
collectd |
Starts collectd when it is down and /etc/init.d/collectd exists. |
xinetd |
Starts xinetd when it is down and /etc/init.d/xinetd exists. |
lsyncd |
Starts lsyncd when it is down and /etc/init.d/lsyncd exists. |
All twelve rows are characterised from the source under monitor/check/. The nine daemon
watchdogs (from syslog_legacy down) share a common shape: they act only when the service's
init script is present, so on a box where the daemon is not deployed the watchdog is a no-op
(and where the guard .sh file itself was never fetched, the _proc_control loop skips it
entirely). The eight pure service-recovery guards (bind9 through lsyncd) additionally
bail out early while a BOA upgrade/maintenance run is in progress (_run_to_active: any of
the /root/.run-to-*.cnf markers or /run/boa_run.pid), so service recovery never fights an
in-flight upgrade. (syslog_legacy, hostname_sync, sendmail_guard and convert_guard do
not carry that guard.)
Auth and shell-escape scanners
On every heavy pass, second.sh also launches three log-scanning monitors directly, each
detached with nohup. Unlike the nginx Abuse Guard these are single-pattern auth and
shell-escape scanners — two ban via CSF, one e-mails an alert:
| Scanner | Scans | Looks for | Action |
|---|---|---|---|
hackcheck.sh |
/var/log/auth.log (incremental, byte-offset) |
SSH auth abuse — failed-password / invalid-user / preauth disconnect / reset / timeout / malformed-banner probes from a non-local IPv4 | csf -td <ip> 900 -p 22 (15-min temp ban) |
hackftp.sh |
/var/log/messages (incremental, byte-offset) |
FTP (proftpd) auth abuse — Authentication failed for user and cleartext sessions not accepted, extracting user@IP |
csf -td <ip> 3600 -p 21 (1-hour temp ban) |
escapecheck.sh |
/var/log/lsh/*.log |
lsh restricted-shell escape attempts in the current or previous minute |
Email alert to _MY_EMAIL via s-nail (no ban) |
Shared design notes, verified against the three scripts:
- Cron-race window. All three accept both the current and previous minute so an event logged late in one minute is not missed when the scan runs at the top of the next.
- Dual timestamp formats.
hackcheck.shandhackftp.shparse both classic syslog and ISO 8601 (Debian 12+ rsyslog) timestamps;escapecheck.shuses lsh's single fixedYYYY-MM-DD HH:MM:SS,mmmformat. - Incremental reads.
hackcheck.shandhackftp.shtrack a byte offset (/var/log/scan_hackcheck_lastpos,/var/log/scan_hackftp_lastpos) and read only bytes appended since the previous run, resetting on rotation/truncation — the same approach asscan_nginx.sh. noclobberlock. Each takes a PID-checkednoclobberlock (/var/run/{hackcheck,hackftp,escapecheck}.lock) so overlapping cron runs cannot stack.- Allow-list / maintenance safety.
hackcheck.shnever bans an IP present in/etc/csf/csf.allowor/etc/csf/csf.ignore, never bans an IP that had anAcceptedlogin in the window, and — likehackftp.sh— suppresses direct CSF bans while the/var/xdrago/guest-fire.shmaintenance flag is present (enforcement is then handled byguest-fire.sh). Bans are recycled after their TTL (_BAN_SECONDS, 900 s SSH / 3600 s FTP) so an expired IP can be re-armed.
runner.sh — the Aegir task-queue runner
runner.sh is a separate cron job (its own per-minute line), not part of the
second.sh/minute.sh fan-out. It is what actually drains the Aegir verify/migrate/backup
task queue by executing the /var/xdrago/run-* runners. It is heavily gated so it never adds
load on a box that should stay quiet:
- Hard stops first. It exits immediately if
/root/.proxy.cnf,/etc/boa/.pause_tasks_maint.cnf, or amax_load/critical_loadpid is present, and again if too manyrunner.shinstances are already running, or a SQL backup, the nightlyowl.shmaintenance run, a MySQL restart/cluster-backup, orboa_cron_wait.pidis in flight. That second deferral touches a/var/log/boa/wait-runner.pidmarker and logs "Another BOA task is running, we will try again later..." before exiting. - Load-gated per runner.
_runner_actionruns a/var/xdrago/run-*runner only while the 1-minute per-CPU load is *below `_CPU_TASK_RATIO 100** (default_CPU_TASK_RATIO=3.1` → 310%); above that it waits. This is the same task ratio used by the load-control logic — backend tasks are skipped under load while the web tier stays up. - CI hosts (
.look.like.jenkins.cnf). On a CI instance there is no automatic queue by default. It runs only if the box is a PRO plan (POWER/PHANTOM/CLUSTER/ULTRA/MONSTERin the octopus control file) or/etc/boa/.allow.aegir.queue.cnfis present, and at least onerun-aegir-queue.infoexists. - Small boxes auto-throttle.
runner.shitself writes/root/.slow.cron.cnfand pins it immutable withchattr +iwhen total RAM ≤ 4096 MB. With.slow.cron.cnfpresent (and no.force.queue.runner.cnf) it allows only one concurrent runner and runs a single throttled pass per minute withsleep 15pads. - Fast / forced. With
/root/.fast.cron.cnfor/root/.force.queue.runner.cnfit runs the queue 10 times in the minute (sleep 5between), mirroring thesecond.shcadence.
runner.sh
│ hard-stop flags? (.proxy / pause_tasks / max|critical load / backup running) ── exit
▼
classify cadence by control flag
├─ .look.like.jenkins.cnf → queue only if PRO plan / .allow.aegir.queue.cnf
├─ .slow.cron.cnf → 1 throttled pass (RAM ≤ 4 GB auto-sets this flag)
├─ .fast/.force.queue → 10 passes, sleep 5
└─ default → single pass
▼
_runner_action ── for each /var/xdrago/run-* : load < _CPU_TASK_RATIO*100 ? run : wait
Disabling the queue does not lower idle load. Marking a box CI with
.look.like.jenkins.cnfstopsrunner.shdraining the Aegir queue, but it does not by itself remove the idle CPU cost — that comes from thesecond.sh/minute.shmonitor fan-out, which is a separate set of cron jobs..look.like.jenkins.cnfdoes also pushsecond.sh/minute.shinto theCIbox-class (heavy pass once per minute), but the cheap per-loop sampling still runs. To understand and tune the residual idle load, see Cron cadence & idle-load throttle.
Re-entrancy
Both the launcher (second.sh) and every individual guard protect against overlapping runs
with the same _manage_single_lock pattern:
source lock.inc (/opt/local/bin/lock.inc or /opt/local/lib/lock.inc)
├─ shared single-instance lock available? → _single_instance_lock
└─ else legacy fallback: pgrep -fc <script> > 2 → log to too.many.log, exit
The shared lock.inc lock is preferred; the legacy pgrep -fc fallback (exit if more than
two instances are already running, logging to /var/log/boa/too.many.log) is kept for boxes
where lock.inc is not yet deployed. The security scanners use their own PID-checked
noclobber lock instead, but to the same end.
Profiler
A small /proc profiler, loadreport (aegir/tools/bin/loadreport, deployed to
/opt/local/bin/loadreport), measures which of these scripts is actually consuming CPU/IO
during an idle-load investigation. Cron runs it every 30 minutes with --log under
nice -n10 ionice -c3, without itself adding measurable load. It is read-only; it never kills
or bans. See loadreport.
Related
- Load control & auto-pause — the cheap per-loop half of
second.sh: load sampling, spider protection, web auto-pause, and the_skip_proc_controlgate that governs this page's heavy pass. - Cron cadence & idle-load throttle — the box-class fan-out
throttle, the
_MONITOR_*override variables, and the idle-load model. - Service auto-healing watchdogs — the
minute.shside of the same machinery: the per-service watchdogs andnginx_guard.sh→scan_nginx.sh. - Abuse Guard —
scan_nginx.sh, the security-facing log scorer, launched on theminute.shside of this machinery (vianginx_guard.sh). - Reference appendix —
_CPU_TASK_RATIO,_MONITOR_HEAVY_EVERYand the other override variables referenced above.