Skip to content

Commit 688c27c

Browse files
committed
fix: cloudflare tunnel configuration, ui, etc
1 parent 480ae3d commit 688c27c

File tree

10 files changed

+108
-38
lines changed

10 files changed

+108
-38
lines changed

app/Actions/Server/ConfigureCloudflared.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace App\Actions\Server;
44

5+
use App\Events\CloudflareTunnelConfigured;
56
use App\Models\Server;
67
use Lorisleiva\Actions\Concerns\AsAction;
78
use Symfony\Component\Yaml\Yaml;
@@ -40,12 +41,17 @@ public function handle(Server $server, string $cloudflare_token)
4041
instant_remote_process($commands, $server);
4142
} catch (\Throwable $e) {
4243
ray($e);
44+
$server->settings->is_cloudflare_tunnel = false;
45+
$server->settings->save();
4346
throw $e;
4447
} finally {
48+
CloudflareTunnelConfigured::dispatch($server->team_id);
49+
4550
$commands = collect([
4651
'rm -fr /tmp/cloudflared',
4752
]);
4853
instant_remote_process($commands, $server);
54+
4955
}
5056
}
5157
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php
2+
3+
namespace App\Events;
4+
5+
use Illuminate\Broadcasting\InteractsWithSockets;
6+
use Illuminate\Broadcasting\PrivateChannel;
7+
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
8+
use Illuminate\Foundation\Events\Dispatchable;
9+
use Illuminate\Queue\SerializesModels;
10+
11+
class CloudflareTunnelConfigured implements ShouldBroadcast
12+
{
13+
use Dispatchable, InteractsWithSockets, SerializesModels;
14+
15+
public $teamId;
16+
17+
public function __construct($teamId = null)
18+
{
19+
if (is_null($teamId)) {
20+
$teamId = auth()->user()->currentTeam()->id ?? null;
21+
}
22+
if (is_null($teamId)) {
23+
throw new \Exception('Team id is null');
24+
}
25+
$this->teamId = $teamId;
26+
}
27+
28+
public function broadcastOn(): array
29+
{
30+
return [
31+
new PrivateChannel("team.{$this->teamId}"),
32+
];
33+
}
34+
}

app/Helpers/SshMultiplexingHelper.php

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,11 @@ public static function ensureMultiplexedConnection(Server $server)
3333

3434
self::validateSshKey($sshKeyLocation);
3535

36-
$checkCommand = "ssh -O check -o ControlPath=$muxSocket {$server->user}@{$server->ip}";
36+
$checkCommand = "ssh -O check -o ControlPath=$muxSocket ";
3737
if (data_get($server, 'settings.is_cloudflare_tunnel')) {
38-
$checkCommand = 'cloudflared access ssh --hostname %h -O check -o ControlPath=' . $muxSocket . ' ' . $server->user . '@' . $server->ip;
38+
$checkCommand .= '-o ProxyCommand="cloudflared access ssh --hostname %h" ';
3939
}
40+
$checkCommand .= "{$server->user}@{$server->ip}";
4041
$process = Process::run($checkCommand);
4142

4243
if ($process->exitCode() !== 0) {
@@ -54,14 +55,15 @@ public static function establishNewMultiplexedConnection(Server $server)
5455
$serverInterval = config('constants.ssh.server_interval');
5556
$muxPersistTime = config('constants.ssh.mux_persist_time');
5657

57-
$establishCommand = "ssh -fNM -o ControlMaster=auto -o ControlPath=$muxSocket -o ControlPersist={$muxPersistTime} "
58-
.self::getCommonSshOptions($server, $sshKeyLocation, $connectionTimeout, $serverInterval)
59-
."{$server->user}@{$server->ip}";
58+
$establishCommand = "ssh -fNM -o ControlMaster=auto -o ControlPath=$muxSocket -o ControlPersist={$muxPersistTime} ";
6059

6160
if (data_get($server, 'settings.is_cloudflare_tunnel')) {
62-
$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;
61+
$establishCommand .= ' -o ProxyCommand="cloudflared access ssh --hostname %h" ';
6362
}
6463

64+
$establishCommand .= self::getCommonSshOptions($server, $sshKeyLocation, $connectionTimeout, $serverInterval);
65+
$establishCommand .= "{$server->user}@{$server->ip}";
66+
6567
$establishProcess = Process::run($establishCommand);
6668

6769
if ($establishProcess->exitCode() !== 0) {
@@ -74,10 +76,11 @@ public static function removeMuxFile(Server $server)
7476
$sshConfig = self::serverSshConfiguration($server);
7577
$muxSocket = $sshConfig['muxFilename'];
7678

77-
$closeCommand = "ssh -O exit -o ControlPath=$muxSocket {$server->user}@{$server->ip}";
79+
$closeCommand = "ssh -O exit -o ControlPath=$muxSocket ";
7880
if (data_get($server, 'settings.is_cloudflare_tunnel')) {
79-
$closeCommand = 'cloudflared access ssh --hostname %h -O exit -o ControlPath=' . $muxSocket . ' ' . $server->user . '@' . $server->ip;
81+
$closeCommand .= '-o ProxyCommand="cloudflared access ssh --hostname %h" ';
8082
}
83+
$closeCommand .= "{$server->user}@{$server->ip}";
8184
Process::run($closeCommand);
8285
}
8386

@@ -98,7 +101,7 @@ public static function generateScpCommand(Server $server, string $source, string
98101
}
99102

100103
if (data_get($server, 'settings.is_cloudflare_tunnel')) {
101-
$scp_command = 'timeout ' . $timeout . ' cloudflared access ssh --hostname %h -o ControlMaster=auto -o ControlPath=' . $muxSocket . ' -o ControlPersist=' . $muxPersistTime . ' ';
104+
$scp_command .= '-o ProxyCommand="cloudflared access ssh --hostname %h" ';
102105
}
103106

104107
$scp_command .= self::getCommonSshOptions($server, $sshKeyLocation, config('constants.ssh.connection_timeout'), config('constants.ssh.server_interval'), isScp: true);
@@ -128,7 +131,7 @@ public static function generateSshCommand(Server $server, string $command)
128131
}
129132

130133
if (data_get($server, 'settings.is_cloudflare_tunnel')) {
131-
$ssh_command = 'timeout ' . $timeout . ' cloudflared access ssh --hostname %h -o ControlMaster=auto -o ControlPath=' . $muxSocket . ' -o ControlPersist=' . $muxPersistTime . ' ';
134+
$ssh_command .= "-o ProxyCommand='cloudflared access ssh --hostname %h' ";
132135
}
133136

134137
$ssh_command .= self::getCommonSshOptions($server, $sshKeyLocation, config('constants.ssh.connection_timeout'), config('constants.ssh.server_interval'));

app/Livewire/Server/ConfigureCloudflareTunnels.php

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,12 @@ public function submit()
3131
{
3232
try {
3333
$server = Server::ownedByCurrentTeam()->where('id', $this->server_id)->firstOrFail();
34-
ConfigureCloudflared::run($server, $this->cloudflare_token);
34+
ConfigureCloudflared::dispatch($server, $this->cloudflare_token);
3535
$server->settings->is_cloudflare_tunnel = true;
3636
$server->ip = $this->ssh_domain;
3737
$server->save();
3838
$server->settings->save();
39-
$this->dispatch('success', 'Cloudflare Tunnels configured successfully.');
40-
$this->dispatch('refreshServerShow');
39+
$this->dispatch('warning', 'Cloudflare Tunnels configuration started.');
4140
} catch (\Throwable $e) {
4241
return handleError($e, $this);
4342
}

app/Livewire/Server/Form.php

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,16 @@ class Form extends Component
2424

2525
public $timezones;
2626

27-
protected $listeners = [
28-
'serverInstalled',
29-
'refreshServerShow' => 'serverInstalled',
30-
'revalidate' => '$refresh',
31-
];
27+
public function getListeners()
28+
{
29+
$teamId = auth()->user()->currentTeam()->id;
30+
31+
return [
32+
"echo-private:team.{$teamId},CloudflareTunnelConfigured" => 'cloudflareTunnelConfigured',
33+
'refreshServerShow' => 'serverInstalled',
34+
'revalidate' => '$refresh',
35+
];
36+
}
3237

3338
protected $rules = [
3439
'server.name' => 'required',
@@ -96,6 +101,12 @@ public function updated($field)
96101
}
97102
}
98103

104+
public function cloudflareTunnelConfigured()
105+
{
106+
$this->serverInstalled();
107+
$this->dispatch('success', 'Cloudflare Tunnels configured successfully.');
108+
}
109+
99110
public function serverInstalled()
100111
{
101112
$this->server->refresh();

bootstrap/helpers/docker.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,9 @@ function getContainerStatus(Server $server, string $container_id, bool $all_data
134134
return 'exited';
135135
}
136136
$container = format_docker_command_output_to_json($container);
137+
if ($container->isEmpty()) {
138+
return 'exited';
139+
}
137140
if ($all_data) {
138141
return $container[0];
139142
}

lang/en.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
"service.stop": "This service will be stopped.",
3131
"resource.docker_cleanup": "Run Docker Cleanup (remove unused images and builder cache).",
3232
"resource.non_persistent": "All non-persistent data will be deleted.",
33-
"resource.delete_volumes": "All volumes associated with this resource will be permanently deleted.",
34-
"resource.delete_connected_networks": "All non-predefined networks associated with this resource will be permanently deleted.",
35-
"resource.delete_configurations": "All configuration files will be permanently deleted from the server."
33+
"resource.delete_volumes": "Permanently delete all volumes associated with this resource.",
34+
"resource.delete_connected_networks": "Permanently delete all non-predefined networks associated with this resource.",
35+
"resource.delete_configurations": "Permanently delete all configuration files from the server."
3636
}

resources/views/livewire/project/shared/danger.blade.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<div class="pb-4">This will stop your containers, delete all related data, etc. Beware! There is no coming back!
66
</div>
77
<x-modal-confirmation title="Confirm Resource Deletion?" buttonTitle="Delete" isErrorButton submitAction="delete"
8-
buttonTitle="Delete" :checkboxes="$checkboxes" :actions="['All containers of this resource will be stopped and permanently deleted.']" confirmationText="{{ $resourceName }}"
8+
buttonTitle="Delete" :checkboxes="$checkboxes" :actions="['Permanently delete all containers of this resource.']" confirmationText="{{ $resourceName }}"
99
confirmationLabel="Please confirm the execution of the actions by entering the NAME of the resource below"
10-
shortConfirmationLabel="Resource Name" step3ButtonText="Delete Permanently" />
10+
shortConfirmationLabel="Resource Name" step3ButtonText="Permanently Delete" />
1111
</div>
Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
<form wire:submit.prevent='submit' class="flex flex-col w-full gap-2">
2-
<x-forms.input id="cloudflare_token" required label="Cloudflare Token" />
1+
<form wire:submit.prevent='submit' class="flex flex-col gap-2 w-full">
2+
<x-forms.input id="cloudflare_token" required label="Cloudflare Token" type="password" />
33
<x-forms.input id="ssh_domain" label="Configured SSH Domain" required
4-
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." />
5-
<x-forms.button type="submit" isHighlighted @click="modalOpen=false">Automated Configuration</x-forms.button>
4+
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. <a class='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/cloudflare/tunnels/#automated' target='_blank'>Documentation</a>" />
5+
<x-forms.button type="submit" isHighlighted @click="modalOpen=false">Continue</x-forms.button>
66
</form>

resources/views/livewire/server/form.blade.php

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -117,37 +117,51 @@ class="px-4 py-2 text-gray-800 cursor-pointer hover:bg-gray-100 dark:hover:bg-co
117117
</div>
118118
</div>
119119

120-
<div class="w-96">
120+
<div class="{{ $server->isFunctional() ? 'w-96' : 'w-full' }}">
121121
@if (!$server->isLocalhost())
122122
<x-forms.checkbox instantSave id="server.settings.is_build_server"
123123
label="Use it as a build server?" />
124124
<div class="flex flex-col gap-2 pt-6">
125-
<div class="flex items-center gap-1">
125+
<div class="flex gap-1 items-center">
126126
<h3 class="text-lg font-semibold">Cloudflare Tunnels</h3>
127127
<x-helper class="inline-flex"
128128
helper="If you are using Cloudflare Tunnels, enable this. It will proxy all SSH requests to your server through Cloudflare.<br> You then can close your server's SSH port in the firewall of your hosting provider.<br><span class='dark:text-warning'>If you choose manual configuration, Coolify does not install or set up Cloudflare (cloudflared) on your server.</span>" />
129129
</div>
130130
@if ($server->settings->is_cloudflare_tunnel)
131-
<x-forms.checkbox instantSave id="server.settings.is_cloudflare_tunnel" label="Enabled" />
131+
<div class="w-64">
132+
<x-forms.checkbox instantSave id="server.settings.is_cloudflare_tunnel" label="Enabled" />
133+
</div>
132134
@elseif (!$server->isFunctional())
133-
<div class="p-4 mb-4 text-sm text-yellow-800 bg-yellow-100 rounded-lg dark:bg-yellow-900 dark:text-yellow-300">
134-
<p>Please select manual cloudflare tunnel configuration (first then hit validate server) or validate the server first and then you can select automatic configuration.</p>
135-
<p class="mt-2">For more information, please read our <a href="https://coolify.io/docs/knowledge-base/cloudflare/tunnels/" target="_blank" class="font-medium underline hover:text-yellow-600 dark:hover:text-yellow-200">Documentation</a>.</p>
135+
<div class="p-4 mb-4 w-full text-sm text-yellow-800 bg-yellow-100 rounded dark:bg-yellow-900 dark:text-yellow-300">
136+
<x-slide-over closeWithX fullScreen>
137+
<x-slot:title>Validate & configure</x-slot:title>
138+
<x-slot:content>
139+
<livewire:server.validate-and-install :server="$server" />
140+
</x-slot:content>
141+
To <span class="font-semibold">automatically</span> configure Cloudflare Tunnels, please click
142+
<span @click="slideOverOpen=true"
143+
wire:click.prevent='validateServer' class="underline cursor-pointer">
144+
here.</span> You will need a Cloudflare token and domain.
145+
</x-slide-over>
146+
<br/>
147+
To <span class="font-semibold">manually</span> configure Cloudflare Tunnels, please click <span wire:click="manualCloudflareConfig" class="underline cursor-pointer">here</span>, then you should validate the server.
148+
<br/><br/>
149+
For more information, please read our <a href="https://coolify.io/docs/knowledge-base/cloudflare/tunnels/" target="_blank" class="font-medium underline hover:text-yellow-600 dark:hover:text-yellow-200">documentation</a>.
136150
</div>
137151
@endif
138152
@if (!$server->settings->is_cloudflare_tunnel && $server->isFunctional())
139-
<x-modal-input buttonTitle="Automatic Configuration" title="Cloudflare Tunnels" class="w-full">
153+
<x-modal-input buttonTitle="Automated Configuration" title="Cloudflare Tunnels" class="w-full">
140154
<livewire:server.configure-cloudflare-tunnels :server_id="$server->id" />
141155
</x-modal-input>
142156
@endif
143-
@if (!$server->settings->is_cloudflare_tunnel)
144-
<x-forms.button wire:click="manualCloudflareConfig" class="w-full">
157+
@if ($server->isFunctional() &&!$server->settings->is_cloudflare_tunnel)
158+
<div wire:click="manualCloudflareConfig" class="w-full underline cursor-pointer">
145159
I have configured Cloudflare Tunnels manually
146-
</x-forms.button>
160+
</div>
147161
@endif
148162

149163
</div>
150-
@if (!$server->isBuildServer())
164+
@if (!$server->isBuildServer() && !$server->settings->is_cloudflare_tunnel)
151165
<h3 class="pt-6">Swarm <span class="text-xs text-neutral-500">(experimental)</span></h3>
152166
<div class="pb-4">Read the docs <a class='underline dark:text-white'
153167
href='https://coolify.io/docs/knowledge-base/docker/swarm' target='_blank'>here</a>.

0 commit comments

Comments
 (0)