thelia / web-scheduler-module
Expose Symfony Console commands as HMAC-signed HTTP triggers, adaptive to the hosting capabilities (CLI fork, FastCGI finish, synchronous).
Package info
github.com/thelia-modules/WebScheduler
Type:thelia-module
pkg:composer/thelia/web-scheduler-module
Requires
- php: ^8.3
- symfony/lock: ^6.4 || ^7.0
- symfony/process: ^6.4 || ^7.0
- thelia/installer: ~1.1
README
Trigger Symfony Console commands from signed HTTP URLs, with an execution strategy that adapts to the hosting's capabilities.
Designed for shared hostings (Infomaniak, OVH mutualisé, …) that only offer web-based scheduled tasks and no real cron.
How it works
The hosting's scheduler hits a signed URL on your Thelia site. The module authenticates the request (HMAC-SHA256 signature with a short time window), picks the best available execution strategy and runs the configured command.
Three strategies are available. The auto strategy (default) picks the best
one supported by the hosting — detected and cached at runtime.
| Strategy | Requires | Behaviour |
|---|---|---|
cli_fork |
proc_open + a PHP CLI binary |
Spawns nohup php Thelia <cmd> & detached from the HTTP worker. HTTP responds in milliseconds. Best choice for long-running syncs. |
fastcgi_finish |
fastcgi_finish_request |
Responds to the client, flushes the PHP-FPM buffer, then runs the command in-process (via an external process if proc_open is allowed, in-process Thelia Application otherwise). |
sync |
none | Runs the command in the HTTP request and returns the output. Limited by PHP's max_execution_time. Last-resort fallback. |
Features
- Static HMAC-signed trigger URLs — paste once in your hosting's cron panel, never rotate it
- Per-task opaque slug and secret (secret revealed once at creation); rotating the secret invalidates the URL
- Per-task IP allowlist (CIDR), minimum interval rate limit, maximum runtime
- Concurrency lock (
symfony/lock, file-based) — overlapping calls are skipped - Execution history with status, exit code, strategy used and command output
- Back-office diagnostic panel — shows which capabilities the hosting provides and which strategies are supported
- Back-office CRUD for tasks, manual "trigger now" button, secret regeneration
- Any Symfony Console command registered in Thelia is schedulable (no need to write module-side glue)
Install
composer require thelia/web-scheduler-module
Activate the module in the back-office → Modules.
Quick start
- Back-office → Tools → Web Scheduler
- Create a task — pick a command (auto-completed from your registered
Symfony commands), set optional arguments, choose
autostrategy. - On save, the secret is revealed once — store it if you ever need to sign URLs outside the module.
- Copy the Trigger URL from the task list (or from the task edit page).
- Paste that URL into your hosting's scheduled-tasks panel.
Infomaniak example
On Infomaniak's "Planifier une tâche" panel:
- URL: paste the full trigger URL (including
?ts=...&sig=...) - Password: leave unchecked — the HMAC signature replaces URL basic-auth
Paste the URL once and let Infomaniak call it on its schedule. The URL is stable — there is no timestamp or expiry. If the URL ever leaks and you want to invalidate it, hit Regenerate secret in the task form: the slug stays the same, the signature changes, every previously-copied URL stops working.
Security model
- Each task has a unique 32-hex opaque slug embedded in the URL path.
- The URL is signed:
sig = HMAC-SHA256(slug, secret). The signature is static (no timestamp), so the URL is stable for web-cron usage. - Signature comparison is timing-safe (
hash_equals). - Command arguments are frozen per task — nothing from the query string influences the executed command. No RCE surface.
- Optional per-task IP allowlist (CIDR, one entry per line) — the strongest defence-in-depth layer if the URL leaks.
- Optional per-task rate limit (
min_interval_seconds). - Rejected requests always respond
202 Acceptedwith a neutral body, to avoid leaking task existence. - To invalidate a leaked URL: Regenerate secret in the task form.
Dev notes
- Namespace:
WebScheduler\*(PSR-4) - Requires PHP 8.3+, Thelia 2.6+, Symfony 6.4 or 7.x
- Strategies are services tagged
webscheduler.execution_strategy, loaded intoStrategyResolvervia#[AutowireIterator] - Lock store:
FlockStorerooted atTHELIA_CACHE_DIR/webscheduler_locks - Command capture: output is truncated to 64 KiB per execution
License
LGPL-3.0-or-later