#!/bin/bash -e

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

# shellcheck source=../../../../../helper-scripts/usr/libexec/helper-scripts/check_runtime.bsh
source "${HELPER_SCRIPTS_PATH:-}"/usr/libexec/helper-scripts/check_runtime.bsh

if was_executed "${BASH_SOURCE[0]}"; then
   set -x
   set -o errexit
   set -o nounset
   set -o pipefail
   set -o errtrace
   shopt -s inherit_errexit
   shopt -s shift_verbose
   export LC_ALL=C
fi

should_skip() {
   local skip_script
   # shellcheck disable=SC2086
   for skip_script in ${SKIP_SCRIPTS:-}; do
      if [ "${skip_script}" = "${own_filename}" ]; then
         return 0
      fi
   done
   return 1
}

clean_dhcp() {
   ## 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 off deleting the whole folder.
   safe-rm -r -- /var/lib/dhcp/* || true
}

clean_apt() {
   ## style-ok: allow-apt-get -- used in a safe way here.
   ## 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

   ## /var/lib/dpkg/available
   ## /var/lib/dpkg/available-old
   dpkg --clear-avail

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

   safe-rm -r -- /var/cache/apt/* || true
   safe-rm -r -- /var/lib/apt/lists/* || true
   safe-rm -- /var/lib/dpkg/*-old || true
}

## Print the 'Name:' line of every debconf record that carries a 'Value:' line.
report_answered_question_names() {
   local debconf_db="$1"
   local line name="" has_value="false"
   while IFS= read -r line || [ -n "${line}" ]; do
      case "${line}" in
         "")
            if [ "${has_value}" = "true" ] && [ -n "${name}" ]; then
               printf '%s\n' "${name}"
            fi
            name=""
            has_value="false"
            ;;
         Name:*)
            name="${line}"
            ;;
         Value:*)
            has_value="true"
            ;;
      esac
   done < "${debconf_db}"
   ## Flush the final record if needed.
   if [ "${has_value}" = "true" ] && [ -n "${name}" ]; then
      printf '%s\n' "${name}"
   fi
}

## Delete debconf's password database if it contains no meaningful data.
clean_debconf_passwords() {
   local passwords_dat="${1:-}"
   if [ -f "${passwords_dat}" ]; then
      if grep --quiet -- '^Value:' "${passwords_dat}"; then
         printf '%s\n' "ERROR: ${own_filename}: ${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
         report_answered_question_names "${passwords_dat}" >&2
         return 1
      fi
      safe-rm --force -- "${passwords_dat}"
   fi
}

## Find any debconf records referring to GRUB install targets, and reset them.
## Keeping LC_ALL=C here even though it's declared above to prevent potential
## future bugs.
reset_debconf_grub_devices() {
   local config_dat="${1:-}"
   grep --only-matching --extended-regexp -- '^Name: grub-[^/[:space:]]+/install_devices[a-z_]*' \
      "${config_dat}" \
      | cut -d' ' -f2- \
      | LC_ALL=C sort --unique \
      | sed 's/^/RESET /' \
      | debconf-communicate >/dev/null || true
}

## Ensure no block device references exist in debconf's database. These are
## non-deterministic and reset_debconf_grub_devices is meant to clear them.
check_debconf_device_leak() {
   local config_dat="${1:-}"
   local debconf_device_leak_pattern debconf_device_leak_detect_output
   debconf_device_leak_pattern='/dev/(loop|mapper|sd[a-z]|nvme[0-9]|vd[a-z]|xvd[a-z]|md[0-9]|mmcblk[0-9]|nbd[0-9])'
   if debconf_device_leak_detect_output="$(grep \
      --before-context=2 --extended-regexp -- "${debconf_device_leak_pattern}" \
      "${config_dat}")"; then
      printf '%s\n' "ERROR: ${own_filename}: build-host device references remain in ${config_dat}:" >&2
      printf '%s\n' "${debconf_device_leak_detect_output}" >&2
      return 1
   fi
}

clean_nondeterministic() {
   ## ldconfig recreates the aux-cache file when it is run. It is unclear what
   ## this file caches.
   ##
   ## TODO-HUMAN-ONLY: Verify that this is safe to remove.
   safe-rm --force -- /var/cache/ldconfig/aux-cache || true

   ## update-locale writes /etc/locale.conf in a non-deterministic order. The
   ## order itself isn't meaningful.
   if [ -f /etc/locale.conf ]; then
      sort -- /etc/locale.conf | sponge -- /etc/locale.conf
   fi

   ## VLC's plugin cache is written in whatever order readdir gives it, and
   ## there is no way to make VLC sort its plugin list before writing the
   ## cache. VLC scans plugins at startup when the cache is absent, and the
   ## postinst script can regenerate it.
   safe-rm --force -- /usr/lib/*/vlc/plugins/plugins.dat || true

   ## Erase rotated logs.
   safe-rm -- /var/log/*.[0-9] || true
   safe-rm -- /var/log/*.[0-9].gz || true

   ## Delete logs and other stuff.
   ## style-ok: no-tmp-hardcode -- purging the chroot's own /tmp before shipping.
   safe-rm -r -- /tmp/* || true
   safe-rm -r -- /var/log/installer || true
   safe-rm -- /var/cache/debconf/*-old || true

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

   ## Delete bash history.
   [[ -v user_name ]] || user_name="user"
   safe-rm -- "/home/${user_name}/.bash_history" || true
   safe-rm -- "/root/.bash_history" || true
   history -c || true

   ## Delete X auth cookies.
   safe-rm -- "/home/${user_name}/.Xauthority" || true
   safe-rm -- "/root/.Xauthority" || true

   ## /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
   ## /var/lib/dkms/<module>/<version>/<kernel>/<arch>/log/make.log
   find /var/lib/dkms -type f -name 'make.log' -exec safe-rm --force -- {} \; || true

   ## 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
}

clean_time_and_apt_sources() {
   ## 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
}

clean_seeds_and_ids() {
   ## 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's postinst writes random values here (nvme gen-hostnqn / uuidgen), only at
   ## package configure time and only when missing or empty -- never at boot. So they
   ## differ per build (breaks reproducibility) and would carry the build host's id;
   ## delete them. These images do not use NVMe-over-Fabrics (the only consumer of these
   ## files) and local NVMe disks need neither, so absence has no effect here. If NVMe-oF
   ## were used, libnvme derives an id/NQN in memory at connect time (DMI UUID, else
   ## device-tree, else random) and never persists it back here -- not a stable stored
   ## identity. See libnvme nvme_host_get_ids() (src/nvme/tree.c) and nvmf_hostid_generate()
   ## (src/nvme/fabrics.c).
   ##
   ## TODO-HUMAN-DEVELOPER-ONLY: Verify the analysis from AI above.
   safe-rm --force -- /etc/nvme/hostid
   safe-rm --force -- /etc/nvme/hostnqn

   ## 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
}

main() {
   own_filename="$(basename -- "${BASH_SOURCE[0]}")"

   if should_skip; then
      true "INFO: Skipping ${own_filename}, because SKIP_SCRIPTS includes it."
      exit 0
   fi

   true "INFO: Cleaning up..."

   clean_dhcp
   clean_apt
   clean_debconf_passwords '/var/cache/debconf/passwords.dat'
   reset_debconf_grub_devices '/var/cache/debconf/config.dat'
   check_debconf_device_leak '/var/cache/debconf/config.dat'
   clean_nondeterministic
   clean_time_and_apt_sources
   clean_seeds_and_ids

   sync
}

if was_executed "${BASH_SOURCE[0]}"; then
   main "$@"
fi
