From 14be2262afa2df088860999a96107b1efade5285 Mon Sep 17 00:00:00 2001 From: khmish <30586743+khmish@users.noreply.github.com> Date: Wed, 11 Feb 2026 14:55:31 +0300 Subject: [PATCH 1/3] feat: Add configurable SSH port for source control providers and SSH cloning. feat: Add configurable SSH port for source control providers and SSH cloning. --- .../SourceControlServiceProvider.php | 3 ++ app/SSH/OS/Git.php | 1 + .../AbstractSourceControlProvider.php | 5 +++ app/SourceControlProviders/Gitlab.php | 42 ++++++++++++------- .../SourceControlProvider.php | 2 +- resources/views/ssh/git/clone.blade.php | 19 +++++---- 6 files changed, 48 insertions(+), 24 deletions(-) diff --git a/app/Providers/SourceControlServiceProvider.php b/app/Providers/SourceControlServiceProvider.php index e0b34de8f..6da547ed6 100644 --- a/app/Providers/SourceControlServiceProvider.php +++ b/app/Providers/SourceControlServiceProvider.php @@ -53,6 +53,9 @@ private function gitlab(): void DynamicField::make('url') ->text() ->label('Self hosted URL'), + DynamicField::make('port') + ->text() + ->label('SSH Port'), ]) ) ->register(); diff --git a/app/SSH/OS/Git.php b/app/SSH/OS/Git.php index 8813a97f4..527bf0f5f 100644 --- a/app/SSH/OS/Git.php +++ b/app/SSH/OS/Git.php @@ -19,6 +19,7 @@ public function clone(Site $site, ?string $path = null): void 'path' => $path ?? $site->path, 'branch' => $site->branch, 'key' => $site->getSshKeyName(), + 'port' => $site->sourceControl?->provider()?->getSshPort() ?? 22, ]), 'clone-repository', $site->id diff --git a/app/SourceControlProviders/AbstractSourceControlProvider.php b/app/SourceControlProviders/AbstractSourceControlProvider.php index 2a616bf8e..ea7b8e411 100755 --- a/app/SourceControlProviders/AbstractSourceControlProvider.php +++ b/app/SourceControlProviders/AbstractSourceControlProvider.php @@ -70,4 +70,9 @@ public function getBranches(string $repo, bool $useCache = true): array { return []; } + + public function getSshPort(): ?int + { + return null; + } } diff --git a/app/SourceControlProviders/Gitlab.php b/app/SourceControlProviders/Gitlab.php index dd56bbbb7..b151126f5 100755 --- a/app/SourceControlProviders/Gitlab.php +++ b/app/SourceControlProviders/Gitlab.php @@ -38,6 +38,7 @@ public function createRules(array $input): array 'url:http,https', 'ends_with:/', ], + 'port' => 'nullable|integer', ]; } @@ -45,7 +46,7 @@ public function connect(): bool { try { $res = Http::withToken($this->data()['token']) - ->get($this->getApiUrl().'/version'); + ->get($this->getApiUrl() . '/version'); } catch (Exception) { return false; } @@ -60,7 +61,7 @@ public function getRepo(string $repo): mixed { $repository = $repo !== '' && $repo !== '0' ? urlencode($repo) : null; $res = Http::withToken($this->data()['token']) - ->get($this->getApiUrl().'/projects/'.$repository.'/repository/commits'); + ->get($this->getApiUrl() . '/projects/' . $repository . '/repository/commits'); $this->handleResponseErrors($res, $repo); @@ -82,10 +83,10 @@ public function deployHook(string $repo, array $events, string $secret): array $repository = urlencode($repo); try { $response = Http::withToken($this->data()['token'])->post( - $this->getApiUrl().'/projects/'.$repository.'/hooks', + $this->getApiUrl() . '/projects/' . $repository . '/hooks', [ 'description' => 'deploy', - 'url' => url('/api/git-hooks?secret='.$secret), + 'url' => url('/api/git-hooks?secret=' . $secret), 'push_events' => in_array('push', $events), 'issues_events' => false, 'job_events' => false, @@ -121,7 +122,7 @@ public function destroyHook(string $repo, string $hookId): void $repository = urlencode($repo); try { $response = Http::withToken($this->data()['token'])->delete( - $this->getApiUrl().'/projects/'.$repository.'/hooks/'.$hookId + $this->getApiUrl() . '/projects/' . $repository . '/hooks/' . $hookId ); } catch (Exception $e) { throw new FailedToDestroyGitHook($e->getMessage()); @@ -139,7 +140,7 @@ public function getLastCommit(string $repo, string $branch): ?array { $repository = urlencode($repo); $res = Http::withToken($this->data()['token']) - ->get($this->getApiUrl().'/projects/'.$repository.'/repository/commits?ref_name='.$branch); + ->get($this->getApiUrl() . '/projects/' . $repository . '/repository/commits?ref_name=' . $branch); $this->handleResponseErrors($res, $repo); @@ -167,7 +168,7 @@ public function deployKey(string $title, string $repo, string $key): string $repository = urlencode($repo); try { $response = Http::withToken($this->data()['token'])->post( - $this->getApiUrl().'/projects/'.$repository.'/deploy_keys', + $this->getApiUrl() . '/projects/' . $repository . '/deploy_keys', [ 'title' => $title, 'key' => $key, @@ -190,10 +191,10 @@ public function deleteDeployKey(string $keyId, string $repo): void try { $repository = urlencode($repo); $response = Http::withToken($this->data()['token'])->delete( - $this->getApiUrl().'/projects/'.$repository.'/deploy_keys/'.$keyId + $this->getApiUrl() . '/projects/' . $repository . '/deploy_keys/' . $keyId ); - if (! $response->successful()) { + if (!$response->successful()) { Log::warning('Failed to delete Gitlab deploy key', [ 'repo' => $repo, 'key_id' => $keyId, @@ -210,16 +211,29 @@ public function deleteDeployKey(string $keyId, string $repo): void } } + public function createData(array $input): array + { + return [ + 'token' => $input['token'] ?? '', + 'port' => $input['port'] ?? null, + ]; + } + + public function getSshPort(): ?int + { + return (int) ($this->sourceControl->provider_data['port'] ?? null) ?: null; + } + public function getApiUrl(): string { $host = $this->sourceControl->url ?? $this->defaultApiHost; - return $host.$this->apiVersion; + return $host . $this->apiVersion; } public function getRepos(bool $useCache = true): array { - $cacheKey = 'gitlab_repos_'.md5($this->getApiUrl().$this->data()['token']); + $cacheKey = 'gitlab_repos_' . md5($this->getApiUrl() . $this->data()['token']); if ($useCache && Cache::has($cacheKey)) { return Cache::get($cacheKey); @@ -247,7 +261,7 @@ public function getRepos(bool $useCache = true): array public function getBranches(string $repo, bool $useCache = true): array { - $cacheKey = 'gitlab_branches_'.md5($repo.$this->getApiUrl().$this->data()['token']); + $cacheKey = 'gitlab_branches_' . md5($repo . $this->getApiUrl() . $this->data()['token']); if ($useCache && Cache::has($cacheKey)) { return Cache::get($cacheKey); @@ -291,9 +305,9 @@ private function fetchAllPages(string $endpoint, array $params = []): Collection while ($hasMore) { $params['page'] = $page; $response = Http::withToken($this->data()['token']) - ->get($this->getApiUrl().$endpoint, $params); + ->get($this->getApiUrl() . $endpoint, $params); - if (! $response->successful()) { + if (!$response->successful()) { Log::error('GitLab API request failed', [ 'endpoint' => $endpoint, 'status' => $response->status(), diff --git a/app/SourceControlProviders/SourceControlProvider.php b/app/SourceControlProviders/SourceControlProvider.php index da28346a8..6b57835b6 100755 --- a/app/SourceControlProviders/SourceControlProvider.php +++ b/app/SourceControlProviders/SourceControlProvider.php @@ -64,6 +64,6 @@ public function deleteDeployKey(string $keyId, string $repo): void; public function getWebhookBranch(array $payload): string; public function getRepos(bool $useCache = true): array; - public function getBranches(string $repo, bool $useCache = true): array; + public function getSshPort(): ?int; } diff --git a/resources/views/ssh/git/clone.blade.php b/resources/views/ssh/git/clone.blade.php index b1efcc5a5..5b1c49ede 100755 --- a/resources/views/ssh/git/clone.blade.php +++ b/resources/views/ssh/git/clone.blade.php @@ -1,29 +1,30 @@ echo "Host {{ $host }}-{{ $key }} - Hostname {{ $host }} - IdentityFile=~/.ssh/{{ $key }}" >> ~/.ssh/config +Hostname {{ $host }} +Port {{ $port }} +IdentityFile=~/.ssh/{{ $key }}" >> ~/.ssh/config chmod 600 ~/.ssh/config -ssh-keyscan -H {{ $host }} >> ~/.ssh/known_hosts +ssh-keyscan -p {{ $port }} -H {{ $host }} >> ~/.ssh/known_hosts rm -rf {{ $path }} if ! git config --global core.fileMode false; then - echo 'VITO_SSH_ERROR' && exit 1 +echo 'VITO_SSH_ERROR' && exit 1 fi if ! git clone -b {{ $branch }} {{ $repo }} {{ $path }}; then - echo 'VITO_SSH_ERROR' && exit 1 +echo 'VITO_SSH_ERROR' && exit 1 fi if ! find {{ $path }} -type d -exec chmod 755 {} \;; then - echo 'VITO_SSH_ERROR' && exit 1 +echo 'VITO_SSH_ERROR' && exit 1 fi if ! find {{ $path }} -type f -exec chmod 644 {} \;; then - echo 'VITO_SSH_ERROR' && exit 1 +echo 'VITO_SSH_ERROR' && exit 1 fi if ! cd {{ $path }} && git config core.fileMode false; then - echo 'VITO_SSH_ERROR' && exit 1 -fi +echo 'VITO_SSH_ERROR' && exit 1 +fi \ No newline at end of file From 3127f16b5cf2cbe03d7d418106a33c5dc9fa1122 Mon Sep 17 00:00:00 2001 From: khmish <30586743+khmish@users.noreply.github.com> Date: Thu, 12 Feb 2026 08:14:18 +0300 Subject: [PATCH 2/3] feat: Add a port column to source controls table and update related models and provider logic to use it. --- .../SourceControl/ConnectSourceControl.php | 5 +++-- app/Models/SourceControl.php | 5 ++++- .../AbstractSourceControlProvider.php | 2 +- app/SourceControlProviders/Gitlab.php | 12 ----------- ...4800_add_port_to_source_controls_table.php | 21 +++++++++++++++++++ 5 files changed, 29 insertions(+), 16 deletions(-) create mode 100644 database/migrations/2026_02_12_074800_add_port_to_source_controls_table.php diff --git a/app/Actions/SourceControl/ConnectSourceControl.php b/app/Actions/SourceControl/ConnectSourceControl.php index 9fbb0daf3..92bc4146b 100644 --- a/app/Actions/SourceControl/ConnectSourceControl.php +++ b/app/Actions/SourceControl/ConnectSourceControl.php @@ -23,6 +23,7 @@ public function connect(User $user, array $input): SourceControl 'provider' => $input['provider'], 'profile' => $input['name'], 'url' => isset($input['url']) && $input['url'] ? $input['url'] : null, + 'port' => isset($input['port']) && $input['port'] ? (int) $input['port'] : null, 'project_id' => isset($input['global']) && $input['global'] ? null : $user->currentProject?->id, 'user_id' => $user->id, ]); @@ -30,7 +31,7 @@ public function connect(User $user, array $input): SourceControl $sourceControl->provider_data = $sourceControl->provider()->createData($input); try { - if (! $sourceControl->provider()->connect()) { + if (!$sourceControl->provider()->connect()) { throw ValidationException::withMessages([ 'provider' => __('Cannot connect to :provider or invalid credentials!', ['provider' => $sourceControl->provider]), ]); @@ -75,7 +76,7 @@ private function validate(array $input): void */ private function providerRules(array $input): array { - if (! isset($input['provider'])) { + if (!isset($input['provider'])) { return []; } diff --git a/app/Models/SourceControl.php b/app/Models/SourceControl.php index 0a96b47e9..897ca2bb3 100755 --- a/app/Models/SourceControl.php +++ b/app/Models/SourceControl.php @@ -15,6 +15,7 @@ * @property array $provider_data * @property string $profile * @property ?string $url + * @property ?int $port * @property string $access_token * @property ?int $project_id * @property int $user_id @@ -33,6 +34,7 @@ class SourceControl extends AbstractModel 'provider_data', 'profile', 'url', + 'port', 'access_token', 'project_id', 'user_id', @@ -41,13 +43,14 @@ class SourceControl extends AbstractModel protected $casts = [ 'access_token' => 'encrypted', 'provider_data' => 'encrypted:array', + 'port' => 'integer', 'project_id' => 'integer', 'user_id' => 'integer', ]; public function provider(): SourceControlProvider { - $providerClass = config('source-control.providers.'.$this->provider.'.handler'); + $providerClass = config('source-control.providers.' . $this->provider . '.handler'); /** @var SourceControlProvider $provider */ $provider = new $providerClass($this); diff --git a/app/SourceControlProviders/AbstractSourceControlProvider.php b/app/SourceControlProviders/AbstractSourceControlProvider.php index ea7b8e411..034318623 100755 --- a/app/SourceControlProviders/AbstractSourceControlProvider.php +++ b/app/SourceControlProviders/AbstractSourceControlProvider.php @@ -73,6 +73,6 @@ public function getBranches(string $repo, bool $useCache = true): array public function getSshPort(): ?int { - return null; + return (int) $this->sourceControl->port ?: ((int) ($this->sourceControl->provider_data['port'] ?? null) ?: null); } } diff --git a/app/SourceControlProviders/Gitlab.php b/app/SourceControlProviders/Gitlab.php index b151126f5..ade3b9655 100755 --- a/app/SourceControlProviders/Gitlab.php +++ b/app/SourceControlProviders/Gitlab.php @@ -211,18 +211,6 @@ public function deleteDeployKey(string $keyId, string $repo): void } } - public function createData(array $input): array - { - return [ - 'token' => $input['token'] ?? '', - 'port' => $input['port'] ?? null, - ]; - } - - public function getSshPort(): ?int - { - return (int) ($this->sourceControl->provider_data['port'] ?? null) ?: null; - } public function getApiUrl(): string { diff --git a/database/migrations/2026_02_12_074800_add_port_to_source_controls_table.php b/database/migrations/2026_02_12_074800_add_port_to_source_controls_table.php new file mode 100644 index 000000000..96cb1a761 --- /dev/null +++ b/database/migrations/2026_02_12_074800_add_port_to_source_controls_table.php @@ -0,0 +1,21 @@ +unsignedInteger('port')->nullable()->after('url'); + }); + } + + public function down(): void + { + Schema::table('source_controls', function (Blueprint $table): void { + $table->dropColumn('port'); + }); + } +}; From 9500f1b40d401cb0ba19961629b23ae9bed5a9ac Mon Sep 17 00:00:00 2001 From: khmish <30586743+khmish@users.noreply.github.com> Date: Sun, 15 Feb 2026 13:59:52 +0300 Subject: [PATCH 3/3] feat: add method to retrieve SSH port for GitLab provider --- app/SourceControlProviders/Gitlab.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/SourceControlProviders/Gitlab.php b/app/SourceControlProviders/Gitlab.php index ade3b9655..3a5616d73 100755 --- a/app/SourceControlProviders/Gitlab.php +++ b/app/SourceControlProviders/Gitlab.php @@ -338,4 +338,8 @@ private function fetchAllPages(string $endpoint, array $params = []): Collection return $allData; } + public function getSshPort(): ?int + { + return (int) $this->sourceControl->port ?: ((int) ($this->sourceControl->provider_data['port'] ?? null) ?: null); + } }