mirror of
https://github.com/itflow-org/itflow
synced 2026-08-22 23:45:11 +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). |
|
| `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). |
|
| `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. |
|
| `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. |
|
| `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. |
|
| `includes/` (root) | **Shared** across portals: session/auth bootstrap, DB, layout partials. |
|
||||||
| `post/` (root) | **Shared** POST handlers (logout, misc). |
|
| `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.
|
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:
|
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>
|
||||||
</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
|
// Prevent overlapping runs of this script
|
||||||
$cron_lock_script = __FILE__;
|
$cron_lock_script = __FILE__;
|
||||||
require_once "../includes/cron_lock.php";
|
require_once "includes/cron_lock.php";
|
||||||
|
|
||||||
require_once "../config.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");
|
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
|
// dispatcher rather than being executed directly. Must be defined before anything else
|
||||||
// is loaded.
|
// is loaded.
|
||||||
define('ITFLOW_CRON_DISPATCHER', true);
|
define('ITFLOW_CRON_DISPATCHER', true);
|
||||||
|
|
||||||
require_once "../includes/cron_lock.php";
|
require_once "includes/cron_lock.php";
|
||||||
require_once "../config.php";
|
require_once "../config.php";
|
||||||
|
|
||||||
// Set Timezone
|
// Set Timezone
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ if (php_sapi_name() !== 'cli') {
|
|||||||
|
|
||||||
// Prevent overlapping runs of this script
|
// Prevent overlapping runs of this script
|
||||||
$cron_lock_script = __FILE__;
|
$cron_lock_script = __FILE__;
|
||||||
require_once "../includes/cron_lock.php";
|
require_once "includes/cron_lock.php";
|
||||||
|
|
||||||
require_once "../config.php";
|
require_once "../config.php";
|
||||||
|
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ function cronLockRelease($lock_handle): void
|
|||||||
if (!defined('ITFLOW_CRON_DISPATCHER')) {
|
if (!defined('ITFLOW_CRON_DISPATCHER')) {
|
||||||
|
|
||||||
if (!isset($cron_lock_script)) {
|
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);
|
$cron_lock_handle = cronLockAcquire($cron_lock_script);
|
||||||
@@ -9,7 +9,7 @@ if (php_sapi_name() !== 'cli') {
|
|||||||
|
|
||||||
// Prevent overlapping runs of this script
|
// Prevent overlapping runs of this script
|
||||||
$cron_lock_script = __FILE__;
|
$cron_lock_script = __FILE__;
|
||||||
require_once "../includes/cron_lock.php";
|
require_once "includes/cron_lock.php";
|
||||||
|
|
||||||
require_once "../config.php";
|
require_once "../config.php";
|
||||||
require_once "../includes/inc_set_timezone.php";
|
require_once "../includes/inc_set_timezone.php";
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ if (php_sapi_name() !== 'cli') {
|
|||||||
|
|
||||||
// Prevent overlapping runs of this script
|
// Prevent overlapping runs of this script
|
||||||
$cron_lock_script = __FILE__;
|
$cron_lock_script = __FILE__;
|
||||||
require_once "../includes/cron_lock.php";
|
require_once "includes/cron_lock.php";
|
||||||
|
|
||||||
require_once "../config.php";
|
require_once "../config.php";
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ if (php_sapi_name() !== 'cli') {
|
|||||||
|
|
||||||
// Prevent overlapping runs of this script
|
// Prevent overlapping runs of this script
|
||||||
$cron_lock_script = __FILE__;
|
$cron_lock_script = __FILE__;
|
||||||
require_once "../includes/cron_lock.php";
|
require_once "includes/cron_lock.php";
|
||||||
|
|
||||||
// Autoload (Webklex & any composer deps)
|
// Autoload (Webklex & any composer deps)
|
||||||
require_once "../libs/vendor/autoload.php";
|
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..");
|
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
|
// 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
|
// 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.
|
// 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
|
// Prevent overlapping runs of this script
|
||||||
$cron_lock_script = __FILE__;
|
$cron_lock_script = __FILE__;
|
||||||
require_once "../includes/cron_lock.php";
|
require_once "includes/cron_lock.php";
|
||||||
|
|
||||||
require_once "../config.php";
|
require_once "../config.php";
|
||||||
require_once "../includes/inc_set_timezone.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
|
* 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,
|
* 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.
|
* 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
|
function cronJobRegistry(): array
|
||||||
@@ -48,8 +56,8 @@ function cronJobRegistry(): array
|
|||||||
'label' => 'Domain Refresher',
|
'label' => 'Domain Refresher',
|
||||||
'script' => 'domain_refresher.php',
|
'script' => 'domain_refresher.php',
|
||||||
'description' => 'Refreshes WHOIS and DNS for the domain that was checked longest ago. One domain per run.',
|
'description' => 'Refreshes WHOIS and DNS for the domain that was checked longest ago. One domain per run.',
|
||||||
'schedule' => 'Interval',
|
'schedule' => 'Daily',
|
||||||
'interval_minutes' => 5,
|
'daily_at' => '04:00',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'name' => 'nightly_tasks',
|
'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>
|
<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>
|
<ul>
|
||||||
<li><a href="https://docs.itflow.org/backups">Setup backups</a></li>
|
<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>
|
<li>Star ITFlow on <a href="https://github.com/itflow-org/itflow">Github</a> :)</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user