feat(v5): add server bootstrap job with realtime cluster broadcasting

- Add V5BootstrapServerJob: SSH into new servers, install coold/WireGuard,
  track bootstrap progress via last_bootstrap_action/status/output fields
- Add V5ClusterUpdated event: broadcasts full cluster+servers payload to
  team private channel on bootstrap state changes
- Add V5RealtimeTestEvent + RealtimeTest page for WebSocket connectivity testing
- Consolidate 3 dropped migrations into base cluster/server migrations:
  WireGuard config, builder CPU quota, status check fields, uuid
- Auto-generate uuid on V5Model create via Schema::hasColumn guard
- Expose currentTeam.id in Inertia shared props
- Add showCluster, realtimeTest, broadcastRealtimeTest controller endpoints
- Add dropdown-menu UI component
This commit is contained in:
Andras Bacsai
2026-06-19 11:44:42 +02:00
parent 3ae87334bd
commit 24bbfe07c6
35 changed files with 2744 additions and 639 deletions
@@ -17,6 +17,26 @@ return new class extends Migration
$table->foreignId('created_by_user_id')->constrained('users')->cascadeOnDelete();
$table->string('name');
$table->text('description')->nullable();
$table->string('wireguard_interface')->default('wg0');
$table->string('wireguard_management_pool')->default('100.64.0.0/16');
$table->unsignedInteger('wireguard_listen_port')->default(51820);
$table->string('container_network_pool')->default('10.210.0.0/16');
$table->unsignedTinyInteger('container_network_prefix')->default(24);
$table->json('namespaces')->nullable();
$table->boolean('default_deny_containers')->default(true);
$table->string('coold_version')->default('nightly');
$table->string('corrosion_version')->default('v1.0.0');
$table->unsignedInteger('corrosion_gossip_port')->default(8787);
$table->unsignedInteger('corrosion_api_port')->default(8080);
$table->boolean('builder_enabled')->default(true);
$table->unsignedInteger('builder_capacity')->default(2);
$table->string('builder_cpu_quota')->default('200%');
$table->string('builder_memory_max')->default('2G');
$table->unsignedInteger('builder_timeout_secs')->default(1800);
$table->string('last_cli_action')->nullable();
$table->string('last_cli_status')->nullable();
$table->text('last_cli_summary')->nullable();
$table->timestamp('last_cli_ran_at')->nullable();
$table->timestamps();
$table->unique(['team_id', 'name']);
@@ -13,6 +13,7 @@ return new class extends Migration
{
Schema::create('v5_servers', function (Blueprint $table) {
$table->id();
$table->string('uuid')->nullable()->unique();
$table->foreignId('team_id')->constrained('teams')->cascadeOnDelete();
$table->foreignId('cluster_id')->nullable()->constrained('v5_clusters')->nullOnDelete();
$table->foreignId('created_by_user_id')->constrained('users')->cascadeOnDelete();
@@ -25,7 +26,21 @@ return new class extends Migration
$table->json('capabilities')->nullable();
$table->boolean('builder_enabled')->default(false);
$table->unsignedInteger('builder_capacity')->default(0);
$table->string('builder_cpu_quota')->default('200%');
$table->string('node_address')->nullable();
$table->unsignedInteger('wireguard_listen_port_override')->nullable();
$table->string('wireguard_endpoint_override')->nullable();
$table->string('wireguard_management_ip')->nullable();
$table->string('wireguard_public_key')->nullable();
$table->json('container_subnets')->nullable();
$table->timestamp('last_bootstrapped_at')->nullable();
$table->string('last_bootstrap_action')->nullable();
$table->string('last_bootstrap_status')->nullable();
$table->text('last_bootstrap_output')->nullable();
$table->timestamp('last_bootstrap_ran_at')->nullable();
$table->string('last_status_check')->nullable();
$table->text('last_status_output')->nullable();
$table->timestamp('last_status_checked_at')->nullable();
$table->timestamps();
$table->unique(['team_id', 'host', 'ssh_port']);
@@ -1,90 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('v5_clusters', function (Blueprint $table) {
$table->string('wireguard_interface')->default('wg0')->after('description');
$table->string('wireguard_management_pool')->default('100.64.0.0/16')->after('wireguard_interface');
$table->unsignedInteger('wireguard_listen_port')->default(51820)->after('wireguard_management_pool');
$table->string('container_network_pool')->default('10.210.0.0/16')->after('wireguard_listen_port');
$table->unsignedTinyInteger('container_network_prefix')->default(24)->after('container_network_pool');
$table->json('namespaces')->nullable()->after('container_network_prefix');
$table->boolean('default_deny_containers')->default(true)->after('namespaces');
$table->string('coold_version')->default('nightly')->after('default_deny_containers');
$table->string('corrosion_version')->default('v1.0.0')->after('coold_version');
$table->unsignedInteger('corrosion_gossip_port')->default(8787)->after('corrosion_version');
$table->unsignedInteger('corrosion_api_port')->default(8080)->after('corrosion_gossip_port');
$table->boolean('builder_enabled')->default(true)->after('corrosion_api_port');
$table->unsignedInteger('builder_capacity')->default(2)->after('builder_enabled');
$table->string('builder_cpu_quota')->default('200%')->after('builder_capacity');
$table->string('builder_memory_max')->default('2G')->after('builder_cpu_quota');
$table->unsignedInteger('builder_timeout_secs')->default(1800)->after('builder_memory_max');
$table->string('last_cli_action')->nullable()->after('builder_timeout_secs');
$table->string('last_cli_status')->nullable()->after('last_cli_action');
$table->text('last_cli_summary')->nullable()->after('last_cli_status');
$table->timestamp('last_cli_ran_at')->nullable()->after('last_cli_summary');
});
Schema::table('v5_servers', function (Blueprint $table) {
$table->string('builder_cpu_quota')->default('200%')->after('builder_capacity');
$table->string('node_address')->nullable()->after('builder_cpu_quota');
$table->unsignedInteger('wireguard_listen_port_override')->nullable()->after('node_address');
$table->string('wireguard_endpoint_override')->nullable()->after('wireguard_listen_port_override');
$table->string('wireguard_management_ip')->nullable()->after('wireguard_endpoint_override');
$table->string('wireguard_public_key')->nullable()->after('wireguard_management_ip');
$table->json('container_subnets')->nullable()->after('wireguard_public_key');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('v5_servers', function (Blueprint $table) {
$table->dropColumn([
'builder_cpu_quota',
'node_address',
'wireguard_listen_port_override',
'wireguard_endpoint_override',
'wireguard_management_ip',
'wireguard_public_key',
'container_subnets',
]);
});
Schema::table('v5_clusters', function (Blueprint $table) {
$table->dropColumn([
'wireguard_interface',
'wireguard_management_pool',
'wireguard_listen_port',
'container_network_pool',
'container_network_prefix',
'namespaces',
'default_deny_containers',
'coold_version',
'corrosion_version',
'corrosion_gossip_port',
'corrosion_api_port',
'builder_enabled',
'builder_capacity',
'builder_cpu_quota',
'builder_memory_max',
'builder_timeout_secs',
'last_cli_action',
'last_cli_status',
'last_cli_summary',
'last_cli_ran_at',
]);
});
}
};
@@ -1,36 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
if (Schema::hasColumn('v5_servers', 'builder_cpu_quota')) {
return;
}
Schema::table('v5_servers', function (Blueprint $table) {
$table->string('builder_cpu_quota')->default('200%')->after('builder_capacity');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
if (! Schema::hasColumn('v5_servers', 'builder_cpu_quota')) {
return;
}
Schema::table('v5_servers', function (Blueprint $table) {
$table->dropColumn('builder_cpu_quota');
});
}
};
@@ -1,34 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('v5_servers', function (Blueprint $table) {
$table->string('last_status_check')->nullable()->after('last_bootstrapped_at');
$table->text('last_status_output')->nullable()->after('last_status_check');
$table->timestamp('last_status_checked_at')->nullable()->after('last_status_output');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('v5_servers', function (Blueprint $table) {
$table->dropColumn([
'last_status_check',
'last_status_output',
'last_status_checked_at',
]);
});
}
};
+8 -5
View File
@@ -1353,6 +1353,7 @@ CREATE TABLE IF NOT EXISTS "v5_clusters" (
CREATE TABLE IF NOT EXISTS "v5_servers" (
"id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
"uuid" TEXT,
"team_id" INTEGER NOT NULL,
"cluster_id" INTEGER,
"created_by_user_id" INTEGER NOT NULL,
@@ -1373,6 +1374,10 @@ CREATE TABLE IF NOT EXISTS "v5_servers" (
"wireguard_public_key" TEXT,
"container_subnets" JSON,
"last_bootstrapped_at" TEXT,
"last_bootstrap_action" TEXT,
"last_bootstrap_status" TEXT,
"last_bootstrap_output" TEXT,
"last_bootstrap_ran_at" TEXT,
"last_status_check" TEXT,
"last_status_output" TEXT,
"last_status_checked_at" TEXT,
@@ -1494,6 +1499,7 @@ CREATE INDEX IF NOT EXISTS "user_changelog_reads_release_tag_index" ON "user_cha
CREATE INDEX IF NOT EXISTS "user_changelog_reads_user_id_index" ON "user_changelog_reads" (user_id);
CREATE UNIQUE INDEX IF NOT EXISTS "user_changelog_reads_user_id_release_tag_unique" ON "user_changelog_reads" (user_id, release_tag);
CREATE UNIQUE INDEX IF NOT EXISTS "users_email_unique" ON "users" (email);
CREATE UNIQUE INDEX IF NOT EXISTS "v5_servers_uuid_unique" ON "v5_servers" (uuid);
CREATE UNIQUE INDEX IF NOT EXISTS "webhook_notification_settings_team_id_unique" ON "webhook_notification_settings" (team_id);
-- Migration records
@@ -1811,8 +1817,5 @@ INSERT INTO "migrations" ("id", "migration", "batch") VALUES (311, '2025_12_10_1
INSERT INTO "migrations" ("id", "migration", "batch") VALUES (312, '2025_12_15_143052_trim_s3_storage_credentials', 312);
INSERT INTO "migrations" ("id", "migration", "batch") VALUES (313, '2025_12_17_000001_add_is_wire_navigate_enabled_to_instance_settings_table', 313);
INSERT INTO "migrations" ("id", "migration", "batch") VALUES (314, '2025_12_17_000002_add_restart_tracking_to_standalone_databases', 314);
INSERT INTO "migrations" ("id", "migration", "batch") VALUES (316, '2026_06_16_130650_v5_create_servers_table', 316);
INSERT INTO "migrations" ("id", "migration", "batch") VALUES (317, '2026_06_16_130649_v5_create_clusters_table', 317);
INSERT INTO "migrations" ("id", "migration", "batch") VALUES (318, '2026_06_16_204644_v5_add_wireguard_cli_configuration_to_clusters_and_servers', 318);
INSERT INTO "migrations" ("id", "migration", "batch") VALUES (319, '2026_06_17_165112_v5_add_builder_cpu_quota_to_servers_table', 319);
INSERT INTO "migrations" ("id", "migration", "batch") VALUES (320, '2026_06_17_172845_add_status_check_fields_to_v5_servers_table', 320);
INSERT INTO "migrations" ("id", "migration", "batch") VALUES (316, '2026_06_16_130649_v5_create_clusters_table', 316);
INSERT INTO "migrations" ("id", "migration", "batch") VALUES (317, '2026_06_16_130650_v5_create_servers_table', 317);