Skip to content

Commit 782eabf

Browse files
author
David Courtey
authored
feat: add SSH tunnel support for database connections (#67)
Enable backups and restores for databases behind firewalls via SSH tunneling (bastion/jump server). The system establishes an SSH tunnel before database operations and routes traffic through it. - Add SshTunnelService for tunnel lifecycle management - Support password and private key authentication - Integrate tunneling into BackupTask, RestoreTask, and connection testing - Add UI for SSH configuration with independent connection test - Encrypt sensitive fields (password, private_key, passphrase) at rest - Mask SSH secrets in logs
1 parent 7daa665 commit 782eabf

37 files changed

Lines changed: 2646 additions & 207 deletions

.env

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,9 @@ OAUTH_OIDC_LABEL=SSO
127127
# Enable failure notifications for backup/restore jobs (default: false)
128128
NOTIFICATION_ENABLED=false
129129

130+
# Notification channels (comma-separated): mail, slack, discord
131+
NOTIFICATION_CHANNELS=mail
132+
130133
# Email recipient for failure notifications
131134
NOTIFICATION_MAIL_TO=
132135

.env.mysql.testing

Lines changed: 0 additions & 6 deletions
This file was deleted.

.env.postgres.testing

Lines changed: 0 additions & 6 deletions
This file was deleted.

Makefile

Lines changed: 6 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -49,35 +49,17 @@ create-bucket: ## Create S3 bucket in rustfs (usage: make create-bucket BUCKET=m
4949
test: ## Run all tests in parallel (default)
5050
$(PHP_ARTISAN) test --parallel
5151

52-
test-sequential: ## Run all tests sequentially (for debugging)
53-
$(PHP_ARTISAN) test
54-
55-
test-mysql: ## Run all tests with MySQL
56-
$(DOCKER_COMPOSE) exec -T mysql mysql -uroot -proot -e "DROP DATABASE IF EXISTS databasement_app_test; CREATE DATABASE databasement_app_test;" 2>/dev/null || true
57-
$(DOCKER_COMPOSE) exec -T -e EXTRA_ENV_FILE=.env.mysql.testing $(PHP_SERVICE) php artisan test
58-
5952
test-filter: ## Run tests with filter (usage: make test-filter FILTER=DatabaseServer)
60-
$(PHP_ARTISAN) test --filter="$(FILTER)"
61-
62-
test-filter-mysql: ## Run tests with filter using MySQL (usage: make test-filter-mysql FILTER=DatabaseServer)
63-
$(DOCKER_COMPOSE) exec -T mysql mysql -uroot -proot -e "CREATE DATABASE IF NOT EXISTS databasement_app_test;" 2>/dev/null || true
64-
$(DOCKER_COMPOSE) exec -T -e EXTRA_ENV_FILE=.env.mysql.testing $(PHP_SERVICE) php artisan test --filter="$(FILTER)"
65-
66-
test-postgres: ## Run all tests with PostgreSQL
67-
$(DOCKER_COMPOSE) exec -T postgres psql -U root -d postgres -c "DROP DATABASE IF EXISTS databasement_app_test;"
68-
$(DOCKER_COMPOSE) exec -T postgres psql -U root -d postgres -c "CREATE DATABASE databasement_app_test;"
69-
$(DOCKER_COMPOSE) exec -T -e EXTRA_ENV_FILE=.env.postgres.testing $(PHP_SERVICE) php artisan test
70-
71-
test-filter-postgres: ## Run tests with filter using PostgreSQL (usage: make test-filter-postgres FILTER=DatabaseServer)
72-
$(DOCKER_COMPOSE) exec -T postgres psql -U root -d postgres -c "DROP DATABASE IF EXISTS databasement_app_test;"
73-
$(DOCKER_COMPOSE) exec -T postgres psql -U root -d postgres -c "CREATE DATABASE databasement_app_test;"
74-
$(DOCKER_COMPOSE) exec -T -e EXTRA_ENV_FILE=.env.postgres.testing $(PHP_SERVICE) php artisan test --filter="$(FILTER)"
53+
$(PHP_ARTISAN) test --parallel --filter="$(FILTER)"
7554

7655
test-coverage: ## Run tests with coverage
77-
$(PHP_ARTISAN) test --coverage
56+
$(PHP_ARTISAN) test --parallel --coverage
7857

7958
test-coverage-filter: ## Run tests with coverage and filter (usage: make test-coverage-filter FILTER=FailureNotification)
80-
$(PHP_EXEC) php -d xdebug.mode=coverage ./vendor/bin/pest --filter="$(FILTER)" --coverage
59+
$(PHP_ARTISAN) test --parallel --coverage --filter="$(FILTER)"
60+
61+
test-sequential: ## Run all tests sequentially (for debugging)
62+
$(PHP_ARTISAN) test
8163

8264
##@ Code Quality
8365

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
3+
namespace App\Exceptions;
4+
5+
use RuntimeException;
6+
7+
class SshTunnelException extends RuntimeException
8+
{
9+
public function __construct(
10+
string $message,
11+
int $code = 0,
12+
?\Throwable $previous = null,
13+
public readonly ?string $sshErrorOutput = null
14+
) {
15+
parent::__construct($message, $code, $previous);
16+
}
17+
}

app/Facades/DatabaseConnectionTester.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
use Illuminate\Support\Facades\Facade;
66

77
/**
8-
* @method static array<string, mixed> test(array<string, mixed> $config)
8+
* @method static array<string, mixed> test(array<string, mixed> $config, \App\Models\DatabaseServerSshConfig|null $sshConfig = null)
99
*
1010
* @see \App\Services\DatabaseConnectionTester
1111
*/

app/Livewire/DatabaseServer/Create.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,11 @@ public function testConnection(): void
4343
$this->form->testConnection();
4444
}
4545

46+
public function testSshConnection(): void
47+
{
48+
$this->form->testSshConnection();
49+
}
50+
4651
public function refreshVolumes(): void
4752
{
4853
$this->success(__('Volume list refreshed.'), position: 'toast-bottom');

app/Livewire/DatabaseServer/Edit.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,11 +45,23 @@ public function testConnection(): void
4545
$this->form->testConnection();
4646
}
4747

48+
public function testSshConnection(): void
49+
{
50+
$this->form->testSshConnection();
51+
}
52+
4853
public function refreshVolumes(): void
4954
{
5055
$this->success(__('Volume list refreshed.'), position: 'toast-bottom');
5156
}
5257

58+
public function loadDatabases(): void
59+
{
60+
if (! $this->form->isSqlite()) {
61+
$this->form->loadAvailableDatabases();
62+
}
63+
}
64+
5365
public function render(): View
5466
{
5567
return view('livewire.database-server.edit')

app/Livewire/DatabaseServer/Index.php

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,7 @@ public function clear(): void
6161
public function headers(): array
6262
{
6363
return [
64-
['key' => 'name', 'label' => __('Name'), 'class' => 'w-64'],
65-
['key' => 'host', 'label' => __('Connection'), 'class' => 'w-48'],
64+
['key' => 'name', 'label' => __('Name'), 'class' => 'w-80'],
6665
['key' => 'database_names', 'label' => __('Databases'), 'sortable' => false],
6766
['key' => 'backup', 'label' => __('Backup'), 'sortable' => false],
6867
['key' => 'jobs', 'label' => __('Jobs'), 'sortable' => false, 'class' => 'w-32'],

0 commit comments

Comments
 (0)