Add profile support to BackTunnel with defaults, named remotes, and seamless integration across CLI, GUI, and documentation

This commit is contained in:
2025-09-14 19:44:37 +02:00
parent 905bc24f78
commit 0ad3041fce
6 changed files with 363 additions and 46 deletions

101
Makefile Normal file
View File

@@ -0,0 +1,101 @@
# BackTunnel convenience Makefile (for first-time users and simple installs)
PREFIX ?= /usr
BINDIR := $(PREFIX)/bin
MANDIR := $(PREFIX)/share/man
KIO_SM := $(PREFIX)/share/kio/servicemenus
KSVC5 := $(PREFIX)/share/kservices5/ServiceMenus
APPDIR := $(PREFIX)/share/applications
BCOMP := $(PREFIX)/share/bash-completion/completions
# Config paths for user init
XDG_CONFIG_HOME ?= $(HOME)/.config
BT_CFG_DIR := $(XDG_CONFIG_HOME)/backtunnel
BT_CFG_FILE := $(BT_CFG_DIR)/profiles.ini
BT_CFG_EXAMPLE := docs/profiles.ini.example
.PHONY: all init install uninstall refresh man check shellcheck help
all: help
help:
@echo "BackTunnel Makefile"
@echo " make init # Copy example profiles.ini for current user (first run)"
@echo " make install # Install scripts, man page, service menus (root)"
@echo " make uninstall # Remove installed files (root)"
@echo " make refresh # Refresh KDE/desktop caches (root)"
@echo " make man # View man page locally (man ./man/backtunnel.1)"
@echo " make check # Basic bash syntax check"
@echo " make shellcheck # ShellCheck (if installed)"
# --- First-time user init (no root needed) ---
init:
@mkdir -p "$(BT_CFG_DIR)"
@if [ -f "$(BT_CFG_FILE)" ]; then \
echo "profiles.ini already exists at $(BT_CFG_FILE) — skipping."; \
else \
if [ -f "$(BT_CFG_EXAMPLE)" ]; then \
cp "$(BT_CFG_EXAMPLE)" "$(BT_CFG_FILE)"; \
echo "Created $(BT_CFG_FILE) from $(BT_CFG_EXAMPLE)."; \
else \
echo "Example file $(BT_CFG_EXAMPLE) not found."; exit 1; \
fi \
fi
# --- Install/uninstall (root or DESTDIR) ---
install:
@install -Dm755 scripts/backtunnel-share "$(DESTDIR)$(BINDIR)/backtunnel-share"
@install -Dm755 scripts/backtunnel-access "$(DESTDIR)$(BINDIR)/backtunnel-access"
@install -Dm755 scripts/backtunnel-share-gui "$(DESTDIR)$(BINDIR)/backtunnel-share-gui"
@install -Dm644 man/backtunnel.1 "$(DESTDIR)$(MANDIR)/man1/backtunnel.1"
@install -Dm644 completions/backtunnel.bash "$(DESTDIR)$(BCOMP)/backtunnel-share"
@install -Dm644 completions/backtunnel.bash "$(DESTDIR)$(BCOMP)/backtunnel-access"
@install -Dm644 servicemenus/backtunnel_share.desktop "$(DESTDIR)$(KIO_SM)/backtunnel_share.desktop"
@install -Dm644 servicemenus/backtunnel_access.desktop "$(DESTDIR)$(KIO_SM)/backtunnel_access.desktop"
# Plasma 5 legacy path (harmless if unused)
@install -Dm644 servicemenus/backtunnel_share.desktop "$(DESTDIR)$(KSVC5)/backtunnel_share.desktop"
@install -Dm644 servicemenus/backtunnel_access.desktop "$(DESTDIR)$(KSVC5)/backtunnel_access.desktop"
# Optional desktop launcher if present
@if [ -f desktop/backtunnel.desktop ]; then \
install -Dm644 desktop/backtunnel.desktop "$(DESTDIR)$(APPDIR)/backtunnel.desktop"; \
fi
@$(MAKE) refresh
uninstall:
@rm -f "$(DESTDIR)$(BINDIR)/backtunnel-share" \
"$(DESTDIR)$(BINDIR)/backtunnel-access" \
"$(DESTDIR)$(BINDIR)/backtunnel-share-gui"
@rm -f "$(DESTDIR)$(MANDIR)/man1/backtunnel.1"
@rm -f "$(DESTDIR)$(BCOMP)/backtunnel-share" \
"$(DESTDIR)$(BCOMP)/backtunnel-access"
@rm -f "$(DESTDIR)$(KIO_SM)/backtunnel_share.desktop" \
"$(DESTDIR)$(KIO_SM)/backtunnel_access.desktop"
@rm -f "$(DESTDIR)$(KSVC5)/backtunnel_share.desktop" \
"$(DESTDIR)$(KSVC5)/backtunnel_access.desktop"
@rm -f "$(DESTDIR)$(APPDIR)/backtunnel.desktop" || true
@$(MAKE) refresh
# --- Cache refreshers (no-op if tools missing) ---
refresh:
@command -v update-desktop-database >/dev/null 2>&1 && update-desktop-database -q || true
@if command -v kbuildsycoca6 >/dev/null 2>&1; then \
kbuildsycoca6 --noincremental >/dev/null 2>&1 || true; \
elif command -v kbuildsycoca5 >/dev/null 2>&1; then \
kbuildsycoca5 --noincremental >/dev/null 2>&1 || true; \
fi
# --- Dev helpers ---
man:
@man ./man/backtunnel.1
check:
@bash -n scripts/backtunnel-share
@bash -n scripts/backtunnel-access
@bash -n scripts/backtunnel-share-gui
@echo "bash -n OK."
shellcheck:
@command -v shellcheck >/dev/null 2>&1 || { echo "ShellCheck not installed"; exit 1; }
@shellcheck scripts/backtunnel-share
@shellcheck scripts/backtunnel-access
@shellcheck scripts/backtunnel-share-gui

View File

@@ -58,6 +58,18 @@ backtunnel-access /path/to/folder from remoteuser:remotehost [options]
--- ---
## 📁 Profiles (named remotes)
BackTunnel supports **profiles** to simplify connections. Instead of typing
`user@host -p PORT -l PORT …` every time, you can define defaults and named remotes in:
📖 Example config: see [docs/profiles.ini.example](docs/profiles.ini.example)
System-wide default: /etc/backtunnel/profiles.ini (admins can edit)
Packaged example: /usr/share/backtunnel/profiles.ini
---
## 🧰 Dolphin Service Menus ## 🧰 Dolphin Service Menus
Two context actions for Dolphin: Two context actions for Dolphin:

13
docs/profiles.ini.example Normal file
View File

@@ -0,0 +1,13 @@
[default]
duration=2h
tunnel_port=2222
local_ssh_port=22
invite_mount=~/remote-rssh
invite=true
qr=false
[myvps]
user=me
host=vps.example.com
tunnel_port=4422
invite_mount=~/remote-rssh

View File

@@ -1,51 +1,53 @@
.TH backtunnel 1 "September 2025" "1.2" "BackTunnel Reverse SSH Sharing Toolkit" .TH backtunnel 1 "September 2025" "1.2" "BackTunnel Reverse SSH Sharing Toolkit"
.SH NAME .SH NAME
backtunnel-share, backtunnel-access \- Secure reverse SSH folder sharing and access backtunnel-share, backtunnel-access \- Secure reverse SSH folder sharing and access (with profiles)
.SH SYNOPSIS .SH SYNOPSIS
.B backtunnel-share .B backtunnel-share
/path/to/folder with remoteuser:remotehost for <duration> [options] /path/to/folder with {remoteuser:remotehost|remoteuser@remotehost|@profilename} for <duration> [options]
.B backtunnel-access .B backtunnel-access
/path/to/folder from remoteuser:remotehost [options] /path/to/folder from {remoteuser:remotehost|remoteuser@remotehost} [options]
.SH DESCRIPTION .SH DESCRIPTION
\fBbacktunnel-share\fR starts a reverse SSH tunnel from the local (sharing) machine to a remote, reachable host. \fBbacktunnel-share\fR starts a reverse SSH tunnel from the local (sharing) machine to a remote,
The tunnel exposes the local sshd (typically port 22) on a loopback port on the remote host using \fBssh -R\fR. reachable host. The tunnel exposes the local sshd (typically port 22) on a loopback port on the
The sharing ends automatically after the given \fIduration\fR via \fBtimeout\fR. remote host using \fBssh -R\fR. Sharing ends automatically after the given \fIduration\fR via \fBtimeout\fR.
Profiles can be used to simplify the remote specification and default options.
With the \fB--invite\fR option, \fBbacktunnel-share\fR prints a ready-to-copy access command for the remote user, \fBbacktunnel-access\fR mounts the shared folder from the remote side using \fBsshfs\fR by connecting
which can be pasted directly into a chat or terminal. The invite can also be rendered as a QR code or written to a file. to \fBlocalhost:<port>\fR on the remote host (the port exposed by \fBbacktunnel-share\fR).
\fBbacktunnel-access\fR mounts the shared folder from the remote side using \fBsshfs\fR by connecting to \fBlocalhost:<port>\fR on the remote host
(the port exposed by \fBbacktunnel-share\fR).
.SH OPTIONS .SH OPTIONS
.SS backtunnel-share options .SS backtunnel-share options
.TP .TP
.B -p, --tunnel-port <PORT> .B -p, --tunnel-port <PORT>
Remote port to bind with \fB-R\fR (default: 2222). Remote port to bind with \fB-R\fR (default: 2222). May be provided via profile.
.TP .TP
.B -l, --local-ssh-port <PORT> .B -l, --local-ssh-port <PORT>
Local sshd port to forward to (default: 22). Local sshd port to forward to (default: 22). May be provided via profile.
.TP .TP
.B -i, --invite .B -i, --invite
Print a ready-to-copy invite command for the remote user. Print a ready-to-copy access command for the remote side.
.TP .TP
.B --invite-mount <PATH> .B --invite-mount <PATH>
Mount point suggested in the invite (default: /mnt/remote-rssh). Suggested mount point included in the invite text (default: /mnt/remote-rssh). May be provided via profile.
.TP .TP
.B --invite-file <FILE> .B --invite-file <FILE>
Also write the invite text (including unmount hint) to the given file. Write the invite text (including unmount hint) to the given file.
.TP .TP
.B --qr .B --qr
Render the invite as a terminal QR code (requires \fBqrencode\fR). Additionally render the invite as a QR code (requires \fBqrencode\fR).
.TP
.B -h, --help
Show usage.
.SS backtunnel-access options .SS backtunnel-access options
.TP .TP
@@ -65,9 +67,65 @@ Path to share (server) or to mount (client).
.B remoteuser:remotehost .B remoteuser:remotehost
Or \fBremoteuser@remotehost\fR. The remote host that accepts the initial SSH connection. Or \fBremoteuser@remotehost\fR. The remote host that accepts the initial SSH connection.
.TP
.B @profilename
A named profile that expands to \fBuser@host\fR and may also supply defaults for \fB--tunnel-port\fR,
\fB--local-ssh-port\fR, \fB--invite-mount\fR, and \fIduration\fR. See \fBPROFILES\fR.
.TP .TP
.B <duration> .B <duration>
Time to keep the share active. Accepts \fBn\fR\fIs\fR|\fIm\fR|\fIh\fR|\fId\fR forms, e.g. 30m, 2h, 1d. Passed to \fBtimeout\fR. Time to keep the share active. Accepts \fBn\fR\fIs\fR|\fIm\fR|\fIh\fR|\fId\fR forms, e.g. 30m, 2h, 1d (passed to \fBtimeout\fR).
May be provided via the \fB[default]\fR profile; the positional \fI<duration>\fR takes precedence.
.SH PROFILES
BackTunnel can read defaults and named remote definitions from (searched in order):
.P
\fB~/.config/backtunnel/profiles.ini\fR (per-user)
.br
\fB/etc/backtunnel/profiles.ini\fR (system-wide default)
.br
\fB/usr/share/backtunnel/profiles.ini\fR (packaged example/fallback)
.P
Two kinds of sections are recognized:
.TP
.B [default]
Global defaults applied unless overridden by a named profile or CLI flags:
.IP
\fBduration\fR=2h
.br
\fBtunnel_port\fR=2222
.br
\fBlocal_ssh_port\fR=22
.br
\fBinvite_mount\fR=~/remote-rssh
.br
\fBinvite\fR=true|false
.br
\fBqr\fR=true|false
.TP
.B [name]
A named profile providing at least \fBuser\fR and \fBhost\fR, and optional overrides:
.IP
\fBuser\fR=alice
.br
\fBhost\fR=vps.example.com
.br
\fBtunnel_port\fR=4422
.br
\fBlocal_ssh_port\fR=22
.br
\fBinvite_mount\fR=/mnt/remote-rssh
.P
To use a profile, replace the remote with \fB@name\fR, e.g.:
.P
.nf
backtunnel-share /path/to/folder with @workvps for 1d
.fi
.P
Command-line options \fBalways override\fR values from profiles.
.SH EXAMPLES .SH EXAMPLES
.TP .TP
@@ -81,14 +139,9 @@ Share for 1 day, using custom ports:
/home/user/docs with alice:vps.example.com for 1d -p 4422 -l 2222 /home/user/docs with alice:vps.example.com for 1d -p 4422 -l 2222
.TP .TP
Share for 2 hours and print an invite: Share using a profile, then override port:
.B backtunnel-share .B backtunnel-share
/home/user/docs with alice@vps.example.com for 2h -i /home/user/docs with @workvps for 6h -p 5500
.TP
Share with QR invite:
.B backtunnel-share
/home/user/docs with alice@vps.example.com for 2h -i --qr
.TP .TP
Mount with default port and mount point: Mount with default port and mount point:
@@ -103,12 +156,21 @@ Mount with custom port and mount point:
.SH NOTES .SH NOTES
By default, \fBssh -R\fR binds to 127.0.0.1 on the remote side, limiting access to local users on the remote machine. By default, \fBssh -R\fR binds to 127.0.0.1 on the remote side, limiting access to local users on the remote machine.
The client connects to \fBlocalhost:<port>\fR from the remote host. The client connects to \fBlocalhost:<port>\fR from the remote host.
Ensure the sharing host's sshd provides a valid SFTP subsystem for sshfs.
The invite feature is intended for convenience: copy-paste the printed command into chat for the remote user. .SH FILES
Unmount with \fBfusermount -u <mountpoint>\fR after use. .TP
\fB~/.config/backtunnel/profiles.ini\fR
Per-user configuration file containing defaults and named profiles.
.TP
\fB/etc/backtunnel/profiles.ini\fR
System-wide default profiles (editable by admins).
.TP
\fB/usr/share/backtunnel/profiles.ini\fR
Packaged example/fallback used when user/system config is absent.
.SH SEE ALSO .SH SEE ALSO
ssh(1), sshfs(1), timeout(1), autossh(1), qrencode(1), fusermount(1) ssh(1), sshfs(1), timeout(1), autossh(1), fusermount(1)
.SH AUTHOR .SH AUTHOR
Matjaž Mozetič Matjaž Mozetič

View File

@@ -10,6 +10,75 @@
set -euo pipefail set -euo pipefail
# Config search order: user → system → packaged example
CONFIG_USER="${XDG_CONFIG_HOME:-$HOME/.config}/backtunnel/profiles.ini"
CONFIG_SYS="/etc/backtunnel/profiles.ini"
CONFIG_PKG="/usr/share/backtunnel/profiles.ini"
# resolve CONFIG_FILE to first existing
if [[ -f "$CONFIG_USER" ]]; then
CONFIG_FILE="$CONFIG_USER"
elif [[ -f "$CONFIG_SYS" ]]; then
CONFIG_FILE="$CONFIG_SYS"
else
CONFIG_FILE="$CONFIG_PKG"
fi
# shellcheck disable=SC2317 # invoked later at runtime; ShellCheck can't see call path
ini_get() { # ini_get SECTION KEY -> value
local sec="$1" key="$2"
awk -v s="[""$sec""]" -v k="$key" '
$0==s {ok=1; next}
/^\[/ {ok=0}
ok && $0 ~ /^[[:alnum:]_.-]+[[:space:]]*=/ {
line=$0
sub(/[[:space:]]*=[[:space:]]*/, "=", line)
split(line,a,"=")
if (a[1]==k) {
val=substr(line, index(line,"=")+1)
gsub(/^[[:space:]]+|[[:space:]]+$/,"",val)
print val
exit
}
}' "${CONFIG_FILE}" 2>/dev/null || true
}
# shellcheck disable=SC2317
profile_expand_remote() { # "@name" -> user@host, otherwise pass through
local in="$1"
if [[ "$in" == @* ]]; then
local name="${in#@}" user host
user="$(ini_get "$name" user)"
host="$(ini_get "$name" host)"
if [[ -n "$user" && -n "$host" ]]; then
printf '%s@%s\n' "$user" "$host"
return 0
fi
fi
printf '%s\n' "$in"
}
# shellcheck disable=SC2317
profile_apply_defaults() { # set globals if unset; named overrides default
local name="$1" v
# 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)"; [[ "${v,,}" == "true" ]] && INVITE=true
v="$(ini_get default qr)"; [[ "${v,,}" == "true" ]] && QR=true
if [[ -z "$DURATION" ]]; then
v="$(ini_get default duration)"; [[ -n "$v" ]] && DURATION="$v"
fi
# named section
if [[ -n "$name" ]]; then
v="$(ini_get "$name" tunnel_port)"; [[ -n "$v" ]] && TUNNEL_PORT="$v"
v="$(ini_get "$name" local_ssh_port)"; [[ -n "$v" ]] && LOCAL_SSH_PORT="$v"
v="$(ini_get "$name" invite_mount)"; [[ -n "$v" ]] && INVITE_MOUNT="$v"
fi
}
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
DURATION="" # required: e.g. 30m, 2h, 1d DURATION="" # required: e.g. 30m, 2h, 1d
@@ -61,6 +130,15 @@ shift 5 || true
[[ "$KW1" != "with" ]] && usage [[ "$KW1" != "with" ]] && usage
[[ "$KW2" != "for" ]] && usage [[ "$KW2" != "for" ]] && usage
# Apply [default] + named profile (if REMOTE is @name) before flag overrides
profile_name=""
if [[ "$REMOTE" == @* ]]; then
profile_name="${REMOTE#@}"
fi
profile_apply_defaults "$profile_name"
# Expand @name -> user@host for actual ssh use
REMOTE="$(profile_expand_remote "$REMOTE")"
# --- optional flags --- # --- optional flags ---
while [[ $# -gt 0 ]]; do while [[ $# -gt 0 ]]; do
case "$1" in case "$1" in

View File

@@ -1,45 +1,96 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# Copyright (c) 2025. LUXIM d.o.o., Slovenia - Matjaž Mozetič.
# 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 Konsole. # Prompts for parameters via kdialog and launches backtunnel-share in a terminal.
set -euo pipefail set -euo pipefail
# --- Selection from Dolphin --- LOG="/tmp/backtunnel-share-gui.$UID.log"
exec > >(tee -a "$LOG") 2>&1
export PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:${PATH:-}"
FOLDER="${1:-}" FOLDER="${1:-}"
if [[ -z "$FOLDER" ]]; then if [[ -z "$FOLDER" ]]; then
kdialog --error "No folder selected." || true kdialog --error "No folder selected." || true
echo "No folder argument passed from service menu." >>"$LOG"
exit 1 exit 1
fi fi
# --- Defaults --- # Defaults
REMOTE_DEFAULT="user@vps.example.com" REMOTE_DEFAULT="user@vps.example.com"
DURATION_DEFAULT="2h" DURATION_DEFAULT="2h"
TPORT_DEFAULT="2222" TPORT_DEFAULT="2222"
LPORT_DEFAULT="22" LPORT_DEFAULT="22"
INVITE_MOUNT_DEFAULT="/mnt/remote-rssh" INVITE_MOUNT_DEFAULT="/mnt/remote-rssh"
# --- Prompts --- # Profiles (search order: user -> system -> packaged fallback)
REMOTE="$(kdialog --inputbox "Remote (user@host or user:host):" "$REMOTE_DEFAULT")" || exit 1 PROFILES_USER="${XDG_CONFIG_HOME:-$HOME/.config}/backtunnel/profiles.ini"
DUR="$(kdialog --combobox "Share duration" 30m 2h 6h 1d 2d --editable "$DURATION_DEFAULT")" || exit 1 PROFILES_SYS="/etc/backtunnel/profiles.ini"
TPORT="$(kdialog --inputbox "Tunnel port on remote:" "$TPORT_DEFAULT")" || exit 1 PROFILES_PKG="/usr/share/backtunnel/profiles.ini"
LPORT="$(kdialog --inputbox "Local SSH port to expose:" "$LPORT_DEFAULT")" || exit 1 if [[ -f "$PROFILES_USER" ]]; then PROFILES_FILE="$PROFILES_USER"
elif [[ -f "$PROFILES_SYS" ]]; then PROFILES_FILE="$PROFILES_SYS"
else PROFILES_FILE="$PROFILES_PKG"
fi
INV="" # explicit if-then; avoids SC2015 ("A && B || C is not if-then-else") # If profiles exist, offer a profile picker and prefill dialogs
if [[ -f "$PROFILES_FILE" ]]; then
mapfile -t profs < <(awk '/^\[/{gsub(/^\[|\]$/,"",$1); if ($1!="default") print $1}' "$PROFILES_FILE")
if (( ${#profs[@]} )); then
choice="$(kdialog --combobox "Choose profile (or Cancel for manual)" "${profs[@]}")" || choice=""
if [[ -n "$choice" ]]; then
getval() { awk -v s="[""$choice""]" -v k="$1" '
$0==s{ok=1; next} /^\[/{ok=0}
ok && $0 ~ /^[[:alnum:]_.-]+[[:space:]]*=/ {
line=$0; sub(/[[:space:]]*=[[:space:]]*/, "=", line)
split(line,a,"="); if(a[1]==k){val=substr(line,index(line,"=")+1); gsub(/^[[:space:]]+|[[:space:]]+$/,"",val); print val; exit}
}' "$PROFILES_FILE" 2>/dev/null; }
u="$(getval user)"; h="$(getval host)"
if [[ -n "$u" && -n "$h" ]]; then
REMOTE_DEFAULT="$u@$h"
fi
v="$(getval tunnel_port)"; [[ -n "$v" ]] && TPORT_DEFAULT="$v"
v="$(getval local_ssh_port)"; [[ -n "$v" ]] && LPORT_DEFAULT="$v"
v="$(getval invite_mount)"; [[ -n "$v" ]] && INVITE_MOUNT_DEFAULT="$v"
# optional: default duration from [default]
vd="$(awk -v s="[default]" -v k="duration" '
$0==s{ok=1; next} /^\[/{ok=0}
ok && $0 ~ /^[[:alnum:]_.-]+[[:space:]]*=/ {
line=$0; sub(/[[:space:]]*=[[:space:]]*/, "=", line)
split(line,a,"="); if(a[1]==k){val=substr(line,index(line,"=")+1); gsub(/^[[:space:]]+|[[:space:]]+$/,"",val); print val; exit}
}' "$PROFILES_FILE" 2>/dev/null)"
[[ -n "$vd" ]] && DURATION_DEFAULT="$vd"
fi
fi
fi
# Dialogs (Cancel returns 1; treat as benign exit)
REMOTE="$(kdialog --inputbox "Remote (user@host or user:host):" "$REMOTE_DEFAULT")" || exit 0
DUR="$(kdialog --combobox "Share duration" 30m 2h 6h 1d 2d --editable "$DURATION_DEFAULT")" || exit 0
TPORT="$(kdialog --inputbox "Tunnel port on remote:" "$TPORT_DEFAULT")" || exit 0
LPORT="$(kdialog --inputbox "Local SSH port to expose:" "$LPORT_DEFAULT")" || exit 0
INV="" # set to "-i" if chosen
if kdialog --yesno "Print invite line for chat?"; then if kdialog --yesno "Print invite line for chat?"; then
INV="-i" INV="-i"
fi fi
QR="" QR="" # set to "--qr" if chosen
if kdialog --yesno "Show QR code for the invite?"; then if kdialog --yesno "Show QR code for the invite?"; then
QR="--qr" QR="--qr"
fi fi
MP="$(kdialog --inputbox "Suggested mount point in invite:" "$INVITE_MOUNT_DEFAULT")" || exit 1 MP="$(kdialog --inputbox "Suggested mount point in invite:" "$INVITE_MOUNT_DEFAULT")" || exit 0
# --- Run share in Konsole (keeps session open) --- # Build command safely as an array; append optional flags conditionally (SC2206-safe)
exec konsole --noclose -e backtunnel-share \ cmd=( backtunnel-share "$FOLDER" with "$REMOTE" for "$DUR" -p "$TPORT" -l "$LPORT" --invite-mount "$MP" )
"$FOLDER" with "$REMOTE" for "$DUR" \ if [[ -n "$INV" ]]; then cmd+=("$INV"); fi
-p "$TPORT" -l "$LPORT" $INV $QR --invite-mount "$MP" if [[ -n "$QR" ]]; then cmd+=("$QR"); fi
if command -v konsole >/dev/null 2>&1; then
exec konsole --noclose -e "${cmd[@]}"
elif command -v xterm >/dev/null 2>&1; then
exec xterm -hold -e "${cmd[@]}"
else
nohup "${cmd[@]}" >>"$LOG" 2>&1 &
kdialog --msgbox "Sharing started in background.\nSee log: $LOG"
exit 0
fi