Install.php 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  1. <?php
  2. declare(strict_types=1);
  3. /**
  4. * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
  5. * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
  6. * SPDX-License-Identifier: AGPL-3.0-only
  7. */
  8. namespace OC\Core\Command\Maintenance;
  9. use bantu\IniGetWrapper\IniGetWrapper;
  10. use InvalidArgumentException;
  11. use OC\Console\TimestampFormatter;
  12. use OC\Migration\ConsoleOutput;
  13. use OC\Setup;
  14. use OC\SystemConfig;
  15. use Symfony\Component\Console\Command\Command;
  16. use Symfony\Component\Console\Helper\QuestionHelper;
  17. use Symfony\Component\Console\Input\InputInterface;
  18. use Symfony\Component\Console\Input\InputOption;
  19. use Symfony\Component\Console\Output\OutputInterface;
  20. use Symfony\Component\Console\Question\Question;
  21. use Throwable;
  22. use function get_class;
  23. class Install extends Command {
  24. public function __construct(
  25. private SystemConfig $config,
  26. private IniGetWrapper $iniGetWrapper,
  27. ) {
  28. parent::__construct();
  29. }
  30. protected function configure(): void {
  31. $this
  32. ->setName('maintenance:install')
  33. ->setDescription('install Nextcloud')
  34. ->addOption('database', null, InputOption::VALUE_REQUIRED, 'Supported database type', 'sqlite')
  35. ->addOption('database-name', null, InputOption::VALUE_REQUIRED, 'Name of the database')
  36. ->addOption('database-host', null, InputOption::VALUE_REQUIRED, 'Hostname of the database', 'localhost')
  37. ->addOption('database-port', null, InputOption::VALUE_REQUIRED, 'Port the database is listening on')
  38. ->addOption('database-user', null, InputOption::VALUE_REQUIRED, 'Login to connect to the database')
  39. ->addOption('database-pass', null, InputOption::VALUE_OPTIONAL, 'Password of the database user', null)
  40. ->addOption('database-table-space', null, InputOption::VALUE_OPTIONAL, 'Table space of the database (oci only)', null)
  41. ->addOption('admin-user', null, InputOption::VALUE_REQUIRED, 'Login of the admin account', 'admin')
  42. ->addOption('admin-pass', null, InputOption::VALUE_REQUIRED, 'Password of the admin account')
  43. ->addOption('admin-email', null, InputOption::VALUE_OPTIONAL, 'E-Mail of the admin account')
  44. ->addOption('data-dir', null, InputOption::VALUE_REQUIRED, 'Path to data directory', \OC::$SERVERROOT . '/data');
  45. }
  46. protected function execute(InputInterface $input, OutputInterface $output): int {
  47. // validate the environment
  48. $setupHelper = \OCP\Server::get(\OC\Setup::class);
  49. $sysInfo = $setupHelper->getSystemInfo(true);
  50. $errors = $sysInfo['errors'];
  51. if (count($errors) > 0) {
  52. $this->printErrors($output, $errors);
  53. // ignore the OS X setup warning
  54. if (count($errors) !== 1 ||
  55. (string)$errors[0]['error'] !== 'Mac OS X is not supported and Nextcloud will not work properly on this platform. Use it at your own risk! ') {
  56. return 1;
  57. }
  58. }
  59. // validate user input
  60. $options = $this->validateInput($input, $output, array_keys($sysInfo['databases']));
  61. if ($output->isVerbose()) {
  62. // Prepend each line with a little timestamp
  63. $timestampFormatter = new TimestampFormatter(null, $output->getFormatter());
  64. $output->setFormatter($timestampFormatter);
  65. $migrationOutput = new ConsoleOutput($output);
  66. } else {
  67. $migrationOutput = null;
  68. }
  69. // perform installation
  70. $errors = $setupHelper->install($options, $migrationOutput);
  71. if (count($errors) > 0) {
  72. $this->printErrors($output, $errors);
  73. return 1;
  74. }
  75. if ($setupHelper->shouldRemoveCanInstallFile()) {
  76. $output->writeln('<warn>Could not remove CAN_INSTALL from the config folder. Please remove this file manually.</warn>');
  77. }
  78. $output->writeln('Nextcloud was successfully installed');
  79. return 0;
  80. }
  81. /**
  82. * @param InputInterface $input
  83. * @param OutputInterface $output
  84. * @param string[] $supportedDatabases
  85. * @return array
  86. */
  87. protected function validateInput(InputInterface $input, OutputInterface $output, $supportedDatabases) {
  88. $db = strtolower($input->getOption('database'));
  89. if (!in_array($db, $supportedDatabases)) {
  90. throw new InvalidArgumentException("Database <$db> is not supported. " . implode(', ', $supportedDatabases) . ' are supported.');
  91. }
  92. $dbUser = $input->getOption('database-user');
  93. $dbPass = $input->getOption('database-pass');
  94. $dbName = $input->getOption('database-name');
  95. $dbPort = $input->getOption('database-port');
  96. if ($db === 'oci') {
  97. // an empty hostname needs to be read from the raw parameters
  98. $dbHost = $input->getParameterOption('--database-host', '');
  99. } else {
  100. $dbHost = $input->getOption('database-host');
  101. }
  102. if ($dbPort) {
  103. // Append the port to the host so it is the same as in the config (there is no dbport config)
  104. $dbHost .= ':' . $dbPort;
  105. }
  106. if ($input->hasParameterOption('--database-pass')) {
  107. $dbPass = (string)$input->getOption('database-pass');
  108. }
  109. $adminLogin = $input->getOption('admin-user');
  110. $adminPassword = $input->getOption('admin-pass');
  111. $adminEmail = $input->getOption('admin-email');
  112. $dataDir = $input->getOption('data-dir');
  113. if ($db !== 'sqlite') {
  114. if (is_null($dbUser)) {
  115. throw new InvalidArgumentException('Database account not provided.');
  116. }
  117. if (is_null($dbName)) {
  118. throw new InvalidArgumentException('Database name not provided.');
  119. }
  120. if (is_null($dbPass)) {
  121. /** @var QuestionHelper $helper */
  122. $helper = $this->getHelper('question');
  123. $question = new Question('What is the password to access the database with user <' . $dbUser . '>?');
  124. $question->setHidden(true);
  125. $question->setHiddenFallback(false);
  126. $dbPass = $helper->ask($input, $output, $question);
  127. }
  128. }
  129. if (is_null($adminPassword)) {
  130. /** @var QuestionHelper $helper */
  131. $helper = $this->getHelper('question');
  132. $question = new Question('What is the password you like to use for the admin account <' . $adminLogin . '>?');
  133. $question->setHidden(true);
  134. $question->setHiddenFallback(false);
  135. $adminPassword = $helper->ask($input, $output, $question);
  136. }
  137. if ($adminEmail !== null && !filter_var($adminEmail, FILTER_VALIDATE_EMAIL)) {
  138. throw new InvalidArgumentException('Invalid e-mail-address <' . $adminEmail . '> for <' . $adminLogin . '>.');
  139. }
  140. $options = [
  141. 'dbtype' => $db,
  142. 'dbuser' => $dbUser,
  143. 'dbpass' => $dbPass,
  144. 'dbname' => $dbName,
  145. 'dbhost' => $dbHost,
  146. 'adminlogin' => $adminLogin,
  147. 'adminpass' => $adminPassword,
  148. 'adminemail' => $adminEmail,
  149. 'directory' => $dataDir
  150. ];
  151. if ($db === 'oci') {
  152. $options['dbtablespace'] = $input->getParameterOption('--database-table-space', '');
  153. }
  154. return $options;
  155. }
  156. /**
  157. * @param OutputInterface $output
  158. * @param array<string|array> $errors
  159. */
  160. protected function printErrors(OutputInterface $output, array $errors): void {
  161. foreach ($errors as $error) {
  162. if (is_array($error)) {
  163. $output->writeln('<error>' . $error['error'] . '</error>');
  164. if (isset($error['hint']) && !empty($error['hint'])) {
  165. $output->writeln('<info> -> ' . $error['hint'] . '</info>');
  166. }
  167. if (isset($error['exception']) && $error['exception'] instanceof Throwable) {
  168. $this->printThrowable($output, $error['exception']);
  169. }
  170. } else {
  171. $output->writeln('<error>' . $error . '</error>');
  172. }
  173. }
  174. }
  175. private function printThrowable(OutputInterface $output, Throwable $t): void {
  176. $output->write('<info>Trace: ' . $t->getTraceAsString() . '</info>');
  177. $output->writeln('');
  178. if ($t->getPrevious() !== null) {
  179. $output->writeln('');
  180. $output->writeln('<info>Previous: ' . get_class($t->getPrevious()) . ': ' . $t->getPrevious()->getMessage() . '</info>');
  181. $this->printThrowable($output, $t->getPrevious());
  182. }
  183. }
  184. }