Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion app/views/install/installer/js/modules/progress.js
Original file line number Diff line number Diff line change
Expand Up @@ -1074,7 +1074,9 @@
if (existingInstallId) {
resumeInstall(existingInstallId).then((resumed) => {
if (!resumed) {
startFreshInstall();
clearInstallId?.();
clearInstallLock?.();
window.location.href = '/?step=1';
}
});
} else {
Expand Down
73 changes: 73 additions & 0 deletions src/Appwrite/Platform/Installer/Server.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace Appwrite\Platform\Installer;

use Appwrite\Platform\Installer\Http\Installer\Error;
use Appwrite\Platform\Installer\Runtime\Config;
use Appwrite\Platform\Installer\Runtime\State;
use Swoole\Http\Server as SwooleServer;
use Utopia\Http\Adapter\Swoole\Request;
Expand Down Expand Up @@ -136,6 +137,7 @@ private function startSwooleServer(string $host, int $port, ?string $readyFile =

// Register resources for dependency injection into actions
$config = $this->state->buildConfig();
$this->autoDetectUpgrade($config);
$paths = $this->paths;
$state = $this->state;

Expand Down Expand Up @@ -191,6 +193,77 @@ public function getNativeServer(): SwooleServer
$adapter->start();
}

/**
* Auto-detect upgrade mode by checking for existing config files.
* Sets isUpgrade and lockedDatabase on the config when an existing
* installation is found and these values aren't already set.
*/
private function autoDetectUpgrade(Config $config): void
{
if ($config->isUpgrade()) {
return;
}
Comment on lines +203 to +205
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Early return skips lockedDatabase detection when isUpgrade is already set

If isUpgrade is already true (set explicitly in the saved config) but lockedDatabase is null, this early return means the database auto-detection never runs. The lockedDatabase will remain null even though the installation files are present and the database could be inferred.

If there is a scenario where isUpgrade can be persisted as true without a corresponding lockedDatabase value, the installer may behave incorrectly. Consider checking lockedDatabase independently:

private function autoDetectUpgrade(Config $config): void
{
    $basePath = $config->isLocal() ? '/usr/src/code' : (getcwd() ?: '.');
    $composePath = $basePath . '/docker-compose.yml';
    $envPath = $basePath . '/.env';

    if (!file_exists($composePath) && !file_exists($envPath)) {
        return;
    }

    if (!$config->isUpgrade()) {
        $config->setIsUpgrade(true);
    }

    if ($config->getLockedDatabase() !== null) {
        return;
    }

    $database = $this->detectDatabaseFromFiles($composePath, $envPath);
    if ($database !== null) {
        $config->setLockedDatabase($database);
    }
}


$basePath = $config->isLocal() ? '/usr/src/code' : (getcwd() ?: '.');
$composePath = $basePath . '/docker-compose.yml';
$envPath = $basePath . '/.env';

if (!file_exists($composePath) && !file_exists($envPath)) {
return;
}

$config->setIsUpgrade(true);

if ($config->getLockedDatabase() !== null) {
return;
}

$database = $this->detectDatabaseFromFiles($composePath, $envPath);
if ($database !== null) {
$config->setLockedDatabase($database);
}
}

private function detectDatabaseFromFiles(string $composePath, string $envPath): ?string
{
$dbServices = ['mariadb', 'mongodb', 'postgresql'];

$composeData = @file_get_contents($composePath);
if ($composeData !== false) {
if (preg_match_all('/^\s*(?:container_name:\s*appwrite-(\w+)|(\w+):)\s*$/m', $composeData, $matches)) {
$serviceNames = array_filter(array_merge($matches[1], $matches[2]));
foreach ($dbServices as $db) {
if (in_array($db, $serviceNames, true)) {
return $db;
}
}
}
foreach ($dbServices as $db) {
if (preg_match('/^\s*' . preg_quote($db, '/') . ':\s*$/m', $composeData)) {
return $db;
}
}
Comment on lines +241 to +245
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Redundant fallback regex loop

The second foreach loop (lines 241–245) is unreachable in practice. The first preg_match_all regex already includes the alternative (\w+): which will match any YAML key at the start of a line — including mariadb:, mongodb:, and postgresql:. In a valid docker-compose.yml, preg_match_all will almost always return a positive count (e.g. from version:, services:, etc.), enter the if block, and evaluate the same service names against $dbServices.

The second loop can only execute if preg_match_all returns false (a regex engine error) or 0 (no matches at all — essentially impossible in a non-empty compose file). Consider removing it to simplify the logic:

Suggested change
foreach ($dbServices as $db) {
if (preg_match('/^\s*' . preg_quote($db, '/') . ':\s*$/m', $composeData)) {
return $db;
}
}
}
$envData = @file_get_contents($envPath);

}

$envData = @file_get_contents($envPath);
if ($envData !== false) {
if (preg_match('/^_APP_DB_ADAPTER=(.+)$/m', $envData, $m)) {
$adapter = trim($m[1], " \t\n\r\"'");
if (in_array($adapter, $dbServices, true)) {
return $adapter;
}
}
if (preg_match('/^_APP_DB_HOST=(.+)$/m', $envData, $m)) {
$host = trim($m[1], " \t\n\r\"'");
if (in_array($host, $dbServices, true)) {
return $host;
}
}
}

return null;
}

private function removeDockerInstallerContainer(string $container): void
{
$name = escapeshellarg($container);
Expand Down
Loading