diff --git a/.env b/.env index cdf12cdff..87834e19e 100644 --- a/.env +++ b/.env @@ -1,6 +1,9 @@ _APP_ENV=production _APP_ENV=development _APP_LOCALE=en +_APP_CONSOLE_WHITELIST_ROOT=disabled +_APP_CONSOLE_WHITELIST_EMAILS= +_APP_CONSOLE_WHITELIST_IPS= _APP_SYSTEM_EMAIL_NAME=Appwrite _APP_SYSTEM_EMAIL_ADDRESS=team@appwrite.io _APP_SYSTEM_SECURITY_EMAIL_ADDRESS=security@appwrite.io diff --git a/CHANGES.md b/CHANGES.md index 89f460919..4ea7524f2 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,33 +1,37 @@ # Version 0.8.0 (Not Released Yet) ## Features - +- Refactoring SSL generation to work on every request so no domain environment variable is required for SSL generation (#1133) - Added Anonymous Login ([RFC-010](https://github.com/appwrite/rfc/blob/main/010-anonymous-login.md), #914) - Added events for functions and executions (#971) - Added JWT support (#784) - Added ARM support (#726) -- Splitted token & session models to become 2 different internal entities (#922) +- Split token & session models to become 2 different internal entities (#922) - Added Dart 2.12 as a new Cloud Functions runtime (#989) - Added option to disable email/password (#947) - Added option to disable anonymous login (need to merge and apply changed) (#947) - Added option to disable JWT auth (#947) - Added option to disable team invites (#947) -- Option to limit number of users (good for app launches + god account PR) (#947) +- Option to limit number of users (good for app launches + root account PR) (#947) - Added 2 new endpoints to the projects API to allow new settings - Enabled 501 errors (Not Implemented) from the error handler - Added Python 3.9 as a new Cloud Functions runtime (#1044) - Added Deno 1.8 as a new Cloud Functions runtime (#989) - Upgraded to PHP 8.0 (#713) -- ClamAV is now disabled by default to allow lower min requirments for Appwrite (#1064) +- ClamAV is now disabled by default to allow lower min requirements for Appwrite (#1064) - Added a new env var named `_APP_LOCALE` that allow to change the default `en` locale value (#1056) - Updated all the console bottom control to be consistent. Dropped the `+` icon (#1062) - Added Response Models for Documents and Preferences (#1075, #1102) +- Added new endpoint to update team membership roles (#1142) ## Bugs - Fixed default value for HTTPS force option - Fixed form array casting in dashboard (#1070) - Fixed collection document rule form in dashboard (#1069) +- Bugs in the Teams API: + - Fixed incorrect audit worker event names (#1143) + - Increased limit of memberships fetched in `createTeamMembership` to 2000 (#1143) ## Breaking Changes (Read before upgrading!) diff --git a/Dockerfile b/Dockerfile index 7cff90c3c..b442e694f 100755 --- a/Dockerfile +++ b/Dockerfile @@ -97,6 +97,13 @@ ENV _APP_SERVER=swoole \ _APP_DOMAIN_TARGET=localhost \ _APP_HOME=https://appwrite.io \ _APP_EDITION=community \ + _APP_CONSOLE_WHITELIST_ROOT=enabled \ + _APP_CONSOLE_WHITELIST_EMAILS= \ + _APP_CONSOLE_WHITELIST_IPS= \ + _APP_SYSTEM_EMAIL_NAME= \ + _APP_SYSTEM_EMAIL_ADDRESS= \ + _APP_SYSTEM_RESPONSE_FORMAT= \ + _APP_SYSTEM_SECURITY_EMAIL_ADDRESS= \ _APP_OPTIONS_ABUSE=enabled \ _APP_OPTIONS_FORCE_HTTPS=disabled \ _APP_OPENSSL_KEY_V1=your-secret-key \ diff --git a/app/config/collections.php b/app/config/collections.php index 09b27490c..1cff0031a 100644 --- a/app/config/collections.php +++ b/app/config/collections.php @@ -46,7 +46,7 @@ $collections = [ 'legalTaxId' => '', 'authWhitelistEmails' => (!empty(App::getEnv('_APP_CONSOLE_WHITELIST_EMAILS', null))) ? \explode(',', App::getEnv('_APP_CONSOLE_WHITELIST_EMAILS', null)) : [], 'authWhitelistIPs' => (!empty(App::getEnv('_APP_CONSOLE_WHITELIST_IPS', null))) ? \explode(',', App::getEnv('_APP_CONSOLE_WHITELIST_IPS', null)) : [], - 'authWhitelistDomains' => (!empty(App::getEnv('_APP_CONSOLE_WHITELIST_DOMAINS', null))) ? \explode(',', App::getEnv('_APP_CONSOLE_WHITELIST_DOMAINS', null)) : [], + 'usersAuthLimit' => (App::getEnv('_APP_CONSOLE_WHITELIST_ROOT', 'enabled') === 'enabled') ? 1 : 0, // limit signup to 1 user ], Database::SYSTEM_COLLECTION_COLLECTIONS => [ '$collection' => Database::SYSTEM_COLLECTION_COLLECTIONS, diff --git a/app/config/events.php b/app/config/events.php index bbccb62de..b27a5eafb 100644 --- a/app/config/events.php +++ b/app/config/events.php @@ -197,6 +197,11 @@ return [ 'model' => Response::MODEL_MEMBERSHIP, 'note' => 'version >= 0.7', ], + 'teams.memberships.update' => [ + 'description' => 'This event triggers when a team membership is updated.', + 'model' => Response::MODEL_MEMBERSHIP, + 'note' => 'version >= 0.8', + ], 'teams.memberships.update.status' => [ 'description' => 'This event triggers when a team memberships status is updated.', 'model' => Response::MODEL_MEMBERSHIP, diff --git a/app/config/variables.php b/app/config/variables.php index a5f598d80..57a913127 100644 --- a/app/config/variables.php +++ b/app/config/variables.php @@ -63,9 +63,17 @@ return [ 'required' => true, 'question' => 'Enter a DNS A record hostname to serve as a CNAME for your custom domains.\nYou can use the same value as used for the Appwrite hostname.', ], + [ + 'name' => '_APP_CONSOLE_WHITELIST_ROOT', + 'description' => 'This option allows you to disable the creation of new users on the Appwrite console. When enabled only 1 user will be able to use the registration form. New users can be added by invting them to your project. By default this option is enabled.', + 'introduction' => '0.8.0', + 'default' => 'enabled', + 'required' => false, + 'question' => '', + ], [ 'name' => '_APP_CONSOLE_WHITELIST_EMAILS', - 'description' => 'This option allows you to limit creation of users to Appwrite console. This option is very useful for small teams or sole developers. To enable it, pass a list of allowed email addresses separated by a comma.', + 'description' => 'This option allows you to limit creation of new users on the Appwrite console. This option is very useful for small teams or sole developers. To enable it, pass a list of allowed email addresses separated by a comma.', 'introduction' => '', 'default' => '', 'required' => false, diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 4abcd64f5..107844d66 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -61,7 +61,6 @@ App::post('/v1/account') if ('console' === $project->getId()) { $whitlistEmails = $project->getAttribute('authWhitelistEmails'); $whitlistIPs = $project->getAttribute('authWhitelistIPs'); - $whitlistDomains = $project->getAttribute('authWhitelistDomains'); if (!empty($whitlistEmails) && !\in_array($email, $whitlistEmails)) { throw new Exception('Console registration is restricted to specific emails. Contact your administrator for more information.', 401); @@ -70,10 +69,6 @@ App::post('/v1/account') if (!empty($whitlistIPs) && !\in_array($request->getIP(), $whitlistIPs)) { throw new Exception('Console registration is restricted to specific IPs. Contact your administrator for more information.', 401); } - - if (!empty($whitlistDomains) && !\in_array(\substr(\strrchr($email, '@'), 1), $whitlistDomains)) { - throw new Exception('Console registration is restricted to specific domains. Contact your administrator for more information.', 401); - } } $limit = $project->getAttribute('usersAuthLimit', 0); @@ -472,7 +467,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') 'emailVerification' => true, 'status' => Auth::USER_STATUS_ACTIVATED, // Email should already be authenticated by OAuth2 provider 'password' => Auth::passwordHash(Auth::passwordGenerator()), - 'passwordUpdate' => \time(), + 'passwordUpdate' => 0, 'registration' => \time(), 'reset' => false, 'name' => $name, @@ -942,7 +937,7 @@ App::patch('/v1/account/password') ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_USER) ->param('password', '', new Password(), 'New user password. Must be between 6 to 32 chars.') - ->param('oldPassword', '', new Password(), 'Old user password. Must be between 6 to 32 chars.') + ->param('oldPassword', '', new Password(), 'Old user password. Must be between 6 to 32 chars.', true) ->inject('response') ->inject('user') ->inject('dbForInternal') @@ -953,11 +948,15 @@ App::patch('/v1/account/password') /** @var Utopia\Database\Database $dbForInternal */ /** @var Appwrite\Event\Event $audits */ - if (!Auth::passwordVerify($oldPassword, $user->getAttribute('password'))) { // Double check user password + // Check old password only if its an existing user. + if ($user->getAttribute('passwordUpdate') !== 0 && !Auth::passwordVerify($oldPassword, $user->getAttribute('password'))) { // Double check user password throw new Exception('Invalid credentials', 401); } - $user = $dbForInternal->updateDocument('users', $user->getId(), $user->setAttribute('password', Auth::passwordHash($password))); + $user = $dbForInternal->updateDocument('users', $user->getId(), $user + ->setAttribute('password', Auth::passwordHash($password)) + ->setAttribute('passwordUpdate', \time()) + ); $audits ->setParam('userId', $user->getId()) diff --git a/app/controllers/api/health.php b/app/controllers/api/health.php index f18c31ef0..01a9050e0 100644 --- a/app/controllers/api/health.php +++ b/app/controllers/api/health.php @@ -272,7 +272,7 @@ App::get('/v1/health/anti-virus') App::get('/v1/health/stats') // Currently only used internally ->desc('Get System Stats') ->groups(['api', 'health']) - ->label('scope', 'god') + ->label('scope', 'root') // ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) // ->label('sdk.namespace', 'health') // ->label('sdk.method', 'getStats') diff --git a/app/controllers/api/teams.php b/app/controllers/api/teams.php index f80b5946d..e65f850c3 100644 --- a/app/controllers/api/teams.php +++ b/app/controllers/api/teams.php @@ -289,7 +289,12 @@ App::post('/v1/teams/:teamId/memberships') 'emailVerification' => false, 'status' => Auth::USER_STATUS_UNACTIVATED, 'password' => Auth::passwordHash(Auth::passwordGenerator()), - 'passwordUpdate' => \time(), + /** + * Set the password update time to 0 for users created using + * team invite and OAuth to allow password updates without an + * old password + */ + 'passwordUpdate' => 0, 'registration' => \time(), 'reset' => false, 'name' => $name, @@ -376,9 +381,9 @@ App::post('/v1/teams/:teamId/memberships') ->setParam('{{text-cta}}', '#ffffff') ; - if (!$isPrivilegedUser && !$isAppUser) { // No need in comfirmation when in admin or app mode + if (!$isPrivilegedUser && !$isAppUser) { // No need of confirmation when in admin or app mode $mails - ->setParam('event', 'teams.membership.create') + ->setParam('event', 'teams.memberships.create') ->setParam('from', ($project->getId() === 'console') ? '' : \sprintf($locale->getText('account.emails.team'), $project->getAttribute('name'))) ->setParam('recipient', $email) ->setParam('name', $name) @@ -390,7 +395,7 @@ App::post('/v1/teams/:teamId/memberships') $audits ->setParam('userId', $invitee->getId()) - ->setParam('event', 'teams.membership.create') + ->setParam('event', 'teams.memberships.create') ->setParam('resource', 'teams/'.$teamId) ; @@ -449,6 +454,72 @@ App::get('/v1/teams/:teamId/memberships') ]), Response::MODEL_MEMBERSHIP_LIST); }); + +App::patch('/v1/teams/:teamId/memberships/:membershipId') + ->desc('Update Membership Roles') + ->groups(['api', 'teams']) + ->label('event', 'teams.memberships.update') + ->label('scope', 'teams.write') + ->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT]) + ->label('sdk.namespace', 'teams') + ->label('sdk.method', 'updateMembershipRoles') + ->label('sdk.description', '/docs/references/teams/update-team-membership-roles.md') + ->label('sdk.response.code', Response::STATUS_CODE_OK) + ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) + ->label('sdk.response.model', Response::MODEL_MEMBERSHIP) + ->param('teamId', '', new UID(), 'Team unique ID.') + ->param('membershipId', '', new UID(), 'Membership ID.') + ->param('roles', [], new ArrayList(new Key()), 'Array of strings. Use this param to set the user roles in the team. A role can be any string. Learn more about [roles and permissions](/docs/permissions). Max length for each role is 32 chars.') + ->inject('request') + ->inject('response') + ->inject('user') + ->inject('dbForInternal') + ->inject('audits') + ->action(function ($teamId, $membershipId, $roles, $request, $response, $user, $dbForInternal, $audits) { + /** @var Utopia\Swoole\Request $request */ + /** @var Appwrite\Utopia\Response $response */ + /** @var Appwrite\Database\Document $user */ + /** @var Utopia\Database\Database $dbForInternal */ + /** @var Appwrite\Event\Event $audits */ + + $team = $dbForInternal->getDocument('teams', $teamId); + if ($team->isEmpty()) { + throw new Exception('Team not found', 404); + } + + $membership = $dbForInternal->getDocument('memberships', $membershipId); + if ($membership->isEmpty()) { + throw new Exception('Membership not found', 404); + } + + $profile = $dbForInternal->getDocument('users', $membership->getAttribute('userId')); + if ($profile->isEmpty()) { + throw new Exception('User not found', 404); + } + + $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::$roles); + $isAppUser = Auth::isAppUser(Authorization::$roles); + $isOwner = Authorization::isRole('team:'.$team->getId().'/owner');; + + if (!$isOwner && !$isPrivilegedUser && !$isAppUser) { // Not owner, not admin, not app (server) + throw new Exception('User is not allowed to modify roles', 401); + } + + // Update the roles + $membership->setAttribute('roles', $roles); + $membership = $dbForInternal->updateDocument('memberships', $membership->getId(), $membership); + + //TODO sync updated membership in the user $profile object using TYPE_REPLACE + + $audits + ->setParam('userId', $user->getId()) + ->setParam('event', 'teams.memberships.update') + ->setParam('resource', 'teams/'.$teamId) + ; + + $response->dynamic2($membership, Response::MODEL_MEMBERSHIP); + }); + App::patch('/v1/teams/:teamId/memberships/:membershipId/status') ->desc('Update Team Membership Status') ->groups(['api', 'teams']) @@ -506,7 +577,7 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status') } if ($userId != $membership->getAttribute('userId')) { - throw new Exception('Invite not belong to current user ('.$user->getAttribute('email').')', 401); + throw new Exception('Invite does not belong to current user ('.$user->getAttribute('email').')', 401); } if (empty($user->getId())) { @@ -514,7 +585,7 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status') } if ($membership->getAttribute('userId') !== $user->getId()) { - throw new Exception('Invite not belong to current user ('.$user->getAttribute('email').')', 401); + throw new Exception('Invite does not belong to current user ('.$user->getAttribute('email').')', 401); } $membership // Attach user to team @@ -560,7 +631,7 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status') $audits ->setParam('userId', $user->getId()) - ->setParam('event', 'teams.membership.update') + ->setParam('event', 'teams.memberships.update.status') ->setParam('resource', 'teams/'.$teamId) ; @@ -653,7 +724,7 @@ App::delete('/v1/teams/:teamId/memberships/:membershipId') $audits ->setParam('userId', $membership->getAttribute('userId')) - ->setParam('event', 'teams.membership.delete') + ->setParam('event', 'teams.memberships.delete') ->setParam('resource', 'teams/'.$teamId) ; diff --git a/app/controllers/general.php b/app/controllers/general.php index 4f483e405..4e8b05180 100644 --- a/app/controllers/general.php +++ b/app/controllers/general.php @@ -22,14 +22,60 @@ Config::setParam('domainVerification', false); Config::setParam('cookieDomain', 'localhost'); Config::setParam('cookieSamesite', Response::COOKIE_SAMESITE_NONE); -App::init(function ($utopia, $request, $response, $console, $project, $user, $locale, $clients) { +App::init(function ($utopia, $request, $response, $console, $project, $consoleDB, $user, $locale, $clients) { /** @var Utopia\Swoole\Request $request */ /** @var Appwrite\Utopia\Response $response */ + /** @var Appwrite\Database\Database $consoleDB */ /** @var Appwrite\Database\Document $console */ /** @var Appwrite\Database\Document $project */ /** @var Appwrite\Database\Document $user */ /** @var Utopia\Locale\Locale $locale */ /** @var array $clients */ + + $domain = $request->getHostname(); + $domains = Config::getParam('domains', []); + if (!array_key_exists($domain, $domains)) { + $domain = new Domain(!empty($domain) ? $domain : ''); + + if (empty($domain->get()) || !$domain->isKnown() || $domain->isTest()) { + $domains[$domain->get()] = false; + Console::warning($domain->get() . ' is not a publicly accessible domain. Skipping SSL certificate generation.'); + } else { + Authorization::disable(); + $dbDomain = $consoleDB->getCollectionFirst([ + 'limit' => 1, + 'offset' => 0, + 'filters' => [ + '$collection=' . Database::SYSTEM_COLLECTION_CERTIFICATES, + 'domain=' . $domain->get(), + ], + ]); + + if (empty($dbDomain)) { + $dbDomain = [ + '$collection' => Database::SYSTEM_COLLECTION_CERTIFICATES, + '$permissions' => [ + 'read' => [], + 'write' => [], + ], + 'domain' => $domain->get(), + ]; + $dbDomain = $consoleDB->createDocument($dbDomain); + Authorization::enable(); + + Console::info('Issuing a TLS certificate for the master domain (' . $domain->get() . ') in ~30 seconds..'); // TODO move this to installation script + + ResqueScheduler::enqueueAt(\time() + 30, 'v1-certificates', 'CertificatesV1', [ + 'document' => $dbDomain, + 'domain' => $domain->get(), + 'validateTarget' => false, + 'validateCNAME' => false, + ]); + } + $domains[$domain->get()] = true; + } + Config::setParam('domains', $domains); + } $localeParam = (string)$request->getParam('locale', $request->getHeader('x-appwrite-locale', '')); @@ -211,7 +257,7 @@ App::init(function ($utopia, $request, $response, $console, $project, $user, $lo } }, $user->getAttribute('memberships', [])); - // TDOO Check if user is god + // TDOO Check if user is root if (!\in_array($scope, $scopes)) { if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS !== $project->getCollection()) { // Check if permission is denied because project is missing @@ -229,7 +275,7 @@ App::init(function ($utopia, $request, $response, $console, $project, $user, $lo throw new Exception('Password reset is required', 412); } -}, ['utopia', 'request', 'response', 'console', 'project', 'user', 'locale', 'clients']); +}, ['utopia', 'request', 'response', 'console', 'project', 'consoleDB', 'user', 'locale', 'clients']); App::options(function ($request, $response) { /** @var Utopia\Swoole\Request $request */ @@ -427,4 +473,4 @@ include_once __DIR__ . '/shared/web.php'; foreach (Config::getParam('services', []) as $service) { include_once $service['controller']; -} \ No newline at end of file +} diff --git a/app/controllers/web/home.php b/app/controllers/web/home.php index cf3f3fe67..15b10a2f1 100644 --- a/app/controllers/web/home.php +++ b/app/controllers/web/home.php @@ -1,5 +1,6 @@ label('permission', 'public') ->label('scope', 'home') ->inject('response') - ->action(function ($response) { + ->inject('consoleDB') + ->inject('project') + ->action(function ($response, $consoleDB, $project) { /** @var Appwrite\Utopia\Response $response */ + /** @var Appwrite\Database\Database $consoleDB */ + /** @var Appwrite\Database\Document $project */ - $response->redirect('/auth/signin'); + $response + ->addHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0') + ->addHeader('Expires', 0) + ->addHeader('Pragma', 'no-cache') + ; + + if ('console' === $project->getId() || $project->isEmpty()) { + $whitlistRoot = App::getEnv('_APP_CONSOLE_WHITELIST_ROOT', 'enabled'); + + if($whitlistRoot !== 'disabled') { + $consoleDB->getCollection([ // Count users + 'filters' => [ + '$collection='.Database::SYSTEM_COLLECTION_USERS, + ], + ]); + + $sum = $consoleDB->getSum(); + + if($sum !== 0) { + return $response->redirect('/auth/signin'); + } + } + } + + $response->redirect('/auth/signup'); }); App::get('/auth/signin') @@ -58,6 +87,10 @@ App::get('/auth/signin') $page = new View(__DIR__.'/../../views/home/auth/signin.phtml'); + $page + ->setParam('root', App::getEnv('_APP_CONSOLE_WHITELIST_ROOT', 'enabled')) + ; + $layout ->setParam('title', 'Sign In - '.APP_NAME) ->setParam('body', $page); @@ -72,6 +105,10 @@ App::get('/auth/signup') /** @var Utopia\View $layout */ $page = new View(__DIR__.'/../../views/home/auth/signup.phtml'); + $page + ->setParam('root', App::getEnv('_APP_CONSOLE_WHITELIST_ROOT', 'enabled')) + ; + $layout ->setParam('title', 'Sign Up - '.APP_NAME) ->setParam('body', $page); @@ -87,6 +124,10 @@ App::get('/auth/recovery') $page = new View(__DIR__.'/../../views/home/auth/recovery.phtml'); + $page + ->setParam('smtpEnabled', (!empty(App::getEnv('_APP_SMTP_HOST')))) + ; + $layout ->setParam('title', 'Password Recovery - '.APP_NAME) ->setParam('body', $page); diff --git a/app/http.php b/app/http.php index 6b48cc1c8..40834c95b 100644 --- a/app/http.php +++ b/app/http.php @@ -109,18 +109,6 @@ $http->on('start', function (Server $http) use ($payloadSize, $register) { }); }); -$domain = App::getEnv('_APP_DOMAIN', ''); - -Console::info('Issuing a TLS certificate for the master domain ('.$domain.') in 30 seconds. - Make sure your domain points to your server IP or restart your Appwrite server to try again.'); // TODO move this to installation script - -ResqueScheduler::enqueueAt(\time() + 30, 'v1-certificates', 'CertificatesV1', [ - 'document' => [], - 'domain' => $domain, - 'validateTarget' => false, - 'validateCNAME' => false, -]); - $http->on('request', function (SwooleRequest $swooleRequest, SwooleResponse $swooleResponse) { $request = new Request($swooleRequest); $response = new Response($swooleResponse); diff --git a/app/tasks/doctor.php b/app/tasks/doctor.php index 0f601bb5b..095adfb95 100644 --- a/app/tasks/doctor.php +++ b/app/tasks/doctor.php @@ -61,12 +61,12 @@ $cli 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); - $authWhitelistDomains = App::getEnv('_APP_CONSOLE_WHITELIST_DOMAINS', null); - if(empty($authWhitelistEmails) - && empty($authWhitelistDomains) + if(empty($authWhitelistRoot) + && empty($authWhitelistEmails) && empty($authWhitelistIPs) ) { Console::log('🔴 Console access limits are disabled'); diff --git a/app/tasks/install.php b/app/tasks/install.php index b1b882a21..e2d141f6e 100644 --- a/app/tasks/install.php +++ b/app/tasks/install.php @@ -8,11 +8,15 @@ use Utopia\Analytics\GoogleAnalytics; use Utopia\CLI\Console; use Utopia\Config\Config; use Utopia\View; +use Utopia\Validator\Text; $cli ->task('install') ->desc('Install Appwrite') - ->action(function () { + ->param('httpPort', '', new Text(4), 'Server HTTP port', true) + ->param('httpsPort', '', new Text(4), 'Server HTTPS port', true) + ->param('interactive','Y', new Text(1), 'Run an interactive session', true) + ->action(function ($httpPort, $httpsPort, $interactive) { /** * 1. Start - DONE * 2. Check for older setup and get older version - DONE @@ -108,16 +112,20 @@ $cli } } - $httpPort = Console::confirm('Choose your server HTTP port: (default: '.$defaultHTTPPort.')'); - $httpPort = ($httpPort) ? $httpPort : $defaultHTTPPort; + if(empty($httpPort)) { + $httpPort = Console::confirm('Choose your server HTTP port: (default: '.$defaultHTTPPort.')'); + $httpPort = ($httpPort) ? $httpPort : $defaultHTTPPort; + } - $httpsPort = Console::confirm('Choose your server HTTPS port: (default: '.$defaultHTTPSPort.')'); - $httpsPort = ($httpsPort) ? $httpsPort : $defaultHTTPSPort; + if(empty($httpsPort)) { + $httpsPort = Console::confirm('Choose your server HTTPS port: (default: '.$defaultHTTPSPort.')'); + $httpsPort = ($httpsPort) ? $httpsPort : $defaultHTTPSPort; + } $input = []; foreach($vars as $key => $var) { - if(!$var['required']) { + if(!$var['required'] || !Console::isInteractive() || $interactive !== 'Y') { $input[$var['name']] = $var['default']; continue; } diff --git a/app/views/console/comps/footer.phtml b/app/views/console/comps/footer.phtml index be5696ce4..065dabb14 100644 --- a/app/views/console/comps/footer.phtml +++ b/app/views/console/comps/footer.phtml @@ -1,6 +1,6 @@ getParam('home', ''); -$version = $this->getParam('version', '').'.'.APP_CACHE_BUSTER; +$version = $this->getParam('version', '') . '.' . APP_CACHE_BUSTER; ?>