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.
202 lines
6.1 KiB
Bash
202 lines
6.1 KiB
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)
|
|
|
|
# ---------- 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() {
|
|
local cur prev
|
|
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
|
|
compopt -o filenames 2>/dev/null
|
|
mapfile -t COMPREPLY < <(compgen -d -- "$cur")
|
|
return 0
|
|
fi
|
|
|
|
# Offer "with"/"from" at position 2 depending on command
|
|
if [[ ${COMP_CWORD} -eq 2 ]]; then
|
|
if _bt_is_backtunnel_share; then
|
|
mapfile -t COMPREPLY < <(compgen -W "with" -- "$cur")
|
|
else
|
|
mapfile -t COMPREPLY < <(compgen -W "from" -- "$cur")
|
|
fi
|
|
return 0
|
|
fi
|
|
|
|
# For backtunnel-share: position 3 (REMOTE | @profile)
|
|
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
|
|
fi
|
|
|
|
# For backtunnel-share: position 4 expects "for"
|
|
if _bt_is_backtunnel_share && [[ ${COMP_CWORD} -eq 4 && ${COMP_WORDS[2]} == "with" ]]; then
|
|
mapfile -t COMPREPLY < <(compgen -W "for" -- "$cur")
|
|
return 0
|
|
fi
|
|
|
|
# For backtunnel-share: position 5 duration
|
|
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
|
|
fi
|
|
|
|
# For backtunnel-access: position 3 remote (after "from")
|
|
if _bt_is_backtunnel_access && [[ ${COMP_CWORD} -eq 3 && ${COMP_WORDS[2]} == "from" ]]; then
|
|
local hosts
|
|
hosts=$( _bt_list_ssh_hosts 2>/dev/null )
|
|
mapfile -t COMPREPLY < <(compgen -W "$hosts" -- "$cur")
|
|
return 0
|
|
fi
|
|
|
|
# ----- Option-value specific completions -----
|
|
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"
|
|
mapfile -t COMPREPLY < <(compgen -W "$opts" -- "$cur")
|
|
return 0
|
|
fi
|
|
|
|
COMPREPLY=()
|
|
}
|
|
|
|
# Register for both commands
|
|
complete -F _backtunnel_complete backtunnel-share
|
|
complete -F _backtunnel_complete backtunnel-access
|