Add accessor key authorization and enhance completion logic
Introduce `backtunnel-authorize` for managing restricted SFTP-only keys, and update `backtunnel-share` to support temporary accessor key authorization via `--allow-key` and `--allow-known`. Extend bash completion with profile, accessor, and SSH host suggestions. Revamp README sections to include updated workflows, quick starts, and key management details.
This commit is contained in:
208
README.md
208
README.md
@@ -1,139 +1,95 @@
|
|||||||
# BackTunnel – Secure Reverse SSH Folder Sharing Toolkit
|
# BackTunnel – Secure Reverse SSH Folder Sharing Toolkit
|
||||||
|
|
||||||
**BackTunnel** lets you share and mount folders between Linux machines behind NAT or firewalls using just two friendly commands.
|
**BackTunnel** is a lightweight toolkit to share and mount folders between Linux machines behind NAT or firewalls using **reverse SSH tunnels**.
|
||||||
No central servers. No cloud uploads. Temporary, peer-to-peer, SSH-based.
|
No third-party relay, no cloud dependency – just peer-to-peer, temporary, SSH-based access.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## ⚡ Quick Start (3 steps)
|
## ⚡ Quick Start
|
||||||
|
|
||||||
1. **On the sharing machine (server):**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
backtunnel-share ~/Documents with user@remotehost for 2h -i
|
|
||||||
```
|
|
||||||
|
|
||||||
→ Opens a tunnel for 2 hours and prints an **invite**.
|
|
||||||
|
|
||||||
2. **Send the invite** (via chat, email, QR). It looks like:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
backtunnel-auth-setup -p 2222 user@localhost
|
|
||||||
backtunnel-access '/home/alice/Documents' from user@remotehost -p 2222 -m "$HOME/remote-rssh"
|
|
||||||
```
|
|
||||||
|
|
||||||
3. **On the accessing machine (client):**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
mkdir -p ~/remote-rssh
|
|
||||||
backtunnel-auth-setup -p 2222 user@localhost
|
|
||||||
backtunnel-access '/home/alice/Documents' from user@remotehost -p 2222 -m ~/remote-rssh
|
|
||||||
```
|
|
||||||
|
|
||||||
→ Folder is mounted at `~/remote-rssh`.
|
|
||||||
|
|
||||||
Unmount afterwards with:
|
|
||||||
|
|
||||||
|
### 1. Accessor: Prepare your key (one-time)
|
||||||
```bash
|
```bash
|
||||||
fusermount -u ~/remote-rssh
|
backtunnel-keys print
|
||||||
```
|
```
|
||||||
|
Send the printed public key to the sharer.
|
||||||
|
|
||||||
---
|
### 2. Sharer: Start sharing and authorize the accessor
|
||||||
|
|
||||||
## 🚀 Why BackTunnel?
|
|
||||||
|
|
||||||
* **Works behind NAT/firewalls**: reverse SSH tunnels, no port forwarding needed.
|
|
||||||
* **Private by design**: no third-party relays or cloud.
|
|
||||||
* **Temporary by default**: shares auto-stop after chosen duration.
|
|
||||||
* **Simple invites**: send one-liners (or QR codes) to colleagues.
|
|
||||||
* **Safe**: restricted SFTP-only keys ensure no shell access.
|
|
||||||
* **Desktop integration**: right-click folders in Dolphin to share or access.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ✨ Commands
|
|
||||||
|
|
||||||
### `backtunnel-share` — start a share
|
|
||||||
|
|
||||||
*(sharing side)*
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
backtunnel-share /path/to/folder with user@remotehost for <duration> [options]
|
backtunnel-share ~/Documents with alice@remotehost for 2h -i --allow-key ./alice.pub
|
||||||
```
|
```
|
||||||
|
This injects a **temporary, restricted SFTP-only key** and prints an invite.
|
||||||
|
|
||||||
**Duration formats:** `30m`, `2h`, `1d`
|
### 3. Accessor: Mount the shared folder
|
||||||
**Key options:**
|
|
||||||
|
|
||||||
* `-p, --tunnel-port <PORT>` — Remote port (default: `2222`)
|
|
||||||
* `-l, --local-ssh-port <PORT>` — Local SSHD port (default: `22`)
|
|
||||||
* `-i, --invite` — Print a ready-to-copy **invite**
|
|
||||||
* `--invite-mount <PATH>` — Suggested mount point (default: `~/remote-rssh`)
|
|
||||||
* `--qr` — Show invite as QR code
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### `backtunnel-access` — mount a share
|
|
||||||
|
|
||||||
*(accessing side)*
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
backtunnel-access /path/to/folder from user@remotehost [options]
|
backtunnel-access '/home/sharer/Documents' from alice@remotehost -p 2222 -m ~/remote-rssh
|
||||||
```
|
```
|
||||||
|
|
||||||
**Options:**
|
|
||||||
|
|
||||||
* `-p, --port <PORT>` — Remote tunnel port (default: `2222`)
|
|
||||||
* `-m, --mount-point <PATH>` — Local mount point (default: `~/remote-rssh`)
|
|
||||||
|
|
||||||
Unmount with:
|
Unmount with:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
fusermount -u ~/remote-rssh
|
fusermount -u ~/remote-rssh
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## ✨ Features
|
||||||
|
|
||||||
|
- **NAT/firewall friendly**: works without port forwarding.
|
||||||
|
- **Temporary by design**: shares auto-expire after a set duration.
|
||||||
|
- **Invite workflow**: sharer sends a one-liner or QR code to accessor.
|
||||||
|
- **Restricted keys**: accessor keys are usable *only* for SFTP via the tunnel and are auto-removed when the share ends.
|
||||||
|
- **Profiles**: save defaults and common remotes in `profiles.ini`.
|
||||||
|
- **Desktop integration**: Dolphin (KDE) service menus for GUI sharing and access.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔑 Commands
|
||||||
|
|
||||||
|
### Sharing
|
||||||
|
```bash
|
||||||
|
backtunnel-share /path/to/folder with user@host for <duration> [options]
|
||||||
|
```
|
||||||
|
- `-p, --tunnel-port` Remote bind port (default: 2222)
|
||||||
|
- `-l, --local-ssh-port` Local sshd port to expose (default: 22)
|
||||||
|
- `-i, --invite` Print invite line
|
||||||
|
- `--qr` Show QR code (requires qrencode)
|
||||||
|
- `--allow-key FILE` Authorize a provided public key
|
||||||
|
- `--allow-known NAME` Authorize a stored key (~/.config/backtunnel/authorized/NAME.pub)
|
||||||
|
|
||||||
|
### Accessing
|
||||||
|
```bash
|
||||||
|
backtunnel-access /path/to/folder from user@host [options]
|
||||||
|
```
|
||||||
|
- `-p, --port` Tunnel port (default: 2222)
|
||||||
|
- `-m, --mount-point` Local mount point (default: ~/remote-rssh)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 📁 Profiles
|
## 📁 Profiles
|
||||||
|
|
||||||
Simplify frequent shares with `~/.config/backtunnel/profiles.ini`.
|
`~/.config/backtunnel/profiles.ini`:
|
||||||
Example:
|
|
||||||
|
|
||||||
```ini
|
```ini
|
||||||
[default]
|
[default]
|
||||||
tunnel_port=4422
|
tunnel_port=2222
|
||||||
invite_mount=$HOME/shared
|
invite=true
|
||||||
|
invite_mount=$HOME/remote-rssh
|
||||||
|
|
||||||
[work]
|
[work]
|
||||||
user=alice
|
user=alice
|
||||||
host=vps.example.com
|
host=vps.example.com
|
||||||
tunnel_port=4423
|
tunnel_port=4422
|
||||||
```
|
```
|
||||||
|
Usage:
|
||||||
Use with:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
backtunnel-share ~/reports with @work for 6h -i
|
backtunnel-share ~/reports with @work for 6h -i --allow-known alice
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🔒 Security
|
|
||||||
|
|
||||||
* **Restricted keys**: usable only through the tunnel (127.0.0.1).
|
|
||||||
* **SFTP-only**: enforced with `internal-sftp,restrict`.
|
|
||||||
* **Temporary**: ends when the tunnel closes.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🖥️ Dolphin Integration
|
## 🖥️ Dolphin Integration
|
||||||
|
|
||||||
After install, Dolphin menus appear:
|
- **Share via BackTunnel…** – GUI dialog for sharer options.
|
||||||
|
- **Access via BackTunnel…** – GUI dialog for accessor options.
|
||||||
* **Share via BackTunnel…** — GUI wizard for remote, duration, ports, invite.
|
|
||||||
* **Access via BackTunnel…** — GUI wizard for remote, port, mount point.
|
|
||||||
|
|
||||||
Logs:
|
|
||||||
|
|
||||||
|
Logs are written to:
|
||||||
```
|
```
|
||||||
~/.local/state/backtunnel/servicemenu.*.log
|
~/.local/state/backtunnel/servicemenu.*.log
|
||||||
```
|
```
|
||||||
@@ -142,27 +98,27 @@ Logs:
|
|||||||
|
|
||||||
## 📦 Install
|
## 📦 Install
|
||||||
|
|
||||||
|
### From source
|
||||||
```bash
|
```bash
|
||||||
sudo bash scripts/install.sh
|
sudo bash scripts/install.sh
|
||||||
make init # copy example profiles.ini
|
make init # copy example profiles.ini
|
||||||
```
|
```
|
||||||
|
|
||||||
Uninstall:
|
### Arch Linux
|
||||||
|
|
||||||
```bash
|
|
||||||
sudo bash scripts/uninstall.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
Arch Linux:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
makepkg -si
|
makepkg -si
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Uninstall:
|
||||||
|
```bash
|
||||||
|
sudo bash scripts/uninstall.sh
|
||||||
|
# or with purge of defaults
|
||||||
|
sudo PURGE=1 bash scripts/uninstall.sh
|
||||||
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 📖 Man Page
|
## 📖 Documentation
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
man backtunnel
|
man backtunnel
|
||||||
```
|
```
|
||||||
@@ -170,39 +126,5 @@ man backtunnel
|
|||||||
---
|
---
|
||||||
|
|
||||||
## 🧾 License
|
## 🧾 License
|
||||||
|
GNU GPL v3.0
|
||||||
Licensed under **GNU GPL v3.0**
|
|
||||||
© 2025 LUXIM d.o.o., Slovenia – Matjaž Mozetič
|
© 2025 LUXIM d.o.o., Slovenia – Matjaž Mozetič
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📝 Cheat Sheet (TL;DR)
|
|
||||||
|
|
||||||
**Start a share:**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
backtunnel-share ~/Documents with user@remotehost for 2h -i
|
|
||||||
```
|
|
||||||
|
|
||||||
**Send this invite to your colleague:**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
backtunnel-auth-setup -p 2222 user@localhost
|
|
||||||
backtunnel-access '/home/user/Documents' from user@remotehost -p 2222 -m ~/remote-rssh
|
|
||||||
```
|
|
||||||
|
|
||||||
**On the client:**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
mkdir -p ~/remote-rssh
|
|
||||||
backtunnel-auth-setup -p 2222 user@localhost
|
|
||||||
backtunnel-access '/home/user/Documents' from user@remotehost -p 2222 -m ~/remote-rssh
|
|
||||||
```
|
|
||||||
|
|
||||||
**Unmount:**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
fusermount -u ~/remote-rssh
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|||||||
@@ -1,53 +1,201 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
# BackTunnel bash completion for:
|
||||||
|
# - backtunnel-share
|
||||||
|
# - backtunnel-access
|
||||||
|
#
|
||||||
|
# Features:
|
||||||
|
# - Positional scaffolding: "with"/"from" / "for" and duration suggestions
|
||||||
|
# - @profile completion from profiles.ini (user, system, packaged)
|
||||||
|
# - --allow-key completes *.pub files
|
||||||
|
# - --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)
|
||||||
|
|
||||||
# Copyright (c) 2025. LUXIM d.o.o., Slovenia - Matjaž Mozetič.
|
# ---------- helpers ----------
|
||||||
|
|
||||||
|
_bt_cfg_files() {
|
||||||
|
local u="${XDG_CONFIG_HOME:-$HOME/.config}/backtunnel/profiles.ini"
|
||||||
|
local s="/etc/backtunnel/profiles.ini"
|
||||||
|
local p="/usr/share/backtunnel/profiles.ini"
|
||||||
|
[[ -f "$u" ]] && printf '%s\n' "$u"
|
||||||
|
[[ -f "$s" ]] && printf '%s\n' "$s"
|
||||||
|
[[ -f "$p" ]] && printf '%s\n' "$p"
|
||||||
|
}
|
||||||
|
|
||||||
|
_bt_list_profiles() {
|
||||||
|
# Output section names excluding [default]
|
||||||
|
local f
|
||||||
|
while IFS= read -r f; do
|
||||||
|
awk '
|
||||||
|
/^\[[^]]+\]/ {
|
||||||
|
name=$0; sub(/^\[/,"",name); sub(/\]$/,"",name);
|
||||||
|
if (name != "default") print name
|
||||||
|
}' "$f"
|
||||||
|
done < <(_bt_cfg_files) 2>/dev/null | sort -u
|
||||||
|
}
|
||||||
|
|
||||||
|
_bt_list_authorized_names() {
|
||||||
|
# From ~/.config/backtunnel/authorized/*.pub → basename w/o .pub
|
||||||
|
local d="${XDG_CONFIG_HOME:-$HOME/.config}/backtunnel/authorized"
|
||||||
|
[[ -d "$d" ]] || return 0
|
||||||
|
local f
|
||||||
|
shopt -s nullglob
|
||||||
|
for f in "$d"/*.pub; do
|
||||||
|
f="${f##*/}"; printf '%s\n' "${f%.pub}"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
_bt_list_ssh_hosts() {
|
||||||
|
# Collect hosts from known_hosts and ~/.ssh/config Host entries (best effort)
|
||||||
|
local out=()
|
||||||
|
local kh="$HOME/.ssh/known_hosts"
|
||||||
|
local cfg="$HOME/.ssh/config"
|
||||||
|
local f
|
||||||
|
|
||||||
|
if [[ -f "$kh" ]]; then
|
||||||
|
# First field may be comma-separated list; ignore IP-literals in brackets
|
||||||
|
# and strip hashed or bracketed entries.
|
||||||
|
awk -F'[ ,]' '
|
||||||
|
{
|
||||||
|
if ($1 ~ /^\|1\|/) next; # hashed, skip
|
||||||
|
host=$1
|
||||||
|
gsub(/^\[/,"",host); gsub(/\]$/,"",host)
|
||||||
|
if (host ~ /^[0-9.]+$/) next; # plain IPv4
|
||||||
|
if (host ~ /^[0-9a-fA-F:]+$/) next; # plain IPv6
|
||||||
|
if (host ~ /^\*/) next; # wildcard
|
||||||
|
print host
|
||||||
|
}' "$kh"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -f "$cfg" ]]; then
|
||||||
|
awk '
|
||||||
|
tolower($1)=="host" {
|
||||||
|
for (i=2; i<=NF; i++) if ($i !~ /\*/ ) print $i
|
||||||
|
}' "$cfg"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
_bt_is_backtunnel_share() { [[ ${COMP_WORDS[0]} == backtunnel-share ]]; }
|
||||||
|
_bt_is_backtunnel_access(){ [[ ${COMP_WORDS[0]} == backtunnel-access ]]; }
|
||||||
|
|
||||||
|
# ---------- main completer ----------
|
||||||
|
|
||||||
_backtunnel_complete() {
|
_backtunnel_complete() {
|
||||||
local cur
|
local cur prev
|
||||||
cur="${COMP_WORDS[COMP_CWORD]}"
|
cur="${COMP_WORDS[COMP_CWORD]}"
|
||||||
|
prev="${COMP_WORDS[COMP_CWORD-1]}"
|
||||||
|
|
||||||
|
# Common positional flow:
|
||||||
|
# share: [0]cmd [1]/path [2]with [3]remote|@prof [4]for [5]duration ...
|
||||||
|
# access: [0]cmd [1]/path [2]from [3]remote ...
|
||||||
|
#
|
||||||
|
# Provide directory completion for the first positional:
|
||||||
if [[ ${COMP_CWORD} -eq 1 ]]; then
|
if [[ ${COMP_CWORD} -eq 1 ]]; then
|
||||||
compopt -o filenames 2>/dev/null
|
compopt -o filenames 2>/dev/null
|
||||||
mapfile -t COMPREPLY < <(compgen -d -- "${cur}")
|
mapfile -t COMPREPLY < <(compgen -d -- "$cur")
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Offer "with"/"from" at position 2 depending on command
|
||||||
if [[ ${COMP_CWORD} -eq 2 ]]; then
|
if [[ ${COMP_CWORD} -eq 2 ]]; then
|
||||||
mapfile -t COMPREPLY < <(compgen -W "with from" -- "${cur}")
|
if _bt_is_backtunnel_share; then
|
||||||
|
mapfile -t COMPREPLY < <(compgen -W "with" -- "$cur")
|
||||||
|
else
|
||||||
|
mapfile -t COMPREPLY < <(compgen -W "from" -- "$cur")
|
||||||
|
fi
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ ${COMP_CWORD} -eq 4 && "${COMP_WORDS[2]}" == "with" ]]; then
|
# For backtunnel-share: position 3 (REMOTE | @profile)
|
||||||
mapfile -t COMPREPLY < <(compgen -W "for" -- "${cur}")
|
if _bt_is_backtunnel_share && [[ ${COMP_CWORD} -eq 3 && ${COMP_WORDS[2]} == "with" ]]; then
|
||||||
|
# Build candidates: @profiles + hosts from ssh files
|
||||||
|
local profs hosts
|
||||||
|
profs=$( _bt_list_profiles 2>/dev/null | sed 's/^/@/' )
|
||||||
|
hosts=$( _bt_list_ssh_hosts 2>/dev/null )
|
||||||
|
mapfile -t COMPREPLY < <(compgen -W "$profs $hosts" -- "$cur")
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ ${COMP_CWORD} -eq 5 && "${COMP_WORDS[2]}" == "with" && "${COMP_WORDS[4]}" == "for" ]]; then
|
# For backtunnel-share: position 4 expects "for"
|
||||||
mapfile -t COMPREPLY < <(compgen -W "10m 30m 1h 2h 6h 12h 1d 2d" -- "${cur}")
|
if _bt_is_backtunnel_share && [[ ${COMP_CWORD} -eq 4 && ${COMP_WORDS[2]} == "with" ]]; then
|
||||||
|
mapfile -t COMPREPLY < <(compgen -W "for" -- "$cur")
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ "${COMP_WORDS[2]}" == "from" ]]; then
|
# For backtunnel-share: position 5 duration
|
||||||
COMPREPLY=()
|
if _bt_is_backtunnel_share && [[ ${COMP_CWORD} -eq 5 && ${COMP_WORDS[2]} == "with" && ${COMP_WORDS[4]} == "for" ]]; then
|
||||||
|
mapfile -t COMPREPLY < <(compgen -W "10m 30m 1h 2h 6h 12h 1d 2d" -- "$cur")
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# backtunnel-share: after positionals, propose flags incl. invite
|
# For backtunnel-access: position 3 remote (after "from")
|
||||||
if [[ ${COMP_WORDS[0]} == backtunnel-share && ${COMP_CWORD} -ge 5 ]]; then
|
if _bt_is_backtunnel_access && [[ ${COMP_CWORD} -eq 3 && ${COMP_WORDS[2]} == "from" ]]; then
|
||||||
local opts="-p --tunnel-port -l --local-ssh-port -i --invite --invite-mount --invite-file --qr -h --help"
|
local hosts
|
||||||
mapfile -t COMPREPLY < <(compgen -W "${opts}" -- "${cur}")
|
hosts=$( _bt_list_ssh_hosts 2>/dev/null )
|
||||||
|
mapfile -t COMPREPLY < <(compgen -W "$hosts" -- "$cur")
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# backtunnel-access: after positionals, propose its flags
|
# ----- Option-value specific completions -----
|
||||||
if [[ ${COMP_WORDS[0]} == backtunnel-access && ${COMP_CWORD} -ge 3 ]]; then
|
case "$prev" in
|
||||||
|
-p|--tunnel-port|--port)
|
||||||
|
mapfile -t COMPREPLY < <(compgen -W "2222 4422 5522 6622" -- "$cur")
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
-l|--local-ssh-port)
|
||||||
|
mapfile -t COMPREPLY < <(compgen -W "22 2222" -- "$cur")
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
--invite-mount|-m|--mount-point)
|
||||||
|
compopt -o filenames 2>/dev/null
|
||||||
|
mapfile -t COMPREPLY < <(compgen -d -- "$cur")
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
--allow-key)
|
||||||
|
# complete .pub files
|
||||||
|
compopt -o filenames 2>/dev/null
|
||||||
|
# shellcheck disable=SC2207
|
||||||
|
COMPREPLY=($(compgen -f -- "$cur"))
|
||||||
|
# filter to *.pub only (keep if user is typing a dir)
|
||||||
|
local i out=()
|
||||||
|
for i in "${COMPREPLY[@]}"; do
|
||||||
|
[[ -d "$i" || "$i" == *.pub ]] && out+=("$i")
|
||||||
|
done
|
||||||
|
COMPREPLY=("${out[@]}")
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
--allow-known)
|
||||||
|
mapfile -t COMPREPLY < <(compgen -W "$(_bt_list_authorized_names)" -- "$cur")
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# ----- Generic options after positionals -----
|
||||||
|
if _bt_is_backtunnel_share && [[ ${COMP_CWORD} -ge 5 ]]; then
|
||||||
|
local opts="
|
||||||
|
-p --tunnel-port
|
||||||
|
-l --local-ssh-port
|
||||||
|
-i --invite
|
||||||
|
--invite-mount
|
||||||
|
--invite-file
|
||||||
|
--qr
|
||||||
|
--allow-key
|
||||||
|
--allow-known
|
||||||
|
-h --help"
|
||||||
|
mapfile -t COMPREPLY < <(compgen -W "$opts" -- "$cur")
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if _bt_is_backtunnel_access && [[ ${COMP_CWORD} -ge 3 ]]; then
|
||||||
local opts="-p --port -m --mount-point -h --help"
|
local opts="-p --port -m --mount-point -h --help"
|
||||||
mapfile -t COMPREPLY < <(compgen -W "${opts}" -- "${cur}")
|
mapfile -t COMPREPLY < <(compgen -W "$opts" -- "$cur")
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
COMPREPLY=()
|
COMPREPLY=()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# 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
|
||||||
|
|||||||
198
man/backtunnel.1
198
man/backtunnel.1
@@ -1,7 +1,7 @@
|
|||||||
.TH backtunnel 1 "September 2025" "1.2" "BackTunnel – Reverse SSH Sharing Toolkit"
|
.TH backtunnel 1 "September 2025" "1.3" "BackTunnel – Reverse SSH Sharing Toolkit"
|
||||||
|
|
||||||
.SH NAME
|
.SH NAME
|
||||||
backtunnel-share, backtunnel-access \- Secure reverse SSH folder sharing and access (with profiles)
|
backtunnel-share, backtunnel-access \- Secure reverse SSH folder sharing and access (with profiles, temporary key authorization)
|
||||||
|
|
||||||
.SH SYNOPSIS
|
.SH SYNOPSIS
|
||||||
.B backtunnel-share
|
.B backtunnel-share
|
||||||
@@ -14,151 +14,56 @@ backtunnel-share, backtunnel-access \- Secure reverse SSH folder sharing and acc
|
|||||||
\fBbacktunnel-share\fR starts a reverse SSH tunnel from the local (sharing) machine to a remote,
|
\fBbacktunnel-share\fR starts a reverse SSH tunnel from the local (sharing) machine to a remote,
|
||||||
reachable host. The tunnel exposes the local sshd (typically port 22) on a loopback port on the
|
reachable host. The tunnel exposes the local sshd (typically port 22) on a loopback port on the
|
||||||
remote host using \fBssh -R\fR. 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.
|
|
||||||
|
|
||||||
\fBbacktunnel-access\fR mounts the shared folder from the remote side using \fBsshfs\fR by connecting
|
\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).
|
to \fBlocalhost:<port>\fR on the remote host (the port exposed by \fBbacktunnel-share\fR).
|
||||||
|
|
||||||
|
The tools support \fIprofiles\fR (named remotes and defaults) and a secure workflow without
|
||||||
|
password exchange using temporary, restricted keys.
|
||||||
|
|
||||||
.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). May be provided via profile.
|
Remote port to bind with \fB-R\fR (default: 2222).
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B -l, --local-ssh-port <PORT>
|
.B -l, --local-ssh-port <PORT>
|
||||||
Local sshd port to forward to (default: 22). May be provided via profile.
|
Local sshd port to forward to (default: 22).
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B -i, --invite
|
.B -i, --invite
|
||||||
Print a ready-to-copy access command for the remote side.
|
Print a ready-to-copy access command for the remote side. With \fB--allow-key\fR
|
||||||
|
or \fB--allow-known\fR, the invite includes only the mount command (no auth step).
|
||||||
.TP
|
.TP
|
||||||
.B --invite-mount <PATH>
|
.B --invite-mount <PATH>
|
||||||
Suggested mount point included in the invite text (default: $HOME/remote-rssh). May be provided via profile.
|
Mount point suggested in invite (default: \fI$HOME/remote-rssh\fR).
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B --invite-file <FILE>
|
.B --invite-file <FILE>
|
||||||
Write the invite text (including unmount hint) to the given file.
|
Also write the invite text (with unmount hint) to FILE.
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B --qr
|
.B --qr
|
||||||
Additionally render the invite as a QR code (requires \fBqrencode\fR).
|
Also print a QR code (requires \fBqrencode\fR).
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B -h, --help
|
.B --allow-key <FILE>
|
||||||
Show usage.
|
Temporarily authorize the accessor’s public key for the session by injecting a restricted
|
||||||
|
entry into \fI~/.ssh/authorized_keys\fR and removing it on exit. The line is prefixed with:
|
||||||
|
\fCfrom="127.0.0.1",command="internal-sftp",restrict\fR.
|
||||||
|
.TP
|
||||||
|
.B --allow-known <NAME>
|
||||||
|
Authorize a public key from \fI~/.config/backtunnel/authorized/NAME.pub\fR (same restrictions
|
||||||
|
and auto-removal as \fB--allow-key\fR).
|
||||||
|
|
||||||
.SS backtunnel-access options
|
.SS backtunnel-access options
|
||||||
.TP
|
.TP
|
||||||
.B -p, --port <PORT>
|
.B -p, --port <PORT>
|
||||||
Remote port on which the reverse tunnel listens (default: 2222).
|
Remote port on which the reverse tunnel listens (default: 2222).
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B -m, --mount-point <PATH>
|
.B -m, --mount-point <PATH>
|
||||||
Local mount point for sshfs (default: $HOME/remote-rssh).
|
Local mount point for sshfs (default: \fI$HOME/remote-rssh\fR).
|
||||||
|
|
||||||
.SH ARGUMENTS
|
|
||||||
.TP
|
|
||||||
.B /path/to/folder
|
|
||||||
Path to share (server) or to mount (client).
|
|
||||||
|
|
||||||
.TP
|
|
||||||
.B remoteuser:remotehost
|
|
||||||
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
|
|
||||||
.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).
|
|
||||||
May be provided via the \fB[default]\fR profile; the positional \fI<duration>\fR takes precedence.
|
|
||||||
|
|
||||||
.SH PROFILES
|
.SH PROFILES
|
||||||
BackTunnel can read defaults and named remote definitions from (searched in order):
|
Profiles allow you to use \fB@name\fR instead of \fIuser@host\fR and to set defaults.
|
||||||
.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
|
The configuration search order is:
|
||||||
.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=$HOME/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
|
|
||||||
.TP
|
|
||||||
Share for 2 hours on default ports:
|
|
||||||
.B backtunnel-share
|
|
||||||
/home/user/docs with alice@vps.example.com for 2h
|
|
||||||
|
|
||||||
.TP
|
|
||||||
Share for 1 day, using custom ports:
|
|
||||||
.B backtunnel-share
|
|
||||||
/home/user/docs with alice:vps.example.com for 1d -p 4422 -l 2222
|
|
||||||
|
|
||||||
.TP
|
|
||||||
Share using a profile, then override port:
|
|
||||||
.B backtunnel-share
|
|
||||||
/home/user/docs with @workvps for 6h -p 5500
|
|
||||||
|
|
||||||
.TP
|
|
||||||
Mount with default port and mount point:
|
|
||||||
.B backtunnel-access
|
|
||||||
/home/user/docs from alice@vps.example.com
|
|
||||||
|
|
||||||
.TP
|
|
||||||
Mount with custom port and mount point:
|
|
||||||
.B backtunnel-access
|
|
||||||
/home/user/docs from alice@vps.example.com -p 4422 -m /mnt/alice-docs
|
|
||||||
|
|
||||||
.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.
|
|
||||||
The client connects to \fBlocalhost:<port>\fR from the remote host.
|
|
||||||
Ensure the sharing host's sshd provides a valid SFTP subsystem for sshfs.
|
|
||||||
|
|
||||||
.SH FILES
|
|
||||||
.TP
|
.TP
|
||||||
\fB~/.config/backtunnel/profiles.ini\fR
|
\fB~/.config/backtunnel/profiles.ini\fR
|
||||||
Per-user configuration file containing defaults and named profiles.
|
Per-user configuration file containing defaults and named profiles.
|
||||||
@@ -169,8 +74,63 @@ System-wide default profiles (editable by admins).
|
|||||||
\fB/usr/share/backtunnel/profiles.ini\fR
|
\fB/usr/share/backtunnel/profiles.ini\fR
|
||||||
Packaged example/fallback used when user/system config is absent.
|
Packaged example/fallback used when user/system config is absent.
|
||||||
|
|
||||||
|
An example:
|
||||||
|
.PP
|
||||||
|
.nf
|
||||||
|
[default]
|
||||||
|
tunnel_port=2222
|
||||||
|
invite=true
|
||||||
|
invite_mount=$HOME/remote-rssh
|
||||||
|
|
||||||
|
[work]
|
||||||
|
user=alice
|
||||||
|
host=vps.example.com
|
||||||
|
tunnel_port=4422
|
||||||
|
.fi
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
.PP
|
||||||
|
\fBbacktunnel-share ~/docs with @work for 6h -i --allow-known alice\fR
|
||||||
|
|
||||||
|
.SH SECURITY MODEL
|
||||||
|
The temporary authorized key is restricted with:
|
||||||
|
.PP
|
||||||
|
\fCfrom="127.0.0.1",command="internal-sftp",restrict\fR
|
||||||
|
.PP
|
||||||
|
This limits usage to the reverse tunnel (localhost on the remote end) and SFTP only (no shell,
|
||||||
|
no agent/X11/port forwarding). The entry is removed when the share ends or the process exits.
|
||||||
|
|
||||||
|
.SH EXAMPLES
|
||||||
|
.TP
|
||||||
|
Share for 2 hours on default ports:
|
||||||
|
\fBbacktunnel-share\fR /home/user/docs with alice@vps.example.com for 2h -i
|
||||||
|
.TP
|
||||||
|
Share for 1 day, using custom ports and a known key:
|
||||||
|
\fBbacktunnel-share\fR /home/user/docs with @work for 1d -p 4422 -l 2222 -i --allow-known alice
|
||||||
|
.TP
|
||||||
|
Mount with default port and mount point:
|
||||||
|
\fBbacktunnel-access\fR /home/user/docs from alice@vps.example.com
|
||||||
|
.TP
|
||||||
|
Mount with custom port and mount point:
|
||||||
|
\fBbacktunnel-access\fR /home/user/docs from alice@vps.example.com -p 4422 -m /mnt/alice-docs
|
||||||
|
|
||||||
|
.SH FILES
|
||||||
|
.TP
|
||||||
|
\fB~/.config/backtunnel/profiles.ini\fR
|
||||||
|
Per-user profiles and defaults.
|
||||||
|
.TP
|
||||||
|
\fB/etc/backtunnel/profiles.ini\fR
|
||||||
|
System-wide defaults.
|
||||||
|
.TP
|
||||||
|
\fB/usr/share/backtunnel/profiles.ini\fR
|
||||||
|
Packaged example.
|
||||||
|
.TP
|
||||||
|
\fB~/.config/backtunnel/authorized/*.pub\fR
|
||||||
|
Accessor public keys saved by \fBbacktunnel-authorize\fR for \fB--allow-known\fR.
|
||||||
|
|
||||||
.SH SEE ALSO
|
.SH SEE ALSO
|
||||||
ssh(1), sshfs(1), timeout(1), autossh(1), fusermount(1)
|
ssh(1), sshfs(1), timeout(1), autossh(1), fusermount(1),
|
||||||
|
\fBbacktunnel-keys\fR(1), \fBbacktunnel-auth-setup\fR(1)
|
||||||
|
|
||||||
.SH AUTHOR
|
.SH AUTHOR
|
||||||
Matjaž Mozetič
|
Matjaž Mozetič
|
||||||
|
|||||||
9
scripts/backtunnel-authorize
Normal file
9
scripts/backtunnel-authorize
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
name="${1:-}"
|
||||||
|
file="${2:-}"
|
||||||
|
[[ -n "$name" && -n "$file" && -f "$file" ]] || { echo "Usage: backtunnel-authorize <name> <pubkey-file>"; exit 1; }
|
||||||
|
dir="${XDG_CONFIG_HOME:-$HOME/.config}/backtunnel/authorized"
|
||||||
|
mkdir -p "$dir"
|
||||||
|
install -m 644 "$file" "$dir/$name.pub"
|
||||||
|
echo "Saved: $dir/$name.pub"
|
||||||
32
scripts/backtunnel-keys
Normal file
32
scripts/backtunnel-keys
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# backtunnel-keys: manage accessor-side keys
|
||||||
|
# Usage:
|
||||||
|
# backtunnel-keys print # print (and generate if missing) the public key
|
||||||
|
# backtunnel-keys path # print the private/public key paths
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
KEY="$HOME/.ssh/id_ed25519_backtunnel"
|
||||||
|
PUB="$KEY.pub"
|
||||||
|
|
||||||
|
cmd="${1:-print}"
|
||||||
|
|
||||||
|
case "$cmd" in
|
||||||
|
print)
|
||||||
|
if [[ ! -f "$KEY" ]]; then
|
||||||
|
ssh-keygen -t ed25519 -f "$KEY" -N "" -C "backtunnel" >/dev/null
|
||||||
|
fi
|
||||||
|
if [[ ! -f "$PUB" ]]; then
|
||||||
|
echo "Missing public key $PUB" >&2; exit 1
|
||||||
|
fi
|
||||||
|
cat "$PUB"
|
||||||
|
;;
|
||||||
|
path)
|
||||||
|
echo "private: $KEY"
|
||||||
|
echo "public : $PUB"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Usage: $0 {print|path}" >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
@@ -1,21 +1,34 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
# Copyright (c) 2025. LUXIM d.o.o., Slovenia - Matjaž Mozetič.
|
# Copyright (c) 2025. LUXIM d.o.o., Slovenia - Matjaž Mozetič.
|
||||||
# Licensed under the GNU GPL v3.0
|
# Licensed under the GNU GPL v3.0
|
||||||
|
#
|
||||||
# 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: backtunnel-share /path/to/folder with remoteuser:remotehost for 2h
|
# Syntax:
|
||||||
# Options: -p|--tunnel-port <PORT> -l|--local-ssh-port <PORT>
|
# backtunnel-share /path/to/folder with remoteuser:remotehost for 2h [options]
|
||||||
# -i|--invite [--invite-mount <PATH>] [--invite-file <FILE>] [--qr] -h|--help
|
#
|
||||||
|
# Options:
|
||||||
|
# -p|--tunnel-port <PORT> Remote port to expose with -R (default: 2222)
|
||||||
|
# -l|--local-ssh-port <PORT> Local sshd port to forward to (default: 22)
|
||||||
|
# -i|--invite Print an invite command (and/or QR) for chat
|
||||||
|
# --invite-mount <PATH> Suggested mount point in invite (default: $HOME/remote-rssh)
|
||||||
|
# --invite-file <FILE> Also write invite text (with unmount hint) to FILE
|
||||||
|
# --qr Also render the invite command as a QR code (qrencode)
|
||||||
|
# --allow-key <FILE> Temporarily authorize accessor's public key for this session
|
||||||
|
# --allow-known <NAME> Temporarily authorize ~/.config/backtunnel/authorized/NAME.pub
|
||||||
|
# -h|--help Show help
|
||||||
|
#
|
||||||
|
# Profiles: ~/.config/backtunnel/profiles.ini (overrides /etc/backtunnel/profiles.ini then /usr/share/backtunnel/profiles.ini)
|
||||||
|
# [default] tunnel_port=2222 local_ssh_port=22 invite_mount=$HOME/remote-rssh invite=true qr=false
|
||||||
|
# [alice] user=alice host=vps.example.com tunnel_port=4422
|
||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
# Config search order: user → system → packaged example
|
# ----------------------------
|
||||||
|
# Config discovery
|
||||||
|
# ----------------------------
|
||||||
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"
|
||||||
CONFIG_PKG="/usr/share/backtunnel/profiles.ini"
|
CONFIG_PKG="/usr/share/backtunnel/profiles.ini"
|
||||||
|
|
||||||
# resolve CONFIG_FILE to first existing
|
|
||||||
if [[ -f "$CONFIG_USER" ]]; then
|
if [[ -f "$CONFIG_USER" ]]; then
|
||||||
CONFIG_FILE="$CONFIG_USER"
|
CONFIG_FILE="$CONFIG_USER"
|
||||||
elif [[ -f "$CONFIG_SYS" ]]; then
|
elif [[ -f "$CONFIG_SYS" ]]; then
|
||||||
@@ -24,7 +37,10 @@ else
|
|||||||
CONFIG_FILE="$CONFIG_PKG"
|
CONFIG_FILE="$CONFIG_PKG"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# shellcheck disable=SC2317 # invoked later at runtime; ShellCheck can't see call path
|
# ----------------------------
|
||||||
|
# INI helpers
|
||||||
|
# ----------------------------
|
||||||
|
# shellcheck disable=SC2317
|
||||||
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" '
|
||||||
@@ -78,15 +94,23 @@ profile_apply_defaults() { # set globals if unset; named overrides default
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ----------------------------
|
||||||
|
# Defaults
|
||||||
|
# ----------------------------
|
||||||
|
TUNNEL_PORT=2222 # remote-side port exposed via -R
|
||||||
|
LOCAL_SSH_PORT=22 # local sshd port to forward to
|
||||||
|
DURATION="" # required: e.g. 30m, 2h, 1d
|
||||||
|
|
||||||
TUNNEL_PORT=2222 # remote-side port exposed via -R
|
INVITE=false # print a ready-to-copy access command
|
||||||
LOCAL_SSH_PORT=22 # local sshd port to forward to
|
|
||||||
DURATION="" # required: e.g. 30m, 2h, 1d
|
|
||||||
|
|
||||||
INVITE=false # print a ready-to-copy access command
|
|
||||||
INVITE_MOUNT="$HOME/remote-rssh"
|
INVITE_MOUNT="$HOME/remote-rssh"
|
||||||
INVITE_FILE=""
|
INVITE_FILE=""
|
||||||
QR=false # render invite as terminal QR (requires qrencode)
|
QR=false # render invite as terminal QR (requires qrencode)
|
||||||
|
|
||||||
|
# Accessor authorization (session-scoped)
|
||||||
|
ALLOW_KEY_FILE=""
|
||||||
|
ALLOW_KNOWN_NAME=""
|
||||||
|
AUTHORIZED_STORE="${XDG_CONFIG_HOME:-$HOME/.config}/backtunnel/authorized"
|
||||||
|
ADDED_MARKER_ID="" # unique ID for cleanup removal
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
cat >&2 <<EOF
|
cat >&2 <<EOF
|
||||||
@@ -96,7 +120,7 @@ Usage:
|
|||||||
Positional (required, in order):
|
Positional (required, in order):
|
||||||
/path/to/folder Informational only (the actual folder is mounted by backtunnel-access)
|
/path/to/folder Informational only (the actual folder is mounted by backtunnel-access)
|
||||||
with Literal keyword
|
with Literal keyword
|
||||||
remoteuser:remotehost Or remoteuser@remotehost
|
remoteuser:remotehost Or remoteuser@remotehost (or @profile)
|
||||||
for Literal keyword
|
for Literal keyword
|
||||||
<duration> e.g. 30m, 2h, 1d (passed to 'timeout')
|
<duration> e.g. 30m, 2h, 1d (passed to 'timeout')
|
||||||
|
|
||||||
@@ -107,17 +131,23 @@ Options:
|
|||||||
--invite-mount PATH Mount point suggested in invite (default: ${INVITE_MOUNT})
|
--invite-mount PATH Mount point suggested in invite (default: ${INVITE_MOUNT})
|
||||||
--invite-file FILE Also write the invite text (with unmount hint) to FILE
|
--invite-file FILE Also write the invite text (with unmount hint) to FILE
|
||||||
--qr Also print a QR code (requires 'qrencode')
|
--qr Also print a QR code (requires 'qrencode')
|
||||||
|
|
||||||
|
--allow-key FILE Authorize this public key for this session (restricted SFTP-only, tunnel-only)
|
||||||
|
--allow-known NAME Authorize ~/.config/backtunnel/authorized/NAME.pub
|
||||||
|
|
||||||
-h, --help Show this help
|
-h, --help Show this help
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
$(basename "$0") ~/projects with alice:vps.example.com for 2h
|
$(basename "$0") ~/projects with alice:vps.example.com for 2h
|
||||||
$(basename "$0") ~/projects with alice@vps.example.com for 1d -p 4422 -l 2222
|
$(basename "$0") ~/projects with alice@vps.example.com for 1d -p 4422 -l 2222
|
||||||
$(basename "$0") ~/projects with alice@vps.example.com for 2h -i --qr
|
$(basename "$0") ~/projects with @work for 2h -i --qr --allow-known alice
|
||||||
EOF
|
EOF
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
# --- basic positional parsing ---
|
# ----------------------------
|
||||||
|
# Positional parsing
|
||||||
|
# ----------------------------
|
||||||
[[ $# -lt 5 ]] && usage
|
[[ $# -lt 5 ]] && usage
|
||||||
|
|
||||||
FOLDER=$1
|
FOLDER=$1
|
||||||
@@ -139,7 +169,9 @@ profile_apply_defaults "$profile_name"
|
|||||||
# Expand @name -> user@host for actual ssh use
|
# Expand @name -> user@host for actual ssh use
|
||||||
REMOTE="$(profile_expand_remote "$REMOTE")"
|
REMOTE="$(profile_expand_remote "$REMOTE")"
|
||||||
|
|
||||||
# --- optional flags ---
|
# ----------------------------
|
||||||
|
# Optional flags
|
||||||
|
# ----------------------------
|
||||||
while [[ $# -gt 0 ]]; do
|
while [[ $# -gt 0 ]]; do
|
||||||
case "$1" in
|
case "$1" in
|
||||||
-p|--tunnel-port)
|
-p|--tunnel-port)
|
||||||
@@ -170,6 +202,16 @@ while [[ $# -gt 0 ]]; do
|
|||||||
QR=true
|
QR=true
|
||||||
shift
|
shift
|
||||||
;;
|
;;
|
||||||
|
--allow-key)
|
||||||
|
[[ $# -lt 2 ]] && usage
|
||||||
|
ALLOW_KEY_FILE=$2
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--allow-known)
|
||||||
|
[[ $# -lt 2 ]] && usage
|
||||||
|
ALLOW_KNOWN_NAME=$2
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
-h|--help)
|
-h|--help)
|
||||||
usage
|
usage
|
||||||
;;
|
;;
|
||||||
@@ -180,13 +222,17 @@ while [[ $# -gt 0 ]]; do
|
|||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
# --- validate duration (timeout supports s,m,h,d) ---
|
# ----------------------------
|
||||||
|
# Duration validation
|
||||||
|
# ----------------------------
|
||||||
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
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# --- split remote user/host ---
|
# ----------------------------
|
||||||
|
# Split remote user/host
|
||||||
|
# ----------------------------
|
||||||
REMOTE_USER="" REMOTE_HOST=""
|
REMOTE_USER="" REMOTE_HOST=""
|
||||||
if [[ "$REMOTE" == *:* ]]; then
|
if [[ "$REMOTE" == *:* ]]; then
|
||||||
REMOTE_USER=${REMOTE%%:*}
|
REMOTE_USER=${REMOTE%%:*}
|
||||||
@@ -195,30 +241,120 @@ elif [[ "$REMOTE" == *"@"* ]]; then
|
|||||||
REMOTE_USER=${REMOTE%%@*}
|
REMOTE_USER=${REMOTE%%@*}
|
||||||
REMOTE_HOST=${REMOTE#*@}
|
REMOTE_HOST=${REMOTE#*@}
|
||||||
else
|
else
|
||||||
echo "Invalid remote format. Use remoteuser:remotehost or remoteuser@remotehost" >&2
|
echo "Invalid remote format. Use remoteuser:remotehost or remoteuser@remotehost or @profile" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# --- deps check ---
|
# ----------------------------
|
||||||
|
# Deps check
|
||||||
|
# ----------------------------
|
||||||
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; }
|
||||||
command -v tail >/dev/null 2>&1 || { echo "tail not found."; exit 1; } # used for --pid wait
|
command -v tail >/dev/null 2>&1 || { echo "tail not found."; exit 1; }
|
||||||
|
|
||||||
|
# ----------------------------
|
||||||
|
# Accessor temporary authorization
|
||||||
|
# ----------------------------
|
||||||
|
restrict_key_line() { # usage: restrict_key_line <pubkey_text>
|
||||||
|
local pk="$1"
|
||||||
|
# Normalize
|
||||||
|
pk="${pk//$'\r'/}"
|
||||||
|
pk="${pk//$'\n'/}"
|
||||||
|
if [[ "$pk" != ssh-* ]]; then
|
||||||
|
echo "Provided key does not look like an OpenSSH public key." >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
printf 'from="127.0.0.1",command="internal-sftp",restrict %s' "$pk"
|
||||||
|
}
|
||||||
|
|
||||||
|
add_temp_authorized_key() {
|
||||||
|
local pubkey_text="$1"
|
||||||
|
local ak="$HOME/.ssh/authorized_keys"
|
||||||
|
# SC2155: Declare and assign separately to avoid masking return values.
|
||||||
|
# shellcheck disable=SC2155
|
||||||
|
local marker
|
||||||
|
marker="BACKTUNNEL-TEMP-$(date +%s)-$$-$RANDOM"
|
||||||
|
local start="# ${marker} START"
|
||||||
|
local end="# ${marker} END"
|
||||||
|
|
||||||
|
mkdir -p "$HOME/.ssh"
|
||||||
|
touch "$ak"
|
||||||
|
chmod 700 "$HOME/.ssh"
|
||||||
|
chmod 600 "$ak"
|
||||||
|
|
||||||
|
local restricted
|
||||||
|
restricted="$(restrict_key_line "$pubkey_text")" || return 1
|
||||||
|
|
||||||
|
{
|
||||||
|
echo "$start"
|
||||||
|
echo "$restricted"
|
||||||
|
echo "$end"
|
||||||
|
} >> "$ak"
|
||||||
|
|
||||||
|
ADDED_MARKER_ID="$marker"
|
||||||
|
}
|
||||||
|
|
||||||
|
# shellcheck disable=SC2317
|
||||||
|
remove_temp_authorized_key() {
|
||||||
|
local ak="$HOME/.ssh/authorized_keys"
|
||||||
|
[[ -z "${ADDED_MARKER_ID:-}" ]] && return 0
|
||||||
|
[[ -f "$ak" ]] || return 0
|
||||||
|
awk -v m="$ADDED_MARKER_ID" '
|
||||||
|
$0 ~ "# " m " START" {skip=1; next}
|
||||||
|
$0 ~ "# " m " END" {skip=0; next}
|
||||||
|
!skip {print}
|
||||||
|
' "$ak" > "${ak}.btnew" && mv "${ak}.btnew" "$ak"
|
||||||
|
}
|
||||||
|
|
||||||
|
ACCESSOR_PUBKEY_TEXT=""
|
||||||
|
|
||||||
|
if [[ -n "$ALLOW_KNOWN_NAME" ]]; then
|
||||||
|
f="${AUTHORIZED_STORE}/${ALLOW_KNOWN_NAME}.pub"
|
||||||
|
if [[ -f "$f" ]]; then
|
||||||
|
ACCESSOR_PUBKEY_TEXT="$(cat "$f")"
|
||||||
|
else
|
||||||
|
echo "Known accessor key not found: $f" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -n "$ALLOW_KEY_FILE" ]]; then
|
||||||
|
if [[ -f "$ALLOW_KEY_FILE" ]]; then
|
||||||
|
ACCESSOR_PUBKEY_TEXT="$(cat "$ALLOW_KEY_FILE")"
|
||||||
|
else
|
||||||
|
echo "Given key file not found: $ALLOW_KEY_FILE" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -n "$ACCESSOR_PUBKEY_TEXT" ]]; then
|
||||||
|
add_temp_authorized_key "$ACCESSOR_PUBKEY_TEXT"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ----------------------------
|
||||||
|
# Banner
|
||||||
|
# ----------------------------
|
||||||
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}"
|
||||||
echo " remote bind port : ${TUNNEL_PORT} (on ${REMOTE_HOST})"
|
echo " remote bind port : ${TUNNEL_PORT} (on ${REMOTE_HOST})"
|
||||||
echo " remote user : ${REMOTE_USER}"
|
echo " remote user : ${REMOTE_USER}"
|
||||||
echo " duration : ${DURATION}"
|
echo " duration : ${DURATION}"
|
||||||
|
if [[ -n "$ACCESSOR_PUBKEY_TEXT" ]]; then
|
||||||
|
echo " accessor key : temporarily authorized (restricted)"
|
||||||
|
fi
|
||||||
echo
|
echo
|
||||||
|
|
||||||
# --- print invite (optional) ---
|
# ----------------------------
|
||||||
|
# Invite (optional)
|
||||||
|
# If accessor key was pre-authorized, we can omit the auth-setup line.
|
||||||
|
# ----------------------------
|
||||||
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}'"
|
||||||
# Also provide a one-time auth bootstrap that installs a restricted, tunnel-only SFTP key
|
|
||||||
AUTH_CMD="backtunnel-auth-setup -p ${TUNNEL_PORT} ${REMOTE_USER}@localhost"
|
|
||||||
|
|
||||||
INVITE_TEXT=$(
|
if [[ -z "$ACCESSOR_PUBKEY_TEXT" ]]; then
|
||||||
cat <<EOT
|
AUTH_CMD="backtunnel-auth-setup -p ${TUNNEL_PORT} ${REMOTE_USER}@localhost"
|
||||||
|
INVITE_TEXT=$(
|
||||||
|
cat <<EOT
|
||||||
|
|
||||||
# 1) (one-time) install a tunnel-only, SFTP-only key via the reverse tunnel:
|
# 1) (one-time) install a tunnel-only, SFTP-only key via the reverse tunnel:
|
||||||
${AUTH_CMD}
|
${AUTH_CMD}
|
||||||
@@ -228,13 +364,25 @@ ${INVITE_CMD}
|
|||||||
|
|
||||||
# Unmount when done:
|
# Unmount when done:
|
||||||
fusermount -u '${INVITE_MOUNT}' || fusermount3 -u '${INVITE_MOUNT}'
|
fusermount -u '${INVITE_MOUNT}' || fusermount3 -u '${INVITE_MOUNT}'
|
||||||
|
|
||||||
EOT
|
EOT
|
||||||
)
|
)
|
||||||
|
else
|
||||||
|
AUTH_CMD=""
|
||||||
|
INVITE_TEXT=$(
|
||||||
|
cat <<EOT
|
||||||
|
|
||||||
|
# Mount the share:
|
||||||
|
${INVITE_CMD}
|
||||||
|
|
||||||
|
# Unmount when done:
|
||||||
|
fusermount -u '${INVITE_MOUNT}' || fusermount3 -u '${INVITE_MOUNT}'
|
||||||
|
EOT
|
||||||
|
)
|
||||||
|
fi
|
||||||
|
|
||||||
echo "🔗 Invite (copy to chat):"
|
echo "🔗 Invite (copy to chat):"
|
||||||
echo "------------------------------------------------------------"
|
echo "------------------------------------------------------------"
|
||||||
echo "${AUTH_CMD}"
|
[[ -n "$AUTH_CMD" ]] && echo "${AUTH_CMD}"
|
||||||
echo "${INVITE_CMD}"
|
echo "${INVITE_CMD}"
|
||||||
echo "------------------------------------------------------------"
|
echo "------------------------------------------------------------"
|
||||||
|
|
||||||
@@ -259,25 +407,30 @@ echo " backtunnel-access '${FOLDER}' from ${REMOTE_USER}@${REMOTE_HOST} -p $
|
|||||||
echo "Listener will appear on: ${REMOTE_HOST}:${TUNNEL_PORT} (run access there)."
|
echo "Listener will appear on: ${REMOTE_HOST}:${TUNNEL_PORT} (run access there)."
|
||||||
echo "To stop sharing early: press Ctrl+C in this window."
|
echo "To stop sharing early: press Ctrl+C in this window."
|
||||||
|
|
||||||
# --- optional pre-flight: warn if remote loopback port already in use (best-effort) ---
|
# ----------------------------
|
||||||
|
# Pre-flight: warn if remote loopback port already in use (best-effort)
|
||||||
|
# ----------------------------
|
||||||
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
|
||||||
echo "⚠️ Port ${TUNNEL_PORT} on remote 127.0.0.1 appears in use; choose another with -p." >&2
|
echo "⚠️ Port ${TUNNEL_PORT} on remote 127.0.0.1 appears in use; choose another with -p." >&2
|
||||||
# Uncomment the next line to make it a hard failure:
|
# You may 'exit 1' here if you prefer a hard failure
|
||||||
# exit 1
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# --- run ssh in background; trap Ctrl-C/TERM/EXIT to stop it cleanly ---
|
# ----------------------------
|
||||||
|
# Cleanup & run SSH
|
||||||
|
# ----------------------------
|
||||||
SSH_PID=""
|
SSH_PID=""
|
||||||
|
|
||||||
# shellcheck disable=SC2317 # cleanup is invoked indirectly via trap
|
# shellcheck disable=SC2317
|
||||||
cleanup() {
|
cleanup() {
|
||||||
|
# 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
|
||||||
echo "⏹️ Stopping share..."
|
echo "⏹️ Stopping share..."
|
||||||
kill -TERM "$SSH_PID" 2>/dev/null || true
|
kill -TERM "$SSH_PID" 2>/dev/null || true
|
||||||
# give ssh a moment to exit gracefully
|
|
||||||
wait "$SSH_PID" 2>/dev/null || true
|
wait "$SSH_PID" 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
|
# remove temporary authorized key if we added one
|
||||||
|
remove_temp_authorized_key
|
||||||
}
|
}
|
||||||
trap cleanup INT TERM EXIT
|
trap cleanup INT TERM EXIT
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# BackTunnel installer (terminal/TUI service menus)
|
# BackTunnel installer (scripts, man page, completions, Dolphin service menus)
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
PREFIX=${PREFIX:-/usr}
|
PREFIX=${PREFIX:-/usr}
|
||||||
@@ -12,60 +12,89 @@ KSVC5="$DESTDIR$PREFIX/share/kservices5/ServiceMenus"
|
|||||||
APPDIR="$DESTDIR$PREFIX/share/applications"
|
APPDIR="$DESTDIR$PREFIX/share/applications"
|
||||||
BCOMP="$DESTDIR$PREFIX/share/bash-completion/completions"
|
BCOMP="$DESTDIR$PREFIX/share/bash-completion/completions"
|
||||||
SHARE_DIR="$DESTDIR$PREFIX/share/backtunnel"
|
SHARE_DIR="$DESTDIR$PREFIX/share/backtunnel"
|
||||||
|
ETC_DIR="$DESTDIR/etc/backtunnel"
|
||||||
|
|
||||||
say() { printf '[BackTunnel] %s\n' "$*"; }
|
say() { printf '[BackTunnel] %s\n' "$@"; }
|
||||||
|
|
||||||
say "Installing to PREFIX=${PREFIX} DESTDIR=${DESTDIR}"
|
# --- Create dirs ---
|
||||||
|
install -d -m 755 "$BINDIR" "$MANDIR" "$KIO_SM" "$KSVC5" "$BCOMP" "$SHARE_DIR" "$APPDIR"
|
||||||
|
install -d -m 755 "$ETC_DIR"
|
||||||
|
|
||||||
# --- Binaries (CLI) ---
|
# --- Install binaries ---
|
||||||
install -Dm755 "scripts/backtunnel-share" "$BINDIR/backtunnel-share"
|
for f in \
|
||||||
install -Dm755 "scripts/backtunnel-access" "$BINDIR/backtunnel-access"
|
scripts/backtunnel-share \
|
||||||
install -Dm755 "scripts/backtunnel-auth-setup" "$BINDIR/backtunnel-auth-setup"
|
scripts/backtunnel-access \
|
||||||
|
scripts/backtunnel-share-gui \
|
||||||
# --- GUI wrappers (optional) ---
|
scripts/backtunnel-access-gui \
|
||||||
install -Dm755 "scripts/backtunnel-share-gui" "$BINDIR/backtunnel-share-gui"
|
scripts/backtunnel-open-term \
|
||||||
install -Dm755 "scripts/backtunnel-access-gui" "$BINDIR/backtunnel-access-gui"
|
scripts/backtunnel-share-tui \
|
||||||
|
scripts/backtunnel-access-tui \
|
||||||
# --- Terminal opener + TUIs (used by service menus) ---
|
scripts/backtunnel-keys \
|
||||||
install -Dm755 "scripts/backtunnel-open-term" "$BINDIR/backtunnel-open-term"
|
scripts/backtunnel-authorize \
|
||||||
install -Dm755 "scripts/backtunnel-share-tui" "$BINDIR/backtunnel-share-tui"
|
scripts/backtunnel-auth-setup
|
||||||
install -Dm755 "scripts/backtunnel-access-tui" "$BINDIR/backtunnel-access-tui"
|
do
|
||||||
|
if [[ -f "$f" ]]; then
|
||||||
|
install -m 755 "$f" "$BINDIR/$(basename "$f")"
|
||||||
|
say "Installed $(basename "$f")"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
# --- Man page ---
|
# --- Man page ---
|
||||||
install -Dm644 "man/backtunnel.1" "$MANDIR/backtunnel.1"
|
if [[ -f man/backtunnel.1 ]]; then
|
||||||
|
install -m 644 man/backtunnel.1 "$MANDIR/backtunnel.1"
|
||||||
|
say "Installed man page"
|
||||||
|
fi
|
||||||
|
|
||||||
# --- Bash completions (install for both command names) ---
|
# --- Bash completion (same file serves both commands) ---
|
||||||
install -Dm644 "completions/backtunnel.bash" "$BCOMP/backtunnel-share"
|
if [[ -f completions/backtunnel.bash ]]; then
|
||||||
install -Dm644 "completions/backtunnel.bash" "$BCOMP/backtunnel-access"
|
install -m 644 completions/backtunnel.bash "$BCOMP/backtunnel-share"
|
||||||
|
install -m 644 completions/backtunnel.bash "$BCOMP/backtunnel-access"
|
||||||
|
say "Installed bash completions"
|
||||||
|
fi
|
||||||
|
|
||||||
# --- Dolphin service menus (Plasma 6) ---
|
# --- Dolphin service menus (Plasma 6 primary, Plasma 5 fallback) ---
|
||||||
install -Dm644 "servicemenus/backtunnel_share.desktop" "$KIO_SM/backtunnel_share.desktop"
|
if [[ -f servicemenus/backtunnel_share.desktop ]]; then
|
||||||
install -Dm644 "servicemenus/backtunnel_access.desktop" "$KIO_SM/backtunnel_access.desktop"
|
install -m 644 servicemenus/backtunnel_share.desktop "$KIO_SM/backtunnel_share.desktop"
|
||||||
|
install -m 644 servicemenus/backtunnel_share.desktop "$KSVC5/backtunnel_share.desktop"
|
||||||
# --- Plasma 5 legacy path (harmless if unused) ---
|
say "Installed Dolphin: Share"
|
||||||
install -Dm644 "servicemenus/backtunnel_share.desktop" "$KSVC5/backtunnel_share.desktop"
|
fi
|
||||||
install -Dm644 "servicemenus/backtunnel_access.desktop" "$KSVC5/backtunnel_access.desktop"
|
if [[ -f servicemenus/backtunnel_access.desktop ]]; then
|
||||||
|
install -m 644 servicemenus/backtunnel_access.desktop "$KIO_SM/backtunnel_access.desktop"
|
||||||
|
install -m 644 servicemenus/backtunnel_access.desktop "$KSVC5/backtunnel_access.desktop"
|
||||||
|
say "Installed Dolphin: Access"
|
||||||
|
fi
|
||||||
|
|
||||||
# --- Optional desktop launcher ---
|
# --- Optional desktop launcher ---
|
||||||
if [[ -f "desktop/backtunnel.desktop" ]]; then
|
if [[ -f desktop/backtunnel.desktop ]]; then
|
||||||
install -Dm644 "desktop/backtunnel.desktop" "$APPDIR/backtunnel.desktop"
|
install -m 644 desktop/backtunnel.desktop "$APPDIR/backtunnel.desktop"
|
||||||
|
say "Installed desktop launcher"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# --- Example profiles (system default + packaged fallback) ---
|
# --- Profiles defaults ---
|
||||||
install -Dm644 "docs/profiles.ini.example" "$DESTDIR/etc/backtunnel/profiles.ini"
|
if [[ -f docs/profiles.ini.example ]]; then
|
||||||
install -Dm644 "docs/profiles.ini.example" "$SHARE_DIR/profiles.ini"
|
install -m 644 docs/profiles.ini.example "$SHARE_DIR/profiles.ini"
|
||||||
|
# install system default if not present (don’t overwrite local admin config)
|
||||||
# --- Refresh desktop/KDE cache (best-effort) ---
|
if [[ ! -f "$ETC_DIR/profiles.ini" ]]; then
|
||||||
if command -v update-desktop-database >/dev/null 2>&1; then
|
install -m 644 docs/profiles.ini.example "$ETC_DIR/profiles.ini"
|
||||||
say "Refreshing desktop database..."
|
say "Installed default /etc/backtunnel/profiles.ini"
|
||||||
update-desktop-database -q || true
|
else
|
||||||
|
say "Preserved existing /etc/backtunnel/profiles.ini"
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
if command -v kbuildsycoca6 >/dev/null 2>&1; then
|
|
||||||
say "Rebuilding KDE sycoca (Plasma 6)..."
|
# --- Refresh desktop/KDE cache (best-effort; skip during DESTDIR packaging) ---
|
||||||
kbuildsycoca6 --noincremental >/dev/null 2>&1 || true
|
if [[ -z "${DESTDIR}" ]]; then
|
||||||
elif command -v kbuildsycoca5 >/dev/null 2>&1; then
|
if command -v update-desktop-database >/dev/null 2>&1; then
|
||||||
say "Rebuilding KDE sycoca (Plasma 5)..."
|
say "Refreshing desktop database..."
|
||||||
kbuildsycoca5 --noincremental >/dev/null 2>&1 || true
|
update-desktop-database -q || true
|
||||||
|
fi
|
||||||
|
if command -v kbuildsycoca6 >/dev/null 2>&1; then
|
||||||
|
say "Rebuilding KDE sycoca (Plasma 6)..."
|
||||||
|
kbuildsycoca6 --noincremental >/dev/null 2>&1 || true
|
||||||
|
elif command -v kbuildsycoca5 >/dev/null 2>&1; then
|
||||||
|
say "Rebuilding KDE sycoca (Plasma 5)..."
|
||||||
|
kbuildsycoca5 --noincremental >/dev/null 2>&1 || true
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
say "Install complete."
|
say "Install complete."
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# BackTunnel uninstaller (with optional PURGE=1 to remove shared defaults)
|
# BackTunnel uninstaller (use PURGE=1 to remove /usr/share/backtunnel and /etc/backtunnel)
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
PREFIX=${PREFIX:-/usr}
|
PREFIX=${PREFIX:-/usr}
|
||||||
DESTDIR=${DESTDIR:-}
|
DESTDIR=${DESTDIR:-}
|
||||||
PURGE=${PURGE:-0} # set PURGE=1 to remove /usr/share/backtunnel and /etc/backtunnel
|
PURGE=${PURGE:-0}
|
||||||
|
|
||||||
BINDIR="$DESTDIR$PREFIX/bin"
|
BINDIR="$DESTDIR$PREFIX/bin"
|
||||||
MANDIR="$DESTDIR$PREFIX/share/man/man1"
|
MANDIR="$DESTDIR$PREFIX/share/man/man1"
|
||||||
@@ -15,73 +15,45 @@ BCOMP="$DESTDIR$PREFIX/share/bash-completion/completions"
|
|||||||
SHARE_DIR="$DESTDIR$PREFIX/share/backtunnel"
|
SHARE_DIR="$DESTDIR$PREFIX/share/backtunnel"
|
||||||
ETC_DIR="$DESTDIR/etc/backtunnel"
|
ETC_DIR="$DESTDIR/etc/backtunnel"
|
||||||
|
|
||||||
say() { printf '[BackTunnel] %s\n' "$*"; }
|
say() { printf '[BackTunnel] %s\n' "$@"; }
|
||||||
say_warn() { printf '[BackTunnel] WARN: %s\n' "$*" >&2; }
|
# ... existing code ...
|
||||||
|
rm -f \
|
||||||
|
"$BINDIR/backtunnel-share" \
|
||||||
|
"$BINDIR/backtunnel-access" \
|
||||||
|
"$BINDIR/backtunnel-share-gui" \
|
||||||
|
"$BINDIR/backtunnel-access-gui" \
|
||||||
|
"$BINDIR/backtunnel-open-term" \
|
||||||
|
"$BINDIR/backtunnel-share-tui" \
|
||||||
|
"$BINDIR/backtunnel-access-tui" \
|
||||||
|
"$BINDIR/backtunnel-keys" \
|
||||||
|
"$BINDIR/backtunnel-authorize" \
|
||||||
|
"$BINDIR/backtunnel-auth-setup" \
|
||||||
|
"$BINDIR/backtunnel-init"
|
||||||
|
|
||||||
# Portable directory prune: remove dir if empty, then move up until stop boundary
|
|
||||||
prune_dir() {
|
|
||||||
local dir="${1%/}"
|
|
||||||
local stop="${2%/}"
|
|
||||||
while [[ -n "$dir" && "$dir" != "/" && "$dir" != "$stop" ]]; do
|
|
||||||
rmdir "$dir" 2>/dev/null || break
|
|
||||||
dir="$(dirname "$dir")"
|
|
||||||
done
|
|
||||||
}
|
|
||||||
|
|
||||||
# Friendly notice if uninstalling the live system without root
|
|
||||||
if [[ -z "$DESTDIR" && ${EUID:-$(id -u)} -ne 0 ]]; then
|
|
||||||
say_warn "Running without root; some files may not be removed due to permissions."
|
|
||||||
fi
|
|
||||||
|
|
||||||
say "Uninstalling from PREFIX=${PREFIX} DESTDIR=${DESTDIR} (PURGE=${PURGE})"
|
|
||||||
|
|
||||||
# --- Remove binaries ---
|
|
||||||
rm -f "$BINDIR/backtunnel-share" \
|
|
||||||
"$BINDIR/backtunnel-access" \
|
|
||||||
"$BINDIR/backtunnel-auth-setup" \
|
|
||||||
"$BINDIR/backtunnel-share-gui" \
|
|
||||||
"$BINDIR/backtunnel-access-gui" \
|
|
||||||
"$BINDIR/backtunnel-open-term" \
|
|
||||||
"$BINDIR/backtunnel-share-tui" \
|
|
||||||
"$BINDIR/backtunnel-access-tui"
|
|
||||||
# Optionally remove helper that may be present on some installs
|
|
||||||
rm -f "$BINDIR/backtunnel-init"
|
|
||||||
|
|
||||||
# --- Man page ---
|
|
||||||
rm -f "$MANDIR/backtunnel.1"
|
rm -f "$MANDIR/backtunnel.1"
|
||||||
|
|
||||||
# --- Bash completions ---
|
rm -f \
|
||||||
rm -f "$BCOMP/backtunnel-share" \
|
"$BCOMP/backtunnel-share" \
|
||||||
"$BCOMP/backtunnel-access"
|
"$BCOMP/backtunnel-access"
|
||||||
|
|
||||||
# --- Dolphin service menus (Plasma 6 + legacy) ---
|
rm -f \
|
||||||
rm -f "$KIO_SM/backtunnel_share.desktop" \
|
"$KIO_SM/backtunnel_share.desktop" \
|
||||||
"$KIO_SM/backtunnel_access.desktop" \
|
"$KIO_SM/backtunnel_access.desktop" \
|
||||||
"$KSVC5/backtunnel_share.desktop" \
|
"$KSVC5/backtunnel_share.desktop" \
|
||||||
"$KSVC5/backtunnel_access.desktop"
|
"$KSVC5/backtunnel_access.desktop"
|
||||||
|
|
||||||
# --- Optional desktop launcher ---
|
|
||||||
rm -f "$APPDIR/backtunnel.desktop"
|
rm -f "$APPDIR/backtunnel.desktop"
|
||||||
|
# ... existing code ...
|
||||||
# --- Shared defaults (only if PURGE=1) ---
|
# Optional purge of packaged defaults
|
||||||
if [[ "$PURGE" = "1" ]]; then
|
if [[ "$PURGE" = "1" ]]; then
|
||||||
say "Purging shared defaults under $SHARE_DIR and $ETC_DIR"
|
rm -rf "$SHARE_DIR" || true
|
||||||
rm -f "$SHARE_DIR/profiles.ini" 2>/dev/null || true
|
rm -rf "$ETC_DIR" || true
|
||||||
rm -f "$ETC_DIR/profiles.ini" 2>/dev/null || true
|
say "Purged /usr/share/backtunnel and /etc/backtunnel"
|
||||||
# Remove directories if empty (and prune empty parents up to safe boundaries)
|
|
||||||
prune_dir "$SHARE_DIR" "$DESTDIR$PREFIX/share"
|
|
||||||
prune_dir "$ETC_DIR" "$DESTDIR/etc"
|
|
||||||
else
|
|
||||||
# Optionally clean up empty share dir if package manager removed files already
|
|
||||||
prune_dir "$SHARE_DIR" "$DESTDIR$PREFIX/share"
|
|
||||||
say "Keeping shared defaults: $SHARE_DIR/ and $ETC_DIR/ (set PURGE=1 to remove)"
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# --- Refresh desktop/KDE cache (best-effort, skip during packaging) ---
|
# Refresh caches only if not in DESTDIR (packaging)
|
||||||
if [[ -z "$DESTDIR" ]]; then
|
if [[ -z "${DESTDIR}" ]]; then
|
||||||
if command -v update-desktop-database >/dev/null 2>&1; then
|
command -v update-desktop-database >/dev/null 2>&1 && update-desktop-database -q || true
|
||||||
update-desktop-database -q || true
|
|
||||||
fi
|
|
||||||
if command -v kbuildsycoca6 >/dev/null 2>&1; then
|
if command -v kbuildsycoca6 >/dev/null 2>&1; then
|
||||||
kbuildsycoca6 --noincremental >/dev/null 2>&1 || true
|
kbuildsycoca6 --noincremental >/dev/null 2>&1 || true
|
||||||
elif command -v kbuildsycoca5 >/dev/null 2>&1; then
|
elif command -v kbuildsycoca5 >/dev/null 2>&1; then
|
||||||
|
|||||||
Reference in New Issue
Block a user