wip: non-root user for remote servers
This commit is contained in:
		@@ -16,11 +16,19 @@ class CheckConfiguration
 | 
			
		||||
            return 'OK';
 | 
			
		||||
        }
 | 
			
		||||
        $proxy_path = $server->proxyPath();
 | 
			
		||||
 | 
			
		||||
        $proxy_configuration = instant_remote_process([
 | 
			
		||||
            "mkdir -p $proxy_path",
 | 
			
		||||
            "cat $proxy_path/docker-compose.yml",
 | 
			
		||||
        ], $server, false);
 | 
			
		||||
        if ($server->isNonRoot()) {
 | 
			
		||||
            $payload = [
 | 
			
		||||
                "mkdir -p $proxy_path",
 | 
			
		||||
                "chown -R $server->user:$server->user $proxy_path",
 | 
			
		||||
                "cat $proxy_path/docker-compose.yml",
 | 
			
		||||
            ];
 | 
			
		||||
        } else {
 | 
			
		||||
            $payload = [
 | 
			
		||||
                "mkdir -p $proxy_path",
 | 
			
		||||
                "cat $proxy_path/docker-compose.yml",
 | 
			
		||||
            ];
 | 
			
		||||
        }
 | 
			
		||||
        $proxy_configuration = instant_remote_process($payload, $server, false);
 | 
			
		||||
 | 
			
		||||
        if ($reset || !$proxy_configuration || is_null($proxy_configuration)) {
 | 
			
		||||
            $proxy_configuration = Str::of(generate_default_proxy_configuration($server))->trim()->value;
 | 
			
		||||
 
 | 
			
		||||
@@ -13,8 +13,9 @@ class CheckProxy
 | 
			
		||||
        if ($server->proxyType() === 'NONE') {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
        if (!$server->validateConnection()) {
 | 
			
		||||
            throw new \Exception("Server Connection Error");
 | 
			
		||||
        ['uptime' => $uptime, 'error' => $error] = $server->validateConnection();
 | 
			
		||||
        if (!$uptime) {
 | 
			
		||||
            throw new \Exception($error);
 | 
			
		||||
        }
 | 
			
		||||
        if (!$server->isProxyShouldRun()) {
 | 
			
		||||
            if ($fromUI) {
 | 
			
		||||
 
 | 
			
		||||
@@ -47,7 +47,6 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
 | 
			
		||||
        if (!$this->server->isFunctional()) {
 | 
			
		||||
            return 'Server is not ready.';
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        $applications = $this->server->applications();
 | 
			
		||||
        $skip_these_applications = collect([]);
 | 
			
		||||
        foreach ($applications as $application) {
 | 
			
		||||
@@ -78,6 +77,7 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
 | 
			
		||||
            if (is_null($containers)) {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            $containers = format_docker_command_output_to_json($containers);
 | 
			
		||||
            if ($containerReplicates) {
 | 
			
		||||
                $containerReplicates = format_docker_command_output_to_json($containerReplicates);
 | 
			
		||||
@@ -201,7 +201,6 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
 | 
			
		||||
                                // Notify user that this container should not be there.
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                    }
 | 
			
		||||
                    if (data_get($container, 'Name') === '/coolify-db') {
 | 
			
		||||
                        $foundDatabases[] = 0;
 | 
			
		||||
 
 | 
			
		||||
@@ -11,15 +11,17 @@ class NewActivityMonitor extends Component
 | 
			
		||||
    public ?string $header = null;
 | 
			
		||||
    public $activityId;
 | 
			
		||||
    public $eventToDispatch = 'activityFinished';
 | 
			
		||||
    public $eventData = null;
 | 
			
		||||
    public $isPollingActive = false;
 | 
			
		||||
 | 
			
		||||
    protected $activity;
 | 
			
		||||
    protected $listeners = ['newActivityMonitor' => 'newMonitorActivity'];
 | 
			
		||||
 | 
			
		||||
    public function newMonitorActivity($activityId, $eventToDispatch = 'activityFinished')
 | 
			
		||||
    public function newMonitorActivity($activityId, $eventToDispatch = 'activityFinished', $eventData = null)
 | 
			
		||||
    {
 | 
			
		||||
        $this->activityId = $activityId;
 | 
			
		||||
        $this->eventToDispatch = $eventToDispatch;
 | 
			
		||||
        $this->eventData = $eventData;
 | 
			
		||||
 | 
			
		||||
        $this->hydrateActivity();
 | 
			
		||||
 | 
			
		||||
@@ -55,8 +57,12 @@ class NewActivityMonitor extends Component
 | 
			
		||||
                    }
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
                $this->dispatch($this->eventToDispatch);
 | 
			
		||||
                ray('Dispatched event: ' . $this->eventToDispatch);
 | 
			
		||||
                if (!is_null($this->eventData)) {
 | 
			
		||||
                    $this->dispatch($this->eventToDispatch, $this->eventData);
 | 
			
		||||
                } else {
 | 
			
		||||
                    $this->dispatch($this->eventToDispatch);
 | 
			
		||||
                }
 | 
			
		||||
                ray('Dispatched event: ' . $this->eventToDispatch . ' with data: ' . $this->eventData);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -76,14 +76,14 @@ class Form extends Component
 | 
			
		||||
    public function checkLocalhostConnection()
 | 
			
		||||
    {
 | 
			
		||||
        $this->submit();
 | 
			
		||||
        $uptime = $this->server->validateConnection();
 | 
			
		||||
        ['uptime' => $uptime, 'error' => $error] = $this->server->validateConnection();
 | 
			
		||||
        if ($uptime) {
 | 
			
		||||
            $this->dispatch('success', 'Server is reachable.');
 | 
			
		||||
            $this->server->settings->is_reachable = true;
 | 
			
		||||
            $this->server->settings->is_usable = true;
 | 
			
		||||
            $this->server->settings->save();
 | 
			
		||||
        } else {
 | 
			
		||||
            $this->dispatch('error', 'Server is not reachable.', 'Please validate your configuration and connection.<br><br>Check this <a target="_blank" class="underline" href="https://coolify.io/docs/knowledge-base/server/openssh">documentation</a> for further help.');
 | 
			
		||||
            $this->dispatch('error', 'Server is not reachable.', 'Please validate your configuration and connection.<br><br>Check this <a target="_blank" class="underline" href="https://coolify.io/docs/knowledge-base/server/openssh">documentation</a> for further help. <br><br>Error: ' . $error);
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -35,10 +35,11 @@ class ShowPrivateKey extends Component
 | 
			
		||||
    public function checkConnection()
 | 
			
		||||
    {
 | 
			
		||||
        try {
 | 
			
		||||
            $uptime = $this->server->validateConnection();
 | 
			
		||||
            ['uptime' => $uptime, 'error' => $error] = $this->server->validateConnection();
 | 
			
		||||
            if ($uptime) {
 | 
			
		||||
                $this->dispatch('success', 'Server is reachable.');
 | 
			
		||||
            } else {
 | 
			
		||||
                ray($error);
 | 
			
		||||
                $this->dispatch('error', 'Server is not reachable.<br>Please validate your configuration and connection.<br><br>Check this <a target="_blank" class="underline" href="https://coolify.io/docs/knowledge-base/server/openssh">documentation</a> for further help.');
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 
 | 
			
		||||
@@ -11,7 +11,7 @@ class ValidateAndInstall extends Component
 | 
			
		||||
{
 | 
			
		||||
    public Server $server;
 | 
			
		||||
    public int $number_of_tries = 0;
 | 
			
		||||
    public int $max_tries = 1;
 | 
			
		||||
    public int $max_tries = 3;
 | 
			
		||||
    public bool $install = true;
 | 
			
		||||
    public $uptime = null;
 | 
			
		||||
    public $supported_os_type = null;
 | 
			
		||||
@@ -32,9 +32,8 @@ class ValidateAndInstall extends Component
 | 
			
		||||
        'refresh' => '$refresh',
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    public function init(bool $install = true)
 | 
			
		||||
    public function init(int $data = 0)
 | 
			
		||||
    {
 | 
			
		||||
        $this->install = $install;
 | 
			
		||||
        $this->uptime = null;
 | 
			
		||||
        $this->supported_os_type = null;
 | 
			
		||||
        $this->docker_installed = null;
 | 
			
		||||
@@ -42,7 +41,7 @@ class ValidateAndInstall extends Component
 | 
			
		||||
        $this->docker_compose_installed = null;
 | 
			
		||||
        $this->proxy_started = null;
 | 
			
		||||
        $this->error = null;
 | 
			
		||||
        $this->number_of_tries = 0;
 | 
			
		||||
        $this->number_of_tries = $data;
 | 
			
		||||
        if (!$this->ask) {
 | 
			
		||||
            $this->dispatch('validateConnection');
 | 
			
		||||
        }
 | 
			
		||||
@@ -66,16 +65,15 @@ class ValidateAndInstall extends Component
 | 
			
		||||
            } else {
 | 
			
		||||
                $this->proxy_started = true;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        } catch (\Throwable $e) {
 | 
			
		||||
            return handleError($e, $this);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    public function validateConnection()
 | 
			
		||||
    {
 | 
			
		||||
        $this->uptime = $this->server->validateConnection();
 | 
			
		||||
        ['uptime' => $this->uptime, 'error' => $error] = $this->server->validateConnection();
 | 
			
		||||
        if (!$this->uptime) {
 | 
			
		||||
            $this->error = 'Server is not reachable. Please validate your configuration and connection.<br><br>Check this <a target="_blank" class="underline" href="https://coolify.io/docs/knowledge-base/server/openssh">documentation</a> for further help.';
 | 
			
		||||
            $this->error = 'Server is not reachable. Please validate your configuration and connection.<br><br>Check this <a target="_blank" class="underline" href="https://coolify.io/docs/knowledge-base/server/openssh">documentation</a> for further help. <br><br>Error: ' . $error;
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        $this->dispatch('validateOS');
 | 
			
		||||
@@ -99,10 +97,10 @@ class ValidateAndInstall extends Component
 | 
			
		||||
                    $this->error = 'Docker Engine could not be installed. Please install Docker manually before continuing: <a target="_blank" class="underline" href="https://docs.docker.com/engine/install/#server">documentation</a>.';
 | 
			
		||||
                    return;
 | 
			
		||||
                } else {
 | 
			
		||||
                    if ($this->number_of_tries == 0) {
 | 
			
		||||
                    if ($this->number_of_tries <= $this->max_tries) {
 | 
			
		||||
                        $activity = $this->server->installDocker();
 | 
			
		||||
                        $this->number_of_tries++;
 | 
			
		||||
                        $this->dispatch('newActivityMonitor', $activity->id, 'init');
 | 
			
		||||
                        $this->dispatch('newActivityMonitor', $activity->id, 'init', $this->number_of_tries);
 | 
			
		||||
                    }
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
 
 | 
			
		||||
@@ -481,8 +481,8 @@ $schema://$host {
 | 
			
		||||
        // ray('serverUptimeCheckNumber: ' . $serverUptimeCheckNumber);
 | 
			
		||||
        // ray('serverUptimeCheckNumberMax: ' . $serverUptimeCheckNumberMax);
 | 
			
		||||
 | 
			
		||||
        $result = $this->validateConnection();
 | 
			
		||||
        if ($result) {
 | 
			
		||||
        ['uptime' => $uptime] = $this->validateConnection();
 | 
			
		||||
        if ($uptime) {
 | 
			
		||||
            if ($this->unreachable_notification_sent === true) {
 | 
			
		||||
                $this->update(['unreachable_notification_sent' => false]);
 | 
			
		||||
            }
 | 
			
		||||
@@ -551,7 +551,7 @@ $schema://$host {
 | 
			
		||||
    public function loadUnmanagedContainers()
 | 
			
		||||
    {
 | 
			
		||||
        if ($this->isFunctional()) {
 | 
			
		||||
            $containers = instant_remote_process(["docker ps -a  --format '{{json .}}' "], $this);
 | 
			
		||||
            $containers = instant_remote_process(["docker ps -a --format '{{json .}}'"], $this);
 | 
			
		||||
            $containers = format_docker_command_output_to_json($containers);
 | 
			
		||||
            $containers = $containers->map(function ($container) {
 | 
			
		||||
                $labels = data_get($container, 'Labels');
 | 
			
		||||
@@ -748,34 +748,31 @@ $schema://$host {
 | 
			
		||||
 | 
			
		||||
        $server = Server::find($this->id);
 | 
			
		||||
        if (!$server) {
 | 
			
		||||
            return false;
 | 
			
		||||
            return ['uptime' => false, 'error' => 'Server not found.'];
 | 
			
		||||
        }
 | 
			
		||||
        if ($server->skipServer()) {
 | 
			
		||||
            return false;
 | 
			
		||||
            return ['uptime' => false, 'error' => 'Server skipped.'];
 | 
			
		||||
        }
 | 
			
		||||
        // EC2 does not have `uptime` command, lol
 | 
			
		||||
 | 
			
		||||
        $uptime = instant_remote_process(['ls /'], $server, false);
 | 
			
		||||
        if (!$uptime) {
 | 
			
		||||
            $server->settings()->update([
 | 
			
		||||
                'is_reachable' => false,
 | 
			
		||||
            ]);
 | 
			
		||||
            return false;
 | 
			
		||||
        } else {
 | 
			
		||||
        try {
 | 
			
		||||
            // EC2 does not have `uptime` command, lol
 | 
			
		||||
            instant_remote_process(['ls /'], $server);
 | 
			
		||||
            $server->settings()->update([
 | 
			
		||||
                'is_reachable' => true,
 | 
			
		||||
            ]);
 | 
			
		||||
            $server->update([
 | 
			
		||||
                'unreachable_count' => 0,
 | 
			
		||||
            ]);
 | 
			
		||||
            if (data_get($server, 'unreachable_notification_sent') === true) {
 | 
			
		||||
                $server->team?->notify(new Revived($server));
 | 
			
		||||
                $server->update(['unreachable_notification_sent' => false]);
 | 
			
		||||
            }
 | 
			
		||||
            return ['uptime' => true, 'error' => null];
 | 
			
		||||
        } catch (\Throwable $e) {
 | 
			
		||||
            $server->settings()->update([
 | 
			
		||||
                'is_reachable' => false,
 | 
			
		||||
            ]);
 | 
			
		||||
            return ['uptime' => false, 'error' => $e->getMessage()];
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (data_get($server, 'unreachable_notification_sent') === true) {
 | 
			
		||||
            $server->team?->notify(new Revived($server));
 | 
			
		||||
            $server->update(['unreachable_notification_sent' => false]);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
    public function installDocker()
 | 
			
		||||
    {
 | 
			
		||||
@@ -784,7 +781,7 @@ $schema://$host {
 | 
			
		||||
    }
 | 
			
		||||
    public function validateDockerEngine($throwError = false)
 | 
			
		||||
    {
 | 
			
		||||
        $dockerBinary = instant_remote_process(["command -v docker"], $this, false);
 | 
			
		||||
        $dockerBinary = instant_remote_process(["command -v docker"], $this, false, no_sudo: true);
 | 
			
		||||
        if (is_null($dockerBinary)) {
 | 
			
		||||
            $this->settings->is_usable = false;
 | 
			
		||||
            $this->settings->save();
 | 
			
		||||
@@ -861,4 +858,8 @@ $schema://$host {
 | 
			
		||||
            return instant_remote_process(["docker network create coolify --attachable >/dev/null 2>&1 || true"], $this, false);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    public function isNonRoot()
 | 
			
		||||
    {
 | 
			
		||||
        return $this->user !== 'root';
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -32,6 +32,33 @@ function remote_process(
 | 
			
		||||
    if ($command instanceof Collection) {
 | 
			
		||||
        $command = $command->toArray();
 | 
			
		||||
    }
 | 
			
		||||
    if ($server->isNonRoot()) {
 | 
			
		||||
        $command = collect($command)->map(function ($line) {
 | 
			
		||||
            if (!str($line)->startSwith('cd')) {
 | 
			
		||||
                return "sudo $line";
 | 
			
		||||
            }
 | 
			
		||||
            return $line;
 | 
			
		||||
        })->toArray();
 | 
			
		||||
        $command = collect($command)->map(function ($line) use ($server) {
 | 
			
		||||
            if (Str::startsWith($line, 'sudo mkdir -p')) {
 | 
			
		||||
                return "$line && sudo chown -R $server->user:$server->user " . Str::after($line, 'sudo mkdir -p') . ' && sudo chmod -R o-rwx ' . Str::after($line, 'sudo mkdir -p');
 | 
			
		||||
            }
 | 
			
		||||
            return $line;
 | 
			
		||||
        })->toArray();
 | 
			
		||||
        $command = collect($command)->map(function ($line) {
 | 
			
		||||
            if (str($line)->contains('$(') || str($line)->contains('`')) {
 | 
			
		||||
                return str($line)->replace('$(', '$(sudo ')->replace('`', '`sudo ')->value();
 | 
			
		||||
            }
 | 
			
		||||
            if (str($line)->contains('||')) {
 | 
			
		||||
                return str($line)->replace('||', '|| sudo ')->value();
 | 
			
		||||
            }
 | 
			
		||||
            if (str($line)->contains('&&')) {
 | 
			
		||||
                return str($line)->replace('&&', '&& sudo ')->value();
 | 
			
		||||
            }
 | 
			
		||||
            return $line;
 | 
			
		||||
        })->toArray();
 | 
			
		||||
    }
 | 
			
		||||
    ray($command);
 | 
			
		||||
    $command_string = implode("\n", $command);
 | 
			
		||||
    if (auth()->user()) {
 | 
			
		||||
        $teams = auth()->user()->teams->pluck('id');
 | 
			
		||||
@@ -144,7 +171,6 @@ function generateSshCommand(Server $server, string $command)
 | 
			
		||||
        $ssh_command .= '-o ProxyCommand="/usr/local/bin/cloudflared access ssh --hostname %h" ';
 | 
			
		||||
    }
 | 
			
		||||
    $command = "PATH=\$PATH:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/host/usr/local/sbin:/host/usr/local/bin:/host/usr/sbin:/host/usr/bin:/host/sbin:/host/bin && $command";
 | 
			
		||||
 | 
			
		||||
    $delimiter = Hash::make($command);
 | 
			
		||||
    $command = str_replace($delimiter, '', $command);
 | 
			
		||||
    $ssh_command .= "-i {$privateKeyLocation} "
 | 
			
		||||
@@ -160,17 +186,42 @@ function generateSshCommand(Server $server, string $command)
 | 
			
		||||
        . $command . PHP_EOL
 | 
			
		||||
        . $delimiter;
 | 
			
		||||
    // ray($ssh_command);
 | 
			
		||||
    // ray($delimiter);
 | 
			
		||||
    return $ssh_command;
 | 
			
		||||
}
 | 
			
		||||
function instant_remote_process(Collection|array $command, Server $server, $throwError = true)
 | 
			
		||||
function instant_remote_process(Collection|array $command, Server $server, bool $throwError = true, bool $no_sudo = false)
 | 
			
		||||
{
 | 
			
		||||
    $timeout = config('constants.ssh.command_timeout');
 | 
			
		||||
    if ($command instanceof Collection) {
 | 
			
		||||
        $command = $command->toArray();
 | 
			
		||||
    }
 | 
			
		||||
    if ($server->isNonRoot() && !$no_sudo) {
 | 
			
		||||
        $command = collect($command)->map(function ($line) {
 | 
			
		||||
            if (!str($line)->startSwith('cd')) {
 | 
			
		||||
                return "sudo $line";
 | 
			
		||||
            }
 | 
			
		||||
            return $line;
 | 
			
		||||
        })->toArray();
 | 
			
		||||
        $command = collect($command)->map(function ($line) use ($server) {
 | 
			
		||||
            if (Str::startsWith($line, 'sudo mkdir -p')) {
 | 
			
		||||
                return "$line && sudo chown -R $server->user:$server->user " . Str::after($line, 'sudo mkdir -p') . ' && sudo chmod -R o-rwx ' . Str::after($line, 'sudo mkdir -p');
 | 
			
		||||
            }
 | 
			
		||||
            return $line;
 | 
			
		||||
        })->toArray();
 | 
			
		||||
        $command = collect($command)->map(function ($line) {
 | 
			
		||||
            if (str($line)->contains('$(') || str($line)->contains('`')) {
 | 
			
		||||
                return str($line)->replace('$(', '$(sudo ')->replace('`', '`sudo ')->value();
 | 
			
		||||
            }
 | 
			
		||||
            if (str($line)->contains('||')) {
 | 
			
		||||
                return str($line)->replace('||', '|| sudo ')->value();
 | 
			
		||||
            }
 | 
			
		||||
            if (str($line)->contains('&&')) {
 | 
			
		||||
                return str($line)->replace('&&', '&& sudo ')->value();
 | 
			
		||||
            }
 | 
			
		||||
            return $line;
 | 
			
		||||
        })->toArray();
 | 
			
		||||
    }
 | 
			
		||||
    $command_string = implode("\n", $command);
 | 
			
		||||
    $ssh_command = generateSshCommand($server, $command_string);
 | 
			
		||||
    $ssh_command = generateSshCommand($server, $command_string, $no_sudo);
 | 
			
		||||
    $process = Process::timeout($timeout)->run($ssh_command);
 | 
			
		||||
    $output = trim($process->output());
 | 
			
		||||
    $exitCode = $process->exitCode();
 | 
			
		||||
 
 | 
			
		||||
@@ -10,7 +10,7 @@
 | 
			
		||||
            <div class="flex gap-2">
 | 
			
		||||
                <x-forms.input id="ip" label="IP Address/Domain" required
 | 
			
		||||
                    helper="An IP Address (127.0.0.1) or domain (example.com)." />
 | 
			
		||||
                <x-forms.input id="user" label  ="User" required />
 | 
			
		||||
                {{-- <x-forms.input id="user" label="User" required /> --}}
 | 
			
		||||
                <x-forms.input type="number" id="port" label="Port" required />
 | 
			
		||||
            </div>
 | 
			
		||||
            <x-forms.select label="Private Key" id="private_key_id">
 | 
			
		||||
 
 | 
			
		||||
@@ -618,7 +618,7 @@
 | 
			
		||||
    "n8n": {
 | 
			
		||||
        "documentation": "https:\/\/n8n.io",
 | 
			
		||||
        "slogan": "n8n is an extendable workflow automation tool.",
 | 
			
		||||
        "compose": "c2VydmljZXM6CiAgbjhuOgogICAgaW1hZ2U6IGRvY2tlci5uOG4uaW8vbjhuaW8vbjhuCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX0ZRRE5fTjhOXzU2NzgKICAgICAgLSAnTjhOX0VESVRPUl9CQVNFX1VSTD0ke1NFUlZJQ0VfRlFETl9OOE59JwogICAgICAtICdXRUJIT09LX1VSTD0ke1NFUlZJQ0VfRlFETl9OOE59JwogICAgICAtICdOOE5fSE9TVD0ke1NFUlZJQ0VfVVJMX044Tn0nCiAgICAgIC0gJ0dFTkVSSUNfVElNRVpPTkU9IkV1cm9wZS9CZXJsaW4iJwogICAgICAtICdUWj0iRXVyb3BlL0JlcmxpbiInCiAgICB2b2x1bWVzOgogICAgICAtICduOG4tZGF0YTovaG9tZS9ub2RlLy5uOG4nCg==",
 | 
			
		||||
        "compose": "c2VydmljZXM6CiAgbjhuOgogICAgaW1hZ2U6IGRvY2tlci5uOG4uaW8vbjhuaW8vbjhuCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX0ZRRE5fTjhOXzU2NzgKICAgICAgLSAnTjhOX0VESVRPUl9CQVNFX1VSTD0ke1NFUlZJQ0VfRlFETl9OOE59JwogICAgICAtICdXRUJIT09LX1VSTD0ke1NFUlZJQ0VfRlFETl9OOE59JwogICAgICAtICdOOE5fSE9TVD0ke1NFUlZJQ0VfVVJMX044Tn0nCiAgICAgIC0gJ0dFTkVSSUNfVElNRVpPTkU9IkV1cm9wZS9CZXJsaW4iJwogICAgICAtICdUWj0iRXVyb3BlL0JlcmxpbiInCiAgICB2b2x1bWVzOgogICAgICAtICduOG4tZGF0YTovaG9tZS9ub2RlLy5uOG4nCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRC1TSEVMTAogICAgICAgIC0gJ3dnZXQgLXFPLSBodHRwOi8vbG9jYWxob3N0OjU2NzgvJwogICAgICBpbnRlcnZhbDogNXMKICAgICAgdGltZW91dDogMjBzCiAgICAgIHJldHJpZXM6IDEwCg==",
 | 
			
		||||
        "tags": [
 | 
			
		||||
            "n8n",
 | 
			
		||||
            "workflow",
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user