manage-worker.php 1.7 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152
  1. <?php
  2. /**
  3. * Background worker for the scheduled backup and heartbeat.
  4. *
  5. * Same shape and same trust model as worker.php: the caller is this server
  6. * making an HTTP request to itself, so there is no admin session to check and
  7. * the key in data/worker-key.json authenticates instead. A wrong or missing key
  8. * is indistinguishable from the script not existing.
  9. *
  10. * Reached in three ways, all of which run the same code:
  11. *
  12. * - an admin page load past the interval (manage_kick(), app/manage.php),
  13. * - real cron, if the host has it: curl this URL every 15 minutes,
  14. * - by hand, when testing.
  15. *
  16. * What it never does is update the software. That is a deliberate act, from
  17. * Admin -> Maintenance.
  18. */
  19. require __DIR__ . '/app/bootstrap.php';
  20. if (!hash_equals(archive_worker_key(), (string)($_GET['key'] ?? ''))) {
  21. http_response_code(404);
  22. exit;
  23. }
  24. // The dispatcher hung up after a fraction of a second. Without this, PHP would
  25. // kill this process the moment it noticed the disconnect.
  26. ignore_user_abort(true);
  27. @set_time_limit(0);
  28. // Nothing is ever read from the response — the caller is not listening.
  29. http_response_code(204);
  30. if (function_exists('fastcgi_finish_request')) {
  31. @fastcgi_finish_request();
  32. }
  33. // One backup at a time. The client refuses concurrent runs itself, but with an
  34. // exception in the log; taking the lock here keeps that noise out and lets an
  35. // overlapping worker leave quietly.
  36. $lock = fopen(DATA_DIR . '/manage.lock', 'c');
  37. if ($lock === false || !flock($lock, LOCK_EX | LOCK_NB)) {
  38. exit;
  39. }
  40. $done = manage_run_due();
  41. flock($lock, LOCK_UN);
  42. fclose($lock);
  43. if ($done !== []) {
  44. manageClientLog('INFO', 'Scheduled run finished', ['jobs' => $done]);
  45. }