Feature: queued Updates which now eliminates the need for shell_exec from the webui, but if enabled you can still update from the webui the old way too

This commit is contained in:
johnnyq
2026-08-15 16:41:42 -04:00
parent d5ee0bff74
commit 4c4c172f59
8 changed files with 410 additions and 29 deletions

View File

@@ -306,8 +306,67 @@ function getRepoBranch(): string
return $branch === '' ? 'master' : $branch;
}
/*
* Whether this PHP can run external commands at all. ITFlow updates itself with git, so the
* update path needs exec() and shell_exec(); hosts that disable them - shared hosting, a
* hardened php.ini, an FPM pool locked down while the CLI is not - can still update through
* cron, which runs under a different php.ini and its own settings.
*
* function_exists() already reports a disabled function as missing. disable_functions is
* read as well because some hardening extensions leave the function defined and refuse the
* call instead, and a fatal on the Update page is a poor way to find that out.
*/
function shellCommandsAvailable(): bool
{
$disabled = array_map('trim', explode(',', (string) ini_get('disable_functions')));
foreach (['exec', 'shell_exec'] as $shell_function) {
if (!function_exists($shell_function) || in_array($shell_function, $disabled, true)) {
return false;
}
}
return true;
}
/*
* Whether a settings column exists yet.
*
* Maintenance > Update is the page that APPLIES database updates, so it has to keep working
* against a schema older than the code it is running - the window between the files being
* updated and the database catching up is exactly when somebody opens it. mysqli throws on
* an unknown column, so a page that reads a column newer than the oldest schema it might
* meet dies before it can render the button that fixes it.
*
* Anything else that runs before the migrations have caught up has the same problem, which
* is why this takes the column name rather than answering one question.
*/
function settingsColumnExists($mysqli, string $column): bool
{
$column = escapeSql($column);
$result = mysqli_query($mysqli, "SHOW COLUMNS FROM `settings` LIKE '$column'");
return $result && mysqli_num_rows($result) > 0;
}
function checkForUpdates() {
$updates = new stdClass();
// Nothing here can run without a shell. Reported as a failed check rather than left to
// fatal, because the nightly job calls this too and one host's php.ini must not take
// the whole cron cycle down with it.
if (!shellCommandsAvailable()) {
$updates->output = ["PHP on this server cannot run external commands, so ITFlow cannot check for updates."];
$updates->result = 127;
$updates->current_version = '';
$updates->latest_version = '';
$updates->update_message = "Cannot check for updates";
return $updates;
}
$remote_ref = escapeshellarg("origin/" . getRepoBranch());
// Fetch the latest code changes but don't apply them. stderr is merged in because git
@@ -324,7 +383,6 @@ function checkForUpdates() {
}
$updates = new stdClass();
$updates->output = $output;
$updates->result = $result;
$updates->current_version = $current_version;