nixos-module: add scheduler timer for artisan schedule:run
Invoice Ninja relies on Laravel's scheduler for recurring invoices, scheduled reports, and other internal cron-driven jobs. Without a systemd unit invoking `artisan schedule:run` periodically, the app's dashboard reports "cron jobs are not enabled" after login. Adds invoiceninja-scheduler.timer (every minute, matching upstream's documented cron cadence) and its oneshot service, and extends the automated test to assert the timer is active and schedule:run succeeds. Note: the app's own is_scheduler_running flag only flips on a 5-minute wall-clock boundary (app/Console/Kernel.php's everyFiveMinutes() job), so the dashboard warning can take a few minutes to clear after boot - confirmed working via the "Refresh Data" button in that same modal. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -92,6 +92,10 @@ expect Nix to report the correct hash on a failed build (or use `nix-prefetch`/`
|
|||||||
`app.php`, and guards one-time actions (`migrate`, `db:seed`, admin account creation) behind marker files
|
`app.php`, and guards one-time actions (`migrate`, `db:seed`, admin account creation) behind marker files
|
||||||
(`.initial-migration`, `.db-seeded`, `.admin-created`) in `dataDir`. `phpfpm-invoiceninja.service` and
|
(`.initial-migration`, `.db-seeded`, `.admin-created`) in `dataDir`. `phpfpm-invoiceninja.service` and
|
||||||
`invoiceninja-queue-worker.service` both depend on it.
|
`invoiceninja-queue-worker.service` both depend on it.
|
||||||
|
- `invoiceninja-scheduler.timer`/`.service` run `artisan schedule:run` every minute — this is what drives
|
||||||
|
Invoice Ninja's own internal cron-scheduled jobs (recurring invoices, scheduled reports, etc.); without it the
|
||||||
|
app's dashboard reports "cron jobs are not enabled". Laravel's scheduler is just a dispatcher, so this one
|
||||||
|
timer is sufficient regardless of how many scheduled tasks Invoice Ninja itself defines.
|
||||||
- `invoiceninja-manage` is a generated wrapper script (`invoiceninja-manage <artisan-args>`) that `cd`s into the
|
- `invoiceninja-manage` is a generated wrapper script (`invoiceninja-manage <artisan-args>`) that `cd`s into the
|
||||||
package output and runs `php artisan` as the configured service user (via `sudo` if invoked as another user).
|
package output and runs `php artisan` as the configured service user (via `sudo` if invoked as another user).
|
||||||
It's added to `environment.systemPackages`, so it's the normal way to run artisan commands
|
It's added to `environment.systemPackages`, so it's the normal way to run artisan commands
|
||||||
|
|||||||
@@ -623,6 +623,32 @@ in
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
systemd.services.invoiceninja-scheduler = {
|
||||||
|
description = "Invoice Ninja scheduled tasks (artisan schedule:run)";
|
||||||
|
after = [ "invoiceninja-data-setup.service" ];
|
||||||
|
requires = [ "phpfpm-invoiceninja.service" ];
|
||||||
|
# Ensure chromium is available, for any scheduled tasks that generate PDFs.
|
||||||
|
path = extraPrograms;
|
||||||
|
|
||||||
|
serviceConfig = {
|
||||||
|
Type = "oneshot";
|
||||||
|
ExecStart = "${invoiceninja-manage}/bin/invoiceninja-manage schedule:run";
|
||||||
|
User = user;
|
||||||
|
Group = group;
|
||||||
|
StateDirectory = lib.mkIf (cfg.dataDir == "/var/lib/invoiceninja") "invoiceninja";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
systemd.timers.invoiceninja-scheduler = {
|
||||||
|
description = "Run Invoice Ninja's scheduled tasks every minute";
|
||||||
|
wantedBy = [ "timers.target" ];
|
||||||
|
timerConfig = {
|
||||||
|
OnCalendar = "minutely";
|
||||||
|
Persistent = true;
|
||||||
|
Unit = "invoiceninja-scheduler.service";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
systemd.services.invoiceninja-data-setup = {
|
systemd.services.invoiceninja-data-setup = {
|
||||||
description = "Invoice Ninja setup: migrations, environment file update, cache reload, data changes";
|
description = "Invoice Ninja setup: migrations, environment file update, cache reload, data changes";
|
||||||
wantedBy = [ "multi-user.target" ];
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
|||||||
@@ -52,6 +52,11 @@ pkgs.testers.nixosTest {
|
|||||||
with subtest("invoiceninja-manage wrapper can run artisan commands"):
|
with subtest("invoiceninja-manage wrapper can run artisan commands"):
|
||||||
machine.succeed("invoiceninja-manage migrate:status")
|
machine.succeed("invoiceninja-manage migrate:status")
|
||||||
|
|
||||||
|
with subtest("scheduler timer is enabled and schedule:run succeeds"):
|
||||||
|
# Without this, the app's dashboard reports "cron jobs are not enabled".
|
||||||
|
machine.succeed("systemctl is-active invoiceninja-scheduler.timer")
|
||||||
|
machine.succeed("systemctl start --wait invoiceninja-scheduler.service")
|
||||||
|
|
||||||
with subtest("admin account can log in via the API"):
|
with subtest("admin account can log in via the API"):
|
||||||
login_response = machine.succeed(
|
login_response = machine.succeed(
|
||||||
"curl -sf -X POST http://localhost/api/v1/login "
|
"curl -sf -X POST http://localhost/api/v1/login "
|
||||||
|
|||||||
Reference in New Issue
Block a user