From 0e82955af57925b6df6a1dd8a41b29ea26f6495a Mon Sep 17 00:00:00 2001 From: sysadminmatmoz Date: Sun, 21 Sep 2025 09:45:43 +0200 Subject: [PATCH] Add comprehensive inline metadata documentation to all BackTunnel scripts --- completions/backtunnel.bash | 47 +++++++++++++- scripts/backtunnel-access | 43 ++++++++++++- scripts/backtunnel-access-gui | 34 +++++++++- scripts/backtunnel-access-tui | 31 +++++++++ scripts/backtunnel-auth-setup | 37 ++++++++++- scripts/backtunnel-authorize | 40 +++++++++++- scripts/backtunnel-keys | 30 ++++++++- scripts/backtunnel-open-term | 43 +++++++++++-- scripts/backtunnel-share | 114 ++++++++++++++++++++++++++++++++-- scripts/backtunnel-share-gui | 35 ++++++++++- scripts/backtunnel-share-tui | 32 ++++++++++ 11 files changed, 466 insertions(+), 20 deletions(-) diff --git a/completions/backtunnel.bash b/completions/backtunnel.bash index 2da2d05..b70739b 100644 --- a/completions/backtunnel.bash +++ b/completions/backtunnel.bash @@ -1,4 +1,29 @@ #!/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-share # - backtunnel-access @@ -10,10 +35,12 @@ # - --allow-known completes names from ~/.config/backtunnel/authorized/*.pub # - SSH host completion from known_hosts and ssh config Host entries # - Path completion for first positional and --invite-mount / --mount-point -# - Option-aware value completion (-p/-l suggest typical ports) # ---------- 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() { local u="${XDG_CONFIG_HOME:-$HOME/.config}/backtunnel/profiles.ini" local s="/etc/backtunnel/profiles.ini" @@ -23,6 +50,9 @@ _bt_cfg_files() { [[ -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() { # Output section names excluding [default] local f @@ -35,6 +65,9 @@ _bt_list_profiles() { 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() { # From ~/.config/backtunnel/authorized/*.pub → basename w/o .pub local d="${XDG_CONFIG_HOME:-$HOME/.config}/backtunnel/authorized" @@ -46,6 +79,10 @@ _bt_list_authorized_names() { 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() { # Collect hosts from known_hosts and ~/.ssh/config Host entries (best effort) local out=() @@ -76,11 +113,17 @@ _bt_list_ssh_hosts() { fi } +# Predicates to distinguish which command is being completed _bt_is_backtunnel_share() { [[ ${COMP_WORDS[0]} == backtunnel-share ]]; } _bt_is_backtunnel_access(){ [[ ${COMP_WORDS[0]} == backtunnel-access ]]; } # ---------- 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() { local cur prev cur="${COMP_WORDS[COMP_CWORD]}" @@ -198,4 +241,4 @@ _backtunnel_complete() { # Register for both commands complete -F _backtunnel_complete backtunnel-share -complete -F _backtunnel_complete backtunnel-access +complete -F _backtunnel_complete backtunnel-access \ No newline at end of file diff --git a/scripts/backtunnel-access b/scripts/backtunnel-access index 4269a1f..e9edef5 100644 --- a/scripts/backtunnel-access +++ b/scripts/backtunnel-access @@ -1,5 +1,37 @@ #!/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 # Usage: backtunnel-access /path/to/folder from remoteuser:remotehost [-p PORT] [-m MOUNTPOINT] @@ -15,6 +47,7 @@ usage() { } # --- parse positional args --- +# Purpose: enforce grammar " from " and collect required args. [[ $# -lt 3 ]] && usage FOLDER=$1 @@ -25,6 +58,7 @@ 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) @@ -48,6 +82,7 @@ while [[ $# -gt 0 ]]; do 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 @@ -74,6 +109,7 @@ if [[ -n "$(ls -A -- "$MOUNTPOINT" 2>/dev/null || true)" ]]; then 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="" @@ -89,6 +125,7 @@ else 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; } @@ -103,6 +140,7 @@ 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 ) @@ -125,12 +163,14 @@ 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=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 +# Build ssh command used by sshfs (adds keepalive/connect-timeout, identity if present). 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 @@ -140,6 +180,7 @@ if [[ ${#SSH_IDENTITY_OPTS[@]} -gt 0 ]]; then done fi +# Perform the mount with reconnect/keepalive options for resilience. sshfs \ -p "$PORT" \ -o reconnect \ diff --git a/scripts/backtunnel-access-gui b/scripts/backtunnel-access-gui index 6fa047e..80cec1e 100644 --- a/scripts/backtunnel-access-gui +++ b/scripts/backtunnel-access-gui @@ -1,6 +1,36 @@ #!/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-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..log for troubleshooting. +# +# Usage: +# backtunnel-access-gui +# +# 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 diff --git a/scripts/backtunnel-access-tui b/scripts/backtunnel-access-tui index a8c3116..c0d8276 100644 --- a/scripts/backtunnel-access-tui +++ b/scripts/backtunnel-access-tui @@ -1,4 +1,33 @@ #!/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 +# +# 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 SEL_DIR="${1:-}" [[ -n "$SEL_DIR" ]] || { echo "Usage: backtunnel-access-tui "; exit 1; } @@ -11,6 +40,7 @@ if [[ -d "$SEL_DIR" ]] && [[ -n "$(ls -A -- "$SEL_DIR" 2>/dev/null || true)" ]]; DEFAULT_MP="$SEL_DIR/backtunnel" fi +# ---- Interactive prompts (with sensible defaults) ---- read -r -p "Remote (user@host or user:host) [user@vps.example.com]: " REMOTE REMOTE="${REMOTE:-user@vps.example.com}" @@ -34,4 +64,5 @@ fi echo echo "Running: backtunnel-access '' from '$REMOTE' -p '$PORT' -m '$MP'" 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" diff --git a/scripts/backtunnel-auth-setup b/scripts/backtunnel-auth-setup index 3805ccf..51c1dbe 100644 --- a/scripts/backtunnel-auth-setup +++ b/scripts/backtunnel-auth-setup @@ -1,4 +1,39 @@ #!/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) # Usage: backtunnel-auth-setup [-p PORT] user@localhost set -euo pipefail @@ -28,7 +63,7 @@ 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")" +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 diff --git a/scripts/backtunnel-authorize b/scripts/backtunnel-authorize index 002b6e4..fb4197a 100644 --- a/scripts/backtunnel-authorize +++ b/scripts/backtunnel-authorize @@ -1,9 +1,45 @@ #!/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 . +# +# Usage: +# backtunnel-authorize +# +# 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/.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:-}" file="${2:-}" [[ -n "$name" && -n "$file" && -f "$file" ]] || { echo "Usage: backtunnel-authorize "; exit 1; } + +# ---- Destination directory (XDG-compliant) ---- 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" + +# ---- Confirmation ---- echo "Saved: $dir/$name.pub" diff --git a/scripts/backtunnel-keys b/scripts/backtunnel-keys index 9d7eb66..8339def 100644 --- a/scripts/backtunnel-keys +++ b/scripts/backtunnel-keys @@ -1,8 +1,34 @@ #!/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: # backtunnel-keys print # print (and generate if missing) the public key # 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 @@ -12,6 +38,7 @@ PUB="$KEY.pub" cmd="${1:-print}" case "$cmd" in + # print: ensure the key exists, then print the public key print) if [[ ! -f "$KEY" ]]; then ssh-keygen -t ed25519 -f "$KEY" -N "" -C "backtunnel" >/dev/null @@ -21,6 +48,7 @@ case "$cmd" in fi cat "$PUB" ;; + # path: show private/public key locations path) echo "private: $KEY" echo "public : $PUB" diff --git a/scripts/backtunnel-open-term b/scripts/backtunnel-open-term index 4feddd0..97daa28 100644 --- a/scripts/backtunnel-open-term +++ b/scripts/backtunnel-open-term @@ -1,13 +1,44 @@ #!/usr/bin/env bash -# Open a command in the user's available terminal emulator, with logging. -# Usage: backtunnel-open-term [args...] +# SPDX-License-Identifier: GPL-3.0-or-later +# 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..log, keeping the +# last 10 logs with a portable rotation routine. +# +# Usage: +# backtunnel-open-term [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 - +# ... existing code ... LOG_DIR="${XDG_STATE_HOME:-$HOME/.local/state}/backtunnel" [[ -d "$LOG_DIR" ]] || mkdir -p "$LOG_DIR" 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) +# Purpose: remove older log files while remaining compatible across GNU/BSD userlands. shopt -s nullglob logs=( "$LOG_DIR"/servicemenu.*.log ) if (( ${#logs[@]} > 10 )); then @@ -40,7 +71,9 @@ cmd=( "$@" ) echo "Env: BACKTUNNEL_DEBUG=${BACKTUNNEL_DEBUG:-} SHELL=${SHELL:-} DISPLAY=${DISPLAY:-}" 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() { if [[ -n "${KDE_FULL_SESSION:-}" ]] && command -v konsole >/dev/null 2>&1; then echo "konsole"; return @@ -50,7 +83,7 @@ cmd=( "$@" ) done echo "" # none } - + # ... existing code ... term="$(detect_term)" echo "Chosen terminal: ${term:-}"; echo diff --git a/scripts/backtunnel-share b/scripts/backtunnel-share index dffe4d7..922985c 100644 --- a/scripts/backtunnel-share +++ b/scripts/backtunnel-share @@ -1,7 +1,59 @@ #!/usr/bin/env bash -# Copyright (c) 2025. LUXIM d.o.o., Slovenia - Matjaž Mozetič. -# Licensed under the GNU GPL v3.0 +# SPDX-License-Identifier: GPL-3.0-or-later +# 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 [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 # Syntax: # backtunnel-share /path/to/folder with remoteuser:remotehost for 2h [options] @@ -25,6 +77,7 @@ set -euo pipefail # ---------------------------- # Config discovery +# Purpose: choose the highest-precedence profiles.ini available. # ---------------------------- CONFIG_USER="${XDG_CONFIG_HOME:-$HOME/.config}/backtunnel/profiles.ini" CONFIG_SYS="/etc/backtunnel/profiles.ini" @@ -41,6 +94,16 @@ fi # INI helpers # ---------------------------- # 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 local sec="$1" key="$2" awk -v s="[""$sec""]" -v k="$key" ' @@ -60,6 +123,11 @@ ini_get() { # ini_get SECTION KEY -> value } # 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 local in="$1" if [[ "$in" == @* ]]; then @@ -75,6 +143,13 @@ profile_expand_remote() { # "@name" -> user@host, otherwise pass through } # 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 local name="$1" v # defaults @@ -96,6 +171,7 @@ profile_apply_defaults() { # set globals if unset; named overrides default # ---------------------------- # Defaults +# Purpose: initialize built-in defaults before applying profiles and flags. # ---------------------------- TUNNEL_PORT=2222 # remote-side port exposed via -R LOCAL_SSH_PORT=22 # local sshd port to forward to @@ -147,6 +223,7 @@ EOF # ---------------------------- # Positional parsing +# Purpose: enforce command grammar and capture the five required positionals. # ---------------------------- [[ $# -lt 5 ]] && usage @@ -171,6 +248,7 @@ REMOTE="$(profile_expand_remote "$REMOTE")" # ---------------------------- # Optional flags +# Purpose: parse options and override derived defaults. # ---------------------------- while [[ $# -gt 0 ]]; do case "$1" in @@ -224,6 +302,7 @@ done # ---------------------------- # Duration validation +# Purpose: accept forms like 30m, 2h, 1d (s/m/h/d). # ---------------------------- if [[ ! "$DURATION" =~ ^[0-9]+[smhd]$ ]]; then echo "Invalid duration '$DURATION' (use forms like 30m, 2h, 1d)." >&2 @@ -232,6 +311,7 @@ fi # ---------------------------- # Split remote user/host +# Purpose: support user:host or user@host; reject anything else (except @profile pre-expanded). # ---------------------------- REMOTE_USER="" REMOTE_HOST="" if [[ "$REMOTE" == *:* ]]; then @@ -247,6 +327,7 @@ fi # ---------------------------- # 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 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 +# 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 local pk="$1" # Normalize @@ -267,6 +354,12 @@ restrict_key_line() { # usage: restrict_key_line 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() { local pubkey_text="$1" local ak="$HOME/.ssh/authorized_keys" @@ -295,6 +388,11 @@ add_temp_authorized_key() { } # 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() { local ak="$HOME/.ssh/authorized_keys" [[ -z "${ADDED_MARKER_ID:-}" ]] && return 0 @@ -333,6 +431,7 @@ fi # ---------------------------- # Banner +# Purpose: inform the user what will happen and where to connect from the remote. # ---------------------------- echo "⏳ Sharing '${FOLDER}' via reverse SSH:" echo " local sshd port : ${LOCAL_SSH_PORT}" @@ -346,7 +445,9 @@ echo # ---------------------------- # 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 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) +# Purpose: give an actionable warning before attempting the -R bind. # ---------------------------- 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 @@ -418,10 +520,14 @@ fi # ---------------------------- # Cleanup & run SSH +# Purpose: start the tunnel, wait bounded by duration, and guarantee cleanup via trap. # ---------------------------- SSH_PID="" # 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() { # stop ssh child if running if [[ -n "${SSH_PID:-}" ]] && kill -0 "$SSH_PID" 2>/dev/null; then @@ -444,7 +550,7 @@ ssh -N \ SSH_PID=$! # 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 # ssh exited on its own before timeout exit 0 diff --git a/scripts/backtunnel-share-gui b/scripts/backtunnel-share-gui index e6dfe57..93e39f1 100644 --- a/scripts/backtunnel-share-gui +++ b/scripts/backtunnel-share-gui @@ -1,5 +1,36 @@ #!/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..log for troubleshooting. +# +# Usage: +# backtunnel-share-gui +# +# 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) # Prompts for parameters via kdialog and launches backtunnel-share in a terminal. @@ -95,4 +126,4 @@ else nohup "${cmd[@]}" >>"$LOG" 2>&1 & kdialog --msgbox "Sharing started in background.\nSee log: $LOG" exit 0 -fi +fi \ No newline at end of file diff --git a/scripts/backtunnel-share-tui b/scripts/backtunnel-share-tui index c976019..73fc3ee 100644 --- a/scripts/backtunnel-share-tui +++ b/scripts/backtunnel-share-tui @@ -1,8 +1,37 @@ #!/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 +# +# 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 FOLDER="${1:-}" [[ -n "$FOLDER" ]] || { echo "Usage: backtunnel-share-tui "; exit 1; } +# ---- Interactive prompts (with sensible defaults) ---- read -r -p "Remote (user@host or user:host) [user@vps.example.com]: " REMOTE REMOTE="${REMOTE:-user@vps.example.com}" @@ -15,6 +44,7 @@ TPORT="${TPORT:-2222}" read -r -p "Local sshd port to expose [22]: " LPORT LPORT="${LPORT:-22}" +# Invite toggles read -r -p "Print invite line for chat? (y/N): " yn INV=; [[ "${yn,,}" == "y" ]] && INV="-i" @@ -27,6 +57,8 @@ fi read -r -p "Invite: suggested mount point [/mnt/remote-rssh]: " MP MP="${MP:-/mnt/remote-rssh}" +# ---- Run the command ---- echo 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"