#!/bin/dash
# Reclaims disk space for cmd.gateway.clean_cache / clean_logs / clean_package.
#
# Only rotated/archived logs are ever removed. A live *.log is held open by its
# service, so unlinking it frees no space until that service restarts.
#
# Every accepted argument combination is enumerated in /etc/sudoers.d/fhbutler,
# so this script must not grow options that widen what fhbutler can delete.
#
# Each removed path is written to stdout NUL-terminated, so names containing
# newlines survive; the caller counts the records. Any other output must come
# after the last path, as the caller treats the tail after the final NUL as
# noise.
set -e

# Authentication and audit trails are never swept. They are the record of who
# reached the hub, so a disk-cleanup command must not be able to erase them --
# on a full hub they account for well under 1 MB anyway.
AUDIT='.*/(wtmp|btmp|lastlog|faillog|auth\.log|secure)([.-].*)?'

# purge <dir> <maxdepth> [mindepth] -- delete rotated logs under dir, printing
# each path. mindepth defaults to 1, i.e. no restriction below maxdepth itself.
purge() {
	[ -d "$1" ] || return 0

	# dash has no pipefail, so the delete runs alone under `set -e` -- piping
	# would hide a mid-traversal find failure behind the reader's own success.
	find "$1" -mindepth "${3:-1}" -maxdepth "$2" -type f -regextype posix-extended \
		-regex '.*(\.[0-9]+|\.gz|\.xz|\.bz2|\.old)$' \
		! -regex "$AUDIT" \
		-printf '%p\0' -delete
}

case "$1 $2" in
"cache ")
	# Unfiltered except for the persistent lock file: apt-get clean also removes
	# archives/partial/ and cache metadata, not just top-level *.deb files, but
	# never removes archives/lock, which would otherwise inflate an empty count.
	if [ -d /var/cache/apt/archives ]; then
		find /var/cache/apt/archives -type f ! -name lock -printf '%p\0'
	fi
	/usr/bin/apt-get clean
	;;
"logs rotated")
	purge /var/log 1
	;;
"logs all")
	purge /var/log 1
	purge /var/log/futurehome 2 2
	purge /var/log/thingsplex 2 2
	;;
"packages remove")
	pkg=$3

	# The name is about to reach apt as root, so only dpkg-legal names pass, and a
	# Futurehome package never does -- however apt classifies it. The explicit names
	# are hub components carrying no fh/futurehome marker whose removal would take
	# down connectivity, a radio or the local broker; keep in sync with corePackages
	# in src/internal/hub/cleanup.go.
	case "$pkg" in
	fh* | fimp* | *futurehome* | backup-restore | cloud-bridge | ecollector | influxdb | kind-owl | \
		mosquitto | tpflow | unicomplex | vinculum | zigbee-ad | zipgateway | zwave-ad)
		echo "refused: protected package $pkg" >&2
		exit 1
		;;
	esac
	echo "$pkg" | grep -qE '^[a-z0-9][a-z0-9+.-]*[a-z0-9]$' || {
		echo "refused: bad package name $pkg" >&2
		exit 1
	}

	# Each simulation is captured on its own line so `set -e` sees apt's exit code:
	# inside a pipeline the status is awk's, which would turn a broken dpkg state
	# into a refusal blaming the package.
	unused=$(/usr/bin/apt-get -s autoremove)

	# Only what apt itself considers unused may go. -F keeps a name legally holding
	# '.' from matching a different package as a regular expression.
	echo "$unused" | awk '/^Remv /{print $2}' | grep -qxF "$pkg" || {
		echo "refused: $pkg is not unused" >&2
		exit 1
	}

	# Removing it must not drag anything else out with it.
	removal=$(/usr/bin/apt-get -s remove "$pkg")
	plan=$(echo "$removal" | awk '/^Remv /{print $2}')
	[ "$plan" = "$pkg" ] || {
		echo "refused: would also remove $plan" >&2
		exit 1
	}

	# Both simulations run without the dpkg lock, so a concurrent apt could widen the
	# plan between them and the removal below. Accepted: apt offers no atomic "remove
	# only this", and a competing apt makes this line fail on the dpkg lock instead,
	# which is the safe way to lose the race.
	#
	# apt shares this stdout however its chatter is redirected, so the record goes
	# first and the chatter lands after the final NUL, where the caller reads it as
	# noise. A failed removal exits non-zero and the caller then ignores stdout.
	#
	# purge, not remove: remove leaves a "deinstall ok config-files" record still
	# carrying Version, which GetInstalledPackages reports as installed. The plan
	# check stays on -s remove -- purge removes the same set, it only also drops
	# conffiles, and it prints Purg where the awk above expects Remv.
	printf '%s\0' "$pkg"
	/usr/bin/apt-get -qy purge "$pkg" >&2
	;;
*)
	echo "usage: $0 cache | logs rotated | logs all | packages remove <pkg>" >&2
	exit 1
	;;
esac
