Merge pull request #1236 from coollabsio/next

v4.0.0-beta.41
This commit is contained in:
Andras Bacsai 2023-09-18 12:26:00 +02:00 committed by GitHub
commit 6bb6de188c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 378 additions and 177 deletions

View File

@ -2,12 +2,14 @@
namespace App\Actions\Proxy; namespace App\Actions\Proxy;
use Lorisleiva\Actions\Concerns\AsAction;
use App\Models\Server; use App\Models\Server;
use Illuminate\Support\Str; use Illuminate\Support\Str;
class CheckConfigurationSync class CheckConfiguration
{ {
public function __invoke(Server $server, bool $reset = false) use AsAction;
public function handle(Server $server, bool $reset = false)
{ {
$proxy_path = get_proxy_path(); $proxy_path = get_proxy_path();
$proxy_configuration = instant_remote_process([ $proxy_configuration = instant_remote_process([

View File

@ -0,0 +1,27 @@
<?php
namespace App\Actions\Proxy;
use App\Models\Server;
use Illuminate\Support\Str;
use Lorisleiva\Actions\Concerns\AsAction;
class SaveConfiguration
{
use AsAction;
public function handle(Server $server)
{
$proxy_settings = CheckConfiguration::run($server, true);
$proxy_path = get_proxy_path();
$docker_compose_yml_base64 = base64_encode($proxy_settings);
$server->proxy->last_saved_settings = Str::of($docker_compose_yml_base64)->pipe('md5')->value;
$server->save();
return instant_remote_process([
"mkdir -p $proxy_path",
"echo '$docker_compose_yml_base64' | base64 -d > $proxy_path/docker-compose.yml",
], $server);
}
}

View File

@ -1,29 +0,0 @@
<?php
namespace App\Actions\Proxy;
use App\Models\Server;
use Illuminate\Support\Str;
class SaveConfigurationSync
{
public function __invoke(Server $server)
{
try {
$proxy_settings = resolve(CheckConfigurationSync::class)($server, true);
$proxy_path = get_proxy_path();
$docker_compose_yml_base64 = base64_encode($proxy_settings);
$server->proxy->last_saved_settings = Str::of($docker_compose_yml_base64)->pipe('md5')->value;
$server->save();
instant_remote_process([
"mkdir -p $proxy_path",
"echo '$docker_compose_yml_base64' | base64 -d > $proxy_path/docker-compose.yml",
], $server);
} catch (\Throwable $e) {
ray($e);
}
}
}

View File

@ -32,8 +32,10 @@ public function __invoke(Server $server, bool $async = true): Activity|string
return "docker network ls --format '{{.Name}}' | grep '^$network$' >/dev/null 2>&1 || docker network create --attachable $network > /dev/null 2>&1"; return "docker network ls --format '{{.Name}}' | grep '^$network$' >/dev/null 2>&1 || docker network create --attachable $network > /dev/null 2>&1";
}); });
$configuration = resolve(CheckConfigurationSync::class)($server); $configuration = CheckConfiguration::run($server);
if (!$configuration) {
throw new \Exception("Configuration is not synced");
}
$docker_compose_yml_base64 = base64_encode($configuration); $docker_compose_yml_base64 = base64_encode($configuration);
$server->proxy->last_applied_settings = Str::of($docker_compose_yml_base64)->pipe('md5')->value; $server->proxy->last_applied_settings = Str::of($docker_compose_yml_base64)->pipe('md5')->value;
$server->save(); $server->save();

View File

@ -4,11 +4,10 @@
use App\Models\Server; use App\Models\Server;
use App\Models\StandaloneDocker; use App\Models\StandaloneDocker;
use App\Models\Team;
class InstallDocker class InstallDocker
{ {
public function __invoke(Server $server, Team $team) public function __invoke(Server $server, bool $instant = false)
{ {
$dockerVersion = '24.0'; $dockerVersion = '24.0';
$config = base64_encode('{ $config = base64_encode('{
@ -19,15 +18,16 @@ public function __invoke(Server $server, Team $team)
} }
}'); }');
$found = StandaloneDocker::where('server_id', $server->id); $found = StandaloneDocker::where('server_id', $server->id);
if ($found->count() == 0) { if ($found->count() == 0 && $server->id) {
StandaloneDocker::create([ StandaloneDocker::create([
'name' => 'coolify', 'name' => 'coolify',
'network' => 'coolify', 'network' => 'coolify',
'server_id' => $server->id, 'server_id' => $server->id,
]); ]);
} }
if (isDev()) {
return remote_process([ if (isDev() && $server->id === 0) {
$command = [
"echo '####### Installing Prerequisites...'", "echo '####### Installing Prerequisites...'",
"sleep 1", "sleep 1",
"echo '####### Installing/updating Docker Engine...'", "echo '####### Installing/updating Docker Engine...'",
@ -35,9 +35,9 @@ public function __invoke(Server $server, Team $team)
"sleep 4", "sleep 4",
"echo '####### Restarting Docker Engine...'", "echo '####### Restarting Docker Engine...'",
"ls -l /tmp" "ls -l /tmp"
], $server); ];
} else { } else {
return remote_process([ $command = [
"echo '####### Installing Prerequisites...'", "echo '####### Installing Prerequisites...'",
"command -v jq >/dev/null || apt-get update", "command -v jq >/dev/null || apt-get update",
"command -v jq >/dev/null || apt install -y jq", "command -v jq >/dev/null || apt install -y jq",
@ -53,7 +53,11 @@ public function __invoke(Server $server, Team $team)
"echo '####### Creating default Docker network (coolify)...'", "echo '####### Creating default Docker network (coolify)...'",
"docker network create --attachable coolify >/dev/null 2>&1 || true", "docker network create --attachable coolify >/dev/null 2>&1 || true",
"echo '####### Done!'" "echo '####### Done!'"
], $server); ];
} }
if ($instant) {
return instant_remote_process($command, $server);
}
return remote_process($command, $server);
} }
} }

View File

@ -15,6 +15,7 @@ class Index extends Component
{ {
public string $currentState = 'welcome'; public string $currentState = 'welcome';
public ?string $selectedServerType = null;
public ?Collection $privateKeys = null; public ?Collection $privateKeys = null;
public ?int $selectedExistingPrivateKey = null; public ?int $selectedExistingPrivateKey = null;
public ?string $privateKeyType = null; public ?string $privateKeyType = null;
@ -37,6 +38,7 @@ class Index extends Component
public ?int $selectedExistingProject = null; public ?int $selectedExistingProject = null;
public ?Project $createdProject = null; public ?Project $createdProject = null;
public bool $dockerInstallationStarted = false;
public function mount() public function mount()
{ {
$this->privateKeyName = generate_random_name(); $this->privateKeyName = generate_random_name();
@ -64,12 +66,14 @@ public function explanation()
public function restartBoarding() public function restartBoarding()
{ {
if ($this->createdServer) { // if ($this->selectedServerType !== 'localhost') {
$this->createdServer->delete(); // if ($this->createdServer) {
} // $this->createdServer->delete();
if ($this->createdPrivateKey) { // }
$this->createdPrivateKey->delete(); // if ($this->createdPrivateKey) {
} // $this->createdPrivateKey->delete();
// }
// }
return redirect()->route('boarding'); return redirect()->route('boarding');
} }
public function skipBoarding() public function skipBoarding()
@ -84,13 +88,14 @@ public function skipBoarding()
public function setServerType(string $type) public function setServerType(string $type)
{ {
if ($type === 'localhost') { $this->selectedServerType = $type;
if ($this->selectedServerType === 'localhost') {
$this->createdServer = Server::find(0); $this->createdServer = Server::find(0);
if (!$this->createdServer) { if (!$this->createdServer) {
return $this->emit('error', 'Localhost server is not found. Something went wrong during installation. Please try to reinstall or contact support.'); return $this->emit('error', 'Localhost server is not found. Something went wrong during installation. Please try to reinstall or contact support.');
} }
return $this->validateServer('localhost'); return $this->validateServer('localhost');
} elseif ($type === 'remote') { } elseif ($this->selectedServerType === 'remote') {
$this->privateKeys = PrivateKey::ownedByCurrentTeam(['name'])->where('id', '!=', 0)->get(); $this->privateKeys = PrivateKey::ownedByCurrentTeam(['name'])->where('id', '!=', 0)->get();
if ($this->privateKeys->count() > 0) { if ($this->privateKeys->count() > 0) {
$this->selectedExistingPrivateKey = $this->privateKeys->first()->id; $this->selectedExistingPrivateKey = $this->privateKeys->first()->id;
@ -114,8 +119,6 @@ public function selectExistingServer()
} }
$this->selectedExistingPrivateKey = $this->createdServer->privateKey->id; $this->selectedExistingPrivateKey = $this->createdServer->privateKey->id;
$this->validateServer(); $this->validateServer();
$this->getProxyType();
$this->getProjects();
} }
public function getProxyType() public function getProxyType()
{ {
@ -128,6 +131,7 @@ public function getProxyType()
} }
public function selectExistingPrivateKey() public function selectExistingPrivateKey()
{ {
$this->createdPrivateKey = PrivateKey::find($this->selectedExistingPrivateKey);
$this->currentState = 'create-server'; $this->currentState = 'create-server';
} }
public function createNewServer() public function createNewServer()
@ -150,39 +154,38 @@ public function savePrivateKey()
'privateKeyName' => 'required', 'privateKeyName' => 'required',
'privateKey' => 'required', 'privateKey' => 'required',
]); ]);
$this->createdPrivateKey = PrivateKey::create([
'name' => $this->privateKeyName,
'description' => $this->privateKeyDescription,
'private_key' => $this->privateKey,
'team_id' => currentTeam()->id
]);
$this->createdPrivateKey->save();
$this->currentState = 'create-server'; $this->currentState = 'create-server';
} }
public function saveServer() public function saveServer()
{ {
$this->validate([ $this->validate([
'remoteServerName' => 'required', 'remoteServerName' => 'required',
'remoteServerHost' => 'required|ip', 'remoteServerHost' => 'required',
'remoteServerPort' => 'required|integer', 'remoteServerPort' => 'required|integer',
'remoteServerUser' => 'required', 'remoteServerUser' => 'required',
]); ]);
$this->privateKey = formatPrivateKey($this->privateKey); $this->privateKey = formatPrivateKey($this->privateKey);
$this->createdPrivateKey = new PrivateKey();
$this->createdPrivateKey->private_key = $this->privateKey;
$this->createdPrivateKey->name = $this->privateKeyName;
$this->createdPrivateKey->description = $this->privateKeyDescription;
$this->createdPrivateKey->team_id = currentTeam()->id;
$foundServer = Server::whereIp($this->remoteServerHost)->first(); $foundServer = Server::whereIp($this->remoteServerHost)->first();
if ($foundServer) { if ($foundServer) {
return $this->emit('error', 'IP address is already in use by another team.'); return $this->emit('error', 'IP address is already in use by another team.');
} }
$this->createdServer = new Server(); $this->createdServer = Server::create([
$this->createdServer->uuid = (string)new Cuid2(7); 'name' => $this->remoteServerName,
$this->createdServer->name = $this->remoteServerName; 'ip' => $this->remoteServerHost,
$this->createdServer->ip = $this->remoteServerHost; 'port' => $this->remoteServerPort,
$this->createdServer->port = $this->remoteServerPort; 'user' => $this->remoteServerUser,
$this->createdServer->user = $this->remoteServerUser; 'description' => $this->remoteServerDescription,
$this->createdServer->description = $this->remoteServerDescription; 'private_key_id' => $this->createdPrivateKey->id,
$this->createdServer->privateKey = $this->createdPrivateKey; 'team_id' => currentTeam()->id,
$this->createdServer->team_id = currentTeam()->id; ]);
$this->createdServer->save();
ray($this->createdServer);
$this->validateServer(); $this->validateServer();
} }
public function validateServer(?string $type = null) public function validateServer(?string $type = null)
@ -190,47 +193,36 @@ public function validateServer(?string $type = null)
try { try {
$customErrorMessage = "Server is not reachable:"; $customErrorMessage = "Server is not reachable:";
config()->set('coolify.mux_enabled', false); config()->set('coolify.mux_enabled', false);
instant_remote_process(['uptime'], $this->createdServer, true); instant_remote_process(['uptime'], $this->createdServer, true);
$this->createdServer->settings->update([
'is_reachable' => true,
]);
$dockerVersion = instant_remote_process(["docker version|head -2|grep -i version| awk '{print $2}'"], $this->createdServer, true); $dockerVersion = instant_remote_process(["docker version|head -2|grep -i version| awk '{print $2}'"], $this->createdServer, true);
$dockerVersion = checkMinimumDockerEngineVersion($dockerVersion); $dockerVersion = checkMinimumDockerEngineVersion($dockerVersion);
if (is_null($dockerVersion)) { if (is_null($dockerVersion)) {
throw new \Exception('No Docker Engine or older than 23 version installed.'); $this->currentState = 'install-docker';
throw new \Exception('Docker version is not supported or not installed.');
} }
$customErrorMessage = "Cannot create Server or Private Key. Please try again."; $this->dockerInstalledOrSkipped();
if ($type !== 'localhost') {
$createdPrivateKey = PrivateKey::create([
'name' => $this->privateKeyName,
'description' => $this->privateKeyDescription,
'private_key' => $this->privateKey,
'team_id' => currentTeam()->id
]);
$server = Server::create([
'name' => $this->remoteServerName,
'ip' => $this->remoteServerHost,
'port' => $this->remoteServerPort,
'user' => $this->remoteServerUser,
'description' => $this->remoteServerDescription,
'private_key_id' => $createdPrivateKey->id,
'team_id' => currentTeam()->id,
]);
$server->settings->is_reachable = true;
$server->settings->is_usable = true;
$server->settings->save();
} else {
$this->createdServer->settings->update([
'is_reachable' => true,
]);
}
$this->getProxyType();
} catch (\Throwable $e) { } catch (\Throwable $e) {
return handleError(error: $e, customErrorMessage: $customErrorMessage, livewire: $this); return handleError(error: $e, customErrorMessage: $customErrorMessage, livewire: $this);
} }
} }
public function installDocker() public function installDocker()
{ {
$activity = resolve(InstallDocker::class)($this->createdServer, currentTeam()); $this->dockerInstallationStarted = true;
$activity = resolve(InstallDocker::class)($this->createdServer);
$this->emit('newMonitorActivity', $activity->id); $this->emit('newMonitorActivity', $activity->id);
$this->currentState = 'select-proxy'; }
public function dockerInstalledOrSkipped()
{
$this->createdServer->settings->update([
'is_usable' => true,
]);
$this->getProxyType();
} }
public function selectProxy(string|null $proxyType = null) public function selectProxy(string|null $proxyType = null)
{ {

View File

@ -15,6 +15,7 @@ class Form extends Component
public $dockerVersion; public $dockerVersion;
public string|null $wildcard_domain = null; public string|null $wildcard_domain = null;
public int $cleanup_after_percentage; public int $cleanup_after_percentage;
public bool $dockerInstallationStarted = false;
protected $rules = [ protected $rules = [
'server.name' => 'required|min:6', 'server.name' => 'required|min:6',
@ -44,7 +45,8 @@ public function mount()
public function installDocker() public function installDocker()
{ {
$activity = resolve(InstallDocker::class)($this->server, currentTeam()); $this->dockerInstallationStarted = true;
$activity = resolve(InstallDocker::class)($this->server);
$this->emit('newMonitorActivity', $activity->id); $this->emit('newMonitorActivity', $activity->id);
} }
@ -56,7 +58,10 @@ public function validateServer()
$this->uptime = $uptime; $this->uptime = $uptime;
$this->emit('success', 'Server is reachable.'); $this->emit('success', 'Server is reachable.');
} else { } else {
ray($this->uptime);
$this->emit('error', 'Server is not reachable.'); $this->emit('error', 'Server is not reachable.');
return; return;
} }
if ($dockerVersion) { if ($dockerVersion) {

View File

@ -26,7 +26,7 @@ class ByIp extends Component
protected $rules = [ protected $rules = [
'name' => 'required|string', 'name' => 'required|string',
'description' => 'nullable|string', 'description' => 'nullable|string',
'ip' => 'required|ip', 'ip' => 'required',
'user' => 'required|string', 'user' => 'required|string',
'port' => 'required|integer', 'port' => 'required|integer',
]; ];

View File

@ -2,9 +2,8 @@
namespace App\Http\Livewire\Server; namespace App\Http\Livewire\Server;
use App\Actions\Proxy\CheckConfigurationSync; use App\Actions\Proxy\CheckConfiguration;
use App\Actions\Proxy\SaveConfigurationSync; use App\Actions\Proxy\SaveConfiguration;
use App\Enums\ProxyTypes;
use App\Models\Server; use App\Models\Server;
use Livewire\Component; use Livewire\Component;
@ -48,8 +47,7 @@ public function select_proxy($proxy_type)
public function submit() public function submit()
{ {
try { try {
resolve(SaveConfigurationSync::class)($this->server); SaveConfiguration::run($this->server);
$this->server->proxy->redirect_url = $this->redirect_url; $this->server->proxy->redirect_url = $this->redirect_url;
$this->server->save(); $this->server->save();
@ -63,7 +61,7 @@ public function submit()
public function reset_proxy_configuration() public function reset_proxy_configuration()
{ {
try { try {
$this->proxy_settings = resolve(CheckConfigurationSync::class)($this->server, true); $this->proxy_settings = CheckConfiguration::run($this->server, true);
} catch (\Throwable $e) { } catch (\Throwable $e) {
return handleError($e); return handleError($e);
} }
@ -72,8 +70,7 @@ public function reset_proxy_configuration()
public function loadProxyConfiguration() public function loadProxyConfiguration()
{ {
try { try {
ray('loadProxyConfiguration'); $this->proxy_settings = CheckConfiguration::run($this->server);
$this->proxy_settings = resolve(CheckConfigurationSync::class)($this->server);
} catch (\Throwable $e) { } catch (\Throwable $e) {
return handleError($e); return handleError($e);
} }

View File

@ -2,7 +2,7 @@
namespace App\Http\Livewire\Server\Proxy; namespace App\Http\Livewire\Server\Proxy;
use App\Actions\Proxy\SaveConfigurationSync; use App\Actions\Proxy\SaveConfiguration;
use App\Actions\Proxy\StartProxy; use App\Actions\Proxy\StartProxy;
use App\Models\Server; use App\Models\Server;
use Livewire\Component; use Livewire\Component;
@ -13,20 +13,25 @@ class Deploy extends Component
public $proxy_settings = null; public $proxy_settings = null;
protected $listeners = ['proxyStatusUpdated']; protected $listeners = ['proxyStatusUpdated'];
public function proxyStatusUpdated() { public function proxyStatusUpdated()
{
$this->server->refresh(); $this->server->refresh();
} }
public function startProxy() public function startProxy()
{ {
if ( try {
$this->server->proxy->last_applied_settings && if (
$this->server->proxy->last_saved_settings !== $this->server->proxy->last_applied_settings $this->server->proxy->last_applied_settings &&
) { $this->server->proxy->last_saved_settings !== $this->server->proxy->last_applied_settings
resolve(SaveConfigurationSync::class)($this->server); ) {
} SaveConfiguration::run($this->server);
}
$activity = resolve(StartProxy::class)($this->server); $activity = resolve(StartProxy::class)($this->server);
$this->emit('newMonitorActivity', $activity->id); $this->emit('newMonitorActivity', $activity->id);
} catch (\Throwable $e) {
return handleError($e);
}
} }
public function stop() public function stop()

View File

@ -2,6 +2,7 @@
namespace App\Http\Livewire\Server\Proxy; namespace App\Http\Livewire\Server\Proxy;
use App\Jobs\ContainerStatusJob;
use App\Models\Server; use App\Models\Server;
use Livewire\Component; use Livewire\Component;
@ -18,9 +19,7 @@ public function getProxyStatus()
{ {
try { try {
if ($this->server->isFunctional()) { if ($this->server->isFunctional()) {
$container = getContainerStatus(server: $this->server, container_id: 'coolify-proxy'); dispatch_sync(new ContainerStatusJob($this->server));
$this->server->proxy->status = $container;
$this->server->save();
$this->emit('proxyStatusUpdated'); $this->emit('proxyStatusUpdated');
} }
} catch (\Throwable $e) { } catch (\Throwable $e) {

View File

@ -37,14 +37,27 @@ public function checkConnection()
try { try {
['uptime' => $uptime, 'dockerVersion' => $dockerVersion] = validateServer($this->server, true); ['uptime' => $uptime, 'dockerVersion' => $dockerVersion] = validateServer($this->server, true);
if ($uptime) { if ($uptime) {
$this->server->settings->update([
'is_reachable' => true
]);
$this->emit('success', 'Server is reachable with this private key.'); $this->emit('success', 'Server is reachable with this private key.');
} else { } else {
$this->server->settings->update([
'is_reachable' => false,
'is_usable' => false
]);
$this->emit('error', 'Server is not reachable with this private key.'); $this->emit('error', 'Server is not reachable with this private key.');
return; return;
} }
if ($dockerVersion) { if ($dockerVersion) {
$this->server->settings->update([
'is_usable' => true
]);
$this->emit('success', 'Server is usable for Coolify.'); $this->emit('success', 'Server is usable for Coolify.');
} else { } else {
$this->server->settings->update([
'is_usable' => false
]);
$this->emit('error', 'Old (lower than 23) or no Docker version detected. Install Docker Engine on the General tab.'); $this->emit('error', 'Old (lower than 23) or no Docker version detected. Install Docker Engine on the General tab.');
} }
} catch (\Throwable $e) { } catch (\Throwable $e) {

View File

@ -40,7 +40,8 @@ public function uniqueId(): string
return $this->server->uuid; return $this->server->uuid;
} }
private function checkServerConnection() { private function checkServerConnection()
{
$uptime = instant_remote_process(['uptime'], $this->server, false); $uptime = instant_remote_process(['uptime'], $this->server, false);
if (!is_null($uptime)) { if (!is_null($uptime)) {
return true; return true;
@ -51,7 +52,7 @@ public function handle(): void
try { try {
// ray()->clearAll(); // ray()->clearAll();
$serverUptimeCheckNumber = 0; $serverUptimeCheckNumber = 0;
$serverUptimeCheckNumberMax = 5; $serverUptimeCheckNumberMax = 3;
while (true) { while (true) {
if ($serverUptimeCheckNumber >= $serverUptimeCheckNumberMax) { if ($serverUptimeCheckNumber >= $serverUptimeCheckNumberMax) {
$this->server->settings()->update(['is_reachable' => false]); $this->server->settings()->update(['is_reachable' => false]);
@ -65,19 +66,29 @@ public function handle(): void
$serverUptimeCheckNumber++; $serverUptimeCheckNumber++;
sleep(5); sleep(5);
} }
$containers = instant_remote_process(["docker container ls -q"], $this->server);
if (!$containers) {
return;
}
$containers = instant_remote_process(["docker container inspect $(docker container ls -q) --format '{{json .}}'"], $this->server); $containers = instant_remote_process(["docker container inspect $(docker container ls -q) --format '{{json .}}'"], $this->server);
$containers = format_docker_command_output_to_json($containers); $containers = format_docker_command_output_to_json($containers);
$applications = $this->server->applications(); $applications = $this->server->applications();
$databases = $this->server->databases(); $databases = $this->server->databases();
$previews = $this->server->previews(); $previews = $this->server->previews();
if ($this->server->isProxyShouldRun()) {
$foundProxyContainer = $containers->filter(function ($value, $key) { /// Check if proxy is running
return data_get($value, 'Name') === '/coolify-proxy'; $foundProxyContainer = $containers->filter(function ($value, $key) {
})->first(); return data_get($value, 'Name') === '/coolify-proxy';
if (!$foundProxyContainer) { })->first();
if (!$foundProxyContainer) {
if ($this->server->isProxyShouldRun()) {
resolve(StartProxy::class)($this->server, false); resolve(StartProxy::class)($this->server, false);
$this->server->team->notify(new ContainerRestarted('coolify-proxy', $this->server)); $this->server->team->notify(new ContainerRestarted('coolify-proxy', $this->server));
} }
} else {
ray($foundProxyContainer);
$this->server->proxy->status = data_get($foundProxyContainer, 'State.Status');
$this->server->save();
} }
$foundApplications = []; $foundApplications = [];
$foundApplicationPreviews = []; $foundApplicationPreviews = [];
@ -88,10 +99,10 @@ public function handle(): void
$labels = Arr::undot(format_docker_labels_to_json($labels)); $labels = Arr::undot(format_docker_labels_to_json($labels));
$labelId = data_get($labels, 'coolify.applicationId'); $labelId = data_get($labels, 'coolify.applicationId');
if ($labelId) { if ($labelId) {
if (str_contains($labelId,'-pr-')) { if (str_contains($labelId, '-pr-')) {
$previewId = (int) Str::after($labelId, '-pr-'); $previewId = (int) Str::after($labelId, '-pr-');
$applicationId = (int) Str::before($labelId, '-pr-'); $applicationId = (int) Str::before($labelId, '-pr-');
$preview = ApplicationPreview::where('application_id', $applicationId)->where('pull_request_id',$previewId)->first(); $preview = ApplicationPreview::where('application_id', $applicationId)->where('pull_request_id', $previewId)->first();
if ($preview) { if ($preview) {
$foundApplicationPreviews[] = $preview->id; $foundApplicationPreviews[] = $preview->id;
$statusFromDb = $preview->status; $statusFromDb = $preview->status;
@ -128,10 +139,9 @@ public function handle(): void
} }
} }
} }
} }
$notRunningApplications = $applications->pluck('id')->diff($foundApplications); $notRunningApplications = $applications->pluck('id')->diff($foundApplications);
foreach($notRunningApplications as $applicationId) { foreach ($notRunningApplications as $applicationId) {
$application = $applications->where('id', $applicationId)->first(); $application = $applications->where('id', $applicationId)->first();
if ($application->status === 'exited') { if ($application->status === 'exited') {
continue; continue;
@ -170,14 +180,14 @@ public function handle(): void
$this->server->team->notify(new ContainerStopped($containerName, $this->server, $url)); $this->server->team->notify(new ContainerStopped($containerName, $this->server, $url));
} }
$notRunningDatabases = $databases->pluck('id')->diff($foundDatabases); $notRunningDatabases = $databases->pluck('id')->diff($foundDatabases);
foreach($notRunningDatabases as $database) { foreach ($notRunningDatabases as $database) {
$database = $databases->where('id', $database)->first(); $database = $databases->where('id', $database)->first();
if ($database->status === 'exited') { if ($database->status === 'exited') {
continue; continue;
} }
$database->update(['status' => 'exited']); $database->update(['status' => 'exited']);
$name = data_get($database, 'name'); $name = data_get($database, 'name');
$fqdn = data_get($database, 'fqdn'); $fqdn = data_get($database, 'fqdn');
$containerName = $name; $containerName = $name;
@ -249,7 +259,6 @@ public function handle(): void
return Str::startsWith(data_get($value, 'Name'), "/$uuid-pr-{$preview->id}"); return Str::startsWith(data_get($value, 'Name'), "/$uuid-pr-{$preview->id}");
})->first(); })->first();
} }
} }
foreach ($databases as $database) { foreach ($databases as $database) {
$uuid = data_get($database, 'uuid'); $uuid = data_get($database, 'uuid');
@ -276,7 +285,7 @@ public function handle(): void
} }
} }
} }
// TODO Monitor other containers not managed by Coolify // TODO Monitor other containers not managed by Coolify
} catch (\Throwable $e) { } catch (\Throwable $e) {
send_internal_notification('ContainerStatusJob failed with: ' . $e->getMessage()); send_internal_notification('ContainerStatusJob failed with: ' . $e->getMessage());
ray($e->getMessage()); ray($e->getMessage());

View File

@ -96,7 +96,7 @@ function setup_default_redirect_404(string|null $redirect_url, Server $server)
$traefik_default_redirect_file = "$traefik_dynamic_conf_path/default_redirect_404.yaml"; $traefik_default_redirect_file = "$traefik_dynamic_conf_path/default_redirect_404.yaml";
ray($redirect_url); ray($redirect_url);
if (empty($redirect_url)) { if (empty($redirect_url)) {
remote_process([ instant_remote_process([
"rm -f $traefik_default_redirect_file", "rm -f $traefik_default_redirect_file",
], $server); ], $server);
} else { } else {
@ -157,7 +157,7 @@ function setup_default_redirect_404(string|null $redirect_url, Server $server)
$base64 = base64_encode($yaml); $base64 = base64_encode($yaml);
ray("mkdir -p $traefik_dynamic_conf_path"); ray("mkdir -p $traefik_dynamic_conf_path");
remote_process([ instant_remote_process([
"mkdir -p $traefik_dynamic_conf_path", "mkdir -p $traefik_dynamic_conf_path",
"echo '$base64' | base64 -d > $traefik_default_redirect_file", "echo '$base64' | base64 -d > $traefik_default_redirect_file",
], $server); ], $server);

View File

@ -7,15 +7,13 @@
use App\Models\ApplicationDeploymentQueue; use App\Models\ApplicationDeploymentQueue;
use App\Models\PrivateKey; use App\Models\PrivateKey;
use App\Models\Server; use App\Models\Server;
use App\Notifications\Server\NotReachable;
use Carbon\Carbon; use Carbon\Carbon;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Process; use Illuminate\Support\Facades\Process;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Sleep;
use Spatie\Activitylog\Models\Activity;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use Spatie\Activitylog\Contracts\Activity;
function remote_process( function remote_process(
array $command, array $command,
@ -110,11 +108,28 @@ function instant_remote_process(array $command, Server $server, $throwError = tr
if (!$throwError) { if (!$throwError) {
return null; return null;
} }
throw new \RuntimeException($process->errorOutput(), $exitCode); return excludeCertainErrors($process->errorOutput(), $exitCode);
} }
return $output; return $output;
} }
function excludeCertainErrors(string $errorOutput, ?int $exitCode = null) {
$ignoredErrors = collect([
'Permission denied (publickey',
'Could not resolve hostname',
]);
$ignored = false;
foreach ($ignoredErrors as $ignoredError) {
if (Str::contains($errorOutput, $ignoredError)) {
$ignored = true;
break;
}
}
if ($ignored) {
// TODO: Create new exception and disable in sentry
throw new \RuntimeException($errorOutput, $exitCode);
}
throw new \RuntimeException($errorOutput, $exitCode);
}
function decode_remote_command_output(?ApplicationDeploymentQueue $application_deployment_queue = null): Collection function decode_remote_command_output(?ApplicationDeploymentQueue $application_deployment_queue = null): Collection
{ {
$application = Application::find(data_get($application_deployment_queue, 'application_id')); $application = Application::find(data_get($application_deployment_queue, 'application_id'));

View File

@ -22,6 +22,7 @@
"lcobucci/jwt": "^5.0.0", "lcobucci/jwt": "^5.0.0",
"league/flysystem-aws-s3-v3": "^3.0", "league/flysystem-aws-s3-v3": "^3.0",
"livewire/livewire": "^v2.12.3", "livewire/livewire": "^v2.12.3",
"lorisleiva/laravel-actions": "^2.7",
"masmerise/livewire-toaster": "^1.2", "masmerise/livewire-toaster": "^1.2",
"nubs/random-name-generator": "^2.2", "nubs/random-name-generator": "^2.2",
"phpseclib/phpseclib": "~3.0", "phpseclib/phpseclib": "~3.0",

151
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "cf138424c896f30b035bc8cdff63e8d1", "content-hash": "de2c45be3f03d43430549d963778dc4a",
"packages": [ "packages": [
{ {
"name": "aws/aws-crt-php", "name": "aws/aws-crt-php",
@ -3059,6 +3059,153 @@
], ],
"time": "2023-08-11T04:02:34+00:00" "time": "2023-08-11T04:02:34+00:00"
}, },
{
"name": "lorisleiva/laravel-actions",
"version": "v2.7.1",
"source": {
"type": "git",
"url": "https://github.com/lorisleiva/laravel-actions.git",
"reference": "5250614fd6b77e8e2780be0206174e069e94661d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/lorisleiva/laravel-actions/zipball/5250614fd6b77e8e2780be0206174e069e94661d",
"reference": "5250614fd6b77e8e2780be0206174e069e94661d",
"shasum": ""
},
"require": {
"illuminate/contracts": "9.0 - 9.34 || ^9.36 || ^10.0",
"lorisleiva/lody": "^0.4",
"php": "^8.0"
},
"require-dev": {
"orchestra/testbench": "^8.5",
"pestphp/pest": "^1.23",
"phpunit/phpunit": "^9.6"
},
"type": "library",
"extra": {
"laravel": {
"providers": [
"Lorisleiva\\Actions\\ActionServiceProvider"
],
"aliases": {
"Action": "Lorisleiva\\Actions\\Facades\\Actions"
}
}
},
"autoload": {
"psr-4": {
"Lorisleiva\\Actions\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Loris Leiva",
"email": "loris.leiva@gmail.com",
"homepage": "https://lorisleiva.com",
"role": "Developer"
}
],
"description": "Laravel components that take care of one specific task",
"homepage": "https://github.com/lorisleiva/laravel-actions",
"keywords": [
"action",
"command",
"component",
"controller",
"job",
"laravel",
"object"
],
"support": {
"issues": "https://github.com/lorisleiva/laravel-actions/issues",
"source": "https://github.com/lorisleiva/laravel-actions/tree/v2.7.1"
},
"funding": [
{
"url": "https://github.com/sponsors/lorisleiva",
"type": "github"
}
],
"time": "2023-08-24T10:20:57+00:00"
},
{
"name": "lorisleiva/lody",
"version": "v0.4.0",
"source": {
"type": "git",
"url": "https://github.com/lorisleiva/lody.git",
"reference": "1a43e8e423f3b2b64119542bc44a2071208fae16"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/lorisleiva/lody/zipball/1a43e8e423f3b2b64119542bc44a2071208fae16",
"reference": "1a43e8e423f3b2b64119542bc44a2071208fae16",
"shasum": ""
},
"require": {
"illuminate/contracts": "^8.0|^9.0|^10.0",
"php": "^8.0"
},
"require-dev": {
"orchestra/testbench": "^8.0",
"pestphp/pest": "^1.20.0",
"phpunit/phpunit": "^9.5.10"
},
"type": "library",
"extra": {
"laravel": {
"providers": [
"Lorisleiva\\Lody\\LodyServiceProvider"
],
"aliases": {
"Lody": "Lorisleiva\\Lody\\Lody"
}
}
},
"autoload": {
"psr-4": {
"Lorisleiva\\Lody\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Loris Leiva",
"email": "loris.leiva@gmail.com",
"homepage": "https://lorisleiva.com",
"role": "Developer"
}
],
"description": "Load files and classes as lazy collections in Laravel.",
"homepage": "https://github.com/lorisleiva/lody",
"keywords": [
"classes",
"collection",
"files",
"laravel",
"load"
],
"support": {
"issues": "https://github.com/lorisleiva/lody/issues",
"source": "https://github.com/lorisleiva/lody/tree/v0.4.0"
},
"funding": [
{
"url": "https://github.com/sponsors/lorisleiva",
"type": "github"
}
],
"time": "2023-02-05T15:03:45+00:00"
},
{ {
"name": "masmerise/livewire-toaster", "name": "masmerise/livewire-toaster",
"version": "1.3.0", "version": "1.3.0",
@ -13089,5 +13236,5 @@
"php": "^8.2" "php": "^8.2"
}, },
"platform-dev": [], "platform-dev": [],
"plugin-api-version": "2.3.0" "plugin-api-version": "2.6.0"
} }

View File

@ -7,7 +7,7 @@
// The release version of your application // The release version of your application
// Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD')) // Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD'))
'release' => '4.0.0-beta.40', 'release' => '4.0.0-beta.41',
// When left empty or `null` the Laravel environment will be used // When left empty or `null` the Laravel environment will be used
'environment' => config('app.env'), 'environment' => config('app.env'),

View File

@ -1,3 +1,3 @@
<?php <?php
return '4.0.0-beta.40'; return '4.0.0-beta.41';

View File

@ -1,5 +1,5 @@
<div class="pb-6"> <div class="pb-6">
<livewire:server.proxy.modal :server="$server" /> <livewire:server.proxy.modal :server="$server" />
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<h1>Server</h1> <h1>Server</h1>
@if ($server->settings->is_reachable) @if ($server->settings->is_reachable)

View File

@ -1,9 +1,19 @@
@extends('layouts.base') @extends('layouts.base')
@section('body') @section('body')
<main class="min-h-screen hero"> <main class="min-h-screen hero">
<div class="hero-content"> <div class="hero-content">
{{ $slot }} <x-modal modalId="installDocker">
</div> <x-slot:modalBody>
</main> <livewire:activity-monitor header="Docker Installation Logs" />
@parent </x-slot:modalBody>
<x-slot:modalSubmit>
<x-forms.button onclick="installDocker.close()" type="submit">
Close
</x-forms.button>
</x-slot:modalSubmit>
</x-modal>
{{ $slot }}
</div>
</main>
@parent
@endsection @endsection

View File

@ -23,9 +23,12 @@
<x-highlighted text="Self-hosting with superpowers!" /></span> <x-highlighted text="Self-hosting with superpowers!" /></span>
</x-slot:question> </x-slot:question>
<x-slot:explanation> <x-slot:explanation>
<p><x-highlighted text="Task automation:" /> You do not to manage your servers too much. Coolify do it for you.</p> <p><x-highlighted text="Task automation:" /> You do not to manage your servers too much. Coolify do
<p><x-highlighted text="No vendor lock-in:" /> All configurations are stored on your server, so everything works without Coolify (except integrations and automations).</p> it for you.</p>
<p><x-highlighted text="Monitoring:" />You will get notified on your favourite platform (Discord, Telegram, Email, etc.) when something goes wrong, or an action needed from your side.</p> <p><x-highlighted text="No vendor lock-in:" /> All configurations are stored on your server, so
everything works without Coolify (except integrations and automations).</p>
<p><x-highlighted text="Monitoring:" />You will get notified on your favourite platform (Discord,
Telegram, Email, etc.) when something goes wrong, or an action needed from your side.</p>
</x-slot:explanation> </x-slot:explanation>
<x-slot:actions> <x-slot:actions>
<x-forms.button class="justify-center box" wire:click="explanation">Next <x-forms.button class="justify-center box" wire:click="explanation">Next
@ -194,16 +197,6 @@
</div> </div>
<div> <div>
@if ($currentState === 'install-docker') @if ($currentState === 'install-docker')
<x-modal modalId="installDocker">
<x-slot:modalBody>
<livewire:activity-monitor header="Docker Installation Logs" />
</x-slot:modalBody>
<x-slot:modalSubmit>
<x-forms.button onclick="installDocker.close()" type="submit">
Close
</x-forms.button>
</x-slot:modalSubmit>
</x-modal>
<x-boarding-step title="Install Docker"> <x-boarding-step title="Install Docker">
<x-slot:question> <x-slot:question>
Could not find Docker Engine on your server. Do you want me to install it for you? Could not find Docker Engine on your server. Do you want me to install it for you?
@ -211,8 +204,11 @@
<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"
onclick="installDocker.showModal()"> onclick="installDocker.showModal()">
Let's do Let's do it!</x-forms.button>
it!</x-forms.button> @if ($dockerInstallationStarted)
<x-forms.button class="justify-center box" wire:click="dockerInstalledOrSkipped">
Next</x-forms.button>
@endif
</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

View File

@ -48,10 +48,16 @@
</x-forms.button> </x-forms.button>
@endif @endif
@if ($server->settings->is_reachable && !$server->settings->is_usable && $server->id !== 0) @if ($server->settings->is_reachable && !$server->settings->is_usable && $server->id !== 0)
<x-forms.button class="mt-8 mb-4 box" onclick="installDocker.showModal()" wire:click.prevent='installDocker' @if ($dockerInstallationStarted)
isHighlighted> <x-forms.button class="mt-8 mb-4 box" wire:click.prevent='validateServer'>
Install Docker Engine 24.0 Validate Server
</x-forms.button> </x-forms.button>
@else
<x-forms.button class="mt-8 mb-4 box" onclick="installDocker.showModal()"
wire:click.prevent='installDocker' isHighlighted>
Install Docker Engine 24.0
</x-forms.button>
@endif
@endif @endif
@if ($server->isFunctional()) @if ($server->isFunctional())
<h3 class="py-4">Settings</h3> <h3 class="py-4">Settings</h3>

View File

@ -1,4 +1,3 @@
<div class="flex gap-2" x-init="$wire.getProxyStatus"> <div class="flex gap-2" x-init="$wire.getProxyStatus">
@if ($server->proxy->status === 'running') @if ($server->proxy->status === 'running')
<x-status.running text="Proxy Running" /> <x-status.running text="Proxy Running" />
@ -7,7 +6,8 @@
@else @else
<x-status.stopped text="Proxy Stopped" /> <x-status.stopped text="Proxy Stopped" />
@endif @endif
<button wire:click.prevent='getProxyStatusWithNoti'><svg class="icon" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"> <button wire:loading.remove.delay.longer wire:click.prevent='getProxyStatusWithNoti'>
<svg class="icon" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<g fill="#FCD44F"> <g fill="#FCD44F">
<path <path
d="M12.079 3v-.75V3Zm-8.4 8.333h-.75h.75Zm0 1.667l-.527.532a.75.75 0 0 0 1.056 0L3.68 13Zm2.209-1.134A.75.75 0 1 0 4.83 10.8l1.057 1.065ZM2.528 10.8a.75.75 0 0 0-1.056 1.065L2.528 10.8Zm16.088-3.408a.75.75 0 1 0 1.277-.786l-1.277.786ZM12.079 2.25c-5.047 0-9.15 4.061-9.15 9.083h1.5c0-4.182 3.42-7.583 7.65-7.583v-1.5Zm-9.15 9.083V13h1.5v-1.667h-1.5Zm1.28 2.2l1.679-1.667L4.83 10.8l-1.68 1.667l1.057 1.064Zm0-1.065L2.528 10.8l-1.057 1.065l1.68 1.666l1.056-1.064Zm15.684-5.86A9.158 9.158 0 0 0 12.08 2.25v1.5a7.658 7.658 0 0 1 6.537 3.643l1.277-.786Z" /> d="M12.079 3v-.75V3Zm-8.4 8.333h-.75h.75Zm0 1.667l-.527.532a.75.75 0 0 0 1.056 0L3.68 13Zm2.209-1.134A.75.75 0 1 0 4.83 10.8l1.057 1.065ZM2.528 10.8a.75.75 0 0 0-1.056 1.065L2.528 10.8Zm16.088-3.408a.75.75 0 1 0 1.277-.786l-1.277.786ZM12.079 2.25c-5.047 0-9.15 4.061-9.15 9.083h1.5c0-4.182 3.42-7.583 7.65-7.583v-1.5Zm-9.15 9.083V13h1.5v-1.667h-1.5Zm1.28 2.2l1.679-1.667L4.83 10.8l-1.68 1.667l1.057 1.064Zm0-1.065L2.528 10.8l-1.057 1.065l1.68 1.666l1.056-1.064Zm15.684-5.86A9.158 9.158 0 0 0 12.08 2.25v1.5a7.658 7.658 0 0 1 6.537 3.643l1.277-.786Z" />

View File

@ -4,7 +4,7 @@
"version": "3.12.36" "version": "3.12.36"
}, },
"v4": { "v4": {
"version": "4.0.0-beta.40" "version": "4.0.0-beta.41"
} }
} }
} }