Remove thr web updater this is now either queued from the web or ran from the shell to update

This commit is contained in:
johnnyq
2026-08-26 22:53:29 -04:00
parent 93fc6cd2ef
commit 51c925522c
3 changed files with 23 additions and 329 deletions

View File

@@ -3,10 +3,11 @@
defined('FROM_POST_HANDLER') || die("Direct file access is not allowed"); defined('FROM_POST_HANDLER') || die("Direct file access is not allowed");
/* /*
* Hands the update to cron instead of running it in this request. scripts/update_cli.php * Queueing is the ONLY way to update the application. There used to be an ?update handler
* replaces the files this request is executing from and then applies the migrations that * here that ran git pull (or a hard reset) inside this request; it is gone deliberately.
* came with them, which is not something to do half way through a page load - and on a host * scripts/update_cli.php replaces the files this request is executing from and then applies
* where PHP cannot run external commands it is the only way to update the application at all. * the migrations that came with them, which is not something to do half way through a page
* load, and a host where PHP cannot run external commands could never do it at all.
* *
* The row is written as well as the settings column: config_update_queued_at is what * The row is written as well as the settings column: config_update_queued_at is what
* cron/app_update.php acts on, and cron_job_run_now is what gets the dispatcher to look * cron/app_update.php acts on, and cron_job_run_now is what gets the dispatcher to look
@@ -36,301 +37,6 @@ if (isset($_GET['queue_update'])) {
} }
if (isset($_GET['update'])) {
validateCSRFToken();
enforceAdminPermission();
// Reached only from buttons this install cannot draw without a shell, so anything
// arriving here without one is a crafted request rather than somebody's mistake
if (!shellCommandsAvailable()) {
flashAlert("PHP on this server cannot run Git. Queue the update instead and cron will apply it.", 'error');
redirect();
}
// git fetch downloads the latest from the remote without merging or rebasing anything.
// The hard reset then throws away every local change and makes the working tree match
// the tracked branch exactly.
//
// That reset used to name origin/master outright, so a force update on an install
// tracking any other branch silently moved it onto master and discarded the code it was
// actually running.
if (isset($_GET['force_update']) == 1) {
$remote_ref = escapeshellarg("origin/" . getRepoBranch());
exec("git fetch --all");
exec("git reset --hard $remote_ref");
} else {
exec("git pull");
}
//header("Location: post.php?update_db");
// Send Telemetry if enabled during update
if ($config_telemetry > 0 OR $config_telemetry = 2) {
$sql = mysqli_query($mysqli,"SELECT * FROM companies WHERE company_id = 1");
$row = mysqli_fetch_assoc($sql);
$company_name = escapeSql($row['company_name']);
$website = escapeSql($row['company_website']);
$city = escapeSql($row['company_city']);
$state = escapeSql($row['company_state']);
$country = escapeSql($row['company_country']);
$currency = escapeSql($row['company_currency']);
$current_version = exec("git rev-parse HEAD");
// Client Count
$row = mysqli_fetch_assoc(mysqli_query($mysqli,"SELECT COUNT('client_id') AS num FROM clients"));
$client_count = $row['num'];
// Ticket Count
$row = mysqli_fetch_assoc(mysqli_query($mysqli,"SELECT COUNT('recurring_id') AS num FROM tickets"));
$ticket_count = $row['num'];
// Recurring Ticket Count
$row = mysqli_fetch_assoc(mysqli_query($mysqli, "SELECT COUNT('recurring_ticket_id') AS num FROM recurring_tickets"));
$recurring_ticket_count = $row['num'];
// Calendar Event Count
$row = mysqli_fetch_assoc(mysqli_query($mysqli,"SELECT COUNT('event_id') AS num FROM calendar_events"));
$calendar_event_count = $row['num'];
// Quote Count
$row = mysqli_fetch_assoc(mysqli_query($mysqli,"SELECT COUNT('quote_id') AS num FROM quotes"));
$quote_count = $row['num'];
// Invoice Count
$row = mysqli_fetch_assoc(mysqli_query($mysqli,"SELECT COUNT('invoice_id') AS num FROM invoices"));
$invoice_count = $row['num'];
// Revenue Count
$row = mysqli_fetch_assoc(mysqli_query($mysqli,"SELECT COUNT('revenue_id') AS num FROM revenues"));
$revenue_count = $row['num'];
// Recurring Invoice Count
$row = mysqli_fetch_assoc(mysqli_query($mysqli,"SELECT COUNT('recurring_invoice_id') AS num FROM recurring_invoices"));
$recurring_invoice_count = $row['num'];
// Account Count
$row = mysqli_fetch_assoc(mysqli_query($mysqli,"SELECT COUNT('account_id') AS num FROM accounts"));
$account_count = $row['num'];
// Tax Count
$row = mysqli_fetch_assoc(mysqli_query($mysqli,"SELECT COUNT('tax_id') AS num FROM taxes"));
$tax_count = $row['num'];
// Product Count
$row = mysqli_fetch_assoc(mysqli_query($mysqli,"SELECT COUNT('product_id') AS num FROM products"));
$product_count = $row['num'];
// Payment Count
$row = mysqli_fetch_assoc(mysqli_query($mysqli,"SELECT COUNT('payment_id') AS num FROM payments WHERE payment_invoice_id > 0"));
$payment_count = $row['num'];
// Company Vendor Count
$row = mysqli_fetch_assoc(mysqli_query($mysqli,"SELECT COUNT('vendor_id') AS num FROM vendors WHERE vendor_client_id = 0"));
$company_vendor_count = $row['num'];
// Expense Count
$row = mysqli_fetch_assoc(mysqli_query($mysqli,"SELECT COUNT('expense_id') AS num FROM expenses WHERE expense_vendor_id > 0"));
$expense_count = $row['num'];
// Trip Count
$row = mysqli_fetch_assoc(mysqli_query($mysqli,"SELECT COUNT('trip_id') AS num FROM trips"));
$trip_count = $row['num'];
// Transfer Count
$row = mysqli_fetch_assoc(mysqli_query($mysqli,"SELECT COUNT('transfer_id') AS num FROM transfers"));
$transfer_count = $row['num'];
// Contact Count
$row = mysqli_fetch_assoc(mysqli_query($mysqli,"SELECT COUNT('contact_id') AS num FROM contacts"));
$contact_count = $row['num'];
// Location Count
$row = mysqli_fetch_assoc(mysqli_query($mysqli,"SELECT COUNT('location_id') AS num FROM locations"));
$location_count = $row['num'];
// Asset Count
$row = mysqli_fetch_assoc(mysqli_query($mysqli,"SELECT COUNT('asset_id') AS num FROM assets"));
$asset_count = $row['num'];
// Software Count
$row = mysqli_fetch_assoc(mysqli_query($mysqli,"SELECT COUNT('software_id') AS num FROM software"));
$software_count = $row['num'];
// Software Template Count
$row = mysqli_fetch_assoc(mysqli_query($mysqli,"SELECT COUNT('software_template_id') AS num FROM software_templates"));
$software_template_count = $row['num'];
// Password Count
$row = mysqli_fetch_assoc(mysqli_query($mysqli,"SELECT COUNT('credential_id') AS num FROM credentials"));
$credential_count = $row['num'];
// Network Count
$row = mysqli_fetch_assoc(mysqli_query($mysqli,"SELECT COUNT('network_id') AS num FROM networks"));
$network_count = $row['num'];
// Certificate Count
$row = mysqli_fetch_assoc(mysqli_query($mysqli,"SELECT COUNT('certificate_id') AS num FROM certificates"));
$certificate_count = $row['num'];
// Domain Count
$row = mysqli_fetch_assoc(mysqli_query($mysqli,"SELECT COUNT('domain_id') AS num FROM domains"));
$domain_count = $row['num'];
// Service Count
$row = mysqli_fetch_assoc(mysqli_query($mysqli,"SELECT COUNT('service_id') AS num FROM services"));
$service_count = $row['num'];
// Client Vendor Count
$row = mysqli_fetch_assoc(mysqli_query($mysqli,"SELECT COUNT('vendor_id') AS num FROM vendors WHERE vendor_client_id > 0"));
$client_vendor_count = $row['num'];
// Vendor Template Count
$row = mysqli_fetch_assoc(mysqli_query($mysqli,"SELECT COUNT('vendor_template_id') AS num FROM vendor_templates"));
$vendor_template_count = $row['num'];
// File Count
$row = mysqli_fetch_assoc(mysqli_query($mysqli,"SELECT COUNT('file_id') AS num FROM files"));
$file_count = $row['num'];
// Document Count
$row = mysqli_fetch_assoc(mysqli_query($mysqli,"SELECT COUNT('document_id') AS num FROM documents"));
$document_count = $row['num'];
// Document Template Count
$row = mysqli_fetch_assoc(mysqli_query($mysqli,"SELECT COUNT('document_template_id') AS num FROM document_templates"));
$document_template_count = $row['num'];
// Shared Item Count
$row = mysqli_fetch_assoc(mysqli_query($mysqli,"SELECT COUNT('item_id') AS num FROM shared_items"));
$shared_item_count = $row['num'];
// Company Count
$row = mysqli_fetch_assoc(mysqli_query($mysqli,"SELECT COUNT('company_id') AS num FROM companies"));
$company_count = $row['num'];
// User Count
$row = mysqli_fetch_assoc(mysqli_query($mysqli,"SELECT COUNT('user_id') AS num FROM users"));
$user_count = $row['num'];
// Category Expense Count
$row = mysqli_fetch_assoc(mysqli_query($mysqli,"SELECT COUNT('category_id') AS num FROM categories WHERE category_type = 'Expense'"));
$category_expense_count = $row['num'];
// Category Income Count
$row = mysqli_fetch_assoc(mysqli_query($mysqli,"SELECT COUNT('category_id') AS num FROM categories WHERE category_type = 'Income'"));
$category_income_count = $row['num'];
// Category Referral Count
$row = mysqli_fetch_assoc(mysqli_query($mysqli,"SELECT COUNT('category_id') AS num FROM categories WHERE category_type = 'Referral'"));
$category_referral_count = $row['num'];
// Category Payment Method Count
$row = mysqli_fetch_assoc(mysqli_query($mysqli,"SELECT COUNT('category_id') AS num FROM categories WHERE category_type = 'Payment Method'"));
$category_payment_method_count = $row['num'];
// Tag Count
$row = mysqli_fetch_assoc(mysqli_query($mysqli,"SELECT COUNT('tag_id') AS num FROM tags"));
$tag_count = $row['num'];
// API Key Count
$row = mysqli_fetch_assoc(mysqli_query($mysqli,"SELECT COUNT('api_key_id') AS num FROM api_keys"));
$api_key_count = $row['num'];
// Log Count
$row = mysqli_fetch_assoc(mysqli_query($mysqli,"SELECT COUNT('log_id') AS num FROM logs"));
$log_count = $row['num'];
$postdata = http_build_query(
array(
'installation_id' => "$installation_id",
'version' => "$current_version",
'company_name' => "$company_name",
'website' => "$website",
'city' => "$city",
'state' => "$state",
'country' => "$country",
'currency' => "$currency",
'comments' => "$comments",
'client_count' => $client_count,
'ticket_count' => $ticket_count,
'recurring_ticket_count' => $recurring_ticket_count,
'calendar_event_count' => $calendar_event_count,
'quote_count' => $quote_count,
'invoice_count' => $invoice_count,
'revenue_count' => $revenue_count,
'recurring_invoice_count' => $recurring_invoice_count,
'account_count' => $account_count,
'tax_count' => $tax_count,
'product_count' => $product_count,
'payment_count' => $payment_count,
'company_vendor_count' => $company_vendor_count,
'expense_count' => $expense_count,
'trip_count' => $trip_count,
'transfer_count' => $transfer_count,
'contact_count' => $contact_count,
'location_count' => $location_count,
'asset_count' => $asset_count,
'software_count' => $software_count,
'software_template_count' => $software_template_count,
'credential_count' => $credential_count,
'network_count' => $network_count,
'certificate_count' => $certificate_count,
'domain_count' => $domain_count,
'service_count' => $service_count,
'client_vendor_count' => $client_vendor_count,
'vendor_template_count' => $vendor_template_count,
'file_count' => $file_count,
'document_count' => $document_count,
'document_template_count' => $document_template_count,
'shared_item_count' => $shared_item_count,
'company_count' => $company_count,
'user_count' => $user_count,
'category_expense_count' => $category_expense_count,
'category_income_count' => $category_income_count,
'category_referral_count' => $category_referral_count,
'category_payment_method_count' => $category_payment_method_count,
'tag_count' => $tag_count,
'api_key_count' => $api_key_count,
'log_count' => $log_count,
'config_theme' => "$config_theme",
'config_enable_cron' => $config_enable_cron,
'config_ticket_email_parse' => $config_ticket_email_parse,
'config_module_enable_itdoc' => $config_module_enable_itdoc,
'config_module_enable_ticketing' => $config_module_enable_ticketing,
'config_module_enable_accounting' => $config_module_enable_accounting,
'config_telemetry' => $config_telemetry,
'collection_method' => 4
)
);
$opts = array('http' =>
array(
'method' => 'POST',
'header' => 'Content-type: application/x-www-form-urlencoded',
'content' => $postdata
)
);
$context = stream_context_create($opts);
$result = file_get_contents('https://telemetry.itflow.org', false, $context);
}
logAudit("App", "Update", "$session_name ran updates");
flashAlert("Update successful");
sleep(1);
redirect();
}
if (isset($_GET['update_db'])) { if (isset($_GET['update_db'])) {
validateCSRFToken(); validateCSRFToken();

View File

@@ -7,11 +7,12 @@ $repo_branch = getRepoBranch();
$remote_ref = escapeshellarg("origin/$repo_branch"); $remote_ref = escapeshellarg("origin/$repo_branch");
/* /*
* Everything git can tell us needs a shell. Where PHP cannot run one - shared hosting, a * The web server never applies an application update - it queues one and cron runs it. The
* hardened php.ini, an FPM pool locked down while the command line is not - the page cannot * shell gate below therefore only decides whether this page can READ the git remote to say
* say whether an update is waiting and the web server cannot apply one either, so the * an update is waiting; where PHP cannot run one - shared hosting, a hardened php.ini, an
* application half of this page is replaced by the queue, which cron carries out. The * FPM pool locked down while the command line is not - the page offers the queue blind,
* database half is plain PHP and works everywhere. * which is harmless when there is nothing to fetch. The database half is plain PHP and
* works everywhere.
*/ */
$shell_available = shellCommandsAvailable(); $shell_available = shellCommandsAvailable();
@@ -109,10 +110,10 @@ $app_update_available = !empty($pending_commits);
<?php if (!$shell_available) { ?> <?php if (!$shell_available) { ?>
<div class="alert alert-info"> <div class="alert alert-info">
<h5><i class="fas fa-fw fa-info-circle me-2"></i>This server cannot run Git from the web</h5> <h5><i class="fas fa-fw fa-info-circle me-2"></i>This server cannot check for updates</h5>
PHP here has <code>exec</code> and <code>shell_exec</code> disabled, so this page can neither check for PHP here has <code>exec</code> and <code>shell_exec</code> disabled, so this page cannot read the Git
application updates nor apply one. Queue an update instead: cron runs it from the command line, which remote to tell you whether one is waiting. Queueing still works - cron runs the update from the command
is usually not restricted the same way. Database updates are plain PHP and are unaffected. line, which is usually not restricted the same way. Database updates are plain PHP and are unaffected.
</div> </div>
<?php } ?> <?php } ?>
@@ -194,8 +195,8 @@ $app_update_available = !empty($pending_commits);
<?php if ($app_update_available && $db_update_available) { ?> <?php if ($app_update_available && $db_update_available) { ?>
<p class="text-muted"> <p class="text-muted">
<i class="fas fa-fw fa-info-circle me-1"></i>Both are pending. Update the application files first - <i class="fas fa-fw fa-info-circle me-1"></i>Both are pending. Queueing the update covers both -
they bring the database migrations that the second step then applies. cron applies the files and then the migrations they bring, in that order, in one run.
</p> </p>
<?php } ?> <?php } ?>
@@ -214,28 +215,14 @@ $app_update_available = !empty($pending_commits);
</p> </p>
<?php } ?> <?php } ?>
<?php if ($shell_available) { ?>
<a class="btn btn-primary confirm-link" href="post.php?update&csrf_token=<?= $_SESSION['csrf_token'] ?>">
<i class="fas fa-fw fa-download me-2"></i>Update App
</a>
<a class="btn btn-outline-danger ms-2 confirm-link" href="post.php?update&force_update=1&csrf_token=<?= $_SESSION['csrf_token'] ?>">
<i class="fas fa-fw fa-hammer me-2"></i>Force Update
</a>
<?php } ?>
<?php if ($update_queue_available) { ?> <?php if ($update_queue_available) { ?>
<a class="btn btn-dark <?= $shell_available ? 'ms-2' : '' ?> confirm-link" href="post.php?queue_update&csrf_token=<?= $_SESSION['csrf_token'] ?>"> <a class="btn btn-primary confirm-link" href="post.php?queue_update&csrf_token=<?= $_SESSION['csrf_token'] ?>">
<i class="fas fa-fw fa-clock me-2"></i>Queue Update <i class="fas fa-fw fa-clock me-2"></i>Queue Update
</a> </a>
<?php } ?> <?php } ?>
<p class="text-muted mt-2 mb-0"> <p class="text-muted mt-2 mb-0">
<small> <small>
<?php if ($shell_available) { ?>
Update App runs <code>git pull</code>. Force Update discards every local change and resets
the files to <code><?= escapeHtml("origin/$repo_branch") ?></code> - use it only when a
normal update will not apply. Both run inside this request and stop if PHP runs out of time.
<?php } ?>
<?php if ($update_queue_available) { ?> <?php if ($update_queue_available) { ?>
Queue Update hands the job to cron, which runs Queue Update hands the job to cron, which runs
<code>scripts/update_cli.php</code> in its own process - it updates the files and then the <code>scripts/update_cli.php</code> in its own process - it updates the files and then the

View File

@@ -336,10 +336,11 @@ function getRepoBranch(): string
} }
/* /*
* Whether this PHP can run external commands at all. ITFlow updates itself with git, so the * Whether this PHP can run external commands at all. Applying an update is cron's job now, so
* update path needs exec() and shell_exec(); hosts that disable them - shared hosting, a * the only thing the web tier still wants a shell for is READING git - checkForUpdates() and
* hardened php.ini, an FPM pool locked down while the CLI is not - can still update through * the pending-commit list on Maintenance > Update. Hosts that disable exec()/shell_exec() -
* cron, which runs under a different php.ini and its own settings. * shared hosting, a hardened php.ini, an FPM pool locked down while the CLI is not - lose the
* "an update is waiting" readout, not the ability to update.
* *
* function_exists() already reports a disabled function as missing. disable_functions is * 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 * read as well because some hardening extensions leave the function defined and refuse the