diff --git a/Containers/nextcloud/entrypoint.sh b/Containers/nextcloud/entrypoint.sh index 3c6f2c30ba3..530e90e65b3 100644 --- a/Containers/nextcloud/entrypoint.sh +++ b/Containers/nextcloud/entrypoint.sh @@ -401,41 +401,10 @@ EOF # AIO update to latest start # Do not remove or change this line! if [ "$INSTALL_LATEST_MAJOR" = yes ]; then - php /var/www/html/occ config:system:set updatedirectory --value="/nc-updater" - INSTALLED_AT="$(php /var/www/html/occ config:app:get core installedat)" - if [ -n "${INSTALLED_AT}" ]; then - # Set the installdat to 00 which will allow to skip staging and install the next major directly - # shellcheck disable=SC2001 - INSTALLED_AT="$(echo "${INSTALLED_AT}" | sed "s|[0-9][0-9]$|00|")" - php /var/www/html/occ config:app:set core installedat --value="${INSTALLED_AT}" - fi - php /var/www/html/updater/updater.phar --no-interaction --no-backup - if ! php /var/www/html/occ -V || php /var/www/html/occ status | grep maintenance | grep -q 'true'; then - echo "Installation of Nextcloud failed!" - touch "$NEXTCLOUD_DATA_DIR/install.failed" + if ! bash /upgrade-latest-major.sh; then + echo "Upgrade to latest major version failed! Check the output above for details." exit 1 fi - # shellcheck disable=SC2016 - installed_version="$(php -r 'require "/var/www/html/version.php"; echo implode(".", $OC_Version);')" - INSTALLED_MAJOR="${installed_version%%.*}" - IMAGE_MAJOR="${image_version%%.*}" - # If a valid upgrade path, trigger the Nextcloud built-in Updater - if ! [ "$INSTALLED_MAJOR" -gt "$IMAGE_MAJOR" ]; then - php /var/www/html/updater/updater.phar --no-interaction --no-backup - if ! php /var/www/html/occ -V || php /var/www/html/occ status | grep maintenance | grep -q 'true'; then - echo "Installation of Nextcloud failed!" - # TODO: Add a hint here about what to do / where to look / updater.log? - touch "$NEXTCLOUD_DATA_DIR/install.failed" - exit 1 - fi - # shellcheck disable=SC2016 - installed_version="$(php -r 'require "/var/www/html/version.php"; echo implode(".", $OC_Version);')" - fi - php /var/www/html/occ config:system:set updatechecker --type=bool --value=true - php /var/www/html/occ app:enable nextcloud-aio --force - php /var/www/html/occ db:add-missing-columns - php /var/www/html/occ db:add-missing-primary-keys - yes | php /var/www/html/occ db:convert-filecache-bigint fi # AIO update to latest end # Do not remove or change this line! diff --git a/Containers/nextcloud/upgrade-latest-major.sh b/Containers/nextcloud/upgrade-latest-major.sh new file mode 100644 index 00000000000..3729e2a4bf0 --- /dev/null +++ b/Containers/nextcloud/upgrade-latest-major.sh @@ -0,0 +1,40 @@ +#!/bin/bash + +# shellcheck disable=SC2016 +image_version="$(php -r 'require "/var/www/html/version.php"; echo implode(".", $OC_Version);')" +IMAGE_MAJOR="${image_version%%.*}" + +php /var/www/html/occ config:system:set updatedirectory --value="/nc-updater" +INSTALLED_AT="$(php /var/www/html/occ config:app:get core installedat)" +if [ -n "${INSTALLED_AT}" ]; then + # Set the installedat to 00 which will allow to skip staging and install the next major directly + # shellcheck disable=SC2001 + INSTALLED_AT="$(echo "${INSTALLED_AT}" | sed "s|[0-9][0-9]$|00|")" + php /var/www/html/occ config:app:set core installedat --value="${INSTALLED_AT}" +fi +php /var/www/html/updater/updater.phar --no-interaction --no-backup +if ! php /var/www/html/occ -V || php /var/www/html/occ status | grep maintenance | grep -q 'true'; then + echo "Installation of Nextcloud failed!" + touch "$NEXTCLOUD_DATA_DIR/install.failed" + exit 1 +fi +# shellcheck disable=SC2016 +installed_version="$(php -r 'require "/var/www/html/version.php"; echo implode(".", $OC_Version);')" +INSTALLED_MAJOR="${installed_version%%.*}" +# If a valid upgrade path, trigger the Nextcloud built-in Updater +if ! [ "$INSTALLED_MAJOR" -gt "$IMAGE_MAJOR" ]; then + php /var/www/html/updater/updater.phar --no-interaction --no-backup + if ! php /var/www/html/occ -V || php /var/www/html/occ status | grep maintenance | grep -q 'true'; then + echo "Installation of Nextcloud failed!" + # TODO: Add a hint here about what to do / where to look / updater.log? + touch "$NEXTCLOUD_DATA_DIR/install.failed" + exit 1 + fi + # shellcheck disable=SC2016 + installed_version="$(php -r 'require "/var/www/html/version.php"; echo implode(".", $OC_Version);')" +fi +php /var/www/html/occ config:system:set updatechecker --type=bool --value=true +php /var/www/html/occ app:enable nextcloud-aio --force +php /var/www/html/occ db:add-missing-columns +php /var/www/html/occ db:add-missing-primary-keys +yes | php /var/www/html/occ db:convert-filecache-bigint diff --git a/php/public/index.php b/php/public/index.php index 5d706c2d40a..71b0c40004c 100644 --- a/php/public/index.php +++ b/php/public/index.php @@ -103,6 +103,7 @@ $app->post('/api/docker/backup-test', AIO\Controller\DockerController::class . ':StartBackupContainerTest'); $app->post('/api/docker/restore', AIO\Controller\DockerController::class . ':StartBackupContainerRestore'); $app->post('/api/docker/stop', AIO\Controller\DockerController::class . ':StopContainer'); +$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'); diff --git a/php/src/Controller/DockerController.php b/php/src/Controller/DockerController.php index 835ed6d58dc..f515dd3cb25 100644 --- a/php/src/Controller/DockerController.php +++ b/php/src/Controller/DockerController.php @@ -15,6 +15,10 @@ readonly class DockerController { private const string TOP_CONTAINER = 'nextcloud-aio-apache'; + private function getLatestMajorVersion(): string { + return '33'; + } + public function __construct( private DockerActionManager $dockerActionManager, private ContainerDefinitionFetcher $containerDefinitionFetcher, @@ -221,7 +225,7 @@ public function StartContainer(Request $request, Response $response, array $args } if (isset($request->getParsedBody()['install_latest_major'])) { - $installLatestMajor = '33'; + $installLatestMajor = $this->getLatestMajorVersion(); } else { $installLatestMajor = ''; } @@ -328,6 +332,23 @@ public function StopContainer(Request $request, Response $response, array $args) return $nonbufResp; } + public function RunNextcloudUpgradeToLatestMajor(Request $request, Response $response, array $args) : Response { + $this->configurationManager->installLatestMajor = $this->getLatestMajorVersion(); + + // Get streaming response start and closure + $nonbufResp = $this->startStreamingResponse($response); + $body = $nonbufResp->getBody(); + $addToStreamingResponseBody = function (string $message) use ($body) : void { + $body->write("
" . htmlspecialchars($message, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') . "
"); + }; + + $this->dockerActionManager->RunNextcloudUpgradeToLatestMajor($addToStreamingResponseBody); + + // 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); diff --git a/php/src/Cron/BackupNotification.php b/php/src/Cron/BackupNotification.php index 6fbab65f383..d641ee48e45 100644 --- a/php/src/Cron/BackupNotification.php +++ b/php/src/Cron/BackupNotification.php @@ -24,10 +24,10 @@ if (getenv('SEND_SUCCESS_NOTIFICATIONS') === "0") { error_log("Daily backup successful! Only logging successful backup and not sending backup notification since that has been disabled! You can get further info by looking at the backup logs in the AIO interface."); } else { - $dockerActionManager->sendNotification($nextcloudContainer, 'Daily backup successful!', 'You can get further info by looking at the backup logs in the AIO interface.'); + $dockerActionManager->execCommandInContainer($nextcloudContainer, ['bash', '/notify.sh', 'Daily backup successful!', 'You can get further info by looking at the backup logs in the AIO interface.']); } } if ($backupExitCode > 0) { - $dockerActionManager->sendNotification($nextcloudContainer, 'Daily backup failed!', 'You can get further info by looking at the backup logs in the AIO interface.'); + $dockerActionManager->execCommandInContainer($nextcloudContainer, ['bash', '/notify.sh', 'Daily backup failed!', 'You can get further info by looking at the backup logs in the AIO interface.']); } diff --git a/php/src/Cron/CheckFreeDiskSpace.php b/php/src/Cron/CheckFreeDiskSpace.php index 1b5d2d64b44..6612487f47a 100644 --- a/php/src/Cron/CheckFreeDiskSpace.php +++ b/php/src/Cron/CheckFreeDiskSpace.php @@ -22,5 +22,5 @@ $df = disk_free_space(DataConst::GetDataDirectory()); if ($df !== false && (int)$df < 1024 * 1024 * 1024 * 5) { error_log("The drive that hosts the mastercontainer volume has less than 5 GB free space. Container updates and backups might not succeed due to that!"); - $dockerActionManager->sendNotification($nextcloudContainer, 'Low on space!', 'The drive that hosts the mastercontainer volume has less than 5 GB free space. Container updates and backups might not succeed due to that!'); + $dockerActionManager->execCommandInContainer($nextcloudContainer, ['bash', '/notify.sh', 'Low on space!', 'The drive that hosts the mastercontainer volume has less than 5 GB free space. Container updates and backups might not succeed due to that!']); } diff --git a/php/src/Cron/OutdatedNotification.php b/php/src/Cron/OutdatedNotification.php index 628f09244a7..9a844af8120 100644 --- a/php/src/Cron/OutdatedNotification.php +++ b/php/src/Cron/OutdatedNotification.php @@ -21,6 +21,6 @@ $isNextcloudImageOutdated = $dockerActionManager->isNextcloudImageOutdated(); if ($isNextcloudImageOutdated === true) { - $dockerActionManager->sendNotification($nextcloudContainer, 'AIO is outdated!', 'Please open the AIO interface or ask an administrator to update it. If you do not want to do it manually each time, you can enable the daily backup feature from the AIO interface which automatically updates all containers.', '/notify-all.sh'); + $dockerActionManager->execCommandInContainer($nextcloudContainer, ['bash', '/notify-all.sh', 'AIO is outdated!', 'Please open the AIO interface or ask an administrator to update it. If you do not want to do it manually each time, you can enable the daily backup feature from the AIO interface which automatically updates all containers.']); } diff --git a/php/src/Cron/UpdateNotification.php b/php/src/Cron/UpdateNotification.php index 2c12e2f488f..ef3d028350f 100644 --- a/php/src/Cron/UpdateNotification.php +++ b/php/src/Cron/UpdateNotification.php @@ -22,9 +22,9 @@ $isAnyUpdateAvailable = $dockerActionManager->isAnyUpdateAvailable(); if ($isMastercontainerUpdateAvailable === true) { - $dockerActionManager->sendNotification($nextcloudContainer, 'Mastercontainer update available!', 'Please open your AIO interface to update it. If you do not want to do it manually each time, you can enable the daily backup feature from the AIO interface which also automatically updates the mastercontainer.'); + $dockerActionManager->execCommandInContainer($nextcloudContainer, ['bash', '/notify.sh', 'Mastercontainer update available!', 'Please open your AIO interface to update it. If you do not want to do it manually each time, you can enable the daily backup feature from the AIO interface which also automatically updates the mastercontainer.']); } if ($isAnyUpdateAvailable === true) { - $dockerActionManager->sendNotification($nextcloudContainer, 'Container updates available!', 'Please open your AIO interface to update them. If you do not want to do it manually each time, you can enable the daily backup feature from the AIO interface which also automatically updates your containers and your Nextcloud apps.'); + $dockerActionManager->execCommandInContainer($nextcloudContainer, ['bash', '/notify.sh', 'Container updates available!', 'Please open your AIO interface to update them. If you do not want to do it manually each time, you can enable the daily backup feature from the AIO interface which also automatically updates your containers and your Nextcloud apps.']); } diff --git a/php/src/Docker/DockerActionManager.php b/php/src/Docker/DockerActionManager.php index 940814fe8f5..3f0dc51d886 100644 --- a/php/src/Docker/DockerActionManager.php +++ b/php/src/Docker/DockerActionManager.php @@ -739,49 +739,76 @@ public function IsMastercontainerUpdateAvailable(): bool { return true; } - public function sendNotification(Container $container, string $subject, string $message, string $file = '/notify.sh'): void { - if ($this->GetContainerStartingState($container) === ContainerState::Running) { - - $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)); + // Create exec instance + $url = $this->BuildApiUrl(sprintf('containers/%s/exec', urlencode($containerName))); + $response = json_decode( $this->guzzleClient->request( '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->guzzleClient->request('POST', $url, $requestOptions); + + if ($outputCallback !== null) { + $body = $startResponse->getBody(); + $buffer = ''; + while (!$body->eof()) { + $chunk = $body->read(1024); + $buffer .= $chunk; + while (($pos = strpos($buffer, "\n")) !== false) { + $line = substr($buffer, 0, $pos); + $buffer = substr($buffer, $pos + 1); + $line = rtrim($line, "\r"); + if ($line !== '') { + $outputCallback($line); + } + } + } + if (trim($buffer) !== '') { + $outputCallback(trim($buffer)); + } } } @@ -1006,6 +1033,11 @@ public function GetLatestDigestOfTag(string $imageName, string $tag): ?string { } } + 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 diff --git a/php/templates/containers.twig b/php/templates/containers.twig index adfe3161b6a..8c817b15b44 100644 --- a/php/templates/containers.twig +++ b/php/templates/containers.twig @@ -298,7 +298,12 @@ {% if newMajorVersionString != '' and isAnyRunning == true and isApacheStarting != true %}
Note about Nextcloud Hub {{ newMajorVersionString }} -

If you haven't upgraded to Nextcloud Hub {{ newMajorVersionString }} yet and want to do that now, feel free to follow this documentation

+

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!

+
+ + + +
{% endif %} {% endif %}