Pārlūkot izejas kodu

configuring non-cron deployment, bump version to 1.4.0

Medowar 1 mēnesi atpakaļ
vecāks
revīzija
6ae3fb52ff
4 mainītis faili ar 162 papildinājumiem un 26 dzēšanām
  1. 7 19
      admin/index.php
  2. 35 6
      docs/UPDATE_AND_BACKUP.md
  3. 119 0
      includes/manage-activity.php
  4. 1 1
      includes/version.php

+ 7 - 19
admin/index.php

@@ -1,7 +1,7 @@
 <?php
 require_once __DIR__ . '/../config.php';
 require_once __DIR__ . '/../includes/functions.php';
-require_once __DIR__ . '/../manage-client/lib/client.php';
+require_once __DIR__ . '/../includes/manage-activity.php';
 
 if (empty($_SESSION['admin_logged_in'])) {
     header('Location: login.php');
@@ -9,24 +9,12 @@ if (empty($_SESSION['admin_logged_in'])) {
 }
 
 $pageTitle = 'Admin Dashboard';
-$backupAutoMessage = '';
-$backupAutoMessageType = '';
-
-try {
-    $backup = manageBackupCreateAutomaticIfDue();
-    if ($backup !== null) {
-        $backupAutoMessage =
-            'Automatisches Backup wurde erstellt: ' .
-            $backup['filename'] .
-            '.';
-        $backupAutoMessageType = 'success';
-    }
-} catch (Throwable $exception) {
-    $backupAutoMessage =
-        'Automatisches Backup konnte nicht erstellt werden: ' .
-        $exception->getMessage();
-    $backupAutoMessageType = 'warning';
-}
+
+// No cron on this deployment: the periodic manage-client jobs run here, on
+// admin activity. See includes/manage-activity.php.
+$manageActivity = manageActivityRunDueTasks();
+$backupAutoMessage = $manageActivity['message'];
+$backupAutoMessageType = $manageActivity['type'];
 $orders = getOrders();
 
 $backorderGroups = getBackorderGroups();

+ 35 - 6
docs/UPDATE_AND_BACKUP.md

@@ -25,11 +25,12 @@ Er ersetzt die früheren Komponenten `includes/backup.php`, `admin/updater.php`,
 | `manage-client/config.sample.php` | Vorlage dafür |
 | `admin/manage.php` | Adminseite „Update & Backup“ (prüft den Login, bindet dann das Panel ein) |
 | `includes/after-update.php` | Post-Update-Hook (`psaAfterUpdate`) |
+| `includes/manage-activity.php` | Cron-Ersatz: Backup und Statusmeldung durch Adminaktivität |
 | `migrations/` | einmalige Migrationsskripte, die mit einem Release ausgeliefert werden |
 | `scripts/create-release-zip.sh` | baut das Release-ZIP |
 
 Laufzeitdaten liegen unter `data/manage/` (`backups/`, `updates/`, `work/`,
-`migrations.json`, `manage-client.log`) und sind über die `.htaccess` im
+`migrations.json`, `manage-client.log`, `activity.json`) und sind über die `.htaccess` im
 Projektwurzelverzeichnis nicht über das Web erreichbar.
 
 ## Konfiguration (`manage-client/config.php`)
@@ -66,14 +67,42 @@ mehr.
   offene Migrationen und verlinkt auf die Vollansicht.
 - **Update & Backup** (`admin/manage.php`) bietet: Backup erstellen, Update
   ausrollen, Migrationen nachholen, Status melden, lokale Backups herunterladen.
-- Das Dashboard (`admin/index.php`) ruft `manageBackupCreateAutomaticIfDue()`
-  auf: ist seit dem letzten automatischen Backup das Intervall vergangen, wird
-  eines erstellt, sonst passiert nichts. Das ist der Ersatz für Cron auf
-  Hostings ohne Cron.
+- Das Dashboard (`admin/index.php`) stößt die periodischen Aufgaben an, siehe
+  unten.
+
+## Ohne Cron: Auslösung durch Adminaktivität
+
+Auf dieser Installation steht in der Regel kein Cron zur Verfügung. Die beiden
+Aufgaben, die sonst geplant laufen würden, hängen deshalb an der Adminaktivität:
+`admin/index.php` ruft beim Aufruf `manageActivityRunDueTasks()` aus
+[`includes/manage-activity.php`](../includes/manage-activity.php) auf. Der Login
+leitet immer auf das Dashboard, jede Adminsitzung kommt also dort vorbei.
+
+| Aufgabe | Intervall | Konstante |
+|---|---|---|
+| Automatisches Backup | wöchentlich | `MANAGE_BACKUP_AUTO_INTERVAL_SECONDS` |
+| Statusmeldung (Heartbeat) | stündlich | `MANAGE_ACTIVITY_HEARTBEAT_INTERVAL` |
+
+Ist das Intervall noch nicht abgelaufen, passiert nichts. Nach einem frisch
+erstellten Backup wird die Statusmeldung unabhängig vom Intervall gesendet,
+damit der Manage-Server nicht bis zu eine Stunde lang ein veraltetes „letztes
+Backup“ anzeigt.
+
+Der Zeitstempel der letzten Statusmeldung steht in `data/manage/activity.json`
+und wird **vor** dem Request geschrieben: ein nicht erreichbarer Manage-Server
+kostet dadurch einen Versuch pro Intervall, nicht einen pro Seitenaufruf. Keine
+der beiden Aufgaben kann die Seite abbrechen — Fehler landen im Client-Log
+(`data/manage/manage-client.log`), ein fehlgeschlagenes Backup zusätzlich als
+Hinweis auf dem Dashboard.
+
+Die Update-Prüfung braucht keine eigene Auslösung: die Einstellungsseite und
+`admin/manage.php` holen das Manifest beim Rendern.
 
 ## Cron
 
-Empfohlen, sobald Cron verfügbar ist:
+Nur relevant, wenn auf einer Installation doch Cron zur Verfügung steht - dann
+sind die Kommandos der zuverlässigere Weg und die Auslösung durch
+Adminaktivität greift nur noch selten ein:
 
 ```cron
 20 3 * * * /usr/bin/php /pfad/zum/projekt/manage-client/bin/manage-client.php backup --trigger=cron --quiet

+ 119 - 0
includes/manage-activity.php

@@ -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;
+}

+ 1 - 1
includes/version.php

@@ -1,3 +1,3 @@
 <?php
 
-define("APP_VERSION", "v1.3.14");
+define("APP_VERSION", "v1.4.0");