diff --git a/lib/OC.php b/lib/OC.php index c7072df64b88d..c1810d8fcb56d 100644 --- a/lib/OC.php +++ b/lib/OC.php @@ -1128,8 +1128,17 @@ public static function handleRequest(): void { if ($requestPath === '/heartbeat') { return; } + $serveAppApiDuringMaintenance = false; if (substr($requestPath, -3) !== '.js') { // we need these files during the upgrade - self::checkMaintenanceMode($systemConfig); + if (((bool)$systemConfig->getValue('maintenance', false)) && !\OCP\Util::needUpgrade() + && ($requestPath === '/apps/app_api' || str_starts_with($requestPath, '/apps/app_api/')) + && Server::get(\OCP\App\IAppManager::class)->isEnabledForAnyone('app_api')) { + // Keep serving ExApp traffic (HaRP metadata) while the instance is in maintenance mode + $serveAppApiDuringMaintenance = true; + } + if (!$serveAppApiDuringMaintenance) { + self::checkMaintenanceMode($systemConfig); + } if (\OCP\Util::needUpgrade()) { if (function_exists('opcache_reset')) { @@ -1193,6 +1202,10 @@ public static function handleRequest(): void { $appManager->loadApps(['filesystem', 'logging']); $appManager->loadApps(); } + if ($serveAppApiDuringMaintenance) { + // loadApps() above is a no-op during maintenance, load app_api explicitly + $appManager->loadApp('app_api'); + } Server::get(\OC\Route\Router::class)->match($request->getRawPathInfo()); return; } catch (Symfony\Component\Routing\Exception\ResourceNotFoundException $e) { diff --git a/lib/private/Console/Application.php b/lib/private/Console/Application.php index 2aaa6f21baf13..e18dcc5ff8ef0 100644 --- a/lib/private/Console/Application.php +++ b/lib/private/Console/Application.php @@ -87,6 +87,22 @@ public function loadCommands( if (Util::needUpgrade()) { throw new NeedsUpdateException(); } elseif ($this->config->getSystemValueBool('maintenance')) { + if ($this->appManager->isEnabledForAnyone('app_api')) { + // AppAPI must stay usable during maintenance mode; + // loading commands from register_command.php is intentionally skipped. + $this->appManager->loadApp('app_api'); + $info = $this->appManager->getAppInfo('app_api'); + if (isset($info['commands'])) { + try { + $this->loadCommandsFromInfoXml($info['commands']); + } catch (\Throwable $e) { + $output->writeln('' . $e->getMessage() . ''); + $this->logger->error($e->getMessage(), [ + 'exception' => $e, + ]); + } + } + } $this->writeMaintenanceModeInfo($input, $output); } else { $this->appManager->loadApps(); @@ -161,8 +177,13 @@ private function writeMaintenanceModeInfo(InputInterface $input, ConsoleOutputIn && $input->getArgument('command') !== 'maintenance:mode' && $input->getArgument('command') !== 'status') { $errOutput = $output->getErrorOutput(); - $errOutput->writeln('Nextcloud is in maintenance mode, no apps are loaded.'); - $errOutput->writeln('Commands provided by apps are unavailable.'); + if ($this->appManager->isEnabledForAnyone('app_api')) { + $errOutput->writeln('Nextcloud is in maintenance mode, only AppAPI commands are loaded.'); + $errOutput->writeln('Commands provided by other apps are unavailable.'); + } else { + $errOutput->writeln('Nextcloud is in maintenance mode, no apps are loaded.'); + $errOutput->writeln('Commands provided by apps are unavailable.'); + } } } diff --git a/ocs/v1.php b/ocs/v1.php index 5c4d125f9eb84..f97e69980f5c1 100644 --- a/ocs/v1.php +++ b/ocs/v1.php @@ -30,7 +30,18 @@ $request = Server::get(IRequest::class); -if ((Util::needUpgrade() || Server::get(IConfig::class)->getSystemValueBool('maintenance')) && $request->getPathInfo() !== '/core/update') { +$serveAppApiDuringMaintenance = false; +if (!Util::needUpgrade() && Server::get(IConfig::class)->getSystemValueBool('maintenance')) { + $pathInfo = $request->getPathInfo(); + // AppAPI must keep serving HaRP traffic (signed OCS calls and ExApp callbacks) + $serveAppApiDuringMaintenance + = ($pathInfo === '/apps/app_api' || str_starts_with($pathInfo, '/apps/app_api/')) + && Server::get(IAppManager::class)->isEnabledForAnyone('app_api'); +} + +if ((Util::needUpgrade() + || (Server::get(IConfig::class)->getSystemValueBool('maintenance') && !$serveAppApiDuringMaintenance)) + && $request->getPathInfo() !== '/core/update') { // since the behavior of apps or remotes are unpredictable during // an upgrade, return a 503 directly ApiHelper::respond(503, 'Service unavailable', ['X-Nextcloud-Maintenance-Mode' => '1'], 503); @@ -49,6 +60,10 @@ $request->throwDecodingExceptionIfAny(); if ($request->getPathInfo() !== '/core/update') { + if ($serveAppApiDuringMaintenance) { + // loadApps() below is a no-op during maintenance, load app_api explicitly + $appManager->loadApp('app_api'); + } // load all apps to get all api routes properly setup // FIXME: this should ideally appear after handleLogin but will cause // side effects in existing apps