| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110 |
- <?php
- /**
- * The one cron entry point. Optional, and off until cron.enabled is set.
- *
- * Installed as a single crontab line, either way round — every five minutes, as
- * a URL fetch or from the shell (see scripts/manage-client.cron for both):
- *
- * curl -s -m 300 "https://www.example.com/cron.php?key=YOUR_WORKER_KEY"
- * /usr/bin/php /path/to/cron.php --quiet
- *
- * It drains every outstanding background job — all due gallery archives, the
- * scheduled backup, the heartbeat — and the web-driven flow keeps running
- * alongside it untouched. The logic, and why that is safe, is in app/cron.php.
- *
- * Over HTTP the key in data/worker-key.json authenticates, exactly as it does
- * for worker.php and manage-worker.php: a wrong or missing key is
- * indistinguishable from the script not existing. From the shell there is no
- * request to authenticate — reaching the CLI already means shell access.
- *
- * Unlike worker.php, whose caller hangs up immediately, this caller is listening:
- * cron mails what a job writes. So the response is a plain-text summary, and
- * --quiet (the manage client's convention) keeps a healthy run silent while
- * errors still go to STDERR where cron will mail them.
- */
- require __DIR__ . '/app/bootstrap.php';
- $cli = PHP_SAPI === 'cli';
- $quiet = $cli && in_array('--quiet', $argv ?? [], true);
- if (!$cli) {
- if (!hash_equals(archive_worker_key(), (string)($_GET['key'] ?? ''))) {
- http_response_code(404);
- exit;
- }
- header('Content-Type: text/plain; charset=utf-8');
- header('Cache-Control: no-store');
- }
- /** Report a line the operator should see even under --quiet, and stop. */
- function cron_fail(string $message, bool $cli): never
- {
- if ($cli) {
- fwrite(STDERR, $message . "\n");
- exit(1);
- }
- echo $message, "\n";
- exit;
- }
- if (!cron_enabled()) {
- // A crontab line that can never do anything is worth one mail, not silence.
- cron_fail("cron is disabled: set 'cron' => ['enabled' => true] in config/config.php", $cli);
- }
- // Over HTTP the fetcher may hang up on its own timeout long before the work is
- // done; finish the slice in hand either way rather than leaving a half-written
- // buffer behind. set_time_limit is not honoured everywhere and nothing relies on
- // it — cron.max_seconds is what actually bounds the run.
- ignore_user_abort(true);
- @set_time_limit(0);
- // One invocation at a time. A gallery slower to archive than the cron interval
- // would otherwise pile invocations up behind itself.
- $lock = fopen(cron_lock_file(), 'c');
- if ($lock === false || !flock($lock, LOCK_EX | LOCK_NB)) {
- if (!$quiet) {
- echo "already running; nothing to do\n";
- }
- exit;
- }
- try {
- $run = cron_run_all();
- } finally {
- flock($lock, LOCK_UN);
- fclose($lock);
- }
- json_write(cron_state_file(), [
- 'last_run_at' => time(),
- 'duration' => round($run['duration'], 1),
- 'jobs' => $run['jobs'],
- 'remaining' => $run['remaining'],
- ]);
- if ($run['jobs'] !== [] && function_exists('manageClientLog')) {
- manageClientLog('INFO', 'Cron run finished', [
- 'jobs' => $run['jobs'],
- 'duration' => round($run['duration'], 1),
- 'remaining' => $run['remaining'],
- ]);
- }
- if ($quiet) {
- exit;
- }
- foreach ($run['jobs'] as $line) {
- echo $line, "\n";
- }
- if ($run['jobs'] === []) {
- echo "nothing to do\n";
- }
- if ($run['remaining'] > 0) {
- // Not handed to worker.php: the next tick picks it up, and the web flow is
- // still a second driver. A third path would add nothing.
- echo $run['remaining'], " gallery(s) still queued\n";
- }
- echo 'done in ', round($run['duration'], 1), "s\n";
|