#!/usr/bin/env bash

# gnuhealth-control
# The GNU Health control center 

##############################################################################
#
#    GNU Health: The Free Health and Hospital Information System
#    Copyright (C) 2008-2020 Luis Falcon <lfalcon@gnusolidario.org>
#    Copyright (C) 2011-2020 GNU Solidario <health@gnusolidario.org>
#
#    This program is free software: you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation, either version 3 of the License, or
#    (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################

VERSION="3.6.3"

TRYTON_URL="https://downloads-cdn.tryton.org"
GNUHEALTH_URL="https://ftp.gnu.org/gnu/health"
TRANSLATE_URL="http://translate.gnusolidario.org"

TRYTON_MODULES="account account_invoice account_product company country \
    currency party product stock stock_lot purchase account_invoice_stock \
    stock_supply purchase_request"

UPDATE_DOWNLOAD_DIR="/tmp/gnuhealth_update"

PATCH_TRYTON=0

TRYTON_PATCHES=""

BSDTAR="bsdtar"

usage()
{
    cat << EOF

This is GNU Health control center ${VERSION}

usage: `basename $0` command [options]

Command:
 
  version : Show version
  backup  : Backup he gnuhealth kernel, attach dir and database
  update  : Download and install the patches
  getlang : Get and install / update the language pack code
  status  : Show environment and GNU Health Tryton server status

Options:

 --backdir  : destination directory for the backup file
 --dry-run  : Check, download and preview, but don't actually update process
 --database : database name to use with the backup command 

EOF
    exit 0
}

help()
{
    cat << EOF
    The GNU Health Control Center (gnuhealth-control) is the main tool for 
    administrative tasks of the GNU Health environment.
    
    It can perform backups and updates of the instance
    
    Updates
    -------

    When gnuhealth-control is invoked with the update command, 
    it will update GNU Health components within the same major number
    The following components will be checked and updated if necessary

        - Trytond : Tryton server version
        - GNU Health patchsets

    This will be valid for version with the same major and minor numbers, for example
    2.8.x will look for the latest tryton updates and GNU Health updates
    associated to that release.

    GNU Health Control is available for release 2.8 and newer.
    You can get the latest version of GNU Health update at GNU
    ftp://ftp.gnu.org/gnu/health


EOF
    usage
    exit 0
}

message()
{
    local UTC="$(date -u +'%Y-%m-%d %H:%M:%S')"
    
    case $1 in
      ERROR ) echo -e "\e[00;31m${UTC} [ERROR] $2\e[00m";;
      WARNING ) echo -e "\e[0;33m${UTC} [WARNING] $2\e[m" ;;
      INFO ) echo -e "\e[0;36m${UTC} [INFO] $2\e[m" ;;
    esac
}

get_current_values()
{
    # Bail out if no GNU Health profile exists
    if [ ! -e $HOME/.gnuhealthrc ]
    then
        message "ERROR" "No GNU Health profile found !"
        bailout
    fi
    
    # Stop if it can't find the GNU Health version
    if [ -z "$GNUHEALTH_VERSION" ]
    then
        message "ERROR" "Could not find the GNU Health version env. variable"
        bailout
    fi

    # Stop if current GNU Health version < 3.0.0
    local raw_ver=`echo $GNUHEALTH_VERSION | tr -d '.'`
    if [ $raw_ver -lt 300 ]
    then
        message "ERROR" "GNU Health version must be at least 3.0"
        bailout
    fi
    
    message "INFO" "Environment variables"
    message "INFO" "GNUHEALTH_VERSION = ${GNUHEALTH_VERSION}"
    message "INFO" "TRYTON VERSION = ${TRYTON_VERSION}"
    message "INFO" "PYTHONPATH = $PYTHONPATH"
    
}

do_backup()
{

    get_current_values
    
    local COMMAND=$1
    local BACKDATE=`date -u +%Y-%m-%d_%H%M%S`
    local LOCKFILE="/tmp/.gnuhealth_backup.lock"
    local INFOFILE="/tmp/gnuhealth_backup.log"
    local BACKDIR=""
    local DB=""


    shift # Remove the command and deal only with the options
    
    if [ $# -ne 4 ]; then
        echo -e "Usage : gnuhealth-control backup --backdir <directory> --database <dbname>"
        exit
    fi

    for option in "$@"
    do
      case $option in
          --backdir ) BACKDIR=$2;;
          --database ) DB=$2 ;;
      esac
      shift
    done
    
    if [ -f $LOCKFILE ]
    then
        message "ERROR" "Backup in progress or stale lock file found ..." | tee -a $INFOFILE
        bailout
    fi


    if [ ! -e ${BACKDIR} ]
    then
        message "ERROR" "Backup directory ${BACKDIR} not found !"
        bailout
    fi

    echo $$ > $LOCKFILE 
    
    # Backup start

    message "INFO" "START Database Backup" | tee -a $INFOFILE
    
    pg_dump $DB > $BACKDIR/backup\_$DB\_$BACKDATE || bailout 

    message "INFO" "Compressing Database Backup" | tee -a $INFOFILE

    gzip "${BACKDIR}/backup_${DB}_${BACKDATE}" || bailout
    
    message "INFO" "Compressing GNU Health home / Kernel directory" | tee -a $INFOFILE

    tar -cvzf "${BACKDIR}/gnuhealth_${DB}_fs_backup_${BACKDATE}.tar.gz" $HOME || bailout

    message "INFO" "Creating tarball with compressed DB and GNU Health home directory" | tee -a $INFOFILE

    tar -cvf "${BACKDIR}/gnuhealth_${DB}_with_fs_backup_${BACKDATE}.tar" \
        ${BACKDIR}/backup_${DB}_${BACKDATE}.gz ${BACKDIR}/gnuhealth_${DB}_fs_backup_${BACKDATE}.tar.gz \
        || bailout

    message "INFO" "Backup Successful" | tee -a $INFOFILE

    #Remove lock file
    rm $LOCKFILE

}

check_status()
{
    TRYTOND_PIDS=`pgrep -f "^.*python.*trytond.*$"`
    if [ $? = 0 ]
    then
        message "INFO" "GNU Health / Tryton instance(s) with PID(s) :"
        echo $TRYTOND_PIDS
    else
        message "INFO" "No GNU Health instance seems to be running"
    fi

}

check_download_dir()
{
    if [ -d $UPDATE_DOWNLOAD_DIR ]; then
        message "ERROR" "Update download directory exists. Bailing out"
        bailout
    fi
}

check_updates()
{
    source $HOME/.gnuhealthrc
    UTIL_DIR="${GNUHEALTH_DIR}/tryton/server/util"
    
    local TRYTOND_PATCHLEVEL=`echo ${TRYTON_VERSION} | cut -d'.' -f3`
        
    TRYTON_MAJOR_MINOR=`echo $TRYTON_VERSION | cut -d'.' -f1-2`

    GNUHEALTH_MAJOR_MINOR=`echo $GNUHEALTH_VERSION | cut -d'.' -f1-2`
    GNUHEALTH_PATCHSET=`echo $GNUHEALTH_VERSION | cut -d'.' -f3`

    GCONTROL_PATCHSET=`echo $VERSION | cut -d'.' -f3`

    NEED_UPDATE_GCONTROL=0
    NEED_UPDATE_TRYTOND=0
    NEED_UPDATE_MODULES=0
    NEED_UPDATE_PATCHSETS=0
    NEED_PATCH_TRYTON=0
    MOD_UPDATES=""
    NEED_DELETE=0
    TO_DELETE=""

    message "INFO" "GNUHEALTH-CONTROL VERSION : ${VERSION}"

    # Retrieve the latest control center that is compatible with the current GNU Health version
    LATEST_GHCONTROL=`wget --quiet -O - ${GNUHEALTH_URL} | egrep -o gnuhealth-control-${GNUHEALTH_MAJOR_MINOR}.[0-9\.]+.tar.gz | sort -V | tail -1`
    local LATEST_GHCONTROL_PATCHSET=`echo ${LATEST_GHCONTROL} | cut -d'.' -f3`
    local GHCONTROL_PATCHSET=`echo $VERSION | cut -d'.' -f3`
    
    if (test ${LATEST_GHCONTROL}); then
        if (( ${GHCONTROL_PATCHSET} < ${LATEST_GHCONTROL_PATCHSET} )); then
            message "WARNING" "Current version ${VERSION} is outdated. A new version (${LATEST_GHCONTROL}) is available"
            NEED_UPDATE_GCONTROL=1
        else
            message "INFO" "GNU Health control center is at the latest version ${VERSION}"
        fi
    else
        message "ERROR" "Error on getting the latest GNU Health Control Verion. Maybe a development release"
    fi
    
    message "INFO" "TRYTON SERVER : Checking latest patchlevel"
    
    LATEST_TRYTOND=`wget --quiet -O - ${TRYTON_URL}/${TRYTON_MAJOR_MINOR} | egrep -o trytond-${TRYTON_MAJOR_MINOR}.[0-9\.]+.tar.gz | sort -V | tail -1`
    local LATEST_TRYTOND_PATCHLEVEL=`echo ${LATEST_TRYTOND} | cut -d'.' -f3`

    # Check latest tryton server against local version
    if (( ${TRYTOND_PATCHLEVEL} < ${LATEST_TRYTOND_PATCHLEVEL} )); then
        message "WARNING" "TRYTON SERVER patchlevel ${TRYTOND_PATCHLEVEL} is outdated ! A newer version is available (${LATEST_TRYTOND})"
        NEED_DELETE=1
        TO_DELETE="${TO_DELETE} ${GNUHEALTH_DIR}/tryton/server/trytond-${TRYTON_MAJOR_MINOR}.${TRYTOND_PATCHLEVEL}"
        NEED_UPDATE_TRYTOND=1
    else
        message "INFO" "TRYTON SERVER patchlevel ${TRYTOND_PATCHLEVEL} is at the latest version"
    fi
    
    # Check latest tryton modules against local version
    cd ${GNUHEALTH_DIR}/tryton/server/modules
    
    for MODULE in ${TRYTON_MODULES}; do
        MOD=`ls -1d trytond_${MODULE}-*`
        message "INFO" "Checking MODULE ${MOD}"
        MODNAME=`echo $MOD | cut -d'-' -f1`
        MODULE_PATCHLEVEL=`echo $MOD | sed  's/^.*\.\([[:digit:]]*\)$/\1/'`
        LATEST_MODULE=`wget --quiet -O - ${TRYTON_URL}/${TRYTON_MAJOR_MINOR} | egrep -o ${MODNAME}-${TRYTON_MAJOR_MINOR}.[0-9\.]+.tar.gz | sort -V | tail -1`
        LATEST_MODULE_PATCHLEVEL=`echo ${LATEST_MODULE} | cut -d'.' -f3`
        if (( ${MODULE_PATCHLEVEL} < ${LATEST_MODULE_PATCHLEVEL} )); then
            message "WARNING" "${MODNAME} patchlevel ${MODULE_PATCHLEVEL} is outdated ! A newer version is available (${LATEST_MODULE})"
            NEED_UPDATE_MODULES=1
            MOD_UPDATES="${MOD_UPDATES} ${LATEST_MODULE}"
            NEED_DELETE=1
            TO_DELETE="${TO_DELETE} ${GNUHEALTH_DIR}/tryton/server/modules/${MODNAME}-${TRYTON_MAJOR_MINOR}.${MODULE_PATCHLEVEL}"
        else
            message "INFO" "${MODNAME} patchlevel ${MODULE_PATCHLEVEL} is at the latest version"
        fi
    done

    # Check latest GNU HEALTH PATCHSETS against local version
    message "INFO" "GNU HEALTH KERNEL : Checking latest PATCHSETS"
    
    PATCHSETS_NUM=`wget --quiet -O - ${GNUHEALTH_URL}/ | egrep -o gnuhealth_patchset-${GNUHEALTH_MAJOR_MINOR}\.[0-9\.]+.tar.gz | uniq | wc -l | tr -d ' '`
    
    if (( ${PATCHSETS_NUM} > 0 )); then
        message "INFO" "Number of Patchsets for this version : ${PATCHSETS_NUM}"

        LATEST_GNUHEALTH=`wget --quiet -O - ${GNUHEALTH_URL}/ | egrep -o gnuhealth_patchset-${GNUHEALTH_MAJOR_MINOR}\.[0-9\.]+.tar.gz | sort -V | tail -1`
        LATEST_GNUHEALTH_PATCHSET=`echo ${LATEST_GNUHEALTH} | cut -d'.' -f3`

        if (( ${GNUHEALTH_PATCHSET} < ${LATEST_GNUHEALTH_PATCHSET} )); then
            message "WARNING" "GNU HEALTH patchset ${GNUHEALTH_PATCHSET} is outdated ! A newer version is available (${LATEST_GNUHEALTH})"
            NEED_UPDATE_PATCHSETS=1
            let PSET=GNUHEALTH_PATCHSET+1 
            for n in `seq $PSET $LATEST_GNUHEALTH_PATCHSET`
            do
                PATCHSETS="$PATCHSETS gnuhealth_patchset-${GNUHEALTH_MAJOR_MINOR}.$n.tar.gz"
            done
            
        else
            message "INFO" "GNU HEALTH patchset ${GNUHEALTH_PATCHSET} is at the latest version"
        fi

    else
        message "INFO" "** NO GNU HEALTH PATCHSETS FOUND FOR THIS VERSION **"
    fi

    # CHECK SECURITY ADVISORIES AND OTHER PATCHES NOT PRESENT IN THE STANDARD TRYTON KERNEL   
    
    if (( ${PATCH_TRYTON} == 1 )); then
        message "INFO" "Checking Security Advisories and other patches not present in the standard tryton kernel"

            for n in ${TRYTON_PATCHES}
            do
                message "INFO" "Downloading patch for Tryton server : ${n}"
                wget --quiet --directory-prefix=${UPDATE_DOWNLOAD_DIR}/ ${GNUHEALTH_URL}/security/${n} || message "ERROR" "Could not get patch"
                message "INFO" "Checking elegibility of the patch"                
                cd ${GNUHEALTH_DIR}/tryton/server/trytond-${TRYTON_VERSION}*
                patch --dry-run --silent -N -p1 < ${UPDATE_DOWNLOAD_DIR}/${n}
                if [ $? -eq 0 ]; then
                    message "WARNING" "Patch ${n} needs to be applied"
                    NEED_PATCH_TRYTON=1
                else
                    message "INFO" "Patch ${n} already applied or not elegible"
                fi
                
            done
            
    else
        message "WARNING" "PATCHING STANDARD TRYTON IS DISABLED. NO EXTRA SECURITY PATCHES OR FUNCTIONALITY WILL BE APPLIED"
    fi
}

install_updates()
{
    if [ $NEED_UPDATE_GCONTROL -eq 1 ]; then
        message "INFO" "Downloading ${LATEST_GHCONTROL} ..."
        wget --quiet --directory-prefix=${UPDATE_DOWNLOAD_DIR}/ ${GNUHEALTH_URL}/${LATEST_GHCONTROL} || bailout
        message "INFO" "Uncompressing ${LATEST_GHCONTROL} ..."        
        tar -xzf ${UPDATE_DOWNLOAD_DIR}/${LATEST_GHCONTROL} --directory ${UTIL_DIR}|| bailout
        message "INFO" "Sucessfully installed ${LATEST_GHCONTROL} "
        message "INFO" "Removing temporary download directory "
        rm -rf ${UPDATE_DOWNLOAD_DIR} || bailout
        message "INFO" "Please restart now the update with the new control center"
        exit 0

    fi

    if [ $NEED_UPDATE_TRYTOND -eq 1 ]; then
        message "INFO" "Downloading TRYTON SERVER $LATEST_TRYTOND"
        wget --quiet --directory-prefix=${UPDATE_DOWNLOAD_DIR}/trytond ${TRYTON_URL}/${TRYTON_MAJOR_MINOR}/${LATEST_TRYTOND} || bailout
        message "INFO" "--> Uncompressing TRYTON SERVER $LATEST_TRYTOND"
        tar -xzf ${UPDATE_DOWNLOAD_DIR}/trytond/${LATEST_TRYTOND} --directory ${GNUHEALTH_DIR}/tryton/server || bailout
    fi

    if [ $NEED_UPDATE_MODULES -eq 1 ]; then
        for mod in ${MOD_UPDATES}
        do
            message "INFO" "Downloading $mod"
            wget --quiet --directory-prefix=${UPDATE_DOWNLOAD_DIR}/modules ${TRYTON_URL}/${TRYTON_MAJOR_MINOR}/${mod} || bailout
            message "INFO" "--> Uncompressing ${mod}"
            tar -xzf ${UPDATE_DOWNLOAD_DIR}/modules/${mod} --directory ${GNUHEALTH_DIR}/tryton/server/modules || bailout
        done
    fi

    if [ $NEED_UPDATE_PATCHSETS -eq 1 ]; then
        for patchset in ${PATCHSETS}
        do
            message "INFO" "Downloading $patchset"
            wget --quiet --directory-prefix=${UPDATE_DOWNLOAD_DIR}/patchsets ${GNUHEALTH_URL}/${patchset} || bailout
            message "INFO" "--> Applying PATCHSET $patchset"
            tar -xzf ${UPDATE_DOWNLOAD_DIR}/patchsets/${patchset} --directory ${HOME} || bailout
        done
    fi

    # APPLY SECURITY ADVISORIES AND OTHER PATCHES NOT PRESENT IN THE STANDARD TRYTON KERNEL   
    
    if (( ${PATCH_TRYTON} == 1 )); then
        if (( ${NEED_PATCH_TRYTON} == 1 )); then
            message "INFO" "APPLY SECURITY ADVISORIES AND OTHER PATCHES NOT PRESENT IN THE STANDARD TRYTON KERNEL"
            for n in ${TRYTON_PATCHES}
            do
                message "INFO" "Checking elegibility of the patch"                
                cd ${GNUHEALTH_DIR}/tryton/server/trytond-${TRYTON_VERSION}
                
                patch --dry-run --silent -N -p1 < ${UPDATE_DOWNLOAD_DIR}/${n}
                if [ $? -eq 0 ]; then
                    message "WARNING" "Applying patch ${n} to Tryton kernel"
                    patch -p1 < ${UPDATE_DOWNLOAD_DIR}/${n} || bailout
                    message "INFO" "Tryton kernel sucessfully patched"
                else
                    message "INFO" "Patch ${n} already applied or not elegible"
                fi
                
            done
        fi
    else
        message "WARNING" "PATCHING STANDARD TRYTON IS DISABLED. NO EXTRA SECURITY PATCHES OR FUNCTIONALITY WILL BE APPLIED"
    fi

}

remove_old()
{
    if [ ${NEED_DELETE} -eq 1 ]; then
        message "WARNING" "Removing obsolete kernel and/or modules : ${TO_DELETE}"
        rm -rf ${TO_DELETE}
    fi
}

relink_mods()
{
    source $HOME/.gnuhealthrc
    
    if [ ${NEED_DELETE} -eq 1 ]; then
        for mod in ${TRYTON_MODULES}; do
            local modname=`ls -1d trytond_${mod}-*`
            message "INFO" "Relinking : ${mod}"
            ln -sf ${GNUHEALTH_DIR}/tryton/server/modules/${modname} ${GNUHEALTH_DIR}/tryton/server/${TRYTOND}/trytond/modules/${mod} || bailout
        done
        
        message "INFO" "Relinking GNU Health modules ..."
        local HEALTH_MODS=`cd ${GNUHEALTH_DIR}/tryton/server/modules; ls -1d health*`
        for mod in ${HEALTH_MODS}; do
            message "INFO" "--> Relinking : ${mod}"
            ln -sf ${GNUHEALTH_DIR}/tryton/server/modules/${mod} ${GNUHEALTH_DIR}/tryton/server/${TRYTOND}/trytond/modules/ || bailout
        done

        # Remove symlinks health_caldav and health_webdav3_server, that will use the
        # names calendar and webdav respectively
        rm -f ${GNUHEALTH_DIR}/tryton/server/${TRYTOND}/trytond/modules/health_caldav
        rm -f ${GNUHEALTH_DIR}/tryton/server/${TRYTOND}/trytond/modules/health_webdav3_server

        message "INFO" "Relinking local modules and customizations ..."

        local LOCAL_MODS=`cd ${GNUHEALTH_DIR}/tryton/server/modules/local; ls -A`
        for mod in ${LOCAL_MODS}; do
            message "INFO" "--> Relinking : ${mod}"
            ln -sf ${GNUHEALTH_DIR}/tryton/server/modules/local/${mod} ${GNUHEALTH_DIR}/tryton/server/${TRYTOND}/trytond/modules/ || bailout
        done

        # WEBDAV3 ports for GNU Health.
        message "INFO" "Relinking WebDAV3 related modules"
        local WEBDAV3="health_webdav3_server"
        local CALENDAR_WEBDAV3="health_caldav"
        ln -sf ${GNUHEALTH_DIR}/tryton/server/modules/${WEBDAV3} ${GNUHEALTH_DIR}/tryton/server/${TRYTOND}/trytond/modules/webdav || bailout
        ln -sf ${GNUHEALTH_DIR}/tryton/server/modules/${CALENDAR_WEBDAV3} ${GNUHEALTH_DIR}/tryton/server/${TRYTOND}/trytond/modules/calendar || bailout

    fi
}
    
do_update()
{
    if [ $# -gt 1 ];then
        if [ $2 != "--dry-run" ];then
        message "ERROR" "Unrecognized update option"
        bailout
        fi
    fi

    check_download_dir
    get_current_values
    check_updates
    if [ $# -gt 1 ];then
        if [ $2 == "--dry-run" ];then
            cleanup
            exit 0
        fi
    fi
    install_updates
    remove_old
    relink_mods 
    cleanup
}

getlang() {
    if [ $# -eq 1 ]; then
        usage
    fi
    
    local lang_to_install=$2
    local lang_file=${lang_to_install}.zip
    source $HOME/.gnuhealthrc || bailout
    message "INFO" "Going to modules directory..." 

    cd ${GNUHEALTH_DIR}/tryton/server/modules || bailout
    message "INFO" "Retrieving language pack file for ${lang_to_install}" 
    wget ${TRANSLATE_URL}/export/?path=/${lang_to_install}/GNUHEALTH/ -O /tmp/${lang_file} || bailout
    message "INFO" "Installing / Updating language files for ${lang_to_install} ..."

    ${BSDTAR} --strip-components 3 -xzf /tmp/${lang_file} || bailout
    message "INFO" "Language pack ${lang_to_install} sucessfully installed / updated"
    message "INFO" "You now need to update the database modules"
    cd
}

bailout() {
    message "ERROR" "Bailing out !"
    message "ERROR" "Removing lock files"
    rm -f $LOCKFILE
    exit 1
}

cleanup()
{
    # Delete temporary download directory
    rm -rf ${UPDATE_DOWNLOAD_DIR} || bailout
    exit 0
}

parse_command_line()
{
    if [ $# -eq 0 ]; then
        usage
    fi
    
    case $1 in
        version) echo $VERSION;;
        backup) do_backup $@;;
        update) do_update $@;;
        status) check_status;;
        getlang) getlang $@;;
        help) help;;
        *) echo $1: Unrecognized command; exit 1;;
    esac
}

parse_command_line $@
