#!/bin/bash
# barium helper scripts
# author: rosalinux.ru: betcher_
. /etc/initvars 
CURPWD="$(pwd)"
spid=''
PATH="${PATH}:/usr/lib/rosa-rw/scripts"
MUPDRSYNCOPT=" -ah --progress --stats "

export TEXTDOMAINDIR=/usr/share/locale
export TEXTDOMAIN=rosa-rw_functions
MSG01="$(gettext -s "Update process started. Please wait")"
MSG02="$(gettext -s "Update finished")"
MSG03="$(gettext -s "Please reboot")"
MSG04="$(gettext -s "Update system")"
MSG05="$(gettext -s "Check updates")"
MSG06="$(gettext -s "Aborted by user")"
MSG07="$(gettext -s "OS has not been restarted since the previous update")"
MSG08="$(gettext -s "Updates are not found")"
MSG09="$(gettext -s "Download failed")"
MSG10="$(gettext -s "Update operation is not supported for the backup version of the OS")"


function supress_screenlock() {
  command -V xdotool >/dev/null 2>&1
  ( while true ; do
  sleep 60
  xdotool mousemove_relative -- 0 1
  xdotool mousemove_relative -- 0 -1
  echo "wake up" 1>&2 
  done) &
  echo "$!" > /tmp/barium_update_screen_lock
}

function checkfreemedia() {
  if [ -f ${SYSMNT}/layer-base/0/VERSION ] ; then
    echolog "Freemedia mode disabled, system media already mountеd"
    return 0
  else
    echolog "Freemedia mode enabled. Try -mm (mount media key)"
    return 1 
  fi 
}

function check_instance(){
    [ $(pgrep -f 'barium[[:space:]]*update' |wc -l) -gt 2 ] && echoerror "An instance of the barium update is already running"
}

function mountmedia () {
  for script in /run/bin/remount_* ; do
    $script >/dev/null
  done
  if [ -f ${SYSMNT}/layer-base/0/VERSION ] ; then
    echolog "${LINENO}: System media mounted successfully"
    NEEDUMOUNT=yes
    return 0
  fi
  echolog "${LINENO}: System media is not mounted and cannot be mounted automatically"
  return 1
}

function echolog() {
 echo "$(basename $0): $(date +"%Y-%m-%d %H:%M:%S") $@" >>$LOGFILE
 echo "==> $@"
}

function echoerror() {
 echolog "$@"
 echowall "$@"
 resetMounts 
 exit 1
}

function echowall() {
  mdialog --passivepopup "barium_update: $@"  2>/dev/null 
}

function findsystemdir() {
  DISTRDEV=$(grep -m1 " $SYSMNT/layer-base/0 " /proc/self/mountinfo | awk '{ print $10 }')
  [ -e "$DISTRDEV" ] || checkfreemedia || exit 1 
  DISTRMPOINT=$(findmnt -lnf $DISTRDEV -o TARGET)
  [ -d "$DISTRMPOINT" ] || echoerror "System distro source mount point not found"
  DISTRPATH=${DISTRMPOINT}$(grep -m1 " $SYSMNT/layer-base/0 " /proc/self/mountinfo | awk '{ print $4 }')
  echolog "${LINENO}: ROSA-SYSTEM: $DISTRPATH"
  [[ "$DISTRPATH" == *.bak ]] && echoerror "$MSG10"
  [ -f "$DISTRPATH/vmlinuz" -a -d "$DISTRPATH/base" ] || return  1
  DISTRNAME=$(cat ${DISTRPATH}/VERSION |awk '{print $1}')
  DISTRVER=$(cat ${DISTRPATH}/VERSION |awk '{print $2}')
  [ -z "$DISTRNAME" -o -z "$DISTRVER" ] && return  1
}

function initparameters() {
  findsystemdir
  UPDATEPROT=rsync
  UPDATESERVER=barium.rosa.ru
  UPDATESHARE=/rosa/repository
  UPDATEMODULES=no
  [ -f /etc/dnf/vars/token ] && UPDATELOGIN=$(cat /etc/dnf/vars/token |head -n1)
  . /usr/lib/rosa-rw/os-config
  . /etc/ROSA-RW/config
  export RSYNC_PASSWORD=""
  [ "${UPDATEPASS}" ] && export RSYNC_PASSWORD="${UPDATEPASS}"
  [ "${UPDATELOGIN}" ] && UPDATESERVER="${UPDATELOGIN}@$UPDATESERVER"
  UPDATEURL=$UPDATEPROT://$UPDATESERVER/$UPDATESHARE/$UPDATEDIR/ROSA-SYSTEM/
}

# $1 filename
# $2 directory to save
function download() {
  echolog "${LINENO}: Downloading: $UPDATEURL$1" 
  echolog "${LINENO}: Save to: ${2}/$(basename $1)"
  case $UPDATEPROT in
  rsync)
    if [ -f ${2}/$(basename $1) ] ; then
      FILE_SIZE=$(du -k ${2}/$(basename $1) |cut -f1)
      FREE_SPACE=$(df ${2} --output=avail -k |tail -n1)
      echolog "${LINENO}: Old file exists: ${FILE_SIZE}Kb, sync" 
      if [ $(( "$FILE_SIZE" + 50000 )) -gt "$FREE_SPACE"  ] ; then
          echolog "${LINENO}: Not enouth free space, using rsync --inplace ..." 
          MUPDRSYNCOPT=" $MUPDRSYNCOPT --inplace "
      fi
    fi
    rsync $MUPDRSYNCOPT "$UPDATEURL$1" "$2"  && return 0
    ;;
  *)
      echoerror "${LINENO}: Protocol $UPDATEPROT is not supported currently, sorry"
  esac
  echoerror "${LINENO}: $MSG09"
}

# $1 filename
# $2 directory to save
function cdownload() {
  [ -f "$2/$1" ] && return 0
  download "$1" "$2" || return 1
}

# $1 filelist (MD5SUM)
# $2 directory to save
# $3 filter
function downloadfrom_repolist() {
  N=$(cat $1 |wc -l)
  step=$((60 / $N))
  percent=20
  echolog "${LINENO}: Download files from $1 list" "$(( percent + 2 ))"
  for a in $(gawk '{print $1}' $1) ;do
  percent=$(($percent + $step))
  echolog "${LINENO}: processing:  $a" $percent
  echo $a | grep -q "$3" || continue
  name2save=$(realpath $2)/"$a"
  path2save=$(dirname $name2save)
  oldfile=${DISTRPATH}/"$a"
  [ -f $oldfile ] && rsync -ah --progress $oldfile ${path2save}/ 
  download $a $path2save
  done
}

function checkserver() {
  echolog "${LINENO}: Checking connection to server ..."
  download VERSION       "$TMPDIR"
  download _REPOLIST     "$TMPDIR"
  download _REPOLIST.sig "$TMPDIR"
  echolog "${LINENO}: Connection to server is ok"
}

# $1 original _REPOLIST
# $2 new _REPOLIST
# $3 _REPOLIST with updates
function compare_repolists() {
  rm -f "$3" 2>/dev/null
    cat $1 $2 |sed 's/ .*MD5=/ /' |sort |uniq -u | cat - $2 |sed 's/ .*MD5=/ /' |sort |uniq -d > $3
  [ -n "$(cat $3)" ] || return 1
}


# $1 _REPOLIST file
# $2 directory with files
# $3 filter
function checkfilesums() {
local  checked MD5_FILE MD5_REPOLIST
  [ -f "$1" ] || echoerror "${LINENO}: $1 not exists"
  echolog "${LINENO}: Checking MD5 sums for files..."
  checked=ok 
  for a in $(find "$2" -type f) ;do
    echo "${LINENO}: Check: $a"
    if echo "$3" |grep -qw $(basename $a) ; then
        echo "${LINENO}: Skip $a"
        continue
    fi
    MD5_FILE=$(md5sum "$a" |cut -f1 -d ' ' |xargs)
    MD5_REPOLIST=$(cat $1 |grep -m1 $(echo $a |sed "s:^$2::") |sed 's/.*MD5=//' |xargs)
    if [ -z "${MD5_REPOLIST}" ] ; then
        echolog "${LINENO}: $a is not in $1"
    elif [ "${MD5_FILE}" != "${MD5_REPOLIST}" ] ; then
        echolog "${LINENO}: WARNING!!! md5sums not equal $a"
        checked=error
    fi
  done   
    if [ "$checked" != 'ok' ] ; then
        echolog "${LINENO}: Something went wrong, as you see. Continue (Y/N)?"     
        read aaa  
        [ "$aaa" != 'Y' -a "$aaa" != 'y' ] && echoerror "${LINENO}: Aborted..."
    else
        echolog "${LINENO}: Checking complete."
    fi
}

# $1 signature
# $2 file to check
function checksign() {
  if gpg --verify --keyring /usr/share/rosa-rw/pubkeys/main.gpg "$1" "$2" ;then
    echolog "${LINENO}: File $(basename $2) signed by /usr/share/rosa-rw/pubkeys/main.gpg"
    return 0
  fi
 echoerror "${LINENO}: Incorrect signature in $(basename $1)"
}

function checknewversion() {
  echolog "${LINENO}: Checking for new version ..."
  cdownload VERSION    "$TMPDIR"
  cdownload _REPOLIST     "$TMPDIR"
  cdownload _REPOLIST.sig "$TMPDIR"
  checksign "$TMPDIR/_REPOLIST.sig" "$TMPDIR/_REPOLIST" || exit 1
  CURVER=$(gawk '{print $2}' "${DISTRPATH}/VERSION" )
  NEWVER=$(gawk '{print $2}' "$TMPDIR/VERSION")
  # may be float
  if awk -v new="$NEWVER" -v cur="$CURVER" 'BEGIN {return_code=(new <= cur) ? 0 : 1; exit} END {exit return_code}'; then
    echolog "${LINENO}: New version not found."
    echolog "${LINENO}: server --> $NEWVER, current --> $CURVER"
    return 1
  fi
  echolog "${LINENO}: Found new version $NEWVER"
}

function checkupdates() {
  echolog "${LINENO}: Checking for updates ..."
  cdownload VERSION    "$TMPDIR"
  cdownload _REPOLIST     "$TMPDIR"
  cdownload _REPOLIST.sig "$TMPDIR"
  checksign "$TMPDIR/_REPOLIST.sig" "$TMPDIR/_REPOLIST" || exit 1
  CURVER=$(cat ${DISTRPATH}/VERSION |awk '{print $2}')
  NEWVER=$(gawk '{print $2}' "$TMPDIR/VERSION")
  if [ "0$NEWVER" != "0$CURVER" ] ;then
    echolog "${LINENO}: Current version doesn't match server version"
    return 1
  fi
  if ! compare_repolists "$DISTRPATH/_REPOLIST" "$TMPDIR/_REPOLIST" "$TMPDIR/_UPDATES" ;then
    echolog "${LINENO}: Updates are not found."
    return 1
  fi
  cat "$TMPDIR/_UPDATES" | while read a ;do
    echolog "${LINENO}: Found updated file: $a"
  done
}

function checkrootfs() {
  if grep $DISTRMPOINT  /proc/mounts | awk '{print "," $4 ","}' | grep -q ,ro, ;then
    echolog "${LINENO}: Checkrootfs: root filesystem is mounted as readonly."
    NEEDREMOUNT=yes
    return 1
  fi
}

function remountRw () {
  mount -o remount,rw "$DISTRMPOINT"
  echolog "${LINENO}: remountRw: FS $DISTRMPOINT remounted RW"
}

function resetMounts() {
  [ "$NEEDREMOUNT" ] && mount -o remount,ro "$DISTRMPOINT"
  [ "$NEEDUMOUNT" ] && grep $DISTRDEV /proc/mounts |tac |cut -f2 -d ' ' |while read mpoint ; do
  umount $mpoint ; done
  cd "$CURPWD"
  if [ -f /tmp/barium_update_screen_lock ]; then
    kill $(cat /tmp/barium_update_screen_lock 2>/dev/null |head -n1) 2>/dev/null
  fi
  rm -f /tmp/barium_update_screen_lock 2>/dev/null
}

function checkneedroot() {
  if [ -w "$DISTRPATH" ] ;then
    echolog "${LINENO}: Checking user acceess: ok."
  else
    echolog "${LINENO}: Checking user acceess: failed."
    return 1
  fi
}

# $1 optional parameter
function restartasroot() {
  if [ "$UID" != "0" ] ;then
    echolog "${LINENO}: Restarting script as root."
    exec pkexec barium "$(basename $0)" "$@"
  fi
}

function startwall() {
  echolog "$MSG01 : $(basename $0) : $UID"
  echowall "$MSG01"
}

function endwall() {
  if grep -qi "please[[:space:]]*reboot" "$LOGFILE" ;then
    echowall "${MSG02}. $MSG03"
  else
    echowall "$MSG02"
  fi
}

# $1 modulename
function ismodactive() {
   barium ls --raw '$bname_source' | grep -q $(basename "$1") || return 1
}

# $1 modulename
function deletemodule(){
  if [ -d $SYSMNT/ovl/changes ] ; then
    echolog "${LINENO}: Overlayfs not allows to deactivate module $(basename $1), please reboot."
    mv -f "$1" "$TMPDIR"
    return 1
  fi
  if ismodactive "$1" ;then
    if ! pfsunload $(basename "$1") ;then
        echolog "${LINENO}: Can't deactivate module $(basename $1), please reboot."
        mv -f "$1" "$TMPDIR"
    return 1
  fi
  fi
  rm -f "$1"
}

# $1 full path to MD5SUM
# $2 path to base directory
function deleteobsolete() {
  pushd "$2" >/dev/null
  echolog "${LINENO}: Removing obsolete modules..."
  for a in * ;do
    grep -q "/$a" "$1"  && continue
    #echo $a | grep -E -qi "local|[*]" && continue
    if [ "$DEACTIVATE" = "yes" ] ;then
        deletemodule "$a"
    else
        rm -f "$a"
    fi
    echolog "${LINENO}: removed obsolete file $a"
  done
  popd >/dev/null
}

function movedisabled() {
  echolog "${LINENO}: Moving disabled base modules to optional..." 92
  cd base
  for a in *.xzm ;do
    [ -f "$DISTRPATH/optional/$a" ] && [ ! -f "$DISTRPATH/base/$a" ] || continue
    [ -f "$a" ] && mv -f "$a" ../optional && echolog "${LINENO}: Moved module $a to optional"
  done
  cd ..
}

function moveupdates() {
  echolog "${LINENO}: Moving updates to system directory..." 95
  SUBSHELL_LINENO=$(( LINENO + 1 ))
  find ./ -type f | grep -v _REPOLIST | sed s=^./== | while read a ;do
    echolog "${SUBSHELL_LINENO}-${LINENO}: Moving file $a"
    ismodactive "$a" && deletemodule $DISTRPATH/$a
    if [ ! -f "$DISTRPATH/$a" ] ; then
        mv -f "$a" "$DISTRPATH/$a"
        [ -d $SYSMNT/ovl/changes ] || { barium add "$DISTRPATH/$a" && echolog "$a - enabled" ; }
    else
        mv -f "$a" "$DISTRPATH/$a"
    fi
  done
  echolog "${LINENO}: Moving complete."
}

function updateversion() {
  [ -f /run/barium_updated ] && echoerror "${LINENO}: $MSG07" 
  startwall
  echolog "${LINENO}: Preparing work directory ${DISTRPATH}.upd ..."
  if ! [ -d "${DISTRPATH}.upd" ]; then
    if cp -fax --reflink=always "${DISTRPATH}" "${DISTRPATH}.upd" 2>/dev/null; then
      echolog "${LINENO}: Reflink supported, created copy instantly, rsync use --inplace mode"
      MUPDRSYNCOPT+=" --inplace "
      [ -d "${DISTRPATH}.bak" ] && rm -rf "${DISTRPATH}.bak"
    else
      echolog "${LINENO}: Reflink not supported, need space for regular copy"
      [ -d "${DISTRPATH}.bak" ] && rm -rf "${DISTRPATH}.bak"
      if cp -fax "${DISTRPATH}" "${DISTRPATH}.upd"; then
        echolog "${LINENO}: Created via regular copy"
      else
        echolog "${LINENO}: ERROR: Failed to create ${DISTRPATH}.upd"
        exit 1
      fi
    fi
  else
    echolog "  ${DISTRPATH}.upd already exists"
  fi
  cd "${DISTRPATH}.upd" || echoerror "${LINENO}: Can't create directory ${DISTRPATH}.upd"
  [ -f $TMPDIR/VERSION ] && mv -f $TMPDIR/VERSION VERSION
  echolog "${LINENO}: Destination directory is ready now."
  echolog "${LINENO}: Downloading files..."
  download _REPOLIST ./  
  download _REPOLIST.sig ./
  checksign _REPOLIST.sig _REPOLIST
  downloadfrom_repolist _REPOLIST ./ '.'
  echolog "${LINENO}: Downloading finished."
  checkfilesums "_REPOLIST" "./"
  
  echolog "${LINENO}: Move sgn, ini to ${DISTRPATH}.upd"
  for a in $(ls -1 *.ini) ; do mv $a ${a}.upd ; done
  cp -pfr -t ./ "$DISTRPATH/"*.sgn "$DISTRPATH/"*.ini
  movedisabled
  
  DEACTIVATE=no deleteobsolete "$PWD/_REPOLIST" "$PWD/base"
  echolog "${LINENO}: Renaming livemedia folders..."
  sync
  mv "${DISTRPATH}" "${DISTRPATH}.bak" || exit 1
  mv "${DISTRPATH}.upd" "${DISTRPATH}"
  echolog "${LINENO}: Update finished. Please reboot."
  cat $LOGFILE >> "${DISTRPATH}.bak/update.log"
  : > /run/barium_updated 
  endwall
}

function updatebase() {
  [ -f /run/barium_updated ] && echoerror "${LINENO}: $MSG07" 
  startwall
  
  echolog "${LINENO}: Preparing directory ${DISTRPATH}.upd"
  mkdir -p "${DISTRPATH}.upd"/base
  cd "${DISTRPATH}.upd" || echoerror "${LINENO}: Can't create directory ${DISTRPATH}.upd"
  compare_repolists "$DISTRPATH/_REPOLIST" "$TMPDIR/_REPOLIST" "$TMPDIR/_UPDATES"
  echolog "${LINENO}: Destination directory is ready now."
  
  echolog "${LINENO}: Downloading updates..."
  downloadfrom_repolist $TMPDIR/_UPDATES ./  base || exit 1
  echolog "${LINENO}: Downloading finished."
  checkfilesums "$TMPDIR/_REPOLIST" "./"
  movedisabled
  moveupdates
  DEACTIVATE=yes deleteobsolete "$TMPDIR/_REPOLIST" "$DISTRPATH/base"
  cp -pfr -t "$DISTRPATH" "$TMPDIR/_REPOLIST" "$TMPDIR/_REPOLIST.sig"
  rm -fr "${DISTRPATH}.upd"
  echolog "${LINENO}: Update finished."
  : > /run/barium_updated
  endwall
}

function help()
{
  echo "This script updates system from server"
  echo "Usage: $(basename $0) [parameter]"
  echo "Parameters:"
  echo " -h|--help       this help"
  echo " --test              download files only (must be first parameter - $1)"
  echo " -cd|--checkdir      shows system directory and check FS for readonly"
  echo " -cr|--checkroot checks if root access is required"
  echo " -cf|--checkfreemedia    checks fremedia, and trying to remount system media"   
  echo " -cv|--checknewversion   checks for new version is available"
  echo " -cu|--checkupdates  checks for new updated modules are available"
  echo " -cs|--checkserver   checks connection to server"
  echo " -cl|--checklocalfiles verifying signatures and checksums for local files"
  echo " -uv|--updateversion updates system release if it's available"
  echo " -ub|--updatebase    updates system modules if they are was updated on server"
  echo " -uvf|--updateversion-force  updates system release forcibly"
  echo " -ubf|--updatebase-force updates system modules forcibly"
  
  echo "Script without parameters does all checks and tryes to update system if it's necessary."
}


if [ "$1" == "--test" ] ; then
  TESTONLY=yes
  shift
fi

TMPDIR="/tmp/update-$UID"
LOGFILE=/tmp/update-$UID.log
rm -fr "$TMPDIR" 2>/dev/null
mkdir -p "$TMPDIR" || echoerror "Can not create $TMPDIR" 
touch $LOGFILE
chmod 666 $LOGFILE



case $1 in
  -cd|--checkdir)
  initparameters 
  echolog "$DISTRNAME directory found at $DISTRPATH"
  checkrootfs && exit 0 || exit 1
  ;;
  -cr|--checkroot)
  initparameters
  checkneedroot && exit 0 || exit 1
  ;;
  -cf|--checkfreemedia)
  checkfreemedia && exit 0 || exit 1
  ;;
  -mm|--mountmedia)
  mountmedia && exit 0 || exit 1
  ;;
  -cv|--checknewversion)
  initparameters
  checknewversion && exit 0 || exit 1
  ;;
  -cu|--checkupdates)
  initparameters
  checkupdates && exit 0 || exit 1
  ;;
  -cs|--checkserver)
  initparameters
  checkserver && exit 0 || exit 1
  ;;
  -cl|--checklocalfiles)
  findsystemdir
  pushd "$DISTRPATH" >/dev/null 2>&1
    checksign _REPOLIST.sig _REPOLIST
    echo "n"  | checkfilesums "_REPOLIST" "./" "ROSA.ini _REPOLIST.sig _REPOLIST"
  popd >/dev/null 2>&1
  ;;
  -uv|--updateversion)
  check_instance
  restartasroot -uv
  checkfreemedia || mountmedia
  initparameters
  checkrootfs || remountRw
  if checknewversion ;then
      trap 'resetMounts; echolog '"$MSG06"  EXIT HUP INT QUIT TERM
      updateversion && exit 0 || exit 1
  fi
  ;;
  -ub|--updatebase)
  check_instance
  restartasroot -ub
  checkfreemedia || mountmedia
  initparameters 
  checkrootfs || remountRw
  if checkupdates ;then
      trap 'resetMounts; echolog '"$MSG06"  EXIT HUP INT QUIT TERM
      updatebase && exit 0 || exit 1
  fi
  ;;
  -uvf|--updateversion-force)
  restartasroot -uv
  checkfreemedia || mountmedia
  initparameters
  checkrootfs || remountRw
  trap 'resetMounts; echolog '"$MSG06"  EXIT HUP INT QUIT TERM
  updateversion && exit 0 || exit 1
  ;;
  -ubf|--updatebase-force)
  restartasroot -ub
  checkfreemedia || mountmedia
  initparameters 
  checkrootfs || remountRw
  trap 'resetMounts; echolog '"$MSG06"  EXIT HUP INT QUIT TERM
  updatebase && exit 0 || exit 1
  ;;
  ""|--auto)
  check_instance
  restartasroot --auto
  supress_screenlock
  trap 'resetMounts; echolog '"$MSG06"  EXIT HUP INT QUIT TERM
  initparameters
  checkrootfs || remountRw
  checkserver || echoerror "${LINENO}: server did not respond correctly"
  if checknewversion ;then
      echolog "${LINENO}: Starting: update version mode"
      updateversion || UPDATEMODULES=no
  elif checkupdates ;then
      echolog "${LINENO}: Starting: update modules mode"
      updatebase || UPDATEMODULES=no
  else
      echowall "$MSG08"
  fi
  ;;
  *)
      help
  ;;
esac

resetMounts

[ "$UPDATEMODULES" == 'yes' ] && barium update_modules

exit 0

