diff --git a/app/cli.php b/app/cli.php index 1b51282348..b176326191 100644 --- a/app/cli.php +++ b/app/cli.php @@ -3,20 +3,80 @@ require_once __DIR__ . '/init.php'; require_once __DIR__ . '/controllers/general.php'; -use Utopia\App; +use Appwrite\Platform\Appwrite; use Utopia\CLI\CLI; +use Utopia\Database\Validator\Authorization; +use Utopia\Platform\Service; +use Utopia\App; use Utopia\CLI\Console; use Utopia\Cache\Adapter\Sharding; use Utopia\Cache\Cache; use Utopia\Config\Config; use Utopia\Database\Database; -use Utopia\Database\Validator\Authorization; -use InfluxDB\Database as InfluxDatabase; +use Utopia\Database\Document; +use Utopia\Logger\Log; +use Utopia\Pools\Group; +use Utopia\Registry\Registry; -function getInfluxDB(): InfluxDatabase -{ - global $register; +Authorization::disable(); +CLI::setResource('register', fn()=>$register); + +CLI::setResource('cache', function ($pools) { + $list = Config::getParam('pools-cache', []); + $adapters = []; + + foreach ($list as $value) { + $adapters[] = $pools + ->get($value) + ->pop() + ->getResource() + ; + } + + return new Cache(new Sharding($adapters)); +}, ['pools']); + +CLI::setResource('pools', function (Registry $register) { + return $register->get('pools'); +}, ['register']); + +CLI::setResource('dbForConsole', function ($pools, $cache) { + $dbAdapter = $pools + ->get('console') + ->pop() + ->getResource() + ; + + $database = new Database($dbAdapter, $cache); + + $database->setNamespace('console'); + + return $database; +}, ['pools', 'cache']); + +CLI::setResource('getProjectDB', function (Group $pools, Database $dbForConsole, $cache) { + $getProjectDB = function (Document $project) use ($pools, $dbForConsole, $cache) { + if ($project->isEmpty() || $project->getId() === 'console') { + return $dbForConsole; + } + + $dbAdapter = $pools + ->get($project->getAttribute('database')) + ->pop() + ->getResource() + ; + + $database = new Database($dbAdapter, $cache); + $database->setNamespace('_' . $project->getInternalId()); + + return $database; + }; + + return $getProjectDB; +}, ['pools', 'dbForConsole', 'cache']); + +CLI::setResource('influxdb', function (Registry $register) { $client = $register->get('influxdb'); /** @var InfluxDB\Client $client */ $attempts = 0; $max = 10; @@ -38,76 +98,46 @@ function getInfluxDB(): InfluxDatabase } } while ($attempts < $max); return $database; -} +}, ['register']); -function getConsoleDB(): Database -{ - global $register; +CLI::setResource('logError', function (Registry $register) { + return function (Throwable $error, string $namespace, string $action) use ($register) { + $logger = $register->get('logger'); - $pools = $register->get('pools'); /** @var \Utopia\Pools\Group $pools */ + if ($logger) { + $version = App::getEnv('_APP_VERSION', 'UNKNOWN'); - $dbAdapter = $pools - ->get('console') - ->pop() - ->getResource() - ; + $log = new Log(); + $log->setNamespace($namespace); + $log->setServer(\gethostname()); + $log->setVersion($version); + $log->setType(Log::TYPE_ERROR); + $log->setMessage($error->getMessage()); - $database = new Database($dbAdapter, getCache()); + $log->addTag('code', $error->getCode()); + $log->addTag('verboseType', get_class($error)); - $database->setNamespace('console'); + $log->addExtra('file', $error->getFile()); + $log->addExtra('line', $error->getLine()); + $log->addExtra('trace', $error->getTraceAsString()); + $log->addExtra('detailedTrace', $error->getTrace()); - return $database; -} + $log->setAction($action); -function getCache(): Cache -{ - global $register; + $isProduction = App::getEnv('_APP_ENV', 'development') === 'production'; + $log->setEnvironment($isProduction ? Log::ENVIRONMENT_PRODUCTION : Log::ENVIRONMENT_STAGING); - $pools = $register->get('pools'); /** @var \Utopia\Pools\Group $pools */ - - $list = Config::getParam('pools-cache', []); - $adapters = []; - - foreach ($list as $value) { - $adapters[] = $pools - ->get($value) - ->pop() - ->getResource() - ; - } - - return new Cache(new Sharding($adapters)); -} - -Authorization::disable(); - -$cli = new CLI(); - -include 'tasks/doctor.php'; -include 'tasks/maintenance.php'; -include 'tasks/volume-sync.php'; -include 'tasks/install.php'; -include 'tasks/migrate.php'; -include 'tasks/sdks.php'; -include 'tasks/specs.php'; -include 'tasks/ssl.php'; -include 'tasks/vars.php'; -include 'tasks/usage.php'; - -$cli - ->task('version') - ->desc('Get the server version') - ->action(function () { - Console::log(App::getEnv('_APP_VERSION', 'UNKNOWN')); - }); - -$cli - ->error(function ($error) { - if (App::getEnv('_APP_ENV', 'development')) { - Console::error($error); - } else { - Console::error($error->getMessage()); + $responseCode = $logger->addLog($log); + Console::info('Usage stats log pushed with status code: ' . $responseCode); } - }); + Console::warning("Failed: {$error->getMessage()}"); + Console::warning($error->getTraceAsString()); + }; +}, ['register']); + +$platform = new Appwrite(); +$platform->init(Service::TYPE_CLI); + +$cli = $platform->getCli(); $cli->run(); 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 d1aeab2e84..0000000000 --- a/app/tasks/usage.php +++ /dev/null @@ -1,111 +0,0 @@ -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()); -}; - -function aggregateTimeseries(UtopiaDatabase $database, InfluxDatabase $influxDB, callable $logError): void -{ - $interval = (int) App::getEnv('_APP_USAGE_TIMESERIES_INTERVAL', '30'); // 30 seconds (by default) - $usage = new TimeSeries($database, $influxDB, $logError); - - Console::loop(function () use ($interval, $usage) { - $now = date('d-m-Y H:i:s', time()); - Console::info("[{$now}] Aggregating Timeseries Usage data every {$interval} seconds"); - $loopStart = microtime(true); - - $usage->collect(); - - $loopTook = microtime(true) - $loopStart; - $now = date('d-m-Y H:i:s', time()); - Console::info("[{$now}] Aggregation took {$loopTook} seconds"); - }, $interval); -} - -function aggregateDatabase(UtopiaDatabase $database, callable $logError): void -{ - $interval = (int) App::getEnv('_APP_USAGE_DATABASE_INTERVAL', '900'); // 15 minutes (by default) - $usage = new Database($database, $logError); - $aggregrator = new Aggregator($database, $logError); - - Console::loop(function () use ($interval, $usage, $aggregrator) { - $now = date('d-m-Y H:i:s', time()); - Console::info("[{$now}] Aggregating database usage every {$interval} seconds."); - $loopStart = microtime(true); - $usage->collect(); - $aggregrator->collect(); - $loopTook = microtime(true) - $loopStart; - $now = date('d-m-Y H:i:s', time()); - - Console::info("[{$now}] Aggregation took {$loopTook} seconds"); - }, $interval); -} - -$cli - ->task('usage') - ->param('type', 'timeseries', new WhiteList(['timeseries', 'database'])) - ->desc('Schedules syncing data from influxdb to Appwrite console db') - ->action(function (string $type) use ($logError) { - Console::title('Usage Aggregation V1'); - Console::success(APP_NAME . ' usage aggregation process v1 has started'); - - $database = getConsoleDB(); - $influxDB = getInfluxDB(); - - switch ($type) { - case 'timeseries': - aggregateTimeseries($database, $influxDB, $logError); - break; - case 'database': - aggregateDatabase($database, $logError); - break; - default: - Console::error("Unsupported usage aggregation type"); - } - }); diff --git a/composer.json b/composer.json index 1775a2bdf5..25ab2a3f86 100644 --- a/composer.json +++ b/composer.json @@ -43,13 +43,14 @@ "ext-sockets": "*", "appwrite/php-clamav": "1.1.*", "appwrite/php-runtimes": "0.11.*", - "utopia-php/framework": "0.23.*", + "utopia-php/platform": "0.3.*", + "utopia-php/framework": "0.25.*", "utopia-php/logger": "0.3.*", "utopia-php/abuse": "0.16.*", "utopia-php/analytics": "0.2.*", "utopia-php/cache": "0.8.*", "utopia-php/audit": "0.17.*", - "utopia-php/cli": "0.13.*", + "utopia-php/cli": "0.14.*", "utopia-php/config": "0.2.*", "utopia-php/database": "0.28.*", "utopia-php/locale": "0.4.*", @@ -60,8 +61,8 @@ "utopia-php/storage": "0.11.*", "utopia-php/websocket": "0.1.0", "utopia-php/image": "0.5.*", - "utopia-php/orchestration": "0.6.*", - "utopia-php/pools": "dev-feat-optimize-filling as 0.3.0", + "utopia-php/orchestration": "0.7.*", + "utopia-php/pools": "0.4.*", "resque/php-resque": "1.3.6", "matomo/device-detector": "6.0.0", "dragonmantank/cron-expression": "3.3.1", diff --git a/composer.lock b/composer.lock index 6a9bcf4d43..a66a959b7c 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "1c15c309c6fcefe30360915fdac8adad", + "content-hash": "bd99fe9f008b3bf3a68b1e3d0b515cc9", "packages": [ { "name": "adhocore/jwt", @@ -1733,16 +1733,16 @@ }, { "name": "utopia-php/cli", - "version": "0.13.0", + "version": "0.14.0", "source": { "type": "git", "url": "https://github.com/utopia-php/cli.git", - "reference": "69e68f8ed525fe162fae950a0507ed28a0f179bc" + "reference": "c30ef985a4e739758a0d95eb0706b357b6d8c086" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/cli/zipball/69e68f8ed525fe162fae950a0507ed28a0f179bc", - "reference": "69e68f8ed525fe162fae950a0507ed28a0f179bc", + "url": "https://api.github.com/repos/utopia-php/cli/zipball/c30ef985a4e739758a0d95eb0706b357b6d8c086", + "reference": "c30ef985a4e739758a0d95eb0706b357b6d8c086", "shasum": "" }, "require": { @@ -1751,7 +1751,7 @@ }, "require-dev": { "phpunit/phpunit": "^9.3", - "vimeo/psalm": "4.0.1" + "squizlabs/php_codesniffer": "^3.6" }, "type": "library", "autoload": { @@ -1780,9 +1780,9 @@ ], "support": { "issues": "https://github.com/utopia-php/cli/issues", - "source": "https://github.com/utopia-php/cli/tree/0.13.0" + "source": "https://github.com/utopia-php/cli/tree/0.14.0" }, - "time": "2022-04-26T08:41:22+00:00" + "time": "2022-10-09T10:19:07+00:00" }, { "name": "utopia-php/config", @@ -1945,16 +1945,16 @@ }, { "name": "utopia-php/framework", - "version": "0.23.4", + "version": "0.25.0", "source": { "type": "git", "url": "https://github.com/utopia-php/framework.git", - "reference": "97f64aa1732af92b967c3576f16967dc762ad47b" + "reference": "c524f681254255c8204fbf7919c53bf3b4982636" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/framework/zipball/97f64aa1732af92b967c3576f16967dc762ad47b", - "reference": "97f64aa1732af92b967c3576f16967dc762ad47b", + "url": "https://api.github.com/repos/utopia-php/framework/zipball/c524f681254255c8204fbf7919c53bf3b4982636", + "reference": "c524f681254255c8204fbf7919c53bf3b4982636", "shasum": "" }, "require": { @@ -1982,9 +1982,9 @@ ], "support": { "issues": "https://github.com/utopia-php/framework/issues", - "source": "https://github.com/utopia-php/framework/tree/0.23.4" + "source": "https://github.com/utopia-php/framework/tree/0.25.0" }, - "time": "2022-10-31T11:57:14+00:00" + "time": "2022-11-02T09:49:57+00:00" }, { "name": "utopia-php/image", @@ -2157,21 +2157,21 @@ }, { "name": "utopia-php/orchestration", - "version": "0.6.0", + "version": "0.7.0", "source": { "type": "git", "url": "https://github.com/utopia-php/orchestration.git", - "reference": "94263976413871efb6b16157a7101a81df3b6d78" + "reference": "484df2c9275a77722fe05f3630cf0e346dcb372e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/orchestration/zipball/94263976413871efb6b16157a7101a81df3b6d78", - "reference": "94263976413871efb6b16157a7101a81df3b6d78", + "url": "https://api.github.com/repos/utopia-php/orchestration/zipball/484df2c9275a77722fe05f3630cf0e346dcb372e", + "reference": "484df2c9275a77722fe05f3630cf0e346dcb372e", "shasum": "" }, "require": { "php": ">=8.0", - "utopia-php/cli": "0.13.*" + "utopia-php/cli": "0.14.*" }, "require-dev": { "phpunit/phpunit": "^9.3", @@ -2187,12 +2187,6 @@ "license": [ "MIT" ], - "authors": [ - { - "name": "Eldad Fux", - "email": "eldad@appwrite.io" - } - ], "description": "Lite & fast micro PHP abstraction library for container orchestration", "keywords": [ "docker", @@ -2206,22 +2200,71 @@ ], "support": { "issues": "https://github.com/utopia-php/orchestration/issues", - "source": "https://github.com/utopia-php/orchestration/tree/0.6.0" + "source": "https://github.com/utopia-php/orchestration/tree/0.7.0" }, - "time": "2022-07-13T16:47:18+00:00" + "time": "2022-10-28T07:23:38+00:00" }, { - "name": "utopia-php/pools", - "version": "dev-feat-optimize-filling", + "name": "utopia-php/platform", + "version": "0.3.1", "source": { "type": "git", - "url": "https://github.com/utopia-php/pools.git", - "reference": "be603898142f55df9db5058f81a610ead7769d7d" + "url": "https://github.com/utopia-php/platform.git", + "reference": "fe9f64420957dc8fb6201d22b499572f021411e4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/pools/zipball/be603898142f55df9db5058f81a610ead7769d7d", - "reference": "be603898142f55df9db5058f81a610ead7769d7d", + "url": "https://api.github.com/repos/utopia-php/platform/zipball/fe9f64420957dc8fb6201d22b499572f021411e4", + "reference": "fe9f64420957dc8fb6201d22b499572f021411e4", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-redis": "*", + "php": ">=8.0", + "utopia-php/cli": "0.14.*", + "utopia-php/framework": "0.25.*" + }, + "require-dev": { + "phpunit/phpunit": "^9.3", + "squizlabs/php_codesniffer": "^3.6" + }, + "type": "library", + "autoload": { + "psr-4": { + "Utopia\\Platform\\": "src/Platform" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Light and Fast Platform Library", + "keywords": [ + "cache", + "framework", + "php", + "upf", + "utopia" + ], + "support": { + "issues": "https://github.com/utopia-php/platform/issues", + "source": "https://github.com/utopia-php/platform/tree/0.3.1" + }, + "time": "2022-11-10T07:04:24+00:00" + }, + { + "name": "utopia-php/pools", + "version": "0.4.0", + "source": { + "type": "git", + "url": "https://github.com/utopia-php/pools.git", + "reference": "63cf2c32f59675ce9b31619ddd618d0b217e423f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/utopia-php/pools/zipball/63cf2c32f59675ce9b31619ddd618d0b217e423f", + "reference": "63cf2c32f59675ce9b31619ddd618d0b217e423f", "shasum": "" }, "require": { @@ -2229,11 +2272,12 @@ "ext-pdo": "*", "ext-redis": "*", "php": ">=8.0", - "utopia-php/cli": "^0.13.0" + "utopia-php/cli": "^0.14.0" }, "require-dev": { - "phpunit/phpunit": "^9.4", - "vimeo/psalm": "4.0.1" + "laravel/pint": "1.2.*", + "phpstan/phpstan": "1.8.*", + "phpunit/phpunit": "^9.3" }, "type": "library", "autoload": { @@ -2260,9 +2304,9 @@ ], "support": { "issues": "https://github.com/utopia-php/pools/issues", - "source": "https://github.com/utopia-php/pools/tree/feat-optimize-filling" + "source": "https://github.com/utopia-php/pools/tree/0.4.0" }, - "time": "2022-11-03T18:50:28+00:00" + "time": "2022-11-10T09:38:57+00:00" }, { "name": "utopia-php/preloader", @@ -5175,18 +5219,9 @@ "time": "2022-09-28T08:42:51+00:00" } ], - "aliases": [ - { - "package": "utopia-php/pools", - "version": "dev-feat-optimize-filling", - "alias": "0.3.0", - "alias_normalized": "0.3.0.0" - } - ], + "aliases": [], "minimum-stability": "stable", - "stability-flags": { - "utopia-php/pools": 20 - }, + "stability-flags": [], "prefer-stable": false, "prefer-lowest": false, "platform": { @@ -5210,5 +5245,5 @@ "platform-overrides": { "php": "8.0" }, - "plugin-api-version": "2.3.0" + "plugin-api-version": "2.2.0" } diff --git a/src/Appwrite/Platform/Appwrite.php b/src/Appwrite/Platform/Appwrite.php new file mode 100644 index 0000000000..9ef6f38c36 --- /dev/null +++ b/src/Appwrite/Platform/Appwrite.php @@ -0,0 +1,14 @@ +addService('tasks', new Tasks()); + } +} diff --git a/src/Appwrite/Platform/Services/Tasks.php b/src/Appwrite/Platform/Services/Tasks.php new file mode 100644 index 0000000000..7f6a062ed4 --- /dev/null +++ b/src/Appwrite/Platform/Services/Tasks.php @@ -0,0 +1,36 @@ +type = self::TYPE_CLI; + $this + ->addAction(Version::getName(), new Version()) + ->addAction(Usage::getName(), new Usage()) + ->addAction(Vars::getName(), new Vars()) + ->addAction(SSL::getName(), new SSL()) + ->addAction(Doctor::getName(), new Doctor()) + ->addAction(Install::getName(), new Install()) + ->addAction(Maintenance::getName(), new Maintenance()) + ->addAction(Migrate::getName(), new Migrate()) + ->addAction(SDKs::getName(), new SDKs()) + ->addAction(VolumeSync::getName(), new VolumeSync()) + ->addAction(Specs::getName(), new Specs()); + } +} diff --git a/app/tasks/doctor.php b/src/Appwrite/Platform/Tasks/Doctor.php similarity index 95% rename from app/tasks/doctor.php rename to src/Appwrite/Platform/Tasks/Doctor.php index daeaaa3f52..38af7c7376 100644 --- a/app/tasks/doctor.php +++ b/src/Appwrite/Platform/Tasks/Doctor.php @@ -1,20 +1,35 @@ task('doctor') - ->desc('Validate server health') - ->action(function () use ($register) { +class Doctor extends Action +{ + public static function getName(): string + { + return 'doctor'; + } + + public function __construct() + { + $this + ->desc('Validate server health') + ->inject('register') + ->callback(fn (Registry $register) => $this->action($register)); + } + + public function action(Registry $register): void + { Console::log(" __ ____ ____ _ _ ____ __ ____ ____ __ __ / _\ ( _ \( _ \/ )( \( _ \( )(_ _)( __) ( )/ \ / \ ) __/ ) __/\ /\ / ) / )( )( ) _) _ )(( O ) @@ -265,4 +280,5 @@ $cli } catch (\Throwable $th) { Console::error('Failed to check for a newer version' . "\n"); } - }); + } +} diff --git a/app/tasks/install.php b/src/Appwrite/Platform/Tasks/Install.php similarity index 90% rename from app/tasks/install.php rename to src/Appwrite/Platform/Tasks/Install.php index 3519b58d0b..219a03129d 100644 --- a/app/tasks/install.php +++ b/src/Appwrite/Platform/Tasks/Install.php @@ -1,6 +1,6 @@ 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) { +class Install extends Action +{ + public static function getName(): string + { + return 'install'; + } + + public function __construct() + { + $this + ->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) + ->callback(fn ($httpPort, $httpsPort, $organization, $image, $interactive) => $this->action($httpPort, $httpsPort, $organization, $image, $interactive)); + } + + public function action(string $httpPort, string $httpsPort, string $organization, string $image, string $interactive): void + { /** * 1. Start - DONE * 2. Check for older setup and get older version - DONE @@ -239,4 +252,5 @@ $cli $analytics->createEvent('install/server', 'install', APP_VERSION_STABLE . ' - ' . $message); Console::success($message); } - }); + } +} diff --git a/app/tasks/maintenance.php b/src/Appwrite/Platform/Tasks/Maintenance.php similarity index 87% rename from app/tasks/maintenance.php rename to src/Appwrite/Platform/Tasks/Maintenance.php index 1c16ca6911..fe659c8746 100644 --- a/app/tasks/maintenance.php +++ b/src/Appwrite/Platform/Tasks/Maintenance.php @@ -1,20 +1,35 @@ task('maintenance') - ->desc('Schedules maintenance tasks and publishes them to resque') - ->action(function () { +class Maintenance extends Action +{ + public static function getName(): string + { + return 'maintenance'; + } + + public function __construct() + { + $this + ->desc('Schedules maintenance tasks and publishes them to resque') + ->inject('dbForConsole') + ->callback(fn (Database $dbForConsole) => $this->action($dbForConsole)); + } + + public function action(Database $dbForConsole): void + { Console::title('Maintenance V1'); Console::success(APP_NAME . ' maintenance process v1 has started'); @@ -112,9 +127,7 @@ $cli $usageStatsRetention1d = (int) App::getEnv('_APP_MAINTENANCE_RETENTION_USAGE_1D', '8640000'); // 100 days $cacheRetention = (int) App::getEnv('_APP_MAINTENANCE_RETENTION_CACHE', '2592000'); // 30 days - Console::loop(function () use ($interval, $executionLogsRetention, $abuseLogsRetention, $auditLogRetention, $usageStatsRetention30m, $usageStatsRetention1d, $cacheRetention) { - $database = getConsoleDB(); - + Console::loop(function () use ($interval, $executionLogsRetention, $abuseLogsRetention, $auditLogRetention, $usageStatsRetention30m, $usageStatsRetention1d, $cacheRetention, $dbForConsole) { $time = DateTime::now(); Console::info("[{$time}] Notifying workers with maintenance tasks every {$interval} seconds"); @@ -124,7 +137,8 @@ $cli notifyDeleteUsageStats($usageStatsRetention30m, $usageStatsRetention1d); notifyDeleteConnections(); notifyDeleteExpiredSessions(); - renewCertificates($database); + renewCertificates($dbForConsole); notifyDeleteCache($cacheRetention); }, $interval); - }); + } +} diff --git a/app/tasks/migrate.php b/src/Appwrite/Platform/Tasks/Migrate.php similarity index 81% rename from app/tasks/migrate.php rename to src/Appwrite/Platform/Tasks/Migrate.php index bf3d1ff602..d7f591ac1a 100644 --- a/app/tasks/migrate.php +++ b/src/Appwrite/Platform/Tasks/Migrate.php @@ -1,19 +1,35 @@ task('migrate') - ->param('version', APP_VERSION_STABLE, new Text(32), 'Version to migrate to.', true) - ->action(function ($version) use ($register) { +class Migrate extends Action +{ + public static function getName(): string + { + return 'migrate'; + } + + public function __construct() + { + $this + ->desc('Migrate Appwrite to new version') + ->param('version', APP_VERSION_STABLE, new Text(8), 'Version to migrate to.', true) + ->inject('register') + ->callback(fn ($version, $register) => $this->action($version, $register)); + } + + public function action(string $version, Registry $register) + { Authorization::disable(); if (!array_key_exists($version, Migration::$versions)) { Console::error("Version {$version} not found."); @@ -87,4 +103,5 @@ $cli Swoole\Event::wait(); // Wait for Coroutines to finish $redis->flushAll(); Console::success('Data Migration Completed'); - }); + } +} diff --git a/app/tasks/sdks.php b/src/Appwrite/Platform/Tasks/SDKs.php similarity index 91% rename from app/tasks/sdks.php rename to src/Appwrite/Platform/Tasks/SDKs.php index 4ba6f9d028..fe312f8ef2 100644 --- a/app/tasks/sdks.php +++ b/src/Appwrite/Platform/Tasks/SDKs.php @@ -1,5 +1,8 @@ task('sdks') - ->action(function () { +class SDKs extends Action +{ + public static function getName(): string + { + return 'sdks'; + } + + public function __construct() + { + $this + ->desc('Generate Appwrite SDKs') + ->callback(fn () => $this->action()); + } + + public function action(): void + { $platforms = Config::getParam('platforms'); $selected = \strtolower(Console::confirm('Choose SDK ("*" for all):')); $version = Console::confirm('Choose an Appwrite version'); @@ -47,19 +65,19 @@ $cli 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'); + $spec = file_get_contents(__DIR__ . '/../../../app/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'); + $result = \realpath(__DIR__ . '/../../../app') . '/sdks/' . $key . '-' . $language['key']; + $resultExamples = \realpath(__DIR__ . '/../../..') . '/docs/examples/' . $version . '/' . $key . '-' . $language['key']; + $target = \realpath(__DIR__ . '/../../../app') . '/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 = \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 = \realpath(__DIR__ . '/../../../docs/sdks/' . $language['key'] . '/EXAMPLES.md'); $examples = ($examples) ? \file_get_contents($examples) : ''; - $changelog = \realpath(__DIR__ . '/../../docs/sdks/' . $language['key'] . '/CHANGELOG.md'); + $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'; @@ -255,4 +273,5 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND } Console::exit(); - }); + } +} diff --git a/src/Appwrite/Platform/Tasks/SSL.php b/src/Appwrite/Platform/Tasks/SSL.php new file mode 100644 index 0000000000..43026b0753 --- /dev/null +++ b/src/Appwrite/Platform/Tasks/SSL.php @@ -0,0 +1,38 @@ +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) + ->callback(fn ($domain) => $this->action($domain)); + } + + public function action(string $domain): void + { + 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/specs.php b/src/Appwrite/Platform/Tasks/Specs.php similarity index 91% rename from app/tasks/specs.php rename to src/Appwrite/Platform/Tasks/Specs.php index b3ac763b39..10f322f196 100644 --- a/app/tasks/specs.php +++ b/src/Appwrite/Platform/Tasks/Specs.php @@ -1,12 +1,14 @@ task('specs') - ->param('version', 'latest', new Text(16), 'Spec version', true) - ->param('mode', 'normal', new WhiteList(['normal', 'mocks']), 'Spec Mode', true) - ->action(function ($version, $mode) use ($register) { +class Specs extends Action +{ + public static function getName(): string + { + return 'specs'; + } + + public function __construct() + { + $this + ->desc('Generate Appwrite API specifications') + ->param('version', 'latest', new Text(16), 'Spec version', true) + ->param('mode', 'normal', new WhiteList(['normal', 'mocks']), 'Spec Mode', true) + ->inject('register') + ->callback(fn (string $version, string $mode, Registry $register) => $this->action($version, $mode, $register)); + } + + public function action(string $version, string $mode, Registry $register): void + { + $db = $register->get('db'); + $redis = $register->get('cache'); $appRoutes = App::getRoutes(); $response = new Response(new HttpResponse()); $mocks = ($mode === 'mocks'); @@ -247,7 +266,7 @@ $cli continue; } - $path = __DIR__ . '/../config/specs/' . $format . '-' . $version . '-' . $platform . '.json'; + $path = __DIR__ . '/../../../app/config/specs/' . $format . '-' . $version . '-' . $platform . '.json'; if (!file_put_contents($path, json_encode($specs->parse()))) { throw new Exception('Failed to save spec file: ' . $path); @@ -256,4 +275,5 @@ $cli Console::success('Saved spec file: ' . realpath($path)); } } - }); + } +} diff --git a/src/Appwrite/Platform/Tasks/Usage.php b/src/Appwrite/Platform/Tasks/Usage.php new file mode 100644 index 0000000000..73686109d8 --- /dev/null +++ b/src/Appwrite/Platform/Tasks/Usage.php @@ -0,0 +1,89 @@ +desc('Schedules syncing data from influxdb to Appwrite console db') + ->param('type', 'timeseries', new WhiteList(['timeseries', 'database'])) + ->inject('dbForConsole') + ->inject('influxdb') + ->inject('logError') + ->callback(fn ($type, $dbForConsole, $influxDB, $logError) => $this->action($type, $dbForConsole, $influxDB, $logError)); + } + + protected function aggregateTimeseries(UtopiaDatabase $database, InfluxDatabase $influxDB, callable $logError): void + { + $interval = (int) App::getEnv('_APP_USAGE_TIMESERIES_INTERVAL', '30'); // 30 seconds (by default) + $usage = new TimeSeries($database, $influxDB, $logError); + + Console::loop(function () use ($interval, $usage) { + $now = date('d-m-Y H:i:s', time()); + Console::info("[{$now}] Aggregating Timeseries Usage data every {$interval} seconds"); + $loopStart = microtime(true); + + $usage->collect(); + + $loopTook = microtime(true) - $loopStart; + $now = date('d-m-Y H:i:s', time()); + Console::info("[{$now}] Aggregation took {$loopTook} seconds"); + }, $interval); + } + + protected function aggregateDatabase(UtopiaDatabase $database, callable $logError): void + { + $interval = (int) App::getEnv('_APP_USAGE_DATABASE_INTERVAL', '900'); // 15 minutes (by default) + $usage = new Database($database, $logError); + $aggregrator = new Aggregator($database, $logError); + + Console::loop(function () use ($interval, $usage, $aggregrator) { + $now = date('d-m-Y H:i:s', time()); + Console::info("[{$now}] Aggregating database usage every {$interval} seconds."); + $loopStart = microtime(true); + $usage->collect(); + $aggregrator->collect(); + $loopTook = microtime(true) - $loopStart; + $now = date('d-m-Y H:i:s', time()); + + Console::info("[{$now}] Aggregation took {$loopTook} seconds"); + }, $interval); + } + + public function action(string $type, UtopiaDatabase $dbForConsole, InfluxDatabase $influxDB, callable $logError) + { + Console::title('Usage Aggregation V1'); + Console::success(APP_NAME . ' usage aggregation process v1 has started'); + + $errorLogger = fn(Throwable $error, string $action = 'syncUsageStats') => $logError($error, "usage", $action); + + switch ($type) { + case 'timeseries': + $this->aggregateTimeseries($dbForConsole, $influxDB, $errorLogger); + break; + case 'database': + $this->aggregateDatabase($dbForConsole, $errorLogger); + break; + default: + Console::error("Unsupported usage aggregation type"); + } + } +} diff --git a/app/tasks/vars.php b/src/Appwrite/Platform/Tasks/Vars.php similarity index 53% rename from app/tasks/vars.php rename to src/Appwrite/Platform/Tasks/Vars.php index af972e218d..c97f77a9da 100644 --- a/app/tasks/vars.php +++ b/src/Appwrite/Platform/Tasks/Vars.php @@ -1,15 +1,28 @@ task('vars') - ->desc('List all the server environment variables') - ->action(function () { +class Vars extends Action +{ + public static function getName(): string + { + return 'vars'; + } + + public function __construct() + { + $this + ->desc('List all the server environment variables') + ->callback(fn () => $this->action()); + } + + public function action(): void + { $config = Config::getParam('variables', []); $vars = []; @@ -22,4 +35,5 @@ $cli foreach ($vars as $key => $value) { Console::log('- ' . $value['name'] . '=' . App::getEnv($value['name'], '')); } - }); + } +} diff --git a/src/Appwrite/Platform/Tasks/Version.php b/src/Appwrite/Platform/Tasks/Version.php new file mode 100644 index 0000000000..4a9cbf9dcf --- /dev/null +++ b/src/Appwrite/Platform/Tasks/Version.php @@ -0,0 +1,24 @@ +desc('Get the server version') + ->callback(function () { + Console::log(App::getEnv('_APP_VERSION', 'UNKNOWN')); + }); + } +} diff --git a/app/tasks/volume-sync.php b/src/Appwrite/Platform/Tasks/VolumeSync.php similarity index 56% rename from app/tasks/volume-sync.php rename to src/Appwrite/Platform/Tasks/VolumeSync.php index 8d5fec201b..6197b20fbd 100644 --- a/app/tasks/volume-sync.php +++ b/src/Appwrite/Platform/Tasks/VolumeSync.php @@ -1,19 +1,32 @@ task('volume-sync') - ->desc('Runs rsync to sync certificates between the storage mount and traefik.') - ->param('source', null, new Text(255), 'Source path to sync from.', false) - ->param('destination', null, new Text(255), 'Destination path to sync to.', false) - ->param('interval', null, new Integer(true), 'Interval to run rsync', false) - ->action(function ($source, $destination, $interval) { +class VolumeSync extends Action +{ + public static function getName(): string + { + return 'volume-sync'; + } + + public function __construct() + { + $this + ->desc('Runs rsync to sync certificates between the storage mount and traefik.') + ->param('source', null, new Text(255), 'Source path to sync from.', false) + ->param('destination', null, new Text(255), 'Destination path to sync to.', false) + ->param('interval', null, new Integer(true), 'Interval to run rsync', false) + ->callback(fn ($source, $destination, $interval) => $this->action($source, $destination, $interval)); + } + + public function action(string $source, string $destination, int $interval) + { Console::title('RSync V1'); Console::success(APP_NAME . ' rsync process v1 has started'); @@ -42,4 +55,5 @@ $cli Console::success($stdout); Console::error($stderr); }, $interval); - }); + } +}