fix: ensure scheduler cron and timezone fallback
This commit is contained in:
@@ -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;
|
||||||
|
|||||||
@@ -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.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|
||||||
|
|||||||
42
tests/Unit/BackupScheduleNextRunTest.php
Normal file
42
tests/Unit/BackupScheduleNextRunTest.php
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user