fix: stabilization pass — fail-closed auth, ai flag validation, lint/config/security hardening, regression tests
gates / consistency-and-conventions (push) Successful in 26s

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.
This commit is contained in:
Your Name
2026-09-06 07:25:44 -04:00
parent 528b16676e
commit d817c37652
69 changed files with 5161 additions and 406 deletions
+9 -6
View File
@@ -9,15 +9,18 @@
provider_name() { printf 'Local llama.cpp'; }
provider_default_model() {
local port="${LLAMACPP_PORT:-8088}"
# Honor LLAMACPP_HOST — must match the address the server binds (default
# 127.0.0.1); otherwise the adapter talks to a different host than the one
# the server actually listens on.
local host="${LLAMACPP_HOST:-127.0.0.1}" port="${LLAMACPP_PORT:-8088}"
local model
model="$(curl -sf "http://127.0.0.1:$port/v1/models" 2>/dev/null | jq -r '.data[0].id // empty')"
model="$(curl -sf "http://$host:$port/v1/models" 2>/dev/null | jq -r '.data[0].id // empty')"
[ -n "$model" ] && printf '%s' "$model" || printf '(no model loaded)'
}
# $1=model $2=messages JSON ({"messages":[{role,content}]}) $3=optional system prompt
provider_generate() {
local model="$1" messages="$2" system="${3:-}" port="${LLAMACPP_PORT:-8088}"
local model="$1" messages="$2" system="${3:-}" host="${LLAMACPP_HOST:-127.0.0.1}" port="${LLAMACPP_PORT:-8088}"
local body resp code body_out
# Build messages array with optional system prompt
if [ -n "$system" ]; then
@@ -28,7 +31,7 @@ provider_generate() {
fi
body="$(printf '%s' "$body" | jq -nc --arg m "$model" --argjson msgs "$body" \
'{model:$m, messages:$msgs, stream:false}')"
resp="$(curl -sS -m 120 -X POST "http://127.0.0.1:$port/v1/chat/completions" \
resp="$(curl -sS -m 120 -X POST "http://$host:$port/v1/chat/completions" \
-H "Content-Type: application/json" \
--write-out $'\n%{http_code}' \
--data "$body")" || { echo "request failed (curl exit $?)" >&2; return 1; }
@@ -43,8 +46,8 @@ provider_generate() {
# $1=current default model → stdout=formatted model list
provider_models_list() {
local model="$1" port="${LLAMACPP_PORT:-8088}" resp code body
resp="$(curl -sf "http://127.0.0.1:$port/v1/models" \
local model="$1" host="${LLAMACPP_HOST:-127.0.0.1}" port="${LLAMACPP_PORT:-8088}" resp code body
resp="$(curl -sf "http://$host:$port/v1/models" \
--write-out $'\n%{http_code}')" || { echo "server not running" >&2; return 1; }
code="${resp##*$'\n'}"
body="${resp%$'\n'*}"
+49
View File
@@ -307,6 +307,55 @@ cfg_scope_keys() {
return 0
}
# ── Canonical env-file loader ────────────────────────────────────
# load_env_file <file> [scope]
# The one shared KEY=VALUE config loader for every pos tool (D-D).
# <file> env file path; a bare basename (no '/') is resolved under
# ${CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/linux_post_install}/,
# so callers may pass "ai.env", "$CONFIG_DIR/ai.env", or any path.
# [scope] optional label (informational only; reserved, not used).
# Reads KEY=VALUE lines, skipping blank and '#' comment lines; strips a
# trailing CR from every value (CRLF files parse cleanly); trims one pair
# of surrounding quotes. Exports each key, but ONLY when the variable is
# not already set in the environment, so an exported env var always wins
# over the file. Full precedence contract, matching the historic behavior
# of every migrated tool:
# CLI flags > environment > config file > defaults
# (CLI flags are applied by each tool's own arg parser, defaults via
# ${VAR:-default} at declaration — the loader implements the middle step.)
# Loaded keys are APPENDED to the global LOADED_ENV_KEYS array so callers
# can tell which values came from the file (a caller that needs only one
# file's set resets LOADED_ENV_KEYS=() before the call). The loader never
# creates files and never chmods — chmod-600 semantics stay with cfg_write
# and the tools' own writers. A missing/unreadable file is a quiet no-op.
#
# NOTE: load_env_file supersedes lib/common.sh's load_system_env(), which is
# functionally identical (env-wins export loop). common.sh deliberately does
# NOT source this file — new tools should use load_env_file; the three legacy
# load_system_env callers keep working unchanged.
load_env_file() {
local f="$1" _scope="${2:-}" k v
if ! declare -p LOADED_ENV_KEYS &>/dev/null 2>&1; then
LOADED_ENV_KEYS=()
fi
case "$f" in
*/*) : ;; # full path as given
*) f="${CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/linux_post_install}/$f" ;;
esac
[ -f "$f" ] || return 0
while IFS='=' read -r k v; do
[ -n "$k" ] || continue
case "$k" in \#*) continue ;; esac
v="${v//$'\r'/}"
v="${v%\"}"; v="${v#\"}"; v="${v%\'}"; v="${v#\'}"
if [ -z "${!k:-}" ]; then
export "$k"="$v"
LOADED_ENV_KEYS+=("$k")
fi
done < <(grep -E '^[A-Z_]+=' "$f" || true)
return 0
}
# Current value of a key in an env file (file is the source of truth, never sourced).
cfg_value() {
local file="$1" key="$2" v
+12 -23
View File
@@ -21,36 +21,25 @@ source "$(dirname "${BASH_SOURCE[0]}")/../lib/user-timers-lib.sh" 2>/dev/null \
|| source "$(dirname "$0")/../lib/user-timers-lib.sh" 2>/dev/null \
|| source "$(dirname "$0")/user-timers-lib.sh"
# Canonical config read/write + env loader (cfg_value/cfg_write/load_env_file).
source "$(dirname "${BASH_SOURCE[0]}")/../lib/config-ui.sh" 2>/dev/null \
|| source "$(dirname "${BASH_SOURCE[0]}")/config-ui.sh" 2>/dev/null \
|| source "$(dirname "$0")/../lib/config-ui.sh" 2>/dev/null \
|| source "$(dirname "$0")/config-ui.sh"
# Per-plugin last-run state (rc + timestamp + first output line).
LAST_RUN_DIR="${LAST_RUN_DIR:-$HOME/.local/share/linux_post_install/entertainment/last}"
# ── Config file helpers (file is the source of truth, never sourced) ──
# ── Config file helpers (thin wrappers over lib/config-ui.sh — the
# canonical read/write API; same semantics, CRLF-safe, chmod 600) ──
config_value() {
local k="$1" v
[ -f "$CONFIG_FILE" ] || return 0
v="$(sed -n "s|^${k}=||p" "$CONFIG_FILE" | tail -1)"
v="${v%\"}"; v="${v#\"}"; v="${v%\'}"; v="${v#\'}"
printf '%s' "$v"
local k="$1"
cfg_value "$CONFIG_FILE" "$k"
}
write_config_key() {
local key="$1" val="$2" tmp
val="${val//$'\r'/}"
val="${val%%$'\n'*}"
mkdir -p "$CONFIG_DIR"
if [ "$val" = "-" ]; then
[ -f "$CONFIG_FILE" ] || return 0
tmp="$(mktemp)"
grep -v "^${key}=" "$CONFIG_FILE" >"$tmp" || true
mv "$tmp" "$CONFIG_FILE"
chmod 600 "$CONFIG_FILE"
return 0
fi
tmp="$(mktemp)"
grep -v "^${key}=" "$CONFIG_FILE" 2>/dev/null >"$tmp" || true
printf '%s="%s"\n' "$key" "$val" >>"$tmp"
mv "$tmp" "$CONFIG_FILE"
chmod 600 "$CONFIG_FILE"
local key="$1" val="$2"
cfg_write "$CONFIG_FILE" "$key" "$val"
}
# ── Plugin lookup ──────────────────────────────────────────────────
+3 -1
View File
@@ -56,7 +56,9 @@ share_require_bin() {
# rc 0 reachable within 3s · rc 1 unreachable/no-route. Message policy (targeted
# hints, firewall wording) belongs to the caller.
share_port_probe() {
timeout 3 bash -c "exec 3<>/dev/tcp/${1}/${2}" 2>/dev/null
# host/port are positional args ($1/$2), never interpolated into the
# command source — a hostile host string stays a literal argument.
timeout 3 bash -c 'exec 3<>/dev/tcp/$1/$2' _ "${1}" "${2}" 2>/dev/null
}
# ── systemd unit state probe ───────────────────────────────────