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
+78
View File
@@ -22,6 +22,8 @@ install_base() {
echo "[DRY-RUN] Would offer Caddy reverse proxy install (full repo only)"
echo "[DRY-RUN] Would offer CrowdSec intrusion prevention install (full repo only)"
echo "[DRY-RUN] Would offer to add SSH Host aliases to ~/.ssh/config"
echo "[DRY-RUN] Would add setup.sh tab completion to ~/.bashrc (if not already there)"
echo "[DRY-RUN] Would offer daily pruning of old *.backup.* config files (systemd timer)"
return 0
fi
@@ -82,6 +84,82 @@ install_base() {
# ── SSH Host aliases ─────────────────────────────────────────────────────
_base_setup_ssh_aliases
# ── setup.sh tab completion ─────────────────────────────────────────────
_base_setup_tab_completion
# ── Old config-backup pruning ────────────────────────────────────────────
_base_setup_backup_pruning
}
# Wires tools/setup-completion.bash into ACTUAL_USER's shell automatically —
# no reason to make everyone find and run this by hand when base already
# touches ~/.bashrc for other things. Idempotent (checked by grep before
# appending), so reruns don't pile up duplicate source lines.
_base_setup_tab_completion() {
local comp_script="$HERE/tools/setup-completion.bash"
[ -f "$comp_script" ] || return 0
local bashrc="$ACTUAL_HOME/.bashrc"
[ -f "$bashrc" ] || return 0
grep -qF "$comp_script" "$bashrc" 2>/dev/null && return 0
# No DRY_RUN check here — install_base()'s own top-level one (above)
# already returns before this helper is ever called in that mode,
# unlike install_glow()'s check further down, which is independently
# invokable (sudo ./setup.sh glow --dry-run) and genuinely reachable.
{
echo ""
echo "# ubuntu-post-install: setup.sh tab completion"
echo "source $comp_script"
} >> "$bashrc"
chown "$ACTUAL_USER:$ACTUAL_USER" "$bashrc" 2>/dev/null || true
log_success "setup.sh tab completion added to $bashrc (takes effect in new shells, or: source $bashrc)"
}
# Every service in this repo backs up a live config before overwriting it
# (Caddyfile, /etc/fstab, ...) but none of them ever clean those up
# afterward — see tools/prune-old-backups.sh's own header for the full
# reasoning. Offers a daily systemd timer that prunes anything older than
# 30 days, always keeping at least the single newest backup per file
# regardless of age.
_base_setup_backup_pruning() {
command -v systemctl >/dev/null 2>&1 || return 0
systemctl list-unit-files prune-old-backups.timer --no-legend 2>/dev/null | grep -q . && return 0
local prune_script="$HERE/tools/prune-old-backups.sh"
[ -f "$prune_script" ] || return 0
echo ""
local ENABLE_PRUNE=""
prompt_yn "Automatically prune old config backups (Caddyfile.backup.*, fstab.backup.*, etc — keeps 30 days, always keeps at least the newest one)? (y/n):" "y" ENABLE_PRUNE
[[ "$ENABLE_PRUNE" =~ ^[Yy]$ ]] || return 0
cat > /etc/systemd/system/prune-old-backups.service << UNIT
[Unit]
Description=Prune old *.backup.* config backups (Caddyfile, fstab, etc)
[Service]
Type=oneshot
ExecStart=/bin/bash ${prune_script} 30
UNIT
cat > /etc/systemd/system/prune-old-backups.timer << 'UNIT'
[Unit]
Description=Daily backup pruning
[Timer]
OnCalendar=daily
Persistent=true
[Install]
WantedBy=timers.target
UNIT
systemctl daemon-reload
if systemctl enable --now prune-old-backups.timer >/dev/null 2>&1; then
log_success "Old config backups will be pruned daily, keeping 30 days (systemd timer: prune-old-backups)"
else
log_warning "Couldn't enable the pruning timer — run $prune_script manually to prune old backups."
fi
}
_base_setup_nvidia_gpu() {