#!/bin/bash
# barium helper scripts
# author: rosalinux.ru: betcher_
. /etc/initvars 
CURPWD="$(pwd)"
ppid=''
spid=''

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  start_progress () {
    rm -f /tmp/progress-pipe
    mkfifo /tmp/progress-pipe
    ( while true ; do
        read a < /tmp/progress-pipe
        echo "$a"
    done | xuserrun zenity --progress \
  --title="$MSG04" \
  --text="$MSG05" \
  --auto-close \
  --no-cancel \
  --percentage=0 2>/dev/null
  rm -f /tmp/progress-pipe ) & 
  ppid=$!
}

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/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 "==> $@" >>/dev/stdout
 if [ "$2" ] ; then 
    (echo "$2" > /tmp/progress-pipe
    sleep 1
    echo "# $1" > /tmp/progress-pipe) &
 fi
}

function echoerror() {
 echolog $@
 echowall "$@"
 echo "100" > /tmp/progress-pipe
 resetMounts 
 exit 1
}

function echowall() {
 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=mirror.yandex.ru
    UPDATESHARE=/rosa/repository
    . /usr/lib/rosa-rw/os-config
    . /etc/ROSA-RW/config
    UPDATEURL=$UPDATEPROT://$UPDATESERVER/$UPDATESHARE/$UPDATEDIR/ROSA-SYSTEM/
    UPDATEMODSURL=$UPDATEPROT://$UPDATESERVER/$UPDATESHARE/$UPDATEDIR/modules/
    UPDATEINTURL=$UPDATEPROT://$UPDATESERVER/$UPDATESHARE/$UPDATEDIR/i18/
}

# $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"
    echo "100" > /tmp/progress-pipe
    kill $ppid 2>/dev/null
    kill $spid 2>/dev/null
    rm -f /tmp/progress-pipe 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."
    bashsu "$(readlink -f $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 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 ..." 12
 if ! [ -d "${DISTRPATH}.upd" ] ; then
    [ -d "${DISTRPATH}.bak" ] &&  mv  "${DISTRPATH}.bak" "${DISTRPATH}.upd"
    mkdir -p "${DISTRPATH}.upd" 2>/dev/null
 fi
 echo "15" > /tmp/progress-pipe
 rsync -ahx --delete "${DISTRPATH}/" "${DISTRPATH}.upd"
 cd "${DISTRPATH}.upd" || echoerror "${LINENO}: Can't create directory ${DISTRPATH}.upd"
 mv -f $TMPDIR/VERSION VERSION
 #cp -pfr -t ./ "$DISTRPATH/modules" "$DISTRPATH/optional" "$DISTRPATH/base"
 echolog "${LINENO}: Destination directory is ready now."
 echolog "${LINENO}: Downloading files..." 20
 download _REPOLIST ./  
 download _REPOLIST.sig ./
 checksign _REPOLIST.sig _REPOLIST
 downloadfrom_repolist _REPOLIST ./ '.'
 echolog "${LINENO}: Downloading finished." 90
 
 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..." 95
 sync
 echo "95" > /tmp/progress-pipe
 mv "${DISTRPATH}" "${DISTRPATH}.bak" || exit 1
 mv "${DISTRPATH}.upd" "${DISTRPATH}"
 echolog "${LINENO}: Update finished. Please reboot." 100
 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" 12
 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." 20

 echolog "${LINENO}: Downloading updates..." 21
 downloadfrom_repolist $TMPDIR/_UPDATES ./  base || exit 1
 #download _REPOLIST
 
 echolog "${LINENO}: Downloading finished." 90
 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." 100
 : > /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
    start_progress
    supress_screenlock
    checkfreemedia || mountmedia
    echo "5" > /tmp/progress-pipe
    initparameters
    checkrootfs || remountRw
    checkserver || echoerror "${LINENO}: server did not respond correctly" 100
    if checknewversion ;then
        echolog "${LINENO}: Starting: update version mode" 10
        updateversion
    elif checkupdates ;then
        echolog "${LINENO}: Starting: update modules mode" 10
        updatebase
    else
        echowall "$MSG08"
    fi
    ;;
    *)
        help
    ;;
esac

resetMounts

exit 0

