Skip to content

Commit 56f7e42

Browse files
[Fix] Available updates + kernel fixes alert (#1159)
* kernel fixes * server status bug * Address PR review: kernel-specific log type, reuse BroadcastServerUpdate, cleaner update flash message * fix
1 parent b7c21bc commit 56f7e42

16 files changed

Lines changed: 246 additions & 10 deletions

File tree

app/Actions/Server/CheckConnection.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ public function check(Server $server, int $retry = 2): Server
2121
if (in_array($status, [ServerStatus::DISCONNECTED, ServerStatus::UPDATING])) {
2222
$server->status = ServerStatus::READY;
2323
$server->save();
24+
app(BroadcastServerUpdate::class)->broadcast($server);
2425
if ($status === ServerStatus::DISCONNECTED) {
2526
Notifier::send($server, new ServerConnected($server));
2627
}
@@ -34,6 +35,7 @@ public function check(Server $server, int $retry = 2): Server
3435
if ($status !== ServerStatus::DISCONNECTED) {
3536
$server->status = ServerStatus::DISCONNECTED;
3637
$server->save();
38+
app(BroadcastServerUpdate::class)->broadcast($server);
3739
Notifier::send($server, new ServerDisconnected($server));
3840
}
3941
}

app/Actions/Server/RebootServer.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ public function reboot(Server $server): Server
1414
$server->os()->reboot();
1515
$server->status = ServerStatus::DISCONNECTED;
1616
$server->save();
17+
app(BroadcastServerUpdate::class)->broadcast($server);
1718
} catch (Throwable) {
1819
$server = $server->checkConnection();
1920
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
3+
namespace App\Actions\Server;
4+
5+
use App\Enums\ServerStatus;
6+
use App\Jobs\Server\UpdateKernelJob;
7+
use App\Models\Server;
8+
9+
class UpdateKernel
10+
{
11+
public function updateKernel(Server $server): void
12+
{
13+
$server->status = ServerStatus::UPDATING;
14+
$server->save();
15+
dispatch(new UpdateKernelJob($server))->onQueue('ssh');
16+
}
17+
}

app/Http/Controllers/ServerController.php

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
use App\Actions\Server\RebootServer;
99
use App\Actions\Server\TransferServer;
1010
use App\Actions\Server\Update;
11+
use App\Actions\Server\UpdateKernel;
1112
use App\Exceptions\SSHError;
1213
use App\Http\Resources\ServerLogResource;
1314
use App\Http\Resources\ServerProviderResource;
@@ -122,7 +123,15 @@ public function checkForUpdates(Server $server): RedirectResponse
122123

123124
$server->checkForUpdates();
124125

125-
return back()->with('info', 'Available updates: '.$server->refresh()->updates);
126+
$server->refresh();
127+
128+
$message = "Available updates: {$server->updates}";
129+
130+
if ($server->kernel_updates > 0) {
131+
$message .= " (plus {$server->kernel_updates} kernel)";
132+
}
133+
134+
return back()->with('info', $message);
126135
}
127136

128137
#[Post('/{server}/update', name: 'servers.update')]
@@ -135,6 +144,16 @@ public function update(Server $server): RedirectResponse
135144
return back()->with('info', 'Server is being updated. This may take a while.');
136145
}
137146

147+
#[Post('/{server}/update-kernel', name: 'servers.update-kernel')]
148+
public function updateKernel(Server $server): RedirectResponse
149+
{
150+
$this->authorize('update', $server);
151+
152+
app(UpdateKernel::class)->updateKernel($server);
153+
154+
return back()->with('info', 'Kernel is being updated and the server will restart.');
155+
}
156+
138157
#[Post('/{server}/transfer', name: 'servers.transfer')]
139158
public function transfer(Server $server, Request $request): RedirectResponse
140159
{

app/Http/Resources/ServerResource.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ public function toArray(Request $request): array
3636
'progress' => $this->progress,
3737
'progress_step' => $this->progress_step,
3838
'updates' => $this->updates,
39+
'kernel_updates' => $this->kernel_updates,
3940
'last_update_check' => $this->last_update_check,
4041
'status_color' => $this->status->getColor(),
4142
'features' => $this->features(),
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<?php
2+
3+
namespace App\Jobs\Server;
4+
5+
use App\Actions\Server\BroadcastServerUpdate;
6+
use App\Actions\Server\RebootServer;
7+
use App\Facades\Notifier;
8+
use App\Models\Server;
9+
use App\Models\ServerLog;
10+
use App\Notifications\ServerUpdateFailed;
11+
use App\Traits\UniqueQueue;
12+
use Exception;
13+
use Illuminate\Contracts\Queue\ShouldQueue;
14+
use Illuminate\Foundation\Queue\Queueable;
15+
16+
class UpdateKernelJob implements ShouldQueue
17+
{
18+
use Queueable;
19+
use UniqueQueue;
20+
21+
public function __construct(protected Server $server) {}
22+
23+
public function handle(): void
24+
{
25+
$this->run("server-{$this->server->id}", function () {
26+
$this->server->os()->upgradeKernel();
27+
$this->server->checkConnection();
28+
$this->server->checkForUpdates();
29+
app(BroadcastServerUpdate::class)->broadcast($this->server);
30+
app(RebootServer::class)->reboot($this->server);
31+
});
32+
}
33+
34+
public function failed(Exception $e): void
35+
{
36+
Notifier::send($this->server, new ServerUpdateFailed($this->server));
37+
$this->server->checkConnection();
38+
app(BroadcastServerUpdate::class)->broadcast($this->server);
39+
40+
ServerLog::log(
41+
$this->server,
42+
'update-kernel-failed',
43+
$e->getMessage()
44+
);
45+
}
46+
}

app/Models/Server.php

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@
6969
* @property Collection<int, SshKey> $sshKeys
7070
* @property string $hostname
7171
* @property int $updates
72+
* @property int $kernel_updates
7273
* @property ?Carbon $last_update_check
7374
*/
7475
class Server extends AbstractModel
@@ -96,6 +97,7 @@ class Server extends AbstractModel
9697
'progress',
9798
'progress_step',
9899
'updates',
100+
'kernel_updates',
99101
'last_update_check',
100102
'feature_data',
101103
];
@@ -109,6 +111,7 @@ class Server extends AbstractModel
109111
'auto_update' => 'boolean',
110112
'progress' => 'float',
111113
'updates' => 'integer',
114+
'kernel_updates' => 'integer',
112115
'last_update_check' => 'datetime',
113116
'feature_data' => 'json',
114117
'os' => OperatingSystem::class,
@@ -642,7 +645,9 @@ public function securityScore(): array
642645
*/
643646
public function checkForUpdates(): void
644647
{
645-
$this->updates = $this->os()->availableUpdates();
648+
$result = $this->os()->availableUpdates();
649+
$this->updates = $result['updates'];
650+
$this->kernel_updates = $result['kernel'];
646651
$this->last_update_check = now();
647652
$this->save();
648653
}
@@ -701,6 +706,13 @@ public function getWarnings(): array
701706
];
702707
}
703708

709+
if ($this->kernel_updates > 0) {
710+
$warnings[] = [
711+
'key' => 'kernel_update_available',
712+
'count' => $this->kernel_updates,
713+
];
714+
}
715+
704716
$latestMetric = $this->relationLoaded('latestMetric')
705717
? $this->latestMetric
706718
: $this->latestMetric()->first();

app/SSH/OS/OS.php

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,14 +51,30 @@ public function upgrade(): void
5151
/**
5252
* @throws SSHError
5353
*/
54-
public function availableUpdates(): int
54+
public function upgradeKernel(): void
55+
{
56+
$this->server->ssh()->exec(
57+
view('ssh.os.upgrade-kernel'),
58+
'upgrade-kernel'
59+
);
60+
}
61+
62+
/**
63+
* @return array{updates: int, kernel: int}
64+
*
65+
* @throws SSHError
66+
*/
67+
public function availableUpdates(): array
5568
{
5669
$result = $this->server->ssh()->exec(
5770
view('ssh.os.available-updates'),
5871
'check-available-updates'
5972
);
6073

61-
return max(str($result)->after('Available updates:')->trim()->toInteger(), 0);
74+
return [
75+
'updates' => max(str($result)->after('Available updates:')->trim()->toInteger(), 0),
76+
'kernel' => max(str($result)->after('Kernel updates:')->trim()->toInteger(), 0),
77+
];
6278
}
6379

6480
/**
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
use Illuminate\Database\Migrations\Migration;
4+
use Illuminate\Database\Schema\Blueprint;
5+
use Illuminate\Support\Facades\Schema;
6+
7+
return new class extends Migration
8+
{
9+
public function up(): void
10+
{
11+
Schema::table('servers', function (Blueprint $table): void {
12+
$table->integer('kernel_updates')->default(0);
13+
});
14+
}
15+
16+
public function down(): void
17+
{
18+
Schema::table('servers', function (Blueprint $table): void {
19+
$table->dropColumn('kernel_updates');
20+
});
21+
}
22+
};

resources/js/components/server-banners.tsx

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export default function ServerBanners({ server }: { server: Server }) {
1010

1111
const rebootRequiredWarning = warnings.find((w) => w.key === 'reboot_required');
1212
const updatesWarning = warnings.find((w) => w.key === 'updates_available');
13+
const kernelUpdateWarning = warnings.find((w) => w.key === 'kernel_update_available');
1314

1415
if (rebootRequiredWarning) {
1516
items.push({
@@ -63,6 +64,33 @@ export default function ServerBanners({ server }: { server: Server }) {
6364
});
6465
}
6566

67+
if (kernelUpdateWarning) {
68+
const kernelCount = kernelUpdateWarning.count;
69+
items.push({
70+
key: 'kernel-update',
71+
title: `Kernel update available`,
72+
description: <>Install the pending kernel {kernelCount === 1 ? 'package' : 'packages'} and restart to apply the new kernel.</>,
73+
action: (
74+
<Button
75+
variant="outline"
76+
size="sm"
77+
onClick={() =>
78+
dialog.confirm.open({
79+
title: `Update kernel on ${server.name}?`,
80+
description: `This installs the pending kernel ${kernelCount === 1 ? 'package' : 'packages'} (a full upgrade that may install or remove packages), then restarts the server to boot the new kernel. The server will be unavailable for a minute or two and connections in flight will be dropped.`,
81+
variant: 'destructive',
82+
confirmLabel: 'Update & restart',
83+
method: 'post',
84+
url: route('servers.update-kernel', server.id),
85+
})
86+
}
87+
>
88+
Update &amp; restart
89+
</Button>
90+
),
91+
});
92+
}
93+
6694
if (items.length === 0) return null;
6795

6896
return (

0 commit comments

Comments
 (0)