diff --git a/app/Actions/Server/CheckUpdates.php b/app/Actions/Server/CheckUpdates.php
index 55b06e863..a8b1be11d 100644
--- a/app/Actions/Server/CheckUpdates.php
+++ b/app/Actions/Server/CheckUpdates.php
@@ -83,8 +83,6 @@ class CheckUpdates
$out = $this->parseDnfOutput($output);
$out['osId'] = $osId;
$out['package_manager'] = $packageManager;
- $rebootRequired = instant_remote_process(['LANG=C dnf needs-restarting -r'], $server);
- $out['reboot_required'] = $rebootRequired !== '0';
return $out;
case 'apt':
@@ -94,8 +92,6 @@ class CheckUpdates
$out = $this->parseAptOutput($output);
$out['osId'] = $osId;
$out['package_manager'] = $packageManager;
- $rebootRequired = instant_remote_process(['LANG=C test -f /var/run/reboot-required && echo "YES" || echo "NO"'], $server);
- $out['reboot_required'] = $rebootRequired === 'YES' ? true : false;
return $out;
default:
diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php
index c03475647..372a6c119 100644
--- a/app/Console/Kernel.php
+++ b/app/Console/Kernel.php
@@ -12,6 +12,7 @@ use App\Jobs\PullTemplatesFromCDN;
use App\Jobs\RegenerateSslCertJob;
use App\Jobs\ScheduledTaskJob;
use App\Jobs\ServerCheckJob;
+use App\Jobs\ServerPatchCheckJob;
use App\Jobs\ServerStorageCheckJob;
use App\Jobs\UpdateCoolifyJob;
use App\Models\InstanceSettings;
@@ -175,6 +176,9 @@ class Kernel extends ConsoleKernel
}
$this->scheduleInstance->job(new DockerCleanupJob($server))->cron($dockerCleanupFrequency)->timezone($serverTimezone)->onOneServer();
+ // Server patch check - weekly
+ $this->scheduleInstance->job(new ServerPatchCheckJob($server))->weekly()->timezone($serverTimezone)->onOneServer();
+
// Cleanup multiplexed connections every hour
// $this->scheduleInstance->job(new ServerCleanupMux($server))->hourly()->onOneServer();
diff --git a/app/Jobs/ServerPatchCheckJob.php b/app/Jobs/ServerPatchCheckJob.php
new file mode 100644
index 000000000..c88770d3a
--- /dev/null
+++ b/app/Jobs/ServerPatchCheckJob.php
@@ -0,0 +1,66 @@
+server->uuid))->dontRelease()];
+ }
+
+ public function __construct(public Server $server) {}
+
+ public function handle(): void
+ {
+ try {
+ if ($this->server->isFunctional() === false) {
+ return;
+ }
+
+ $team = data_get($this->server, 'team');
+ if (! $team) {
+ return;
+ }
+
+ // Check for updates
+ $patchData = CheckUpdates::run($this->server);
+
+ if (isset($patchData['error'])) {
+ return; // Skip if there's an error checking for updates
+ }
+
+ $totalUpdates = $patchData['total_updates'] ?? 0;
+
+ // Only send notification if there are updates available
+ if ($totalUpdates > 0) {
+ $team->notify(new ServerPatchCheck($this->server, $patchData));
+ }
+ } catch (\Throwable $e) {
+ // Log error but don't fail the job
+ \Illuminate\Support\Facades\Log::error('ServerPatchCheckJob failed: '.$e->getMessage(), [
+ 'server_id' => $this->server->id,
+ 'server_name' => $this->server->name,
+ 'error' => $e->getMessage(),
+ 'trace' => $e->getTraceAsString(),
+ ]);
+ }
+ }
+}
diff --git a/app/Livewire/Notifications/Discord.php b/app/Livewire/Notifications/Discord.php
index 9489eb128..e0425fa17 100644
--- a/app/Livewire/Notifications/Discord.php
+++ b/app/Livewire/Notifications/Discord.php
@@ -56,6 +56,9 @@ class Discord extends Component
#[Validate(['boolean'])]
public bool $serverUnreachableDiscordNotifications = true;
+ #[Validate(['boolean'])]
+ public bool $serverPatchDiscordNotifications = false;
+
#[Validate(['boolean'])]
public bool $discordPingEnabled = true;
@@ -89,6 +92,7 @@ class Discord extends Component
$this->settings->server_disk_usage_discord_notifications = $this->serverDiskUsageDiscordNotifications;
$this->settings->server_reachable_discord_notifications = $this->serverReachableDiscordNotifications;
$this->settings->server_unreachable_discord_notifications = $this->serverUnreachableDiscordNotifications;
+ $this->settings->server_patch_discord_notifications = $this->serverPatchDiscordNotifications;
$this->settings->discord_ping_enabled = $this->discordPingEnabled;
@@ -110,6 +114,7 @@ class Discord extends Component
$this->serverDiskUsageDiscordNotifications = $this->settings->server_disk_usage_discord_notifications;
$this->serverReachableDiscordNotifications = $this->settings->server_reachable_discord_notifications;
$this->serverUnreachableDiscordNotifications = $this->settings->server_unreachable_discord_notifications;
+ $this->serverPatchDiscordNotifications = $this->settings->server_patch_discord_notifications;
$this->discordPingEnabled = $this->settings->discord_ping_enabled;
}
diff --git a/app/Livewire/Notifications/Email.php b/app/Livewire/Notifications/Email.php
index c5f518e16..4b5d1c6a0 100644
--- a/app/Livewire/Notifications/Email.php
+++ b/app/Livewire/Notifications/Email.php
@@ -98,6 +98,9 @@ class Email extends Component
#[Validate(['boolean'])]
public bool $serverUnreachableEmailNotifications = true;
+ #[Validate(['boolean'])]
+ public bool $serverPatchEmailNotifications = false;
+
#[Validate(['nullable', 'email'])]
public ?string $testEmailAddress = null;
@@ -146,6 +149,7 @@ class Email extends Component
$this->settings->server_disk_usage_email_notifications = $this->serverDiskUsageEmailNotifications;
$this->settings->server_reachable_email_notifications = $this->serverReachableEmailNotifications;
$this->settings->server_unreachable_email_notifications = $this->serverUnreachableEmailNotifications;
+ $this->settings->server_patch_email_notifications = $this->serverPatchEmailNotifications;
$this->settings->save();
} else {
@@ -177,6 +181,7 @@ class Email extends Component
$this->serverDiskUsageEmailNotifications = $this->settings->server_disk_usage_email_notifications;
$this->serverReachableEmailNotifications = $this->settings->server_reachable_email_notifications;
$this->serverUnreachableEmailNotifications = $this->settings->server_unreachable_email_notifications;
+ $this->serverPatchEmailNotifications = $this->settings->server_patch_email_notifications;
}
}
diff --git a/app/Livewire/Notifications/Pushover.php b/app/Livewire/Notifications/Pushover.php
index f1e4c464d..bd5ab79c8 100644
--- a/app/Livewire/Notifications/Pushover.php
+++ b/app/Livewire/Notifications/Pushover.php
@@ -64,6 +64,9 @@ class Pushover extends Component
#[Validate(['boolean'])]
public bool $serverUnreachablePushoverNotifications = true;
+ #[Validate(['boolean'])]
+ public bool $serverPatchPushoverNotifications = false;
+
public function mount()
{
try {
@@ -95,6 +98,7 @@ class Pushover extends Component
$this->settings->server_disk_usage_pushover_notifications = $this->serverDiskUsagePushoverNotifications;
$this->settings->server_reachable_pushover_notifications = $this->serverReachablePushoverNotifications;
$this->settings->server_unreachable_pushover_notifications = $this->serverUnreachablePushoverNotifications;
+ $this->settings->server_patch_pushover_notifications = $this->serverPatchPushoverNotifications;
$this->settings->save();
refreshSession();
@@ -115,6 +119,7 @@ class Pushover extends Component
$this->serverDiskUsagePushoverNotifications = $this->settings->server_disk_usage_pushover_notifications;
$this->serverReachablePushoverNotifications = $this->settings->server_reachable_pushover_notifications;
$this->serverUnreachablePushoverNotifications = $this->settings->server_unreachable_pushover_notifications;
+ $this->serverPatchPushoverNotifications = $this->settings->server_patch_pushover_notifications;
}
}
diff --git a/app/Livewire/Notifications/Slack.php b/app/Livewire/Notifications/Slack.php
index f47ad8a97..9c847ce57 100644
--- a/app/Livewire/Notifications/Slack.php
+++ b/app/Livewire/Notifications/Slack.php
@@ -61,6 +61,9 @@ class Slack extends Component
#[Validate(['boolean'])]
public bool $serverUnreachableSlackNotifications = true;
+ #[Validate(['boolean'])]
+ public bool $serverPatchSlackNotifications = false;
+
public function mount()
{
try {
@@ -91,6 +94,7 @@ class Slack extends Component
$this->settings->server_disk_usage_slack_notifications = $this->serverDiskUsageSlackNotifications;
$this->settings->server_reachable_slack_notifications = $this->serverReachableSlackNotifications;
$this->settings->server_unreachable_slack_notifications = $this->serverUnreachableSlackNotifications;
+ $this->settings->server_patch_slack_notifications = $this->serverPatchSlackNotifications;
$this->settings->save();
refreshSession();
@@ -110,6 +114,7 @@ class Slack extends Component
$this->serverDiskUsageSlackNotifications = $this->settings->server_disk_usage_slack_notifications;
$this->serverReachableSlackNotifications = $this->settings->server_reachable_slack_notifications;
$this->serverUnreachableSlackNotifications = $this->settings->server_unreachable_slack_notifications;
+ $this->serverPatchSlackNotifications = $this->settings->server_patch_slack_notifications;
}
}
diff --git a/app/Livewire/Notifications/Telegram.php b/app/Livewire/Notifications/Telegram.php
index 01afb4ac6..07393d4ea 100644
--- a/app/Livewire/Notifications/Telegram.php
+++ b/app/Livewire/Notifications/Telegram.php
@@ -64,6 +64,9 @@ class Telegram extends Component
#[Validate(['boolean'])]
public bool $serverUnreachableTelegramNotifications = true;
+ #[Validate(['boolean'])]
+ public bool $serverPatchTelegramNotifications = false;
+
#[Validate(['nullable', 'string'])]
public ?string $telegramNotificationsDeploymentSuccessThreadId = null;
@@ -100,6 +103,9 @@ class Telegram extends Component
#[Validate(['nullable', 'string'])]
public ?string $telegramNotificationsServerUnreachableThreadId = null;
+ #[Validate(['nullable', 'string'])]
+ public ?string $telegramNotificationsServerPatchThreadId = null;
+
public function mount()
{
try {
@@ -131,6 +137,7 @@ class Telegram extends Component
$this->settings->server_disk_usage_telegram_notifications = $this->serverDiskUsageTelegramNotifications;
$this->settings->server_reachable_telegram_notifications = $this->serverReachableTelegramNotifications;
$this->settings->server_unreachable_telegram_notifications = $this->serverUnreachableTelegramNotifications;
+ $this->settings->server_patch_telegram_notifications = $this->serverPatchTelegramNotifications;
$this->settings->telegram_notifications_deployment_success_thread_id = $this->telegramNotificationsDeploymentSuccessThreadId;
$this->settings->telegram_notifications_deployment_failure_thread_id = $this->telegramNotificationsDeploymentFailureThreadId;
@@ -144,6 +151,7 @@ class Telegram extends Component
$this->settings->telegram_notifications_server_disk_usage_thread_id = $this->telegramNotificationsServerDiskUsageThreadId;
$this->settings->telegram_notifications_server_reachable_thread_id = $this->telegramNotificationsServerReachableThreadId;
$this->settings->telegram_notifications_server_unreachable_thread_id = $this->telegramNotificationsServerUnreachableThreadId;
+ $this->settings->telegram_notifications_server_patch_thread_id = $this->telegramNotificationsServerPatchThreadId;
$this->settings->save();
} else {
@@ -163,6 +171,7 @@ class Telegram extends Component
$this->serverDiskUsageTelegramNotifications = $this->settings->server_disk_usage_telegram_notifications;
$this->serverReachableTelegramNotifications = $this->settings->server_reachable_telegram_notifications;
$this->serverUnreachableTelegramNotifications = $this->settings->server_unreachable_telegram_notifications;
+ $this->serverPatchTelegramNotifications = $this->settings->server_patch_telegram_notifications;
$this->telegramNotificationsDeploymentSuccessThreadId = $this->settings->telegram_notifications_deployment_success_thread_id;
$this->telegramNotificationsDeploymentFailureThreadId = $this->settings->telegram_notifications_deployment_failure_thread_id;
@@ -176,6 +185,7 @@ class Telegram extends Component
$this->telegramNotificationsServerDiskUsageThreadId = $this->settings->telegram_notifications_server_disk_usage_thread_id;
$this->telegramNotificationsServerReachableThreadId = $this->settings->telegram_notifications_server_reachable_thread_id;
$this->telegramNotificationsServerUnreachableThreadId = $this->settings->telegram_notifications_server_unreachable_thread_id;
+ $this->telegramNotificationsServerPatchThreadId = $this->settings->telegram_notifications_server_patch_thread_id;
}
}
diff --git a/app/Models/DiscordNotificationSettings.php b/app/Models/DiscordNotificationSettings.php
index 1ba16ccd8..34adfc997 100644
--- a/app/Models/DiscordNotificationSettings.php
+++ b/app/Models/DiscordNotificationSettings.php
@@ -28,6 +28,7 @@ class DiscordNotificationSettings extends Model
'server_disk_usage_discord_notifications',
'server_reachable_discord_notifications',
'server_unreachable_discord_notifications',
+ 'server_patch_discord_notifications',
'discord_ping_enabled',
];
@@ -46,6 +47,7 @@ class DiscordNotificationSettings extends Model
'server_disk_usage_discord_notifications' => 'boolean',
'server_reachable_discord_notifications' => 'boolean',
'server_unreachable_discord_notifications' => 'boolean',
+ 'server_patch_discord_notifications' => 'boolean',
'discord_ping_enabled' => 'boolean',
];
diff --git a/app/Models/EmailNotificationSettings.php b/app/Models/EmailNotificationSettings.php
index 445987619..39617b4cf 100644
--- a/app/Models/EmailNotificationSettings.php
+++ b/app/Models/EmailNotificationSettings.php
@@ -35,6 +35,7 @@ class EmailNotificationSettings extends Model
'scheduled_task_success_email_notifications',
'scheduled_task_failure_email_notifications',
'server_disk_usage_email_notifications',
+ 'server_patch_email_notifications',
];
protected $casts = [
@@ -61,6 +62,7 @@ class EmailNotificationSettings extends Model
'scheduled_task_success_email_notifications' => 'boolean',
'scheduled_task_failure_email_notifications' => 'boolean',
'server_disk_usage_email_notifications' => 'boolean',
+ 'server_patch_email_notifications' => 'boolean',
];
public function team()
diff --git a/app/Models/PushoverNotificationSettings.php b/app/Models/PushoverNotificationSettings.php
index e3191dcc3..a75fd71d7 100644
--- a/app/Models/PushoverNotificationSettings.php
+++ b/app/Models/PushoverNotificationSettings.php
@@ -29,6 +29,7 @@ class PushoverNotificationSettings extends Model
'server_disk_usage_pushover_notifications',
'server_reachable_pushover_notifications',
'server_unreachable_pushover_notifications',
+ 'server_patch_pushover_notifications',
];
protected $casts = [
@@ -47,6 +48,7 @@ class PushoverNotificationSettings extends Model
'server_disk_usage_pushover_notifications' => 'boolean',
'server_reachable_pushover_notifications' => 'boolean',
'server_unreachable_pushover_notifications' => 'boolean',
+ 'server_patch_pushover_notifications' => 'boolean',
];
public function team()
diff --git a/app/Models/SlackNotificationSettings.php b/app/Models/SlackNotificationSettings.php
index 48153f2ea..2b52bfd5b 100644
--- a/app/Models/SlackNotificationSettings.php
+++ b/app/Models/SlackNotificationSettings.php
@@ -28,6 +28,7 @@ class SlackNotificationSettings extends Model
'server_disk_usage_slack_notifications',
'server_reachable_slack_notifications',
'server_unreachable_slack_notifications',
+ 'server_patch_slack_notifications',
];
protected $casts = [
@@ -45,6 +46,7 @@ class SlackNotificationSettings extends Model
'server_disk_usage_slack_notifications' => 'boolean',
'server_reachable_slack_notifications' => 'boolean',
'server_unreachable_slack_notifications' => 'boolean',
+ 'server_patch_slack_notifications' => 'boolean',
];
public function team()
diff --git a/app/Models/TelegramNotificationSettings.php b/app/Models/TelegramNotificationSettings.php
index 78bd841bd..94315ee30 100644
--- a/app/Models/TelegramNotificationSettings.php
+++ b/app/Models/TelegramNotificationSettings.php
@@ -29,6 +29,7 @@ class TelegramNotificationSettings extends Model
'server_disk_usage_telegram_notifications',
'server_reachable_telegram_notifications',
'server_unreachable_telegram_notifications',
+ 'server_patch_telegram_notifications',
'telegram_notifications_deployment_success_thread_id',
'telegram_notifications_deployment_failure_thread_id',
@@ -41,6 +42,7 @@ class TelegramNotificationSettings extends Model
'telegram_notifications_server_disk_usage_thread_id',
'telegram_notifications_server_reachable_thread_id',
'telegram_notifications_server_unreachable_thread_id',
+ 'telegram_notifications_server_patch_thread_id',
];
protected $casts = [
@@ -59,6 +61,7 @@ class TelegramNotificationSettings extends Model
'server_disk_usage_telegram_notifications' => 'boolean',
'server_reachable_telegram_notifications' => 'boolean',
'server_unreachable_telegram_notifications' => 'boolean',
+ 'server_patch_telegram_notifications' => 'boolean',
'telegram_notifications_deployment_success_thread_id' => 'encrypted',
'telegram_notifications_deployment_failure_thread_id' => 'encrypted',
@@ -71,6 +74,7 @@ class TelegramNotificationSettings extends Model
'telegram_notifications_server_disk_usage_thread_id' => 'encrypted',
'telegram_notifications_server_reachable_thread_id' => 'encrypted',
'telegram_notifications_server_unreachable_thread_id' => 'encrypted',
+ 'telegram_notifications_server_patch_thread_id' => 'encrypted',
];
public function team()
diff --git a/app/Notifications/Channels/TelegramChannel.php b/app/Notifications/Channels/TelegramChannel.php
index ea4ab7171..c2fa3ff10 100644
--- a/app/Notifications/Channels/TelegramChannel.php
+++ b/app/Notifications/Channels/TelegramChannel.php
@@ -34,6 +34,7 @@ class TelegramChannel
\App\Notifications\Server\HighDiskUsage::class => $settings->telegram_notifications_server_disk_usage_thread_id,
\App\Notifications\Server\Unreachable::class => $settings->telegram_notifications_server_unreachable_thread_id,
\App\Notifications\Server\Reachable::class => $settings->telegram_notifications_server_reachable_thread_id,
+ \App\Notifications\Server\ServerPatchCheck::class => $settings->telegram_notifications_server_patch_thread_id,
default => null,
};
diff --git a/app/Notifications/Server/ServerPatchCheck.php b/app/Notifications/Server/ServerPatchCheck.php
new file mode 100644
index 000000000..8453e0dd6
--- /dev/null
+++ b/app/Notifications/Server/ServerPatchCheck.php
@@ -0,0 +1,245 @@
+onQueue('high');
+ $this->serverUrl = route('server.security.patches', ['server_uuid' => $this->server->uuid]);
+ if (isDev()) {
+ $this->serverUrl = 'https://staging-but-dev.coolify.io/server/'.$this->server->uuid.'/security/patches';
+ }
+ }
+
+ public function via(object $notifiable): array
+ {
+ return $notifiable->getEnabledChannels('server_patch');
+ }
+
+ public function toMail($notifiable = null): MailMessage
+ {
+ $mail = new MailMessage;
+ $totalUpdates = $this->patchData['total_updates'] ?? 0;
+ $mail->subject("Coolify: [ACTION REQUIRED] {$totalUpdates} server patches available on {$this->server->name}");
+ $mail->view('emails.server-patches', [
+ 'name' => $this->server->name,
+ 'total_updates' => $totalUpdates,
+ 'updates' => $this->patchData['updates'] ?? [],
+ 'osId' => $this->patchData['osId'] ?? 'unknown',
+ 'package_manager' => $this->patchData['package_manager'] ?? 'unknown',
+ 'server_url' => $this->serverUrl,
+ ]);
+
+ return $mail;
+ }
+
+ public function toDiscord(): DiscordMessage
+ {
+ $totalUpdates = $this->patchData['total_updates'] ?? 0;
+ $updates = $this->patchData['updates'] ?? [];
+ $osId = $this->patchData['osId'] ?? 'unknown';
+ $packageManager = $this->patchData['package_manager'] ?? 'unknown';
+
+ $description = "**{$totalUpdates} package updates** available for server {$this->server->name}\n\n";
+ $description .= "**Summary:**\n";
+ $description .= '• OS: '.ucfirst($osId)."\n";
+ $description .= "• Package Manager: {$packageManager}\n";
+ $description .= "• Total Updates: {$totalUpdates}\n\n";
+
+ // Show first few packages
+ if (count($updates) > 0) {
+ $description .= "**Sample Updates:**\n";
+ $sampleUpdates = array_slice($updates, 0, 5);
+ foreach ($sampleUpdates as $update) {
+ $description .= "• {$update['package']}: {$update['current_version']} → {$update['new_version']}\n";
+ }
+ if (count($updates) > 5) {
+ $description .= '• ... and '.(count($updates) - 5)." more packages\n";
+ }
+
+ // Check for critical packages
+ $criticalPackages = collect($updates)->filter(function ($update) {
+ return str_contains(strtolower($update['package']), 'docker') ||
+ str_contains(strtolower($update['package']), 'kernel') ||
+ str_contains(strtolower($update['package']), 'openssh') ||
+ str_contains(strtolower($update['package']), 'ssl');
+ });
+
+ if ($criticalPackages->count() > 0) {
+ $description .= "\n **Critical packages detected** ({$criticalPackages->count()} packages may require restarts)";
+ }
+ $description .= "\n [Manage Server Patches]($this->serverUrl)";
+ }
+
+ return new DiscordMessage(
+ title: ':warning: Coolify: [ACTION REQUIRED] Server patches available on '.$this->server->name,
+ description: $description,
+ color: DiscordMessage::errorColor(),
+ );
+
+ }
+
+ public function toTelegram(): array
+ {
+ $totalUpdates = $this->patchData['total_updates'] ?? 0;
+ $updates = $this->patchData['updates'] ?? [];
+ $osId = $this->patchData['osId'] ?? 'unknown';
+ $packageManager = $this->patchData['package_manager'] ?? 'unknown';
+
+ $message = "🔧 Coolify: [ACTION REQUIRED] {$totalUpdates} server patches available on {$this->server->name}!\n\n";
+ $message .= "📊 Summary:\n";
+ $message .= '• OS: '.ucfirst($osId)."\n";
+ $message .= "• Package Manager: {$packageManager}\n";
+ $message .= "• Total Updates: {$totalUpdates}\n\n";
+
+ if (count($updates) > 0) {
+ $message .= "📦 Sample Updates:\n";
+ $sampleUpdates = array_slice($updates, 0, 5);
+ foreach ($sampleUpdates as $update) {
+ $message .= "• {$update['package']}: {$update['current_version']} → {$update['new_version']}\n";
+ }
+ if (count($updates) > 5) {
+ $message .= '• ... and '.(count($updates) - 5)." more packages\n";
+ }
+
+ // Check for critical packages
+ $criticalPackages = collect($updates)->filter(function ($update) {
+ return str_contains(strtolower($update['package']), 'docker') ||
+ str_contains(strtolower($update['package']), 'kernel') ||
+ str_contains(strtolower($update['package']), 'openssh') ||
+ str_contains(strtolower($update['package']), 'ssl');
+ });
+
+ if ($criticalPackages->count() > 0) {
+ $message .= "\n⚠️ Critical packages detected: {$criticalPackages->count()} packages may require restarts\n";
+ foreach ($criticalPackages->take(3) as $package) {
+ $message .= "• {$package['package']}: {$package['current_version']} → {$package['new_version']}\n";
+ }
+ if ($criticalPackages->count() > 3) {
+ $message .= '• ... and '.($criticalPackages->count() - 3)." more critical packages\n";
+ }
+ }
+ }
+
+ return [
+ 'message' => $message,
+ 'buttons' => [
+ [
+ 'text' => 'Manage Server Patches',
+ 'url' => $this->serverUrl,
+ ],
+ ],
+ ];
+ }
+
+ public function toPushover(): PushoverMessage
+ {
+ $totalUpdates = $this->patchData['total_updates'] ?? 0;
+ $updates = $this->patchData['updates'] ?? [];
+ $osId = $this->patchData['osId'] ?? 'unknown';
+ $packageManager = $this->patchData['package_manager'] ?? 'unknown';
+
+ $message = "[ACTION REQUIRED] {$totalUpdates} server patches available on {$this->server->name}!\n\n";
+ $message .= "Summary:\n";
+ $message .= '• OS: '.ucfirst($osId)."\n";
+ $message .= "• Package Manager: {$packageManager}\n";
+ $message .= "• Total Updates: {$totalUpdates}\n\n";
+
+ if (count($updates) > 0) {
+ $message .= "Sample Updates:\n";
+ $sampleUpdates = array_slice($updates, 0, 3);
+ foreach ($sampleUpdates as $update) {
+ $message .= "• {$update['package']}: {$update['current_version']} → {$update['new_version']}\n";
+ }
+ if (count($updates) > 3) {
+ $message .= '• ... and '.(count($updates) - 3)." more packages\n";
+ }
+
+ // Check for critical packages
+ $criticalPackages = collect($updates)->filter(function ($update) {
+ return str_contains(strtolower($update['package']), 'docker') ||
+ str_contains(strtolower($update['package']), 'kernel') ||
+ str_contains(strtolower($update['package']), 'openssh') ||
+ str_contains(strtolower($update['package']), 'ssl');
+ });
+
+ if ($criticalPackages->count() > 0) {
+ $message .= "\nCritical packages detected: {$criticalPackages->count()} may require restarts";
+ }
+ }
+
+ return new PushoverMessage(
+ title: 'Server patches available',
+ level: 'error',
+ message: $message,
+ buttons: [
+ [
+ 'text' => 'Manage Server Patches',
+ 'url' => $this->serverUrl,
+ ],
+ ],
+ );
+ }
+
+ public function toSlack(): SlackMessage
+ {
+ $totalUpdates = $this->patchData['total_updates'] ?? 0;
+ $updates = $this->patchData['updates'] ?? [];
+ $osId = $this->patchData['osId'] ?? 'unknown';
+ $packageManager = $this->patchData['package_manager'] ?? 'unknown';
+
+ $description = "{$totalUpdates} server patches available on '{$this->server->name}'!\n\n";
+ $description .= "*Summary:*\n";
+ $description .= '• OS: '.ucfirst($osId)."\n";
+ $description .= "• Package Manager: {$packageManager}\n";
+ $description .= "• Total Updates: {$totalUpdates}\n\n";
+
+ if (count($updates) > 0) {
+ $description .= "*Sample Updates:*\n";
+ $sampleUpdates = array_slice($updates, 0, 5);
+ foreach ($sampleUpdates as $update) {
+ $description .= "• `{$update['package']}`: {$update['current_version']} → {$update['new_version']}\n";
+ }
+ if (count($updates) > 5) {
+ $description .= '• ... and '.(count($updates) - 5)." more packages\n";
+ }
+
+ // Check for critical packages
+ $criticalPackages = collect($updates)->filter(function ($update) {
+ return str_contains(strtolower($update['package']), 'docker') ||
+ str_contains(strtolower($update['package']), 'kernel') ||
+ str_contains(strtolower($update['package']), 'openssh') ||
+ str_contains(strtolower($update['package']), 'ssl');
+ });
+
+ if ($criticalPackages->count() > 0) {
+ $description .= "\n:warning: *Critical packages detected:* {$criticalPackages->count()} packages may require restarts\n";
+ foreach ($criticalPackages->take(3) as $package) {
+ $description .= "• `{$package['package']}`: {$package['current_version']} → {$package['new_version']}\n";
+ }
+ if ($criticalPackages->count() > 3) {
+ $description .= '• ... and '.($criticalPackages->count() - 3)." more critical packages\n";
+ }
+ }
+ }
+
+ $description .= "\n:link: <{$this->serverUrl}|Manage Server Patches>";
+
+ return new SlackMessage(
+ title: 'Coolify: [ACTION REQUIRED] Server patches available',
+ description: $description,
+ color: SlackMessage::errorColor()
+ );
+ }
+}
diff --git a/database/migrations/2025_05_26_100258_add_server_patch_notifications.php b/database/migrations/2025_05_26_100258_add_server_patch_notifications.php
new file mode 100644
index 000000000..6d6611173
--- /dev/null
+++ b/database/migrations/2025_05_26_100258_add_server_patch_notifications.php
@@ -0,0 +1,74 @@
+boolean('server_patch_email_notifications')->default(true);
+ });
+
+ // Add server patch notification fields to discord notification settings
+ Schema::table('discord_notification_settings', function (Blueprint $table) {
+ $table->boolean('server_patch_discord_notifications')->default(true);
+ });
+
+ // Add server patch notification fields to telegram notification settings
+ Schema::table('telegram_notification_settings', function (Blueprint $table) {
+ $table->boolean('server_patch_telegram_notifications')->default(true);
+ $table->string('telegram_notifications_server_patch_thread_id')->nullable();
+ });
+
+ // Add server patch notification fields to slack notification settings
+ Schema::table('slack_notification_settings', function (Blueprint $table) {
+ $table->boolean('server_patch_slack_notifications')->default(true);
+ });
+
+ // Add server patch notification fields to pushover notification settings
+ Schema::table('pushover_notification_settings', function (Blueprint $table) {
+ $table->boolean('server_patch_pushover_notifications')->default(true);
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ // Remove server patch notification fields from email notification settings
+ Schema::table('email_notification_settings', function (Blueprint $table) {
+ $table->dropColumn('server_patch_email_notifications');
+ });
+
+ // Remove server patch notification fields from discord notification settings
+ Schema::table('discord_notification_settings', function (Blueprint $table) {
+ $table->dropColumn('server_patch_discord_notifications');
+ });
+
+ // Remove server patch notification fields from telegram notification settings
+ Schema::table('telegram_notification_settings', function (Blueprint $table) {
+ $table->dropColumn([
+ 'server_patch_telegram_notifications',
+ 'telegram_notifications_server_patch_thread_id',
+ ]);
+ });
+
+ // Remove server patch notification fields from slack notification settings
+ Schema::table('slack_notification_settings', function (Blueprint $table) {
+ $table->dropColumn('server_patch_slack_notifications');
+ });
+
+ // Remove server patch notification fields from pushover notification settings
+ Schema::table('pushover_notification_settings', function (Blueprint $table) {
+ $table->dropColumn('server_patch_pushover_notifications');
+ });
+ }
+};
diff --git a/resources/views/emails/server-patches.blade.php b/resources/views/emails/server-patches.blade.php
new file mode 100644
index 000000000..37644eaa2
--- /dev/null
+++ b/resources/views/emails/server-patches.blade.php
@@ -0,0 +1,53 @@
+
Create Slack APP"
+ required id="slackWebhookUrl" label="Webhook" />
@@ -73,6 +73,7 @@
label="Server Reachable" />
- @if (data_get_str($update, 'package')->contains('docker'))
+ @if (data_get_str($update, 'package')->contains('docker') || data_get_str($update, 'package')->contains('kernel'))
|