<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Cronjob on wt-modules</title><link>https://bschwede.codeberg.page/wt-modules/en/docs/cronjob/</link><description>Recent content in Cronjob on wt-modules</description><generator>Hugo</generator><language>en</language><atom:link href="https://bschwede.codeberg.page/wt-modules/en/docs/cronjob/index.xml" rel="self" type="application/rss+xml"/><item><title>Usage</title><link>https://bschwede.codeberg.page/wt-modules/en/docs/cronjob/usage/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://bschwede.codeberg.page/wt-modules/en/docs/cronjob/usage/</guid><description>&lt;h2 id="creating-a-job"&gt;Creating a job&lt;a class="td-heading-self-link" href="#creating-a-job" aria-label="Heading self-link"&gt;&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;&lt;code&gt;Control panel → Cron Job Scheduler → New Job&lt;/code&gt;:&lt;/p&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th style="text-align: left"&gt;Field&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th style="text-align: left"&gt;Meaning&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: left"&gt;&lt;strong&gt;Name (slug)&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: left"&gt;technical identifier, &lt;code&gt;a-z 0-9 _ -&lt;/code&gt;. &lt;strong&gt;Read-only for &lt;a href="https://bschwede.codeberg.page/wt-modules/en/docs/cronjob/howto-use/self-registration/"&gt;module-offered jobs&lt;/a&gt;&lt;/strong&gt; (their &lt;code&gt;&amp;lt;module&amp;gt;:&amp;lt;name&amp;gt;&lt;/code&gt; key is fixed); editable for jobs you create yourself.&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: left"&gt;&lt;strong&gt;Title&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: left"&gt;human readable name&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: left"&gt;&lt;strong&gt;Triggers&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: left"&gt;one or more &lt;strong&gt;time triggers&lt;/strong&gt; (cron schedules) and/or &lt;strong&gt;event triggers&lt;/strong&gt; — the job fires when &lt;em&gt;any&lt;/em&gt; trigger fires. At least one is required.&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: left"&gt;&lt;strong&gt;Cron expression&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: left"&gt;(time trigger) standard 5-field cron or a macro (&lt;code&gt;@daily&lt;/code&gt;, &lt;code&gt;@weekly&lt;/code&gt;, …). &lt;strong&gt;Times are UTC.&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: left"&gt;&lt;strong&gt;Event name&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: left"&gt;(event trigger) the &lt;a href="https://bschwede.codeberg.page/wt-modules/en/docs/cronjob/events/"&gt;event&lt;/a&gt; this job reacts to. Slug or &lt;code&gt;&amp;lt;domain&amp;gt;:slug&lt;/code&gt;, max 64 chars.&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: left"&gt;&lt;strong&gt;Command&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: left"&gt;a &lt;code&gt;modules_v4/&amp;lt;module&amp;gt;/cli/&amp;lt;script&amp;gt;.php&lt;/code&gt; path or an allowlisted core command.&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: left"&gt;&lt;strong&gt;Arguments&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: left"&gt;plain options/values only (e.g. &lt;code&gt;--limit=5000&lt;/code&gt;), max 10 tokens&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: left"&gt;&lt;strong&gt;Timeout&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: left"&gt;30 - 3600 s&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: left"&gt;&lt;strong&gt;Notify on failure&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: left"&gt;opt-in: alert the administrator accounts when this job fails&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;Available event names and commands are provided as datalists to make data entry easier.&lt;/p&gt;</description></item><item><title>The Tick</title><link>https://bschwede.codeberg.page/wt-modules/en/docs/cronjob/tick/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://bschwede.codeberg.page/wt-modules/en/docs/cronjob/tick/</guid><description>&lt;p&gt;The &lt;strong&gt;tick&lt;/strong&gt; is the heart of the module: once a minute it looks at every enabled job, decides what is due, and runs it. Neither the webtrees core nor the operating system does any scheduling itself — they only start &lt;a href="https://github.com/bschwede/wt-cronjob/tree/main/cli/tick.php" target="_blank" rel="noopener"&gt;&lt;code&gt;tick.php&lt;/code&gt;&lt;/a&gt;&#10;.&lt;/p&gt;&#10;&#10;&#10;&#10;&lt;pre class="mermaid"&gt;flowchart TD&#10; subgraph Triggers[&amp;#34;`⏱ Triggers *(once per minute)*`&amp;#34;]&#10; CRON[&amp;#34;`🕑 OS cron / systemd&amp;lt;br/&amp;gt;*every minute*`&amp;#34;]&#10; WATCH[&amp;#34;`👁 watch daemon&amp;lt;br/&amp;gt;*(in-module)*`&amp;#34;]&#10; end&#10;&#10; subgraph Guard[&amp;#34;🛡 Guard&amp;#34;]&#10; TP[&amp;#34;`📄 cli/tick.php · **cron:tick**&amp;lt;br/&amp;gt;*guard(): 403 over HTTP*`&amp;#34;]&#10; OFF{&amp;#34;`⚠ site offline?&amp;lt;br/&amp;gt;*data/offline.txt*`&amp;#34;}&#10; BOOT[&amp;#34;`🚀 boot()&amp;lt;br/&amp;gt;*webtrees &amp;#43; DB*`&amp;#34;]&#10; end&#10;&#10; subgraph Selection[&amp;#34;🔎 Select work&amp;#34;]&#10; TIME[&amp;#34;`📅 time-based jobs&amp;lt;br/&amp;gt;**next_run_at due** *(UTC)*`&amp;#34;]&#10; CE[(&amp;#34;`📦 cj_event&amp;lt;br/&amp;gt;*event queue*`&amp;#34;)]&#10; DRAIN[&amp;#34;`🧮 drain queue&amp;lt;br/&amp;gt;*coalesced · 60 s budget*`&amp;#34;]&#10; end&#10;&#10; subgraph Execution[&amp;#34;`⚙ Execution *(isolated child)*`&amp;#34;]&#10; RUN[&amp;#34;`🧪 JobRunner&amp;lt;br/&amp;gt;**proc_open**`&amp;#34;]&#10; LIM[&amp;#34;`⏲ timeout 30–3600 s&amp;lt;br/&amp;gt;last **64 KB** · exit code`&amp;#34;]&#10; end&#10;&#10; subgraph Storage[&amp;#34;💾 Storage&amp;#34;]&#10; CJRUN[(&amp;#34;`cj_run&amp;lt;br/&amp;gt;*run history*`&amp;#34;)]&#10; MARK[&amp;#34;`🏷 tick.last&amp;lt;br/&amp;gt;*stale &amp;gt; 300 s → tick_stale*`&amp;#34;]&#10; end&#10;&#10; CRON --&amp;gt; Guard&#10; WATCH --&amp;gt; Guard&#10; TP --&amp;gt; OFF&#10; OFF --&amp;gt;|&amp;#34;offline&amp;#34;| SKIP[&amp;#34;⏭ skip tick&amp;#34;]&#10; OFF --&amp;gt;|&amp;#34;online&amp;#34;| BOOT&#10; BOOT --&amp;gt; TIME&#10; BOOT --&amp;gt; DRAIN&#10; CE --&amp;gt; DRAIN&#10; TIME --&amp;gt; Execution&#10; DRAIN --&amp;gt; Execution&#10; RUN --&amp;gt; LIM&#10; LIM --&amp;gt; CJRUN&#10; BOOT --&amp;gt; MARK&#10;&#10; classDef sourceStyle fill:#e3f2fd,stroke:#1565C0,stroke-width:2px&#10; classDef queueStyle fill:#fff3e0,stroke:#E65100,stroke-width:2px&#10; classDef tickStyle fill:#e8f5e9,stroke:#2E7D32,stroke-width:2px&#10; classDef execStyle fill:#fce4ec,stroke:#C2185B,stroke-width:2px&#10; classDef storageStyle fill:#f5f5f5,stroke:#616161,stroke-width:1px&#10;&#10; class CRON,WATCH sourceStyle&#10; class OFF,CE queueStyle&#10; class TP,TIME,DRAIN tickStyle&#10; class RUN,LIM execStyle&#10; class CJRUN,MARK,SKIP storageStyle&#10;&lt;/pre&gt;&#10;&lt;h2 id="what-starts-a-tick"&gt;What starts a tick&lt;a class="td-heading-self-link" href="#what-starts-a-tick" aria-label="Heading self-link"&gt;&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;Pick &lt;strong&gt;one&lt;/strong&gt; trigger (once):&lt;/p&gt;</description></item><item><title>CLI Scripts</title><link>https://bschwede.codeberg.page/wt-modules/en/docs/cronjob/cli-scripts/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://bschwede.codeberg.page/wt-modules/en/docs/cronjob/cli-scripts/</guid><description>&lt;h2 id="scripts"&gt;Scripts&lt;a class="td-heading-self-link" href="#scripts" aria-label="Heading self-link"&gt;&lt;/a&gt;&lt;/h2&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#cad3f5;background-color:#24273a;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;php modules_v4/cronjob/cli/tick.php &lt;span style="color:#6e738d;font-style:italic"&gt;# run all due jobs&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;php modules_v4/cronjob/cli/tick.php --dry-run &lt;span style="color:#6e738d;font-style:italic"&gt;# show what would run&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;php modules_v4/cronjob/cli/tick.php --job&lt;span style="color:#91d7e3;font-weight:bold"&gt;=&lt;/span&gt;&amp;lt;name&amp;gt; &lt;span style="color:#6e738d;font-style:italic"&gt;# run one specific job now&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;php modules_v4/cronjob/cli/tick.php --strict &lt;span style="color:#6e738d;font-style:italic"&gt;# exit 1 if any job failed&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;php modules_v4/cronjob/cli/tick.php --full-output &lt;span style="color:#6e738d;font-style:italic"&gt;# echo full job output (debugging!)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;php modules_v4/cronjob/cli/watch.php &lt;span style="color:#6e738d;font-style:italic"&gt;# run the resident watch daemon&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Exit codes: &lt;code&gt;0&lt;/code&gt; = tick ran (job failures are recorded in the DB), &lt;code&gt;1&lt;/code&gt; = environment problem or (with &lt;code&gt;--strict&lt;/code&gt;) at least one job failed.&lt;/p&gt;</description></item><item><title>Security</title><link>https://bschwede.codeberg.page/wt-modules/en/docs/cronjob/security/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://bschwede.codeberg.page/wt-modules/en/docs/cronjob/security/</guid><description>&lt;ul&gt;&#10;&lt;li&gt;&lt;strong&gt;No shell, ever.&lt;/strong&gt; Commands are executed via &lt;a href="https://www.php.net/manual/en/function.proc-open.php"&gt;&lt;code&gt;proc_open&lt;/code&gt;&lt;/a&gt; with an &lt;a href="https://www.php.net/manual/en/reserved.variables.argv.php"&gt;argv array&lt;/a&gt;.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Command whitelist.&lt;/strong&gt; &lt;a href="https://bschwede.codeberg.page/wt-modules/en/docs/cronjob/cli-scripts/"&gt;Module scripts&lt;/a&gt; must match &lt;code&gt;modules_v4/&amp;lt;module&amp;gt;/cli/&amp;lt;script&amp;gt;.php&lt;/code&gt;, exist, and resolve inside a &lt;code&gt;modules_v4/&amp;lt;module&amp;gt;/cli/&lt;/code&gt; directory. Core commands are limited to the read-only/export allowlist.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Argument validation.&lt;/strong&gt; Every argument token must be a plain option or value — no shell metacharacters, no spaces inside tokens, max 10 tokens.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Working directories.&lt;/strong&gt; Module scripts run from the webtrees root; core commands run from &lt;code&gt;data/&lt;/code&gt;.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;No sensitive data in stdout.&lt;/strong&gt; The &lt;a href="https://bschwede.codeberg.page/wt-modules/en/docs/cronjob/tick/"&gt;tick&lt;/a&gt; echoes summary lines only; full output stays in the admin-only run history.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;UI actions&lt;/strong&gt; are all named &lt;code&gt;*Admin*&lt;/code&gt; (admin-only, &lt;a href="https://developer.mozilla.org/en/docs/Web/Security/Attacks/CSRF"&gt;CSRF&lt;/a&gt;-protected).&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Kill switch:&lt;/strong&gt; disable the module — the tick then does nothing.&lt;/li&gt;&#10;&lt;li&gt;The tick is single-instance (flock), and a job&amp;rsquo;s child process is hard-killed after its timeout (&lt;a href="https://en.wikipedia.org/wiki/SIGKILL"&gt;&lt;code&gt;SIGKILL&lt;/code&gt;&lt;/a&gt;).&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Event-queue coalescing.&lt;/strong&gt; A flood of identical &lt;a href="https://bschwede.codeberg.page/wt-modules/en/docs/cronjob/events/"&gt;events&lt;/a&gt; cannot amplify into one job run per queued row.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Webhook token header-only.&lt;/strong&gt; Send the token exclusively in the &lt;code&gt;X-Cronjob-Token&lt;/code&gt; header.&lt;/li&gt;&#10;&lt;/ul&gt;</description></item></channel></rss>