diff --git a/app/cli.php b/app/cli.php index 66c4208e14..ef0445e67f 100644 --- a/app/cli.php +++ b/app/cli.php @@ -3,13 +3,14 @@ require_once __DIR__ . '/init.php'; require_once __DIR__ . '/controllers/general.php'; -use Appwrite\Task\CLIPlatform; +use Appwrite\CLI\Tasks; use Utopia\Database\Validator\Authorization; +use Utopia\Platform\Service; Authorization::disable(); -$cliPlatform = new CLIPlatform(); -$cliPlatform->init('CLI'); +$cliPlatform = new Tasks(); +$cliPlatform->init(Service::TYPE_CLI); $cli = $cliPlatform->getCli(); $cli->run(); diff --git a/app/tasks/doctor.php b/app/tasks/doctor.php deleted file mode 100644 index d79e8d530f..0000000000 --- a/app/tasks/doctor.php +++ /dev/null @@ -1,243 +0,0 @@ -task('doctor') - ->desc('Validate server health') - ->action(function () use ($register) { - Console::log(" __ ____ ____ _ _ ____ __ ____ ____ __ __ - / _\ ( _ \( _ \/ )( \( _ \( )(_ _)( __) ( )/ \ -/ \ ) __/ ) __/\ /\ / ) / )( )( ) _) _ )(( O ) -\_/\_/(__) (__) (_/\_)(__\_)(__) (__) (____)(_)(__)\__/ "); - - Console::log("\n" . '👩‍⚕️ Running ' . APP_NAME . ' Doctor for version ' . App::getEnv('_APP_VERSION', 'UNKNOWN') . ' ...' . "\n"); - - Console::log('Checking for production best practices...'); - - $domain = new Domain(App::getEnv('_APP_DOMAIN')); - - if (!$domain->isKnown() || $domain->isTest()) { - Console::log('🔴 Hostname has no public suffix (' . $domain->get() . ')'); - } else { - Console::log('🟢 Hostname has a public suffix (' . $domain->get() . ')'); - } - - $domain = new Domain(App::getEnv('_APP_DOMAIN_TARGET')); - - if (!$domain->isKnown() || $domain->isTest()) { - Console::log('🔴 CNAME target has no public suffix (' . $domain->get() . ')'); - } else { - Console::log('🟢 CNAME target has a public suffix (' . $domain->get() . ')'); - } - - if (App::getEnv('_APP_OPENSSL_KEY_V1') === 'your-secret-key' || empty(App::getEnv('_APP_OPENSSL_KEY_V1'))) { - Console::log('🔴 Not using a unique secret key for encryption'); - } else { - Console::log('🟢 Using a unique secret key for encryption'); - } - - if (App::getEnv('_APP_ENV', 'development') !== 'production') { - Console::log('🔴 App environment is set for development'); - } else { - Console::log('🟢 App environment is set for production'); - } - - if ('enabled' !== App::getEnv('_APP_OPTIONS_ABUSE', 'disabled')) { - Console::log('🔴 Abuse protection is disabled'); - } else { - Console::log('🟢 Abuse protection is enabled'); - } - - $authWhitelistRoot = App::getEnv('_APP_CONSOLE_WHITELIST_ROOT', null); - $authWhitelistEmails = App::getEnv('_APP_CONSOLE_WHITELIST_EMAILS', null); - $authWhitelistIPs = App::getEnv('_APP_CONSOLE_WHITELIST_IPS', null); - - if ( - empty($authWhitelistRoot) - && empty($authWhitelistEmails) - && empty($authWhitelistIPs) - ) { - Console::log('🔴 Console access limits are disabled'); - } else { - Console::log('🟢 Console access limits are enabled'); - } - - if ('enabled' !== App::getEnv('_APP_OPTIONS_FORCE_HTTPS', 'disabled')) { - Console::log('🔴 HTTPS force option is disabled'); - } else { - Console::log('🟢 HTTPS force option is enabled'); - } - - - $providerName = App::getEnv('_APP_LOGGING_PROVIDER', ''); - $providerConfig = App::getEnv('_APP_LOGGING_CONFIG', ''); - - if (empty($providerName) || empty($providerConfig) || !Logger::hasProvider($providerName)) { - Console::log('🔴 Logging adapter is disabled'); - } else { - Console::log('🟢 Logging adapter is enabled (' . $providerName . ')'); - } - - \sleep(0.2); - - try { - Console::log("\n" . 'Checking connectivity...'); - } catch (\Throwable $th) { - //throw $th; - } - - try { - $register->get('db'); /* @var $db PDO */ - Console::success('Database............connected 👍'); - } catch (\Throwable $th) { - Console::error('Database.........disconnected 👎'); - } - - try { - $register->get('cache'); - Console::success('Queue...............connected 👍'); - } catch (\Throwable $th) { - Console::error('Queue............disconnected 👎'); - } - - try { - $register->get('cache'); - Console::success('Cache...............connected 👍'); - } catch (\Throwable $th) { - Console::error('Cache............disconnected 👎'); - } - - if (App::getEnv('_APP_STORAGE_ANTIVIRUS') === 'enabled') { // Check if scans are enabled - try { - $antivirus = new Network( - App::getEnv('_APP_STORAGE_ANTIVIRUS_HOST', 'clamav'), - (int) App::getEnv('_APP_STORAGE_ANTIVIRUS_PORT', 3310) - ); - - if ((@$antivirus->ping())) { - Console::success('Antivirus...........connected 👍'); - } else { - Console::error('Antivirus........disconnected 👎'); - } - } catch (\Throwable $th) { - Console::error('Antivirus........disconnected 👎'); - } - } - - try { - $mail = $register->get('smtp'); /* @var $mail \PHPMailer\PHPMailer\PHPMailer */ - - $mail->addAddress('demo@example.com', 'Example.com'); - $mail->Subject = 'Test SMTP Connection'; - $mail->Body = 'Hello World'; - $mail->AltBody = 'Hello World'; - - $mail->send(); - Console::success('SMTP................connected 👍'); - } catch (\Throwable $th) { - Console::error('SMTP.............disconnected 👎'); - } - - $host = App::getEnv('_APP_STATSD_HOST', 'telegraf'); - $port = App::getEnv('_APP_STATSD_PORT', 8125); - - if ($fp = @\fsockopen('udp://' . $host, $port, $errCode, $errStr, 2)) { - Console::success('StatsD..............connected 👍'); - \fclose($fp); - } else { - Console::error('StatsD...........disconnected 👎'); - } - - $host = App::getEnv('_APP_INFLUXDB_HOST', ''); - $port = App::getEnv('_APP_INFLUXDB_PORT', ''); - - if ($fp = @\fsockopen($host, $port, $errCode, $errStr, 2)) { - Console::success('InfluxDB............connected 👍'); - \fclose($fp); - } else { - Console::error('InfluxDB.........disconnected 👎'); - } - - \sleep(0.2); - - Console::log(''); - Console::log('Checking volumes...'); - - foreach ( - [ - 'Uploads' => APP_STORAGE_UPLOADS, - 'Cache' => APP_STORAGE_CACHE, - 'Config' => APP_STORAGE_CONFIG, - 'Certs' => APP_STORAGE_CERTIFICATES - ] as $key => $volume - ) { - $device = new Local($volume); - - if (\is_readable($device->getRoot())) { - Console::success('🟢 ' . $key . ' Volume is readable'); - } else { - Console::error('🔴 ' . $key . ' Volume is unreadable'); - } - - if (\is_writable($device->getRoot())) { - Console::success('🟢 ' . $key . ' Volume is writeable'); - } else { - Console::error('🔴 ' . $key . ' Volume is unwriteable'); - } - } - - \sleep(0.2); - - Console::log(''); - Console::log('Checking disk space usage...'); - - foreach ( - [ - 'Uploads' => APP_STORAGE_UPLOADS, - 'Cache' => APP_STORAGE_CACHE, - 'Config' => APP_STORAGE_CONFIG, - 'Certs' => APP_STORAGE_CERTIFICATES - ] as $key => $volume - ) { - $device = new Local($volume); - - $percentage = (($device->getPartitionTotalSpace() - $device->getPartitionFreeSpace()) - / $device->getPartitionTotalSpace()) * 100; - - $message = $key . ' Volume has ' . Storage::human($device->getPartitionFreeSpace()) . ' free space (' . \round($percentage, 2) . '% used)'; - - if ($percentage < 80) { - Console::success('🟢 ' . $message); - } else { - Console::error('🔴 ' . $message); - } - } - - try { - if (App::isProduction()) { - Console::log(''); - $version = \json_decode(@\file_get_contents(App::getEnv('_APP_HOME', 'http://localhost') . '/v1/health/version'), true); - - if ($version && isset($version['version'])) { - if (\version_compare($version['version'], App::getEnv('_APP_VERSION', 'UNKNOWN')) === 0) { - Console::info('You are running the latest version of ' . APP_NAME . '! 🥳'); - } else { - Console::info('A new version (' . $version['version'] . ') is available! 🥳' . "\n"); - } - } else { - Console::error('Failed to check for a newer version' . "\n"); - } - } - } catch (\Throwable $th) { - Console::error('Failed to check for a newer version' . "\n"); - } - }); diff --git a/app/tasks/install.php b/app/tasks/install.php deleted file mode 100644 index 3519b58d0b..0000000000 --- a/app/tasks/install.php +++ /dev/null @@ -1,242 +0,0 @@ -task('install') - ->desc('Install Appwrite') - ->param('httpPort', '', new Text(4), 'Server HTTP port', true) - ->param('httpsPort', '', new Text(4), 'Server HTTPS port', true) - ->param('organization', 'appwrite', new Text(0), 'Docker Registry organization', true) - ->param('image', 'appwrite', new Text(0), 'Main appwrite docker image', true) - ->param('interactive', 'Y', new Text(1), 'Run an interactive session', true) - ->action(function ($httpPort, $httpsPort, $organization, $image, $interactive) { - /** - * 1. Start - DONE - * 2. Check for older setup and get older version - DONE - * 2.1 If older version is equal or bigger(?) than current version, **stop setup** - * 2.2. Get ENV vars - DONE - * 2.2.1 Fetch from older docker-compose.yml file - * 2.2.2 Fetch from older .env file (manually parse) - * 2.3 Use old ENV vars as default values - * 2.4 Ask for all required vars not given as CLI args and if in interactive mode - * Otherwise, just use default vars. - DONE - * 3. Ask user to backup important volumes, env vars, and SQL tables - * In th future we can try and automate this for smaller/medium size setups - * 4. Drop new docker-compose.yml setup (located inside the container, no network dependencies with appwrite.io) - DONE - * 5. Run docker compose up -d - DONE - * 6. Run data migration - */ - $config = Config::getParam('variables'); - $path = '/usr/src/code/appwrite'; - $defaultHTTPPort = '80'; - $defaultHTTPSPort = '443'; - $vars = []; - - /** - * We are using a random value every execution for identification. - * This allows us to collect information without invading the privacy of our users. - */ - $analytics = new GoogleAnalytics('UA-26264668-9', uniqid('server.', true)); - - foreach ($config as $category) { - foreach ($category['variables'] ?? [] as $var) { - $vars[] = $var; - } - } - - Console::success('Starting Appwrite installation...'); - - // Create directory with write permissions - if (null !== $path && !\file_exists(\dirname($path))) { - if (!@\mkdir(\dirname($path), 0755, true)) { - Console::error('Can\'t create directory ' . \dirname($path)); - Console::exit(1); - } - } - - $data = @file_get_contents($path . '/docker-compose.yml'); - - if ($data !== false) { - $time = \time(); - Console::info('Compose file found, creating backup: docker-compose.yml.' . $time . '.backup'); - file_put_contents($path . '/docker-compose.yml.' . $time . '.backup', $data); - $compose = new Compose($data); - $appwrite = $compose->getService('appwrite'); - $oldVersion = ($appwrite) ? $appwrite->getImageVersion() : null; - try { - $ports = $compose->getService('traefik')->getPorts(); - } catch (\Throwable $th) { - $ports = [ - $defaultHTTPPort => $defaultHTTPPort, - $defaultHTTPSPort => $defaultHTTPSPort - ]; - Console::warning('Traefik not found. Falling back to default ports.'); - } - - if ($oldVersion) { - foreach ($compose->getServices() as $service) { // Fetch all env vars from previous compose file - if (!$service) { - continue; - } - - $env = $service->getEnvironment()->list(); - - foreach ($env as $key => $value) { - if (is_null($value)) { - continue; - } - foreach ($vars as &$var) { - if ($var['name'] === $key) { - $var['default'] = $value; - } - } - } - } - - $data = @file_get_contents($path . '/.env'); - - if ($data !== false) { // Fetch all env vars from previous .env file - Console::info('Env file found, creating backup: .env.' . $time . '.backup'); - file_put_contents($path . '/.env.' . $time . '.backup', $data); - $env = new Env($data); - - foreach ($env->list() as $key => $value) { - if (is_null($value)) { - continue; - } - foreach ($vars as &$var) { - if ($var['name'] === $key) { - $var['default'] = $value; - } - } - } - } - - foreach ($ports as $key => $value) { - if ($value === $defaultHTTPPort) { - $defaultHTTPPort = $key; - } - - if ($value === $defaultHTTPSPort) { - $defaultHTTPSPort = $key; - } - } - } - } - - if (empty($httpPort)) { - $httpPort = Console::confirm('Choose your server HTTP port: (default: ' . $defaultHTTPPort . ')'); - $httpPort = ($httpPort) ? $httpPort : $defaultHTTPPort; - } - - if (empty($httpsPort)) { - $httpsPort = Console::confirm('Choose your server HTTPS port: (default: ' . $defaultHTTPSPort . ')'); - $httpsPort = ($httpsPort) ? $httpsPort : $defaultHTTPSPort; - } - - $input = []; - - foreach ($vars as $key => $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']] = Auth::tokenGenerator(); - continue; - } - - if ($var['filter'] === 'password') { - $input[$var['name']] = Auth::passwordGenerator(); - continue; - } - } - if (!$var['required'] || !Console::isInteractive() || $interactive !== 'Y') { - $input[$var['name']] = $var['default']; - continue; - } - - $input[$var['name']] = Console::confirm($var['question'] . ' (default: \'' . $var['default'] . '\')'); - - if (empty($input[$var['name']])) { - $input[$var['name']] = $var['default']; - } - - 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", "@", ""); - Console::warning("\nUse 'AAAA' if you're using an IPv6 address and 'A' if you're using an IPv4 address.\n"); - } - } - } - - $templateForCompose = new View(__DIR__ . '/../views/install/compose.phtml'); - $templateForEnv = new View(__DIR__ . '/../views/install/env.phtml'); - - $templateForCompose - ->setParam('httpPort', $httpPort) - ->setParam('httpsPort', $httpsPort) - ->setParam('version', APP_VERSION_STABLE) - ->setParam('organization', $organization) - ->setParam('image', $image) - ; - - $templateForEnv - ->setParam('vars', $input) - ; - - if (!file_put_contents($path . '/docker-compose.yml', $templateForCompose->render(false))) { - $message = 'Failed to save Docker Compose file'; - $analytics->createEvent('install/server', 'install', APP_VERSION_STABLE . ' - ' . $message); - Console::error($message); - Console::exit(1); - } - - if (!file_put_contents($path . '/.env', $templateForEnv->render(false))) { - $message = 'Failed to save environment variables file'; - $analytics->createEvent('install/server', 'install', APP_VERSION_STABLE . ' - ' . $message); - Console::error($message); - Console::exit(1); - } - - $env = ''; - $stdout = ''; - $stderr = ''; - - foreach ($input as $key => $value) { - if ($value) { - $env .= $key . '=' . \escapeshellarg($value) . ' '; - } - } - - Console::log("Running \"docker compose -f {$path}/docker-compose.yml up -d --remove-orphans --renew-anon-volumes\""); - - $exit = Console::execute("${env} docker compose -f {$path}/docker-compose.yml up -d --remove-orphans --renew-anon-volumes", '', $stdout, $stderr); - - if ($exit !== 0) { - $message = 'Failed to install Appwrite dockers'; - $analytics->createEvent('install/server', 'install', APP_VERSION_STABLE . ' - ' . $message); - Console::error($message); - Console::error($stderr); - Console::exit($exit); - } else { - $message = 'Appwrite installed successfully'; - $analytics->createEvent('install/server', 'install', APP_VERSION_STABLE . ' - ' . $message); - Console::success($message); - } - }); diff --git a/app/tasks/maintenance.php b/app/tasks/maintenance.php deleted file mode 100644 index eb50dbc0e2..0000000000 --- a/app/tasks/maintenance.php +++ /dev/null @@ -1,151 +0,0 @@ -get('cache'))); - $database = new Database(new MariaDB($register->get('db')), $cache); - $database->setDefaultDatabase(App::getEnv('_APP_DB_SCHEMA', 'appwrite')); - $database->setNamespace('_console'); // Main DB - - if (!$database->exists($database->getDefaultDatabase(), 'certificates')) { - throw new \Exception('Console project not ready'); - } - - break; // leave loop if successful - } catch (\Exception $e) { - Console::warning("Database not ready. Retrying connection ({$attempts})..."); - if ($attempts >= DATABASE_RECONNECT_MAX_ATTEMPTS) { - throw new \Exception('Failed to connect to database: ' . $e->getMessage()); - } - sleep(DATABASE_RECONNECT_SLEEP); - } - } while ($attempts < DATABASE_RECONNECT_MAX_ATTEMPTS); - - return $database; -} - -$cli - ->task('maintenance') - ->desc('Schedules maintenance tasks and publishes them to resque') - ->action(function () { - Console::title('Maintenance V1'); - Console::success(APP_NAME . ' maintenance process v1 has started'); - - function notifyDeleteExecutionLogs(int $interval) - { - (new Delete()) - ->setType(DELETE_TYPE_EXECUTIONS) - ->setTimestamp(time() - $interval) - ->trigger(); - } - - function notifyDeleteAbuseLogs(int $interval) - { - (new Delete()) - ->setType(DELETE_TYPE_ABUSE) - ->setTimestamp(time() - $interval) - ->trigger(); - } - - function notifyDeleteAuditLogs(int $interval) - { - (new Delete()) - ->setType(DELETE_TYPE_AUDIT) - ->setTimestamp(time() - $interval) - ->trigger(); - } - - function notifyDeleteUsageStats(int $interval30m, int $interval1d) - { - (new Delete()) - ->setType(DELETE_TYPE_USAGE) - ->setTimestamp1d(time() - $interval1d) - ->setTimestamp30m(time() - $interval30m) - ->trigger(); - } - - function notifyDeleteConnections() - { - (new Delete()) - ->setType(DELETE_TYPE_REALTIME) - ->setTimestamp(time() - 60) - ->trigger(); - } - - function notifyDeleteExpiredSessions() - { - (new Delete()) - ->setType(DELETE_TYPE_SESSIONS) - ->setTimestamp(time() - Auth::TOKEN_EXPIRATION_LOGIN_LONG) - ->trigger(); - } - - function renewCertificates($dbForConsole) - { - $time = date('d-m-Y H:i:s', time()); - $certificates = $dbForConsole->find('certificates', [ - new Query('attempts', Query::TYPE_LESSEREQUAL, [5]), // Maximum 5 attempts - new Query('renewDate', Query::TYPE_LESSEREQUAL, [\time()]) // includes 60 days cooldown (we have 30 days to renew) - ], 200); // Limit 200 comes from LetsEncrypt (300 orders per 3 hours, keeping some for new domains) - - - if (\count($certificates) > 0) { - Console::info("[{$time}] Found " . \count($certificates) . " certificates for renewal, scheduling jobs."); - - $event = new Certificate(); - foreach ($certificates as $certificate) { - $event - ->setDomain(new Document([ - 'domain' => $certificate->getAttribute('domain') - ])) - ->trigger(); - } - } else { - Console::info("[{$time}] No certificates for renewal."); - } - } - - // # of days in seconds (1 day = 86400s) - $interval = (int) App::getEnv('_APP_MAINTENANCE_INTERVAL', '86400'); - $executionLogsRetention = (int) App::getEnv('_APP_MAINTENANCE_RETENTION_EXECUTION', '1209600'); - $auditLogRetention = (int) App::getEnv('_APP_MAINTENANCE_RETENTION_AUDIT', '1209600'); - $abuseLogsRetention = (int) App::getEnv('_APP_MAINTENANCE_RETENTION_ABUSE', '86400'); - $usageStatsRetention30m = (int) App::getEnv('_APP_MAINTENANCE_RETENTION_USAGE_30M', '129600'); //36 hours - $usageStatsRetention1d = (int) App::getEnv('_APP_MAINTENANCE_RETENTION_USAGE_1D', '8640000'); // 100 days - - Console::loop(function () use ($interval, $executionLogsRetention, $abuseLogsRetention, $auditLogRetention, $usageStatsRetention30m, $usageStatsRetention1d) { - $database = getConsoleDB(); - - $time = date('d-m-Y H:i:s', time()); - Console::info("[{$time}] Notifying workers with maintenance tasks every {$interval} seconds"); - notifyDeleteExecutionLogs($executionLogsRetention); - notifyDeleteAbuseLogs($abuseLogsRetention); - notifyDeleteAuditLogs($auditLogRetention); - notifyDeleteUsageStats($usageStatsRetention30m, $usageStatsRetention1d); - notifyDeleteConnections(); - notifyDeleteExpiredSessions(); - renewCertificates($database); - }, $interval); - }); diff --git a/app/tasks/migrate.php b/app/tasks/migrate.php deleted file mode 100644 index ff5ec3593f..0000000000 --- a/app/tasks/migrate.php +++ /dev/null @@ -1,84 +0,0 @@ -task('migrate') - ->param('version', APP_VERSION_STABLE, new Text(8), 'Version to migrate to.', true) - ->action(function ($version) use ($register) { - Authorization::disable(); - if (!array_key_exists($version, Migration::$versions)) { - Console::error("Version {$version} not found."); - Console::exit(1); - return; - } - - $app = new App('UTC'); - - Console::success('Starting Data Migration to version ' . $version); - - $db = $register->get('db', true); - $redis = $register->get('cache', true); - $redis->flushAll(); - $cache = new Cache(new RedisCache($redis)); - - $projectDB = new Database(new MariaDB($db), $cache); - $projectDB->setDefaultDatabase(App::getEnv('_APP_DB_SCHEMA', 'appwrite')); - - $consoleDB = new Database(new MariaDB($db), $cache); - $consoleDB->setDefaultDatabase(App::getEnv('_APP_DB_SCHEMA', 'appwrite')); - $consoleDB->setNamespace('_project_console'); - - $console = $app->getResource('console'); - - $limit = 30; - $sum = 30; - $offset = 0; - $projects = [$console]; - $count = 0; - - try { - $totalProjects = $consoleDB->count('projects') + 1; - } catch (\Throwable $th) { - $consoleDB->setNamespace('_console'); - $totalProjects = $consoleDB->count('projects') + 1; - } - - $class = 'Appwrite\\Migration\\Version\\' . Migration::$versions[$version]; - $migration = new $class(); - - while (!empty($projects)) { - foreach ($projects as $project) { - try { - $migration - ->setProject($project, $projectDB, $consoleDB) - ->execute(); - } catch (\Throwable $th) { - throw $th; - Console::error('Failed to update project ("' . $project->getId() . '") version with error: ' . $th->getMessage()); - } - } - - $sum = \count($projects); - $projects = $consoleDB->find('projects', limit: $limit, offset: $offset); - - $offset = $offset + $limit; - $count = $count + $sum; - - Console::log('Migrated ' . $count . '/' . $totalProjects . ' projects...'); - } - - Swoole\Event::wait(); // Wait for Coroutines to finish - $redis->flushAll(); - Console::success('Data Migration Completed'); - }); diff --git a/app/tasks/sdks.php b/app/tasks/sdks.php deleted file mode 100644 index d873167743..0000000000 --- a/app/tasks/sdks.php +++ /dev/null @@ -1,257 +0,0 @@ -task('sdks') - ->action(function () { - $platforms = Config::getParam('platforms'); - $selected = \strtolower(Console::confirm('Choose SDK ("*" for all):')); - $version = Console::confirm('Choose an Appwrite version'); - $git = (Console::confirm('Should we use git push? (yes/no)') == 'yes'); - $production = ($git) ? (Console::confirm('Type "Appwrite" to push code to production git repos') == 'Appwrite') : false; - $message = ($git) ? Console::confirm('Please enter your commit message:') : ''; - - if (!in_array($version, ['0.6.x', '0.7.x', '0.8.x', '0.9.x', '0.10.x', '0.11.x', '0.12.x', '0.13.x', '0.14.x', '0.15.x', 'latest'])) { - throw new Exception('Unknown version given'); - } - - foreach ($platforms as $key => $platform) { - foreach ($platform['languages'] as $language) { - if ($selected !== $language['key'] && $selected !== '*') { - continue; - } - - if (!$language['enabled']) { - Console::warning($language['name'] . ' for ' . $platform['name'] . ' is disabled'); - continue; - } - - Console::info('Fetching API Spec for ' . $language['name'] . ' for ' . $platform['name'] . ' (version: ' . $version . ')'); - - $spec = file_get_contents(__DIR__ . '/../config/specs/swagger2-' . $version . '-' . $language['family'] . '.json'); - - $cover = 'https://appwrite.io/images/github.png'; - $result = \realpath(__DIR__ . '/..') . '/sdks/' . $key . '-' . $language['key']; - $resultExamples = \realpath(__DIR__ . '/../..') . '/docs/examples/' . $version . '/' . $key . '-' . $language['key']; - $target = \realpath(__DIR__ . '/..') . '/sdks/git/' . $language['key'] . '/'; - $readme = \realpath(__DIR__ . '/../../docs/sdks/' . $language['key'] . '/README.md'); - $readme = ($readme) ? \file_get_contents($readme) : ''; - $gettingStarted = \realpath(__DIR__ . '/../../docs/sdks/' . $language['key'] . '/GETTING_STARTED.md'); - $gettingStarted = ($gettingStarted) ? \file_get_contents($gettingStarted) : ''; - $examples = \realpath(__DIR__ . '/../../docs/sdks/' . $language['key'] . '/EXAMPLES.md'); - $examples = ($examples) ? \file_get_contents($examples) : ''; - $changelog = \realpath(__DIR__ . '/../../docs/sdks/' . $language['key'] . '/CHANGELOG.md'); - $changelog = ($changelog) ? \file_get_contents($changelog) : '# Change Log'; - $warning = '**This SDK is compatible with Appwrite server version ' . $version . '. For older versions, please check [previous releases](' . $language['url'] . '/releases).**'; - $license = 'BSD-3-Clause'; - $licenseContent = 'Copyright (c) ' . date('Y') . ' Appwrite (https://appwrite.io) and individual contributors. -All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - - 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - - 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - - 3. Neither the name Appwrite nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.'; - - switch ($language['key']) { - case 'web': - $config = new Web(); - $config->setNPMPackage('appwrite'); - $config->setBowerPackage('appwrite'); - break; - case 'cli': - $config = new CLI(); - $config->setNPMPackage('appwrite-cli'); - $config->setExecutableName('appwrite'); - $config->setLogo(json_encode(" - _ _ _ ___ __ _____ - /_\ _ __ _ ____ ___ __(_) |_ ___ / __\ / / \_ \ - //_\\\| '_ \| '_ \ \ /\ / / '__| | __/ _ \ / / / / / /\/ - / _ \ |_) | |_) \ V V /| | | | || __/ / /___/ /___/\/ /_ - \_/ \_/ .__/| .__/ \_/\_/ |_| |_|\__\___| \____/\____/\____/ - |_| |_| - -")); - $config->setLogoUnescaped(" - _ _ _ ___ __ _____ - /_\ _ __ _ ____ ___ __(_) |_ ___ / __\ / / \_ \ - //_\\\| '_ \| '_ \ \ /\ / / '__| | __/ _ \ / / / / / /\/ - / _ \ |_) | |_) \ V V /| | | | || __/ / /___/ /___/\/ /_ - \_/ \_/ .__/| .__/ \_/\_/ |_| |_|\__\___| \____/\____/\____/ - |_| |_| "); - break; - case 'php': - $config = new PHP(); - $config->setComposerVendor('appwrite'); - $config->setComposerPackage('appwrite'); - break; - case 'nodejs': - $config = new Node(); - $config->setNPMPackage('node-appwrite'); - $config->setBowerPackage('appwrite'); - $warning = $warning . "\n\n > This is the Node.js SDK for integrating with Appwrite from your Node.js server-side code. - If you're looking to integrate from the browser, you should check [appwrite/sdk-for-web](https://github.com/appwrite/sdk-for-web)"; - break; - case 'deno': - $config = new Deno(); - break; - case 'python': - $config = new Python(); - $config->setPipPackage('appwrite'); - $license = 'BSD License'; // license edited due to classifiers in pypi - break; - case 'ruby': - $config = new Ruby(); - $config->setGemPackage('appwrite'); - break; - case 'flutter': - $config = new Flutter(); - $config->setPackageName('appwrite'); - break; - case 'flutter-dev': - $config = new Flutter(); - $config->setPackageName('appwrite_dev'); - break; - case 'dart': - $config = new Dart(); - $config->setPackageName('dart_appwrite'); - $warning = $warning . "\n\n > This is the Dart SDK for integrating with Appwrite from your Dart server-side code. If you're looking for the Flutter SDK you should check [appwrite/sdk-for-flutter](https://github.com/appwrite/sdk-for-flutter)"; - break; - case 'go': - $config = new Go(); - break; - case 'swift': - $config = new Swift(); - $warning = $warning . "\n\n > This is the Swift SDK for integrating with Appwrite from your Swift server-side code. If you're looking for the Apple SDK you should check [appwrite/sdk-for-apple](https://github.com/appwrite/sdk-for-apple)"; - break; - case 'apple': - $config = new SwiftClient(); - break; - case 'dotnet': - $cover = ''; - $config = new DotNet(); - break; - case 'android': - $config = new Android(); - break; - case 'kotlin': - $config = new Kotlin(); - $warning = $warning . "\n\n > This is the Kotlin SDK for integrating with Appwrite from your Kotlin server-side code. If you're looking for the Android SDK you should check [appwrite/sdk-for-android](https://github.com/appwrite/sdk-for-android)"; - break; - default: - throw new Exception('Language "' . $language['key'] . '" not supported'); - break; - } - - Console::info("Generating {$language['name']} SDK..."); - - $sdk = new SDK($config, new Swagger2($spec)); - - $sdk - ->setName($language['name']) - ->setNamespace('io appwrite') - ->setDescription("Appwrite is an open-source backend as a service server that abstract and simplify complex and repetitive development tasks behind a very simple to use REST API. Appwrite aims to help you develop your apps faster and in a more secure way. Use the {$language['name']} SDK to integrate your app with the Appwrite server to easily start interacting with all of Appwrite backend APIs and tools. For full API documentation and tutorials go to [https://appwrite.io/docs](https://appwrite.io/docs)") - ->setShortDescription('Appwrite is an open-source self-hosted backend server that abstract and simplify complex and repetitive development tasks behind a very simple REST API') - ->setLicense($license) - ->setLicenseContent($licenseContent) - ->setVersion($language['version']) - ->setGitURL($language['url']) - ->setGitRepo($language['gitUrl']) - ->setGitRepoName($language['gitRepoName']) - ->setGitUserName($language['gitUserName']) - ->setLogo($cover) - ->setURL('https://appwrite.io') - ->setShareText('Appwrite is a backend as a service for building web or mobile apps') - ->setShareURL('http://appwrite.io') - ->setShareTags('JS,javascript,reactjs,angular,ios,android,serverless') - ->setShareVia('appwrite') - ->setWarning($warning) - ->setReadme($readme) - ->setGettingStarted($gettingStarted) - ->setChangelog($changelog) - ->setExamples($examples) - ->setTwitter(APP_SOCIAL_TWITTER_HANDLE) - ->setDiscord(APP_SOCIAL_DISCORD_CHANNEL, APP_SOCIAL_DISCORD) - ->setDefaultHeaders([ - 'X-Appwrite-Response-Format' => '0.15.0', - ]); - - try { - $sdk->generate($result); - } catch (Exception $exception) { - Console::error($exception->getMessage()); - } catch (Throwable $exception) { - Console::error($exception->getMessage()); - } - - $gitUrl = $language['gitUrl']; - $gitBranch = $language['gitBranch']; - - - if (!$production) { - $gitUrl = 'git@github.com:aw-tests/' . $language['gitRepoName'] . '.git'; - } - - if ($git && !empty($gitUrl)) { - \exec('rm -rf ' . $target . ' && \ - mkdir -p ' . $target . ' && \ - cd ' . $target . ' && \ - git init --initial-branch=' . $gitBranch . ' && \ - git remote add origin ' . $gitUrl . ' && \ - git fetch && \ - git pull ' . $gitUrl . ' && \ - rm -rf ' . $target . '/* && \ - cp -r ' . $result . '/* ' . $target . '/ && \ - git add . && \ - git commit -m "' . $message . '" && \ - git push -u origin ' . $gitBranch . ' - '); - - Console::success("Pushed {$language['name']} SDK to {$gitUrl}"); - - \exec('rm -rf ' . $target); - Console::success("Remove temp directory '{$target}' for {$language['name']} SDK"); - } - - $docDirectories = $language['docDirectories'] ?? ['']; - - if ($version === 'latest') { - continue; - } - - foreach ($docDirectories as $languageTitle => $path) { - $languagePath = strtolower($languageTitle !== 0 ? '/' . $languageTitle : ''); - \exec( - 'mkdir -p ' . $resultExamples . $languagePath . ' && \ - cp -r ' . $result . '/docs/examples' . $languagePath . ' ' . $resultExamples - ); - Console::success("Copied code examples for {$language['name']} SDK to: {$resultExamples}"); - } - } - } - - Console::exit(); - }); diff --git a/app/tasks/specs.php b/app/tasks/specs.php deleted file mode 100644 index 8e8fdb4e7c..0000000000 --- a/app/tasks/specs.php +++ /dev/null @@ -1,256 +0,0 @@ -task('specs') - ->param('version', 'latest', new Text(8), 'Spec version', true) - ->param('mode', 'normal', new WhiteList(['normal', 'mocks']), 'Spec Mode', true) - ->action(function ($version, $mode) use ($register) { - $db = $register->get('db'); - $redis = $register->get('cache'); - $appRoutes = App::getRoutes(); - $response = new Response(new HttpResponse()); - $mocks = ($mode === 'mocks'); - - App::setResource('request', fn () => new Request()); - App::setResource('response', fn () => $response); - App::setResource('db', fn () => $db); - App::setResource('cache', fn () => $redis); - - $platforms = [ - 'client' => APP_PLATFORM_CLIENT, - 'server' => APP_PLATFORM_SERVER, - 'console' => APP_PLATFORM_CONSOLE, - ]; - - $authCounts = [ - 'client' => 1, - 'server' => 2, - 'console' => 1, - ]; - - $keys = [ - APP_PLATFORM_CLIENT => [ - 'Project' => [ - 'type' => 'apiKey', - 'name' => 'X-Appwrite-Project', - 'description' => 'Your project ID', - 'in' => 'header', - ], - 'JWT' => [ - 'type' => 'apiKey', - 'name' => 'X-Appwrite-JWT', - 'description' => 'Your secret JSON Web Token', - 'in' => 'header', - ], - 'Locale' => [ - 'type' => 'apiKey', - 'name' => 'X-Appwrite-Locale', - 'description' => '', - 'in' => 'header', - ], - ], - APP_PLATFORM_SERVER => [ - 'Project' => [ - 'type' => 'apiKey', - 'name' => 'X-Appwrite-Project', - 'description' => 'Your project ID', - 'in' => 'header', - ], - 'Key' => [ - 'type' => 'apiKey', - 'name' => 'X-Appwrite-Key', - 'description' => 'Your secret API key', - 'in' => 'header', - ], - 'JWT' => [ - 'type' => 'apiKey', - 'name' => 'X-Appwrite-JWT', - 'description' => 'Your secret JSON Web Token', - 'in' => 'header', - ], - 'Locale' => [ - 'type' => 'apiKey', - 'name' => 'X-Appwrite-Locale', - 'description' => '', - 'in' => 'header', - ], - ], - APP_PLATFORM_CONSOLE => [ - 'Project' => [ - 'type' => 'apiKey', - 'name' => 'X-Appwrite-Project', - 'description' => 'Your project ID', - 'in' => 'header', - ], - 'Key' => [ - 'type' => 'apiKey', - 'name' => 'X-Appwrite-Key', - 'description' => 'Your secret API key', - 'in' => 'header', - ], - 'JWT' => [ - 'type' => 'apiKey', - 'name' => 'X-Appwrite-JWT', - 'description' => 'Your secret JSON Web Token', - 'in' => 'header', - ], - 'Locale' => [ - 'type' => 'apiKey', - 'name' => 'X-Appwrite-Locale', - 'description' => '', - 'in' => 'header', - ], - 'Mode' => [ - 'type' => 'apiKey', - 'name' => 'X-Appwrite-Mode', - 'description' => '', - 'in' => 'header', - ], - ], - ]; - - foreach ($platforms as $platform) { - $routes = []; - $models = []; - $services = []; - - foreach ($appRoutes as $key => $method) { - foreach ($method as $route) { - /** @var \Utopia\Route $route */ - $routeSecurity = $route->getLabel('sdk.auth', []); - $sdkPlaforms = []; - - foreach ($routeSecurity as $value) { - switch ($value) { - case APP_AUTH_TYPE_SESSION: - $sdkPlaforms[] = APP_PLATFORM_CLIENT; - break; - case APP_AUTH_TYPE_KEY: - $sdkPlaforms[] = APP_PLATFORM_SERVER; - break; - case APP_AUTH_TYPE_JWT: - $sdkPlaforms[] = APP_PLATFORM_SERVER; - break; - case APP_AUTH_TYPE_ADMIN: - $sdkPlaforms[] = APP_PLATFORM_CONSOLE; - break; - } - } - - if (empty($routeSecurity)) { - $sdkPlaforms[] = APP_PLATFORM_CLIENT; - } - - if (!$route->getLabel('docs', true)) { - continue; - } - - if ($route->getLabel('sdk.mock', false) && !$mocks) { - continue; - } - - if (!$route->getLabel('sdk.mock', false) && $mocks) { - continue; - } - - if (empty($route->getLabel('sdk.namespace', null))) { - continue; - } - - if ($platform !== APP_PLATFORM_CONSOLE && !\in_array($platforms[$platform], $sdkPlaforms)) { - continue; - } - - $routes[] = $route; - } - } - - foreach (Config::getParam('services', []) as $service) { - if ( - !isset($service['docs']) // Skip service if not part of the public API - || !isset($service['sdk']) - || !$service['docs'] - || !$service['sdk'] - ) { - continue; - } - - $services[] = [ - 'name' => $service['key'] ?? '', - 'description' => $service['subtitle'] ?? '', - 'x-globalAttributes' => $service['globalAttributes'] ?? [], - ]; - } - - $models = $response->getModels(); - - foreach ($models as $key => $value) { - if ($platform !== APP_PLATFORM_CONSOLE && !$value->isPublic()) { - unset($models[$key]); - } - } - // var_dump($models); - $arguments = [new App('UTC'), $services, $routes, $models, $keys[$platform], $authCounts[$platform] ?? 0]; - foreach (['swagger2', 'open-api3'] as $format) { - $formatInstance = match ($format) { - 'swagger2' => new Swagger2(...$arguments), - 'open-api3' => new OpenAPI3(...$arguments), - default => throw new Exception('Format not found: ' . $format) - }; - - $specs = new Specification($formatInstance); - $endpoint = App::getEnv('_APP_HOME', '[HOSTNAME]'); - $email = App::getEnv('_APP_SYSTEM_EMAIL_ADDRESS', APP_EMAIL_TEAM); - - $formatInstance - ->setParam('name', APP_NAME) - ->setParam('description', 'Appwrite backend as a service cuts up to 70% of the time and costs required for building a modern application. We abstract and simplify common development tasks behind a REST APIs, to help you develop your app in a fast and secure way. For full API documentation and tutorials go to [https://appwrite.io/docs](https://appwrite.io/docs)') - ->setParam('endpoint', 'https://HOSTNAME/v1') - ->setParam('version', APP_VERSION_STABLE) - ->setParam('terms', $endpoint . '/policy/terms') - ->setParam('support.email', $email) - ->setParam('support.url', $endpoint . '/support') - ->setParam('contact.name', APP_NAME . ' Team') - ->setParam('contact.email', $email) - ->setParam('contact.url', $endpoint . '/support') - ->setParam('license.name', 'BSD-3-Clause') - ->setParam('license.url', 'https://raw.githubusercontent.com/appwrite/appwrite/master/LICENSE') - ->setParam('docs.description', 'Full API docs, specs and tutorials') - ->setParam('docs.url', $endpoint . '/docs'); - - if ($mocks) { - $path = __DIR__ . '/../config/specs/' . $format . '-mocks-' . $platform . '.json'; - - if (!file_put_contents($path, json_encode($specs->parse()))) { - throw new Exception('Failed to save mocks spec file: ' . $path); - } - - Console::success('Saved mocks spec file: ' . realpath($path)); - - continue; - } - - $path = __DIR__ . '/../config/specs/' . $format . '-' . $version . '-' . $platform . '.json'; - - if (!file_put_contents($path, json_encode($specs->parse()))) { - throw new Exception('Failed to save spec file: ' . $path); - } - - Console::success('Saved spec file: ' . realpath($path)); - } - } - }); diff --git a/app/tasks/ssl.php b/app/tasks/ssl.php deleted file mode 100644 index 345da1f22e..0000000000 --- a/app/tasks/ssl.php +++ /dev/null @@ -1,24 +0,0 @@ -task('ssl') - ->desc('Validate server certificates') - ->param('domain', App::getEnv('_APP_DOMAIN', ''), new Hostname(), 'Domain to generate certificate for. If empty, main domain will be used.', true) - ->action(function ($domain) { - Console::success('Scheduling a job to issue a TLS certificate for domain: ' . $domain); - - (new Certificate()) - ->setDomain(new Document([ - 'domain' => $domain - ])) - ->setSkipRenewCheck(true) - ->trigger(); - }); diff --git a/app/tasks/usage.php b/app/tasks/usage.php deleted file mode 100644 index 7a22d7e79f..0000000000 --- a/app/tasks/usage.php +++ /dev/null @@ -1,164 +0,0 @@ -get('db'); - $redis = $register->get('cache'); - - $cache = new Cache(new RedisCache($redis)); - $database = new Database(new MariaDB($db), $cache); - $database->setDefaultDatabase(App::getEnv('_APP_DB_SCHEMA', 'appwrite')); - $database->setNamespace($namespace); - - if (!$database->exists($database->getDefaultDatabase(), 'projects')) { - throw new Exception('Projects collection not ready'); - } - break; // leave loop if successful - } catch (\Exception$e) { - Console::warning("Database not ready. Retrying connection ({$attempts})..."); - if ($attempts >= DATABASE_RECONNECT_MAX_ATTEMPTS) { - throw new \Exception('Failed to connect to database: ' . $e->getMessage()); - } - sleep(DATABASE_RECONNECT_SLEEP); - } - } while ($attempts < DATABASE_RECONNECT_MAX_ATTEMPTS); - - return $database; -} - -function getInfluxDB(Registry &$register): InfluxDatabase -{ - /** @var InfluxDB\Client $client */ - $client = $register->get('influxdb'); - $attempts = 0; - $max = 10; - $sleep = 1; - - do { // check if telegraf database is ready - try { - $attempts++; - $database = $client->selectDB('telegraf'); - if (in_array('telegraf', $client->listDatabases())) { - break; // leave the do-while if successful - } - } catch (\Throwable$th) { - Console::warning("InfluxDB not ready. Retrying connection ({$attempts})..."); - if ($attempts >= $max) { - throw new \Exception('InfluxDB database not ready yet'); - } - sleep($sleep); - } - } while ($attempts < $max); - return $database; -} - -$logError = function (Throwable $error, string $action = 'syncUsageStats') use ($register) { - $logger = $register->get('logger'); - - if ($logger) { - $version = App::getEnv('_APP_VERSION', 'UNKNOWN'); - - $log = new Log(); - $log->setNamespace("usage"); - $log->setServer(\gethostname()); - $log->setVersion($version); - $log->setType(Log::TYPE_ERROR); - $log->setMessage($error->getMessage()); - - $log->addTag('code', $error->getCode()); - $log->addTag('verboseType', get_class($error)); - - $log->addExtra('file', $error->getFile()); - $log->addExtra('line', $error->getLine()); - $log->addExtra('trace', $error->getTraceAsString()); - $log->addExtra('detailedTrace', $error->getTrace()); - - $log->setAction($action); - - $isProduction = App::getEnv('_APP_ENV', 'development') === 'production'; - $log->setEnvironment($isProduction ? Log::ENVIRONMENT_PRODUCTION : Log::ENVIRONMENT_STAGING); - - $responseCode = $logger->addLog($log); - Console::info('Usage stats log pushed with status code: ' . $responseCode); - } - - Console::warning("Failed: {$error->getMessage()}"); - Console::warning($error->getTraceAsString()); -}; - -$cli - ->task('usage') - ->desc('Schedules syncing data from influxdb to Appwrite console db') - ->action(function () use ($register, $logError) { - Console::title('Usage Aggregation V1'); - Console::success(APP_NAME . ' usage aggregation process v1 has started'); - - $interval = (int) App::getEnv('_APP_USAGE_AGGREGATION_INTERVAL', '30'); // 30 seconds (by default) - - $database = getDatabase($register, '_console'); - $influxDB = getInfluxDB($register); - - $usage = new Usage($database, $influxDB, $logError); - $usageDB = new UsageDB($database, $logError); - - $iterations = 0; - Console::loop(function () use ($interval, $usage, $usageDB, &$iterations) { - $now = date('d-m-Y H:i:s', time()); - Console::info("[{$now}] Aggregating usage data every {$interval} seconds"); - - $loopStart = microtime(true); - - /** - * Aggregate InfluxDB every 30 seconds - */ - $usage->collect(); - - if ($iterations % 30 != 0) { // return if 30 iterations has not passed - $iterations++; - $loopTook = microtime(true) - $loopStart; - $now = date('d-m-Y H:i:s', time()); - Console::info("[{$now}] Aggregation took {$loopTook} seconds"); - return; - } - - $iterations = 0; // Reset iterations to prevent overflow when running for long time - /** - * Aggregate MariaDB every 15 minutes - * Some of the queries here might contain full-table scans. - */ - $now = date('d-m-Y H:i:s', time()); - Console::info("[{$now}] Aggregating database counters."); - - $usageDB->collect(); - - $iterations++; - $loopTook = microtime(true) - $loopStart; - $now = date('d-m-Y H:i:s', time()); - - Console::info("[{$now}] Aggregation took {$loopTook} seconds"); - }, $interval); - }); diff --git a/app/tasks/vars.php b/app/tasks/vars.php deleted file mode 100644 index af972e218d..0000000000 --- a/app/tasks/vars.php +++ /dev/null @@ -1,25 +0,0 @@ -task('vars') - ->desc('List all the server environment variables') - ->action(function () { - $config = Config::getParam('variables', []); - $vars = []; - - foreach ($config as $category) { - foreach ($category['variables'] ?? [] as $var) { - $vars[] = $var; - } - } - - foreach ($vars as $key => $value) { - Console::log('- ' . $value['name'] . '=' . App::getEnv($value['name'], '')); - } - }); diff --git a/composer.lock b/composer.lock index f2bc50530e..b94d95b84a 100644 --- a/composer.lock +++ b/composer.lock @@ -5441,5 +5441,5 @@ "platform-overrides": { "php": "8.0" }, - "plugin-api-version": "2.2.0" + "plugin-api-version": "2.3.0" } diff --git a/src/Appwrite/CLI/Tasks.php b/src/Appwrite/CLI/Tasks.php new file mode 100644 index 0000000000..7f07b8137a --- /dev/null +++ b/src/Appwrite/CLI/Tasks.php @@ -0,0 +1,13 @@ +addService('cliTasks', new TasksService()); + } +} diff --git a/src/Appwrite/Task/Doctor.php b/src/Appwrite/CLI/Tasks/Doctor.php similarity index 99% rename from src/Appwrite/Task/Doctor.php rename to src/Appwrite/CLI/Tasks/Doctor.php index 281efe4d35..35fe9d048e 100644 --- a/src/Appwrite/Task/Doctor.php +++ b/src/Appwrite/CLI/Tasks/Doctor.php @@ -1,6 +1,6 @@ addService('cliTasks', new Tasks()); - } -}