mirror of
https://github.com/nextcloud/all-in-one.git
synced 2026-06-04 01:40:09 +00:00
aio-interface: extract Nextcloud latest-major upgrade logic to dedicated script and add UI trigger button (#7988)
* Extract Nextcloud major upgrade logic to script and add UI button Agent-Logs-Url: https://github.com/nextcloud/all-in-one/sessions/8cd11b09-5073-4e27-8e59-9afffaf96c1f Rename sendNotification to execCommandInContainer and reuse for upgrade method Agent-Logs-Url: https://github.com/nextcloud/all-in-one/sessions/88744552-9d64-4de2-9f64-5a98a5e3b200 Add $cmd array validation to execCommandInContainer Agent-Logs-Url: https://github.com/nextcloud/all-in-one/sessions/45d5228c-7834-404e-ba54-90b5c8c207c8 Apply suggestion from @szaimen Signed-off-by: Simon L. <szaimen@e.mail.de> Apply suggestion from @szaimen Signed-off-by: Simon L. <szaimen@e.mail.de> Apply suggestion from @szaimen Signed-off-by: Simon L. <szaimen@e.mail.de> Apply suggestion from @szaimen Signed-off-by: Simon L. <szaimen@e.mail.de> Set installLatestMajor when upgrade-to-latest-major button is clicked Agent-Logs-Url: https://github.com/nextcloud/all-in-one/sessions/7b977c85-9b74-4027-a536-152e49a01976 Extract getLatestMajorVersion() to avoid duplicating the version string Agent-Logs-Url: https://github.com/nextcloud/all-in-one/sessions/d5ec921f-8629-4f6e-949a-e8f89f1eb85f Address PR review comments and hardcode updater channel to stable Agent-Logs-Url: https://github.com/nextcloud/all-in-one/sessions/c40941ff-2bf8-4a57-82be-2a0bd22b19a2 Restore sendNotification(), update cron files, extract getPlainStreamingCallback() Agent-Logs-Url: https://github.com/nextcloud/all-in-one/sessions/a5b6cd86-d278-4771-8a11-976c4a862966 Remove getPlainStreamingCallback, unify on getAddToStreamingResponseBody Agent-Logs-Url: https://github.com/nextcloud/all-in-one/sessions/15a4b815-076b-469f-95b2-c61df688a28d Revert "Remove getPlainStreamingCallback, unify on getAddToStreamingResponseBody" This reverts commit 6846c3a99549703121461f910cc26e6c116e0dc4. * Refactor creating and using addToStreamingResponseBody() This way we stick to having one implementation of the function, not three. Signed-off-by: Pablo Zmdl <pablo@nextcloud.com> * Read streamed output line by line, not via buffer This way the code doesn't wait for a buffer to be filled, and we don't need to implement logic ourselves that is provided by a present library already. Signed-off-by: Pablo Zmdl <pablo@nextcloud.com> * Ensure all HTTP requests are proxied, even with streaming When requesting a streamed response, Guzzle apparently doesn't use curl, and thus we have to specify the unix socket proxy differently. We can't specify it when creating the client, though (Guzzle complains). Signed-off-by: Pablo Zmdl <pablo@nextcloud.com> * Fix syntax errors Signed-off-by: Pablo Zmdl <pablo@nextcloud.com> * Remove broken code Signed-off-by: Pablo Zmdl <pablo@nextcloud.com> * Fix readline line from streaming response Signed-off-by: Pablo Zmdl <pablo@nextcloud.com> * Strip ANSI codes from command output before sending it to the browser Signed-off-by: Pablo Zmdl <pablo@nextcloud.com> * Run PHP commands as www-data Signed-off-by: Pablo Zmdl <pablo@nextcloud.com> * Properly compare version numbers Signed-off-by: Pablo Zmdl <pablo@nextcloud.com> * Fix using memory limits from env Signed-off-by: Pablo Zmdl <pablo@nextcloud.com> * Fix return type spec This method always returns a closure, never null. Signed-off-by: Pablo Zmdl <pablo@nextcloud.com> * Use more general return type Signed-off-by: Pablo Zmdl <pablo@nextcloud.com> * Avoid psalm complaint Signed-off-by: Pablo Zmdl <pablo@nextcloud.com> * Fix namespace of return type Signed-off-by: Pablo Zmdl <pablo@nextcloud.com> * Apply suggestion from @szaimen Signed-off-by: Simon L. <szaimen@e.mail.de> --------- Signed-off-by: Pablo Zmdl <pablo@nextcloud.com> Signed-off-by: Simon L. <szaimen@e.mail.de> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Pablo Zmdl <pablo@nextcloud.com> Co-authored-by: Simon L. <szaimen@e.mail.de>
This commit is contained in:
@@ -104,6 +104,7 @@ $app->post('/api/docker/backup-test', AIO\Controller\DockerController::class . '
|
||||
$app->post('/api/docker/restore', AIO\Controller\DockerController::class . ':StartBackupContainerRestore');
|
||||
$app->post('/api/docker/stop', AIO\Controller\DockerController::class . ':StopContainer');
|
||||
$app->post('/api/docker/backup-reset-location', AIO\Controller\DockerController::class . ':DeleteBorgBackupConfig');
|
||||
$app->post('/api/docker/nextcloud-upgrade-to-latest-major', AIO\Controller\DockerController::class . ':RunNextcloudUpgradeToLatestMajor');
|
||||
$app->post('/api/docker/prune', AIO\Controller\DockerController::class . ':SystemPrune');
|
||||
$app->get('/api/docker/logs', AIO\Controller\DockerController::class . ':GetLogs');
|
||||
$app->post('/api/auth/login', AIO\Controller\LoginController::class . ':TryLogin');
|
||||
|
||||
@@ -14,6 +14,7 @@ use Slim\Psr7\NonBufferedBody;
|
||||
|
||||
readonly class DockerController {
|
||||
private const string TOP_CONTAINER = 'nextcloud-aio-apache';
|
||||
private const string LATEST_MAJOR_VERSION = '34';
|
||||
|
||||
public function __construct(
|
||||
private DockerActionManager $dockerActionManager,
|
||||
@@ -221,7 +222,7 @@ readonly class DockerController {
|
||||
}
|
||||
|
||||
if (isset($request->getParsedBody()['install_latest_major'])) {
|
||||
$installLatestMajor = '34';
|
||||
$installLatestMajor = self::LATEST_MAJOR_VERSION;
|
||||
} else {
|
||||
$installLatestMajor = '';
|
||||
}
|
||||
@@ -298,7 +299,7 @@ readonly class DockerController {
|
||||
}
|
||||
|
||||
if ($addToStreamingResponseBody !== null) {
|
||||
$addToStreamingResponseBody($container, "Stopping container");
|
||||
$addToStreamingResponseBody("Stopping container", $container);
|
||||
}
|
||||
|
||||
// Stop itself first and then all the dependencies
|
||||
@@ -333,14 +334,30 @@ readonly class DockerController {
|
||||
return $response->withStatus(201)->withHeader('Location', '.');
|
||||
}
|
||||
|
||||
public function RunNextcloudUpgradeToLatestMajor(Request $request, Response $response, array $args) : Response {
|
||||
$this->configurationManager->installLatestMajor = self::LATEST_MAJOR_VERSION;
|
||||
|
||||
// Get streaming response start and closure
|
||||
$nonbufResp = $this->startStreamingResponse($response);
|
||||
$addToStreamingResponseBody = $this->getAddToStreamingResponseBody($nonbufResp);
|
||||
|
||||
$this->dockerActionManager->RunNextcloudUpgradeToLatestMajor($addToStreamingResponseBody);
|
||||
|
||||
// We automatically reload after 10s so that the output can be read or copied if necessary
|
||||
$addToStreamingResponseBody("Automatically reloading the page after 10s.");
|
||||
sleep(10);
|
||||
|
||||
// End streaming response
|
||||
$this->finalizeStreamingResponse($nonbufResp);
|
||||
return $nonbufResp;
|
||||
}
|
||||
|
||||
public function SystemPrune(Request $request, Response $response, array $args) : Response {
|
||||
// Get streaming response start and closure
|
||||
$nonbufResp = $this->startStreamingResponse($response);
|
||||
|
||||
$body = $nonbufResp->getBody();
|
||||
$addToStreamingResponseBody = function (string $message) use ($body) : void {
|
||||
$body->write("<div>$message</div>");
|
||||
};
|
||||
$addToStreamingResponseBody = $this->getAddToStreamingResponseBody($nonbufResp);
|
||||
|
||||
$this->dockerActionManager->SystemPrune($addToStreamingResponseBody);
|
||||
|
||||
@@ -426,12 +443,17 @@ readonly class DockerController {
|
||||
return $nonbufResp;
|
||||
}
|
||||
|
||||
private function getAddToStreamingResponseBody(Response $nonbufResp) : ?\Closure {
|
||||
private function getAddToStreamingResponseBody(Response $nonbufResp) : \Closure {
|
||||
// Create a closure to pass around to the code, which should to the logging (because it e.g. decides
|
||||
// if it'll actually pull an image), but which should not need to know anything about the
|
||||
// wanted markup or formatting.
|
||||
$addToStreamingResponseBody = function (Container $container, string $message) use ($nonbufResp) : void {
|
||||
$nonbufResp->getBody()->write("<div>{$container->displayName}: {$message}</div>");
|
||||
$addToStreamingResponseBody = function (string $message, ?Container $container = null) use ($nonbufResp) : void {
|
||||
// Strip ANSI codes.
|
||||
$message = preg_replace('/\e[[][A-Za-z0-9];?[0-9]*m?/', '', $message);
|
||||
if ($container) {
|
||||
$message = "{$container->displayName}: {$message}";
|
||||
}
|
||||
$nonbufResp->getBody()->write("<div>" . htmlspecialchars("{$message}", ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') . "</div>");
|
||||
};
|
||||
|
||||
return $addToStreamingResponseBody;
|
||||
|
||||
@@ -12,6 +12,7 @@ use AIO\Data\DataConst;
|
||||
use AIO\Helper\NetworkHelper;
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Exception\RequestException;
|
||||
use GuzzleHttp\Psr7\Utils;
|
||||
use http\Env\Response;
|
||||
|
||||
readonly class DockerActionManager {
|
||||
@@ -48,7 +49,7 @@ readonly class DockerActionManager {
|
||||
public function GetContainerRunningState(Container $container): ContainerState {
|
||||
$url = $this->BuildApiUrl(sprintf('containers/%s/json', urlencode($container->identifier)));
|
||||
try {
|
||||
$response = $this->guzzleClient->get($url);
|
||||
$response = $this->sendHttpRequest('GET', $url);
|
||||
} catch (RequestException $e) {
|
||||
if ($e->getCode() === 404) {
|
||||
return ContainerState::ImageDoesNotExist;
|
||||
@@ -68,7 +69,7 @@ readonly class DockerActionManager {
|
||||
public function GetContainerRestartingState(Container $container): ContainerState {
|
||||
$url = $this->BuildApiUrl(sprintf('containers/%s/json', urlencode($container->identifier)));
|
||||
try {
|
||||
$response = $this->guzzleClient->get($url);
|
||||
$response = $this->sendHttpRequest('GET', $url);
|
||||
} catch (RequestException $e) {
|
||||
if ($e->getCode() === 404) {
|
||||
return ContainerState::ImageDoesNotExist;
|
||||
@@ -138,7 +139,7 @@ readonly class DockerActionManager {
|
||||
public function DeleteContainer(Container $container): void {
|
||||
$url = $this->BuildApiUrl(sprintf('containers/%s?v=true', urlencode($container->identifier)));
|
||||
try {
|
||||
$this->guzzleClient->delete($url);
|
||||
$this->sendHttpRequest('DELETE', $url);
|
||||
} catch (RequestException $e) {
|
||||
if ($e->getCode() !== 404) {
|
||||
throw $e;
|
||||
@@ -155,7 +156,7 @@ readonly class DockerActionManager {
|
||||
// Delete the borg cache volume
|
||||
$url = $this->BuildApiUrl('volumes/nextcloud_aio_backup_cache');
|
||||
try {
|
||||
$this->guzzleClient->delete($url);
|
||||
$this->sendHttpRequest('DELETE', $url);
|
||||
error_log('nextcloud_aio_backup_cache volume deleted successfully.');
|
||||
} catch (RequestException $e) {
|
||||
if ($e->getCode() !== 404) {
|
||||
@@ -174,7 +175,7 @@ readonly class DockerActionManager {
|
||||
urlencode($id),
|
||||
$since
|
||||
));
|
||||
$responseBody = (string)$this->guzzleClient->get($url)->getBody();
|
||||
$responseBody = (string)$this->sendHttpRequest('GET', $url)->getBody();
|
||||
|
||||
$response = "";
|
||||
$separator = "\r\n";
|
||||
@@ -194,9 +195,9 @@ readonly class DockerActionManager {
|
||||
$url = $this->BuildApiUrl(sprintf('containers/%s/start', urlencode($container->identifier)));
|
||||
try {
|
||||
if ($addToStreamingResponseBody !== null) {
|
||||
$addToStreamingResponseBody($container, "Starting container");
|
||||
$addToStreamingResponseBody("Starting container", $container);
|
||||
}
|
||||
$this->guzzleClient->post($url);
|
||||
$this->sendHttpRequest('POST', $url);
|
||||
} catch (RequestException $e) {
|
||||
throw new \Exception("Could not start container " . $container->identifier . ": " . $e->getResponse()?->getBody()->getContents());
|
||||
}
|
||||
@@ -215,7 +216,7 @@ readonly class DockerActionManager {
|
||||
|
||||
$firstChar = substr($volume->name, 0, 1);
|
||||
if (!in_array($firstChar, $forbiddenChars)) {
|
||||
$this->guzzleClient->request(
|
||||
$this->sendHttpRequest(
|
||||
'POST',
|
||||
$url,
|
||||
[
|
||||
@@ -494,7 +495,7 @@ readonly class DockerActionManager {
|
||||
|
||||
$url = $this->BuildApiUrl('containers/create?name=' . $container->identifier);
|
||||
try {
|
||||
$this->guzzleClient->request(
|
||||
$this->sendHttpRequest(
|
||||
'POST',
|
||||
$url,
|
||||
[
|
||||
@@ -551,10 +552,10 @@ readonly class DockerActionManager {
|
||||
$imageIsThere = true;
|
||||
try {
|
||||
if ($addToStreamingResponseBody) {
|
||||
$addToStreamingResponseBody($container, "Pulling image");
|
||||
$addToStreamingResponseBody("Pulling image", $container);
|
||||
}
|
||||
$imageUrl = $this->BuildApiUrl(sprintf('images/%s/json', $encodedImageName));
|
||||
$this->guzzleClient->get($imageUrl)->getBody()->getContents();
|
||||
$this->sendHttpRequest('GET', $imageUrl)->getBody()->getContents();
|
||||
} catch (\Throwable $e) {
|
||||
$imageIsThere = false;
|
||||
}
|
||||
@@ -562,7 +563,7 @@ readonly class DockerActionManager {
|
||||
$maxRetries = 3;
|
||||
for ($attempt = 1; $attempt <= $maxRetries; $attempt++) {
|
||||
try {
|
||||
$this->guzzleClient->post($url);
|
||||
$this->sendHttpRequest('POST', $url);
|
||||
break;
|
||||
} catch (RequestException $e) {
|
||||
$message = "Could not pull image " . $imageName . " (attempt $attempt/$maxRetries): " . $e->getResponse()?->getBody()->getContents();
|
||||
@@ -647,11 +648,11 @@ readonly class DockerActionManager {
|
||||
private function GetRepoDigestsOfContainer(string $containerName): ?array {
|
||||
try {
|
||||
$containerUrl = $this->BuildApiUrl(sprintf('containers/%s/json', $containerName));
|
||||
$containerOutput = json_decode($this->guzzleClient->get($containerUrl)->getBody()->getContents(), true, 512, JSON_THROW_ON_ERROR);
|
||||
$containerOutput = json_decode($this->sendHttpRequest('GET', $containerUrl)->getBody()->getContents(), true, 512, JSON_THROW_ON_ERROR);
|
||||
$imageName = $containerOutput['Image'];
|
||||
|
||||
$imageUrl = $this->BuildApiUrl(sprintf('images/%s/json', $imageName));
|
||||
$imageOutput = json_decode($this->guzzleClient->get($imageUrl)->getBody()->getContents(), true, 512, JSON_THROW_ON_ERROR);
|
||||
$imageOutput = json_decode($this->sendHttpRequest('GET', $imageUrl)->getBody()->getContents(), true, 512, JSON_THROW_ON_ERROR);
|
||||
|
||||
if (!isset($imageOutput['RepoDigests'])) {
|
||||
error_log('RepoDigests is not set of container ' . $containerName);
|
||||
@@ -695,7 +696,7 @@ readonly class DockerActionManager {
|
||||
$containerName = 'nextcloud-aio-mastercontainer';
|
||||
$url = $this->BuildApiUrl(sprintf('containers/%s/json', $containerName));
|
||||
try {
|
||||
$output = json_decode($this->guzzleClient->get($url)->getBody()->getContents(), true, 512, JSON_THROW_ON_ERROR);
|
||||
$output = json_decode($this->sendHttpRequest('GET', $url)->getBody()->getContents(), true, 512, JSON_THROW_ON_ERROR);
|
||||
$imageNameArray = explode(':', $output['Config']['Image']);
|
||||
if (count($imageNameArray) === 2) {
|
||||
$imageName = $imageNameArray[0];
|
||||
@@ -722,7 +723,7 @@ readonly class DockerActionManager {
|
||||
$containerName = 'nextcloud-aio-mastercontainer';
|
||||
$url = $this->BuildApiUrl(sprintf('containers/%s/json', $containerName));
|
||||
try {
|
||||
$output = json_decode($this->guzzleClient->get($url)->getBody()->getContents(), true, 512, JSON_THROW_ON_ERROR);
|
||||
$output = json_decode($this->sendHttpRequest('GET', $url)->getBody()->getContents(), true, 512, JSON_THROW_ON_ERROR);
|
||||
$tagArray = explode(':', $output['Config']['Image']);
|
||||
if (count($tagArray) === 2) {
|
||||
$tag = $tagArray[1];
|
||||
@@ -763,48 +764,69 @@ readonly class DockerActionManager {
|
||||
}
|
||||
|
||||
public function sendNotification(Container $container, string $subject, string $message, string $file = '/notify.sh'): void {
|
||||
if ($this->GetContainerStartingState($container) === ContainerState::Running) {
|
||||
$this->execCommandInContainer($container, ['bash', $file, $subject, $message]);
|
||||
}
|
||||
|
||||
$containerName = $container->identifier;
|
||||
public function execCommandInContainer(Container $container, array $cmd, ?\Closure $outputCallback = null): void {
|
||||
if ($cmd === []) {
|
||||
throw new \InvalidArgumentException('$cmd must not be empty.');
|
||||
}
|
||||
foreach ($cmd as $arg) {
|
||||
if (!is_string($arg) || $arg === '') {
|
||||
throw new \InvalidArgumentException('Every element of $cmd must be a non-empty string.');
|
||||
}
|
||||
}
|
||||
|
||||
// schedule the exec
|
||||
$url = $this->BuildApiUrl(sprintf('containers/%s/exec', urlencode($containerName)));
|
||||
$response = json_decode(
|
||||
$this->guzzleClient->request(
|
||||
'POST',
|
||||
$url,
|
||||
[
|
||||
'json' => [
|
||||
'AttachStdout' => true,
|
||||
'Tty' => true,
|
||||
'Cmd' => [
|
||||
'bash',
|
||||
$file,
|
||||
$subject,
|
||||
$message
|
||||
],
|
||||
],
|
||||
]
|
||||
)->getBody()->getContents(),
|
||||
true,
|
||||
512,
|
||||
JSON_THROW_ON_ERROR,
|
||||
);
|
||||
if ($this->GetContainerStartingState($container) !== ContainerState::Running) {
|
||||
return;
|
||||
}
|
||||
|
||||
$id = $response['Id'];
|
||||
$containerName = $container->identifier;
|
||||
|
||||
// start the exec
|
||||
$url = $this->BuildApiUrl(sprintf('exec/%s/start', $id));
|
||||
$this->guzzleClient->request(
|
||||
// Create exec instance
|
||||
$url = $this->BuildApiUrl(sprintf('containers/%s/exec', urlencode($containerName)));
|
||||
$response = json_decode(
|
||||
$this->sendHttpRequest(
|
||||
'POST',
|
||||
$url,
|
||||
[
|
||||
'json' => [
|
||||
'Detach' => false,
|
||||
'AttachStdout' => true,
|
||||
'AttachStderr' => true,
|
||||
'Tty' => true,
|
||||
'Cmd' => $cmd,
|
||||
],
|
||||
]
|
||||
);
|
||||
)->getBody()->getContents(),
|
||||
true,
|
||||
512,
|
||||
JSON_THROW_ON_ERROR,
|
||||
);
|
||||
|
||||
$execId = $response['Id'];
|
||||
|
||||
// Start exec
|
||||
$url = $this->BuildApiUrl(sprintf('exec/%s/start', $execId));
|
||||
$requestOptions = [
|
||||
'json' => [
|
||||
'Detach' => false,
|
||||
'Tty' => true,
|
||||
],
|
||||
];
|
||||
if ($outputCallback !== null) {
|
||||
$requestOptions['stream'] = true;
|
||||
}
|
||||
|
||||
$startResponse = $this->sendHttpRequest('POST', $url, $requestOptions);
|
||||
|
||||
if ($outputCallback !== null) {
|
||||
$body = $startResponse->getBody();
|
||||
while (!$body->eof()) {
|
||||
$line = rtrim(Utils::readLine($body), "\r");;
|
||||
if ($line !== '') {
|
||||
$outputCallback($line);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -815,7 +837,7 @@ readonly class DockerActionManager {
|
||||
);
|
||||
|
||||
try {
|
||||
$this->guzzleClient->request(
|
||||
$this->sendHttpRequest(
|
||||
'POST',
|
||||
$url,
|
||||
[
|
||||
@@ -836,7 +858,7 @@ readonly class DockerActionManager {
|
||||
if ($createNetwork) {
|
||||
$url = $this->BuildApiUrl('networks/create');
|
||||
try {
|
||||
$this->guzzleClient->request(
|
||||
$this->sendHttpRequest(
|
||||
'POST',
|
||||
$url,
|
||||
[
|
||||
@@ -865,7 +887,7 @@ readonly class DockerActionManager {
|
||||
}
|
||||
|
||||
try {
|
||||
$this->guzzleClient->request(
|
||||
$this->sendHttpRequest(
|
||||
'POST',
|
||||
$url,
|
||||
[
|
||||
@@ -910,7 +932,7 @@ readonly class DockerActionManager {
|
||||
}
|
||||
$url = $this->BuildApiUrl(sprintf('containers/%s/stop?t=%s', urlencode($container->identifier), $maxShutDownTime));
|
||||
try {
|
||||
$this->guzzleClient->post($url);
|
||||
$this->sendHttpRequest('POST', $url);
|
||||
} catch (RequestException $e) {
|
||||
if ($e->getCode() !== 404 && $e->getCode() !== 304) {
|
||||
throw $e;
|
||||
@@ -922,7 +944,7 @@ readonly class DockerActionManager {
|
||||
$containerName = 'nextcloud-aio-borgbackup';
|
||||
$url = $this->BuildApiUrl(sprintf('containers/%s/json', urlencode($containerName)));
|
||||
try {
|
||||
$response = $this->guzzleClient->get($url);
|
||||
$response = $this->sendHttpRequest('GET', $url);
|
||||
} catch (RequestException $e) {
|
||||
if ($e->getCode() === 404) {
|
||||
return -1;
|
||||
@@ -944,7 +966,7 @@ readonly class DockerActionManager {
|
||||
$containerName = 'nextcloud-aio-database';
|
||||
$url = $this->BuildApiUrl(sprintf('containers/%s/json', urlencode($containerName)));
|
||||
try {
|
||||
$response = $this->guzzleClient->get($url);
|
||||
$response = $this->sendHttpRequest('GET', $url);
|
||||
} catch (RequestException $e) {
|
||||
if ($e->getCode() === 404) {
|
||||
return -1;
|
||||
@@ -984,7 +1006,7 @@ readonly class DockerActionManager {
|
||||
$imageName = $imageName . ':' . $this->GetCurrentChannel();
|
||||
try {
|
||||
$imageUrl = $this->BuildApiUrl(sprintf('images/%s/json', $imageName));
|
||||
$imageOutput = json_decode($this->guzzleClient->get($imageUrl)->getBody()->getContents(), true, 512, JSON_THROW_ON_ERROR);
|
||||
$imageOutput = json_decode($this->sendHttpRequest('GET', $imageUrl)->getBody()->getContents(), true, 512, JSON_THROW_ON_ERROR);
|
||||
|
||||
if (!isset($imageOutput['Created'])) {
|
||||
error_log('Created is not set of image ' . $imageName);
|
||||
@@ -1029,6 +1051,11 @@ readonly class DockerActionManager {
|
||||
}
|
||||
}
|
||||
|
||||
public function RunNextcloudUpgradeToLatestMajor(\Closure $addToStreamingResponseBody): void {
|
||||
$container = $this->containerDefinitionFetcher->GetContainerById('nextcloud-aio-nextcloud');
|
||||
$this->execCommandInContainer($container, ['bash', '/upgrade-latest-major.sh'], $addToStreamingResponseBody);
|
||||
}
|
||||
|
||||
public function SystemPrune(?\Closure $addToStreamingResponseBody = null): void {
|
||||
$endpoints = [
|
||||
// Remove stopped containers
|
||||
@@ -1057,7 +1084,7 @@ readonly class DockerActionManager {
|
||||
}
|
||||
|
||||
try {
|
||||
$response = $this->guzzleClient->post($url);
|
||||
$response = $this->sendHttpRequest('POST', $url);
|
||||
if ($addToStreamingResponseBody !== null) {
|
||||
$data = json_decode((string)$response->getBody(), true);
|
||||
$deleted = 0;
|
||||
@@ -1095,4 +1122,12 @@ readonly class DockerActionManager {
|
||||
sleep(10);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function sendHttpRequest(string $httpMethod, string $url, array $requestOptions = []): \Psr\Http\Message\ResponseInterface {
|
||||
if (($requestOptions['stream'] ?? null) === true) {
|
||||
$requestOptions['proxy'] = 'unix:///var/run/docker.sock';
|
||||
}
|
||||
return $this->guzzleClient->request($httpMethod, $url, $requestOptions);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -298,7 +298,12 @@
|
||||
{% if newMajorVersionString != '' and isAnyRunning == true and isApacheStarting != true %}
|
||||
<details>
|
||||
<summary>Note about <strong>Nextcloud Hub {{ newMajorVersionString }}</strong></summary>
|
||||
<p>If you haven't upgraded to Nextcloud Hub {{ newMajorVersionString }} yet and want to do that now, feel free to follow <strong><a target="_blank" href="https://github.com/nextcloud/all-in-one/discussions/8223">this documentation</a></strong></p>
|
||||
<p>If you haven't upgraded to Nextcloud Hub {{ newMajorVersionString }} yet and want to do that now, feel free to click the button below. ⚠️ Warning: make sure to create a backup before clicking the button as the update can go wrong and will leave your instance in a broken state!</p>
|
||||
<form method="POST" action="api/docker/nextcloud-upgrade-to-latest-major" target="overlay-log">
|
||||
<input type="hidden" name="{{csrf.keys.name}}" value="{{csrf.name}}">
|
||||
<input type="hidden" name="{{csrf.keys.value}}" value="{{csrf.value}}">
|
||||
<input type="submit" value="Upgrade to Nextcloud Hub {{ newMajorVersionString }}" data-confirm="Upgrade to Nextcloud Hub {{ newMajorVersionString }}? You should consider creating a backup first." />
|
||||
</form>
|
||||
</details>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
Reference in New Issue
Block a user