mirror of
https://github.com/itflow-org/itflow
synced 2026-08-17 21:15:13 +00:00
update_cli.php now does both db and forces app updates all in one go no more switches
This commit is contained in:
@@ -130,7 +130,7 @@ echo "\nDone.\n";
|
|||||||
echo "\nNext steps:\n";
|
echo "\nNext steps:\n";
|
||||||
echo " 1. Log in with the credentials that were in use when the backup was taken.\n";
|
echo " 1. Log in with the credentials that were in use when the backup was taken.\n";
|
||||||
echo " 2. If the backup is older than the code in this directory, finish the database update:\n";
|
echo " 2. If the backup is older than the code in this directory, finish the database update:\n";
|
||||||
echo " php " . realpath(__DIR__) . "/update_cli.php --update_db\n";
|
echo " php " . realpath(__DIR__) . "/update_cli.php\n";
|
||||||
echo " 3. Check Maintenance > Cron - the schedule came back with the database.\n";
|
echo " 3. Check Maintenance > Cron - the schedule came back with the database.\n";
|
||||||
|
|
||||||
exit(0);
|
exit(0);
|
||||||
|
|||||||
@@ -23,169 +23,163 @@ if ($currentUser !== $fileOwner) {
|
|||||||
require_once "../config.php";
|
require_once "../config.php";
|
||||||
require_once "../functions.php";
|
require_once "../functions.php";
|
||||||
|
|
||||||
// A function to print the help message so that we don't duplicate it
|
/*
|
||||||
function printHelp() {
|
* This script takes no options. It updates the application and then the database, in that
|
||||||
echo "Usage: php update_cli.php [options]\n\n";
|
* order, because new code against an old schema is what breaks an install mid-upgrade.
|
||||||
echo "Options:\n";
|
*
|
||||||
echo " --help Show this help message.\n";
|
* The application update is a hard reset onto the branch this install tracks, so local
|
||||||
echo " --update Update the application only (git pull).\n";
|
* modifications to tracked files are discarded. Untracked files are left alone, which is
|
||||||
echo " --force_update Update the application only, discarding local changes: git fetch\n";
|
* everything an install keeps for itself: config.php, uploads/ and the custom/ directories.
|
||||||
echo " and hard reset to the branch this install tracks.\n";
|
*/
|
||||||
echo " --update_db Update the database structure only.\n";
|
|
||||||
echo "\nWith no options a full update is performed - the application first, then the\n";
|
function printUsage($stream = STDOUT) {
|
||||||
echo "database. That is the same as running --update --update_db.\n";
|
fwrite($stream, "Usage: php update_cli.php\n\n");
|
||||||
|
fwrite($stream, "Updates the application to the latest code on the branch this install tracks,\n");
|
||||||
|
fwrite($stream, "discarding local changes to tracked files, and then applies any outstanding\n");
|
||||||
|
fwrite($stream, "database updates. There are no options.\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Define allowed options (removed 'user')
|
/*
|
||||||
$allowed_options = [
|
* Set on the child process this script starts for the database phase - see the comment
|
||||||
'help',
|
* further down. It is not part of the interface, and it is what stops that child from
|
||||||
'update',
|
* starting a child of its own.
|
||||||
'force_update',
|
*/
|
||||||
'update_db'
|
$database_phase_only = getenv('ITFLOW_UPDATE_PHASE') === 'database';
|
||||||
];
|
|
||||||
|
|
||||||
// Parse command-line options
|
/*
|
||||||
$options = getopt('', ['update', 'force_update', 'update_db', 'help']);
|
* The switches this script used to take are gone. Anything on the command line is refused
|
||||||
|
* rather than ignored, so that an old --update_db call from a script or a set of notes
|
||||||
|
* cannot silently trigger a hard reset of the application instead.
|
||||||
|
*/
|
||||||
|
$arguments = array_slice($argv, 1);
|
||||||
|
|
||||||
// Check for invalid options by comparing argv against allowed options
|
if (!$database_phase_only && count($arguments) > 0) {
|
||||||
$argv_copy = $argv;
|
|
||||||
array_shift($argv_copy); // Remove script name
|
|
||||||
|
|
||||||
foreach ($argv_copy as $arg) {
|
if (in_array($arguments[0], ['--help', '-h', 'help'], true)) {
|
||||||
if (substr($arg, 0, 2) === '--') {
|
printUsage();
|
||||||
// Extract the option name (everything after -- and before = if present)
|
exit;
|
||||||
$eqPos = strpos($arg, '=');
|
|
||||||
if ($eqPos !== false) {
|
|
||||||
$optName = substr($arg, 2, $eqPos - 2);
|
|
||||||
} else {
|
|
||||||
$optName = substr($arg, 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if option name is allowed
|
|
||||||
if (!in_array($optName, $allowed_options)) {
|
|
||||||
echo "Error: Unrecognized option: $arg\n\n";
|
|
||||||
printHelp();
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// If "help" is requested, show instructions and exit
|
fwrite(STDERR, "Error: this script takes no options.\n\n");
|
||||||
if (isset($options['help'])) {
|
printUsage(STDERR);
|
||||||
printHelp();
|
exit(1);
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If no recognized options are passed, update everything - the application and then the
|
|
||||||
// database. The switches stay surgical for anyone who wants one or the other, which is
|
|
||||||
// what --update_db is for after updating the files from the web UI.
|
|
||||||
if (count($options) === 0) {
|
|
||||||
$options['update'] = true;
|
|
||||||
$options['update_db'] = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Whether the working tree actually moved. Decides how the database phase runs below
|
// Whether the working tree actually moved. Decides how the database phase runs below
|
||||||
$app_updated = false;
|
$app_updated = false;
|
||||||
|
|
||||||
// If "update" or "force_update" is requested
|
if (!$database_phase_only) {
|
||||||
if (isset($options['update']) || isset($options['force_update'])) {
|
|
||||||
|
|
||||||
// Recorded either side of the update so that "did anything change" is a commit
|
/*
|
||||||
// comparison rather than a match on git's wording, which varies by version and locale
|
* An install that was dropped in as a zip has no .git and cannot be updated this way,
|
||||||
$head_before = trim((string) exec("git rev-parse HEAD 2>/dev/null"));
|
* but its database still can, so the application phase is skipped rather than fatal.
|
||||||
|
* Checked on the filesystem rather than by asking git, so that a missing git binary
|
||||||
|
* still reports as the failure it is instead of looking like "not a checkout".
|
||||||
|
*/
|
||||||
|
if (!file_exists(dirname(__DIR__) . "/.git")) {
|
||||||
|
|
||||||
$output = [];
|
echo "This install is not a git checkout - skipping the application update.\n";
|
||||||
|
|
||||||
if (isset($options['force_update'])) {
|
} else {
|
||||||
// Perform a hard reset onto the tracked branch. This named origin/master outright
|
|
||||||
// until 26.08, which moved any install tracking another branch onto master.
|
$update_branch = getRepoBranch();
|
||||||
$remote_ref = escapeshellarg("origin/" . getRepoBranch());
|
$remote_ref = escapeshellarg("origin/" . $update_branch);
|
||||||
|
|
||||||
|
// Recorded either side of the update so that "did anything change" is a commit
|
||||||
|
// comparison rather than a match on git's wording, which varies by version and locale
|
||||||
|
$head_before = trim((string) exec("git rev-parse HEAD 2>/dev/null"));
|
||||||
|
|
||||||
|
// A hard reset throws local modifications away without ever saying what they were
|
||||||
|
$local_changes = [];
|
||||||
|
exec("git status --porcelain --untracked-files=no 2>/dev/null", $local_changes);
|
||||||
|
|
||||||
|
if (count($local_changes) > 0) {
|
||||||
|
echo "Discarding local changes to " . count($local_changes) . " tracked file(s):\n";
|
||||||
|
echo " " . implode("\n ", $local_changes) . "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "Updating the application to origin/$update_branch\n";
|
||||||
|
|
||||||
|
// A hard reset onto the tracked branch. This named origin/master outright until
|
||||||
|
// 26.08, which moved any install tracking another branch onto master.
|
||||||
|
$output = [];
|
||||||
exec("git fetch --all 2>&1", $output, $return_var);
|
exec("git fetch --all 2>&1", $output, $return_var);
|
||||||
|
|
||||||
if ($return_var === 0) {
|
if ($return_var === 0) {
|
||||||
exec("git reset --hard $remote_ref 2>&1", $output, $return_var);
|
exec("git reset --hard $remote_ref 2>&1", $output, $return_var);
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
// Perform a standard update (git pull)
|
|
||||||
exec("git pull 2>&1", $output, $return_var);
|
|
||||||
}
|
|
||||||
|
|
||||||
echo implode("\n", $output) . "\n";
|
echo implode("\n", $output) . "\n";
|
||||||
|
|
||||||
// git reports merge conflicts, a dirty working tree and network failures through its
|
// git reports a missing remote, a bad ref and network failures through its exit
|
||||||
// exit status. Without this the script printed the error, said "Update successful"
|
// status. Without this the script printed the error, said the update succeeded and
|
||||||
// and exited 0 - and would now go on to update the database as if all was well
|
// exited 0 - and would now go on to update the database as if all was well
|
||||||
if ($return_var !== 0) {
|
if ($return_var !== 0) {
|
||||||
fwrite(STDERR, "Error: the application update failed - see the output above.\n");
|
fwrite(STDERR, "Error: the application update failed - see the output above.\n");
|
||||||
if (isset($options['update_db'])) {
|
fwrite(STDERR, "The database has been left alone. Fix the problem and run this script again.\n");
|
||||||
fwrite(STDERR, "The database has been left alone. Fix the problem and run this script again, or run\nphp update_cli.php --update_db on its own to update only the database.\n");
|
exit(1);
|
||||||
}
|
}
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
$head_after = trim((string) exec("git rev-parse HEAD 2>/dev/null"));
|
$head_after = trim((string) exec("git rev-parse HEAD 2>/dev/null"));
|
||||||
$app_updated = $head_before !== '' && $head_after !== '' && $head_before !== $head_after;
|
$app_updated = $head_before !== '' && $head_after !== '' && $head_before !== $head_after;
|
||||||
|
|
||||||
if ($app_updated) {
|
echo $app_updated ? "Update successful\n" : "The application was already up to date.\n";
|
||||||
echo "Update successful\n";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If "update_db" is requested
|
/*
|
||||||
if (isset($options['update_db'])) {
|
* If the application was just updated, this process is still running the code it started
|
||||||
|
* with - config.php, functions.php and everything functions.php loaded are the pre-update
|
||||||
/*
|
* copies - while the migration runner and the migration files themselves get read fresh off
|
||||||
* If the application was just updated, this process is still running the code it
|
* the new tree. A migration calling a helper added in the same release then dies with
|
||||||
* started with - config.php, functions.php and everything functions.php loaded are
|
* "Call to undefined function".
|
||||||
* the pre-update copies - while the migration runner and the migration files
|
*
|
||||||
* themselves get read fresh off the new tree. A migration calling a helper added in
|
* So hand the database phase to a new process, which loads the new code from the start. Its
|
||||||
* the same release then dies with "Call to undefined function".
|
* output and its exit status are this script's. A process that did not update anything is
|
||||||
*
|
* already running the code on disk and needs none of this.
|
||||||
* So hand the database phase to a new process, which loads the new code from the
|
*/
|
||||||
* start. Its output and its exit status are this script's. A process that did not
|
if ($app_updated && PHP_BINARY !== '' && function_exists('passthru')) {
|
||||||
* update anything is already running the code on disk and needs none of this.
|
echo "Running the database update against the updated code\n";
|
||||||
*/
|
putenv("ITFLOW_UPDATE_PHASE=database");
|
||||||
if ($app_updated && PHP_BINARY !== '' && function_exists('passthru')) {
|
passthru(escapeshellarg(PHP_BINARY) . " " . escapeshellarg(__FILE__), $db_return_var);
|
||||||
echo "Running the database update against the updated code\n";
|
exit($db_return_var);
|
||||||
passthru(escapeshellarg(PHP_BINARY) . " " . escapeshellarg(__FILE__) . " --update_db", $db_return_var);
|
}
|
||||||
exit($db_return_var);
|
|
||||||
}
|
require_once "../includes/database_version.php";
|
||||||
|
|
||||||
require_once "../includes/database_version.php";
|
$latest_db_version = LATEST_DATABASE_VERSION;
|
||||||
|
|
||||||
$latest_db_version = LATEST_DATABASE_VERSION;
|
// Fetch the current version from the database
|
||||||
|
$result = mysqli_query($mysqli, "SELECT config_current_database_version FROM settings LIMIT 1");
|
||||||
// Fetch the current version from the database
|
$row = $result ? mysqli_fetch_assoc($result) : null;
|
||||||
$result = mysqli_query($mysqli, "SELECT config_current_database_version FROM settings LIMIT 1");
|
$old_db_version = trim((string) ($row['config_current_database_version'] ?? ''));
|
||||||
$row = $result ? mysqli_fetch_assoc($result) : null;
|
|
||||||
$old_db_version = trim((string) ($row['config_current_database_version'] ?? ''));
|
// No version to work from means no way to tell which migrations have already run, and
|
||||||
|
// starting from the first one against a partly built database breaks it
|
||||||
// No version to work from means no way to tell which migrations have already run, and
|
if ($old_db_version === '') {
|
||||||
// starting from the first one against a partly built database breaks it
|
fwrite(STDERR, "Error: could not read the current database version from the settings table.\n");
|
||||||
if ($old_db_version === '') {
|
fwrite(STDERR, "This install looks incomplete - finish it at /setup before updating the database.\n");
|
||||||
fwrite(STDERR, "Error: could not read the current database version from the settings table.\n");
|
exit(1);
|
||||||
fwrite(STDERR, "This install looks incomplete - finish it at /setup before updating the database.\n");
|
}
|
||||||
exit(1);
|
|
||||||
}
|
DEFINE("CURRENT_DATABASE_VERSION", $old_db_version);
|
||||||
|
|
||||||
DEFINE("CURRENT_DATABASE_VERSION", $old_db_version);
|
// Run the migrations - populates $database_updates_applied and $database_updates_error
|
||||||
|
require_once "../admin/database_updates.php";
|
||||||
// Run the migrations - populates $database_updates_applied and $database_updates_error
|
|
||||||
require_once "../admin/database_updates.php";
|
foreach ($database_updates_applied as $applied_version) {
|
||||||
|
echo "Applied database update $applied_version\n";
|
||||||
foreach ($database_updates_applied as $applied_version) {
|
}
|
||||||
echo "Applied database update $applied_version\n";
|
|
||||||
}
|
if ($database_updates_error) {
|
||||||
|
$stopped_at_version = count($database_updates_applied) > 0 ? end($database_updates_applied) : $old_db_version;
|
||||||
if ($database_updates_error) {
|
fwrite(STDERR, "Error: database update failed at $database_updates_error\n");
|
||||||
$stopped_at_version = count($database_updates_applied) > 0 ? end($database_updates_applied) : $old_db_version;
|
fwrite(STDERR, "The database is at version $stopped_at_version - re-running will resume at the failed update.\n");
|
||||||
fwrite(STDERR, "Error: database update failed at $database_updates_error\n");
|
exit(1);
|
||||||
fwrite(STDERR, "The database is at version $stopped_at_version - re-running will resume at the failed update.\n");
|
}
|
||||||
exit(1);
|
|
||||||
}
|
if (count($database_updates_applied) > 0) {
|
||||||
|
echo "Database updated from version $old_db_version to $latest_db_version.\n";
|
||||||
if (count($database_updates_applied) > 0) {
|
} else {
|
||||||
echo "Database updated from version $old_db_version to $latest_db_version.\n";
|
echo "Database is already at the latest version ($latest_db_version). No updates were applied.\n";
|
||||||
} else {
|
|
||||||
echo "Database is already at the latest version ($latest_db_version). No updates were applied.\n";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user