more improvements to safe upgrade

This commit is contained in:
Andy Miller
2025-10-17 10:02:22 -06:00
parent 78fc74a77e
commit 654c2bb9c4
4 changed files with 146 additions and 20 deletions

View File

@@ -290,6 +290,7 @@ class SafeUpgradeManager
$result = [
'job' => $manifest ?: null,
'progress' => $progress,
'context' => $this->buildStatusContext(),
];
$this->clearJobContext();
@@ -417,6 +418,7 @@ class SafeUpgradeManager
'log' => $logPath,
'progress' => $this->getProgress(),
'job' => $this->readManifest(),
'context' => $this->buildStatusContext(),
];
}
@@ -553,6 +555,7 @@ class SafeUpgradeManager
'status' => 'noop',
'version' => $localVersion,
'message' => 'Grav is already up to date.',
'context' => $this->buildStatusContext(),
];
}
@@ -647,6 +650,7 @@ class SafeUpgradeManager
'version' => $remoteVersion,
'manifest' => $manifest,
'previous_version' => $localVersion,
'context' => $this->buildStatusContext(),
];
}
@@ -896,6 +900,7 @@ class SafeUpgradeManager
'status' => 'finalized',
'version' => $localVersion,
'message' => 'Post-install scripts completed.',
'context' => $this->buildStatusContext(),
];
}
@@ -1011,9 +1016,31 @@ class SafeUpgradeManager
return [
'status' => 'error',
'message' => $message,
'context' => $this->buildStatusContext(),
] + $extra;
}
protected function buildStatusContext(): ?string
{
$context = [];
if ($this->jobManifestPath) {
$context['manifest'] = $this->jobManifestPath;
}
if ($this->progressPath) {
$context['progress'] = $this->progressPath;
}
if (!$context) {
return null;
}
$encoded = json_encode($context);
return $encoded === false ? null : base64_encode($encoded);
}
protected function ensureExecutablePermissions(): void
{
$executables = [

View File

@@ -33,11 +33,73 @@ $readJson = static function (string $path): ?array {
$progress = null;
$manifest = null;
$manifestPath = null;
$progressPath = null;
$normalizeDir = static function (string $path): string {
$normalized = str_replace('\\', '/', $path);
return rtrim($normalized, '/');
};
$jobsDirNormalized = $normalizeDir($jobsDir);
$userDataDirNormalized = $normalizeDir(dirname($jobsDir));
$contextParam = $_GET['context'] ?? '';
if ($contextParam !== '') {
$decodedRaw = base64_decode(strtr($contextParam, ' ', '+'), true);
if ($decodedRaw !== false) {
$decoded = json_decode($decodedRaw, true);
if (is_array($decoded)) {
$validatePath = static function (string $candidate) use ($normalizeDir, $jobsDirNormalized, $userDataDirNormalized) {
$candidate = str_replace('\\', '/', $candidate);
$directory = dirname($candidate);
$real = realpath($directory);
if ($real === false) {
return null;
}
$real = $normalizeDir($real);
if (strpos($real, $jobsDirNormalized) !== 0 && strpos($real, $userDataDirNormalized) !== 0) {
return null;
}
return $candidate;
};
if (!empty($decoded['manifest'])) {
$candidate = $validatePath((string)$decoded['manifest']);
if ($candidate) {
$manifestPath = $candidate;
if (is_file($candidate)) {
$manifest = $readJson($candidate);
}
}
}
if (!empty($decoded['progress'])) {
$candidate = $validatePath((string)$decoded['progress']);
if ($candidate) {
$progressPath = $candidate;
if (is_file($candidate)) {
$progress = $readJson($candidate);
}
}
}
}
}
}
if ($jobId !== '') {
$jobPath = $jobsDir . '/' . $jobId;
$progress = $readJson($jobPath . '/progress.json');
$manifest = $readJson($jobPath . '/manifest.json');
$progressPath = $progressPath ?: ($jobPath . '/progress.json');
$manifestPath = $manifestPath ?: ($jobPath . '/manifest.json');
if (is_file($progressPath)) {
$progress = $readJson($progressPath);
}
if (is_file($manifestPath)) {
$manifest = $readJson($manifestPath);
}
if (!$progress && !$manifest && !is_dir($jobPath)) {
$progress = $readJson($fallbackProgress) ?: [
@@ -46,37 +108,45 @@ if ($jobId !== '') {
'percent' => null,
'timestamp' => time(),
];
echo json_encode([
'status' => 'success',
'message' => 'Safe upgrade job not found.',
'data' => [
'job' => null,
'progress' => $progress,
],
]);
exit;
}
}
if ($progress === null) {
if ($progressPath && is_file($progressPath)) {
$progress = $readJson($progressPath);
}
if ($progress === null) {
$progress = $readJson($fallbackProgress) ?: [
'stage' => 'idle',
'message' => '',
'percent' => null,
'timestamp' => time(),
];
$progressPath = $fallbackProgress;
}
}
if ($jobId !== '' && is_array($progress) && !isset($progress['job_id'])) {
$progress['job_id'] = $jobId;
}
$contextPayload = [];
if ($manifestPath) {
$contextPayload['manifest'] = $manifestPath;
}
if ($progressPath) {
$contextPayload['progress'] = $progressPath;
}
$contextToken = $contextPayload ? base64_encode(json_encode($contextPayload)) : null;
echo json_encode([
'status' => 'success',
'data' => [
'job' => $manifest ?: null,
'progress' => $progress,
'context' => $contextToken,
],
]);

View File

@@ -54,6 +54,7 @@ export default class SafeUpgrade {
this.active = false;
this.jobId = null;
this.statusFailures = 0;
this.statusContext = null;
this.directStatusUrl = this.resolveDirectStatusUrl();
this.preferDirectStatus = !!this.directStatusUrl;
@@ -120,6 +121,7 @@ export default class SafeUpgrade {
this.decisions = {};
this.statusFailures = 0;
this.preferDirectStatus = !!this.directStatusUrl;
this.statusContext = null;
this.renderLoading();
this.modal.open();
this.fetchPreflight();
@@ -412,6 +414,7 @@ export default class SafeUpgrade {
}
this.statusFailures = 0;
this.preferDirectStatus = !!this.directStatusUrl;
this.statusContext = data.context || null;
this.beginPolling(1200);
} else {
this.renderResult(data);
@@ -453,8 +456,18 @@ export default class SafeUpgrade {
resolveStatusEndpoint() {
const useDirect = this.directStatusUrl && this.preferDirectStatus;
let url = useDirect ? this.directStatusUrl : this.urls.status;
const params = [];
if (this.jobId) {
url += (url.includes('?') ? '&' : '?') + `job=${encodeURIComponent(this.jobId)}`;
params.push(`job=${encodeURIComponent(this.jobId)}`);
}
if (this.statusContext) {
params.push(`context=${encodeURIComponent(this.statusContext)}`);
}
if (params.length) {
url += (url.includes('?') ? '&' : '?') + params.join('&');
}
return {
@@ -532,6 +545,9 @@ export default class SafeUpgrade {
}
const payload = response.data || {};
if (Object.prototype.hasOwnProperty.call(payload, 'context')) {
this.statusContext = payload.context || null;
}
const job = payload.job || {};
const data = payload.progress || payload;
nextStage = data.stage || null;

View File

@@ -4610,6 +4610,7 @@ var SafeUpgrade = /*#__PURE__*/function () {
this.active = false;
this.jobId = null;
this.statusFailures = 0;
this.statusContext = null;
this.directStatusUrl = this.resolveDirectStatusUrl();
this.preferDirectStatus = !!this.directStatusUrl;
this.registerEvents();
@@ -4676,6 +4677,7 @@ var SafeUpgrade = /*#__PURE__*/function () {
this.decisions = {};
this.statusFailures = 0;
this.preferDirectStatus = !!this.directStatusUrl;
this.statusContext = null;
this.renderLoading();
this.modal.open();
this.fetchPreflight();
@@ -4882,6 +4884,7 @@ var SafeUpgrade = /*#__PURE__*/function () {
}
_this4.statusFailures = 0;
_this4.preferDirectStatus = !!_this4.directStatusUrl;
_this4.statusContext = data.context || null;
_this4.beginPolling(1200);
} else {
_this4.renderResult(data);
@@ -4921,8 +4924,15 @@ var SafeUpgrade = /*#__PURE__*/function () {
value: function resolveStatusEndpoint() {
var useDirect = this.directStatusUrl && this.preferDirectStatus;
var url = useDirect ? this.directStatusUrl : this.urls.status;
var params = [];
if (this.jobId) {
url += (url.indexOf('?') !== -1 ? '&' : '?') + "job=".concat(encodeURIComponent(this.jobId));
params.push("job=".concat(encodeURIComponent(this.jobId)));
}
if (this.statusContext) {
params.push("context=".concat(encodeURIComponent(this.statusContext)));
}
if (params.length) {
url += (url.indexOf('?') !== -1 ? '&' : '?') + params.join('&');
}
return {
url: url,
@@ -5002,6 +5012,9 @@ var SafeUpgrade = /*#__PURE__*/function () {
return;
}
var payload = response.data || {};
if (Object.prototype.hasOwnProperty.call(payload, 'context')) {
_this6.statusContext = payload.context || null;
}
var job = payload.job || {};
var data = payload.progress || payload;
nextStage = data.stage || null;