From bf7db8cda05544e2fc6a41ee0b874c7a5e4499cf Mon Sep 17 00:00:00 2001 From: Demian Katz Date: Fri, 4 Aug 2023 12:37:26 -0400 Subject: [PATCH] [VUFIND-1568] Make Solr port configurable during installation (#2962) --- .gitignore | 2 + build.xml | 54 +++++- .../VuFind/Controller/InstallController.php | 26 +++ .../VuFindTest/Feature/LiveDetectionTrait.php | 3 +- .../Command/Install/InstallCommand.php | 169 ++++++++++++++++-- .../Command/Install/InstallCommandTest.php | 55 +++++- packages/DEBIAN/postinst | 12 +- 7 files changed, 286 insertions(+), 35 deletions(-) diff --git a/.gitignore b/.gitignore index 50ec3ff6abb..2d49261220b 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,8 @@ ChangeLog TAGS composer.phar +env.bat +env.sh import/solrmarc.log* lessphp_*.list module/VuFind/tests/.phpunit.result.cache diff --git a/build.xml b/build.xml index a9a3116688d..d583995849b 100644 --- a/build.xml +++ b/build.xml @@ -46,6 +46,7 @@ + @@ -272,7 +273,7 @@ - + @@ -292,12 +293,12 @@ - + - + @@ -400,7 +401,7 @@ - + @@ -408,6 +409,18 @@ Solr PID file (${solr_pid_file}) detected. Is the system already running? + + + + Environment file (env.bat) detected. Is the system already running? + + + + + + Environment file (env.sh) detected. Is the system already running? + + @@ -484,7 +497,7 @@ - + @@ -503,6 +516,15 @@ + + + + + + + + + @@ -562,7 +584,7 @@ - + @@ -646,7 +668,7 @@ ${git_status} - + @@ -667,6 +689,18 @@ ${git_status} + + + + + + + + + + + + @@ -711,7 +745,7 @@ ${git_status} - + @@ -739,6 +773,10 @@ ${git_status} + + + + diff --git a/module/VuFind/src/VuFind/Controller/InstallController.php b/module/VuFind/src/VuFind/Controller/InstallController.php index 3fc4b5636b7..e0f246385ce 100644 --- a/module/VuFind/src/VuFind/Controller/InstallController.php +++ b/module/VuFind/src/VuFind/Controller/InstallController.php @@ -135,6 +135,29 @@ protected function checkBasicConfig() ]; } + /** + * Extract the Solr base URL from the SolrMarc configuration file, + * so a custom Solr port configured in install.php can be applied to + * the initial config.ini file. + * + * Return null if no custom Solr URL can be found. + * + * @return ?string + */ + protected function getSolrUrlFromImportConfig() + { + $resolver = $this->serviceLocator->get(\VuFind\Config\PathResolver::class); + $importConfig = $resolver->getLocalConfigPath('import.properties', 'import'); + if (file_exists($importConfig)) { + $props = file_get_contents($importConfig); + preg_match('|solr.hosturl\s*=\s*(https?://\w+:\d+/\w+)|', $props, $matches); + if (!empty($matches[1])) { + return $matches[1]; + } + } + return null; + } + /** * Display repair instructions for basic configuration problems. * @@ -152,6 +175,9 @@ public function fixbasicconfigAction() $serverUrl = $this->getViewRenderer()->plugin('serverurl'); $path = $this->url()->fromRoute('home'); $writer->set('Site', 'url', rtrim($serverUrl($path), '/')); + if ($solrUrl = $this->getSolrUrlFromImportConfig()) { + $writer->set('Index', 'url', $solrUrl); + } if (!$writer->save()) { throw new \Exception('Cannot write config to disk.'); } diff --git a/module/VuFind/src/VuFindTest/Feature/LiveDetectionTrait.php b/module/VuFind/src/VuFindTest/Feature/LiveDetectionTrait.php index 44461c42cf3..26299bf2ac7 100644 --- a/module/VuFind/src/VuFindTest/Feature/LiveDetectionTrait.php +++ b/module/VuFind/src/VuFindTest/Feature/LiveDetectionTrait.php @@ -56,6 +56,7 @@ trait LiveDetectionTrait public function continuousIntegrationRunning() { // We'll assume that if the CI Solr PID is present, then CI is active: - return file_exists(__DIR__ . '/../../../../../local/solr-8983.pid'); + $port = getenv('SOLR_PORT') ?? '8983'; + return file_exists(__DIR__ . "/../../../../../local/solr-$port.pid"); } } diff --git a/module/VuFindConsole/src/VuFindConsole/Command/Install/InstallCommand.php b/module/VuFindConsole/src/VuFindConsole/Command/Install/InstallCommand.php index 81adf334829..d5b25953561 100644 --- a/module/VuFindConsole/src/VuFindConsole/Command/Install/InstallCommand.php +++ b/module/VuFindConsole/src/VuFindConsole/Command/Install/InstallCommand.php @@ -99,6 +99,13 @@ class InstallCommand extends Command */ protected $basePath = '/vufind'; + /** + * Solr port to use. + * + * @var string + */ + protected $solrPort = '8983'; + /** * Constructor * @@ -161,6 +168,12 @@ protected function configure() null, InputOption::VALUE_REQUIRED, 'Specify the hostname for the VuFind Site, when multisite=host' + )->addOption( + 'solr-port', + null, + InputOption::VALUE_OPTIONAL, + 'Port number to use for Solr' + . " (defaults to {$this->solrPort} when --non-interactive is set)" )->addOption( 'non-interactive', null, @@ -281,6 +294,21 @@ protected function validateBasePath($basePath, $allowEmpty = false) . ' alphanumeric characters, dash or underscore.'; } + /** + * Validate a Solr port number. Returns true on success, message on failure. + * + * @param string $solrPort Port to validate. + * + * @return bool|string + */ + protected function validateSolrPort($solrPort) + { + if (is_numeric($solrPort)) { + return true; + } + return 'Solr port must be a number.'; + } + /** * Get a base path from the user (or return a default). * @@ -307,6 +335,32 @@ protected function getBasePath(InputInterface $input, OutputInterface $output) } } + /** + * Get a Solr port number from the user (or return a default). + * + * @param InputInterface $input Input object + * @param OutputInterface $output Output object + * + * @return string + */ + protected function getSolrPort(InputInterface $input, OutputInterface $output) + { + // Get VuFind base path: + while (true) { + $solrInput = $this->getInput( + $input, + $output, + "What port number should Solr use? [{$this->solrPort}] " + ); + if (empty($solrInput)) { + return $this->solrPort; + } elseif (($result = $this->validateSolrPort($solrInput)) === true) { + return $solrInput; + } + $output->writeln($result); + } + } + /** * Initialize the override directory and report success or failure. * @@ -529,6 +583,28 @@ protected function getInput( return $this->getHelper('question')->ask($input, $output, $question); } + /** + * Back up an existing file and inform the user. Return true on success, + * error message otherwise. + * + * @param OutputInterface $output Output object + * @param string $filename File to back up (if it exists) + * @param string $desc Description of file (for output message) + * + * @return bool|string + */ + protected function backUpFile(OutputInterface $output, string $filename, string $desc) + { + if (file_exists($filename)) { + $bak = $filename . '.bak.' . time(); + if (!copy($filename, $bak)) { + return "Problem backing up $filename to $bak"; + } + $output->writeln("Backed up existing $desc to $bak."); + } + return true; + } + /** * Generate the Apache configuration. Returns true on success, error message * otherwise. @@ -586,29 +662,74 @@ protected function buildApacheConfig(OutputInterface $output) } $target = $this->overrideDir . '/httpd-vufind.conf'; - if (file_exists($target)) { - $bak = $target . '.bak.' . time(); - copy($target, $bak); - $output->writeln("Backed up existing Apache configuration to $bak."); + if (($msg = $this->backUpFile($output, $target, "Apache configuration")) !== true) { + return $msg; } return $this->writeFileToDisk($target, $config) ? true : "Problem writing {$this->overrideDir}/httpd-vufind.conf."; } + /** + * Get an array of environment variables. + * + * @return array + */ + protected function getEnvironmentVariables(): array + { + $vars = [ + 'VUFIND_HOME' => $this->baseDir, + 'VUFIND_LOCAL_DIR' => $this->overrideDir, + 'VUFIND_LOCAL_MODULES' => $this->module, + 'SOLR_PORT' => $this->solrPort, + ]; + if (empty($vars['VUFIND_LOCAL_MODULES'])) { + unset($vars['VUFIND_LOCAL_MODULES']); + } + return $vars; + } + + /** + * Build the Unix-specific environment configuration. Returns true on success, + * error message otherwise. + * + * @param OutputInterface $output Output object + * + * @return bool|string + */ + protected function buildUnixEnvironment($output) + { + $filename = $this->baseDir . '/env.sh'; + if (($msg = $this->backUpFile($output, $filename, "Unix environment file")) !== true) { + return $msg; + } + $env = ''; + foreach ($this->getEnvironmentVariables() as $key => $val) { + $env .= "export $key=$val\n"; + } + return $this->writeFileToDisk($filename, $env) + ? true : "Problem writing {$filename}."; + } + /** * Build the Windows-specific startup configuration. Returns true on success, * error message otherwise. * + * @param OutputInterface $output Output object + * * @return bool|string */ - protected function buildWindowsConfig() + protected function buildWindowsConfig($output) { - $module = empty($this->module) - ? '' : "@set VUFIND_LOCAL_MODULES={$this->module}\n"; - $batch = "@set VUFIND_HOME={$this->baseDir}\n" - . "@set VUFIND_LOCAL_DIR={$this->overrideDir}\n" . $module; - return $this->writeFileToDisk($this->baseDir . '/env.bat', $batch) - ? true : "Problem writing {$this->baseDir}/env.bat."; + $filename = $this->baseDir . '/env.bat'; + if (($msg = $this->backUpFile($output, $filename, "Windows environment file")) !== true) { + return $msg; + } + $batch = ''; + foreach ($this->getEnvironmentVariables() as $key => $val) { + $batch .= "@set $key=$val\n"; + } + return $this->writeFileToDisk($filename, $batch) + ? true : "Problem writing {$filename}."; } /** @@ -630,7 +751,11 @@ protected function buildImportConfig(OutputInterface $output, $filename) return true; } $import = @file_get_contents($this->baseDir . '/import/' . $filename); - $import = str_replace("/usr/local/vufind", $this->baseDir, $import); + $import = str_replace( + ['/usr/local/vufind', ':8983'], + [$this->baseDir, ':' . $this->solrPort], + $import + ); $import = preg_replace( "/^\s*solrmarc.path\s*=.*$/m", "solrmarc.path = {$this->overrideDir}/import|{$this->baseDir}/import", @@ -837,6 +962,16 @@ protected function collectParameters( $userInputNeeded['basePath'] = true; } + $solrPort = trim($input->getOption('solr-port') ?? ''); + if (!empty($solrPort)) { + if (($result = $this->validateSolrPort($solrPort)) !== true) { + return $this->failWithError($output, $result); + } + $this->solrPort = $solrPort; + } elseif ($interactive) { + $userInputNeeded['solr-port'] = true; + } + // We assume "single site" mode unless the --multisite option is set; // note that $mode will be null if the user provided the option with // no value specified, and false if the user did not provide the option. @@ -865,6 +1000,9 @@ protected function collectParameters( if (isset($userInputNeeded['basePath'])) { $this->basePath = $this->getBasePath($input, $output); } + if (isset($userInputNeeded['solr-port'])) { + $this->solrPort = $this->getSolrPort($input, $output); + } if (isset($userInputNeeded['multisiteMode'])) { $this->multisiteMode = $this->getMultisiteMode($input, $output); } @@ -903,7 +1041,12 @@ protected function processParameters(OutputInterface $output) } // Build the Windows start file in case we need it: - if (($result = $this->buildWindowsConfig()) !== true) { + if (($result = $this->buildWindowsConfig($output)) !== true) { + return $this->failWithError($output, $result); + } + + // Build a Unix environment file in case we need it: + if (($result = $this->buildUnixEnvironment($output)) !== true) { return $this->failWithError($output, $result); } diff --git a/module/VuFindConsole/tests/unit-tests/src/VuFindTest/Command/Install/InstallCommandTest.php b/module/VuFindConsole/tests/unit-tests/src/VuFindTest/Command/Install/InstallCommandTest.php index 52a72797fc1..9f03b8b9a84 100644 --- a/module/VuFindConsole/tests/unit-tests/src/VuFindTest/Command/Install/InstallCommandTest.php +++ b/module/VuFindConsole/tests/unit-tests/src/VuFindTest/Command/Install/InstallCommandTest.php @@ -55,9 +55,10 @@ public function testInteractiveInstallation() $expectedBaseDir = realpath(__DIR__ . '/../../../../../../../../'); $localFixtures = $expectedBaseDir . '/module/VuFindConsole/tests/fixtures'; $command = $this->getMockCommand( - ['buildDirs', 'getApacheLocation', 'getInput', 'writeFileToDisk'] + ['backUpFile', 'buildDirs', 'getApacheLocation', 'getInput', 'writeFileToDisk'] ); - $command->expects($this->exactly(3))->method('getInput') + $command->expects($this->exactly(3))->method('backUpFile')->will($this->returnValue(true)); + $command->expects($this->exactly(4))->method('getInput') ->withConsecutive( [ $this->isInstanceOf(InputInterface::class), @@ -74,8 +75,13 @@ public function testInteractiveInstallation() $this->isInstanceOf(InputInterface::class), $this->isInstanceOf(OutputInterface::class), 'What base path should be used in VuFind\'s URL? [/vufind] ', + ], + [ + $this->isInstanceOf(InputInterface::class), + $this->isInstanceOf(OutputInterface::class), + 'What port number should Solr use? [8983] ', ] - )->willReturnOnConsecutiveCalls($localFixtures, '', '/bar'); + )->willReturnOnConsecutiveCalls($localFixtures, '', '/bar', '8080'); $expectedDirs = [ $localFixtures, $localFixtures . '/cache', @@ -87,10 +93,13 @@ public function testInteractiveInstallation() ->with($this->equalTo($expectedDirs)) ->will($this->returnValue(true)); $expectedEnvBat = "@set VUFIND_HOME=$expectedBaseDir\n" - . "@set VUFIND_LOCAL_DIR=$localFixtures\n"; - $command->expects($this->exactly(4))->method('writeFileToDisk') + . "@set VUFIND_LOCAL_DIR=$localFixtures\n" + . "@set SOLR_PORT=8080\n"; + $expectedEnvSh = str_replace('@set', 'export', $expectedEnvBat); + $command->expects($this->exactly(5))->method('writeFileToDisk') ->withConsecutive( ["$expectedBaseDir/env.bat", $expectedEnvBat], + ["$expectedBaseDir/env.sh", $expectedEnvSh], ["$localFixtures/import/import.properties"], ["$localFixtures/import/import_auth.properties"], ["$localFixtures/httpd-vufind.conf"] @@ -134,7 +143,7 @@ public function testNonInteractiveInstallation() $expectedBaseDir = realpath(__DIR__ . '/../../../../../../../../'); $localFixtures = $expectedBaseDir . '/module/VuFindConsole/tests/fixtures'; $command = $this->getMockCommand( - ['buildDirs', 'getApacheLocation', 'getInput', 'writeFileToDisk'] + ['backUpFile', 'buildDirs', 'getApacheLocation', 'getInput', 'writeFileToDisk'] ); $expectedDirs = [ $localFixtures, @@ -143,14 +152,18 @@ public function testNonInteractiveInstallation() $localFixtures . '/harvest', $localFixtures . '/import', ]; + $command->expects($this->exactly(3))->method('backUpFile')->will($this->returnValue(true)); $command->expects($this->once())->method('buildDirs') ->with($this->equalTo($expectedDirs)) ->will($this->returnValue(true)); $expectedEnvBat = "@set VUFIND_HOME=$expectedBaseDir\n" - . "@set VUFIND_LOCAL_DIR=$localFixtures\n"; - $command->expects($this->exactly(4))->method('writeFileToDisk') + . "@set VUFIND_LOCAL_DIR=$localFixtures\n" + . "@set SOLR_PORT=8983\n"; + $expectedEnvSh = str_replace('@set', 'export', $expectedEnvBat); + $command->expects($this->exactly(5))->method('writeFileToDisk') ->withConsecutive( ["$expectedBaseDir/env.bat", $expectedEnvBat], + ["$expectedBaseDir/env.sh", $expectedEnvSh], ["$localFixtures/import/import.properties"], ["$localFixtures/import/import_auth.properties"], ["$localFixtures/httpd-vufind.conf"] @@ -181,6 +194,32 @@ public function testNonInteractiveInstallation() $this->assertEquals(0, $commandTester->getStatusCode()); } + /** + * Test that providing an invalid Solr port number causes an error. + * + * @return void + */ + public function testInvalidSolrPort() + { + $expectedBaseDir = realpath(__DIR__ . '/../../../../../../../../'); + $command = $this->getMockCommand( + ['backUpFile', 'buildDirs', 'getApacheLocation', 'getInput', 'writeFileToDisk'] + ); + $commandTester = new CommandTester($command); + $commandTester->execute( + ['--solr-port' => 'bad'] + ); + $expectedOutput = <<assertEquals( + $expectedOutput, + trim($commandTester->getDisplay()) + ); + $this->assertEquals(1, $commandTester->getStatusCode()); + } + /** * Get a mock command object * diff --git a/packages/DEBIAN/postinst b/packages/DEBIAN/postinst index 2dd322432b1..5622e4158fe 100644 --- a/packages/DEBIAN/postinst +++ b/packages/DEBIAN/postinst @@ -3,11 +3,9 @@ # Script for installing VuFind on Ubuntu # This does not include the OCI8 libraries -# Update the profile file to set required environment variables: -sh -c 'echo export JAVA_HOME=\"/usr/lib/jvm/default-java\" > /etc/profile.d/vufind.sh' -sh -c 'echo export VUFIND_HOME=\"/usr/local/vufind\" >> /etc/profile.d/vufind.sh' -sh -c 'echo export VUFIND_LOCAL_DIR=\"\$VUFIND_HOME/local\" >> /etc/profile.d/vufind.sh' -source /etc/profile.d/vufind.sh +# Set environment variables for use below +VUFIND_HOME=/usr/local/vufind +VUFIND_LOCAL_DIR=$VUFIND_HOME/local # Turn on mod_rewrite in Apache. a2enmod rewrite @@ -24,6 +22,10 @@ chmod 777 $VUFIND_LOCAL_DIR/cache/cli cd $VUFIND_HOME php install.php --use-defaults +# Update the profile file to set required environment variables: +sh -c 'echo export JAVA_HOME=\"/usr/lib/jvm/default-java\" > /etc/profile.d/vufind.sh' +cat $VUFIND_HOME/env.sh >> /etc/profile.d/vufind.sh + # Set up Apache for VuFind and reload configuration APACHE_CONF_DIR=/etc/apache2/conf.d if [ ! -d $APACHE_CONF_DIR ]; then