diff --git a/README.md b/README.md index 1a381c8..3365d9e 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ backtunnel-share /path/to/folder with remoteuser:remotehost for [opti - `-p, --tunnel-port `: Remote port to expose with `-R` (default: 2222) - `-l, --local-ssh-port `: Local sshd port to forward to (default: 22) - `-i, --invite`: Print a ready-to-copy access command for the remote user -- `--invite-mount `: Suggest mount point in the invite (default: `/mnt/remote-rssh`) +- `--invite-mount `: Suggest mount point in the invite (default: `~/remote-rssh`) - `--invite-file `: Also write the invite text (including unmount hint) to a file - `--qr`: Render the invite as a QR code (requires `qrencode`) @@ -36,12 +36,12 @@ backtunnel-share ~/projects with alice@vps.example.com for 1d -p 4422 -l 2222 -i The invite will look like this and can be pasted on the remote host: ```bash -backtunnel-access '/home/user/projects' from alice@vps.example.com -p 4422 -m '/mnt/remote-rssh' +backtunnel-access '/home/user/projects' from alice@vps.example.com -p 4422 -m "$HOME/remote-rssh" ``` Unmount on the remote side with: ```bash -fusermount -u /mnt/remote-rssh +fusermount -u ~/remote-rssh ``` ### `backtunnel-access` @@ -54,7 +54,7 @@ backtunnel-access /path/to/folder from remoteuser:remotehost [options] **Options** - `-p, --port `: Port on the remote host where the reverse tunnel listens (default: 2222) -- `-m, --mount-point `: Local mount point (default: `/mnt/remote-rssh`) +- `-m, --mount-point `: Local mount point (default: `~/remote-rssh`) --- @@ -183,7 +183,7 @@ Each run produces a log under `${XDG_STATE_HOME:-$HOME/.local/state}/backtunnel/ 3. When prompted: - **Print invite line for chat?** → Yes to get a one-liner your colleague can paste. - **Show QR code for the invite?** → Yes (requires `qrencode`) to display a terminal QR. - - **Suggested mount point** → Accept `/mnt/remote-rssh` or set your own. + - **Suggested mount point** → Accept `~/remote-rssh` or set your own. 4. A terminal opens, shows the **invite** (and QR if selected), and keeps the share open for the chosen duration. - Stop early with **Ctrl+C**. @@ -191,9 +191,9 @@ Each run produces a log under `${XDG_STATE_HOME:-$HOME/.local/state}/backtunnel/ ```bash # Paste the invite you sent them, e.g.: -backtunnel-access '/path/to/folder' from user@vps.example.com -p 2222 -m '/mnt/remote-rssh' +backtunnel-access '/path/to/folder' from user@vps.example.com -p 2222 -m "$HOME/remote-rssh" # Unmount when done: -fusermount -u /mnt/remote-rssh # or: umount /mnt/remote-rssh +fusermount -u ~/remote-rssh # or: umount ~/remote-rssh ``` --- diff --git a/docs/profiles.ini.example b/docs/profiles.ini.example index a80eda1..131db28 100644 --- a/docs/profiles.ini.example +++ b/docs/profiles.ini.example @@ -2,7 +2,7 @@ duration=2h tunnel_port=2222 local_ssh_port=22 -invite_mount=~/remote-rssh +invite_mount=$HOME/remote-rssh invite=true qr=false @@ -10,4 +10,4 @@ qr=false user=me host=vps.example.com tunnel_port=4422 -invite_mount=~/remote-rssh +invite_mount=$HOME/remote-rssh diff --git a/man/backtunnel.1 b/man/backtunnel.1 index 43b3dad..9b7991c 100644 --- a/man/backtunnel.1 +++ b/man/backtunnel.1 @@ -35,7 +35,7 @@ Print a ready-to-copy access command for the remote side. .TP .B --invite-mount -Suggested mount point included in the invite text (default: /mnt/remote-rssh). May be provided via profile. +Suggested mount point included in the invite text (default: $HOME/remote-rssh). May be provided via profile. .TP .B --invite-file @@ -56,7 +56,7 @@ Remote port on which the reverse tunnel listens (default: 2222). .TP .B -m, --mount-point -Local mount point for sshfs (default: /mnt/remote-rssh). +Local mount point for sshfs (default: $HOME/remote-rssh). .SH ARGUMENTS .TP @@ -116,7 +116,7 @@ A named profile providing at least \fBuser\fR and \fBhost\fR, and optional overr .br \fBlocal_ssh_port\fR=22 .br -\fBinvite_mount\fR=/mnt/remote-rssh +\fBinvite_mount\fR=$HOME/remote-rssh .P To use a profile, replace the remote with \fB@name\fR, e.g.: diff --git a/scripts/backtunnel-access b/scripts/backtunnel-access index ba687ab..4269a1f 100644 --- a/scripts/backtunnel-access +++ b/scripts/backtunnel-access @@ -7,7 +7,7 @@ set -euo pipefail PORT=2222 -MOUNTPOINT="/mnt/remote-rssh" +MOUNTPOINT="$HOME/remote-rssh" usage() { echo "Usage: $0 /path/to/folder from remoteuser:remotehost [-p PORT] [-m MOUNTPOINT]" >&2 @@ -47,6 +47,32 @@ while [[ $# -gt 0 ]]; do esac done +# --- normalize and prepare mount point --- +# 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) --- REMOTE_USER="" REMOTE_HOST="" @@ -66,10 +92,7 @@ fi 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; } -# --- prepare mountpoint --- -if [[ ! -d "$MOUNTPOINT" ]]; then - mkdir -p -- "$MOUNTPOINT" -fi +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 @@ -79,13 +102,51 @@ fi echo "🔗 Mounting '$FOLDER' from '$REMOTE_USER@$REMOTE_HOST' via reverse-tunnel localhost:$PORT → '$MOUNTPOINT' ..." +# --- ensure passwordless auth via tunnel (optional but user-friendly) --- +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=accept-new \ + -p "$PORT" "${SSH_IDENTITY_OPTS[@]}" "$REMOTE_USER@localhost" true 2>/dev/null; then cat >&2 </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 + +SSH_CMD="ssh -o ConnectTimeout=10 -o StrictHostKeyChecking=accept-new" +# 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 + sshfs \ -p "$PORT" \ -o reconnect \ -o ServerAliveInterval=15 \ -o ServerAliveCountMax=3 \ - -o ssh_command="ssh -o ConnectTimeout=10" \ + -o ssh_command="$SSH_CMD" \ -- "$REMOTE_USER@localhost:$FOLDER" "$MOUNTPOINT" echo "✅ Mounted at: $MOUNTPOINT" -echo "To unmount: fusermount -u \"$MOUNTPOINT\"" +echo "To unmount: fusermount -u \"$MOUNTPOINT\" || fusermount3 -u \"$MOUNTPOINT\"" diff --git a/scripts/backtunnel-auth-setup b/scripts/backtunnel-auth-setup new file mode 100644 index 0000000..3805ccf --- /dev/null +++ b/scripts/backtunnel-auth-setup @@ -0,0 +1,44 @@ +#!/usr/bin/env bash +# Initialize tunnel-only SSH auth for BackTunnel (Option A) +# Usage: backtunnel-auth-setup [-p PORT] user@localhost +set -euo pipefail + +PORT=2222 +while [[ $# -gt 0 ]]; do + case "$1" in + -p|--port) PORT="$2"; shift 2;; + -h|--help) + echo "Usage: backtunnel-auth-setup [-p PORT] user@localhost" + exit 0;; + *) break;; + esac +done + +DEST="${1:-}" +[[ -n "$DEST" ]] || { echo "Missing destination (e.g., user@localhost)."; exit 1; } + +KEY="$HOME/.ssh/id_ed25519_backtunnel" +PUB="$KEY.pub" + +# 1) Create a dedicated key if missing +if [[ ! -f "$KEY" ]]; then + echo "Generating dedicated key at $KEY ..." + ssh-keygen -t ed25519 -f "$KEY" -N "" -C "backtunnel" +fi + +# 2) Append restricted key only (idempotent): tunnel-only + SFTP-only +echo "Installing restricted key (tunnel-only, SFTP-only) via port $PORT ..." +RESTRICTED_LINE="$(printf 'from="127.0.0.1",command="internal-sftp",restrict '; cat "$PUB")" +ssh -p "$PORT" "$DEST" bash -lc ' + set -euo pipefail + umask 077 + mkdir -p ~/.ssh + touch ~/.ssh/authorized_keys + chmod 600 ~/.ssh/authorized_keys + # Only append if not already present + if ! grep -Fqx -- "$RESTRICTED_LINE" ~/.ssh/authorized_keys 2>/dev/null; then + printf "%s\n" "$RESTRICTED_LINE" >> ~/.ssh/authorized_keys + fi +' _ RESTRICTED_LINE="$RESTRICTED_LINE" + +echo "Done. This key will only work via the reverse tunnel (127.0.0.1) and only for SFTP." diff --git a/scripts/backtunnel-share b/scripts/backtunnel-share index 484c450..0dce337 100644 --- a/scripts/backtunnel-share +++ b/scripts/backtunnel-share @@ -64,7 +64,7 @@ profile_apply_defaults() { # set globals if unset; named overrides default # defaults v="$(ini_get default tunnel_port)"; [[ -n "$v" && "${TUNNEL_PORT}" == "2222" ]] && TUNNEL_PORT="$v" v="$(ini_get default local_ssh_port)"; [[ -n "$v" && "${LOCAL_SSH_PORT}" == "22" ]] && LOCAL_SSH_PORT="$v" - v="$(ini_get default invite_mount)"; [[ -n "$v" && "${INVITE_MOUNT}" == "/mnt/remote-rssh" ]] && INVITE_MOUNT="$v" + v="$(ini_get default invite_mount)"; [[ -n "$v" && "${INVITE_MOUNT}" == "$HOME/remote-rssh" ]] && INVITE_MOUNT="$v" v="$(ini_get default invite)"; [[ "${v,,}" == "true" ]] && INVITE=true v="$(ini_get default qr)"; [[ "${v,,}" == "true" ]] && QR=true if [[ -z "$DURATION" ]]; then @@ -84,7 +84,7 @@ 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_MOUNT="$HOME/remote-rssh" INVITE_FILE="" QR=false # render invite as terminal QR (requires qrencode) @@ -214,19 +214,30 @@ echo # --- print invite (optional) --- if $INVITE; then INVITE_CMD="backtunnel-access '${FOLDER}' from ${REMOTE_USER}@${REMOTE_HOST} -p ${TUNNEL_PORT} -m '${INVITE_MOUNT}'" + # Also provide a one-time auth bootstrap that installs a restricted, tunnel-only SFTP key + AUTH_CMD="backtunnel-auth-setup -p ${TUNNEL_PORT} ${REMOTE_USER}@localhost" + INVITE_TEXT=$( - cat < "${INVITE_FILE}" echo "Saved invite to: ${INVITE_FILE}" diff --git a/scripts/backtunnel-share-gui b/scripts/backtunnel-share-gui index d2ade4f..e6dfe57 100644 --- a/scripts/backtunnel-share-gui +++ b/scripts/backtunnel-share-gui @@ -22,7 +22,7 @@ REMOTE_DEFAULT="user@vps.example.com" DURATION_DEFAULT="2h" TPORT_DEFAULT="2222" LPORT_DEFAULT="22" -INVITE_MOUNT_DEFAULT="/mnt/remote-rssh" +INVITE_MOUNT_DEFAULT="$HOME/remote-rssh" # Profiles (search order: user -> system -> packaged fallback) PROFILES_USER="${XDG_CONFIG_HOME:-$HOME/.config}/backtunnel/profiles.ini" diff --git a/scripts/install.sh b/scripts/install.sh index f2645bd..3a4006a 100644 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -20,6 +20,7 @@ say "Installing to PREFIX=${PREFIX} DESTDIR=${DESTDIR}" # --- Binaries (CLI) --- install -Dm755 "scripts/backtunnel-share" "$BINDIR/backtunnel-share" install -Dm755 "scripts/backtunnel-access" "$BINDIR/backtunnel-access" +install -Dm755 "scripts/backtunnel-auth-setup" "$BINDIR/backtunnel-auth-setup" # --- GUI wrappers (optional) --- install -Dm755 "scripts/backtunnel-share-gui" "$BINDIR/backtunnel-share-gui" diff --git a/scripts/uninstall.sh b/scripts/uninstall.sh index 30c99e3..dea8277 100644 --- a/scripts/uninstall.sh +++ b/scripts/uninstall.sh @@ -38,6 +38,7 @@ say "Uninstalling from PREFIX=${PREFIX} DESTDIR=${DESTDIR} (PURGE=${PURGE})" # --- Remove binaries --- rm -f "$BINDIR/backtunnel-share" \ "$BINDIR/backtunnel-access" \ + "$BINDIR/backtunnel-auth-setup" \ "$BINDIR/backtunnel-share-gui" \ "$BINDIR/backtunnel-access-gui" \ "$BINDIR/backtunnel-open-term" \