625569485f
Adds `fn doctor` read-only diagnostic command with subcommands artefacts, services, sync, uses-functions, unused, and --json flag for agents. Each subcommand wraps a registry function in functions/infra/. New functions: - artefact_doctor, services_status, pc_locations_drift, audit_uses_functions, find_unused_functions (Go diagnostics) - backup_sqlite_db, rotate_backups, wait_for_http, wait_for_port, port_kill, tail_journal, pre_commit_hook_install (bash utilities) - notify_telegram (Go HTTP) - backup_all pipeline (tag launcher) Plus prior session leftovers (scan_secrets_in_dirty, append_diary_entry, git utilities, http_session_cookie_middleware, compile/full-git pipelines). Fixes pc_locations_drift filepath.Join bug with absolute dir_path. Documents fn doctor in CLAUDE.md, .claude/rules/fn_doctor.md (rule 23), docs/architecture.md, CHANGELOG.md (2026-05-07), and diary entry. First fn doctor uses-functions run found drift in 7/12 apps (deuda para sincronizar app.md con imports reales). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
91 lines
2.6 KiB
Bash
91 lines
2.6 KiB
Bash
#!/usr/bin/env bash
|
|
# port_kill — Mata los procesos que escuchan en un puerto TCP dado.
|
|
|
|
port_kill() {
|
|
local port="${1:-}"
|
|
local signal="${2:-TERM}"
|
|
|
|
# Validar puerto
|
|
if [[ -z "$port" ]] || ! [[ "$port" =~ ^[0-9]+$ ]] || (( port < 1 || port > 65535 )); then
|
|
echo "ERROR: puerto invalido: '$port' (debe ser 1-65535)" >&2
|
|
return 2
|
|
fi
|
|
|
|
# Verificar herramienta disponible
|
|
local tool=""
|
|
if command -v lsof &>/dev/null; then
|
|
tool="lsof"
|
|
elif command -v fuser &>/dev/null; then
|
|
tool="fuser"
|
|
else
|
|
echo "ERROR: se requiere lsof o fuser (ninguno disponible)" >&2
|
|
return 5
|
|
fi
|
|
|
|
# Obtener PIDs
|
|
local pids=()
|
|
if [[ "$tool" == "lsof" ]]; then
|
|
mapfile -t pids < <(lsof -ti "tcp:${port}" -sTCP:LISTEN 2>/dev/null)
|
|
else
|
|
mapfile -t pids < <(fuser -n tcp "${port}" 2>/dev/null | tr ' ' '\n' | grep -E '^[0-9]+$')
|
|
fi
|
|
|
|
if (( ${#pids[@]} == 0 )); then
|
|
echo "NO_PROCESS port=${port}"
|
|
return 0
|
|
fi
|
|
|
|
# Matar cada PID
|
|
local permission_denied=0
|
|
for pid in "${pids[@]}"; do
|
|
[[ -z "$pid" ]] && continue
|
|
if kill "-${signal}" "$pid" 2>/dev/null; then
|
|
echo "KILLED pid=${pid} signal=${signal} port=${port}"
|
|
else
|
|
echo "PERMISSION_DENIED pid=${pid}" >&2
|
|
permission_denied=1
|
|
fi
|
|
done
|
|
|
|
if (( permission_denied )); then
|
|
return 4
|
|
fi
|
|
|
|
# Esperar 2s y verificar
|
|
sleep 2
|
|
local remaining=()
|
|
if [[ "$tool" == "lsof" ]]; then
|
|
mapfile -t remaining < <(lsof -ti "tcp:${port}" -sTCP:LISTEN 2>/dev/null)
|
|
else
|
|
mapfile -t remaining < <(fuser -n tcp "${port}" 2>/dev/null | tr ' ' '\n' | grep -E '^[0-9]+$')
|
|
fi
|
|
|
|
if (( ${#remaining[@]} == 0 )); then
|
|
return 0
|
|
fi
|
|
|
|
# Segundo intento con KILL si signal != KILL
|
|
if [[ "$signal" != "KILL" && "$signal" != "9" ]]; then
|
|
for pid in "${remaining[@]}"; do
|
|
[[ -z "$pid" ]] && continue
|
|
kill -KILL "$pid" 2>/dev/null && echo "KILLED pid=${pid} signal=KILL port=${port}"
|
|
done
|
|
sleep 1
|
|
local still=()
|
|
if [[ "$tool" == "lsof" ]]; then
|
|
mapfile -t still < <(lsof -ti "tcp:${port}" -sTCP:LISTEN 2>/dev/null)
|
|
else
|
|
mapfile -t still < <(fuser -n tcp "${port}" 2>/dev/null | tr ' ' '\n' | grep -E '^[0-9]+$')
|
|
fi
|
|
if (( ${#still[@]} > 0 )); then
|
|
echo "ERROR: puerto ${port} sigue ocupado tras SIGKILL" >&2
|
|
return 3
|
|
fi
|
|
else
|
|
echo "ERROR: puerto ${port} sigue ocupado tras SIGKILL" >&2
|
|
return 3
|
|
fi
|
|
|
|
return 0
|
|
}
|