mirror of
https://github.com/appwrite/appwrite.git
synced 2026-05-26 13:51:13 +00:00
Add web installer
This commit is contained in:
+1
-3
@@ -102,11 +102,9 @@ class Get extends Action
|
||||
// Use transaction-aware document retrieval if transactionId is provided
|
||||
if ($transactionId !== null) {
|
||||
$document = $transactionState->getDocument($collectionTableId, $documentId, $transactionId, $queries);
|
||||
} elseif (! empty($selects)) {
|
||||
// has selects, allow relationship on documents!
|
||||
} elseif (!empty($selects)) {
|
||||
$document = $dbForProject->getDocument($collectionTableId, $documentId, $queries);
|
||||
} else {
|
||||
// has no selects, disable relationship looping on documents!
|
||||
$document = $dbForProject->skipRelationships(fn () => $dbForProject->getDocument($collectionTableId, $documentId, $queries));
|
||||
}
|
||||
} catch (QueryException $e) {
|
||||
|
||||
@@ -16,6 +16,7 @@ use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Exception\Order as OrderException;
|
||||
use Utopia\Database\Exception\Query as QueryException;
|
||||
use Utopia\Database\Exception\Timeout;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\Query\Cursor;
|
||||
@@ -150,6 +151,8 @@ class XList extends Action
|
||||
throw new Exception(Exception::DATABASE_QUERY_ORDER_NULL, $message);
|
||||
} catch (QueryException $e) {
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
|
||||
} catch (Timeout) {
|
||||
throw new Exception(Exception::DATABASE_TIMEOUT);
|
||||
}
|
||||
|
||||
$operations = 0;
|
||||
|
||||
@@ -12,10 +12,12 @@ use Utopia\Config\Config;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Validator\Boolean;
|
||||
use Utopia\Validator\Text;
|
||||
use Utopia\Validator\WhiteList;
|
||||
|
||||
class Install extends Action
|
||||
{
|
||||
protected string $path = '/usr/src/code/appwrite';
|
||||
protected ?string $hostPath = null;
|
||||
|
||||
public static function getName(): string
|
||||
{
|
||||
@@ -33,6 +35,9 @@ class Install extends Action
|
||||
->param('interactive', 'Y', new Text(1), 'Run an interactive session', true)
|
||||
->param('no-start', false, new Boolean(true), 'Run an interactive session', true)
|
||||
->callback($this->action(...));
|
||||
|
||||
$this->hostPath = $this->detectHostPath($this->path);
|
||||
$this->ensureHostPathLink();
|
||||
}
|
||||
|
||||
public function action(string $httpPort, string $httpsPort, string $organization, string $image, string $interactive, bool $noStart): void
|
||||
@@ -40,7 +45,7 @@ class Install extends Action
|
||||
$config = Config::getParam('variables');
|
||||
$defaultHTTPPort = '80';
|
||||
$defaultHTTPSPort = '443';
|
||||
/** @var array<string, array<string, string>> $vars array whre key is variable name and value is variable */
|
||||
/** @var array<string, array<string, string>> $vars array where key is variable name and value is variable */
|
||||
$vars = [];
|
||||
|
||||
foreach ($config as $category) {
|
||||
@@ -59,18 +64,11 @@ class Install extends Action
|
||||
}
|
||||
}
|
||||
|
||||
// Check for existing installation
|
||||
$data = @file_get_contents($this->path . '/docker-compose.yml');
|
||||
$existingInstallation = $data !== false;
|
||||
|
||||
if ($data !== false) {
|
||||
if ($interactive == 'Y' && Console::isInteractive()) {
|
||||
$answer = Console::confirm('Previous installation found, do you want to overwrite it (a backup will be created before overwriting)? (Y/n)');
|
||||
|
||||
if (\strtolower($answer) !== 'y') {
|
||||
Console::info('No action taken.');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if ($existingInstallation) {
|
||||
$time = \time();
|
||||
Console::info('Compose file found, creating backup: docker-compose.yml.' . $time . '.backup');
|
||||
file_put_contents($this->path . '/docker-compose.yml.' . $time . '.backup', $data);
|
||||
@@ -88,7 +86,7 @@ class Install extends Action
|
||||
}
|
||||
|
||||
if ($oldVersion) {
|
||||
foreach ($compose->getServices() as $service) { // Fetch all env vars from previous compose file
|
||||
foreach ($compose->getServices() as $service) {
|
||||
if (!$service) {
|
||||
continue;
|
||||
}
|
||||
@@ -107,12 +105,12 @@ class Install extends Action
|
||||
}
|
||||
}
|
||||
|
||||
$data = @file_get_contents($this->path . '/.env');
|
||||
$envData = @file_get_contents($this->path . '/.env');
|
||||
|
||||
if ($data !== false) { // Fetch all env vars from previous .env file
|
||||
if ($envData !== false) {
|
||||
Console::info('Env file found, creating backup: .env.' . $time . '.backup');
|
||||
file_put_contents($this->path . '/.env.' . $time . '.backup', $data);
|
||||
$env = new Env($data);
|
||||
file_put_contents($this->path . '/.env.' . $time . '.backup', $envData);
|
||||
$env = new Env($envData);
|
||||
|
||||
foreach ($env->list() as $key => $value) {
|
||||
if (is_null($value)) {
|
||||
@@ -138,6 +136,17 @@ class Install extends Action
|
||||
}
|
||||
}
|
||||
|
||||
// If interactive and web mode enabled, start web server
|
||||
if ($interactive === 'Y' && Console::isInteractive()) {
|
||||
Console::success('Starting web installer...');
|
||||
Console::info('Open your browser at: http://localhost:8080');
|
||||
Console::info('Press Ctrl+C to cancel installation');
|
||||
|
||||
$this->startWebServer($defaultHTTPPort, $defaultHTTPSPort, $organization, $image, $noStart, $vars);
|
||||
return;
|
||||
}
|
||||
|
||||
// Fall back to CLI mode
|
||||
if (empty($httpPort)) {
|
||||
$httpPort = Console::confirm('Choose your server HTTP port: (default: ' . $defaultHTTPPort . ')');
|
||||
$httpPort = ($httpPort) ? $httpPort : $defaultHTTPPort;
|
||||
@@ -148,97 +157,438 @@ class Install extends Action
|
||||
$httpsPort = ($httpsPort) ? $httpsPort : $defaultHTTPSPort;
|
||||
}
|
||||
|
||||
$input = [];
|
||||
|
||||
$password = new Password();
|
||||
$token = new Token();
|
||||
foreach ($vars as $var) {
|
||||
if (!empty($var['filter']) && ($interactive !== 'Y' || !Console::isInteractive())) {
|
||||
if ($data && $var['default'] !== null) {
|
||||
$input[$var['name']] = $var['default'];
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($var['filter'] === 'token') {
|
||||
$input[$var['name']] = $token->generate();
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($var['filter'] === 'password') {
|
||||
$input[$var['name']] = $password->generate();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (!$var['required'] || !Console::isInteractive() || $interactive !== 'Y') {
|
||||
$input[$var['name']] = $var['default'];
|
||||
continue;
|
||||
}
|
||||
|
||||
$input[$var['name']] = Console::confirm($var['question'] . ' (default: \'' . $var['default'] . '\')');
|
||||
$value = Console::confirm($var['question'] . ' (default: \'' . $var['default'] . '\')');
|
||||
|
||||
if (empty($input[$var['name']])) {
|
||||
$input[$var['name']] = $var['default'];
|
||||
if (!empty($value)) {
|
||||
$userInput[$var['name']] = $value;
|
||||
}
|
||||
|
||||
if ($var['filter'] === 'domainTarget') {
|
||||
if ($input[$var['name']] !== 'localhost') {
|
||||
Console::warning("\nIf you haven't already done so, set the following record for {$input[$var['name']]} on your DNS provider:\n");
|
||||
$mask = "%-15.15s %-10.10s %-30.30s\n";
|
||||
printf($mask, "Type", "Name", "Value");
|
||||
printf($mask, "A or AAAA", "@", "<YOUR PUBLIC IP>");
|
||||
Console::warning("\nUse 'AAAA' if you're using an IPv6 address and 'A' if you're using an IPv4 address.\n");
|
||||
}
|
||||
if ($var['filter'] === 'domainTarget' && !empty($value) && $value !== 'localhost') {
|
||||
Console::warning("\nIf you haven't already done so, set the following record for {$value} on your DNS provider:\n");
|
||||
$mask = "%-15.15s %-10.10s %-30.30s\n";
|
||||
printf($mask, "Type", "Name", "Value");
|
||||
printf($mask, "A or AAAA", "@", "<YOUR PUBLIC IP>");
|
||||
Console::warning("\nUse 'AAAA' if you're using an IPv6 address and 'A' if you're using an IPv4 address.\n");
|
||||
}
|
||||
}
|
||||
|
||||
$input = $this->prepareEnvironmentVariables($userInput, $vars);
|
||||
$this->performInstallation($httpPort, $httpsPort, $organization, $image, $input, $noStart);
|
||||
}
|
||||
|
||||
private function detectHostPath(string $path): ?string
|
||||
{
|
||||
if (!is_file('/proc/self/mountinfo')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$bestMatch = null;
|
||||
$bestLength = 0;
|
||||
|
||||
foreach (@file('/proc/self/mountinfo') as $line) {
|
||||
$line = trim($line);
|
||||
if ($line === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$parts = explode(' - ', $line, 2);
|
||||
if (count($parts) < 2) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$left = preg_split('/\s+/', $parts[0]);
|
||||
$right = preg_split('/\s+/', $parts[1]);
|
||||
|
||||
if (count($left) < 5 || count($right) < 2) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$mountPoint = $left[4];
|
||||
$root = $left[3];
|
||||
$fsType = $right[0];
|
||||
$source = $right[1];
|
||||
|
||||
if (!str_starts_with($path, $mountPoint)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$mountLength = strlen($mountPoint);
|
||||
if ($mountLength < $bestLength) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$relative = substr($path, $mountLength);
|
||||
$relative = ltrim($relative, '/');
|
||||
|
||||
$hostBase = null;
|
||||
|
||||
if ($fsType === 'fakeowner' && str_starts_with($source, '/run/host_mark/')) {
|
||||
$share = basename($source);
|
||||
$hostBase = '/' . $share . rtrim($root, '/');
|
||||
} elseif ($root !== '/' && $root !== '.') {
|
||||
$hostBase = $root;
|
||||
} elseif ($source !== 'none') {
|
||||
$hostBase = $source;
|
||||
}
|
||||
|
||||
if (!$hostBase || $hostBase === '.') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($hostBase[0] !== '/') {
|
||||
$hostBase = '/' . $hostBase;
|
||||
}
|
||||
|
||||
$candidate = rtrim($hostBase, '/');
|
||||
if ($relative !== '') {
|
||||
$candidate .= '/' . $relative;
|
||||
}
|
||||
|
||||
$candidate = preg_replace('#//+#', '/', $candidate);
|
||||
|
||||
$bestMatch = $candidate;
|
||||
$bestLength = $mountLength;
|
||||
}
|
||||
|
||||
return $bestMatch;
|
||||
}
|
||||
|
||||
private function ensureHostPathLink(): void
|
||||
{
|
||||
if (empty($this->hostPath) || $this->hostPath === $this->path) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_link($this->hostPath)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_dir($this->hostPath)) {
|
||||
@rmdir($this->hostPath);
|
||||
}
|
||||
|
||||
$parent = dirname($this->hostPath);
|
||||
if (!is_dir($parent)) {
|
||||
@mkdir($parent, 0755, true);
|
||||
}
|
||||
|
||||
if (!file_exists($this->hostPath)) {
|
||||
@symlink($this->path, $this->hostPath);
|
||||
}
|
||||
}
|
||||
|
||||
protected function startWebServer(string $defaultHTTPPort, string $defaultHTTPSPort, string $organization, string $image, bool $noStart, array $vars, bool $isUpgrade = false, ?string $lockedDatabase = null): void
|
||||
{
|
||||
$port = 8080;
|
||||
$host = '0.0.0.0';
|
||||
$url = "http://localhost:$port";
|
||||
|
||||
// Create a router script for handling requests
|
||||
$routerScript = \sys_get_temp_dir() . '/appwrite-installer-router.php';
|
||||
$this->createRouterScript($routerScript, $defaultHTTPPort, $defaultHTTPSPort, $organization, $image, $noStart, $vars, $isUpgrade, $lockedDatabase);
|
||||
|
||||
// Start PHP built-in server in background
|
||||
$command = \sprintf(
|
||||
'php -S %s:%d %s >/dev/null 2>&1 &',
|
||||
$host,
|
||||
$port,
|
||||
\escapeshellarg($routerScript)
|
||||
);
|
||||
|
||||
// Start the server
|
||||
\exec($command, $output, $exitCode);
|
||||
\sleep(3);
|
||||
|
||||
// Check if the server actually started
|
||||
$handle = @fsockopen('localhost', $port, $errno, $errstr, 1);
|
||||
if ($handle === false) {
|
||||
Console::exit(1);
|
||||
}
|
||||
\fclose($handle);
|
||||
|
||||
// Wait for the server process to finish
|
||||
while (true) {
|
||||
$handle = @fsockopen('localhost', $port, $errno, $errstr, 1);
|
||||
if ($handle === false) {
|
||||
// Server has stopped
|
||||
break;
|
||||
}
|
||||
\fclose($handle);
|
||||
\sleep(1);
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
if (\file_exists($routerScript)) {
|
||||
\unlink($routerScript);
|
||||
}
|
||||
}
|
||||
|
||||
private function createRouterScript(string $path, string $defaultHTTPPort, string $defaultHTTPSPort, string $organization, string $image, bool $noStart, array $vars, bool $isUpgrade = false, ?string $lockedDatabase = null): void
|
||||
{
|
||||
$installPhpPath = __FILE__;
|
||||
$appViewsPath = __DIR__ . '/../../../../app/views/install';
|
||||
$publicPath = __DIR__ . '/../../../../public';
|
||||
$vendorPath = __DIR__ . '/../../../../vendor/autoload.php';
|
||||
$appwritePath = __DIR__ . '/../../../../app/init.php';
|
||||
|
||||
$script = <<<'PHP'
|
||||
<?php
|
||||
require_once '%s';
|
||||
require_once '%s';
|
||||
|
||||
define('INSTALL_PHP_PATH', '%s');
|
||||
define('APP_VIEWS_PATH', '%s');
|
||||
define('PUBLIC_PATH', '%s');
|
||||
define('DEFAULT_HTTP_PORT', '%s');
|
||||
define('DEFAULT_HTTPS_PORT', '%s');
|
||||
define('ORGANIZATION', '%s');
|
||||
define('IMAGE', '%s');
|
||||
define('NO_START', %s);
|
||||
define('VARS_JSON', '%s');
|
||||
define('IS_UPGRADE', %s);
|
||||
define('LOCKED_DATABASE', %s);
|
||||
|
||||
$uri = urldecode(parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH));
|
||||
|
||||
// Serve static files
|
||||
if ($uri !== '/' && $uri !== '') {
|
||||
$filePath = PUBLIC_PATH . $uri;
|
||||
if (file_exists($filePath) && is_file($filePath)) {
|
||||
// Determine content type
|
||||
$extension = pathinfo($filePath, PATHINFO_EXTENSION);
|
||||
$mimeTypes = [
|
||||
'css' => 'text/css',
|
||||
'js' => 'application/javascript',
|
||||
'json' => 'application/json',
|
||||
'png' => 'image/png',
|
||||
'jpg' => 'image/jpeg',
|
||||
'jpeg' => 'image/jpeg',
|
||||
'gif' => 'image/gif',
|
||||
'svg' => 'image/svg+xml',
|
||||
'ico' => 'image/x-icon',
|
||||
'woff' => 'font/woff',
|
||||
'woff2' => 'font/woff2',
|
||||
'ttf' => 'font/ttf',
|
||||
'eot' => 'application/vnd.ms-fontobject',
|
||||
];
|
||||
|
||||
$contentType = $mimeTypes[$extension] ?? 'application/octet-stream';
|
||||
header('Content-Type: ' . $contentType);
|
||||
header('Content-Length: ' . filesize($filePath));
|
||||
readfile($filePath);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle POST request (installation)
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && $uri === '/install') {
|
||||
header('Content-Type: application/json');
|
||||
|
||||
$input = json_decode(file_get_contents('php://input'), true);
|
||||
|
||||
if (!$input) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['success' => false, 'message' => 'Invalid request']);
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
require_once INSTALL_PHP_PATH;
|
||||
$installer = new \Appwrite\Platform\Tasks\Install();
|
||||
|
||||
// Prepare user inputs - use locked database if in upgrade mode
|
||||
$userInput = [
|
||||
'_APP_ENV' => 'production',
|
||||
'_APP_OPENSSL_KEY_V1' => $input['opensslKey'] ?? '',
|
||||
'_APP_DOMAIN' => $input['appDomain'] ?? 'localhost',
|
||||
'_APP_DOMAIN_TARGET' => $input['appDomain'] ?? 'localhost',
|
||||
'_APP_EMAIL_CERTIFICATES' => $input['emailCertificates'] ?? '',
|
||||
'_APP_DB_ADAPTER' => LOCKED_DATABASE ?? ($input['database'] ?? 'mongodb'),
|
||||
];
|
||||
|
||||
// Use the prepareEnvironmentVariables method to merge with defaults
|
||||
$vars = json_decode(VARS_JSON, true);
|
||||
$envVars = $installer->prepareEnvironmentVariables($userInput, $vars);
|
||||
|
||||
// Call performInstallation method
|
||||
$installer->performInstallation(
|
||||
$input['httpPort'] ?? DEFAULT_HTTP_PORT,
|
||||
$input['httpsPort'] ?? DEFAULT_HTTPS_PORT,
|
||||
ORGANIZATION,
|
||||
IMAGE,
|
||||
$envVars,
|
||||
NO_START
|
||||
);
|
||||
|
||||
echo json_encode(['success' => true, 'message' => 'Installation completed successfully']);
|
||||
|
||||
// Stop the server after successful installation
|
||||
register_shutdown_function(function() {
|
||||
sleep(2); // Give time for response to be sent
|
||||
posix_kill(posix_getpid(), SIGTERM);
|
||||
});
|
||||
|
||||
} catch (\Throwable $e) {
|
||||
http_response_code(500);
|
||||
echo json_encode(['success' => false, 'message' => $e->getMessage()]);
|
||||
}
|
||||
exit;
|
||||
}
|
||||
|
||||
// Serve installer UI
|
||||
if ($uri === '/' || $uri === '') {
|
||||
header('Content-Type: text/html');
|
||||
|
||||
$vars = json_decode(VARS_JSON, true);
|
||||
$defaultHttpPort = DEFAULT_HTTP_PORT;
|
||||
$defaultHttpsPort = DEFAULT_HTTPS_PORT;
|
||||
$isUpgrade = IS_UPGRADE;
|
||||
$lockedDatabase = LOCKED_DATABASE;
|
||||
|
||||
include APP_VIEWS_PATH . '/installer.phtml';
|
||||
exit;
|
||||
}
|
||||
|
||||
// 404
|
||||
http_response_code(404);
|
||||
echo '404 Not Found';
|
||||
PHP;
|
||||
|
||||
$varsJson = json_encode($vars, JSON_UNESCAPED_SLASHES);
|
||||
$script = sprintf(
|
||||
$script,
|
||||
$vendorPath,
|
||||
$appwritePath,
|
||||
$installPhpPath,
|
||||
$appViewsPath,
|
||||
$publicPath,
|
||||
$defaultHTTPPort,
|
||||
$defaultHTTPSPort,
|
||||
$organization,
|
||||
$image,
|
||||
var_export($noStart, true),
|
||||
str_replace("'", "\\'", $varsJson),
|
||||
var_export($isUpgrade, true),
|
||||
var_export($lockedDatabase, true)
|
||||
);
|
||||
|
||||
file_put_contents($path, $script);
|
||||
}
|
||||
|
||||
public function prepareEnvironmentVariables(array $userInput, array $vars): array
|
||||
{
|
||||
$input = [];
|
||||
$password = new Password();
|
||||
$token = new Token();
|
||||
|
||||
// Start with all defaults
|
||||
foreach ($vars as $var) {
|
||||
if (!empty($var['filter']) && $var['filter'] === 'token') {
|
||||
$input[$var['name']] = $token->generate();
|
||||
} elseif (!empty($var['filter']) && $var['filter'] === 'password') {
|
||||
$input[$var['name']] = $password->generate();
|
||||
} else {
|
||||
$input[$var['name']] = $var['default'];
|
||||
}
|
||||
}
|
||||
|
||||
// Override with user inputs
|
||||
foreach ($userInput as $key => $value) {
|
||||
if ($value !== null && $value !== '') {
|
||||
$input[$key] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
// Set database-specific connection details
|
||||
$database = $input['_APP_DB_ADAPTER'] ?? 'mongodb';
|
||||
if ($database === 'mongodb') {
|
||||
$input['_APP_DB_HOST'] = 'mongodb';
|
||||
$input['_APP_DB_PORT'] = 27017;
|
||||
} elseif ($database === 'mariadb') {
|
||||
$input['_APP_DB_HOST'] = 'mariadb';
|
||||
$input['_APP_DB_PORT'] = 3306;
|
||||
}
|
||||
|
||||
return $input;
|
||||
}
|
||||
|
||||
public function performInstallation(
|
||||
string $httpPort,
|
||||
string $httpsPort,
|
||||
string $organization,
|
||||
string $image,
|
||||
array $input,
|
||||
bool $noStart
|
||||
): void {
|
||||
// Check if we're running in CLI context
|
||||
$isCLI = php_sapi_name() === 'cli';
|
||||
|
||||
$templateForCompose = new View(__DIR__ . '/../../../../app/views/install/compose.phtml');
|
||||
$templateForEnv = new View(__DIR__ . '/../../../../app/views/install/env.phtml');
|
||||
|
||||
$database = $input['_APP_DB_ADAPTER'] ?? 'mongodb';
|
||||
|
||||
$templateForCompose
|
||||
->setParam('httpPort', $httpPort)
|
||||
->setParam('httpsPort', $httpsPort)
|
||||
->setParam('version', APP_VERSION_STABLE)
|
||||
->setParam('version', 'next')
|
||||
->setParam('organization', $organization)
|
||||
->setParam('image', $image);
|
||||
->setParam('image', $image)
|
||||
->setParam('database', $database)
|
||||
->setParam('hostPath', $this->hostPath);
|
||||
|
||||
$templateForEnv->setParam('vars', $input);
|
||||
|
||||
if (!file_put_contents($this->path . '/docker-compose.yml', $templateForCompose->render(false))) {
|
||||
$message = 'Failed to save Docker Compose file';
|
||||
Console::error($message);
|
||||
Console::exit(1);
|
||||
if (!\file_put_contents($this->path . '/docker-compose.yml', $templateForCompose->render(false))) {
|
||||
throw new \Exception('Failed to save Docker Compose file');
|
||||
}
|
||||
|
||||
if (!file_put_contents($this->path . '/.env', $templateForEnv->render(false))) {
|
||||
$message = 'Failed to save environment variables file';
|
||||
Console::error($message);
|
||||
Console::exit(1);
|
||||
if (!\file_put_contents($this->path . '/.env', $templateForEnv->render(false))) {
|
||||
throw new \Exception('Failed to save environment variables file');
|
||||
}
|
||||
|
||||
$env = '';
|
||||
$stdout = '';
|
||||
$stderr = '';
|
||||
// Copy MongoDB entrypoint script for replica set setup
|
||||
if ($database === 'mongodb') {
|
||||
$mongoEntrypoint = __DIR__ . '/../../../../mongo-entrypoint.sh';
|
||||
|
||||
foreach ($input as $key => $value) {
|
||||
if ($value) {
|
||||
$env .= $key . '=' . \escapeshellarg($value) . ' ';
|
||||
if (file_exists($mongoEntrypoint)) {
|
||||
copy($mongoEntrypoint, $this->path . '/mongo-entrypoint.sh');
|
||||
}
|
||||
}
|
||||
|
||||
$exit = 0;
|
||||
if (!$noStart) {
|
||||
Console::log("Running \"docker compose up -d --remove-orphans --renew-anon-volumes\"");
|
||||
$exit = Console::execute("$env docker compose --project-directory $this->path up -d --remove-orphans --renew-anon-volumes", '', $stdout, $stderr);
|
||||
}
|
||||
$env = '';
|
||||
foreach ($input as $key => $value) {
|
||||
if ($value) {
|
||||
$env .= $key . '=' . \escapeshellarg($value) . ' ';
|
||||
}
|
||||
}
|
||||
|
||||
if ($exit !== 0) {
|
||||
$message = 'Failed to install Appwrite dockers';
|
||||
Console::error($message);
|
||||
Console::error($stderr);
|
||||
Console::exit($exit);
|
||||
if ($isCLI) {
|
||||
Console::log("Running \"docker compose up -d --remove-orphans --renew-anon-volumes\"");
|
||||
}
|
||||
|
||||
$command = "$env docker compose --project-directory $this->path up -d --remove-orphans --renew-anon-volumes 2>&1";
|
||||
\exec($command, $output, $exit);
|
||||
|
||||
if ($exit !== 0) {
|
||||
throw new \Exception('Failed to start Appwrite: ' . implode("\n", $output));
|
||||
}
|
||||
|
||||
if ($isCLI) {
|
||||
Console::success('Appwrite installed successfully');
|
||||
}
|
||||
} else {
|
||||
$message = 'Appwrite installed successfully';
|
||||
Console::success($message);
|
||||
if ($isCLI) {
|
||||
Console::success('Installation files created. Run "docker compose up -d" to start Appwrite');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@ use Utopia\Validator\Text;
|
||||
|
||||
class Upgrade extends Install
|
||||
{
|
||||
private ?string $lockedDatabase = null;
|
||||
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'upgrade';
|
||||
@@ -15,6 +17,8 @@ class Upgrade extends Install
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this
|
||||
->desc('Upgrade Appwrite')
|
||||
->param('http-port', '', new Text(4), 'Server HTTP port', true)
|
||||
@@ -39,6 +43,15 @@ class Upgrade extends Install
|
||||
Console::log(' └── docker-compose.yml');
|
||||
Console::exit(1);
|
||||
}
|
||||
parent::action($httpPort, $httpsPort, $organization, $image, $interactive, $noStart);
|
||||
|
||||
$database = System::getEnv('_APP_DB_ADAPTER', 'mongodb');
|
||||
$this->lockedDatabase = $database;
|
||||
|
||||
parent::action($httpPort, $httpsPort, $organization, $image, $interactive, $noStart, $database);
|
||||
}
|
||||
|
||||
protected function startWebServer(string $defaultHTTPPort, string $defaultHTTPSPort, string $organization, string $image, bool $noStart, array $vars, bool $isUpgrade = false, ?string $lockedDatabase = null): void
|
||||
{
|
||||
parent::startWebServer($defaultHTTPPort, $defaultHTTPSPort, $organization, $image, $noStart, $vars, true, $this->lockedDatabase);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user