mirror of
https://github.com/ershisan99/coolify.git
synced 2025-12-25 20:49:28 +00:00
Compare commits
207 Commits
v4.0.0-bet
...
v4.0.0-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cfdab13d77 | ||
|
|
4fc8988ff4 | ||
|
|
ab5619292e | ||
|
|
b02e5d3f27 | ||
|
|
5f0c9c3a31 | ||
|
|
ea6ec07a45 | ||
|
|
36be325d0d | ||
|
|
6138ddcac6 | ||
|
|
0509da6730 | ||
|
|
5b877b84c2 | ||
|
|
0c35726a8d | ||
|
|
e74899611b | ||
|
|
e9149e534d | ||
|
|
7cded7a36d | ||
|
|
ba74d55b4c | ||
|
|
a1d13fc14e | ||
|
|
cdb2a3a8e5 | ||
|
|
074c56edaa | ||
|
|
66bc03b4cd | ||
|
|
2dcb7fec05 | ||
|
|
249bccb49e | ||
|
|
a55eaa10ac | ||
|
|
ca2c75ce19 | ||
|
|
250d7cbc53 | ||
|
|
92a53a151e | ||
|
|
db3148c080 | ||
|
|
c46eeac4b5 | ||
|
|
19111ba059 | ||
|
|
9bc61a0a17 | ||
|
|
78b9166bdb | ||
|
|
1752448050 | ||
|
|
3fc78bc760 | ||
|
|
0c30b6222d | ||
|
|
3f74609c7f | ||
|
|
31b0ccba99 | ||
|
|
3fc544e0b9 | ||
|
|
67078fdc71 | ||
|
|
c91f426af3 | ||
|
|
9c2fea4b2e | ||
|
|
53d1fa0331 | ||
|
|
4ae7e46e81 | ||
|
|
ebfc0bd1e1 | ||
|
|
e1a1490911 | ||
|
|
6b75ff7de4 | ||
|
|
301469de6b | ||
|
|
b4d69a22df | ||
|
|
a86e971020 | ||
|
|
145af41c82 | ||
|
|
69c0b7240a | ||
|
|
6543132bcb | ||
|
|
9134437218 | ||
|
|
b7acf99cde | ||
|
|
21d52d7846 | ||
|
|
ab5929cc69 | ||
|
|
1452cdf5ad | ||
|
|
3eb1a1f48c | ||
|
|
5f7a97c31f | ||
|
|
9cba0a6df3 | ||
|
|
af57b2aa73 | ||
|
|
b9b9582601 | ||
|
|
c8ba98b93d | ||
|
|
a50e1e2f0c | ||
|
|
1093294f06 | ||
|
|
c023be2348 | ||
|
|
deece51e83 | ||
|
|
e2ab569244 | ||
|
|
93b202bde4 | ||
|
|
6bb6de188c | ||
|
|
61f58fa30f | ||
|
|
026f3fd72d | ||
|
|
9a706d55b4 | ||
|
|
9646969107 | ||
|
|
df433efe62 | ||
|
|
efa7cab3e2 | ||
|
|
a386a1bde5 | ||
|
|
cc1bf023be | ||
|
|
edb06fa233 | ||
|
|
abdb8074b1 | ||
|
|
157da798dd | ||
|
|
9c5501326e | ||
|
|
3ea462efc9 | ||
|
|
f77df5b732 | ||
|
|
b77074fe4e | ||
|
|
a7b7d3fa32 | ||
|
|
f4f3034389 | ||
|
|
4b2ffb456f | ||
|
|
e17ff99c5b | ||
|
|
1cf036bbc6 | ||
|
|
da4c2ee60f | ||
|
|
fcf7c5ddd5 | ||
|
|
27926322e1 | ||
|
|
c735ff545e | ||
|
|
b4f048b028 | ||
|
|
54a57d217f | ||
|
|
cf28490acc | ||
|
|
019670d5d1 | ||
|
|
b07cc500e7 | ||
|
|
82c235d5af | ||
|
|
307e4a6990 | ||
|
|
ddb7af63a6 | ||
|
|
7a429ee5bb | ||
|
|
bb591604ac | ||
|
|
81f7a65dd5 | ||
|
|
4b313bb1c6 | ||
|
|
aba0b2f13c | ||
|
|
9a284e47da | ||
|
|
9f2fbc661a | ||
|
|
949407368e | ||
|
|
93c65f6a79 | ||
|
|
d9fe16a3ee | ||
|
|
adaca4d4e3 | ||
|
|
a6d5f3038c | ||
|
|
4d49132821 | ||
|
|
c287276d0e | ||
|
|
e89868c692 | ||
|
|
f93317cd2e | ||
|
|
8412802f4d | ||
|
|
53c20e1e99 | ||
|
|
3c8c8e20b1 | ||
|
|
49f8abcd79 | ||
|
|
4a4d73b87b | ||
|
|
046eab3776 | ||
|
|
17c0e91a0d | ||
|
|
fe4a0ae166 | ||
|
|
52c84f8d22 | ||
|
|
b22fecb615 | ||
|
|
23b7fc3c54 | ||
|
|
1efb1235b4 | ||
|
|
0924070a13 | ||
|
|
12fc5a8f91 | ||
|
|
9eba058cf7 | ||
|
|
fa4f5fea8c | ||
|
|
898563fe7c | ||
|
|
c418a17161 | ||
|
|
cd0da04ea2 | ||
|
|
01e942c6a0 | ||
|
|
bb9abafa82 | ||
|
|
d0cd926517 | ||
|
|
9baf0161c7 | ||
|
|
8ba18b2ce1 | ||
|
|
ab021ee535 | ||
|
|
c76a1b1ba5 | ||
|
|
6266a5e500 | ||
|
|
5d27e89bfa | ||
|
|
6da4e78374 | ||
|
|
be30651172 | ||
|
|
95764c2b76 | ||
|
|
92a75685b5 | ||
|
|
a1592373aa | ||
|
|
5747a87f66 | ||
|
|
9cda671aef | ||
|
|
2c9983046c | ||
|
|
11d33f328e | ||
|
|
f79c741d95 | ||
|
|
8a39a4469a | ||
|
|
42daae10c6 | ||
|
|
64a65e2018 | ||
|
|
16c71f3647 | ||
|
|
363f525ad1 | ||
|
|
f4fb519d55 | ||
|
|
b7786504b8 | ||
|
|
f0adf10e6a | ||
|
|
cc8c6c5d16 | ||
|
|
4782446f42 | ||
|
|
230155312f | ||
|
|
3ab4365fca | ||
|
|
7349068b95 | ||
|
|
da6cc151d1 | ||
|
|
50527cf0a3 | ||
|
|
26f490bb00 | ||
|
|
dc4f412227 | ||
|
|
ec4234e243 | ||
|
|
f47fcb01ce | ||
|
|
02f6673345 | ||
|
|
e6cd8702b5 | ||
|
|
fda4ea8cca | ||
|
|
655d004ce7 | ||
|
|
56981d134c | ||
|
|
b9d49d2951 | ||
|
|
0c1e7c499e | ||
|
|
32fead5753 | ||
|
|
e5e9faba35 | ||
|
|
2852630d6c | ||
|
|
a4cc406114 | ||
|
|
53b15a5762 | ||
|
|
929a4e6474 | ||
|
|
45b597bbab | ||
|
|
0d1a2aa5d1 | ||
|
|
b82353d5e2 | ||
|
|
b17c09f7a7 | ||
|
|
f6c3fe7888 | ||
|
|
2e855e030f | ||
|
|
49f86621f4 | ||
|
|
03d9f93397 | ||
|
|
c472042a94 | ||
|
|
9f4356f67d | ||
|
|
a50317cc76 | ||
|
|
8afa98a1ca | ||
|
|
f6737f21dd | ||
|
|
e4a51cc116 | ||
|
|
acd78ae196 | ||
|
|
953bcfb5bb | ||
|
|
dacfab8b29 | ||
|
|
48b3e99939 | ||
|
|
41ad67c7c9 | ||
|
|
b49725cb1c | ||
|
|
75e674a966 |
26
.github/ISSUE_TEMPLATE/BUG_REPORT.yml
vendored
Normal file
26
.github/ISSUE_TEMPLATE/BUG_REPORT.yml
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
name: Bug report
|
||||
description: Create a new bug report
|
||||
title: '[Bug]: '
|
||||
body:
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Description
|
||||
description: A clear and concise description of the problem
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Minimal Reproduction (if possible, example repository)
|
||||
description: Please provide a step by step guide to reproduce the issue
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Exception or Error
|
||||
description: Please provide error logs if possible.
|
||||
- type: input
|
||||
attributes:
|
||||
label: Version
|
||||
description: Coolify's version (see bottom left corner).
|
||||
validations:
|
||||
required: true
|
||||
8
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
8
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: 🤔 Community Support (Chat)
|
||||
url: https://coollabs.io/discord
|
||||
about: Reach out to us on Discord.
|
||||
- name: 🙋♂️ Feature Requests
|
||||
url: https://github.com/coollabsio/coolify/discussions/categories/feature-requests-ideas
|
||||
about: All feature requests will be discussed here.
|
||||
2
.github/workflows/development-build.yml
vendored
2
.github/workflows/development-build.yml
vendored
@@ -13,7 +13,7 @@ env:
|
||||
|
||||
jobs:
|
||||
amd64:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: [self-hosted, x64]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Login to ghcr.io
|
||||
|
||||
2
.github/workflows/production-build.yml
vendored
2
.github/workflows/production-build.yml
vendored
@@ -10,7 +10,7 @@ env:
|
||||
|
||||
jobs:
|
||||
amd64:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: [self-hosted, x64]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Login to ghcr.io
|
||||
|
||||
@@ -5,14 +5,12 @@ namespace App\Actions\CoolifyTask;
|
||||
use App\Enums\ActivityTypes;
|
||||
use App\Enums\ProcessStatus;
|
||||
use App\Jobs\ApplicationDeploymentJob;
|
||||
use App\Models\Server;
|
||||
use Illuminate\Process\ProcessResult;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Process;
|
||||
use Spatie\Activitylog\Models\Activity;
|
||||
|
||||
const TIMEOUT = 3600;
|
||||
const IDLE_TIMEOUT = 3600;
|
||||
|
||||
class RunRemoteProcess
|
||||
{
|
||||
public Activity $activity;
|
||||
@@ -76,8 +74,7 @@ class RunRemoteProcess
|
||||
$this->time_start = hrtime(true);
|
||||
|
||||
$status = ProcessStatus::IN_PROGRESS;
|
||||
|
||||
$processResult = Process::timeout(TIMEOUT)->idleTimeout(IDLE_TIMEOUT)->run($this->getCommand(), $this->handleOutput(...));
|
||||
$processResult = Process::forever()->run($this->getCommand(), $this->handleOutput(...));
|
||||
|
||||
if ($this->activity->properties->get('status') === ProcessStatus::ERROR->value) {
|
||||
$status = ProcessStatus::ERROR;
|
||||
@@ -97,9 +94,8 @@ class RunRemoteProcess
|
||||
'status' => $status->value,
|
||||
]);
|
||||
$this->activity->save();
|
||||
|
||||
if ($processResult->exitCode() != 0 && !$this->ignore_errors) {
|
||||
throw new \RuntimeException($processResult->errorOutput());
|
||||
throw new \RuntimeException($processResult->errorOutput(), $processResult->exitCode());
|
||||
}
|
||||
|
||||
return $processResult;
|
||||
@@ -107,13 +103,11 @@ class RunRemoteProcess
|
||||
|
||||
protected function getCommand(): string
|
||||
{
|
||||
$user = $this->activity->getExtraProperty('user');
|
||||
$server_ip = $this->activity->getExtraProperty('server_ip');
|
||||
$private_key_location = $this->activity->getExtraProperty('private_key_location');
|
||||
$port = $this->activity->getExtraProperty('port');
|
||||
$server_uuid = $this->activity->getExtraProperty('server_uuid');
|
||||
$command = $this->activity->getExtraProperty('command');
|
||||
$server = Server::whereUuid($server_uuid)->firstOrFail();
|
||||
|
||||
return generate_ssh_command($private_key_location, $server_ip, $user, $port, $command);
|
||||
return generateSshCommand($server, $command);
|
||||
}
|
||||
|
||||
protected function handleOutput(string $type, string $output)
|
||||
|
||||
@@ -21,6 +21,7 @@ class StartPostgresql
|
||||
$this->configuration_dir = database_configuration_dir() . '/' . $container_name;
|
||||
|
||||
$this->commands = [
|
||||
"echo '####### Starting {$database->name}.'",
|
||||
"mkdir -p $this->configuration_dir",
|
||||
"mkdir -p $this->configuration_dir/docker-entrypoint-initdb.d/"
|
||||
];
|
||||
@@ -96,6 +97,7 @@ class StartPostgresql
|
||||
$readme = generate_readme_file($this->database->name, now());
|
||||
$this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md";
|
||||
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
|
||||
$this->commands[] = "echo '####### {$database->name} started.'";
|
||||
return remote_process($this->commands, $server);
|
||||
}
|
||||
|
||||
|
||||
@@ -57,13 +57,13 @@ class CheckResaleLicense
|
||||
throw new \Exception('Invalid license key.');
|
||||
}
|
||||
throw new \Exception('Cannot activate license key.');
|
||||
} catch (\Throwable $th) {
|
||||
ray($th);
|
||||
} catch (\Throwable $e) {
|
||||
ray($e);
|
||||
$settings->update([
|
||||
'resale_license' => null,
|
||||
'is_resale_license_active' => false,
|
||||
]);
|
||||
throw $th;
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,24 +2,27 @@
|
||||
|
||||
namespace App\Actions\Proxy;
|
||||
|
||||
use Lorisleiva\Actions\Concerns\AsAction;
|
||||
use App\Models\Server;
|
||||
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_configuration = instant_remote_process([
|
||||
"mkdir -p $proxy_path",
|
||||
"cat $proxy_path/docker-compose.yml",
|
||||
], $server, false);
|
||||
|
||||
if ($reset || is_null($proxy_configuration)) {
|
||||
if ($reset || !$proxy_configuration || is_null($proxy_configuration)) {
|
||||
$proxy_configuration = Str::of(generate_default_proxy_configuration($server))->trim()->value;
|
||||
resolve(SaveConfigurationSync::class)($server, $proxy_configuration);
|
||||
return $proxy_configuration;
|
||||
}
|
||||
|
||||
if (!$proxy_configuration || is_null($proxy_configuration)) {
|
||||
throw new \Exception("Could not generate proxy configuration");
|
||||
}
|
||||
return $proxy_configuration;
|
||||
}
|
||||
}
|
||||
29
app/Actions/Proxy/SaveConfiguration.php
Normal file
29
app/Actions/Proxy/SaveConfiguration.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?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, ?string $proxy_settings = null)
|
||||
{
|
||||
if (is_null($proxy_settings)) {
|
||||
$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);
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\Proxy;
|
||||
|
||||
use App\Models\Server;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class SaveConfigurationSync
|
||||
{
|
||||
public function __invoke(Server $server, string $configuration)
|
||||
{
|
||||
try {
|
||||
$proxy_path = get_proxy_path();
|
||||
$docker_compose_yml_base64 = base64_encode($configuration);
|
||||
|
||||
$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 $th) {
|
||||
ray($th);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -2,49 +2,60 @@
|
||||
|
||||
namespace App\Actions\Proxy;
|
||||
|
||||
use App\Enums\ProxyStatus;
|
||||
use App\Enums\ProxyTypes;
|
||||
use App\Models\Server;
|
||||
use Illuminate\Support\Str;
|
||||
use Lorisleiva\Actions\Concerns\AsAction;
|
||||
use Spatie\Activitylog\Models\Activity;
|
||||
|
||||
class StartProxy
|
||||
{
|
||||
public function __invoke(Server $server): Activity
|
||||
use AsAction;
|
||||
public function handle(Server $server, bool $async = true): Activity|string
|
||||
{
|
||||
$proxy_path = get_proxy_path();
|
||||
$networks = collect($server->standaloneDockers)->map(function ($docker) {
|
||||
return $docker['network'];
|
||||
})->unique();
|
||||
if ($networks->count() === 0) {
|
||||
$networks = collect(['coolify']);
|
||||
$commands = collect([]);
|
||||
$proxyType = $server->proxyType();
|
||||
if ($proxyType === 'none') {
|
||||
return 'OK';
|
||||
}
|
||||
$proxy_path = get_proxy_path();
|
||||
$configuration = CheckConfiguration::run($server);
|
||||
if (!$configuration) {
|
||||
throw new \Exception("Configuration is not synced");
|
||||
}
|
||||
$create_networks_command = $networks->map(function ($network) {
|
||||
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);
|
||||
|
||||
$docker_compose_yml_base64 = base64_encode($configuration);
|
||||
$server->proxy->last_applied_settings = Str::of($docker_compose_yml_base64)->pipe('md5')->value;
|
||||
$server->save();
|
||||
|
||||
$activity = remote_process([
|
||||
"echo 'Creating required Docker networks...'",
|
||||
...$create_networks_command,
|
||||
"cd $proxy_path",
|
||||
"echo 'Creating Docker Compose file...'",
|
||||
"echo 'Pulling docker image...'",
|
||||
'docker compose pull -q',
|
||||
"echo 'Stopping existing proxy...'",
|
||||
'docker compose down -v --remove-orphans',
|
||||
"lsof -nt -i:80 | xargs -r kill -9",
|
||||
"lsof -nt -i:443 | xargs -r kill -9",
|
||||
"echo 'Starting proxy...'",
|
||||
$commands = $commands->merge([
|
||||
"apt-get update > /dev/null 2>&1 || true",
|
||||
"command -v lsof >/dev/null || echo '####### Installing lsof.'",
|
||||
"command -v lsof >/dev/null || apt install -y lsof",
|
||||
"command -v lsof >/dev/null || command -v fuser >/dev/null || apt install -y psmisc",
|
||||
"mkdir -p $proxy_path && cd $proxy_path",
|
||||
"echo '####### Creating Docker Compose file.'",
|
||||
"echo '####### Pulling docker image.'",
|
||||
'docker compose pull',
|
||||
"echo '####### Stopping existing coolify-proxy.'",
|
||||
"docker compose down -v --remove-orphans > /dev/null 2>&1",
|
||||
"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...'",
|
||||
"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 apache2 > /dev/null 2>&1 || true",
|
||||
"systemctl disable apache > /dev/null 2>&1 || true",
|
||||
"echo '####### Starting coolify-proxy.'",
|
||||
'docker compose up -d --remove-orphans',
|
||||
"echo 'Proxy installed successfully...'"
|
||||
], $server);
|
||||
|
||||
return $activity;
|
||||
"echo '####### Proxy installed successfully.'"
|
||||
]);
|
||||
$commands = $commands->merge(connectProxyToNetworks($server));
|
||||
if (!$async) {
|
||||
instant_remote_process($commands, $server);
|
||||
return 'OK';
|
||||
} else {
|
||||
$activity = remote_process($commands, $server);
|
||||
return $activity;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,11 +4,10 @@ namespace App\Actions\Server;
|
||||
|
||||
use App\Models\Server;
|
||||
use App\Models\StandaloneDocker;
|
||||
use App\Models\Team;
|
||||
|
||||
class InstallDocker
|
||||
{
|
||||
public function __invoke(Server $server, Team $team)
|
||||
public function __invoke(Server $server)
|
||||
{
|
||||
$dockerVersion = '24.0';
|
||||
$config = base64_encode('{
|
||||
@@ -18,42 +17,44 @@ class InstallDocker
|
||||
"max-file": "3"
|
||||
}
|
||||
}');
|
||||
if (isDev()) {
|
||||
$activity = remote_process([
|
||||
"echo ####### Installing Prerequisites...",
|
||||
"echo ####### Installing/updating Docker Engine...",
|
||||
"echo ####### Configuring Docker Engine (merging existing configuration with the required)...",
|
||||
"echo ####### Restarting Docker Engine...",
|
||||
], $server);
|
||||
$found = StandaloneDocker::where('server_id', $server->id);
|
||||
if ($found->count() == 0 && $server->id) {
|
||||
StandaloneDocker::create([
|
||||
'name' => 'coolify',
|
||||
'network' => 'coolify',
|
||||
'server_id' => $server->id,
|
||||
]);
|
||||
}
|
||||
|
||||
if (isDev() && $server->id === 0) {
|
||||
$command = [
|
||||
"echo '####### Installing Prerequisites...'",
|
||||
"sleep 1",
|
||||
"echo '####### Installing/updating Docker Engine...'",
|
||||
"echo '####### Configuring Docker Engine (merging existing configuration with the required)...'",
|
||||
"sleep 4",
|
||||
"echo '####### Restarting Docker Engine...'",
|
||||
"ls -l /tmp"
|
||||
];
|
||||
} else {
|
||||
$activity = remote_process([
|
||||
"echo ####### Installing Prerequisites...",
|
||||
$command = [
|
||||
"echo '####### Installing Prerequisites...'",
|
||||
"command -v jq >/dev/null || apt-get update",
|
||||
"command -v jq >/dev/null || apt install -y jq",
|
||||
"echo ####### Installing/updating Docker Engine...",
|
||||
"echo '####### Installing/updating Docker Engine...'",
|
||||
"curl https://releases.rancher.com/install-docker/{$dockerVersion}.sh | sh",
|
||||
"echo ####### Configuring Docker Engine (merging existing configuration with the required)...",
|
||||
"echo '####### Configuring Docker Engine (merging existing configuration with the required)...'",
|
||||
"test -s /etc/docker/daemon.json && cp /etc/docker/daemon.json \"/etc/docker/daemon.json.original-`date +\"%Y%m%d-%H%M%S\"`\" || echo '{$config}' | base64 -d > /etc/docker/daemon.json",
|
||||
"echo '{$config}' | base64 -d > /etc/docker/daemon.json.coolify",
|
||||
"cat <<< $(jq . /etc/docker/daemon.json.coolify) > /etc/docker/daemon.json.coolify",
|
||||
"cat <<< $(jq -s '.[0] * .[1]' /etc/docker/daemon.json /etc/docker/daemon.json.coolify) > /etc/docker/daemon.json",
|
||||
"echo ####### Restarting Docker Engine...",
|
||||
"echo '####### Restarting Docker Engine...'",
|
||||
"systemctl restart docker",
|
||||
"echo ####### Creating default network...",
|
||||
"docker network create --attachable coolify",
|
||||
"echo ####### Done!"
|
||||
], $server);
|
||||
$found = StandaloneDocker::where('server_id', $server->id);
|
||||
if ($found->count() == 0) {
|
||||
StandaloneDocker::create([
|
||||
'name' => 'coolify',
|
||||
'network' => 'coolify',
|
||||
'server_id' => $server->id,
|
||||
]);
|
||||
}
|
||||
"echo '####### Creating default Docker network (coolify)...'",
|
||||
"docker network create --attachable coolify >/dev/null 2>&1 || true",
|
||||
"echo '####### Done!'"
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
return $activity;
|
||||
return remote_process($command, $server);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,11 +43,11 @@ class UpdateCoolify
|
||||
$this->update();
|
||||
}
|
||||
send_internal_notification('InstanceAutoUpdateJob done to version: ' . $this->latestVersion . ' from version: ' . $this->currentVersion);
|
||||
} catch (\Exception $th) {
|
||||
} catch (\Throwable $e) {
|
||||
ray('InstanceAutoUpdateJob failed');
|
||||
ray($th->getMessage());
|
||||
send_internal_notification('InstanceAutoUpdateJob failed: ' . $th->getMessage());
|
||||
throw $th;
|
||||
ray($e->getMessage());
|
||||
send_internal_notification('InstanceAutoUpdateJob failed: ' . $e->getMessage());
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
33
app/Actions/Service/StartService.php
Normal file
33
app/Actions/Service/StartService.php
Normal file
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\Service;
|
||||
|
||||
use Lorisleiva\Actions\Concerns\AsAction;
|
||||
use App\Models\Service;
|
||||
|
||||
class StartService
|
||||
{
|
||||
use AsAction;
|
||||
public function handle(Service $service)
|
||||
{
|
||||
$workdir = service_configuration_dir() . "/{$service->uuid}";
|
||||
$commands[] = "echo '####### Starting service {$service->name} on {$service->server->name}.'";
|
||||
$commands[] = "echo '####### Pulling images.'";
|
||||
$commands[] = "mkdir -p $workdir";
|
||||
$commands[] = "cd $workdir";
|
||||
|
||||
$docker_compose_base64 = base64_encode($service->docker_compose);
|
||||
$commands[] = "echo $docker_compose_base64 | base64 -d > docker-compose.yml";
|
||||
$envs = $service->environment_variables()->get();
|
||||
$commands[] = "rm -f .env || true";
|
||||
foreach ($envs as $env) {
|
||||
$commands[] = "echo '{$env->key}={$env->value}' >> .env";
|
||||
}
|
||||
$commands[] = "docker compose pull --quiet";
|
||||
$commands[] = "echo '####### Starting containers.'";
|
||||
$commands[] = "docker compose up -d >/dev/null 2>&1";
|
||||
$commands[] = "docker network connect $service->uuid coolify-proxy 2>/dev/null || true";
|
||||
$activity = remote_process($commands, $service->server);
|
||||
return $activity;
|
||||
}
|
||||
}
|
||||
25
app/Actions/Service/StopService.php
Normal file
25
app/Actions/Service/StopService.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\Service;
|
||||
|
||||
use Lorisleiva\Actions\Concerns\AsAction;
|
||||
use App\Models\Service;
|
||||
|
||||
class StopService
|
||||
{
|
||||
use AsAction;
|
||||
public function handle(Service $service)
|
||||
{
|
||||
$applications = $service->applications()->get();
|
||||
foreach ($applications as $application) {
|
||||
instant_remote_process(["docker rm -f {$application->name}-{$service->uuid}"], $service->server);
|
||||
$application->update(['status' => 'exited']);
|
||||
}
|
||||
$dbs = $service->databases()->get();
|
||||
foreach ($dbs as $db) {
|
||||
instant_remote_process(["docker rm -f {$db->name}-{$service->uuid}"], $service->server);
|
||||
$db->update(['status' => 'exited']);
|
||||
}
|
||||
instant_remote_process(["docker network disconnect {$service->uuid} coolify-proxy 2>/dev/null"], $service->server, false);
|
||||
}
|
||||
}
|
||||
@@ -2,12 +2,16 @@
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Jobs\SendConfirmationForWaitlistJob;
|
||||
use App\Models\Application;
|
||||
use App\Models\ApplicationPreview;
|
||||
use App\Models\ScheduledDatabaseBackup;
|
||||
use App\Models\Server;
|
||||
use App\Models\StandalonePostgresql;
|
||||
use App\Models\Team;
|
||||
use App\Models\TeamInvitation;
|
||||
use App\Models\User;
|
||||
use App\Models\Waitlist;
|
||||
use App\Notifications\Application\DeploymentFailed;
|
||||
use App\Notifications\Application\DeploymentSuccess;
|
||||
use App\Notifications\Application\StatusChanged;
|
||||
@@ -20,33 +24,36 @@ use Illuminate\Console\Command;
|
||||
use Illuminate\Mail\Message;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use Mail;
|
||||
use Str;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
use function Laravel\Prompts\confirm;
|
||||
use function Laravel\Prompts\select;
|
||||
use function Laravel\Prompts\text;
|
||||
|
||||
class TestEmail extends Command
|
||||
class Emails extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'email:test';
|
||||
protected $signature = 'emails';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Send a test email to the admin';
|
||||
protected $description = 'Send out test / prod emails';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
private ?MailMessage $mail = null;
|
||||
private ?string $email = null;
|
||||
public function handle()
|
||||
{
|
||||
$email = select(
|
||||
$type = select(
|
||||
'Which Email should be sent?',
|
||||
options: [
|
||||
'emails-test' => 'Test',
|
||||
@@ -55,20 +62,25 @@ class TestEmail extends Command
|
||||
'application-status-changed' => 'Application - Status Changed',
|
||||
'backup-success' => 'Database - Backup Success',
|
||||
'backup-failed' => 'Database - Backup Failed',
|
||||
'invitation-link' => 'Invitation Link',
|
||||
// 'invitation-link' => 'Invitation Link',
|
||||
'waitlist-invitation-link' => 'Waitlist Invitation Link',
|
||||
'waitlist-confirmation' => 'Waitlist Confirmation',
|
||||
'realusers-before-trial' => 'REAL - Registered Users Before Trial without Subscription',
|
||||
'realusers-server-lost-connection' => 'REAL - Server Lost Connection',
|
||||
],
|
||||
);
|
||||
$type = set_transanctional_email_settings();
|
||||
if (!$type) {
|
||||
throw new Exception('No email settings found.');
|
||||
$emailsGathered = ['realusers-before-trial','realusers-server-lost-connection'];
|
||||
if (!in_array($type, $emailsGathered)) {
|
||||
$this->email = text('Email Address to send to');
|
||||
}
|
||||
set_transanctional_email_settings();
|
||||
|
||||
$this->mail = new MailMessage();
|
||||
$this->mail->subject("Test Email");
|
||||
switch ($email) {
|
||||
switch ($type) {
|
||||
case 'emails-test':
|
||||
$this->mail = (new Test())->toMail();
|
||||
$this->sendEmail();
|
||||
break;
|
||||
case 'application-deployment-success':
|
||||
$application = Application::all()->first();
|
||||
@@ -129,56 +141,107 @@ class TestEmail extends Command
|
||||
$this->mail = (new BackupSuccess($backup, $db))->toMail();
|
||||
$this->sendEmail();
|
||||
break;
|
||||
case 'invitation-link':
|
||||
$user = User::all()->first();
|
||||
$invitation = TeamInvitation::whereEmail($user->email)->first();
|
||||
if (!$invitation) {
|
||||
$invitation = TeamInvitation::create([
|
||||
'uuid' => Str::uuid(),
|
||||
'email' => $user->email,
|
||||
'team_id' => 1,
|
||||
'link' => 'http://example.com',
|
||||
]);
|
||||
}
|
||||
$this->mail = (new InvitationLink($user))->toMail();
|
||||
$this->sendEmail();
|
||||
break;
|
||||
// case 'invitation-link':
|
||||
// $user = User::all()->first();
|
||||
// $invitation = TeamInvitation::whereEmail($user->email)->first();
|
||||
// if (!$invitation) {
|
||||
// $invitation = TeamInvitation::create([
|
||||
// 'uuid' => Str::uuid(),
|
||||
// 'email' => $user->email,
|
||||
// 'team_id' => 1,
|
||||
// 'link' => 'http://example.com',
|
||||
// ]);
|
||||
// }
|
||||
// $this->mail = (new InvitationLink($user))->toMail();
|
||||
// $this->sendEmail();
|
||||
// break;
|
||||
case 'waitlist-invitation-link':
|
||||
$this->mail = new MailMessage();
|
||||
$this->mail->view('emails.waitlist-invitation', [
|
||||
'email' => 'test2@example.com',
|
||||
'password' => "supersecretpassword",
|
||||
'loginLink' => 'https://coolify.io',
|
||||
]);
|
||||
$this->mail->subject('Congratulations! You are invited to join Coolify Cloud.');
|
||||
$this->sendEmail();
|
||||
break;
|
||||
case 'waitlist-confirmation':
|
||||
$found = Waitlist::where('email', $this->email)->first();
|
||||
if ($found) {
|
||||
SendConfirmationForWaitlistJob::dispatch($this->email, $found->uuid);
|
||||
} else {
|
||||
throw new Exception('Waitlist not found');
|
||||
}
|
||||
|
||||
break;
|
||||
case 'realusers-before-trial':
|
||||
$this->mail = new MailMessage();
|
||||
$this->mail->view(
|
||||
'emails.waitlist-confirmation',
|
||||
[
|
||||
'confirmation_url' => 'http://example.com',
|
||||
'cancel_url' => 'http://example.com',
|
||||
]
|
||||
);
|
||||
$this->mail->subject('You are on the waitlist!');
|
||||
$this->sendEmail();
|
||||
$this->mail->view('emails.before-trial-conversion');
|
||||
$this->mail->subject('Trial period has been added for all subscription plans.');
|
||||
$teams = Team::doesntHave('subscription')->where('id', '!=', 0)->get();
|
||||
if (!$teams || $teams->isEmpty()) {
|
||||
echo 'No teams found.' . PHP_EOL;
|
||||
return;
|
||||
}
|
||||
$emails = [];
|
||||
foreach ($teams as $team) {
|
||||
foreach ($team->members as $member) {
|
||||
if ($member->email) {
|
||||
$emails[] = $member->email;
|
||||
}
|
||||
}
|
||||
}
|
||||
$emails = array_unique($emails);
|
||||
$this->info("Sending to " . count($emails) . " emails.");
|
||||
foreach ($emails as $email) {
|
||||
$this->info($email);
|
||||
}
|
||||
$confirmed = confirm('Are you sure?');
|
||||
if ($confirmed) {
|
||||
foreach ($emails as $email) {
|
||||
$this->sendEmail($email);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'realusers-server-lost-connection':
|
||||
$serverId = text('Server Id');
|
||||
$server = Server::find($serverId);
|
||||
if (!$server) {
|
||||
throw new Exception('Server not found');
|
||||
}
|
||||
$admins = [];
|
||||
$members = $server->team->members;
|
||||
foreach ($members as $member) {
|
||||
if ($member->isAdmin()) {
|
||||
$admins[] = $member->email;
|
||||
}
|
||||
}
|
||||
$this->info('Sending to ' . count($admins) . ' admins.');
|
||||
foreach ($admins as $admin) {
|
||||
$this->info($admin);
|
||||
}
|
||||
$this->mail = new MailMessage();
|
||||
$this->mail->view('emails.server-lost-connection', [
|
||||
'name' => $server->name,
|
||||
]);
|
||||
$this->mail->subject('Action required: Server ' . $server->name . ' lost connection.');
|
||||
foreach ($admins as $email) {
|
||||
$this->sendEmail($email);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
private function sendEmail()
|
||||
private function sendEmail(string $email = null)
|
||||
{
|
||||
if ($email) {
|
||||
$this->email = $email;
|
||||
}
|
||||
Mail::send(
|
||||
[],
|
||||
[],
|
||||
fn (Message $message) => $message
|
||||
->from(
|
||||
'internal@example.com',
|
||||
'Test Email',
|
||||
)
|
||||
->to('test@example.com')
|
||||
->to($this->email)
|
||||
->subject($this->mail->subject)
|
||||
->html((string)$this->mail->render())
|
||||
);
|
||||
$this->info("Email sent to $this->email successfully. 📧");
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,7 @@ class Init extends Command
|
||||
$deployment->status = ApplicationDeploymentStatus::FAILED->value;
|
||||
$deployment->save();
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
} catch (\Throwable $e) {
|
||||
echo "Error: {$e->getMessage()}\n";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,7 +82,7 @@ class SyncBunny extends Command
|
||||
$pool->purge("$bunny_cdn/$bunny_cdn_path/$versions"),
|
||||
]);
|
||||
echo "All files uploaded & purged...\n";
|
||||
} catch (\Exception $e) {
|
||||
} catch (\Throwable $e) {
|
||||
echo $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ class WaitlistInvite extends Command
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'waitlist:invite {email?} {--only-email}';
|
||||
protected $signature = 'waitlist:invite {--people=1} {--only-email} {email?}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
@@ -33,6 +33,12 @@ class WaitlistInvite extends Command
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$people = $this->option('people');
|
||||
for ($i = 0; $i < $people; $i++) {
|
||||
$this->main();
|
||||
}
|
||||
}
|
||||
private function main() {
|
||||
if ($this->argument('email')) {
|
||||
if ($this->option('only-email')) {
|
||||
$this->next_patient = User::whereEmail($this->argument('email'))->first();
|
||||
@@ -86,13 +92,10 @@ class WaitlistInvite extends Command
|
||||
}
|
||||
private function send_email()
|
||||
{
|
||||
ray($this->next_patient->email, $this->password);
|
||||
$token = Crypt::encryptString("{$this->next_patient->email}@@@$this->password");
|
||||
$loginLink = route('auth.link', ['token' => $token]);
|
||||
$mail = new MailMessage();
|
||||
$mail->view('emails.waitlist-invitation', [
|
||||
'email' => $this->next_patient->email,
|
||||
'password' => $this->password,
|
||||
'loginLink' => $loginLink,
|
||||
]);
|
||||
$mail->subject('Congratulations! You are invited to join Coolify Cloud.');
|
||||
|
||||
@@ -2,19 +2,15 @@
|
||||
|
||||
namespace App\Console;
|
||||
|
||||
use App\Jobs\ApplicationContainerStatusJob;
|
||||
use App\Jobs\CheckResaleLicenseJob;
|
||||
use App\Jobs\CleanupInstanceStuffsJob;
|
||||
use App\Jobs\DatabaseBackupJob;
|
||||
use App\Jobs\DatabaseContainerStatusJob;
|
||||
use App\Jobs\DockerCleanupJob;
|
||||
use App\Jobs\InstanceAutoUpdateJob;
|
||||
use App\Jobs\ProxyCheckJob;
|
||||
use App\Jobs\ResourceStatusJob;
|
||||
use App\Models\Application;
|
||||
use App\Jobs\ContainerStatusJob;
|
||||
use App\Models\InstanceSettings;
|
||||
use App\Models\ScheduledDatabaseBackup;
|
||||
use App\Models\StandalonePostgresql;
|
||||
use App\Models\Server;
|
||||
use Illuminate\Console\Scheduling\Schedule;
|
||||
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
|
||||
|
||||
@@ -23,37 +19,35 @@ class Kernel extends ConsoleKernel
|
||||
protected function schedule(Schedule $schedule): void
|
||||
{
|
||||
if (isDev()) {
|
||||
$schedule->command('horizon:snapshot')->everyMinute();
|
||||
// $schedule->job(new ResourceStatusJob)->everyMinute();
|
||||
$schedule->job(new ProxyCheckJob)->everyFiveMinutes();
|
||||
$schedule->job(new CleanupInstanceStuffsJob)->everyMinute();
|
||||
// $schedule->job(new ContainerStatusJob(Server::find(0)))->everyTenMinutes()->onOneServer();
|
||||
// $schedule->command('horizon:snapshot')->everyMinute();
|
||||
$schedule->job(new CleanupInstanceStuffsJob)->everyMinute()->onOneServer();
|
||||
// $schedule->job(new CheckResaleLicenseJob)->hourly();
|
||||
$schedule->job(new DockerCleanupJob)->everyOddHour();
|
||||
// $schedule->job(new DockerCleanupJob)->everyOddHour();
|
||||
// $this->instance_auto_update($schedule);
|
||||
// $this->check_scheduled_backups($schedule);
|
||||
$this->check_resources($schedule);
|
||||
} else {
|
||||
$schedule->command('horizon:snapshot')->everyFiveMinutes();
|
||||
$schedule->job(new CleanupInstanceStuffsJob)->everyTenMinutes()->onOneServer();
|
||||
// $schedule->job(new ResourceStatusJob)->everyMinute()->onOneServer();
|
||||
$schedule->job(new CleanupInstanceStuffsJob)->everyTwoMinutes()->onOneServer();
|
||||
$schedule->job(new CheckResaleLicenseJob)->hourly()->onOneServer();
|
||||
$schedule->job(new ProxyCheckJob)->everyFiveMinutes()->onOneServer();
|
||||
$schedule->job(new DockerCleanupJob)->everyTenMinutes()->onOneServer();
|
||||
$this->instance_auto_update($schedule);
|
||||
$this->check_scheduled_backups($schedule);
|
||||
$this->check_resources($schedule);
|
||||
}
|
||||
$this->instance_auto_update($schedule);
|
||||
$this->check_scheduled_backups($schedule);
|
||||
$this->check_resources($schedule);
|
||||
}
|
||||
private function check_resources($schedule)
|
||||
{
|
||||
$applications = Application::all();
|
||||
foreach ($applications as $application) {
|
||||
$schedule->job(new ApplicationContainerStatusJob($application))->everyMinute()->onOneServer();
|
||||
}
|
||||
$servers = Server::all()->where('settings.is_usable', true)->where('settings.is_reachable', true);
|
||||
ray($servers);
|
||||
|
||||
$postgresqls = StandalonePostgresql::all();
|
||||
foreach ($postgresqls as $postgresql) {
|
||||
$schedule->job(new DatabaseContainerStatusJob($postgresql))->everyMinute()->onOneServer();
|
||||
foreach ($servers as $server) {
|
||||
$schedule->job(new ContainerStatusJob($server))->everyMinute()->onOneServer();
|
||||
}
|
||||
}
|
||||
private function instance_auto_update($schedule){
|
||||
private function instance_auto_update($schedule)
|
||||
{
|
||||
if (isDev()) {
|
||||
return;
|
||||
}
|
||||
@@ -74,6 +68,11 @@ class Kernel extends ConsoleKernel
|
||||
if (!$scheduled_backup->enabled) {
|
||||
continue;
|
||||
}
|
||||
if (is_null(data_get($scheduled_backup, 'database'))) {
|
||||
ray('database not found');
|
||||
$scheduled_backup->delete();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isset(VALID_CRON_STRINGS[$scheduled_backup->frequency])) {
|
||||
$scheduled_backup->frequency = VALID_CRON_STRINGS[$scheduled_backup->frequency];
|
||||
|
||||
@@ -12,16 +12,16 @@ use Spatie\LaravelData\Data;
|
||||
class CoolifyTaskArgs extends Data
|
||||
{
|
||||
public function __construct(
|
||||
public string $server_ip,
|
||||
public string $private_key_location,
|
||||
public string $server_uuid,
|
||||
public string $command,
|
||||
public int $port,
|
||||
public string $user,
|
||||
public string $type,
|
||||
public ?string $type_uuid = null,
|
||||
public ?Model $model = null,
|
||||
public string $status = ProcessStatus::QUEUED->value,
|
||||
public ?string $status = null ,
|
||||
public bool $ignore_errors = false,
|
||||
) {
|
||||
if(is_null($status)){
|
||||
$this->status = ProcessStatus::QUEUED->value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace App\Exceptions;
|
||||
|
||||
use App\Models\InstanceSettings;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
|
||||
use Sentry\Laravel\Integration;
|
||||
use Sentry\State\Scope;
|
||||
@@ -25,7 +26,7 @@ class Handler extends ExceptionHandler
|
||||
* @var array<int, class-string<\Throwable>>
|
||||
*/
|
||||
protected $dontReport = [
|
||||
//
|
||||
ProcessException::class
|
||||
];
|
||||
/**
|
||||
* A list of the inputs that are never flashed to the session on validation exceptions.
|
||||
@@ -45,13 +46,21 @@ class Handler extends ExceptionHandler
|
||||
public function register(): void
|
||||
{
|
||||
$this->reportable(function (Throwable $e) {
|
||||
if (isDev()) {
|
||||
return;
|
||||
}
|
||||
$this->settings = InstanceSettings::get();
|
||||
if ($this->settings->do_not_track || isDev()) {
|
||||
if ($this->settings->do_not_track) {
|
||||
return;
|
||||
}
|
||||
app('sentry')->configureScope(
|
||||
function (Scope $scope){
|
||||
$scope->setUser(['id'=> config('sentry.server_name')]);
|
||||
function (Scope $scope) {
|
||||
$scope->setUser(
|
||||
[
|
||||
'email' => auth()->user()->email,
|
||||
'instanceAdmin' => User::find(0)->email
|
||||
]
|
||||
);
|
||||
}
|
||||
);
|
||||
Integration::captureUnhandledException($e);
|
||||
|
||||
10
app/Exceptions/ProcessException.php
Normal file
10
app/Exceptions/ProcessException.php
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exceptions;
|
||||
|
||||
use Exception;
|
||||
|
||||
class ProcessException extends Exception
|
||||
{
|
||||
|
||||
}
|
||||
@@ -3,21 +3,17 @@
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\InstanceSettings;
|
||||
use App\Models\Project;
|
||||
use App\Models\S3Storage;
|
||||
use App\Models\StandalonePostgresql;
|
||||
use App\Models\TeamInvitation;
|
||||
use App\Models\User;
|
||||
use Auth;
|
||||
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||
use Illuminate\Foundation\Validation\ValidatesRequests;
|
||||
use Illuminate\Routing\Controller as BaseController;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Crypt;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Throwable;
|
||||
use Str;
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class Controller extends BaseController
|
||||
{
|
||||
@@ -35,8 +31,15 @@ class Controller extends BaseController
|
||||
return redirect()->route('login');
|
||||
}
|
||||
if (Hash::check($password, $user->password)) {
|
||||
$invitation = TeamInvitation::whereEmail($email);
|
||||
if ($invitation->exists()) {
|
||||
$team = $invitation->first()->team;
|
||||
$user->teams()->attach($team->id, ['role' => $invitation->first()->role]);
|
||||
$invitation->delete();
|
||||
} else {
|
||||
$team = $user->teams()->first();
|
||||
}
|
||||
Auth::login($user);
|
||||
$team = $user->teams()->first();
|
||||
session(['currentTeam' => $team]);
|
||||
return redirect()->route('dashboard');
|
||||
}
|
||||
@@ -137,25 +140,21 @@ class Controller extends BaseController
|
||||
try {
|
||||
$invitation = TeamInvitation::whereUuid(request()->route('uuid'))->firstOrFail();
|
||||
$user = User::whereEmail($invitation->email)->firstOrFail();
|
||||
if (is_null(auth()->user())) {
|
||||
return redirect()->route('login');
|
||||
}
|
||||
if (auth()->user()->id !== $user->id) {
|
||||
abort(401);
|
||||
}
|
||||
|
||||
$createdAt = $invitation->created_at;
|
||||
$diff = $createdAt->diffInMinutes(now());
|
||||
if ($diff <= config('constants.invitation.link.expiration')) {
|
||||
$invitationValid = $invitation->isValid();
|
||||
if ($invitationValid) {
|
||||
$user->teams()->attach($invitation->team->id, ['role' => $invitation->role]);
|
||||
refreshSession($invitation->team);
|
||||
$invitation->delete();
|
||||
return redirect()->route('team.index');
|
||||
} else {
|
||||
$invitation->delete();
|
||||
abort(401);
|
||||
}
|
||||
} catch (Throwable $th) {
|
||||
throw $th;
|
||||
} catch (\Throwable $e) {
|
||||
ray($e->getMessage());
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,8 +171,8 @@ class Controller extends BaseController
|
||||
}
|
||||
$invitation->delete();
|
||||
return redirect()->route('team.index');
|
||||
} catch (Throwable $th) {
|
||||
throw $th;
|
||||
} catch (\Throwable $e) {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ use App\Actions\Server\InstallDocker;
|
||||
use App\Models\PrivateKey;
|
||||
use App\Models\Project;
|
||||
use App\Models\Server;
|
||||
use App\Models\Team;
|
||||
use Illuminate\Support\Collection;
|
||||
use Livewire\Component;
|
||||
|
||||
@@ -13,6 +14,7 @@ class Index extends Component
|
||||
{
|
||||
public string $currentState = 'welcome';
|
||||
|
||||
public ?string $selectedServerType = null;
|
||||
public ?Collection $privateKeys = null;
|
||||
public ?int $selectedExistingPrivateKey = null;
|
||||
public ?string $privateKeyType = null;
|
||||
@@ -35,6 +37,11 @@ class Index extends Component
|
||||
public ?int $selectedExistingProject = null;
|
||||
public ?Project $createdProject = null;
|
||||
|
||||
public bool $dockerInstallationStarted = false;
|
||||
|
||||
public string $serverPublicKey;
|
||||
public bool $serverReachable = true;
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->privateKeyName = generate_random_name();
|
||||
@@ -52,40 +59,39 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
|
||||
$this->remoteServerHost = 'coolify-testing-host';
|
||||
}
|
||||
}
|
||||
public function welcome() {
|
||||
public function explanation()
|
||||
{
|
||||
if (isCloud()) {
|
||||
return $this->setServerType('remote');
|
||||
}
|
||||
$this->currentState = 'select-server-type';
|
||||
}
|
||||
|
||||
public function restartBoarding()
|
||||
{
|
||||
if ($this->createdServer) {
|
||||
$this->createdServer->delete();
|
||||
}
|
||||
if ($this->createdPrivateKey) {
|
||||
$this->createdPrivateKey->delete();
|
||||
}
|
||||
return redirect()->route('boarding');
|
||||
}
|
||||
public function skipBoarding()
|
||||
{
|
||||
currentTeam()->update([
|
||||
Team::find(currentTeam()->id)->update([
|
||||
'show_boarding' => false
|
||||
]);
|
||||
ray(currentTeam());
|
||||
refreshSession();
|
||||
return redirect()->route('dashboard');
|
||||
}
|
||||
|
||||
public function setServerType(string $type)
|
||||
{
|
||||
if ($type === 'localhost') {
|
||||
$this->selectedServerType = $type;
|
||||
if ($this->selectedServerType === 'localhost') {
|
||||
$this->createdServer = Server::find(0);
|
||||
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->validateServer();
|
||||
} elseif ($type === 'remote') {
|
||||
$this->serverPublicKey = $this->createdServer->privateKey->publicKey();
|
||||
return $this->validateServer('localhost');
|
||||
} elseif ($this->selectedServerType === 'remote') {
|
||||
$this->privateKeys = PrivateKey::ownedByCurrentTeam(['name'])->where('id', '!=', 0)->get();
|
||||
if ($this->privateKeys->count() > 0) {
|
||||
$this->selectedExistingPrivateKey = $this->privateKeys->first()->id;
|
||||
@@ -108,11 +114,11 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
|
||||
return;
|
||||
}
|
||||
$this->selectedExistingPrivateKey = $this->createdServer->privateKey->id;
|
||||
$this->serverPublicKey = $this->createdServer->privateKey->publicKey();
|
||||
$this->validateServer();
|
||||
$this->getProxyType();
|
||||
$this->getProjects();
|
||||
}
|
||||
public function getProxyType() {
|
||||
public function getProxyType()
|
||||
{
|
||||
$proxyTypeSet = $this->createdServer->proxy->type;
|
||||
if (!$proxyTypeSet) {
|
||||
$this->currentState = 'select-proxy';
|
||||
@@ -122,6 +128,7 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
|
||||
}
|
||||
public function selectExistingPrivateKey()
|
||||
{
|
||||
$this->createdPrivateKey = PrivateKey::find($this->selectedExistingPrivateKey);
|
||||
$this->currentState = 'create-server';
|
||||
}
|
||||
public function createNewServer()
|
||||
@@ -133,7 +140,7 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
|
||||
{
|
||||
$this->selectedExistingPrivateKey = null;
|
||||
$this->privateKeyType = $type;
|
||||
if ($type === 'create' && !isDev()) {
|
||||
if ($type === 'create') {
|
||||
$this->createNewPrivateKey();
|
||||
}
|
||||
$this->currentState = 'create-private-key';
|
||||
@@ -144,6 +151,13 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
|
||||
'privateKeyName' => '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';
|
||||
}
|
||||
public function saveServer()
|
||||
@@ -151,16 +165,14 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
|
||||
$this->validate([
|
||||
'remoteServerName' => 'required',
|
||||
'remoteServerHost' => 'required',
|
||||
'remoteServerPort' => 'required',
|
||||
'remoteServerPort' => 'required|integer',
|
||||
'remoteServerUser' => 'required',
|
||||
]);
|
||||
$this->privateKey = formatPrivateKey($this->privateKey);
|
||||
$this->createdPrivateKey = PrivateKey::create([
|
||||
'name' => $this->privateKeyName,
|
||||
'description' => $this->privateKeyDescription,
|
||||
'private_key' => $this->privateKey,
|
||||
'team_id' => currentTeam()->id
|
||||
]);
|
||||
$foundServer = Server::whereIp($this->remoteServerHost)->first();
|
||||
if ($foundServer) {
|
||||
return $this->emit('error', 'IP address is already in use by another team.');
|
||||
}
|
||||
$this->createdServer = Server::create([
|
||||
'name' => $this->remoteServerName,
|
||||
'ip' => $this->remoteServerHost,
|
||||
@@ -168,38 +180,52 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
|
||||
'user' => $this->remoteServerUser,
|
||||
'description' => $this->remoteServerDescription,
|
||||
'private_key_id' => $this->createdPrivateKey->id,
|
||||
'team_id' => currentTeam()->id
|
||||
'team_id' => currentTeam()->id,
|
||||
]);
|
||||
$this->createdServer->save();
|
||||
$this->validateServer();
|
||||
}
|
||||
public function validateServer() {
|
||||
public function validateServer()
|
||||
{
|
||||
try {
|
||||
['uptime' => $uptime, 'dockerVersion' => $dockerVersion] = validateServer($this->createdServer);
|
||||
if (!$uptime) {
|
||||
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();
|
||||
$customErrorMessage = "Server is not reachable:";
|
||||
config()->set('coolify.mux_enabled', false);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return general_error_handler(customErrorMessage: "Server is not reachable. Reason: {$e->getMessage()}", that: $this);
|
||||
instant_remote_process(['uptime'], $this->createdServer, true);
|
||||
|
||||
$this->createdServer->settings()->update([
|
||||
'is_reachable' => true,
|
||||
]);
|
||||
} catch (\Throwable $e) {
|
||||
$this->serverReachable = false;
|
||||
return handleError(error: $e, customErrorMessage: $customErrorMessage, livewire: $this);
|
||||
}
|
||||
|
||||
try {
|
||||
$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->createdServer->settings()->update([
|
||||
'is_usable' => true,
|
||||
]);
|
||||
$this->getProxyType();
|
||||
} catch (\Throwable $e) {
|
||||
$this->dockerInstallationStarted = false;
|
||||
return handleError(error: $e, customErrorMessage: $customErrorMessage, livewire: $this);
|
||||
}
|
||||
}
|
||||
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->currentState = 'select-proxy';
|
||||
}
|
||||
public function dockerInstalledOrSkipped()
|
||||
{
|
||||
$this->validateServer();
|
||||
}
|
||||
public function selectProxy(string|null $proxyType = null)
|
||||
{
|
||||
@@ -212,14 +238,16 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
|
||||
$this->getProjects();
|
||||
}
|
||||
|
||||
public function getProjects() {
|
||||
public function getProjects()
|
||||
{
|
||||
$this->projects = Project::ownedByCurrentTeam(['name'])->get();
|
||||
if ($this->projects->count() > 0) {
|
||||
$this->selectedExistingProject = $this->projects->first()->id;
|
||||
}
|
||||
$this->currentState = 'create-project';
|
||||
}
|
||||
public function selectExistingProject() {
|
||||
public function selectExistingProject()
|
||||
{
|
||||
$this->createdProject = Project::find($this->selectedExistingProject);
|
||||
$this->currentState = 'create-resource';
|
||||
}
|
||||
@@ -239,7 +267,7 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
|
||||
[
|
||||
'project_uuid' => $this->createdProject->uuid,
|
||||
'environment_name' => 'production',
|
||||
'server'=> $this->createdServer->id,
|
||||
'server' => $this->createdServer->id,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
@@ -34,9 +34,9 @@ class CheckLicense extends Component
|
||||
try {
|
||||
resolve(CheckResaleLicense::class)();
|
||||
$this->emit('reloadWindow');
|
||||
} catch (\Throwable $th) {
|
||||
session()->flash('error', 'Something went wrong. Please contact support. <br>Error: ' . $th->getMessage());
|
||||
ray($th->getMessage());
|
||||
} catch (\Throwable $e) {
|
||||
session()->flash('error', 'Something went wrong. Please contact support. <br>Error: ' . $e->getMessage());
|
||||
ray($e->getMessage());
|
||||
return redirect()->to('/settings/license');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,8 +37,8 @@ class Form extends Component
|
||||
}
|
||||
$this->destination->delete();
|
||||
return redirect()->route('dashboard');
|
||||
} catch (\Exception $e) {
|
||||
return general_error_handler(err: $e);
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,14 +71,14 @@ class StandaloneDocker extends Component
|
||||
}
|
||||
$this->createNetworkAndAttachToProxy();
|
||||
return redirect()->route('destination.show', $docker->uuid);
|
||||
} catch (\Exception $e) {
|
||||
return general_error_handler(err: $e, that: $this);
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
|
||||
private function createNetworkAndAttachToProxy()
|
||||
{
|
||||
instant_remote_process(['docker network create --attachable ' . $this->network], $this->server, throwError: false);
|
||||
instant_remote_process(["docker network connect $this->network coolify-proxy"], $this->server, throwError: false);
|
||||
$connectProxyToDockerNetworks = connectProxyToNetworks($this->server);
|
||||
instant_remote_process($connectProxyToDockerNetworks, $this->server, false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,8 +36,8 @@ class ForcePasswordReset extends Component
|
||||
send_internal_notification('First login for ' . auth()->user()->email);
|
||||
}
|
||||
return redirect()->route('dashboard');
|
||||
} catch (\Exception $e) {
|
||||
return general_error_handler(err: $e, that: $this);
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ class Help extends Component
|
||||
];
|
||||
public function mount()
|
||||
{
|
||||
$this->path = Route::current()->uri();
|
||||
$this->path = Route::current()?->uri() ?? null;
|
||||
if (isDev()) {
|
||||
$this->description = "I'm having trouble with {$this->path}";
|
||||
$this->subject = "Help with {$this->path}";
|
||||
@@ -30,7 +30,7 @@ class Help extends Component
|
||||
try {
|
||||
$this->rateLimit(1, 60);
|
||||
$this->validate();
|
||||
$subscriptionType = auth()->user()?->subscription?->type() ?? 'unknown';
|
||||
$subscriptionType = auth()->user()?->subscription?->type() ?? 'Free';
|
||||
$debug = "Route: {$this->path}";
|
||||
$mail = new MailMessage();
|
||||
$mail->view(
|
||||
@@ -41,10 +41,10 @@ class Help extends Component
|
||||
]
|
||||
);
|
||||
$mail->subject("[HELP - {$subscriptionType}]: {$this->subject}");
|
||||
send_user_an_email($mail, '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.');
|
||||
} catch (\Exception $e) {
|
||||
return general_error_handler($e, $this);
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
public function render()
|
||||
|
||||
@@ -29,7 +29,7 @@ class DiscordSettings extends Component
|
||||
{
|
||||
try {
|
||||
$this->submit();
|
||||
} catch (\Exception $e) {
|
||||
} catch (\Throwable $e) {
|
||||
ray($e->getMessage());
|
||||
$this->team->discord_enabled = false;
|
||||
$this->validate();
|
||||
@@ -46,9 +46,7 @@ class DiscordSettings extends Component
|
||||
public function saveModel()
|
||||
{
|
||||
$this->team->save();
|
||||
if (is_a($this->team, Team::class)) {
|
||||
refreshSession();
|
||||
}
|
||||
refreshSession();
|
||||
$this->emit('success', 'Settings saved.');
|
||||
}
|
||||
|
||||
|
||||
@@ -62,9 +62,10 @@ class EmailSettings extends Component
|
||||
'team.smtp_from_name' => 'required',
|
||||
]);
|
||||
$this->team->save();
|
||||
refreshSession();
|
||||
$this->emit('success', 'Settings saved successfully.');
|
||||
} catch (\Exception $e) {
|
||||
return general_error_handler($e, $this);
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
public function sendTestNotification()
|
||||
@@ -81,9 +82,10 @@ class EmailSettings extends Component
|
||||
$this->team->smtp_enabled = false;
|
||||
$this->team->resend_enabled = false;
|
||||
$this->team->save();
|
||||
refreshSession();
|
||||
$this->emit('success', 'Settings saved successfully.');
|
||||
} catch (\Exception $e) {
|
||||
return general_error_handler($e, $this);
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,9 +94,9 @@ class EmailSettings extends Component
|
||||
try {
|
||||
$this->team->smtp_enabled = false;
|
||||
$this->submitResend();
|
||||
} catch (\Exception $e) {
|
||||
} catch (\Throwable $e) {
|
||||
$this->team->smtp_enabled = false;
|
||||
return general_error_handler($e, $this);
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
public function instantSave()
|
||||
@@ -102,17 +104,15 @@ class EmailSettings extends Component
|
||||
try {
|
||||
$this->team->resend_enabled = false;
|
||||
$this->submit();
|
||||
} catch (\Exception $e) {
|
||||
} catch (\Throwable $e) {
|
||||
$this->team->smtp_enabled = false;
|
||||
return general_error_handler($e, $this);
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
public function saveModel()
|
||||
{
|
||||
$this->team->save();
|
||||
if (is_a($this->team, Team::class)) {
|
||||
refreshSession();
|
||||
}
|
||||
refreshSession();
|
||||
$this->emit('success', 'Settings saved.');
|
||||
}
|
||||
public function submit()
|
||||
@@ -130,10 +130,11 @@ class EmailSettings extends Component
|
||||
'team.smtp_timeout' => 'nullable',
|
||||
]);
|
||||
$this->team->save();
|
||||
refreshSession();
|
||||
$this->emit('success', 'Settings saved successfully.');
|
||||
} catch (\Exception $e) {
|
||||
} catch (\Throwable $e) {
|
||||
$this->team->smtp_enabled = false;
|
||||
return general_error_handler($e, $this);
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
public function submitResend()
|
||||
@@ -141,14 +142,16 @@ class EmailSettings extends Component
|
||||
try {
|
||||
$this->resetErrorBag();
|
||||
$this->validate([
|
||||
'team.smtp_from_address' => 'required|email',
|
||||
'team.smtp_from_name' => 'required',
|
||||
'team.resend_api_key' => 'required'
|
||||
]);
|
||||
$this->team->save();
|
||||
refreshSession();
|
||||
$this->emit('success', 'Settings saved successfully.');
|
||||
} catch (\Exception $e) {
|
||||
} catch (\Throwable $e) {
|
||||
$this->team->resend_enabled = false;
|
||||
return general_error_handler($e, $this);
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
public function copyFromInstanceSettings()
|
||||
|
||||
@@ -17,6 +17,10 @@ class TelegramSettings extends Component
|
||||
'team.telegram_notifications_deployments' => 'nullable|boolean',
|
||||
'team.telegram_notifications_status_changes' => 'nullable|boolean',
|
||||
'team.telegram_notifications_database_backups' => 'nullable|boolean',
|
||||
'team.telegram_notifications_test_message_thread_id' => 'nullable|string',
|
||||
'team.telegram_notifications_deployments_message_thread_id' => 'nullable|string',
|
||||
'team.telegram_notifications_status_changes_message_thread_id' => 'nullable|string',
|
||||
'team.telegram_notifications_database_backups_message_thread_id' => 'nullable|string',
|
||||
];
|
||||
protected $validationAttributes = [
|
||||
'team.telegram_token' => 'Token',
|
||||
@@ -31,7 +35,7 @@ class TelegramSettings extends Component
|
||||
{
|
||||
try {
|
||||
$this->submit();
|
||||
} catch (\Exception $e) {
|
||||
} catch (\Throwable $e) {
|
||||
ray($e->getMessage());
|
||||
$this->team->telegram_enabled = false;
|
||||
$this->validate();
|
||||
@@ -48,9 +52,7 @@ class TelegramSettings extends Component
|
||||
public function saveModel()
|
||||
{
|
||||
$this->team->save();
|
||||
if (is_a($this->team, Team::class)) {
|
||||
refreshSession();
|
||||
}
|
||||
refreshSession();
|
||||
$this->emit('success', 'Settings saved.');
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ use Livewire\Component;
|
||||
class Change extends Component
|
||||
{
|
||||
public PrivateKey $private_key;
|
||||
|
||||
public $public_key;
|
||||
protected $rules = [
|
||||
'private_key.name' => 'required|string',
|
||||
'private_key.description' => 'nullable|string',
|
||||
@@ -21,6 +21,14 @@ class Change extends Component
|
||||
'private_key.private_key' => 'private key'
|
||||
];
|
||||
|
||||
public function mount()
|
||||
{
|
||||
try {
|
||||
$this->public_key = $this->private_key->publicKey();
|
||||
}catch(\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
public function delete()
|
||||
{
|
||||
try {
|
||||
@@ -30,8 +38,8 @@ class Change extends Component
|
||||
return redirect()->route('security.private-key.index');
|
||||
}
|
||||
$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 (\Exception $e) {
|
||||
return general_error_handler(err: $e, that: $this);
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,8 +49,8 @@ class Change extends Component
|
||||
$this->private_key->private_key = formatPrivateKey($this->private_key->private_key);
|
||||
$this->private_key->save();
|
||||
refresh_server_connection($this->private_key);
|
||||
} catch (\Exception $e) {
|
||||
return general_error_handler(err: $e, that: $this);
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,14 +3,20 @@
|
||||
namespace App\Http\Livewire\PrivateKey;
|
||||
|
||||
use App\Models\PrivateKey;
|
||||
use DanHarrin\LivewireRateLimiting\WithRateLimiting;
|
||||
use Livewire\Component;
|
||||
use phpseclib3\Crypt\PublicKeyLoader;
|
||||
|
||||
class Create extends Component
|
||||
{
|
||||
public string|null $from = null;
|
||||
use WithRateLimiting;
|
||||
public string $name;
|
||||
public string|null $description = null;
|
||||
public string $value;
|
||||
|
||||
public ?string $from = null;
|
||||
public ?string $description = null;
|
||||
public ?string $publicKey = null;
|
||||
|
||||
protected $rules = [
|
||||
'name' => 'required|string',
|
||||
'value' => 'required|string',
|
||||
@@ -20,6 +26,32 @@ class Create extends Component
|
||||
'value' => 'private Key',
|
||||
];
|
||||
|
||||
public function generateNewKey()
|
||||
{
|
||||
try {
|
||||
$this->rateLimit(10);
|
||||
$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)
|
||||
{
|
||||
if ($updateProperty === 'value') {
|
||||
try {
|
||||
$this->publicKey = PublicKeyLoader::load($this->$updateProperty)->getPublicKey()->toString('OpenSSH',['comment' => '']);
|
||||
} catch (\Throwable $e) {
|
||||
if ($this->$updateProperty === "") {
|
||||
$this->publicKey = "";
|
||||
} else {
|
||||
$this->publicKey = "Invalid private key";
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->validateOnly($updateProperty);
|
||||
}
|
||||
public function createPrivateKey()
|
||||
{
|
||||
$this->validate();
|
||||
@@ -37,9 +69,9 @@ class Create extends Component
|
||||
if ($this->from === 'server') {
|
||||
return redirect()->route('server.create');
|
||||
}
|
||||
return redirect()->route('private-key.show', ['private_key_uuid' => $private_key->uuid]);
|
||||
} catch (\Exception $e) {
|
||||
return general_error_handler(err: $e, that: $this);
|
||||
return redirect()->route('security.private-key.show', ['private_key_uuid' => $private_key->uuid]);
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ class Form extends Component
|
||||
'name' => $this->name,
|
||||
]);
|
||||
} catch (\Throwable $e) {
|
||||
return general_error_handler(err: $e, that: $this);
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,8 +28,8 @@ class AddEmpty extends Component
|
||||
'team_id' => currentTeam()->id,
|
||||
]);
|
||||
return redirect()->route('project.show', $project->uuid);
|
||||
} catch (\Exception $e) {
|
||||
general_error_handler($e, $this);
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
} finally {
|
||||
$this->name = '';
|
||||
}
|
||||
|
||||
@@ -31,8 +31,8 @@ class AddEnvironment extends Component
|
||||
'project_uuid' => $this->project->uuid,
|
||||
'environment_name' => $environment->name,
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
general_error_handler($e, $this);
|
||||
} catch (\Throwable $e) {
|
||||
handleError($e, $this);
|
||||
} finally {
|
||||
$this->name = '';
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ class DeploymentNavbar extends Component
|
||||
]);
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
return general_error_handler(err: $e, that: $this);
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,15 +4,18 @@ namespace App\Http\Livewire\Project\Application;
|
||||
|
||||
use App\Models\Application;
|
||||
use App\Models\InstanceSettings;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Str;
|
||||
use Livewire\Component;
|
||||
use Spatie\Url\Url;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
|
||||
class General extends Component
|
||||
{
|
||||
public string $applicationId;
|
||||
|
||||
public Application $application;
|
||||
public Collection $services;
|
||||
public string $name;
|
||||
public string|null $fqdn;
|
||||
public string $git_repository;
|
||||
@@ -31,6 +34,7 @@ class General extends Component
|
||||
public bool $is_auto_deploy_enabled;
|
||||
public bool $is_force_https_enabled;
|
||||
|
||||
|
||||
protected $rules = [
|
||||
'application.name' => 'required',
|
||||
'application.description' => 'nullable',
|
||||
@@ -66,6 +70,7 @@ class General extends Component
|
||||
'application.ports_exposes' => 'Ports exposes',
|
||||
'application.ports_mappings' => 'Ports mappings',
|
||||
'application.dockerfile' => 'Dockerfile',
|
||||
|
||||
];
|
||||
|
||||
public function instantSave()
|
||||
@@ -86,8 +91,8 @@ class General extends Component
|
||||
$this->application->settings->save();
|
||||
$this->application->save();
|
||||
$this->application->refresh();
|
||||
$this->emit('success', 'Application settings updated!');
|
||||
$this->checkWildCardDomain();
|
||||
$this->emit('success', 'Application settings updated!');
|
||||
}
|
||||
|
||||
protected function checkWildCardDomain()
|
||||
@@ -136,16 +141,15 @@ class General extends Component
|
||||
|
||||
public function submit()
|
||||
{
|
||||
ray($this->application);
|
||||
try {
|
||||
$this->validate();
|
||||
if (data_get($this->application,'fqdn')) {
|
||||
if (data_get($this->application, 'fqdn')) {
|
||||
$domains = Str::of($this->application->fqdn)->trim()->explode(',')->map(function ($domain) {
|
||||
return Str::of($domain)->trim()->lower();
|
||||
});
|
||||
$this->application->fqdn = $domains->implode(',');
|
||||
}
|
||||
if ($this->application->dockerfile) {
|
||||
if (data_get($this->application, 'dockerfile')) {
|
||||
$port = get_port_from_dockerfile($this->application->dockerfile);
|
||||
if ($port) {
|
||||
$this->application->ports_exposes = $port;
|
||||
@@ -159,8 +163,8 @@ class General extends Component
|
||||
}
|
||||
$this->application->save();
|
||||
$this->emit('success', 'Application settings updated!');
|
||||
} catch (\Exception $e) {
|
||||
return general_error_handler(err: $e, that: $this);
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,9 +2,8 @@
|
||||
|
||||
namespace App\Http\Livewire\Project\Application;
|
||||
|
||||
use App\Jobs\ApplicationContainerStatusJob;
|
||||
use App\Jobs\ContainerStatusJob;
|
||||
use App\Models\Application;
|
||||
use App\Notifications\Application\StatusChanged;
|
||||
use Livewire\Component;
|
||||
use Visus\Cuid2\Cuid2;
|
||||
|
||||
@@ -22,10 +21,11 @@ class Heading extends Component
|
||||
|
||||
public function check_status()
|
||||
{
|
||||
dispatch_sync(new ApplicationContainerStatusJob(
|
||||
application: $this->application,
|
||||
));
|
||||
dispatch(new ContainerStatusJob($this->application->destination->server));
|
||||
$this->application->refresh();
|
||||
$this->application->previews->each(function ($preview) {
|
||||
$preview->refresh();
|
||||
});
|
||||
}
|
||||
|
||||
public function force_deploy_without_cache()
|
||||
@@ -64,14 +64,15 @@ class Heading extends Component
|
||||
foreach ($containers as $container) {
|
||||
$containerName = data_get($container, 'Names');
|
||||
if ($containerName) {
|
||||
remote_process(
|
||||
instant_remote_process(
|
||||
["docker rm -f {$containerName}"],
|
||||
$this->application->destination->server
|
||||
);
|
||||
$this->application->status = 'stopped';
|
||||
$this->application->status = 'exited';
|
||||
$this->application->save();
|
||||
// $this->application->environment->project->team->notify(new StatusChanged($this->application));
|
||||
}
|
||||
}
|
||||
$this->application->refresh();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
namespace App\Http\Livewire\Project\Application;
|
||||
|
||||
use App\Jobs\ApplicationContainerStatusJob;
|
||||
use App\Models\Application;
|
||||
use App\Models\ApplicationPreview;
|
||||
use Illuminate\Support\Collection;
|
||||
@@ -23,14 +22,6 @@ class Previews extends Component
|
||||
$this->parameters = get_route_parameters();
|
||||
}
|
||||
|
||||
public function loadStatus($pull_request_id)
|
||||
{
|
||||
dispatch(new ApplicationContainerStatusJob(
|
||||
application: $this->application,
|
||||
pullRequestId: $pull_request_id
|
||||
));
|
||||
}
|
||||
|
||||
public function load_prs()
|
||||
{
|
||||
try {
|
||||
@@ -39,7 +30,7 @@ class Previews extends Component
|
||||
$this->pull_requests = $data->sortBy('number')->values();
|
||||
} catch (\Throwable $e) {
|
||||
$this->rate_limit_remaining = 0;
|
||||
return general_error_handler(err: $e, that: $this);
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,7 +59,7 @@ class Previews extends Component
|
||||
'environment_name' => $this->parameters['environment_name'],
|
||||
]);
|
||||
} catch (\Throwable $e) {
|
||||
return general_error_handler(err: $e, that: $this);
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,14 +72,14 @@ class Previews extends Component
|
||||
public function stop(int $pull_request_id)
|
||||
{
|
||||
try {
|
||||
$container_name = generateApplicationContainerName($this->application->uuid, $pull_request_id);
|
||||
$container_name = generateApplicationContainerName($this->application);
|
||||
ray('Stopping container: ' . $container_name);
|
||||
|
||||
instant_remote_process(["docker rm -f $container_name"], $this->application->destination->server, throwError: false);
|
||||
ApplicationPreview::where('application_id', $this->application->id)->where('pull_request_id', $pull_request_id)->delete();
|
||||
$this->application->refresh();
|
||||
} catch (\Throwable $e) {
|
||||
return general_error_handler(err: $e, that: $this);
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -65,7 +65,7 @@ class Rollback extends Component
|
||||
];
|
||||
})->toArray();
|
||||
} catch (\Throwable $e) {
|
||||
return general_error_handler(err: $e, that: $this);
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ class BackupEdit extends Component
|
||||
$this->backup->save();
|
||||
$this->backup->refresh();
|
||||
$this->emit('success', 'Backup updated successfully');
|
||||
} catch (\Exception $e) {
|
||||
} catch (\Throwable $e) {
|
||||
$this->emit('error', $e->getMessage());
|
||||
}
|
||||
}
|
||||
@@ -76,7 +76,7 @@ class BackupEdit extends Component
|
||||
$this->backup->save();
|
||||
$this->backup->refresh();
|
||||
$this->emit('success', 'Backup updated successfully');
|
||||
} catch (\Exception $e) {
|
||||
} catch (\Throwable $e) {
|
||||
$this->emit('error', $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,8 +42,8 @@ class CreateScheduledBackup extends Component
|
||||
'team_id' => currentTeam()->id,
|
||||
]);
|
||||
$this->emit('refreshScheduledBackups');
|
||||
} catch (\Exception $e) {
|
||||
general_error_handler($e, $this);
|
||||
} catch (\Throwable $e) {
|
||||
handleError($e, $this);
|
||||
} finally {
|
||||
$this->frequency = '';
|
||||
$this->save_s3 = true;
|
||||
|
||||
@@ -3,8 +3,7 @@
|
||||
namespace App\Http\Livewire\Project\Database;
|
||||
|
||||
use App\Actions\Database\StartPostgresql;
|
||||
use App\Jobs\DatabaseContainerStatusJob;
|
||||
use App\Notifications\Application\StatusChanged;
|
||||
use App\Jobs\ContainerStatusJob;
|
||||
use Livewire\Component;
|
||||
|
||||
class Heading extends Component
|
||||
@@ -25,9 +24,7 @@ class Heading extends Component
|
||||
|
||||
public function check_status()
|
||||
{
|
||||
dispatch_sync(new DatabaseContainerStatusJob(
|
||||
database: $this->database,
|
||||
));
|
||||
dispatch_sync(new ContainerStatusJob($this->database->destination->server));
|
||||
$this->database->refresh();
|
||||
}
|
||||
|
||||
@@ -38,7 +35,7 @@ class Heading extends Component
|
||||
|
||||
public function stop()
|
||||
{
|
||||
remote_process(
|
||||
instant_remote_process(
|
||||
["docker rm -f {$this->database->uuid}"],
|
||||
$this->database->destination->server
|
||||
);
|
||||
@@ -46,9 +43,9 @@ class Heading extends Component
|
||||
stopPostgresProxy($this->database);
|
||||
$this->database->is_public = false;
|
||||
}
|
||||
$this->database->status = 'stopped';
|
||||
$this->database->status = 'exited';
|
||||
$this->database->save();
|
||||
$this->emit('refresh');
|
||||
$this->check_status();
|
||||
// $this->database->environment->project->team->notify(new StatusChanged($this->database));
|
||||
}
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ class InitScript extends Component
|
||||
$this->script['filename'] = $this->filename;
|
||||
$this->emitUp('save_init_script', $this->script);
|
||||
} catch (Exception $e) {
|
||||
return general_error_handler(err: $e, that: $this);
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace App\Http\Livewire\Project\Database\Postgresql;
|
||||
use App\Models\StandalonePostgresql;
|
||||
use Exception;
|
||||
use Livewire\Component;
|
||||
|
||||
use function Aws\filter;
|
||||
|
||||
class General extends Component
|
||||
@@ -64,6 +65,7 @@ class General extends Component
|
||||
return;
|
||||
}
|
||||
if ($this->database->is_public) {
|
||||
$this->emit('success', 'Starting TCP proxy...');
|
||||
startPostgresProxy($this->database);
|
||||
$this->emit('success', 'Database is now publicly accessible.');
|
||||
} else {
|
||||
@@ -72,9 +74,9 @@ class General extends Component
|
||||
}
|
||||
$this->getDbUrl();
|
||||
$this->database->save();
|
||||
} catch(Exception $e) {
|
||||
} catch(\Throwable $e) {
|
||||
$this->database->is_public = !$this->database->is_public;
|
||||
return general_error_handler(err: $e, that: $this);
|
||||
return handleError($e, $this);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -139,7 +141,7 @@ class General extends Component
|
||||
$this->database->save();
|
||||
$this->emit('success', 'Database updated successfully.');
|
||||
} catch (Exception $e) {
|
||||
return general_error_handler(err: $e, that: $this);
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,8 +19,8 @@ class Edit extends Component
|
||||
try {
|
||||
$this->project->save();
|
||||
$this->emit('saved');
|
||||
} catch (\Exception $e) {
|
||||
return general_error_handler($e, $this);
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
74
app/Http/Livewire/Project/New/DockerCompose.php
Normal file
74
app/Http/Livewire/Project/New/DockerCompose.php
Normal file
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Livewire\Project\New;
|
||||
|
||||
use App\Models\Project;
|
||||
use App\Models\Service;
|
||||
use Livewire\Component;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class DockerCompose extends Component
|
||||
{
|
||||
public string $dockercompose = '';
|
||||
public array $parameters;
|
||||
public array $query;
|
||||
public function mount()
|
||||
{
|
||||
$this->parameters = get_route_parameters();
|
||||
$this->query = request()->query();
|
||||
if (isDev()) {
|
||||
$this->dockercompose = 'services:
|
||||
ghost:
|
||||
documentation: https://ghost.org/docs/config
|
||||
image: ghost:5
|
||||
volumes:
|
||||
- ghost-content-data:/var/lib/ghost/content
|
||||
environment:
|
||||
- url=$SERVICE_FQDN_GHOST
|
||||
- database__client=mysql
|
||||
- database__connection__host=mysql
|
||||
- database__connection__user=$SERVICE_USER_MYSQL
|
||||
- database__connection__password=$SERVICE_PASSWORD_MYSQL
|
||||
- database__connection__database=${MYSQL_DATABASE-ghost}
|
||||
depends_on:
|
||||
- mysql
|
||||
mysql:
|
||||
documentation: https://hub.docker.com/_/mysql
|
||||
image: mysql:8.0
|
||||
volumes:
|
||||
- ghost-mysql-data:/var/lib/mysql
|
||||
environment:
|
||||
- MYSQL_USER=${SERVICE_USER_MYSQL}
|
||||
- MYSQL_PASSWORD=${SERVICE_PASSWORD_MYSQL}
|
||||
- MYSQL_DATABASE=${MYSQL_DATABASE}
|
||||
- MYSQL_ROOT_PASSWORD=${SERVICE_PASSWORD_MYSQL_ROOT}
|
||||
';
|
||||
}
|
||||
}
|
||||
public function submit()
|
||||
{
|
||||
$this->validate([
|
||||
'dockercompose' => 'required'
|
||||
]);
|
||||
$server_id = $this->query['server_id'];
|
||||
|
||||
$project = Project::where('uuid', $this->parameters['project_uuid'])->first();
|
||||
$environment = $project->load(['environments'])->environments->where('name', $this->parameters['environment_name'])->first();
|
||||
|
||||
$service = Service::create([
|
||||
'name' => 'service' . Str::random(10),
|
||||
'docker_compose_raw' => $this->dockercompose,
|
||||
'environment_id' => $environment->id,
|
||||
'server_id' => (int) $server_id,
|
||||
]);
|
||||
$service->name = "service-$service->uuid";
|
||||
|
||||
$service->parse(isNew: true);
|
||||
|
||||
return redirect()->route('project.service', [
|
||||
'service_uuid' => $service->uuid,
|
||||
'environment_name' => $environment->name,
|
||||
'project_uuid' => $project->uuid,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -9,8 +9,8 @@ use App\Models\StandaloneDocker;
|
||||
use App\Models\SwarmDocker;
|
||||
use App\Traits\SaveFromRedirect;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use Livewire\Component;
|
||||
use Route;
|
||||
|
||||
class GithubPrivateRepository extends Component
|
||||
{
|
||||
@@ -40,21 +40,6 @@ class GithubPrivateRepository extends Component
|
||||
public string|null $publish_directory = null;
|
||||
protected int $page = 1;
|
||||
|
||||
// public function saveFromRedirect(string $route, ?Collection $parameters = null){
|
||||
// session()->forget('from');
|
||||
// if (!$parameters || $parameters->count() === 0) {
|
||||
// $parameters = $this->parameters;
|
||||
// }
|
||||
// $parameters = collect($parameters) ?? collect([]);
|
||||
// $queries = collect($this->query) ?? collect([]);
|
||||
// $parameters = $parameters->merge($queries);
|
||||
// session(['from'=> [
|
||||
// 'back'=> $this->currentRoute,
|
||||
// 'route' => $route,
|
||||
// 'parameters' => $parameters
|
||||
// ]]);
|
||||
// return redirect()->route($route);
|
||||
// }
|
||||
|
||||
public function mount()
|
||||
{
|
||||
@@ -159,13 +144,20 @@ class GithubPrivateRepository extends Component
|
||||
$application->settings->is_static = $this->is_static;
|
||||
$application->settings->save();
|
||||
|
||||
$application->fqdn = "http://{$application->uuid}.{$destination->server->ip}.sslip.io";
|
||||
if (isDev()) {
|
||||
$application->fqdn = "http://{$application->uuid}.127.0.0.1.sslip.io";
|
||||
}
|
||||
$application->name = generate_application_name($this->selected_repository_owner . '/' . $this->selected_repository_repo, $this->selected_branch_name, $application->uuid);
|
||||
$application->save();
|
||||
|
||||
redirect()->route('project.application.configuration', [
|
||||
'application_uuid' => $application->uuid,
|
||||
'environment_name' => $environment->name,
|
||||
'project_uuid' => $project->uuid,
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
return general_error_handler(err: $e, that: $this);
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -112,13 +112,20 @@ class GithubPrivateRepositoryDeployKey extends Component
|
||||
$application->settings->is_static = $this->is_static;
|
||||
$application->settings->save();
|
||||
|
||||
$application->fqdn = "http://{$application->uuid}.{$destination->server->ip}.sslip.io";
|
||||
if (isDev()) {
|
||||
$application->fqdn = "http://{$application->uuid}.127.0.0.1.sslip.io";
|
||||
}
|
||||
$application->name = generate_random_name($application->uuid);
|
||||
$application->save();
|
||||
|
||||
return redirect()->route('project.application.configuration', [
|
||||
'project_uuid' => $project->uuid,
|
||||
'environment_name' => $environment->name,
|
||||
'application_uuid' => $application->uuid,
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
return general_error_handler(err: $e, that: $this);
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -69,21 +69,21 @@ class PublicGitRepository extends Component
|
||||
{
|
||||
try {
|
||||
$this->branch_found = false;
|
||||
$this->validate([
|
||||
'repository_url' => 'required|url'
|
||||
]);
|
||||
$this->get_git_source();
|
||||
$this->get_branch();
|
||||
$this->selected_branch = $this->git_branch;
|
||||
} catch (\Exception $e) {
|
||||
return general_error_handler(err: $e, that: $this);
|
||||
$this->validate([
|
||||
'repository_url' => 'required|url'
|
||||
]);
|
||||
$this->get_git_source();
|
||||
$this->get_branch();
|
||||
$this->selected_branch = $this->git_branch;
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
if (!$this->branch_found && $this->git_branch == 'main') {
|
||||
try {
|
||||
$this->git_branch = 'master';
|
||||
$this->get_branch();
|
||||
} catch (\Exception $e) {
|
||||
return general_error_handler(err: $e, that: $this);
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -137,7 +137,6 @@ class PublicGitRepository extends Component
|
||||
$project = Project::where('uuid', $project_uuid)->first();
|
||||
$environment = $project->load(['environments'])->environments->where('name', $environment_name)->first();
|
||||
|
||||
|
||||
$application_init = [
|
||||
'name' => generate_application_name($this->git_repository, $this->git_branch),
|
||||
'git_repository' => $this->git_repository,
|
||||
@@ -153,16 +152,24 @@ class PublicGitRepository extends Component
|
||||
];
|
||||
|
||||
$application = Application::create($application_init);
|
||||
|
||||
$application->settings->is_static = $this->is_static;
|
||||
$application->settings->save();
|
||||
|
||||
$application->fqdn = "http://{$application->uuid}.{$destination->server->ip}.sslip.io";
|
||||
if (isDev()) {
|
||||
$application->fqdn = "http://{$application->uuid}.127.0.0.1.sslip.io";
|
||||
}
|
||||
$application->name = generate_application_name($this->git_repository, $this->git_branch, $application->uuid);
|
||||
$application->save();
|
||||
|
||||
return redirect()->route('project.application.configuration', [
|
||||
'project_uuid' => $project->uuid,
|
||||
'environment_name' => $environment->name,
|
||||
'application_uuid' => $application->uuid,
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
return general_error_handler(err: $e, that: $this);
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ class Select extends Component
|
||||
public string $type;
|
||||
public string $server_id;
|
||||
public string $destination_uuid;
|
||||
public Countable|array|Server $servers;
|
||||
public Countable|array|Server $servers = [];
|
||||
public Collection|array $standaloneDockers = [];
|
||||
public Collection|array $swarmDockers = [];
|
||||
public array $parameters;
|
||||
@@ -40,8 +40,8 @@ class Select extends Component
|
||||
// try {
|
||||
// instantCommand("psql {$this->existingPostgresqlUrl} -c 'SELECT 1'");
|
||||
// $this->emit('success', 'Successfully connected to the database.');
|
||||
// } catch (\Exception $e) {
|
||||
// return general_error_handler($e, $this);
|
||||
// } catch (\Throwable $e) {
|
||||
// return handleError($e, $this);
|
||||
// }
|
||||
// }
|
||||
public function setType(string $type)
|
||||
@@ -83,6 +83,7 @@ class Select extends Component
|
||||
'environment_name' => $this->parameters['environment_name'],
|
||||
'type' => $this->type,
|
||||
'destination' => $this->destination_uuid,
|
||||
'server_id' => $this->server_id,
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -59,8 +59,14 @@ CMD ["nginx", "-g", "daemon off;"]
|
||||
'source_id' => 0,
|
||||
'source_type' => GithubApp::class
|
||||
]);
|
||||
|
||||
$fqdn = "http://{$application->uuid}.{$destination->server->ip}.sslip.io";
|
||||
if (isDev()) {
|
||||
$fqdn = "http://{$application->uuid}.127.0.0.1.sslip.io";
|
||||
}
|
||||
$application->update([
|
||||
'name' => 'dockerfile-' . $application->id
|
||||
'name' => 'dockerfile-' . $application->uuid,
|
||||
'fqdn' => $fqdn
|
||||
]);
|
||||
|
||||
redirect()->route('project.application.configuration', [
|
||||
|
||||
32
app/Http/Livewire/Project/Service/Application.php
Normal file
32
app/Http/Livewire/Project/Service/Application.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Livewire\Project\Service;
|
||||
|
||||
use App\Models\ServiceApplication;
|
||||
use Livewire\Component;
|
||||
|
||||
class Application extends Component
|
||||
{
|
||||
public ServiceApplication $application;
|
||||
protected $rules = [
|
||||
'application.human_name' => 'nullable',
|
||||
'application.description' => 'nullable',
|
||||
'application.fqdn' => 'nullable',
|
||||
];
|
||||
public function render()
|
||||
{
|
||||
ray($this->application->fileStorages()->get());
|
||||
return view('livewire.project.service.application');
|
||||
}
|
||||
public function submit()
|
||||
{
|
||||
try {
|
||||
$this->validate();
|
||||
$this->application->save();
|
||||
} catch (\Throwable $e) {
|
||||
ray($e);
|
||||
} finally {
|
||||
$this->emit('generateDockerCompose');
|
||||
}
|
||||
}
|
||||
}
|
||||
31
app/Http/Livewire/Project/Service/Database.php
Normal file
31
app/Http/Livewire/Project/Service/Database.php
Normal file
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Livewire\Project\Service;
|
||||
|
||||
use App\Models\ServiceApplication;
|
||||
use App\Models\ServiceDatabase;
|
||||
use Livewire\Component;
|
||||
|
||||
class Database extends Component
|
||||
{
|
||||
public ServiceDatabase $database;
|
||||
protected $rules = [
|
||||
'database.human_name' => 'nullable',
|
||||
'database.description' => 'nullable',
|
||||
];
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.project.service.database');
|
||||
}
|
||||
public function submit()
|
||||
{
|
||||
try {
|
||||
$this->validate();
|
||||
$this->database->save();
|
||||
} catch (\Throwable $e) {
|
||||
ray($e);
|
||||
} finally {
|
||||
$this->emit('generateDockerCompose');
|
||||
}
|
||||
}
|
||||
}
|
||||
21
app/Http/Livewire/Project/Service/FileStorage.php
Normal file
21
app/Http/Livewire/Project/Service/FileStorage.php
Normal file
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Livewire\Project\Service;
|
||||
|
||||
use App\Models\LocalFileVolume;
|
||||
use Livewire\Component;
|
||||
|
||||
class FileStorage extends Component
|
||||
{
|
||||
public LocalFileVolume $fileStorage;
|
||||
|
||||
protected $rules = [
|
||||
'fileStorage.fs_path' => 'required',
|
||||
'fileStorage.mount_path' => 'required',
|
||||
'fileStorage.content' => 'nullable',
|
||||
];
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.project.service.file-storage');
|
||||
}
|
||||
}
|
||||
46
app/Http/Livewire/Project/Service/Index.php
Normal file
46
app/Http/Livewire/Project/Service/Index.php
Normal file
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Livewire\Project\Service;
|
||||
|
||||
use App\Models\Service;
|
||||
use Livewire\Component;
|
||||
|
||||
class Index extends Component
|
||||
{
|
||||
public Service $service;
|
||||
|
||||
public array $parameters;
|
||||
public array $query;
|
||||
protected $rules = [
|
||||
'service.docker_compose_raw' => 'required',
|
||||
'service.docker_compose' => 'required',
|
||||
'service.name' => 'required',
|
||||
'service.description' => 'required',
|
||||
];
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->parameters = get_route_parameters();
|
||||
$this->query = request()->query();
|
||||
$this->service = Service::whereUuid($this->parameters['service_uuid'])->firstOrFail();
|
||||
}
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.project.service.index');
|
||||
}
|
||||
public function save() {
|
||||
$this->service->save();
|
||||
$this->service->parse();
|
||||
$this->service->refresh();
|
||||
$this->emit('refreshEnvs');
|
||||
}
|
||||
public function submit() {
|
||||
try {
|
||||
$this->validate();
|
||||
$this->service->save();
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
16
app/Http/Livewire/Project/Service/Modal.php
Normal file
16
app/Http/Livewire/Project/Service/Modal.php
Normal file
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Livewire\Project\Service;
|
||||
|
||||
use Livewire\Component;
|
||||
|
||||
class Modal extends Component
|
||||
{
|
||||
public function serviceStatusUpdated() {
|
||||
$this->emit('serviceStatusUpdated');
|
||||
}
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.project.service.modal');
|
||||
}
|
||||
}
|
||||
43
app/Http/Livewire/Project/Service/Navbar.php
Normal file
43
app/Http/Livewire/Project/Service/Navbar.php
Normal file
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Livewire\Project\Service;
|
||||
|
||||
use App\Actions\Service\StartService;
|
||||
use App\Actions\Service\StopService;
|
||||
use App\Jobs\ContainerStatusJob;
|
||||
use App\Models\Service;
|
||||
use Livewire\Component;
|
||||
|
||||
class Navbar extends Component
|
||||
{
|
||||
public Service $service;
|
||||
public array $parameters;
|
||||
public array $query;
|
||||
protected $listeners = ['serviceStatusUpdated'];
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.project.service.navbar');
|
||||
}
|
||||
public function serviceStatusUpdated()
|
||||
{
|
||||
ray('serviceStatusUpdated');
|
||||
$this->check_status();
|
||||
}
|
||||
public function check_status()
|
||||
{
|
||||
dispatch_sync(new ContainerStatusJob($this->service->server));
|
||||
$this->service->refresh();
|
||||
}
|
||||
public function deploy()
|
||||
{
|
||||
$this->service->parse();
|
||||
$activity = StartService::run($this->service);
|
||||
$this->emit('newMonitorActivity', $activity->id);
|
||||
}
|
||||
public function stop()
|
||||
{
|
||||
StopService::run($this->service);
|
||||
$this->service->refresh();
|
||||
}
|
||||
}
|
||||
42
app/Http/Livewire/Project/Service/Show.php
Normal file
42
app/Http/Livewire/Project/Service/Show.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Livewire\Project\Service;
|
||||
|
||||
use App\Models\Service;
|
||||
use App\Models\ServiceApplication;
|
||||
use App\Models\ServiceDatabase;
|
||||
use Illuminate\Support\Collection;
|
||||
use Livewire\Component;
|
||||
|
||||
class Show extends Component
|
||||
{
|
||||
public Service $service;
|
||||
public ServiceApplication $serviceApplication;
|
||||
public ServiceDatabase $serviceDatabase;
|
||||
public array $parameters;
|
||||
public array $query;
|
||||
public Collection $services;
|
||||
protected $listeners = ['generateDockerCompose'];
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->services = collect([]);
|
||||
$this->parameters = get_route_parameters();
|
||||
$this->query = request()->query();
|
||||
$this->service = Service::whereUuid($this->parameters['service_uuid'])->firstOrFail();
|
||||
$service = $this->service->applications()->whereName($this->parameters['service_name'])->first();
|
||||
if ($service) {
|
||||
$this->serviceApplication = $service;
|
||||
} else {
|
||||
$this->serviceDatabase = $this->service->databases()->whereName($this->parameters['service_name'])->first();
|
||||
}
|
||||
}
|
||||
public function generateDockerCompose()
|
||||
{
|
||||
$this->service->parse();
|
||||
}
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.project.service.show');
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Http\Livewire\Project\Shared;
|
||||
|
||||
use App\Actions\Service\StopService;
|
||||
use Livewire\Component;
|
||||
use Visus\Cuid2\Cuid2;
|
||||
|
||||
@@ -19,13 +20,25 @@ class Danger extends Component
|
||||
|
||||
public function delete()
|
||||
{
|
||||
$destination = $this->resource->destination->getMorphClass()::where('id', $this->resource->destination->id)->first();
|
||||
|
||||
instant_remote_process(["docker rm -f {$this->resource->uuid}"], $destination->server);
|
||||
$this->resource->delete();
|
||||
return redirect()->route('project.resources', [
|
||||
'project_uuid' => $this->parameters['project_uuid'],
|
||||
'environment_name' => $this->parameters['environment_name']
|
||||
]);
|
||||
try {
|
||||
if ($this->resource->type() === 'service') {
|
||||
$server = $this->resource->server;
|
||||
StopService::run($this->resource);
|
||||
} else {
|
||||
$destination = data_get($this->resource, 'destination');
|
||||
if ($destination) {
|
||||
$destination = $this->resource->destination->getMorphClass()::where('id', $this->resource->destination->id)->first();
|
||||
$server = $destination->server;
|
||||
}
|
||||
instant_remote_process(["docker rm -f {$this->resource->uuid}"], $server);
|
||||
}
|
||||
$this->resource->delete();
|
||||
return redirect()->route('project.resources', [
|
||||
'project_uuid' => $this->parameters['project_uuid'],
|
||||
'environment_name' => $this->parameters['environment_name']
|
||||
]);
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,6 @@ class Add extends Component
|
||||
|
||||
public function submit()
|
||||
{
|
||||
ray('submitting');
|
||||
$this->validate();
|
||||
$this->emitUp('submit', [
|
||||
'key' => $this->key,
|
||||
|
||||
@@ -5,21 +5,95 @@ namespace App\Http\Livewire\Project\Shared\EnvironmentVariable;
|
||||
use App\Models\EnvironmentVariable;
|
||||
use Livewire\Component;
|
||||
use Visus\Cuid2\Cuid2;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class All extends Component
|
||||
{
|
||||
public $resource;
|
||||
public bool $showPreview = false;
|
||||
public string|null $modalId = null;
|
||||
public ?string $variables = null;
|
||||
public ?string $variablesPreview = null;
|
||||
public string $view = 'normal';
|
||||
protected $listeners = ['refreshEnvs', 'submit'];
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$resourceClass = get_class($this->resource);
|
||||
$resourceWithPreviews = ['App\Models\Application'];
|
||||
$simpleDockerfile = !is_null(data_get($this->resource, 'dockerfile'));
|
||||
if (Str::of($resourceClass)->contains($resourceWithPreviews) && !$simpleDockerfile) {
|
||||
$this->showPreview = true;
|
||||
}
|
||||
$this->modalId = new Cuid2(7);
|
||||
$this->getDevView();
|
||||
}
|
||||
public function getDevView()
|
||||
{
|
||||
$this->variables = $this->resource->environment_variables->map(function ($item) {
|
||||
return "$item->key=$item->value";
|
||||
})->sort()->join('
|
||||
');
|
||||
if ($this->showPreview) {
|
||||
$this->variablesPreview = $this->resource->environment_variables_preview->map(function ($item) {
|
||||
return "$item->key=$item->value";
|
||||
})->sort()->join('
|
||||
');
|
||||
}
|
||||
}
|
||||
public function switch()
|
||||
{
|
||||
$this->view = $this->view === 'normal' ? 'dev' : 'normal';
|
||||
}
|
||||
public function saveVariables($isPreview)
|
||||
{
|
||||
if ($isPreview) {
|
||||
$variables = parseEnvFormatToArray($this->variablesPreview);
|
||||
$existingVariables = $this->resource->environment_variables_preview();
|
||||
$this->resource->environment_variables_preview()->delete();
|
||||
} else {
|
||||
$variables = parseEnvFormatToArray($this->variables);
|
||||
ray($variables);
|
||||
$existingVariables = $this->resource->environment_variables();
|
||||
$this->resource->environment_variables()->delete();
|
||||
}
|
||||
foreach ($variables as $key => $variable) {
|
||||
$found = $existingVariables->where('key', $key)->first();
|
||||
if ($found) {
|
||||
$found->value = $variable;
|
||||
$found->save();
|
||||
continue;
|
||||
} else {
|
||||
$environment = new EnvironmentVariable();
|
||||
$environment->key = $key;
|
||||
$environment->value = $variable;
|
||||
$environment->is_build_time = false;
|
||||
$environment->is_preview = $isPreview ? true : false;
|
||||
switch ($this->resource->type()) {
|
||||
case 'application':
|
||||
$environment->application_id = $this->resource->id;
|
||||
break;
|
||||
case 'standalone-postgresql':
|
||||
$environment->standalone_postgresql_id = $this->resource->id;
|
||||
break;
|
||||
case 'service':
|
||||
$environment->service_id = $this->resource->id;
|
||||
break;
|
||||
}
|
||||
$environment->save();
|
||||
}
|
||||
}
|
||||
if ($isPreview) {
|
||||
$this->emit('success', 'Preview environment variables updated successfully.');
|
||||
} else {
|
||||
$this->emit('success', 'Environment variables updated successfully.');
|
||||
}
|
||||
$this->refreshEnvs();
|
||||
}
|
||||
|
||||
public function refreshEnvs()
|
||||
{
|
||||
$this->resource->refresh();
|
||||
$this->getDevView();
|
||||
}
|
||||
|
||||
public function submit($data)
|
||||
@@ -43,10 +117,10 @@ class All extends Component
|
||||
$environment->standalone_postgresql_id = $this->resource->id;
|
||||
}
|
||||
$environment->save();
|
||||
$this->resource->refresh();
|
||||
$this->refreshEnvs();
|
||||
$this->emit('success', 'Environment variable added successfully.');
|
||||
} catch (\Exception $e) {
|
||||
return general_error_handler(err: $e, that: $this);
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,9 @@ class Show extends Component
|
||||
{
|
||||
public $parameters;
|
||||
public ModelsEnvironmentVariable $env;
|
||||
public string|null $modalId = null;
|
||||
public ?string $modalId = null;
|
||||
public string $type;
|
||||
|
||||
protected $rules = [
|
||||
'env.key' => 'required|string',
|
||||
'env.value' => 'required|string',
|
||||
@@ -37,6 +39,7 @@ class Show extends Component
|
||||
$this->validate();
|
||||
$this->env->save();
|
||||
$this->emit('success', 'Environment variable updated successfully.');
|
||||
$this->emit('refreshEnvs');
|
||||
}
|
||||
|
||||
public function delete()
|
||||
|
||||
39
app/Http/Livewire/Project/Shared/HealthChecks.php
Normal file
39
app/Http/Livewire/Project/Shared/HealthChecks.php
Normal file
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Livewire\Project\Shared;
|
||||
|
||||
use Livewire\Component;
|
||||
|
||||
class HealthChecks extends Component
|
||||
{
|
||||
|
||||
public $resource;
|
||||
protected $rules = [
|
||||
'resource.health_check_path' => 'string',
|
||||
'resource.health_check_port' => 'nullable|string',
|
||||
'resource.health_check_host' => 'string',
|
||||
'resource.health_check_method' => 'string',
|
||||
'resource.health_check_return_code' => 'integer',
|
||||
'resource.health_check_scheme' => 'string',
|
||||
'resource.health_check_response_text' => 'nullable|string',
|
||||
'resource.health_check_interval' => 'integer',
|
||||
'resource.health_check_timeout' => 'integer',
|
||||
'resource.health_check_retries' => 'integer',
|
||||
'resource.health_check_start_period' => 'integer',
|
||||
|
||||
];
|
||||
public function submit()
|
||||
{
|
||||
try {
|
||||
$this->validate();
|
||||
$this->resource->save();
|
||||
$this->emit('saved');
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.project.shared.health-checks');
|
||||
}
|
||||
}
|
||||
@@ -53,8 +53,8 @@ class ResourceLimits extends Component
|
||||
$this->validate();
|
||||
$this->resource->save();
|
||||
$this->emit('success', 'Resource limits updated successfully.');
|
||||
} catch (\Exception $e) {
|
||||
return general_error_handler(err: $e, that: $this);
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,8 +28,8 @@ class All extends Component
|
||||
$this->resource->refresh();
|
||||
$this->emit('success', 'Storage added successfully');
|
||||
$this->emit('clearAddStorage');
|
||||
} catch (\Exception $e) {
|
||||
return general_error_handler(err: $e, that: $this);
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,13 +2,16 @@
|
||||
|
||||
namespace App\Http\Livewire\Project\Shared\Storages;
|
||||
|
||||
use App\Models\LocalPersistentVolume;
|
||||
use Livewire\Component;
|
||||
use Visus\Cuid2\Cuid2;
|
||||
|
||||
class Show extends Component
|
||||
{
|
||||
public $storage;
|
||||
public string|null $modalId = null;
|
||||
public LocalPersistentVolume $storage;
|
||||
public bool $isReadOnly = false;
|
||||
public ?string $modalId = null;
|
||||
|
||||
protected $rules = [
|
||||
'storage.name' => 'required|string',
|
||||
'storage.mount_path' => 'required|string',
|
||||
|
||||
@@ -32,8 +32,8 @@ class RunCommand extends Component
|
||||
try {
|
||||
$activity = remote_process([$this->command], Server::where('uuid', $this->server)->first(), ignore_errors: true);
|
||||
$this->emit('newMonitorActivity', $activity->id);
|
||||
} catch (\Exception $e) {
|
||||
return general_error_handler(err: $e);
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ class Form extends Component
|
||||
public $dockerVersion;
|
||||
public string|null $wildcard_domain = null;
|
||||
public int $cleanup_after_percentage;
|
||||
public bool $dockerInstallationStarted = false;
|
||||
|
||||
protected $rules = [
|
||||
'server.name' => 'required|min:6',
|
||||
@@ -22,6 +23,7 @@ class Form extends Component
|
||||
'server.ip' => 'required',
|
||||
'server.user' => 'required',
|
||||
'server.port' => 'required',
|
||||
'server.settings.is_cloudflare_tunnel' => 'required',
|
||||
'server.settings.is_reachable' => 'required',
|
||||
'server.settings.is_part_of_swarm' => 'required',
|
||||
'wildcard_domain' => 'nullable|url',
|
||||
@@ -32,6 +34,7 @@ class Form extends Component
|
||||
'server.ip' => 'ip',
|
||||
'server.user' => 'user',
|
||||
'server.port' => 'port',
|
||||
'server.settings.is_cloudflare_tunnel' => 'Cloudflare Tunnel',
|
||||
'server.settings.is_reachable' => 'is reachable',
|
||||
'server.settings.is_part_of_swarm' => 'is part of swarm'
|
||||
];
|
||||
@@ -41,26 +44,39 @@ class Form extends Component
|
||||
$this->wildcard_domain = $this->server->settings->wildcard_domain;
|
||||
$this->cleanup_after_percentage = $this->server->settings->cleanup_after_percentage;
|
||||
}
|
||||
|
||||
public function instantSave() {
|
||||
refresh_server_connection($this->server->privateKey);
|
||||
$this->validateServer();
|
||||
$this->server->settings->save();
|
||||
}
|
||||
public function installDocker()
|
||||
{
|
||||
$activity = resolve(InstallDocker::class)($this->server, currentTeam());
|
||||
$this->dockerInstallationStarted = true;
|
||||
$activity = resolve(InstallDocker::class)($this->server);
|
||||
$this->emit('newMonitorActivity', $activity->id);
|
||||
}
|
||||
|
||||
public function validateServer()
|
||||
{
|
||||
try {
|
||||
['uptime' => $uptime, 'dockerVersion' => $dockerVersion] = validateServer($this->server);
|
||||
['uptime' => $uptime, 'dockerVersion' => $dockerVersion] = validateServer($this->server, true);
|
||||
if ($uptime) {
|
||||
$this->uptime = $uptime;
|
||||
$this->emit('success', 'Server is reachable.');
|
||||
} else {
|
||||
$this->emit('error', 'Server is not reachable.');
|
||||
return;
|
||||
}
|
||||
if ($dockerVersion) {
|
||||
$this->dockerVersion = $dockerVersion;
|
||||
$this->emit('proxyStatusUpdated');
|
||||
$this->emit('success', 'Docker Engine 23+ is installed!');
|
||||
} else {
|
||||
$this->emit('error', 'No Docker Engine or older than 23 version installed.');
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
return general_error_handler(customErrorMessage: "Server is not reachable. Reason: {$e->getMessage()}", that: $this);
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this, customErrorMessage: "Server is not reachable: ");
|
||||
} finally {
|
||||
$this->emit('proxyStatusUpdated');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,26 +90,20 @@ class Form extends Component
|
||||
}
|
||||
$this->server->delete();
|
||||
return redirect()->route('server.all');
|
||||
} catch (\Exception $e) {
|
||||
return general_error_handler(err: $e, that: $this);
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
public function submit()
|
||||
{
|
||||
$this->validate();
|
||||
// $validation = Validator::make($this->server->toArray(), [
|
||||
// 'ip' => [
|
||||
// 'ip'
|
||||
// ],
|
||||
// ]);
|
||||
// if ($validation->fails()) {
|
||||
// foreach ($validation->errors()->getMessages() as $key => $value) {
|
||||
// $this->addError("server.{$key}", $value[0]);
|
||||
// }
|
||||
// return;
|
||||
// }
|
||||
$uniqueIPs = Server::all()->reject(function (Server $server) {
|
||||
return $server->id === $this->server->id;
|
||||
})->pluck('ip')->toArray();
|
||||
if (in_array($this->server->ip, $uniqueIPs)) {
|
||||
$this->emit('error', 'IP address is already in use by another team.');
|
||||
return;
|
||||
}
|
||||
$this->server->settings->wildcard_domain = $this->wildcard_domain;
|
||||
$this->server->settings->cleanup_after_percentage = $this->cleanup_after_percentage;
|
||||
$this->server->settings->save();
|
||||
|
||||
@@ -26,7 +26,7 @@ class ByIp extends Component
|
||||
protected $rules = [
|
||||
'name' => 'required|string',
|
||||
'description' => 'nullable|string',
|
||||
'ip' => 'required|ip',
|
||||
'ip' => 'required',
|
||||
'user' => 'required|string',
|
||||
'port' => 'required|integer',
|
||||
];
|
||||
@@ -58,7 +58,7 @@ class ByIp extends Component
|
||||
{
|
||||
$this->validate();
|
||||
try {
|
||||
if (!$this->private_key_id) {
|
||||
if (is_null($this->private_key_id)) {
|
||||
return $this->emit('error', 'You must select a private key');
|
||||
}
|
||||
$server = Server::create([
|
||||
@@ -78,8 +78,8 @@ class ByIp extends Component
|
||||
$server->settings->is_part_of_swarm = $this->is_part_of_swarm;
|
||||
$server->settings->save();
|
||||
return redirect()->route('server.show', $server->uuid);
|
||||
} catch (\Exception $e) {
|
||||
return general_error_handler(err: $e);
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
|
||||
namespace App\Http\Livewire\Server;
|
||||
|
||||
use App\Actions\Proxy\CheckConfigurationSync;
|
||||
use App\Actions\Proxy\SaveConfigurationSync;
|
||||
use App\Enums\ProxyTypes;
|
||||
use App\Actions\Proxy\CheckConfiguration;
|
||||
use App\Actions\Proxy\SaveConfiguration;
|
||||
use App\Models\Server;
|
||||
use Livewire\Component;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class Proxy extends Component
|
||||
{
|
||||
@@ -14,14 +14,14 @@ class Proxy extends Component
|
||||
|
||||
public ?string $selectedProxy = null;
|
||||
public $proxy_settings = null;
|
||||
public string|null $redirect_url = null;
|
||||
public ?string $redirect_url = null;
|
||||
|
||||
protected $listeners = ['proxyStatusUpdated', 'saveConfiguration' => 'submit'];
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->selectedProxy = $this->server->proxy->type;
|
||||
$this->redirect_url = $this->server->proxy->redirect_url;
|
||||
$this->selectedProxy = data_get($this->server, 'proxy.type');
|
||||
$this->redirect_url = data_get($this->server, 'proxy.redirect_url');
|
||||
}
|
||||
|
||||
public function proxyStatusUpdated()
|
||||
@@ -48,33 +48,38 @@ class Proxy extends Component
|
||||
public function submit()
|
||||
{
|
||||
try {
|
||||
resolve(SaveConfigurationSync::class)($this->server, $this->proxy_settings);
|
||||
|
||||
SaveConfiguration::run($this->server, $this->proxy_settings);
|
||||
$this->server->proxy->redirect_url = $this->redirect_url;
|
||||
$this->server->save();
|
||||
|
||||
setup_default_redirect_404(redirect_url: $this->server->proxy->redirect_url, server: $this->server);
|
||||
$this->emit('success', 'Proxy configuration saved.');
|
||||
} catch (\Exception $e) {
|
||||
return general_error_handler(err: $e);
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
|
||||
public function reset_proxy_configuration()
|
||||
{
|
||||
try {
|
||||
$this->proxy_settings = resolve(CheckConfigurationSync::class)($this->server, true);
|
||||
} catch (\Exception $e) {
|
||||
return general_error_handler(err: $e);
|
||||
$this->proxy_settings = CheckConfiguration::run($this->server, true);
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
|
||||
public function load_proxy_configuration()
|
||||
public function loadProxyConfiguration()
|
||||
{
|
||||
try {
|
||||
$this->proxy_settings = resolve(CheckConfigurationSync::class)($this->server);
|
||||
} catch (\Exception $e) {
|
||||
return general_error_handler(err: $e);
|
||||
$this->proxy_settings = CheckConfiguration::run($this->server);
|
||||
if (Str::of($this->proxy_settings)->contains('--api.dashboard=true') && Str::of($this->proxy_settings)->contains('--api.insecure=true')) {
|
||||
$this->emit('traefikDashboardAvailable', true);
|
||||
} else {
|
||||
$this->emit('traefikDashboardAvailable', false);
|
||||
}
|
||||
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,18 +9,28 @@ use Livewire\Component;
|
||||
class Deploy extends Component
|
||||
{
|
||||
public Server $server;
|
||||
public $proxy_settings = null;
|
||||
public bool $traefikDashboardAvailable = false;
|
||||
public ?string $currentRoute = null;
|
||||
protected $listeners = ['proxyStatusUpdated', 'traefikDashboardAvailable'];
|
||||
|
||||
public function start_proxy()
|
||||
public function mount() {
|
||||
$this->currentRoute = request()->route()->getName();
|
||||
}
|
||||
public function traefikDashboardAvailable(bool $data) {
|
||||
$this->traefikDashboardAvailable = $data;
|
||||
}
|
||||
public function proxyStatusUpdated()
|
||||
{
|
||||
if (
|
||||
$this->server->proxy->last_applied_settings &&
|
||||
$this->server->proxy->last_saved_settings !== $this->server->proxy->last_applied_settings
|
||||
) {
|
||||
$this->emit('saveConfiguration', $this->server);
|
||||
$this->server->refresh();
|
||||
}
|
||||
public function startProxy()
|
||||
{
|
||||
try {
|
||||
$activity = StartProxy::run($this->server);
|
||||
$this->emit('newMonitorActivity', $activity->id);
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
$activity = resolve(StartProxy::class)($this->server);
|
||||
$this->emit('newMonitorActivity', $activity->id);
|
||||
}
|
||||
|
||||
public function stop()
|
||||
|
||||
16
app/Http/Livewire/Server/Proxy/Modal.php
Normal file
16
app/Http/Livewire/Server/Proxy/Modal.php
Normal file
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Livewire\Server\Proxy;
|
||||
|
||||
use App\Models\Server;
|
||||
use Livewire\Component;
|
||||
|
||||
class Modal extends Component
|
||||
{
|
||||
public Server $server;
|
||||
|
||||
public function proxyStatusUpdated()
|
||||
{
|
||||
$this->emit('proxyStatusUpdated');
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace App\Http\Livewire\Server\Proxy;
|
||||
|
||||
use App\Jobs\ProxyContainerStatusJob;
|
||||
use App\Jobs\ContainerStatusJob;
|
||||
use App\Models\Server;
|
||||
use Livewire\Component;
|
||||
|
||||
@@ -10,14 +10,25 @@ class Status extends Component
|
||||
{
|
||||
public Server $server;
|
||||
|
||||
public function get_status()
|
||||
protected $listeners = ['proxyStatusUpdated'];
|
||||
public function proxyStatusUpdated()
|
||||
{
|
||||
if (data_get($this->server,'settings.is_usable')) {
|
||||
dispatch_sync(new ProxyContainerStatusJob(
|
||||
server: $this->server
|
||||
));
|
||||
$this->server->refresh();
|
||||
$this->emit('proxyStatusUpdated');
|
||||
$this->server->refresh();
|
||||
}
|
||||
public function getProxyStatus()
|
||||
{
|
||||
try {
|
||||
if ($this->server->isFunctional()) {
|
||||
dispatch_sync(new ContainerStatusJob($this->server));
|
||||
$this->emit('proxyStatusUpdated');
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
public function getProxyStatusWithNoti()
|
||||
{
|
||||
$this->emit('success', 'Refreshed proxy status.');
|
||||
$this->getProxyStatus();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,9 +13,12 @@ class Show extends Component
|
||||
public function mount()
|
||||
{
|
||||
try {
|
||||
$this->server = Server::ownedByCurrentTeam(['name', 'description', 'ip', 'port', 'user', 'proxy'])->whereUuid(request()->server_uuid)->firstOrFail();
|
||||
$this->server = Server::ownedByCurrentTeam(['name', 'description', 'ip', 'port', 'user', 'proxy'])->whereUuid(request()->server_uuid)->first();
|
||||
if (is_null($this->server)) {
|
||||
return redirect()->route('server.all');
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
return general_error_handler(err: $e, that: $this);
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
public function render()
|
||||
|
||||
@@ -4,7 +4,6 @@ namespace App\Http\Livewire\Server;
|
||||
|
||||
use App\Models\Server;
|
||||
use Livewire\Component;
|
||||
use Masmerise\Toaster\Toaster;
|
||||
|
||||
class ShowPrivateKey extends Component
|
||||
{
|
||||
@@ -12,28 +11,57 @@ class ShowPrivateKey extends Component
|
||||
public $privateKeys;
|
||||
public $parameters;
|
||||
|
||||
public function setPrivateKey($private_key_id)
|
||||
public function setPrivateKey($newPrivateKeyId)
|
||||
{
|
||||
$this->server->update([
|
||||
'private_key_id' => $private_key_id
|
||||
]);
|
||||
refresh_server_connection($this->server->privateKey);
|
||||
$this->server->refresh();
|
||||
$this->checkConnection();
|
||||
try {
|
||||
refresh_server_connection($this->server->privateKey);
|
||||
$oldPrivateKeyId = $this->server->private_key_id;
|
||||
$this->server->update([
|
||||
'private_key_id' => $newPrivateKeyId
|
||||
]);
|
||||
$this->server->refresh();
|
||||
refresh_server_connection($this->server->privateKey);
|
||||
$this->checkConnection();
|
||||
} catch (\Throwable $e) {
|
||||
$this->server->update([
|
||||
'private_key_id' => $oldPrivateKeyId
|
||||
]);
|
||||
$this->server->refresh();
|
||||
refresh_server_connection($this->server->privateKey);
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
|
||||
public function checkConnection()
|
||||
{
|
||||
try {
|
||||
['uptime' => $uptime, 'dockerVersion' => $dockerVersion] = validateServer($this->server);
|
||||
['uptime' => $uptime, 'dockerVersion' => $dockerVersion] = validateServer($this->server, true);
|
||||
if ($uptime) {
|
||||
Toaster::success('Server is reachable with this private key.');
|
||||
$this->server->settings->update([
|
||||
'is_reachable' => true
|
||||
]);
|
||||
$this->emit('success', 'Server is reachable with this private key.');
|
||||
} else {
|
||||
$this->server->settings->update([
|
||||
'is_reachable' => false,
|
||||
'is_usable' => false
|
||||
]);
|
||||
$this->emit('error', 'Server is not reachable with this private key.');
|
||||
return;
|
||||
}
|
||||
if ($dockerVersion) {
|
||||
Toaster::success('Server is usable for Coolify.');
|
||||
$this->server->settings->update([
|
||||
'is_usable' => true
|
||||
]);
|
||||
$this->emit('success', 'Server is usable for Coolify.');
|
||||
} 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.');
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
return general_error_handler(customErrorMessage: "Server is not reachable. Reason: {$e->getMessage()}", that: $this);
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace App\Http\Livewire\Settings;
|
||||
|
||||
use App\Jobs\ProxyStartJob;
|
||||
use App\Jobs\ContainerStatusJob;
|
||||
use App\Models\InstanceSettings as ModelsInstanceSettings;
|
||||
use App\Models\Server;
|
||||
use Livewire\Component;
|
||||
@@ -124,7 +124,7 @@ class Configuration extends Component
|
||||
];
|
||||
}
|
||||
$this->save_configuration_to_disk($traefik_dynamic_conf, $file);
|
||||
dispatch(new ProxyStartJob($this->server));
|
||||
dispatch(new ContainerStatusJob($this->server));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -50,8 +50,8 @@ class Email extends Component
|
||||
]);
|
||||
$this->settings->save();
|
||||
$this->emit('success', 'Settings saved successfully.');
|
||||
} catch (\Exception $e) {
|
||||
return general_error_handler($e, $this);
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
public function submitResend() {
|
||||
@@ -60,20 +60,28 @@ class Email extends Component
|
||||
$this->validate([
|
||||
'settings.resend_api_key' => 'required'
|
||||
]);
|
||||
$this->settings->smtp_enabled = false;
|
||||
$this->settings->save();
|
||||
$this->emit('success', 'Settings saved successfully.');
|
||||
} catch (\Exception $e) {
|
||||
} catch (\Throwable $e) {
|
||||
$this->settings->resend_enabled = false;
|
||||
return general_error_handler($e, $this);
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
public function instantSaveResend() {
|
||||
try {
|
||||
$this->settings->smtp_enabled = false;
|
||||
$this->submitResend();
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
public function instantSave()
|
||||
{
|
||||
try {
|
||||
$this->settings->resend_enabled = false;
|
||||
$this->submit();
|
||||
} catch (\Exception $e) {
|
||||
return general_error_handler($e, $this);
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,11 +97,10 @@ class Email extends Component
|
||||
'settings.smtp_password' => 'nullable',
|
||||
'settings.smtp_timeout' => 'nullable',
|
||||
]);
|
||||
$this->settings->resend_enabled = false;
|
||||
$this->settings->save();
|
||||
$this->emit('success', 'Settings saved successfully.');
|
||||
} catch (\Exception $e) {
|
||||
return general_error_handler($e, $this);
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -51,8 +51,8 @@ class Change extends Component
|
||||
try {
|
||||
$this->validate();
|
||||
$this->github_app->save();
|
||||
} catch (\Exception $e) {
|
||||
return general_error_handler(err: $e, that: $this);
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,8 +65,8 @@ class Change extends Component
|
||||
try {
|
||||
$this->github_app->delete();
|
||||
redirect()->route('source.all');
|
||||
} catch (\Exception $e) {
|
||||
return general_error_handler(err: $e, that: $this);
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,8 +49,8 @@ class Create extends Component
|
||||
session(['from' => session('from') + ['source_id' => $github_app->id]]);
|
||||
}
|
||||
redirect()->route('source.github.show', ['github_app_uuid' => $github_app->uuid]);
|
||||
} catch (\Exception $e) {
|
||||
return general_error_handler(err: $e, that: $this);
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,8 +30,8 @@ class Actions extends Component
|
||||
$this->emit('success', 'Subscription cancelled successfully. Reloading in 5s.');
|
||||
$this->emit('reloadWindow', 5000);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
return general_error_handler($e, $this);
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
public function resume()
|
||||
@@ -65,8 +65,8 @@ class Actions extends Component
|
||||
$this->emit('success', 'Subscription resumed successfully. Reloading in 5s.');
|
||||
$this->emit('reloadWindow', 5000);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
return general_error_handler($e, $this);
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
public function stripeCustomerPortal() {
|
||||
|
||||
@@ -8,8 +8,13 @@ use Stripe\Checkout\Session;
|
||||
|
||||
class PricingPlans extends Component
|
||||
{
|
||||
public bool $isTrial = false;
|
||||
public function mount() {
|
||||
$this->isTrial = !data_get(currentTeam(),'subscription.stripe_trial_already_ended');
|
||||
}
|
||||
public function subscribeStripe($type)
|
||||
{
|
||||
$team = currentTeam();
|
||||
Stripe::setApiKey(config('subscription.stripe_api_key'));
|
||||
switch ($type) {
|
||||
case 'basic-monthly':
|
||||
@@ -39,6 +44,7 @@ class PricingPlans extends Component
|
||||
return;
|
||||
}
|
||||
$payload = [
|
||||
'billing_address_collection' => 'required',
|
||||
'client_reference_id' => auth()->user()->id . ':' . currentTeam()->id,
|
||||
'line_items' => [[
|
||||
'price' => $priceId,
|
||||
@@ -50,10 +56,23 @@ class PricingPlans extends Component
|
||||
'automatic_tax' => [
|
||||
'enabled' => true,
|
||||
],
|
||||
|
||||
'mode' => 'subscription',
|
||||
'success_url' => route('dashboard', ['success' => true]),
|
||||
'cancel_url' => route('subscription.index', ['cancelled' => true]),
|
||||
];
|
||||
|
||||
if (!data_get($team,'subscription.stripe_trial_already_ended')) {
|
||||
$payload['subscription_data'] = [
|
||||
'trial_period_days' => config('constants.limits.trial_period'),
|
||||
'trial_settings' => [
|
||||
'end_behavior' => [
|
||||
'missing_payment_method' => 'cancel',
|
||||
]
|
||||
],
|
||||
];
|
||||
$payload['payment_method_collection'] = 'if_required';
|
||||
}
|
||||
$customer = currentTeam()->subscription?->stripe_customer_id ?? null;
|
||||
if ($customer) {
|
||||
$payload['customer'] = $customer;
|
||||
|
||||
@@ -23,7 +23,7 @@ class SwitchTeam extends Component
|
||||
if (!$team_to_switch_to) {
|
||||
return;
|
||||
}
|
||||
session(['currentTeam' => $team_to_switch_to]);
|
||||
refreshSession($team_to_switch_to);
|
||||
return redirect(request()->header('Referer'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,8 +31,8 @@ class Create extends Component
|
||||
auth()->user()->teams()->attach($team, ['role' => 'admin']);
|
||||
refreshSession();
|
||||
return redirect()->route('team.index');
|
||||
} catch (\Throwable $th) {
|
||||
return general_error_handler($th, $this);
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ class Delete extends Component
|
||||
$currentTeam = currentTeam();
|
||||
$currentTeam->delete();
|
||||
|
||||
$team = auth()->user()->teams()->first();
|
||||
$currentTeam->members->each(function ($user) use ($currentTeam) {
|
||||
if ($user->id === auth()->user()->id) {
|
||||
return;
|
||||
|
||||
@@ -28,8 +28,8 @@ class Form extends Component
|
||||
try {
|
||||
$this->team->save();
|
||||
refreshSession();
|
||||
} catch (\Throwable $th) {
|
||||
return general_error_handler($th, $this);
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,9 +4,13 @@ namespace App\Http\Livewire\Team;
|
||||
|
||||
use App\Models\TeamInvitation;
|
||||
use App\Models\User;
|
||||
use App\Notifications\TransactionalEmails\InvitationLink;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
use Livewire\Component;
|
||||
use Visus\Cuid2\Cuid2;
|
||||
use Illuminate\Support\Facades\Crypt;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class InviteLink extends Component
|
||||
{
|
||||
@@ -20,64 +24,74 @@ class InviteLink extends Component
|
||||
|
||||
public function viaEmail()
|
||||
{
|
||||
$this->generate_invite_link(isEmail: true);
|
||||
$this->generate_invite_link(sendEmail: true);
|
||||
}
|
||||
|
||||
private function generate_invite_link(bool $isEmail = false)
|
||||
public function viaLink()
|
||||
{
|
||||
$this->generate_invite_link(sendEmail: false);
|
||||
}
|
||||
private function generate_invite_link(bool $sendEmail = false)
|
||||
{
|
||||
try {
|
||||
$uuid = new Cuid2(32);
|
||||
$link = url('/') . config('constants.invitation.link.base_url') . $uuid;
|
||||
|
||||
$user = User::whereEmail($this->email);
|
||||
|
||||
if (!$user->exists()) {
|
||||
return general_error_handler(that: $this, customErrorMessage: "$this->email must be registered first (or activate transactional emails to invite via email).");
|
||||
}
|
||||
|
||||
$member_emails = currentTeam()->members()->get()->pluck('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);
|
||||
$link = url('/') . config('constants.invitation.link.base_url') . $uuid;
|
||||
$user = User::whereEmail($this->email)->first();
|
||||
|
||||
$invitation = TeamInvitation::whereEmail($this->email);
|
||||
|
||||
if ($invitation->exists()) {
|
||||
$created_at = $invitation->first()->created_at;
|
||||
$diff = $created_at->diffInMinutes(now());
|
||||
if ($diff <= config('constants.invitation.link.expiration')) {
|
||||
return general_error_handler(that: $this, customErrorMessage: "Invitation already sent to $this->email and waiting for action.");
|
||||
if (is_null($user)) {
|
||||
$password = Str::password();
|
||||
$user = User::create([
|
||||
'name' => Str::of($this->email)->before('@'),
|
||||
'email' => $this->email,
|
||||
'password' => Hash::make($password),
|
||||
'force_password_reset' => true,
|
||||
]);
|
||||
$token = Crypt::encryptString("{$user->email}@@@$password");
|
||||
$link = route('auth.link', ['token' => $token]);
|
||||
}
|
||||
$invitation = TeamInvitation::whereEmail($this->email)->first();
|
||||
if (!is_null($invitation)) {
|
||||
$invitationValid = $invitation->isValid();
|
||||
if ($invitationValid) {
|
||||
return handleError(livewire: $this, customErrorMessage: "Pending invitation already exists for $this->email.");
|
||||
} else {
|
||||
$invitation->delete();
|
||||
}
|
||||
}
|
||||
|
||||
TeamInvitation::firstOrCreate([
|
||||
$invitation = TeamInvitation::firstOrCreate([
|
||||
'team_id' => currentTeam()->id,
|
||||
'uuid' => $uuid,
|
||||
'email' => $this->email,
|
||||
'role' => $this->role,
|
||||
'link' => $link,
|
||||
'via' => $isEmail ? 'email' : 'link',
|
||||
'via' => $sendEmail ? 'email' : 'link',
|
||||
]);
|
||||
if ($isEmail) {
|
||||
$user->first()->notify(new InvitationLink);
|
||||
if ($sendEmail) {
|
||||
$mail = new MailMessage();
|
||||
$mail->view('emails.invitation-link', [
|
||||
'team' => currentTeam()->name,
|
||||
'invitation_link' => $link,
|
||||
]);
|
||||
$mail->subject('You have been invited to ' . currentTeam()->name . ' on ' . config('app.name') . '.');
|
||||
send_user_an_email($mail, $this->email);
|
||||
$this->emit('success', 'Invitation sent via email successfully.');
|
||||
$this->emit('refreshInvitations');
|
||||
return;
|
||||
} else {
|
||||
$this->emit('success', 'Invitation link generated.');
|
||||
$this->emit('refreshInvitations');
|
||||
}
|
||||
$this->emit('refreshInvitations');
|
||||
} catch (\Throwable $e) {
|
||||
$error_message = $e->getMessage();
|
||||
if ($e->getCode() === '23505') {
|
||||
$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);
|
||||
}
|
||||
}
|
||||
|
||||
public function viaLink()
|
||||
{
|
||||
$this->generate_invite_link();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace App\Http\Livewire\Team;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Livewire\Component;
|
||||
|
||||
class Member extends Component
|
||||
@@ -24,6 +25,10 @@ class Member extends Component
|
||||
public function remove()
|
||||
{
|
||||
$this->member->teams()->detach(currentTeam());
|
||||
Cache::forget("team:{$this->member->id}");
|
||||
Cache::remember('team:' . $this->member->id, 3600, function() {
|
||||
return $this->member->teams()->first();
|
||||
});
|
||||
$this->emit('reloadWindow');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,8 +67,8 @@ class Create extends Component
|
||||
$this->emit('success', 'Connection is working. Tested with "ListObjectsV2" action.');
|
||||
$this->storage->save();
|
||||
return redirect()->route('team.storages.show', $this->storage->uuid);
|
||||
} catch (\Throwable $th) {
|
||||
return general_error_handler($th, $this);
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,8 +77,8 @@ class Create extends Component
|
||||
try {
|
||||
$this->storage->testConnection();
|
||||
return $this->emit('success', 'Connection is working. Tested with "ListObjectsV2" action.');
|
||||
} catch (\Throwable $th) {
|
||||
return general_error_handler($th, $this);
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,8 +32,8 @@ class Form extends Component
|
||||
try {
|
||||
$this->storage->testConnection();
|
||||
return $this->emit('success', 'Connection is working. Tested with "ListObjectsV2" action.');
|
||||
} catch (\Throwable $th) {
|
||||
return general_error_handler($th, $this);
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,8 +42,8 @@ class Form extends Component
|
||||
try {
|
||||
$this->storage->delete();
|
||||
return redirect()->route('team.storages.all');
|
||||
} catch (\Throwable $th) {
|
||||
return general_error_handler($th, $this);
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,8 +55,8 @@ class Form extends Component
|
||||
$this->emit('success', 'Connection is working. Tested with "ListObjectsV2" action.');
|
||||
$this->storage->save();
|
||||
$this->emit('success', 'Storage settings saved.');
|
||||
} catch (\Throwable $th) {
|
||||
return general_error_handler($th, $this);
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,9 +36,9 @@ class Upgrade extends Component
|
||||
}
|
||||
$this->showProgress = true;
|
||||
resolve(UpdateCoolify::class)(true);
|
||||
Toaster::success("Upgrading to {$this->latestVersion} version...");
|
||||
} catch (\Exception $e) {
|
||||
return general_error_handler(err: $e, that: $this);
|
||||
$this->emit('success', "Upgrading to {$this->latestVersion} version...");
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,10 +6,12 @@ use App\Jobs\SendConfirmationForWaitlistJob;
|
||||
use App\Models\User;
|
||||
use App\Models\Waitlist;
|
||||
use Livewire\Component;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class Index extends Component
|
||||
{
|
||||
public string $email;
|
||||
public int $users = 0;
|
||||
public int $waitingInLine = 0;
|
||||
|
||||
protected $rules = [
|
||||
@@ -22,6 +24,7 @@ class Index extends Component
|
||||
public function mount()
|
||||
{
|
||||
$this->waitingInLine = Waitlist::whereVerified(true)->count();
|
||||
$this->users = User::count();
|
||||
if (isDev()) {
|
||||
$this->email = 'waitlist@example.com';
|
||||
}
|
||||
@@ -44,14 +47,14 @@ class Index extends Component
|
||||
return;
|
||||
}
|
||||
$waitlist = Waitlist::create([
|
||||
'email' => $this->email,
|
||||
'email' => Str::lower($this->email),
|
||||
'type' => 'registration',
|
||||
]);
|
||||
|
||||
$this->emit('success', 'Check your email to verify your email address.');
|
||||
dispatch(new SendConfirmationForWaitlistJob($this->email, $waitlist->uuid));
|
||||
} catch (\Exception $e) {
|
||||
return general_error_handler(err: $e, that: $this);
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace App\Http\Middleware;
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class IsBoardingFlow
|
||||
{
|
||||
@@ -15,8 +16,11 @@ class IsBoardingFlow
|
||||
*/
|
||||
public function handle(Request $request, Closure $next): Response
|
||||
{
|
||||
ray()->showQueries()->color('orange');
|
||||
// ray()->showQueries()->color('orange');
|
||||
if (showBoarding() && !in_array($request->path(), allowedPathsForBoardingAccounts())) {
|
||||
if (Str::startsWith($request->path(), 'invitations')) {
|
||||
return $next($request);
|
||||
}
|
||||
return redirect('boarding');
|
||||
}
|
||||
return $next($request);
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace App\Http\Middleware;
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class IsSubscriptionValid
|
||||
{
|
||||
@@ -31,6 +32,9 @@ class IsSubscriptionValid
|
||||
if (!isSubscriptionActive() && !isSubscriptionOnGracePeriod()) {
|
||||
// ray('SubscriptionValid Middleware');
|
||||
if (!in_array($request->path(), allowedPathsForUnsubscribedAccounts())) {
|
||||
if (Str::startsWith($request->path(), 'invitations')) {
|
||||
return $next($request);
|
||||
}
|
||||
return redirect('subscription');
|
||||
} else {
|
||||
return $next($request);
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Models\Application;
|
||||
use App\Models\ApplicationPreview;
|
||||
use App\Notifications\Application\StatusChanged;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldBeUnique;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class ApplicationContainerStatusJob implements ShouldQueue, ShouldBeUnique
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
public string $containerName;
|
||||
|
||||
public function __construct(
|
||||
public Application $application,
|
||||
public int $pullRequestId = 0)
|
||||
{
|
||||
$this->containerName = generateApplicationContainerName($application->uuid, $pullRequestId);
|
||||
}
|
||||
|
||||
public function uniqueId(): string
|
||||
{
|
||||
return $this->containerName;
|
||||
}
|
||||
|
||||
public function handle(): void
|
||||
{
|
||||
try {
|
||||
$status = getApplicationContainerStatus(application: $this->application);
|
||||
if ($this->application->status === 'running' && $status !== 'running') {
|
||||
// $this->application->environment->project->team->notify(new StatusChanged($this->application));
|
||||
}
|
||||
|
||||
if ($this->pullRequestId !== 0) {
|
||||
$preview = ApplicationPreview::findPreviewByApplicationAndPullId($this->application->id, $this->pullRequestId);
|
||||
$preview->status = $status;
|
||||
$preview->save();
|
||||
} else {
|
||||
$this->application->status = $status;
|
||||
$this->application->save();
|
||||
}
|
||||
} catch (\Exception $th) {
|
||||
ray($th->getMessage());
|
||||
throw $th;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,10 +17,10 @@ use App\Notifications\Application\DeploymentSuccess;
|
||||
use App\Traits\ExecuteRemoteCommand;
|
||||
use Exception;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\Middleware\WithoutOverlapping;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Str;
|
||||
@@ -29,7 +29,7 @@ use Symfony\Component\Yaml\Yaml;
|
||||
use Throwable;
|
||||
use Visus\Cuid2\Cuid2;
|
||||
|
||||
class ApplicationDeploymentJob implements ShouldQueue
|
||||
class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, ExecuteRemoteCommand;
|
||||
|
||||
@@ -37,6 +37,7 @@ class ApplicationDeploymentJob implements ShouldQueue
|
||||
|
||||
private int $application_deployment_queue_id;
|
||||
|
||||
private bool $newVersionIsHealthy = false;
|
||||
private ApplicationDeploymentQueue $application_deployment_queue;
|
||||
private Application $application;
|
||||
private string $deployment_uuid;
|
||||
@@ -47,7 +48,6 @@ class ApplicationDeploymentJob implements ShouldQueue
|
||||
private GithubApp|GitlabApp $source;
|
||||
private StandaloneDocker|SwarmDocker $destination;
|
||||
private Server $server;
|
||||
private string $private_key_location;
|
||||
private ApplicationPreview|null $preview = null;
|
||||
|
||||
private string $container_name;
|
||||
@@ -89,15 +89,15 @@ class ApplicationDeploymentJob implements ShouldQueue
|
||||
$this->build_workdir = "{$this->workdir}" . rtrim($this->application->base_directory, '/');
|
||||
$this->is_debug_enabled = $this->application->settings->is_debug_enabled;
|
||||
|
||||
$this->container_name = generateApplicationContainerName($this->application->uuid, $this->pull_request_id);
|
||||
$this->private_key_location = save_private_key_for_server($this->server);
|
||||
$this->container_name = generateApplicationContainerName($this->application);
|
||||
savePrivateKeyToFs($this->server);
|
||||
$this->saved_outputs = collect();
|
||||
|
||||
// Set preview fqdn
|
||||
if ($this->pull_request_id !== 0) {
|
||||
$this->preview = ApplicationPreview::findPreviewByApplicationAndPullId($this->application->id, $this->pull_request_id);
|
||||
if ($this->application->fqdn) {
|
||||
$preview_fqdn = data_get($this->preview, 'fqdn');
|
||||
$preview_fqdn = getFqdnWithoutPort(data_get($this->preview, 'fqdn'));
|
||||
$template = $this->application->preview_url_template;
|
||||
$url = Url::fromString($this->application->fqdn);
|
||||
$host = $url->getHost();
|
||||
@@ -120,6 +120,9 @@ class ApplicationDeploymentJob implements ShouldQueue
|
||||
if ($containers->count() > 0) {
|
||||
$this->currently_running_container_name = data_get($containers[0], 'Names');
|
||||
}
|
||||
if ($this->pull_request_id !== 0 && $this->pull_request_id !== null) {
|
||||
$this->currently_running_container_name = $this->container_name;
|
||||
}
|
||||
$this->application_deployment_queue->update([
|
||||
'status' => ApplicationDeploymentStatus::IN_PROGRESS->value,
|
||||
]);
|
||||
@@ -133,7 +136,9 @@ class ApplicationDeploymentJob implements ShouldQueue
|
||||
$this->deploy();
|
||||
}
|
||||
}
|
||||
if ($this->application->fqdn) dispatch(new ProxyStartJob($this->server));
|
||||
if ($this->server->isProxyShouldRun()) {
|
||||
dispatch(new ContainerStatusJob($this->server));
|
||||
}
|
||||
$this->next(ApplicationDeploymentStatus::FINISHED->value);
|
||||
} catch (Exception $e) {
|
||||
ray($e);
|
||||
@@ -162,6 +167,54 @@ class ApplicationDeploymentJob implements ShouldQueue
|
||||
);
|
||||
}
|
||||
}
|
||||
private function deploy_docker_compose()
|
||||
{
|
||||
$dockercompose_base64 = base64_encode($this->application->dockercompose);
|
||||
$this->execute_remote_command(
|
||||
[
|
||||
"echo 'Starting deployment of {$this->application->name}.'"
|
||||
],
|
||||
);
|
||||
$this->prepare_builder_image();
|
||||
$this->execute_remote_command(
|
||||
[
|
||||
executeInDocker($this->deployment_uuid, "echo '$dockercompose_base64' | base64 -d > $this->workdir/docker-compose.yaml")
|
||||
],
|
||||
);
|
||||
$this->build_image_name = Str::lower("{$this->application->git_repository}:build");
|
||||
$this->production_image_name = Str::lower("{$this->application->uuid}:latest");
|
||||
$this->save_environment_variables();
|
||||
$containers = getCurrentApplicationContainerStatus($this->application->destination->server, $this->application->id);
|
||||
if ($containers->count() > 0) {
|
||||
foreach ($containers as $container) {
|
||||
$containerName = data_get($container, 'Names');
|
||||
if ($containerName) {
|
||||
instant_remote_process(
|
||||
["docker rm -f {$containerName}"],
|
||||
$this->application->destination->server
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->execute_remote_command(
|
||||
["echo -n 'Starting services (could take a while)...'"],
|
||||
[executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} up -d"), "hidden" => true],
|
||||
);
|
||||
}
|
||||
private function save_environment_variables()
|
||||
{
|
||||
$envs = collect([]);
|
||||
foreach ($this->application->environment_variables as $env) {
|
||||
$envs->push($env->key . '=' . $env->value);
|
||||
}
|
||||
$envs_base64 = base64_encode($envs->implode("\n"));
|
||||
$this->execute_remote_command(
|
||||
[
|
||||
executeInDocker($this->deployment_uuid, "echo '$envs_base64' | base64 -d > $this->workdir/.env")
|
||||
],
|
||||
);
|
||||
}
|
||||
private function deploy_simple_dockerfile()
|
||||
{
|
||||
$dockerfile_base64 = base64_encode($this->application->dockerfile);
|
||||
@@ -173,7 +226,7 @@ class ApplicationDeploymentJob implements ShouldQueue
|
||||
$this->prepare_builder_image();
|
||||
$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");
|
||||
@@ -231,9 +284,20 @@ class ApplicationDeploymentJob implements ShouldQueue
|
||||
|
||||
private function rolling_update()
|
||||
{
|
||||
$this->start_by_compose_file();
|
||||
$this->health_check();
|
||||
$this->stop_running_container();
|
||||
if (count($this->application->ports_mappings_array) > 0){
|
||||
$this->execute_remote_command(
|
||||
["echo -n 'Application has ports mapped to the host system, rolling update is not supported. Stopping current container.'"],
|
||||
);
|
||||
$this->stop_running_container(force: true);
|
||||
$this->start_by_compose_file();
|
||||
} else {
|
||||
$this->execute_remote_command(
|
||||
["echo -n 'Rolling update started.'"],
|
||||
);
|
||||
$this->start_by_compose_file();
|
||||
$this->health_check();
|
||||
$this->stop_running_container();
|
||||
}
|
||||
}
|
||||
private function health_check()
|
||||
{
|
||||
@@ -242,7 +306,7 @@ class ApplicationDeploymentJob implements ShouldQueue
|
||||
$counter = 0;
|
||||
$this->execute_remote_command(
|
||||
[
|
||||
"echo 'Waiting for health check to pass on the new version of your application.'"
|
||||
"echo 'Waiting for healthcheck to pass on the new version of your application.'"
|
||||
],
|
||||
);
|
||||
while ($counter < $this->application->health_check_retries) {
|
||||
@@ -259,15 +323,20 @@ class ApplicationDeploymentJob implements ShouldQueue
|
||||
);
|
||||
$this->execute_remote_command(
|
||||
[
|
||||
"echo 'New version health check status: {$this->saved_outputs->get('health_check')}'"
|
||||
"echo 'New version healthcheck status: {$this->saved_outputs->get('health_check')}'"
|
||||
],
|
||||
);
|
||||
if (Str::of($this->saved_outputs->get('health_check'))->contains('healthy')) {
|
||||
$this->newVersionIsHealthy = true;
|
||||
$this->execute_remote_command(
|
||||
[
|
||||
"echo 'New version of your application is healthy.'"
|
||||
],
|
||||
[
|
||||
"echo 'Rolling update completed.'"
|
||||
],
|
||||
);
|
||||
$this->application->update(['status' => 'running']);
|
||||
break;
|
||||
}
|
||||
$counter++;
|
||||
@@ -294,7 +363,11 @@ class ApplicationDeploymentJob implements ShouldQueue
|
||||
// $this->generate_build_env_variables();
|
||||
// $this->add_build_env_variables_to_dockerfile();
|
||||
$this->build_image();
|
||||
$this->rolling_update();
|
||||
$this->stop_running_container();
|
||||
$this->execute_remote_command(
|
||||
["echo -n 'Starting preview deployment.'"],
|
||||
[executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} up -d >/dev/null"), "hidden" => true],
|
||||
);
|
||||
}
|
||||
|
||||
private function prepare_builder_image()
|
||||
@@ -315,16 +388,12 @@ class ApplicationDeploymentJob implements ShouldQueue
|
||||
"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()
|
||||
{
|
||||
@@ -336,7 +405,7 @@ class ApplicationDeploymentJob implements ShouldQueue
|
||||
$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,
|
||||
"save" => "git_commit_sha"
|
||||
],
|
||||
@@ -363,13 +432,13 @@ class ApplicationDeploymentJob implements ShouldQueue
|
||||
$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);
|
||||
|
||||
$commands->push($this->execute_in_builder($git_clone_command));
|
||||
$commands->push(executeInDocker($this->deployment_uuid, $git_clone_command));
|
||||
} else {
|
||||
$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) {
|
||||
$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(' && ');
|
||||
}
|
||||
@@ -379,10 +448,10 @@ class ApplicationDeploymentJob implements ShouldQueue
|
||||
$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);
|
||||
$commands = collect([
|
||||
$this->execute_in_builder("mkdir -p /root/.ssh"),
|
||||
$this->execute_in_builder("echo '{$private_key}' | base64 -d > /root/.ssh/id_rsa"),
|
||||
$this->execute_in_builder("chmod 600 /root/.ssh/id_rsa"),
|
||||
$this->execute_in_builder($git_clone_command)
|
||||
executeInDocker($this->deployment_uuid, "mkdir -p /root/.ssh"),
|
||||
executeInDocker($this->deployment_uuid, "echo '{$private_key}' | base64 -d > /root/.ssh/id_rsa"),
|
||||
executeInDocker($this->deployment_uuid, "chmod 600 /root/.ssh/id_rsa"),
|
||||
executeInDocker($this->deployment_uuid, $git_clone_command)
|
||||
]);
|
||||
return $commands->implode(' && ');
|
||||
}
|
||||
@@ -405,7 +474,7 @@ class ApplicationDeploymentJob implements ShouldQueue
|
||||
private function cleanup_git()
|
||||
{
|
||||
$this->execute_remote_command(
|
||||
[$this->execute_in_builder("rm -fr {$this->workdir}/.git")],
|
||||
[executeInDocker($this->deployment_uuid, "rm -fr {$this->workdir}/.git")],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -416,8 +485,8 @@ class ApplicationDeploymentJob implements ShouldQueue
|
||||
"echo -n 'Generating nixpacks configuration.'",
|
||||
],
|
||||
[$this->nixpacks_build_cmd()],
|
||||
[$this->execute_in_builder("cp {$this->workdir}/.nixpacks/Dockerfile {$this->workdir}/Dockerfile")],
|
||||
[$this->execute_in_builder("rm -f {$this->workdir}/.nixpacks/Dockerfile")]
|
||||
[executeInDocker($this->deployment_uuid, "cp {$this->workdir}/.nixpacks/Dockerfile {$this->workdir}/Dockerfile")],
|
||||
[executeInDocker($this->deployment_uuid, "rm -f {$this->workdir}/.nixpacks/Dockerfile")]
|
||||
);
|
||||
}
|
||||
|
||||
@@ -435,7 +504,7 @@ class ApplicationDeploymentJob implements ShouldQueue
|
||||
$nixpacks_command .= " --install-cmd \"{$this->application->install_command}\"";
|
||||
}
|
||||
$nixpacks_command .= " {$this->workdir}";
|
||||
return $this->execute_in_builder($nixpacks_command);
|
||||
return executeInDocker($this->deployment_uuid, $nixpacks_command);
|
||||
}
|
||||
|
||||
private function generate_env_variables()
|
||||
@@ -470,7 +539,7 @@ class ApplicationDeploymentJob implements ShouldQueue
|
||||
'container_name' => $this->container_name,
|
||||
'restart' => RESTART_MODE,
|
||||
'environment' => $environment_variables,
|
||||
'labels' => $this->set_labels_for_applications(),
|
||||
'labels' => generateLabelsApplication($this->application, $this->preview),
|
||||
'expose' => $ports,
|
||||
'networks' => [
|
||||
$this->destination->network,
|
||||
@@ -498,7 +567,7 @@ class ApplicationDeploymentJob implements ShouldQueue
|
||||
$this->destination->network => [
|
||||
'external' => true,
|
||||
'name' => $this->destination->network,
|
||||
'attachable' => true,
|
||||
'attachable' => true
|
||||
]
|
||||
]
|
||||
];
|
||||
@@ -513,7 +582,7 @@ class ApplicationDeploymentJob implements ShouldQueue
|
||||
}
|
||||
$this->docker_compose = Yaml::dump($docker_compose, 10);
|
||||
$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()
|
||||
@@ -572,82 +641,24 @@ class ApplicationDeploymentJob implements ShouldQueue
|
||||
return $environment_variables->all();
|
||||
}
|
||||
|
||||
private function set_labels_for_applications()
|
||||
{
|
||||
$labels = [];
|
||||
$labels[] = 'coolify.managed=true';
|
||||
$labels[] = 'coolify.version=' . config('version');
|
||||
$labels[] = 'coolify.applicationId=' . $this->application->id;
|
||||
$labels[] = 'coolify.type=application';
|
||||
$labels[] = 'coolify.name=' . $this->application->name;
|
||||
if ($this->pull_request_id !== 0) {
|
||||
$labels[] = 'coolify.pullRequestId=' . $this->pull_request_id;
|
||||
}
|
||||
if ($this->application->fqdn) {
|
||||
if ($this->pull_request_id !== 0) {
|
||||
$domains = Str::of(data_get($this->preview, 'fqdn'))->explode(',');
|
||||
} else {
|
||||
$domains = Str::of(data_get($this->application, 'fqdn'))->explode(',');
|
||||
}
|
||||
if ($this->application->destination->server->proxy->type === ProxyTypes::TRAEFIK_V2->value) {
|
||||
$labels[] = 'traefik.enable=true';
|
||||
foreach ($domains as $domain) {
|
||||
$url = Url::fromString($domain);
|
||||
$host = $url->getHost();
|
||||
$path = $url->getPath();
|
||||
$schema = $url->getScheme();
|
||||
$slug = Str::slug($host . $path);
|
||||
|
||||
$http_label = "{$this->container_name}-{$slug}-http";
|
||||
$https_label = "{$this->container_name}-{$slug}-https";
|
||||
|
||||
if ($schema === 'https') {
|
||||
// Set labels for https
|
||||
$labels[] = "traefik.http.routers.{$https_label}.rule=Host(`{$host}`) && PathPrefix(`{$path}`)";
|
||||
$labels[] = "traefik.http.routers.{$https_label}.entryPoints=https";
|
||||
$labels[] = "traefik.http.routers.{$https_label}.middlewares=gzip";
|
||||
if ($path !== '/') {
|
||||
$labels[] = "traefik.http.routers.{$https_label}.middlewares={$https_label}-stripprefix";
|
||||
$labels[] = "traefik.http.middlewares.{$https_label}-stripprefix.stripprefix.prefixes={$path}";
|
||||
}
|
||||
|
||||
$labels[] = "traefik.http.routers.{$https_label}.tls=true";
|
||||
$labels[] = "traefik.http.routers.{$https_label}.tls.certresolver=letsencrypt";
|
||||
|
||||
// Set labels for http (redirect to https)
|
||||
$labels[] = "traefik.http.routers.{$http_label}.rule=Host(`{$host}`) && PathPrefix(`{$path}`)";
|
||||
$labels[] = "traefik.http.routers.{$http_label}.entryPoints=http";
|
||||
if ($this->application->settings->is_force_https_enabled) {
|
||||
$labels[] = "traefik.http.routers.{$http_label}.middlewares=redirect-to-https";
|
||||
}
|
||||
} else {
|
||||
// Set labels for http
|
||||
$labels[] = "traefik.http.routers.{$http_label}.rule=Host(`{$host}`) && PathPrefix(`{$path}`)";
|
||||
$labels[] = "traefik.http.routers.{$http_label}.entryPoints=http";
|
||||
$labels[] = "traefik.http.routers.{$http_label}.middlewares=gzip";
|
||||
if ($path !== '/') {
|
||||
$labels[] = "traefik.http.routers.{$http_label}.middlewares={$http_label}-stripprefix";
|
||||
$labels[] = "traefik.http.middlewares.{$http_label}-stripprefix.stripprefix.prefixes={$path}";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $labels;
|
||||
}
|
||||
|
||||
private function generate_healthcheck_commands()
|
||||
{
|
||||
if ($this->application->dockerfile || $this->application->build_pack === 'dockerfile') {
|
||||
// TODO: disabled HC because there are several ways to hc a simple docker image, hard to figure out a good way. Like some docker images (pocketbase) does not have curl.
|
||||
return 'exit 0';
|
||||
}
|
||||
if (!$this->application->health_check_port) {
|
||||
$this->application->health_check_port = $this->application->ports_exposes_array[0];
|
||||
$health_check_port = $this->application->ports_exposes_array[0];
|
||||
} else {
|
||||
$health_check_port = $this->application->health_check_port;
|
||||
}
|
||||
if ($this->application->health_check_path) {
|
||||
$generated_healthchecks_commands = [
|
||||
"curl -s -X {$this->application->health_check_method} -f {$this->application->health_check_scheme}://{$this->application->health_check_host}:{$this->application->health_check_port}{$this->application->health_check_path} > /dev/null"
|
||||
"curl -s -X {$this->application->health_check_method} -f {$this->application->health_check_scheme}://{$this->application->health_check_host}:{$health_check_port}{$this->application->health_check_path} > /dev/null"
|
||||
];
|
||||
} else {
|
||||
$generated_healthchecks_commands = [
|
||||
"curl -s -X {$this->application->health_check_method} -f {$this->application->health_check_scheme}://{$this->application->health_check_host}:{$this->application->health_check_port}/"
|
||||
"curl -s -X {$this->application->health_check_method} -f {$this->application->health_check_scheme}://{$this->application->health_check_host}:{$health_check_port}/"
|
||||
];
|
||||
}
|
||||
return implode(' ', $generated_healthchecks_commands);
|
||||
@@ -661,7 +672,7 @@ class ApplicationDeploymentJob implements ShouldQueue
|
||||
|
||||
if ($this->application->settings->is_static) {
|
||||
$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}
|
||||
@@ -688,37 +699,44 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
||||
}");
|
||||
$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 {
|
||||
$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
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
private function stop_running_container()
|
||||
private function stop_running_container(bool $force = false)
|
||||
{
|
||||
if ($this->currently_running_container_name) {
|
||||
$this->execute_remote_command(
|
||||
["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],
|
||||
);
|
||||
if ($this->newVersionIsHealthy || $force) {
|
||||
$this->execute_remote_command(
|
||||
["echo -n 'Removing old version of your application.'"],
|
||||
[executeInDocker($this->deployment_uuid, "docker rm -f $this->currently_running_container_name >/dev/null 2>&1"), "hidden" => true],
|
||||
);
|
||||
} else {
|
||||
$this->execute_remote_command(
|
||||
["echo -n 'New version is not healthy, rolling back to the old version.'"],
|
||||
[executeInDocker($this->deployment_uuid, "docker rm -f $this->container_name >/dev/null 2>&1"), "hidden" => true],
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function start_by_compose_file()
|
||||
{
|
||||
$this->execute_remote_command(
|
||||
["echo -n 'Rolling update started.'"],
|
||||
[$this->execute_in_builder("docker compose --project-directory {$this->workdir} up -d >/dev/null"), "hidden" => true],
|
||||
["echo -n 'Starting application (could take a while).'"],
|
||||
[executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} up -d >/dev/null"), "hidden" => true],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -741,7 +759,7 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
||||
private function add_build_env_variables_to_dockerfile()
|
||||
{
|
||||
$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"));
|
||||
|
||||
@@ -750,7 +768,7 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
||||
}
|
||||
$dockerfile_base64 = base64_encode($dockerfile->implode("\n"));
|
||||
$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
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -6,12 +6,13 @@ use App\Enums\ProcessStatus;
|
||||
use App\Models\Application;
|
||||
use App\Models\ApplicationPreview;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class ApplicationPullRequestUpdateJob implements ShouldQueue
|
||||
class ApplicationPullRequestUpdateJob implements ShouldQueue, ShouldBeEncrypted
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
@@ -57,7 +58,7 @@ class ApplicationPullRequestUpdateJob implements ShouldQueue
|
||||
} else {
|
||||
$this->create_comment();
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
} catch (\Throwable $e) {
|
||||
ray($e);
|
||||
throw $e;
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user