honor staging_root

Signed-off-by: Andy Miller <rhuk@mac.com>
This commit is contained in:
Andy Miller
2025-10-15 13:49:36 -06:00
parent f88c09adca
commit 23da92d0ff
3 changed files with 87 additions and 15 deletions

View File

@@ -39,6 +39,8 @@ use function uniqid;
use function trim; use function trim;
use function strpos; use function strpos;
use function unlink; use function unlink;
use function ltrim;
use function preg_replace;
use const GRAV_ROOT; use const GRAV_ROOT;
use const GLOB_ONLYDIR; use const GLOB_ONLYDIR;
use const JSON_PRETTY_PRINT; use const JSON_PRETTY_PRINT;
@@ -75,15 +77,29 @@ class SafeUpgradeService
$root = $options['root'] ?? GRAV_ROOT; $root = $options['root'] ?? GRAV_ROOT;
$this->rootPath = rtrim($root, DIRECTORY_SEPARATOR); $this->rootPath = rtrim($root, DIRECTORY_SEPARATOR);
$this->parentDir = $options['parent_dir'] ?? dirname($this->rootPath); $this->parentDir = $options['parent_dir'] ?? dirname($this->rootPath);
$defaultStaging = $options['staging_root'] ?? ($this->parentDir . DIRECTORY_SEPARATOR . 'grav-upgrades');
if (!$this->ensureWritable($defaultStaging)) { $candidates = [];
$fallback = getenv('HOME') ? getenv('HOME') . DIRECTORY_SEPARATOR . 'grav-upgrades' : sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'grav-upgrades'; if (!empty($options['staging_root'])) {
if (!$this->ensureWritable($fallback)) { $candidates[] = $options['staging_root'];
throw new RuntimeException(sprintf('Unable to create staging directory at %s or fallback %s. Adjust permissions or configure system.updates.staging_root.', $defaultStaging, $fallback)); }
} $candidates[] = $this->parentDir . DIRECTORY_SEPARATOR . 'grav-upgrades';
$defaultStaging = $fallback; if (getenv('HOME')) {
$candidates[] = getenv('HOME') . DIRECTORY_SEPARATOR . 'grav-upgrades';
}
$candidates[] = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'grav-upgrades';
$this->stagingRoot = null;
foreach ($candidates as $candidate) {
$resolved = $this->resolveStagingPath($candidate);
if ($resolved) {
$this->stagingRoot = $resolved;
break;
}
}
if (null === $this->stagingRoot) {
throw new RuntimeException('Unable to locate writable staging directory. Configure system.updates.staging_root or adjust permissions.');
} }
$this->stagingRoot = realpath($defaultStaging) ?: $defaultStaging;
$this->manifestStore = $options['manifest_store'] ?? ($this->rootPath . DIRECTORY_SEPARATOR . 'user' . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR . 'upgrades'); $this->manifestStore = $options['manifest_store'] ?? ($this->rootPath . DIRECTORY_SEPARATOR . 'user' . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR . 'upgrades');
if (isset($options['ignored_dirs']) && is_array($options['ignored_dirs'])) { if (isset($options['ignored_dirs']) && is_array($options['ignored_dirs'])) {
$this->ignoredDirs = $options['ignored_dirs']; $this->ignoredDirs = $options['ignored_dirs'];
@@ -509,19 +525,54 @@ class SafeUpgradeService
* @param string $path * @param string $path
* @return bool * @return bool
*/ */
private function ensureWritable(string $path): bool private function resolveStagingPath(?string $path): ?string
{ {
if (null === $path || $path === '') {
return null;
}
$expanded = $path;
if (0 === strpos($expanded, '~')) {
$home = getenv('HOME');
if ($home) {
$expanded = rtrim($home, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . ltrim($expanded, '~\/');
}
}
if (!$this->isAbsolutePath($expanded)) {
$expanded = $this->rootPath . DIRECTORY_SEPARATOR . ltrim($expanded, DIRECTORY_SEPARATOR);
}
$expanded = $this->normalizePath($expanded);
try { try {
Folder::create($path); Folder::create($expanded);
} catch (\RuntimeException $e) { } catch (\RuntimeException $e) {
return null;
}
return is_writable($expanded) ? $expanded : null;
}
private function isAbsolutePath(string $path): bool
{
if ($path === '') {
return false; return false;
} }
if (!is_writable($path)) { if ($path[0] === '/' || $path[0] === '\\') {
return false; return true;
} }
return true; return (bool)preg_match('#^[A-Za-z]:[\\/]#', $path);
}
private function normalizePath(string $path): string
{
$path = str_replace('\\', '/', $path);
$path = preg_replace('#/+#', '/', $path);
$path = rtrim($path, '/');
return str_replace('/', DIRECTORY_SEPARATOR, $path);
} }
/** /**

View File

@@ -278,7 +278,18 @@ class SelfupgradeCommand extends GpmCommand
*/ */
protected function createSafeUpgradeService(): SafeUpgradeService protected function createSafeUpgradeService(): SafeUpgradeService
{ {
return new SafeUpgradeService(); $config = null;
try {
$config = Grav::instance()['config'] ?? null;
} catch (\Throwable $e) {
$config = null;
}
$stagingRoot = $config ? $config->get('system.updates.staging_root') : null;
return new SafeUpgradeService([
'staging_root' => $stagingRoot,
]);
} }
/** /**

View File

@@ -262,7 +262,17 @@ ERR;
$this->updater->install(); $this->updater->install();
if ($this->shouldUseSafeUpgrade()) { if ($this->shouldUseSafeUpgrade()) {
$service = new SafeUpgradeService(); $options = [];
try {
$grav = Grav::instance();
if ($grav && isset($grav['config'])) {
$options['staging_root'] = $grav['config']->get('system.updates.staging_root');
}
} catch (\Throwable $e) {
// ignore
}
$service = new SafeUpgradeService($options);
$service->promote($this->location, $this->getVersion(), $this->ignores); $service->promote($this->location, $this->getVersion(), $this->ignores);
Installer::setError(Installer::OK); Installer::setError(Installer::OK);
} else { } else {