#!/bin/bash
# Tries to update the Virtual/Physical Appliance package once a day. Please note that this
# might fail if another apt-get (e.g. te-agent auto-update is running), but as n
# approaches infinity (n being the number of days), te-va is guaranteed to be
# updated (we could poll until apt-get is no longer locked, but this could hang
# other cron jobs).

set -euo pipefail

export DEBIAN_FRONTEND=noninteractive

CMD_TIMEOUT_SECONDS=43200
LOG_FILE="/var/log/te-va/te-va-cron.log"
LOG_DIR="$(dirname "${LOG_FILE}")"
mkdir -p "${LOG_DIR}"

timestamp() {
  date "+%Y-%m-%d  %H:%M:%S"
}

log_line() {
  local prefix="$1"
  local command="$2"
  printf '%s %s %s\n' "$(timestamp)" "${prefix}" "${command}" >> "${LOG_FILE}"
}

log_blank_line() {
  printf '\n' >> "${LOG_FILE}"
}

apt_locks_available() {
  local -a apt_locks=(
    /var/lib/dpkg/lock
    /var/lib/dpkg/lock-frontend
    /var/lib/apt/lists/lock
    /var/cache/apt/archives/lock
  )
  local lock
  for lock in "${apt_locks[@]}"; do
    local lock_pid
    if ! lock_pid=$(lsof -t "${lock}" 2>/dev/null | head -n1); then
      continue
    fi
    if [[ -n "${lock_pid}" ]]; then
      log_line "Lock busy:" "${lock} held; skipping sidecar update run."
      local pstree_output
      pstree_output=$(pstree -asA -p "${lock_pid}" 2>/dev/null || true)
      log_line "Lock pstree:" "${lock} -> ${pstree_output:-unavailable}"
      return 1
    fi
  done
  return 0
}

log_and_run() {
  if (($# == 0)); then
    return 1
  fi

  local -a run_command=( "$@" )
  if command -v timeout >/dev/null 2>&1; then
    run_command=( timeout --foreground "${CMD_TIMEOUT_SECONDS}" "${run_command[@]}" )
  fi

  local final_command
  printf -v final_command '%q ' "${run_command[@]}"
  final_command="${final_command% }"

  log_blank_line
  log_line "Log started:" "${final_command}"
  "${run_command[@]}" >> "${LOG_FILE}" 2>&1
  local status=$?

  log_line "Log ended:" "${final_command} (exit ${status})"
  log_blank_line

  return "${status}"
}

function te_update {
  log_and_run apt-get clean
  log_and_run apt-get update
  log_and_run apt-get -y --allow-downgrades --allow-change-held-packages install te-va
  log_and_run apt-get -y --allow-downgrades --allow-change-held-packages install te-agent-utils
}

function kernel_cleanup {
  for kernel in $(
    dpkg -l |
      grep -E "^ii[[:space:]]+linux-image-[0-9]" |
      awk '{print $2}' |
      grep -v "$(uname -r)" |
      sed -n '$!p'
  ); do
    log_and_run apt-mark auto "$kernel"
  done
  log_and_run apt-get autoremove -y
}

# CA-510a
function unhold_libnss3 {
  for i in $(apt-mark showhold | grep libnss3); do
    # apt-mark unhold not always work if there are internal dependencies
    # dpkg does work
    log_and_run "echo \"${i} install\" | dpkg --set-selections"
  done
}

function update {
  if ! apt_locks_available; then
    return 0
  fi

  te_update
  kernel_cleanup
  unhold_libnss3
}

update
