cron.php 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110
  1. <?php
  2. /**
  3. * The one cron entry point. Optional, and off until cron.enabled is set.
  4. *
  5. * Installed as a single crontab line, either way round — every five minutes, as
  6. * a URL fetch or from the shell (see scripts/manage-client.cron for both):
  7. *
  8. * curl -s -m 300 "https://www.example.com/cron.php?key=YOUR_WORKER_KEY"
  9. * /usr/bin/php /path/to/cron.php --quiet
  10. *
  11. * It drains every outstanding background job — all due gallery archives, the
  12. * scheduled backup, the heartbeat — and the web-driven flow keeps running
  13. * alongside it untouched. The logic, and why that is safe, is in app/cron.php.
  14. *
  15. * Over HTTP the key in data/worker-key.json authenticates, exactly as it does
  16. * for worker.php and manage-worker.php: a wrong or missing key is
  17. * indistinguishable from the script not existing. From the shell there is no
  18. * request to authenticate — reaching the CLI already means shell access.
  19. *
  20. * Unlike worker.php, whose caller hangs up immediately, this caller is listening:
  21. * cron mails what a job writes. So the response is a plain-text summary, and
  22. * --quiet (the manage client's convention) keeps a healthy run silent while
  23. * errors still go to STDERR where cron will mail them.
  24. */
  25. require __DIR__ . '/app/bootstrap.php';
  26. $cli = PHP_SAPI === 'cli';
  27. $quiet = $cli && in_array('--quiet', $argv ?? [], true);
  28. if (!$cli) {
  29. if (!hash_equals(archive_worker_key(), (string)($_GET['key'] ?? ''))) {
  30. http_response_code(404);
  31. exit;
  32. }
  33. header('Content-Type: text/plain; charset=utf-8');
  34. header('Cache-Control: no-store');
  35. }
  36. /** Report a line the operator should see even under --quiet, and stop. */
  37. function cron_fail(string $message, bool $cli): never
  38. {
  39. if ($cli) {
  40. fwrite(STDERR, $message . "\n");
  41. exit(1);
  42. }
  43. echo $message, "\n";
  44. exit;
  45. }
  46. if (!cron_enabled()) {
  47. // A crontab line that can never do anything is worth one mail, not silence.
  48. cron_fail("cron is disabled: set 'cron' => ['enabled' => true] in config/config.php", $cli);
  49. }
  50. // Over HTTP the fetcher may hang up on its own timeout long before the work is
  51. // done; finish the slice in hand either way rather than leaving a half-written
  52. // buffer behind. set_time_limit is not honoured everywhere and nothing relies on
  53. // it — cron.max_seconds is what actually bounds the run.
  54. ignore_user_abort(true);
  55. @set_time_limit(0);
  56. // One invocation at a time. A gallery slower to archive than the cron interval
  57. // would otherwise pile invocations up behind itself.
  58. $lock = fopen(cron_lock_file(), 'c');
  59. if ($lock === false || !flock($lock, LOCK_EX | LOCK_NB)) {
  60. if (!$quiet) {
  61. echo "already running; nothing to do\n";
  62. }
  63. exit;
  64. }
  65. try {
  66. $run = cron_run_all();
  67. } finally {
  68. flock($lock, LOCK_UN);
  69. fclose($lock);
  70. }
  71. json_write(cron_state_file(), [
  72. 'last_run_at' => time(),
  73. 'duration' => round($run['duration'], 1),
  74. 'jobs' => $run['jobs'],
  75. 'remaining' => $run['remaining'],
  76. ]);
  77. if ($run['jobs'] !== [] && function_exists('manageClientLog')) {
  78. manageClientLog('INFO', 'Cron run finished', [
  79. 'jobs' => $run['jobs'],
  80. 'duration' => round($run['duration'], 1),
  81. 'remaining' => $run['remaining'],
  82. ]);
  83. }
  84. if ($quiet) {
  85. exit;
  86. }
  87. foreach ($run['jobs'] as $line) {
  88. echo $line, "\n";
  89. }
  90. if ($run['jobs'] === []) {
  91. echo "nothing to do\n";
  92. }
  93. if ($run['remaining'] > 0) {
  94. // Not handed to worker.php: the next tick picks it up, and the web flow is
  95. // still a second driver. A third path would add nothing.
  96. echo $run['remaining'], " gallery(s) still queued\n";
  97. }
  98. echo 'done in ', round($run['duration'], 1), "s\n";