#!/usr/bin/env bash # Temporary reverse SSH tunnel for Linux/macOS. Automatically prepare SSH when needed; no password storage. set -euo pipefail local_port=22 check_only=false install_ssh=false install_only=false no_install=false while [[ $# -gt 0 ]]; do case "$1" in --local-port) [[ $# -ge 2 ]] || { echo 'Missing local port.' >&2; exit 2; }; local_port=$2; shift 2 ;; --check) check_only=true; shift ;; --install) install_ssh=true; shift ;; --no-install) no_install=true; shift ;; --install-only) install_ssh=true; install_only=true; shift ;; -h|--help) echo 'Usage: bash connect.sh [--local-port 22] [--check | --no-install | --install-only]' echo '--check: inspect SSH service and configuration without changing the machine.' echo 'Default: detect SSH, install/enable if needed, then connect.' echo '--no-install: connect only when SSH is already ready.' echo '--install-only: install/enable SSH and verify it, without a tunnel.' echo 'Requires OpenSSH client and a local SSH server. Ctrl-C disconnects.' exit 0 ;; *) echo "Unknown argument: $1" >&2; exit 2 ;; esac done if [[ ! $local_port =~ ^[0-9]{1,5}$ ]] || (( 10#$local_port < 1 || 10#$local_port > 65535 )); then echo 'Local port must be 1..65535.' >&2; exit 2 fi local_port=$((10#$local_port)) if $check_only && $install_ssh; then echo '--check cannot be combined with installation.' >&2; exit 2 fi if $no_install && $install_ssh; then echo '--no-install cannot be combined with installation.' >&2; exit 2 fi if $install_ssh && [[ $local_port != 22 ]]; then echo 'Installation uses the existing SSH configuration and requires --local-port 22. Configure custom ports manually.' >&2 exit 2 fi # Custom ports and read-only modes never install automatically. if ! $check_only && ! $no_install && [[ $local_port == 22 ]]; then install_ssh=true; fi ssh_ready() { command -v ssh-keyscan >/dev/null || return 1 local keys keys=$(ssh-keyscan -4 -T 3 -p "$local_port" 127.0.0.1 2>/dev/null) || return 1 # Verify an SSH handshake and host key, not merely an open TCP socket. printf '%s\n' "$keys" | awk '$2 ~ /^(ssh-|ecdsa-)/ && NF >= 3 { found=1 } END { exit !found }' } as_root() { if (( EUID == 0 )); then "$@" General > Sharing > Remote Login; the terminal may need Full Disk Access.' >&2 return 1 fi return fi [[ $os == Linux ]] || { echo "Unsupported OS: $os. Install OpenSSH manually." >&2; return 1; } # Check the service manager before installing any packages. if command -v systemctl >/dev/null && [[ -d /run/systemd/system ]]; then manager=systemd elif command -v rc-service >/dev/null && command -v rc-update >/dev/null; then manager=openrc else echo 'Automatic installation requires systemd or OpenRC (including systemd-enabled WSL). No packages installed.' >&2 return 1 fi if command -v ssh >/dev/null && command -v ssh-keyscan >/dev/null && { command -v sshd >/dev/null || [[ -x /usr/sbin/sshd || -x /usr/local/sbin/sshd ]]; }; then need_packages=false fi echo 'Installing/enabling OpenSSH server. This service persists after the tunnel exits.' if command -v apt-get >/dev/null; then if $need_packages; then as_root apt-get update as_root env DEBIAN_FRONTEND=noninteractive apt-get install -y openssh-client openssh-server fi service_name=ssh elif command -v dnf >/dev/null; then if $need_packages; then as_root dnf install -y openssh-clients openssh-server; fi service_name=sshd elif command -v yum >/dev/null; then if $need_packages; then as_root yum install -y openssh-clients openssh-server; fi service_name=sshd elif command -v apk >/dev/null; then if $need_packages; then as_root apk add openssh-client openssh-server; fi service_name=sshd else echo 'Supported installers: apt-get, dnf, yum, apk. Install OpenSSH manually on this distribution.' >&2 return 1 fi if [[ $manager == systemd ]]; then as_root systemctl enable --now "$service_name" else as_root rc-update add "$service_name" default as_root rc-service "$service_name" start fi } ready=false if ssh_ready; then ready=true; fi if $install_ssh && { ! $ready || ! command -v ssh >/dev/null; }; then if (( EUID != 0 )); then command -v sudo >/dev/null || { echo 'Run from a root shell; sudo is unavailable.' >&2; exit 1; } sudo -v fi install_server hash -r # Allow a newly started service time to become ready. for _ in 1 2 3 4 5; do if ssh_ready; then ready=true; break; fi sleep 1 done fi if ! command -v ssh >/dev/null || ! $ready; then echo "SSH check failed: need an OpenSSH client and a working SSH service on 127.0.0.1:$local_port." >&2 echo 'Run this downloaded script with --install to install/start SSH on port 22.' >&2 echo 'Existing custom port and authentication settings are preserved; check them if installation does not restore service.' >&2 exit 1 fi echo "SSH service ready: handshake verified on 127.0.0.1:$local_port (login credentials not tested)." if $install_only; then exit 0; fi umask 077 work=$(mktemp -d "${TMPDIR:-/tmp}/quick-tunnel.XXXXXXXX") trap 'rm -rf -- "$work"' EXIT trap 'exit 130' INT trap 'exit 143' TERM printf '%s\n' '[6f3907bcb084.sn.mynetname.net]:22022 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKZqy8G5eBCTsLi/tRhKCcktl/SvL01l8r1fCJF4EeYZ' > "$work/known_hosts" ssh_args=(-F /dev/null -4 -NT -o PreferredAuthentications=password -o PubkeyAuthentication=no -o KbdInteractiveAuthentication=no -o PasswordAuthentication=yes -o BatchMode=no -o NumberOfPasswordPrompts=3 -o ExitOnForwardFailure=yes -o ServerAliveInterval=30 -o ServerAliveCountMax=3 -o ConnectTimeout=15 -o StrictHostKeyChecking=yes -o "UserKnownHostsFile=\"$work/known_hosts\"" -o GlobalKnownHostsFile=/dev/null -o ControlMaster=no -o ControlPath=none -o ControlPersist=no -p 22022 -R "0.0.0.0:10020:127.0.0.1:$local_port" temp-tunnel@6f3907bcb084.sn.mynetname.net) if $check_only; then ssh -G "${ssh_args[@]}" >/dev/null echo "Service and configuration OK: receiver 10020 -> this machine 127.0.0.1:$local_port. No connection opened." exit 0 fi # Keep SSH attached to an interactive terminal even when launched from a download command. if ! { exec 3<>/dev/tty; } 2>/dev/null; then echo 'Run this script in an interactive terminal to enter the tunnel password.' >&2; exit 1 fi printf 'Forward: receiver 10020 -> this machine 127.0.0.1:%s\n' "$local_port" echo 'Enter the tunnel password. Keep this terminal open; Ctrl-C disconnects.' ssh "${ssh_args[@]}" <&3