From fa5231803c24c90562c7aec18d64fa21ba31afc7 Mon Sep 17 00:00:00 2001 From: codex Date: Mon, 2 Feb 2026 03:55:55 +0200 Subject: [PATCH] fix: ensure scheduler cron and timezone fallback --- VERSION | 2 +- app/Filament/Admin/Pages/Backups.php | 8 ++++- app/Models/BackupSchedule.php | 40 ++++++++++++++++------ install.sh | 8 ++++- tests/Unit/BackupScheduleNextRunTest.php | 42 ++++++++++++++++++++++++ 5 files changed, 87 insertions(+), 13 deletions(-) create mode 100644 tests/Unit/BackupScheduleNextRunTest.php diff --git a/VERSION b/VERSION index cacf117..44df55f 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -VERSION=0.9-rc50 +VERSION=0.9-rc51 diff --git a/app/Filament/Admin/Pages/Backups.php b/app/Filament/Admin/Pages/Backups.php index 544be98..a77c36f 100644 --- a/app/Filament/Admin/Pages/Backups.php +++ b/app/Filament/Admin/Pages/Backups.php @@ -1602,7 +1602,13 @@ class Backups extends Page implements HasActions, HasForms, HasTable { static $timezone = null; if ($timezone === null) { - $timezone = trim(shell_exec('cat /etc/timezone 2>/dev/null') ?? '') ?: 'UTC'; + $timezone = trim(shell_exec('cat /etc/timezone 2>/dev/null') ?? ''); + if ($timezone === '') { + $timezone = trim(shell_exec('timedatectl show -p Timezone --value 2>/dev/null') ?? ''); + } + if ($timezone === '') { + $timezone = 'UTC'; + } } return $timezone; diff --git a/app/Models/BackupSchedule.php b/app/Models/BackupSchedule.php index 9fad271..0dbafb9 100644 --- a/app/Models/BackupSchedule.php +++ b/app/Models/BackupSchedule.php @@ -82,11 +82,11 @@ class BackupSchedule extends Model */ public function shouldRun(): bool { - if (!$this->is_active) { + if (! $this->is_active) { return false; } - if (!$this->next_run_at) { + if (! $this->next_run_at) { return true; } @@ -98,11 +98,13 @@ class BackupSchedule extends Model */ public function calculateNextRun(): Carbon { + $timezone = $this->getSystemTimezone(); + $now = Carbon::now($timezone); $time = explode(':', $this->time); $hour = (int) ($time[0] ?? 2); $minute = (int) ($time[1] ?? 0); - $next = Carbon::now()->setTime($hour, $minute, 0); + $next = $now->copy()->setTime($hour, $minute, 0); // If time already passed today, start from tomorrow if ($next->isPast()) { @@ -111,7 +113,7 @@ class BackupSchedule extends Model switch ($this->frequency) { case 'hourly': - $next = Carbon::now()->addHour()->startOfHour(); + $next = $now->copy()->addHour()->startOfHour(); break; case 'daily': @@ -135,9 +137,10 @@ class BackupSchedule extends Model break; } - $this->next_run_at = $next; + $nextUtc = $next->copy()->setTimezone('UTC'); + $this->attributes['next_run_at'] = $nextUtc->format($this->getDateFormat()); - return $next; + return $nextUtc; } /** @@ -147,9 +150,9 @@ class BackupSchedule extends Model { $base = match ($this->frequency) { 'hourly' => 'Every hour', - 'daily' => 'Daily at ' . $this->time, - 'weekly' => 'Weekly on ' . $this->getDayName() . ' at ' . $this->time, - 'monthly' => 'Monthly on day ' . ($this->day_of_month ?? 1) . ' at ' . $this->time, + 'daily' => 'Daily at '.$this->time, + 'weekly' => 'Weekly on '.$this->getDayName().' at '.$this->time, + 'monthly' => 'Monthly on day '.($this->day_of_month ?? 1).' at '.$this->time, default => ucfirst($this->frequency), }; @@ -162,9 +165,26 @@ class BackupSchedule extends Model protected function getDayName(): string { $days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']; + return $days[$this->day_of_week ?? 0]; } + protected function getSystemTimezone(): string + { + static $timezone = null; + if ($timezone === null) { + $timezone = trim((string) @file_get_contents('/etc/timezone')); + if ($timezone === '') { + $timezone = trim((string) @shell_exec('timedatectl show -p Timezone --value 2>/dev/null')); + } + if ($timezone === '') { + $timezone = 'UTC'; + } + } + + return $timezone; + } + /** * Scope for active schedules. */ @@ -181,7 +201,7 @@ class BackupSchedule extends Model return $query->active() ->where(function ($q) { $q->whereNull('next_run_at') - ->orWhere('next_run_at', '<=', now()); + ->orWhere('next_run_at', '<=', now()); }); } diff --git a/install.sh b/install.sh index 5249e7c..1d324ab 100755 --- a/install.sh +++ b/install.sh @@ -16,7 +16,7 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" if [[ -f "$SCRIPT_DIR/VERSION" ]]; then JABALI_VERSION="$(sed -n 's/^VERSION=//p' "$SCRIPT_DIR/VERSION")" fi -JABALI_VERSION="${JABALI_VERSION:-0.9-rc50}" +JABALI_VERSION="${JABALI_VERSION:-0.9-rc51}" # Colors RED='\033[0;31m' @@ -3019,6 +3019,12 @@ setup_scheduler_cron() { mkdir -p "$JABALI_DIR/storage/logs" chown -R www-data:www-data "$JABALI_DIR/storage/logs" + # Ensure cron service is enabled and running + if command -v systemctl >/dev/null 2>&1; then + systemctl enable cron >/dev/null 2>&1 || true + systemctl start cron >/dev/null 2>&1 || true + fi + # Add cron job for Laravel scheduler as www-data (runs every minute) CRON_LINE="* * * * * cd $JABALI_DIR && php artisan schedule:run >> /dev/null 2>&1" diff --git a/tests/Unit/BackupScheduleNextRunTest.php b/tests/Unit/BackupScheduleNextRunTest.php new file mode 100644 index 0000000..9158094 --- /dev/null +++ b/tests/Unit/BackupScheduleNextRunTest.php @@ -0,0 +1,42 @@ +/dev/null')); + } + if ($timezone === '') { + $timezone = 'UTC'; + } + $now = Carbon::create(2026, 2, 2, 1, 0, 0, $timezone); + Carbon::setTestNow($now); + + $schedule = new BackupSchedule([ + 'frequency' => 'daily', + 'time' => '02:00', + 'is_active' => true, + ]); + + $nextRun = $schedule->calculateNextRun(); + + $expectedLocal = $now->copy()->setTime(2, 0, 0); + if ($expectedLocal->isPast()) { + $expectedLocal->addDay(); + } + + $expectedUtc = $expectedLocal->copy()->setTimezone('UTC'); + + $this->assertSame($expectedUtc->timestamp, $nextRun->timestamp); + } +}