heartbeat.php 2.7 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394
  1. <?php
  2. declare(strict_types=1);
  3. // Status report to the manage server.
  4. //
  5. // Sent from cron (manage-client.php heartbeat) and after every update or
  6. // backup, so the server dashboard shows the installed version, the last backup
  7. // and any pending migrations without polling the instance.
  8. //
  9. // The response doubles as a cheap update check: one request is enough for a
  10. // monitoring job to learn that an instance is behind.
  11. function manageHeartbeatPayload(): array
  12. {
  13. $lastBackupAt = "";
  14. $pendingMigrations = 0;
  15. try {
  16. $backups = manageBackupList();
  17. if ($backups !== []) {
  18. $lastBackupAt = (string) ($backups[0]["created_at"] ?? "");
  19. }
  20. } catch (Throwable $exception) {
  21. // A broken backup index must not stop the heartbeat; the server simply
  22. // keeps the previous value.
  23. manageClientLog("WARNING", "Heartbeat could not read backup index", [
  24. "error" => $exception->getMessage(),
  25. ]);
  26. }
  27. try {
  28. $pendingMigrations = count(manageUpdatePendingMigrations());
  29. } catch (Throwable $exception) {
  30. manageClientLog("WARNING", "Heartbeat could not read migrations", [
  31. "error" => $exception->getMessage(),
  32. ]);
  33. }
  34. $diskFree = 0;
  35. try {
  36. $free = @disk_free_space(manageClientAppRoot());
  37. if (is_float($free) || is_int($free)) {
  38. $diskFree = (int) $free;
  39. }
  40. } catch (Throwable $exception) {
  41. $diskFree = 0;
  42. }
  43. return [
  44. "version" => manageClientVersion(),
  45. "php_version" => PHP_VERSION,
  46. "disk_free" => $diskFree,
  47. "pending_migrations" => $pendingMigrations,
  48. "last_backup_at" => $lastBackupAt,
  49. ];
  50. }
  51. /**
  52. * Sends the heartbeat.
  53. *
  54. * @return array{success: bool, latest: string, update_available: bool, server_time: string}
  55. */
  56. function manageHeartbeatSend(): array
  57. {
  58. $decoded = manageClientRequestJson(
  59. "POST",
  60. "heartbeat.php",
  61. manageHeartbeatPayload(),
  62. (int) MANAGE_HTTP_TIMEOUT,
  63. );
  64. return [
  65. "success" => !empty($decoded["success"]),
  66. "latest" => (string) ($decoded["latest"] ?? ""),
  67. "update_available" => !empty($decoded["update_available"]),
  68. "server_time" => (string) ($decoded["server_time"] ?? ""),
  69. ];
  70. }
  71. /**
  72. * Heartbeat that never throws. For call sites inside a host application where
  73. * an unreachable server must not surface as an error.
  74. */
  75. function manageHeartbeatSendQuietly(): ?array
  76. {
  77. try {
  78. return manageHeartbeatSend();
  79. } catch (Throwable $exception) {
  80. manageClientLog("WARNING", "Heartbeat failed", ["error" => $exception->getMessage()]);
  81. return null;
  82. }
  83. }