From 4e71a54faefb891052910039c2b3f4b8a5aaf14f Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Tue, 31 Mar 2026 14:44:20 +1300 Subject: [PATCH] (fix): send SSE done event before tracking to prevent installer hang --- .../Installer/Http/Installer/Install.php | 40 ++++++++++++------- src/Appwrite/Platform/Tasks/Install.php | 25 +++++++++--- 2 files changed, 45 insertions(+), 20 deletions(-) diff --git a/src/Appwrite/Platform/Installer/Http/Installer/Install.php b/src/Appwrite/Platform/Installer/Http/Installer/Install.php index e29222a703..fa978892d3 100644 --- a/src/Appwrite/Platform/Installer/Http/Installer/Install.php +++ b/src/Appwrite/Platform/Installer/Http/Installer/Install.php @@ -321,6 +321,28 @@ class Install extends Action } }; + $responseSent = false; + $onComplete = function () use ($wantsStream, $swooleResponse, $response, $installId, $state, &$responseSent) { + if ($responseSent) { + return; + } + $responseSent = true; + $state->updateGlobalLock($installId, Server::STATUS_COMPLETED); + if ($wantsStream) { + $this->writeSseEvent($swooleResponse, 'done', ['installId' => $installId, 'success' => true]); + usleep(self::SSE_KEEPALIVE_DELAY_MICROSECONDS); + $swooleResponse->write(": keepalive\n\n"); + usleep(self::SSE_KEEPALIVE_DELAY_MICROSECONDS); + $swooleResponse->end(); + } else { + $response->json([ + 'success' => true, + 'installId' => $installId, + 'message' => 'Installation completed successfully', + ]); + } + }; + $installer->performInstallation( $httpPort ?: $config->getDefaultHttpPort(), $httpsPort ?: $config->getDefaultHttpsPort(), @@ -331,23 +353,11 @@ class Install extends Action $progress, $retryStep, $config->isUpgrade(), - $account + $account, + $onComplete, ); - if ($wantsStream) { - $this->writeSseEvent($swooleResponse, 'done', ['installId' => $installId, 'success' => true]); - usleep(self::SSE_KEEPALIVE_DELAY_MICROSECONDS); - $swooleResponse->write(": keepalive\n\n"); - usleep(self::SSE_KEEPALIVE_DELAY_MICROSECONDS); - $swooleResponse->end(); - } else { - $response->json([ - 'success' => true, - 'installId' => $installId, - 'message' => 'Installation completed successfully', - ]); - } - $state->updateGlobalLock($installId, Server::STATUS_COMPLETED); + $onComplete(); } catch (\Throwable $e) { $this->handleInstallationError($e, $installId, $wantsStream, $response, $swooleResponse, $state); } diff --git a/src/Appwrite/Platform/Tasks/Install.php b/src/Appwrite/Platform/Tasks/Install.php index eab6babc66..70862568b0 100644 --- a/src/Appwrite/Platform/Tasks/Install.php +++ b/src/Appwrite/Platform/Tasks/Install.php @@ -510,7 +510,8 @@ class Install extends Action ?callable $progress = null, ?string $resumeFromStep = null, bool $isUpgrade = false, - array $account = [] + array $account = [], + ?callable $onComplete = null, ): void { $isLocalInstall = $this->isLocalInstall(); $this->applyLocalPaths($isLocalInstall, false); @@ -633,8 +634,20 @@ class Install extends Action $this->createInitialAdminAccount($account, $progress, $apiUrl, $domain); } - // Track installs - $this->trackSelfHostedInstall($input, $isUpgrade, $version, $account); + // Signal completion before tracking so the SSE stream + // finishes and the frontend can redirect immediately. + // Tracking is best-effort and must never block the user. + if ($onComplete) { + try { + $onComplete(); + } catch (\Throwable) { + } + } + + try { + $this->trackSelfHostedInstall($input, $isUpgrade, $version, $account); + } catch (\Throwable) { + } if ($isCLI) { Console::success('Appwrite installed successfully'); @@ -753,7 +766,7 @@ class Install extends Action $name = $account['name'] ?? 'Admin'; $email = $account['email'] ?? 'admin@selfhosted.local'; - $hostIp = gethostbyname($domain); + $hostIp = @gethostbyname($domain); $payload = [ 'action' => $type, @@ -767,7 +780,7 @@ class Install extends Action 'email' => $email, 'domain' => $domain, 'database' => $database, - 'hostIp' => $hostIp !== $domain ? $hostIp : null, + 'hostIp' => ($hostIp !== false && $hostIp !== $domain) ? $hostIp : null, 'os' => php_uname('s') . ' ' . php_uname('r'), 'arch' => php_uname('m'), 'cpus' => ((int) trim((string) \shell_exec('nproc'))) ?: null, @@ -778,6 +791,8 @@ class Install extends Action try { $client = new Client(); $client + ->setConnectTimeout(5000) + ->setTimeout(5000) ->addHeader('Content-Type', 'application/json') ->fetch(self::GROWTH_API_URL . '/analytics', Client::METHOD_POST, $payload); } catch (\Throwable) {