Add automatic tab-completion setup and old config-backup pruning

Two things surfaced from actual use this session:

1. Tab completion (tools/setup-completion.bash, added earlier) required
   manually editing ~/.bashrc — easy to skip or get wrong (confirmed
   live: the source line never actually landed the first time). base
   now wires it in automatically (idempotent, checked by grep first),
   matching how it already touches ~/.bashrc for SSH Host aliases.

2. No pruning existed anywhere for the *.backup.<timestamp> files ~60
   different services create before overwriting a live config
   (Caddyfile, /etc/fstab, etc) — every one of them backs up, none
   clean up, so they accumulate forever on a box reconfigured
   regularly. tools/prune-old-backups.sh prunes by file mtime (not by
   parsing the timestamp out of the filename — robust to the
   %Y%m%d-%H%M%S vs %Y%m%d_%H%M%S inconsistency across services),
   always keeping the single newest backup per distinct file
   regardless of age. Verified both the normal case (mixed old/new,
   prunes only the old ones) and the edge case (every backup for a
   file is old, keeps the newest one anyway) against real fixtures.
   base offers it as a daily systemd timer (prompted, since it deletes
   files — unlike the tab-completion wiring, which doesn't).

Also added logrotate for Caddy's own access logs
(/var/log/caddy/*.log), which had no rotation at all and grow
unbounded on an active box. Uses copytruncate specifically: the log
directory is bind-mounted into the running Caddy container and read
live by CrowdSec, so truncating in place avoids either of them needing
to notice or react to a rotation happening.

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 04:48:11 +00:00
parent 91340c5f2f
commit c50704e1b3
4 changed files with 196 additions and 0 deletions
+24
View File
@@ -233,6 +233,7 @@ install_caddy() {
echo "[DRY-RUN] Would write a starter $CADDY_DIR/Caddyfile (if none exists)"
echo "[DRY-RUN] Would write $CADDY_DIR/README.md"
echo "[DRY-RUN] Would open 80/tcp, 443/tcp, 443/udp in UFW (Docker's own iptables rules let this traffic through either way, but ufw status should actually reflect it)"
echo "[DRY-RUN] Would configure logrotate for /var/log/caddy/*.log (14 days, copytruncate)"
echo "[DRY-RUN] Would optionally start Caddy (docker compose up -d)"
return 0
fi
@@ -385,6 +386,29 @@ CADDYFILE
chown -R "$ACTUAL_USER:$ACTUAL_USER" "$CADDY_DIR"
echo " ✓ Caddy configured at $CADDY_DIR"
# Every site's access log (one JSON file per domain, per the "log {}"
# block configure_caddy_for_service writes) grows forever otherwise —
# nothing in this repo ever rotated them. copytruncate, not the usual
# rename+recreate rotation: the log directory is bind-mounted into the
# running Caddy container (still holding the file open) and read live
# by CrowdSec, so truncating in place avoids both "Caddy keeps writing
# to the old, now-unlinked file" and "CrowdSec's tail loses the file"
# — neither has to detect or react to a rotation at all this way.
if [ -d /etc/logrotate.d ]; then
cat > /etc/logrotate.d/caddy << 'LOGROTATE'
/var/log/caddy/*.log {
daily
rotate 14
compress
delaycompress
missingok
notifempty
copytruncate
}
LOGROTATE
echo " ✓ Access log rotation configured (/var/log/caddy/*.log, 14 days, copytruncate)"
fi
# Every other service in this repo opens its own UFW rule; this file
# never did — Docker manipulates iptables directly for published
# container ports (the ports: mapping above), which bypasses UFW's own