|
|
@@ -0,0 +1,119 @@
|
|
|
+<?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;
|
|
|
+}
|