#!/usr/bin/env bash # Copyright (c) 2025. LUXIM d.o.o., Slovenia - Matjaž Mozetič. # backtunnel-share: Share a folder using reverse SSH for a limited duration # Syntax: backtunnel-share /path/to/folder with remoteuser:remotehost for 2h # Options: -p|--tunnel-port -l|--local-ssh-port -i|--invite [--invite-mount ] [--invite-file ] [--qr] -h|--help set -euo pipefail TUNNEL_PORT=2222 # remote-side port exposed via -R LOCAL_SSH_PORT=22 # local sshd port to forward to DURATION="" # required: e.g. 30m, 2h, 1d INVITE=false # print a ready-to-copy access command INVITE_MOUNT="/mnt/remote-rssh" INVITE_FILE="" QR=false # render invite as terminal QR (requires qrencode) usage() { cat >&2 < [options] Positional (required, in order): /path/to/folder Informational only (the actual folder is mounted by backtunnel-access) with Literal keyword remoteuser:remotehost Or remoteuser@remotehost for Literal keyword e.g. 30m, 2h, 1d (passed to 'timeout') Options: -p, --tunnel-port N Remote tunnel port (default: ${TUNNEL_PORT}) -l, --local-ssh-port N Local sshd port to expose (default: ${LOCAL_SSH_PORT}) -i, --invite Print a ready-to-copy access command for the remote side --invite-mount PATH Mount point suggested in invite (default: ${INVITE_MOUNT}) --invite-file FILE Also write the invite text (with unmount hint) to FILE --qr Also print a QR code (requires 'qrencode') -h, --help Show this help Examples: $(basename "$0") ~/projects with alice:vps.example.com for 2h $(basename "$0") ~/projects with alice@vps.example.com for 1d -p 4422 -l 2222 $(basename "$0") ~/projects with alice@vps.example.com for 2h -i --qr EOF exit 1 } # --- basic positional parsing --- [[ $# -lt 5 ]] && usage FOLDER=$1 KW1=$2 REMOTE=$3 KW2=$4 DURATION=$5 shift 5 || true [[ "$KW1" != "with" ]] && usage [[ "$KW2" != "for" ]] && usage # --- optional flags --- while [[ $# -gt 0 ]]; do case "$1" in -p|--tunnel-port) [[ $# -lt 2 ]] && usage TUNNEL_PORT=$2 shift 2 ;; -l|--local-ssh-port) [[ $# -lt 2 ]] && usage LOCAL_SSH_PORT=$2 shift 2 ;; -i|--invite) INVITE=true shift ;; --invite-mount) [[ $# -lt 2 ]] && usage INVITE_MOUNT=$2 shift 2 ;; --invite-file) [[ $# -lt 2 ]] && usage INVITE_FILE=$2 shift 2 ;; --qr) QR=true shift ;; -h|--help) usage ;; *) echo "Unknown option: $1" >&2 usage ;; esac done # --- validate duration (timeout supports s,m,h,d) --- if [[ ! "$DURATION" =~ ^[0-9]+[smhd]$ ]]; then echo "Invalid duration '$DURATION' (use forms like 30m, 2h, 1d)." >&2 exit 1 fi # --- split remote user/host --- REMOTE_USER="" REMOTE_HOST="" if [[ "$REMOTE" == *:* ]]; then REMOTE_USER=${REMOTE%%:*} REMOTE_HOST=${REMOTE#*:} elif [[ "$REMOTE" == *"@"* ]]; then REMOTE_USER=${REMOTE%%@*} REMOTE_HOST=${REMOTE#*@} else echo "Invalid remote format. Use remoteuser:remotehost or remoteuser@remotehost" >&2 exit 1 fi # --- deps check --- command -v ssh >/dev/null 2>&1 || { echo "ssh not found."; exit 1; } command -v timeout >/dev/null 2>&1 || { echo "timeout not found."; exit 1; } echo "⏳ Sharing '${FOLDER}' via reverse SSH:" echo " local sshd port : ${LOCAL_SSH_PORT}" echo " remote bind port : ${TUNNEL_PORT} (on ${REMOTE_HOST})" echo " remote user : ${REMOTE_USER}" echo " duration : ${DURATION}" echo # --- print invite (optional) --- if $INVITE; then INVITE_CMD="backtunnel-access '${FOLDER}' from ${REMOTE_USER}@${REMOTE_HOST} -p ${TUNNEL_PORT} -m '${INVITE_MOUNT}'" INVITE_TEXT=$( cat < "${INVITE_FILE}" echo "Saved invite to: ${INVITE_FILE}" fi if $QR; then if command -v qrencode >/dev/null 2>&1; then echo echo "📱 QR (scan to copy the command):" printf "%s" "${INVITE_CMD}" | qrencode -t ansiutf8 else echo "⚠️ 'qrencode' not installed; skipping QR." fi fi echo fi echo "Tip: On the remote side, mount with:" echo " backtunnel-access '${FOLDER}' from ${REMOTE_USER}@${REMOTE_HOST} -p ${TUNNEL_PORT}" timeout "$DURATION" ssh -N \ -R "${TUNNEL_PORT}:localhost:${LOCAL_SSH_PORT}" \ -- "${REMOTE_USER}@${REMOTE_HOST}" rc=$? if [[ $rc -eq 124 ]]; then echo "⏹️ Sharing ended: reached duration (${DURATION})." exit 0 fi exit "$rc"