diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php
index fd4c508a0..17ef4397c 100644
--- a/app/Console/Kernel.php
+++ b/app/Console/Kernel.php
@@ -44,8 +44,8 @@ protected function schedule(Schedule $schedule): void
// Instance Jobs
$schedule->command('horizon:snapshot')->everyFiveMinutes();
$schedule->command('cleanup:unreachable-servers')->daily()->onOneServer();
- $schedule->job(new PullCoolifyImageJob)->cron($settings->update_check_frequency)->onOneServer();
- $schedule->job(new PullTemplatesFromCDN)->cron($settings->update_check_frequency)->onOneServer();
+ $schedule->job(new PullCoolifyImageJob)->cron($settings->update_check_frequency)->timezone($settings->instance_timezone)->onOneServer();
+ $schedule->job(new PullTemplatesFromCDN)->cron($settings->update_check_frequency)->timezone($settings->instance_timezone)->onOneServer();
$schedule->job(new CleanupInstanceStuffsJob)->everyTwoMinutes()->onOneServer();
$this->schedule_updates($schedule);
@@ -73,9 +73,12 @@ private function pull_images($schedule)
if ($status !== 'running') {
PullSentinelImageJob::dispatch($server);
}
- })->cron($settings->update_check_frequency)->onOneServer();
+ })->cron($settings->update_check_frequency)->timezone($settings->instance_timezone)->onOneServer();
}
- $schedule->job(new PullHelperImageJob($server))->cron($settings->update_check_frequency)->onOneServer();
+ $schedule->job(new PullHelperImageJob($server))
+ ->cron($settings->update_check_frequency)
+ ->timezone($settings->instance_timezone)
+ ->onOneServer();
}
}
@@ -84,11 +87,17 @@ private function schedule_updates($schedule)
$settings = InstanceSettings::get();
$updateCheckFrequency = $settings->update_check_frequency;
- $schedule->job(new CheckForUpdatesJob)->cron($updateCheckFrequency)->onOneServer();
+ $schedule->job(new CheckForUpdatesJob)
+ ->cron($updateCheckFrequency)
+ ->timezone($settings->instance_timezone)
+ ->onOneServer();
if ($settings->is_auto_update_enabled) {
$autoUpdateFrequency = $settings->auto_update_frequency;
- $schedule->job(new UpdateCoolifyJob)->cron($autoUpdateFrequency)->onOneServer();
+ $schedule->job(new UpdateCoolifyJob)
+ ->cron($autoUpdateFrequency)
+ ->timezone($settings->instance_timezone)
+ ->onOneServer();
}
}
@@ -103,15 +112,12 @@ private function check_resources($schedule)
}
foreach ($servers as $server) {
$schedule->job(new ServerCheckJob($server))->everyMinute()->onOneServer();
- //The lines below need to be added as soon as timzone is merged!!
- //$serverTimezone = $server->settings->server_timezone;
- //$schedule->job(new DockerCleanupJob($server))->cron($server->settings->docker_cleanup_frequency)->timezone($serverTimezone)->onOneServer();
+ $serverTimezone = $server->settings->server_timezone;
if ($server->settings->force_docker_cleanup) {
- $schedule->job(new DockerCleanupJob($server))->cron($server->settings->docker_cleanup_frequency)->onOneServer();
+ $schedule->job(new DockerCleanupJob($server))->cron($server->settings->docker_cleanup_frequency)->timezone($serverTimezone)->onOneServer();
} else {
- $schedule->job(new DockerCleanupJob($server))->everyTenMinutes()->onOneServer();
+ $schedule->job(new DockerCleanupJob($server))->everyTenMinutes()->timezone($serverTimezone)->onOneServer();
}
-
}
}
@@ -128,16 +134,18 @@ private function check_scheduled_backups($schedule)
if (is_null(data_get($scheduled_backup, 'database'))) {
ray('database not found');
$scheduled_backup->delete();
-
continue;
}
+ $server = $scheduled_backup->server();
+ $serverTimezone = $server->settings->server_timezone;
+
if (isset(VALID_CRON_STRINGS[$scheduled_backup->frequency])) {
$scheduled_backup->frequency = VALID_CRON_STRINGS[$scheduled_backup->frequency];
}
$schedule->job(new DatabaseBackupJob(
backup: $scheduled_backup
- ))->cron($scheduled_backup->frequency)->onOneServer();
+ ))->cron($scheduled_backup->frequency)->timezone($serverTimezone)->onOneServer();
}
}
@@ -157,7 +165,6 @@ private function check_scheduled_tasks($schedule)
if (! $application && ! $service) {
ray('application/service attached to scheduled task does not exist');
$scheduled_task->delete();
-
continue;
}
if ($application) {
@@ -170,19 +177,23 @@ private function check_scheduled_tasks($schedule)
continue;
}
}
+
+ $server = $scheduled_task->server();
+ $serverTimezone = $server->settings->server_timezone ?: config('app.timezone');
+
if (isset(VALID_CRON_STRINGS[$scheduled_task->frequency])) {
$scheduled_task->frequency = VALID_CRON_STRINGS[$scheduled_task->frequency];
}
$schedule->job(new ScheduledTaskJob(
task: $scheduled_task
- ))->cron($scheduled_task->frequency)->onOneServer();
+ ))->cron($scheduled_task->frequency)->timezone($serverTimezone)->onOneServer();
}
}
protected function commands(): void
{
- $this->load(__DIR__.'/Commands');
+ $this->load(__DIR__ . '/Commands');
require base_path('routes/console.php');
}
-}
+}
\ No newline at end of file
diff --git a/app/Jobs/ScheduledTaskJob.php b/app/Jobs/ScheduledTaskJob.php
index 819e28f89..93d5fca70 100644
--- a/app/Jobs/ScheduledTaskJob.php
+++ b/app/Jobs/ScheduledTaskJob.php
@@ -36,6 +36,8 @@ class ScheduledTaskJob implements ShouldQueue
public array $containers = [];
+ public string $server_timezone;
+
public function __construct($task)
{
$this->task = $task;
@@ -47,6 +49,19 @@ public function __construct($task)
throw new \RuntimeException('ScheduledTaskJob failed: No resource found.');
}
$this->team = Team::find($task->team_id);
+ $this->server_timezone = $this->getServerTimezone();
+ }
+
+ private function getServerTimezone(): string
+ {
+ if ($this->resource instanceof Application) {
+ $timezone = $this->resource->destination->server->settings->server_timezone;
+ return $timezone;
+ } elseif ($this->resource instanceof Service) {
+ $timezone = $this->resource->server->settings->server_timezone;
+ return $timezone;
+ }
+ return 'UTC';
}
public function middleware(): array
@@ -61,6 +76,7 @@ public function uniqueId(): int
public function handle(): void
{
+
try {
$this->task_log = ScheduledTaskExecution::create([
'scheduled_task_id' => $this->task->id,
@@ -78,12 +94,12 @@ public function handle(): void
} elseif ($this->resource->type() == 'service') {
$this->resource->applications()->get()->each(function ($application) {
if (str(data_get($application, 'status'))->contains('running')) {
- $this->containers[] = data_get($application, 'name').'-'.data_get($this->resource, 'uuid');
+ $this->containers[] = data_get($application, 'name') . '-' . data_get($this->resource, 'uuid');
}
});
$this->resource->databases()->get()->each(function ($database) {
if (str(data_get($database, 'status'))->contains('running')) {
- $this->containers[] = data_get($database, 'name').'-'.data_get($this->resource, 'uuid');
+ $this->containers[] = data_get($database, 'name') . '-' . data_get($this->resource, 'uuid');
}
});
}
@@ -96,8 +112,8 @@ public function handle(): void
}
foreach ($this->containers as $containerName) {
- if (count($this->containers) == 1 || str_starts_with($containerName, $this->task->container.'-'.$this->resource->uuid)) {
- $cmd = "sh -c '".str_replace("'", "'\''", $this->task->command)."'";
+ if (count($this->containers) == 1 || str_starts_with($containerName, $this->task->container . '-' . $this->resource->uuid)) {
+ $cmd = "sh -c '" . str_replace("'", "'\''", $this->task->command) . "'";
$exec = "docker exec {$containerName} {$cmd}";
$this->task_output = instant_remote_process([$exec], $this->server, true);
$this->task_log->update([
@@ -121,6 +137,7 @@ public function handle(): void
$this->team?->notify(new TaskFailed($this->task, $e->getMessage()));
// send_internal_notification('ScheduledTaskJob failed with: ' . $e->getMessage());
throw $e;
+ } finally {
}
}
}
diff --git a/app/Livewire/Project/Database/BackupExecutions.php b/app/Livewire/Project/Database/BackupExecutions.php
index c9db7f5fc..5d56ea53d 100644
--- a/app/Livewire/Project/Database/BackupExecutions.php
+++ b/app/Livewire/Project/Database/BackupExecutions.php
@@ -8,9 +8,8 @@
class BackupExecutions extends Component
{
public ?ScheduledDatabaseBackup $backup = null;
-
+ public $database;
public $executions = [];
-
public $setDeletableBackup;
public function getListeners()
@@ -61,4 +60,50 @@ public function refreshBackupExecutions(): void
$this->executions = $this->backup->executions()->get();
}
}
+
+ public function mount(ScheduledDatabaseBackup $backup)
+ {
+ $this->backup = $backup;
+ $this->database = $backup->database;
+ $this->refreshBackupExecutions();
+ }
+
+ public function server()
+ {
+ if ($this->database) {
+ $server = null;
+
+ if ($this->database instanceof \App\Models\ServiceDatabase) {
+ $server = $this->database->service->destination->server;
+ } elseif ($this->database->destination && $this->database->destination->server) {
+ $server = $this->database->destination->server;
+ }
+ if ($server) {
+ return $server;
+ }
+ }
+ return null;
+ }
+
+ public function getServerTimezone()
+ {
+ $server = $this->server();
+ if (!$server) {
+ return 'UTC';
+ }
+ $serverTimezone = $server->settings->server_timezone;
+ return $serverTimezone;
+ }
+
+ public function formatDateInServerTimezone($date)
+ {
+ $serverTimezone = $this->getServerTimezone();
+ $dateObj = new \DateTime($date);
+ try {
+ $dateObj->setTimezone(new \DateTimeZone($serverTimezone));
+ } catch (\Exception $e) {
+ $dateObj->setTimezone(new \DateTimeZone('UTC'));
+ }
+ return $dateObj->format('Y-m-d H:i:s T');
+ }
}
diff --git a/app/Livewire/Project/Shared/ScheduledTask/Executions.php b/app/Livewire/Project/Shared/ScheduledTask/Executions.php
index 7a2e14e89..5bd6b4b9b 100644
--- a/app/Livewire/Project/Shared/ScheduledTask/Executions.php
+++ b/app/Livewire/Project/Shared/ScheduledTask/Executions.php
@@ -7,8 +7,8 @@
class Executions extends Component
{
public $executions = [];
-
public $selectedKey;
+ public $task;
public function getListeners()
{
@@ -26,4 +26,44 @@ public function selectTask($key): void
}
$this->selectedKey = $key;
}
+
+ public function server()
+ {
+ if (!$this->task) {
+ return null;
+ }
+
+ if ($this->task->application) {
+ if ($this->task->application->destination && $this->task->application->destination->server) {
+ return $this->task->application->destination->server;
+ }
+ } elseif ($this->task->service) {
+ if ($this->task->service->destination && $this->task->service->destination->server) {
+ return $this->task->service->destination->server;
+ }
+ }
+ return null;
+ }
+
+ public function getServerTimezone()
+ {
+ $server = $this->server();
+ if (!$server) {
+ return 'UTC';
+ }
+ $serverTimezone = $server->settings->server_timezone;
+ return $serverTimezone;
+ }
+
+ public function formatDateInServerTimezone($date)
+ {
+ $serverTimezone = $this->getServerTimezone();
+ $dateObj = new \DateTime($date);
+ try {
+ $dateObj->setTimezone(new \DateTimeZone($serverTimezone));
+ } catch (\Exception $e) {
+ $dateObj->setTimezone(new \DateTimeZone('UTC'));
+ }
+ return $dateObj->format('Y-m-d H:i:s T');
+ }
}
diff --git a/app/Livewire/Server/Form.php b/app/Livewire/Server/Form.php
index 1ab70c004..106d3d761 100644
--- a/app/Livewire/Server/Form.php
+++ b/app/Livewire/Server/Form.php
@@ -5,7 +5,8 @@
use App\Actions\Server\StartSentinel;
use App\Actions\Server\StopSentinel;
use App\Jobs\PullSentinelImageJob;
-use App\Models\Server;
+use App\Models\Server;;
+
use Livewire\Component;
class Form extends Component
@@ -43,6 +44,7 @@ class Form extends Component
'server.settings.metrics_history_days' => 'required|integer|min:1',
'wildcard_domain' => 'nullable|url',
'server.settings.is_server_api_enabled' => 'required|boolean',
+ 'server.settings.server_timezone' => 'required|string|timezone',
'server.settings.force_docker_cleanup' => 'required|boolean',
'server.settings.docker_cleanup_frequency' => 'required_if:server.settings.force_docker_cleanup,true|string',
'server.settings.docker_cleanup_threshold' => 'required_if:server.settings.force_docker_cleanup,false|integer|min:1|max:100',
@@ -66,10 +68,15 @@ class Form extends Component
'server.settings.metrics_refresh_rate_seconds' => 'Metrics Interval',
'server.settings.metrics_history_days' => 'Metrics History',
'server.settings.is_server_api_enabled' => 'Server API',
+ 'server.settings.server_timezone' => 'Server Timezone',
];
- public function mount()
+ public $timezones;
+
+ public function mount(Server $server)
{
+ $this->server = $server;
+ $this->timezones = collect(timezone_identifiers_list())->sort()->values()->toArray();
$this->wildcard_domain = $this->server->settings->wildcard_domain;
$this->server->settings->docker_cleanup_threshold = $this->server->settings->docker_cleanup_threshold;
$this->server->settings->docker_cleanup_frequency = $this->server->settings->docker_cleanup_frequency;
@@ -167,7 +174,7 @@ public function checkLocalhostConnection()
$this->server->settings->save();
$this->dispatch('proxyStatusUpdated');
} else {
- $this->dispatch('error', 'Server is not reachable.', 'Please validate your configuration and connection.
Check this documentation for further help.
Error: '.$error);
+ $this->dispatch('error', 'Server is not reachable.', 'Please validate your configuration and connection.
Check this documentation for further help.
Error: ' . $error);
return;
}
@@ -207,6 +214,23 @@ public function submit()
} else {
$this->server->settings->docker_cleanup_threshold = $this->server->settings->docker_cleanup_threshold;
}
+ $currentTimezone = $this->server->settings->getOriginal('server_timezone');
+ $newTimezone = $this->server->settings->server_timezone;
+ if ($currentTimezone !== $newTimezone || $currentTimezone === '') {
+ try {
+ $timezoneUpdated = $this->updateServerTimezone($newTimezone);
+ if ($timezoneUpdated) {
+ $this->server->settings->server_timezone = $newTimezone;
+ $this->server->settings->save();
+ } else {
+ return;
+ }
+ } catch (\Exception $e) {
+ $this->dispatch('error', 'Failed to update server timezone: ' . $e->getMessage());
+ return;
+ }
+ }
+
$this->server->settings->save();
$this->server->save();
$this->dispatch('success', 'Server updated.');
@@ -214,4 +238,90 @@ public function submit()
return handleError($e, $this);
}
}
+
+ public function updatedServerTimezone($value)
+ {
+ if (!is_string($value) || !in_array($value, timezone_identifiers_list())) {
+ $this->addError('server.settings.server_timezone', 'Invalid timezone.');
+ return;
+ }
+ $this->server->settings->server_timezone = $value;
+ $this->updateServerTimezone($value);
+ }
+
+ private function updateServerTimezone($desired_timezone)
+ {
+ try {
+ $commands = [
+ "if command -v timedatectl > /dev/null 2>&1 && pidof systemd > /dev/null; then",
+ " timedatectl set-timezone " . escapeshellarg($desired_timezone),
+ "elif [ -f /etc/timezone ]; then",
+ " echo " . escapeshellarg($desired_timezone) . " > /etc/timezone",
+ " rm -f /etc/localtime",
+ " ln -sf /usr/share/zoneinfo/" . escapeshellarg($desired_timezone) . " /etc/localtime",
+ "elif [ -f /etc/localtime ]; then",
+ " rm -f /etc/localtime",
+ " ln -sf /usr/share/zoneinfo/" . escapeshellarg($desired_timezone) . " /etc/localtime",
+ "else",
+ " echo 'Unable to set timezone'",
+ " exit 1",
+ "fi",
+ "if command -v dpkg-reconfigure > /dev/null 2>&1; then",
+ " dpkg-reconfigure -f noninteractive tzdata",
+ "elif command -v tzdata-update > /dev/null 2>&1; then",
+ " tzdata-update",
+ "elif [ -f /etc/sysconfig/clock ]; then",
+ " sed -i 's/^ZONE=.*/ZONE=\"" . $desired_timezone . "\"/' /etc/sysconfig/clock",
+ " source /etc/sysconfig/clock",
+ "fi",
+ "if command -v systemctl > /dev/null 2>&1 && pidof systemd > /dev/null; then",
+ " systemctl try-restart systemd-timesyncd.service || true",
+ "elif command -v service > /dev/null 2>&1; then",
+ " service ntpd restart || service ntp restart || true",
+ "fi",
+ "echo \"Timezone updated to: $desired_timezone\"",
+ "date"
+ ];
+
+ instant_remote_process($commands, $this->server);
+
+ $verificationCommands = [
+ "readlink /etc/localtime | sed 's#/usr/share/zoneinfo/##'",
+ "date +'%Z %:z'"
+ ];
+ $verificationResult = instant_remote_process($verificationCommands, $this->server, false);
+ $verificationLines = explode("\n", trim($verificationResult));
+
+ if (count($verificationLines) !== 2) {
+ $this->dispatch('error', 'Failed to verify timezone update. Unexpected server response.');
+ return false;
+ }
+
+ $actualTimezone = trim($verificationLines[0]);
+ [$abbreviation, $offset] = explode(' ', trim($verificationLines[1]));
+
+ $desiredTz = new \DateTimeZone($desired_timezone);
+ $desiredAbbr = (new \DateTime('now', $desiredTz))->format('T');
+ $desiredOffset = $this->formatOffset($desiredTz->getOffset(new \DateTime('now', $desiredTz)));
+
+ if ($actualTimezone === $desired_timezone && $abbreviation === $desiredAbbr && $offset === $desiredOffset) {
+ $this->server->settings->server_timezone = $desired_timezone;
+ $this->server->settings->save();
+ return true;
+ } else {
+ $this->dispatch('error', 'Failed to update server timezone. The server reported a different timezone than requested.');
+ return false;
+ }
+ } catch (\Exception $e) {
+ $this->dispatch('error', 'Failed to update server timezone: ' . $e->getMessage());
+ return false;
+ }
+ }
+
+ private function formatOffset($offsetSeconds)
+ {
+ $hours = abs($offsetSeconds) / 3600;
+ $minutes = (abs($offsetSeconds) % 3600) / 60;
+ return sprintf('%s%02d:%02d', $offsetSeconds >= 0 ? '+' : '-', $hours, $minutes);
+ }
}
diff --git a/app/Livewire/Settings/Index.php b/app/Livewire/Settings/Index.php
index f593fb78b..eff09f0e5 100644
--- a/app/Livewire/Settings/Index.php
+++ b/app/Livewire/Settings/Index.php
@@ -40,6 +40,7 @@ class Index extends Component
'settings.is_auto_update_enabled' => 'boolean',
'auto_update_frequency' => 'string',
'update_check_frequency' => 'string',
+ 'settings.instance_timezone' => 'required|string|timezone',
];
protected $validationAttributes = [
@@ -54,6 +55,8 @@ class Index extends Component
'update_check_frequency' => 'Update Check Frequency',
];
+ public $timezones;
+
public function mount()
{
if (isInstanceAdmin()) {
@@ -65,6 +68,7 @@ public function mount()
$this->is_api_enabled = $this->settings->is_api_enabled;
$this->auto_update_frequency = $this->settings->auto_update_frequency;
$this->update_check_frequency = $this->settings->update_check_frequency;
+ $this->timezones = collect(timezone_identifiers_list())->sort()->values()->toArray();
} else {
return redirect()->route('dashboard');
}
@@ -170,4 +174,4 @@ public function render()
{
return view('livewire.settings.index');
}
-}
+}
\ No newline at end of file
diff --git a/app/Models/ScheduledDatabaseBackup.php b/app/Models/ScheduledDatabaseBackup.php
index edd840e7d..2d0e200da 100644
--- a/app/Models/ScheduledDatabaseBackup.php
+++ b/app/Models/ScheduledDatabaseBackup.php
@@ -34,4 +34,14 @@ public function get_last_days_backup_status($days = 7)
{
return $this->hasMany(ScheduledDatabaseBackupExecution::class)->where('created_at', '>=', now()->subDays($days))->get();
}
+ public function server()
+ {
+ if ($this->database) {
+ if ($this->database->destination && $this->database->destination->server) {
+ $server = $this->database->destination->server;
+ return $server;
+ }
+ }
+ return null;
+ }
}
diff --git a/app/Models/ScheduledTask.php b/app/Models/ScheduledTask.php
index afff4ef11..7b1c0d275 100644
--- a/app/Models/ScheduledTask.php
+++ b/app/Models/ScheduledTask.php
@@ -4,6 +4,8 @@
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasOne;
+use App\Models\Service;
+use App\Models\Application;
class ScheduledTask extends BaseModel
{
@@ -28,4 +30,25 @@ public function executions(): HasMany
{
return $this->hasMany(ScheduledTaskExecution::class)->orderBy('created_at', 'desc');
}
+
+ public function server()
+ {
+ if ($this->application) {
+ if ($this->application->destination && $this->application->destination->server) {
+ $server = $this->application->destination->server;
+ return $server;
+ }
+ } elseif ($this->service) {
+ if ($this->service->destination && $this->service->destination->server) {
+ $server = $this->service->destination->server;
+ return $server;
+ }
+ } elseif ($this->database) {
+ if ($this->database->destination && $this->database->destination->server) {
+ $server = $this->database->destination->server;
+ return $server;
+ }
+ }
+ return null;
+ }
}
diff --git a/database/migrations/2024_08_12_155023_add_timezone_to_server_and_instance_settings.php b/database/migrations/2024_08_12_155023_add_timezone_to_server_and_instance_settings.php
new file mode 100644
index 000000000..5bc73a54e
--- /dev/null
+++ b/database/migrations/2024_08_12_155023_add_timezone_to_server_and_instance_settings.php
@@ -0,0 +1,30 @@
+string('server_timezone')->default('');
+ });
+
+ Schema::table('instance_settings', function (Blueprint $table) {
+ $table->string('instance_timezone')->default('UTC');
+ });
+ }
+
+ public function down()
+ {
+ Schema::table('server_settings', function (Blueprint $table) {
+ $table->dropColumn('server_timezone');
+ });
+
+ Schema::table('instance_settings', function (Blueprint $table) {
+ $table->dropColumn('instance_timezone');
+ });
+ }
+}
diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php
index b3fac350f..736646ec6 100644
--- a/database/seeders/DatabaseSeeder.php
+++ b/database/seeders/DatabaseSeeder.php
@@ -33,6 +33,7 @@ public function run(): void
ScheduledDatabaseBackupSeeder::class,
ScheduledDatabaseBackupExecutionSeeder::class,
OauthSettingSeeder::class,
+ ServerTimezoneSeeder::class,
]);
}
}
diff --git a/database/seeders/ProductionSeeder.php b/database/seeders/ProductionSeeder.php
index c88a35f6a..a3bdab536 100644
--- a/database/seeders/ProductionSeeder.php
+++ b/database/seeders/ProductionSeeder.php
@@ -79,7 +79,8 @@ public function run(): void
],
[
'name' => 'localhost\'s key',
- 'description' => 'The private key for the Coolify host machine (localhost).', 'private_key' => $coolify_key,
+ 'description' => 'The private key for the Coolify host machine (localhost).',
+ 'private_key' => $coolify_key,
]
);
} else {
@@ -180,5 +181,8 @@ public function run(): void
$oauth_settings_seeder = new OauthSettingSeeder;
$oauth_settings_seeder->run();
+
+ $server_timezone_seeder = new ServerTimezoneSeeder;
+ $server_timezone_seeder->run();
}
}
diff --git a/database/seeders/ServerTimezoneSeeder.php b/database/seeders/ServerTimezoneSeeder.php
new file mode 100644
index 000000000..9dd6636f9
--- /dev/null
+++ b/database/seeders/ServerTimezoneSeeder.php
@@ -0,0 +1,58 @@
+whereNull('server_timezone')->orWhere('server_timezone', '');
+ })->each(function ($server) use ($defaultTimezone) {
+ DB::transaction(function () use ($server, $defaultTimezone) {
+ $this->updateServerTimezone($server, $defaultTimezone);
+ });
+ });
+ }
+
+ private function updateServerTimezone($server, $desired_timezone)
+ {
+ $commands = [
+ "if command -v timedatectl > /dev/null 2>&1 && pidof systemd > /dev/null; then",
+ " timedatectl set-timezone " . escapeshellarg($desired_timezone),
+ "elif [ -f /etc/timezone ]; then",
+ " echo " . escapeshellarg($desired_timezone) . " > /etc/timezone",
+ " rm -f /etc/localtime",
+ " ln -sf /usr/share/zoneinfo/" . escapeshellarg($desired_timezone) . " /etc/localtime",
+ "elif [ -f /etc/localtime ]; then",
+ " rm -f /etc/localtime",
+ " ln -sf /usr/share/zoneinfo/" . escapeshellarg($desired_timezone) . " /etc/localtime",
+ "fi",
+ "if command -v dpkg-reconfigure > /dev/null 2>&1; then",
+ " dpkg-reconfigure -f noninteractive tzdata",
+ "elif command -v tzdata-update > /dev/null 2>&1; then",
+ " tzdata-update",
+ "elif [ -f /etc/sysconfig/clock ]; then",
+ " sed -i 's/^ZONE=.*/ZONE=\"" . $desired_timezone . "\"/' /etc/sysconfig/clock",
+ " source /etc/sysconfig/clock",
+ "fi",
+ "if command -v systemctl > /dev/null 2>&1 && pidof systemd > /dev/null; then",
+ " systemctl try-restart systemd-timesyncd.service || true",
+ "elif command -v service > /dev/null 2>&1; then",
+ " service ntpd restart || service ntp restart || true",
+ "fi"
+ ];
+
+ instant_remote_process($commands, $server);
+
+ $server->settings->server_timezone = $desired_timezone;
+ $server->settings->save();
+ }
+}
diff --git a/resources/views/livewire/project/database/backup-executions.blade.php b/resources/views/livewire/project/database/backup-executions.blade.php
index 644ac9fa4..b1cc7fa66 100644
--- a/resources/views/livewire/project/database/backup-executions.blade.php
+++ b/resources/views/livewire/project/database/backup-executions.blade.php
@@ -1,57 +1,66 @@
{{ data_get($execution, 'message') }}+
{{ data_get($execution, 'message') }}-
{{ data_get($execution, 'message') }}+