This commit is contained in:
Andras Bacsai 2023-11-28 15:49:24 +01:00
parent 706e4b13ee
commit c505a6ce9c
12 changed files with 198 additions and 55 deletions

View File

@ -13,6 +13,9 @@ class StartProxy
public function handle(Server $server, bool $async = true): string|Activity public function handle(Server $server, bool $async = true): string|Activity
{ {
try { try {
if ($server->isSwarm()) {
throw new \Exception("Server is part of swarm, not implemented yet.");
}
$proxyType = $server->proxyType(); $proxyType = $server->proxyType();
$commands = collect([]); $commands = collect([]);
$proxy_path = get_proxy_path(); $proxy_path = get_proxy_path();
@ -46,11 +49,9 @@ class StartProxy
$server->save(); $server->save();
return 'OK'; return 'OK';
} }
} catch(\Throwable $e) { } catch (\Throwable $e) {
ray($e); ray($e);
throw $e; throw $e;
} }
} }
} }

View File

@ -31,6 +31,7 @@ class Index extends Component
public ?string $remoteServerHost = null; public ?string $remoteServerHost = null;
public ?int $remoteServerPort = 22; public ?int $remoteServerPort = 22;
public ?string $remoteServerUser = 'root'; public ?string $remoteServerUser = 'root';
public bool $isPartOfSwarm = false;
public ?Server $createdServer = null; public ?Server $createdServer = null;
public Collection $projects; public Collection $projects;
@ -182,7 +183,9 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
'private_key_id' => $this->createdPrivateKey->id, 'private_key_id' => $this->createdPrivateKey->id,
'team_id' => currentTeam()->id, 'team_id' => currentTeam()->id,
]); ]);
$this->createdServer->save(); $this->createdServer->settings->is_part_of_swarm = $this->isPartOfSwarm;
$this->createdServer->settings->save();
$this->createdServer->addInitialNetwork();
$this->validateServer(); $this->validateServer();
} }
public function validateServer() public function validateServer()

View File

@ -14,6 +14,8 @@ class Dashboard extends Component
public function mount() public function mount()
{ {
$this->servers = Server::ownedByCurrentTeam()->get(); $this->servers = Server::ownedByCurrentTeam()->get();
ray($this->servers[1]);
ray($this->servers[1]->standaloneDockers);
$this->projects = Project::ownedByCurrentTeam()->get(); $this->projects = Project::ownedByCurrentTeam()->get();
} }
// public function getIptables() // public function getIptables()

View File

@ -126,6 +126,7 @@ class Select extends Component
{ {
$this->server_id = $server->id; $this->server_id = $server->id;
$this->standaloneDockers = $server->standaloneDockers; $this->standaloneDockers = $server->standaloneDockers;
ray($server);
$this->swarmDockers = $server->swarmDockers; $this->swarmDockers = $server->swarmDockers;
$this->current_step = 'destinations'; $this->current_step = 'destinations';
} }

View File

@ -17,14 +17,14 @@ class Form extends Component
protected $listeners = ['serverRefresh']; protected $listeners = ['serverRefresh'];
protected $rules = [ protected $rules = [
'server.name' => 'required|min:6', 'server.name' => 'required',
'server.description' => 'nullable', 'server.description' => 'nullable',
'server.ip' => 'required', 'server.ip' => 'required',
'server.user' => 'required', 'server.user' => 'required',
'server.port' => 'required', 'server.port' => 'required',
'server.settings.is_cloudflare_tunnel' => 'required', 'server.settings.is_cloudflare_tunnel' => 'required|boolean',
'server.settings.is_reachable' => 'required', 'server.settings.is_reachable' => 'required',
'server.settings.is_part_of_swarm' => 'required', 'server.settings.is_part_of_swarm' => 'required|boolean',
'wildcard_domain' => 'nullable|url', 'wildcard_domain' => 'nullable|url',
]; ];
protected $validationAttributes = [ protected $validationAttributes = [
@ -49,9 +49,14 @@ class Form extends Component
} }
public function instantSave() public function instantSave()
{ {
refresh_server_connection($this->server->privateKey); try {
$this->validateServer(); refresh_server_connection($this->server->privateKey);
$this->server->settings->save(); $this->validateServer(false);
$this->server->settings->save();
$this->emit('success', 'Server updated successfully.');
} catch (\Throwable $e) {
return handleError($e, $this);
}
} }
public function installDocker() public function installDocker()
{ {
@ -100,6 +105,12 @@ class Form extends Component
$install && $this->installDocker(); $install && $this->installDocker();
return; return;
} }
if ($this->server->isSwarm()) {
$swarmInstalled = $this->server->validateDockerSwarm();
if ($swarmInstalled) {
$install && $this->emit('success', 'Docker Swarm is initiated.');
}
}
} catch (\Throwable $e) { } catch (\Throwable $e) {
return handleError($e, $this); return handleError($e, $this);
} finally { } finally {

View File

@ -29,6 +29,7 @@ class ByIp extends Component
'ip' => 'required', 'ip' => 'required',
'user' => 'required|string', 'user' => 'required|string',
'port' => 'required|integer', 'port' => 'required|integer',
'is_part_of_swarm' => 'required|boolean',
]; ];
protected $validationAttributes = [ protected $validationAttributes = [
'name' => 'Name', 'name' => 'Name',
@ -36,6 +37,7 @@ class ByIp extends Component
'ip' => 'IP Address/Domain', 'ip' => 'IP Address/Domain',
'user' => 'User', 'user' => 'User',
'port' => 'Port', 'port' => 'Port',
'is_part_of_swarm' => 'Is part of swarm',
]; ];
public function mount() public function mount()
@ -72,11 +74,11 @@ class ByIp extends Component
'proxy' => [ 'proxy' => [
"type" => ProxyTypes::TRAEFIK_V2->value, "type" => ProxyTypes::TRAEFIK_V2->value,
"status" => ProxyStatus::EXITED->value, "status" => ProxyStatus::EXITED->value,
] ],
]); ]);
$server->settings->is_part_of_swarm = $this->is_part_of_swarm; $server->settings->is_part_of_swarm = $this->is_part_of_swarm;
$server->settings->save(); $server->settings->save();
$server->addInitialNetwork();
return redirect()->route('server.show', $server->uuid); return redirect()->route('server.show', $server->uuid);
} catch (\Throwable $e) { } catch (\Throwable $e) {
return handleError($e, $this); return handleError($e, $this);

View File

@ -35,25 +35,10 @@ class Server extends BaseModel
} }
$server->forceFill($payload); $server->forceFill($payload);
}); });
static::created(function ($server) { static::created(function ($server) {
ServerSetting::create([ ServerSetting::create([
'server_id' => $server->id, 'server_id' => $server->id,
]); ]);
if ($server->id === 0) {
StandaloneDocker::create([
'id' => 0,
'name' => 'coolify',
'network' => 'coolify',
'server_id' => $server->id,
]);
} else {
StandaloneDocker::create([
'name' => 'coolify',
'network' => 'coolify',
'server_id' => $server->id,
]);
}
}); });
static::deleting(function ($server) { static::deleting(function ($server) {
$server->destinations()->each(function ($destination) { $server->destinations()->each(function ($destination) {
@ -101,7 +86,39 @@ class Server extends BaseModel
{ {
return $this->hasOne(ServerSetting::class); return $this->hasOne(ServerSetting::class);
} }
public function addInitialNetwork() {
if ($this->id === 0) {
if ($this->isSwarm()) {
SwarmDocker::create([
'id' => 0,
'name' => 'coolify',
'network' => 'coolify-overlay',
'server_id' => $this->id,
]);
} else {
StandaloneDocker::create([
'id' => 0,
'name' => 'coolify',
'network' => 'coolify',
'server_id' => $this->id,
]);
}
} else {
if ($this->isSwarm()) {
SwarmDocker::create([
'name' => 'coolify',
'network' => 'coolify-overlay',
'server_id' => $this->id,
]);
} else {
StandaloneDocker::create([
'name' => 'coolify',
'network' => 'coolify',
'server_id' => $this->id,
]);
}
}
}
public function proxyType() public function proxyType()
{ {
$proxyType = $this->proxy->get('type'); $proxyType = $this->proxy->get('type');
@ -357,12 +374,16 @@ class Server extends BaseModel
return false; return false;
} }
} }
public function isSwarm()
{
return data_get($this, 'settings.is_part_of_swarm');
}
public function validateConnection() public function validateConnection()
{ {
$server = Server::find($this->id);
if ($this->skipServer()) { if ($this->skipServer()) {
return false; return false;
} }
$uptime = instant_remote_process(['uptime'], $this, false); $uptime = instant_remote_process(['uptime'], $this, false);
if (!$uptime) { if (!$uptime) {
$this->settings()->update([ $this->settings()->update([
@ -373,14 +394,14 @@ class Server extends BaseModel
$this->settings()->update([ $this->settings()->update([
'is_reachable' => true, 'is_reachable' => true,
]); ]);
$this->update([ $server->update([
'unreachable_count' => 0, 'unreachable_count' => 0,
]); ]);
} }
if (data_get($this, 'unreachable_notification_sent') === true) { if (data_get($this, 'unreachable_notification_sent') === true) {
$this->team->notify(new Revived($this)); $this->team->notify(new Revived($this));
$this->update(['unreachable_notification_sent' => false]); $server->update(['unreachable_notification_sent' => false]);
} }
return true; return true;
@ -398,7 +419,20 @@ class Server extends BaseModel
} }
$this->settings->is_usable = true; $this->settings->is_usable = true;
$this->settings->save(); $this->settings->save();
$this->validateCoolifyNetwork(); $this->validateCoolifyNetwork(isSwarm: false);
return true;
}
public function validateDockerSwarm()
{
$swarmStatus = instant_remote_process(["docker info|grep -i swarm"], $this, false);
$swarmStatus = str($swarmStatus)->trim()->after(':')->trim();
if ($swarmStatus === 'inactive') {
throw new \Exception('Docker Swarm is not initiated. Please join the server to a swarm before continuing.');
return false;
}
$this->settings->is_usable = true;
$this->settings->save();
$this->validateCoolifyNetwork(isSwarm: true);
return true; return true;
} }
public function validateDockerEngineVersion() public function validateDockerEngineVersion()
@ -415,9 +449,13 @@ class Server extends BaseModel
$this->settings->save(); $this->settings->save();
return true; return true;
} }
public function validateCoolifyNetwork() public function validateCoolifyNetwork($isSwarm = false)
{ {
return instant_remote_process(["docker network create coolify --attachable >/dev/null 2>&1 || true"], $this, false); if ($isSwarm) {
return instant_remote_process(["docker network create --driver overlay coolify-overlay >/dev/null 2>&1 || true"], $this, false);
} else {
return instant_remote_process(["docker network create coolify --attachable >/dev/null 2>&1 || true"], $this, false);
}
} }
public function executeRemoteCommand(Collection $commands, ?ApplicationDeploymentQueue $loggingModel = null) public function executeRemoteCommand(Collection $commands, ?ApplicationDeploymentQueue $loggingModel = null)
{ {

View File

@ -4,13 +4,57 @@ namespace App\Models;
class SwarmDocker extends BaseModel class SwarmDocker extends BaseModel
{ {
protected $guarded = [];
public function applications() public function applications()
{ {
return $this->morphMany(Application::class, 'destination'); return $this->morphMany(Application::class, 'destination');
} }
public function postgresqls()
{
return $this->morphMany(StandalonePostgresql::class, 'destination');
}
public function redis()
{
return $this->morphMany(StandaloneRedis::class, 'destination');
}
public function mongodbs()
{
return $this->morphMany(StandaloneMongodb::class, 'destination');
}
public function mysqls()
{
return $this->morphMany(StandaloneMysql::class, 'destination');
}
public function mariadbs()
{
return $this->morphMany(StandaloneMariadb::class, 'destination');
}
public function server() public function server()
{ {
return $this->belongsTo(Server::class); return $this->belongsTo(Server::class);
} }
public function services()
{
return $this->morphMany(Service::class, 'destination');
}
public function databases()
{
$postgresqls = $this->postgresqls;
$redis = $this->redis;
$mongodbs = $this->mongodbs;
$mysqls = $this->mysqls;
$mariadbs = $this->mariadbs;
return $postgresqls->concat($redis)->concat($mongodbs)->concat($mysqls)->concat($mariadbs);
}
public function attachedTo()
{
return $this->applications?->count() > 0 || $this->databases()->count() > 0;
}
} }

View File

@ -0,0 +1,30 @@
<?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('swarm_dockers', function (Blueprint $table) {
$table->string('network');
$table->unique(['server_id', 'network']);
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('swarm_dockers', function (Blueprint $table) {
$table->dropColumn('network');
});
}
};

View File

@ -131,16 +131,16 @@
</form> </form>
</div> </div>
@if (!$serverReachable) @if (!$serverReachable)
This server is not reachable with the following public key. This server is not reachable with the following public key.
<br /> <br /> <br /> <br />
Please make sure you have the correct public key in your ~/.ssh/authorized_keys file for user Please make sure you have the correct public key in your ~/.ssh/authorized_keys file for user
'root' or skip the boarding process and add a new private key manually to Coolify and to the 'root' or skip the boarding process and add a new private key manually to Coolify and to the
server. server.
<x-forms.input readonly id="serverPublicKey"></x-forms.input> <x-forms.input readonly id="serverPublicKey"></x-forms.input>
<x-forms.button class="box" wire:target="validateServer" <x-forms.button class="box" wire:target="validateServer" wire:click="validateServer">Check
wire:click="validateServer">Check again again
</x-forms.button> </x-forms.button>
@endif @endif
</x-slot:actions> </x-slot:actions>
<x-slot:explanation> <x-slot:explanation>
<p>Private Keys are used to connect to a remote server through a secure shell, called SSH.</p> <p>Private Keys are used to connect to a remote server through a secure shell, called SSH.</p>
@ -200,14 +200,17 @@
label="Description" id="remoteServerDescription" /> label="Description" id="remoteServerDescription" />
</div> </div>
<div class="flex gap-2"> <div class="flex gap-2">
<x-forms.input required placeholder="127.0.0.1" label="IP Address" <x-forms.input required placeholder="127.0.0.1" label="IP Address" id="remoteServerHost" />
id="remoteServerHost" />
<x-forms.input required placeholder="Port number of your server. Default is 22." <x-forms.input required placeholder="Port number of your server. Default is 22."
label="Port" id="remoteServerPort" /> label="Port" id="remoteServerPort" />
<x-forms.input required readonly <x-forms.input required readonly
placeholder="Username to connect to your server. Default is root." label="Username" placeholder="Username to connect to your server. Default is root." label="Username"
id="remoteServerUser" /> id="remoteServerUser" />
</div> </div>
<div class="w-64">
<x-forms.checkbox type="checkbox" id="isPartOfSwarm"
label="Is it part of a Swarm cluster?" />
</div>
<x-forms.button type="submit">Check Connection</x-forms.button> <x-forms.button type="submit">Check Connection</x-forms.button>
</form> </form>
</x-slot:actions> </x-slot:actions>
@ -226,7 +229,7 @@
</x-slot:question> </x-slot:question>
<x-slot:actions> <x-slot:actions>
<x-forms.button class="justify-center box" wire:click="installDocker"> <x-forms.button class="justify-center box" wire:click="installDocker">
Let's do it!</x-forms.button> Let's do it!</x-forms.button>
@if ($dockerInstallationStarted) @if ($dockerInstallationStarted)
<x-forms.button class="justify-center box" wire:click="dockerInstalledOrSkipped"> <x-forms.button class="justify-center box" wire:click="dockerInstalledOrSkipped">
Validate Server & Continue</x-forms.button> Validate Server & Continue</x-forms.button>
@ -234,7 +237,10 @@
</x-slot:actions> </x-slot:actions>
<x-slot:explanation> <x-slot:explanation>
<p>This will install the latest Docker Engine on your server, configure a few things to be able <p>This will install the latest Docker Engine on your server, configure a few things to be able
to run optimal.<br><br>Minimum Docker Engine version is: 22<br><br>To manually install Docker Engine, check <a target="_blank" class="underline text-warning" href="https://coolify.io/docs/servers#install-docker-engine-manually">this documentation</a>.</p> to run optimal.<br><br>Minimum Docker Engine version is: 22<br><br>To manually install Docker
Engine, check <a target="_blank" class="underline text-warning"
href="https://coolify.io/docs/servers#install-docker-engine-manually">this
documentation</a>.</p>
</x-slot:explanation> </x-slot:explanation>
</x-boarding-step> </x-boarding-step>

View File

@ -38,8 +38,7 @@
<x-forms.input id="server.description" label="Description" /> <x-forms.input id="server.description" label="Description" />
<x-forms.input placeholder="https://example.com" id="wildcard_domain" label="Wildcard Domain" <x-forms.input placeholder="https://example.com" id="wildcard_domain" label="Wildcard Domain"
helper="Wildcard domain for your applications. If you set this, you will get a random generated domain for your new applications.<br><span class='font-bold text-white'>Example:</span><br>In case you set:<span class='text-helper'>https://example.com</span> your applications will get:<br> <span class='text-helper'>https://randomId.example.com</span>" /> helper="Wildcard domain for your applications. If you set this, you will get a random generated domain for your new applications.<br><span class='font-bold text-white'>Example:</span><br>In case you set:<span class='text-helper'>https://example.com</span> your applications will get:<br> <span class='text-helper'>https://randomId.example.com</span>" />
{{-- <x-forms.checkbox disabled type="checkbox" id="server.settings.is_part_of_swarm"
label="Is it part of a Swarm cluster?" /> --}}
</div> </div>
<div class="flex flex-col w-full gap-2 lg:flex-row"> <div class="flex flex-col w-full gap-2 lg:flex-row">
<x-forms.input id="server.ip" label="IP Address/Domain" <x-forms.input id="server.ip" label="IP Address/Domain"
@ -49,13 +48,15 @@
<x-forms.input type="number" id="server.port" label="Port" required /> <x-forms.input type="number" id="server.port" label="Port" required />
</div> </div>
</div> </div>
@if (!$server->isLocalhost()) <div class="w-64">
<div class="w-64"> @if (!$server->isLocalhost())
<x-forms.checkbox instantSave <x-forms.checkbox instantSave
helper="If you are using Cloudflare Tunnels, enable this. It will proxy all ssh requests to your server through Cloudflare.<span class='text-warning'>Coolify does not install/setup Cloudflare (cloudflared) on your server.</span>" helper="If you are using Cloudflare Tunnels, enable this. It will proxy all ssh requests to your server through Cloudflare.<span class='text-warning'>Coolify does not install/setup Cloudflare (cloudflared) on your server.</span>"
id="server.settings.is_cloudflare_tunnel" label="Cloudflare Tunnel" /> id="server.settings.is_cloudflare_tunnel" label="Cloudflare Tunnel" />
</div> @endif
@endif <x-forms.checkbox instantSave type="checkbox" id="server.settings.is_part_of_swarm"
label="Is it part of a Swarm cluster?" />
</div>
</div> </div>
@if ($server->isFunctional()) @if ($server->isFunctional())

View File

@ -3,7 +3,7 @@
<x-limit-reached name="servers" /> <x-limit-reached name="servers" />
@else @else
<h1>Create a new Server</h1> <h1>Create a new Server</h1>
<div class="subtitle ">Servers are the main blocks of your infrastructure.</div> <div class="subtitle">Servers are the main blocks of your infrastructure.</div>
<form class="flex flex-col gap-2" wire:submit.prevent='submit'> <form class="flex flex-col gap-2" wire:submit.prevent='submit'>
<div class="flex gap-2"> <div class="flex gap-2">
<x-forms.input id="name" label="Name" required /> <x-forms.input id="name" label="Name" required />
@ -25,6 +25,10 @@
@endif @endif
@endforeach @endforeach
</x-forms.select> </x-forms.select>
<div class="w-64">
<x-forms.checkbox type="checkbox" id="is_part_of_swarm"
label="Is it part of a Swarm cluster?" />
</div>
<x-forms.button type="submit"> <x-forms.button type="submit">
Save New Server Save New Server
</x-forms.button> </x-forms.button>