mirror of
https://github.com/appwrite/appwrite.git
synced 2026-05-26 13:51:13 +00:00
Compare commits
12
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
94c511f39a | ||
|
|
7d72fd77e5 | ||
|
|
7477b43653 | ||
|
|
569505c54d | ||
|
|
e0d2db0c24 | ||
|
|
dc66be4b2c | ||
|
|
ccd43c9c2a | ||
|
|
fa6e8b8158 | ||
|
|
96d94c9452 | ||
|
|
6454ef55ad | ||
|
|
d502320a94 | ||
|
|
850bd81511 |
@@ -26,4 +26,9 @@ test-results
|
||||
docker-compose.web-installer.yml
|
||||
.env.web-installer
|
||||
docker-compose.web-installer.yml.**.backup
|
||||
|
||||
# local installer output dir
|
||||
appwrite/
|
||||
|
||||
# screenshots
|
||||
tests/playwright/screenshots
|
||||
|
||||
@@ -115,7 +115,8 @@ function router(Http $utopia, Database $dbForPlatform, callable $getProjectDB, S
|
||||
}
|
||||
}
|
||||
|
||||
if (!in_array($host, $platformHostnames)) {
|
||||
$routerProtectionEnabled = System::getEnv('_APP_OPTIONS_ROUTER_PROTECTION', 'disabled') === 'enabled';
|
||||
if ($routerProtectionEnabled && !in_array($host, $platformHostnames)) {
|
||||
throw new AppwriteException(AppwriteException::GENERAL_ACCESS_FORBIDDEN, 'Router protection does not allow accessing Appwrite over this domain. Please add it as custom domain to your project or disable _APP_OPTIONS_ROUTER_PROTECTION environment variable.', view: $errorView);
|
||||
}
|
||||
|
||||
|
||||
@@ -392,5 +392,8 @@ const COOKIE_NAME_PREVIEW = 'a_jwt_console';
|
||||
const CACHE_RECONNECT_MAX_RETRIES = 2;
|
||||
const CACHE_RECONNECT_RETRY_DELAY = 1000;
|
||||
|
||||
/* Web installer */
|
||||
const LOCAL_API_TIMEOUT = 30 * 1000; // 30 seconds
|
||||
|
||||
// Project status
|
||||
const PROJECT_STATUS_ACTIVE = 'active';
|
||||
|
||||
@@ -851,6 +851,14 @@
|
||||
const retryButton = event.target.closest('[data-install-retry]');
|
||||
|
||||
if (consoleButton) {
|
||||
fetch('/install/cleanup', {
|
||||
method: 'POST',
|
||||
headers: withCsrfHeader({
|
||||
'Content-Type': 'application/json'
|
||||
})
|
||||
}).catch(() => {});
|
||||
|
||||
// Redirect immediately
|
||||
redirectToApp();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Installer\Http\Installer;
|
||||
|
||||
use Appwrite\Platform\Installer\Server;
|
||||
use Utopia\Http\Adapter\Swoole\Request;
|
||||
use Utopia\Http\Adapter\Swoole\Response;
|
||||
use Utopia\Platform\Action;
|
||||
|
||||
class Cleanup extends Action
|
||||
{
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'installerCleanup';
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(Action::HTTP_REQUEST_METHOD_POST)
|
||||
->setHttpPath('/install/cleanup')
|
||||
->desc('Cleanup installer container')
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(Request $request, Response $response): void
|
||||
{
|
||||
if (!Validate::validateCsrf($request)) {
|
||||
$response->setStatusCode(Response::STATUS_CODE_BAD_REQUEST);
|
||||
$response->json(['success' => false, 'message' => 'Invalid CSRF token']);
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove the installer container
|
||||
$container = escapeshellarg(Server::DEFAULT_CONTAINER);
|
||||
@exec("docker rm -f $container >/dev/null 2>&1");
|
||||
|
||||
$response->json(['success' => true]);
|
||||
}
|
||||
}
|
||||
@@ -153,7 +153,7 @@ class Server
|
||||
->inject('response')
|
||||
->action($errorHandler->action(...));
|
||||
|
||||
$adapter = new class($host, $port, ['worker_num' => 1]) extends SwooleAdapter {
|
||||
$adapter = new class ($host, $port, ['worker_num' => 1]) extends SwooleAdapter {
|
||||
public function getNativeServer(): SwooleServer
|
||||
{
|
||||
return $this->server;
|
||||
@@ -198,14 +198,27 @@ class Server
|
||||
|
||||
private function cleanupWebInstallerFiles(): void
|
||||
{
|
||||
$cwd = getcwd();
|
||||
if ($cwd === false) {
|
||||
return;
|
||||
$baseDir = null;
|
||||
try {
|
||||
$cfg = $this->state->buildConfig();
|
||||
if ($cfg->isLocal() && !empty($cfg->getHostPath())) {
|
||||
$baseDir = rtrim($cfg->getHostPath(), '/') . '/appwrite';
|
||||
}
|
||||
} catch (\Throwable) {
|
||||
// Fall back to cwd
|
||||
}
|
||||
|
||||
if ($baseDir === null) {
|
||||
$cwd = getcwd();
|
||||
if ($cwd === false) {
|
||||
return;
|
||||
}
|
||||
$baseDir = $cwd;
|
||||
}
|
||||
|
||||
$filesToRemove = [
|
||||
$cwd . '/.env.web-installer',
|
||||
$cwd . '/docker-compose.web-installer.yml',
|
||||
$baseDir . '/.env.web-installer',
|
||||
$baseDir . '/docker-compose.web-installer.yml',
|
||||
];
|
||||
|
||||
foreach ($filesToRemove as $file) {
|
||||
@@ -241,7 +254,7 @@ class Server
|
||||
}
|
||||
}
|
||||
|
||||
private function ensureLocalInstallerTag(string $source, string $target): void
|
||||
private function ensureCorrectTag(string $source, string $target): void
|
||||
{
|
||||
$sourceArg = escapeshellarg($source);
|
||||
$targetArg = escapeshellarg($target);
|
||||
@@ -259,7 +272,7 @@ class Server
|
||||
if (!$this->dockerImageExists($image)) {
|
||||
$this->buildDockerInstallerImage($image);
|
||||
}
|
||||
$this->ensureLocalInstallerTag($image, 'appwrite/appwrite:local');
|
||||
$this->ensureCorrectTag($image, 'appwrite/appwrite:local');
|
||||
$port = (string)self::INSTALLER_WEB_PORT;
|
||||
$entrypoint = isset($opts['upgrade']) ? 'upgrade' : 'install';
|
||||
|
||||
@@ -290,6 +303,7 @@ class Server
|
||||
'-p', "127.0.0.1:$port:" . self::INSTALLER_WEB_PORT,
|
||||
'--volume', '/var/run/docker.sock:/var/run/docker.sock',
|
||||
'--volume', "$volumePath:/usr/src/code:rw",
|
||||
'--volume', "$volumePath:$volumePath:rw",
|
||||
];
|
||||
$args[] = '-e';
|
||||
$args[] = 'APPWRITE_INSTALLER_CONFIG=' . $configJson;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace Appwrite\Platform\Installer\Services;
|
||||
|
||||
use Appwrite\Platform\Installer\Http\Installer\Cleanup;
|
||||
use Appwrite\Platform\Installer\Http\Installer\Complete;
|
||||
use Appwrite\Platform\Installer\Http\Installer\Install;
|
||||
use Appwrite\Platform\Installer\Http\Installer\Shutdown;
|
||||
@@ -19,6 +20,7 @@ class Http extends Service
|
||||
$this->addAction(View::getName(), new View());
|
||||
$this->addAction(Status::getName(), new Status());
|
||||
$this->addAction(Validate::getName(), new Validate());
|
||||
$this->addAction(Cleanup::getName(), new Cleanup());
|
||||
$this->addAction(Complete::getName(), new Complete());
|
||||
$this->addAction(Shutdown::getName(), new Shutdown());
|
||||
$this->addAction(Install::getName(), new Install());
|
||||
|
||||
@@ -471,6 +471,11 @@ class Install extends Action
|
||||
): void {
|
||||
$isLocalInstall = $this->isLocalInstall();
|
||||
$this->applyLocalPaths($isLocalInstall, false);
|
||||
if ($isLocalInstall && !is_dir($this->path)) {
|
||||
if (!@mkdir($this->path, 0755, true)) {
|
||||
throw new \Exception('Can\'t create directory ' . $this->path);
|
||||
}
|
||||
}
|
||||
|
||||
$isCLI = php_sapi_name() === 'cli';
|
||||
if ($isLocalInstall) {
|
||||
@@ -490,10 +495,9 @@ class Install extends Action
|
||||
|
||||
$database = $input['_APP_DB_ADAPTER'] ?? 'mongodb';
|
||||
|
||||
$version = \defined('APP_VERSION_STABLE') ? APP_VERSION_STABLE : 'latest';
|
||||
if ($isLocalInstall) {
|
||||
$version = 'local';
|
||||
}
|
||||
$version = $isLocalInstall
|
||||
? 'local'
|
||||
: (defined('APP_VERSION_STABLE') ? APP_VERSION_STABLE : 'latest');
|
||||
|
||||
$assistantKey = (string) ($input['_APP_ASSISTANT_OPENAI_API_KEY'] ?? '');
|
||||
$enableAssistant = trim($assistantKey) !== '';
|
||||
@@ -505,8 +509,8 @@ class Install extends Action
|
||||
->setParam('organization', $organization)
|
||||
->setParam('image', $image)
|
||||
->setParam('database', $database)
|
||||
->setParam('hostPath', $this->hostPath)
|
||||
->setParam('enableAssistant', $enableAssistant);
|
||||
->setParam('enableAssistant', $enableAssistant)
|
||||
->setParam('hostPath', $this->hostPath);
|
||||
|
||||
$templateForEnv->setParam('vars', $input);
|
||||
|
||||
@@ -588,7 +592,12 @@ class Install extends Action
|
||||
}
|
||||
} else {
|
||||
if ($isCLI) {
|
||||
Console::success('Installation files created. Run "docker compose up -d" to start Appwrite');
|
||||
if ($isLocalInstall) {
|
||||
$composePath = $this->path . '/' . $this->getComposeFileName();
|
||||
Console::success('Installation files created in ' . $this->path . '. Run "docker compose -f ' . $composePath . ' up -d" to start Appwrite');
|
||||
} else {
|
||||
Console::success('Installation files created. Run "docker compose up -d" to start Appwrite');
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
@@ -822,7 +831,7 @@ class Install extends Action
|
||||
{
|
||||
$client = new Client();
|
||||
$client
|
||||
->setTimeout(30000)
|
||||
->setTimeout(LOCAL_API_TIMEOUT)
|
||||
->setConnectTimeout(10000)
|
||||
->addHeader('Content-Type', 'application/json')
|
||||
->addHeader('X-Appwrite-Project', 'console')
|
||||
@@ -1043,8 +1052,9 @@ class Install extends Action
|
||||
if (!$force && $this->hostPath !== '') {
|
||||
return;
|
||||
}
|
||||
$this->path = '/usr/src/code';
|
||||
$this->hostPath = $this->getInstallerHostPath();
|
||||
$hostPath = rtrim($this->hostPath, '/');
|
||||
$this->path = $hostPath . '/appwrite';
|
||||
}
|
||||
|
||||
protected function readExistingCompose(): string
|
||||
@@ -1066,12 +1076,12 @@ class Install extends Action
|
||||
|
||||
protected function getComposeFileName(): string
|
||||
{
|
||||
return $this->isLocalInstall() ? 'docker-compose.web-installer.yml' : 'docker-compose.yml';
|
||||
return 'docker-compose.yml';
|
||||
}
|
||||
|
||||
protected function getEnvFileName(): string
|
||||
{
|
||||
return $this->isLocalInstall() ? '.env.web-installer' : '.env';
|
||||
return '.env';
|
||||
}
|
||||
|
||||
private function isInstallationComplete(int $port): bool
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace Tests\Unit\Platform\Modules\Installer;
|
||||
|
||||
use Appwrite\Platform\Installer\Http\Installer\Cleanup;
|
||||
use Appwrite\Platform\Installer\Http\Installer\Complete;
|
||||
use Appwrite\Platform\Installer\Http\Installer\Error;
|
||||
use Appwrite\Platform\Installer\Http\Installer\Install;
|
||||
@@ -41,10 +42,11 @@ class ModuleTest extends TestCase
|
||||
$service = reset($services);
|
||||
$actions = $service->getActions();
|
||||
|
||||
$this->assertCount(6, $actions);
|
||||
$this->assertCount(7, $actions);
|
||||
$this->assertArrayHasKey('installerView', $actions);
|
||||
$this->assertArrayHasKey('installerStatus', $actions);
|
||||
$this->assertArrayHasKey('installerValidate', $actions);
|
||||
$this->assertArrayHasKey('installerCleanup', $actions);
|
||||
$this->assertArrayHasKey('installerComplete', $actions);
|
||||
$this->assertArrayHasKey('installerShutdown', $actions);
|
||||
$this->assertArrayHasKey('installerInstall', $actions);
|
||||
@@ -97,6 +99,17 @@ class ModuleTest extends TestCase
|
||||
$this->assertActionInjects($action, ['request', 'response', 'installerState']);
|
||||
}
|
||||
|
||||
public function testCleanupAction(): void
|
||||
{
|
||||
$action = $this->getAction('installerCleanup');
|
||||
|
||||
$this->assertEquals('installerCleanup', Cleanup::getName());
|
||||
$this->assertEquals(Action::HTTP_REQUEST_METHOD_POST, $action->getHttpMethod());
|
||||
$this->assertEquals('/install/cleanup', $action->getHttpPath());
|
||||
$this->assertEquals(Action::TYPE_DEFAULT, $action->getType());
|
||||
$this->assertActionInjects($action, ['request', 'response']);
|
||||
}
|
||||
|
||||
public function testShutdownAction(): void
|
||||
{
|
||||
$action = $this->getAction('installerShutdown');
|
||||
@@ -138,7 +151,7 @@ class ModuleTest extends TestCase
|
||||
*/
|
||||
public function testRouteRegistration(): void
|
||||
{
|
||||
$platform = new class(new Module()) extends Platform {};
|
||||
$platform = new class (new Module()) extends Platform {};
|
||||
$platform->init(Service::TYPE_HTTP);
|
||||
|
||||
// If we get here without exceptions, route registration succeeded
|
||||
|
||||
Reference in New Issue
Block a user