#!/bin/sh

set -eu

logmsg() {
  priority=$1
  shift

  logger -p "$priority" -t puavo-pkg-update "$@" || true
  echo "PUAVO-PKG-UPDATE: $@" >&2 || true
}

if ! mountpoint -q /images; then
  logmsg user.err \
    'trying to run puavo-pkg-update where /images is not a mounted partition'
  exit 1
fi

installed_links_dir='/var/lib/puavo-pkg/installed'
installers_dir='/images/puavo-pkg/installers'
topdomain=$(cat /etc/puavo/topdomain)

reload_menudata=false

puavo_pkg_server=$(puavo-conf puavo.image.servers | awk '{ print $1 }')
if [ -z "$puavo_pkg_server" ]; then
  read topdomain < /etc/puavo/topdomain
  puavo_pkg_server="cdn.${topdomain}"
fi

if ! puavo_pkgs_branch=$(puavo-conf puavo.pkgs.branch); then
  echo 'can not get puavo.pkgs.branch puavo-conf, can not update packages' >&2
  exit 1
fi
if ! printf "%s" "$puavo_pkgs_branch" | grep -Eqx '[a-z]+'; then
  echo 'the puavo.pkgs.branch is not in correct format, exiting' >&2
  exit 1
fi

mkdir -p "$installers_dir"

is_version_number() {
  echo "$1" | grep -Eqx '[0-9]+' && [ "$1" -lt 1000000 ]
}

download_installer() {
  puavo_pkg=$1
  version=$2

  rm -f ${installers_dir}/${puavo_pkg}/*.tmp

  do_file_check=false
  if [ "$version" = 'latest' ]; then
    :
  elif is_version_number "$version"; then
    version="version-${version}"
  else
    do_file_check=true
  fi

  installer_subpath="${puavo_pkg}/${version}.tar.gz"
  installer_fullpath="${installers_dir}/${installer_subpath}"

  if $do_file_check && [ -e "$installer_fullpath" ]; then
    return 0
  fi

  installer_tmppath="${installer_fullpath}.tmp"
  pkg_url="https://${puavo_pkg_server}/puavo-pkg/${puavo_pkgs_branch}/${installer_subpath}.gpg"
  unverified_installer_tmppath="${installer_fullpath}.gpg.tmp"

  logmsg user.info "downloading ${pkg_url}"

  mkdir -p "${installers_dir}/${puavo_pkg}" || true
  if ! wget -q --ca-certificate=/etc/puavo-conf/rootca.pem \
    -O "$unverified_installer_tmppath" "$pkg_url"; then
      logmsg user.err "could not download installer from ${pkg_url}"
      rm -f "$unverified_installer_tmppath"
      return 1
  fi

  logmsg user.info "verifying ${pkg_url}"

  gnupg_dir="/root/.puavo/gnupg/${puavo_pkg_server}/pkg"
  exec 4< "$gnupg_dir"
  if ! flock -w 10 -x 4; then
    echo "could not lock $gnupg_dir for gpg verification" >&2
    return 1
  fi
  if ! gpg --decrypt --homedir "$gnupg_dir" \
    "$unverified_installer_tmppath" 2>/dev/null > "$installer_tmppath"; then
      logmsg user.err "verification failed for ${unverified_installer_tmppath}"
      rm -f "$unverified_installer_tmppath" "$installer_tmppath" || true
      flock -u 4
      return 1
  fi
  flock -u 4
  rm -f "$unverified_installer_tmppath"

  # we lookup package id, because $version might be "latest" or just
  # the version number

  pkg_id=$(
    tar --wildcards -Ozx -f "$installer_tmppath" '*/.puavo-pkg-version' \
      | awk '$1 == "package-id" { print $2 }') || return 1
  if [ -z "$pkg_id" ]; then
    logmsg user.err "could not determine package id from ${installer_subpath}"
    rm -f "$installer_tmppath"
    return 1
  fi

  # get real version and put it into the version specific path
  installer_fullpath="${installers_dir}/${puavo_pkg}/${pkg_id}.tar.gz"

  if [ ! -e "$installer_fullpath" ]; then
    mv "$installer_tmppath" "$installer_fullpath" || return 1
    logmsg user.notice "downloaded/verified installer for ${puavo_pkg} with id ${pkg_id}"
  else
    rm -f "$installer_tmppath"
    logmsg user.info "we already have ${puavo_pkg} with id ${pkg_id}"
  fi

  echo "$installer_fullpath"
}

extract_pkg() {
  puavo_pkg=$1
  version=$2

  if ! installer_fullpath=$(download_installer "$puavo_pkg" "$version"); then
    return 1
  fi
  if ! puavo-pkg show "$installer_fullpath" > /dev/null 2>&1; then
    return 1
  fi
}

install_pkg() {
  puavo_pkg=$1
  version=$2

  if ! installer_fullpath=$(download_installer "$puavo_pkg" "$version"); then
    return 1
  fi

  old_package_version=$(
    awk '$1 == "version" { print $2 }' \
      "${installed_links_dir}/${puavo_pkg}/.puavo-pkg-version" 2>/dev/null || true)
  if ! new_package_version=$(
         tar --wildcards -Ozx -f "$installer_fullpath" '*/.puavo-pkg-version' \
           | awk '$1 == "version" { print $2 }'); then
    echo "Could not determine new package version for $puavo_pkg $version" >&2
    return 1
  fi

  if [ -z "$old_package_version" ] \
    || [ "$old_package_version" -ne "$new_package_version" ]; then
      logmsg user.notice "installing ${puavo_pkg} from ${installer_fullpath}"
      puavo-pkg install "$installer_fullpath"
      reload_menudata=true
  fi
}

prepare_pkg() {
  puavo_pkg=$1
  version=$2

  installation_dir=$(readlink "${installed_links_dir}/${puavo_pkg}" \
                       2>/dev/null || true)
  # Do not unconfigure if installed into the image
  # (or if not installed at all).  Allows preparing packages on the side
  # while they are still in the image.
  case "$installation_dir" in
    '')                            ;;
    /var/lib/puavo-pkg/packages/*) ;;
    *)                             puavo-pkg unconfigure "$puavo_pkg" || return 1 ;;
  esac

  if ! installer_fullpath=$(download_installer "$puavo_pkg" "$version"); then
    return 1
  fi

  puavo-pkg prepare "$installer_fullpath"
}

remove_pkg() {
  puavo_pkg=$1

  puavo_pkg_status=0
  SC_ERR_NOPKG=3
  puavo-pkg remove "$puavo_pkg" || puavo_pkg_status=$?
  if [ "$puavo_pkg_status" -eq "$SC_ERR_NOPKG" ]; then
    return 0
  elif [ "$puavo_pkg_status" -ne 0 ]; then
    logmsg user.err "error in removing ${puavo_pkg}"
    return 1
  fi

  logmsg user.notice "removed ${puavo_pkg}"

  reload_menudata=true
}

logmsg user.notice 'puavo-pkg-update starting'

if [ "${1:-}" = '--update-installers' ]; then
  puavo_pkg_package=''
  puavo_pkg_specs=$(puavo-conf puavo.pkgs.ui.pkglist \
                      | xargs -n 1 | awk '$1 != "" { print $1, "extract" }')
else
  puavo_pkg_package=${1:-}
  puavo_pkg_specs=$(
    puavo-conf \
      | awk -v pkg="$puavo_pkg_package" '
          $1 ~ /^puavo\.pkg\.[^.]+$/ {
            gsub(/^puavo\.pkg\./, "", $1)
            if (pkg == "" || pkg == $1) { print }
          }')
fi

if [ -n "$puavo_pkg_package" -a -z "$puavo_pkg_specs" ]; then
  logmsg user.err "no such puavo-pkg package as '${puavo_pkg_package}'"
  exit 1
fi

status=0

OLDIFS="$IFS"
IFS="
"
for pkgspec in $puavo_pkg_specs; do
  IFS="$OLDIFS"
  puavo_pkg=$(echo "$pkgspec" | awk '{ print $1 }')
  action=$(echo "$pkgspec" | awk '{ print $2 }')

  puavo_pkg_installers_dir="${installers_dir}/${puavo_pkg}"
  mkdir -p "$puavo_pkg_installers_dir"
  exec 3< "$puavo_pkg_installers_dir"
  if ! flock -w 10 -x 3; then
    logmsg user.err "did not get a lock on ${puavo_pkg_installers_dir}"
    status=1
    continue
  fi

  if [ -z "$action" ]; then
    action='switch-to-system'
  else
    logmsg user.info "installation directive on ${puavo_pkg} is: $action"
  fi

  case "$action" in
    do-nothing)
      ;;
    extract)
      extract_pkg "$puavo_pkg" latest || status=1
      ;;
    extract:*)
      version=${action#*:}
      extract_pkg "$puavo_pkg" "$version" || status=1
      ;;
    install|latest)
      install_pkg "$puavo_pkg" latest || status=1
      ;;
    install:*)
      version=${action#*:}
      install_pkg "$puavo_pkg" "$version" || status=1
      ;;
    prepare)
      prepare_pkg "$puavo_pkg" latest || status=1
      ;;
    prepare:*)
      version=${action#*:}
      prepare_pkg "$puavo_pkg" "$version" || status=1
      ;;
    remove)
      remove_pkg "$puavo_pkg" || status=1
      ;;
    switch-to-system)
      puavo-pkg switch-to-system "$puavo_pkg" || status=1
      ;;
    *)
      logmsg user.err "unsupported action on package ${puavo_pkg}: ${action}"
      status=1
      ;;
  esac

  flock -u 3
done

exec 3< "$installers_dir"
if ! flock -w 10 -x 3; then
  logmsg user.err "did not get a lock on ${installers_dir}, exiting"
  exit 1
fi

logmsg user.notice 'garbage collecting old/broken puavo-pkg packages'
if ! puavo-pkg gc-installations; then
  logmsg user.err 'problem in garbage collecting puavo packages'
  status=1
fi

logmsg user.notice 'garbage collecting old puavo-pkg upstream packs'
if ! puavo-pkg gc-upstream-packs; then
  logmsg user.err 'problem in garbage collecting puavo-pkg upstream packs'
  status=1
fi

if $reload_menudata; then
  logmsg user.notice 'reloading puavomenu data'
  for puavomenu_socket in /run/user/*/puavomenu; do
    echo reload-menudata | nc -U -w 5 "$puavomenu_socket" || true
  done
fi

logmsg user.notice "puavo-pkg-update finishing with status ${status}"

exit $status
