Files
BackTunnel/completions/backtunnel.bash
sysadminmatmoz fcbd6514cc 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.
2025-09-20 17:17:26 +02:00

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