2015-02-21 14:52:32 -05:00
< ? php
2024-01-29 09:12:53 -05:00
declare ( strict_types = 1 );
2015-03-26 06:44:34 -04:00
/**
2024-05-24 13:43:47 -04:00
* SPDX - FileCopyrightText : 2016 - 2024 Nextcloud GmbH and Nextcloud contributors
* SPDX - FileCopyrightText : 2016 ownCloud , Inc .
* SPDX - License - Identifier : AGPL - 3.0 - only
2015-03-26 06:44:34 -04:00
*/
2015-02-21 14:52:32 -05:00
namespace OC\Core\Command\Maintenance ;
2020-08-20 08:08:18 -04:00
use bantu\IniGetWrapper\IniGetWrapper ;
2015-02-21 14:52:32 -05:00
use InvalidArgumentException ;
2023-10-31 07:06:09 -04:00
use OC\Console\TimestampFormatter ;
use OC\Migration\ConsoleOutput ;
2015-03-10 18:44:29 -04:00
use OC\Setup ;
2017-03-17 18:37:48 -04:00
use OC\SystemConfig ;
2025-05-14 09:51:42 -04:00
use OCP\Server ;
2015-02-21 14:52:32 -05:00
use Symfony\Component\Console\Command\Command ;
2016-09-06 15:08:08 -04:00
use Symfony\Component\Console\Helper\QuestionHelper ;
2015-02-21 14:52:32 -05:00
use Symfony\Component\Console\Input\InputInterface ;
use Symfony\Component\Console\Input\InputOption ;
use Symfony\Component\Console\Output\OutputInterface ;
2016-09-06 15:08:08 -04:00
use Symfony\Component\Console\Question\Question ;
2021-01-07 14:04:04 -05:00
use Throwable ;
use function get_class ;
2015-02-21 14:52:32 -05:00
class Install extends Command {
2023-06-12 10:46:44 -04:00
public function __construct (
private SystemConfig $config ,
private IniGetWrapper $iniGetWrapper ,
) {
2015-02-21 14:52:32 -05:00
parent :: __construct ();
}
2024-01-29 09:12:53 -05:00
protected function configure () : void {
2015-02-21 14:52:32 -05:00
$this
-> setName ( 'maintenance:install' )
2016-07-28 14:55:26 -04:00
-> setDescription ( 'install Nextcloud' )
2015-02-21 14:52:32 -05:00
-> addOption ( 'database' , null , InputOption :: VALUE_REQUIRED , 'Supported database type' , 'sqlite' )
-> addOption ( 'database-name' , null , InputOption :: VALUE_REQUIRED , 'Name of the database' )
-> addOption ( 'database-host' , null , InputOption :: VALUE_REQUIRED , 'Hostname of the database' , 'localhost' )
2016-07-06 05:31:28 -04:00
-> addOption ( 'database-port' , null , InputOption :: VALUE_REQUIRED , 'Port the database is listening on' )
2024-02-13 08:37:09 -05:00
-> addOption ( 'database-user' , null , InputOption :: VALUE_REQUIRED , 'Login to connect to the database' )
2015-04-29 16:53:16 -04:00
-> addOption ( 'database-pass' , null , InputOption :: VALUE_OPTIONAL , 'Password of the database user' , null )
2017-07-21 05:17:12 -04:00
-> addOption ( 'database-table-space' , null , InputOption :: VALUE_OPTIONAL , 'Table space of the database (oci only)' , null )
2025-05-30 15:55:44 -04:00
-> addOption ( 'disable-admin-user' , null , InputOption :: VALUE_NONE , 'Disable the creation of an admin user' )
2024-02-13 08:37:09 -05:00
-> addOption ( 'admin-user' , null , InputOption :: VALUE_REQUIRED , 'Login of the admin account' , 'admin' )
2015-02-21 14:52:32 -05:00
-> addOption ( 'admin-pass' , null , InputOption :: VALUE_REQUIRED , 'Password of the admin account' )
2018-09-13 08:37:06 -04:00
-> addOption ( 'admin-email' , null , InputOption :: VALUE_OPTIONAL , 'E-Mail of the admin account' )
2026-02-02 06:04:15 -05:00
-> addOption ( 'data-dir' , null , InputOption :: VALUE_REQUIRED , 'Path to data directory' , \OC :: $SERVERROOT . '/data' )
-> addOption ( 'password-salt' , null , InputOption :: VALUE_OPTIONAL , 'Password salt, at least ' . Setup :: MIN_PASSWORD_SALT_LENGTH . ' characters (will be randomly generated if not provided)' )
-> addOption ( 'server-secret' , null , InputOption :: VALUE_OPTIONAL , 'Server secret, at least ' . Setup :: MIN_SECRET_LENGTH . ' characters (will be randomly generated if not provided)' );
2015-02-21 14:52:32 -05:00
}
2020-06-26 08:54:51 -04:00
protected function execute ( InputInterface $input , OutputInterface $output ) : int {
2015-03-10 18:44:29 -04:00
// validate the environment
2025-05-14 09:51:42 -04:00
$setupHelper = Server :: get ( Setup :: class );
2015-03-11 10:47:24 -04:00
$sysInfo = $setupHelper -> getSystemInfo ( true );
2015-03-10 18:44:29 -04:00
$errors = $sysInfo [ 'errors' ];
if ( count ( $errors ) > 0 ) {
$this -> printErrors ( $output , $errors );
2025-12-07 07:32:43 -05:00
return 1 ;
2015-02-21 14:52:32 -05:00
}
2015-03-10 18:44:29 -04:00
// validate user input
$options = $this -> validateInput ( $input , $output , array_keys ( $sysInfo [ 'databases' ]));
2023-10-31 07:06:09 -04:00
if ( $output -> isVerbose ()) {
// Prepend each line with a little timestamp
$timestampFormatter = new TimestampFormatter ( null , $output -> getFormatter ());
$output -> setFormatter ( $timestampFormatter );
$migrationOutput = new ConsoleOutput ( $output );
} else {
$migrationOutput = null ;
}
2015-03-10 18:44:29 -04:00
// perform installation
2023-10-31 07:06:09 -04:00
$errors = $setupHelper -> install ( $options , $migrationOutput );
2015-03-10 18:44:29 -04:00
if ( count ( $errors ) > 0 ) {
$this -> printErrors ( $output , $errors );
return 1 ;
}
2021-06-19 16:06:57 -04:00
if ( $setupHelper -> shouldRemoveCanInstallFile ()) {
2021-06-19 15:01:06 -04:00
$output -> writeln ( '<warn>Could not remove CAN_INSTALL from the config folder. Please remove this file manually.</warn>' );
}
2016-07-28 14:55:26 -04:00
$output -> writeln ( 'Nextcloud was successfully installed' );
2015-03-10 18:44:29 -04:00
return 0 ;
2015-02-21 14:52:32 -05:00
}
/**
* @ param InputInterface $input
* @ param OutputInterface $output
2015-03-10 18:44:29 -04:00
* @ param string [] $supportedDatabases
2015-02-21 14:52:32 -05:00
* @ return array
*/
2015-03-10 18:44:29 -04:00
protected function validateInput ( InputInterface $input , OutputInterface $output , $supportedDatabases ) {
2015-02-21 14:52:32 -05:00
$db = strtolower ( $input -> getOption ( 'database' ));
if ( ! in_array ( $db , $supportedDatabases )) {
2023-05-04 08:43:48 -04:00
throw new InvalidArgumentException ( " Database < $db > is not supported. " . implode ( ', ' , $supportedDatabases ) . ' are supported.' );
2015-02-21 14:52:32 -05:00
}
$dbUser = $input -> getOption ( 'database-user' );
$dbPass = $input -> getOption ( 'database-pass' );
$dbName = $input -> getOption ( 'database-name' );
2016-07-06 03:58:38 -04:00
$dbPort = $input -> getOption ( 'database-port' );
2016-06-10 05:16:32 -04:00
if ( $db === 'oci' ) {
// an empty hostname needs to be read from the raw parameters
$dbHost = $input -> getParameterOption ( '--database-host' , '' );
} else {
$dbHost = $input -> getOption ( 'database-host' );
}
2020-02-05 06:47:08 -05:00
if ( $dbPort ) {
// Append the port to the host so it is the same as in the config (there is no dbport config)
$dbHost .= ':' . $dbPort ;
}
2015-04-29 06:15:40 -04:00
if ( $input -> hasParameterOption ( '--database-pass' )) {
$dbPass = ( string ) $input -> getOption ( 'database-pass' );
}
2025-05-30 15:55:44 -04:00
$disableAdminUser = ( bool ) $input -> getOption ( 'disable-admin-user' );
2015-02-21 14:52:32 -05:00
$adminLogin = $input -> getOption ( 'admin-user' );
$adminPassword = $input -> getOption ( 'admin-pass' );
2018-09-13 08:37:06 -04:00
$adminEmail = $input -> getOption ( 'admin-email' );
2015-02-21 14:52:32 -05:00
$dataDir = $input -> getOption ( 'data-dir' );
if ( $db !== 'sqlite' ) {
if ( is_null ( $dbUser )) {
2022-09-21 11:44:32 -04:00
throw new InvalidArgumentException ( 'Database account not provided.' );
2015-02-21 14:52:32 -05:00
}
if ( is_null ( $dbName )) {
throw new InvalidArgumentException ( 'Database name not provided.' );
}
if ( is_null ( $dbPass )) {
2016-09-06 15:08:08 -04:00
/** @var QuestionHelper $helper */
$helper = $this -> getHelper ( 'question' );
$question = new Question ( 'What is the password to access the database with user <' . $dbUser . '>?' );
$question -> setHidden ( true );
$question -> setHiddenFallback ( false );
$dbPass = $helper -> ask ( $input , $output , $question );
2015-02-21 14:52:32 -05:00
}
}
2025-05-30 15:55:44 -04:00
if ( ! $disableAdminUser && $adminPassword === null ) {
2016-09-06 15:08:08 -04:00
/** @var QuestionHelper $helper */
$helper = $this -> getHelper ( 'question' );
$question = new Question ( 'What is the password you like to use for the admin account <' . $adminLogin . '>?' );
$question -> setHidden ( true );
$question -> setHiddenFallback ( false );
$adminPassword = $helper -> ask ( $input , $output , $question );
2015-02-21 14:52:32 -05:00
}
2025-05-30 15:55:44 -04:00
if ( ! $disableAdminUser && $adminEmail !== null && ! filter_var ( $adminEmail , FILTER_VALIDATE_EMAIL )) {
2018-09-25 15:53:04 -04:00
throw new InvalidArgumentException ( 'Invalid e-mail-address <' . $adminEmail . '> for <' . $adminLogin . '>.' );
}
2026-02-02 06:04:15 -05:00
$passwordSalt = $input -> getOption ( 'password-salt' );
$secret = $input -> getOption ( 'server-secret' );
if ( $passwordSalt !== null && strlen ( $passwordSalt ) < Setup :: MIN_PASSWORD_SALT_LENGTH ) {
throw new InvalidArgumentException ( 'Password salt must be at least ' . Setup :: MIN_PASSWORD_SALT_LENGTH . ' characters long.' );
}
if ( $secret !== null && strlen ( $secret ) < Setup :: MIN_SECRET_LENGTH ) {
throw new InvalidArgumentException ( 'Server secret must be at least ' . Setup :: MIN_SECRET_LENGTH . ' characters long.' );
}
2015-02-21 14:52:32 -05:00
$options = [
'dbtype' => $db ,
'dbuser' => $dbUser ,
'dbpass' => $dbPass ,
'dbname' => $dbName ,
'dbhost' => $dbHost ,
2025-05-30 15:55:44 -04:00
'admindisable' => $disableAdminUser ,
2015-02-21 14:52:32 -05:00
'adminlogin' => $adminLogin ,
'adminpass' => $adminPassword ,
2018-09-13 08:37:06 -04:00
'adminemail' => $adminEmail ,
2026-02-02 06:04:15 -05:00
'directory' => $dataDir ,
'passwordsalt' => $passwordSalt ,
'secret' => $secret ,
2015-02-21 14:52:32 -05:00
];
2017-07-21 05:17:12 -04:00
if ( $db === 'oci' ) {
$options [ 'dbtablespace' ] = $input -> getParameterOption ( '--database-table-space' , '' );
}
2015-02-21 14:52:32 -05:00
return $options ;
}
2015-03-10 18:44:29 -04:00
/**
* @ param OutputInterface $output
2024-01-29 10:10:31 -05:00
* @ param array < string | array > $errors
2015-03-10 18:44:29 -04:00
*/
2024-01-29 10:10:31 -05:00
protected function printErrors ( OutputInterface $output , array $errors ) : void {
2015-03-10 18:44:29 -04:00
foreach ( $errors as $error ) {
if ( is_array ( $error )) {
2021-01-07 14:04:04 -05:00
$output -> writeln ( '<error>' . $error [ 'error' ] . '</error>' );
if ( isset ( $error [ 'hint' ]) && ! empty ( $error [ 'hint' ])) {
$output -> writeln ( '<info> -> ' . $error [ 'hint' ] . '</info>' );
}
if ( isset ( $error [ 'exception' ]) && $error [ 'exception' ] instanceof Throwable ) {
$this -> printThrowable ( $output , $error [ 'exception' ]);
}
2015-03-10 18:44:29 -04:00
} else {
2021-01-07 14:04:04 -05:00
$output -> writeln ( '<error>' . $error . '</error>' );
2015-03-10 18:44:29 -04:00
}
}
}
2021-01-07 14:04:04 -05:00
private function printThrowable ( OutputInterface $output , Throwable $t ) : void {
$output -> write ( '<info>Trace: ' . $t -> getTraceAsString () . '</info>' );
2021-01-07 15:04:11 -05:00
$output -> writeln ( '' );
2021-01-07 14:04:04 -05:00
if ( $t -> getPrevious () !== null ) {
$output -> writeln ( '' );
$output -> writeln ( '<info>Previous: ' . get_class ( $t -> getPrevious ()) . ': ' . $t -> getPrevious () -> getMessage () . '</info>' );
$this -> printThrowable ( $output , $t -> getPrevious ());
}
}
2015-02-21 14:52:32 -05:00
}