From 688c27c901c2285ee462c745c4837bc84ade34bd Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Mon, 23 Sep 2024 23:18:23 +0200 Subject: [PATCH] fix: cloudflare tunnel configuration, ui, etc --- app/Actions/Server/ConfigureCloudflared.php | 6 ++++ app/Events/CloudflareTunnelConfigured.php | 34 ++++++++++++++++++ app/Helpers/SshMultiplexingHelper.php | 23 ++++++------ .../Server/ConfigureCloudflareTunnels.php | 5 ++- app/Livewire/Server/Form.php | 21 ++++++++--- bootstrap/helpers/docker.php | 3 ++ lang/en.json | 6 ++-- .../livewire/project/shared/danger.blade.php | 4 +-- .../configure-cloudflare-tunnels.blade.php | 8 ++--- .../views/livewire/server/form.blade.php | 36 +++++++++++++------ 10 files changed, 108 insertions(+), 38 deletions(-) create mode 100644 app/Events/CloudflareTunnelConfigured.php diff --git a/app/Actions/Server/ConfigureCloudflared.php b/app/Actions/Server/ConfigureCloudflared.php index 3946afe95..0d36e8863 100644 --- a/app/Actions/Server/ConfigureCloudflared.php +++ b/app/Actions/Server/ConfigureCloudflared.php @@ -2,6 +2,7 @@ namespace App\Actions\Server; +use App\Events\CloudflareTunnelConfigured; use App\Models\Server; use Lorisleiva\Actions\Concerns\AsAction; use Symfony\Component\Yaml\Yaml; @@ -40,12 +41,17 @@ class ConfigureCloudflared instant_remote_process($commands, $server); } catch (\Throwable $e) { ray($e); + $server->settings->is_cloudflare_tunnel = false; + $server->settings->save(); throw $e; } finally { + CloudflareTunnelConfigured::dispatch($server->team_id); + $commands = collect([ 'rm -fr /tmp/cloudflared', ]); instant_remote_process($commands, $server); + } } } diff --git a/app/Events/CloudflareTunnelConfigured.php b/app/Events/CloudflareTunnelConfigured.php new file mode 100644 index 000000000..3d7076d0d --- /dev/null +++ b/app/Events/CloudflareTunnelConfigured.php @@ -0,0 +1,34 @@ +user()->currentTeam()->id ?? null; + } + if (is_null($teamId)) { + throw new \Exception('Team id is null'); + } + $this->teamId = $teamId; + } + + public function broadcastOn(): array + { + return [ + new PrivateChannel("team.{$this->teamId}"), + ]; + } +} diff --git a/app/Helpers/SshMultiplexingHelper.php b/app/Helpers/SshMultiplexingHelper.php index 3efcb221b..b0a832605 100644 --- a/app/Helpers/SshMultiplexingHelper.php +++ b/app/Helpers/SshMultiplexingHelper.php @@ -33,10 +33,11 @@ class SshMultiplexingHelper self::validateSshKey($sshKeyLocation); - $checkCommand = "ssh -O check -o ControlPath=$muxSocket {$server->user}@{$server->ip}"; + $checkCommand = "ssh -O check -o ControlPath=$muxSocket "; if (data_get($server, 'settings.is_cloudflare_tunnel')) { - $checkCommand = 'cloudflared access ssh --hostname %h -O check -o ControlPath=' . $muxSocket . ' ' . $server->user . '@' . $server->ip; + $checkCommand .= '-o ProxyCommand="cloudflared access ssh --hostname %h" '; } + $checkCommand .= "{$server->user}@{$server->ip}"; $process = Process::run($checkCommand); if ($process->exitCode() !== 0) { @@ -54,14 +55,15 @@ class SshMultiplexingHelper $serverInterval = config('constants.ssh.server_interval'); $muxPersistTime = config('constants.ssh.mux_persist_time'); - $establishCommand = "ssh -fNM -o ControlMaster=auto -o ControlPath=$muxSocket -o ControlPersist={$muxPersistTime} " - .self::getCommonSshOptions($server, $sshKeyLocation, $connectionTimeout, $serverInterval) - ."{$server->user}@{$server->ip}"; + $establishCommand = "ssh -fNM -o ControlMaster=auto -o ControlPath=$muxSocket -o ControlPersist={$muxPersistTime} "; if (data_get($server, 'settings.is_cloudflare_tunnel')) { - $establishCommand = 'cloudflared access ssh --hostname %h -fNM -o ControlMaster=auto -o ControlPath=' . $muxSocket . ' -o ControlPersist=' . $muxPersistTime . ' ' . self::getCommonSshOptions($server, $sshKeyLocation, $connectionTimeout, $serverInterval) . $server->user . '@' . $server->ip; + $establishCommand .= ' -o ProxyCommand="cloudflared access ssh --hostname %h" '; } + $establishCommand .= self::getCommonSshOptions($server, $sshKeyLocation, $connectionTimeout, $serverInterval); + $establishCommand .= "{$server->user}@{$server->ip}"; + $establishProcess = Process::run($establishCommand); if ($establishProcess->exitCode() !== 0) { @@ -74,10 +76,11 @@ class SshMultiplexingHelper $sshConfig = self::serverSshConfiguration($server); $muxSocket = $sshConfig['muxFilename']; - $closeCommand = "ssh -O exit -o ControlPath=$muxSocket {$server->user}@{$server->ip}"; + $closeCommand = "ssh -O exit -o ControlPath=$muxSocket "; if (data_get($server, 'settings.is_cloudflare_tunnel')) { - $closeCommand = 'cloudflared access ssh --hostname %h -O exit -o ControlPath=' . $muxSocket . ' ' . $server->user . '@' . $server->ip; + $closeCommand .= '-o ProxyCommand="cloudflared access ssh --hostname %h" '; } + $closeCommand .= "{$server->user}@{$server->ip}"; Process::run($closeCommand); } @@ -98,7 +101,7 @@ class SshMultiplexingHelper } if (data_get($server, 'settings.is_cloudflare_tunnel')) { - $scp_command = 'timeout ' . $timeout . ' cloudflared access ssh --hostname %h -o ControlMaster=auto -o ControlPath=' . $muxSocket . ' -o ControlPersist=' . $muxPersistTime . ' '; + $scp_command .= '-o ProxyCommand="cloudflared access ssh --hostname %h" '; } $scp_command .= self::getCommonSshOptions($server, $sshKeyLocation, config('constants.ssh.connection_timeout'), config('constants.ssh.server_interval'), isScp: true); @@ -128,7 +131,7 @@ class SshMultiplexingHelper } if (data_get($server, 'settings.is_cloudflare_tunnel')) { - $ssh_command = 'timeout ' . $timeout . ' cloudflared access ssh --hostname %h -o ControlMaster=auto -o ControlPath=' . $muxSocket . ' -o ControlPersist=' . $muxPersistTime . ' '; + $ssh_command .= "-o ProxyCommand='cloudflared access ssh --hostname %h' "; } $ssh_command .= self::getCommonSshOptions($server, $sshKeyLocation, config('constants.ssh.connection_timeout'), config('constants.ssh.server_interval')); diff --git a/app/Livewire/Server/ConfigureCloudflareTunnels.php b/app/Livewire/Server/ConfigureCloudflareTunnels.php index f7306a5b5..a69a5e15d 100644 --- a/app/Livewire/Server/ConfigureCloudflareTunnels.php +++ b/app/Livewire/Server/ConfigureCloudflareTunnels.php @@ -31,13 +31,12 @@ class ConfigureCloudflareTunnels extends Component { try { $server = Server::ownedByCurrentTeam()->where('id', $this->server_id)->firstOrFail(); - ConfigureCloudflared::run($server, $this->cloudflare_token); + ConfigureCloudflared::dispatch($server, $this->cloudflare_token); $server->settings->is_cloudflare_tunnel = true; $server->ip = $this->ssh_domain; $server->save(); $server->settings->save(); - $this->dispatch('success', 'Cloudflare Tunnels configured successfully.'); - $this->dispatch('refreshServerShow'); + $this->dispatch('warning', 'Cloudflare Tunnels configuration started.'); } catch (\Throwable $e) { return handleError($e, $this); } diff --git a/app/Livewire/Server/Form.php b/app/Livewire/Server/Form.php index ddf2b6ccb..3cb3305b5 100644 --- a/app/Livewire/Server/Form.php +++ b/app/Livewire/Server/Form.php @@ -24,11 +24,16 @@ class Form extends Component public $timezones; - protected $listeners = [ - 'serverInstalled', - 'refreshServerShow' => 'serverInstalled', - 'revalidate' => '$refresh', - ]; + public function getListeners() + { + $teamId = auth()->user()->currentTeam()->id; + + return [ + "echo-private:team.{$teamId},CloudflareTunnelConfigured" => 'cloudflareTunnelConfigured', + 'refreshServerShow' => 'serverInstalled', + 'revalidate' => '$refresh', + ]; + } protected $rules = [ 'server.name' => 'required', @@ -96,6 +101,12 @@ class Form extends Component } } + public function cloudflareTunnelConfigured() + { + $this->serverInstalled(); + $this->dispatch('success', 'Cloudflare Tunnels configured successfully.'); + } + public function serverInstalled() { $this->server->refresh(); diff --git a/bootstrap/helpers/docker.php b/bootstrap/helpers/docker.php index 8dce52f15..e252bda10 100644 --- a/bootstrap/helpers/docker.php +++ b/bootstrap/helpers/docker.php @@ -134,6 +134,9 @@ function getContainerStatus(Server $server, string $container_id, bool $all_data return 'exited'; } $container = format_docker_command_output_to_json($container); + if ($container->isEmpty()) { + return 'exited'; + } if ($all_data) { return $container[0]; } diff --git a/lang/en.json b/lang/en.json index e1603c303..45fd72743 100644 --- a/lang/en.json +++ b/lang/en.json @@ -30,7 +30,7 @@ "service.stop": "This service will be stopped.", "resource.docker_cleanup": "Run Docker Cleanup (remove unused images and builder cache).", "resource.non_persistent": "All non-persistent data will be deleted.", - "resource.delete_volumes": "All volumes associated with this resource will be permanently deleted.", - "resource.delete_connected_networks": "All non-predefined networks associated with this resource will be permanently deleted.", - "resource.delete_configurations": "All configuration files will be permanently deleted from the server." + "resource.delete_volumes": "Permanently delete all volumes associated with this resource.", + "resource.delete_connected_networks": "Permanently delete all non-predefined networks associated with this resource.", + "resource.delete_configurations": "Permanently delete all configuration files from the server." } diff --git a/resources/views/livewire/project/shared/danger.blade.php b/resources/views/livewire/project/shared/danger.blade.php index 9d6d902f1..54e34f203 100644 --- a/resources/views/livewire/project/shared/danger.blade.php +++ b/resources/views/livewire/project/shared/danger.blade.php @@ -5,7 +5,7 @@
This will stop your containers, delete all related data, etc. Beware! There is no coming back!
+ shortConfirmationLabel="Resource Name" step3ButtonText="Permanently Delete" /> diff --git a/resources/views/livewire/server/configure-cloudflare-tunnels.blade.php b/resources/views/livewire/server/configure-cloudflare-tunnels.blade.php index c340a558b..d2342ee68 100644 --- a/resources/views/livewire/server/configure-cloudflare-tunnels.blade.php +++ b/resources/views/livewire/server/configure-cloudflare-tunnels.blade.php @@ -1,6 +1,6 @@ -
- + + - Automated Configuration + helper="The SSH domain you configured in Cloudflare. Make sure there is no protocol like http(s):// so you provide a FQDN not a URL. Documentation" /> + Continue diff --git a/resources/views/livewire/server/form.blade.php b/resources/views/livewire/server/form.blade.php index 4be803a95..cdab85e7f 100644 --- a/resources/views/livewire/server/form.blade.php +++ b/resources/views/livewire/server/form.blade.php @@ -117,37 +117,51 @@ -
+
@if (!$server->isLocalhost())
-
+

Cloudflare Tunnels

@if ($server->settings->is_cloudflare_tunnel) - +
+ +
@elseif (!$server->isFunctional()) -
-

Please select manual cloudflare tunnel configuration (first then hit validate server) or validate the server first and then you can select automatic configuration.

-

For more information, please read our Documentation.

+
+ + Validate & configure + + + + To automatically configure Cloudflare Tunnels, please click + + here. You will need a Cloudflare token and domain. + +
+ To manually configure Cloudflare Tunnels, please click here, then you should validate the server. +

+ For more information, please read our documentation.
@endif @if (!$server->settings->is_cloudflare_tunnel && $server->isFunctional()) - + @endif - @if (!$server->settings->is_cloudflare_tunnel) - + @if ($server->isFunctional() &&!$server->settings->is_cloudflare_tunnel) +
I have configured Cloudflare Tunnels manually - +
@endif
- @if (!$server->isBuildServer()) + @if (!$server->isBuildServer() && !$server->settings->is_cloudflare_tunnel)

Swarm (experimental)

Read the docs here.