mirror of
https://github.com/boxpositron/absolute-dotfiles.git
synced 2026-10-24 08:23:19 +00:00
feat(docker-nuke): wire up main, dry-run mode, and docs
This commit is contained in:
+83
-4
@@ -23,7 +23,6 @@ TERM_WAIT=5 # seconds to wait after SIGTERM before SIGKILL
|
|||||||
# entry snapshot never knew about, so this must be a real cap, never an
|
# entry snapshot never knew about, so this must be a real cap, never an
|
||||||
# unbounded loop, or a respawning Docker could hang this tool forever.
|
# unbounded loop, or a respawning Docker could hang this tool forever.
|
||||||
KILL_RETRY_ROUNDS=3
|
KILL_RETRY_ROUNDS=3
|
||||||
# shellcheck disable=SC2034 # consumed by Task 8's main() when it calls wait_for_daemon "$RESTART_TIMEOUT"; not yet referenced by this task's code
|
|
||||||
RESTART_TIMEOUT=90 # seconds to wait for the daemon to come back
|
RESTART_TIMEOUT=90 # seconds to wait for the daemon to come back
|
||||||
POLL_INTERVAL=2 # seconds between daemon health polls
|
POLL_INTERVAL=2 # seconds between daemon health polls
|
||||||
SAMPLE_DURATION=2 # seconds of stack sampling per process
|
SAMPLE_DURATION=2 # seconds of stack sampling per process
|
||||||
@@ -577,7 +576,6 @@ parse_args() {
|
|||||||
OPT_DRY_RUN=0
|
OPT_DRY_RUN=0
|
||||||
OPT_DEEP=0
|
OPT_DEEP=0
|
||||||
while [ "$#" -gt 0 ]; do
|
while [ "$#" -gt 0 ]; do
|
||||||
# shellcheck disable=SC2034 # OPT_FORCE, OPT_NO_RESTART, OPT_DRY_RUN are read by main once it is wired up. OPT_DEEP is already read by nuke_docker but stays under this same directive since it covers the whole case statement.
|
|
||||||
case "$1" in
|
case "$1" in
|
||||||
-f|--force) OPT_FORCE=1 ;;
|
-f|--force) OPT_FORCE=1 ;;
|
||||||
-n|--no-restart) OPT_NO_RESTART=1 ;;
|
-n|--no-restart) OPT_NO_RESTART=1 ;;
|
||||||
@@ -597,13 +595,94 @@ parse_args() {
|
|||||||
|
|
||||||
# --- entrypoint -------------------------------------------------------------
|
# --- entrypoint -------------------------------------------------------------
|
||||||
|
|
||||||
|
confirm_healthy_nuke() {
|
||||||
|
local info
|
||||||
|
info="$(run_timeout "$HEALTH_TIMEOUT" docker info \
|
||||||
|
--format '{{.ServerVersion}} ({{.ContainersRunning}} running)' 2>/dev/null || true)"
|
||||||
|
log "docker-nuke: Docker looks healthy: ${info:-unknown}"
|
||||||
|
log "Nuking will stop running containers."
|
||||||
|
printf 'Proceed anyway? [y/N] '
|
||||||
|
local reply=""
|
||||||
|
read -r reply || true
|
||||||
|
case "$reply" in
|
||||||
|
y|Y|yes|YES) return 0 ;;
|
||||||
|
*) log "docker-nuke: aborted"; return 2 ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
dry_run_report() {
|
||||||
|
local pids
|
||||||
|
pids="$(docker_pids | tr '\n' ' ')"
|
||||||
|
log "docker-nuke: DRY RUN — no changes made"
|
||||||
|
if [ -z "$pids" ]; then
|
||||||
|
log " would kill: (no Docker processes found)"
|
||||||
|
else
|
||||||
|
log " would kill: $pids"
|
||||||
|
# shellcheck disable=SC2086
|
||||||
|
ps -o pid,stat,%cpu,%mem,etime,comm -p $pids 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
log " would dump to: $DUMP_ROOT/<timestamp>"
|
||||||
|
if [ "$OPT_NO_RESTART" -eq 1 ]; then
|
||||||
|
log " would not restart Docker (--no-restart)"
|
||||||
|
else
|
||||||
|
log " would restart Docker and wait up to ${RESTART_TIMEOUT}s"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
main() {
|
main() {
|
||||||
local rc=0
|
local rc=0
|
||||||
parse_args "$@" || rc=$?
|
parse_args "$@" || rc=$?
|
||||||
if [ "$rc" -eq 3 ]; then return 0; fi
|
if [ "$rc" -eq 3 ]; then return 0; fi
|
||||||
if [ "$rc" -ne 0 ]; then return "$rc"; fi
|
if [ "$rc" -ne 0 ]; then return "$rc"; fi
|
||||||
log "docker-nuke: not yet implemented"
|
|
||||||
return 0
|
if [ "$OPT_DRY_RUN" -eq 1 ]; then
|
||||||
|
dry_run_report
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if docker_healthy && [ "$OPT_FORCE" -eq 0 ]; then
|
||||||
|
confirm_healthy_nuke || return 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
local started elapsed verdict
|
||||||
|
started="$(date +%s)"
|
||||||
|
|
||||||
|
run_dump
|
||||||
|
nuke_docker || true
|
||||||
|
|
||||||
|
if [ "$OPT_NO_RESTART" -eq 1 ]; then
|
||||||
|
verdict="killed, not restarted (--no-restart)"
|
||||||
|
elapsed=$(( $(date +%s) - started ))
|
||||||
|
write_summary "$verdict" "$elapsed"
|
||||||
|
prune_dumps
|
||||||
|
log "docker-nuke: $verdict"
|
||||||
|
log "docker-nuke: diagnostics in $DUMP_DIR"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
start_docker
|
||||||
|
if wait_for_daemon "$RESTART_TIMEOUT"; then
|
||||||
|
verdict="recovered"
|
||||||
|
elapsed=$(( $(date +%s) - started ))
|
||||||
|
write_summary "$verdict" "$elapsed"
|
||||||
|
prune_dumps
|
||||||
|
local info
|
||||||
|
info="$(run_timeout "$HEALTH_TIMEOUT" docker info \
|
||||||
|
--format '{{.ServerVersion}} ({{.ContainersRunning}} running)' 2>/dev/null || true)"
|
||||||
|
log "docker-nuke: Docker is back after ${elapsed}s — ${info:-ok}"
|
||||||
|
log "docker-nuke: diagnostics in $DUMP_DIR"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
verdict="daemon did not return within ${RESTART_TIMEOUT}s"
|
||||||
|
elapsed=$(( $(date +%s) - started ))
|
||||||
|
write_summary "$verdict" "$elapsed"
|
||||||
|
prune_dumps
|
||||||
|
warn "docker-nuke: $verdict"
|
||||||
|
warn "docker-nuke: still running: $(docker_pids | tr '\n' ' ')"
|
||||||
|
warn "docker-nuke: diagnostics in $DUMP_DIR"
|
||||||
|
warn "docker-nuke: try 'docker-nuke --deep', or check disk space in $DUMP_DIR/disk.txt"
|
||||||
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
# Only run when executed, not when sourced by the test harness.
|
# Only run when executed, not when sourced by the test harness.
|
||||||
|
|||||||
@@ -931,6 +931,33 @@ test_write_summary_survives() {
|
|||||||
}
|
}
|
||||||
test_write_summary_survives
|
test_write_summary_survives
|
||||||
|
|
||||||
|
printf '\n=== dry run ===\n'
|
||||||
|
|
||||||
|
test_dry_run() {
|
||||||
|
local out rc before
|
||||||
|
|
||||||
|
# shellcheck disable=SC2012 # dump dir names are our own generated timestamps, never contain newlines; a plain count needs plain lines, not find -print0
|
||||||
|
before="$(ls -1 "$DUMP_ROOT" 2>/dev/null | wc -l | tr -d ' ')"
|
||||||
|
rc=0
|
||||||
|
out="$("$SCRIPT_DIR/docker-nuke" --dry-run 2>&1)" || rc=$?
|
||||||
|
assert_eq "dry run exits 0" "0" "$rc"
|
||||||
|
assert_contains "dry run says it changed nothing" "$out" "no changes made"
|
||||||
|
assert_contains "dry run lists processes it would kill" "$out" "would kill"
|
||||||
|
# shellcheck disable=SC2012 # dump dir names are our own generated timestamps, never contain newlines; a plain count needs plain lines, not find -print0
|
||||||
|
assert_eq "dry run writes no dump" "$before" \
|
||||||
|
"$(ls -1 "$DUMP_ROOT" 2>/dev/null | wc -l | tr -d ' ')"
|
||||||
|
|
||||||
|
rc=0
|
||||||
|
out="$("$SCRIPT_DIR/docker-nuke" --help 2>&1)" || rc=$?
|
||||||
|
assert_eq "--help exits 0" "0" "$rc"
|
||||||
|
assert_contains "--help prints usage" "$out" "Usage:"
|
||||||
|
|
||||||
|
rc=0
|
||||||
|
"$SCRIPT_DIR/docker-nuke" --nonsense >/dev/null 2>&1 || rc=$?
|
||||||
|
assert_eq "unknown flag exits 2" "2" "$rc"
|
||||||
|
}
|
||||||
|
test_dry_run
|
||||||
|
|
||||||
printf '\n=== summary ===\n'
|
printf '\n=== summary ===\n'
|
||||||
teardown_tmp
|
teardown_tmp
|
||||||
printf 'ran %d, failed %d\n' "$TESTS_RUN" "$TESTS_FAILED"
|
printf 'ran %d, failed %d\n' "$TESTS_RUN" "$TESTS_FAILED"
|
||||||
|
|||||||
@@ -183,6 +183,7 @@ Custom scripts and binaries are stored in `.local/bin/`:
|
|||||||
- `cl`: Claude CLI wrapper
|
- `cl`: Claude CLI wrapper
|
||||||
- `claude-tmux`: Claude integration with tmux
|
- `claude-tmux`: Claude integration with tmux
|
||||||
- `cldir`: Change directory with Claude context
|
- `cldir`: Change directory with Claude context
|
||||||
|
- `docker-nuke`: Force-restart a wedged Docker Desktop, capturing diagnostics to `~/.local/state/docker-nuke/` first (`--dry-run` to preview, `--no-restart` to leave it down, `--deep` for root helpers)
|
||||||
|
|
||||||
Ensure this directory is in your PATH:
|
Ensure this directory is in your PATH:
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user