CLI Scripts

Command-line scripts and maintenance

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 as php modules_v4/<module>/cli/<script>.php
  • Start with require autoload.php and CliBootstrap::guard()
  • Idempotent and incremental (--limit, --since), batched
  • Exit code 0 on 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’s cli/, rename, and adapt
    • or if the module already depends on cronjob, this bootstrap can instead be delegated to the shared cli wrapper

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 answers 403 instead 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() on data/<module>/<name>.lock so 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:

  1. Payload modules_v4/<module>/cli/<name>.logic.php — the real logic.
  2. 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.

Last modified November 4, 2026: cross-references (60f426f)