comment(Inspiring::quote()); })->purpose('Display an inspiring quote'); // Advance every due provisioning run once a minute (runs in the scheduler service). Schedule::call(fn () => app(TickProvisioning::class)()) ->everyMinute() ->name('provisioning-tick') ->withoutOverlapping(); // Refresh the VPN peer state (handshakes, traffic) so the console does not go // stale while nobody has it open. The job itself runs on the provisioning // queue — that worker owns wg0. Schedule::job(new \App\Provisioning\Jobs\SyncVpnPeers) ->everyMinute() ->name('vpn-sync'); // Sample network counters and enforce the monthly allowance. Runs on the // provisioning queue, which is where the Proxmox credentials are usable. // Turns the monitoring column from a claim into a measurement. Without it the // console and the public status page report every instance healthy forever. Schedule::job(new \App\Provisioning\Jobs\SyncMonitoringStatus) ->everyFiveMinutes() ->withoutOverlapping(); Schedule::job(new \App\Provisioning\Jobs\CollectInstanceTraffic) ->everyFifteenMinutes() ->name('traffic-collect'); // Discard Stripe events that were held for a contract which never appeared. // Most of them never will: Stripe also delivers events for subscriptions // belonging to other environments pointed at this endpoint, and for objects // made by hand in the dashboard. A week is far longer than the moment a // checkout takes to become a contract, and short enough that the table stays // a holding area rather than a second copy of Stripe's event log. Schedule::call(fn () => \App\Models\StripePendingEvent::query() ->where('created_at', '<', now()->subWeek()) ->delete()) ->daily() ->name('stripe-pending-prune'); // Copies of invoices that never reached the archive — a NAS that was rebooting // when the invoice was issued, a mount that had gone away. Copy-on-issue is // right and is not enough: nobody opens an archive to check whether last // Tuesday is in it, so something has to go and look. // // Hourly rather than by the minute: the failure it repairs lasts as long as an // outage lasts, and a retry storm against a mount that is down helps nobody. Schedule::command('clupilot:archive-invoices') ->hourly() ->withoutOverlapping(); // Empty a handover directory of folders past their keep-by. Only where a // destination asks for it, and only folders whose name is a date this // application wrote — see the command for why it leaves everything else alone. Schedule::command('clupilot:prune-exports') ->dailyAt('03:20') ->withoutOverlapping(); // One sample of every public status component, into today's row. // // The ninety-day bar on the status page is the one figure a reader checks // against their own memory, so it has to come from something recorded at the // time — there is no way to reconstruct it later. Beside the monitoring sync it // reads from, at the same cadence. Schedule::command('clupilot:sample-status') ->everyFiveMinutes() ->withoutOverlapping(); // Re-read the DNS proof for every custom domain. // // Not a one-off: a token checked once can be taken straight back out, and a // domain that later lapses and is registered by somebody else keeps resolving // to the same instance. Nightly, and at an hour where withdrawing a domain // inconveniences nobody — a withdrawal takes a working Nextcloud off its own // address, which is not a thing to do at eleven in the morning. Schedule::command('clupilot:verify-domains') ->dailyAt('03:40') ->withoutOverlapping(); // Take an available release inside the configured window, if the owner has // switched that on. Every five minutes rather than once at the start of the // window: a scheduler that was down for those sixty seconds would otherwise // skip the whole night. Schedule::command('clupilot:auto-update') ->everyFiveMinutes() ->withoutOverlapping(); // Ask every host whether it is still there. // // hosts.last_seen_at drove the console's health dot and was written exactly // once, at onboarding — so every host read "offline" half an hour later, // permanently. Nothing was measuring host reachability at all, which is also // why it could not appear on the status page. Schedule::job(new \App\Provisioning\Jobs\PingHosts) ->everyMinute() ->name('host-ping');