Add comprehensive inline metadata documentation to all BackTunnel scripts
This commit is contained in:
@@ -1,4 +1,29 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
# Name: BackTunnel Bash Completion
|
||||||
|
# Summary: Programmable completion for backtunnel-share and backtunnel-access.
|
||||||
|
# Description:
|
||||||
|
# Provides contextual tab-completion for BackTunnel CLI tools, including positional
|
||||||
|
# scaffolding ("with"/"from", "for", duration suggestions), @profile expansion,
|
||||||
|
# SSH host suggestions from known_hosts and ~/.ssh/config, key-name and *.pub completion,
|
||||||
|
# and directory completion for mount paths.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# - Source in your shell (for current session):
|
||||||
|
# source /path/to/backtunnel.bash
|
||||||
|
# - Install system-wide or in your completion.d directory (auto-sourced by bash-completion).
|
||||||
|
#
|
||||||
|
# Dependencies:
|
||||||
|
# - bash with programmable completion (bash-completion recommended)
|
||||||
|
# - awk (for profile parsing)
|
||||||
|
#
|
||||||
|
# Compatibility:
|
||||||
|
# - Targets Bash completion v1 (COMP_WORDS, COMPREPLY, compgen, compopt).
|
||||||
|
#
|
||||||
|
# Notes:
|
||||||
|
# - Reads profiles from ${XDG_CONFIG_HOME:-$HOME/.config}/backtunnel/profiles.ini,
|
||||||
|
# /etc/backtunnel/profiles.ini, and /usr/share/backtunnel/profiles.ini when present.
|
||||||
|
# - Best-effort parsing and host extraction; hashed known_hosts entries are ignored.
|
||||||
|
|
||||||
# BackTunnel bash completion for:
|
# BackTunnel bash completion for:
|
||||||
# - backtunnel-share
|
# - backtunnel-share
|
||||||
# - backtunnel-access
|
# - backtunnel-access
|
||||||
@@ -10,10 +35,12 @@
|
|||||||
# - --allow-known completes names from ~/.config/backtunnel/authorized/*.pub
|
# - --allow-known completes names from ~/.config/backtunnel/authorized/*.pub
|
||||||
# - SSH host completion from known_hosts and ssh config Host entries
|
# - SSH host completion from known_hosts and ssh config Host entries
|
||||||
# - Path completion for first positional and --invite-mount / --mount-point
|
# - Path completion for first positional and --invite-mount / --mount-point
|
||||||
# - Option-aware value completion (-p/-l suggest typical ports)
|
|
||||||
|
|
||||||
# ---------- helpers ----------
|
# ---------- helpers ----------
|
||||||
|
|
||||||
|
# _bt_cfg_files: print existing profiles.ini files in precedence order (high → low).
|
||||||
|
# Arguments: none
|
||||||
|
# Output: absolute paths, one per line
|
||||||
_bt_cfg_files() {
|
_bt_cfg_files() {
|
||||||
local u="${XDG_CONFIG_HOME:-$HOME/.config}/backtunnel/profiles.ini"
|
local u="${XDG_CONFIG_HOME:-$HOME/.config}/backtunnel/profiles.ini"
|
||||||
local s="/etc/backtunnel/profiles.ini"
|
local s="/etc/backtunnel/profiles.ini"
|
||||||
@@ -23,6 +50,9 @@ _bt_cfg_files() {
|
|||||||
[[ -f "$p" ]] && printf '%s\n' "$p"
|
[[ -f "$p" ]] && printf '%s\n' "$p"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# _bt_list_profiles: list profile section names (excluding [default]) from all config files.
|
||||||
|
# Arguments: none
|
||||||
|
# Output: profile names, one per line (deduplicated)
|
||||||
_bt_list_profiles() {
|
_bt_list_profiles() {
|
||||||
# Output section names excluding [default]
|
# Output section names excluding [default]
|
||||||
local f
|
local f
|
||||||
@@ -35,6 +65,9 @@ _bt_list_profiles() {
|
|||||||
done < <(_bt_cfg_files) 2>/dev/null | sort -u
|
done < <(_bt_cfg_files) 2>/dev/null | sort -u
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# _bt_list_authorized_names: list stored accessor key names from the authorized store.
|
||||||
|
# Arguments: none
|
||||||
|
# Output: key names (basename without .pub), one per line
|
||||||
_bt_list_authorized_names() {
|
_bt_list_authorized_names() {
|
||||||
# From ~/.config/backtunnel/authorized/*.pub → basename w/o .pub
|
# From ~/.config/backtunnel/authorized/*.pub → basename w/o .pub
|
||||||
local d="${XDG_CONFIG_HOME:-$HOME/.config}/backtunnel/authorized"
|
local d="${XDG_CONFIG_HOME:-$HOME/.config}/backtunnel/authorized"
|
||||||
@@ -46,6 +79,10 @@ _bt_list_authorized_names() {
|
|||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# _bt_list_ssh_hosts: gather SSH host candidates from known_hosts and ~/.ssh/config.
|
||||||
|
# Arguments: none
|
||||||
|
# Output: hostnames (best-effort), one per line
|
||||||
|
# Notes: skips hashed known_hosts entries and wildcards; filters out plain IP literals.
|
||||||
_bt_list_ssh_hosts() {
|
_bt_list_ssh_hosts() {
|
||||||
# Collect hosts from known_hosts and ~/.ssh/config Host entries (best effort)
|
# Collect hosts from known_hosts and ~/.ssh/config Host entries (best effort)
|
||||||
local out=()
|
local out=()
|
||||||
@@ -76,11 +113,17 @@ _bt_list_ssh_hosts() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Predicates to distinguish which command is being completed
|
||||||
_bt_is_backtunnel_share() { [[ ${COMP_WORDS[0]} == backtunnel-share ]]; }
|
_bt_is_backtunnel_share() { [[ ${COMP_WORDS[0]} == backtunnel-share ]]; }
|
||||||
_bt_is_backtunnel_access(){ [[ ${COMP_WORDS[0]} == backtunnel-access ]]; }
|
_bt_is_backtunnel_access(){ [[ ${COMP_WORDS[0]} == backtunnel-access ]]; }
|
||||||
|
|
||||||
# ---------- main completer ----------
|
# ---------- main completer ----------
|
||||||
|
|
||||||
|
# _backtunnel_complete: top-level completion function for both commands.
|
||||||
|
# Arguments:
|
||||||
|
# Uses global completion variables: COMP_WORDS, COMP_CWORD
|
||||||
|
# Output:
|
||||||
|
# Sets COMPREPLY array with candidates appropriate to the current cursor position.
|
||||||
_backtunnel_complete() {
|
_backtunnel_complete() {
|
||||||
local cur prev
|
local cur prev
|
||||||
cur="${COMP_WORDS[COMP_CWORD]}"
|
cur="${COMP_WORDS[COMP_CWORD]}"
|
||||||
@@ -198,4 +241,4 @@ _backtunnel_complete() {
|
|||||||
|
|
||||||
# Register for both commands
|
# Register for both commands
|
||||||
complete -F _backtunnel_complete backtunnel-share
|
complete -F _backtunnel_complete backtunnel-share
|
||||||
complete -F _backtunnel_complete backtunnel-access
|
complete -F _backtunnel_complete backtunnel-access
|
||||||
@@ -1,5 +1,37 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# Copyright (c) 2025. LUXIM d.o.o., Slovenia - Matjaž Mozetič.
|
# 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 sharer’s local sshd.
|
||||||
|
# - If passwordless auth isn’t 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
|
# backtunnel-access: Mount a folder shared over reverse SSH
|
||||||
# Usage: backtunnel-access /path/to/folder from remoteuser:remotehost [-p PORT] [-m MOUNTPOINT]
|
# Usage: backtunnel-access /path/to/folder from remoteuser:remotehost [-p PORT] [-m MOUNTPOINT]
|
||||||
@@ -15,6 +47,7 @@ usage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# --- parse positional args ---
|
# --- parse positional args ---
|
||||||
|
# Purpose: enforce grammar "<folder> from <remote>" and collect required args.
|
||||||
[[ $# -lt 3 ]] && usage
|
[[ $# -lt 3 ]] && usage
|
||||||
|
|
||||||
FOLDER=$1
|
FOLDER=$1
|
||||||
@@ -25,6 +58,7 @@ shift 3 || true
|
|||||||
[[ "$KEYWORD" != "from" ]] && usage
|
[[ "$KEYWORD" != "from" ]] && usage
|
||||||
|
|
||||||
# Optional flags
|
# Optional flags
|
||||||
|
# Purpose: allow custom tunnel port and mount destination.
|
||||||
while [[ $# -gt 0 ]]; do
|
while [[ $# -gt 0 ]]; do
|
||||||
case "$1" in
|
case "$1" in
|
||||||
-p|--port)
|
-p|--port)
|
||||||
@@ -48,6 +82,7 @@ while [[ $# -gt 0 ]]; do
|
|||||||
done
|
done
|
||||||
|
|
||||||
# --- normalize and prepare mount point ---
|
# --- 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
|
# Expand leading '~' even if quoted or passed via GUI
|
||||||
# Note: default uses $HOME; still expand '~' if passed via CLI/GUI
|
# Note: default uses $HOME; still expand '~' if passed via CLI/GUI
|
||||||
if [[ "${MOUNTPOINT:-}" == "~"* ]]; then
|
if [[ "${MOUNTPOINT:-}" == "~"* ]]; then
|
||||||
@@ -74,6 +109,7 @@ if [[ -n "$(ls -A -- "$MOUNTPOINT" 2>/dev/null || true)" ]]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# --- split remote user/host (supports user:host or user@host) ---
|
# --- split remote user/host (supports user:host or user@host) ---
|
||||||
|
# Purpose: accept flexible remote formats commonly used in SSH tooling.
|
||||||
REMOTE_USER=""
|
REMOTE_USER=""
|
||||||
REMOTE_HOST=""
|
REMOTE_HOST=""
|
||||||
|
|
||||||
@@ -89,6 +125,7 @@ else
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# --- deps check ---
|
# --- 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 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 mountpoint >/dev/null 2>&1 || { echo "mountpoint utility not found."; exit 1; }
|
||||||
|
|
||||||
@@ -103,6 +140,7 @@ fi
|
|||||||
echo "🔗 Mounting '$FOLDER' from '$REMOTE_USER@$REMOTE_HOST' via reverse-tunnel localhost:$PORT → '$MOUNTPOINT' ..."
|
echo "🔗 Mounting '$FOLDER' from '$REMOTE_USER@$REMOTE_HOST' via reverse-tunnel localhost:$PORT → '$MOUNTPOINT' ..."
|
||||||
|
|
||||||
# --- ensure passwordless auth via tunnel (optional but user-friendly) ---
|
# --- 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=()
|
SSH_IDENTITY_OPTS=()
|
||||||
if [[ -f "$HOME/.ssh/id_ed25519_backtunnel" ]]; then
|
if [[ -f "$HOME/.ssh/id_ed25519_backtunnel" ]]; then
|
||||||
SSH_IDENTITY_OPTS+=( -o IdentityFile="$HOME/.ssh/id_ed25519_backtunnel" -o IdentitiesOnly=yes )
|
SSH_IDENTITY_OPTS+=( -o IdentityFile="$HOME/.ssh/id_ed25519_backtunnel" -o IdentitiesOnly=yes )
|
||||||
@@ -125,12 +163,14 @@ fi
|
|||||||
|
|
||||||
echo "Checking remote path visibility via SFTP ..."
|
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=accept-new "${SFTP_ID_OPTS[@]}" \
|
if ! sftp -q -P "$PORT" -o StrictHostKeyChecking=accept-new "${SFTP_ID_OPTS[@]}" \
|
||||||
"$REMOTE_USER@localhost" <<< "ls -1 \"$FOLDER\"" >/dev/null 2>&1; then
|
"$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 "⚠️ 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
|
echo " Proceeding to mount; sshfs may fail if the path is invalid." >&2
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Build ssh command used by sshfs (adds keepalive/connect-timeout, identity if present).
|
||||||
SSH_CMD="ssh -o ConnectTimeout=10 -o StrictHostKeyChecking=accept-new"
|
SSH_CMD="ssh -o ConnectTimeout=10 -o StrictHostKeyChecking=accept-new"
|
||||||
# If identity options are present, append them to SSH_CMD
|
# If identity options are present, append them to SSH_CMD
|
||||||
if [[ ${#SSH_IDENTITY_OPTS[@]} -gt 0 ]]; then
|
if [[ ${#SSH_IDENTITY_OPTS[@]} -gt 0 ]]; then
|
||||||
@@ -140,6 +180,7 @@ if [[ ${#SSH_IDENTITY_OPTS[@]} -gt 0 ]]; then
|
|||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Perform the mount with reconnect/keepalive options for resilience.
|
||||||
sshfs \
|
sshfs \
|
||||||
-p "$PORT" \
|
-p "$PORT" \
|
||||||
-o reconnect \
|
-o reconnect \
|
||||||
|
|||||||
@@ -1,6 +1,36 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
# Copyright (c) 2025. LUXIM d.o.o., Slovenia - Matjaž Mozetič.
|
# Copyright (c) 2025 LUXIM d.o.o., Slovenia
|
||||||
|
# Author: Matjaž Mozetič
|
||||||
|
#
|
||||||
|
# Name: backtunnel-access-gui
|
||||||
|
# Summary: KDE/GUI wrapper to mount a BackTunnel share with dialogs and logging.
|
||||||
|
# Description:
|
||||||
|
# GUI front-end that prompts for remote, tunnel port, and mount point using kdialog,
|
||||||
|
# then launches backtunnel-access in a terminal (Konsole/xterm) to perform the mount.
|
||||||
|
# Prefills fields from BackTunnel profiles.ini when available (read-only, best-effort).
|
||||||
|
# All output is logged to /tmp/backtunnel-access-gui.<uid>.log for troubleshooting.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# backtunnel-access-gui <selected-folder>
|
||||||
|
#
|
||||||
|
# Examples:
|
||||||
|
# backtunnel-access-gui ~/Downloads
|
||||||
|
#
|
||||||
|
# Dependencies:
|
||||||
|
# - bash
|
||||||
|
# - kdialog (for GUI prompts)
|
||||||
|
# - konsole or xterm (preferred terminals; falls back to background run if missing)
|
||||||
|
# - awk (for simple INI parsing), tee
|
||||||
|
# - backtunnel-access (invoked to perform the actual mount)
|
||||||
|
#
|
||||||
|
# Exit codes:
|
||||||
|
# 0 command launched (terminal or background)
|
||||||
|
# 1 invalid usage or no folder selected
|
||||||
|
#
|
||||||
|
# Notes:
|
||||||
|
# - Profiles are used only to prefill fields (access side does not support @profile directly).
|
||||||
|
# - If no terminal emulator is available, runs in background and shows a message with the log path.
|
||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,33 @@
|
|||||||
#!/usr/bin/env bash
|
#!/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-tui
|
||||||
|
# Summary: Minimal TUI to mount a BackTunnel share into a chosen directory.
|
||||||
|
# Description:
|
||||||
|
# Interactive, terminal-based helper that asks for remote, tunnel port, and mount point,
|
||||||
|
# then invokes backtunnel-access to perform the actual sshfs mount. Defaults to mounting
|
||||||
|
# into the selected directory; if non-empty, proposes a "backtunnel" subdirectory.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# backtunnel-access-tui <selected-dir>
|
||||||
|
#
|
||||||
|
# Examples:
|
||||||
|
# backtunnel-access-tui ~/Downloads
|
||||||
|
#
|
||||||
|
# Dependencies:
|
||||||
|
# - bash
|
||||||
|
# - backtunnel-access (invoked via exec at the end)
|
||||||
|
#
|
||||||
|
# Exit codes:
|
||||||
|
# 0 success (exec replaces the shell on success)
|
||||||
|
# 1 invalid usage or mountpoint not writable
|
||||||
|
#
|
||||||
|
# Notes:
|
||||||
|
# - Expands leading "~" in the chosen mount point.
|
||||||
|
# - This tool only gathers inputs; mounting is performed by backtunnel-access.
|
||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
SEL_DIR="${1:-}"
|
SEL_DIR="${1:-}"
|
||||||
[[ -n "$SEL_DIR" ]] || { echo "Usage: backtunnel-access-tui <selected-dir>"; exit 1; }
|
[[ -n "$SEL_DIR" ]] || { echo "Usage: backtunnel-access-tui <selected-dir>"; exit 1; }
|
||||||
@@ -11,6 +40,7 @@ if [[ -d "$SEL_DIR" ]] && [[ -n "$(ls -A -- "$SEL_DIR" 2>/dev/null || true)" ]];
|
|||||||
DEFAULT_MP="$SEL_DIR/backtunnel"
|
DEFAULT_MP="$SEL_DIR/backtunnel"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# ---- Interactive prompts (with sensible defaults) ----
|
||||||
read -r -p "Remote (user@host or user:host) [user@vps.example.com]: " REMOTE
|
read -r -p "Remote (user@host or user:host) [user@vps.example.com]: " REMOTE
|
||||||
REMOTE="${REMOTE:-user@vps.example.com}"
|
REMOTE="${REMOTE:-user@vps.example.com}"
|
||||||
|
|
||||||
@@ -34,4 +64,5 @@ fi
|
|||||||
echo
|
echo
|
||||||
echo "Running: backtunnel-access '<remote-folder>' from '$REMOTE' -p '$PORT' -m '$MP'"
|
echo "Running: backtunnel-access '<remote-folder>' from '$REMOTE' -p '$PORT' -m '$MP'"
|
||||||
echo "Note: you'll be prompted on the remote for the exact folder (as per your workflow)."
|
echo "Note: you'll be prompted on the remote for the exact folder (as per your workflow)."
|
||||||
|
# Replace this process with backtunnel-access (no return to this script after exec)
|
||||||
exec backtunnel-access "$MP" from "$REMOTE" -p "$PORT" -m "$MP"
|
exec backtunnel-access "$MP" from "$REMOTE" -p "$PORT" -m "$MP"
|
||||||
|
|||||||
@@ -1,4 +1,39 @@
|
|||||||
#!/usr/bin/env bash
|
#!/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-auth-setup
|
||||||
|
# Summary: Initialize a tunnel-only, SFTP-only SSH key for BackTunnel access via reverse tunnel.
|
||||||
|
# Description:
|
||||||
|
# Generates (if missing) a dedicated SSH key (~/.ssh/id_ed25519_backtunnel) and installs its public
|
||||||
|
# key on the remote account’s authorized_keys, restricted to:
|
||||||
|
# from="127.0.0.1",command="internal-sftp",restrict
|
||||||
|
# This makes the key usable only through the reverse tunnel (localhost on the remote) and only for SFTP,
|
||||||
|
# not for shell or port-forwarding.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# backtunnel-auth-setup [-p PORT] user@localhost
|
||||||
|
#
|
||||||
|
# Examples:
|
||||||
|
# backtunnel-auth-setup alice@localhost
|
||||||
|
# backtunnel-auth-setup -p 4422 alice@localhost
|
||||||
|
#
|
||||||
|
# Dependencies:
|
||||||
|
# - bash
|
||||||
|
# - ssh, ssh-keygen
|
||||||
|
#
|
||||||
|
# Exit codes:
|
||||||
|
# 0 success (key exists/created, restricted entry ensured)
|
||||||
|
# 1 invalid usage (missing destination) or other failures
|
||||||
|
#
|
||||||
|
# Security:
|
||||||
|
# - The installed authorized_keys entry is tightly scoped to 127.0.0.1 and internal-sftp with restrict.
|
||||||
|
# - Remote authorized_keys is created with 600 permissions; umask 077 enforced during remote script.
|
||||||
|
#
|
||||||
|
# Notes:
|
||||||
|
# - Idempotent: re-running won’t duplicate the restricted line if it is already present.
|
||||||
|
|
||||||
# Initialize tunnel-only SSH auth for BackTunnel (Option A)
|
# Initialize tunnel-only SSH auth for BackTunnel (Option A)
|
||||||
# Usage: backtunnel-auth-setup [-p PORT] user@localhost
|
# Usage: backtunnel-auth-setup [-p PORT] user@localhost
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
@@ -28,7 +63,7 @@ fi
|
|||||||
|
|
||||||
# 2) Append restricted key only (idempotent): tunnel-only + SFTP-only
|
# 2) Append restricted key only (idempotent): tunnel-only + SFTP-only
|
||||||
echo "Installing restricted key (tunnel-only, SFTP-only) via port $PORT ..."
|
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")"
|
RESTRICTED_LINE="$(printf 'from=\"127.0.0.1\",command=\"internal-sftp\",restrict '; cat "$PUB")"
|
||||||
ssh -p "$PORT" "$DEST" bash -lc '
|
ssh -p "$PORT" "$DEST" bash -lc '
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
umask 077
|
umask 077
|
||||||
|
|||||||
@@ -1,9 +1,45 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -euo pipefail
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
# Copyright (c) 2025 LUXIM d.o.o., Slovenia
|
||||||
|
# Author: Matjaž Mozetič
|
||||||
|
#
|
||||||
|
# Name: backtunnel-authorize
|
||||||
|
# Summary: Register a named public key for later use by other tools (e.g., to grant temporary access).
|
||||||
|
# Description:
|
||||||
|
# Copies a provided OpenSSH public key file into the per-user BackTunnel authorized store
|
||||||
|
# under a chosen name. Other scripts can later reference this key by --allow-known <name>.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# backtunnel-authorize <name> <pubkey-file>
|
||||||
|
#
|
||||||
|
# Examples:
|
||||||
|
# backtunnel-authorize alice ~/.ssh/alice_ed25519.pub
|
||||||
|
#
|
||||||
|
# Dependencies:
|
||||||
|
# - bash
|
||||||
|
# - install (coreutils or compatible)
|
||||||
|
#
|
||||||
|
# Exit codes:
|
||||||
|
# 0 success
|
||||||
|
# 1 invalid usage or file not found
|
||||||
|
#
|
||||||
|
# Notes:
|
||||||
|
# - Keys are stored at: ${XDG_CONFIG_HOME:-$HOME/.config}/backtunnel/authorized/<name>.pub
|
||||||
|
# - Existing file with the same name will be overwritten (install default behavior).
|
||||||
|
|
||||||
|
set -euo pipefail # Fail on error, undefined vars, and pipeline errors
|
||||||
|
|
||||||
|
# ---- Parse & validate arguments ----
|
||||||
name="${1:-}"
|
name="${1:-}"
|
||||||
file="${2:-}"
|
file="${2:-}"
|
||||||
[[ -n "$name" && -n "$file" && -f "$file" ]] || { echo "Usage: backtunnel-authorize <name> <pubkey-file>"; exit 1; }
|
[[ -n "$name" && -n "$file" && -f "$file" ]] || { echo "Usage: backtunnel-authorize <name> <pubkey-file>"; exit 1; }
|
||||||
|
|
||||||
|
# ---- Destination directory (XDG-compliant) ----
|
||||||
dir="${XDG_CONFIG_HOME:-$HOME/.config}/backtunnel/authorized"
|
dir="${XDG_CONFIG_HOME:-$HOME/.config}/backtunnel/authorized"
|
||||||
mkdir -p "$dir"
|
mkdir -p "$dir" # Ensure the store exists
|
||||||
|
|
||||||
|
# ---- Install the key with sane permissions (rw-r--r--) ----
|
||||||
install -m 644 "$file" "$dir/$name.pub"
|
install -m 644 "$file" "$dir/$name.pub"
|
||||||
|
|
||||||
|
# ---- Confirmation ----
|
||||||
echo "Saved: $dir/$name.pub"
|
echo "Saved: $dir/$name.pub"
|
||||||
|
|||||||
@@ -1,8 +1,34 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# backtunnel-keys: manage accessor-side keys
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
# Copyright (c) 2025 LUXIM d.o.o., Slovenia
|
||||||
|
# Author: Matjaž Mozetič
|
||||||
|
#
|
||||||
|
# Name: backtunnel-keys
|
||||||
|
# Summary: Manage the accessor-side BackTunnel SSH key pair.
|
||||||
|
# Description:
|
||||||
|
# Provides simple operations for the dedicated BackTunnel SSH key (~/.ssh/id_ed25519_backtunnel):
|
||||||
|
# - print: output the public key to stdout (generates key pair if missing)
|
||||||
|
# - path : show filesystem paths for the private/public key
|
||||||
|
#
|
||||||
# Usage:
|
# Usage:
|
||||||
# backtunnel-keys print # print (and generate if missing) the public key
|
# backtunnel-keys print # print (and generate if missing) the public key
|
||||||
# backtunnel-keys path # print the private/public key paths
|
# backtunnel-keys path # print the private/public key paths
|
||||||
|
#
|
||||||
|
# Examples:
|
||||||
|
# backtunnel-keys print > /tmp/accessor.pub
|
||||||
|
# backtunnel-keys path
|
||||||
|
#
|
||||||
|
# Dependencies:
|
||||||
|
# - bash
|
||||||
|
# - ssh-keygen (for key generation on first use)
|
||||||
|
#
|
||||||
|
# Exit codes:
|
||||||
|
# 0 success
|
||||||
|
# 1 invalid usage, missing public key, or other error
|
||||||
|
#
|
||||||
|
# Notes:
|
||||||
|
# - The key is generated with no passphrase for non-interactive usage by BackTunnel.
|
||||||
|
# - Public key is printed to stdout for easy piping/redirection.
|
||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
@@ -12,6 +38,7 @@ PUB="$KEY.pub"
|
|||||||
cmd="${1:-print}"
|
cmd="${1:-print}"
|
||||||
|
|
||||||
case "$cmd" in
|
case "$cmd" in
|
||||||
|
# print: ensure the key exists, then print the public key
|
||||||
print)
|
print)
|
||||||
if [[ ! -f "$KEY" ]]; then
|
if [[ ! -f "$KEY" ]]; then
|
||||||
ssh-keygen -t ed25519 -f "$KEY" -N "" -C "backtunnel" >/dev/null
|
ssh-keygen -t ed25519 -f "$KEY" -N "" -C "backtunnel" >/dev/null
|
||||||
@@ -21,6 +48,7 @@ case "$cmd" in
|
|||||||
fi
|
fi
|
||||||
cat "$PUB"
|
cat "$PUB"
|
||||||
;;
|
;;
|
||||||
|
# path: show private/public key locations
|
||||||
path)
|
path)
|
||||||
echo "private: $KEY"
|
echo "private: $KEY"
|
||||||
echo "public : $PUB"
|
echo "public : $PUB"
|
||||||
|
|||||||
@@ -1,13 +1,44 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# Open a command in the user's available terminal emulator, with logging.
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
# Usage: backtunnel-open-term <cmd> [args...]
|
# Copyright (c) 2025 LUXIM d.o.o., Slovenia
|
||||||
|
# Author: Matjaž Mozetič
|
||||||
|
#
|
||||||
|
# Name: backtunnel-open-term
|
||||||
|
# Summary: Open a command in the user's available terminal emulator, with logging and simple rotation.
|
||||||
|
# Description:
|
||||||
|
# Detects an installed terminal emulator (preferring Konsole on KDE sessions), opens it,
|
||||||
|
# and executes the provided command with arguments. Logs session metadata and output to
|
||||||
|
# ${XDG_STATE_HOME:-$HOME/.local/state}/backtunnel/servicemenu.<timestamp>.log, keeping the
|
||||||
|
# last 10 logs with a portable rotation routine.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# backtunnel-open-term <cmd> [args...]
|
||||||
|
#
|
||||||
|
# Examples:
|
||||||
|
# backtunnel-open-term backtunnel-access-tui "/path/to/dir"
|
||||||
|
# backtunnel-open-term bash -lc 'echo Hello'
|
||||||
|
#
|
||||||
|
# Dependencies:
|
||||||
|
# - bash
|
||||||
|
# - A terminal emulator (one of: konsole, kitty, alacritty, gnome-terminal, kgx, tilix, xfce4-terminal, xterm)
|
||||||
|
# - stat, sort, cut, date (coreutils-compatible)
|
||||||
|
#
|
||||||
|
# Exit codes:
|
||||||
|
# 0 started successfully (or backgrounded if no terminal is available)
|
||||||
|
# 1+ invalid usage or failures during setup/execution
|
||||||
|
#
|
||||||
|
# Notes:
|
||||||
|
# - If no terminal emulator is found, the command is started in the background via nohup
|
||||||
|
# and the log path is printed.
|
||||||
|
# - Uses exec for supported terminals to replace the current process; otherwise prints info then exits.
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
# ... existing code ...
|
||||||
LOG_DIR="${XDG_STATE_HOME:-$HOME/.local/state}/backtunnel"
|
LOG_DIR="${XDG_STATE_HOME:-$HOME/.local/state}/backtunnel"
|
||||||
[[ -d "$LOG_DIR" ]] || mkdir -p "$LOG_DIR"
|
[[ -d "$LOG_DIR" ]] || mkdir -p "$LOG_DIR"
|
||||||
LOG_FILE="${LOG_DIR}/servicemenu.$(date +%Y%m%d-%H%M%S).log"
|
LOG_FILE="${LOG_DIR}/servicemenu.$(date +%Y%m%d-%H%M%S).log"
|
||||||
|
|
||||||
# Simple rotation: keep the last 10 files (portable, avoids SC2207 and xargs -r)
|
# Simple rotation: keep the last 10 files (portable, avoids SC2207 and xargs -r)
|
||||||
|
# Purpose: remove older log files while remaining compatible across GNU/BSD userlands.
|
||||||
shopt -s nullglob
|
shopt -s nullglob
|
||||||
logs=( "$LOG_DIR"/servicemenu.*.log )
|
logs=( "$LOG_DIR"/servicemenu.*.log )
|
||||||
if (( ${#logs[@]} > 10 )); then
|
if (( ${#logs[@]} > 10 )); then
|
||||||
@@ -40,7 +71,9 @@ cmd=( "$@" )
|
|||||||
echo "Env: BACKTUNNEL_DEBUG=${BACKTUNNEL_DEBUG:-} SHELL=${SHELL:-} DISPLAY=${DISPLAY:-}"
|
echo "Env: BACKTUNNEL_DEBUG=${BACKTUNNEL_DEBUG:-} SHELL=${SHELL:-} DISPLAY=${DISPLAY:-}"
|
||||||
echo
|
echo
|
||||||
|
|
||||||
# Prefer Konsole on KDE sessions; otherwise probe common terminals
|
# detect_term: choose a terminal emulator to launch the command
|
||||||
|
# Output: prints the chosen terminal name or empty string if none found.
|
||||||
|
# Prefers Konsole when KDE session is detected.
|
||||||
detect_term() {
|
detect_term() {
|
||||||
if [[ -n "${KDE_FULL_SESSION:-}" ]] && command -v konsole >/dev/null 2>&1; then
|
if [[ -n "${KDE_FULL_SESSION:-}" ]] && command -v konsole >/dev/null 2>&1; then
|
||||||
echo "konsole"; return
|
echo "konsole"; return
|
||||||
@@ -50,7 +83,7 @@ cmd=( "$@" )
|
|||||||
done
|
done
|
||||||
echo "" # none
|
echo "" # none
|
||||||
}
|
}
|
||||||
|
# ... existing code ...
|
||||||
term="$(detect_term)"
|
term="$(detect_term)"
|
||||||
echo "Chosen terminal: ${term:-<none>}"; echo
|
echo "Chosen terminal: ${term:-<none>}"; echo
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,59 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# Copyright (c) 2025. LUXIM d.o.o., Slovenia - Matjaž Mozetič.
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
# Licensed under the GNU GPL v3.0
|
# Copyright (c) 2025 LUXIM d.o.o., Slovenia
|
||||||
|
# Author: Matjaž Mozetič
|
||||||
#
|
#
|
||||||
|
# Name: backtunnel-share
|
||||||
|
# Summary: Time-bounded reverse-SSH tunnel to expose the local SSH service for remote, temporary SFTP-based access.
|
||||||
|
# Description:
|
||||||
|
# Sets up a reverse SSH tunnel (ssh -R) from a remote host back to the local sshd, for a fixed duration.
|
||||||
|
# Prints an optional “invite” command that the remote side can run to mount a given local folder over SFTP
|
||||||
|
# using a companion tool. Optionally and temporarily adds a restricted public key entry to authorized_keys,
|
||||||
|
# scoped to localhost and internal-sftp only, and removes it on exit.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# backtunnel-share /path/to/folder with remoteuser:remotehost for <duration> [options]
|
||||||
|
#
|
||||||
|
# Examples:
|
||||||
|
# backtunnel-share ~/projects with alice:vps.example.com for 2h
|
||||||
|
# backtunnel-share ~/projects with alice@vps.example.com for 1d -p 4422 -l 2222
|
||||||
|
# backtunnel-share ~/projects with @work for 2h -i --qr --allow-known alice
|
||||||
|
#
|
||||||
|
# Dependencies:
|
||||||
|
# - bash >= 4.x
|
||||||
|
# - ssh, timeout, tail
|
||||||
|
# - optional: qrencode (for --qr)
|
||||||
|
#
|
||||||
|
# Configuration:
|
||||||
|
# Profiles file precedence (high → low):
|
||||||
|
# - ${XDG_CONFIG_HOME:-$HOME/.config}/backtunnel/profiles.ini
|
||||||
|
# - /etc/backtunnel/profiles.ini
|
||||||
|
# - /usr/share/backtunnel/profiles.ini
|
||||||
|
# Sections:
|
||||||
|
# - [default] global defaults
|
||||||
|
# - [name] referenced via @name → expands to user@host and overrides select defaults
|
||||||
|
#
|
||||||
|
# Exit codes:
|
||||||
|
# 0 success (including duration reached)
|
||||||
|
# 1 invalid usage/arguments or validation failure
|
||||||
|
# 124 timeout reached (handled and normalized to 0 when expected)
|
||||||
|
# 2+ other runtime errors (from commands like ssh/timeout/tail)
|
||||||
|
#
|
||||||
|
# Security:
|
||||||
|
# - Reverse tunnel binds on remote loopback only (127.0.0.1:PORT).
|
||||||
|
# - Temporary authorized_keys entries (if enabled) are restricted with:
|
||||||
|
# from="127.0.0.1",command="internal-sftp",restrict
|
||||||
|
# and are removed on exit via trap.
|
||||||
|
# - ~/.ssh perms are enforced (700 dir, 600 authorized_keys).
|
||||||
|
#
|
||||||
|
# Portability/Assumptions:
|
||||||
|
# - Uses bash arrays, [[ ]] tests, and bash-specific parameter expansions.
|
||||||
|
# - Uses awk for INI parsing (best-effort).
|
||||||
|
#
|
||||||
|
# Notes:
|
||||||
|
# - set -euo pipefail: stop on error/undefined vars; treat pipeline errors as failures.
|
||||||
|
# - Traps ensure SSH child is terminated and temporary keys are cleaned up.
|
||||||
|
|
||||||
# backtunnel-share: Share a folder using reverse SSH for a limited duration
|
# backtunnel-share: Share a folder using reverse SSH for a limited duration
|
||||||
# Syntax:
|
# Syntax:
|
||||||
# backtunnel-share /path/to/folder with remoteuser:remotehost for 2h [options]
|
# backtunnel-share /path/to/folder with remoteuser:remotehost for 2h [options]
|
||||||
@@ -25,6 +77,7 @@ set -euo pipefail
|
|||||||
|
|
||||||
# ----------------------------
|
# ----------------------------
|
||||||
# Config discovery
|
# Config discovery
|
||||||
|
# Purpose: choose the highest-precedence profiles.ini available.
|
||||||
# ----------------------------
|
# ----------------------------
|
||||||
CONFIG_USER="${XDG_CONFIG_HOME:-$HOME/.config}/backtunnel/profiles.ini"
|
CONFIG_USER="${XDG_CONFIG_HOME:-$HOME/.config}/backtunnel/profiles.ini"
|
||||||
CONFIG_SYS="/etc/backtunnel/profiles.ini"
|
CONFIG_SYS="/etc/backtunnel/profiles.ini"
|
||||||
@@ -41,6 +94,16 @@ fi
|
|||||||
# INI helpers
|
# INI helpers
|
||||||
# ----------------------------
|
# ----------------------------
|
||||||
# shellcheck disable=SC2317
|
# shellcheck disable=SC2317
|
||||||
|
# ini_get: read a value from CONFIG_FILE INI [SECTION] key=value
|
||||||
|
# Arguments:
|
||||||
|
# $1: section name
|
||||||
|
# $2: key
|
||||||
|
# Env:
|
||||||
|
# CONFIG_FILE (read)
|
||||||
|
# Returns:
|
||||||
|
# prints the value on success; empty string if not found
|
||||||
|
# Notes:
|
||||||
|
# - Best-effort parsing; trims spaces around '=' and the value.
|
||||||
ini_get() { # ini_get SECTION KEY -> value
|
ini_get() { # ini_get SECTION KEY -> value
|
||||||
local sec="$1" key="$2"
|
local sec="$1" key="$2"
|
||||||
awk -v s="[""$sec""]" -v k="$key" '
|
awk -v s="[""$sec""]" -v k="$key" '
|
||||||
@@ -60,6 +123,11 @@ ini_get() { # ini_get SECTION KEY -> value
|
|||||||
}
|
}
|
||||||
|
|
||||||
# shellcheck disable=SC2317
|
# shellcheck disable=SC2317
|
||||||
|
# profile_expand_remote: expand @name -> user@host based on profiles.ini, else pass through.
|
||||||
|
# Arguments:
|
||||||
|
# $1: input remote spec (e.g., @work or user@host or user:host)
|
||||||
|
# Returns:
|
||||||
|
# prints expanded remote (user@host) if @name is resolvable; otherwise returns input unchanged
|
||||||
profile_expand_remote() { # "@name" -> user@host, otherwise pass through
|
profile_expand_remote() { # "@name" -> user@host, otherwise pass through
|
||||||
local in="$1"
|
local in="$1"
|
||||||
if [[ "$in" == @* ]]; then
|
if [[ "$in" == @* ]]; then
|
||||||
@@ -75,6 +143,13 @@ profile_expand_remote() { # "@name" -> user@host, otherwise pass through
|
|||||||
}
|
}
|
||||||
|
|
||||||
# shellcheck disable=SC2317
|
# shellcheck disable=SC2317
|
||||||
|
# profile_apply_defaults: set global defaults from [default], then override from named profile.
|
||||||
|
# Arguments:
|
||||||
|
# $1: profile name (without '@'), may be empty
|
||||||
|
# Side effects:
|
||||||
|
# - Modifies global variables: TUNNEL_PORT, LOCAL_SSH_PORT, INVITE_MOUNT, INVITE, QR, DURATION
|
||||||
|
# Notes:
|
||||||
|
# - Only applies default values if globals still hold built-in defaults.
|
||||||
profile_apply_defaults() { # set globals if unset; named overrides default
|
profile_apply_defaults() { # set globals if unset; named overrides default
|
||||||
local name="$1" v
|
local name="$1" v
|
||||||
# defaults
|
# defaults
|
||||||
@@ -96,6 +171,7 @@ profile_apply_defaults() { # set globals if unset; named overrides default
|
|||||||
|
|
||||||
# ----------------------------
|
# ----------------------------
|
||||||
# Defaults
|
# Defaults
|
||||||
|
# Purpose: initialize built-in defaults before applying profiles and flags.
|
||||||
# ----------------------------
|
# ----------------------------
|
||||||
TUNNEL_PORT=2222 # remote-side port exposed via -R
|
TUNNEL_PORT=2222 # remote-side port exposed via -R
|
||||||
LOCAL_SSH_PORT=22 # local sshd port to forward to
|
LOCAL_SSH_PORT=22 # local sshd port to forward to
|
||||||
@@ -147,6 +223,7 @@ EOF
|
|||||||
|
|
||||||
# ----------------------------
|
# ----------------------------
|
||||||
# Positional parsing
|
# Positional parsing
|
||||||
|
# Purpose: enforce command grammar and capture the five required positionals.
|
||||||
# ----------------------------
|
# ----------------------------
|
||||||
[[ $# -lt 5 ]] && usage
|
[[ $# -lt 5 ]] && usage
|
||||||
|
|
||||||
@@ -171,6 +248,7 @@ REMOTE="$(profile_expand_remote "$REMOTE")"
|
|||||||
|
|
||||||
# ----------------------------
|
# ----------------------------
|
||||||
# Optional flags
|
# Optional flags
|
||||||
|
# Purpose: parse options and override derived defaults.
|
||||||
# ----------------------------
|
# ----------------------------
|
||||||
while [[ $# -gt 0 ]]; do
|
while [[ $# -gt 0 ]]; do
|
||||||
case "$1" in
|
case "$1" in
|
||||||
@@ -224,6 +302,7 @@ done
|
|||||||
|
|
||||||
# ----------------------------
|
# ----------------------------
|
||||||
# Duration validation
|
# Duration validation
|
||||||
|
# Purpose: accept forms like 30m, 2h, 1d (s/m/h/d).
|
||||||
# ----------------------------
|
# ----------------------------
|
||||||
if [[ ! "$DURATION" =~ ^[0-9]+[smhd]$ ]]; then
|
if [[ ! "$DURATION" =~ ^[0-9]+[smhd]$ ]]; then
|
||||||
echo "Invalid duration '$DURATION' (use forms like 30m, 2h, 1d)." >&2
|
echo "Invalid duration '$DURATION' (use forms like 30m, 2h, 1d)." >&2
|
||||||
@@ -232,6 +311,7 @@ fi
|
|||||||
|
|
||||||
# ----------------------------
|
# ----------------------------
|
||||||
# Split remote user/host
|
# Split remote user/host
|
||||||
|
# Purpose: support user:host or user@host; reject anything else (except @profile pre-expanded).
|
||||||
# ----------------------------
|
# ----------------------------
|
||||||
REMOTE_USER="" REMOTE_HOST=""
|
REMOTE_USER="" REMOTE_HOST=""
|
||||||
if [[ "$REMOTE" == *:* ]]; then
|
if [[ "$REMOTE" == *:* ]]; then
|
||||||
@@ -247,6 +327,7 @@ fi
|
|||||||
|
|
||||||
# ----------------------------
|
# ----------------------------
|
||||||
# Deps check
|
# Deps check
|
||||||
|
# Purpose: fail fast if mandatory tools are missing.
|
||||||
# ----------------------------
|
# ----------------------------
|
||||||
command -v ssh >/dev/null 2>&1 || { echo "ssh not found."; exit 1; }
|
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; }
|
command -v timeout >/dev/null 2>&1 || { echo "timeout not found."; exit 1; }
|
||||||
@@ -254,7 +335,13 @@ command -v tail >/dev/null 2>&1 || { echo "tail not found."; exit 1; }
|
|||||||
|
|
||||||
# ----------------------------
|
# ----------------------------
|
||||||
# Accessor temporary authorization
|
# Accessor temporary authorization
|
||||||
|
# Purpose: optionally add and later remove a restricted authorized_keys entry.
|
||||||
# ----------------------------
|
# ----------------------------
|
||||||
|
# restrict_key_line: produce a hardened authorized_keys line for an OpenSSH public key.
|
||||||
|
# Arguments:
|
||||||
|
# $1: public key text (one line, ssh-*)
|
||||||
|
# Returns:
|
||||||
|
# prints a restricted options prefix + key; nonzero if key is invalid
|
||||||
restrict_key_line() { # usage: restrict_key_line <pubkey_text>
|
restrict_key_line() { # usage: restrict_key_line <pubkey_text>
|
||||||
local pk="$1"
|
local pk="$1"
|
||||||
# Normalize
|
# Normalize
|
||||||
@@ -267,6 +354,12 @@ restrict_key_line() { # usage: restrict_key_line <pubkey_text>
|
|||||||
printf 'from="127.0.0.1",command="internal-sftp",restrict %s' "$pk"
|
printf 'from="127.0.0.1",command="internal-sftp",restrict %s' "$pk"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# add_temp_authorized_key: append a uniquely marked restricted key block to ~/.ssh/authorized_keys.
|
||||||
|
# Arguments:
|
||||||
|
# $1: public key text
|
||||||
|
# Side effects:
|
||||||
|
# - Ensures ~/.ssh perms (700) and authorized_keys perms (600)
|
||||||
|
# - Writes marker lines and sets ADDED_MARKER_ID for later removal
|
||||||
add_temp_authorized_key() {
|
add_temp_authorized_key() {
|
||||||
local pubkey_text="$1"
|
local pubkey_text="$1"
|
||||||
local ak="$HOME/.ssh/authorized_keys"
|
local ak="$HOME/.ssh/authorized_keys"
|
||||||
@@ -295,6 +388,11 @@ add_temp_authorized_key() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# shellcheck disable=SC2317
|
# shellcheck disable=SC2317
|
||||||
|
# remove_temp_authorized_key: delete the previously added marked block from authorized_keys.
|
||||||
|
# Env:
|
||||||
|
# ADDED_MARKER_ID (read)
|
||||||
|
# Notes:
|
||||||
|
# No-op if no marker or authorized_keys missing. Best-effort; ignores errors.
|
||||||
remove_temp_authorized_key() {
|
remove_temp_authorized_key() {
|
||||||
local ak="$HOME/.ssh/authorized_keys"
|
local ak="$HOME/.ssh/authorized_keys"
|
||||||
[[ -z "${ADDED_MARKER_ID:-}" ]] && return 0
|
[[ -z "${ADDED_MARKER_ID:-}" ]] && return 0
|
||||||
@@ -333,6 +431,7 @@ fi
|
|||||||
|
|
||||||
# ----------------------------
|
# ----------------------------
|
||||||
# Banner
|
# Banner
|
||||||
|
# Purpose: inform the user what will happen and where to connect from the remote.
|
||||||
# ----------------------------
|
# ----------------------------
|
||||||
echo "⏳ Sharing '${FOLDER}' via reverse SSH:"
|
echo "⏳ Sharing '${FOLDER}' via reverse SSH:"
|
||||||
echo " local sshd port : ${LOCAL_SSH_PORT}"
|
echo " local sshd port : ${LOCAL_SSH_PORT}"
|
||||||
@@ -346,7 +445,9 @@ echo
|
|||||||
|
|
||||||
# ----------------------------
|
# ----------------------------
|
||||||
# Invite (optional)
|
# Invite (optional)
|
||||||
# If accessor key was pre-authorized, we can omit the auth-setup line.
|
# Purpose: print a ready-to-copy command (and QR) for the remote side.
|
||||||
|
# Notes:
|
||||||
|
# If a key was not pre-authorized, prepend an auth-setup step executed over the tunnel.
|
||||||
# ----------------------------
|
# ----------------------------
|
||||||
if $INVITE; then
|
if $INVITE; then
|
||||||
INVITE_CMD="backtunnel-access '${FOLDER}' from ${REMOTE_USER}@${REMOTE_HOST} -p ${TUNNEL_PORT} -m '${INVITE_MOUNT}'"
|
INVITE_CMD="backtunnel-access '${FOLDER}' from ${REMOTE_USER}@${REMOTE_HOST} -p ${TUNNEL_PORT} -m '${INVITE_MOUNT}'"
|
||||||
@@ -409,6 +510,7 @@ echo "To stop sharing early: press Ctrl+C in this window."
|
|||||||
|
|
||||||
# ----------------------------
|
# ----------------------------
|
||||||
# Pre-flight: warn if remote loopback port already in use (best-effort)
|
# Pre-flight: warn if remote loopback port already in use (best-effort)
|
||||||
|
# Purpose: give an actionable warning before attempting the -R bind.
|
||||||
# ----------------------------
|
# ----------------------------
|
||||||
if ssh -o BatchMode=yes -o ConnectTimeout=5 "${REMOTE_USER}@${REMOTE_HOST}" \
|
if ssh -o BatchMode=yes -o ConnectTimeout=5 "${REMOTE_USER}@${REMOTE_HOST}" \
|
||||||
"command -v nc >/dev/null 2>&1 && nc -z 127.0.0.1 ${TUNNEL_PORT}"; then
|
"command -v nc >/dev/null 2>&1 && nc -z 127.0.0.1 ${TUNNEL_PORT}"; then
|
||||||
@@ -418,10 +520,14 @@ fi
|
|||||||
|
|
||||||
# ----------------------------
|
# ----------------------------
|
||||||
# Cleanup & run SSH
|
# Cleanup & run SSH
|
||||||
|
# Purpose: start the tunnel, wait bounded by duration, and guarantee cleanup via trap.
|
||||||
# ----------------------------
|
# ----------------------------
|
||||||
SSH_PID=""
|
SSH_PID=""
|
||||||
|
|
||||||
# shellcheck disable=SC2317
|
# shellcheck disable=SC2317
|
||||||
|
# cleanup: stop the background ssh process and remove any temporary authorized key.
|
||||||
|
# Notes:
|
||||||
|
# - Invoked on INT/TERM/EXIT to ensure resources are released.
|
||||||
cleanup() {
|
cleanup() {
|
||||||
# stop ssh child if running
|
# stop ssh child if running
|
||||||
if [[ -n "${SSH_PID:-}" ]] && kill -0 "$SSH_PID" 2>/dev/null; then
|
if [[ -n "${SSH_PID:-}" ]] && kill -0 "$SSH_PID" 2>/dev/null; then
|
||||||
@@ -444,7 +550,7 @@ ssh -N \
|
|||||||
SSH_PID=$!
|
SSH_PID=$!
|
||||||
|
|
||||||
# Wait for ssh to exit, but bounded by duration:
|
# Wait for ssh to exit, but bounded by duration:
|
||||||
# Use 'timeout … tail --pid=PID -f /dev/null' so we don't need bash -c 'wait "$1"'
|
# Rationale: use 'timeout … tail --pid=PID -f /dev/null' to avoid subshell/wait-loop complexity.
|
||||||
if timeout "$DURATION" tail --pid="$SSH_PID" -f /dev/null; then
|
if timeout "$DURATION" tail --pid="$SSH_PID" -f /dev/null; then
|
||||||
# ssh exited on its own before timeout
|
# ssh exited on its own before timeout
|
||||||
exit 0
|
exit 0
|
||||||
|
|||||||
@@ -1,5 +1,36 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# Copyright (c) 2025. LUXIM d.o.o., Slovenia - Matjaž Mozetič.
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
# Copyright (c) 2025 LUXIM d.o.o., Slovenia
|
||||||
|
# Author: Matjaž Mozetič
|
||||||
|
#
|
||||||
|
# Name: backtunnel-share-gui
|
||||||
|
# Summary: KDE/GUI wrapper to start a BackTunnel share with dialogs and logging.
|
||||||
|
# Description:
|
||||||
|
# GUI front-end that prompts for remote, duration, ports, and invite options using kdialog,
|
||||||
|
# then launches backtunnel-share in a terminal (Konsole/xterm) to create the reverse-SSH share.
|
||||||
|
# Prefills fields from BackTunnel profiles.ini when available (read-only, best-effort).
|
||||||
|
# All output is logged to /tmp/backtunnel-share-gui.<uid>.log for troubleshooting.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# backtunnel-share-gui <folder>
|
||||||
|
#
|
||||||
|
# Examples:
|
||||||
|
# backtunnel-share-gui ~/projects
|
||||||
|
#
|
||||||
|
# Dependencies:
|
||||||
|
# - bash
|
||||||
|
# - kdialog (for GUI prompts)
|
||||||
|
# - konsole or xterm (preferred terminals; falls back to background run if missing)
|
||||||
|
# - awk (for simple INI parsing), tee
|
||||||
|
# - backtunnel-share (invoked to perform the actual sharing)
|
||||||
|
#
|
||||||
|
# Exit codes:
|
||||||
|
# 0 command launched (terminal or background)
|
||||||
|
# 1 invalid usage or no folder selected
|
||||||
|
#
|
||||||
|
# Notes:
|
||||||
|
# - Profiles are used only to prefill fields; @profile is not used directly by this GUI.
|
||||||
|
# - If no terminal emulator is available, runs in background and shows a message with the log path.
|
||||||
|
|
||||||
# GUI wrapper for BackTunnel "Share" action (Dolphin service menu)
|
# GUI wrapper for BackTunnel "Share" action (Dolphin service menu)
|
||||||
# Prompts for parameters via kdialog and launches backtunnel-share in a terminal.
|
# Prompts for parameters via kdialog and launches backtunnel-share in a terminal.
|
||||||
@@ -95,4 +126,4 @@ else
|
|||||||
nohup "${cmd[@]}" >>"$LOG" 2>&1 &
|
nohup "${cmd[@]}" >>"$LOG" 2>&1 &
|
||||||
kdialog --msgbox "Sharing started in background.\nSee log: $LOG"
|
kdialog --msgbox "Sharing started in background.\nSee log: $LOG"
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
@@ -1,8 +1,37 @@
|
|||||||
#!/usr/bin/env bash
|
#!/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-share-tui
|
||||||
|
# Summary: Minimal TUI to start a time-bounded BackTunnel share with optional invite/QR.
|
||||||
|
# Description:
|
||||||
|
# Interactive, terminal-based helper that prompts for remote, duration, ports, and invite options,
|
||||||
|
# then invokes backtunnel-share to create a reverse-SSH tunnel for the selected folder.
|
||||||
|
# Can optionally include a ready-to-copy invite command and QR code for convenience.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# backtunnel-share-tui <folder>
|
||||||
|
#
|
||||||
|
# Examples:
|
||||||
|
# backtunnel-share-tui ~/projects
|
||||||
|
#
|
||||||
|
# Dependencies:
|
||||||
|
# - bash
|
||||||
|
# - backtunnel-share (invoked via exec at the end)
|
||||||
|
#
|
||||||
|
# Exit codes:
|
||||||
|
# 0 success (exec replaces this process on success)
|
||||||
|
# 1 invalid usage
|
||||||
|
#
|
||||||
|
# Notes:
|
||||||
|
# - This tool only collects inputs; the actual sharing is done by backtunnel-share.
|
||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
FOLDER="${1:-}"
|
FOLDER="${1:-}"
|
||||||
[[ -n "$FOLDER" ]] || { echo "Usage: backtunnel-share-tui <folder>"; exit 1; }
|
[[ -n "$FOLDER" ]] || { echo "Usage: backtunnel-share-tui <folder>"; exit 1; }
|
||||||
|
|
||||||
|
# ---- Interactive prompts (with sensible defaults) ----
|
||||||
read -r -p "Remote (user@host or user:host) [user@vps.example.com]: " REMOTE
|
read -r -p "Remote (user@host or user:host) [user@vps.example.com]: " REMOTE
|
||||||
REMOTE="${REMOTE:-user@vps.example.com}"
|
REMOTE="${REMOTE:-user@vps.example.com}"
|
||||||
|
|
||||||
@@ -15,6 +44,7 @@ TPORT="${TPORT:-2222}"
|
|||||||
read -r -p "Local sshd port to expose [22]: " LPORT
|
read -r -p "Local sshd port to expose [22]: " LPORT
|
||||||
LPORT="${LPORT:-22}"
|
LPORT="${LPORT:-22}"
|
||||||
|
|
||||||
|
# Invite toggles
|
||||||
read -r -p "Print invite line for chat? (y/N): " yn
|
read -r -p "Print invite line for chat? (y/N): " yn
|
||||||
INV=; [[ "${yn,,}" == "y" ]] && INV="-i"
|
INV=; [[ "${yn,,}" == "y" ]] && INV="-i"
|
||||||
|
|
||||||
@@ -27,6 +57,8 @@ fi
|
|||||||
read -r -p "Invite: suggested mount point [/mnt/remote-rssh]: " MP
|
read -r -p "Invite: suggested mount point [/mnt/remote-rssh]: " MP
|
||||||
MP="${MP:-/mnt/remote-rssh}"
|
MP="${MP:-/mnt/remote-rssh}"
|
||||||
|
|
||||||
|
# ---- Run the command ----
|
||||||
echo
|
echo
|
||||||
echo "Running: backtunnel-share '$FOLDER' with '$REMOTE' for '$DUR' -p '$TPORT' -l '$LPORT' ${INV:+-i} ${QR:+--qr} --invite-mount '$MP'"
|
echo "Running: backtunnel-share '$FOLDER' with '$REMOTE' for '$DUR' -p '$TPORT' -l '$LPORT' ${INV:+-i} ${QR:+--qr} --invite-mount '$MP'"
|
||||||
|
# Replace this process with backtunnel-share (no return after exec)
|
||||||
exec backtunnel-share "$FOLDER" with "$REMOTE" for "$DUR" -p "$TPORT" -l "$LPORT" ${INV:+-i} ${QR:+--qr} --invite-mount "$MP"
|
exec backtunnel-share "$FOLDER" with "$REMOTE" for "$DUR" -p "$TPORT" -l "$LPORT" ${INV:+-i} ${QR:+--qr} --invite-mount "$MP"
|
||||||
|
|||||||
Reference in New Issue
Block a user