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
|
docker-compose.web-installer.yml
|
||||||
.env.web-installer
|
.env.web-installer
|
||||||
docker-compose.web-installer.yml.**.backup
|
docker-compose.web-installer.yml.**.backup
|
||||||
|
|
||||||
|
# local installer output dir
|
||||||
|
appwrite/
|
||||||
|
|
||||||
|
# screenshots
|
||||||
tests/playwright/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);
|
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_MAX_RETRIES = 2;
|
||||||
const CACHE_RECONNECT_RETRY_DELAY = 1000;
|
const CACHE_RECONNECT_RETRY_DELAY = 1000;
|
||||||
|
|
||||||
|
/* Web installer */
|
||||||
|
const LOCAL_API_TIMEOUT = 30 * 1000; // 30 seconds
|
||||||
|
|
||||||
// Project status
|
// Project status
|
||||||
const PROJECT_STATUS_ACTIVE = 'active';
|
const PROJECT_STATUS_ACTIVE = 'active';
|
||||||
|
|||||||
@@ -851,6 +851,14 @@
|
|||||||
const retryButton = event.target.closest('[data-install-retry]');
|
const retryButton = event.target.closest('[data-install-retry]');
|
||||||
|
|
||||||
if (consoleButton) {
|
if (consoleButton) {
|
||||||
|
fetch('/install/cleanup', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: withCsrfHeader({
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
})
|
||||||
|
}).catch(() => {});
|
||||||
|
|
||||||
|
// Redirect immediately
|
||||||
redirectToApp();
|
redirectToApp();
|
||||||
return;
|
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')
|
->inject('response')
|
||||||
->action($errorHandler->action(...));
|
->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
|
public function getNativeServer(): SwooleServer
|
||||||
{
|
{
|
||||||
return $this->server;
|
return $this->server;
|
||||||
@@ -198,14 +198,27 @@ class Server
|
|||||||
|
|
||||||
private function cleanupWebInstallerFiles(): void
|
private function cleanupWebInstallerFiles(): void
|
||||||
{
|
{
|
||||||
$cwd = getcwd();
|
$baseDir = null;
|
||||||
if ($cwd === false) {
|
try {
|
||||||
return;
|
$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 = [
|
$filesToRemove = [
|
||||||
$cwd . '/.env.web-installer',
|
$baseDir . '/.env.web-installer',
|
||||||
$cwd . '/docker-compose.web-installer.yml',
|
$baseDir . '/docker-compose.web-installer.yml',
|
||||||
];
|
];
|
||||||
|
|
||||||
foreach ($filesToRemove as $file) {
|
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);
|
$sourceArg = escapeshellarg($source);
|
||||||
$targetArg = escapeshellarg($target);
|
$targetArg = escapeshellarg($target);
|
||||||
@@ -259,7 +272,7 @@ class Server
|
|||||||
if (!$this->dockerImageExists($image)) {
|
if (!$this->dockerImageExists($image)) {
|
||||||
$this->buildDockerInstallerImage($image);
|
$this->buildDockerInstallerImage($image);
|
||||||
}
|
}
|
||||||
$this->ensureLocalInstallerTag($image, 'appwrite/appwrite:local');
|
$this->ensureCorrectTag($image, 'appwrite/appwrite:local');
|
||||||
$port = (string)self::INSTALLER_WEB_PORT;
|
$port = (string)self::INSTALLER_WEB_PORT;
|
||||||
$entrypoint = isset($opts['upgrade']) ? 'upgrade' : 'install';
|
$entrypoint = isset($opts['upgrade']) ? 'upgrade' : 'install';
|
||||||
|
|
||||||
@@ -290,6 +303,7 @@ class Server
|
|||||||
'-p', "127.0.0.1:$port:" . self::INSTALLER_WEB_PORT,
|
'-p', "127.0.0.1:$port:" . self::INSTALLER_WEB_PORT,
|
||||||
'--volume', '/var/run/docker.sock:/var/run/docker.sock',
|
'--volume', '/var/run/docker.sock:/var/run/docker.sock',
|
||||||
'--volume', "$volumePath:/usr/src/code:rw",
|
'--volume', "$volumePath:/usr/src/code:rw",
|
||||||
|
'--volume', "$volumePath:$volumePath:rw",
|
||||||
];
|
];
|
||||||
$args[] = '-e';
|
$args[] = '-e';
|
||||||
$args[] = 'APPWRITE_INSTALLER_CONFIG=' . $configJson;
|
$args[] = 'APPWRITE_INSTALLER_CONFIG=' . $configJson;
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace Appwrite\Platform\Installer\Services;
|
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\Complete;
|
||||||
use Appwrite\Platform\Installer\Http\Installer\Install;
|
use Appwrite\Platform\Installer\Http\Installer\Install;
|
||||||
use Appwrite\Platform\Installer\Http\Installer\Shutdown;
|
use Appwrite\Platform\Installer\Http\Installer\Shutdown;
|
||||||
@@ -19,6 +20,7 @@ class Http extends Service
|
|||||||
$this->addAction(View::getName(), new View());
|
$this->addAction(View::getName(), new View());
|
||||||
$this->addAction(Status::getName(), new Status());
|
$this->addAction(Status::getName(), new Status());
|
||||||
$this->addAction(Validate::getName(), new Validate());
|
$this->addAction(Validate::getName(), new Validate());
|
||||||
|
$this->addAction(Cleanup::getName(), new Cleanup());
|
||||||
$this->addAction(Complete::getName(), new Complete());
|
$this->addAction(Complete::getName(), new Complete());
|
||||||
$this->addAction(Shutdown::getName(), new Shutdown());
|
$this->addAction(Shutdown::getName(), new Shutdown());
|
||||||
$this->addAction(Install::getName(), new Install());
|
$this->addAction(Install::getName(), new Install());
|
||||||
|
|||||||
@@ -471,6 +471,11 @@ class Install extends Action
|
|||||||
): void {
|
): void {
|
||||||
$isLocalInstall = $this->isLocalInstall();
|
$isLocalInstall = $this->isLocalInstall();
|
||||||
$this->applyLocalPaths($isLocalInstall, false);
|
$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';
|
$isCLI = php_sapi_name() === 'cli';
|
||||||
if ($isLocalInstall) {
|
if ($isLocalInstall) {
|
||||||
@@ -490,10 +495,9 @@ class Install extends Action
|
|||||||
|
|
||||||
$database = $input['_APP_DB_ADAPTER'] ?? 'mongodb';
|
$database = $input['_APP_DB_ADAPTER'] ?? 'mongodb';
|
||||||
|
|
||||||
$version = \defined('APP_VERSION_STABLE') ? APP_VERSION_STABLE : 'latest';
|
$version = $isLocalInstall
|
||||||
if ($isLocalInstall) {
|
? 'local'
|
||||||
$version = 'local';
|
: (defined('APP_VERSION_STABLE') ? APP_VERSION_STABLE : 'latest');
|
||||||
}
|
|
||||||
|
|
||||||
$assistantKey = (string) ($input['_APP_ASSISTANT_OPENAI_API_KEY'] ?? '');
|
$assistantKey = (string) ($input['_APP_ASSISTANT_OPENAI_API_KEY'] ?? '');
|
||||||
$enableAssistant = trim($assistantKey) !== '';
|
$enableAssistant = trim($assistantKey) !== '';
|
||||||
@@ -505,8 +509,8 @@ class Install extends Action
|
|||||||
->setParam('organization', $organization)
|
->setParam('organization', $organization)
|
||||||
->setParam('image', $image)
|
->setParam('image', $image)
|
||||||
->setParam('database', $database)
|
->setParam('database', $database)
|
||||||
->setParam('hostPath', $this->hostPath)
|
->setParam('enableAssistant', $enableAssistant)
|
||||||
->setParam('enableAssistant', $enableAssistant);
|
->setParam('hostPath', $this->hostPath);
|
||||||
|
|
||||||
$templateForEnv->setParam('vars', $input);
|
$templateForEnv->setParam('vars', $input);
|
||||||
|
|
||||||
@@ -588,7 +592,12 @@ class Install extends Action
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if ($isCLI) {
|
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) {
|
} catch (\Throwable $e) {
|
||||||
@@ -822,7 +831,7 @@ class Install extends Action
|
|||||||
{
|
{
|
||||||
$client = new Client();
|
$client = new Client();
|
||||||
$client
|
$client
|
||||||
->setTimeout(30000)
|
->setTimeout(LOCAL_API_TIMEOUT)
|
||||||
->setConnectTimeout(10000)
|
->setConnectTimeout(10000)
|
||||||
->addHeader('Content-Type', 'application/json')
|
->addHeader('Content-Type', 'application/json')
|
||||||
->addHeader('X-Appwrite-Project', 'console')
|
->addHeader('X-Appwrite-Project', 'console')
|
||||||
@@ -1043,8 +1052,9 @@ class Install extends Action
|
|||||||
if (!$force && $this->hostPath !== '') {
|
if (!$force && $this->hostPath !== '') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$this->path = '/usr/src/code';
|
|
||||||
$this->hostPath = $this->getInstallerHostPath();
|
$this->hostPath = $this->getInstallerHostPath();
|
||||||
|
$hostPath = rtrim($this->hostPath, '/');
|
||||||
|
$this->path = $hostPath . '/appwrite';
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function readExistingCompose(): string
|
protected function readExistingCompose(): string
|
||||||
@@ -1066,12 +1076,12 @@ class Install extends Action
|
|||||||
|
|
||||||
protected function getComposeFileName(): string
|
protected function getComposeFileName(): string
|
||||||
{
|
{
|
||||||
return $this->isLocalInstall() ? 'docker-compose.web-installer.yml' : 'docker-compose.yml';
|
return 'docker-compose.yml';
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function getEnvFileName(): string
|
protected function getEnvFileName(): string
|
||||||
{
|
{
|
||||||
return $this->isLocalInstall() ? '.env.web-installer' : '.env';
|
return '.env';
|
||||||
}
|
}
|
||||||
|
|
||||||
private function isInstallationComplete(int $port): bool
|
private function isInstallationComplete(int $port): bool
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace Tests\Unit\Platform\Modules\Installer;
|
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\Complete;
|
||||||
use Appwrite\Platform\Installer\Http\Installer\Error;
|
use Appwrite\Platform\Installer\Http\Installer\Error;
|
||||||
use Appwrite\Platform\Installer\Http\Installer\Install;
|
use Appwrite\Platform\Installer\Http\Installer\Install;
|
||||||
@@ -41,10 +42,11 @@ class ModuleTest extends TestCase
|
|||||||
$service = reset($services);
|
$service = reset($services);
|
||||||
$actions = $service->getActions();
|
$actions = $service->getActions();
|
||||||
|
|
||||||
$this->assertCount(6, $actions);
|
$this->assertCount(7, $actions);
|
||||||
$this->assertArrayHasKey('installerView', $actions);
|
$this->assertArrayHasKey('installerView', $actions);
|
||||||
$this->assertArrayHasKey('installerStatus', $actions);
|
$this->assertArrayHasKey('installerStatus', $actions);
|
||||||
$this->assertArrayHasKey('installerValidate', $actions);
|
$this->assertArrayHasKey('installerValidate', $actions);
|
||||||
|
$this->assertArrayHasKey('installerCleanup', $actions);
|
||||||
$this->assertArrayHasKey('installerComplete', $actions);
|
$this->assertArrayHasKey('installerComplete', $actions);
|
||||||
$this->assertArrayHasKey('installerShutdown', $actions);
|
$this->assertArrayHasKey('installerShutdown', $actions);
|
||||||
$this->assertArrayHasKey('installerInstall', $actions);
|
$this->assertArrayHasKey('installerInstall', $actions);
|
||||||
@@ -97,6 +99,17 @@ class ModuleTest extends TestCase
|
|||||||
$this->assertActionInjects($action, ['request', 'response', 'installerState']);
|
$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
|
public function testShutdownAction(): void
|
||||||
{
|
{
|
||||||
$action = $this->getAction('installerShutdown');
|
$action = $this->getAction('installerShutdown');
|
||||||
@@ -138,7 +151,7 @@ class ModuleTest extends TestCase
|
|||||||
*/
|
*/
|
||||||
public function testRouteRegistration(): void
|
public function testRouteRegistration(): void
|
||||||
{
|
{
|
||||||
$platform = new class(new Module()) extends Platform {};
|
$platform = new class (new Module()) extends Platform {};
|
||||||
$platform->init(Service::TYPE_HTTP);
|
$platform->init(Service::TYPE_HTTP);
|
||||||
|
|
||||||
// If we get here without exceptions, route registration succeeded
|
// If we get here without exceptions, route registration succeeded
|
||||||
|
|||||||
Reference in New Issue
Block a user