Files
Linux_post_install/bin/pos
T
2026-08-05 05:47:45 -04:00

209 lines
6.9 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
self="$(cd "$(dirname "$0")" && pwd)"
# ── Colors (auto-off when not a TTY) ───────────────────────────
if [ -t 1 ]; then
BOLD=$(tput bold 2>/dev/null || true)
CYAN=$(tput setaf 6 2>/dev/null || true)
DIM=$(tput dim 2>/dev/null || true)
RESET=$(tput sgr0 2>/dev/null || true)
else
BOLD=""; CYAN=""; DIM=""; RESET=""
fi
# ── Collect available commands ─────────────────────────────────
_pos_commands() {
local cmds=()
local f
for f in "$self"/pos-*; do
[ -x "$f" ] || continue
local name="${f##*/pos-}"
cmds+=("$name")
done
echo "${cmds[*]}"
}
# ── Category list (sorted, filename-derived) ─────────────────────
_pos_category_list() {
local -A cats=()
local cmd cat f
for f in "$self"/pos-*; do
[ -x "$f" ] || continue
cmd="${f##*/pos-}"
cat="${cmd%%-*}"
[ "$cat" = "$cmd" ] && continue
cats["$cat"]+="${cmd#*-} "
done
local c line subs joined
for c in $(printf '%s\n' "${!cats[@]}" | sort); do
subs=(${cats[$c]})
joined="$(IFS='|'; echo "${subs[*]}")"
printf " %-14s%s\n" "$c" "$joined"
done
}
# ── Category helpers ────────────────────────────────────────────
_pos_category_exists() {
local cat="${1:-}" f
[ -n "$cat" ] || return 1
for f in "$self"/pos-"$cat"-*; do
[ -x "$f" ] && return 0
done
return 1
}
_pos_category_help() {
local cat="$1" sub subs=()
local f
for f in "$self"/pos-"$cat"-*; do
[ -x "$f" ] || continue
subs+=("${f##*/pos-$cat-}")
done
echo "pos $cat$cat tools"
echo
echo "USAGE"
echo " pos $cat <command> [args]"
echo
echo "COMMANDS"
printf " %s\n" "${subs[@]}"
echo
echo "Run 'pos $cat <command> --help' for details on a command."
exit 0
}
# ── Help text ──────────────────────────────────────────────────
usage() {
local cats
cats="$(_pos_category_list)"
cat <<EOF
pos — Personal OS Toolkit
A unified CLI for network, docker, media, system,
and SSH tools on Debian/Ubuntu.
USAGE
pos <category> <command> [args]
CATEGORIES
$cats
EXAMPLES
pos network ip Show interfaces, routes, public IP
pos network checkport 10.0.0.1:80 Check if a TCP port is open
pos network scan 192.168.1.0/24 Fast parallel ping sweep
pos network hotspot Launch wihotspot-gui
pos docker ps List containers (health, IPs, ports)
pos docker compose ls List available ScaleTail services
pos docker compose up jellyfin Deploy a service with Tailscale
pos docker health One-glance health dashboard
pos media mp3 <url> Download audio as MP3
pos media mp4 <url> Download video as MP4
pos system firewall Interactive UFW manager
pos system backup /srv/project Encrypted (AES-256) folder snapshot
pos system backup --service Pick a folder from /srv or ~/srv
pos ssh load-keys Load all SSH keys into agent
pos usb server --ls List USB devices + connected clients
pos communication telegram --send "Backup done"
Send a Telegram message
pos docker vbox create lab1 Create disposable Docker VM
pos docker vbox create lab1 --dir . Create VM using current directory
pos docker vbox enter lab1 Shell into a Docker VM
pos docker vbox ls List Docker VMs
HELP
pos <category> --help Show a category's commands
pos help <command> Show help for a command
pos --help Show this help
LEGACY WRAPPERS
wr-ip, wr-checkport, wr-scan-ping, wr-docker,
wr-compose, wr-ufw, mp3, mp4, vbox, ssh-load-all
still work and forward to pos.
EOF
exit 0
}
# ── Handle --help / -h ─────────────────────────────────────────
case "${1:-}" in
-h|--help|"") usage ;;
esac
# ── pos help <command> ─────────────────────────────────────────
if [ "${1:-}" = "help" ]; then
shift
[ $# -eq 0 ] && usage
cmd="pos-$*"
cmd="${cmd// /-}"
if command -v "$cmd" &>/dev/null; then
exec "$cmd" --help
fi
[ -x "$self/$cmd" ] && exec "$self/$cmd" --help
echo "pos: unknown command '$*'" >&2
echo "Run 'pos --help' to see available commands." >&2
exit 1
fi
# ── Dispatch ───────────────────────────────────────────────────
args=("$@")
n=${#args[@]}
# ── Category help: pos <category> [--help] ─────────────────────
if _pos_category_exists "${args[0]:-}"; then
if [ "$n" -eq 1 ] || { [ "$n" -eq 2 ] && { [ "${args[1]}" = "-h" ] || [ "${args[1]}" = "--help" ]; }; }; then
_pos_category_help "${args[0]}"
fi
fi
# ── Logging setup ──────────────────────────────────────────────
LOG_DIR="$HOME/.local/share/linux_post_install/logs"
mkdir -p "$LOG_DIR" 2>/dev/null || true
CMD_SAFE=$(echo "${args[*]}" | tr ' /' '__')
LOG_FILE="$LOG_DIR/$(date +%Y%m%d_%H%M%S)_pos_${CMD_SAFE}.log"
MAIN_LOG="$LOG_DIR/pos.log"
log_cmd() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $* → exit $2" >> "$MAIN_LOG"; }
# Commands that read from stdin interactively — only log invocation
INTERACTIVE_CMDS="system-firewall media-mp4 system-backup usb-server"
for ((i=n-1; i>=0; i--)); do
cmd="pos"
for ((j=0; j<=i; j++)); do
cmd="${cmd}-${args[$j]}"
done
resolved=""
if command -v "$cmd" &>/dev/null; then
resolved="$cmd"
elif [ -x "$self/$cmd" ]; then
resolved="$self/$cmd"
fi
[ -z "$resolved" ] && continue
sub="${cmd#pos-}"
if [[ " $INTERACTIVE_CMDS " == *" $sub "* ]]; then
# Interactive: log invocation only, then exec normally
log_cmd "pos $*" "" 0
exec "$resolved" "${args[@]:i+1}"
else
# Non-interactive: capture full output
"$resolved" "${args[@]:i+1}" 2>&1 | tee "$LOG_FILE"
rc=${PIPESTATUS[0]}
log_cmd "pos $*" "$LOG_FILE" "$rc"
exit "$rc"
fi
done
echo "pos: unknown command '${args[0]}'" >&2
echo "Run 'pos --help' to see available commands." >&2
exit 1