# CLI Scripts

> Command-line scripts and maintenance

---

## Scripts

```bash
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 <a href="https://github.com/bschwede/wt-cronjob/tree/main/cli/tick.php" target="_blank" rel="noopener"><code>tick.php</code></a>
 script also supports the `-v` and `-vv` parameters, which provide more verbose console output.

## Acceptance test

```bash
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:** <a href="https://github.com/bschwede/wt-cronjob/tree/main/cli/_template-maintenance.php" target="_blank" rel="noopener"><code>_template-maintenance.php</code></a>
 — 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

<a href="https://github.com/bschwede/wt-cronjob/tree/main/cli/_template-maintenance.php" target="_blank" rel="noopener"><code>_template-maintenance.php</code></a>
 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
<?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);
}
```

<a href="https://github.com/bschwede/wt-cronjob/tree/main/cli/wrap.php" target="_blank" rel="noopener"><code>wrap.php</code></a>
 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.
