Pass the server timezone parameter to shouldRunNow() call at line 127, ensuring ServerCheckJob dispatch respects the server's local timezone instead of falling back to the instance default. This aligns the behavior with other scheduled tasks in the same method: - ServerStorageCheckJob (line 137) - ServerPatchCheckJob (line 144) - Sentinel restart (line 152) All scheduled tasks in processServerTasks() now consistently use the server's configured timezone for cron evaluation. Added unit test to verify timezone-aware cron schedule evaluation. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
171 lines
5.8 KiB
PHP
171 lines
5.8 KiB
PHP
<?php
|
|
|
|
namespace App\Jobs;
|
|
|
|
use App\Models\InstanceSettings;
|
|
use App\Models\Server;
|
|
use App\Models\Team;
|
|
use Cron\CronExpression;
|
|
use Illuminate\Bus\Queueable;
|
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
|
use Illuminate\Foundation\Bus\Dispatchable;
|
|
use Illuminate\Queue\InteractsWithQueue;
|
|
use Illuminate\Queue\SerializesModels;
|
|
use Illuminate\Support\Carbon;
|
|
use Illuminate\Support\Collection;
|
|
use Illuminate\Support\Facades\Log;
|
|
|
|
class ServerManagerJob implements ShouldQueue
|
|
{
|
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
|
|
|
/**
|
|
* The time when this job execution started.
|
|
*/
|
|
private ?Carbon $executionTime = null;
|
|
|
|
private InstanceSettings $settings;
|
|
|
|
private string $instanceTimezone;
|
|
|
|
private string $checkFrequency = '* * * * *';
|
|
|
|
/**
|
|
* Create a new job instance.
|
|
*/
|
|
public function __construct()
|
|
{
|
|
$this->onQueue('high');
|
|
}
|
|
|
|
public function handle(): void
|
|
{
|
|
// Freeze the execution time at the start of the job
|
|
$this->executionTime = Carbon::now();
|
|
if (isCloud()) {
|
|
$this->checkFrequency = '*/5 * * * *';
|
|
}
|
|
$this->settings = instanceSettings();
|
|
$this->instanceTimezone = $this->settings->instance_timezone ?: config('app.timezone');
|
|
|
|
if (validate_timezone($this->instanceTimezone) === false) {
|
|
$this->instanceTimezone = config('app.timezone');
|
|
}
|
|
|
|
// Get all servers to process
|
|
$servers = $this->getServers();
|
|
|
|
// Dispatch ServerConnectionCheck for all servers efficiently
|
|
$this->dispatchConnectionChecks($servers);
|
|
|
|
// Process server-specific scheduled tasks
|
|
$this->processScheduledTasks($servers);
|
|
}
|
|
|
|
private function getServers(): Collection
|
|
{
|
|
$allServers = Server::where('ip', '!=', '1.2.3.4');
|
|
|
|
if (isCloud()) {
|
|
$servers = $allServers->whereRelation('team.subscription', 'stripe_invoice_paid', true)->get();
|
|
$own = Team::find(0)->servers;
|
|
|
|
return $servers->merge($own);
|
|
} else {
|
|
return $allServers->get();
|
|
}
|
|
}
|
|
|
|
private function dispatchConnectionChecks(Collection $servers): void
|
|
{
|
|
|
|
if ($this->shouldRunNow($this->checkFrequency)) {
|
|
$servers->each(function (Server $server) {
|
|
try {
|
|
ServerConnectionCheckJob::dispatch($server);
|
|
} catch (\Exception $e) {
|
|
Log::channel('scheduled-errors')->error('Failed to dispatch ServerConnectionCheck', [
|
|
'server_id' => $server->id,
|
|
'server_name' => $server->name,
|
|
'error' => get_class($e).': '.$e->getMessage(),
|
|
]);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
private function processScheduledTasks(Collection $servers): void
|
|
{
|
|
foreach ($servers as $server) {
|
|
try {
|
|
$this->processServerTasks($server);
|
|
} catch (\Exception $e) {
|
|
Log::channel('scheduled-errors')->error('Error processing server tasks', [
|
|
'server_id' => $server->id,
|
|
'server_name' => $server->name,
|
|
'error' => get_class($e).': '.$e->getMessage(),
|
|
]);
|
|
}
|
|
}
|
|
}
|
|
|
|
private function processServerTasks(Server $server): void
|
|
{
|
|
// Get server timezone (used for all scheduled tasks)
|
|
$serverTimezone = data_get($server->settings, 'server_timezone', $this->instanceTimezone);
|
|
if (validate_timezone($serverTimezone) === false) {
|
|
$serverTimezone = config('app.timezone');
|
|
}
|
|
|
|
// Check if we should run sentinel-based checks
|
|
$lastSentinelUpdate = $server->sentinel_updated_at;
|
|
$waitTime = $server->waitBeforeDoingSshCheck();
|
|
$sentinelOutOfSync = Carbon::parse($lastSentinelUpdate)->isBefore($this->executionTime->copy()->subSeconds($waitTime));
|
|
|
|
if ($sentinelOutOfSync) {
|
|
// Dispatch ServerCheckJob if Sentinel is out of sync
|
|
if ($this->shouldRunNow($this->checkFrequency, $serverTimezone)) {
|
|
ServerCheckJob::dispatch($server);
|
|
}
|
|
}
|
|
|
|
// Dispatch ServerStorageCheckJob if due (independent of Sentinel status)
|
|
$serverDiskUsageCheckFrequency = data_get($server->settings, 'server_disk_usage_check_frequency', '0 23 * * *');
|
|
if (isset(VALID_CRON_STRINGS[$serverDiskUsageCheckFrequency])) {
|
|
$serverDiskUsageCheckFrequency = VALID_CRON_STRINGS[$serverDiskUsageCheckFrequency];
|
|
}
|
|
$shouldRunStorageCheck = $this->shouldRunNow($serverDiskUsageCheckFrequency, $serverTimezone);
|
|
|
|
if ($shouldRunStorageCheck) {
|
|
ServerStorageCheckJob::dispatch($server);
|
|
}
|
|
|
|
// Dispatch ServerPatchCheckJob if due (weekly)
|
|
$shouldRunPatchCheck = $this->shouldRunNow('0 0 * * 0', $serverTimezone);
|
|
|
|
if ($shouldRunPatchCheck) { // Weekly on Sunday at midnight
|
|
ServerPatchCheckJob::dispatch($server);
|
|
}
|
|
|
|
// Dispatch Sentinel restart if due (daily for Sentinel-enabled servers)
|
|
$isSentinelEnabled = $server->isSentinelEnabled();
|
|
$shouldRestartSentinel = $isSentinelEnabled && $this->shouldRunNow('0 0 * * *', $serverTimezone);
|
|
|
|
if ($shouldRestartSentinel) {
|
|
dispatch(function () use ($server) {
|
|
$server->restartContainer('coolify-sentinel');
|
|
});
|
|
}
|
|
}
|
|
|
|
private function shouldRunNow(string $frequency, ?string $timezone = null): bool
|
|
{
|
|
$cron = new CronExpression($frequency);
|
|
|
|
// Use the frozen execution time, not the current time
|
|
$baseTime = $this->executionTime ?? Carbon::now();
|
|
$executionTime = $baseTime->copy()->setTimezone($timezone ?? config('app.timezone'));
|
|
|
|
return $cron->isDue($executionTime);
|
|
}
|
|
}
|