diff --git a/admin/post/update.php b/admin/post/update.php index d999dd1c9..75ed60955 100644 --- a/admin/post/update.php +++ b/admin/post/update.php @@ -3,10 +3,11 @@ 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 - * replaces the files this request is executing from and then applies the migrations that - * came with them, which is not something to do half way through a page load - and on a host - * where PHP cannot run external commands it is the only way to update the application at all. + * Queueing is the ONLY way to update the application. There used to be an ?update handler + * here that ran git pull (or a hard reset) inside this request; it is gone deliberately. + * scripts/update_cli.php replaces the files this request is executing from and then applies + * 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 * 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'])) { validateCSRFToken(); diff --git a/admin/update.php b/admin/update.php index 519475660..47987aca3 100644 --- a/admin/update.php +++ b/admin/update.php @@ -7,11 +7,12 @@ $repo_branch = getRepoBranch(); $remote_ref = escapeshellarg("origin/$repo_branch"); /* - * Everything git can tell us needs a shell. Where PHP cannot run one - shared hosting, a - * hardened php.ini, an FPM pool locked down while the command line is not - the page cannot - * say whether an update is waiting and the web server cannot apply one either, so the - * application half of this page is replaced by the queue, which cron carries out. The - * database half is plain PHP and works everywhere. + * The web server never applies an application update - it queues one and cron runs it. The + * shell gate below therefore only decides whether this page can READ the git remote to say + * an update is waiting; where PHP cannot run one - shared hosting, a hardened php.ini, an + * FPM pool locked down while the command line is not - the page offers the queue blind, + * which is harmless when there is nothing to fetch. The database half is plain PHP and + * works everywhere. */ $shell_available = shellCommandsAvailable(); @@ -109,10 +110,10 @@ $app_update_available = !empty($pending_commits);
exec and shell_exec disabled, so this page can neither check for
- application updates nor apply one. Queue an update instead: cron runs it from the command line, which
- is usually not restricted the same way. Database updates are plain PHP and are unaffected.
+ exec and shell_exec disabled, so this page cannot read the Git
+ remote to tell you whether one is waiting. Queueing still works - cron runs the update from the command
+ line, which is usually not restricted the same way. Database updates are plain PHP and are unaffected.
- Both are pending. Update the application files first - - they bring the database migrations that the second step then applies. + Both are pending. Queueing the update covers both - + cron applies the files and then the migrations they bring, in that order, in one run.
@@ -214,28 +215,14 @@ $app_update_available = !empty($pending_commits); - - - Update App - - - Force Update - - - - + Queue Update
-
- Update App runs git pull. Force Update discards every local change and resets
- the files to = escapeHtml("origin/$repo_branch") ?> - use it only when a
- normal update will not apply. Both run inside this request and stop if PHP runs out of time.
-
Queue Update hands the job to cron, which runs
scripts/update_cli.php in its own process - it updates the files and then the
diff --git a/functions/app.php b/functions/app.php
index 239fa1127..851b1db96 100644
--- a/functions/app.php
+++ b/functions/app.php
@@ -336,10 +336,11 @@ function getRepoBranch(): string
}
/*
- * 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.
+ * Whether this PHP can run external commands at all. Applying an update is cron's job now, so
+ * the only thing the web tier still wants a shell for is READING git - checkForUpdates() and
+ * the pending-commit list on Maintenance > Update. Hosts that disable exec()/shell_exec() -
+ * 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
* read as well because some hardening extensions leave the function defined and refuse the