New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Tools] Ubuntu production environment "bootstrap" script #3981
Changes from all commits
d9975db
2109384
3965b04
488f788
15b4606
e39d7c4
4c80262
9c94782
03b0604
361b8ac
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,103 @@ | ||||||
<?php | ||||||
|
||||||
/** | ||||||
* Use bash to determine if certain unix tools are installed. Which is used for | ||||||
* generic system tools (e.g. wget), and dpkg if which fails. | ||||||
* | ||||||
* @param string $tool Name of tool | ||||||
* | ||||||
* @return bool representing if tool is installed | ||||||
*/ | ||||||
function installed($tool) : bool | ||||||
{ | ||||||
// `which` returns empty if a tool is not installed. | ||||||
// shell_exec captures this output. | ||||||
if (shell_exec("which $tool")) { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this would be more reliable if it used exec and checked the shell return code, rather than trying to parse stdout. |
||||||
return true; | ||||||
} | ||||||
// check installed pacakages with dpkg. Returns 0 if installed. | ||||||
johnsaigle marked this conversation as resolved.
Show resolved
Hide resolved
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are you sure about that? dpkg(1) says:
(but doesn't really clarify what "a check or assertion command" is) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm well I mean the |
||||||
exec("dpkg -s $tool", $output, $status); | ||||||
if ($status === 0) { | ||||||
return true; | ||||||
} | ||||||
return false; | ||||||
} | ||||||
|
||||||
/** | ||||||
* Prompts a user with a question and acceptable responses. | ||||||
* | ||||||
* @param string $question Prompt to display to user | ||||||
* @param array $answers List of acceptable answers to prompt | ||||||
* | ||||||
* @return void | ||||||
*/ | ||||||
function writeQuestion($question, $answers) : void | ||||||
{ | ||||||
echo $question . ' (' . implode('/', $answers) . '): ' . PHP_EOL; | ||||||
} | ||||||
|
||||||
/** | ||||||
* Gets user input from STDIN and checks if it matches an option in | ||||||
* $possibleAnswers. If not, the default answer is used. Intended to follow | ||||||
* function writeQuestion | ||||||
* | ||||||
* @param array $possibleAnswers Possible answers to a prompt | ||||||
* @param array $defaultAnswer Response if user entered invalid response | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In most cases if a user responds with an invalid response, shouldn't the scripts be re-prompting for a valid answer? |
||||||
* | ||||||
* @return string The user input representing their answer or the default answer | ||||||
*/ | ||||||
function readAnswer($possibleAnswers, $defaultAnswer) : string | ||||||
{ | ||||||
$in = fopen('php://stdin', 'rw+'); | ||||||
johnsaigle marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
$answer = trim(fgets($in)); | ||||||
|
||||||
if (!in_array($answer, $possibleAnswers, true)) { | ||||||
return $defaultAnswer; | ||||||
} | ||||||
|
||||||
return $answer; | ||||||
} | ||||||
|
||||||
/** | ||||||
* Prints and executes a bash command using exec. Prints an error message on | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Still not necessarily bash. |
||||||
* failure (a 0 exit code in bash is a success. Anything else is considered an | ||||||
* error here. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
* | ||||||
* @param string $cmd An executable shell command | ||||||
* | ||||||
* @return bool True if command exits normally. False otherwise. | ||||||
*/ | ||||||
function doExec($cmd) : bool | ||||||
{ | ||||||
echo "[+] Executing command `$cmd`... " . PHP_EOL; | ||||||
exec($cmd, $output, $status); | ||||||
if ($status !== 0) { | ||||||
echo bashErrorToString($cmd, $output, $status); | ||||||
return false; | ||||||
} | ||||||
echo '[+] OK.' . PHP_EOL; | ||||||
return true; | ||||||
} | ||||||
|
||||||
/** | ||||||
* A to-string method for exec. Captures bash exit code and error message for | ||||||
* debugging purposes. Also prints the command that was run. Modelled on PHP | ||||||
* `exec` function. | ||||||
* | ||||||
* @param string $cmd A bash command that has been executed | ||||||
* @param string $output Output of above command. | ||||||
* @param string $status Exit status of above command | ||||||
* | ||||||
* @return string The error message describing what happened in bash | ||||||
*/ | ||||||
function bashErrorToString($cmd, $output, $status) : string | ||||||
{ | ||||||
echo PHP_EOL; | ||||||
$error = "[-] ERROR: Command `$cmd` failed (error code $status)" . PHP_EOL; | ||||||
if (is_iterable($output)) { | ||||||
foreach ($output as $item) { | ||||||
$error .= $item . PHP_EOL; | ||||||
} | ||||||
} | ||||||
return $error; | ||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,214 @@ | ||
#!/usr/bin/env php | ||
# This script verifies a development installation of LORIS by ensuring that | ||
# the system has all of the required dependencies such as the correct PHP and | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What if PHP isn't installed? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'll add a check for it There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I mean the script is written in PHP, so how can it check that? |
||
# Apache versions as well as other miscellaneous extensions and system tools. | ||
# | ||
# Production environments should not run this tool as their dependency | ||
# management should be performed by a proper package such as a .deb file. | ||
# | ||
# Currently only Ubuntu environments are supported by this script. | ||
<?php | ||
error_reporting(E_ALL); | ||
|
||
// Go to LORIS root. | ||
chdir(dirname(__FILE__) . '/..'); | ||
require('tools/PHP_CLI_Helper.class.inc'); | ||
|
||
//TODO: Update these values as time passes | ||
$required_major_php = 7; | ||
$required_minor_php = 2; | ||
$required_major_apache = 2; | ||
$required_minor_apache = 4; | ||
// PHP version required for LORIS. | ||
$required_php = "$required_major_php.$required_minor_php"; | ||
|
||
/* Validate apache */ | ||
// Get string representation of apache version number | ||
$apache_parts = explode( | ||
'/', | ||
// this command yields e.g. Apache/2.4.34 | ||
shell_exec( | ||
"apache2 -v | " . | ||
"head -n 1 | " . | ||
"cut -d ' ' -f 3" | ||
) | ||
); | ||
$apache_version = end($apache_parts); | ||
/* Look for the string "$major.$minor" in info string. Also match on versions | ||
* higher than minor version because we want AT LEAST that version. | ||
*/ | ||
$pattern = "/$required_major_apache\.[$required_minor_apache-9].[0-9]/"; | ||
// When preg_match returns 0 it means no match was found. | ||
if (preg_match($pattern, $apache_version) === 0) { | ||
$required_apache = "$required_major_apache.$required_minor_apache"; | ||
die( | ||
"ERROR: LORIS requires Apache v$required_apache or higher." | ||
. PHP_EOL | ||
. "This must be done manually as it has possible security ramifications." | ||
. PHP_EOL | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The second sentence is unnecessary. |
||
); | ||
} | ||
|
||
// NOTE Update as time passes. | ||
// Dependencies last updated for version: 20.0.1 | ||
// This list should consist only of packages that can be installed via apt on | ||
// Ubuntu environments and must not include libraries that should be installed | ||
// via tools such as npm and composer. | ||
$apt_dependencies = array( | ||
"wget", | ||
"zip", | ||
"unzip", | ||
"php-json", | ||
"npm", | ||
"software-properties-common", | ||
"php-ast", | ||
"php$required_php", | ||
"php$required_php-mysql", | ||
"php$required_php-xml", | ||
"php$required_php-json", | ||
"php$required_php-mbstring", | ||
"php$required_php-gd", | ||
"libapache2-mod-php$required_php", | ||
); | ||
|
||
if ( | ||
!(installMissingRequirements($apt_dependencies)) | ||
&& (installAptPackages($apt_dependencies)) | ||
) { | ||
die( | ||
'Could not upgrade all required packages. Exiting.' | ||
. PHP_EOL | ||
); | ||
} | ||
|
||
|
||
// Run package managers and add dev flag if supplied to the script | ||
if (runPackageManagers(isset($argv[1]) && $argv[1] === 'dev')) { | ||
echo '[**] Dependencies up-to-date.' . PHP_EOL; | ||
} | ||
|
||
/** | ||
* Prints a list of missing apt packages and prompts user to install them. | ||
* | ||
* @param array $requirements Required packages determined missing earlier. | ||
* | ||
* @return bool True if all packages install properly. False otherwise. | ||
*/ | ||
function installMissingRequirements(array $requirements): bool | ||
{ | ||
$to_install = getMissingRequirements($requirements); | ||
if (empty($to_install)) { | ||
return true; | ||
} | ||
echo '[-] Required package(s) not installed:' . PHP_EOL; | ||
foreach ($to_install as $tool) { | ||
echo "\t* {$tool}" . PHP_EOL; | ||
} | ||
$answers = [ | ||
'Y', | ||
'n', | ||
]; | ||
$defaultAnswer = 'Y'; | ||
writeQuestion('Install now?', $answers); | ||
$answer = readAnswer($answers, $defaultAnswer); | ||
if ($answer != 'Y') { | ||
echo '[-] Not installing requirements...' . PHP_EOL; | ||
return false; | ||
} | ||
echo '[*] Installing requirements...' . PHP_EOL; | ||
return installAptPackages($to_install); | ||
} | ||
|
||
/** | ||
* Takes an array of packages to install using apt-get. Uses exec to install | ||
* or upgrade apt packages based on $upgrade_mode. | ||
* | ||
* @param array $packages List of apt packages to install. | ||
* @param bool $upgrade_mode If true, only upgrades packages. False to install. | ||
* | ||
* @return bool true if all packages installed properly. False otherwise. | ||
*/ | ||
function installAptPackages(array $packages, bool $upgrade_mode = false): bool | ||
{ | ||
foreach ($packages as $package) { | ||
if (installAptPackage($package, $upgrade_mode) !== true) { | ||
return false; | ||
} | ||
} | ||
return true; | ||
} | ||
|
||
/** | ||
* Installs or upgrades an individual apt package. | ||
* | ||
* @param string $name Name of package to install | ||
* @param bool $only_upgrade Whether to upgrade or install a package | ||
* | ||
* @return bool True if package installed/upgraded successfull. Otherwise false | ||
*/ | ||
function installAptPackage(string $name, bool $only_upgrade = false): bool | ||
{ | ||
if ($only_upgrade) { | ||
$cmd = "sudo apt-get install --only-upgrade "; | ||
} else { | ||
$cmd = "sudo apt-get install "; | ||
} | ||
$cmd .= escapeshellarg($name); | ||
return doExec($cmd); | ||
} | ||
|
||
/** | ||
* Runs 3rd-party package managers using exec | ||
* | ||
* @param bool $dev Whether the script should be run in Dev mode. | ||
* | ||
* @return bool True if all commands execute correctly. False otherwise | ||
*/ | ||
function runPackageManagers(bool $dev = false): bool | ||
{ | ||
if (posix_geteuid() === 0) { | ||
echo "[-] ERROR: Refusing to run package managers as root. Please " | ||
. "try again with a lower-privileged user or without sudo." | ||
. PHP_EOL; | ||
return false; | ||
} | ||
|
||
// Run dependency/package managers | ||
$cmd = 'composer install'; | ||
if ($dev) { | ||
$cmd .= ' --no-dev'; | ||
} | ||
if (doExec($cmd) === false) { | ||
return false; | ||
} | ||
|
||
$cmd = 'npm install'; | ||
if (doExec($cmd) === false) { | ||
return false; | ||
} | ||
|
||
$cmd = 'git describe --tags --always > VERSION'; | ||
if (doExec($cmd) === false) { | ||
return false; | ||
} | ||
return true; | ||
} | ||
|
||
/** | ||
* Check if all tools in $required are installed | ||
* | ||
* @param array $required Packages required by LORIS | ||
* | ||
* @return array of names of missing requirements | ||
*/ | ||
function getMissingRequirements(array $required): array | ||
{ | ||
$missing = []; | ||
foreach ($required as $tool) { | ||
if (!installed($tool)) { | ||
$missing[] = $tool; | ||
} | ||
} | ||
|
||
return $missing; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This isn't using bash, it's using the shell