Update default mount point to $HOME/remote-rssh for consistency, and introduce backtunnel-auth-setup script for restricted SFTP-only key management. Update docs, scripts, and uninstall/install logic to reflect changes. Ensure robust handling of user-specified mount points in backtunnel-access.

This commit is contained in:
2025-09-20 10:49:45 +02:00
parent c46a1da405
commit 85e73ca4da
9 changed files with 144 additions and 26 deletions

View File

@@ -18,7 +18,7 @@ backtunnel-share /path/to/folder with remoteuser:remotehost for <duration> [opti
- `-p, --tunnel-port <PORT>`: Remote port to expose with `-R` (default: 2222)
- `-l, --local-ssh-port <PORT>`: Local sshd port to forward to (default: 22)
- `-i, --invite`: Print a ready-to-copy access command for the remote user
- `--invite-mount <PATH>`: Suggest mount point in the invite (default: `/mnt/remote-rssh`)
- `--invite-mount <PATH>`: Suggest mount point in the invite (default: `~/remote-rssh`)
- `--invite-file <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>`: Port on the remote host where the reverse tunnel listens (default: 2222)
- `-m, --mount-point <PATH>`: Local mount point (default: `/mnt/remote-rssh`)
- `-m, --mount-point <PATH>`: 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
```
---

View File

@@ -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

View File

@@ -35,7 +35,7 @@ Print a ready-to-copy access command for the remote side.
.TP
.B --invite-mount <PATH>
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 <FILE>
@@ -56,7 +56,7 @@ Remote port on which the reverse tunnel listens (default: 2222).
.TP
.B -m, --mount-point <PATH>
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.:

View File

@@ -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 <<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 ..."
if ! sftp -q -P "$PORT" -o StrictHostKeyChecking=accept-new "${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
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\""

View File

@@ -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."

View File

@@ -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 <<EOT
# Paste this on the REMOTE host (or SSH there first, then paste):
cat <<EOT
# 1) (one-time) install a tunnel-only, SFTP-only key via the reverse tunnel:
${AUTH_CMD}
# 2) mount the share:
${INVITE_CMD}
# Unmount when done:
# (use the one available on your system)
fusermount -u '${INVITE_MOUNT}' || fusermount3 -u '${INVITE_MOUNT}'
EOT
)
)
echo "🔗 Invite (copy to chat):"
echo "------------------------------------------------------------"
echo "${AUTH_CMD}"
echo "${INVITE_CMD}"
echo "------------------------------------------------------------"
if [[ -n "${INVITE_FILE}" ]]; then
printf "%s\n" "${INVITE_TEXT}" > "${INVITE_FILE}"
echo "Saved invite to: ${INVITE_FILE}"

View File

@@ -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"

View File

@@ -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"

View File

@@ -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" \