updater.php 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. <?php
  2. declare(strict_types=1);
  3. require_once __DIR__ . "/../config.php";
  4. require_once __DIR__ . "/../includes/version.php";
  5. require_once __DIR__ . "/../includes/updater.php";
  6. if (empty($_SESSION["admin_logged_in"])) {
  7. header("Location: login.php");
  8. exit();
  9. }
  10. if (!defined("UPDATE_MANIFEST_URL")) {
  11. define("UPDATE_MANIFEST_URL", "");
  12. }
  13. if (!defined("UPDATE_WORK_DIR")) {
  14. define("UPDATE_WORK_DIR", DATA_DIR . "updates/work/");
  15. }
  16. if (!defined("UPDATE_BACKUP_DIR")) {
  17. define("UPDATE_BACKUP_DIR", DATA_DIR . "updates/backups/");
  18. }
  19. if (!defined("UPDATE_PRESERVE_PATHS")) {
  20. define("UPDATE_PRESERVE_PATHS", ["config.php", "data/", ".git/"]);
  21. }
  22. if (!defined("UPDATE_REQUIRED_PACKAGE_PATHS")) {
  23. define("UPDATE_REQUIRED_PACKAGE_PATHS", ["index.php", "admin/", "includes/"]);
  24. }
  25. if (!defined("UPDATE_USER_AGENT")) {
  26. define("UPDATE_USER_AGENT", "Simple-PHP-Updater/" . APP_VERSION);
  27. }
  28. $messages = [];
  29. $errors = [];
  30. $manifest = null;
  31. $updateAvailable = false;
  32. function updaterEscape($value): string
  33. {
  34. return htmlspecialchars((string) $value, ENT_QUOTES, "UTF-8");
  35. }
  36. function updaterCsrfToken(): string
  37. {
  38. if (empty($_SESSION["updater_csrf_token"])) {
  39. $_SESSION["updater_csrf_token"] = bin2hex(random_bytes(32));
  40. }
  41. return $_SESSION["updater_csrf_token"];
  42. }
  43. function updaterValidateCsrfToken(string $token): bool
  44. {
  45. return !empty($_SESSION["updater_csrf_token"]) &&
  46. hash_equals($_SESSION["updater_csrf_token"], $token);
  47. }
  48. try {
  49. $appRoot = realpath(__DIR__ . "/..");
  50. $updater = new SimpleUpdater([
  51. "manifest_url" => UPDATE_MANIFEST_URL,
  52. "work_dir" => UPDATE_WORK_DIR,
  53. "backup_dir" => UPDATE_BACKUP_DIR,
  54. "current_version" => APP_VERSION,
  55. "app_root" => $appRoot === false ? "" : $appRoot,
  56. "user_agent" => UPDATE_USER_AGENT,
  57. "preserve_paths" => UPDATE_PRESERVE_PATHS,
  58. "required_package_paths" => UPDATE_REQUIRED_PACKAGE_PATHS,
  59. ]);
  60. try {
  61. $manifest = $updater->fetchManifest();
  62. $updateAvailable = $updater->updateAvailable($manifest);
  63. } catch (Throwable $exception) {
  64. $errors[] = $exception->getMessage();
  65. }
  66. if ($_SERVER["REQUEST_METHOD"] === "POST") {
  67. if (!updaterValidateCsrfToken((string) ($_POST["csrf_token"] ?? ""))) {
  68. $errors[] = "Invalid token. Please reload the page and try again.";
  69. } else {
  70. try {
  71. $manifest = $updater->fetchManifest();
  72. $force = !empty($_POST["force_redeploy"]);
  73. $updateAvailable = $updater->updateAvailable($manifest);
  74. if (!$updateAvailable && !$force) {
  75. throw new RuntimeException(
  76. "No newer update is available. Enable force redeployment to deploy this package anyway.",
  77. );
  78. }
  79. $result = $updater->deploy($manifest);
  80. $messages[] = "Deployment finished.";
  81. $messages[] = "Files copied: " . $result["copied"];
  82. $messages[] = "Files backed up: " . $result["backed_up"];
  83. $messages[] = "Old backup directories removed: " . $result["removed_backups"];
  84. $messages[] = "Skipped preserved paths: " . $result["skipped"];
  85. $messages[] = "Backup directory: " . $result["backup_dir"];
  86. } catch (Throwable $exception) {
  87. $errors[] = $exception->getMessage();
  88. }
  89. }
  90. }
  91. } catch (Throwable $exception) {
  92. $errors[] = $exception->getMessage();
  93. }
  94. ?>
  95. <!DOCTYPE html>
  96. <html lang="de">
  97. <head>
  98. <meta charset="UTF-8">
  99. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  100. <title>Updater</title>
  101. </head>
  102. <body>
  103. <h1>Updater</h1>
  104. <p><a href="settings.php">Back to settings</a></p>
  105. <?php foreach ($messages as $message): ?>
  106. <p><strong><?php echo updaterEscape($message); ?></strong></p>
  107. <?php endforeach; ?>
  108. <?php foreach ($errors as $error): ?>
  109. <p><strong>Error:</strong> <?php echo updaterEscape($error); ?></p>
  110. <?php endforeach; ?>
  111. <table border="1" cellpadding="6" cellspacing="0">
  112. <tbody>
  113. <tr>
  114. <th align="left">Installed version</th>
  115. <td><?php echo updaterEscape(APP_VERSION); ?></td>
  116. </tr>
  117. <tr>
  118. <th align="left">Update target URL</th>
  119. <td><?php echo updaterEscape(UPDATE_MANIFEST_URL); ?></td>
  120. </tr>
  121. <tr>
  122. <th align="left">Available version</th>
  123. <td><?php echo updaterEscape($manifest["version"] ?? "Unavailable"); ?></td>
  124. </tr>
  125. <tr>
  126. <th align="left">Package URL</th>
  127. <td><?php echo updaterEscape($manifest["package_url"] ?? "Unavailable"); ?></td>
  128. </tr>
  129. <tr>
  130. <th align="left">SHA-256</th>
  131. <td><?php echo updaterEscape($manifest["sha256"] ?? "Unavailable"); ?></td>
  132. </tr>
  133. <tr>
  134. <th align="left">Published at</th>
  135. <td><?php echo updaterEscape($manifest["published_at"] ?? "Unavailable"); ?></td>
  136. </tr>
  137. <tr>
  138. <th align="left">Update available</th>
  139. <td><?php echo $updateAvailable ? "Yes" : "No"; ?></td>
  140. </tr>
  141. </tbody>
  142. </table>
  143. <h2>Manual deployment</h2>
  144. <form method="POST">
  145. <input type="hidden" name="csrf_token" value="<?php echo updaterEscape(updaterCsrfToken()); ?>">
  146. <p>
  147. <label>
  148. <input type="checkbox" name="force_redeploy" value="1">
  149. Force redeployment
  150. </label>
  151. </p>
  152. <button type="submit" name="deploy_update" value="1">Deploy update</button>
  153. </form>
  154. </body>
  155. </html>