CLI Scripts
Scripts
php modules_v4/cronjob/cli/tick.php # run all due jobs
php modules_v4/cronjob/cli/tick.php --dry-run # show what would run
php modules_v4/cronjob/cli/tick.php --job=<name> # run one specific job now
php modules_v4/cronjob/cli/tick.php --strict # exit 1 if any job failed
php modules_v4/cronjob/cli/tick.php --full-output # echo full job output (debugging!)
php modules_v4/cronjob/cli/watch.php # run the resident watch daemon
Exit codes: 0 = tick ran (job failures are recorded in the DB), 1 = environment problem or (with --strict) at least one job failed.
Log hygiene: stdout carries summary lines only. Full job output lives in the run history (admin-only). Never redirect --full-output into a world-readable location.
To help with troubleshooting, the tick.php
script also supports the -v and -vv parameters, which provide more verbose console output.
Acceptance test
php modules_v4/cronjob/cli/smoke-job.php # prints ok, exit 0
php modules_v4/cronjob/cli/smoke-job.php --sleep=10 # stays alive 10 s (timeout testing)
Create a job with command modules_v4/cronjob/cli/smoke-job.php, hit Run now, wait a minute — the history page should show a green ok row.
Conventions for job scripts
New job scripts in other modules must follow these conventions:
- Location
modules_v4/<module>/cli/, invoked asphp modules_v4/<module>/cli/<script>.php - Start with
require autoload.phpandCliBootstrap::guard() - Idempotent and incremental (
--limit,--since), batched - Exit code
0on success - No interactive input; everything via CLI options
- Template files are prefixed with
_(excluded from job discovery) - For your own cli scripts you can start with
- a Template:
_template-maintenance.php— copy into your module’scli/, rename, and adapt - or if the module already depends on cronjob, this bootstrap can instead be delegated to the shared cli wrapper
- a Template:
Writing maintenance scripts
Using the Template
_template-maintenance.php
is a copy-paste template for longer-running maintenance jobs.
The conventions:
- SAPI guard: every script starts with
CliBootstrap::guard()— the directory is URL-addressable, the guard answers403instead of running. - Bootstrap: use
CliBootstrap::boot()— the core CLI bootstrap sequence (app bootstrap, i18n, config, database) with a hard failure on missing config or DB connection. - Arguments: plain
$argv(--limit=N,--help). - Lock:
flock()ondata/<module>/<name>.lockso overlapping cron runs do not collide; a held lock is not an error (exit 0). - Idempotent batches: design the job so one run finishes in roughly 5–10 minutes and the next run continues where the last one stopped.
- Logging: plain stdout lines (cron mail / log file). Never print personal data — counters and XREFs, never names.
- Exit codes:
0= ok (incl. “nothing to do”, “lock held”),1= error.
Using the CLI wrapper
A job script that needs webtrees + the database can delegate bootstrap to the cronjob module’s shared wrapper:
- Payload
modules_v4/<module>/cli/<name>.logic.php— the real logic. - Stub
modules_v4/<module>/cli/<name>.php— what the job row points to:
<?php
declare(strict_types=1);
$wrapper = __DIR__ . '/../../cronjob/cli/wrap.php';
if (is_file($wrapper)) {
require $wrapper;
} else {
fwrite(STDERR, "cronjob module required for this job\n");
exit(1);
}
wrap.php
bootstraps webtrees + DB and then includes the payload (sibling file with .logic.php extension). The payload path is derived from the entry script, never taken from an argument.