diff --git a/.gitattributes b/.gitattributes index 78f41d7a8..c48a5898b 100644 --- a/.gitattributes +++ b/.gitattributes @@ -5,3 +5,7 @@ *.html diff=html *.md diff=markdown *.php diff=php + +/.github export-ignore +CHANGELOG.md export-ignore +.styleci.yml export-ignore \ No newline at end of file diff --git a/README.md b/README.md index d77719a0d..8868bcea6 100644 --- a/README.md +++ b/README.md @@ -142,12 +142,10 @@ By subscribing to the cloud version, you get the Coolify server for the same pri # Core Maintainers - - andrasbacsai - - - peaklabs-dev - +| Andras Bacsai | Peak | +|------------|------------| +| Andras Bacsai | Peak Labs | +| | | # Repo Activity diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index 3fb4de60b..e113dbe9a 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -28,6 +28,8 @@ class Kernel extends ConsoleKernel { private $allServers; + private Schedule $scheduleInstance; + private InstanceSettings $settings; private string $updateCheckFrequency; @@ -36,82 +38,90 @@ class Kernel extends ConsoleKernel protected function schedule(Schedule $schedule): void { + $this->scheduleInstance = $schedule; $this->allServers = Server::where('ip', '!=', '1.2.3.4'); $this->settings = instanceSettings(); $this->updateCheckFrequency = $this->settings->update_check_frequency ?: '0 * * * *'; + $this->instanceTimezone = $this->settings->instance_timezone ?: config('app.timezone'); - $schedule->job(new CleanupStaleMultiplexedConnections)->hourly(); + if (validate_timezone($this->instanceTimezone) === false) { + $this->instanceTimezone = config('app.timezone'); + } + + $this->scheduleInstance->job(new CleanupStaleMultiplexedConnections)->hourly(); if (isDev()) { // Instance Jobs - $schedule->command('horizon:snapshot')->everyMinute(); - $schedule->job(new CleanupInstanceStuffsJob)->everyMinute()->onOneServer(); - $schedule->job(new CheckHelperImageJob)->everyTenMinutes()->onOneServer(); + $this->scheduleInstance->command('horizon:snapshot')->everyMinute(); + $this->scheduleInstance->job(new CleanupInstanceStuffsJob)->everyMinute()->onOneServer(); + $this->scheduleInstance->job(new CheckHelperImageJob)->everyTenMinutes()->onOneServer(); // Server Jobs - $this->checkResources($schedule); + $this->checkResources(); - $this->checkScheduledBackups($schedule); - $this->checkScheduledTasks($schedule); + $this->checkScheduledBackups(); + $this->checkScheduledTasks(); - $schedule->command('uploads:clear')->everyTwoMinutes(); + $this->scheduleInstance->command('uploads:clear')->everyTwoMinutes(); } else { // Instance Jobs - $schedule->command('horizon:snapshot')->everyFiveMinutes(); - $schedule->command('cleanup:unreachable-servers')->daily()->onOneServer(); - $schedule->job(new PullTemplatesFromCDN)->cron($this->updateCheckFrequency)->timezone($this->instanceTimezone)->onOneServer(); - $schedule->job(new CleanupInstanceStuffsJob)->everyTwoMinutes()->onOneServer(); - $this->scheduleUpdates($schedule); + $this->scheduleInstance->command('horizon:snapshot')->everyFiveMinutes(); + $this->scheduleInstance->command('cleanup:unreachable-servers')->daily()->onOneServer(); + + $this->scheduleInstance->job(new PullTemplatesFromCDN)->cron($this->updateCheckFrequency)->timezone($this->instanceTimezone)->onOneServer(); + + $this->scheduleInstance->job(new CleanupInstanceStuffsJob)->everyTwoMinutes()->onOneServer(); + $this->scheduleUpdates(); // Server Jobs - $this->checkResources($schedule); + $this->checkResources(); - $this->pullImages($schedule); + $this->pullImages(); - $this->checkScheduledBackups($schedule); - $this->checkScheduledTasks($schedule); + $this->checkScheduledBackups(); + $this->checkScheduledTasks(); - $schedule->command('cleanup:database --yes')->daily(); - $schedule->command('uploads:clear')->everyTwoMinutes(); + $this->scheduleInstance->command('cleanup:database --yes')->daily(); + $this->scheduleInstance->command('uploads:clear')->everyTwoMinutes(); } } - private function pullImages($schedule): void + private function pullImages(): void { $servers = $this->allServers->whereRelation('settings', 'is_usable', true)->whereRelation('settings', 'is_reachable', true)->get(); foreach ($servers as $server) { if ($server->isSentinelEnabled()) { - $schedule->job(function () use ($server) { + $this->scheduleInstance->job(function () use ($server) { CheckAndStartSentinelJob::dispatch($server); })->cron($this->updateCheckFrequency)->timezone($this->instanceTimezone)->onOneServer(); } } - $schedule->job(new CheckHelperImageJob) + $this->scheduleInstance->job(new CheckHelperImageJob) ->cron($this->updateCheckFrequency) ->timezone($this->instanceTimezone) ->onOneServer(); } - private function scheduleUpdates($schedule): void + private function scheduleUpdates(): void { - $schedule->job(new CheckForUpdatesJob) + $this->scheduleInstance->job(new CheckForUpdatesJob) ->cron($this->updateCheckFrequency) ->timezone($this->instanceTimezone) ->onOneServer(); if ($this->settings->is_auto_update_enabled) { $autoUpdateFrequency = $this->settings->auto_update_frequency; - $schedule->job(new UpdateCoolifyJob) + $this->scheduleInstance->job(new UpdateCoolifyJob) ->cron($autoUpdateFrequency) ->timezone($this->instanceTimezone) ->onOneServer(); } } - private function checkResources($schedule): void + private function checkResources(): void { if (isCloud()) { $servers = $this->allServers->whereHas('team.subscription')->get(); @@ -128,31 +138,34 @@ class Kernel extends ConsoleKernel $lastSentinelUpdate = $server->sentinel_updated_at; if (Carbon::parse($lastSentinelUpdate)->isBefore(now()->subSeconds($server->waitBeforeDoingSshCheck()))) { // Check container status every minute if Sentinel does not activated - $schedule->job(new ServerCheckJob($server))->everyMinute()->onOneServer(); - // $schedule->job(new \App\Jobs\ServerCheckNewJob($server))->everyMinute()->onOneServer(); + if (validate_timezone($serverTimezone) === false) { + $serverTimezone = config('app.timezone'); + } + $this->scheduleInstance->job(new ServerCheckJob($server))->timezone($serverTimezone)->everyMinute()->onOneServer(); + // $this->scheduleInstance->job(new \App\Jobs\ServerCheckNewJob($server))->everyMinute()->onOneServer(); // Check storage usage every 10 minutes if Sentinel does not activated - $schedule->job(new ServerStorageCheckJob($server))->everyTenMinutes()->onOneServer(); + $this->scheduleInstance->job(new ServerStorageCheckJob($server))->everyTenMinutes()->onOneServer(); } if ($server->settings->force_docker_cleanup) { - $schedule->job(new DockerCleanupJob($server))->cron($server->settings->docker_cleanup_frequency)->timezone($serverTimezone)->onOneServer(); + $this->scheduleInstance->job(new DockerCleanupJob($server))->cron($server->settings->docker_cleanup_frequency)->timezone($serverTimezone)->onOneServer(); } else { - $schedule->job(new DockerCleanupJob($server))->everyTenMinutes()->timezone($serverTimezone)->onOneServer(); + $this->scheduleInstance->job(new DockerCleanupJob($server))->everyTenMinutes()->timezone($serverTimezone)->onOneServer(); } // Cleanup multiplexed connections every hour - $schedule->job(new ServerCleanupMux($server))->hourly()->onOneServer(); + $this->scheduleInstance->job(new ServerCleanupMux($server))->hourly()->onOneServer(); // Temporary solution until we have better memory management for Sentinel if ($server->isSentinelEnabled()) { - $schedule->job(function () use ($server) { + $this->scheduleInstance->job(function () use ($server) { $server->restartContainer('coolify-sentinel'); })->daily()->onOneServer(); } } } - private function checkScheduledBackups($schedule): void + private function checkScheduledBackups(): void { $scheduled_backups = ScheduledDatabaseBackup::where('enabled', true)->get(); if ($scheduled_backups->isEmpty()) { @@ -174,13 +187,13 @@ class Kernel extends ConsoleKernel if (isset(VALID_CRON_STRINGS[$scheduled_backup->frequency])) { $scheduled_backup->frequency = VALID_CRON_STRINGS[$scheduled_backup->frequency]; } - $schedule->job(new DatabaseBackupJob( + $this->scheduleInstance->job(new DatabaseBackupJob( backup: $scheduled_backup ))->cron($scheduled_backup->frequency)->timezone($this->instanceTimezone)->onOneServer(); } } - private function checkScheduledTasks($schedule): void + private function checkScheduledTasks(): void { $scheduled_tasks = ScheduledTask::where('enabled', true)->get(); if ($scheduled_tasks->isEmpty()) { @@ -214,7 +227,7 @@ class Kernel extends ConsoleKernel if (isset(VALID_CRON_STRINGS[$scheduled_task->frequency])) { $scheduled_task->frequency = VALID_CRON_STRINGS[$scheduled_task->frequency]; } - $schedule->job(new ScheduledTaskJob( + $this->scheduleInstance->job(new ScheduledTaskJob( task: $scheduled_task ))->cron($scheduled_task->frequency)->timezone($this->instanceTimezone)->onOneServer(); } diff --git a/app/Jobs/DockerCleanupJob.php b/app/Jobs/DockerCleanupJob.php index 0d7e63dd2..80542e03b 100644 --- a/app/Jobs/DockerCleanupJob.php +++ b/app/Jobs/DockerCleanupJob.php @@ -26,7 +26,7 @@ class DockerCleanupJob implements ShouldBeEncrypted, ShouldQueue public function middleware(): array { - return [(new WithoutOverlapping($this->server->id))->dontRelease()]; + return [(new WithoutOverlapping($this->server->uuid))->dontRelease()]; } public function __construct(public Server $server, public bool $manualCleanup = false) {} diff --git a/app/Jobs/ServerCheckJob.php b/app/Jobs/ServerCheckJob.php index c584f493d..0d5e2fd36 100644 --- a/app/Jobs/ServerCheckJob.php +++ b/app/Jobs/ServerCheckJob.php @@ -28,7 +28,7 @@ class ServerCheckJob implements ShouldBeEncrypted, ShouldQueue public function middleware(): array { - return [(new WithoutOverlapping($this->server->id))->dontRelease()]; + return [(new WithoutOverlapping($this->server->uuid))->dontRelease()]; } public function __construct(public Server $server) {} diff --git a/app/Livewire/Server/Show.php b/app/Livewire/Server/Show.php index bb9188f1c..5a1743f96 100644 --- a/app/Livewire/Server/Show.php +++ b/app/Livewire/Server/Show.php @@ -127,7 +127,14 @@ class Show extends Component $this->server->settings->sentinel_custom_url = $this->sentinelCustomUrl; $this->server->settings->is_sentinel_enabled = $this->isSentinelEnabled; $this->server->settings->is_sentinel_debug_enabled = $this->isSentinelDebugEnabled; - $this->server->settings->server_timezone = $this->serverTimezone; + + if (! validate_timezone($this->serverTimezone)) { + $this->serverTimezone = config('app.timezone'); + throw new \Exception('Invalid timezone.'); + } else { + $this->server->settings->server_timezone = $this->serverTimezone; + } + $this->server->settings->save(); } else { $this->name = $this->server->name; diff --git a/app/Livewire/Settings/Index.php b/app/Livewire/Settings/Index.php index 2991b8ae8..55ba49867 100644 --- a/app/Livewire/Settings/Index.php +++ b/app/Livewire/Settings/Index.php @@ -139,6 +139,14 @@ class Index extends Component $error_show = false; $this->server = Server::findOrFail(0); $this->resetErrorBag(); + + if (! validate_timezone($this->instance_timezone)) { + $this->instance_timezone = config('app.timezone'); + throw new \Exception('Invalid timezone.'); + } else { + $this->settings->instance_timezone = $this->instance_timezone; + } + if ($this->settings->public_port_min > $this->settings->public_port_max) { $this->addError('settings.public_port_min', 'The minimum port must be lower than the maximum port.'); diff --git a/bootstrap/helpers/docker.php b/bootstrap/helpers/docker.php index b3d7f04cd..302193131 100644 --- a/bootstrap/helpers/docker.php +++ b/bootstrap/helpers/docker.php @@ -394,7 +394,7 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_ $middlewares->push('gzip'); } if (str($image)->contains('ghost')) { - $middlewares->push('redir-ghost'); + $middlewares->push("redir-ghost-{$uuid}"); } if ($redirect_direction === 'non-www' && str($host)->startsWith('www.')) { $labels = $labels->merge($redirect_to_non_www); @@ -417,7 +417,7 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_ $middlewares->push('gzip'); } if (str($image)->contains('ghost')) { - $middlewares->push('redir-ghost'); + $middlewares->push("redir-ghost-{$uuid}"); } if ($redirect_direction === 'non-www' && str($host)->startsWith('www.')) { $labels = $labels->merge($redirect_to_non_www); @@ -466,7 +466,7 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_ $middlewares->push('gzip'); } if (str($image)->contains('ghost')) { - $middlewares->push('redir-ghost'); + $middlewares->push("redir-ghost-{$uuid}"); } if ($redirect_direction === 'non-www' && str($host)->startsWith('www.')) { $labels = $labels->merge($redirect_to_non_www); @@ -489,7 +489,7 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_ $middlewares->push('gzip'); } if (str($image)->contains('ghost')) { - $middlewares->push('redir-ghost'); + $middlewares->push("redir-ghost-{$uuid}"); } if ($redirect_direction === 'non-www' && str($host)->startsWith('www.')) { $labels = $labels->merge($redirect_to_non_www); diff --git a/bootstrap/helpers/shared.php b/bootstrap/helpers/shared.php index d8334383b..f746ad7ce 100644 --- a/bootstrap/helpers/shared.php +++ b/bootstrap/helpers/shared.php @@ -385,6 +385,11 @@ function validate_cron_expression($expression_to_validate): bool return $isValid; } + +function validate_timezone(string $timezone): bool +{ + return in_array($timezone, timezone_identifiers_list()); +} function send_internal_notification(string $message): void { try { diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index e60defbce..05b9f9cfb 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -74,8 +74,9 @@ services: networks: - coolify testing-host: - image: "ghcr.io/coollabsio/coolify-testing-host:latest" - pull_policy: always + build: + context: . + dockerfile: ./docker/testing-host/Dockerfile init: true container_name: coolify-testing-host volumes: diff --git a/docker/dev/etc/s6-overlay/s6-rc.d/horizon/run b/docker/dev/etc/s6-overlay/s6-rc.d/horizon/run index a67e6bbd1..87471097e 100644 --- a/docker/dev/etc/s6-overlay/s6-rc.d/horizon/run +++ b/docker/dev/etc/s6-overlay/s6-rc.d/horizon/run @@ -1,5 +1,5 @@ #!/command/execlineb -P foreground { s6-sleep 5 - su - www-data -c "php /var/www/html/artisan start:horizon" + su - webuser -c "php /var/www/html/artisan start:horizon" } diff --git a/docker/dev/etc/s6-overlay/s6-rc.d/scheduler-worker/run b/docker/dev/etc/s6-overlay/s6-rc.d/scheduler-worker/run index bd371a5a5..87ca0cae1 100644 --- a/docker/dev/etc/s6-overlay/s6-rc.d/scheduler-worker/run +++ b/docker/dev/etc/s6-overlay/s6-rc.d/scheduler-worker/run @@ -1,5 +1,5 @@ #!/command/execlineb -P foreground { s6-sleep 5 - su - www-data -c "php /var/www/html/artisan start:scheduler" + su - webuser -c "php /var/www/html/artisan start:scheduler" } diff --git a/docker/prod/etc/s6-overlay/s6-rc.d/horizon/run b/docker/prod/etc/s6-overlay/s6-rc.d/horizon/run index a67e6bbd1..87471097e 100644 --- a/docker/prod/etc/s6-overlay/s6-rc.d/horizon/run +++ b/docker/prod/etc/s6-overlay/s6-rc.d/horizon/run @@ -1,5 +1,5 @@ #!/command/execlineb -P foreground { s6-sleep 5 - su - www-data -c "php /var/www/html/artisan start:horizon" + su - webuser -c "php /var/www/html/artisan start:horizon" } diff --git a/docker/prod/etc/s6-overlay/s6-rc.d/init-script/up b/docker/prod/etc/s6-overlay/s6-rc.d/init-script/up index 3d8c4cfa8..3b252b782 100644 --- a/docker/prod/etc/s6-overlay/s6-rc.d/init-script/up +++ b/docker/prod/etc/s6-overlay/s6-rc.d/init-script/up @@ -1,3 +1,3 @@ #!/command/execlineb -P -s6-setuidgid www-data +s6-setuidgid webuser php /var/www/html/artisan app:init diff --git a/docker/prod/etc/s6-overlay/s6-rc.d/scheduler-worker/run b/docker/prod/etc/s6-overlay/s6-rc.d/scheduler-worker/run index bd371a5a5..87ca0cae1 100644 --- a/docker/prod/etc/s6-overlay/s6-rc.d/scheduler-worker/run +++ b/docker/prod/etc/s6-overlay/s6-rc.d/scheduler-worker/run @@ -1,5 +1,5 @@ #!/command/execlineb -P foreground { s6-sleep 5 - su - www-data -c "php /var/www/html/artisan start:scheduler" + su - webuser -c "php /var/www/html/artisan start:scheduler" } diff --git a/resources/views/components/forms/checkbox.blade.php b/resources/views/components/forms/checkbox.blade.php index c8c4a499f..ef4d5a7f3 100644 --- a/resources/views/components/forms/checkbox.blade.php +++ b/resources/views/components/forms/checkbox.blade.php @@ -14,8 +14,8 @@ 'w-full' => $fullWidth, ])> @if (!$hideLabel) -