#! /bin/bash

shopt -s nullglob
# 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

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

. "$(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")
}

# 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() {
  if [ "$?" != "0" ]; then
    error "Error: Self update failed!"
  else
    milestone "Self update successfully finished"
  fi

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

    curl $curl_options --silent --globoff --location --retry 3 --retry-connrefused \
      --connect-timeout 20 --max-time 10 --fail --show-error --output "$RESPONSE_FILE" \
      -- "$query"

    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
    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 the "ssl_verify=no" query to the repository URL for libzypp
        # NOTE: there is a "trurl" (https://github.com/curl/trurl) tool for processing
        # URLs in CLI and scripts, but it is not included in the distribution :-/
        local base
        local query
        # split the string at the "?" separator
        query="${update_url#*\?}"
        base="${update_url%\?*}"

        # 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
          update_url="${base}?ssl_verify=no"
        else
          # a query is already present, append the new part
          update_url="${base}?${query}&ssl_verify=no"
        fi
      fi

      local 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="$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 [ -s "$REG_REPO_FILE" ]; then
      prepare_chroot
      add_update_repos "$REG_REPO_FILE"
    # 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
}

install_updates
