snipe-it/upgrade.php

576 lines
21 KiB
PHP
Raw Normal View History

2017-09-25 20:30:18 -07:00
<?php
(PHP_SAPI !== 'cli' || isset($_SERVER['HTTP_USER_AGENT'])) && die('Access denied.');
// We define this because we can't reliable use file_get_contents because some
// machines don't allow URL access via allow_url_fopen being set to off
function url_get_contents ($Url) {
if (!function_exists('curl_init')){
die("cURL is not installed!\nThis is required for Snipe-IT as well as the upgrade script, so you will need to fix this before continuing.\nAborting upgrade...\n");
}
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $Url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$output = curl_exec($ch);
curl_close($ch);
return $output;
}
$app_environment = 'develop';
$skip_php_checks = false;
$branch = 'master';
$branch_override = false;
$no_interactive = false;
// Check for branch or other overrides
if ($argc > 1){
for ($arg=1; $arg<$argc; $arg++){
switch ($argv[$arg]) {
case '--skip-php-compatibility-checks':
$skip_php_checks = true;
break;
case '--branch':
$arg++;
$branch = $argv[$arg];
$branch_override = true;
break;
case '--no-interactive':
$no_interactive = true;
break;
default: // for legacy support from before we started using --branch
$branch = $argv[$arg];
$branch_override = true;
break;
}
}
}
echo "--------------------------------------------------------\n";
echo "WELCOME TO THE SNIPE-IT UPGRADER! \n";
echo "--------------------------------------------------------\n\n";
echo "This script will attempt to: \n\n";
echo "- validate some very basic .env file settings \n";
echo "- check your PHP version and extension requirements \n";
echo "- check directory permissions \n";
echo "- do a git pull to bring you to the latest version \n";
echo "- run composer install to get your vendors up to date \n";
echo "- run migrations to get your schema up to date \n";
echo "- clear out old cache settings\n\n";
// Fetching most current upgrade requirements from github. Read more here: https://github.com/snipe/snipe-it/pull/14127
$remote_requirements_file = "https://raw.githubusercontent.com/snipe/snipe-it/$branch/.upgrade_requirements.json";
$upgrade_requirements_raw = url_get_contents($remote_requirements_file);
$upgrade_requirements = json_decode($upgrade_requirements_raw, true);
if (! $upgrade_requirements) {
if(!$skip_php_checks){
echo "\nERROR: Failed to retrieve remote requirements from $remote_requirements_file\n\n";
if ($branch_override){
echo "NOTE: You passed a custom branch: $branch\n";
echo " If the above URL doesn't work, that may be why. Please check you branch spelling/extistance\n\n";
}
if (json_last_error()) {
print "JSON DECODE ERROR DETECTED:\n";
print json_last_error_msg() . "\n\n";
print "Raw curl output:\n";
print $upgrade_requirements_raw . "\n\n";
}
echo "We suggest correcting this, but if you can't, please verify that your requirements conform to those at that url.\n\n";
echo " -- DANGER -- DO AT YOUR OWN RISK --\n";
echo " IF YOU ARE SURE, re-run this script with --skip-php-compatibility-checks to skip this check.\n";
echo " -- DANGER -- THIS COULD BREAK YOUR INSTALLATION";
die("Exiting.\n\n");
}
echo "NOTICE: Unable to fetch upgrade requirements, but continuing because you passed --skip-php-compatibility-checks...\n";
}
echo "Launching using branch: $branch\n";
if($upgrade_requirements){
$php_min_works = $upgrade_requirements['php_min_version'];
$php_max_wontwork = $upgrade_requirements['php_max_wontwork'];
echo "Found PHP requirements, will check for PHP > $php_min_works and < $php_max_wontwork\n";
}
// done fetching requirements
if (!$no_interactive) {
$yesno = readline("\nProceed with upgrade? [y/N]: ");
} else {
$yesno = "yes";
}
if ($yesno == "yes" || $yesno == "YES" ||$yesno == "y" ||$yesno == "Y"){
# don't do anything
} else {
die("Exiting.\n\n");
}
echo "\n";
2018-08-02 20:55:52 -07:00
if ((strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') || (!function_exists('posix_getpwuid'))) {
echo "Skipping user check as it is not supported on Windows or Posix is not installed on this server. \n";
2017-11-08 20:28:18 -08:00
} else {
2017-12-12 09:10:05 -08:00
$pwu_data = posix_getpwuid(posix_geteuid());
$username = $pwu_data['name'];
2017-12-12 09:10:05 -08:00
if (($username=='root') || ($username=='admin')) {
die("\nERROR: This script should not be run as root/admin. Exiting.\n\n");
}
}
2017-10-02 20:11:07 -07:00
2017-09-25 20:30:18 -07:00
echo "--------------------------------------------------------\n";
echo "STEP 1: Checking .env file: \n";
echo "- Your .env is located at ".getcwd()."/.env \n";
2018-08-02 20:55:52 -07:00
echo "--------------------------------------------------------\n\n";
// Check the .env looks ok
$env = file('.env');
if (! $env){
echo "\n!!!!!!!!!!!!!!!!!!!!!!!!!! .ENV FILE ERROR !!!!!!!!!!!!!!!!!!!!!!!!!!\n";
echo "Your .env file doesn't seem to exist in this directory or isn't readable! Please look into that.\n";
exit(1);
}
$env_good = '';
$env_bad = '';
// Loop through each line of the .env
foreach ($env as $line_num => $line) {
if ((strlen($line) > 1) && (strpos($line, "#") !== 0)) {
list ($env_key, $env_value) = $env_line = explode('=', $line);
// The array starts at 0
$show_line_num = $line_num+1;
$env_value = trim($env_value);
// Strip out the quote marks if there are any
$env_value = str_replace('"', '',$env_value);
$env_value = str_replace("'", '',$env_value);
/**
* We set this $app_environment here to determine which version of composer to use, --no-dev or with dev dependencies.
* This doesn't actually *change* anything in the .env file, but if the user is running this with
* APP_ENV set to anything OTHER than production, they'll get an error when they try to dump-autoload
* because the Dusk service provider only tries to load if the app is not in production mode.
*
* It's 100% okay if they're not in production mode, but this will avoid any confusion as they get
* erroneous errors using this upgrader if they are not in production mode when they run this script.
*
* We use this further down in the composer section of this upgrader.
*/
if ($env_key == "APP_ENV") {
if ($env_value == 'production') {
$app_environment = 'production';
}
}
if ($env_key == 'APP_KEY') {
if (($env_value=='') || (strlen($env_value) < 20)) {
$env_bad .= "✘ APP_KEY ERROR in your .env on line #'.{$show_line_num}.': Your APP_KEY should not be blank. Run `php artisan key:generate` to generate one.\n";
} else {
$env_good .= "√ Your APP_KEY is not blank. \n";
}
}
if ($env_key == 'APP_URL') {
if (($env_value!="null") && ($env_value!="")) {
$env_good .= '√ Your APP_URL is not null or blank. It is set to '.$env_value."\n";
if (!str_begins(trim($env_value), 'http://') && (!str_begins($env_value, 'https://'))) {
$env_bad .= '✘ APP_URL ERROR in your .env on line #'.$show_line_num.': Your APP_URL should start with https:// or http://!! It is currently set to: '.$env_value;
} else {
$env_good .= '√ Your APP_URL is set to '.$env_value.' and starts with the protocol (https:// or http://)'."\n";
}
if (str_ends(trim($env_value), "/")) {
$env_bad .= '✘ APP_URL ERROR in your .env on line #'.$show_line_num.': Your APP_URL should NOT end with a trailing slash. It is currently set to: '.$env_value;
} else {
$env_good .= '√ Your APP_URL ('.$env_value.') does not have a trailing slash.'."\n";
}
} else {
$env_bad .= "✘ APP_URL ERROR in your .env on line #".$show_line_num.": Your APP_URL CANNOT be set to null or left blank.\n";
}
}
}
}
echo $env_good;
if ($env_bad !='') {
echo "!!!!!!!!!!!!!!!!!!!!!!!!!! .ENV FILE ERROR !!!!!!!!!!!!!!!!!!!!!!!!!!\n";
echo "Your .env file is misconfigured. Upgrade cannot continue.\n";
echo "--------------------------------------------------------\n\n";
echo $env_bad;
echo "\n\n--------------------------------------------------------\n";
echo "!!!!!!!!!!!!!!!!!!!!!!!!! ABORTING THE UPGRADER !!!!!!!!!!!!!!!!!!!!!!\n";
echo "Please correct the issues above in ".getcwd()."/.env and try again.\n";
echo "--------------------------------------------------------\n";
exit(1);
}
if(!$skip_php_checks){
echo "\n--------------------------------------------------------\n";
echo "STEP 2: Checking PHP requirements: (Required PHP >=". $php_min_works. " - <".$php_max_wontwork.") \n";
echo "--------------------------------------------------------\n\n";
if ((version_compare(phpversion(), $php_min_works, '>=')) && (version_compare(phpversion(), $php_max_wontwork, '<'))) {
2018-08-02 20:55:52 -07:00
echo "√ Current PHP version: (" . phpversion() . ") is at least " . $php_min_works . " and less than ".$php_max_wontwork."! Continuing... \n";
echo sprintf("FYI: The php.ini used by this PHP is: %s\n\n", get_cfg_var('cfg_file_path'));
2018-08-02 20:55:52 -07:00
} else {
echo "!!!!!!!!!!!!!!!!!!!!!!!!! PHP VERSION ERROR !!!!!!!!!!!!!!!!!!!!!!!!!\n";
echo "This version of PHP (".phpversion().") is NOT compatible with Snipe-IT.\n";
echo "Snipe-IT requires PHP versions between ".$php_min_works." and ".$php_max_wontwork.".\n";
echo "Please install a compatible version of PHP and re-run this script again. \n";
echo "!!!!!!!!!!!!!!!!!!!!!!!!! ABORTING THE UPGRADER !!!!!!!!!!!!!!!!!!!!!!\n";
exit(1);
}
}
echo "Checking Required PHP extensions... \n\n";
// Get the list of installed extensions
$loaded_exts_array = get_loaded_extensions();
// The PHP extensions PHP is *required* to have enabled in order to run
$required_exts_array =
[
'bcmath',
'curl',
'exif',
'fileinfo',
'gd|imagick',
'json',
'ldap',
'mbstring',
'mysqli|pgsql',
'openssl',
'PDO',
'sodium',
'tokenizer',
'xml',
'zip',
];
$recommended_exts_array =
[
'sodium', //note that extensions need to be in BOTH the $required_exts_array and this one to be 'optional'
];
$ext_missing = '';
$ext_installed = '';
// Loop through the required extensions
foreach ($required_exts_array as $required_ext) {
// If we don't find the string in the array....
if (!in_array($required_ext, $loaded_exts_array)) {
// Let's check for any options with pipes in them - those mean you can have either or
if (strpos($required_ext, '|')) {
// Split the either/ors by their pipe and put them into an array
$require_either = explode("|", $required_ext);
$has_one_required_ext = false;
// Now loop through the either/or array and see whether any of the options match
foreach ($require_either as $require_either_value) {
if (in_array($require_either_value, $loaded_exts_array)) {
$ext_installed .= '√ '.$require_either_value." is installed!\n";
$has_one_required_ext = true;
break;
}
}
// If no match, add it to the string for errors
if (!$has_one_required_ext) {
$ext_missing .= '✘ MISSING PHP EXTENSION: '.str_replace("|", " OR ", $required_ext)."\n";
break;
}
// If this isn't an either/or option, just add it to the string of errors conventionally
} elseif (!in_array($required_ext, $recommended_exts_array)){
$ext_missing .= '✘ MISSING PHP EXTENSION: '.$required_ext."\n";
} else {
$ext_installed .= '- '.$required_ext." is *NOT* installed, but is recommended...\n";
}
// The required extension string was found in the array of installed extensions - yay!
} else {
$ext_installed .= '√ '.$required_ext." is installed!\n";
}
}
// Print out a useful error message and abort the install
if ($ext_missing!='') {
echo "--------------------------------------------------------\n";
echo "You have the following extensions installed: \n";
echo "--------------------------------------------------------\n";
foreach ($loaded_exts_array as $loaded_ext) {
echo "- ".$loaded_ext."\n";
}
echo "--------------------- !! ERROR !! ----------------------\n";
echo $ext_missing;
echo "--------------------------------------------------------\n";
echo "ABORTING THE INSTALLER \n";
echo "Please install the extensions above and re-run this script.\n";
echo "--------------------------------------------------------\n";
exit(1);
} else {
echo $ext_installed."\n";
2020-11-10 09:06:44 -08:00
2018-08-02 20:55:52 -07:00
}
echo "--------------------------------------------------------\n";
echo "STEP 3: Checking directory permissions: \n";
echo "--------------------------------------------------------\n\n";
$writable_dirs_array =
[
'bootstrap/cache',
'storage',
'storage/logs',
'storage/logs/laravel.log',
'storage/framework',
'storage/framework/cache',
'storage/framework/sessions',
'storage/framework/views',
'storage/app',
'storage/app/backups',
'storage/app/backup-temp',
'storage/private_uploads',
'public/uploads',
];
$dirs_writable = '';
$dirs_not_writable = '';
// Loop through the directories that need to be writable
foreach ($writable_dirs_array as $writable_dir) {
if (is_writable($writable_dir)) {
$dirs_writable .= '√ '.getcwd().'/'.$writable_dir." is writable \n";
} else {
$dirs_not_writable .= '✘ PERMISSIONS ERROR: '.getcwd().'/'.$writable_dir." is NOT writable\n";
}
}
echo $dirs_writable."\n";
// Print out a useful error message
if ($dirs_not_writable!='') {
echo "--------------------------------------------------------\n";
echo "The following directories/files do not seem writable: \n";
echo "--------------------------------------------------------\n";
echo $dirs_not_writable;
echo "--------------------- !! ERROR !! ----------------------\n";
echo "Please check the permissions on the directories above and re-run this script.\n";
echo "------------------------- :( ---------------------------\n\n";
exit(1);
}
2018-08-02 20:55:52 -07:00
echo "--------------------------------------------------------\n";
echo "STEP 4: Backing up database: \n";
2017-09-25 20:30:18 -07:00
echo "--------------------------------------------------------\n\n";
$backup = exec('php artisan snipeit:backup', $backup_results, $return_code);
echo '-- ' . implode("\n", $backup_results) . "\n\n";
if ($return_code > 0) {
die("Something went wrong with your backup. Aborting!\n\n");
}
unset($return_code);
2017-09-25 20:30:18 -07:00
echo "--------------------------------------------------------\n";
echo "STEP 5: Putting application into maintenance mode: \n";
2017-09-25 20:30:18 -07:00
echo "--------------------------------------------------------\n\n";
exec('php artisan down', $down_results, $return_code);
echo '-- ' . implode("\n", $down_results) . "\n";
if ($return_code > 0) {
die("Something went wrong with downing you site. This can't be good. Please investigate the error. Aborting!n\n");
}
unset($return_code);
2017-09-25 20:30:18 -07:00
2017-10-02 20:11:07 -07:00
echo "--------------------------------------------------------\n";
echo "STEP 6: Pulling latest from Git (".$branch." branch): \n";
2017-10-02 20:11:07 -07:00
echo "--------------------------------------------------------\n\n";
$git_version = shell_exec('git --version');
if ((strpos('git version', $git_version)) === false) {
echo "Git is installed. \n";
$git_fetch = shell_exec('git fetch');
2017-10-02 20:11:07 -07:00
$git_checkout = shell_exec('git checkout '.$branch);
$git_stash = shell_exec('git stash');
$git_pull = shell_exec('git pull');
echo $git_fetch;
2017-10-02 20:32:42 -07:00
echo '-- '.$git_stash;
echo '-- '.$git_checkout;
echo '-- '.$git_pull."\n";
2017-10-02 20:11:07 -07:00
} else {
echo "Git is NOT installed. You can still use this upgrade script to run common \n";
echo "migration commands, but you will have to manually download the updated files. \n\n";
echo "Please note that this script will not download the latest Snipe-IT \n";
echo "files for you unless you have git installed. \n";
echo "It simply runs the standard composer, artisan, and migration \n";
echo "commands needed to finalize the upgrade after. \n\n";
2017-10-02 20:11:07 -07:00
}
2017-10-02 20:11:07 -07:00
2017-09-25 20:30:18 -07:00
echo "--------------------------------------------------------\n";
echo "STEP 7: Cleaning up old cached files:\n";
2017-09-25 20:30:18 -07:00
echo "--------------------------------------------------------\n\n";
// Build an array of the files we generally want to delete because they
// can cause issues with funky caching
$unused_files = [
"bootstrap/cache/compiled.php",
"bootstrap/cache/services.php",
"bootstrap/cache/config.php",
"vendor/symfony/translation/TranslatorInterface.php",
];
foreach ($unused_files as $unused_file) {
if (file_exists($unused_file)) {
echo "√ Deleting ".$unused_file.". It is no longer used.\n";
@unlink($unused_file);
} else {
echo "√ No ".$unused_file.", so nothing to delete.\n";
}
2017-09-25 20:30:18 -07:00
}
echo "\n";
2017-09-25 20:30:18 -07:00
$config_clear = shell_exec('php artisan config:clear');
$cache_clear = shell_exec('php artisan cache:clear');
2017-09-26 13:05:34 -07:00
$route_clear = shell_exec('php artisan route:clear');
$view_clear = shell_exec('php artisan view:clear');
echo '-- '.$config_clear;
echo '-- '.$cache_clear;
echo '-- '.$route_clear;
echo '-- '.$view_clear;
2017-09-25 20:30:18 -07:00
echo "\n";
echo "--------------------------------------------------------\n";
echo "STEP 8: Updating composer dependencies:\n";
2020-11-03 01:00:37 -08:00
echo "(This may take a moment.)\n";
2017-09-25 20:30:18 -07:00
echo "--------------------------------------------------------\n\n";
echo "-- Running the app in ".$app_environment." mode.\n";
2017-09-25 20:30:18 -07:00
// Composer install
if (file_exists('composer.phar')) {
echo "√ Local composer.phar detected, so we'll use that.\n\n";
echo "-- Updating local composer.phar\n\n";
$composer_update = shell_exec('php composer.phar self-update');
echo $composer_update."\n\n";
// Use --no-dev only if we are in production mode.
// This will cause errors otherwise, if the user is in develop or local for their APP_ENV
if ($app_environment == 'production') {
$composer = shell_exec('php composer.phar install --no-dev --prefer-source');
} else {
$composer = shell_exec('php composer.phar install --prefer-source');
}
$composer_dump = shell_exec('php composer.phar dump');
2017-09-26 13:05:34 -07:00
} else {
echo "-- We couldn't find a local composer.phar. No worries, trying globally.\n";
echo "Since you are running composer globally, we won't try to update it for you.\n";
echo "If you run into issues with this step, try running `composer self-update` \n";
echo "before running this updater again\n\n";
if ($app_environment == 'production') {
$composer = shell_exec('composer install --no-dev --prefer-source');
} else {
$composer = shell_exec('composer install --prefer-source');
}
$composer_dump = shell_exec('composer dump');
2017-09-25 20:30:18 -07:00
}
echo $composer_dump."\n";
echo $composer;
2017-09-25 20:30:18 -07:00
echo "--------------------------------------------------------\n";
echo "STEP 9: Migrating database:\n";
2017-09-25 20:30:18 -07:00
echo "--------------------------------------------------------\n\n";
$migrations = shell_exec('php artisan migrate --force');
echo $migrations."\n";
2017-09-25 20:30:18 -07:00
echo "--------------------------------------------------------\n";
echo "STEP 10: Checking for OAuth keys:\n";
2017-09-25 20:30:18 -07:00
echo "--------------------------------------------------------\n\n";
if ((!file_exists('storage/oauth-public.key')) || (!file_exists('storage/oauth-private.key'))) {
echo "- No OAuth keys detected. Running passport install now.\n\n";
$passport = shell_exec('php artisan passport:install');
echo $passport;
} else {
echo "√ OAuth keys detected. Skipping passport install.\n\n";
2017-09-25 20:30:18 -07:00
}
echo "--------------------------------------------------------\n";
echo "STEP 11: Taking application out of maintenance mode:\n";
2017-09-25 20:30:18 -07:00
echo "--------------------------------------------------------\n\n";
$up = shell_exec('php artisan up');
echo '-- '.$up."\n";
2017-09-25 20:30:18 -07:00
[WIP] Added #5957 - Flysystem support (#6262) * Added AWS url to example env * Upgrader - added check for new storage path and attempt to move * Ignore symlink * Updated paths for models * Moved copy methods * Added AWS_URL support For some reasin, Flysystem was generating the wrong AWS url (with a region included) * Switch to Flysystem for image uploads * Nicer display of image preview * Updated image preview on edit blades to use Flysystem * Twiddled some more paths * Working filesystems config * Updated Asset Models and Departments to use Flysystem * Janky workaround for differing S3/local urls/paths * Try to smartly use S3 as public disk if S3 is configured * Use public disk Storage options for public files * Additional transformer edits for Flysystem * Removed debugging * Added missing use Storage directive * Updated seeders to use Flysystem * Default logo * Set a default width We can potentially override this in settings later * Use Flysystem for logo upload * Update downloadFile to use Flysystem * Updated AssetFilesController to use Flysystem * Updated acceptance signatures to use Flysystem * Updated signature view to use Flysystem This isn’t working 100% yet * Use Flysystem facade for displaying asset image * Set assets path Should clean all these up when we’re done here * Added Rackspace support for Flysystem * Added Flysystem migrator console command * Added use Storage directive for categories * Added user avatars to Flysystem * Added profile avatar to Flysystem * Added the option to delete local files with the migrator * Added a check to prevent people from trying to move from local to local * Fixed the selectlists for Flysystem * Fixed the getImageUrl method to reflect Flysystem * Fixed AWS copy process * Fixed models path * More selectlist updates for Flysystem * Updated example .envs with updated env variable names * *sigh* * Updated non-asset getImageUrl() methods to use Flysystem * Removed S3 hardcoding * Use Flysystem in email headers * Fixed typo * Removed camera support from asset file upload We’ll find a way to add this in later (and add that support to all of the other image uploads as well) * Fixed path for categories * WIP - Switched to standard handleImages for asset upload. This is currently broken as I refact the handleImages method. Because the assets store/create methods use their own Form Request, the handleImages method doesn’t exist in that Form Request so it wil error now. * Fixed css URL error * Updated Debugbar to latest version (#6265) v3.2 adds support for Laravel 5.7 * Fixed: Missing CSS file in basic.blade.php (#6264) * Fixed missing CSS file in basic.blade.php * Added * Changed stylesheet import for authorize.blade.php * Updated composer lock * Added AWS_BUCKET_ROOT as env variable * Use nicer image preview for logo upload * Removed AssetRequest form request * Removed asset form request, moved custom field validation into model * Added additional help text for logo upload * Increased the size of the image resize - should make this a setting tho * Few more formatting tweaks to logo section of branding blade preview * Use Flysystem for asset/license file uploads * Use Flysystem for removing images from models that have been deleted * Enable backups to use Flysystem This only handles part of the problem. This just makes it so we can ship files to S3 if we want, but does not account for how we backup files that are hosted on S3 * Use Flysystem to download license files * Updated audits to use Flysystem
2018-09-29 21:33:52 -07:00
echo "---------------------- FINISHED! -----------------------\n";
echo "All done! Clear your browser cookies and re-login to use \n";
echo "your upgraded Snipe-IT!\n";
2017-09-25 21:50:41 -07:00
echo "--------------------------------------------------------\n\n";
2017-09-25 20:30:18 -07:00
function str_begins($haystack, $needle) {
return (substr_compare($haystack, $needle, 0, strlen($needle)) === 0);
}
function str_ends($haystack, $needle) {
return (substr_compare($haystack, $needle, -strlen($needle)) === 0);
}