#!/bin/bash
########################################################################
# Use of code or any part of it is strictly prohibited. File protected by copyright law and provided under license.
# To Use any part of this code you need to get a writen approval from the code owner: info@centos-webpanel.com
########################################################################
#
# CWP installer for EL7 / EL8 / EL9 (one script, the EL version is detected)
#
# Faster than the old per-EL installers:
#   - packages go in a few large yum/dnf transactions (parallel downloads on EL8/EL9) instead of ~40 single calls;
#   - the CWP code, services, mail configs, CSF, phpMyAdmin and Roundcube are downloaded from the CWP CDN in the
#     background while the packages install;
#   - current versions are installed directly (panel PHP 8.3, PHP Switcher 8.3 RPM, MariaDB 10.11 LTS, and
#     phpMyAdmin / Roundcube in the versions the CWP update scripts of this release pin) - no old version first
#     and an upgrade right after.
# The full output goes to /var/log/cwp-install.log, the console shows the steps.
#
########################################################################

export LANG=en_US.UTF-8
export LC_ALL=en_US.UTF-8 2>/dev/null
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

CDN="http://static.cdn-cwp.com/files"
CWP_ZIP="cwp-el7-1.30.4.zip"          # panel code (admin + resources), the same for every EL
SERVICES_ZIP="cwp-services.zip"     # user panel, user API ...
LOG="/var/log/cwp-install.log"
DL="/usr/local/src/cwp-install"
START=$(date +%s)

help() {
  echo "Usage: $0 [OPTIONS]
  -r, --restart       Restart server after install  [yes]  default: no
  -p, --phpfpm        Install PHP-FPM version(s), comma separated  [EL7/EL8: 5.6 ... 8.5, EL9: 7.4 ... 8.5]  default: none
  -c, --phpcgi        Install PHP Selector (CGI) version(s), comma separated  [EL7/EL8: 5.6 ... 8.5, EL9: 7.4 ... 8.5]  default: none
  -s, --softaculous   Install Softaculous  [yes]  default: no
  -m, --modsecurity   Install ModSecurity with the CWAF rules  [yes]  default: no
  -d, --mariadb       MariaDB version  [10.11|11.4|distro]  default: 10.11
  -h, --help          Print this help

  Example: sh $0 -r yes --phpfpm 8.3,8.4 --phpcgi 7.4,8.1 --softaculous yes --modsecurity yes"
  exit 1
}

for argument; do
    delimiter=""
    case "$argument" in
        --restart)      arguments="${arguments}-r " ;;
        --phpfpm)       arguments="${arguments}-p " ;;
        --phpcgi)       arguments="${arguments}-c " ;;
        --softaculous)  arguments="${arguments}-s " ;;
        --modsecurity)  arguments="${arguments}-m " ;;
        --mariadb)      arguments="${arguments}-d " ;;
        --help)         arguments="${arguments}-h " ;;
        *)              [[ "${argument:0:1}" == "-" ]] || delimiter="\""
                        arguments="${arguments}${delimiter}${argument}${delimiter} ";;
    esac
done
eval set -- "$arguments"
while getopts "r:p:c:s:m:d:h" Oflags; do
    case $Oflags in
        r) restart=$OPTARG ;;
        p) phpfpm=$OPTARG ;;
        c) phpcgi=$OPTARG ;;
        s) softaculous=$OPTARG ;;
        m) modsecurity=$OPTARG ;;
        d) mariadb=$OPTARG ;;
        *) help ;;
    esac
done
mariadb=${mariadb:-10.11}

if [[ $EUID -ne 0 ]]; then
    echo "This script must be run as root"
    exit 1
fi
if [ -e "/usr/local/cwpsrv/" ]; then
    echo
    echo "CWP is already installed on your server."
    echo "If you want to update it, run this command: sh /scripts/update_cwp"
    echo
    exit 1
fi

: > "$LOG"
chmod 600 "$LOG"

# step "text": console + log; everything else goes only to the log
step() {
    printf '[%4ss] %s\n' "$(( $(date +%s) - START ))" "$*" | tee -a "$LOG"
}
run() {
    echo "+ $*" >> "$LOG"
    "$@" >> "$LOG" 2>&1
}
fail() {
    echo
    echo "Installation FAILED: $*"
    {
        echo "Installation FAILED: $*"
        echo "==============================================="
        echo "Installer Script Executed: $0"
        echo "Arguments: $arguments"
        echo "==============================================="
    } >> "$LOG"
    curl -s -m 30 "$CDN/s_scripts/sinfo.sh" | sh >> "$LOG" 2>&1
    tail -n 300 "$LOG" > /tmp/cwp.log
    curl -s -m 60 -F"operation=upload" -F"file=@/tmp/cwp.log" "http://error-reporting.control-webpanel.com/?service=installer" >/dev/null 2>&1
    echo "The full log is in $LOG"
    echo "Please contact CWP support about this issue and include the last lines of the log:"
    echo "http://centos-webpanel.com/contact"
    exit 1
}

########################################################################
# Checks
########################################################################
if [ "$(uname -m)" != "x86_64" ]; then
    echo "Unsupported Architecture ($(uname -m)), please use a 64bit EL7, EL8 or EL9 OS."
    exit 1
fi
if [ ! -f /etc/os-release ]; then
    echo "Cannot determine OS, /etc/os-release missing. Please use an EL7, EL8, EL9 or EL10 distribution."
    exit 1
fi
. /etc/os-release
EL=$(echo "$VERSION_ID" | cut -d. -f1)
case "$EL" in
    7|8|9|10) ;;
    *) echo "Unsupported Operating System ($NAME $VERSION_ID): this installer supports EL7, EL8, EL9 and EL10."
       exit 1 ;;
esac
if [ "$EL" = "7" ] && [ "$ID" != "centos" ] && [ "$ID" != "cloudlinux" ] && [ "$ID" != "rhel" ] && [ "$ID" != "ol" ]; then
    echo "Unsupported Operating System ($NAME $VERSION_ID), please use CentOS 7.x 64bit"
    exit 1
fi
# CDN build scripts (files/php/selector/elN): el7-el10 exist, a newer EL uses el10
CDNEL=$EL; [ "$EL" -gt 10 ] && CDNEL=10
CLOUDLINUX=0
grep -qi cloudlinux /etc/redhat-release 2>/dev/null && CLOUDLINUX=1

if [ "$(date +%Y)" -le 2014 ]; then
    echo "You have incorrect date set on your server!"
    date
    exit 1
fi

mem_mb=$(awk '/^MemTotal:/{print int($2/1024)}' /proc/meminfo)
swap_mb=$(awk '/^SwapTotal:/{print int($2/1024)}' /proc/meminfo)
if [ $((mem_mb + swap_mb)) -lt 900 ]; then
    echo "CWP needs at least 1 GB of RAM (+ swap), this server has ${mem_mb} MB RAM and ${swap_mb} MB swap."
    exit 1
fi
free_gb=$(df -P -BG / | awk 'NR==2{gsub("G","",$4); print $4}')
if [ "${free_gb:-0}" -lt 5 ]; then
    echo "At least 5 GB of free disk space on / is needed (free: ${free_gb} GB)."
    exit 1
fi

case "$mariadb" in
    10.11|11.4|distro) ;;
    *) echo "Unsupported MariaDB version '$mariadb' (use 10.11, 11.4 or distro)"; exit 1 ;;
esac
if [ -n "$phpfpm" ]; then
    for v in ${phpfpm//,/ }; do
        if ! [[ "$v" =~ ^[0-9]\.[0-9]$ ]]; then
            echo "Invalid PHP-FPM version '$v' (example: --phpfpm 8.3,8.4)"
            exit 1
        fi
    done
fi
if [ -n "$phpcgi" ]; then
    for v in ${phpcgi//,/ }; do
        if ! [[ "$v" =~ ^[0-9]\.[0-9]$ ]]; then
            echo "Invalid PHP-CGI version '$v' (example: --phpcgi 7.4,8.1)"
            exit 1
        fi
    done
fi

echo "CWP installer for $PRETTY_NAME - log: $LOG"
echo

step "Checking DNS resolution"
if ! getent ahosts static.cdn-cwp.com >/dev/null 2>&1; then
    echo "[!] DNS is not working, setting Google and Cloudflare DNS (8.8.8.8, 1.1.1.1)" | tee -a "$LOG"
    cp /etc/resolv.conf /etc/resolv.conf.bak 2>/dev/null
    echo -e "nameserver 8.8.8.8\nnameserver 1.1.1.1\noptions timeout:2 retries:2" > /etc/resolv.conf
    if [ -d /etc/NetworkManager/conf.d ]; then
        echo -e "[main]\ndns=none" > /etc/NetworkManager/conf.d/no-dns.conf
        systemctl reload NetworkManager >/dev/null 2>&1 || true
    fi
    getent ahosts static.cdn-cwp.com >/dev/null 2>&1 || { echo "[ERROR] DNS still fails. Please check outgoing network traffic/firewall on port 53!"; exit 1; }
fi

# existing MySQL/MariaDB (e.g. a reinstall or a hosting image): keep it and ask for its root password
type mysql >/dev/null 2>&1 && MYSQLCHK="on" || MYSQLCHK="off"
NEW_INSTALL=0
if [ "$MYSQLCHK" = "on" ]; then
    if [ -f /root/.my.cnf ]; then
        password=$(grep -i '^password' /root/.my.cnf | head -n1 | cut -d= -f2- | sed 's/["[:space:]]//g')
    fi
    check=$(mysql -u root -p"$password" -e "show databases;" -B 2>/dev/null | head -n1)
    while [ "$check" != "Database" ]; do
        read -r -p "MySQL root password []:" password
        check=$(mysql -u root -p"$password" -e "show databases;" -B 2>/dev/null | head -n1)
        [ "$check" = "Database" ] || echo "MySQL root password is invalid! You can remove the MySQL server (yum remove mysql) and run the installer again."
    done
else
    password=$(tr -dc A-Za-z0-9 </dev/urandom 2>/dev/null | head -c16)
    NEW_INSTALL=1
fi

########################################################################
# Repositories
########################################################################
step "Preparing the server and the repositories"
sed -i 's/SHA_CRYPT_MAX_ROUNDS/#SHA_CRYPT_MAX_ROUNDS/g' /etc/login.defs
sed -i "s/umask\=002/umask\=022/g" /etc/bashrc >/dev/null 2>&1
if [ -e "/etc/selinux/config" ]; then
    sed -i 's/SELINUX=enforcing/SELINUX=disabled/g' /etc/selinux/config
    setenforce 0 >/dev/null 2>&1
fi
# CSF/LFD is the firewall
systemctl stop firewalld >/dev/null 2>&1
systemctl disable firewalld >/dev/null 2>&1
systemctl mask firewalld >/dev/null 2>&1

if [ "$EL" = "7" ]; then
    # CentOS 7 is EOL: mirror.centos.org / mirrorlist are gone -> vault
    mkdir -p /root/yum.repos.d.cwp-backup && cp -a /etc/yum.repos.d/. /root/yum.repos.d.cwp-backup/ 2>/dev/null
    sed -i -e 's|^\s*\(mirrorlist=http.*mirrorlist\.centos\.org.*\)$|#\1|' \
           -e 's|^#*\s*baseurl=http\(s\)*://mirror\.centos\.org/|baseurl=http://vault.centos.org/|' \
           -e 's|^#*\s*baseurl=http\(s\)*://centos\.mirrors\.ovh\.net/ftp\.centos\.org/|baseurl=http://vault.centos.org/centos/|' \
           /etc/yum.repos.d/CentOS-*.repo 2>/dev/null
    PKG="yum -y"
    grep -q '^fastestmirror' /etc/yum/pluginconf.d/fastestmirror.conf 2>/dev/null && sed -i 's/^enabled=.*/enabled=0/' /etc/yum/pluginconf.d/fastestmirror.conf
    PKG_OPTS="--setopt=skip_missing_names_on_install=True"
else
    PKG="dnf -y"
    PKG_OPTS="--setopt=max_parallel_downloads=10 --setopt=install_weak_deps=False --skip-broken"
fi
case "$ID" in
    almalinux) run rpm --import https://repo.almalinux.org/almalinux/RPM-GPG-KEY-AlmaLinux ;;
esac

run $PKG $PKG_OPTS install ca-certificates epel-release wget tar unzip curl
# --whatprovides: some EL builds ship EPEL under another name (AlmaLinux x86_64_v2: epel-release-almalinux-altarch)
rpm -q --whatprovides epel-release >/dev/null 2>&1 || run $PKG install "https://dl.fedoraproject.org/pub/epel/epel-release-latest-${EL}.noarch.rpm"
rpm -q --whatprovides epel-release >/dev/null 2>&1 || fail "epel-release could not be installed"
# EPEL: http metalink (old CA bundles), nginx only from the CWP repo
sed -i "s/metalink=https/metalink=http/" /etc/yum.repos.d/epel*.repo
grep -q '^exclude=nginx' /etc/yum.repos.d/epel.repo || sed -i "/enabled=1/a exclude=nginx*" /etc/yum.repos.d/epel.repo

if [ "$EL" != "7" ]; then
    run dnf -y install dnf-plugins-core
    if [ "$EL" = "8" ]; then
        for r in /etc/yum.repos.d/*PowerTools*.repo /etc/yum.repos.d/*powertools*.repo; do
            [ -e "$r" ] || continue
            sed -i -e "s/enabled=0/enabled=1/g" -e "s/\[PowerTools\]/\[powertools\]/g" -e "s/\[Stream-PowerTools\]/\[powertools\]/g" "$r"
        done
        run dnf config-manager --set-enabled powertools
        grep -q ol8_codeready_builder /etc/yum.repos.d/*.repo 2>/dev/null && run dnf config-manager --set-enabled ol8_codeready_builder
    else
        run dnf config-manager --set-enabled crb || run /usr/bin/crb enable
        grep -q ol9_codeready_builder /etc/yum.repos.d/*.repo 2>/dev/null && run dnf config-manager --set-enabled ol9_codeready_builder
    fi
fi

# test hook: CWP_REPO_URL=<baseurl> uses another CWP repository (a local repo with new packages)
cat > /etc/yum.repos.d/cwp.repo <<EOF
[cwp]
name=CentOS Web Panel repo for Linux $EL - \$basearch
baseurl=${CWP_REPO_URL:-http://repo.centos-webpanel.com/repo/$EL/\$basearch}
enabled=1
gpgcheck=0
priority=1
EOF

# MariaDB LTS from the official MariaDB repository (not on CloudLinux, which ships its own MySQL/MariaDB governor builds)
MARIADB_REPO=0
if [ "$NEW_INSTALL" = "1" ] && [ "$mariadb" != "distro" ] && [ "$CLOUDLINUX" = "0" ]; then
    if [ "$EL" = "7" ]; then
        # the last MariaDB 10.11 packages built for CentOS 7 (archive); 11.x has no EL7 packages
        mdb_url="https://archive.mariadb.org/mariadb-10.11.9/yum/centos7-amd64"
        [ "$mariadb" = "11.4" ] && echo "MariaDB 11.4 has no EL7 packages, installing 10.11" | tee -a "$LOG"
    else
        mdb_url="https://yum.mariadb.org/$mariadb/rhel$EL-amd64"
    fi
    cat > /etc/yum.repos.d/mariadb.repo <<EOF
[mariadb]
name = MariaDB
baseurl = $mdb_url
gpgkey = https://yum.mariadb.org/RPM-GPG-KEY-MariaDB
gpgcheck = 1
enabled = 1
module_hotfixes = 1
EOF
    MARIADB_REPO=1
    [ "$EL" = "8" ] && run dnf -y module disable mariadb mysql
fi

########################################################################
# Background downloads from the CWP CDN (run while the packages install)
########################################################################
step "Downloading CWP files in the background"
rm -rf "$DL"; mkdir -p "$DL"
dl() { # dl <url> <file>
    wget -q --timeout=60 --tries=5 --waitretry=3 -O "$DL/$2.part" "$1" && mv -f "$DL/$2.part" "$DL/$2"
}
(
    dl "$CDN/cwp/el7/$CWP_ZIP" cwp.zip &
    dl "$CDN/cwp/el7/$SERVICES_ZIP" services.zip &
    dl "$CDN/mail/el7/mail_server_quota.zip" mail.zip &
    dl "$CDN/csf.tgz" csf.tgz &
    dl "$CDN/mail/rc_plugins.zip" rc_plugins.zip &
    dl "$CDN/cwp/sql/root_cwp.sql" root_cwp.sql &
    dl "$CDN/cwp/sql/oauthv2.sql" oauthv2.sql &
    dl "$CDN/cwp/sql/db_conn.txt" db_conn.txt &
    dl "$CDN/cwp/modules/example.txt" example.txt &
    dl "http://centos-webpanel.com/webpanel/main.php?dl=postfix.sql" postfix.sql &
    dl "http://centos-webpanel.com/webpanel/main.php?dl=postfix.txt" postfix.txt &
    dl "$CDN/3rdparty/stats/goaccess/goaccess.conf" goaccess.conf &
    wait
) >> "$LOG" 2>&1 &
DL_PID=$!

########################################################################
# Packages
########################################################################
step "Installing the base packages"
BASE="gcc gcc-c++ make automake autoconf rsync nano e2fsprogs rsyslog net-tools man mlocate which screen sysstat at zip git
      cronie perl-libwww-perl perl-LWP-Protocol-https links file subversion apr apr-util openssl ipset bind bind-utils bind-libs
      cyrus-sasl-devel cyrus-sasl-sql quota quota-devel"
case "$EL" in
    7) BASE="$BASE cpulimit perl-Crypt-SSLeay iptables iptables-services initscripts" ;;
    8) BASE="$BASE initscripts iptables iptables-ebtables ipset-libs nftables util-linux-user glibc-all-langpacks" ;;
    9) BASE="$BASE initscripts-service iptables-nft util-linux-user glibc-all-langpacks" ;;
    *) BASE="$BASE initscripts-service iptables-nft glibc-all-langpacks" ;;
esac
# EL10 stores passwords as yescrypt: the CWP user panel login checks SHA-512/SHA-256/MD5 crypt (the new login also
# accepts yescrypt) - new passwords as SHA-512 as on EL7-EL9
if [ "$EL" -ge 10 ]; then
    sed -i -E 's/^ENCRYPT_METHOD[[:space:]]+YESCRYPT/ENCRYPT_METHOD SHA512/' /etc/login.defs
    for f in /etc/pam.d/system-auth /etc/pam.d/password-auth; do
        [ -e "$f" ] && sed -i --follow-symlinks -E '/^password.*pam_unix\.so/ s/\byescrypt\b/sha512/' "$f"
    done
fi
# EL10 screen is not setgid but its /run/screen is 0775: screen refuses to start ("must have mode 777")
if [ "$EL" -ge 10 ]; then
    printf '# CWP: screen without setgid needs a world-writable (sticky) socket dir\nd /run/screen 1777 root root -\n' > /etc/tmpfiles.d/screen.conf
fi
# EL10 package names (mlocate -> plocate, links -> elinks, man -> man-db; util-linux-user is part of util-linux)
[ "$EL" -ge 10 ] && BASE=$(echo $BASE | sed -e 's/\bmlocate\b/plocate/' -e 's/\blinks\b/elinks/' -e 's/\bman\b/man-db/')
run $PKG remove exim sendmail
run $PKG $PKG_OPTS install $BASE || fail "base packages (see $LOG)"
[ -f /etc/tmpfiles.d/screen.conf ] && systemd-tmpfiles --create /etc/tmpfiles.d/screen.conf >/dev/null 2>&1

step "Installing MariaDB, mail, DNS and FTP packages"
if [ "$NEW_INSTALL" = "1" ]; then
    if [ "$MARIADB_REPO" = "1" ]; then
        DBPKG="MariaDB-server MariaDB-client MariaDB-backup"
        # MariaDB 11.x ships the mysql/mysqldump/mysqladmin/mysqld names only in the -compat packages: without them
        # "mysql" was missing, the install was taken as failed and stopped ("Installation FAILED: MariaDB")
        case "$mariadb" in 11.*) [ "$EL" != "7" ] && DBPKG="$DBPKG MariaDB-client-compat MariaDB-server-compat" ;; esac
    else
        DBPKG="mariadb mariadb-server"
    fi
fi
MAILPKG="dovecot dovecot-mysql dovecot-pigeonhole pure-ftpd perl-MailTools perl-MIME-EncWords perl-TimeDate perl-Mail-Sender
         perl-Log-Log4perl perl-Razor-Agent perl-Convert-BinHex amavisd-new clamd perl-Digest-SHA1 perl-IO-stringy opendkim
         opendkim-tools perl-Mail-DKIM perl-Mail-SPF pypolicyd-spf goaccess"
[ "$EL" = "7" ] && MAILPKG="$MAILPKG libopendkim"
[ "$EL" -ge 10 ] && MAILPKG="$(echo $MAILPKG | sed 's/\bamavisd-new\b/amavis/') perl-Socket6 perl-IO-Socket-INET6"
run $PKG $PKG_OPTS --enablerepo=cwp install postfix || fail "postfix"
# the CWP repo has an older MariaDB build with priority=1: install MariaDB from the MariaDB repo only
[ -n "$DBPKG" ] && run $PKG $PKG_OPTS --disablerepo=cwp install $DBPKG
run $PKG $PKG_OPTS install $MAILPKG
if [ "$NEW_INSTALL" = "1" ] && ! type mysql >/dev/null 2>&1; then
    # the official MariaDB repository not reachable: fall back to the distribution MariaDB
    echo "MariaDB $mariadb install failed, using the distribution MariaDB" | tee -a "$LOG"
    rm -f /etc/yum.repos.d/mariadb.repo
    MARIADB_REPO=0
    run $PKG $PKG_OPTS install mariadb mariadb-server
fi
type mysql >/dev/null 2>&1 || fail "MariaDB"
[ -e /etc/pure-ftpd/pure-ftpd.conf ] || fail "pure-ftpd"
# the distribution postfix (EL9) has the MySQL maps in a separate package; the CWP postfix (EL7/EL8) has them built in
postconf -m 2>/dev/null | grep -q mysql || run $PKG install postfix-mysql

step "Installing the CWP web server, panel and PHP"
# newest build of a CWP package in x86_64/ or beta/ (the beta directory has no yum metadata -> install by URL).
# Only used for the PHP Switcher PHP: the EL7 beta builds of cwp-httpd (2.4.68, mod_ssl needs OpenSSL 1.1) and
# cwpsrv (1.15.10-6, broken webmail.conf) do not work, so Apache and the panel server come from the main repo.
cwp_rpm() {
    local n=$1 d f best=""
    for d in x86_64 beta; do
        f=$(curl -s -m 30 "http://repo.centos-webpanel.com/repo/$EL/$d/" | grep -oE "href=\"$n-[0-9][^\"]*\.x86_64\.rpm\"" \
            | sed 's/^href="//; s/"$//' | sort -V | tail -n1)
        [ -n "$f" ] && best="$best$f $d"$'\n'
    done
    best=$(printf '%s' "$best" | sort -V -k1,1 | tail -n1)
    [ -n "$best" ] && echo "http://repo.centos-webpanel.com/repo/$EL/${best#* }/${best% *}"
}
run $PKG $PKG_OPTS install cwp-httpd cwp-suphp || fail "httpd"
[ -e /usr/local/apache/bin/httpd ] || fail "httpd (no /usr/local/apache/bin/httpd)"
# PHP Switcher PHP (default PHP for the websites): the cwp-php-switcher83 package the PHP Switcher also installs,
# the older cwp-php package when it is not available
if [ -n "$CWP_TEST_NO_SWITCHER" ]; then
    # test hook (bootstrap of a new EL): no PHP Switcher package yet, it is built afterwards through the panel
    echo "CWP_TEST_NO_SWITCHER: PHP Switcher PHP not installed" | tee -a "$LOG"
elif ! run $PKG $PKG_OPTS --enablerepo=epel install cwp-php-switcher83 || [ ! -e /usr/local/bin/php ]; then
    u=$(cwp_rpm cwp-php)
    run $PKG --enablerepo=epel install ${u:-cwp-php} || run $PKG --enablerepo=epel install cwp-php
fi
[ -e /usr/local/bin/php ] || [ -n "$CWP_TEST_NO_SWITCHER" ] || fail "php (no /usr/local/bin/php)"
# panel server + panel PHP
run $PKG $PKG_OPTS --enablerepo=epel install cwpsrv cwpphp
[ -e /usr/local/cwpsrv/bin/cwpsrv ] || fail "cwpsrv"
[ -e /usr/local/cwp/php71/bin/php ] || fail "cwp php-fpm (no /usr/local/cwp/php71/bin/php)"

if [ -e "/usr/local/bin/php-config" ]; then
    ext_dir=$(/usr/local/bin/php-config --extension-dir)
    grep -q ^extension_dir /usr/local/php/php.ini || echo "extension_dir='$ext_dir'" >> /usr/local/php/php.ini
fi
mkdir -p /usr/local/src /usr/local/cwp/.conf

pubip=$(curl -s -m 20 http://centos-webpanel.com/webpanel/main.php?app=showip)
[ -z "$pubip" ] && pubip=$(hostname -I | awk '{print $1}')
fqdn=$(/bin/hostname -f 2>/dev/null) || fqdn=$(/bin/hostname)
[ -z "$fqdn" ] && fqdn=$(/bin/hostname)

########################################################################
# MariaDB
########################################################################
step "Configuring MariaDB"
if [ "$NEW_INSTALL" = "1" ]; then
    sed -i "s|old_passwords=1|#old_passwords=1|" /etc/my.cnf 2>/dev/null
    [ -e /var/run/mariadb ] && chown -R mysql:mysql /var/run/mariadb/
    systemctl daemon-reload
    run systemctl enable mariadb
    run systemctl restart mariadb || fail "MariaDB does not start"
    if mysql -u root -e "SELECT VERSION()" 2>/dev/null | grep -qE '^(10\.([4-9]|1[0-9])|1[1-9])\.'; then
        # 10.4+: root keeps unix_socket login, plus the password the panel uses
        mysql -u root -e "ALTER USER 'root'@'localhost' IDENTIFIED VIA mysql_native_password USING PASSWORD('$password') OR unix_socket; FLUSH PRIVILEGES;" >> "$LOG" 2>&1
    else
        mysqladmin -u root password "$password" >> "$LOG" 2>&1
    fi
    mysql -u root -p"$password" -e "DROP DATABASE IF EXISTS test; DELETE FROM mysql.user WHERE User='' OR (User='root' AND Host NOT IN ('localhost')); FLUSH PRIVILEGES;" >> "$LOG" 2>&1
fi
cat > /root/.my.cnf <<EOF
[client]
password=$password
user=root
EOF
chmod 600 /root/.my.cnf
mysql -u root -p"$password" -e "SELECT 1" >/dev/null 2>&1 || fail "MySQL root login with the new password"
# MySQL 5.7+/MariaDB 10.2+ (ALTER USER) or older (PASSWORD())
DBVER=$(mysql -N -e "SELECT VERSION()" 2>/dev/null)
NEW=1
case "$DBVER" in 5.5*|5.6*|10.0*|10.1*) NEW=0 ;; esac

########################################################################
# Apache, PHP
########################################################################
step "Configuring Apache and PHP"
openssl genrsa -out /etc/pki/tls/cwp-$pubip.key 2048 >> "$LOG" 2>&1
openssl req -new -x509 -key /etc/pki/tls/cwp-$pubip.key -out /etc/pki/tls/cwp-$pubip.cert -days 3650 -subj /CN=$pubip >> "$LOG" 2>&1

sed -i "s|#Include conf/extra/httpd-userdir.conf|Include conf/extra/httpd-userdir.conf|" /usr/local/apache/conf/httpd.conf
cat > /usr/local/apache/conf.d/server-status.conf <<EOF
<Location /server-status>
    SetHandler server-status
    Order deny,allow
    Allow from localhost
</Location>
EOF
grep -q ^LimitNOFILE /usr/lib/systemd/system/httpd.service || echo -e "\n[Service]\nLimitNOFILE=65535" >> /usr/lib/systemd/system/httpd.service
systemctl daemon-reload
sed -i "s|\;date\.timezone \=.*|date\.timezone = Etc/UTC|" /usr/local/php/php.ini
sed -i "s|short_open_tag = Off|short_open_tag = On|" /usr/local/cwp/php71/php.ini /usr/local/php/php.ini
grep -q " $fqdn\$" /etc/hosts || echo "127.0.0.1 $fqdn" >> /etc/hosts
run systemctl enable httpd
[ -e /var/lib/php/session ] && chmod 777 /var/lib/php/session/
ln -sf /usr/local/bin/php /bin/php
ln -sf /usr/local/bin/php /usr/bin/php

########################################################################
# CWP files
########################################################################
step "Waiting for the CWP downloads"
wait "$DL_PID"
for f in cwp.zip services.zip mail.zip csf.tgz root_cwp.sql oauthv2.sql db_conn.txt example.txt postfix.sql postfix.txt; do
    [ -s "$DL/$f" ] || fail "download of $f from the CWP CDN"
done
unzip -tq "$DL/cwp.zip" >/dev/null 2>&1 || fail "the CWP code archive is corrupt"
unzip -tq "$DL/services.zip" >/dev/null 2>&1 || fail "the CWP services archive is corrupt"

step "Installing the CWP files"
mkdir -p /usr/local/cwpsrv/htdocs /usr/local/cwpsrv/var/services
unzip -o -q "$DL/cwp.zip" -d /usr/local/cwpsrv/htdocs || fail "unzip of the CWP code"
unzip -o -q "$DL/services.zip" -d /usr/local/cwpsrv/var/services || fail "unzip of the CWP services"
# test hook: newer resources/scripts on top of the release zip (CWP_TEST_OVERLAY=/path/scripts.tgz)
if [ -n "$CWP_TEST_OVERLAY" ] && [ -s "$CWP_TEST_OVERLAY" ]; then
    tar -xzf "$CWP_TEST_OVERLAY" -C /usr/local/cwpsrv/htdocs || fail "test overlay"
fi
cp "$DL/db_conn.txt" /usr/local/cwpsrv/htdocs/resources/admin/include/db_conn.php
cp "$DL/example.txt" /usr/local/cwpsrv/htdocs/resources/admin/modules/example.php
ran_password2=$(tr -dc A-Za-z0-9 </dev/urandom 2>/dev/null | head -c12)
sed -i "s|\$crypt_pwd = ''|\$crypt_pwd = '${ran_password2}'|" /usr/local/cwpsrv/htdocs/resources/admin/include/db_conn.php
sed -i "s|\$db_pass = ''|\$db_pass = '$password'|" /usr/local/cwpsrv/htdocs/resources/admin/include/db_conn.php
chmod 600 /usr/local/cwpsrv/htdocs/resources/admin/include/db_conn.php
[ -e /scripts ] || ln -s /usr/local/cwpsrv/htdocs/resources/scripts /scripts
chmod +x /scripts/*
cp /usr/local/cwpsrv/htdocs/resources/admin/tpl/new_account_tpl/* /usr/local/apache/htdocs/. 2>/dev/null
# the new code needs the panel PHP 8.3 build (installed above): skip the one-time 7.2 -> 8.3 migration
if /usr/local/cwp/php71/bin/php -n -r 'exit(PHP_MAJOR_VERSION >= 8 ? 0 : 1);' 2>/dev/null; then
    touch /usr/local/cwp/.conf/.cwpphp83.done
fi

mysql -u root -p"$password" < "$DL/root_cwp.sql" >> "$LOG" 2>&1 || fail "import of the CWP database"
mysql -u root -p"$password" < "$DL/oauthv2.sql" >> "$LOG" 2>&1
mysql -u root -p"$password" -e "UPDATE root_cwp.settings SET shared_ip='$pubip';" >> "$LOG" 2>&1

# cron jobs
cat > /etc/cron.daily/cwp <<EOF
/usr/local/cwp/php71/bin/php -d max_execution_time=1000000 -q /usr/local/cwpsrv/htdocs/resources/admin/include/cron.php
/usr/local/cwp/php71/bin/php -d max_execution_time=1000000 -q /usr/local/cwpsrv/htdocs/resources/admin/include/cron_backup.php
EOF
chmod +x /etc/cron.daily/cwp
CRONDATE1=$(date +%M\ %H -d '1 hour ago')
echo "$CRONDATE1 * * * /usr/local/cwp/php71/bin/php -d max_execution_time=18000 -q /usr/local/cwpsrv/htdocs/resources/admin/include/cron_autossl_all_domains.php" >> /var/spool/cron/root
echo "0 0 * * * /usr/local/cwp/php71/bin/php -d max_execution_time=18000 -q /usr/local/cwpsrv/htdocs/resources/admin/include/alertandautorenewssl.php" >> /var/spool/cron/root

# NAT-ed network (public IP not on an interface): the vhosts must listen on the local IP, CWP takes it from nat.conf
if ! /sbin/ip addr sh | grep -q "inet $pubip/"; then
    touch /usr/local/cwp/.conf/nat_check.conf
    localip=$(ip -4 route get 1.1.1.1 2>/dev/null | grep -oP 'src \K[0-9.]+')
    [ -n "$localip" ] && [ ! -e /usr/local/cwp/.conf/nat.conf ] && echo "$localip" > /usr/local/cwp/.conf/nat.conf
fi

########################################################################
# DNS
########################################################################
sed -i "s|127.0.0.1|any|" /etc/named.conf
sed -i "s|localhost|any|" /etc/named.conf
sed -i 's/recursion yes/recursion no/g' /etc/named.conf
run systemctl enable named

########################################################################
# CSF firewall
########################################################################
step "Installing the CSF firewall"
# EL10 kernels have nft_compat / xt_multiport / xt_conntrack / xt_state only in kernel-modules-extra: without them CSF
# cannot load its rules and its DROP policy blocks all traffic of the server
if [ "$EL" -ge 10 ]; then
    run $PKG install "kernel-modules-extra-$(uname -r)" kernel-modules-extra
    modprobe nft_compat >/dev/null 2>&1
fi
cd "$DL" && tar -xzf csf.tgz && cd csf && run sh install.sh
cd /
[ -e /etc/csf/csf.conf ] || fail "CSF"
sed -i "s|80,110,113,443|80,110,113,443,2030,2031,2082,2083,2086,2087,2095,2096|" /etc/csf/csf.conf
sed -i 's|TESTING = "1"|TESTING = "0"|' /etc/csf/csf.conf
echo "# Run external commands before csf configures iptables" >> /usr/local/csf/bin/csfpre.sh
echo "# Run external commands after csf configures iptables" >> /usr/local/csf/bin/csfpost.sh
cat >> /etc/csf/csf.pignore <<EOF
# CWP CUSTOM
exe:/usr/sbin/clamd
exe:/usr/sbin/opendkim
exe:/usr/libexec/mysqld
exe:/usr/sbin/mysqld
exe:/usr/sbin/mariadbd
exe:/usr/bin/postgres
exe:/usr/bin/mongod
exe:/usr/libexec/dovecot/anvil
exe:/usr/libexec/dovecot/auth
exe:/usr/libexec/dovecot/imap-login
exe:/usr/libexec/dovecot/dict
exe:/usr/libexec/dovecot/stats
exe:/usr/libexec/dovecot/pop3-login
exe:/usr/local/cwp/php71/sbin/php-fpm

exe:/usr/libexec/postfix/tlsmgr
exe:/usr/libexec/postfix/qmgr
exe:/usr/libexec/postfix/pickup
exe:/usr/libexec/postfix/smtpd
exe:/usr/libexec/postfix/smtp
exe:/usr/libexec/postfix/bounce
exe:/usr/libexec/postfix/scache
exe:/usr/libexec/postfix/anvil
exe:/usr/libexec/postfix/cleanup
exe:/usr/libexec/postfix/proxymap
exe:/usr/libexec/postfix/trivial-rewrite
exe:/usr/libexec/postfix/local
exe:/usr/libexec/postfix/pipe
exe:/usr/libexec/postfix/spawn
exe:/usr/libexec/postfix/showq
exe:/usr/libexec/postfix/lmtp

exe:/usr/sbin/varnishd
exe:/usr/sbin/nginx
exe:/usr/sbin/rpcbind
exe:/usr/bin/memcached
exe:/usr/sbin/rngd
exe:/usr/lib/systemd/systemd-resolved

exe:/usr/bin/perl
user:amavis
cmd:/usr/sbin/amavisd
user:netdata
EOF
touch /var/lib/csf/csf.tempban /var/lib/csf/csf.tempallow
sed -i "s|CUSTOM1_LOG.*|CUSTOM1_LOG = \"/var/log/cwp_client_login.log\"|g" /etc/csf/csf.conf
sed -i "s|CUSTOM2_LOG.*|CUSTOM2_LOG = \"/usr/local/apache/domlogs/*.log\"|g" /etc/csf/csf.conf
sed -i "s|^HTACCESS_LOG.*|HTACCESS_LOG = \"/usr/local/apache/logs/error_log\"|g" /etc/csf/csf.conf
sed -i "s|^MODSEC_LOG.*|MODSEC_LOG = \"/usr/local/apache/logs/error_log\"|g" /etc/csf/csf.conf
sed -i "s|^POP3D_LOG.*|POP3D_LOG = \"/var/log/dovecot-info.log\"|g" /etc/csf/csf.conf
sed -i "s|^IMAPD_LOG.*|IMAPD_LOG = \"/var/log/dovecot-info.log\"|g" /etc/csf/csf.conf
sed -i "s|^SMTPAUTH_LOG.*|SMTPAUTH_LOG = \"/var/log/maillog\"|g" /etc/csf/csf.conf
sed -i "s|^FTPD_LOG.*|FTPD_LOG = \"/var/log/messages\"|g" /etc/csf/csf.conf
cat > /usr/local/csf/bin/regex.custom.pm <<EOF
#!/usr/bin/perl
sub custom_line {
        my \$line = shift;
        my \$lgfile = shift;
# Do not edit before this point

# CWP Failed Login Protection
if ((\$globlogs{CUSTOM1_LOG}{\$lgfile}) and (\$line =~ /^\S+\s+\S+\s+(\S+)\s+Failed Login from:\s+(\S+) on: (\S+)/)) {
               return ("Failed CWP-Login login for User: \$1 from IP: \$2 URL: \$3",\$2,"cwplogin","5","2030,2031","1");
}

# Wordpress XMLRPC
if ((\$globlogs{CUSTOM2_LOG}{\$lgfile}) and (\$line =~ /(\S+).*] "\w*(?:GET|POST) \/xmlrpc\.php.*" /)) {
return ("WP XMLPRC Attack",\$1,"XMLRPC","10","80,82,443,8181,8443","1");
}

# Wordpress WP-LOGINS
if ((\$globlogs{CUSTOM2_LOG}{\$lgfile}) and (\$line =~ /(\S+).*] "\w*(?:GET|POST) \/wp-login\.php.*" /)) {
return ("WP Login Attack",\$1,"WPLOGIN","10","80,82,443,8181,8443","1");
}

# Do not edit beyond this point
        return 0;
}
1;
EOF
run csf -x

########################################################################
# Mail server
########################################################################
step "Configuring the mail server"
sed -i "s|inet_interfaces = localhost|inet_interfaces = all|" /etc/postfix/main.cf
sed -i "s|#home_mailbox = Maildir/|home_mailbox = Maildir/|" /etc/postfix/main.cf
touch /var/log/dovecot-debug.log /var/log/dovecot-info.log /var/log/dovecot.log
chmod 600 /var/log/dovecot-debug.log /var/log/dovecot-info.log /var/log/dovecot.log
usermod -G mail dovecot

postfix_pwd=$(tr -dc A-Za-z0-9 </dev/urandom 2>/dev/null | head -c16)
cnf_hostname=$fqdn
mysql -u root -p"$password" >> "$LOG" 2>&1 <<EOF
DROP USER IF EXISTS 'postfix'@'localhost';
CREATE DATABASE IF NOT EXISTS postfix;
CREATE USER IF NOT EXISTS 'postfix'@'localhost' IDENTIFIED BY '$postfix_pwd';
GRANT ALL PRIVILEGES ON postfix.* TO 'postfix'@'localhost';
FLUSH PRIVILEGES;
EOF
# the 2013 schema uses MyISAM: its utf8 keys (vacation_notification) are longer than the MyISAM limit of 1000 bytes on
# MariaDB 10.6+ ("Specified key was too long") -> InnoDB
sed -e 's/ENGINE=MyISAM/ENGINE=InnoDB/g' "$DL/postfix.sql" | mysql -u root -p"$password" postfix >> "$LOG" 2>&1 || fail "import of the postfix database"

# clamd + amavisd
sed -i '/^Example$/d' /etc/clamd.d/scan.conf /etc/freshclam.conf 2>/dev/null
sed -i 's/^#LocalSocket /LocalSocket /' /etc/clamd.d/scan.conf 2>/dev/null
sed -i 's/^LocalSocketGroup.*$/LocalSocketGroup amavis/' /etc/clamd.d/scan.conf 2>/dev/null
usermod -a -G amavis clamscan >/dev/null 2>&1
if [ -e /usr/lib/systemd/system/clamd@.service ]; then
    cd /usr/lib/systemd/system
    mv clamd\@.service clamd.service
    sed -i 's/^ExecStart.*$/ExecStart = \/usr\/sbin\/clamd -c \/etc\/clamd.d\/amavisd.conf --foreground=yes/' clamd.service
    sed -i "s/^Type \= forking/Type \= simple/" clamd.service
    [ -e clamd\@scan.service ] && mv clamd\@scan.service clamd-scan.service && sed -i 's/clamd@.service/clamd.service/' clamd-scan.service
    cd /
    systemctl daemon-reload
fi

# CWP mail configs (postfix, dovecot, vacation ...)
unzip -o -q "$DL/mail.zip" -d / || fail "unzip of the mail configs"
mkdir -p /var/vmail && chmod 770 /var/vmail
id vmail >/dev/null 2>&1 || useradd -r -u 101 -g mail -d /var/vmail -s /sbin/nologin -c "Virtual mailbox" vmail
chown vmail:mail /var/vmail
touch /etc/postfix/virtual_regexp
id vacation >/dev/null 2>&1 || useradd -r -d /var/spool/vacation -s /sbin/nologin -c "Virtual vacation" vacation
mkdir -p /var/spool/vacation && chmod 770 /var/spool/vacation
ln -sf /etc/postfix/vacation.php /var/spool/vacation/vacation.php
chmod +x /etc/postfix/vacation.php
usermod -G mail vacation
chown vacation /etc/postfix/vacation.php
echo "autoreply.$cnf_hostname vacation:" > /etc/postfix/transport
postmap /etc/postfix/transport
chown -R vacation:vacation /var/spool/vacation
grep -q "autoreply.$cnf_hostname" /etc/hosts || echo "127.0.0.1 autoreply.$cnf_hostname" >> /etc/hosts

mkdir -p /var/sieve/
cat > /var/sieve/globalfilter.sieve <<EOF
require "fileinto";
if exists "X-Spam-Flag" {
if header :contains "X-Spam-Flag" "NO" {
} else {
fileinto "Spam";
stop;
}
}
if header :contains "subject" ["***SPAM***"] {
fileinto "Spam";
stop;
}
EOF
chown -R vmail:mail /var/sieve

# hostname certificate (self signed, 10 years) for postfix/dovecot/panel until AutoSSL replaces it
openssl req -x509 -nodes -newkey rsa:2048 -days 3650 -keyout /etc/pki/tls/private/hostname.key -out /etc/pki/tls/certs/hostname.crt \
    -subj "/C=HR/ST=Zagreb/L=HR/O=CentOS Web Panel/OU=CentOS Web Panel/CN=$cnf_hostname" >> "$LOG" 2>&1 || fail "hostname certificate"
cp /etc/pki/tls/certs/hostname.crt /etc/pki/tls/certs/hostname.bundle
ln -sf /etc/pki/tls/private/hostname.key /etc/pki/tls/private/$cnf_hostname.key
ln -sf /etc/pki/tls/certs/hostname.crt /etc/pki/tls/certs/$cnf_hostname.crt

# pure-ftpd
touch /etc/pure-ftpd/pureftpd.passwd
pure-pw mkdb /etc/pure-ftpd/pureftpd.pdb -f /etc/pure-ftpd/pureftpd.passwd -m >> "$LOG" 2>&1
sed -i 's|.*pureftpd.pdb.*|PureDB /etc/pure-ftpd/pureftpd.pdb|g' /etc/pure-ftpd/pure-ftpd.conf
sed -i 's|.*PAMAuthentication.*yes|PAMAuthentication    yes|g' /etc/pure-ftpd/pure-ftpd.conf
sed -i 's|.*UnixAuthentication.*yes|UnixAuthentication       yes|g' /etc/pure-ftpd/pure-ftpd.conf
grep -q "^/sbin/nologin$" /etc/shells || echo "/sbin/nologin" >> /etc/shells
run systemctl enable pure-ftpd

# postfix / dovecot / vacation passwords and names
sed -i "s|MY_HOSTNAME|$cnf_hostname|g" /etc/postfix/main.cf
sed -i "s|MY_HOSTNAME|autoreply.$cnf_hostname|g" /etc/postfix/mysql-virtual_vacation.cf
sed -i "s|MY_DOMAIN|$cnf_hostname|g" /etc/postfix/main.cf
sed -i "s|MYSQL_PASSWORD|$postfix_pwd|g" /etc/postfix/mysql-*.cf
sed -i '/^.*smtpd_bind_address.*$/d' /etc/postfix/master.cf
sed -i '/^.*virtual_mailbox_limit_maps.*$/d' /etc/postfix/main.cf
[ -e /etc/postfix/postfix-files ] || touch /etc/postfix/postfix-files
P=/usr/local/cwpsrv/htdocs/resources/admin/include/postfix.php
[ -e "$P" ] || cp "$DL/postfix.txt" "$P"
sed -i "s|\$db_pass_postfix = ''|\$db_pass_postfix = '$postfix_pwd'|" "$P"
chmod 600 "$P"
sed -i "s|MYSQL_PASSWORD|$postfix_pwd|" /etc/postfix/vacation.conf
sed -i "s|AUTO_REPLAY|autoreply.$cnf_hostname|" /etc/postfix/vacation.conf
sed -i "s|MYSQL_PASSWORD|$postfix_pwd|" /etc/dovecot/dovecot-dict-quota.conf /etc/dovecot/dovecot-mysql.conf /etc/dovecot/dovecot-token.conf
sed -i "s|MY_DOMAIN|$cnf_hostname|g" /etc/dovecot/dovecot.conf
chmod 640 /etc/postfix/mysql-*.cf /etc/dovecot/dovecot-*.conf
chown root:mail /etc/postfix/mysql-*.cf /etc/dovecot/dovecot-*.conf
# EL10 postfix has no hash maps (lmdb only): switch the CWP main.cf, else no mail is delivered
[ -e /usr/local/cwpsrv/htdocs/resources/scripts/fix_postfix_dbtype ] && bash /usr/local/cwpsrv/htdocs/resources/scripts/fix_postfix_dbtype >> "$LOG" 2>&1
# incoming SPF check (reject hard SPF failures) and SRS for forwarded mail (postsrsd)
[ -e /usr/local/cwpsrv/htdocs/resources/scripts/mail_spf_policy ] && bash /usr/local/cwpsrv/htdocs/resources/scripts/mail_spf_policy >> "$LOG" 2>&1
[ -e /usr/local/cwpsrv/htdocs/resources/scripts/mail_srs ] && bash /usr/local/cwpsrv/htdocs/resources/scripts/mail_srs >> "$LOG" 2>&1

# opendkim
cat > /etc/opendkim.conf << EOL
AutoRestart             Yes
AutoRestartRate         10/1h
LogWhy                  Yes
Syslog                  Yes
SyslogSuccess           Yes
Mode                    sv
Canonicalization        relaxed/simple
ExternalIgnoreList      refile:/etc/opendkim/TrustedHosts
InternalHosts           refile:/etc/opendkim/TrustedHosts
KeyTable                refile:/etc/opendkim/KeyTable
SigningTable            refile:/etc/opendkim/SigningTable
SignatureAlgorithm      rsa-sha256
Socket                  inet:8891@localhost
PidFile                 /var/run/opendkim/opendkim.pid
UMask                   022
UserID                  opendkim:opendkim
TemporaryDirectory      /var/tmp
EOL

########################################################################
# phpMyAdmin + Roundcube (current versions directly)
########################################################################
# versions: the ones this CWP release's update scripts install
RC_VER=$(grep -m1 '^RC_VER=' /scripts/update_roundcube 2>/dev/null | cut -d'"' -f2)
[ -z "$RC_VER" ] && RC_VER=$(curl -s -m 20 "$CDN/mail/rc.txt")
step "Installing phpMyAdmin and Roundcube $RC_VER"
SVC=/usr/local/cwpsrv/var/services
# both update scripts check their login page through the panel server
chown -R cwpsvc:cwpsvc $SVC
run sh /scripts/restart_cwpsrv
# phpMyAdmin: the CWP update script does the checksum check, the CWP config and the panel single sign-on
run sh /scripts/mysql_phpmyadmin_update
[ -e $SVC/pma/index.php ] || fail "phpMyAdmin"

# Roundcube
dl "$CDN/mail/roundcubemail-${RC_VER}-complete.tar.gz" roundcube.tar.gz
if [ -s "$DL/roundcube.tar.gz" ] && tar -tzf "$DL/roundcube.tar.gz" >/dev/null 2>&1; then
    rm -rf $SVC/roundcube && mkdir -p $SVC/roundcube
    tar -xzf "$DL/roundcube.tar.gz" -C $SVC/roundcube --strip-components=1
else
    fail "Roundcube download"
fi
rc_pwd=$(tr -dc A-Za-z0-9 </dev/urandom 2>/dev/null | head -c16)
mysql -u root -p"$password" >> "$LOG" 2>&1 <<EOF
CREATE DATABASE IF NOT EXISTS roundcube;
CREATE USER IF NOT EXISTS 'roundcube'@'localhost' IDENTIFIED BY '$rc_pwd';
GRANT ALL PRIVILEGES ON roundcube.* TO 'roundcube'@'localhost';
FLUSH PRIVILEGES;
EOF
mysql -u root -p"$password" roundcube < $SVC/roundcube/SQL/mysql.initial.sql >> "$LOG" 2>&1 || fail "Roundcube database"
RCC=$SVC/roundcube/config/config.inc.php
des_key=$(tr -dc A-Za-z0-9 </dev/urandom 2>/dev/null | head -c24)
cat > $RCC <<EOF
<?php
\$config = [];
\$config['db_dsnw'] = 'mysqli://roundcube:${rc_pwd}@localhost/roundcube';
\$config['imap_host'] = 'localhost:143';
\$config['smtp_host'] = 'localhost:25';
// Roundcube 1.5 names of the same settings
\$config['default_host'] = 'localhost';
\$config['smtp_server'] = 'localhost';
\$config['smtp_port'] = 25;
\$config['smtp_user'] = '%u';
\$config['smtp_pass'] = '%p';
\$config['support_url'] = '';
\$config['des_key'] = '${des_key}';
\$config['product_name'] = 'Roundcube Webmail';
// one plugin per line: mail_roundcube_update adds the CWP plugins after the managesieve line
\$config['plugins'] = [
    'archive',
    'zipdownload',
    'password',
    'managesieve',
];
\$config['skin'] = 'elastic';
\$config['quota_zero_as_unlimited'] = true;
\$config['preview_pane'] = true;
\$config['read_when_deleted'] = false;
\$config['check_all_folders'] = true;
\$config['display_next'] = true;
\$config['reply_mode'] = 1;
\$config['sig_below'] = false;
\$config['login_lc'] = 2;
\$config['enable_installer'] = false;
// relative links: Roundcube 1.6 otherwise posts the /webmail/ login to the website root
\$config['request_path'] = './';
EOF
# password plugin: change the mailbox password in the CWP postfix database (same settings as the old CWP Roundcube)
cat > $SVC/roundcube/plugins/password/config.inc.php <<EOF
<?php
\$config['password_driver'] = 'sql';
\$config['password_confirm_current'] = true;
\$config['password_minimum_length'] = 0;
\$config['password_require_nonalpha'] = false;
\$config['password_log'] = false;
\$config['password_force_save'] = false;
\$config['password_algorithm'] = 'clear';
\$config['password_algorithm_prefix'] = '';
\$config['password_db_dsn'] = 'mysqli://postfix:${postfix_pwd}@localhost/postfix';
\$config['password_query'] = 'UPDATE \`mailbox\` SET \`password\` = %c, modified=now() WHERE \`username\` = %u LIMIT 1';
\$config['password_crypt_hash'] = 'md5';
\$config['password_idn_ascii'] = false;
\$config['password_hash_algorithm'] = 'sha1';
\$config['password_hash_base64'] = false;
EOF
rm -rf $SVC/roundcube/installer
chmod 755 $SVC/roundcube/temp $SVC/roundcube/logs
# CWP plugins (calendar, tasks, carddav, CWP auto-login) and their tables
run sh /scripts/mail_roundcube_update
chown -R cwpsvc:cwpsvc $SVC/roundcube
chmod 640 $RCC

########################################################################
# Optional: Softaculous, ModSecurity
########################################################################
if [ "$softaculous" = "yes" ]; then
    step "Installing Softaculous"
    SOFTACULOUSPWD=$(tr -dc A-Za-z0-9 </dev/urandom 2>/dev/null | head -c12)
    grep -q softaculous /usr/local/cwp/.conf/.api_keys 2>/dev/null || echo "softaculous:${SOFTACULOUSPWD}:1: " > /usr/local/cwp/.conf/.api_keys
    [ -e /usr/local/cwp/php ] || ln -s /usr/local/cwp/php71/ /usr/local/cwp/php
    cd /usr/local/src && rm -f install.sh && wget -q -N http://files.softaculous.com/install.sh && chmod 755 install.sh && run sh /usr/local/src/install.sh --quick
    rm -f /usr/local/cwpsrv/conf.d/softaculous.conf /usr/local/cwpsrv/conf/include/softaculous.conf
    wget -q "$CDN/3rdparty/softaculous/el7/softaculous.conf" -O /usr/local/cwpsrv/conf/include/softaculous.conf
    cd /
fi

if [ "$modsecurity" = "yes" ]; then
    step "Installing ModSecurity"
    MODSECCONF="/usr/local/cwp/.conf/mod_security.conf"
    MODSECMAINCONF="/usr/local/apache/conf.d/mod_security.conf"
    run $PKG $PKG_OPTS install libxml2 libxml2-devel pcre-devel curl-devel expat-devel apr-devel apr-util-devel libuuid-devel lua lua-devel
    cd /usr/local/src && wget -q "$CDN/apache/modsecurity-2.9.1.tar.gz" && tar -xzf modsecurity-2.9.1.tar.gz && cd modsecurity-2.9.1 \
        && run ./configure --with-apxs=/usr/local/apache/bin/apxs --with-apr=/usr/bin/apr-1-config --with-apu=/usr/bin/apu-1-config \
        && run make -j"$(nproc)" && run make install
    cd /
    touch $MODSECCONF
    if [ -e "/usr/local/apache/modules/mod_security2.so" ]; then
        echo "modsecurityinstall = 1" >> $MODSECCONF
        lua_so=$(ls /usr/lib64/liblua-5.*.so /usr/lib64/liblua.so 2>/dev/null | head -n1)
        cat > $MODSECMAINCONF <<EOF
LoadFile /usr/lib64/libxml2.so
${lua_so:+LoadFile $lua_so}

<IfModule !unique_id_module>
  LoadModule unique_id_module modules/mod_unique_id.so
</IfModule>

<IfModule !mod_security2.c>
  LoadModule security2_module  modules/mod_security2.so
</IfModule>

<IfModule mod_security2.c>
  <IfModule mod_ruid2.c>
    SecAuditLogStorageDir /usr/local/apache/logs/modsec_audit
    SecAuditLogType Concurrent
  </IfModule>
  <IfModule itk.c>
    SecAuditLogStorageDir /usr/local/apache/logs/modsec_audit
    SecAuditLogType Concurrent
  </IfModule>

  SecRuleEngine On
  SecAuditEngine RelevantOnly
  SecAuditLog /usr/local/apache/logs/modsec_audit.log
  SecDebugLog /usr/local/apache/logs/modsec_debug.log
  SecAuditLogType Serial
  SecDebugLogLevel 0
  SecRequestBodyAccess On
  SecDataDir /tmp
  SecTmpDir /tmp
  SecUploadDir /tmp
  SecCollectionTimeout 600
  SecPcreMatchLimit 1250000
  SecPcreMatchLimitRecursion 1250000
  Include "/usr/local/apache/modsecurity-cwaf/cwaf.conf"
</IfModule>
EOF
        cd /usr/local/apache/ && rm -Rf modsecurity-cwaf modsecurity-cwaf.zip
        wget -q "$CDN/apache/mod-security/modsecurity-cwaf.zip" && unzip -q modsecurity-cwaf.zip && rm -f modsecurity-cwaf.zip
        cd /usr/local/apache/modsecurity-cwaf/rules && wget -q "$CDN/apache/mod-security/comodo_waf.zip" && unzip -o -q comodo_waf.zip && rm -f comodo_waf.zip
        echo "modsecurityrules = 3" >> $MODSECCONF
        mkdir -p /usr/local/apache/logs/tmp && chown nobody:root /usr/local/apache/logs/tmp
        cd /
    fi
fi

########################################################################
# Final configuration
########################################################################
step "Final configuration"
cat > /usr/local/cwp/.conf/web_servers.conf <<EOF
{
    "webserver_setup": "apache-only",
    "apache-main": true,
    "php-cgi": true,
    "php-fpm": true,
    "apache_template-type-default": "default",
    "apache_template-name-default": "default"
}
EOF
chmod 711 /home
mkdir -p /usr/local/apache/htdocs/.well-known
chown -R nobody:nobody /usr/local/apache/htdocs/*
chown vmail:mail /var/log/dovecot*
[ -e "$DL/goaccess.conf" ] && cp "$DL/goaccess.conf" /etc/goaccess.conf

# login banner
grep -q "bash_cwp" /root/.bash_profile 2>/dev/null || echo "sh /root/.bash_cwp" >> /root/.bash_profile
cat > /root/.bash_cwp <<EOF
echo ""
echo "********************************************"
echo " Welcome to CWP (CentOS WebPanel) server"
echo "********************************************"
echo ""
echo "CWP Wiki: http://wiki.centos-webpanel.com"
echo "CWP Forum: http://forum.centos-webpanel.com"
echo "CWP Support: http://centos-webpanel.com/support-services"
echo ""
w
echo ""
EOF

# disk quota on /home (or /)
if [ -e "/usr/sbin/repquota" ] && ! grep -qi quota /etc/fstab; then
    step "Setting up the disk quota"
    cp /etc/fstab /etc/fstab.backup
    if grep -qE '[[:space:]]/home[[:space:]]' /etc/fstab; then MNT=/home; else MNT=/; fi
    if [ "$MNT" = "/" ]; then
        LN=$(awk '$2=="/"{print NR; exit}' /etc/fstab)
    else
        LN=$(awk '$2=="/home"{print NR; exit}' /etc/fstab)
    fi
    FSTYPE=$(awk -v m="$MNT" '$2==m{print $3; exit}' /etc/fstab)
    if [ -n "$LN" ]; then
        # add usrquota,grpquota to the mount options of that line
        awk -v ln="$LN" 'NR==ln{ $4 = $4 ",usrquota,grpquota" } {print}' OFS='\t' /etc/fstab > /etc/fstab.cwp && cat /etc/fstab.cwp > /etc/fstab && rm -f /etc/fstab.cwp
        if [ "$FSTYPE" = "xfs" ]; then
            # XFS quota is enabled at mount time only: for / it needs rootflags on the kernel command line + reboot
            if [ "$MNT" = "/" ] && command -v grubby >/dev/null 2>&1; then
                grubby --update-kernel=ALL --args="rootflags=usrquota,grpquota" >> "$LOG" 2>&1
            fi
            mount -o remount "$MNT" >> "$LOG" 2>&1
        else
            if mount -o remount "$MNT" >> "$LOG" 2>&1; then
                quotacheck -cugm "$MNT" >> "$LOG" 2>&1
                quotaon "$MNT" >> "$LOG" 2>&1
            else
                echo "Remount of $MNT with quota failed, /etc/fstab restored (add usrquota,grpquota manually)" | tee -a "$LOG"
                cp /etc/fstab.backup /etc/fstab
            fi
        fi
        echo "$MNT" > /usr/local/cwp/.conf/quota_part.conf
    fi
fi

# services
systemctl daemon-reload
for s in httpd cwpsrv mariadb postfix dovecot named pure-ftpd; do run systemctl enable "$s"; done
/usr/bin/chattr +i /usr/local/cwpsrv/htdocs/admin 2>/dev/null
chown -R cwpsvc:cwpsvc /usr/local/cwpsrv/var/services

step "Running the CWP update (first configuration)"
chmod +x /scripts/cwp_api
run sh /scripts/update_cwp
run sh /scripts/cwp_set_memory_limit
# the remote API (port 2304: billing, CWP->CWP migration source) - the cwpsrv package no longer ships its config;
# port 2304 stays closed in CSF until the admin opens it, and the API answers only to keys made in API Manager
[ -e /usr/local/cwpsrv/conf.d/api.conf ] || run sh /scripts/install_api
mysql -e "DROP USER IF EXISTS 'Any'@'%'; DROP USER IF EXISTS 'Any'@'localhost';" >> "$LOG" 2>&1

step "Starting the services"
# vhosts of the server host name (HTTPS on the host name showed another site's certificate): cron.php writes them too,
# but only at its first nightly run - a new server went up to a day without them
/usr/local/cwp/php71/bin/php -r 'chdir("/usr/local/cwpsrv/htdocs/resources/admin/include"); include "db_conn.php"; include "functions.php"; if (function_exists("WebServers_ApacheHostname") && !file_exists("/usr/local/apache/conf.d/hostname.conf")) { WebServers_ApacheHostname(); }' >> "$LOG" 2>&1
if [ -f /usr/local/apache/conf.d/hostname.conf ] && ! /usr/local/apache/bin/apachectl -t >> "$LOG" 2>&1; then
    rm -f /usr/local/apache/conf.d/hostname.conf /usr/local/apache/conf.d/hostname-ssl.conf
fi
run sh /scripts/restart_cwpsrv
for s in httpd postfix dovecot named pure-ftpd; do run systemctl restart "$s"; done
run csf -e
run csf -r

########################################################################
# PHP-FPM versions (optional, after the panel is ready): ready-made RPMs from the CWP repo (seconds), a version
# without an RPM for this EL is built from source as before
########################################################################
if [ -n "$phpfpm" ]; then
    rpm_list=""; build_rest=""
    for v in ${phpfpm//,/ }; do rpm_list="$rpm_list cwp-php-fpm${v//./}"; done
    step "Installing PHP-FPM $phpfpm"
    # PKG_OPTS skips a missing name: a version without a package for this EL is built from source below
    run $PKG $PKG_OPTS --enablerepo=epel install $rpm_list
    for v in ${phpfpm//,/ }; do
        if [ -x /opt/alt/php-fpm${v//./}/usr/sbin/php-fpm ]; then
            echo "  PHP-FPM $v: $(/opt/alt/php-fpm${v//./}/usr/bin/php -n -r 'echo PHP_VERSION;') installed" | tee -a "$LOG"
        else
            build_rest="${build_rest:+$build_rest,}$v"
        fi
    done
    phpfpm=$build_rest
fi
if [ -n "$phpfpm" ]; then
    mkdir -p /usr/local/cwp/.conf/php-fpm_conf/
    SEL="$CDN/php/selector/el$CDNEL/"
    CONFDIR="/usr/local/cwpsrv/htdocs/resources/conf/el$EL/php-fpm_selector"
    BUILD_LIST=""
    for v in ${phpfpm//,/ }; do
        if [ ! -e "$CONFDIR/$v.ini" ]; then
            echo "PHP-FPM $v is not available for EL$EL, skipped" | tee -a "$LOG"
            continue
        fi
        # latest patch version for this branch (first entry in versions.ini), build options = the panel defaults
        full=$(awk -v s="[$v]" '$0==s{f=1;next} /^\[/{f=0} f&&/^version\[\]=/{sub(/^version\[\]=/,"");print;exit}' "$CONFDIR/versions.ini")
        /usr/local/cwp/php71/bin/php -n -r '
            $v = $argv[1]; $dir = $argv[2]; $id = str_replace(".", "", $v);
            $ini = parse_ini_file("$dir/$v.ini", true); $opt = "./configure "; $ext = ""; $pre = "";
            foreach ($ini as $k => $o) {
                if (empty($o["default"])) continue;
                if (!empty($o["option"])) $opt .= " " . $o["option"];
                if (!empty($o["script"])) $ext .= "sh $dir/external_modules/$v/" . $o["script"] . "; ";
                if (!empty($o["pre-script"])) $pre .= "sh $dir/pre_run/$v/" . $o["pre-script"] . "; ";
            }
            if (strpos($opt, "--prefix=/opt/alt/php-fpm$id/usr") === false) exit(1);
            file_put_contents("/usr/local/cwp/.conf/php-fpm_conf/php$id.conf", $opt);
            if ($ext !== "") file_put_contents("/usr/local/cwp/.conf/php-fpm_conf/php{$id}_external.conf", $ext);
            if ($pre !== "") file_put_contents("/usr/local/cwp/.conf/php-fpm_conf/php{$id}_pre.conf", $pre);
        ' "$v" "$CONFDIR" || { echo "PHP-FPM $v: no build options, skipped" | tee -a "$LOG"; continue; }
        wget -q "${SEL}php-fpm-$v.sh" -O "/usr/local/src/php-fpm-$v.sh" || continue
        [ -n "$full" ] && sed -i "s/^version=.*/version=$full/g" "/usr/local/src/php-fpm-$v.sh"
        BUILD_LIST="${BUILD_LIST}sh /usr/local/src/php-fpm-$v.sh;"
    done
    if [ -n "$BUILD_LIST" ]; then
        step "Building PHP-FPM $phpfpm (this takes a while, log: /var/log/php-selector-rebuild.log)"
        wget -q "${SEL}php-build.sh" -O /usr/local/src/php-build.sh
        sed -i "s|CONFIGURE_VERSIONS_TO_BUILD|$BUILD_LIST|g" /usr/local/src/php-build.sh
        sh /usr/local/src/php-build.sh > /var/log/php-selector-rebuild.log 2>&1
        for v in ${phpfpm//,/ }; do
            id=${v//./}
            if [ -x /opt/alt/php-fpm$id/usr/sbin/php-fpm ]; then echo "  PHP-FPM $v: OK" | tee -a "$LOG"; else echo "  PHP-FPM $v: FAILED (see /var/log/php-selector-rebuild.log)" | tee -a "$LOG"; fi
        done
    fi
fi

########################################################################
# PHP Selector (CGI) versions: the ready-made CWP packages (/opt/alt/phpNN), the rest built from source like the
# PHP Selector does. The build options are saved as the panel saves them, so a later rebuild uses the same options.
########################################################################
if [ -n "$phpcgi" ]; then
    mkdir -p /usr/local/cwp/.conf/php_conf/
    CONFDIR="/usr/local/cwpsrv/htdocs/resources/conf/el$EL/php_selector"
    cgi_ok=""
    for v in ${phpcgi//,/ }; do
        [ -e "$CONFDIR/$v.ini" ] || { echo "PHP-CGI $v is not available for EL$EL, skipped" | tee -a "$LOG"; continue; }
        /usr/local/cwp/php71/bin/php -n -r '
            $v = $argv[1]; $dir = $argv[2]; $id = str_replace(".", "", $v);
            $ini = parse_ini_file("$dir/$v.ini", true); $opt = "./configure "; $ext = ""; $pre = "";
            foreach ($ini as $k => $o) {
                if (empty($o["default"])) continue;
                if (!empty($o["option"])) $opt .= " " . $o["option"];
                if (!empty($o["script"])) $ext .= "sh $dir/external_modules/$v/" . $o["script"] . "; ";
                if (!empty($o["pre-script"])) $pre .= "sh $dir/pre_run/$v/" . $o["pre-script"] . "; ";
            }
            if (strpos($opt, "--prefix=/opt/alt/php$id/usr") === false) exit(1);
            file_put_contents("/usr/local/cwp/.conf/php_conf/php$id.conf", $opt);
            if ($ext !== "") file_put_contents("/usr/local/cwp/.conf/php_conf/php{$id}_external.conf", $ext);
            if ($pre !== "") file_put_contents("/usr/local/cwp/.conf/php_conf/php{$id}_pre.conf", $pre);
        ' "$v" "$CONFDIR" || { echo "PHP-CGI $v: no build options, skipped" | tee -a "$LOG"; continue; }
        cgi_ok="${cgi_ok:+$cgi_ok,}$v"
    done
    phpcgi=$cgi_ok
fi
if [ -n "$phpcgi" ]; then
    rpm_list=""; build_rest=""
    for v in ${phpcgi//,/ }; do rpm_list="$rpm_list cwp-php-cgi${v//./}"; done
    step "Installing PHP-CGI (PHP Selector) $phpcgi"
    run $PKG $PKG_OPTS --enablerepo=epel install $rpm_list
    for v in ${phpcgi//,/ }; do
        if [ -x /opt/alt/php${v//./}/usr/bin/php ]; then
            echo "  PHP-CGI $v: $(/opt/alt/php${v//./}/usr/bin/php -n -r 'echo PHP_VERSION;') installed" | tee -a "$LOG"
        else
            build_rest="${build_rest:+$build_rest,}$v"
        fi
    done
    phpcgi=$build_rest
fi
if [ -n "$phpcgi" ]; then
    SEL="$CDN/php/selector/el$CDNEL/"
    CONFDIR="/usr/local/cwpsrv/htdocs/resources/conf/el$EL/php_selector"
    BUILD_LIST=""
    for v in ${phpcgi//,/ }; do
        full=$(awk -v s="[$v]" '$0==s{f=1;next} /^\[/{f=0} f&&/^version\[\]=/{sub(/^version\[\]=/,"");print;exit}' "$CONFDIR/versions.ini")
        wget -q "${SEL}php-$v.sh" -O "/usr/local/src/php-$v.sh" || continue
        [ -n "$full" ] && sed -i "s/^version=.*/version=$full/g" "/usr/local/src/php-$v.sh"
        BUILD_LIST="${BUILD_LIST}sh /usr/local/src/php-$v.sh;"
    done
    if [ -n "$BUILD_LIST" ]; then
        step "Building PHP-CGI $phpcgi (this takes a while, log: /var/log/php-selector-rebuild.log)"
        wget -q "${SEL}php-build.sh" -O /usr/local/src/php-build.sh
        sed -i "s|CONFIGURE_VERSIONS_TO_BUILD|$BUILD_LIST|g" /usr/local/src/php-build.sh
        sh /usr/local/src/php-build.sh >> /var/log/php-selector-rebuild.log 2>&1
        for v in ${phpcgi//,/ }; do
            id=${v//./}
            if [ -x /opt/alt/php$id/usr/bin/php ]; then echo "  PHP-CGI $v: OK" | tee -a "$LOG"; else echo "  PHP-CGI $v: FAILED (see /var/log/php-selector-rebuild.log)" | tee -a "$LOG"; fi
        done
    fi
fi
# the user panel lists the installed PHP versions from this cache (otherwise new versions show up after the nightly cron)
/usr/local/cwp/php71/bin/php /usr/local/cwpsrv/htdocs/resources/admin/include/php_versions_cache.php >> "$LOG" 2>&1

########################################################################
# Check
########################################################################
step "Checking the installation"
problems=""
for s in httpd cwpsrv mariadb postfix dovecot named; do
    systemctl is-active --quiet "$s" || problems="$problems $s"
done
code=$(curl -sk -m 20 -o /dev/null -w '%{http_code}' https://127.0.0.1:2031/)
case "$code" in 200|301|302) ;; *) problems="$problems panel(https:2031=$code)" ;; esac
rc_ok=$(curl -sk -m 20 https://127.0.0.1:2096/ | grep -c rcmloginuser)
[ "$rc_ok" -ge 1 ] || problems="$problems roundcube"
rm -rf "$DL"
yum clean all >/dev/null 2>&1

took=$(( $(date +%s) - START ))
[ -t 1 ] && clear
echo "#############################"
echo "#      CWP Installed        #"
echo "#############################"
echo ""
echo "Installed in $((took / 60)) min $((took % 60)) s"
[ -n "$problems" ] && echo "WARNING - not working:$problems (see $LOG)"
echo ""
echo "Go to CentOS WebPanel Admin GUI at http://SERVER_IP:2030/"
echo ""
echo "http://${pubip}:2030"
echo "SSL: https://${pubip}:2031"
echo -e "---------------------"
echo "Username: root"
echo "Password: ssh server root password"
echo "MySQL root Password: $password"
echo
echo "#########################################################"
echo "SSL Cert name (hostname): ${cnf_hostname}"
echo "SSL Cert file location /etc/pki/tls/ private|certs"
echo "#########################################################"
echo
echo "Visit for help: www.centos-webpanel.com"

if [ "$restart" = "yes" ]; then
    echo "restarting server...."
    shutdown -r now
else
    echo "Please reboot the server!"
    echo "Reboot command: shutdown -r now"
fi
