From cfdf5bdd1f1e1ef0d10bf65b3460f674b52eed0a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 23 Apr 2026 19:31:01 +0000 Subject: [PATCH] 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 Co-authored-by: szaimen <42591237+szaimen@users.noreply.github.com> --- php/src/Cron/BackupNotification.php | 4 +- php/src/Cron/CheckFreeDiskSpace.php | 2 +- php/src/Cron/OutdatedNotification.php | 2 +- php/src/Cron/UpdateNotification.php | 4 +- php/src/Docker/DockerActionManager.php | 143 +++++++++---------------- 5 files changed, 59 insertions(+), 96 deletions(-) diff --git a/php/src/Cron/BackupNotification.php b/php/src/Cron/BackupNotification.php index 6fbab65f..d641ee48 100644 --- a/php/src/Cron/BackupNotification.php +++ b/php/src/Cron/BackupNotification.php @@ -24,10 +24,10 @@ if ($backupExitCode === 0) { 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 1b5d2d64..6612487f 100644 --- a/php/src/Cron/CheckFreeDiskSpace.php +++ b/php/src/Cron/CheckFreeDiskSpace.php @@ -22,5 +22,5 @@ $nextcloudContainer = $containerDefinitionFetcher->GetContainerById($id); $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 628f0924..9a844af8 100644 --- a/php/src/Cron/OutdatedNotification.php +++ b/php/src/Cron/OutdatedNotification.php @@ -21,6 +21,6 @@ $nextcloudContainer = $containerDefinitionFetcher->GetContainerById($id); $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 2c12e2f4..ef3d0283 100644 --- a/php/src/Cron/UpdateNotification.php +++ b/php/src/Cron/UpdateNotification.php @@ -22,9 +22,9 @@ $isMastercontainerUpdateAvailable = $dockerActionManager->IsMastercontainerUpdat $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 b7f7d9f9..c2148fe8 100644 --- a/php/src/Docker/DockerActionManager.php +++ b/php/src/Docker/DockerActionManager.php @@ -760,49 +760,67 @@ readonly class DockerActionManager { return true; } - public function sendNotification(Container $container, string $subject, string $message, string $file = '/notify.sh'): void { - if ($this->GetContainerStartingState($container) === ContainerState::Running) { + public function execCommandInContainer(Container $container, array $cmd, ?\Closure $outputCallback = null): void { + if ($this->GetContainerStartingState($container) !== ContainerState::Running) { + return; + } - $containerName = $container->identifier; + $containerName = $container->identifier; - // 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, - ); - - $id = $response['Id']; - - // 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)); + } } } @@ -1028,63 +1046,8 @@ readonly class DockerActionManager { } public function RunNextcloudUpgradeToLatestMajor(\Closure $addToStreamingResponseBody): void { - $containerName = 'nextcloud-aio-nextcloud'; - - // Create exec instance - $url = $this->BuildApiUrl(sprintf('containers/%s/exec', urlencode($containerName))); - $response = json_decode( - $this->guzzleClient->request( - 'POST', - $url, - [ - 'json' => [ - 'AttachStdout' => true, - 'AttachStderr' => true, - 'Tty' => true, - 'Cmd' => ['bash', '/upgrade-latest-major.sh'], - ], - ] - )->getBody()->getContents(), - true, - 512, - JSON_THROW_ON_ERROR, - ); - - $execId = $response['Id']; - - // Start exec and stream output - $url = $this->BuildApiUrl(sprintf('exec/%s/start', $execId)); - $streamResponse = $this->guzzleClient->request( - 'POST', - $url, - [ - 'stream' => true, - 'json' => [ - 'Detach' => false, - 'Tty' => true, - ], - ] - ); - - $body = $streamResponse->getBody(); - $buffer = ''; - while (!$body->eof()) { - $chunk = $body->read(1024); - $buffer .= $chunk; - // Flush complete lines - while (($pos = strpos($buffer, "\n")) !== false) { - $line = substr($buffer, 0, $pos); - $buffer = substr($buffer, $pos + 1); - $line = rtrim($line, "\r"); - if ($line !== '') { - $addToStreamingResponseBody($line); - } - } - } - // Flush any remaining output - if (trim($buffer) !== '') { - $addToStreamingResponseBody(trim($buffer)); - } + $container = $this->containerDefinitionFetcher->GetContainerById('nextcloud-aio-nextcloud'); + $this->execCommandInContainer($container, ['bash', '/upgrade-latest-major.sh'], $addToStreamingResponseBody); } public function SystemPrune(?\Closure $addToStreamingResponseBody = null): void {