diff --git a/app/Actions/Proxy/CheckProxySettingsInSync.php b/app/Actions/Proxy/CheckProxySettingsInSync.php index 038a1bb8c..410ebcfba 100644 --- a/app/Actions/Proxy/CheckProxySettingsInSync.php +++ b/app/Actions/Proxy/CheckProxySettingsInSync.php @@ -2,34 +2,32 @@ namespace App\Actions\Proxy; -use App\Enums\ActivityTypes; use App\Enums\ProxyTypes; use App\Models\Server; -use Spatie\Activitylog\Models\Activity; -use Symfony\Component\Yaml\Yaml; +use Illuminate\Support\Str; class CheckProxySettingsInSync { public function __invoke(Server $server) { - // @TODO What is the mechanism to make sure setting in sync? - $folder_name = match ($server->extra_attributes->proxy) { - ProxyTypes::TRAEFIK_V2->value => 'proxy', - }; - - $container_name = 'coolify-proxy'; - + $proxy_path = config('coolify.proxy_config_path'); $output = instantRemoteProcess([ - // Folder exists, in ~/projects/ - 'if [ -d "projects/' . $folder_name . '" ]; then echo "true"; else echo "false"; fi', - // Container of name is running - <</dev/null)" == "true" ]] && echo "true" || echo "false" - EOT, - ], $server); - - return collect( - explode(PHP_EOL, $output) - )->every(fn ($output) => $output === 'true'); + "cat $proxy_path/docker-compose.yml", + ], $server, false); + if (is_null($output)) { + $final_output = Str::of(getProxyConfiguration($server))->trim(); + } else { + $final_output = Str::of($output)->trim(); + } + $docker_compose_yml_base64 = base64_encode($final_output); + $server->extra_attributes->last_saved_proxy_settings = Str::of($docker_compose_yml_base64)->pipe('md5')->value; + $server->save(); + if (is_null($output)) { + instantRemoteProcess([ + "mkdir -p $proxy_path", + "echo '$docker_compose_yml_base64' | base64 -d > $proxy_path/docker-compose.yml", + ], $server); + } + return $final_output; } } diff --git a/app/Actions/Proxy/InstallProxy.php b/app/Actions/Proxy/InstallProxy.php index 42864b415..2cbd4103d 100644 --- a/app/Actions/Proxy/InstallProxy.php +++ b/app/Actions/Proxy/InstallProxy.php @@ -5,103 +5,63 @@ use App\Enums\ActivityTypes; use App\Enums\ProxyTypes; use App\Models\Server; +use Illuminate\Support\Collection; use Spatie\Activitylog\Models\Activity; -use Symfony\Component\Yaml\Yaml; +use Illuminate\Support\Str; class InstallProxy { + public Collection $networks; + public function __invoke(Server $server): Activity { - $docker_compose_yml_base64 = base64_encode( - $this->getDockerComposeContents() - ); + $proxy_path = config('coolify.proxy_config_path'); + + $networks = collect($server->standaloneDockers)->map(function ($docker) { + return $docker['network']; + })->unique(); + if ($networks->count() === 0) { + $this->networks = collect(['coolify']); + } + $create_networks_command = $this->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 = instantRemoteProcess([ + "cat $proxy_path/docker-compose.yml", + ], $server, false); + if (is_null($configuration)) { + $configuration = Str::of(getProxyConfiguration($server))->trim(); + } else { + $configuration = Str::of($configuration)->trim(); + } + $docker_compose_yml_base64 = base64_encode($configuration); + $server->extra_attributes->last_applied_proxy_settings = Str::of($docker_compose_yml_base64)->pipe('md5')->value; + $server->save(); $env_file_base64 = base64_encode( $this->getEnvContents() ); - $activity = remoteProcess([ - "docker network ls --format '{{.Name}}' | grep '^coolify$' || docker network create coolify", - 'mkdir -p projects', - 'mkdir -p projects/proxy', - 'mkdir -p projects/proxy/letsencrypt', - 'cd projects/proxy', - "echo '$docker_compose_yml_base64' | base64 -d > docker-compose.yml", - "echo '$env_file_base64' | base64 -d > .env", + ...$create_networks_command, + "echo 'Docker networks created...'", + "mkdir -p $proxy_path", + "cd $proxy_path", + "echo '$docker_compose_yml_base64' | base64 -d > $proxy_path/docker-compose.yml", + "echo '$env_file_base64' | base64 -d > $proxy_path/.env", + "echo 'Docker compose file created...'", + "echo 'Pulling docker image...'", + 'docker compose pull -q', + "echo 'Stopping proxy...'", + 'docker compose down -v --remove-orphans', + "echo 'Starting proxy...'", 'docker compose up -d --remove-orphans', - 'docker ps', + "echo 'Proxy installed successfully...'" ], $server, ActivityTypes::INLINE->value); - // Persist to Database - $server->extra_attributes->proxy = ProxyTypes::TRAEFIK_V2->value; - $server->save(); - return $activity; } - protected function getDockerComposeContents() - { - return Yaml::dump($this->getComposeData()); - } - - /** - * @return array - */ - protected function getComposeData(): array - { - $cwd = config('app.env') === 'local' - ? config('proxy.project_path_on_host') . '/_testing_hosts/host_2_proxy' - : '.'; - - return [ - "version" => "3.7", - "networks" => [ - "coolify" => [ - "external" => true, - ], - ], - "services" => [ - "traefik" => [ - "container_name" => "coolify-proxy", - "image" => "traefik:v2.10", - "restart" => "always", - "extra_hosts" => [ - "host.docker.internal:host-gateway", - ], - "networks" => [ - "coolify", - ], - "ports" => [ - "80:80", - "443:443", - "8080:8080", - ], - "volumes" => [ - "/var/run/docker.sock:/var/run/docker.sock:ro", - "{$cwd}/letsencrypt:/letsencrypt", - "{$cwd}/traefik.auth:/auth/traefik.auth", - ], - "command" => [ - "--api.dashboard=true", - "--api.insecure=true", - "--entrypoints.http.address=:80", - "--entrypoints.https.address=:443", - "--providers.docker=true", - "--providers.docker.exposedbydefault=false", - ], - "labels" => [ - "traefik.enable=true", - "traefik.http.routers.traefik.entrypoints=http", - 'traefik.http.routers.traefik.rule=Host(`${TRAEFIK_DASHBOARD_HOST}`)', - "traefik.http.routers.traefik.service=api@internal", - "traefik.http.services.traefik.loadbalancer.server.port=8080", - "traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https", - ], - ], - ], - ]; - } - protected function getEnvContents() { $data = [ diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index 04e3415d5..a636e1c5d 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -4,6 +4,7 @@ use App\Jobs\ContainerStatusJob; use App\Jobs\DockerCleanupDanglingImagesJob; +use App\Jobs\ProxyCheckJob; use Illuminate\Console\Scheduling\Schedule; use Illuminate\Foundation\Console\Kernel as ConsoleKernel; @@ -16,6 +17,7 @@ protected function schedule(Schedule $schedule): void { $schedule->job(new ContainerStatusJob)->everyMinute(); $schedule->job(new DockerCleanupDanglingImagesJob)->everyMinute(); + // $schedule->job(new ProxyCheckJob)->everyMinute(); } /** diff --git a/app/Http/Livewire/Server/Proxy.php b/app/Http/Livewire/Server/Proxy.php index 16e92cf48..ada0a6457 100644 --- a/app/Http/Livewire/Server/Proxy.php +++ b/app/Http/Livewire/Server/Proxy.php @@ -4,7 +4,8 @@ use App\Actions\Proxy\CheckProxySettingsInSync; use App\Actions\Proxy\InstallProxy; -use App\Enums\ActivityTypes; +use App\Enums\ProxyTypes; +use Illuminate\Support\Str; use App\Models\Server; use Livewire\Component; @@ -12,34 +13,60 @@ class Proxy extends Component { public Server $server; - protected string $selectedProxy = ''; - + public ProxyTypes $selectedProxy = ProxyTypes::TRAEFIK_V2; public $is_proxy_installed; public $is_check_proxy_complete = false; - public $is_proxy_settings_in_sync = false; + public $proxy_settings = null; - public function mount(Server $server) - { - $this->server = $server; - } - - public function runInstallProxy() + public function installProxy() { + $this->saveConfiguration($this->server); $activity = resolve(InstallProxy::class)($this->server); - $this->emit('newMonitorActivity', $activity->id); } + public function proxyStatus() + { + $this->server->extra_attributes->proxy_status = checkContainerStatus(server: $this->server, container_id: 'coolify-proxy'); + $this->server->save(); + } + public function setProxy() + { + $this->server->extra_attributes->proxy_type = $this->selectedProxy->value; + $this->server->extra_attributes->proxy_status = 'exited'; + $this->server->save(); + } + public function stopProxy() + { + instantRemoteProcess([ + "docker rm -f coolify-proxy", + ], $this->server); + $this->server->extra_attributes->proxy_status = 'exited'; + $this->server->save(); + } + public function saveConfiguration() + { + try { + $proxy_path = config('coolify.proxy_config_path'); + $this->proxy_settings = Str::of($this->proxy_settings)->trim(); + $docker_compose_yml_base64 = base64_encode($this->proxy_settings); + $this->server->extra_attributes->last_saved_proxy_settings = Str::of($docker_compose_yml_base64)->pipe('md5')->value; + $this->server->save(); + instantRemoteProcess([ + "echo '$docker_compose_yml_base64' | base64 -d > $proxy_path/docker-compose.yml", + ], $this->server); + } catch (\Exception $e) { + return generalErrorHandler($e); + } + } public function checkProxySettingsInSync() { - $this->is_proxy_settings_in_sync = resolve(CheckProxySettingsInSync::class)($this->server); - - $this->is_check_proxy_complete = true; - } - - public function render() - { - return view('livewire.server.proxy'); + try { + $this->proxy_settings = resolve(CheckProxySettingsInSync::class)($this->server); + $this->is_check_proxy_complete = true; + } catch (\Exception $e) { + return generalErrorHandler($e); + } } } diff --git a/app/Jobs/ContainerStatusJob.php b/app/Jobs/ContainerStatusJob.php index bc05d6e0b..de3660de4 100644 --- a/app/Jobs/ContainerStatusJob.php +++ b/app/Jobs/ContainerStatusJob.php @@ -67,9 +67,7 @@ protected function checkContainerStatus() return; } if ($application->destination->server) { - $container = instantRemoteProcess(["docker inspect --format '{{json .State}}' {$this->container_id}"], $application->destination->server); - $container = formatDockerCmdOutputToJson($container); - $application->status = $container[0]['Status']; + $application->status = checkContainerStatus(server: $application->destination->server, container_id: $this->container_id); $application->save(); } } diff --git a/app/Jobs/DeployApplicationJob.php b/app/Jobs/DeployApplicationJob.php index 0e1ad11b5..31159d2df 100644 --- a/app/Jobs/DeployApplicationJob.php +++ b/app/Jobs/DeployApplicationJob.php @@ -69,7 +69,6 @@ public function __construct( protected function stopRunningContainer() { $this->executeNow([ - "echo -n 'Removing old instance... '", $this->execute_in_builder("docker rm -f {$this->application->uuid} >/dev/null 2>&1"), "echo 'Done.'", diff --git a/app/Jobs/ProxyCheckJob.php b/app/Jobs/ProxyCheckJob.php new file mode 100755 index 000000000..4c7ce12e6 --- /dev/null +++ b/app/Jobs/ProxyCheckJob.php @@ -0,0 +1,45 @@ +get(); + + foreach ($servers as $server) { + $status = checkContainerStatus(server: $server, container_id: $container_name); + if ($status === 'running') { + continue; + } + resolve(InstallProxy::class)($server); + } + } catch (\Throwable $th) { + //throw $th; + } + } +} diff --git a/bootstrap/helpers.php b/bootstrap/helpers.php index 61996bb21..085604cd5 100644 --- a/bootstrap/helpers.php +++ b/bootstrap/helpers.php @@ -40,7 +40,7 @@ function generalErrorHandler(\Throwable $e, $that = null, $isJson = false) 'error' => $error->getMessage(), ]); } else { - dump($error); + // dump($error); } } } @@ -165,10 +165,9 @@ function instantRemoteProcess(array $command, Server $server, $throwError = true $exitCode = $process->exitCode(); if ($exitCode !== 0) { if (!$throwError) { - return false; + return null; } - Log::error($process->errorOutput()); - throw new \RuntimeException('There was an error running the command.'); + throw new \RuntimeException($process->errorOutput()); } return $output; } @@ -196,6 +195,7 @@ function generateRandomName() use Lcobucci\JWT\Signer\Key\InMemory; use Lcobucci\JWT\Signer\Rsa\Sha256; use Lcobucci\JWT\Token\Builder; +use Symfony\Component\Yaml\Yaml; if (!function_exists('generate_github_installation_token')) { function generate_github_installation_token(GithubApp $source) @@ -244,3 +244,73 @@ function getParameters() return Route::current()->parameters(); } } +if (!function_exists('checkContainerStatus')) { + function checkContainerStatus(Server $server, string $container_id, bool $throwError = false) + { + $container = instantRemoteProcess(["docker inspect --format '{{json .State}}' {$container_id}"], $server, $throwError); + if (!$container) { + return 'exited'; + } + $container = formatDockerCmdOutputToJson($container); + return $container[0]['Status']; + } +} +if (!function_exists('getProxyConfiguration')) { + function getProxyConfiguration(Server $server) + { + $proxy_config_path = config('coolify.proxy_config_path'); + $networks = collect($server->standaloneDockers)->map(function ($docker) { + return $docker['network']; + })->unique(); + if ($networks->count() === 0) { + $networks = collect(['coolify']); + } + $array_of_networks = collect([]); + $networks->map(function ($network) use ($array_of_networks) { + $array_of_networks[$network] = [ + "external" => true, + ]; + }); + return Yaml::dump([ + "version" => "3.8", + "networks" => $array_of_networks->toArray(), + "services" => [ + "traefik" => [ + "container_name" => "coolify-proxy", # Do not modify this! You will break everything! + "image" => "traefik:v2.10", + "restart" => "always", + "extra_hosts" => [ + "host.docker.internal:host-gateway", + ], + "networks" => $networks->toArray(), # Do not modify this! You will break everything! + "ports" => [ + "80:80", + "443:443", + "8080:8080", + ], + "volumes" => [ + "/var/run/docker.sock:/var/run/docker.sock:ro", + "{$proxy_config_path}/letsencrypt:/letsencrypt", # Do not modify this! You will break everything! + "{$proxy_config_path}/traefik.auth:/auth/traefik.auth", # Do not modify this! You will break everything! + ], + "command" => [ + "--api.dashboard=true", + "--api.insecure=true", + "--entrypoints.http.address=:80", + "--entrypoints.https.address=:443", + "--providers.docker=true", + "--providers.docker.exposedbydefault=false", + ], + "labels" => [ + "traefik.enable=true", # Do not modify this! You will break everything! + "traefik.http.routers.traefik.entrypoints=http", + 'traefik.http.routers.traefik.rule=Host(`${TRAEFIK_DASHBOARD_HOST}`)', + "traefik.http.routers.traefik.service=api@internal", + "traefik.http.services.traefik.loadbalancer.server.port=8080", + "traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https", + ], + ], + ], + ], 4, 2); + } +} diff --git a/config/coolify.php b/config/coolify.php index fd95a7faa..0afb921dc 100644 --- a/config/coolify.php +++ b/config/coolify.php @@ -1,9 +1,8 @@ '4.0.0-nightly.2', - 'mux_enabled' => env('MUX_ENABLED', true), - 'dev_webhook' => env('SERVEO_URL'), + 'base_config_path' => env('BASE_CONFIG_PATH', '/data/coolify'), + 'proxy_config_path' => env('BASE_CONFIG_PATH', '/data/coolify') . "/proxy", ]; diff --git a/resources/views/components/proxy/options.blade.php b/resources/views/components/proxy/options.blade.php index 5e6657c5f..04a6f04ba 100644 --- a/resources/views/components/proxy/options.blade.php +++ b/resources/views/components/proxy/options.blade.php @@ -1,4 +1,4 @@ - +@props(['proxy_settings'])
- diff --git a/resources/views/components/proxy/problems.blade.php b/resources/views/components/proxy/problems.blade.php deleted file mode 100644 index 751f7f370..000000000 --- a/resources/views/components/proxy/problems.blade.php +++ /dev/null @@ -1,3 +0,0 @@ -
- Problems! -
diff --git a/resources/views/errors/404.blade.php b/resources/views/errors/404.blade.php index 7549540d8..c5c95717d 100644 --- a/resources/views/errors/404.blade.php +++ b/resources/views/errors/404.blade.php @@ -1,5 +1,3 @@ -@extends('errors::minimal') - -@section('title', __('Not Found')) -@section('code', '404') -@section('message', __('Not Found')) +
+ You are lost. Go home +
diff --git a/resources/views/livewire/server/proxy.blade.php b/resources/views/livewire/server/proxy.blade.php index 6a81549ff..ec216d2a4 100644 --- a/resources/views/livewire/server/proxy.blade.php +++ b/resources/views/livewire/server/proxy.blade.php @@ -1,40 +1,55 @@
-

Proxy

- - @if ($this->server->extra_attributes->proxy) -
-
- Proxy type: {{ $this->server->extra_attributes->proxy }} -
- -
- - {{-- Proxy is being checked against DB information --}} - @if (!$this->is_check_proxy_complete) - - @endif - - @if ($this->is_check_proxy_complete && !$this->is_proxy_settings_in_sync) - - @else - - @endif - -
- -
- @else - {{-- There is no Proxy installed --}} - - No proxy installed. +
+

Proxy

+
{{ $this->server->extra_attributes->proxy_status }}
+
+ @if ($this->server->extra_attributes->proxy_status !== 'running') - + Set Proxy + @endif + @if ($this->server->extra_attributes->proxy_type) +
+ @if ( + $this->server->extra_attributes->last_applied_proxy_settings && + $this->server->extra_attributes->last_saved_proxy_settings !== + $this->server->extra_attributes->last_applied_proxy_settings) +
Configuration out of sync.
+ @endif + @if ($this->server->extra_attributes->proxy_status !== 'running') + + Install + + @endif + Stop + + Show Configuration + +
+ +
+ +
+
@endif - - -