fix: ensure scheduler cron and timezone fallback

This commit is contained in:
codex
2026-02-02 03:55:55 +02:00
parent 27a9dfa84d
commit fa5231803c
5 changed files with 87 additions and 13 deletions

View File

@@ -1 +1 @@
VERSION=0.9-rc50 VERSION=0.9-rc51

View File

@@ -1602,7 +1602,13 @@ class Backups extends Page implements HasActions, HasForms, HasTable
{ {
static $timezone = null; static $timezone = null;
if ($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; return $timezone;

View File

@@ -82,11 +82,11 @@ class BackupSchedule extends Model
*/ */
public function shouldRun(): bool public function shouldRun(): bool
{ {
if (!$this->is_active) { if (! $this->is_active) {
return false; return false;
} }
if (!$this->next_run_at) { if (! $this->next_run_at) {
return true; return true;
} }
@@ -98,11 +98,13 @@ class BackupSchedule extends Model
*/ */
public function calculateNextRun(): Carbon public function calculateNextRun(): Carbon
{ {
$timezone = $this->getSystemTimezone();
$now = Carbon::now($timezone);
$time = explode(':', $this->time); $time = explode(':', $this->time);
$hour = (int) ($time[0] ?? 2); $hour = (int) ($time[0] ?? 2);
$minute = (int) ($time[1] ?? 0); $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 time already passed today, start from tomorrow
if ($next->isPast()) { if ($next->isPast()) {
@@ -111,7 +113,7 @@ class BackupSchedule extends Model
switch ($this->frequency) { switch ($this->frequency) {
case 'hourly': case 'hourly':
$next = Carbon::now()->addHour()->startOfHour(); $next = $now->copy()->addHour()->startOfHour();
break; break;
case 'daily': case 'daily':
@@ -135,9 +137,10 @@ class BackupSchedule extends Model
break; 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) { $base = match ($this->frequency) {
'hourly' => 'Every hour', 'hourly' => 'Every hour',
'daily' => 'Daily at ' . $this->time, 'daily' => 'Daily at '.$this->time,
'weekly' => 'Weekly on ' . $this->getDayName() . ' at ' . $this->time, 'weekly' => 'Weekly on '.$this->getDayName().' at '.$this->time,
'monthly' => 'Monthly on day ' . ($this->day_of_month ?? 1) . ' at ' . $this->time, 'monthly' => 'Monthly on day '.($this->day_of_month ?? 1).' at '.$this->time,
default => ucfirst($this->frequency), default => ucfirst($this->frequency),
}; };
@@ -162,9 +165,26 @@ class BackupSchedule extends Model
protected function getDayName(): string protected function getDayName(): string
{ {
$days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']; $days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
return $days[$this->day_of_week ?? 0]; 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. * Scope for active schedules.
*/ */

View File

@@ -16,7 +16,7 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
if [[ -f "$SCRIPT_DIR/VERSION" ]]; then if [[ -f "$SCRIPT_DIR/VERSION" ]]; then
JABALI_VERSION="$(sed -n 's/^VERSION=//p' "$SCRIPT_DIR/VERSION")" JABALI_VERSION="$(sed -n 's/^VERSION=//p' "$SCRIPT_DIR/VERSION")"
fi fi
JABALI_VERSION="${JABALI_VERSION:-0.9-rc50}" JABALI_VERSION="${JABALI_VERSION:-0.9-rc51}"
# Colors # Colors
RED='\033[0;31m' RED='\033[0;31m'
@@ -3019,6 +3019,12 @@ setup_scheduler_cron() {
mkdir -p "$JABALI_DIR/storage/logs" mkdir -p "$JABALI_DIR/storage/logs"
chown -R www-data:www-data "$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) # 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" CRON_LINE="* * * * * cd $JABALI_DIR && php artisan schedule:run >> /dev/null 2>&1"

View File

@@ -0,0 +1,42 @@
<?php
declare(strict_types=1);
namespace Tests\Unit;
use App\Models\BackupSchedule;
use Carbon\Carbon;
use Tests\TestCase;
class BackupScheduleNextRunTest extends TestCase
{
public function test_calculate_next_run_uses_system_timezone_and_stores_utc(): void
{
$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';
}
$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);
}
}