array_values($backups)]; } function backupManageWriteIndex(array $backups): void { backupManageWriteJsonFile((string) BACKUP_SERVER_INDEX_FILE, [ "backups" => array_values($backups), ]); } function backupManageReadSettings(): array { $settings = backupManageReadJsonFile((string) BACKUP_SERVER_SETTINGS_FILE); $retention = isset($settings["retention"]) ? max(1, (int) $settings["retention"]) : max(1, (int) BACKUP_SERVER_RETENTION); $instances = isset($settings["instances"]) && is_array($settings["instances"]) ? $settings["instances"] : []; $allowedInstances = []; foreach ($instances as $instance) { try { $allowedInstances[] = backupManageValidateInstance((string) $instance); } catch (Throwable $exception) { continue; } } $allowedInstances = array_values(array_unique($allowedInstances)); sort($allowedInstances); return [ "retention" => $retention, "instances" => $allowedInstances, ]; } function backupManageWriteSettings(array $settings): void { $instances = isset($settings["instances"]) && is_array($settings["instances"]) ? $settings["instances"] : backupManageReadSettings()["instances"]; $allowedInstances = []; foreach ($instances as $instance) { $allowedInstances[] = backupManageValidateInstance((string) $instance); } $allowedInstances = array_values(array_unique($allowedInstances)); sort($allowedInstances); backupManageWriteJsonFile((string) BACKUP_SERVER_SETTINGS_FILE, [ "retention" => max(1, (int) ($settings["retention"] ?? BACKUP_SERVER_RETENTION)), "instances" => $allowedInstances, ]); } function backupManageInstanceDir(string $instance): string { return rtrim((string) BACKUP_SERVER_BACKUP_DIR, "/\\") . DIRECTORY_SEPARATOR . $instance; } function backupManageValidateInstance(string $instance): string { $instance = trim($instance); if ( $instance === "" || strlen($instance) > 120 || preg_match('/^[A-Za-z0-9][A-Za-z0-9._-]*$/', $instance) !== 1 ) { throw new RuntimeException("Invalid instance."); } return $instance; } function backupManageValidateFilename(string $filename): string { $filename = basename(trim($filename)); if (preg_match('/^backup-\d{8}-\d{6}(?:-\d+)?\.zip$/', $filename) !== 1) { throw new RuntimeException("Invalid backup filename."); } return $filename; } function backupManageFormatBytes(int $bytes): string { if ($bytes >= 1073741824) { return number_format($bytes / 1073741824, 2, ",", ".") . " GB"; } if ($bytes >= 1048576) { return number_format($bytes / 1048576, 2, ",", ".") . " MB"; } if ($bytes >= 1024) { return number_format($bytes / 1024, 1, ",", ".") . " KB"; } return $bytes . " B"; } function backupManageFindBackup(string $instance, string $filename): ?array { foreach (backupManageReadIndex()["backups"] as $backup) { if (!is_array($backup)) { continue; } if (($backup["instance"] ?? "") === $instance && ($backup["filename"] ?? "") === $filename) { return $backup; } } return null; } function backupManageBackupPath(string $instance, string $filename): string { return backupManageInstanceDir($instance) . DIRECTORY_SEPARATOR . $filename; } function backupManageSendDownload(string $instance, string $filename): void { $backup = backupManageFindBackup($instance, $filename); if ($backup === null) { throw new RuntimeException("Backup not found."); } $path = backupManageBackupPath($instance, $filename); $size = filesize($path); $handle = fopen($path, "rb"); if ($size === false || $handle === false) { throw new RuntimeException("Backup cannot be opened."); } header("Content-Type: application/zip"); header("Content-Disposition: attachment; filename=\"" . addcslashes($instance . "-" . $filename, "\"\\") . "\""); header("Content-Length: " . (string) $size); header("Cache-Control: private, no-store"); header("X-Content-Type-Options: nosniff"); fpassthru($handle); fclose($handle); exit; } function backupManageDeleteBackup(string $instance, string $filename): void { $index = backupManageReadIndex(); $kept = []; $found = false; foreach ($index["backups"] as $backup) { if ( is_array($backup) && ($backup["instance"] ?? "") === $instance && ($backup["filename"] ?? "") === $filename ) { $found = true; continue; } $kept[] = $backup; } if (!$found) { throw new RuntimeException("Backup not found."); } $path = backupManageBackupPath($instance, $filename); if (is_file($path)) { unlink($path); } backupManageWriteIndex($kept); } function backupManageApplyRetentionForInstance(string $instance): void { $index = backupManageReadIndex(); $retention = backupManageReadSettings()["retention"]; $instanceBackups = []; $otherBackups = []; foreach ($index["backups"] as $backup) { if (!is_array($backup)) { continue; } if (($backup["instance"] ?? "") === $instance) { $instanceBackups[] = $backup; } else { $otherBackups[] = $backup; } } usort($instanceBackups, function ($left, $right) { return strcmp((string) ($right["uploaded_at"] ?? ""), (string) ($left["uploaded_at"] ?? "")); }); $keep = array_slice($instanceBackups, 0, $retention); $remove = array_slice($instanceBackups, $retention); foreach ($remove as $backup) { $filename = basename((string) ($backup["filename"] ?? "")); if ($filename !== "") { $path = backupManageBackupPath($instance, $filename); if (is_file($path)) { @unlink($path); } } } backupManageWriteIndex(array_merge($otherBackups, $keep)); } function backupManageApplyRetentionAll(): void { $instances = []; foreach (backupManageReadIndex()["backups"] as $backup) { if (is_array($backup)) { $instance = (string) ($backup["instance"] ?? ""); if ($instance !== "") { $instances[$instance] = true; } } } foreach (array_keys($instances) as $instance) { backupManageApplyRetentionForInstance($instance); } } function backupManageAddInstance(string $instance): void { $instance = backupManageValidateInstance($instance); $settings = backupManageReadSettings(); $settings["instances"][] = $instance; backupManageWriteSettings($settings); } function backupManageRemoveInstance(string $instance): void { $instance = backupManageValidateInstance($instance); $settings = backupManageReadSettings(); $settings["instances"] = array_values( array_filter($settings["instances"], function ($existing) use ($instance) { return $existing !== $instance; }), ); backupManageWriteSettings($settings); } function backupManageGroupBackupsByInstance(array $backups): array { $grouped = []; foreach ($backups as $backup) { if (!is_array($backup)) { continue; } $instance = (string) ($backup["instance"] ?? ""); $filename = basename((string) ($backup["filename"] ?? "")); if ($instance === "" || $filename === "" || !is_file(backupManageBackupPath($instance, $filename))) { continue; } $backup["filename"] = $filename; $backup["size"] = (int) (filesize(backupManageBackupPath($instance, $filename)) ?: ($backup["size"] ?? 0)); $grouped[$instance][] = $backup; } ksort($grouped); foreach ($grouped as &$records) { usort($records, function ($left, $right) { return strcmp((string) ($right["uploaded_at"] ?? ""), (string) ($left["uploaded_at"] ?? "")); }); } unset($records); return $grouped; } if ($_SERVER["REQUEST_METHOD"] === "POST") { $action = (string) ($_POST["action"] ?? ""); if ($action === "login") { if (!backupManagePasswordConfigured()) { $errors[] = "No password is configured."; } elseif (backupManagePasswordMatches((string) ($_POST["password"] ?? ""))) { session_regenerate_id(true); $_SESSION["backup_server_logged_in"] = true; $messages[] = "Logged in."; } else { $errors[] = "Wrong password."; } } elseif ($action === "logout") { unset($_SESSION["backup_server_logged_in"], $_SESSION["backup_server_csrf_token"]); $messages[] = "Logged out."; } elseif (!backupManageIsLoggedIn()) { $errors[] = "Login required."; } elseif (!backupManageCsrfIsValid((string) ($_POST["csrf_token"] ?? ""))) { $errors[] = "Invalid token. Please reload the page and try again."; } else { try { if ($action === "update_retention") { $retention = max(1, (int) ($_POST["retention"] ?? BACKUP_SERVER_RETENTION)); $settings = backupManageReadSettings(); $settings["retention"] = $retention; backupManageWriteSettings($settings); backupManageApplyRetentionAll(); $messages[] = "Retention updated."; } elseif ($action === "add_instance") { backupManageAddInstance((string) ($_POST["instance"] ?? "")); $messages[] = "Instance added."; } elseif ($action === "remove_instance") { backupManageRemoveInstance((string) ($_POST["instance"] ?? "")); $messages[] = "Instance removed."; } elseif ($action === "download") { backupManageSendDownload( backupManageValidateInstance((string) ($_POST["instance"] ?? "")), backupManageValidateFilename((string) ($_POST["filename"] ?? "")), ); } elseif ($action === "delete") { backupManageDeleteBackup( backupManageValidateInstance((string) ($_POST["instance"] ?? "")), backupManageValidateFilename((string) ($_POST["filename"] ?? "")), ); $messages[] = "Backup deleted."; } } catch (Throwable $exception) { $errors[] = $exception->getMessage(); } } } try { $settings = backupManageReadSettings(); $groupedBackups = backupManageGroupBackupsByInstance(backupManageReadIndex()["backups"]); } catch (Throwable $exception) { $settings = ["retention" => max(1, (int) BACKUP_SERVER_RETENTION)]; $groupedBackups = []; $errors[] = $exception->getMessage(); } ?>
Error:
Distributed instances should upload to upload.php.
No instances allowed. Uploads will be rejected until an instance is added.
| Instance | Actions |
|---|---|
No backups uploaded.
$backups): ?>| Uploaded | Filename | Size | SHA-256 | Source IP | Actions |
|---|---|---|---|---|---|