1+ #! /bin/bash
2+
3+ # SSH Tunnel Manager Script
4+ # Manages multiple SSH tunnels using screen sessions
5+ # Compatible with macOS default bash (3.2+)
6+
7+ # Configuration file path
8+ CONFIG_FILE=" ${CONFIG_FILE:- ./ tunnels.conf} "
9+
10+ # Load tunnel configurations from file
11+ load_config () {
12+ if [ ! -f " $CONFIG_FILE " ]; then
13+ echo -e " ${RED} Error: Configuration file '$CONFIG_FILE ' not found${NC} "
14+ echo -e " ${YELLOW} Create a file with tunnel configurations in this format:${NC} "
15+ echo " tunnel1:2222:localhost:22:[email protected] " 16+ echo " tunnel2:3306:localhost:3306:[email protected] " 17+ echo " tunnel3:8080:localhost:80:[email protected] " 18+ echo " "
19+ echo -e " ${YELLOW} You can also set a custom config file path with:${NC} "
20+ echo " export CONFIG_FILE=/path/to/your/tunnels.conf"
21+ exit 1
22+ fi
23+
24+ # Read non-empty lines that don't start with #
25+ TUNNEL_CONFIGS=()
26+ while IFS= read -r line; do
27+ # Skip empty lines and comments
28+ if [[ -n " $line " && ! " $line " =~ ^[[:space:]]* # ]]; then
29+ TUNNEL_CONFIGS+= (" $line " )
30+ fi
31+ done < " $CONFIG_FILE "
32+
33+ if [ ${# TUNNEL_CONFIGS[@]} -eq 0 ]; then
34+ echo -e " ${RED} Error: No valid tunnel configurations found in '$CONFIG_FILE '${NC} "
35+ exit 1
36+ fi
37+ }
38+
39+ # Colors for output
40+ RED= ' \033[0;31m'
41+ GREEN= ' \033[0;32m'
42+ YELLOW= ' \033[1;33m'
43+ BLUE= ' \033[0;34m'
44+ NC= ' \033[0m' # No Color
45+
46+ # Function to check if screen is installed
47+ check_screen () {
48+ if ! command -v screen > /dev/null 2>&1 ; then
49+ echo -e " ${RED} Error: screen is not installed. Install it with: brew install screen${NC} "
50+ exit 1
51+ fi
52+ }
53+
54+ # Function to parse tunnel config by name
55+ get_tunnel_config () {
56+ local name=$1
57+ for config in " ${TUNNEL_CONFIGS[@]} " ; do
58+ local tunnel_name=$( echo " $config " | cut -d: -f1)
59+ if [ " $tunnel_name " = " $name " ]; then
60+ echo " $config "
61+ return 0
62+ fi
63+ done
64+ return 1
65+ }
66+
67+ # Function to check if tunnel name exists
68+ tunnel_exists () {
69+ local name=$1
70+ for config in " ${TUNNEL_CONFIGS[@]} " ; do
71+ local tunnel_name=$( echo " $config " | cut -d: -f1)
72+ if [ " $tunnel_name " = " $name " ]; then
73+ return 0
74+ fi
75+ done
76+ return 1
77+ }
78+
79+ # Function to build SSH command from config
80+ build_ssh_command () {
81+ local config=$1
82+ local local_port=$( echo " $config " | cut -d: -f2)
83+ local remote_host=$( echo " $config " | cut -d: -f3)
84+ local remote_port=$( echo " $config " | cut -d: -f4)
85+ local ssh_server=$( echo " $config " | cut -d: -f5)
86+
87+ echo " ssh -L ${local_port} :${remote_host} :${remote_port} ${ssh_server} "
88+ }
89+
90+ # Function to start a single tunnel
91+ start_tunnel () {
92+ local name=$1
93+ local config=$( get_tunnel_config " $name " )
94+
95+ if [ $? -ne 0 ]; then
96+ echo -e " ${RED} Error: Tunnel '$name ' not found in configuration${NC} "
97+ return 1
98+ fi
99+
100+ if screen -list | grep -q " ssh_tunnel_${name} " ; then
101+ echo -e " ${YELLOW} Tunnel '${name} ' is already running${NC} "
102+ return 1
103+ fi
104+
105+ local ssh_cmd=$( build_ssh_command " $config " )
106+
107+ echo -e " ${BLUE} Starting tunnel: ${name}${NC} "
108+ echo -e " ${BLUE} Command: ${ssh_cmd}${NC} "
109+
110+ # Start SSH tunnel in a detached screen session
111+ screen -dmS " ssh_tunnel_${name} " bash -c " $ssh_cmd "
112+
113+ # Give it a moment to establish
114+ sleep 2
115+
116+ # Check if the screen session is still running
117+ if screen -list | grep -q " ssh_tunnel_${name} " ; then
118+ echo -e " ${GREEN} ✓ Tunnel '${name} ' started successfully${NC} "
119+ return 0
120+ else
121+ echo -e " ${RED} ✗ Failed to start tunnel '${name} '${NC} "
122+ return 1
123+ fi
124+ }
125+
126+ # Function to stop a single tunnel
127+ stop_tunnel () {
128+ local name=$1
129+
130+ if ! tunnel_exists " $name " ; then
131+ echo -e " ${RED} Error: Tunnel '$name ' not found in configuration${NC} "
132+ return 1
133+ fi
134+
135+ if ! screen -list | grep -q " ssh_tunnel_${name} " ; then
136+ echo -e " ${YELLOW} Tunnel '${name} ' is not running${NC} "
137+ return 1
138+ fi
139+
140+ echo -e " ${BLUE} Stopping tunnel: ${name}${NC} "
141+ screen -S " ssh_tunnel_${name} " -X quit
142+
143+ sleep 1
144+
145+ if ! screen -list | grep -q " ssh_tunnel_${name} " ; then
146+ echo -e " ${GREEN} ✓ Tunnel '${name} ' stopped successfully${NC} "
147+ return 0
148+ else
149+ echo -e " ${RED} ✗ Failed to stop tunnel '${name} '${NC} "
150+ return 1
151+ fi
152+ }
153+
154+ # Function to show tunnel status
155+ show_status () {
156+ echo -e " ${BLUE} SSH Tunnel Status:${NC} "
157+ echo " ===================="
158+
159+ for config in " ${TUNNEL_CONFIGS[@]} " ; do
160+ local name=$( echo " $config " | cut -d: -f1)
161+ if screen -list | grep -q " ssh_tunnel_${name} " ; then
162+ echo -e " ${name} : ${GREEN} RUNNING${NC} "
163+ else
164+ echo -e " ${name} : ${RED} STOPPED${NC} "
165+ fi
166+ done
167+
168+ echo " "
169+ echo -e " ${BLUE} Active screen sessions:${NC} "
170+ screen -list | grep " ssh_tunnel_" || echo " None"
171+ }
172+
173+ # Function to start all tunnels
174+ start_all () {
175+ echo -e " ${BLUE} Starting all SSH tunnels...${NC} "
176+ for config in " ${TUNNEL_CONFIGS[@]} " ; do
177+ local name=$( echo " $config " | cut -d: -f1)
178+ start_tunnel " $name "
179+ done
180+ }
181+
182+ # Function to stop all tunnels
183+ stop_all () {
184+ echo -e " ${BLUE} Stopping all SSH tunnels...${NC} "
185+ for config in " ${TUNNEL_CONFIGS[@]} " ; do
186+ local name=$( echo " $config " | cut -d: -f1)
187+ stop_tunnel " $name "
188+ done
189+ }
190+
191+ # Function to connect to a tunnel's screen session
192+ connect_tunnel () {
193+ local name=$1
194+
195+ if ! tunnel_exists " $name " ; then
196+ echo -e " ${RED} Error: Tunnel '$name ' not found in configuration${NC} "
197+ return 1
198+ fi
199+
200+ if ! screen -list | grep -q " ssh_tunnel_${name} " ; then
201+ echo -e " ${RED} Tunnel '${name} ' is not running${NC} "
202+ return 1
203+ fi
204+
205+ echo -e " ${BLUE} Connecting to tunnel '${name} ' screen session...${NC} "
206+ echo -e " ${YELLOW} Press Ctrl+A then D to detach from the session${NC} "
207+ sleep 2
208+ screen -r " ssh_tunnel_${name} "
209+ }
210+
211+ # Function to show help
212+ show_help () {
213+ echo " SSH Tunnel Manager"
214+ echo " =================="
215+ echo " Usage: $0 [command] [tunnel_name]"
216+ echo " "
217+ echo " Commands:"
218+ echo " start [name] - Start specific tunnel or all if no name given"
219+ echo " stop [name] - Stop specific tunnel or all if no name given"
220+ echo " restart [name] - Restart specific tunnel or all if no name given"
221+ echo " status - Show status of all tunnels"
222+ echo " connect [name] - Connect to tunnel's screen session"
223+ echo " list - List available tunnel configurations"
224+ echo " help - Show this help message"
225+ echo " "
226+ echo " Available tunnels:"
227+ for config in " ${TUNNEL_CONFIGS[@]} " ; do
228+ local name=$( echo " $config " | cut -d: -f1)
229+ local ssh_cmd=$( build_ssh_command " $config " )
230+ echo " ${name} : ${ssh_cmd} "
231+ done
232+ }
233+
234+ # Function to list available tunnels
235+ list_tunnels () {
236+ echo -e " ${BLUE} Available tunnel configurations:${NC} "
237+ echo " ================================"
238+ for config in " ${TUNNEL_CONFIGS[@]} " ; do
239+ local name=$( echo " $config " | cut -d: -f1)
240+ local ssh_cmd=$( build_ssh_command " $config " )
241+ echo -e " ${GREEN}${name}${NC} : ${ssh_cmd} "
242+ done
243+ }
244+
245+ # Main script logic
246+ check_screen
247+ load_config
248+
249+ case " ${1:- help} " in
250+ " start" )
251+ if [ -n " $2 " ]; then
252+ if tunnel_exists " $2 " ; then
253+ start_tunnel " $2 "
254+ else
255+ echo -e " ${RED} Error: Tunnel '$2 ' not found${NC} "
256+ exit 1
257+ fi
258+ else
259+ start_all
260+ fi
261+ ;;
262+ " stop" )
263+ if [ -n " $2 " ]; then
264+ if tunnel_exists " $2 " ; then
265+ stop_tunnel " $2 "
266+ else
267+ echo -e " ${RED} Error: Tunnel '$2 ' not found${NC} "
268+ exit 1
269+ fi
270+ else
271+ stop_all
272+ fi
273+ ;;
274+ " restart" )
275+ if [ -n " $2 " ]; then
276+ if tunnel_exists " $2 " ; then
277+ stop_tunnel " $2 "
278+ sleep 2
279+ start_tunnel " $2 "
280+ else
281+ echo -e " ${RED} Error: Tunnel '$2 ' not found${NC} "
282+ exit 1
283+ fi
284+ else
285+ stop_all
286+ sleep 3
287+ start_all
288+ fi
289+ ;;
290+ " status" )
291+ show_status
292+ ;;
293+ " connect" )
294+ if [ -n " $2 " ]; then
295+ if tunnel_exists " $2 " ; then
296+ connect_tunnel " $2 "
297+ else
298+ echo -e " ${RED} Error: Tunnel '$2 ' not found${NC} "
299+ exit 1
300+ fi
301+ else
302+ echo -e " ${RED} Error: Please specify tunnel name${NC} "
303+ show_help
304+ exit 1
305+ fi
306+ ;;
307+ " list" )
308+ list_tunnels
309+ ;;
310+ " help" |* )
311+ show_help
312+ ;;
313+ esac
0 commit comments