Add a generic service removal command: ./setup.sh <name> --remove

No removal path existed anywhere in this repo — manually removing a
service meant hand-editing docker-compose.yml, the Caddyfile, and UFW
rules yourself, or just leaving orphaned config behind.

remove_service (lib/common.sh) handles the common case: stop/remove
the service's containers (with an explicit y/n on whether to also wipe
data volumes, default no), find and remove its Caddy site block if one
exists, remove any UFW rule tagged with its name, and optionally
delete its ~/docker/<name> directory (default no — keep data as a
safety net unless explicitly confirmed).

The Caddy site block removal (_remove_caddy_site_block) tracks actual
brace depth rather than scanning to the next blank line or EOF — the
same class of bug this repo already hit once with a naive Samba
config edit. Verified against a multi-block test Caddyfile with nested
log{}/header{} blocks: removes exactly the targeted block, leaves
every other block (including ones with their own nested braces)
byte-for-byte intact, and is a safe no-op when nothing matches.

Also fixes the UFW rule-number extraction: ufw status numbered pads
single-digit rule numbers with a leading space ("[ 3]" vs "[10]") to
align columns, which the regex didn't account for — every single-digit
rule would have silently never matched and never gotten deleted.

Wired into setup.sh as a new --remove flag, resolving SERVICE_ALIAS
and validating the name the same way run_service already does.
Scoped to the common case (a Docker service at $DOCKER_DIR/<name> with
a standard configure_caddy_for_service site block); a hand-built Caddy
block or non-standard layout may need manual cleanup for the parts
this can't find. Non-Docker services (base, ssh-key-import, etc.)
report cleanly that they're not handled rather than erroring
confusingly.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H4k6J1qXXyYxhGEgnJaMvn
This commit is contained in:
Claude
2026-08-11 03:29:10 +00:00
parent ad011b2db8
commit efcad48755
2 changed files with 183 additions and 1 deletions
+29 -1
View File
@@ -12,6 +12,10 @@
# Flags:
# --dry-run preview actions without making changes
# --unattended use defaults, no prompts (pair with explicit service names)
# --remove remove instead of install (pair with a service name, e.g.
# ./setup.sh filebrowser --remove) — stops/removes its
# containers, its Caddy site block (if any), any UFW rule
# tagged for it, and optionally its ~/docker/<name> directory
#
# Every service lives in services/<name>.sh, registers itself with
# register_service, and defines install_<name>. Adding a service = adding one
@@ -34,7 +38,7 @@ declare -A SERVICE_PRIORITY=( [caddy]=1 [crowdsec]=2 [authelia]=3 )
declare -A SERVICE_ALIAS=( [asterisk-digital-ocean]=asterisk )
# ── Parse flags / collect service names ──────────────────────────────────────
DRY_RUN=false; UNATTENDED=false; DO_LIST=false; DO_STATUS=false
DRY_RUN=false; UNATTENDED=false; DO_LIST=false; DO_STATUS=false; DO_REMOVE=false
REQUESTED=()
for arg in "$@"; do
case "$arg" in
@@ -42,6 +46,7 @@ for arg in "$@"; do
--unattended) UNATTENDED=true ;;
--list|-l) DO_LIST=true ;;
--status) DO_STATUS=true ;;
--remove) DO_REMOVE=true ;;
--version|-V) cat "$HERE/VERSION" 2>/dev/null || echo "unknown"; exit 0 ;;
-h|--help) sed -n '2,18p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//'; exit 0 ;;
-*) echo "Unknown flag: $arg" >&2; exit 1 ;;
@@ -267,6 +272,29 @@ if [ "${REQUESTED[*]:-}" = "configure" ]; then
exit 0
fi
# ── --remove: ./setup.sh filebrowser --remove ────────────────────────────────
if [ "$DO_REMOVE" = true ]; then
require_root
if [ "${#REQUESTED[@]}" -eq 0 ]; then
log_error "--remove needs a service name, e.g. ./setup.sh filebrowser --remove"
exit 1
fi
rc=0
for name in "${REQUESTED[@]}"; do
if [ -n "${SERVICE_ALIAS[$name]:-}" ]; then
log_info "'$name' is now part of '${SERVICE_ALIAS[$name]}' — removing that instead."
name="${SERVICE_ALIAS[$name]}"
fi
if [ -z "${SERVICE_GROUP[$name]:-}" ]; then
log_error "Unknown service: $name (try --list)"
rc=1
continue
fi
remove_service "$name" || rc=1
done
exit "$rc"
fi
# ── Direct install: ./setup.sh caddy homeassistant ──────────────────────────
if [ "${#REQUESTED[@]}" -gt 0 ]; then
require_root