containerDefinitionFetcher->GetContainerById($id); // Start all dependencies first and then itself foreach($container->dependsOn as $dependency) { $this->PerformRecursiveContainerStart($dependency, $pullImage, $addToStreamingResponseBody); } // Don't start if container is already running // This is expected to happen if a container is defined in depends_on of multiple containers if ($container->GetRunningState() === ContainerState::Running) { error_log('Not starting ' . $id . ' because it was already started.'); return; } $this->dockerActionManager->DeleteContainer($container); $this->dockerActionManager->CreateVolumes($container); $this->dockerActionManager->PullImage($container, $pullImage, $addToStreamingResponseBody); $this->dockerActionManager->CreateContainer($container); $this->dockerActionManager->StartContainer($container, $addToStreamingResponseBody); $this->dockerActionManager->ConnectContainerToNetwork($container); } private function PerformRecursiveImagePull(string $id) : void { $container = $this->containerDefinitionFetcher->GetContainerById($id); // Pull all dependencies first and then itself foreach($container->dependsOn as $dependency) { $this->PerformRecursiveImagePull($dependency); } $this->dockerActionManager->PullImage($container, true); } public function PullAllContainerImages(): void { $id = self::TOP_CONTAINER; $this->PerformRecursiveImagePull($id); } public function GetLogs(Request $request, Response $response, array $args) : Response { $requestParams = $request->getQueryParams(); $id = ''; if (isset($requestParams['id']) && is_string($requestParams['id'])) { $id = $requestParams['id']; } if (str_starts_with($id, 'nextcloud-aio-')) { $since = $this->getTimestampForDockerLogsApiSince($requestParams['since'] ?? ''); $logs = $this->dockerActionManager->GetLogs($id, $since); } else { $logs = 'Container not found.'; } $body = $response->getBody(); $body->write($logs); return $response ->withStatus(200) ->withHeader('Content-Type', 'text/plain; charset=utf-8') ->withHeader('Content-Disposition', 'inline'); } public function StartBackupContainerBackup(Request $request, Response $response, array $args) : Response { // Get streaming response start and closure $nonbufResp = $this->startStreamingResponse($response); $addToStreamingResponseBody = $this->getAddToStreamingResponseBody($nonbufResp); $forceStopNextcloud = true; $this->startBackup($forceStopNextcloud, $addToStreamingResponseBody); // End streaming response $this->finalizeStreamingResponse($nonbufResp); return $nonbufResp; } public function startBackup(bool $forceStopNextcloud = false, ?\Closure $addToStreamingResponseBody = null) : void { $this->configurationManager->backupMode = 'backup'; $id = self::TOP_CONTAINER; $this->PerformRecursiveContainerStop($id, $forceStopNextcloud, $addToStreamingResponseBody); $id = 'nextcloud-aio-borgbackup'; $this->PerformRecursiveContainerStart($id, true, $addToStreamingResponseBody); } public function StartBackupContainerCheck(Request $request, Response $response, array $args) : Response { // Get streaming response start and closure $nonbufResp = $this->startStreamingResponse($response); $addToStreamingResponseBody = $this->getAddToStreamingResponseBody($nonbufResp); $this->checkBackup($addToStreamingResponseBody); // End streaming response $this->finalizeStreamingResponse($nonbufResp); return $nonbufResp; } public function StartBackupContainerList(Request $request, Response $response, array $args) : Response { // Get streaming response start and closure $nonbufResp = $this->startStreamingResponse($response); $addToStreamingResponseBody = $this->getAddToStreamingResponseBody($nonbufResp); $this->listBackup($addToStreamingResponseBody); // End streaming response $this->finalizeStreamingResponse($nonbufResp); return $nonbufResp; } public function checkBackup(?\Closure $addToStreamingResponseBody = null) : void { $this->configurationManager->backupMode = 'check'; $id = 'nextcloud-aio-borgbackup'; $this->PerformRecursiveContainerStart($id, true, $addToStreamingResponseBody); } private function listBackup(?\Closure $addToStreamingResponseBody = null) : void { $this->configurationManager->backupMode = 'list'; $id = 'nextcloud-aio-borgbackup'; $this->PerformRecursiveContainerStart($id, true, $addToStreamingResponseBody); } public function StartBackupContainerRestore(Request $request, Response $response, array $args) : Response { $this->configurationManager->startTransaction(); $this->configurationManager->backupMode = 'restore'; $this->configurationManager->selectedRestoreTime = $request->getParsedBody()['selected_restore_time'] ?? ''; $this->configurationManager->restoreExcludePreviews = isset($request->getParsedBody()['restore-exclude-previews']); $this->configurationManager->commitTransaction(); // Get streaming response start and closure $nonbufResp = $this->startStreamingResponse($response); $addToStreamingResponseBody = $this->getAddToStreamingResponseBody($nonbufResp); $id = self::TOP_CONTAINER; $forceStopNextcloud = true; $this->PerformRecursiveContainerStop($id, $forceStopNextcloud, $addToStreamingResponseBody); $id = 'nextcloud-aio-borgbackup'; $this->PerformRecursiveContainerStart($id, true, $addToStreamingResponseBody); // End streaming response $this->finalizeStreamingResponse($nonbufResp); return $nonbufResp; } public function StartBackupContainerCheckRepair(Request $request, Response $response, array $args) : Response { $this->configurationManager->backupMode = 'check-repair'; // Get streaming response start and closure $nonbufResp = $this->startStreamingResponse($response); $addToStreamingResponseBody = $this->getAddToStreamingResponseBody($nonbufResp); $id = 'nextcloud-aio-borgbackup'; $this->PerformRecursiveContainerStart($id, true, $addToStreamingResponseBody); // Restore to backup check which is needed to make the UI logic work correctly $this->configurationManager->backupMode = 'check'; // End streaming response $this->finalizeStreamingResponse($nonbufResp); return $nonbufResp; } public function StartBackupContainerTest(Request $request, Response $response, array $args) : Response { $this->configurationManager->startTransaction(); $this->configurationManager->backupMode = 'test'; $this->configurationManager->instanceRestoreAttempt = false; $this->configurationManager->commitTransaction(); // Get streaming response start and closure $nonbufResp = $this->startStreamingResponse($response); $addToStreamingResponseBody = $this->getAddToStreamingResponseBody($nonbufResp); $id = self::TOP_CONTAINER; $this->PerformRecursiveContainerStop($id, true, $addToStreamingResponseBody); $id = 'nextcloud-aio-borgbackup'; $this->PerformRecursiveContainerStart($id, true, $addToStreamingResponseBody); // End streaming response $this->finalizeStreamingResponse($nonbufResp); return $nonbufResp; } public function StartContainer(Request $request, Response $response, array $args) : Response { $uri = $request->getUri(); $host = $uri->getHost(); $port = $uri->getPort(); $path = $request->getParsedBody()['base_path'] ?? ''; if ($port === 8000) { error_log('The AIO_URL-port was discovered to be 8000 which is not expected. It is now set to 443.'); $port = 443; } if (isset($request->getParsedBody()['install_latest_major'])) { $installLatestMajor = '33'; } else { $installLatestMajor = ''; } $this->configurationManager->startTransaction(); $this->configurationManager->installLatestMajor = $installLatestMajor; // set AIO_URL $this->configurationManager->aioUrl = $host . ':' . (string)$port . $path; // set wasStartButtonClicked $this->configurationManager->wasStartButtonClicked = true; $this->configurationManager->commitTransaction(); // Do not pull container images in case 'bypass_container_update' is set via url params // Needed for local testing $pullImage = !isset($request->getParsedBody()['bypass_container_update']); if ($pullImage === false) { error_log('WARNING: Not pulling container images. Instead, using local ones.'); } // Get streaming response start and closure $nonbufResp = $this->startStreamingResponse($response); $addToStreamingResponseBody = $this->getAddToStreamingResponseBody($nonbufResp); // Start container $this->startTopContainer($pullImage, $addToStreamingResponseBody); // Clear apcu cache in order to check if container updates are available // Temporarily disabled as it leads much faster to docker rate limits // apcu_clear_cache(); // End streaming response $this->finalizeStreamingResponse($nonbufResp); return $nonbufResp; } public function startTopContainer(bool $pullImage, ?\Closure $addToStreamingResponseBody = null) : void { $this->configurationManager->aioToken = bin2hex(random_bytes(24)); // Stop domaincheck since apache would not be able to start otherwise $this->StopDomaincheckContainer(); $id = self::TOP_CONTAINER; $this->PerformRecursiveContainerStart($id, $pullImage, $addToStreamingResponseBody); } public function StartWatchtowerContainer(Request $request, Response $response, array $args) : Response { // Get streaming response start and closure $nonbufResp = $this->startStreamingResponse($response); $addToStreamingResponseBody = $this->getAddToStreamingResponseBody($nonbufResp); $this->startWatchtower($addToStreamingResponseBody); // End streaming response $this->finalizeStreamingResponse($nonbufResp); return $nonbufResp; } public function startWatchtower(?\Closure $addToStreamingResponseBody = null) : void { $id = 'nextcloud-aio-watchtower'; $this->PerformRecursiveContainerStart($id, true, $addToStreamingResponseBody); } private function PerformRecursiveContainerStop(string $id, bool $forceStopNextcloud = false, ?\Closure $addToStreamingResponseBody = null) : void { $container = $this->containerDefinitionFetcher->GetContainerById($id); // This is a hack but no better solution was found for the meantime // Stop Collabora first to make sure it force-saves // See https://github.com/nextcloud/richdocuments/issues/3799 if ($id === self::TOP_CONTAINER && $this->configurationManager->isCollaboraEnabled) { $this->PerformRecursiveContainerStop('nextcloud-aio-collabora', false, $addToStreamingResponseBody); } if ($addToStreamingResponseBody !== null) { $addToStreamingResponseBody($container, "Stopping container"); } // Stop itself first and then all the dependencies if ($id !== 'nextcloud-aio-nextcloud') { $this->dockerActionManager->StopContainer($container); } else { // We want to stop the Nextcloud container after 10s and not wait for the configured stop_grace_period $this->dockerActionManager->StopContainer($container, $forceStopNextcloud); } foreach($container->dependsOn as $dependency) { $this->PerformRecursiveContainerStop($dependency, $forceStopNextcloud, $addToStreamingResponseBody); } } public function StopContainer(Request $request, Response $response, array $args) : Response { // Get streaming response start and closure $nonbufResp = $this->startStreamingResponse($response); $addToStreamingResponseBody = $this->getAddToStreamingResponseBody($nonbufResp); $id = self::TOP_CONTAINER; $forceStopNextcloud = true; $this->PerformRecursiveContainerStop($id, $forceStopNextcloud, $addToStreamingResponseBody); // End streaming response $this->finalizeStreamingResponse($nonbufResp); return $nonbufResp; } public function DeleteBorgBackupConfig(Request $request, Response $response, array $args) : Response { $this->dockerActionManager->deleteBorgBackupConfig(); return $response->withStatus(201)->withHeader('Location', '.'); } 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("