#!/bin/bash

## Copyright (C) 2012 - 2025 ENCRYPTED SUPPORT LLC <adrelanos@whonix.org>
## See the file COPYING for copying conditions.

set -x
set -o errexit
set -o nounset
set -o pipefail
set -o errtrace
shopt -s inherit_errexit
shopt -s shift_verbose

own_filename="$(basename -- "${BASH_SOURCE[0]}")"
## SKIP_SCRIPTS is an optional space-separated list from the chroot runner; default it
## empty so 'nounset' does not abort when no scripts are being skipped. Word-splitting is
## intentional (it is a list), hence the deliberately unquoted expansion.
# shellcheck disable=SC2086
for skip_script in ${SKIP_SCRIPTS:-}; do
   if [ "${skip_script}" = "${own_filename}" ]; then
      true "INFO: Skipping ${own_filename}, because SKIP_SCRIPTS includes it."
      exit 0
   fi
done
unset skip_script

true "INFO: Cleaning up..."

## Kill dhclient3 to prevent rewrite of /var/lib/dhcp/*.
killall dhclient3 || true
## There are .leases.
safe-rm -- /var/lib/dhcp/*.leases || true
## And there are .lease.
safe-rm -- /var/lib/dhcp/*.lease || true
## We are best of deleting the whole folder.
safe-rm -r -- /var/lib/dhcp/* || true

## Cleanup.
## || true to support re-running the script.
apt-get --yes autoremove --purge || true

## Get rid of /var/cache/apt/pkgcache.bin. (non-deterministic)
## || true to support re-running the script.
apt-get --yes clean || true

## No longer deleting /var/lib/tor. We install but forbid to run software such as Tor we install.
## Therefore /var/lib/tor should be empty.
## Ensure to delete /var/lib/tor. It contains sensitive stuff like the Tor consensus and the Tor entry guards.
## safe-rm -r /var/lib/tor/* || true

## Delete logs and other stuff.
safe-rm -r -- /tmp/* || true
safe-rm -- /var/log/installer/* || true
safe-rm -r -- /var/cache/apt/* || true
safe-rm -r -- /var/lib/apt/lists/* || true
safe-rm -r -- /var/log/installer || true
safe-rm -- /var/lib/dpkg/*-old || true
safe-rm -- /var/cache/debconf/*-old || true

## Drop the debconf PASSWORD database, but never silently. debconf stores password answers
## here (mode 600), so a database holding an ANSWER means the build captured a credential:
## shipping it would leak the credential, and deleting it quietly would hide that it ever
## existed. Measured across four builds on two machines it holds only the two Secure Boot
## MOK prompts (shim/secureboot_key, shim/secureboot_key_again) with no stored answer, so
## the normal case is discarding a file that has nothing in it.
##
## "Holds an answer" is a 'Value:' field, not a non-zero size: the two prompts above are
## 180 bytes of question ownership with no answer, which is the empty case. Report the
## offending question NAMES on failure, never their values.
if [ -f /var/cache/debconf/passwords.dat ]; then
   if grep --quiet -- '^Value:' /var/cache/debconf/passwords.dat; then
      printf '%s\n' "ERROR: ${own_filename}: /var/cache/debconf/passwords.dat holds stored answers." >&2
      printf '%s\n' "A password was captured during the build; refusing to ship it or to delete it silently." >&2
      printf '%s\n' "affected question(s):" >&2
      grep -- '^Name:' /var/cache/debconf/passwords.dat >&2
      exit 1
   fi
   safe-rm --force -- /var/cache/debconf/passwords.dat
fi

## KEEP the debconf answers database (config.dat). Measured byte-identical across four
## builds on two machines, and every stored answer is package configuration (keymap,
## locale, fontconfig, CA list, PAM profiles, timezone) -- nothing confidential. Keeping it
## means dpkg-reconfigure, debconf-show and the first upgrade see the real prior answers
## instead of silently falling back to defaults.
##
## One class of answer must not ship: grub records the BUILD HOST's install device
## (grub-efi-amd64/install_devices = /dev/loop0). That is build-environment state, and
## loop-device numbering is allocation-dependent -- a build host with loops already in use
## writes a different value, so the image would stop being reproducible. Reset those
## answers rather than discarding the whole database. Matched by name so the BIOS variant
## (grub-pc) and the -disks_changed / _empty companions are covered too.
##
## One pipeline rather than a name loop: debconf-communicate reads its commands from stdin,
## so the matched question names go straight in. The pattern covers the BIOS variant
## (grub-pc) and the _empty / _disks_changed companions, which record device paths too.
grep --only-matching --extended-regexp -- 'grub-[^/[:space:]]+/install_devices[a-z_]*' \
   /var/cache/debconf/config.dat \
   | LC_ALL=C sort --unique \
   | sed 's/^/RESET /' \
   | debconf-communicate >/dev/null || true

## Prove it worked. The answers database SHIPS, so a build-host block device left in it is a
## defect, not a warning: it leaks the build environment into the image, and because loop
## numbering is allocation-dependent it also breaks reproducibility on a host where loop0 was
## already taken. Measured on a real image, config.dat contains exactly ONE /dev/ reference --
## the grub answer reset above -- so this fires only on a genuine leftover. Block devices are
## matched specifically, so a legitimate /dev/null answer never trips it.
debconf_device_leak_pattern='/dev/(loop|mapper/loop|sd[a-z]|nvme[0-9]|vd[a-z]|xvd[a-z])'
if grep --quiet --extended-regexp -- "${debconf_device_leak_pattern}" /var/cache/debconf/config.dat; then
   printf '%s\n' "ERROR: ${own_filename}: build-host device references remain in /var/cache/debconf/config.dat:" >&2
   grep --before-context=2 --extended-regexp -- "${debconf_device_leak_pattern}" /var/cache/debconf/config.dat >&2
   exit 1
fi
unset debconf_device_leak_pattern

## Do NOT delete templates.dat: it holds the question templates; removing it breaks
## dpkg-reconfigure (2014 regression: `dpkg-reconfigure console-data` -> keymap "NONE").

## non-deterministic
## /var/cache/ldconfig/aux-cache -- ldconfig's binary auxiliary cache. Same size but
## differing bytes between two independent builds (it records per-file identity, not just
## paths). Safe to delete: ldconfig recreates it on the next run, and any package install
## triggers one.
safe-rm --force -- /var/cache/ldconfig/aux-cache || true

## non-deterministic
## /etc/locale.conf (target of the /etc/default/locale symlink) -- update-locale writes its
## assignment lines in a non-deterministic order, so two builds differ by line order alone.
## Sort it: the header comment sorts before the KEY=value lines, and the relative order of
## those lines is irrelevant to every consumer.
##
## Replace it atomically. Writing the sorted content back over the live file truncates it
## first, so an interrupted write leaves the locale configuration empty or partial with no
## way to recover it -- and this runs late in the build, after the answers that produced it
## are gone. Stage the sorted copy in /etc so the rename is within one filesystem, carry
## the original mode and owner onto it, then rename over the original.
if [ -f /etc/locale.conf ]; then
   locale_conf_sorted="$(mktemp -- /etc/locale.conf.XXXXXXXX)"
   chmod --reference=/etc/locale.conf -- "${locale_conf_sorted}"
   ## Numeric owner via stat, NOT a --reference to the file itself. lintian's
   ## script/deprecated ownership check greps for the old dotted user/group form and matches
   ## a dot in the referenced FILENAME, so a --reference to this file raises a false positive
   ## -- no such spec is present -- but genmkfile fails closed on any lintian tag, so it
   ## broke the build. Passing uid:gid gives lintian no dot to misread, and is exact rather
   ## than suppressed. Do not restore the --reference form here, and do not name the
   ## offending command in a comment either: lintian greps the whole FILE, so even an
   ## explanation of the problem can re-trigger it.
   locale_conf_owner="$(stat --format='%u:%g' -- /etc/locale.conf)"
   chown -- "${locale_conf_owner}" "${locale_conf_sorted}"
   LC_ALL=C sort -- /etc/locale.conf | tee -- "${locale_conf_sorted}" >/dev/null
   mv -- "${locale_conf_sorted}" /etc/locale.conf
   unset locale_conf_sorted locale_conf_owner
fi

## non-deterministic
## /usr/lib/<multiarch>/vlc/plugins/plugins.dat -- VLC's plugin cache, written by
## vlc-cache-gen in raw readdir order (VLC's AllocatePluginDir does not sort and
## ignores SOURCE_DATE_EPOCH), so it differs build-to-build. No in-chroot way to
## regenerate it deterministically without patching VLC. Safe to delete: VLC scans
## plugins at startup when the cache is absent, and Debian's dpkg trigger on
## /usr/lib/*/vlc/plugins rebuilds it on the next apt transaction (Debian #755154).
safe-rm --force -- /usr/lib/*/vlc/plugins/plugins.dat || true

## Erase rotated logs (usually won't appear unless you left your VM running for several days).
safe-rm -- /var/log/*.[0-9] || true
safe-rm -- /var/log/*.[0-9].gz || true

## Deletes /etc/system/sysinit.target.wants/systemd-timesyncd.service.
## Otherwise timedatectl still thinks systemd-timesyncd is enabled.
timedatectl set-ntp false >/dev/null 2>&1 || true
## Make sure it gets really deleted even if timedatectl does not work.
safe-rm --force -- /etc/systemd/system/sysinit.target.wants/systemd-timesyncd.service

## Deletes /etc/apt/sources.list.d/derivative.sources.
## Otherwise garbage apt config pointing to the derivative-maker approx proxy
## will be left on the disk.
safe-rm --force -- /etc/apt/sources.list.d/derivative.sources

## Deletes /etc/apt/sources.list.
## This file is no longer used since we've switched to deb822 format, and it
## ends up being left as a blank file on the disk.
safe-rm --force -- /etc/apt/sources.list

## non-deterministic [0]
## /var/lib/dpkg/available
## /var/lib/dpkg/available-old
dpkg --clear-avail

## non-deterministic [14]
## /etc/init.d/.depend.boot
## /etc/init.d/.depend.start
## /etc/init.d/.depend.stop
## Recreate those and therefore hopefully come up with deterministic results.
##
## Only attempt to run `insserv` when it's installed. This ensures, that this
## works on CI systems (Ubuntu) and in modified derivative versions as well.
if [ -x /sbin/insserv ]; then
   /sbin/insserv --showall
   /sbin/insserv --verbose
fi

## non-deterministic [15]
## /var/lib/urandom/random-seed
## This is no longer required for Debian Jessie?
## Should always be deleted for security reasons.
safe-rm --force -- /var/lib/urandom/random-seed

safe-rm --force -- /var/lib/systemd/random-seed
safe-rm --force -- /var/lib/random-seed

## non-deterministic
## /etc/nvme/hostid
## /etc/nvme/hostnqn
## nvme-cli generates these randomly at install time (a different value per build). A
## shipped image must not carry a build-host id; they are re-created as needed on first
## boot. Deletion mirrors the random-seed handling above.
safe-rm --force -- /etc/nvme/hostid
safe-rm --force -- /etc/nvme/hostnqn

## non-deterministic
## /var/lib/dkms/<module>/<version>/<kernel>/<arch>/log/make.log
## dkms leaves a build log with timestamps when it builds a module in-chroot (e.g. tirdad).
## A shipped image should not carry build logs; the built modules stay, only the log goes.
find /var/lib/dkms -type f -name 'make.log' -exec safe-rm --force -- {} \; || true

## non-deterministic
## /run/blkid/blkid.tab
## /run/blkid/blkid.tab.old
## /run is runtime tmpfs state and must be empty in a shipped image, but a build-time blkid
## run leaked the disk-uuid cache into the rootfs. Strip it.
safe-rm -r -- /run/blkid/* || true

## non-deterministic [21]
## Only required when using the anon-shared-build-inst-tb chroot-post.d script.
## No need to manually re-create it.
## Gets automatically re-created on next run of tb-updater.
## This is no longer required for Debian Jessie?
safe-rm -r -- /var/cache/tb-binary/.cache/tb/gpgtmpdir || true

## Leftover from build process using mmdebstrap.
safe-rm --force -- /etc/apt/apt.conf.d/99mmdebstrap

## Truncate all log files, keeping user groups and permissions.
find /var/log -type f -exec cp /dev/null {} \;

[[ -v user_name ]] || user_name="user"

## Delete bash history.
safe-rm -- "/home/$user_name/.bash_history" || true
safe-rm -- "/home/$user_name/.Xauthority" || true
safe-rm -- "/root/.bash_history" || true
safe-rm -- "/root/.Xauthority" || true
history -c || true

sync
