feat(v5): add server clusters and CLI bootstrap

Create v5 cluster/server persistence, expose them on the home page,
and add a bootstrap endpoint backed by the coolify CLI. Add dev Lima
server sync support and update the dev script firewall flow.
This commit is contained in:
Andras Bacsai committed 2026-06-16 15:45:16 +02:00
1 parent 586588196d
commit 1e038fecae
16 files changed
+1389 -108

No files matched your search

@@ -0,0 +1,33 @@
<?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::create('v5_clusters', function (Blueprint $table) {
$table->id();
$table->foreignId('team_id')->constrained('teams')->cascadeOnDelete();
$table->foreignId('created_by_user_id')->constrained('users')->cascadeOnDelete();
$table->string('name');
$table->text('description')->nullable();
$table->timestamps();
$table->unique(['team_id', 'name']);
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('v5_clusters');
}
};
@@ -0,0 +1,42 @@
<?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::create('v5_servers', function (Blueprint $table) {
$table->id();
$table->foreignId('team_id')->constrained('teams')->cascadeOnDelete();
$table->foreignId('created_by_user_id')->constrained('users')->cascadeOnDelete();
$table->foreignId('private_key_id')->nullable()->constrained('private_keys')->nullOnDelete();
$table->string('name');
$table->string('host');
$table->string('ssh_user');
$table->unsignedInteger('ssh_port')->default(22);
$table->string('status')->default('installed');
$table->json('capabilities')->nullable();
$table->boolean('builder_enabled')->default(false);
$table->unsignedInteger('builder_capacity')->default(0);
$table->timestamp('last_bootstrapped_at')->nullable();
$table->timestamps();
$table->unique(['team_id', 'host', 'ssh_port']);
$table->index(['team_id', 'status']);
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('v5_servers');
}
};
@@ -0,0 +1,35 @@
<?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->foreignId('cluster_id')
->nullable()
->after('team_id')
->constrained('v5_clusters')
->nullOnDelete();
$table->index(['team_id', 'cluster_id']);
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('v5_servers', function (Blueprint $table) {
$table->dropIndex(['team_id', 'cluster_id']);
$table->dropConstrainedForeignId('cluster_id');
});
}
};
@@ -0,0 +1,28 @@
<?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->foreignId('private_key_id')->nullable()->change();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('v5_servers', function (Blueprint $table) {
$table->foreignId('private_key_id')->nullable(false)->change();
});
}
};
+35 -1
View File
@@ -689,7 +689,7 @@ CREATE TABLE IF NOT EXISTS "servers" (
"port" INTEGER DEFAULT 22 NOT NULL,
"user" TEXT DEFAULT 'root' NOT NULL,
"team_id" INTEGER NOT NULL,
"private_key_id" INTEGER NOT NULL,
"private_key_id" INTEGER,
"proxy" TEXT,
"created_at" TEXT,
"updated_at" TEXT,
@@ -1321,9 +1321,39 @@ CREATE TABLE IF NOT EXISTS "users" (
"email_change_code_expires_at" TEXT
);
CREATE TABLE IF NOT EXISTS "v5_clusters" (
"id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
"team_id" INTEGER NOT NULL,
"created_by_user_id" INTEGER NOT NULL,
"name" TEXT NOT NULL,
"description" TEXT,
"created_at" TEXT,
"updated_at" TEXT
);
CREATE TABLE IF NOT EXISTS "v5_servers" (
"id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
"team_id" INTEGER NOT NULL,
"cluster_id" INTEGER,
"created_by_user_id" INTEGER NOT NULL,
"private_key_id" INTEGER,
"name" TEXT NOT NULL,
"host" TEXT NOT NULL,
"ssh_user" TEXT NOT NULL,
"ssh_port" INTEGER DEFAULT '22' NOT NULL,
"status" TEXT DEFAULT 'installed' NOT NULL,
"capabilities" TEXT,
"builder_enabled" INTEGER DEFAULT false NOT NULL,
"builder_capacity" INTEGER DEFAULT '0' NOT NULL,
"last_bootstrapped_at" TEXT,
"created_at" TEXT,
"updated_at" TEXT
);
CREATE TABLE IF NOT EXISTS "v5_projects" (
"id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
"team_id" INTEGER NOT NULL,
"cluster_id" INTEGER,
"created_by_user_id" INTEGER NOT NULL,
"name" TEXT NOT NULL,
"description" TEXT,
@@ -1763,3 +1793,7 @@ INSERT INTO "migrations" ("id", "migration", "batch") VALUES (312, '2025_12_15_1
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 (315, '2026_06_04_050157_create_v5_projects_table', 315);
INSERT INTO "migrations" ("id", "migration", "batch") VALUES (316, '2026_06_16_130650_create_v5_servers_table', 316);
INSERT INTO "migrations" ("id", "migration", "batch") VALUES (317, '2026_06_16_130649_create_v5_clusters_table', 317);
INSERT INTO "migrations" ("id", "migration", "batch") VALUES (318, '2026_06_16_131229_add_cluster_id_to_v5_servers_table', 318);
INSERT INTO "migrations" ("id", "migration", "batch") VALUES (319, '2026_06_16_132000_make_v5_server_private_key_nullable', 319);