A terminal UI for managing SSH port forwarding tunnels with profile support. Inspired by Bitvise SSH Client's tunnel management feature.
- Profile-based configuration - Create and switch between multiple connection profiles
- Profile selector - Visual profile picker on startup (like Bitvise)
- Remembers last profile - Automatically opens your last used profile
- Vim-style navigation - Familiar keybindings for efficient navigation
- Import from Bitvise - Parse and import
.tlptunnel configuration files - Auto-reconnect - Automatic retry with exponential backoff on disconnect
- Real-time logs - Scrollable log panel with connection status
- PPK key support - Automatic conversion of PuTTY keys (requires puttygen)
- Inline editing - Edit tunnels directly in the interface
- Installation
- Quick Start
- Usage
- Configuration
- Importing from Bitvise
- Troubleshooting
- Requirements
- License
# Clone the repository
git clone https://github.com/saidkamolxon/ssh-tunnel-manager.git
cd ssh-tunnel-manager
# Install in development mode
pip install -e .
# Now you can run it with:
stm# Run directly from the source directory
python main.py-
First run - Creates a default profile and shows the setup wizard:
stm
-
Configure SSH connection - Enter your server details in the setup wizard:
- Host:
your-server.com - Port:
22 - User:
your-username - Key file:
~/.ssh/id_rsa(optional, press Tab to browse)
- Host:
-
Add tunnels - Press
ato add a new tunnel:- Listen Address:
127.0.0.1 - Listen Port:
8080 - Remote Address:
172.16.0.10 - Remote Port:
80 - Name:
Web Server
- Listen Address:
-
Connect - Press
cto establish the SSH connection -
Save - Type
:wto save your configuration
stm [profile] [options]| Option | Description |
|---|---|
profile |
Profile name or path to .stm file |
--new NAME, -n NAME |
Create a new profile with the given name |
--list, -l |
List all available profiles |
--select, -s |
Always show profile selector on startup |
--import FILE, -i FILE |
Import tunnels from a Bitvise .tlp file |
--profiles-dir |
Print the profiles directory path |
--version, -v |
Show version number |
--help, -h |
Show help message |
# Show profile selector (when multiple profiles exist)
stm
# Open a specific profile by name
stm production
# Open a profile file directly
stm ~/configs/my-server.stm
# Create a new profile
stm --new staging
# Import tunnels from Bitvise and create a profile
stm --new work --import ~/Downloads/office.tlp
# List all profiles (* marks last used)
stm --list
# Always show profile selector
stm --selectProfiles allow you to maintain separate configurations for different servers or environments.
Creating profiles:
# Via command line
stm --new production
# Or press 'n' in the profile selectorSwitching profiles:
# Open specific profile
stm work
# Or use the profile selector
stm --selectListing profiles:
stm --list
# Output:
# Available profiles:
# default
# production * <- last used
# stagingWhen you have multiple profiles, the profile selector appears on startup:
SSH Tunnel Manager
Profile Selector
┌─ Profiles ─────────────────────────────────────┐
│ ▶ production ★ │
│ staging │
│ development │
│ default │
└────────────────────────────────────────────────┘
Server: ubuntu@prod.example.com:22
Tunnels: 5/8 enabled
Enter: Open | n: New | q: Quit | j/k: Navigate
| Key | Action |
|---|---|
j / ↓ |
Move down |
k / ↑ |
Move up |
Enter |
Open selected profile |
n |
Create new profile |
g |
Go to top |
G |
Go to bottom |
q / Esc |
Quit |
The ★ symbol marks your last used profile.
SSH Tunnel Manager [production] (3/5 enabled) ubuntu@server.com [●]
# │ ST │ Listen Addr │ Port │ Remote Addr │ Port │ Name
─────┼────┼─────────────────┼─────────┼───────────────────┼─────────┼──────────
1 │ ● │ 127.0.0.1 │ 8080 │ 172.16.100.10 │ 80 │ Web App
2 │ ● │ 127.0.0.1 │ 5432 │ 172.16.100.20 │ 5432 │ PostgreSQL
> 3* │ ● │ 127.0.0.1 │ 6379 │ 172.16.100.30 │ 6379 │ Redis
4 │ ○ │ 127.0.0.1 │ 3306 │ 172.16.100.40 │ 3306 │ MySQL
5 │ ○ │ 127.0.0.1 │ 9000 │ 172.16.100.50 │ 9000 │ Minio
─── Logs ───────────────────────────────────────────────────────────────────
[INF] 14:23:01 SSH Tunnel Manager started
[INF] 14:23:01 Profile: production
[INF] 14:23:05 Connected! 3 tunnels active
j/k:nav Space:toggle i:edit a:add d:del s:ssh c:connect l:logs :help
Interface elements:
| Element | Meaning |
|---|---|
[production] |
Current profile name |
(3/5 enabled) |
3 of 5 tunnels are enabled |
[●] |
Connected to SSH server |
[○] |
Disconnected |
[...] |
Connecting |
> |
Selected row |
* |
Unsaved changes |
| Green text | Enabled tunnel |
| Gray text | Disabled tunnel |
| Key | Action |
|---|---|
j / ↓ |
Move down |
k / ↑ |
Move up |
g |
Go to first tunnel |
G |
Go to last tunnel |
| Key | Action |
|---|---|
Space |
Toggle tunnel enabled/disabled |
i |
Edit selected tunnel |
a |
Add new tunnel |
d |
Delete selected tunnel |
| Key | Action |
|---|---|
c |
Connect or disconnect |
s |
Open SSH settings |
| Key | Action |
|---|---|
l |
Enter log scroll mode |
: |
Enter command mode |
q |
Quit (warns if unsaved) |
| Key | Action |
|---|---|
Tab |
Next field |
Shift+Tab |
Previous field |
Enter |
Save changes |
Esc |
Cancel editing |
| Key | Action |
|---|---|
j / ↓ |
Scroll down (newer) |
k / ↑ |
Scroll up (older) |
g |
Go to oldest log |
G |
Go to newest log |
PgUp / PgDn |
Page up/down |
l / q / Esc |
Exit log mode |
Enter command mode by pressing :, then type a command and press Enter.
| Command | Description |
|---|---|
:w |
Save profile |
:wc |
Save profile and reconnect tunnels |
:q |
Quit (warns if unsaved changes) |
:q! |
Force quit without saving |
:wq |
Save and quit |
| Command | Description |
|---|---|
:c or :connect |
Connect to SSH server |
:dc or :disconnect |
Disconnect from SSH server |
:ssh |
Open SSH settings editor |
| Command | Description |
|---|---|
:all on or :enable all |
Enable all tunnels |
:all off or :disable all |
Disable all tunnels |
:sort <field> |
Sort tunnels by field |
:import <path> |
Import tunnels from .tlp file |
Sort fields: id, name, port, remote
| Command | Description |
|---|---|
:verbose or :v |
Toggle verbose SSH logging |
:retry or :autoretry |
Toggle auto-reconnect |
:profiles |
List all available profiles |
| Command | Description |
|---|---|
:help |
Show available commands |
~/.config/ssh-tunnel-manager/
├── profiles/
│ ├── default.stm # Default profile
│ ├── production.stm # Custom profiles
│ ├── staging.stm
│ └── ...
└── state.json # Application state (last profile, etc.)
Profiles are stored as JSON files with .stm extension:
{
"name": "production",
"ssh": {
"host": "server.example.com",
"port": 22,
"user": "ubuntu",
"key_file": "~/.ssh/production_key"
},
"tunnels": [
{
"listen_host": "127.0.0.1",
"listen_port": 8080,
"remote_host": "172.16.100.10",
"remote_port": 80,
"name": "Web Application",
"enabled": true
},
{
"listen_host": "127.0.0.1",
"listen_port": 5432,
"remote_host": "172.16.100.20",
"remote_port": 5432,
"name": "PostgreSQL Database",
"enabled": true
}
]
}The state.json file tracks application state:
{
"last_profile": "production",
"recent_profiles": ["production", "staging", "default"],
"auto_connect": false
}You can import tunnel configurations from Bitvise SSH Client .tlp files:
# Import into a new profile
stm --new work --import ~/Downloads/office-tunnels.tlp
# Import into existing profile (via command mode)
stm work
# Then type: :import ~/Downloads/office-tunnels.tlpThe importer extracts:
- SSH host, port, and username
- All tunnel configurations (local port forwarding)
- Tunnel names and enabled states
"Permission denied" error:
- Check that your key file path is correct
- Ensure the key file has proper permissions:
chmod 600 ~/.ssh/your_key - Verify the username is correct
"Connection refused" error:
- Verify the SSH server is running on the target host
- Check the port number (default: 22)
- Ensure firewall allows SSH connections
"Host key verification failed":
- The server's host key has changed or is unknown
- Connect once manually with
ssh user@hostto accept the key
If using PuTTY .ppk keys:
# Install puttygen on macOS
brew install putty
# Install on Ubuntu/Debian
sudo apt install putty-toolsThe application automatically converts PPK keys to OpenSSH format when needed.
Enable verbose logging for debugging connection issues:
- Press
:to enter command mode - Type
verboseand press Enter - Try connecting again
- Check the log panel for detailed SSH output
If logs scroll too fast:
- Press
lto enter log mode - Use
j/kto scroll through logs - Press
lorEscto exit log mode
- Python 3.9+
- SSH client - OpenSSH (
sshcommand must be available) - curses - Included in Python on Unix systems
- puttygen (optional) - For PPK key conversion
| Platform | Status |
|---|---|
| macOS | Fully supported |
| Linux | Fully supported |
| Windows | Not supported (curses limitation) |
ssh-tunnel-manager/
├── src/
│ └── ssh_tunnel_manager/
│ ├── __init__.py # Package exports
│ ├── cli.py # Command line interface
│ ├── config.py # Profile & configuration management
│ ├── ssh_manager.py # SSH connection handling
│ ├── tlp_parser.py # Bitvise .tlp file parser
│ └── tui.py # Terminal UI components
├── main.py # Development entry point
├── pyproject.toml # Package configuration
├── README.md # This file
└── LICENSE # MIT License
MIT License - see LICENSE for details.
Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request