added back snapshots in Install.php

Signed-off-by: Andy Miller <rhuk@mac.com>
This commit is contained in:
Andy Miller
2025-11-10 19:34:46 +00:00
parent 1d5d1357b8
commit 4fab5f99bb
2 changed files with 238 additions and 0 deletions

View File

@@ -333,6 +333,51 @@ class SafeUpgradeService
return $manifest; return $manifest;
} }
/**
* Create a snapshot specifically for automated upgrades.
*
* @param string $targetVersion
* @param string|null $label
* @return array
*/
public function createUpgradeSnapshot(string $targetVersion, ?string $label = null): array
{
$entries = $this->collectPackageEntries($this->rootPath);
if (!$entries) {
throw new RuntimeException('Unable to locate files to snapshot.');
}
$stageId = uniqid('upgrade-', false);
$backupPath = $this->stagingRoot . DIRECTORY_SEPARATOR . 'snapshot-' . $stageId;
$this->reportProgress('snapshot', sprintf('Capturing snapshot before upgrading to %s...', $targetVersion), null, [
'operation' => 'upgrade',
'target_version' => $targetVersion,
]);
$this->createBackupSnapshot($entries, $backupPath);
$manifest = $this->buildManifest($stageId, $targetVersion, $this->rootPath, $backupPath, $entries);
$manifest['package_path'] = null;
if ($label !== null && $label !== '') {
$manifest['label'] = $label;
}
$manifest['operation'] = 'upgrade';
$manifest['mode'] = 'pre-upgrade';
$this->persistManifest($manifest);
$this->lastManifest = $manifest;
$this->pruneOldSnapshots();
$this->reportProgress('snapshot', sprintf('Snapshot %s captured.', $stageId), 100, [
'operation' => 'upgrade',
'snapshot' => $stageId,
'target_version' => $targetVersion,
]);
return $manifest;
}
private function collectPackageEntries(string $packagePath): array private function collectPackageEntries(string $packagePath): array
{ {
$entries = []; $entries = [];

View File

@@ -12,14 +12,31 @@ namespace Grav\Installer;
use Composer\Autoload\ClassLoader; use Composer\Autoload\ClassLoader;
use Exception; use Exception;
use Grav\Common\Cache; use Grav\Common\Cache;
use Grav\Common\Filesystem\Folder;
use Grav\Common\GPM\Installer; use Grav\Common\GPM\Installer;
use Grav\Common\Grav; use Grav\Common\Grav;
use Grav\Common\Plugins; use Grav\Common\Plugins;
use RuntimeException; use RuntimeException;
use function class_exists; use function class_exists;
use function date;
use function dirname; use function dirname;
use function floor;
use function function_exists; use function function_exists;
use function is_dir;
use function is_file;
use function is_link;
use function is_string; use function is_string;
use function is_writable;
use function json_encode;
use function readlink;
use function sort;
use function sprintf;
use function symlink;
use function time;
use function uniqid;
use function unlink;
use const GRAV_ROOT;
use const JSON_PRETTY_PRINT;
/** /**
* Grav installer. * Grav installer.
@@ -295,6 +312,16 @@ ERR;
$this->updater->install(); $this->updater->install();
$safeUpgradeRequested = $this->shouldUseSafeUpgrade(); $safeUpgradeRequested = $this->shouldUseSafeUpgrade();
$targetVersion = $this->getVersion();
$snapshotManifest = null;
if ($safeUpgradeRequested) {
$snapshotManifest = $this->captureCoreSnapshot($targetVersion);
if ($snapshotManifest) {
$this->relayProgress('snapshot', sprintf('Snapshot %s captured.', $snapshotManifest['id']), 100);
} else {
$this->relayProgress('snapshot', 'Snapshot capture unavailable; continuing without it.', null);
}
}
$progressMessage = $safeUpgradeRequested $progressMessage = $safeUpgradeRequested
? 'Safe upgrade temporarily using legacy installer...' ? 'Safe upgrade temporarily using legacy installer...'
: 'Running legacy installer...'; : 'Running legacy installer...';
@@ -355,6 +382,172 @@ ERR;
return false; return false;
} }
private function captureCoreSnapshot(string $targetVersion): ?array
{
$entries = $this->collectSnapshotEntries();
if (!$entries) {
return null;
}
$snapshotRoot = $this->resolveSnapshotStore();
if (!$snapshotRoot) {
return null;
}
$snapshotId = 'snapshot-' . date('YmdHis');
$snapshotPath = $snapshotRoot . '/' . $snapshotId;
try {
Folder::create($snapshotPath);
} catch (\Throwable $e) {
error_log('[Grav Upgrade] Unable to create snapshot directory: ' . $e->getMessage());
return null;
}
$total = count($entries);
foreach ($entries as $index => $entry) {
$percent = $total > 0 ? (int)floor((($index + 1) / $total) * 100) : null;
$this->relayProgress('snapshot', sprintf('Snapshotting %s (%d/%d)', $entry, $index + 1, $total), $percent);
$source = GRAV_ROOT . '/' . $entry;
$destination = $snapshotPath . '/' . $entry;
try {
$this->snapshotCopyEntry($source, $destination);
} catch (\Throwable $e) {
error_log('[Grav Upgrade] Snapshot copy failed for ' . $entry . ': ' . $e->getMessage());
return null;
}
}
$manifest = [
'id' => $snapshotId,
'created_at' => time(),
'source_version' => GRAV_VERSION,
'target_version' => $targetVersion,
'php_version' => PHP_VERSION,
'entries' => $entries,
'package_path' => null,
'backup_path' => $snapshotPath,
'operation' => 'upgrade',
'mode' => 'pre-upgrade',
];
$this->persistSnapshotManifest($manifest);
$this->lastManifest = $manifest;
return $manifest;
}
private function collectSnapshotEntries(): array
{
$ignores = array_fill_keys($this->ignores, true);
$ignores['user'] = true;
$entries = [];
try {
$iterator = new \DirectoryIterator(GRAV_ROOT);
foreach ($iterator as $item) {
if ($item->isDot()) {
continue;
}
$name = $item->getFilename();
if (isset($ignores[$name])) {
continue;
}
$entries[] = $name;
}
} catch (\Throwable $e) {
error_log('[Grav Upgrade] Unable to enumerate snapshot entries: ' . $e->getMessage());
return [];
}
sort($entries);
return $entries;
}
private function snapshotCopyEntry(string $source, string $destination): void
{
if (is_link($source)) {
$linkTarget = readlink($source);
Folder::create(dirname($destination));
if (is_link($destination) || is_file($destination)) {
@unlink($destination);
}
if ($linkTarget !== false) {
@symlink($linkTarget, $destination);
}
return;
}
if (is_dir($source)) {
Folder::rcopy($source, $destination);
return;
}
Folder::create(dirname($destination));
if (!@copy($source, $destination)) {
throw new RuntimeException(sprintf('Failed to copy file %s during snapshot.', $source));
}
}
private function resolveSnapshotStore(): ?string
{
$candidates = [];
try {
$grav = Grav::instance();
if ($grav && isset($grav['locator'])) {
$path = $grav['locator']->findResource('tmp://grav-snapshots', true, true);
if ($path) {
$candidates[] = $path;
}
}
} catch (\Throwable $e) {
// ignore locator issues
}
$candidates[] = GRAV_ROOT . '/tmp/grav-snapshots';
foreach ($candidates as $candidate) {
if (!$candidate) {
continue;
}
try {
Folder::create($candidate);
} catch (\Throwable $e) {
continue;
}
if (is_dir($candidate) && is_writable($candidate)) {
return rtrim($candidate, '\\/');
}
}
error_log('[Grav Upgrade] Unable to locate writable snapshot directory; skipping snapshot.');
return null;
}
private function persistSnapshotManifest(array $manifest): void
{
$store = GRAV_ROOT . '/user/data/upgrades';
try {
Folder::create($store);
$path = $store . '/' . $manifest['id'] . '.json';
@file_put_contents($path, json_encode($manifest, JSON_PRETTY_PRINT));
} catch (\Throwable $e) {
error_log('[Grav Upgrade] Unable to write snapshot manifest: ' . $e->getMessage());
}
}
/** /**
* @return void * @return void