#!/bin/bash # doppio-ssh # # Script to run doppio-web over an SSH connection and automatically set up port # forwarding by finding an open TCP port on the local host and connecting it to the # remote port chosen by the Doppio server. The script will then open a local web browser # to view Doppio on the forwarded connection. # # By default, this script assumes that 'doppio-web' is available on the PATH on the # remote host. If it is not, set COMMAND below with the absolute path to the doppio-web # executable. # # Usage is exactly like ssh. All arguments are passed through directly to ssh itself. # In particular, if you would like to launch X-forwarded applications from Doppio, you # should pass the -X or -Y argument, or enable the ForwardX11 or ForwardX11Trusted # option in your ssh config. # # Example: # doppio-ssh -Y user@host # Ensure bash exits on error set -euo pipefail # The command to run on the remote server COMMAND="bash -lic 'doppio-web --no-browser'" # Ensure at least one argument is provided if [[ $# -lt 1 ]]; then echo "Usage: $0 [ssh options] [user@]host" exit 1 fi # Set up a file name for the SSH Control Master mkdir -p ~/.ssh/controlmasters CONTROL_SOCKET="$HOME/.ssh/controlmasters/ssh_mux_$$" # Clean up control socket on exit cleanup() { ssh -S "$CONTROL_SOCKET" -O exit "$@" 2>/dev/null || true rm -f "$CONTROL_SOCKET" } trap cleanup EXIT # Generate a random port in the range 1024–65535 using $RANDOM random_port() { local rand=$(( RANDOM + RANDOM )) # Combine two RANDOMs to get up to full range of port numbers echo $((1024 + (rand % 64512))) # 64512 = 65535 - 1023 = number of non-privileged ports } # Check if nc is available if command -v nc >/dev/null 2>&1; then is_port_free() { local port=$1 ! nc -z 127.0.0.1 "$port" 2>/dev/null } else is_port_free() { local port=$1 # Try to bind using a dummy listener (fallback method) (echo >"/dev/tcp/127.0.0.1/$port") >/dev/null 2>&1 && return 1 || return 0 } fi # Function to find a free port and establish forwarding start_port_forwarding() { local remote_port=$1 local try_port=$1 while :; do if is_port_free "$try_port"; then ssh -S "$CONTROL_SOCKET" -O forward -L "$try_port:127.0.0.1:$remote_port" "$@" 2>/dev/null LOCAL_PORT=$try_port printf "Forwarded local port %s to remote port %s\r\n" "$LOCAL_PORT" "$remote_port" break fi try_port=$(random_port) done } open_browser() { local url="$1" printf "\r\n" printf "doppio-ssh is opening your browser to navigate to: %s\r\n" "$url" printf "\r\n" printf "If the browser doesn't open, copy and paste the link instead, or\r\n" printf "depending on your terminal you might be able to open the link with\r\n" printf "Ctrl-click or Cmd-click.\r\n" printf "\r\n" printf "You will need to copy the access token and paste it into the Doppio\r\n" printf "authorisation page. Note: do not press Ctrl+C to copy it because that\r\n" printf "will shut down the Doppio server! Use Ctrl+Shift+C, or Cmd+C, or select\r\n" printf "and middle mouse click, or whatever other method works on your operating\r\n" printf "system.\r\n" printf "\r\n" if command -v xdg-open >/dev/null 2>&1; then xdg-open "$url" >/dev/null 2>&1 elif command -v open >/dev/null 2>&1; then open "$url" >/dev/null 2>&1 elif grep -qEi "(Microsoft|WSL)" /proc/version &>/dev/null; then powershell.exe start "$url" >/dev/null 2>&1 else printf "Could not open your browser. Please open the link manually.\r\n" printf "\r\n" fi } FORWARDING_STARTED=0 # Run the SSH command and process output ssh -M -S "$CONTROL_SOCKET" "$@" -t "$COMMAND" 2> >(while IFS= read -r line; do echo "$line" >&2 done) | while IFS= read -r line; do echo "$line" if [[ "$FORWARDING_STARTED" -eq 0 ]]; then # Strip out ANSI escape sequences clean_line=$(echo "$line" | sed -E 's/\x1B\[[0-9;]*[mK]//g') if [[ "$clean_line" =~ ^Server\ URL:\ [a-zA-Z]+://[a-zA-Z0-9._-]+:([0-9]+) ]]; then REMOTE_PORT="${BASH_REMATCH[1]}" FORWARDING_STARTED=1 # Start port forwarding in the background to avoid missing output lines { start_port_forwarding "$REMOTE_PORT" open_browser "http://127.0.0.1:$LOCAL_PORT/" # Prevent new SSH multiplexed connections, but keep the master alive ssh -S "$CONTROL_SOCKET" -O stop "$@" 2>/dev/null || true } & fi fi done echo "Finished, exiting doppio-ssh"