feat: added a ssh utility that supports smart-aliases and background ssh
tunnels
This commit is contained in:
119
config/ansible/tasks/global/utils/smart-ssh/README.md
Normal file
119
config/ansible/tasks/global/utils/smart-ssh/README.md
Normal file
@@ -0,0 +1,119 @@
|
||||
# SSH Utility - Smart SSH Connection Manager
|
||||
|
||||
A transparent SSH wrapper that automatically chooses between local and remote connections based on network connectivity.
|
||||
|
||||
## What it does
|
||||
|
||||
This utility acts as a drop-in replacement for the `ssh` command that intelligently routes connections:
|
||||
|
||||
- When you type `ssh desktop`, it automatically checks if your local network is available
|
||||
- If local: connects via `desktop-local` (faster local connection)
|
||||
- If remote: connects via `desktop` (Tailscale/VPN connection)
|
||||
- All other SSH usage passes through unchanged (`ssh --help`, `ssh user@host`, etc.)
|
||||
|
||||
## Installation
|
||||
|
||||
The utility is automatically compiled and installed to `~/.local/bin/ssh` via Ansible when you run your dotfiles setup.
|
||||
|
||||
## Configuration
|
||||
|
||||
1. Copy the example config:
|
||||
```bash
|
||||
mkdir -p ~/.config/ssh-util
|
||||
cp ~/.dotfiles/config/ssh-util/config.yaml ~/.config/ssh-util/
|
||||
```
|
||||
|
||||
2. Edit `~/.config/ssh-util/config.yaml` to match your setup:
|
||||
```yaml
|
||||
smart_aliases:
|
||||
desktop:
|
||||
primary: "desktop-local" # SSH config entry for local connection
|
||||
fallback: "desktop" # SSH config entry for remote connection
|
||||
check_host: "192.168.86.22" # IP to ping for connectivity test
|
||||
timeout: "2s" # Ping timeout
|
||||
```
|
||||
|
||||
3. Ensure your `~/.ssh/config` contains the referenced host entries:
|
||||
```
|
||||
Host desktop
|
||||
HostName mennos-cachyos-desktop
|
||||
User menno
|
||||
Port 400
|
||||
ForwardAgent yes
|
||||
AddKeysToAgent yes
|
||||
|
||||
Host desktop-local
|
||||
HostName 192.168.86.22
|
||||
User menno
|
||||
Port 400
|
||||
ForwardAgent yes
|
||||
AddKeysToAgent yes
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
Once configured, simply use SSH as normal:
|
||||
|
||||
```bash
|
||||
# Smart connection - automatically chooses local vs remote
|
||||
ssh desktop
|
||||
|
||||
# All other SSH usage works exactly the same
|
||||
ssh --help
|
||||
ssh --version
|
||||
ssh user@example.com
|
||||
ssh -L 8080:localhost:80 server
|
||||
```
|
||||
|
||||
## How it works
|
||||
|
||||
1. When you run `ssh <alias>`, the utility checks if `<alias>` is defined in the smart_aliases config
|
||||
2. If yes, it pings the `check_host` IP address
|
||||
3. If ping succeeds: executes `ssh <primary>` instead
|
||||
4. If ping fails: executes `ssh <fallback>` instead
|
||||
5. If not a smart alias: passes through to real SSH unchanged
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### SSH utility not found
|
||||
Make sure `~/.local/bin` is in your PATH:
|
||||
```bash
|
||||
echo $PATH | grep -o ~/.local/bin
|
||||
```
|
||||
|
||||
### Config not loading
|
||||
Check the config file exists and has correct syntax:
|
||||
```bash
|
||||
ls -la ~/.config/ssh-util/config.yaml
|
||||
cat ~/.config/ssh-util/config.yaml
|
||||
```
|
||||
|
||||
### Connectivity test failing
|
||||
Test manually:
|
||||
```bash
|
||||
ping -c 1 -W 2 192.168.86.22
|
||||
```
|
||||
|
||||
### Falls back to real SSH
|
||||
If there are any errors loading config or parsing, the utility safely falls back to executing the real SSH binary at `/usr/bin/ssh`.
|
||||
|
||||
## Adding more aliases
|
||||
|
||||
To add more smart aliases, just extend the config:
|
||||
|
||||
```yaml
|
||||
smart_aliases:
|
||||
desktop:
|
||||
primary: "desktop-local"
|
||||
fallback: "desktop"
|
||||
check_host: "192.168.86.22"
|
||||
timeout: "2s"
|
||||
|
||||
server:
|
||||
primary: "server-local"
|
||||
fallback: "server-remote"
|
||||
check_host: "192.168.1.100"
|
||||
timeout: "1s"
|
||||
```
|
||||
|
||||
Remember to create the corresponding entries in your `~/.ssh/config`.
|
85
config/ansible/tasks/global/utils/smart-ssh/config.yaml
Normal file
85
config/ansible/tasks/global/utils/smart-ssh/config.yaml
Normal file
@@ -0,0 +1,85 @@
|
||||
# SSH Utility Configuration
|
||||
# This file defines smart aliases that automatically choose between local and remote connections
|
||||
|
||||
# Logging configuration
|
||||
logging:
|
||||
enabled: true
|
||||
# Levels: debug, info, warn, error
|
||||
level: "info"
|
||||
# Formats: console, json
|
||||
format: "console"
|
||||
|
||||
smart_aliases:
|
||||
# Desktop connection - tries local network first, falls back to Tailscale
|
||||
desktop:
|
||||
primary: "desktop-local" # Use this SSH config entry when local network is available
|
||||
fallback: "desktop" # Use this SSH config entry when local network is not available
|
||||
check_host: "192.168.86.22" # IP address to ping for connectivity test
|
||||
timeout: "2s" # Timeout for connectivity check
|
||||
|
||||
# Background SSH Tunnel Definitions
|
||||
tunnels:
|
||||
# Example: Desktop database tunnel
|
||||
desktop-database:
|
||||
type: local
|
||||
local_port: 5432
|
||||
remote_host: database
|
||||
remote_port: 5432
|
||||
ssh_host: desktop # Uses smart alias logic (desktop-local/desktop)
|
||||
|
||||
# Example: Development API tunnel
|
||||
dev-api:
|
||||
type: local
|
||||
local_port: 8080
|
||||
remote_host: api
|
||||
remote_port: 80
|
||||
ssh_host: dev-server
|
||||
|
||||
# Example: SOCKS proxy tunnel
|
||||
socks-proxy:
|
||||
type: dynamic
|
||||
local_port: 1080
|
||||
ssh_host: bastion
|
||||
|
||||
# Modem web interface tunnel
|
||||
modem-web:
|
||||
type: local
|
||||
local_port: 8443
|
||||
remote_host: 192.168.1.1
|
||||
remote_port: 443
|
||||
ssh_host: desktop
|
||||
# Tunnel Management Commands:
|
||||
# ssh --tunnel --open desktop-database (or ssh -TO desktop-database)
|
||||
# ssh --tunnel --close desktop-database (or ssh -TC desktop-database)
|
||||
# ssh --tunnel --list (or ssh -TL)
|
||||
#
|
||||
# Ad-hoc tunnels (not in config):
|
||||
# ssh -TO temp-api --local 8080:api:80 --via server
|
||||
|
||||
# Logging options:
|
||||
# - enabled: true/false - whether to show any logs
|
||||
# - level: debug (verbose), info (normal), warn (warnings only), error (errors only)
|
||||
# - format: console (human readable), json (structured)
|
||||
# Logs are written to stderr so they don't interfere with SSH output
|
||||
|
||||
# How it works:
|
||||
# 1. When you run: ssh desktop
|
||||
# 2. The utility pings 192.168.86.22 with a 2s timeout
|
||||
# 3. If ping succeeds: runs "ssh desktop-local" instead
|
||||
# 4. If ping fails: runs "ssh desktop" instead
|
||||
# 5. All other SSH usage (flags, user@host, etc.) passes through unchanged
|
||||
|
||||
# Your SSH config should contain the actual host definitions:
|
||||
# Host desktop
|
||||
# HostName mennos-cachyos-desktop
|
||||
# User menno
|
||||
# Port 400
|
||||
# ForwardAgent yes
|
||||
# AddKeysToAgent yes
|
||||
#
|
||||
# Host desktop-local
|
||||
# HostName 192.168.86.22
|
||||
# User menno
|
||||
# Port 400
|
||||
# ForwardAgent yes
|
||||
# AddKeysToAgent yes
|
20
config/ansible/tasks/global/utils/smart-ssh/go.mod
Normal file
20
config/ansible/tasks/global/utils/smart-ssh/go.mod
Normal file
@@ -0,0 +1,20 @@
|
||||
module ssh-util
|
||||
|
||||
go 1.21
|
||||
|
||||
require (
|
||||
github.com/jedib0t/go-pretty/v6 v6.4.9
|
||||
github.com/rs/zerolog v1.31.0
|
||||
github.com/spf13/cobra v1.8.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.19 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.13 // indirect
|
||||
github.com/rivo/uniseg v0.2.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
golang.org/x/sys v0.12.0 // indirect
|
||||
)
|
46
config/ansible/tasks/global/utils/smart-ssh/go.sum
Normal file
46
config/ansible/tasks/global/utils/smart-ssh/go.sum
Normal file
@@ -0,0 +1,46 @@
|
||||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/jedib0t/go-pretty/v6 v6.4.9 h1:vZ6bjGg2eBSrJn365qlxGcaWu09Id+LHtrfDWlB2Usc=
|
||||
github.com/jedib0t/go-pretty/v6 v6.4.9/go.mod h1:Ndk3ase2CkQbXLLNf5QDHoYb6J9WtVfmHZu9n8rk2xs=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
|
||||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
|
||||
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/profile v1.6.0/go.mod h1:qBsxPvzyUincmltOk6iyRVxHYg4adc0OFOv72ZdLa18=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
||||
github.com/rs/zerolog v1.31.0 h1:FcTR3NnLWW+NnTwwhFWiJSZr4ECLpqCm6QsEnyvbV4A=
|
||||
github.com/rs/zerolog v1.31.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
|
||||
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.4 h1:wZRexSlwd7ZXfKINDLsO4r7WBt3gTKONc6K/VesHvHM=
|
||||
github.com/stretchr/testify v1.7.4/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
1073
config/ansible/tasks/global/utils/smart-ssh/smart-ssh.go
Normal file
1073
config/ansible/tasks/global/utils/smart-ssh/smart-ssh.go
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user