| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119 |
- <?php
- // Cron replacement: the periodic manage-client tasks, triggered by admin
- // activity instead of a scheduler.
- //
- // This deployment usually has no cron, so the two jobs that would otherwise run
- // from manage-client/bin/manage-client.php happen when an admin loads the
- // dashboard: the automatic backup once its interval has passed, and the status
- // report to the manage server once its own, much shorter interval has passed.
- //
- // Both are throttled and neither may break the page it runs on, so nothing in
- // here throws.
- require_once __DIR__ . "/../manage-client/lib/client.php";
- // How long an admin session may pass without the manage server hearing from
- // this instance. Short enough that the server dashboard stays meaningful,
- // long enough that it is not one request per admin page view.
- if (!defined("MANAGE_ACTIVITY_HEARTBEAT_INTERVAL")) {
- define("MANAGE_ACTIVITY_HEARTBEAT_INTERVAL", 3600);
- }
- function manageActivityStateFile(): string
- {
- // Next to the client's own log, in data/manage/ - gitignored and blocked
- // from the web by the .htaccess in the project root.
- return dirname((string) MANAGE_LOG_FILE) . "/activity.json";
- }
- function manageActivityReadState(): array
- {
- $file = manageActivityStateFile();
- if (!is_file($file) || !is_readable($file)) {
- return [];
- }
- $decoded = json_decode((string) file_get_contents($file), true);
- return is_array($decoded) ? $decoded : [];
- }
- function manageActivityWriteState(array $state): void
- {
- $file = manageActivityStateFile();
- $dir = dirname($file);
- if (!is_dir($dir) && !mkdir($dir, 02775, true) && !is_dir($dir)) {
- return;
- }
- @file_put_contents($file, json_encode($state, JSON_PRETTY_PRINT), LOCK_EX);
- }
- /**
- * Sends the status report if the interval has passed.
- *
- * The timestamp is written before the request, not after: a manage server that
- * hangs must not turn every following page load into another attempt.
- *
- * @return bool whether a heartbeat was attempted
- */
- function manageActivityHeartbeatIfDue(bool $force = false): bool
- {
- $interval = (int) MANAGE_ACTIVITY_HEARTBEAT_INTERVAL;
- if (!$force && $interval <= 0) {
- return false;
- }
- if (!manageClientConfigured()) {
- return false;
- }
- $state = manageActivityReadState();
- $last = (int) ($state["last_heartbeat_at"] ?? 0);
- if (!$force && $last > 0 && time() - $last < $interval) {
- return false;
- }
- $state["last_heartbeat_at"] = time();
- manageActivityWriteState($state);
- manageHeartbeatSendQuietly();
- return true;
- }
- /**
- * Runs the due jobs for one admin page view.
- *
- * @return array{message: string, type: string} empty message when nothing happened
- */
- function manageActivityRunDueTasks(): array
- {
- $result = ["message" => "", "type" => ""];
- $backup = null;
- try {
- $backup = manageBackupCreateAutomaticIfDue();
- if ($backup !== null) {
- $result = [
- "message" => "Automatisches Backup wurde erstellt: " . $backup["filename"] . ".",
- "type" => "success",
- ];
- }
- } catch (Throwable $exception) {
- $result = [
- "message" => "Automatisches Backup konnte nicht erstellt werden: " .
- $exception->getMessage(),
- "type" => "warning",
- ];
- }
- // A fresh backup is worth reporting right away, so the server dashboard
- // does not show a stale "last backup" for up to another interval.
- manageActivityHeartbeatIfDue($backup !== null);
- return $result;
- }
|