#!/bin/bash
# SPDX-Version: 3.0
# SPDX-CreationInfo: 2025-05-05; WEIDNER, Marc S.; <msw@coresecret.dev>
# SPDX-ExternalRef: GIT https://git.coresecret.dev/msw/CISS.debian.live.builder.git
# SPDX-FileContributor: WEIDNER, Marc S.; Centurion Intelligence Consulting Agency
# SPDX-FileCopyrightText: 2024-2025; WEIDNER, Marc S.; <msw@coresecret.dev>
# SPDX-FileType: SOURCE
# SPDX-License-Identifier: EUPL-1.2 OR LicenseRef-CCLA-1.0
# SPDX-LicenseComment: This file is part of the CISS.debian.installer.secure framework.
# SPDX-PackageName: CISS.debian.live.builder
# SPDX-Security-Contact: security@coresecret.eu

declare -gr VERSION="Master V8.13.404.2025.11.10"

### VERY EARLY CHECK FOR DEBUGGING
if [[ $* == *" --debug "* ]]; then
  declare -gr EARLY_DEBUG=true
  # Set a verbose PS4 prompt including timestamp, source, line, exit status and function name
  declare -gr PS4='\e[97m+\e[0m\e[96m$(date +%T.%4N)\e[0m\e[97m:\e[0m\e[92m[${BASH_SOURCE[0]}:${LINENO}]\e[0m\e[97m|\e[0m\e[93m$?\e[0m\e[97m>\e[0m\e[95m${FUNCNAME[0]:-main}()\e[0m \e[97m>>\e[0m '
  # shellcheck disable=SC2155
  declare -gr DEBUG_LOG="/tmp/ciss_live_builder_$$.log"
  # Generates empty DEBUG_LOG
  touch "${DEBUG_LOG}" && chmod 0600 "${DEBUG_LOG}"
  # Open file descriptor 42 for writing to the debug log
  exec 42>| "${DEBUG_LOG}"
  # Write Debug Log Header https://www.gnu.org/software/bash/manual/html_node/Bash-Variables
  {
    printf "\e[97m+\e[0m\e[92m%s: CISS.debian.live.builder Debug Log \e[0m\n" "$(date +%T.%4N)"
    printf "\e[97m+\e[0m\e[92m%s: Version            : %s \e[0m\n" "$(date +%T.%4N)" "${VERSION}"
    printf "\e[97m+\e[0m\e[92m%s: Epoch              : %s \e[0m\n" "$(date +%T.%4N)" "${EPOCHREALTIME}"
    printf "\e[97m+\e[0m\e[92m%s: Bash MAJ Release   : %s \e[0m\n" "$(date +%T.%4N)" "${BASH_VERSINFO[0]}"
    printf "\e[97m+\e[0m\e[92m%s: Bash MIN Version   : %s \e[0m\n" "$(date +%T.%4N)" "${BASH_VERSINFO[1]}"
    printf "\e[97m+\e[0m\e[92m%s: Bash Patch Level   : %s \e[0m\n" "$(date +%T.%4N)" "${BASH_VERSINFO[2]}"
    printf "\e[97m+\e[0m\e[92m%s: Bash Build Version : %s \e[0m\n" "$(date +%T.%4N)" "${BASH_VERSINFO[3]}"
    printf "\e[97m+\e[0m\e[92m%s: Bash Release       : %s \e[0m\n" "$(date +%T.%4N)" "${BASH_VERSINFO[4]}"
    printf "\e[97m+\e[0m\e[92m%s: UID                : %s \e[0m\n" "$(date +%T.%4N)" "${UID}"
    printf "\e[97m+\e[0m\e[92m%s: EUID               : %s \e[0m\n" "$(date +%T.%4N)" "${EUID}"
    printf "\e[97m+\e[0m\e[92m%s: Hostname           : %s \e[0m\n" "$(date +%T.%4N)" "${HOSTNAME}"
    printf "\e[97m+\e[0m\e[92m%s: Script name        : %s \e[0m\n" "$(date +%T.%4N)" "$0"
    printf "\e[97m+\e[0m\e[92m%s: Argument counter   : %s \e[0m\n" "$(date +%T.%4N)" "$#"
    printf "\e[97m+\e[0m\e[92m%s: Argument string    : %s \e[0m\n" "$(date +%T.%4N)" "$*"
    printf "\e[97m+\e[0m\e[92m%s: Script PID         : %s \e[0m\n" "$(date +%T.%4N)" "$$"
    printf "\e[97m+\e[0m\e[92m%s: Script Parent PID  : %s \e[0m\n" "$(date +%T.%4N)" "${PPID}"
    printf "\e[97m+\e[0m\e[92m%s: Script work DIR    : %s \e[0m\n" "$(date +%T.%4N)" "${PWD}"
    printf "\e[97m+\e[0m\e[92m%s: Shell Options      : %s \e[0m\n" "$(date +%T.%4N)" "$-"
    printf "\e[97m+\e[0m\e[92m%s: BASHOPTS           : %s \e[0m\n" "$(date +%T.%4N)" "${BASHOPTS}"
    printf "\e[97m+\e[0m\e[92m%s: === Debug Log ===  : \e[0m\n" "$(date +%T.%4N)"
  } >&42
  # Tell Bash to send xtrace output to FD 42
  export BASH_XTRACEFD=42
  # Enable inheritable shell options
  export SHELLOPTS
  # Turn on xtrace
  set -x
else
  declare -gr EARLY_DEBUG=false
fi

### Definition of error codes
declare -gir ERR_NOT_USER_0=128
declare -gir ERR_UNSPPTBASH=255

### Definition of error trap vars
# declare -g errcode="" # = $?                   = $1 = ERRCODE
# declare -g errscrt="" # = ${BASH_SOURCE[0]}    = $2 = ERRSCRT
# declare -g errline="" # = ${LINENO}            = $3 = ERRLINE
# declare -g errfunc="" # = ${FUNCNAME[0]:-main} = $4 = ERRFUNC
# declare -g errcmmd="" # = ${$BASH_COMMAND}     = $5 = ERRCMMD

### Preliminary vars declaration
declare -gr argument_count="$#"
declare -gr argument_string="$*"

### Preliminary checks
[[ ${EUID} -ne 0 ]] \
  && printf "\e[91m❌ Please make sure you are 'root'! Bye... \e[0m\n" >&2 && exit "${ERR_NOT_USER_0}"
[[ -z ${BASH_VERSINFO[0]} ]] \
  && printf "\e[91m❌ Please make sure you are using 'bash'! Bye... \e[0m\n" >&2 && exit "${ERR_UNSPPTBASH}"
[[ $(kill -l | grep -c SIG) -eq 0 ]] \
  && printf "\e[91m❌ Please make sure you are calling the script without leading 'sh'! Bye... \e[0m\n" >&2 && exit "${ERR_UNSPPTBASH}"
[[ ${BASH_VERSINFO[0]} -lt 5 ]] \
  && printf "\e[91m❌ Minimum requirement is bash 5.1. You are using '%s'! Bye... \e[0m\n" "${BASH_VERSION}" >&2 && exit "${ERR_UNSPPTBASH}"
[[ ${BASH_VERSINFO[0]} -le 5 ]] && [[ ${BASH_VERSINFO[1]} -le 1 ]] \
  && printf "\e[91m❌ Minimum requirement is bash 5.1. You are using '%s'! Bye... \e[0m\n" "${BASH_VERSION}" >&2 && exit "${ERR_UNSPPTBASH}"

### For all options see https://www.gnu.org/software/bash/manual/bash.html#The-Set-Builtin
set -o errexit   # Exit script when a command exits with non-zero status, the same as "set -e".
set -o nounset   # Exit script on use of an undefined variable, the same as "set -u".
set -o pipefail  # Makes pipelines return the exit status of the last command in the pipe that failed.
set -o noclobber # Prevent overwriting, the same as "set -C".

#######################################
# Trap function to be called on 'ERR'.
# Globals:
#   DEBUG_LOG
#   EARLY_DEBUG
#   VERSION
#   argument_count
#   argument_string
# Arguments:
#   $1: $?
#   $2: ${BASH_SOURCE[0]}
#   $3: ${LINENO}
#   $4: ${FUNCNAME[0]:-main}
#   $5: ${BASH_COMMAND}
#######################################
# shellcheck disable=SC2317
trap_on_err() {
  declare -r errcode="$1"
  declare -r errscrt="$2"
  declare -r errline="$3"
  declare -r errfunc="$4"
  declare -r errcmmd="$5"
  trap - ERR
  if [[ "${errcode}" -ne 127 ]]; then
    printf "\e[91m❌ Hash Generation Process failed.\e[0m\n" >&2
    printf "\e[91m❌ Version     : '%s' \e[0m\n" "${VERSION}" >&2
    printf "\e[91m❌ Error       : '%s' \e[0m\n" "${errcode}" >&2
    printf "\e[91m❌ Line        : '%s' \e[0m\n" "${errline}" >&2
    printf "\e[91m❌ Script      : '%s' \e[0m\n" "${errscrt}" >&2
    printf "\e[91m❌ Function    : '%s' \e[0m\n" "${errfunc}" >&2
    printf "\e[91m❌ Command     : '%s' \e[0m\n" "${errcmmd}" >&2
    printf "\e[91m❌ Arguments # : '%s' \e[0m\n" "${argument_count}" >&2
    printf "\e[91m❌ Arguments   : '%s' \e[0m\n" "${argument_string}" >&2
    if "${EARLY_DEBUG}"; then
      printf "\e[91m❌ Debug Log   : '%s' \e[0m\n" "${DEBUG_LOG}" >&2
      printf "\e[91m❌           cat  %s  \e[0m\n" "${DEBUG_LOG}" >&2
    fi
    printf "\n"
  fi
}

trap 'trap_on_err "$?" "${BASH_SOURCE[0]}" "${LINENO}" "${FUNCNAME[0]:-main}" "${BASH_COMMAND}"' ERR

### Initialization
# shellcheck disable=SC2155
declare -gr SCRIPT_FULLPATH="$(readlink -f "${BASH_SOURCE[0]:-$0}")"
# shellcheck disable=SC2155
declare -gr WORK_DIR="$(dirname "${SCRIPT_FULLPATH}")"
declare -gr BASE_DIR="${WORK_DIR%/.iso}"
declare -gr CFG_DIR="${BASE_DIR}/.cfg"
declare -gr PRES_FILE="${BASE_DIR}/preseed.cfg"
declare -gr HASH_FILE="${CFG_DIR}/md5sum.txt"
declare -ga hashes=()

# shellcheck disable=SC2188
>| "${HASH_FILE}"

#######################################
# Generator for md5 Hashes
# Globals:
#   CFG_DIR
#   HASH_FILE
#   hash
#   hashes
# Arguments:
#  None
#######################################
gen_hash() {
  # Enable nullglob so that non-matching patterns expand to nothing
  shopt -s nullglob
  declare file
  declare filename
  # Loop over all *.cfg files in CFG_DIR
  for file in "${CFG_DIR}"/*.cfg; do
    # Only process if it's a regular file
    if [[ -f "${file}" ]]; then
      # Calculate md5 hash (only the hash value)
      hash=$(md5sum "${file}" | awk '{ print $1 }')
      # Extract the filename without a path
      filename=${file##*/}
      # Append "hash filename" to HASH_FILE
      echo "${hash} ${filename}" >> "${HASH_FILE}"
      # Add hash to array
      hashes+=("${hash}")
    fi
  done
}

gen_hash

{
  declare in_hash_block=false
  declare outer_line
  declare hash
  while IFS= read -r outer_line; do
    # Check if a line contains "#BOH" and start the hash insertion block
    if [[ ${outer_line} == "#BOH" ]]; then
      echo "${outer_line}"
      # shellcheck disable=SC1003
      echo 'd-i preseed/include/checksum string \'

      # Add all new hashes from the array "hashes" except the last one
      for ((i = 0; i < ${#hashes[@]} - 1; i++)); do
        hash="${hashes[i]}"
        echo "${hash} \\"
      done

      # Output the last hash without the trailing backslash.
      echo "${hashes[@]: -1}"

      # Set the flag for the hash block to "true".
      in_hash_block=true
      continue
    fi

    # Check if the line "#EOH" has been reached to end the hash block.
    if [[ ${outer_line} == "#EOH" && ${in_hash_block} == true ]]; then
      echo "${outer_line}"
      in_hash_block=false
      continue
    fi

    # Skip lines within the hash block (old hashes and d-i line).
    if [[ ${in_hash_block} == true ]]; then
      # Skip the line "d-i preseed/include/checksum string".
      if [[ ${outer_line} =~ ^d-i\ preseed/include/checksum\ string ]]; then
        continue
      fi
      # Skip lines with old hashes.
      if [[ ${outer_line} =~ [a-f0-9]{32}   ]]; then
        continue
      fi
    fi

    # Leave all other rows unchanged.
    echo "${outer_line}"

  done < "${PRES_FILE}"
} >| "${PRES_FILE}.tmp"

mv -f "${PRES_FILE}.tmp" "${PRES_FILE}"
sed -i ':a;N;/\n#EOH/!ba;s/\(\n\)\+\(#EOH\)/\n#EOH/' "${PRES_FILE}"
sed -i '$d' "$PRES_FILE"
echo "# Written by: $0 Version: ${VERSION} at: $(date +%T.%4N)" >> "${PRES_FILE}"
printf "\e[92m✅ '%s' Process successful.\e[0m\n" "${0}"
exit 0
# vim: number et ts=2 sw=2 sts=2 ai tw=128 ft=sh
