artemyurov/laravel-autossh-tunnel
最新稳定版本:v0.5.1
Composer 安装命令:
composer require artemyurov/laravel-autossh-tunnel
包简介
Modern SSH Tunnel Manager for Laravel with autossh support and automatic lifecycle management
README 文档
README
Modern SSH Tunnel Manager for Laravel with autossh support and automatic lifecycle management.
Features
- 🚀 Automatic tunnel lifecycle management via callback pattern
- 🔄 AutoSSH support for automatic reconnection
- 🛡️ Comprehensive error handling with detailed messages
- 🔌 Port availability checking
- 🗄️ Laravel Database connections integration
- ⚙️ Flexible configuration (env, config files or direct parameters)
- ✅ Configuration validation
- 📝 Detailed logging
- 🎯 Multiple simultaneous tunnels support
Installation
composer require artemyurov/laravel-autossh-tunnel
Publish Configuration
# Publish config and .env example php artisan vendor:publish --tag=tunnel # Or publish separately php artisan vendor:publish --tag=tunnel-config # config/tunnel.php only php artisan vendor:publish --tag=tunnel-env # .env.example.tunnel only
After publishing, copy the tunnel environment variables to your .env:
cat .env.example.tunnel >> .env # Then edit .env with your actual credentials
Configuration
Environment Variables
The configuration uses a clear logical order:
- SSH Connection - How to connect to the SSH server
- Remote/Local - What to forward and where
- SSH Options - Connection behavior settings
# Default tunnel connection TUNNEL_CONNECTION=remote_db TUNNEL_DEBUG=false TUNNEL_AUTOSSH_ENABLED=true # SSH Connection (how to connect) TUNNEL_SSH_USER=your_ssh_user TUNNEL_SSH_HOST=your_server.com TUNNEL_SSH_PORT=22 TUNNEL_SSH_KEY=/path/to/ssh/key # Remote Target (what to forward on SSH server) TUNNEL_REMOTE_HOST=localhost TUNNEL_REMOTE_PORT=5432 # Local Bind (where to bind locally) TUNNEL_LOCAL_HOST=127.0.0.1 TUNNEL_LOCAL_PORT=15432 # SSH Options TUNNEL_SSH_STRICT_HOST_KEY_CHECKING=false TUNNEL_SSH_SERVER_ALIVE_INTERVAL=60 TUNNEL_SSH_SERVER_ALIVE_COUNT_MAX=3 TUNNEL_SSH_EXIT_ON_FORWARD_FAILURE=true TUNNEL_SSH_TCP_KEEP_ALIVE=true TUNNEL_SSH_CONNECT_TIMEOUT=10 # Database connection using tunnel TUNNEL_DB_CONNECTION=pgsql TUNNEL_DB_HOST="${TUNNEL_LOCAL_HOST}" TUNNEL_DB_PORT="${TUNNEL_LOCAL_PORT}" TUNNEL_DB_DATABASE=database_name TUNNEL_DB_USERNAME=db_user TUNNEL_DB_PASSWORD=db_password
Configuration File
After publishing the config, edit config/tunnel.php:
return [ // Default tunnel name 'default' => env('TUNNEL_CONNECTION', 'remote_db'), // Enable detailed logging 'debug' => env('TUNNEL_DEBUG', env('APP_DEBUG', false)), // AutoSSH configuration 'autossh' => [ 'enabled' => env('TUNNEL_AUTOSSH_ENABLED', true), ], // Retry configuration for database operations 'retry' => [ 'max_attempts' => env('TUNNEL_RETRY_MAX_ATTEMPTS', 3), 'delay' => env('TUNNEL_RETRY_DELAY', 2), 'exponential' => env('TUNNEL_RETRY_EXPONENTIAL', false), ], // Connection validation settings 'validation' => [ 'port_timeout' => env('TUNNEL_VALIDATION_PORT_TIMEOUT', 1), 'database_timeout' => env('TUNNEL_VALIDATION_DATABASE_TIMEOUT', 5), 'database_max_attempts' => env('TUNNEL_VALIDATION_DATABASE_MAX_ATTEMPTS', 5), 'database_retry_delay' => env('TUNNEL_VALIDATION_DATABASE_RETRY_DELAY', 2), ], // Signal handling (SIGINT, SIGTERM) 'signals' => [ 'enabled' => env('TUNNEL_SIGNALS_ENABLED', true), 'handlers' => ['SIGINT', 'SIGTERM'], ], // Tunnel reuse settings 'reuse' => [ 'use_pid_file' => env('TUNNEL_REUSE_PID_FILE', true), 'use_port_scan' => env('TUNNEL_REUSE_PORT_SCAN', true), 'pid_directory' => env('TUNNEL_PID_DIRECTORY', sys_get_temp_dir() . '/laravel-autossh-tunnel'), ], 'connections' => [ 'remote_db' => [ 'type' => 'forward', // SSH Connection 'user' => env('TUNNEL_SSH_USER'), 'host' => env('TUNNEL_SSH_HOST'), 'port' => env('TUNNEL_SSH_PORT', 22), 'identity_file' => env('TUNNEL_SSH_KEY'), // Remote Target 'remote_host' => env('TUNNEL_REMOTE_HOST', 'localhost'), 'remote_port' => env('TUNNEL_REMOTE_PORT', 5432), // Local Bind 'local_host' => env('TUNNEL_LOCAL_HOST', '127.0.0.1'), 'local_port' => env('TUNNEL_LOCAL_PORT', 15432), // SSH Options (all optional, defaults shown) 'ssh_options' => [ 'StrictHostKeyChecking' => env('TUNNEL_SSH_STRICT_HOST_KEY_CHECKING', false), 'ServerAliveInterval' => env('TUNNEL_SSH_SERVER_ALIVE_INTERVAL', 60), 'ServerAliveCountMax' => env('TUNNEL_SSH_SERVER_ALIVE_COUNT_MAX', 3), 'ExitOnForwardFailure' => env('TUNNEL_SSH_EXIT_ON_FORWARD_FAILURE', true), 'TCPKeepAlive' => env('TUNNEL_SSH_TCP_KEEP_ALIVE', true), 'ConnectTimeout' => env('TUNNEL_SSH_CONNECT_TIMEOUT', 10), ], ], 'local_webhooks' => [ 'type' => 'reverse', 'user' => env('WEBHOOK_SSH_USER'), 'host' => env('WEBHOOK_SSH_HOST'), 'port' => env('WEBHOOK_SSH_PORT', 22), 'identity_file' => env('WEBHOOK_SSH_KEY'), 'remote_host' => env('WEBHOOK_REMOTE_HOST', 'localhost'), // Or 0.0.0.0 for public access 'remote_port' => env('WEBHOOK_REMOTE_PORT', 8080), 'local_host' => env('WEBHOOK_LOCAL_HOST', '127.0.0.1'), 'local_port' => env('WEBHOOK_LOCAL_PORT', 8000), 'ssh_options' => [ 'StrictHostKeyChecking' => env('WEBHOOK_SSH_STRICT_HOST_KEY_CHECKING', false), 'ServerAliveInterval' => env('WEBHOOK_SSH_SERVER_ALIVE_INTERVAL', 60), 'ServerAliveCountMax' => env('WEBHOOK_SSH_SERVER_ALIVE_COUNT_MAX', 3), 'ExitOnForwardFailure' => env('WEBHOOK_SSH_EXIT_ON_FORWARD_FAILURE', true), 'TCPKeepAlive' => env('WEBHOOK_SSH_TCP_KEEP_ALIVE', true), 'ConnectTimeout' => env('WEBHOOK_SSH_CONNECT_TIMEOUT', 10), ], ], ], ];
Multiple Connections Example
For multiple tunnel connections, use unique prefixes for each connection:
# PostgreSQL Development Server REMOTE_DEV_SSH_USER=www-backend REMOTE_DEV_SSH_HOST=dev.example.com REMOTE_DEV_SSH_PORT=22 REMOTE_DEV_SSH_KEY= REMOTE_DEV_REMOTE_HOST=localhost REMOTE_DEV_REMOTE_PORT=5432 REMOTE_DEV_LOCAL_HOST=127.0.0.1 REMOTE_DEV_LOCAL_PORT=16432 REMOTE_DEV_DB_DATABASE=project_db REMOTE_DEV_DB_USERNAME=db_user REMOTE_DEV_DB_PASSWORD=secret # MySQL Legacy Database LEGACY_SSH_USER=root LEGACY_SSH_HOST=legacy.example.com LEGACY_SSH_PORT=22 LEGACY_REMOTE_HOST=127.0.0.1 LEGACY_REMOTE_PORT=3306 LEGACY_LOCAL_HOST=127.0.0.1 LEGACY_LOCAL_PORT=13306 LEGACY_DB_DATABASE=legacy_db LEGACY_DB_USERNAME=legacy_user LEGACY_DB_PASSWORD=secret
// config/tunnel.php 'connections' => [ 'remote_dev_db' => [ 'type' => 'forward', 'user' => env('REMOTE_DEV_SSH_USER'), 'host' => env('REMOTE_DEV_SSH_HOST'), 'port' => env('REMOTE_DEV_SSH_PORT', 22), 'identity_file' => env('REMOTE_DEV_SSH_KEY'), 'remote_host' => env('REMOTE_DEV_REMOTE_HOST', 'localhost'), 'remote_port' => env('REMOTE_DEV_REMOTE_PORT', 5432), 'local_host' => env('REMOTE_DEV_LOCAL_HOST', '127.0.0.1'), 'local_port' => env('REMOTE_DEV_LOCAL_PORT', 16432), ], 'legacy_db' => [ 'type' => 'forward', 'user' => env('LEGACY_SSH_USER'), 'host' => env('LEGACY_SSH_HOST'), 'port' => env('LEGACY_SSH_PORT', 22), 'identity_file' => env('LEGACY_SSH_KEY'), 'remote_host' => env('LEGACY_REMOTE_HOST', '127.0.0.1'), 'remote_port' => env('LEGACY_REMOTE_PORT', 3306), 'local_host' => env('LEGACY_LOCAL_HOST', '127.0.0.1'), 'local_port' => env('LEGACY_LOCAL_PORT', 13306), ], ],
Tunnel Types
The package supports two types of SSH tunnels:
Forward Tunnel (-L) - Access Remote Services
Forward tunnels allow you to access remote services from your local machine.
┌─────────────┐ SSH Tunnel ┌─────────────┐
│ Local │ ──────────────────────────> │ Remote │
│ Machine │ localhost:15432 │ Server │
│ │ │ │
│ │ │ PostgreSQL │
│ │ <───────────────────────── │ :5432 │
└─────────────┘ └─────────────┘
SSH Command:
ssh -L 15432:localhost:5432 user@server.com
│ │ │
│ │ └─ remote_port (порт на удалённом сервере)
│ └────────── remote_host (хост на удалённом сервере)
└───────────────── local_port (порт на локальной машине)
Use Cases:
- Access production/staging databases for debugging
- Connect to internal APIs not exposed to the internet
- Access remote services (Redis, Elasticsearch, etc.)
- Secure connection to remote development environments
Example:
use ArtemYurov\Autossh\Facades\Tunnel; // Access remote database Tunnel::connection('remote_db')->execute(function() { // Connect to remote PostgreSQL via localhost:15432 $users = DB::connection('pgsql_remote')->table('users')->get(); });
Reverse Tunnel (-R) - Expose Local Services
Reverse tunnels expose your local application to a remote server, making it accessible from the internet.
┌─────────────┐ SSH Tunnel ┌─────────────┐
│ Local │ <────────────────────────── │ Remote │
│ Machine │ │ Server │
│ │ │ │
│ Laravel │ │ Public IP │
│ :8000 │ ───────────────────────> │ :8080 │
└─────────────┘ └─────────────┘
│
Webhooks from:
• Stripe
• GitHub
• Telegram
• PayPal
SSH Command:
ssh -R 8080:localhost:8000 user@server.com
│ │ │
│ │ └─ local_port (порт локальной машины)
│ └────────── remote_host (bind адрес на удалённом сервере)
└───────────────── remote_port (порт на удалённом сервере)
Important: remote_host is the bind address on the remote server:
localhost- tunnel accessible only locally on remote server (127.0.0.1:8080)0.0.0.0- tunnel publicly accessible (your-server.com:8080 from internet)
Use Cases:
- Test webhooks locally (Stripe, PayPal, GitHub, Telegram)
- Demo local application to clients without deployment
- Temporary public access to development environment
- Receive callbacks from external services
Example:
use ArtemYurov\Autossh\Facades\Tunnel; // Expose local Laravel app for webhook testing Tunnel::connection('local_webhooks')->execute(function() { $this->info('Local app is now accessible at http://your-server.com:8080'); $this->info('Configure webhooks to point to this URL'); // Keep tunnel open while testing sleep(3600); // 1 hour });
Usage
Callback Pattern (Recommended)
Automatic tunnel closure after execution:
use ArtemYurov\Autossh\Facades\Tunnel; // Using configuration from config/tunnel.php Tunnel::connection('remote_db')->execute(function() { // Tunnel is active, you can work with remote service // Tunnel will automatically close after execution });
With Laravel Database Integration
use ArtemYurov\Autossh\Facades\Tunnel; use Illuminate\Support\Facades\DB; Tunnel::connection('remote_db') ->withDatabaseConnection('pgsql_remote', [ 'driver' => 'pgsql', 'database' => env('REMOTE_DB_DATABASE'), 'username' => env('REMOTE_DB_USERNAME'), 'password' => env('REMOTE_DB_PASSWORD'), ]) ->execute(function() { // Now you can use the connection $users = DB::connection('pgsql_remote')->table('users')->get(); // Tunnel will automatically close after execution });
Manual Management
use ArtemYurov\Autossh\Facades\Tunnel; $connection = Tunnel::connection('remote_db')->start(); try { // Work with tunnel $pid = $connection->getPid(); $isRunning = $connection->isRunning(); } finally { // Must close the tunnel $connection->stop(); }
Multiple Tunnels Simultaneously
use ArtemYurov\Autossh\Tunnel; // PostgreSQL tunnel $pgTunnel = Tunnel::connection('pgsql')->start(); // MySQL tunnel $mysqlTunnel = Tunnel::connection('mysql')->start(); try { // Work with both tunnels } finally { $pgTunnel->stop(); $mysqlTunnel->stop(); }
Using in Artisan Commands
namespace App\Console\Commands; use Illuminate\Console\Command; use ArtemYurov\Autossh\Facades\Tunnel; use Illuminate\Support\Facades\DB; class SyncDatabase extends Command { protected $signature = 'db:sync'; public function handle(): int { return Tunnel::connection('remote_db') ->withDatabaseConnection('remote_db', [ 'driver' => 'pgsql', 'database' => env('REMOTE_DB_DATABASE'), 'username' => env('REMOTE_DB_USERNAME'), 'password' => env('REMOTE_DB_PASSWORD'), ]) ->execute(function() { $this->info('Syncing database...'); // Your sync logic $tables = DB::connection('remote_db') ->select("SELECT tablename FROM pg_tables WHERE schemaname = 'public'"); $this->info('Found tables: ' . count($tables)); return Command::SUCCESS; }); } }
Long-Running Tunnels
For persistent tunnels that stay active (similar to ngrok), use the Artisan commands:
Start with Live Monitoring
Start a tunnel with real-time status updates (like ngrok):
php artisan tunnel:start
# or specify connection
php artisan tunnel:start remote_db
This will show a live dashboard with tunnel information:
╔════════════════════════════════════════════════════════════════╗
║ SSH Tunnel Monitor ║
╠════════════════════════════════════════════════════════════════╣
║ Connection: remote_db ║
║ Local Port: 15432 ║
║ Remote: localhost:5432 ║
║ SSH: user@example.com ║
║ PID: 12345 ║
╠════════════════════════════════════════════════════════════════╣
║ Press Ctrl+C to stop the tunnel ║
╚════════════════════════════════════════════════════════════════╝
Status: ● ACTIVE | Uptime: 2m 34s | PID: 12345
Press Ctrl+C to gracefully stop the tunnel.
Start in Background (Daemon Mode)
Run tunnel in background without monitoring (detached daemon mode):
php artisan tunnel:start --detach
# or
php artisan tunnel:start remote_db --detach
Check Tunnel Status
View status of running tunnels:
# Check specific tunnel php artisan tunnel:status php artisan tunnel:status remote_db # Check all running tunnels php artisan tunnel:status --all
Stop Tunnel
Stop a running tunnel:
php artisan tunnel:stop
php artisan tunnel:stop remote_db
# Stop all tunnels
php artisan tunnel:stop --all
Run as System Service
For production environments, you can run the tunnel as a persistent systemd service.
📖 See detailed guide: SYSTEMD.md
Quick example:
# Create service file sudo nano /etc/systemd/system/ssh-tunnel.service # Enable and start sudo systemctl enable ssh-tunnel sudo systemctl start ssh-tunnel
Related Resources
- Running as systemd Service - Complete guide for production deployments
- Self-hosted ngrok alternative - Building your own tunnel infrastructure
AutoSSH
The package automatically detects autossh availability and uses it instead of regular ssh to provide automatic reconnection on connection loss.
Installing autossh
macOS:
brew install autossh
Ubuntu/Debian:
apt-get install autossh
The package uses command -v autossh to detect autossh automatically. No additional configuration needed.
Disable AutoSSH
If you want to use regular SSH even when autossh is available:
TUNNEL_AUTOSSH_ENABLED=false
Docker & DDEV
SSH tunnels work inside containers — you need access to SSH keys and autossh installed.
DDEV (Docker Desktop)
DDEV runs on Docker Desktop, which provides built-in
SSH agent forwarding
through the socket /run/host-services/ssh-auth.sock.
- Add
autosshtoconfig.yaml:
webimage_extra_packages: [autossh]
- Create
.ddev/docker-compose.ssh-auth-socket.yamlfor SSH agent forwarding:
services: web: volumes: - type: bind source: /run/host-services/ssh-auth.sock target: /run/host-services/ssh-auth.sock environment: SSH_AUTH_SOCK: /run/host-services/ssh-auth.sock
- Restart and verify:
ddev restart ddev exec ssh-add -l # should list your SSH keys ddev exec php artisan tunnel:start
Docker Compose (Server / Docker Engine)
On servers there is no Docker Desktop, so SSH agent forwarding via
/run/host-services/ssh-auth.sock is not available. Instead, mount the host's
~/.ssh directory directly into the container (read-only).
- Add
openssh-client autosshto your Dockerfile:
RUN apt-get update && apt-get install -y openssh-client autossh
- Create
docker-compose.ssh-keys.yaml:
services: app: volumes: - ~/.ssh:/root/.ssh:ro
Note: Adjust the target path (
/root/.ssh) to match the user running the application inside the container (e.g./home/www-data/.sshor/var/www/.ssh).
- Include it via
COMPOSE_FILEin.env:
COMPOSE_FILE=docker-compose.yaml:docker-compose.ssh-keys.yaml
- Rebuild and verify:
docker compose up -d --build docker compose exec app ssh-add -l # should list your SSH keys docker compose exec app php artisan tunnel:start
Error Handling
use ArtemYurov\Autossh\Facades\Tunnel; use ArtemYurov\Autossh\Exceptions\TunnelConnectionException; use ArtemYurov\Autossh\Exceptions\TunnelConfigException; try { Tunnel::connection('remote_db')->execute(function() { // Your code }); } catch (TunnelConfigException $e) { // Configuration error (invalid port, missing key, etc.) Log::error('Tunnel configuration error: ' . $e->getMessage()); } catch (TunnelConnectionException $e) { // Connection error (port occupied, SSH failed, etc.) Log::error('Tunnel connection error: ' . $e->getMessage()); }
Important Notes
MySQL/MariaDB: localhost vs 127.0.0.1
Critical: In MySQL and MariaDB, localhost has a special hardcoded meaning - it always represents a Unix socket connection, not a TCP/IP connection. This behavior cannot be changed.
The Difference
localhost→ Unix socket connection (/var/run/mysqld/mysqld.sock)127.0.0.1→ TCP/IP connection over loopback interface
When using SSH tunnels with MySQL/MariaDB:
// ❌ WRONG - Will attempt Unix socket, not tunnel Tunnel::connection('mysql_tunnel') ->withDatabaseConnection('mysql_remote', [ 'driver' => 'mysql', 'host' => 'localhost', // ❌ Unix socket 'port' => 13306, ]); // ✅ CORRECT - Will use TCP/IP through tunnel Tunnel::connection('mysql_tunnel') ->withDatabaseConnection('mysql_remote', [ 'driver' => 'mysql', 'host' => '127.0.0.1', // ✅ TCP/IP 'port' => 13306, ]);
Configuration Example
Environment Variables:
# Remote database credentials MYSQL_REMOTE_HOST=127.0.0.1 # ✅ Use 127.0.0.1, not localhost MYSQL_REMOTE_PORT=3306 MYSQL_REMOTE_DATABASE=mydb MYSQL_REMOTE_USERNAME=myuser MYSQL_REMOTE_PASSWORD=secret # SSH Tunnel settings MYSQL_SSH_USER=sshuser MYSQL_SSH_HOST=remote-server.com MYSQL_SSH_PORT=22 # Local tunnel bind MYSQL_LOCAL_HOST=127.0.0.1 # ✅ Bind to specific IPv4 address MYSQL_LOCAL_PORT=13306
Tunnel Configuration:
// config/tunnel.php 'mysql_remote' => [ 'type' => 'forward', 'user' => env('MYSQL_SSH_USER'), 'host' => env('MYSQL_SSH_HOST'), 'port' => env('MYSQL_SSH_PORT', 22), // ✅ IMPORTANT: Use 127.0.0.1 for MySQL/MariaDB 'remote_host' => env('MYSQL_REMOTE_HOST', '127.0.0.1'), 'remote_port' => env('MYSQL_REMOTE_PORT', 3306), 'local_host' => env('MYSQL_LOCAL_HOST', '127.0.0.1'), 'local_port' => env('MYSQL_LOCAL_PORT', 13306), ],
Database User Permissions
MySQL/MariaDB treat user@localhost and user@127.0.0.1 as different users:
-- Unix socket access (localhost means socket) CREATE USER 'myuser'@'localhost' IDENTIFIED BY 'password'; -- TCP/IP access (required for SSH tunnels) CREATE USER 'myuser'@'127.0.0.1' IDENTIFIED BY 'password'; -- Grant permissions GRANT ALL PRIVILEGES ON mydb.* TO 'myuser'@'127.0.0.1'; FLUSH PRIVILEGES;
Important: When using SSH tunnels to access remote MySQL/MariaDB:
- Create database user with
@127.0.0.1host (not@localhost) - Set
remote_hostto127.0.0.1in tunnel config - Use
'host' => '127.0.0.1'in database connection config
Why This Matters
This issue commonly occurs when:
- Remote server's
localhostresolves to IPv6 (::1) - Database user only has
@localhost(Unix socket) permissions - Database rejects connections: "Host '::1' is not allowed to connect"
Solution: Always use 127.0.0.1 for both remote_host in tunnel config and database connection host when working with MySQL/MariaDB over SSH tunnels.
Advanced Features
Tunnel Reuse
The package can automatically detect and reuse existing SSH tunnels instead of creating new ones. This is useful for:
- Avoiding "port already in use" errors
- Sharing tunnels between multiple processes
- Faster command execution (no tunnel startup time)
Smart Tunnel Discovery
The package uses two methods to find existing tunnels:
- PID file - Fastest method, stores tunnel PID in temp directory
- Port scan - Uses
lsof/netstatto find processes by port
use ArtemYurov\Autossh\Tunnel; // Automatically reuse existing tunnel or create new one $tunnel = Tunnel::connection('remote_db')->reuseOrCreate(); // Or explicitly find existing tunnel by port $pid = $tunnel->findExistingByPort(); if ($pid) { echo "Found existing tunnel with PID: $pid"; }
Reuse Command
Find and reuse existing tunnel from command line:
# Find and reuse existing tunnel php artisan tunnel:reuse remote_db # Reuse tunnel and register database connection php artisan tunnel:reuse remote_db \ --db-connection=pgsql_remote \ --db-database=mydb \ --db-username=user \ --db-password=pass
Retry Logic
Execute database operations with automatic retry on connection errors:
use ArtemYurov\Autossh\Tunnel; $tunnel = Tunnel::connection('remote_db') ->withDatabaseConnection('pgsql_remote', [...]) ->start(); // Execute with automatic retry on connection loss $result = $tunnel->executeWithRetry(function() { return DB::connection('pgsql_remote')->table('users')->count(); }, $maxAttempts = 3);
If the operation fails due to connection error, the tunnel will automatically reconnect and retry the operation.
Configuration
Configure retry behavior in config/tunnel.php:
'retry' => [ 'max_attempts' => 3, // Maximum retry attempts 'delay' => 2, // Delay between retries (seconds) 'exponential' => false, // Use exponential backoff (2s, 4s, 8s...) ],
Or use environment variables:
TUNNEL_RETRY_MAX_ATTEMPTS=3 TUNNEL_RETRY_DELAY=2 TUNNEL_RETRY_EXPONENTIAL=false
Database Validation
Verify that database is actually accessible through tunnel (not just port checking):
use ArtemYurov\Autossh\Tunnel; $connection = Tunnel::connection('remote_db') ->withDatabaseConnection('pgsql_remote', [...]) ->start(); // Simple validation (SELECT 1 query) if ($connection->validateDatabase('pgsql_remote')) { echo "Database is accessible"; } // Wait for database with retries if ($connection->waitForDatabase('pgsql_remote', $maxAttempts = 5, $delaySeconds = 2)) { echo "Database became available"; } // Full tunnel validation (process + port + database) $result = $connection->validate('pgsql_remote'); if ($result['valid']) { echo "Tunnel is fully operational"; } else { foreach ($result['errors'] as $error) { echo "Error: $error\n"; } }
Signal Handling
Tunnels can automatically handle system signals for graceful shutdown:
use ArtemYurov\Autossh\Tunnel; $connection = Tunnel::connection('remote_db') ->start() ->setupSignalHandlers(); // Handle SIGINT, SIGTERM // Tunnel will be properly closed when receiving Ctrl+C or kill signal
Requires pcntl extension. Configure in config/tunnel.php:
'signals' => [ 'enabled' => true, 'handlers' => ['SIGINT', 'SIGTERM'], ],
Keep-Alive Tunnels
Create tunnels that persist after script ends:
use ArtemYurov\Autossh\Tunnel; $connection = Tunnel::connection('remote_db') ->start() ->withKeepAlive(true); // Tunnel will stay alive even after script finishes echo "Tunnel PID: " . $connection->getPid();
To stop keep-alive tunnel, use the stop command:
php artisan tunnel:stop remote_db
Diagnostic Tools
Diagnose Command
Comprehensive tunnel health check:
# Basic diagnostics php artisan tunnel:diagnose remote_db # Include database check php artisan tunnel:diagnose remote_db --db-connection=pgsql_remote # Verbose output php artisan tunnel:diagnose remote_db --verbose
The diagnostic tool checks:
- ✓ Configuration existence
- ✓ Process running (PID file + port scan)
- ✓ Process is SSH
- ✓ Port accessibility
- ✓ Database accessibility (optional)
ManagesTunnel Trait for Commands
Convenient trait for managing tunnels in Laravel commands:
namespace App\Console\Commands; use Illuminate\Console\Command; use ArtemYurov\Autossh\Console\Traits\ManagesTunnel; use Illuminate\Support\Facades\DB; class SyncRemoteData extends Command { use ManagesTunnel; protected $signature = 'data:sync'; public function handle(): int { // Setup tunnel with automatic reconnection and validation $this->setupTunnel('remote_db', [ 'connection_name' => 'pgsql_remote', 'driver' => 'pgsql', 'database' => env('REMOTE_DB_DATABASE'), 'username' => env('REMOTE_DB_USERNAME'), 'password' => env('REMOTE_DB_PASSWORD'), ], $keepAlive = false, $validateDb = true); try { // Execute with automatic retry on connection errors $this->withTunnelRetry(function() { $data = DB::connection('pgsql_remote') ->table('users') ->get(); $this->info("Synced " . count($data) . " records"); }); return Command::SUCCESS; } finally { // Graceful tunnel closure $this->closeTunnel(); } } }
ManagesTunnel Methods
setupTunnel($connection, $dbConfig, $keepAlive, $validateDb)- Initialize tunnelensureTunnelConnected($maxAttempts)- Check and reconnect if neededwithTunnelRetry($operation, $maxAttempts)- Execute with retryvalidateTunnelDatabase($connection, $timeout, $wait)- Validate databasevalidateTunnel($connection)- Full validationcloseTunnel()- Graceful shutdownisTunnelRunning()- Check statusgetTunnel()/getTunnelConnection()- Get instances
API Reference
Artisan Commands
tunnel:start {connection?} {--detach}- Start tunnel with live monitoring (or in background with --detach)tunnel:stop {connection?} {--all}- Stop tunnel (or all tunnels with --all)tunnel:status {connection?} {--all}- Show tunnel status (or all tunnels with --all)tunnel:reuse {connection?} {--db-connection=} {--db-driver=} {--db-database=} {--db-username=} {--db-password=}- Find and reuse existing tunneltunnel:diagnose {connection?} {--db-connection=} {--verbose}- Diagnose tunnel health
Tunnel
Static Methods
Tunnel::connection(?string $name = null): Tunnel- Create from config/tunnel.phpTunnel::fromConfig(TunnelConfig $config): Tunnel- Create from config object
Instance Methods
withDatabaseConnection(string $name, array $config): self- Register Laravel DB connectionstart(): TunnelConnection- Start tunnelreuseOrCreate(): TunnelConnection- Smart tunnel reuse or creationfindExistingByPort(): ?int- Find existing tunnel by port using lsofensureConnected(int $maxAttempts = 3): bool- Ensure tunnel is active, reconnect if neededexecute(callable $callback): mixed- Execute callback with automatic tunnel managementgetConfig(): TunnelConfig- Get configurationgetConnection(): ?TunnelConnection- Get active connection
TunnelConnection
isRunning(): bool- Check if tunnel is runninggetPid(): ?int- Get process PIDstop(): void- Stop tunnelverifyConnection(): bool- Verify tunnel availabilityensureConnected(int $maxAttempts = 3): bool- Reconnect if tunnel is downwithKeepAlive(bool $keepAlive = true): self- Set keep-alive flagsetupSignalHandlers(): self- Setup SIGINT/SIGTERM handlersvalidateDatabase(string $connectionName, int $timeout = 5): bool- Validate database accessibilitywaitForDatabase(string $connectionName, int $maxAttempts = 5, int $delaySeconds = 2): bool- Wait for database with retriesexecuteWithRetry(callable $operation, ?int $maxAttempts = null): mixed- Execute with automatic retryvalidate(?string $connectionName = null): array- Full validation (process + port + database)
TunnelManager
saveTunnel(string $name, TunnelConnection $connection): void- Save tunnel info to storagegetTunnelInfo(string $name): ?array- Get tunnel informationstopTunnel(string $name): bool- Stop tunnel by namegetAllTunnels(): array- Get all running tunnelsgetUptime(array $info): int- Get tunnel uptime in secondsformatUptime(int $seconds): string- Format uptime as human-readable string
Logging
The package uses standard Laravel Log facade. For detailed logging:
TUNNEL_DEBUG=true
Or in config/tunnel.php:
'debug' => true,
Requirements
- PHP ^8.2
- Laravel ^10.0|^11.0|^12.0
- SSH client installed on the system
- AutoSSH (optional, for auto-reconnection)
License
MIT License
Author
Artem Yurov (artem@yurov.org)
统计信息
- 总下载量: 22
- 月度下载量: 0
- 日度下载量: 0
- 收藏数: 0
- 点击次数: 1
- 依赖项目数: 1
- 推荐数: 0
其他信息
- 授权协议: MIT
- 更新时间: 2025-12-16