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 strpos;
use function unlink;
use function ltrim;
use function preg_replace;
use const GRAV_ROOT;
use const GLOB_ONLYDIR;
use const JSON_PRETTY_PRINT;
@@ -75,15 +77,29 @@ class SafeUpgradeService
$root = $options['root'] ?? GRAV_ROOT;
$this->rootPath = rtrim($root, DIRECTORY_SEPARATOR);
$this->parentDir = $options['parent_dir'] ?? dirname($this->rootPath);
$defaultStaging = $options['staging_root'] ?? ($this->parentDir . DIRECTORY_SEPARATOR . 'grav-upgrades');
if (!$this->ensureWritable($defaultStaging)) {
$fallback = getenv('HOME') ? getenv('HOME') . DIRECTORY_SEPARATOR . 'grav-upgrades' : sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'grav-upgrades';
if (!$this->ensureWritable($fallback)) {
throw new RuntimeException(sprintf('Unable to create staging directory at %s or fallback %s. Adjust permissions or configure system.updates.staging_root.', $defaultStaging, $fallback));
}
$defaultStaging = $fallback;
$candidates = [];
if (!empty($options['staging_root'])) {
$candidates[] = $options['staging_root'];
}
$candidates[] = $this->parentDir . DIRECTORY_SEPARATOR . 'grav-upgrades';
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');
if (isset($options['ignored_dirs']) && is_array($options['ignored_dirs'])) {
$this->ignoredDirs = $options['ignored_dirs'];
@@ -509,19 +525,54 @@ class SafeUpgradeService
* @param string $path
* @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 {
Folder::create($path);
Folder::create($expanded);
} catch (\RuntimeException $e) {
return null;
}
return is_writable($expanded) ? $expanded : null;
}
private function isAbsolutePath(string $path): bool
{
if ($path === '') {
return false;
}
if (!is_writable($path)) {
return false;
if ($path[0] === '/' || $path[0] === '\\') {
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
{
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();
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);
Installer::setError(Installer::OK);
} else {