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

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

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) &
  spid=$!
}

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

function mountmedia () {
  for script in $(ls /run/bin/remount_*) ; do
    $script >/dev/n ull
  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 "==> $@" >>/dev/stdout
}

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

function echowall() {
  return 0
  xuserrun notify-send "barium update: $@" || mdialog --passivepopup "barium_update: $@"
}

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)
  DISTRPATH=${DISTRMPOINT}$(grep -m1 " $SYSMNT/layer-base/0 " /proc/self/mountinfo | awk '{ print $4 }')
  [ -f "$DISTRPATH/vmlinuz" -a -d "$DISTRPATH/base" ] || return  1
  DISTRNAME=$(cat ${DISTRPATH}/VERSION |awk '{print $1}')
  DISTRVER=$(cat ${DISTRPATH}/VERSION |awk '{print $2}')
  echolog "${LINENO}: ROSA-SYSTEM: $DISTRPATH"
  [ -z "$DISTRNAME" -o -z "$DISTRVER" ] && return  1
}

function initparameters() {
  findsystemdir
  UPDATEPROT=rsync
  UPDATESERVER=barium.rosa.ru
  UPDATESHARE=/rosa/repository
  . /usr/lib/rosa-rw/os-config
  . /etc/ROSA-RW/config
  [ "${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)
    MUPDRSYNCOPT=" -ah --progress --stats "
    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 'BEGIN {return_code=('$NEWVER' <= '$CURVER') ? 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"
  kill $spid 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."
    pkexec barium $(basename $0) $@ || echowall "Authentification failed."
    exit $?
  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" -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
  find ./ -type f | grep -v _REPOLIST | sed s=^./== | while read a ;do
    echolog "${LINENO}: Moving file $a"
    ismodactive "$a" && deletemodule $DISTRPATH/$a
    if [ ! -f "$DISTRPATH/$a" ] ;then
    mv -f "$a" "$DISTRPATH/$a"
        [ -d $SYSMNT/ovl/changes ] || pfsload "$DISTRPATH/$a"
    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
    [ -d "${DISTRPATH}.bak" ] &&  mv  "${DISTRPATH}.bak" "${DISTRPATH}.upd"
    mkdir -p "${DISTRPATH}.upd" 2>/dev/null
  fi
  rsync -ahx --delete "${DISTRPATH}/" "${DISTRPATH}.upd"
  cd "${DISTRPATH}.upd" || echoerror "${LINENO}: Can't create directory ${DISTRPATH}.upd"
  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
  #download _REPOLIST
  
  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"
touch $LOGFILE
chmod 666 $LOGFILE

trap 'resetMounts; echolog '"$MSG06" 2 3

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)
  restartasroot -uv
  checkfreemedia || mountmedia
  initparameters
  checkrootfs || remountRw
  if checknewversion ;then
      updateversion && exit 0 || exit 1
  fi
  ;;
  -ub|--updatebase)
  restartasroot -ub
  checkfreemedia || mountmedia
  initparameters 
  checkrootfs || remountRw
  if checkupdates ;then
      updatebase && exit 0 || exit 1
  fi
  ;;
  -uvf|--updateversion-force)
  restartasroot -uv
  checkfreemedia || mountmedia
  initparameters
  checkrootfs || remountRw
  updateversion && exit 0 || exit 1
  ;;
  -ubf|--updatebase-force)
  restartasroot -ub
  checkfreemedia || mountmedia
  initparameters 
  checkrootfs || remountRw
  updatebase && exit 0 || exit 1
  ;;
  ""|--auto)
  restartasroot --auto
  supress_screenlock
  initparameters
  checkrootfs || remountRw
  checkserver || echoerror "${LINENO}: server did not respond correctly"
  if checknewversion ;then
      echolog "${LINENO}: Starting: update version mode"
      updateversion
  elif checkupdates ;then
      echolog "${LINENO}: Starting: update modules mode"
      updatebase
  else
      echowall "$MSG08"
  fi
  ;;
  *)
      help
  ;;
esac

resetMounts

exit 0

