Merge pull request #1228 from coollabsio/next

v4.0.0-beta.38
This commit is contained in:
Andras Bacsai 2023-09-15 18:05:59 +02:00 committed by GitHub
commit b77074fe4e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
82 changed files with 420 additions and 312 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

@ -27,6 +27,10 @@ class StartProxy
$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",
@ -35,8 +39,11 @@ class StartProxy
'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',
"lsof -nt -i:80 | xargs -r kill -9", "command -v lsof >/dev/null && lsof -nt -i:80 | xargs -r kill -9",
"lsof -nt -i:443 | xargs -r kill -9", "command -v lsof >/dev/null && lsof -nt -i:443 | xargs -r kill -9",
"command -v fuser >/dev/null && fuser -k 80/tcp",
"command -v fuser >/dev/null && fuser -k 443/tcp",
"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...'",
"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

@ -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

@ -45,8 +45,11 @@ 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(

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,6 +9,7 @@ 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
{ {
@ -53,7 +54,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');
} }
@ -115,7 +117,8 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
$this->getProxyType(); $this->getProxyType();
$this->getProjects(); $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';
@ -153,49 +156,68 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
{ {
$this->validate([ $this->validate([
'remoteServerName' => 'required', 'remoteServerName' => 'required',
'remoteServerHost' => 'required', 'remoteServerHost' => 'required|ip',
'remoteServerPort' => 'required', 'remoteServerPort' => 'required|integer',
'remoteServerUser' => 'required', 'remoteServerUser' => 'required',
]); ]);
$this->privateKey = formatPrivateKey($this->privateKey); $this->privateKey = formatPrivateKey($this->privateKey);
$this->createdPrivateKey = PrivateKey::create([ $this->createdPrivateKey = new PrivateKey();
'name' => $this->privateKeyName, $this->createdPrivateKey->private_key = $this->privateKey;
'description' => $this->privateKeyDescription, $this->createdPrivateKey->name = $this->privateKeyName;
'private_key' => $this->privateKey, $this->createdPrivateKey->description = $this->privateKeyDescription;
'team_id' => currentTeam()->id $this->createdPrivateKey->team_id = currentTeam()->id;
]); $foundServer = Server::whereIp($this->remoteServerHost)->first();
$this->createdServer = Server::create([ if ($foundServer) {
'name' => $this->remoteServerName, return $this->emit('error', 'IP address is already in use by another team.');
'ip' => $this->remoteServerHost, }
'port' => $this->remoteServerPort, $this->createdServer = new Server();
'user' => $this->remoteServerUser, $this->createdServer->uuid = (string)new Cuid2(7);
'description' => $this->remoteServerDescription, $this->createdServer->name = $this->remoteServerName;
'private_key_id' => $this->createdPrivateKey->id, $this->createdServer->ip = $this->remoteServerHost;
'team_id' => currentTeam()->id $this->createdServer->port = $this->remoteServerPort;
]); $this->createdServer->user = $this->remoteServerUser;
$this->createdServer->description = $this->remoteServerDescription;
$this->createdServer->privateKey = $this->createdPrivateKey;
$this->createdServer->team_id = currentTeam()->id;
ray($this->createdServer);
$this->validateServer(); $this->validateServer();
} }
public function validateServer() { public function validateServer()
{
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.'); instant_remote_process(['uptime'], $this->createdServer, true);
} else { $dockerVersion = instant_remote_process(["docker version|head -2|grep -i version| awk '{print $2}'"], $this->createdServer, true);
$this->createdServer->settings->update([ $dockerVersion = checkMinimumDockerEngineVersion($dockerVersion);
'is_reachable' => true, if (is_null($dockerVersion)) {
]); throw new \Exception('No Docker Engine or older than 23 version installed.');
$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;
} }
$customErrorMessage = "Cannot create Server or Private Key. Please try again.";
$createdPrivateKey = PrivateKey::create([
'name' => $this->privateKeyName,
'description' => $this->privateKeyDescription,
'private_key' => $this->privateKey,
'team_id' => currentTeam()->id
]);
$server = Server::create([
'name' => $this->remoteServerName,
'ip' => $this->remoteServerHost,
'port' => $this->remoteServerPort,
'user' => $this->remoteServerUser,
'description' => $this->remoteServerDescription,
'private_key_id' => $createdPrivateKey->id,
'team_id' => currentTeam()->id,
]);
$server->settings->is_reachable = true;
$server->settings->is_usable = true;
$server->settings->save();
$this->getProxyType(); $this->getProxyType();
} 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()
@ -215,14 +237,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 +266,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

@ -51,12 +51,12 @@ class Form extends Component
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'); $this->emit('error', 'Server is not reachable.');
return; return;
} }
if ($dockerVersion) { if ($dockerVersion) {
@ -64,10 +64,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 +82,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

@ -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

@ -56,7 +56,7 @@ class Proxy extends Component
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);
} }
} }
@ -65,7 +65,7 @@ class Proxy extends Component
try { try {
$this->proxy_settings = resolve(CheckConfigurationSync::class)($this->server, true); $this->proxy_settings = resolve(CheckConfigurationSync::class)($this->server, true);
} catch (\Throwable $e) { } catch (\Throwable $e) {
return general_error_handler(err: $e); return handleError($e);
} }
} }
@ -75,7 +75,7 @@ class Proxy extends Component
ray('loadProxyConfiguration'); ray('loadProxyConfiguration');
$this->proxy_settings = resolve(CheckConfigurationSync::class)($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

@ -24,7 +24,7 @@ class Status extends Component
$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,14 +28,14 @@ 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->emit('success', 'Server is reachable with this private key.'); $this->emit('success', 'Server is reachable with this private key.');
} else { } else {
@ -48,7 +48,7 @@ class ShowPrivateKey extends Component
$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

@ -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

@ -36,12 +36,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 +64,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,27 +92,21 @@ 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;
} }
@ -161,11 +152,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 +165,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 +173,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

@ -7,7 +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.38',
'server_name' => env('APP_ID', 'coolify'), '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.38';

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,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

@ -182,7 +182,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>

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

@ -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

@ -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

@ -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.38"
} }
} }
} }