From d932875e66d83c1755ddacdff54ee11e50affe0b Mon Sep 17 00:00:00 2001 From: Andy Miller Date: Sat, 18 Oct 2025 18:42:08 -0600 Subject: [PATCH] create adhoc snapshot Signed-off-by: Andy Miller --- bin/restore | 48 +++++++++++++++++++ .../Common/Upgrade/SafeUpgradeService.php | 45 +++++++++++++++++ 2 files changed, 93 insertions(+) diff --git a/bin/restore b/bin/restore index 4322ab3f5..a0c1e80f6 100755 --- a/bin/restore +++ b/bin/restore @@ -43,16 +43,21 @@ Usage: bin/restore remove [ ...] [--staging-root=/absolute/path] Deletes one or more snapshots (interactive selection when no id provided). + bin/restore snapshot [--label=\"optional description\"] [--staging-root=/absolute/path] + Creates a manual snapshot of the current Grav core files. + bin/restore recovery [status|clear] Shows the recovery flag context or clears it. Options: --staging-root Overrides the staging directory (defaults to configured value). + --label Optional label to store with the manual snapshot. Examples: bin/restore list bin/restore apply stage-68eff31cc4104 bin/restore apply stage-68eff31cc4104 --staging-root=/var/grav-backups + bin/restore snapshot --label=\"Before plugin install\" bin/restore recovery status bin/restore recovery clear USAGE; @@ -274,6 +279,45 @@ function applySnapshot(string $snapshotId, array $options): void exit(0); } +/** + * @param array $options + * @return void + */ +function createManualSnapshot(array $options): void +{ + $label = null; + if (isset($options['label']) && is_string($options['label'])) { + $label = trim($options['label']); + if ($label === '') { + $label = null; + } + } + + try { + $service = createUpgradeService($options); + $manifest = $service->createSnapshot($label); + } catch (\Throwable $e) { + fwrite(STDERR, "Snapshot creation failed: " . $e->getMessage() . "\n"); + exit(1); + } + + $snapshotId = $manifest['id'] ?? null; + if (!$snapshotId) { + $snapshotId = 'unknown'; + } + $version = $manifest['source_version'] ?? $manifest['target_version'] ?? 'unknown'; + + echo "Created snapshot {$snapshotId} (Grav {$version}).\n"; + if ($label) { + echo "Label: {$label}\n"; + } + if (!empty($manifest['backup_path'])) { + echo "Snapshot path: {$manifest['backup_path']}\n"; + } + + exit(0); +} + /** * @param list $snapshots * @return string|null @@ -520,6 +564,10 @@ switch ($command) { applySnapshot($snapshotId, $options); break; + case 'snapshot': + createManualSnapshot($options); + break; + case 'recovery': $action = strtolower($arguments[0] ?? 'status'); $manager = new RecoveryManager(GRAV_ROOT); diff --git a/system/src/Grav/Common/Upgrade/SafeUpgradeService.php b/system/src/Grav/Common/Upgrade/SafeUpgradeService.php index ca4c7b811..233a83c58 100644 --- a/system/src/Grav/Common/Upgrade/SafeUpgradeService.php +++ b/system/src/Grav/Common/Upgrade/SafeUpgradeService.php @@ -210,6 +210,51 @@ class SafeUpgradeService return $manifest; } + /** + * Create a manual snapshot of the current Grav installation. + * + * @param string|null $label + * @return array + */ + public function createSnapshot(?string $label = null): array + { + $entries = $this->collectPackageEntries($this->rootPath); + if (!$entries) { + throw new RuntimeException('Unable to locate files to snapshot.'); + } + + $stageId = uniqid('snapshot-', false); + $backupPath = $this->stagingRoot . DIRECTORY_SEPARATOR . 'snapshot-' . $stageId; + + $this->reportProgress('snapshot', 'Creating manual snapshot...', null, [ + 'operation' => 'snapshot', + 'label' => $label, + 'mode' => 'manual', + ]); + + $this->createBackupSnapshot($entries, $backupPath); + + $manifest = $this->buildManifest($stageId, GRAV_VERSION, $this->rootPath, $backupPath, $entries); + $manifest['package_path'] = null; + if ($label !== null && $label !== '') { + $manifest['label'] = $label; + } + $manifest['operation'] = 'snapshot'; + $manifest['mode'] = 'manual'; + + $this->persistManifest($manifest); + $this->pruneOldSnapshots(); + + $this->reportProgress('complete', sprintf('Snapshot %s created.', $stageId), 100, [ + 'operation' => 'snapshot', + 'snapshot' => $stageId, + 'version' => $manifest['target_version'] ?? null, + 'mode' => 'manual', + ]); + + return $manifest; + } + private function collectPackageEntries(string $packagePath): array { $entries = [];