fix(safeclean): dry-run/apply docker consistency, eCryptfs warning, rotated log cleanup

This commit is contained in:
Oskar Kapala 2026-06-30 19:09:07 +02:00
parent f4486d3c3c
commit 37233f0a47

62
safeclean.sh Normal file → Executable file
View file

@ -279,14 +279,44 @@ clean_docker() {
if ! docker info >/dev/null 2>&1; then if ! docker info >/dev/null 2>&1; then
printf ' %-26s %sdaemon not reachable%s\n' "Docker" "$c_dim" "$c_rst"; return printf ' %-26s %sdaemon not reachable%s\n' "Docker" "$c_dim" "$c_rst"; return
fi fi
local age_h=$(( AGE_DAYS * 24 ))
echo " Docker reclaimable space:" echo " Docker reclaimable space:"
docker system df 2>/dev/null | sed 's/^/ /' docker system df 2>/dev/null | sed 's/^/ /'
[ "$DRY_RUN" = 1 ] && return
if [ "$DRY_RUN" = 1 ]; then
if [ "$AGGRESSIVE_DOCKER" = 0 ] && [ "$AGE_DAYS" -gt 0 ]; then
# apply uses --filter until=Xh, so docker system df overstates actual reclaim.
# Estimate: sum sizes of dangling images older than age_h hours (what prune targets).
local img_bytes=0 img_count=0 id s
while IFS= read -r id; do
[ -z "$id" ] && continue
img_count=$(( img_count + 1 ))
s=$(docker image inspect --format '{{.Size}}' "$id" 2>/dev/null) || s=0
img_bytes=$(( img_bytes + ${s:-0} ))
done < <(docker image ls --filter "dangling=true" --filter "until=${age_h}h" \
--format "{{.ID}}" 2>/dev/null)
local img_human
img_human=$(numfmt --to=si --suffix=B -- "$img_bytes" 2>/dev/null \
|| echo "${img_bytes}B")
echo
printf ' %sNOTE: apply uses --filter until=%dh — only items older than %d day(s) are removed.%s\n' \
"$c_yel" "$age_h" "$AGE_DAYS" "$c_rst"
printf ' Dangling images >%dd apply would actually remove: %s%s%s (%d image(s))\n' \
"$AGE_DAYS" "$c_bold" "$img_human" "$c_rst" "$img_count"
printf ' %s(stopped containers + build cache also contribute; totals shown above)%s\n' \
"$c_dim" "$c_rst"
printf ' %sTip: --docker-all removes ALL unused images regardless of age.%s\n' \
"$c_dim" "$c_rst"
fi
return
fi
local flags="-f" local flags="-f"
[ "$AGGRESSIVE_DOCKER" = 1 ] && flags="-af" # -a also removes unused (not just dangling) images [ "$AGGRESSIVE_DOCKER" = 1 ] && flags="-af" # -a also removes unused (not just dangling) images
local until_args=() local until_args=()
[ "$AGE_DAYS" -gt 0 ] && until_args=(--filter "until=$(( AGE_DAYS * 24 ))h") [ "$AGE_DAYS" -gt 0 ] && until_args=(--filter "until=${age_h}h")
if confirm "Run 'docker system prune ${flags} ${until_args[*]}' (volumes are NOT touched)?"; then if confirm "Run 'docker system prune ${flags} ${until_args[*]}' (volumes are NOT touched)?"; then
docker system prune $flags "${until_args[@]}" docker system prune $flags "${until_args[@]}"
else else
@ -298,9 +328,16 @@ clean_system() {
echo echo
echo "${c_bold}System (sudo) ─────────────────────────────────────────────${c_rst}" echo "${c_bold}System (sudo) ─────────────────────────────────────────────${c_rst}"
if [ "$DRY_RUN" = 1 ]; then if [ "$DRY_RUN" = 1 ]; then
echo " ${c_dim}dry-run: would offer apt clean, journald vacuum, old snap removal${c_rst}" echo " ${c_dim}dry-run: would offer apt clean, journald vacuum, rotated log cleanup, old snap removal${c_rst}"
echo " apt archive cache:"; sudo -n du -sh /var/cache/apt/archives 2>/dev/null | sed 's/^/ /' || true echo " apt archive cache:"; sudo -n du -sh /var/cache/apt/archives 2>/dev/null | sed 's/^/ /' || true
echo " journal logs:"; journalctl --disk-usage 2>/dev/null | sed 's/^/ /' || true echo " journal logs:"; journalctl --disk-usage 2>/dev/null | sed 's/^/ /' || true
echo " rotated logs (/var/log/*.gz, *.1, *.2 …):"
local rot_total
rot_total=$(find /var/log -maxdepth 1 -type f \
\( -name '*.gz' -o -name '*.[0-9]' -o -name '*.[0-9][0-9]' \) \
-print0 2>/dev/null | xargs -0 -r du -sch 2>/dev/null \
| tail -n1 | awk '{print $1}') || rot_total=""
printf ' %s%s%s\n' "$c_bold" "${rot_total:-0}" "$c_rst"
return return
fi fi
@ -314,6 +351,22 @@ clean_system() {
sudo journalctl --vacuum-time="${jdays}d" sudo journalctl --vacuum-time="${jdays}d"
fi fi
local rotated=()
while IFS= read -r -d '' f; do rotated+=("$f"); done < <(
find /var/log -maxdepth 1 -type f \
\( -name '*.gz' -o -name '*.[0-9]' -o -name '*.[0-9][0-9]' \) \
-print0 2>/dev/null
)
if [ ${#rotated[@]} -gt 0 ]; then
local rot_size
rot_size=$(du -sch "${rotated[@]}" 2>/dev/null | tail -n1 | awk '{print $1}')
if confirm "Delete rotated system logs (${rot_size}, ${#rotated[@]} file(s) — not active logs)?"; then
sudo rm -f -- "${rotated[@]}" && printf ' %sremoved.%s\n' "$c_grn" "$c_rst"
else
echo " skipped."
fi
fi
if confirm "snap: remove disabled (old) revisions?"; then if confirm "snap: remove disabled (old) revisions?"; then
snap list --all 2>/dev/null | awk '/disabled/{print $1, $3}' | \ snap list --all 2>/dev/null | awk '/disabled/{print $1, $3}' | \
while read -r name rev; do while read -r name rev; do
@ -342,6 +395,9 @@ echo
echo "${c_bold}Largest items under $SCAN_ROOT:${c_rst}" echo "${c_bold}Largest items under $SCAN_ROOT:${c_rst}"
du -h --max-depth=1 "$SCAN_ROOT" 2>/dev/null | sort -rh | head -n 12 | sed 's/^/ /' du -h --max-depth=1 "$SCAN_ROOT" 2>/dev/null | sort -rh | head -n 12 | sed 's/^/ /'
if [ -d "${SCAN_ROOT}/.Private" ] && mount 2>/dev/null | grep -q " on ${SCAN_ROOT} type ecryptfs"; then
printf ' %sNote: eCryptfs detected — .Private and home directory are the same data; sizes above may be double-counted.%s\n' "$c_yel" "$c_rst"
fi
echo echo
echo "${c_bold}Reclaimable categories ──────────────────────────────────────${c_rst}" echo "${c_bold}Reclaimable categories ──────────────────────────────────────${c_rst}"