Compare commits

...

24 Commits

Author SHA1 Message Date
Andras Bacsai
6bb6de188c Merge pull request #1236 from coollabsio/next
v4.0.0-beta.41
2023-09-18 12:26:00 +02:00
Andras Bacsai
61f58fa30f fix: proxy configuration saving 2023-09-18 12:18:45 +02:00
Andras Bacsai
026f3fd72d fix 2023-09-18 12:09:31 +02:00
Andras Bacsai
9a706d55b4 fix: proxy container status 2023-09-18 12:08:19 +02:00
Andras Bacsai
9646969107 fix: errors 2023-09-18 11:49:26 +02:00
Andras Bacsai
df433efe62 fix: boarding 2023-09-18 11:21:10 +02:00
Andras Bacsai
efa7cab3e2 fix: installDocker id not found 2023-09-18 10:44:32 +02:00
Andras Bacsai
a386a1bde5 fix: allow non ip address (DNS) 2023-09-18 10:41:19 +02:00
Andras Bacsai
cc1bf023be fix: do not remove localhost in boarding 2023-09-18 10:41:06 +02:00
Andras Bacsai
edb06fa233 version++ 2023-09-18 10:06:35 +02:00
Andras Bacsai
abdb8074b1 Merge pull request #1235 from coollabsio/next
4.0.0-beta.40
2023-09-18 10:05:52 +02:00
Andras Bacsai
157da798dd fix: proxy start (if not proxy defined, use Traefik) 2023-09-18 09:58:13 +02:00
Andras Bacsai
9c5501326e Merge pull request #1230 from coollabsio/next
4.0.0-beta.39
2023-09-16 16:53:17 +02:00
Andras Bacsai
3ea462efc9 fix: localhost 2023-09-16 16:49:33 +02:00
Andras Bacsai
f77df5b732 feat: sentry add email for better support 2023-09-15 21:13:50 +02:00
Andras Bacsai
b77074fe4e Merge pull request #1228 from coollabsio/next
v4.0.0-beta.38
2023-09-15 18:05:59 +02:00
Andras Bacsai
a7b7d3fa32 version++ 2023-09-15 18:02:55 +02:00
Andras Bacsai
f4f3034389 fix: 4.0.0-beta.37 2023-09-15 17:57:17 +02:00
Andras Bacsai
4b2ffb456f fix: team error 2023-09-15 17:30:26 +02:00
Andras Bacsai
e17ff99c5b fix: missing upgrade js 2023-09-15 15:50:37 +02:00
Andras Bacsai
1cf036bbc6 fix: generate new key 2023-09-15 15:39:25 +02:00
Andras Bacsai
da4c2ee60f fix: boarding
fix: error handling
fix: restarting state
2023-09-15 15:34:25 +02:00
Andras Bacsai
fcf7c5ddd5 fix: restarting container state on ui 2023-09-15 13:44:41 +02:00
Andras Bacsai
27926322e1 version++ 2023-09-15 13:06:27 +02:00
96 changed files with 765 additions and 443 deletions

View File

@@ -5,6 +5,7 @@ namespace App\Actions\CoolifyTask;
use App\Enums\ActivityTypes; use App\Enums\ActivityTypes;
use App\Enums\ProcessStatus; use App\Enums\ProcessStatus;
use App\Jobs\ApplicationDeploymentJob; use App\Jobs\ApplicationDeploymentJob;
use App\Models\Server;
use Illuminate\Process\ProcessResult; use Illuminate\Process\ProcessResult;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Process; use Illuminate\Support\Facades\Process;
@@ -94,7 +95,7 @@ class RunRemoteProcess
]); ]);
$this->activity->save(); $this->activity->save();
if ($processResult->exitCode() != 0 && !$this->ignore_errors) { if ($processResult->exitCode() != 0 && !$this->ignore_errors) {
throw new \RuntimeException($processResult->errorOutput()); throw new \RuntimeException($processResult->errorOutput(), $processResult->exitCode());
} }
return $processResult; return $processResult;
@@ -102,12 +103,11 @@ class RunRemoteProcess
protected function getCommand(): string protected function getCommand(): string
{ {
$user = $this->activity->getExtraProperty('user'); $server_uuid = $this->activity->getExtraProperty('server_uuid');
$server_ip = $this->activity->getExtraProperty('server_ip');
$port = $this->activity->getExtraProperty('port');
$command = $this->activity->getExtraProperty('command'); $command = $this->activity->getExtraProperty('command');
$server = Server::whereUuid($server_uuid)->firstOrFail();
return generateSshCommand($server_ip, $user, $port, $command); return generateSshCommand($server, $command);
} }
protected function handleOutput(string $type, string $output) protected function handleOutput(string $type, string $output)

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

@@ -2,6 +2,8 @@
namespace App\Actions\Proxy; namespace App\Actions\Proxy;
use App\Enums\ProxyStatus;
use App\Enums\ProxyTypes;
use App\Models\Server; use App\Models\Server;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use Spatie\Activitylog\Models\Activity; use Spatie\Activitylog\Models\Activity;
@@ -10,6 +12,15 @@ class StartProxy
{ {
public function __invoke(Server $server, bool $async = true): Activity|string public function __invoke(Server $server, bool $async = true): Activity|string
{ {
$proxyType = data_get($server,'proxy.type');
if ($proxyType === 'none') {
return 'OK';
}
if (is_null($proxyType)) {
$server->proxy->type = ProxyTypes::TRAEFIK_V2->value;
$server->proxy->status = ProxyStatus::EXITED->value;
$server->save();
}
$proxy_path = get_proxy_path(); $proxy_path = get_proxy_path();
$networks = collect($server->standaloneDockers)->map(function ($docker) { $networks = collect($server->standaloneDockers)->map(function ($docker) {
return $docker['network']; return $docker['network'];
@@ -21,12 +32,18 @@ class StartProxy
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();
$commands = [ $commands = [
"command -v lsof >/dev/null || echo '####### Installing lsof...'",
"command -v lsof >/dev/null || apt-get update",
"command -v lsof >/dev/null || apt install -y lsof",
"command -v lsof >/dev/null || command -v fuser >/dev/null || apt install -y psmisc",
"echo '####### Creating required Docker networks...'", "echo '####### Creating required Docker networks...'",
...$create_networks_command, ...$create_networks_command,
"cd $proxy_path", "cd $proxy_path",
@@ -34,9 +51,12 @@ class StartProxy
"echo '####### Pulling docker image...'", "echo '####### Pulling docker image...'",
'docker compose pull', 'docker compose pull',
"echo '####### Stopping existing coolify-proxy...'", "echo '####### Stopping existing coolify-proxy...'",
'docker compose down -v --remove-orphans', "docker compose down -v --remove-orphans > /dev/null 2>&1 || true",
"lsof -nt -i:80 | xargs -r kill -9", "command -v fuser >/dev/null || command -v lsof >/dev/null || echo '####### Could not kill existing processes listening on port 80 & 443. Please stop the process holding these ports...'",
"lsof -nt -i:443 | xargs -r kill -9", "command -v lsof >/dev/null && lsof -nt -i:80 | xargs -r kill -9 || true",
"command -v lsof >/dev/null && lsof -nt -i:443 | xargs -r kill -9 || true",
"command -v fuser >/dev/null && fuser -k 80/tcp || true",
"command -v fuser >/dev/null && fuser -k 443/tcp || true",
"systemctl disable nginx > /dev/null 2>&1 || true", "systemctl disable nginx > /dev/null 2>&1 || true",
"systemctl disable apache2 > /dev/null 2>&1 || true", "systemctl disable apache2 > /dev/null 2>&1 || true",
"systemctl disable apache > /dev/null 2>&1 || true", "systemctl disable apache > /dev/null 2>&1 || true",

View File

@@ -4,11 +4,10 @@ namespace App\Actions\Server;
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 @@ class InstallDocker
} }
}'); }');
$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 @@ class InstallDocker
"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 @@ class InstallDocker
"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

@@ -12,10 +12,8 @@ use Spatie\LaravelData\Data;
class CoolifyTaskArgs extends Data class CoolifyTaskArgs extends Data
{ {
public function __construct( public function __construct(
public string $server_ip, public string $server_uuid,
public string $command, public string $command,
public int $port,
public string $user,
public string $type, public string $type,
public ?string $type_uuid = null, public ?string $type_uuid = null,
public ?Model $model = null, public ?Model $model = null,

View File

@@ -3,6 +3,7 @@
namespace App\Exceptions; namespace App\Exceptions;
use App\Models\InstanceSettings; use App\Models\InstanceSettings;
use App\Models\User;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler; use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Sentry\Laravel\Integration; use Sentry\Laravel\Integration;
use Sentry\State\Scope; use Sentry\State\Scope;
@@ -45,16 +46,19 @@ class Handler extends ExceptionHandler
public function register(): void public function register(): void
{ {
$this->reportable(function (Throwable $e) { $this->reportable(function (Throwable $e) {
if (isDev()) {
return;
}
$this->settings = InstanceSettings::get(); $this->settings = InstanceSettings::get();
if ($this->settings->do_not_track || isDev()) { if ($this->settings->do_not_track) {
return; return;
} }
app('sentry')->configureScope( app('sentry')->configureScope(
function (Scope $scope) { function (Scope $scope) {
$scope->setUser( $scope->setUser(
[ [
'id' => config('sentry.server_name'), 'email' => auth()->user()->email,
'email' => auth()->user()->email 'instanceAdmin' => User::find(0)->email
] ]
); );
} }

View File

@@ -14,7 +14,6 @@ use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Crypt; use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use Throwable;
class Controller extends BaseController class Controller extends BaseController
{ {
@@ -153,7 +152,7 @@ class Controller extends BaseController
} else { } else {
abort(401); abort(401);
} }
} catch (Throwable $e) { } catch (\Throwable $e) {
ray($e->getMessage()); ray($e->getMessage());
throw $e; throw $e;
} }
@@ -172,7 +171,7 @@ class Controller extends BaseController
} }
$invitation->delete(); $invitation->delete();
return redirect()->route('team.index'); return redirect()->route('team.index');
} catch (Throwable $e) { } catch (\Throwable $e) {
throw $e; throw $e;
} }
} }

View File

@@ -9,11 +9,13 @@ use App\Models\Server;
use App\Models\Team; use App\Models\Team;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Livewire\Component; use Livewire\Component;
use Visus\Cuid2\Cuid2;
class Index extends Component 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;
@@ -36,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();
@@ -53,7 +56,8 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
$this->remoteServerHost = 'coolify-testing-host'; $this->remoteServerHost = 'coolify-testing-host';
} }
} }
public function explanation() { public function explanation()
{
if (isCloud()) { if (isCloud()) {
return $this->setServerType('remote'); return $this->setServerType('remote');
} }
@@ -62,12 +66,14 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
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()
@@ -82,13 +88,14 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
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(); 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;
@@ -112,10 +119,9 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
} }
$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()
{
$proxyTypeSet = $this->createdServer->proxy->type; $proxyTypeSet = $this->createdServer->proxy->type;
if (!$proxyTypeSet) { if (!$proxyTypeSet) {
$this->currentState = 'select-proxy'; $this->currentState = 'select-proxy';
@@ -125,6 +131,7 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
} }
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()
@@ -147,6 +154,13 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
'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()
@@ -154,16 +168,14 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
$this->validate([ $this->validate([
'remoteServerName' => 'required', 'remoteServerName' => 'required',
'remoteServerHost' => 'required', 'remoteServerHost' => 'required',
'remoteServerPort' => 'required', 'remoteServerPort' => 'required|integer',
'remoteServerUser' => 'required', 'remoteServerUser' => 'required',
]); ]);
$this->privateKey = formatPrivateKey($this->privateKey); $this->privateKey = formatPrivateKey($this->privateKey);
$this->createdPrivateKey = PrivateKey::create([ $foundServer = Server::whereIp($this->remoteServerHost)->first();
'name' => $this->privateKeyName, if ($foundServer) {
'description' => $this->privateKeyDescription, return $this->emit('error', 'IP address is already in use by another team.');
'private_key' => $this->privateKey, }
'team_id' => currentTeam()->id
]);
$this->createdServer = Server::create([ $this->createdServer = Server::create([
'name' => $this->remoteServerName, 'name' => $this->remoteServerName,
'ip' => $this->remoteServerHost, 'ip' => $this->remoteServerHost,
@@ -171,38 +183,46 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
'user' => $this->remoteServerUser, 'user' => $this->remoteServerUser,
'description' => $this->remoteServerDescription, 'description' => $this->remoteServerDescription,
'private_key_id' => $this->createdPrivateKey->id, 'private_key_id' => $this->createdPrivateKey->id,
'team_id' => currentTeam()->id 'team_id' => currentTeam()->id,
]); ]);
$this->createdServer->save();
$this->validateServer(); $this->validateServer();
} }
public function validateServer() { public function validateServer(?string $type = null)
{
try { try {
['uptime' => $uptime, 'dockerVersion' => $dockerVersion] = validateServer($this->createdServer); $customErrorMessage = "Server is not reachable:";
if (!$uptime) { config()->set('coolify.mux_enabled', false);
throw new \Exception('Server is not reachable.');
} else {
$this->createdServer->settings->update([
'is_reachable' => true,
]);
$this->emit('success', 'Server is reachable.');
}
ray($dockerVersion, $uptime);
if (!$dockerVersion) {
$this->emit('error', 'Docker is not installed on the server.');
$this->currentState = 'install-docker';
return;
}
$this->getProxyType();
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 = checkMinimumDockerEngineVersion($dockerVersion);
if (is_null($dockerVersion)) {
$this->currentState = 'install-docker';
throw new \Exception('Docker version is not supported or not installed.');
}
$this->dockerInstalledOrSkipped();
} catch (\Throwable $e) { } catch (\Throwable $e) {
return general_error_handler(customErrorMessage: "Server is not reachable. Reason: {$e->getMessage()}", that: $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)
{ {
@@ -215,14 +235,16 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
$this->getProjects(); $this->getProjects();
} }
public function getProjects() { public function getProjects()
{
$this->projects = Project::ownedByCurrentTeam(['name'])->get(); $this->projects = Project::ownedByCurrentTeam(['name'])->get();
if ($this->projects->count() > 0) { if ($this->projects->count() > 0) {
$this->selectedExistingProject = $this->projects->first()->id; $this->selectedExistingProject = $this->projects->first()->id;
} }
$this->currentState = 'create-project'; $this->currentState = 'create-project';
} }
public function selectExistingProject() { public function selectExistingProject()
{
$this->createdProject = Project::find($this->selectedExistingProject); $this->createdProject = Project::find($this->selectedExistingProject);
$this->currentState = 'create-resource'; $this->currentState = 'create-resource';
} }
@@ -242,7 +264,7 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
[ [
'project_uuid' => $this->createdProject->uuid, 'project_uuid' => $this->createdProject->uuid,
'environment_name' => 'production', 'environment_name' => 'production',
'server'=> $this->createdServer->id, 'server' => $this->createdServer->id,
] ]
); );
} }

View File

@@ -38,7 +38,7 @@ class Form extends Component
$this->destination->delete(); $this->destination->delete();
return redirect()->route('dashboard'); return redirect()->route('dashboard');
} catch (\Throwable $e) { } catch (\Throwable $e) {
return general_error_handler(err: $e); return handleError($e);
} }
} }
} }

View File

@@ -72,7 +72,7 @@ class StandaloneDocker extends Component
$this->createNetworkAndAttachToProxy(); $this->createNetworkAndAttachToProxy();
return redirect()->route('destination.show', $docker->uuid); return redirect()->route('destination.show', $docker->uuid);
} catch (\Throwable $e) { } catch (\Throwable $e) {
return general_error_handler(err: $e, that: $this); return handleError($e, $this);
} }
} }

View File

@@ -37,7 +37,7 @@ class ForcePasswordReset extends Component
} }
return redirect()->route('dashboard'); return redirect()->route('dashboard');
} catch (\Throwable $e) { } catch (\Throwable $e) {
return general_error_handler(err: $e, that: $this); return handleError($e, $this);
} }
} }
} }

View File

@@ -44,7 +44,7 @@ class Help extends Component
send_user_an_email($mail, auth()->user()?->email, 'hi@coollabs.io'); send_user_an_email($mail, auth()->user()?->email, 'hi@coollabs.io');
$this->emit('success', 'Your message has been sent successfully. We will get in touch with you as soon as possible.'); $this->emit('success', 'Your message has been sent successfully. We will get in touch with you as soon as possible.');
} catch (\Throwable $e) { } catch (\Throwable $e) {
return general_error_handler($e, $this); return handleError($e, $this);
} }
} }
public function render() public function render()

View File

@@ -46,6 +46,7 @@ class DiscordSettings extends Component
public function saveModel() public function saveModel()
{ {
$this->team->save(); $this->team->save();
refreshSession();
$this->emit('success', 'Settings saved.'); $this->emit('success', 'Settings saved.');
} }

View File

@@ -62,9 +62,10 @@ class EmailSettings extends Component
'team.smtp_from_name' => 'required', 'team.smtp_from_name' => 'required',
]); ]);
$this->team->save(); $this->team->save();
refreshSession();
$this->emit('success', 'Settings saved successfully.'); $this->emit('success', 'Settings saved successfully.');
} catch (\Throwable $e) { } catch (\Throwable $e) {
return general_error_handler($e, $this); return handleError($e, $this);
} }
} }
public function sendTestNotification() public function sendTestNotification()
@@ -81,9 +82,10 @@ class EmailSettings extends Component
$this->team->smtp_enabled = false; $this->team->smtp_enabled = false;
$this->team->resend_enabled = false; $this->team->resend_enabled = false;
$this->team->save(); $this->team->save();
refreshSession();
$this->emit('success', 'Settings saved successfully.'); $this->emit('success', 'Settings saved successfully.');
} catch (\Throwable $e) { } catch (\Throwable $e) {
return general_error_handler($e, $this); return handleError($e, $this);
} }
} }
@@ -94,7 +96,7 @@ class EmailSettings extends Component
$this->submitResend(); $this->submitResend();
} catch (\Throwable $e) { } catch (\Throwable $e) {
$this->team->smtp_enabled = false; $this->team->smtp_enabled = false;
return general_error_handler($e, $this); return handleError($e, $this);
} }
} }
public function instantSave() public function instantSave()
@@ -104,12 +106,13 @@ class EmailSettings extends Component
$this->submit(); $this->submit();
} catch (\Throwable $e) { } catch (\Throwable $e) {
$this->team->smtp_enabled = false; $this->team->smtp_enabled = false;
return general_error_handler($e, $this); return handleError($e, $this);
} }
} }
public function saveModel() public function saveModel()
{ {
$this->team->save(); $this->team->save();
refreshSession();
$this->emit('success', 'Settings saved.'); $this->emit('success', 'Settings saved.');
} }
public function submit() public function submit()
@@ -127,10 +130,11 @@ class EmailSettings extends Component
'team.smtp_timeout' => 'nullable', 'team.smtp_timeout' => 'nullable',
]); ]);
$this->team->save(); $this->team->save();
refreshSession();
$this->emit('success', 'Settings saved successfully.'); $this->emit('success', 'Settings saved successfully.');
} catch (\Throwable $e) { } catch (\Throwable $e) {
$this->team->smtp_enabled = false; $this->team->smtp_enabled = false;
return general_error_handler($e, $this); return handleError($e, $this);
} }
} }
public function submitResend() public function submitResend()
@@ -143,10 +147,11 @@ class EmailSettings extends Component
'team.resend_api_key' => 'required' 'team.resend_api_key' => 'required'
]); ]);
$this->team->save(); $this->team->save();
refreshSession();
$this->emit('success', 'Settings saved successfully.'); $this->emit('success', 'Settings saved successfully.');
} catch (\Throwable $e) { } catch (\Throwable $e) {
$this->team->resend_enabled = false; $this->team->resend_enabled = false;
return general_error_handler($e, $this); return handleError($e, $this);
} }
} }
public function copyFromInstanceSettings() public function copyFromInstanceSettings()

View File

@@ -52,6 +52,7 @@ class TelegramSettings extends Component
public function saveModel() public function saveModel()
{ {
$this->team->save(); $this->team->save();
refreshSession();
$this->emit('success', 'Settings saved.'); $this->emit('success', 'Settings saved.');
} }

View File

@@ -25,8 +25,8 @@ class Change extends Component
{ {
try { try {
$this->public_key = $this->private_key->publicKey(); $this->public_key = $this->private_key->publicKey();
}catch(\Exception $e) { }catch(\Throwable $e) {
return general_error_handler(err: $e, that: $this); return handleError($e, $this);
} }
} }
public function delete() public function delete()
@@ -39,7 +39,7 @@ class Change extends Component
} }
$this->emit('error', 'This private key is in use and cannot be deleted. Please delete all servers, applications, and GitHub/GitLab apps that use this private key before deleting it.'); $this->emit('error', 'This private key is in use and cannot be deleted. Please delete all servers, applications, and GitHub/GitLab apps that use this private key before deleting it.');
} catch (\Throwable $e) { } catch (\Throwable $e) {
return general_error_handler(err: $e, that: $this); return handleError($e, $this);
} }
} }
@@ -50,7 +50,7 @@ class Change extends Component
$this->private_key->save(); $this->private_key->save();
refresh_server_connection($this->private_key); refresh_server_connection($this->private_key);
} catch (\Throwable $e) { } catch (\Throwable $e) {
return general_error_handler(err: $e, that: $this); return handleError($e, $this);
} }
} }
} }

View File

@@ -3,16 +3,20 @@
namespace App\Http\Livewire\PrivateKey; namespace App\Http\Livewire\PrivateKey;
use App\Models\PrivateKey; use App\Models\PrivateKey;
use DanHarrin\LivewireRateLimiting\WithRateLimiting;
use Livewire\Component; use Livewire\Component;
use phpseclib3\Crypt\PublicKeyLoader; use phpseclib3\Crypt\PublicKeyLoader;
class Create extends Component class Create extends Component
{ {
public ?string $from = null; use WithRateLimiting;
public string $name; public string $name;
public ?string $description = null;
public string $value; public string $value;
public ?string $from = null;
public ?string $description = null;
public ?string $publicKey = null; public ?string $publicKey = null;
protected $rules = [ protected $rules = [
'name' => 'required|string', 'name' => 'required|string',
'value' => 'required|string', 'value' => 'required|string',
@@ -24,9 +28,14 @@ class Create extends Component
public function generateNewKey() public function generateNewKey()
{ {
$this->name = generate_random_name(); try {
$this->description = 'Created by Coolify'; $this->rateLimit(10);
['private' => $this->value, 'public' => $this->publicKey] = generateSSHKey(); $this->name = generate_random_name();
$this->description = 'Created by Coolify';
['private' => $this->value, 'public' => $this->publicKey] = generateSSHKey();
} catch(\Throwable $e) {
return handleError($e, $this);
}
} }
public function updated($updateProperty) public function updated($updateProperty)
{ {
@@ -34,7 +43,11 @@ class Create extends Component
try { try {
$this->publicKey = PublicKeyLoader::load($this->$updateProperty)->getPublicKey()->toString('OpenSSH',['comment' => '']); $this->publicKey = PublicKeyLoader::load($this->$updateProperty)->getPublicKey()->toString('OpenSSH',['comment' => '']);
} catch (\Throwable $e) { } catch (\Throwable $e) {
$this->publicKey = "Invalid private key"; if ($this->$updateProperty === "") {
$this->publicKey = "";
} else {
$this->publicKey = "Invalid private key";
}
} }
} }
$this->validateOnly($updateProperty); $this->validateOnly($updateProperty);
@@ -58,7 +71,7 @@ class Create extends Component
} }
return redirect()->route('security.private-key.show', ['private_key_uuid' => $private_key->uuid]); return redirect()->route('security.private-key.show', ['private_key_uuid' => $private_key->uuid]);
} catch (\Throwable $e) { } catch (\Throwable $e) {
return general_error_handler(err: $e, that: $this); return handleError($e, $this);
} }
} }
} }

View File

@@ -34,7 +34,7 @@ class Form extends Component
'name' => $this->name, 'name' => $this->name,
]); ]);
} catch (\Throwable $e) { } catch (\Throwable $e) {
return general_error_handler(err: $e, that: $this); return handleError($e, $this);
} }
} }
} }

View File

@@ -29,7 +29,7 @@ class AddEmpty extends Component
]); ]);
return redirect()->route('project.show', $project->uuid); return redirect()->route('project.show', $project->uuid);
} catch (\Throwable $e) { } catch (\Throwable $e) {
general_error_handler($e, $this); return handleError($e, $this);
} finally { } finally {
$this->name = ''; $this->name = '';
} }

View File

@@ -32,7 +32,7 @@ class AddEnvironment extends Component
'environment_name' => $environment->name, 'environment_name' => $environment->name,
]); ]);
} catch (\Throwable $e) { } catch (\Throwable $e) {
general_error_handler($e, $this); handleError($e, $this);
} finally { } finally {
$this->name = ''; $this->name = '';
} }

View File

@@ -65,7 +65,7 @@ class DeploymentNavbar extends Component
]); ]);
} }
} catch (\Throwable $e) { } catch (\Throwable $e) {
return general_error_handler(err: $e, that: $this); return handleError($e, $this);
} }
} }
} }

View File

@@ -160,7 +160,7 @@ class General extends Component
$this->application->save(); $this->application->save();
$this->emit('success', 'Application settings updated!'); $this->emit('success', 'Application settings updated!');
} catch (\Throwable $e) { } catch (\Throwable $e) {
return general_error_handler(err: $e, that: $this); return handleError($e, $this);
} }
} }
} }

View File

@@ -30,7 +30,7 @@ class Previews extends Component
$this->pull_requests = $data->sortBy('number')->values(); $this->pull_requests = $data->sortBy('number')->values();
} catch (\Throwable $e) { } catch (\Throwable $e) {
$this->rate_limit_remaining = 0; $this->rate_limit_remaining = 0;
return general_error_handler(err: $e, that: $this); return handleError($e, $this);
} }
} }
@@ -59,7 +59,7 @@ class Previews extends Component
'environment_name' => $this->parameters['environment_name'], 'environment_name' => $this->parameters['environment_name'],
]); ]);
} catch (\Throwable $e) { } catch (\Throwable $e) {
return general_error_handler(err: $e, that: $this); return handleError($e, $this);
} }
} }
@@ -79,7 +79,7 @@ class Previews extends Component
ApplicationPreview::where('application_id', $this->application->id)->where('pull_request_id', $pull_request_id)->delete(); ApplicationPreview::where('application_id', $this->application->id)->where('pull_request_id', $pull_request_id)->delete();
$this->application->refresh(); $this->application->refresh();
} catch (\Throwable $e) { } catch (\Throwable $e) {
return general_error_handler(err: $e, that: $this); return handleError($e, $this);
} }
} }

View File

@@ -65,7 +65,7 @@ class Rollback extends Component
]; ];
})->toArray(); })->toArray();
} catch (\Throwable $e) { } catch (\Throwable $e) {
return general_error_handler(err: $e, that: $this); return handleError($e, $this);
} }
} }
} }

View File

@@ -43,7 +43,7 @@ class CreateScheduledBackup extends Component
]); ]);
$this->emit('refreshScheduledBackups'); $this->emit('refreshScheduledBackups');
} catch (\Throwable $e) { } catch (\Throwable $e) {
general_error_handler($e, $this); handleError($e, $this);
} finally { } finally {
$this->frequency = ''; $this->frequency = '';
$this->save_s3 = true; $this->save_s3 = true;

View File

@@ -35,7 +35,7 @@ class Heading extends Component
public function stop() public function stop()
{ {
remote_process( instant_remote_process(
["docker rm -f {$this->database->uuid}"], ["docker rm -f {$this->database->uuid}"],
$this->database->destination->server $this->database->destination->server
); );
@@ -45,7 +45,7 @@ class Heading extends Component
} }
$this->database->status = 'stopped'; $this->database->status = 'stopped';
$this->database->save(); $this->database->save();
$this->emit('refresh'); $this->check_status();
// $this->database->environment->project->team->notify(new StatusChanged($this->database)); // $this->database->environment->project->team->notify(new StatusChanged($this->database));
} }

View File

@@ -36,7 +36,7 @@ class InitScript extends Component
$this->script['filename'] = $this->filename; $this->script['filename'] = $this->filename;
$this->emitUp('save_init_script', $this->script); $this->emitUp('save_init_script', $this->script);
} catch (Exception $e) { } catch (Exception $e) {
return general_error_handler(err: $e, that: $this); return handleError($e, $this);
} }
} }

View File

@@ -5,6 +5,7 @@ namespace App\Http\Livewire\Project\Database\Postgresql;
use App\Models\StandalonePostgresql; use App\Models\StandalonePostgresql;
use Exception; use Exception;
use Livewire\Component; use Livewire\Component;
use function Aws\filter; use function Aws\filter;
class General extends Component class General extends Component
@@ -73,9 +74,9 @@ class General extends Component
} }
$this->getDbUrl(); $this->getDbUrl();
$this->database->save(); $this->database->save();
} catch(Exception $e) { } catch(\Throwable $e) {
$this->database->is_public = !$this->database->is_public; $this->database->is_public = !$this->database->is_public;
return general_error_handler(err: $e, that: $this); return handleError($e, $this);
} }
} }
@@ -140,7 +141,7 @@ class General extends Component
$this->database->save(); $this->database->save();
$this->emit('success', 'Database updated successfully.'); $this->emit('success', 'Database updated successfully.');
} catch (Exception $e) { } catch (Exception $e) {
return general_error_handler(err: $e, that: $this); return handleError($e, $this);
} }
} }
} }

View File

@@ -20,7 +20,7 @@ class Edit extends Component
$this->project->save(); $this->project->save();
$this->emit('saved'); $this->emit('saved');
} catch (\Throwable $e) { } catch (\Throwable $e) {
return general_error_handler($e, $this); return handleError($e, $this);
} }
} }
} }

View File

@@ -165,7 +165,7 @@ class GithubPrivateRepository extends Component
'project_uuid' => $project->uuid, 'project_uuid' => $project->uuid,
]); ]);
} catch (\Throwable $e) { } catch (\Throwable $e) {
return general_error_handler(err: $e, that: $this); return handleError($e, $this);
} }
} }

View File

@@ -118,7 +118,7 @@ class GithubPrivateRepositoryDeployKey extends Component
'application_uuid' => $application->uuid, 'application_uuid' => $application->uuid,
]); ]);
} catch (\Throwable $e) { } catch (\Throwable $e) {
return general_error_handler(err: $e, that: $this); return handleError($e, $this);
} }
} }

View File

@@ -76,14 +76,14 @@ class PublicGitRepository extends Component
$this->get_branch(); $this->get_branch();
$this->selected_branch = $this->git_branch; $this->selected_branch = $this->git_branch;
} catch (\Throwable $e) { } catch (\Throwable $e) {
return general_error_handler(err: $e, that: $this); return handleError($e, $this);
} }
if (!$this->branch_found && $this->git_branch == 'main') { if (!$this->branch_found && $this->git_branch == 'main') {
try { try {
$this->git_branch = 'master'; $this->git_branch = 'master';
$this->get_branch(); $this->get_branch();
} catch (\Throwable $e) { } catch (\Throwable $e) {
return general_error_handler(err: $e, that: $this); return handleError($e, $this);
} }
} }
} }
@@ -162,7 +162,7 @@ class PublicGitRepository extends Component
'application_uuid' => $application->uuid, 'application_uuid' => $application->uuid,
]); ]);
} catch (\Throwable $e) { } catch (\Throwable $e) {
return general_error_handler(err: $e, that: $this); return handleError($e, $this);
} }
} }
} }

View File

@@ -41,7 +41,7 @@ class Select extends Component
// instantCommand("psql {$this->existingPostgresqlUrl} -c 'SELECT 1'"); // instantCommand("psql {$this->existingPostgresqlUrl} -c 'SELECT 1'");
// $this->emit('success', 'Successfully connected to the database.'); // $this->emit('success', 'Successfully connected to the database.');
// } catch (\Throwable $e) { // } catch (\Throwable $e) {
// return general_error_handler($e, $this); // return handleError($e, $this);
// } // }
// } // }
public function setType(string $type) public function setType(string $type)

View File

@@ -114,7 +114,7 @@ class All extends Component
$this->refreshEnvs(); $this->refreshEnvs();
$this->emit('success', 'Environment variable added successfully.'); $this->emit('success', 'Environment variable added successfully.');
} catch (\Throwable $e) { } catch (\Throwable $e) {
return general_error_handler(err: $e, that: $this); return handleError($e, $this);
} }
} }
} }

View File

@@ -54,7 +54,7 @@ class ResourceLimits extends Component
$this->resource->save(); $this->resource->save();
$this->emit('success', 'Resource limits updated successfully.'); $this->emit('success', 'Resource limits updated successfully.');
} catch (\Throwable $e) { } catch (\Throwable $e) {
return general_error_handler(err: $e, that: $this); return handleError($e, $this);
} }
} }
} }

View File

@@ -29,7 +29,7 @@ class All extends Component
$this->emit('success', 'Storage added successfully'); $this->emit('success', 'Storage added successfully');
$this->emit('clearAddStorage'); $this->emit('clearAddStorage');
} catch (\Throwable $e) { } catch (\Throwable $e) {
return general_error_handler(err: $e, that: $this); return handleError($e, $this);
} }
} }
} }

View File

@@ -33,7 +33,7 @@ class RunCommand extends Component
$activity = remote_process([$this->command], Server::where('uuid', $this->server)->first(), ignore_errors: true); $activity = remote_process([$this->command], Server::where('uuid', $this->server)->first(), ignore_errors: true);
$this->emit('newMonitorActivity', $activity->id); $this->emit('newMonitorActivity', $activity->id);
} catch (\Throwable $e) { } catch (\Throwable $e) {
return general_error_handler(err: $e); return handleError($e, $this);
} }
} }
} }

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,19 +45,23 @@ class Form extends Component
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);
} }
public function validateServer() public function validateServer()
{ {
try { try {
['uptime' => $uptime, 'dockerVersion' => $dockerVersion] = validateServer($this->server); ['uptime' => $uptime, 'dockerVersion' => $dockerVersion] = validateServer($this->server, true);
if ($uptime) { if ($uptime) {
$this->uptime = $uptime; $this->uptime = $uptime;
$this->emit('success', 'Server is reachable!'); $this->emit('success', 'Server is reachable.');
} else { } else {
$this->emit('error', 'Server is not reachable'); ray($this->uptime);
$this->emit('error', 'Server is not reachable.');
return; return;
} }
if ($dockerVersion) { if ($dockerVersion) {
@@ -64,10 +69,10 @@ class Form extends Component
$this->emit('proxyStatusUpdated'); $this->emit('proxyStatusUpdated');
$this->emit('success', 'Docker Engine 23+ is installed!'); $this->emit('success', 'Docker Engine 23+ is installed!');
} else { } else {
$this->emit('error', 'Old (lower than 23) or no Docker version detected. Install Docker Engine on the General tab.'); $this->emit('error', 'No Docker Engine or older than 23 version installed.');
} }
} catch (\Throwable $e) { } catch (\Throwable $e) {
return general_error_handler($e, that: $this); return handleError($e, $this, customErrorMessage: "Server is not reachable: ");
} }
} }
@@ -82,7 +87,7 @@ class Form extends Component
$this->server->delete(); $this->server->delete();
return redirect()->route('server.all'); return redirect()->route('server.all');
} catch (\Throwable $e) { } catch (\Throwable $e) {
return general_error_handler(err: $e, that: $this); return handleError($e, $this);
} }
} }
public function submit() public function submit()

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',
]; ];
@@ -79,7 +79,7 @@ class ByIp extends Component
$server->settings->save(); $server->settings->save();
return redirect()->route('server.show', $server->uuid); return redirect()->route('server.show', $server->uuid);
} catch (\Throwable $e) { } catch (\Throwable $e) {
return general_error_handler(err: $e); return handleError($e);
} }
} }
} }

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,34 +47,32 @@ class Proxy extends Component
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();
setup_default_redirect_404(redirect_url: $this->server->proxy->redirect_url, server: $this->server); setup_default_redirect_404(redirect_url: $this->server->proxy->redirect_url, server: $this->server);
$this->emit('success', 'Proxy configuration saved.'); $this->emit('success', 'Proxy configuration saved.');
} catch (\Throwable $e) { } catch (\Throwable $e) {
return general_error_handler(err: $e); return handleError($e);
} }
} }
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 general_error_handler(err: $e); return handleError($e);
} }
} }
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 general_error_handler(err: $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,13 +19,11 @@ class Status extends Component
{ {
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) {
return general_error_handler(err: $e); return handleError($e);
} }
} }
public function getProxyStatusWithNoti() public function getProxyStatusWithNoti()

View File

@@ -18,7 +18,7 @@ class Show extends Component
return redirect()->route('server.all'); return redirect()->route('server.all');
} }
} catch (\Throwable $e) { } catch (\Throwable $e) {
return general_error_handler(err: $e, that: $this); return handleError($e, $this);
} }
} }
public function render() public function render()

View File

@@ -28,27 +28,40 @@ class ShowPrivateKey extends Component
]); ]);
$this->server->refresh(); $this->server->refresh();
refresh_server_connection($this->server->privateKey); refresh_server_connection($this->server->privateKey);
return general_error_handler($e, that: $this); return handleError($e, $this);
} }
} }
public function checkConnection() public function checkConnection()
{ {
try { try {
['uptime' => $uptime, 'dockerVersion' => $dockerVersion] = validateServer($this->server); ['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) {
throw new \Exception($e->getMessage()); return handleError($e, $this);
} }
} }

View File

@@ -51,7 +51,7 @@ class Email extends Component
$this->settings->save(); $this->settings->save();
$this->emit('success', 'Settings saved successfully.'); $this->emit('success', 'Settings saved successfully.');
} catch (\Throwable $e) { } catch (\Throwable $e) {
return general_error_handler($e, $this); return handleError($e, $this);
} }
} }
public function submitResend() { public function submitResend() {
@@ -64,7 +64,7 @@ class Email extends Component
$this->emit('success', 'Settings saved successfully.'); $this->emit('success', 'Settings saved successfully.');
} catch (\Throwable $e) { } catch (\Throwable $e) {
$this->settings->resend_enabled = false; $this->settings->resend_enabled = false;
return general_error_handler($e, $this); return handleError($e, $this);
} }
} }
public function instantSaveResend() { public function instantSaveResend() {
@@ -72,7 +72,7 @@ class Email extends Component
$this->settings->smtp_enabled = false; $this->settings->smtp_enabled = false;
$this->submitResend(); $this->submitResend();
} catch (\Throwable $e) { } catch (\Throwable $e) {
return general_error_handler($e, $this); return handleError($e, $this);
} }
} }
public function instantSave() public function instantSave()
@@ -81,7 +81,7 @@ class Email extends Component
$this->settings->resend_enabled = false; $this->settings->resend_enabled = false;
$this->submit(); $this->submit();
} catch (\Throwable $e) { } catch (\Throwable $e) {
return general_error_handler($e, $this); return handleError($e, $this);
} }
} }
@@ -100,7 +100,7 @@ class Email extends Component
$this->settings->save(); $this->settings->save();
$this->emit('success', 'Settings saved successfully.'); $this->emit('success', 'Settings saved successfully.');
} catch (\Throwable $e) { } catch (\Throwable $e) {
return general_error_handler($e, $this); return handleError($e, $this);
} }
} }

View File

@@ -52,7 +52,7 @@ class Change extends Component
$this->validate(); $this->validate();
$this->github_app->save(); $this->github_app->save();
} catch (\Throwable $e) { } catch (\Throwable $e) {
return general_error_handler(err: $e, that: $this); return handleError($e, $this);
} }
} }
@@ -66,7 +66,7 @@ class Change extends Component
$this->github_app->delete(); $this->github_app->delete();
redirect()->route('source.all'); redirect()->route('source.all');
} catch (\Throwable $e) { } catch (\Throwable $e) {
return general_error_handler(err: $e, that: $this); return handleError($e, $this);
} }
} }
} }

View File

@@ -50,7 +50,7 @@ class Create extends Component
} }
redirect()->route('source.github.show', ['github_app_uuid' => $github_app->uuid]); redirect()->route('source.github.show', ['github_app_uuid' => $github_app->uuid]);
} catch (\Throwable $e) { } catch (\Throwable $e) {
return general_error_handler(err: $e, that: $this); return handleError($e, $this);
} }
} }
} }

View File

@@ -31,7 +31,7 @@ class Actions extends Component
$this->emit('reloadWindow', 5000); $this->emit('reloadWindow', 5000);
} }
} catch (\Throwable $e) { } catch (\Throwable $e) {
return general_error_handler($e, $this); return handleError($e, $this);
} }
} }
public function resume() public function resume()
@@ -66,7 +66,7 @@ class Actions extends Component
$this->emit('reloadWindow', 5000); $this->emit('reloadWindow', 5000);
} }
} catch (\Throwable $e) { } catch (\Throwable $e) {
return general_error_handler($e, $this); return handleError($e, $this);
} }
} }
public function stripeCustomerPortal() { public function stripeCustomerPortal() {

View File

@@ -32,7 +32,7 @@ class Create extends Component
refreshSession(); refreshSession();
return redirect()->route('team.index'); return redirect()->route('team.index');
} catch (\Throwable $e) { } catch (\Throwable $e) {
return general_error_handler($e, $this); return handleError($e, $this);
} }
} }
} }

View File

@@ -27,8 +27,9 @@ class Form extends Component
$this->validate(); $this->validate();
try { try {
$this->team->save(); $this->team->save();
refreshSession();
} catch (\Throwable $e) { } catch (\Throwable $e) {
return general_error_handler($e, $this); return handleError($e, $this);
} }
} }
} }

View File

@@ -36,7 +36,7 @@ class InviteLink extends Component
try { try {
$member_emails = currentTeam()->members()->get()->pluck('email'); $member_emails = currentTeam()->members()->get()->pluck('email');
if ($member_emails->contains($this->email)) { if ($member_emails->contains($this->email)) {
return general_error_handler(that: $this, customErrorMessage: "$this->email is already a member of " . currentTeam()->name . "."); return handleError(livewire: $this, customErrorMessage: "$this->email is already a member of " . currentTeam()->name . ".");
} }
$uuid = new Cuid2(32); $uuid = new Cuid2(32);
$link = url('/') . config('constants.invitation.link.base_url') . $uuid; $link = url('/') . config('constants.invitation.link.base_url') . $uuid;
@@ -57,7 +57,7 @@ class InviteLink extends Component
if (!is_null($invitation)) { if (!is_null($invitation)) {
$invitationValid = $invitation->isValid(); $invitationValid = $invitation->isValid();
if ($invitationValid) { if ($invitationValid) {
return general_error_handler(that: $this, customErrorMessage: "Pending invitation already exists for $this->email."); return handleError(livewire: $this, customErrorMessage: "Pending invitation already exists for $this->email.");
} else { } else {
$invitation->delete(); $invitation->delete();
} }
@@ -91,7 +91,7 @@ class InviteLink extends Component
if ($e->getCode() === '23505') { if ($e->getCode() === '23505') {
$error_message = 'Invitation already sent.'; $error_message = 'Invitation already sent.';
} }
return general_error_handler(err: $e, that: $this, customErrorMessage: $error_message); return handleError(error: $e, livewire: $this, customErrorMessage: $error_message);
} }
} }
} }

View File

@@ -68,7 +68,7 @@ class Create extends Component
$this->storage->save(); $this->storage->save();
return redirect()->route('team.storages.show', $this->storage->uuid); return redirect()->route('team.storages.show', $this->storage->uuid);
} catch (\Throwable $e) { } catch (\Throwable $e) {
return general_error_handler($e, $this); return handleError($e, $this);
} }
} }
@@ -78,7 +78,7 @@ class Create extends Component
$this->storage->testConnection(); $this->storage->testConnection();
return $this->emit('success', 'Connection is working. Tested with "ListObjectsV2" action.'); return $this->emit('success', 'Connection is working. Tested with "ListObjectsV2" action.');
} catch (\Throwable $e) { } catch (\Throwable $e) {
return general_error_handler($e, $this); return handleError($e, $this);
} }
} }
} }

View File

@@ -33,7 +33,7 @@ class Form extends Component
$this->storage->testConnection(); $this->storage->testConnection();
return $this->emit('success', 'Connection is working. Tested with "ListObjectsV2" action.'); return $this->emit('success', 'Connection is working. Tested with "ListObjectsV2" action.');
} catch (\Throwable $e) { } catch (\Throwable $e) {
return general_error_handler($e, $this); return handleError($e, $this);
} }
} }
@@ -43,7 +43,7 @@ class Form extends Component
$this->storage->delete(); $this->storage->delete();
return redirect()->route('team.storages.all'); return redirect()->route('team.storages.all');
} catch (\Throwable $e) { } catch (\Throwable $e) {
return general_error_handler($e, $this); return handleError($e, $this);
} }
} }
@@ -56,7 +56,7 @@ class Form extends Component
$this->storage->save(); $this->storage->save();
$this->emit('success', 'Storage settings saved.'); $this->emit('success', 'Storage settings saved.');
} catch (\Throwable $e) { } catch (\Throwable $e) {
return general_error_handler($e, $this); return handleError($e, $this);
} }
} }
} }

View File

@@ -36,9 +36,9 @@ class Upgrade extends Component
} }
$this->showProgress = true; $this->showProgress = true;
resolve(UpdateCoolify::class)(true); resolve(UpdateCoolify::class)(true);
Toaster::success("Upgrading to {$this->latestVersion} version..."); $this->emit('success', "Upgrading to {$this->latestVersion} version...");
} catch (\Throwable $e) { } catch (\Throwable $e) {
return general_error_handler(err: $e, that: $this); return handleError($e, $this);
} }
} }
} }

View File

@@ -54,7 +54,7 @@ class Index extends Component
$this->emit('success', 'Check your email to verify your email address.'); $this->emit('success', 'Check your email to verify your email address.');
dispatch(new SendConfirmationForWaitlistJob($this->email, $waitlist->uuid)); dispatch(new SendConfirmationForWaitlistJob($this->email, $waitlist->uuid));
} catch (\Throwable $e) { } catch (\Throwable $e) {
return general_error_handler(err: $e, that: $this); return handleError($e, $this);
} }
} }
} }

View File

@@ -177,7 +177,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$this->prepare_builder_image(); $this->prepare_builder_image();
$this->execute_remote_command( $this->execute_remote_command(
[ [
$this->execute_in_builder("echo '$dockerfile_base64' | base64 -d > $this->workdir/Dockerfile") executeInDocker($this->deployment_uuid, "echo '$dockerfile_base64' | base64 -d > $this->workdir/Dockerfile")
], ],
); );
$this->build_image_name = Str::lower("{$this->application->git_repository}:build"); $this->build_image_name = Str::lower("{$this->application->git_repository}:build");
@@ -302,7 +302,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$this->stop_running_container(); $this->stop_running_container();
$this->execute_remote_command( $this->execute_remote_command(
["echo -n 'Starting preview deployment.'"], ["echo -n 'Starting preview deployment.'"],
[$this->execute_in_builder("docker compose --project-directory {$this->workdir} up -d >/dev/null"), "hidden" => true], [executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} up -d >/dev/null"), "hidden" => true],
); );
} }
@@ -324,16 +324,12 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
"hidden" => true, "hidden" => true,
], ],
[ [
"command" => $this->execute_in_builder("mkdir -p {$this->workdir}") "command" => executeInDocker($this->deployment_uuid, "mkdir -p {$this->workdir}")
], ],
); );
} }
private function execute_in_builder(string $command)
{
return "docker exec {$this->deployment_uuid} bash -c '{$command}'";
// return "docker exec {$this->deployment_uuid} bash -c '{$command} |& tee -a /proc/1/fd/1; [ \$PIPESTATUS -eq 0 ] || exit \$PIPESTATUS'";
}
private function clone_repository() private function clone_repository()
{ {
@@ -345,7 +341,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$this->importing_git_repository() $this->importing_git_repository()
], ],
[ [
$this->execute_in_builder("cd {$this->workdir} && git rev-parse HEAD"), executeInDocker($this->deployment_uuid, "cd {$this->workdir} && git rev-parse HEAD"),
"hidden" => true, "hidden" => true,
"save" => "git_commit_sha" "save" => "git_commit_sha"
], ],
@@ -372,13 +368,13 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$git_clone_command = "{$git_clone_command} {$this->source->html_url}/{$this->application->git_repository} {$this->workdir}"; $git_clone_command = "{$git_clone_command} {$this->source->html_url}/{$this->application->git_repository} {$this->workdir}";
$git_clone_command = $this->set_git_import_settings($git_clone_command); $git_clone_command = $this->set_git_import_settings($git_clone_command);
$commands->push($this->execute_in_builder($git_clone_command)); $commands->push(executeInDocker($this->deployment_uuid, $git_clone_command));
} else { } else {
$github_access_token = generate_github_installation_token($this->source); $github_access_token = generate_github_installation_token($this->source);
$commands->push($this->execute_in_builder("git clone -q -b {$this->application->git_branch} $source_html_url_scheme://x-access-token:$github_access_token@$source_html_url_host/{$this->application->git_repository}.git {$this->workdir}")); $commands->push(executeInDocker($this->deployment_uuid, "git clone -q -b {$this->application->git_branch} $source_html_url_scheme://x-access-token:$github_access_token@$source_html_url_host/{$this->application->git_repository}.git {$this->workdir}"));
} }
if ($this->pull_request_id !== 0) { if ($this->pull_request_id !== 0) {
$commands->push($this->execute_in_builder("cd {$this->workdir} && git fetch origin pull/{$this->pull_request_id}/head:$pr_branch_name && git checkout $pr_branch_name")); $commands->push(executeInDocker($this->deployment_uuid, "cd {$this->workdir} && git fetch origin pull/{$this->pull_request_id}/head:$pr_branch_name && git checkout $pr_branch_name"));
} }
return $commands->implode(' && '); return $commands->implode(' && ');
} }
@@ -388,10 +384,10 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$git_clone_command = "GIT_SSH_COMMAND=\"ssh -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" {$git_clone_command} {$this->application->git_full_url} {$this->workdir}"; $git_clone_command = "GIT_SSH_COMMAND=\"ssh -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" {$git_clone_command} {$this->application->git_full_url} {$this->workdir}";
$git_clone_command = $this->set_git_import_settings($git_clone_command); $git_clone_command = $this->set_git_import_settings($git_clone_command);
$commands = collect([ $commands = collect([
$this->execute_in_builder("mkdir -p /root/.ssh"), executeInDocker($this->deployment_uuid, "mkdir -p /root/.ssh"),
$this->execute_in_builder("echo '{$private_key}' | base64 -d > /root/.ssh/id_rsa"), executeInDocker($this->deployment_uuid, "echo '{$private_key}' | base64 -d > /root/.ssh/id_rsa"),
$this->execute_in_builder("chmod 600 /root/.ssh/id_rsa"), executeInDocker($this->deployment_uuid, "chmod 600 /root/.ssh/id_rsa"),
$this->execute_in_builder($git_clone_command) executeInDocker($this->deployment_uuid, $git_clone_command)
]); ]);
return $commands->implode(' && '); return $commands->implode(' && ');
} }
@@ -414,7 +410,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
private function cleanup_git() private function cleanup_git()
{ {
$this->execute_remote_command( $this->execute_remote_command(
[$this->execute_in_builder("rm -fr {$this->workdir}/.git")], [executeInDocker($this->deployment_uuid, "rm -fr {$this->workdir}/.git")],
); );
} }
@@ -425,8 +421,8 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
"echo -n 'Generating nixpacks configuration.'", "echo -n 'Generating nixpacks configuration.'",
], ],
[$this->nixpacks_build_cmd()], [$this->nixpacks_build_cmd()],
[$this->execute_in_builder("cp {$this->workdir}/.nixpacks/Dockerfile {$this->workdir}/Dockerfile")], [executeInDocker($this->deployment_uuid, "cp {$this->workdir}/.nixpacks/Dockerfile {$this->workdir}/Dockerfile")],
[$this->execute_in_builder("rm -f {$this->workdir}/.nixpacks/Dockerfile")] [executeInDocker($this->deployment_uuid, "rm -f {$this->workdir}/.nixpacks/Dockerfile")]
); );
} }
@@ -444,7 +440,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$nixpacks_command .= " --install-cmd \"{$this->application->install_command}\""; $nixpacks_command .= " --install-cmd \"{$this->application->install_command}\"";
} }
$nixpacks_command .= " {$this->workdir}"; $nixpacks_command .= " {$this->workdir}";
return $this->execute_in_builder($nixpacks_command); return executeInDocker($this->deployment_uuid, $nixpacks_command);
} }
private function generate_env_variables() private function generate_env_variables()
@@ -522,7 +518,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
} }
$this->docker_compose = Yaml::dump($docker_compose, 10); $this->docker_compose = Yaml::dump($docker_compose, 10);
$this->docker_compose_base64 = base64_encode($this->docker_compose); $this->docker_compose_base64 = base64_encode($this->docker_compose);
$this->execute_remote_command([$this->execute_in_builder("echo '{$this->docker_compose_base64}' | base64 -d > {$this->workdir}/docker-compose.yml"), "hidden" => true]); $this->execute_remote_command([executeInDocker($this->deployment_uuid, "echo '{$this->docker_compose_base64}' | base64 -d > {$this->workdir}/docker-compose.yml"), "hidden" => true]);
} }
private function generate_local_persistent_volumes() private function generate_local_persistent_volumes()
@@ -679,7 +675,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
if ($this->application->settings->is_static) { if ($this->application->settings->is_static) {
$this->execute_remote_command([ $this->execute_remote_command([
$this->execute_in_builder("docker build --network host -f {$this->workdir}/Dockerfile {$this->build_args} --progress plain -t $this->build_image_name {$this->workdir}"), "hidden" => true executeInDocker($this->deployment_uuid, "docker build --network host -f {$this->workdir}/Dockerfile {$this->build_args} --progress plain -t $this->build_image_name {$this->workdir}"), "hidden" => true
]); ]);
$dockerfile = base64_encode("FROM {$this->application->static_image} $dockerfile = base64_encode("FROM {$this->application->static_image}
@@ -706,18 +702,18 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
}"); }");
$this->execute_remote_command( $this->execute_remote_command(
[ [
$this->execute_in_builder("echo '{$dockerfile}' | base64 -d > {$this->workdir}/Dockerfile-prod") executeInDocker($this->deployment_uuid, "echo '{$dockerfile}' | base64 -d > {$this->workdir}/Dockerfile-prod")
], ],
[ [
$this->execute_in_builder("echo '{$nginx_config}' | base64 -d > {$this->workdir}/nginx.conf") executeInDocker($this->deployment_uuid, "echo '{$nginx_config}' | base64 -d > {$this->workdir}/nginx.conf")
], ],
[ [
$this->execute_in_builder("docker build --network host -f {$this->workdir}/Dockerfile-prod {$this->build_args} --progress plain -t $this->production_image_name {$this->workdir}"), "hidden" => true executeInDocker($this->deployment_uuid, "docker build --network host -f {$this->workdir}/Dockerfile-prod {$this->build_args} --progress plain -t $this->production_image_name {$this->workdir}"), "hidden" => true
] ]
); );
} else { } else {
$this->execute_remote_command([ $this->execute_remote_command([
$this->execute_in_builder("docker build --network host -f {$this->workdir}/Dockerfile {$this->build_args} --progress plain -t $this->production_image_name {$this->workdir}"), "hidden" => true executeInDocker($this->deployment_uuid, "docker build --network host -f {$this->workdir}/Dockerfile {$this->build_args} --progress plain -t $this->production_image_name {$this->workdir}"), "hidden" => true
]); ]);
} }
} }
@@ -727,7 +723,7 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
if ($this->currently_running_container_name) { if ($this->currently_running_container_name) {
$this->execute_remote_command( $this->execute_remote_command(
["echo -n 'Removing old version of your application.'"], ["echo -n 'Removing old version of your application.'"],
[$this->execute_in_builder("docker rm -f $this->currently_running_container_name >/dev/null 2>&1"), "hidden" => true], [executeInDocker($this->deployment_uuid, "docker rm -f $this->currently_running_container_name >/dev/null 2>&1"), "hidden" => true],
); );
} }
} }
@@ -736,7 +732,7 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
{ {
$this->execute_remote_command( $this->execute_remote_command(
["echo -n 'Rolling update started.'"], ["echo -n 'Rolling update started.'"],
[$this->execute_in_builder("docker compose --project-directory {$this->workdir} up -d >/dev/null"), "hidden" => true], [executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} up -d >/dev/null"), "hidden" => true],
); );
} }
@@ -759,7 +755,7 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
private function add_build_env_variables_to_dockerfile() private function add_build_env_variables_to_dockerfile()
{ {
$this->execute_remote_command([ $this->execute_remote_command([
$this->execute_in_builder("cat {$this->workdir}/Dockerfile"), "hidden" => true, "save" => 'dockerfile' executeInDocker($this->deployment_uuid, "cat {$this->workdir}/Dockerfile"), "hidden" => true, "save" => 'dockerfile'
]); ]);
$dockerfile = collect(Str::of($this->saved_outputs->get('dockerfile'))->trim()->explode("\n")); $dockerfile = collect(Str::of($this->saved_outputs->get('dockerfile'))->trim()->explode("\n"));
@@ -768,7 +764,7 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
} }
$dockerfile_base64 = base64_encode($dockerfile->implode("\n")); $dockerfile_base64 = base64_encode($dockerfile->implode("\n"));
$this->execute_remote_command([ $this->execute_remote_command([
$this->execute_in_builder("echo '{$dockerfile_base64}' | base64 -d > {$this->workdir}/Dockerfile"), executeInDocker($this->deployment_uuid, "echo '{$dockerfile_base64}' | base64 -d > {$this->workdir}/Dockerfile"),
"hidden" => true "hidden" => true
]); ]);
} }

View File

@@ -40,20 +40,19 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
return $this->server->uuid; return $this->server->uuid;
} }
private function checkServerConnection() { private function checkServerConnection()
ray("Checking server connection to {$this->server->ip}"); {
$uptime = instant_remote_process(['uptime'], $this->server, false); $uptime = instant_remote_process(['uptime'], $this->server, false);
if (!is_null($uptime)) { if (!is_null($uptime)) {
ray('Server is up');
return true; return true;
} }
} }
public function handle(): void 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]);
@@ -67,19 +66,29 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
$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 = [];
@@ -90,10 +99,10 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
$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;
@@ -130,10 +139,9 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
} }
} }
} }
} }
$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;
@@ -172,14 +180,14 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
$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;
@@ -216,12 +224,10 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
$isPR = Str::startsWith(data_get($value, 'Name'), "/$uuid"); $isPR = Str::startsWith(data_get($value, 'Name'), "/$uuid");
$isPR = Str::contains(data_get($value, 'Name'), "-pr-"); $isPR = Str::contains(data_get($value, 'Name'), "-pr-");
if ($isPR) { if ($isPR) {
ray('is pr');
return false; return false;
} }
return $value; return $value;
})->first(); })->first();
ray($foundContainer);
if ($foundContainer) { if ($foundContainer) {
$containerStatus = data_get($foundContainer, 'State.Status'); $containerStatus = data_get($foundContainer, 'State.Status');
$databaseStatus = data_get($application, 'status'); $databaseStatus = data_get($application, 'status');
@@ -253,7 +259,6 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
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');
@@ -280,7 +285,7 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
} }
} }
} }
// 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

@@ -18,7 +18,6 @@ use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\Middleware\WithoutOverlapping; use Illuminate\Queue\Middleware\WithoutOverlapping;
use Illuminate\Queue\SerializesModels; use Illuminate\Queue\SerializesModels;
use Throwable;
use Illuminate\Support\Str; use Illuminate\Support\Str;
class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
@@ -117,7 +116,7 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
$this->backup_status = 'success'; $this->backup_status = 'success';
$this->team->notify(new BackupSuccess($this->backup, $this->database)); $this->team->notify(new BackupSuccess($this->backup, $this->database));
} catch (Throwable $e) { } catch (\Throwable $e) {
$this->backup_status = 'failed'; $this->backup_status = 'failed';
$this->add_to_backup_output($e->getMessage()); $this->add_to_backup_output($e->getMessage());
ray('Backup failed for ' . $this->container_name . ' at ' . $this->server->name . ':' . $this->backup_location . '\n\nError:' . $e->getMessage()); ray('Backup failed for ' . $this->container_name . ' at ' . $this->server->name . ':' . $this->backup_location . '\n\nError:' . $e->getMessage());

View File

@@ -21,9 +21,9 @@ class Team extends Model implements SendsDiscord, SendsEmail
protected static function booted() protected static function booted()
{ {
static::saved(function () { // static::saved(function () {
refreshSession(); // refreshSession();
}); // });
} }
public function routeNotificationForDiscord() public function routeNotificationForDiscord()

View File

@@ -4,8 +4,6 @@ namespace App\Notifications\Application;
use App\Models\Application; use App\Models\Application;
use App\Models\ApplicationPreview; use App\Models\ApplicationPreview;
use App\Notifications\Channels\DiscordChannel;
use App\Notifications\Channels\EmailChannel;
use Illuminate\Bus\Queueable; use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Notifications\Messages\MailMessage;
@@ -18,13 +16,14 @@ class DeploymentFailed extends Notification implements ShouldQueue
public $tries = 5; public $tries = 5;
public Application $application; public Application $application;
public string $deployment_uuid;
public ?ApplicationPreview $preview = null; public ?ApplicationPreview $preview = null;
public string $deployment_uuid;
public string $application_name; public string $application_name;
public ?string $deployment_url = null;
public string $project_uuid; public string $project_uuid;
public string $environment_name; public string $environment_name;
public ?string $deployment_url = null;
public ?string $fqdn = null; public ?string $fqdn = null;
public function __construct(Application $application, string $deployment_uuid, ?ApplicationPreview $preview = null) public function __construct(Application $application, string $deployment_uuid, ?ApplicationPreview $preview = null)

View File

@@ -4,8 +4,6 @@ namespace App\Notifications\Application;
use App\Models\Application; use App\Models\Application;
use App\Models\ApplicationPreview; use App\Models\ApplicationPreview;
use App\Notifications\Channels\DiscordChannel;
use App\Notifications\Channels\EmailChannel;
use Illuminate\Bus\Queueable; use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Notifications\Messages\MailMessage;
@@ -18,14 +16,15 @@ class DeploymentSuccess extends Notification implements ShouldQueue
public $tries = 5; public $tries = 5;
public Application $application; public Application $application;
public string $deployment_uuid;
public ApplicationPreview|null $preview = null; public ApplicationPreview|null $preview = null;
public string $deployment_uuid;
public string $application_name; public string $application_name;
public string|null $deployment_url = null;
public string $project_uuid; public string $project_uuid;
public string $environment_name; public string $environment_name;
public string|null $fqdn;
public ?string $deployment_url = null;
public ?string $fqdn;
public function __construct(Application $application, string $deployment_uuid, ApplicationPreview|null $preview = null) public function __construct(Application $application, string $deployment_uuid, ApplicationPreview|null $preview = null)
{ {

View File

@@ -2,8 +2,7 @@
namespace App\Notifications\Application; namespace App\Notifications\Application;
use App\Notifications\Channels\DiscordChannel; use App\Models\Application;
use App\Notifications\Channels\EmailChannel;
use Illuminate\Bus\Queueable; use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Notifications\Messages\MailMessage;
@@ -15,13 +14,14 @@ class StatusChanged extends Notification implements ShouldQueue
use Queueable; use Queueable;
public $tries = 5; public $tries = 5;
public $application;
public Application $application;
public string $application_name; public string $application_name;
public string|null $application_url = null;
public string $project_uuid; public string $project_uuid;
public string $environment_name; public string $environment_name;
public string|null $fqdn;
public ?string $application_url = null;
public ?string $fqdn;
public function __construct($application) public function __construct($application)
{ {

View File

@@ -11,7 +11,7 @@ use Illuminate\Support\Str;
trait ExecuteRemoteCommand trait ExecuteRemoteCommand
{ {
public string|null $save = null; public ?string $save = null;
public function execute_remote_command(...$commands) public function execute_remote_command(...$commands)
{ {
@@ -25,11 +25,8 @@ trait ExecuteRemoteCommand
throw new \RuntimeException('Server is not set or is not an instance of Server model'); throw new \RuntimeException('Server is not set or is not an instance of Server model');
} }
$ip = data_get($this->server, 'ip');
$user = data_get($this->server, 'user');
$port = data_get($this->server, 'port');
$commandsText->each(function ($single_command) use ($ip, $user, $port) { $commandsText->each(function ($single_command) {
$command = data_get($single_command, 'command') ?? $single_command[0] ?? null; $command = data_get($single_command, 'command') ?? $single_command[0] ?? null;
if ($command === null) { if ($command === null) {
throw new \RuntimeException('Command is not set'); throw new \RuntimeException('Command is not set');
@@ -38,7 +35,7 @@ trait ExecuteRemoteCommand
$ignore_errors = data_get($single_command, 'ignore_errors', false); $ignore_errors = data_get($single_command, 'ignore_errors', false);
$this->save = data_get($single_command, 'save'); $this->save = data_get($single_command, 'save');
$remote_command = generateSshCommand( $ip, $user, $port, $command); $remote_command = generateSshCommand($this->server, $command);
$process = Process::timeout(3600)->idleTimeout(3600)->start($remote_command, function (string $type, string $output) use ($command, $hidden) { $process = Process::timeout(3600)->idleTimeout(3600)->start($remote_command, function (string $type, string $output) use ($command, $hidden) {
$output = Str::of($output)->trim(); $output = Str::of($output)->trim();
$new_log_entry = [ $new_log_entry = [

View File

@@ -59,6 +59,18 @@ function format_docker_envs_to_json($rawOutput)
return collect([]); return collect([]);
} }
} }
function checkMinimumDockerEngineVersion($dockerVersion) {
$majorDockerVersion = Str::of($dockerVersion)->before('.')->value();
if ($majorDockerVersion <= 22) {
$dockerVersion = null;
}
return $dockerVersion;
}
function executeInDocker(string $containerId, string $command)
{
return "docker exec {$containerId} bash -c '{$command}'";
// return "docker exec {$this->deployment_uuid} bash -c '{$command} |& tee -a /proc/1/fd/1; [ \$PIPESTATUS -eq 0 ] || exit \$PIPESTATUS'";
}
function getApplicationContainerStatus(Application $application) { function getApplicationContainerStatus(Application $application) {
$server = data_get($application,'destination.server'); $server = data_get($application,'destination.server');

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\Application;
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,
@@ -36,12 +34,10 @@ function remote_process(
return resolve(PrepareCoolifyTask::class, [ return resolve(PrepareCoolifyTask::class, [
'remoteProcessArgs' => new CoolifyTaskArgs( 'remoteProcessArgs' => new CoolifyTaskArgs(
server_ip: $server->ip, server_uuid: $server->uuid,
command: <<<EOT command: <<<EOT
{$command_string} {$command_string}
EOT, EOT,
port: $server->port,
user: $server->user,
type: $type, type: $type,
type_uuid: $type_uuid, type_uuid: $type_uuid,
model: $model, model: $model,
@@ -66,15 +62,14 @@ function addPrivateKeyToSshAgent(Server $server)
Storage::disk('ssh-keys')->makeDirectory('.'); Storage::disk('ssh-keys')->makeDirectory('.');
Storage::disk('ssh-mux')->makeDirectory('.'); Storage::disk('ssh-mux')->makeDirectory('.');
Storage::disk('ssh-keys')->put($sshKeyFileLocation, $server->privateKey->private_key); Storage::disk('ssh-keys')->put($sshKeyFileLocation, $server->privateKey->private_key);
return '/var/www/html/storage/app/ssh/keys/' . $sshKeyFileLocation; $location = '/var/www/html/storage/app/ssh/keys/' . $sshKeyFileLocation;
return $location;
} }
function generateSshCommand(string $server_ip, string $user, string $port, string $command, bool $isMux = true) function generateSshCommand(Server $server, string $command, bool $isMux = true)
{ {
$server = Server::where('ip', $server_ip)->first(); $user = $server->user;
if (!$server) { $port = $server->port;
throw new \Exception("Server with ip {$server_ip} not found");
}
$privateKeyLocation = addPrivateKeyToSshAgent($server); $privateKeyLocation = addPrivateKeyToSshAgent($server);
$timeout = config('constants.ssh.command_timeout'); $timeout = config('constants.ssh.command_timeout');
$connectionTimeout = config('constants.ssh.connection_timeout'); $connectionTimeout = config('constants.ssh.connection_timeout');
@@ -95,35 +90,46 @@ function generateSshCommand(string $server_ip, string $user, string $port, strin
. '-o RequestTTY=no ' . '-o RequestTTY=no '
. '-o LogLevel=ERROR ' . '-o LogLevel=ERROR '
. "-p {$port} " . "-p {$port} "
. "{$user}@{$server_ip} " . "{$user}@{$server->ip} "
. " 'bash -se' << \\$delimiter" . PHP_EOL . " 'bash -se' << \\$delimiter" . PHP_EOL
. $command . PHP_EOL . $command . PHP_EOL
. $delimiter; . $delimiter;
// ray($ssh_command); // ray($ssh_command);
return $ssh_command; return $ssh_command;
} }
function instant_remote_process(array $command, Server $server, $throwError = true, $repeat = 1) function instant_remote_process(array $command, Server $server, $throwError = true)
{ {
$command_string = implode("\n", $command); $command_string = implode("\n", $command);
$ssh_command = generateSshCommand($server->ip, $server->user, $server->port, $command_string); $ssh_command = generateSshCommand($server, $command_string);
$process = Process::run($ssh_command); $process = Process::run($ssh_command);
$output = trim($process->output()); $output = trim($process->output());
$exitCode = $process->exitCode(); $exitCode = $process->exitCode();
if ($exitCode !== 0) { if ($exitCode !== 0) {
if ($repeat > 1) {
ray("repeat: ", $repeat);
Sleep::for(200)->milliseconds();
return instant_remote_process($command, $server, $throwError, $repeat - 1);
}
// ray('ERROR OCCURED: ' . $process->errorOutput());
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'));
@@ -161,11 +167,10 @@ function refresh_server_connection(PrivateKey $private_key)
} }
} }
function validateServer(Server $server) function validateServer(Server $server, bool $throwError = false)
{ {
try { try {
refresh_server_connection($server->privateKey); $uptime = instant_remote_process(['uptime'], $server, $throwError);
$uptime = instant_remote_process(['uptime'], $server, false);
if (!$uptime) { if (!$uptime) {
$server->settings->is_reachable = false; $server->settings->is_reachable = false;
return [ return [
@@ -175,7 +180,7 @@ function validateServer(Server $server)
} }
$server->settings->is_reachable = true; $server->settings->is_reachable = true;
$dockerVersion = instant_remote_process(["docker version|head -2|grep -i version| awk '{print $2}'"], $server, false); $dockerVersion = instant_remote_process(["docker version|head -2|grep -i version| awk '{print $2}'"], $server, $throwError);
if (!$dockerVersion) { if (!$dockerVersion) {
$dockerVersion = null; $dockerVersion = null;
return [ return [
@@ -183,9 +188,8 @@ function validateServer(Server $server)
"dockerVersion" => null, "dockerVersion" => null,
]; ];
} }
$majorDockerVersion = Str::of($dockerVersion)->before('.')->value(); $dockerVersion = checkMinimumDockerEngineVersion($dockerVersion);
if ($majorDockerVersion <= 22) { if (is_null($dockerVersion)) {
$dockerVersion = null;
$server->settings->is_usable = false; $server->settings->is_usable = false;
} else { } else {
$server->settings->is_usable = true; $server->settings->is_usable = true;

View File

@@ -62,19 +62,41 @@ function showBoarding(): bool
function refreshSession(?Team $team = null): void function refreshSession(?Team $team = null): void
{ {
if (!$team) { if (!$team) {
if (auth()->user()->currentTeam()) { if (auth()->user()?->currentTeam()) {
$team = Team::find(auth()->user()->currentTeam()->id); $team = Team::find(auth()->user()->currentTeam()->id);
} else { } else {
$team = User::find(auth()->user()->id)->teams->first(); $team = User::find(auth()->user()->id)->teams->first();
} }
} }
Cache::forget('team:' . auth()->user()->id); Cache::forget('team:' . auth()->user()->id);
Cache::remember('team:' . auth()->user()->id, 3600, function() use ($team) { Cache::remember('team:' . auth()->user()->id, 3600, function () use ($team) {
return $team; return $team;
}); });
session(['currentTeam' => $team]); session(['currentTeam' => $team]);
} }
function general_error_handler(Throwable | null $err = null, $that = null, $isJson = false, $customErrorMessage = null): mixed function handleError(?Throwable $error = null, ?Livewire\Component $livewire = null, ?string $customErrorMessage = null)
{
ray('handleError');
if ($error instanceof Throwable) {
$message = $error->getMessage();
} else {
$message = null;
}
if ($customErrorMessage) {
$message = $customErrorMessage . ' ' . $message;
}
if ($error instanceof TooManyRequestsException) {
if (isset($livewire)) {
return $livewire->emit('error', "Too many requests. Please try again in {$error->secondsUntilAvailable} seconds.");
}
return "Too many requests. Please try again in {$error->secondsUntilAvailable} seconds.";
}
if (isset($livewire)) {
return $livewire->emit('error', $message);
}
throw new RuntimeException($message);
}
function general_error_handler(Throwable $err, Livewire\Component $that = null, $isJson = false, $customErrorMessage = null): mixed
{ {
try { try {
ray($err); ray($err);
@@ -95,7 +117,7 @@ function general_error_handler(Throwable | null $err = null, $that = null, $isJs
} }
throw new Exception($customErrorMessage ?? $err->getMessage()); throw new Exception($customErrorMessage ?? $err->getMessage());
} }
} catch (Throwable $e) { } catch (\Throwable $e) {
if ($that) { if ($that) {
return $that->emit('error', $customErrorMessage ?? $e->getMessage()); return $that->emit('error', $customErrorMessage ?? $e->getMessage());
} elseif ($isJson) { } elseif ($isJson) {
@@ -122,7 +144,7 @@ function get_latest_version_of_coolify(): string
$response = Http::get('https://cdn.coollabs.io/coolify/versions.json'); $response = Http::get('https://cdn.coollabs.io/coolify/versions.json');
$versions = $response->json(); $versions = $response->json();
return data_get($versions, 'coolify.v4.version'); return data_get($versions, 'coolify.v4.version');
} catch (Throwable $e) { } catch (\Throwable $e) {
//throw $e; //throw $e;
ray($e->getMessage()); ray($e->getMessage());
return '0.0.0'; return '0.0.0';
@@ -321,7 +343,8 @@ function setNotificationChannels($notifiable, $event)
} }
return $channels; return $channels;
} }
function parseEnvFormatToArray($env_file_contents) { function parseEnvFormatToArray($env_file_contents)
{
$env_array = array(); $env_array = array();
$lines = explode("\n", $env_file_contents); $lines = explode("\n", $env_file_contents);
foreach ($lines as $line) { foreach ($lines as $line) {
@@ -334,8 +357,7 @@ function parseEnvFormatToArray($env_file_contents) {
$value = substr($line, $equals_pos + 1); $value = substr($line, $equals_pos + 1);
if (substr($value, 0, 1) === '"' && substr($value, -1) === '"') { if (substr($value, 0, 1) === '"' && substr($value, -1) === '"') {
$value = substr($value, 1, -1); $value = substr($value, 1, -1);
} } elseif (substr($value, 0, 1) === "'" && substr($value, -1) === "'") {
elseif (substr($value, 0, 1) === "'" && substr($value, -1) === "'") {
$value = substr($value, 1, -1); $value = substr($value, 1, -1);
} }
$env_array[$key] = $value; $env_array[$key] = $value;

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,8 +7,7 @@ return [
// 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.37', 'release' => '4.0.0-beta.41',
'server_name' => env('APP_ID', 'coolify'),
// 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

@@ -30,14 +30,14 @@ return [
* *
* Minimum: 3000 (in milliseconds) * Minimum: 3000 (in milliseconds)
*/ */
'duration' => 5000, 'duration' => 1500,
/** /**
* The horizontal position of each toast. * The horizontal position of each toast.
* *
* Supported: "center", "left" or "right" * Supported: "center", "left" or "right"
*/ */
'position' => 'right', 'position' => 'center',
/** /**
* Whether messages passed as translation keys should be translated automatically. * Whether messages passed as translation keys should be translated automatically.

View File

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

View File

@@ -11,6 +11,9 @@ use App\Models\InstanceSettings;
use App\Models\PrivateKey; use App\Models\PrivateKey;
use App\Models\Server; use App\Models\Server;
use App\Models\StandaloneDocker; use App\Models\StandaloneDocker;
use App\Models\Team;
use App\Models\User;
use DB;
use Illuminate\Database\Seeder; use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\Process; use Illuminate\Support\Facades\Process;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
@@ -19,6 +22,18 @@ class ProductionSeeder extends Seeder
{ {
public function run(): void public function run(): void
{ {
// Fix for 4.0.0-beta.37
if (User::find(0) !== null && Team::find(0) !== null) {
if (DB::table('team_user')->where('user_id', 0)->first() === null) {
DB::table('team_user')->insert([
'user_id' => 0,
'team_id' => 0,
'role' => 'owner',
'created_at' => now(),
'updated_at' => now(),
]);
}
}
if (InstanceSettings::find(0) == null) { if (InstanceSettings::find(0) == null) {
InstanceSettings::create([ InstanceSettings::create([
'id' => 0 'id' => 0

View File

@@ -24,17 +24,17 @@
<form action="/login" method="POST" class="flex flex-col gap-2"> <form action="/login" method="POST" class="flex flex-col gap-2">
@csrf @csrf
@env('local') @env('local')
<x-forms.input value="test@example.com" type="email" name="email" <x-forms.input value="test@example.com" type="email" name="email" required
label="{{ __('input.email') }}" autofocus /> label="{{ __('input.email') }}" autofocus />
<x-forms.input value="password" type="password" name="password" <x-forms.input value="password" type="password" name="password" required
label="{{ __('input.password') }}" /> label="{{ __('input.password') }}" />
<a href="/forgot-password" class="text-xs"> <a href="/forgot-password" class="text-xs">
{{ __('auth.forgot_password') }}? {{ __('auth.forgot_password') }}?
</a> </a>
@else @else
<x-forms.input type="email" name="email" label="{{ __('input.email') }}" autofocus /> <x-forms.input type="email" name="email" required label="{{ __('input.email') }}" autofocus />
<x-forms.input type="password" name="password" label="{{ __('input.password') }}" /> <x-forms.input type="password" name="password" required label="{{ __('input.password') }}" />
<a href="/forgot-password" class="text-xs"> <a href="/forgot-password" class="text-xs">
{{ __('auth.forgot_password') }}? {{ __('auth.forgot_password') }}?
</a> </a>

View File

@@ -11,7 +11,7 @@
<div class="flex-1"></div> <div class="flex-1"></div>
<x-applications.advanced :application="$application" /> <x-applications.advanced :application="$application" />
@if ($application->status === 'running') @if ($application->status !== 'exited')
<button wire:click='deploy' class="flex items-center gap-2 cursor-pointer hover:text-white text-neutral-400"> <button wire:click='deploy' class="flex items-center gap-2 cursor-pointer hover:text-white text-neutral-400">
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-warning" viewBox="0 0 24 24" stroke-width="2" <svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-warning" viewBox="0 0 24 24" stroke-width="2"
stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"> stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">

View File

@@ -11,7 +11,7 @@
<div class="flex-1"></div> <div class="flex-1"></div>
{{-- <x-applications.advanced :application="$application" /> --}} {{-- <x-applications.advanced :application="$application" /> --}}
@if ($database->status === 'running') @if ($database->status !== 'exited')
<button wire:click='stop' class="flex items-center gap-2 cursor-pointer hover:text-white text-neutral-400"> <button wire:click='stop' class="flex items-center gap-2 cursor-pointer hover:text-white text-neutral-400">
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-error" viewBox="0 0 24 24" stroke-width="2" <svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-error" viewBox="0 0 24 24" stroke-width="2"
stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"> stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">

View File

@@ -30,7 +30,7 @@
</label> </label>
@endif @endif
<textarea placeholder="{{ $placeholder }}" {{ $attributes->merge(['class' => $defaultClass]) }} <textarea placeholder="{{ $placeholder }}" {{ $attributes->merge(['class' => $defaultClass]) }}
@if ($realtimeValidation) wire:model.debounce.500ms="{{ $id }}" @if ($realtimeValidation) wire:model.debounce.200ms="{{ $id }}"
@else @else
wire:model.defer={{ $value ?? $id }} wire:model.defer={{ $value ?? $id }}
wire:dirty.class="input-warning"@endif wire:dirty.class="input-warning"@endif

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,8 +1,8 @@
@props([ @props([
'text' => 'Stopped', 'text' => 'Restarting',
]) ])
<x-loading wire:loading.delay /> <x-loading wire:loading.delay />
<div class="flex items-center gap-2" wire:loading.remove.delay.longer> <div class="flex items-center gap-2" wire:loading.remove.delay.longer>
<div class="badge badge-error badge-xs"></div> <div class="badge badge-warning badge-xs"></div>
<div class="text-xs font-medium tracking-wide text-error">{{ $text }}</div> <div class="text-xs font-medium tracking-wide text-warning">{{ $text }}</div>
</div> </div>

View File

@@ -1,6 +1,6 @@
<x-emails.layout> <x-emails.layout>
Container ({{ $containerName }}) has been restarted automatically on {{$serverName}}, because it was stopped unexpected. Container ({{ $containerName }}) has been restarted automatically on {{$serverName}}, because it was stopped unexpectedly.
@if ($containerName === 'coolify-proxy') @if ($containerName === 'coolify-proxy')
Coolify Proxy should run on your server as you have FQDNs set up in one of your resources. Coolify Proxy should run on your server as you have FQDNs set up in one of your resources.

View File

@@ -1,6 +1,6 @@
<x-emails.layout> <x-emails.layout>
Container {{ $containerName }} has been stopped unexpected on {{$serverName}}. Container {{ $containerName }} has been stopped unexpectedly on {{$serverName}}.
@if ($url) @if ($url)
Please check what is going on [here]({{ $url }}). Please check what is going on [here]({{ $url }}).

View File

@@ -58,6 +58,42 @@
} }
} }
function revive() {
if (checkHealthInterval) return true;
console.log('Checking server\'s health...')
checkHealthInterval = setInterval(() => {
fetch('/api/health')
.then(response => {
if (response.ok) {
Toaster.success('Coolify is back online. Reloading...')
if (checkHealthInterval) clearInterval(checkHealthInterval);
setTimeout(() => {
window.location.reload();
}, 5000)
} else {
console.log('Waiting for server to come back from dead...');
}
})
}, 2000);
}
function upgrade() {
if (checkIfIamDeadInterval) return true;
console.log('Update initiated.')
checkIfIamDeadInterval = setInterval(() => {
fetch('/api/health')
.then(response => {
if (response.ok) {
console.log('It\'s alive. Waiting for server to be dead...');
} else {
Toaster.success('Update done, restarting Coolify!')
console.log('It\'s dead. Reviving... Standby... Bzz... Bzz...')
if (checkIfIamDeadInterval) clearInterval(checkIfIamDeadInterval);
revive();
}
})
}, 2000);
}
function copyToClipboard(text) { function copyToClipboard(text) {
navigator.clipboard.writeText(text); navigator.clipboard.writeText(text);
Livewire.emit('success', 'Copied to clipboard.'); Livewire.emit('success', 'Copied to clipboard.');

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
@@ -182,7 +185,7 @@
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>
<x-forms.button type="submit">Save</x-forms.button> <x-forms.button type="submit">Check Connection</x-forms.button>
</form> </form>
</x-slot:actions> </x-slot:actions>
<x-slot:explanation> <x-slot:explanation>
@@ -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

@@ -1,4 +1,5 @@
<div> <div>
<x-forms.button class="mb-4" wire:click="generateNewKey">Generate new SSH key for me</x-forms.button>
<form class="flex flex-col gap-2" wire:submit.prevent='createPrivateKey'> <form class="flex flex-col gap-2" wire:submit.prevent='createPrivateKey'>
<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 />
@@ -6,11 +7,10 @@
</div> </div>
<x-forms.textarea realtimeValidation id="value" rows="10" <x-forms.textarea realtimeValidation id="value" rows="10"
placeholder="-----BEGIN OPENSSH PRIVATE KEY-----" label="Private Key" required /> placeholder="-----BEGIN OPENSSH PRIVATE KEY-----" label="Private Key" required />
<x-forms.button wire:click="generateNewKey">Generate new SSH key for me</x-forms.button> <x-forms.input id="publicKey" readonly label="Public Key" />
<x-forms.textarea id="publicKey" rows="6" readonly label="Public Key" /> <span class="pt-2 pb-4 font-bold text-warning">ACTION REQUIRED: Copy the 'Public Key' to your server's
<span class="font-bold text-warning">ACTION REQUIRED: Copy the 'Public Key' to your server's
~/.ssh/authorized_keys ~/.ssh/authorized_keys
file.</span> file</span>
<x-forms.button type="submit"> <x-forms.button type="submit">
Save Private Key Save Private Key
</x-forms.button> </x-forms.button>

View File

@@ -3,6 +3,6 @@
<div class="">The destination server / network where your application will be deployed to.</div> <div class="">The destination server / network where your application will be deployed to.</div>
<div class="py-4 "> <div class="py-4 ">
<p>Server: {{ data_get($destination, 'server.name') }}</p> <p>Server: {{ data_get($destination, 'server.name') }}</p>
<p>Destination Network: {{ $destination->network }}</p> <p>Destination Network: {{ data_get($destination, 'server.network') }}</p>
</div> </div>
</div> </div>

View File

@@ -14,6 +14,6 @@
</x-forms.button> </x-forms.button>
</form> </form>
<div class="container w-full pt-10 mx-auto"> <div class="container w-full pt-10 mx-auto">
<livewire:activity-monitor header="Logs" /> <livewire:activity-monitor header="Command output" />
</div> </div>
</div> </div>

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

@@ -55,19 +55,19 @@
@else @else
<div> <div>
<h2>Proxy</h2> <h2>Proxy</h2>
<div class="subtitle ">Select a proxy you would like to use on this server.</div> <div class="subtitle">Select a proxy you would like to use on this server.</div>
<div class="flex gap-2"> <div class="grid gap-4">
<x-forms.button class="w-32 box" wire:click="select_proxy('NONE')"> <x-forms.button class="box" wire:click="select_proxy('NONE')">
Custom (None) Custom (None)
</x-forms.button> </x-forms.button>
<x-forms.button class="w-32 box" wire:click="select_proxy('TRAEFIK_V2')"> <x-forms.button class="box" wire:click="select_proxy('TRAEFIK_V2')">
Traefik Traefik
v2 v2
</x-forms.button> </x-forms.button>
<x-forms.button disabled class="w-32 box"> <x-forms.button disabled class="box">
Nginx Nginx
</x-forms.button> </x-forms.button>
<x-forms.button disabled class="w-32 box"> <x-forms.button disabled class="box">
Caddy Caddy
</x-forms.button> </x-forms.button>
</div> </div>

View File

@@ -8,7 +8,7 @@
</x-slot:modalBody> </x-slot:modalBody>
</x-modal> </x-modal>
@if (is_null(data_get($server, 'proxy.type')) || data_get($server, 'proxy.type') !== 'NONE') @if (is_null(data_get($server, 'proxy.type')) || data_get($server, 'proxy.type') !== 'NONE')
@if (data_get($server, 'proxy.status') === 'running') @if (data_get($server, 'proxy.status') !== 'exited')
<div class="flex gap-4"> <div class="flex gap-4">
<button> <button>
<a target="_blank" href="http://{{$server->ip}}:8080"> <a target="_blank" href="http://{{$server->ip}}:8080">

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

@@ -31,60 +31,51 @@
</div> </div>
</form> </form>
<div class="flex flex-col gap-4"> <div class="flex flex-col gap-4">
<details class="border rounded collapse border-coolgray-500 collapse-arrow "> <div class="p-4 border border-coolgray-500">
<summary class="text-xl collapse-title"> <h3>SMTP Server</h3>
<div>SMTP Server</div> <div class="w-32">
<div class="w-32"> <x-forms.checkbox instantSave id="settings.smtp_enabled" label="Enabled" />
<x-forms.checkbox instantSave id="settings.smtp_enabled" label="Enabled" />
</div>
</summary>
<div class="collapse-content">
<form wire:submit.prevent='submit' class="flex flex-col">
<div class="flex flex-col gap-4">
<div class="flex flex-col w-full gap-2 xl:flex-row">
<x-forms.input required id="settings.smtp_host" placeholder="smtp.mailgun.org"
label="Host" />
<x-forms.input required id="settings.smtp_port" placeholder="587" label="Port" />
<x-forms.input id="settings.smtp_encryption" helper="If SMTP uses SSL, set it to 'tls'."
placeholder="tls" label="Encryption" />
</div>
<div class="flex flex-col w-full gap-2 xl:flex-row">
<x-forms.input id="settings.smtp_username" label="SMTP Username" />
<x-forms.input id="settings.smtp_password" type="password" label="SMTP Password" />
<x-forms.input id="settings.smtp_timeout" helper="Timeout value for sending emails."
label="Timeout" />
</div>
</div>
<div class="flex justify-end gap-4 pt-6">
<x-forms.button type="submit">
Save
</x-forms.button>
</div>
</form>
</div> </div>
</details> <form wire:submit.prevent='submit' class="flex flex-col">
<details class="border rounded collapse border-coolgray-500 collapse-arrow"> <div class="flex flex-col gap-4">
<summary class="text-xl collapse-title"> <div class="flex flex-col w-full gap-2 xl:flex-row">
<div>Resend</div> <x-forms.input required id="settings.smtp_host" placeholder="smtp.mailgun.org" label="Host" />
<div class="w-32"> <x-forms.input required id="settings.smtp_port" placeholder="587" label="Port" />
<x-forms.checkbox instantSave='instantSaveResend' id="settings.resend_enabled" label="Enabled" /> <x-forms.input id="settings.smtp_encryption" helper="If SMTP uses SSL, set it to 'tls'."
placeholder="tls" label="Encryption" />
</div>
<div class="flex flex-col w-full gap-2 xl:flex-row">
<x-forms.input id="settings.smtp_username" label="SMTP Username" />
<x-forms.input id="settings.smtp_password" type="password" label="SMTP Password" />
<x-forms.input id="settings.smtp_timeout" helper="Timeout value for sending emails."
label="Timeout" />
</div>
</div> </div>
</summary> <div class="flex justify-end gap-4 pt-6">
<div class="collapse-content"> <x-forms.button type="submit">
<form wire:submit.prevent='submitResend' class="flex flex-col"> Save
<div class="flex flex-col gap-4"> </x-forms.button>
<div class="flex flex-col w-full gap-2 xl:flex-row"> </div>
<x-forms.input type="password" id="settings.resend_api_key" placeholder="API key" </form>
label="Host" /> </div>
</div> <div class="p-4 border border-coolgray-500">
</div> <h3>Resend</h3>
<div class="flex justify-end gap-4 pt-6"> <div class="w-32">
<x-forms.button type="submit"> <x-forms.checkbox instantSave='instantSaveResend' id="settings.resend_enabled" label="Enabled" />
Save
</x-forms.button>
</div>
</form>
</div> </div>
</details> <form wire:submit.prevent='submitResend' class="flex flex-col">
<div class="flex flex-col gap-4">
<div class="flex flex-col w-full gap-2 xl:flex-row">
<x-forms.input type="password" id="settings.resend_api_key" placeholder="API key" required
label="Host" />
</div>
</div>
<div class="flex justify-end gap-4 pt-6">
<x-forms.button type="submit">
Save
</x-forms.button>
</div>
</form>
</div>
</div> </div>
</div> </div>

View File

@@ -43,7 +43,7 @@ Route::get('/source/github/redirect', function () {
$github_app->save(); $github_app->save();
return redirect()->route('source.github.show', ['github_app_uuid' => $github_app->uuid]); return redirect()->route('source.github.show', ['github_app_uuid' => $github_app->uuid]);
} catch (Exception $e) { } catch (Exception $e) {
return general_error_handler(err: $e); return handleError($e);
} }
}); });
@@ -59,7 +59,7 @@ Route::get('/source/github/install', function () {
} }
return redirect()->route('source.github.show', ['github_app_uuid' => $github_app->uuid]); return redirect()->route('source.github.show', ['github_app_uuid' => $github_app->uuid]);
} catch (Exception $e) { } catch (Exception $e) {
return general_error_handler(err: $e); return handleError($e);
} }
}); });
Route::post('/source/github/events', function () { Route::post('/source/github/events', function () {
@@ -179,7 +179,7 @@ Route::post('/source/github/events', function () {
} }
} catch (Exception $e) { } catch (Exception $e) {
ray($e->getMessage()); ray($e->getMessage());
return general_error_handler(err: $e); return handleError($e);
} }
}); });
Route::get('/waitlist/confirm', function () { Route::get('/waitlist/confirm', function () {

View File

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