mirror of
https://github.com/itflow-org/itflow
synced 2026-08-04 22:57:14 +00:00
Fix Cron Schedule and new directory includes created inside cron for the cron lock
This commit is contained in:
@@ -24,7 +24,7 @@ There is no `composer install` or `npm install` step. All third-party libraries
|
||||
| `client/` | The logged-in client portal (contacts of a client). |
|
||||
| `guest/` | Unauthenticated flows via URL keys (view/pay invoice, view quote/ticket, view shared credentials/files/documents). |
|
||||
| `api/v1/` | Key-authenticated JSON CRUD API, one directory per module. |
|
||||
| `cron/` | Scheduled jobs. `cron.php` is the dispatcher and the only entry in the crontab; everything else in the directory is a job it runs. See [Cron](#cron). |
|
||||
| `cron/` | Scheduled jobs. `cron.php` is the dispatcher and the only entry in the crontab; everything else in the directory is a job it runs, with `cron/includes/` for the parts only cron uses. See [Cron](#cron). |
|
||||
| `functions.php` + `functions/` | Shared helper functions, split into topical files (`sanitize.php`, `auth.php`, `logging.php`, …) loaded by `functions.php`. New helpers go in the topical file that matches their concern. |
|
||||
| `includes/` (root) | **Shared** across portals: session/auth bootstrap, DB, layout partials. |
|
||||
| `post/` (root) | **Shared** POST handlers (logout, misc). |
|
||||
@@ -91,7 +91,7 @@ That registry is the only thing that decides **which** scripts can run, and the
|
||||
|
||||
Run Now in the admin UI does not execute anything in the web request: these scripts are CLI-only and some take minutes, so the button sets `cron_job_run_now` and the next dispatch picks it up, through the same lock and claim as a scheduled run.
|
||||
|
||||
Due-ness is recorded in the `cron_jobs` table rather than matched against the clock, so a job whose minute was missed — machine down, previous run still going — runs at the next opportunity instead of being skipped for the day. A job is claimed *before* it runs, not after: a run that dies half way through is not repeated, which matters because `nightly_tasks.php` generates invoices and charges cards. Each job is also locked individually for the length of its own run (`includes/cron_lock.php`), so a long or hung job holds up only itself — the next minute's dispatch picks up everything else in a second process.
|
||||
Due-ness is recorded in the `cron_jobs` table rather than matched against the clock, so a job whose minute was missed — machine down, previous run still going — runs at the next opportunity instead of being skipped for the day. A job is claimed *before* it runs, not after: a run that dies half way through is not repeated, which matters because `nightly_tasks.php` generates invoices and charges cards. Each job is also locked individually for the length of its own run (`cron/includes/cron_lock.php`), so a long or hung job holds up only itself — the next minute's dispatch picks up everything else in a second process.
|
||||
|
||||
Because the jobs share one PHP process, job code has three rules:
|
||||
|
||||
|
||||
@@ -188,4 +188,4 @@ while ($job_row = mysqli_fetch_assoc($sql)) {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/footer.php"; ?>
|
||||
<?php require_once "../includes/footer.php"; ?>
|
||||
|
||||
38
admin/database_updates/2.6.2.php
Normal file
38
admin/database_updates/2.6.2.php
Normal file
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* ITFlow - Database update to version 2.6.2 (from 2.6.1)
|
||||
* Included by admin/database_updates.php - do not access directly
|
||||
*/
|
||||
|
||||
defined('FROM_DB_UPDATER') || die("Direct file access is not allowed");
|
||||
|
||||
// Two things, both about rows that already existed before 2.6.1 ran.
|
||||
//
|
||||
// 2.6.1 added the schedule columns but could not set them, so those rows took the column
|
||||
// defaults - every minute - which is wrong for three jobs and harmful for the nightly run,
|
||||
// whose overdue invoice reminders re-send on every pass. The registry in
|
||||
// includes/cron_jobs.php only ever seeds a row it is creating, so it cannot fix them.
|
||||
//
|
||||
// The domain refresher also moves onto the nightly schedule here, which is why it is
|
||||
// matched at either of the intervals it may be sitting on: 1 from the column default, or 5
|
||||
// if it was already put back by hand.
|
||||
//
|
||||
// Only rows still carrying one of those shipped values are touched, so a schedule someone
|
||||
// has deliberately changed is left alone. Deliberately hardcoded rather than read from the
|
||||
// registry: a migration has to keep meaning the same thing years from now, whatever that
|
||||
// file says by then.
|
||||
mysqli_query($mysqli, "UPDATE `cron_jobs`
|
||||
SET `cron_job_schedule` = 'Daily', `cron_job_daily_at` = '03:00:00'
|
||||
WHERE `cron_job_name` = 'nightly_tasks'
|
||||
AND `cron_job_schedule` = 'Interval' AND `cron_job_interval_minutes` = 1");
|
||||
|
||||
mysqli_query($mysqli, "UPDATE `cron_jobs`
|
||||
SET `cron_job_schedule` = 'Daily', `cron_job_daily_at` = '03:30:00'
|
||||
WHERE `cron_job_name` = 'certificate_refresher'
|
||||
AND `cron_job_schedule` = 'Interval' AND `cron_job_interval_minutes` = 1");
|
||||
|
||||
mysqli_query($mysqli, "UPDATE `cron_jobs`
|
||||
SET `cron_job_schedule` = 'Daily', `cron_job_daily_at` = '04:00:00'
|
||||
WHERE `cron_job_name` = 'domain_refresher'
|
||||
AND `cron_job_schedule` = 'Interval' AND `cron_job_interval_minutes` IN (1, 5)");
|
||||
@@ -10,7 +10,7 @@ if (php_sapi_name() !== 'cli') {
|
||||
|
||||
// Prevent overlapping runs of this script
|
||||
$cron_lock_script = __FILE__;
|
||||
require_once "../includes/cron_lock.php";
|
||||
require_once "includes/cron_lock.php";
|
||||
|
||||
require_once "../config.php";
|
||||
|
||||
|
||||
@@ -44,12 +44,12 @@ if (php_sapi_name() !== 'cli') {
|
||||
die("This script must be run from the command line.\n");
|
||||
}
|
||||
|
||||
// Tells includes/cron_lock.php and the jobs themselves that they are running under the
|
||||
// Tells cron/includes/cron_lock.php and the jobs themselves that they are running under the
|
||||
// dispatcher rather than being executed directly. Must be defined before anything else
|
||||
// is loaded.
|
||||
define('ITFLOW_CRON_DISPATCHER', true);
|
||||
|
||||
require_once "../includes/cron_lock.php";
|
||||
require_once "includes/cron_lock.php";
|
||||
require_once "../config.php";
|
||||
|
||||
// Set Timezone
|
||||
|
||||
@@ -10,7 +10,7 @@ if (php_sapi_name() !== 'cli') {
|
||||
|
||||
// Prevent overlapping runs of this script
|
||||
$cron_lock_script = __FILE__;
|
||||
require_once "../includes/cron_lock.php";
|
||||
require_once "includes/cron_lock.php";
|
||||
|
||||
require_once "../config.php";
|
||||
|
||||
|
||||
@@ -108,7 +108,7 @@ function cronLockRelease($lock_handle): void
|
||||
if (!defined('ITFLOW_CRON_DISPATCHER')) {
|
||||
|
||||
if (!isset($cron_lock_script)) {
|
||||
die("Cron scripts must set \$cron_lock_script = __FILE__ before requiring includes/cron_lock.php.\n");
|
||||
die("Cron scripts must set \$cron_lock_script = __FILE__ before requiring cron/includes/cron_lock.php.\n");
|
||||
}
|
||||
|
||||
$cron_lock_handle = cronLockAcquire($cron_lock_script);
|
||||
@@ -9,7 +9,7 @@ if (php_sapi_name() !== 'cli') {
|
||||
|
||||
// Prevent overlapping runs of this script
|
||||
$cron_lock_script = __FILE__;
|
||||
require_once "../includes/cron_lock.php";
|
||||
require_once "includes/cron_lock.php";
|
||||
|
||||
require_once "../config.php";
|
||||
require_once "../includes/inc_set_timezone.php";
|
||||
|
||||
@@ -18,7 +18,7 @@ if (php_sapi_name() !== 'cli') {
|
||||
|
||||
// Prevent overlapping runs of this script
|
||||
$cron_lock_script = __FILE__;
|
||||
require_once "../includes/cron_lock.php";
|
||||
require_once "includes/cron_lock.php";
|
||||
|
||||
require_once "../config.php";
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ if (php_sapi_name() !== 'cli') {
|
||||
|
||||
// Prevent overlapping runs of this script
|
||||
$cron_lock_script = __FILE__;
|
||||
require_once "../includes/cron_lock.php";
|
||||
require_once "includes/cron_lock.php";
|
||||
|
||||
// Autoload (Webklex & any composer deps)
|
||||
require_once "../libs/vendor/autoload.php";
|
||||
@@ -48,7 +48,7 @@ if ($config_ticket_email_parse == 0) {
|
||||
cronJobStop("Email Parser: Feature is not enabled - check Settings > Ticketing > Email-to-ticket parsing. See https://docs.itflow.org/ticket_email_parse -- Quitting..");
|
||||
}
|
||||
|
||||
// Overlapping runs are prevented by includes/cron_lock.php. This script used to keep a
|
||||
// Overlapping runs are prevented by cron/includes/cron_lock.php. This script used to keep a
|
||||
// lock file of its own alongside that one, which needed a five minute age heuristic to
|
||||
// recover from a killed run and could only end itself with exit() - fatal to a dispatched
|
||||
// job. flock covers the same ground and the kernel drops it however the process ends.
|
||||
|
||||
@@ -9,7 +9,7 @@ if (php_sapi_name() !== 'cli') {
|
||||
|
||||
// Prevent overlapping runs of this script
|
||||
$cron_lock_script = __FILE__;
|
||||
require_once "../includes/cron_lock.php";
|
||||
require_once "includes/cron_lock.php";
|
||||
|
||||
require_once "../config.php";
|
||||
require_once "../includes/inc_set_timezone.php";
|
||||
|
||||
@@ -14,6 +14,14 @@
|
||||
* This file is the only thing that decides which scripts can be run. The database holds
|
||||
* when and whether, never what - a row naming a script that is not listed here is ignored,
|
||||
* so nothing that reaches the database can point the dispatcher at an arbitrary file.
|
||||
*
|
||||
* Loaded from both sides: cron/cron.php requires it on the command line under system cron,
|
||||
* and Settings > Cron requires it in a web request. Nothing in here may touch $_SERVER,
|
||||
* $_SESSION or any other superglobal - there is no DOCUMENT_ROOT, no session and no request
|
||||
* when cron runs it.
|
||||
*
|
||||
* That shared use is why this sits here rather than in cron/includes/ with the lock, which
|
||||
* only cron loads: the admin pages would otherwise be reaching into the cron directory.
|
||||
*/
|
||||
|
||||
function cronJobRegistry(): array
|
||||
@@ -48,8 +56,8 @@ function cronJobRegistry(): array
|
||||
'label' => 'Domain Refresher',
|
||||
'script' => 'domain_refresher.php',
|
||||
'description' => 'Refreshes WHOIS and DNS for the domain that was checked longest ago. One domain per run.',
|
||||
'schedule' => 'Interval',
|
||||
'interval_minutes' => 5,
|
||||
'schedule' => 'Daily',
|
||||
'daily_at' => '04:00',
|
||||
],
|
||||
[
|
||||
'name' => 'nightly_tasks',
|
||||
|
||||
@@ -1555,7 +1555,13 @@ if (isset($_POST['add_telemetry'])) {
|
||||
<p>A few <a href="https://docs.itflow.org/installation#post-installation_essential_housekeeping">housekeeping steps</a> are required to ensure everything runs smoothly, namely:</p>
|
||||
<ul>
|
||||
<li><a href="https://docs.itflow.org/backups">Setup backups</a></li>
|
||||
<li><a href="https://docs.itflow.org/cron">Setup cron</a> *If Installing via script cron jobs will be automatically setup for you.</li>
|
||||
<li>
|
||||
<a href="https://docs.itflow.org/cron">Setup cron</a> - ITFlow needs one entry, which runs
|
||||
every job on the schedule set in Settings > Cron. Add it to the crontab of the user that
|
||||
owns the ITFlow files:
|
||||
<pre class="bg-dark text-white p-2 mt-2"><?= escapeHtml("* * * * * php " . dirname(__DIR__) . "/cron/cron.php >/dev/null") ?></pre>
|
||||
*If installing via the script this is set up for you.
|
||||
</li>
|
||||
<li>Star ITFlow on <a href="https://github.com/itflow-org/itflow">Github</a> :)</li>
|
||||
</ul>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user