Files
Your Name d817c37652
gates / consistency-and-conventions (push) Successful in 26s
fix: stabilization pass — fail-closed auth, ai flag validation, lint/config/security hardening, regression tests
17-point code-level audit executed via Explorer->Architect->Builder->Tester->Reviewer;
Reviewer accepted (APPROVE_WITH_NOTES; 3 block-list items resolved):

- security: telegram sender-owner AND-gate + TELEGRAM_OWNER_ID, matrix
  MATRIX_ROOM_ID fail-closed, gpg --passphrase-fd 3 (no argv secret),
  /dev/tcp positional-arg form (checkport/smb-client/share-lib/NET_PROBE),
  eval deny-by-default + --no-command-execution carried by both chat bridges,
  tty-gated --trust; config/{telegram,matrix}.env reference templates
- ai: all ExecStart flags validated against installed llama.cpp
  (requested->error, default->omit+warn, CONFIG_REQUESTED_FLAGS); single-file
  hf download failure rc=1 + no .hf-meta; LLAMACPP_HOST coherent;
  POS_SUBCMDS + metadata gaps closed
- tooling: lint-conventions Bash-native rewrite (~24-30x faster, rules and
  output byte-identical, :num restored); pos system uninstall covers all 12
  libs + scale-tail + flags dir + systemd user units (|| true) + plugin
  markers; anchored .bash_completion/.bashrc removal replaces sed -i '/pos/d'
- config: canonical load_env_file in lib/config-ui.sh (CRLF strip, env-wins,
  XDG, LOADED_ENV_KEYS); 9 tools migrated; entertainment-lib collapsed to
  wrappers; docker-compose deliberately unmigrated (source semantics)
- tests: first committed regression suite — tests/run-tests.sh zero-dep
  runner + make test; 12 files / 179 checks / 0 skip / ~52s; hard skip
  contract; systemd-analyze verify on generated unit PASS

Verified: make gen idempotent; make check green; make lint 0 FAIL, 0 WARN;
make test green; bash -n clean; git diff --check clean. Audit deliverables +
agent reports + AGENT_TODO Done entry included.
2026-09-06 07:25:44 -04:00

173 lines
5.4 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
source "$(dirname "$0")/../lib/common.sh"
usage() {
cat <<EOF
Usage: bash apps/install.sh [OPTIONS] [app ...]
Install or uninstall optional desktop applications.
Options:
--all Apply action to all available apps without prompting
--uninstall Uninstall the selected apps instead of installing
-h, --help Show this help message
Examples:
bash apps/install.sh # interactive install selection
bash apps/install.sh --all # install everything
bash apps/install.sh brave vscode # install specific apps
bash apps/install.sh --uninstall # interactive uninstall selection
bash apps/install.sh --uninstall --all # uninstall everything
bash apps/install.sh --uninstall brave # uninstall specific apps
EOF
exit 0
}
ALL=0
UNINSTALL=0
POSITIONAL=()
while [[ $# -gt 0 ]]; do
case "$1" in
--all) ALL=1; shift ;;
--uninstall) UNINSTALL=1; shift ;;
-h|--help) usage ;;
-*) err "Unknown option: $1" ;;
*) POSITIONAL+=("$1"); shift ;;
esac
done
APPS_DIR="$(dirname "$0")"
# ── Category display names ─────────────────────────────────────
declare -A CAT_NAMES=(
[ai]="AI / ML"
[browsers]="Browsers"
[development]="Development"
[media]="Media"
[networking]="Networking / VPN"
[remote-access]="Remote Access"
[system]="System / Virtualization"
[utilities]="Utilities"
)
# ── Discover categories and apps ───────────────────────────────
CATEGORIES=()
declare -A CAT_APPS
for cat_dir in "$APPS_DIR"/*/; do
[ -d "$cat_dir" ] || continue
cat_name=$(basename "$cat_dir")
# Skip non-app directories
[[ "$cat_name" == "install" ]] && continue
# Check if directory has any .sh files
has_apps=0
for f in "$cat_dir"*.sh; do
[ -f "$f" ] && has_apps=1 && break
done
[ "$has_apps" -eq 0 ] && continue
CATEGORIES+=("$cat_name")
CAT_APPS["$cat_name"]=""
for f in "$cat_dir"*.sh; do
[ -f "$f" ] || continue
app_name=$(basename "$f" .sh)
CAT_APPS["$cat_name"]+=" $app_name"
done
done
# ── Helper: find which category an app belongs to ──────────────
find_app_category() {
local app="$1"
for cat in "${CATEGORIES[@]}"; do
for a in ${CAT_APPS[$cat]}; do
[ "$a" == "$app" ] && { echo "$cat"; return 0; }
done
done
return 1
}
SELECTED=()
# ── Mode 1: specific apps requested on command line ────────────
if [ "${#POSITIONAL[@]}" -gt 0 ]; then
for req in "${POSITIONAL[@]}"; do
cat=$(find_app_category "$req" 2>/dev/null) && {
SELECTED+=("$req")
} || {
warn "Unknown app: $req (skipping)"
}
done
# ── Mode 2: --all ─────────────────────────────────────────────
elif [ "$ALL" -eq 1 ]; then
for cat in "${CATEGORIES[@]}"; do
for app in ${CAT_APPS[$cat]}; do
SELECTED+=("$app")
done
done
# ── Mode 3: interactive TUI ───────────────────────────────────
else
section "Optional Applications"
if [ "$UNINSTALL" -eq 1 ]; then
echo "Select apps to uninstall (y/n for each):"
else
echo "Select apps to install (y/n for each):"
fi
echo
for cat in "${CATEGORIES[@]}"; do
display="${CAT_NAMES[$cat]:-$cat}"
echo " $display"
for app in ${CAT_APPS[$cat]}; do
if [ "$UNINSTALL" -eq 1 ]; then
read -rp " Remove ${app}? [y/N]: " yn
else
read -rp " Install ${app}? [y/N]: " yn
fi
if [[ "$yn" =~ ^[Yy] ]]; then
SELECTED+=("$app")
fi
done
echo
done
fi
# ── Apply action to selected apps ─────────────────────────────
[ "${#SELECTED[@]}" -eq 0 ] && { warn "No apps selected"; exit 0; }
echo
if [ "$UNINSTALL" -eq 1 ]; then
section "Uninstalling ${SELECTED[*]}"
else
section "Installing ${SELECTED[*]}"
fi
timer_start
count=1
total=${#SELECTED[@]}
for app in "${SELECTED[@]}"; do
cat=$(find_app_category "$app")
step "$count" "$total" "$app"
if [ "$UNINSTALL" -eq 1 ]; then
bash "$APPS_DIR/$cat/$app.sh" uninstall
else
bash "$APPS_DIR/$cat/$app.sh"
fi
count=$((count + 1))
echo
done
echo
if [ "$UNINSTALL" -eq 1 ]; then
echo "${GREEN}════════════════════════════════════════════${RESET}"
echo "${GREEN} Apps uninstalled ($(timer_stop))${RESET}"
echo "${GREEN}════════════════════════════════════════════${RESET}"
else
echo "${GREEN}════════════════════════════════════════════${RESET}"
echo "${GREEN} Apps installed ($(timer_stop))${RESET}"
echo "${GREEN}════════════════════════════════════════════${RESET}"
fi