Files
BackTunnel/scripts/backtunnel-access
sysadminmatmoz ae8ab9a7e0 Add backtunnel-umount script, host key policy support, and extend TUI/CLI integration
Introduce `backtunnel-umount` as a portable unmount helper, preferring `fusermount3`, `fusermount`, or `umount`. Add `BACKTUNNEL_HOSTKEY_POLICY` for configurable host key handling in `backtunnel-share` and `backtunnel-access`. Update TUIs for remote folder prompts and mount point handling. Enhance bash completion for TUI commands with directory suggestions. Revamp terminal selection logic in `backtunnel-open-term` to prioritize modern emulators like wezterm. Extend tests with scaffolds for host key policy and unmount behavior. Update README with new scripts, workflows, features, and troubleshooting tips.
2025-09-21 18:56:15 +02:00

201 lines
6.8 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
# SPDX-License-Identifier: GPL-3.0-or-later
# Copyright (c) 2025 LUXIM d.o.o., Slovenia
# Author: Matjaž Mozetič
#
# Name: backtunnel-access
# Summary: Mount a local folder exposed via a reverse SSH tunnel (SFTP over sshfs).
# Description:
# Connects to a reverse SSH listener on the remote host's loopback (localhost:PORT),
# and mounts the specified local folder from the sharing side using sshfs.
# Performs basic checks (mountpoint readiness, auth hint, SFTP visibility) and
# sets sensible reconnect/keepalive options for a stable mount.
#
# Usage:
# backtunnel-access /path/to/folder from remoteuser:remotehost [-p PORT] [-m MOUNTPOINT]
#
# Examples:
# backtunnel-access ~/projects from alice@vps.example.com -p 2222 -m ~/remote-rssh
# backtunnel-access /data from bob:vps.example.com --port 4422 --mount-point /mnt/remote-rssh
#
# Dependencies:
# - bash
# - sshfs, sftp (OpenSSH), mountpoint
#
# Exit codes:
# 0 success
# 1 invalid usage/arguments or validation failure (e.g., mountpoint not writable)
# 2+ runtime errors from underlying tools (sshfs/sftp/etc.)
#
# Notes:
# - Expects a reverse tunnel to be active on the remote side, binding localhost:PORT
# back to the sharers local sshd.
# - If passwordless auth isnt set for $REMOTE_USER@localhost:$PORT, a one-time
# setup command is suggested; mount may still proceed with password prompts.
# backtunnel-access: Mount a folder shared over reverse SSH
# Usage: backtunnel-access /path/to/folder from remoteuser:remotehost [-p PORT] [-m MOUNTPOINT]
set -euo pipefail
PORT=2222
MOUNTPOINT="$HOME/remote-rssh"
# Host key checking policy: env BACKTUNNEL_HOSTKEY_POLICY = yes|no|ask|accept-new (default: accept-new)
HKP="${BACKTUNNEL_HOSTKEY_POLICY:-accept-new}"
case "$HKP" in
yes|no|ask|accept-new) ;;
*) HKP="accept-new" ;;
esac
usage() {
echo "Usage: $0 /path/to/folder from remoteuser:remotehost [-p PORT] [-m MOUNTPOINT]" >&2
exit 1
}
# --- parse positional args ---
# Purpose: enforce grammar "<folder> from <remote>" and collect required args.
[[ $# -lt 3 ]] && usage
FOLDER=$1
KEYWORD=$2
REMOTE=$3
shift 3 || true
[[ "$KEYWORD" != "from" ]] && usage
# Optional flags
# Purpose: allow custom tunnel port and mount destination.
while [[ $# -gt 0 ]]; do
case "$1" in
-p|--port)
[[ $# -lt 2 ]] && usage
PORT=$2
shift 2
;;
-m|--mount-point)
[[ $# -lt 2 ]] && usage
MOUNTPOINT=$2
shift 2
;;
-h|--help)
usage
;;
*)
echo "Unknown option: $1" >&2
usage
;;
esac
done
# --- normalize and prepare mount point ---
# Purpose: make the mount point usable (expand ~, absolutize when possible, ensure perms).
# Expand leading '~' even if quoted or passed via GUI
# Note: default uses $HOME; still expand '~' if passed via CLI/GUI
if [[ "${MOUNTPOINT:-}" == "~"* ]]; then
MOUNTPOINT="${MOUNTPOINT/#\~/$HOME}"
fi
# Make absolute if realpath exists (doesn't fail if missing)
if command -v realpath >/dev/null 2>&1; then
MOUNTPOINT="$(realpath -m -- "$MOUNTPOINT")"
fi
# Create if missing, with restrictive perms
if [[ ! -d "$MOUNTPOINT" ]]; then
mkdir -p -- "$MOUNTPOINT"
chmod 700 -- "$MOUNTPOINT" 2>/dev/null || true
fi
# Must be user-writable and empty (warn if non-empty to avoid masking files)
if [[ ! -w "$MOUNTPOINT" ]]; then
echo "Mount point '$MOUNTPOINT' is not writable by $(id -un)." >&2
exit 1
fi
# Warn if non-empty to avoid masking existing files
if [[ -n "$(ls -A -- "$MOUNTPOINT" 2>/dev/null || true)" ]]; then
echo "⚠️ Mount point '$MOUNTPOINT' is not empty; its contents will be hidden while mounted." >&2
fi
# --- split remote user/host (supports user:host or user@host) ---
# Purpose: accept flexible remote formats commonly used in SSH tooling.
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 ---
# Purpose: fail fast when core tools are missing.
command -v sshfs >/dev/null 2>&1 || { echo "sshfs not found. Install sshfs first."; exit 1; }
command -v mountpoint >/dev/null 2>&1 || { echo "mountpoint utility not found."; exit 1; }
command -v sftp >/dev/null 2>&1 || { echo "sftp not found (usually provided by openssh)."; exit 1; }
# Avoid double-mount
if mountpoint -q -- "$MOUNTPOINT"; then
echo "Mount point '$MOUNTPOINT' is already mounted. Unmount it first (e.g., 'fusermount -u \"$MOUNTPOINT\"')." >&2
exit 1
fi
echo "🔗 Mounting '$FOLDER' from '$REMOTE_USER@$REMOTE_HOST' via reverse-tunnel localhost:$PORT → '$MOUNTPOINT' ..."
# --- ensure passwordless auth via tunnel (optional but user-friendly) ---
# Purpose: detect whether a dedicated identity exists and hint user if passwordless setup is missing.
SSH_IDENTITY_OPTS=()
if [[ -f "$HOME/.ssh/id_ed25519_backtunnel" ]]; then
SSH_IDENTITY_OPTS+=( -o IdentityFile="$HOME/.ssh/id_ed25519_backtunnel" -o IdentitiesOnly=yes )
fi
SFTP_ID_OPTS=()
if [[ -f "$HOME/.ssh/id_ed25519_backtunnel" ]]; then
SFTP_ID_OPTS+=( -o IdentityFile="$HOME/.ssh/id_ed25519_backtunnel" -o IdentitiesOnly=yes )
fi
if ! ssh -o BatchMode=yes -o StrictHostKeyChecking="$HKP" \
-p "$PORT" "${SSH_IDENTITY_OPTS[@]}" "$REMOTE_USER@localhost" true 2>/dev/null; then cat >&2 <<EOF
⚠️ Passwordless auth not set for $REMOTE_USER@localhost:$PORT.
You can initialize a tunnel-only, SFTP-only key with:
backtunnel-auth-setup -p $PORT $REMOTE_USER@localhost
(It will ask once for the server password to install and restrict the key.)
EOF
# continue anyway; sshfs may prompt for password
fi
echo "Checking remote path visibility via SFTP ..."
# Purpose: quick sanity check that the target path is visible over SFTP before mounting.
if ! sftp -q -P "$PORT" -o StrictHostKeyChecking="$HKP" "${SFTP_ID_OPTS[@]}" \
"$REMOTE_USER@localhost" <<< "ls -1 \"$FOLDER\"" >/dev/null 2>&1; then
echo "⚠️ Remote path '$FOLDER' not listable via SFTP. It may not exist or permissions deny access." >&2
echo " Proceeding to mount; sshfs may fail if the path is invalid." >&2
fi
# Build ssh command used by sshfs (adds keepalive/connect-timeout, identity if present).
SSH_CMD="ssh -o ConnectTimeout=10 -o StrictHostKeyChecking=$HKP"
# If identity options are present, append them to SSH_CMD
if [[ ${#SSH_IDENTITY_OPTS[@]} -gt 0 ]]; then
# Join array safely
for opt in "${SSH_IDENTITY_OPTS[@]}"; do
SSH_CMD+=" $opt"
done
fi
# Perform the mount with reconnect/keepalive options for resilience.
sshfs \
-p "$PORT" \
-o reconnect \
-o ServerAliveInterval=15 \
-o ServerAliveCountMax=3 \
-o ssh_command="$SSH_CMD" \
-- "$REMOTE_USER@localhost:$FOLDER" "$MOUNTPOINT"
echo "✅ Mounted at: $MOUNTPOINT"
echo "To unmount: backtunnel-umount \"$MOUNTPOINT\""