Files
Linux_post_install/bin/pos
T
Your Name 528b16676e
gates / consistency-and-conventions (push) Successful in 2m16s
fix: review-driven hardening of pos ai hf/server + llamacpp provider
Adversarial review of the AI tools (commits 387f23f/0856b25) found 2
BLOCKING + 5 REQUIRED defects; all fixed:

- pos-ai-hf --include/--exclude: bash-case glob filtering (array-safe,
  no jq regex interpolation, composes gguf->filename->include->exclude)
- pos-ai-server: ExecStart rebuilt as single-line properly-quoted command
  (systemd_quote for executable + model path; systemd-analyze verify rc=0)
- --branch/--revision aliased (last wins), dead BRANCH variable removed
- parallel download drains all jobs: per-pid wait, honest
  'X of Y files, N failed' summary, rc=1 on partial failure, no .hf-meta
  for half-downloaded models, EXIT-trap temp cleanup
- detect_llama_version guarded; validate_requested_flags errors on
  unsupported explicit flags with version-aware message
- pos ai hf cache [status|clear]: real implementation, fail-closed confirm
- new bin/pos-ai-llamacpp thin forwarder + llamacpp shorthand in bin/pos-ai
  (pos ai llamacpp <subcmd> = pos ai --provider llamacpp <subcmd>)
- docs synced: bin/pos-ai usage(), DOC/POS.md AI_PROVIDER row, howto/ai.md
  (adapter list, --provider backends, shorthand, providers table); gen
  regenerated (tree/dispatch/completions)

Verified: bash -n all bin/pos*; make gen idempotent; make check green;
make lint 0 FAIL, 0 WARN. Reviewer acceptance: APPROVE_WITH_NOTES
(0 REQUIRED). Audit deliverables + agent reports included for context.
2026-09-06 03:45:53 -04:00

303 lines
12 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) ─────────────────────
# Nested sub-tools (pos-<cat>-<a>-<b> where pos-<cat>-<a> exists) are
# omitted from the cheat-sheet line — they are shown under their parent tool.
_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 subs joined s s2 top=() is_nested
for c in $(printf '%s\n' "${!cats[@]}" | sort); do
subs=(${cats[$c]})
top=()
for s in "${subs[@]}"; do
is_nested=0
for s2 in "${subs[@]}"; do
[ "$s2" = "$s" ] && continue
if [[ "$s" == "$s2-"* ]]; then is_nested=1; break; fi
done
[ "$is_nested" -eq 1 ] || top+=("$s")
done
joined="$(IFS='|'; echo "${top[*]}")"
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" s s2 extra d sc deps
# Lazy registry load — plain dispatch paths never pay the scan cost.
source "$self/../lib/registry.sh" 2>/dev/null || source "$self/registry.sh"
reg_scan "$self"
local files=()
local t
while IFS= read -r t; do
[ -n "$t" ] || continue
files+=("${t#"$cat"-}")
done < <(reg_tools_in "$cat")
mapfile -t files < <(printf '%s\n' "${files[@]}" | sort -u)
local -A desc subcmds _deps
for s in "${files[@]}"; do
d="$(reg_lookup "$cat-$s" desc)"
[ -n "$d" ] && desc["$s"]="$d"
sc="$(reg_lookup "$cat-$s" subcmds)"
[ -n "$sc" ] && subcmds["$s"]="$sc"
deps="$(reg_lookup "$cat-$s" deps)"
[ -n "$deps" ] && _deps["$s"]="$deps"
done
# Nested sub-tools: pos-<cat>-<a>-<b> lists "b" under <a>.
local s2 extra
for s in "${files[@]}"; do
for s2 in "${files[@]}"; do
[ "$s2" = "$s" ] && continue
if [[ "$s2" == "$s-"* ]]; then
extra="${s2#$s-}"
case " ${subcmds[$s]:-} " in
*" $extra "*) ;;
*) subcmds["$s"]="${subcmds[$s]:-}${subcmds[$s]:+ }$extra" ;;
esac
fi
done
done
echo "pos $cat$cat tools"
echo
echo "USAGE"
echo " pos $cat <command> [args]"
echo
echo "COMMANDS"
local is_nested c
for s in "${files[@]}"; do
is_nested=0
for s2 in "${files[@]}"; do
[ "$s2" = "$s" ] && continue
if [[ "$s" == "$s2-"* ]]; then is_nested=1; break; fi
done
[ "$is_nested" -eq 1 ] && continue
printf ' %-28s%s\n' "$s" "${desc[$s]:-}"
[ -n "${_deps[$s]:-}" ] && printf ' [deps: %s]\n' "${_deps[$s]}"
for c in ${subcmds[$s]:-}; do
printf ' %s %s\n' "$s" "$c"
done
done
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, SSH,
USB, communication, and entertainment 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 network download add <url> Enqueue an aria2 download (--tmux = live view)
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 docker stack Containers grouped by compose stack
pos docker stack -a Include stopped/exited containers
pos media mp3 <url> Download audio as MP3
pos media mp4 <url> Download video as MP4
pos media ytsync sync Incremental YouTube channel sync
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 share nfs server share /mnt/hdd Share a folder via NFS
pos share nfs client persist 10.0.0.5:/srv/data /mnt/nfs/data
Persistent NFS mount (systemd)
pos share smb server share /mnt/hdd media --users bob
Share a folder via SMB (Samba)
pos share smb client persist //10.0.0.5/media /mnt/smb/media bob
Persistent SMB mount (automount)
pos ssh load-keys Load all SSH keys into agent
pos share usb server --ls List USB devices + connected clients
pos communication telegram sender send "Backup done"
Send a Telegram message
pos communication telegram listener Edit the /command → bash map
pos communication telegram listener --enable
Install the Telegram bot listener
pos communication matrix sender send "Backup done"
Send a Matrix room message
pos communication matrix sender login --user @me:example.org
Fetch a Matrix access token
pos communication matrix listener Edit the /command → bash map
pos entertainment send weather Fetch Open-Meteo weather → Telegram
pos entertainment send joke --print Preview a joke locally (no send)
pos entertainment enable weather 5m Auto-send weather to Telegram every 5 min
pos entertainment status Show enabled plugins + next run
pos system schedule list Show scheduled jobs + last results
pos system schedule enable Start all job timers (per-job intervals)
pos system schedule migrate Convert old event.env rules to jobs
pos ai gemini ask "Explain DNS in one line"
Ask Google Gemini a one-shot question
pos ai gemini chat Interactive multi-turn chat
pos ai gemini models List available Gemini models
pos config telegram Edit Telegram config interactively
pos config Pick a config scope to edit
pos tree Show the command tree (categories + subcommands)
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="docker-compose docker-vbox network-hotspot system-firewall media-mp4 media-sync system-backup system-uninstall share-usb-server share-smb-server share-smb-client share-nfs-client share-nfs-server communication-telegram-listener communication-matrix-listener ai ai-gemini ai-openrouter ai-llamacpp ai-alias system-schedule entertainment-config config"
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