#!/bin/sh
#
#  This file is part of nzbget. See <https://nzbget.com>.
#
#  Copyright (C) 2015-2017 Andrey Prygunkov <hugbug@users.sourceforge.net>
#  Copyright (C) 2024 phnzb <pavel@nzbget.com>
#
#  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 2 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/>.
#

# Strict error handling for debugging
set -o nounset
set -o errexit

# Installer title
TITLE=
# Size of installer script (package header)
HEADER=
# Size of installer package (header + payload)
TOTAL=
# Md5 sum of payload
MD5=
# List of included CPU architecture binaries
DISTARCHS=
# Target platform
PLATFORM=

SILENT=no
ALLARCHS="$DISTARCHS all"
ARCH=""
SELECT=auto
OUTDIR="nzbget"
PRINTEDTITLE=no
JUSTUNPACK=no
UPDATE=no
VERIFY=yes
OS=""

Info()
{
    if test "$SILENT" = "no"; then
        echo "$1"
    fi
}

Error()
{
    Info "ERROR: $1"
    exit 1
}

ValidArch()
{
    for TARG in $ALLARCHS; do
        if test "$TARG" = "$1"; then
            return 0
        fi 
    done
    return 1
}

PrintArch()
{
    if ValidArch $1; then
        Info "$2"
    fi
}

PrintHelp()
{
    if test "$PRINTEDTITLE" = "no"; then
        Info "Installer for $TITLE"
        Info ""
    fi
    if test "$PLATFORM" = "linux"; then
        Info "This installer supports Linux kernel 2.6 or newer and the following CPU architectures:"
    fi
    if test "$PLATFORM" = "freebsd"; then
        Info "This installer supports FreeBSD 13 or newer and the following CPU architectures:"
    fi
    PrintArch "i686"     "    i686     - x86, 32 or 64 Bit"
    PrintArch "x86_64"   "    x86_64   - x86, 64 Bit"
    PrintArch "armel"    "    armel    - ARMv5/v6 (ARM9 and ARM11 families)"
    PrintArch "armhf"    "    armhf    - ARMv7/v8 (Cortex family)"
    PrintArch "aarch64"  "    aarch64  - ARMv8, 64 Bit"
    PrintArch "mipsel"   "    mipsel   - MIPS (little endian)"
    PrintArch "mipseb"   "    mipseb   - MIPS (big endian)"
    PrintArch "ppc6xx"   "    ppc6xx   - PowerPC 6xx (603e series)"
    PrintArch "ppc500"   "    ppc500   - PowerPC e500 (core e500v1/e500v2)"
    PrintArch "riscv64"  "    riscv64  - RISC-V 64-bit"
    Info ""

    # Check if command 'basename' is available and fallback to full path if it's not
    TEST=`eval TEST='$(basename /base/bin)' 2>/dev/null; echo $TEST`
    if test "$TEST" != ""; then
        EXENAME=$(basename $0)
    else
        EXENAME=$0
    fi

    Info "Usage: sh $EXENAME [options]"
    Info "    --help            - print this help"
    Info "    --arch <cpu>      - set CPU-architecture"
    Info "    --destdir <path>  - set destination directory"
    Info "    --list            - list package content"
    Info "    --unpack          - just unpack, skip setup"
    Info "    --silent          - silent mode"
    Info "    --nocheck         - skip integrity check"
    Info "    --tar <args>      - run custom tar command on archive"
}

Verify()
{
    # Checking installer package file size
    REQSIZE=$TOTAL
    # Trying to get the size via 'stat' command, fallback to 'ls -l' if 'stat' isn't available
    ACTSIZE=`stat -c%s "$0" 2>/dev/null | cat`
    if test "$ACTSIZE" = ""; then
        NUM=
        for FIELD in `ls -l "$0" 2>/dev/null`
        do
            if test "$NUM" = "aaaa"; then
                ACTSIZE="$FIELD"
                break
            fi
            NUM="a$NUM"
        done
    fi
    if test "$REQSIZE" != "$ACTSIZE"; then
        Error "Corrupted installer package detected: file size mismatch."
    fi

    # Checking checksum (MD5) of package payload, only if command 'md5sum' is available
    ACTMD5=`dd "if=$0" bs=$HEADER skip=1 2>/dev/null | /bin/sh -c md5sum 2>/dev/null | cut -b-32 2>/dev/null | cat`
    LEN=${#ACTMD5}
    if test "$LEN" = "32" -a "$MD5" != "$ACTMD5"; then
        Error "Corrupted installer package detected: checksum mismatch."
    fi
}

DetectEndianness()
{
    # Sixth byte of any executable indicates endianness
    ENDBYTE=`dd if=/bin/sh bs=1 count=6 2>/dev/null | sed -n 's/.ELF.\(.*\)/\1/p'`

    ENDIAN=unknown
    if test "$ENDBYTE" = $'\001'; then
        ENDIAN=little
    elif test "$ENDBYTE" = $'\002'; then
        ENDIAN=big
    fi
}

DetectArch()
{
    OS=`uname -s`
    if test "(" "$PLATFORM" = "linux" -a "$OS" != "Linux" -a "$OS" != "FreeBSD" ")" -o \
            "(" "$PLATFORM" = "freebsd" -a "$OS" != "FreeBSD" ")" ; then
        PrintHelp
        Error "Operating system ($OS) isn't supported by this installer."
    fi

    if test "$UPDATE" = "yes"; then
        ARCH=`cat "$OUTDIR/installer.cfg" 2>/dev/null | sed -n 's/^arch=\(.*\)$/\1/p'`
        SELECT=`cat "$OUTDIR/installer.cfg" 2>/dev/null | sed -n 's/^select=\(.*\)$/\1/p'`
    fi

    if test "$ARCH" = ""; then
        CPU=`uname -m`
        case $CPU in
            i386|i686)
                ARCH=i686
                ;;
            x86_64|amd64)
                ARCH=x86_64
                ;;
            mips)
                ARCH=mipsx
                ;;
            armv5*|armv6*|armel)
                ARCH=armel
                ;;
            armv7*|armv8*)
                ARCH=armhf
                ;;
            aarch64)
                ARCH=aarch64
                ;;
            ppc)
                ARCH=ppcx
                ;;
            riscv64)
                ARCH=riscv64
                ;;
        esac
    fi

    if test "$OS" = "Linux"; then
        if test "$ARCH" = ""; then
            MIPS=`cat /proc/cpuinfo | sed -n 's/.*:.*\(mips\).*/&/p'`
            if test "$MIPS" != ""; then
                ARCH=mipsx
            fi
        fi

        if test "$ARCH" = "mipsx"; then
            DetectEndianness
            if test "$ENDIAN" = "big"; then
                ARCH=mipseb
            else
                ARCH=mipsel
            fi
        fi

        if test "$ARCH" = "ppcx"; then
            E500=`cat /proc/cpuinfo | sed -n 's/.*:.*\(e500\).*/&/p'`
            if test "$E500" != ""; then
                ARCH=ppc500
            else
                ARCH=ppc6xx
            fi
        fi
    fi

    if test "$ARCH" = ""; then
        PrintHelp
        Error "CPU architecture ($CPU) isn't supported by this installer."
    fi

    if ! ValidArch $ARCH; then
        Error "CPU architecture ($ARCH) isn't supported by this installer."
    fi
}

Unpack()
{
    mkdir -p "$OUTDIR"

    # Prepare list of files to ignore
    EXARCHS=""
    if test "$JUSTUNPACK" = "no" -a "$ARCH" != "all"; then
        rm -f "$OUTDIR/installer.tmp"
        for TARG in $ALLARCHS
        do
            if test "$TARG" != "$ARCH"; then
                echo "nzbget-$TARG" >> "$OUTDIR/installer.tmp"
                echo "unrar-$TARG" >> "$OUTDIR/installer.tmp"
                echo "unrar7-$TARG" >> "$OUTDIR/installer.tmp"
                echo "7za-$TARG" >> "$OUTDIR/installer.tmp"
                EXARCHS="-X installer.tmp"
            fi
        done
    fi

    # Unpack (skip ignorable files)
    dd "if=$0" bs=$HEADER skip=1 2> /dev/null | gzip -c -d | ( cd "$OUTDIR"; tar xf - $EXARCHS 2>&1 ) || { Error "Unpacking failed."; rm -f "$OUTDIR/installer.tmp"; kill -15 $$; }

    if test "$EXARCHS" != ""; then
        rm -f "$OUTDIR/installer.tmp"
    fi

    # Rename unpacked binary files and store arch selection
    if test "$JUSTUNPACK" = "no" -a "$ARCH" != "all"; then
        OLDDIR=`pwd`
        cd "$OUTDIR"
        rm -f nzbget
        rm -f unrar
        rm -f unrar7
        rm -f 7za
        mv nzbget-$ARCH nzbget
        mv unrar-$ARCH unrar
        mv unrar7-$ARCH unrar7
        mv 7za-$ARCH 7za
        echo "arch=$ARCH" > "installer.cfg"
        echo "select=$SELECT" >> "installer.cfg"
        cd "$OLDDIR"
    fi
}

TAR()
{
    dd "if=$0" bs=$HEADER skip=1 2> /dev/null | gzip -c -d | tar ${ARG}f - $@
    exit $?
}

# 'Expr' provides two versions of expression evaluations: either using shell
# built-in syntax $((arith)) or using command 'expr'.
# If both fail, an empty string is returned (caller must check for this).
# Currently supporting '+' and '/' operations (can be extended for others if needed).
Expr()
{
    ARG1=$1
    OP=$2
    ARG2=$3
    RET=
    TEST=`eval TEST='$((1+2))' 2>/dev/null || 1; echo $TEST`
    if test "$TEST" = "3"; then
        case "$OP" in
            "+")
                eval RET='$((ARG1 + ARG2))'
                ;;
            "/")
                eval RET='$((ARG1 / ARG2))'
                ;;
        esac
    else
        TEST=`expr 1 + 2 2>/dev/null | cat`
        if test "$TEST" = "3"; then
            case "$OP" in
                "+")
                    RET=`expr $ARG1 + $ARG2`
                    ;;
                "/")
                    RET=`expr $ARG1 / $ARG2`
                    ;;
            esac
        fi
    fi
    echo "$RET"
}

ConfigureLinux()
{
    # Adjusting config file to current system

    MEMFREE=`cat /proc/meminfo | sed -n 's/^MemFree: *\([0-9]*\).*/\1/p' 2>/dev/null | cat`
    MEMCACHED=`cat /proc/meminfo | sed -n 's/^Cached: *\([0-9]*\).*/\1/p' 2>/dev/null | cat`
    if test "$MEMFREE" != "" -a "$MEMCACHED" != ""; then
        TOTALFREE=$(Expr $MEMFREE + $MEMCACHED)
        # Expression evaluation with "Expr" may fail, check the result
        TOTALFREE=$(Expr "$TOTALFREE" / 1024)
        if test "$TOTALFREE" != ""; then
            Info "  Free memory detected: $TOTALFREE MB"
        else
            Info "  Free memory detected: $MEMFREE KB + $MEMCACHED KB"
            TOTALFREE=0
        fi
        if test $TOTALFREE -gt 250000 -o $MEMFREE -gt 250000 -o $MEMCACHED -gt 250000; then
            Info "  Activating article cache (ArticleCache=100)"
            sed 's:^ArticleCache=.*:ArticleCache=100:' -i nzbget.conf
            Info "  Increasing write buffer (WriteBuffer=1024)"
            sed 's:^WriteBuffer=.*:WriteBuffer=1024:' -i nzbget.conf
            Info "  Increasing par repair buffer (ParBuffer=100)"
            sed 's:^ParBuffer=.*:ParBuffer=100:' -i nzbget.conf
            Info "  Activating direct rename (DirectRename=yes)"
            sed 's:^DirectRename=.*:DirectRename=yes:' -i nzbget.conf
        elif test $TOTALFREE -gt 25000 -o $MEMFREE -gt 25000 -o $MEMCACHED -gt 25000; then
            Info "  Increasing write buffer (WriteBuffer=256)"
            sed 's:^WriteBuffer=.*:WriteBuffer=256:' -i nzbget.conf
        fi
    fi

    BOGOLIST=`cat /proc/cpuinfo | sed -n 's/^bogomips\s*:\s\([0-9]*\).*/\1/pI' 2>/dev/null | cat`
    if test "$BOGOLIST" != ""; then
        BOGOMIPS=0
        for CPU1 in $BOGOLIST
        do
            BOGOMIPS=$(Expr $BOGOMIPS + $CPU1)
            if test "$BOGOMIPS" = ""; then
                # Expression evaluation with "Expr" failed, using BogoMIPS for first core
                BOGOMIPS=$CPU1
            fi
        done
        Info "  CPU speed detected: $BOGOMIPS BogoMIPS"
        if test $BOGOMIPS -lt 4000; then
            Info "  Disabling download during par check/repair (ParPauseQueue=yes)"
            sed 's:^ParPauseQueue=.*:ParPauseQueue=yes:' -i nzbget.conf
            Info "  Disabling download during unpack (UnpackPauseQueue=yes)"
            sed 's:^UnpackPauseQueue=.*:UnpackPauseQueue=yes:' -i nzbget.conf
            Info "  Disabling download during post-processing (ScriptPauseQueue=yes)"
            sed 's:^ScriptPauseQueue=.*:ScriptPauseQueue=yes:' -i nzbget.conf
        else
            Info "  Simultaneous download and post-processing is on"
            Info "  Activating direct unpack (DirectUnpack=yes)"
            sed 's:^DirectUnpack=.*:DirectUnpack=yes:' -i nzbget.conf
        fi
    fi
}

ConfigureFreeBSD()
{
    # Adjusting config file to current system

    # No memory check, assuming all supported FreeBSD machines have enough memory

    Info "  Activating article cache (ArticleCache=100)"
    sed -i '' 's:^ArticleCache=.*:ArticleCache=100:' nzbget.conf
    Info "  Increasing write buffer (WriteBuffer=1024)"
    sed -i '' 's:^WriteBuffer=.*:WriteBuffer=1024:' nzbget.conf
    Info "  Increasing par repair buffer (ParBuffer=100)"
    sed -i '' 's:^ParBuffer=.*:ParBuffer=100:' nzbget.conf
    Info "  Activating direct rename (DirectRename=yes)"
    sed -i '' 's:^DirectRename=.*:DirectRename=yes:' nzbget.conf
    Info "  Activating direct unpack (DirectUnpack=yes)"
    sed -i '' 's:^DirectUnpack=.*:DirectUnpack=yes:' nzbget.conf
}

Configure()
{
    cd "$OUTDIR"
    QUICKHELP=no

    if test ! -f nzbget.conf; then
        cp ./webui/nzbget.conf.template nzbget.conf

        if test "$OS" = "Linux"; then
            ConfigureLinux
        fi

        if test "$OS" = "FreeBSD"; then
            ConfigureFreeBSD
        fi

        QUICKHELP=yes
    fi
}

Linux2FreeBSD()
{
    if test "$PLATFORM" = "linux" -a "$OS" = "FreeBSD" ; then
        # Using Linux installer on FreeBSD machine

        if test "$1" = "brand" ; then
            Info "  Branding Linux binaries for use on FreeBSD"
            brandelf -t Linux nzbget
            brandelf -t Linux unrar
            brandelf -t Linux unrar7
            brandelf -t Linux 7za
        fi

        if test "$1" = "kernel-check" ; then
            MODLINUX=`kldstat | sed -n 's/\(linux.ko\)/\1/p'`
            if test "$ARCH" = "x86_64"; then
                MODLINUX=`kldstat | sed -n 's/\(linux64.ko\)/\1/p'`
            fi
            if test "$MODLINUX" = ""; then
                Info ""
                Info "WARNING: Linux kernel module isn't loaded. See https://nzbget.com/documentation/installation-on-freebsd/"
            fi
        fi
    fi
}

# ParseCommandLine
while true
do
    PARAM=${1:-}
    case $PARAM in
        -h|--help)
            PrintHelp
            exit 0
            ;;
        --silent)
            SILENT=yes
            shift
            ;;
        --arch)
            ARCH=${2:-}
            SELECT=manual
            if ! ValidArch $ARCH; then
                PrintHelp
                Info ""
                Error "Bad argument ($ARCH) to option --arch."
                exit 1
            fi
            shift 2
            ;;
        --destdir)
            OUTDIR=${2:-}
            if test "$OUTDIR" = ""; then
                PrintHelp
                exit 1
            fi
            shift 2
            ;;
        --unpack)
            JUSTUNPACK=yes
            shift
            ;;
        --list)
            ARG=t
            TAR
            exit $?
            ;;
        --tar)
            ARG=${2:-}
            if test "$ARG" = ""; then
                PrintHelp
                exit 1
            fi
            shift 2
            TAR
            exit $?
            ;;
        --update)
            UPDATE=yes
            shift
            ;;
        --nocheck)
            VERIFY=no
            shift
            ;;
        "")
            break
            ;;
        *)
            PrintHelp
            exit 1
            ;;
    esac
done

Info "Installer for $TITLE"
if test "$SILENT" = "no"; then
    PRINTEDTITLE=yes
fi

if test "$VERIFY" = "yes"; then
    Info "Verifying package..."
    Verify
fi

if test "$JUSTUNPACK" = "no"; then
    Info "Checking system..."
    DetectArch
    if test "$SELECT" = "manual"; then
        Info "CPU-Architecture: $ARCH (manually set)"
    else
        Info "CPU-Architecture: $ARCH"
    fi
fi

Info "Unpacking..."
Unpack

ABSOUTDIR=`cd "$OUTDIR"; pwd`

if test "$JUSTUNPACK" = "no"; then
    Info "Configuring..."
    Configure
    Linux2FreeBSD "brand"

    Info "Installation completed"

    if test "$QUICKHELP" = "yes" -a "$SILENT" = "no"; then
        Info ""
        Info "Quick help (from nzbget-directory):"
        Info "   ./nzbget -s        - start nzbget in console mode"
        Info "   ./nzbget -D        - start nzbget in daemon mode (in background)"
        Info "   ./nzbget -C        - connect to background process"
        Info "   ./nzbget -Q        - stop background process"
        Info "   ./nzbget -h        - help screen with all commands"
        Info ""
        Info "Successfully installed into $ABSOUTDIR"

        # Trying to get current IP-address        
        IP=""
        INTERFACE=""
        {
            # First find default interface (ie 'wlan0') with the 'route' command
            INTERFACE=`route -n | sed -rn 's/^0.0.0.0.* ([^ ]+)$/\1/p'`
        } > /dev/null 2>&1
        if test "$INTERFACE" != ""; then
            # OK, a default route 0.0.0.0 + corresponding interface was found
            # Now find the IPv4 address on that interface:
            {
                IP=`ifconfig "$INTERFACE" | sed -rn 's/.*r:([^ ]+) .*/\1/p'`
            } > /dev/null 2>&1
        fi
        if test "$IP" = ""; then
            IP="localhost"
        fi

        Info "Web-interface is on http://$IP:6789 (login:nzbget, password:tegbzn6789)"
    else
        Info "Successfully installed into $ABSOUTDIR"
    fi
    Info "For support please visit https://nzbget.com/forum/"
    Linux2FreeBSD "kernel-check"
else
    Info "Unpacked into $ABSOUTDIR"
fi

exit
#END-OF-INSTALLER
