#! /bin/bash

shopt -s nullglob

[ -e /dracut-state.sh ] && . /dracut-state.sh

# Later we could disable "-x" or enable it on request, let's be more verbose in
# the initial implementation. Since we have enabled using the plymouth splash
# screen the verbose details will not be visible to users by default.
set -ex

. "$(dirname "$0")"/../lib/live-self-update/conf.sh

RESPONSE_FILE="$RUN_DIR/server_response"
REG_REPO_FILE="$RUN_DIR/reg_repos"
OSR="/etc/os-release"
ARCH=$(uname -m)

# do the cleanup at the end, trap ensures it runs even if something fails
trap cleanup_chroot EXIT

# helper function to log important steps (highlighted in the journalctl output)
# use "set +x" to avoid logging the message again (it is already logged in the
# original caller place so it would be actually logged three times...)
# see https://www.freedesktop.org/software/systemd/man/latest/sd-daemon.html
# for the log level numbers
milestone() {
  ({ set +x; } 2>/dev/null; echo "<5>$1")
}

# log warning
warning() {
  ({ set +x; } 2>/dev/null; echo "<4>$1")
}

# log error
error() {
  ({ set +x; } 2>/dev/null; echo "<3>$1")
}

# normal message
info() {
  ({ set +x; } 2>/dev/null; echo "<6>$1")
}

# the proxy might contain a password, make sure it is not displayed or logged
# when processing it
set +x
# get the configured network proxy (empty string if not configured)
# see ../lib/dracut/modules.d/98dracut-menu/dracut-cmdline-menu.sh
# get the last found, it has the highest priority
PROXY=$(grep "\bproxy=" /proc/cmdline /etc/cmdline.d/*  | tail -n 1 | sed "s/.*\bproxy=\([^ \t]*\).*/\1/")

if [ -n "$PROXY" ]; then
  if [[ "$PROXY" == *@* ]]; then
    # looks like there is a username or password in the URL, do not print it
    milestone "Using authenticated HTTP proxy"
  else
    milestone "Using proxy server $PROXY"
  fi

  # set the proxy for all zypper and curl calls, for security reasons curl
  # uses only the lower case name, zypper accepts that as well,
  # see https://everything.curl.dev/usingcurl/proxies/env.html
  export http_proxy="$PROXY"
  export https_proxy="$PROXY"

  # hide the original URL to avoid leaking the password in URL later
  PROXY=1
fi
set -x

# helper for running a command in the live root system, works both when running
# in initramfs ($NEWROOT is set) and in already mounted root ($NEWROOT unset)
run_command() {
  if [ -z "$NEWROOT" ]; then
    "$@"
  else
    "$NEWROOT/usr/bin/chroot" "$NEWROOT" "$@"
  fi
}

# Prepare the system root to be used by scripts before switching to it.
prepare_chroot() {
  if [ -n "$NEWROOT" ]; then
    milestone "Preparing for the self update..."

    # make system directories active also in the chroot directory
    mount -o bind /run "$NEWROOT"/run
    mount -o bind /sys "$NEWROOT"/sys
    mount -o bind /proc "$NEWROOT"/proc
    mount -o bind /dev "$NEWROOT"/dev
    [ -f /run/NetworkManager/resolv.conf ] && cp /run/NetworkManager/resolv.conf "$NEWROOT"/etc/resolv.conf
  fi
  # remove any existing self-update repositories, probably left-overs from the previous run
  rm -f "$NEWROOT"/etc/zypp/repos.d/self-update-*.repo
}

# Revert the changes done by prepare_chroot() and restore the system to the
# original state, called from bash "trap" handler
cleanup_chroot() {
  local result="$?"

  if [ "$result" != "0" ]; then
    error "Error: Self update failed! (exit status: $result)"
  else
    milestone "Self update successfully finished"
  fi

  # log the result to be checked by openQA or Agama itself
  echo "$result" > "$RESULT_FILE"

  if [ -n "$NEWROOT" ]; then
    rm -f "$NEWROOT"/etc/resolv.conf
    # do not fail if not mounted
    findmnt "$NEWROOT"/run > /dev/null && umount "$NEWROOT"/run
    findmnt "$NEWROOT"/sys > /dev/null && umount "$NEWROOT"/sys
    findmnt "$NEWROOT"/proc > /dev/null && umount "$NEWROOT"/proc
    findmnt "$NEWROOT"/dev > /dev/null && umount "$NEWROOT"/dev
  fi
}

# read the data from /etc/os-release file (source it)
read_os_release() {
  # get the product data
  if [ -f "$OSR" ]; then
    . "$OSR"
  else
    error "File $OSR is missing!"
    exit 1
  fi
}

# ask the registration server (SCC or RMT) for the self-update URL
query_reg_server() {
  local server
  # custom server (RMT)
  if [ -f "$SERVER_FILE" ]; then
    server=$(< "$SERVER_FILE")
  # default server (SCC)
  elif  [ -f "$CONFIG_DEFAULT_REG_SERVER_FILE" ]; then
    server=$(< "$CONFIG_DEFAULT_REG_SERVER_FILE")
  fi

  if [ -n "$server" ]; then
    read_os_release

    local query
    query="$server/connect/repositories/installer?identifier=$NAME&version=$VERSION_ID&arch=$ARCH"
    info "Running query $query...."

    local curl_options
    curl_options=(--silent --globoff --location --retry 3 --retry-connrefused \
      --connect-timeout 20 --max-time 10 --fail --show-error --output "$RESPONSE_FILE")

    if grep -q "\binst.self_update_ssl=0\b" /run/agama/cmdline.d/agama.conf; then
      warning "Disabling SSL check for $server"
      curl_options+=("--insecure")
    fi

    if [ -n "$PROXY" ]; then
      # automatically detect the proxy authentication method (basic/digest)
      curl_options+=("--proxy-anyauth")
    fi

    if curl "${curl_options[@]}" -- "$query" && [ -s "$RESPONSE_FILE" ]; then
      info "Found self-update repository: $(jq -r ".[] | select(.installer_updates==true) | .description" "$RESPONSE_FILE") "
      jq -r ".[] | select(.installer_updates==true) | .url" "$RESPONSE_FILE" > "$REG_REPO_FILE"
    else
      warning "Contacting the registration server failed"
      # SCC/RMT query failed and no fallback configured
      if [ ! -s "$CONFIG_FALLBACK_FILE" ]; then
        error "Cannot obtain the self-update repository"
        exit 1
      fi
    fi
  else
    milestone "Registration server not defined"
  fi
}

# add the self-update repositories into the system
add_update_repos() {
  milestone "Adding self update repositories..."
  # add the self-update repositories
  while read -r update_url; do
    if [ -n "$update_url" ]; then
      local repo
      repo="self-update-$index"

      if grep -q "\binst.self_update_ssl=0\b" /run/agama/cmdline.d/agama.conf; then
        warning "Disabling SSL check for repository $update_url"
        # add "ssl_verify=no" query to the repository URL
        update_url=$(url_add_query "$update_url" "ssl_verify=no")
      fi

      local zypper_options
      zypper_options=()
      # allow using unsigned repository
      if grep -q "\binst.self_update_unsigned_repo=1\b" /run/agama/cmdline.d/agama.conf; then
        warning "Unsigned repository is allowed"
        zypper_options+=("--gpgcheck-allow-unsigned-repo")
      fi

      # completely disable GPG checks, not recommended, if possible the options
      # above should be preferred
      if grep -q "\binst.self_update_gpg=0\b" /run/agama/cmdline.d/agama.conf; then
        warning "Disabling all GPG checks!"
        zypper_options+=("--no-gpgcheck")
      fi

      run_command zypper addrepo "${zypper_options[@]}" -f "$update_url" "$repo"
      self_update_repos+=("$repo")
      ((++index))
    fi
  done < "$1"
}

# Reads the configured fallback repository, the URL might contain several
# placeholders. This way, it works across multiple architectures and does not
# contain a hardcoded version (the fallback URL should work with SLES-16.0,
# SLES-16.1, SLES-16.2...)
read_fallback_repos() {
  # Substituting $arch pattern with the architecture of the current system.
  # Substituting these variables with the /etc/os-release content:
  #   $os_release_name       => NAME
  #   $os_release_id         => ID
  #   $os_release_version    => VERSION
  #   $os_release_version_id => VERSION_ID
  # IMPORTANT NOTE: the "VERSION_ID" replacement must happen *before*
  # the "VERSION" replacement because it partially matches that string!
  sed -e "s/\$os_release_name/$NAME/g" -e "s/\$os_release_id/$ID/g" \
    -e "s/\$os_release_version_id/$VERSION_ID/g" \
    -e "s/\$os_release_version/$VERSION/g" \
    -e "s/\$arch/$ARCH/g" "$CONFIG_FALLBACK_FILE" > "$FALLBACK_REPOS_FILE"
}

# Applies all the updates
#
# Reads the URL of the updates from $AGAMA_DUD_INFO and process each one.
install_updates() {
  index=0
  self_update_repos=()

  if [ -f "$REPO_FILE" ]; then
    prepare_chroot
    add_update_repos "$REPO_FILE"
  else
    # try SCC/RMT
    query_reg_server

    # repo from the registration server
    if [ -f "$REG_REPO_FILE" ]; then
      if [ -s "$REG_REPO_FILE" ]; then
        prepare_chroot
        add_update_repos "$REG_REPO_FILE"
      else
        milestone "No repository found in the registration server, skipping self-update"
        exit 0
      fi
    # use a fallback URL
    elif [ -s "$CONFIG_FALLBACK_FILE" ]; then
      milestone "Using fallback repository"
      read_fallback_repos
      prepare_chroot
      add_update_repos "$FALLBACK_REPOS_FILE"
    # no self-update repo found, exit (in the openSUSE installer)
    else
      milestone "No self-update repository configured, skipping self-update"
      exit 0
    fi
  fi

  local zypper_options
  # automatically import the repository GPG key
  if grep -q "\binst.self_update_import_key=1\b" /run/agama/cmdline.d/agama.conf; then
    warning "Enabled GPG key autoimport"
    zypper_options="--gpg-auto-import-keys"
  fi

  milestone "Loading repositories..."
  run_command zypper $zypper_options refresh "${self_update_repos[@]}"

  # build the list of repository options passed to zypper
  repo_options=()
  for repo in "${self_update_repos[@]}"; do
    repo_options+=("--repo" "$repo")
  done

  milestone "Updating packages..."
  # update the system only using the predefined self-update repositories,
  # allow downgrading and changing vendor (can be useful for testing)
  run_command zypper --non-interactive update --allow-downgrade --allow-vendor-change --details --no-recommends "${repo_options[@]}"

  milestone "Cleaning the system..."
  # delete the repository caches, not needed anymore, saves space in RAM disk
  run_command zypper clean --all

  # disable the self update repositories (keep them for debugging purposes)
  for repo in "${self_update_repos[@]}"; do
    run_command zypper modifyrepo --disable "$repo"
  done
}

# add a query parameter to URL
# $1 - base URL ("http://example.com")
# $2 - the query part ("foo=bar")
# returns "http://example.com?foo=bar"
url_add_query() {
  local base
  local query

  # split the string at the "?" separator
  query="${1#*\?}"
  base="${1%\?*}"

  # if the "?" separator is missing then "$query" equals to "$base"
  if [ -z "$query" ] || [ "$query" = "$base" ]; then
    # no query present, add a query part to the URL
    echo "${base}?${2}"
  else
    # a query is already present, append the new part
    echo "${base}?${query}&${2}"
  fi
}

install_updates
