diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index 46149444f8..03d4794003 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -4,7 +4,6 @@ use App\Jobs\CheckForUpdatesJob; use App\Jobs\CleanupInstanceStuffsJob; -use App\Jobs\CleanupSshKeysJob; use App\Jobs\CleanupStaleMultiplexedConnections; use App\Jobs\DatabaseBackupJob; use App\Jobs\DockerCleanupJob; @@ -45,7 +44,6 @@ protected function schedule(Schedule $schedule): void $schedule->command('telescope:prune')->daily(); - $schedule->job(new CleanupSshKeysJob)->weekly()->onOneServer(); $schedule->job(new PullHelperImageJob)->everyFiveMinutes()->onOneServer(); } else { // Instance Jobs @@ -63,8 +61,6 @@ protected function schedule(Schedule $schedule): void $schedule->command('cleanup:database --yes')->daily(); $schedule->command('uploads:clear')->everyTwoMinutes(); - - $schedule->job(new CleanupSshKeysJob)->weekly()->onOneServer(); } } diff --git a/app/Helpers/SshMultiplexingHelper.php b/app/Helpers/SshMultiplexingHelper.php index 57d4c88a47..71be77506c 100644 --- a/app/Helpers/SshMultiplexingHelper.php +++ b/app/Helpers/SshMultiplexingHelper.php @@ -2,10 +2,10 @@ namespace App\Helpers; -use App\Models\Server; use App\Models\PrivateKey; -use Illuminate\Support\Facades\Process; +use App\Models\Server; use Illuminate\Support\Facades\Hash; +use Illuminate\Support\Facades\Process; class SshMultiplexingHelper { @@ -13,7 +13,7 @@ public static function serverSshConfiguration(Server $server) { $privateKey = PrivateKey::findOrFail($server->private_key_id); $sshKeyLocation = $privateKey->getKeyLocation(); - $muxFilename = '/var/www/html/storage/app/ssh/mux/mux_' . $server->uuid; + $muxFilename = '/var/www/html/storage/app/ssh/mux/mux_'.$server->uuid; return [ 'sshKeyLocation' => $sshKeyLocation, @@ -23,7 +23,7 @@ public static function serverSshConfiguration(Server $server) public static function ensureMultiplexedConnection(Server $server) { - if (!self::isMultiplexingEnabled()) { + if (! self::isMultiplexingEnabled()) { // ray('SSH Multiplexing: DISABLED')->red(); return; } @@ -64,8 +64,8 @@ public static function establishNewMultiplexedConnection(Server $server) $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}"; + .self::getCommonSshOptions($server, $sshKeyLocation, $connectionTimeout, $serverInterval) + ."{$server->user}@{$server->ip}"; // ray('Establish Command:', $establishCommand); @@ -77,13 +77,13 @@ public static function establishNewMultiplexedConnection(Server $server) if ($establishProcess->exitCode() !== 0) { // ray('Failed to establish multiplexed connection')->red(); - throw new \RuntimeException('Failed to establish multiplexed connection: ' . $establishProcess->errorOutput()); + throw new \RuntimeException('Failed to establish multiplexed connection: '.$establishProcess->errorOutput()); } // ray('Successfully established multiplexed connection')->green(); // Check if the mux socket file was created - if (!file_exists($muxSocket)) { + if (! file_exists($muxSocket)) { // ray('Mux socket file not found after connection establishment')->orange(); } } @@ -92,10 +92,10 @@ public static function removeMuxFile(Server $server) { $sshConfig = self::serverSshConfiguration($server); $muxSocket = $sshConfig['muxFilename']; - + $closeCommand = "ssh -O exit -o ControlPath=$muxSocket {$server->user}@{$server->ip}"; $process = Process::run($closeCommand); - + // ray('Closing multiplexed connection')->blue(); // ray('Close command:', $closeCommand); // ray('Close process exit code:', $process->exitCode()); @@ -127,7 +127,7 @@ public static function generateScpCommand(Server $server, string $source, string self::addCloudflareProxyCommand($scp_command, $server); - $scp_command .= self::getCommonSshOptions($server, $sshKeyLocation, config('constants.ssh.connection_timeout'), config('constants.ssh.server_interval')); + $scp_command .= self::getCommonSshOptions($server, $sshKeyLocation, config('constants.ssh.connection_timeout'), config('constants.ssh.server_interval'), isScp: true); $scp_command .= "{$source} {$server->user}@{$server->ip}:{$dest}"; return $scp_command; @@ -170,7 +170,7 @@ public static function generateSshCommand(Server $server, string $command) private static function isMultiplexingEnabled(): bool { - return config('constants.ssh.mux_enabled') && !config('coolify.is_windows_docker_desktop'); + return config('constants.ssh.mux_enabled') && ! config('coolify.is_windows_docker_desktop'); } private static function validateSshKey(string $sshKeyLocation): void @@ -190,15 +190,23 @@ private static function addCloudflareProxyCommand(string &$command, Server $serv } } - private static function getCommonSshOptions(Server $server, string $sshKeyLocation, int $connectionTimeout, int $serverInterval): string + private static function getCommonSshOptions(Server $server, string $sshKeyLocation, int $connectionTimeout, int $serverInterval, bool $isScp = false): string { - return "-i {$sshKeyLocation} " + $options = "-i {$sshKeyLocation} " .'-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null ' .'-o PasswordAuthentication=no ' ."-o ConnectTimeout=$connectionTimeout " ."-o ServerAliveInterval=$serverInterval " .'-o RequestTTY=no ' - .'-o LogLevel=ERROR ' - ."-p {$server->port} "; + .'-o LogLevel=ERROR '; + + // Bruh + if ($isScp) { + $options .= "-P {$server->port} "; + } else { + $options .= "-p {$server->port} "; + } + + return $options; } -} \ No newline at end of file +} diff --git a/app/Livewire/Server/ShowPrivateKey.php b/app/Livewire/Server/ShowPrivateKey.php index 8e9b591572..92869c44b2 100644 --- a/app/Livewire/Server/ShowPrivateKey.php +++ b/app/Livewire/Server/ShowPrivateKey.php @@ -2,9 +2,9 @@ namespace App\Livewire\Server; +use App\Models\PrivateKey; use App\Models\Server; use Livewire\Component; -use App\Models\PrivateKey; class ShowPrivateKey extends Component { @@ -22,7 +22,7 @@ public function setPrivateKey($privateKeyId) $this->server->refresh(); $this->dispatch('success', 'Private key updated successfully.'); } catch (\Exception $e) { - $this->dispatch('error', 'Failed to update private key: ' . $e->getMessage()); + $this->dispatch('error', 'Failed to update private key: '.$e->getMessage()); } } @@ -34,7 +34,7 @@ public function checkConnection() $this->dispatch('success', 'Server is reachable.'); } else { ray($error); - $this->dispatch('error', 'Server is not reachable.
Please validate your configuration and connection.

Check this documentation for further help.'); + $this->dispatch('error', 'Server is not reachable.

Check this documentation for further help.

Error: '.$error); return; } diff --git a/bootstrap/helpers/remoteProcess.php b/bootstrap/helpers/remoteProcess.php index ebc8420c62..67b60d6b75 100644 --- a/bootstrap/helpers/remoteProcess.php +++ b/bootstrap/helpers/remoteProcess.php @@ -11,8 +11,8 @@ use Carbon\Carbon; use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Collection; -use Illuminate\Support\Facades\Process; use Illuminate\Support\Facades\Auth; +use Illuminate\Support\Facades\Process; use Illuminate\Support\Str; use Spatie\Activitylog\Contracts\Activity; @@ -28,16 +28,16 @@ function remote_process( ): Activity { $type = $type ?? ActivityTypes::INLINE->value; $command = $command instanceof Collection ? $command->toArray() : $command; - + if ($server->isNonRoot()) { $command = parseCommandsByLineForSudo(collect($command), $server); } - + $command_string = implode("\n", $command); - + if (Auth::check()) { $teams = Auth::user()->teams->pluck('id'); - if (!$teams->contains($server->team_id) && !$teams->contains(0)) { + if (! $teams->contains($server->team_id) && ! $teams->contains(0)) { throw new \Exception('User is not part of the team that owns this server'); } } @@ -67,13 +67,14 @@ function instant_scp(string $source, string $dest, Server $server, $throwError = if ($exitCode !== 0) { return $throwError ? excludeCertainErrors($process->errorOutput(), $exitCode) : null; } + return $output === 'null' ? null : $output; } function instant_remote_process(Collection|array $command, Server $server, bool $throwError = true, bool $no_sudo = false): ?string { $command = $command instanceof Collection ? $command->toArray() : $command; - if ($server->isNonRoot() && !$no_sudo) { + if ($server->isNonRoot() && ! $no_sudo) { $command = parseCommandsByLineForSudo(collect($command), $server); } $command_string = implode("\n", $command); @@ -92,6 +93,7 @@ function instant_remote_process(Collection|array $command, Server $server, bool if ($exitCode !== 0) { return $throwError ? excludeCertainErrors($process->errorOutput(), $exitCode) : null; } + return $output === 'null' ? null : $output; } @@ -101,7 +103,7 @@ function excludeCertainErrors(string $errorOutput, ?int $exitCode = null) 'Permission denied (publickey', 'Could not resolve hostname', ]); - $ignored = $ignoredErrors->contains(fn($error) => Str::contains($errorOutput, $error)); + $ignored = $ignoredErrors->contains(fn ($error) => Str::contains($errorOutput, $error)); if ($ignored) { // TODO: Create new exception and disable in sentry throw new \RuntimeException($errorOutput, $exitCode); @@ -127,19 +129,21 @@ function decode_remote_command_output(?ApplicationDeploymentQueue $application_d } $seenCommands = collect(); $formatted = collect($decoded); - if (!$is_debug_enabled) { + if (! $is_debug_enabled) { $formatted = $formatted->filter(fn ($i) => $i['hidden'] === false ?? false); } + return $formatted ->sortBy(fn ($i) => data_get($i, 'order')) ->map(function ($i) { data_set($i, 'timestamp', Carbon::parse(data_get($i, 'timestamp'))->format('Y-M-d H:i:s.u')); + return $i; }) ->reduce(function ($deploymentLogLines, $logItem) use ($seenCommands) { $command = data_get($logItem, 'command'); $isStderr = data_get($logItem, 'type') === 'stderr'; - $isNewCommand = !is_null($command) && !$seenCommands->first(function ($seenCommand) use ($logItem) { + $isNewCommand = ! is_null($command) && ! $seenCommands->first(function ($seenCommand) use ($logItem) { return data_get($seenCommand, 'command') === data_get($logItem, 'command') && data_get($seenCommand, 'batch') === data_get($logItem, 'batch'); }); @@ -176,6 +180,7 @@ function decode_remote_command_output(?ApplicationDeploymentQueue $application_d function remove_iip($text) { $text = preg_replace('/x-access-token:.*?(?=@)/', 'x-access-token:'.REDACTED, $text); + return preg_replace('/\x1b\[[0-9;]*m/', '', $text); } @@ -203,7 +208,7 @@ function checkRequiredCommands(Server $server) break; } $commandFound = instant_remote_process(["docker run --rm --privileged --net=host --pid=host --ipc=host --volume /:/host busybox chroot /host bash -c 'command -v {$command}'"], $server, false); - if (!$commandFound) { + if (! $commandFound) { break; } }