#!/usr/bin/env bash
# Copyright (C) 2013 - 2024 Teddysun <i@teddysun.com>
#
# This file is part of the LAMP script.
#
# LAMP is a powerful bash script for the installation of
# Apache + PHP + MySQL/MariaDB and so on.
# You can install Apache + PHP + MySQL/MariaDB in an very easy way.
# Just need to input numbers to choose what you want to install before installation.
# And all things will be done in a few minutes.
#
# System Required:  CentOS 7+ / Debian 9+ / Ubuntu 18+
# Description:  Create, Delete, List Apache Virtual Host
# Website:  https://lamp.sh
# Github:   https://github.com/teddysun/lamp

PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH

apache_location=/usr/local/apache
mysql_location=/usr/local/mysql
mariadb_location=/usr/local/mariadb
web_root_dir=/data/www/default

_red() {
    printf '\033[1;31;31m%b\033[0m' "$1"
    printf "\n"
}

_green() {
    printf '\033[1;31;32m%b\033[0m' "$1"
}

_yellow() {
    printf '\033[1;31;33m%b\033[0m' "$1"
    printf "\n"
}

_printargs() {
    printf -- "%s" "[$(date)] "
    printf -- "%s" "$1"
    printf "\n"
}

_info() {
    _printargs "$@"
}

_error() {
    printf -- "%s" "[$(date)] "
    _red "$1"
    exit 1
}

vhost() {
    case "$1" in
    add)
        vhost_add
        ;;
    del)
        vhost_del
        ;;
    list)
        vhost_list
        ;;
    *)
        _error "action ${action} not found"
        ;;
    esac
}

db_name() {
    if [ -d "${mysql_location}" ]; then
        echo "MySQL"
    elif [ -d "${mariadb_location}" ]; then
        echo "MariaDB"
    else
        echo "MySQL"
    fi
}

set_apache_allow_syntax() {
    if [ -s /usr/sbin/httpd ]; then
        if /usr/sbin/httpd -v | grep -q "Apache/2.4"; then
            allow_from_all="Require all granted"
        else
            _error "Error: Can not get Apache version..."
        fi
    else
        _error "Error: Can not find Apache, may be not installed, please check it and try again"
    fi
}

check_email() {
    regex="^[a-z0-9!#\$%&'*+/=?^_\`{|}~-]+(\.[a-z0-9!#$%&'*+/=?^_\`{|}~-]+)*@([a-z0-9]([a-z0-9-]*[a-z0-9])?\.)+[a-z0-9]([a-z0-9-]*[a-z0-9])?\$"
    if [[ ${1} =~ ${regex} ]]; then
        return 0
    else
        return 1
    fi
}

vhost_add() {
    set_apache_allow_syntax
    while true; do
        read -p "[$(date)] Please enter server names (for example: lamp.sh www.lamp.sh): " server_names
        if [ -z "${server_names}" ]; then
            _info "$(_red "Error: server name can not be empty")"
        else
            for i in ${server_names}; do
                if apache_vhost_is_exist ${i}; then
                    _info "Error: virtual host [$(_red ${i})] is existed, please check it and try again"
                    break
                fi
                break 2
            done
        fi
    done

    main_domain="${server_names%% *}"
    default_root="/data/www/${main_domain}"

    while true; do
        read -p "[$(date)] Please enter website root directory (default: $default_root): " website_root
        website_root=${website_root:=$default_root}
        if echo "${website_root}" | grep -q "^/"; then
            break
        else
            _info "$(_red "Error: ${website_root} is not a correct directory")"
        fi
    done

    _info "Website root directory: ${website_root}"
    php_admin_value=""
    if [ -s /usr/bin/php ]; then
        php_admin_value="php_admin_value open_basedir ${website_root}:/tmp:/var/tmp:/proc"
        if [ -d "${web_root_dir}/phpmyadmin" ]; then
            php_admin_value="${php_admin_value}:${web_root_dir}/phpmyadmin"
        fi
        if [ -d "${web_root_dir}/kod" ]; then
            php_admin_value="${php_admin_value}:${web_root_dir}/kod"
        fi
    fi

    while true; do
        read -p "[$(date)] Please enter Administrator Email address: " email
        if [ -z "${email}" ]; then
            _info "$(_red "Error: Administrator Email address can not be empty")"
        elif check_email ${email}; then
            _info "Administrator Email address: ${email}"
            break
        else
            _info "$(_red "Error: ${email} is not a correct email address")"
        fi
    done

    while true; do
        read -p "[$(date)] Do you want to create a database and user with same name? [y/n]: " create
        case "${create}" in
        y | Y)
            if [ ! "$(command -v "mysql")" ]; then
                _error "Error: $(db_name) is not installed, please check it and try again"
            fi
            mysql_count=$(ps -ef | grep -v grep | grep -c "mysqld")
            if [ ${mysql_count} -eq 0 ]; then
                _info "$(_yellow "Warning: $(db_name) looks like not running, Try to starting $(db_name)...")"
                /etc/init.d/mysqld start >/dev/null 2>&1
                if [ $? -ne 0 ]; then
                    _error "Error: $(db_name) starting failed"
                fi
            fi
            read -p "[$(date)] Please enter your $(db_name) root password: " mysqlroot_passwd
            mysql -uroot -p${mysqlroot_passwd} >/dev/null 2>&1 <<EOF
exit
EOF
            if [ $? -ne 0 ]; then
                _error "Error: $(db_name) root password is incorrect, Please check it and try again"
            fi
            read -p "[$(date)] Please enter the database name: " dbname
            [ -z "${dbname}" ] && _error "Error: database name can not be empty"
            read -p "[$(date)] Please set the password for user [${dbname}]: " mysqlpwd
            [ -z "${mysqlpwd}" ] && _error "Error: user password can not be empty"
            create="y"
            break
            ;;
        n | N)
            _info "You chosen do not create a database"
            create="n"
            break
            ;;
        *)
            _info "Please enter only y or n"
            ;;
        esac
    done

    mkdir -p /data/wwwlog/${main_domain} ${website_root}

    cat >${apache_location}/conf/vhost/${main_domain}.conf <<EOF
<VirtualHost *:80>
    ServerAdmin ${email}
    ${php_admin_value}
    ServerName ${main_domain}
    ServerAlias ${server_names}
    DocumentRoot ${website_root}
    <Directory ${website_root}>
        SetOutputFilter DEFLATE
        Options FollowSymLinks
        AllowOverride All
        Order Deny,Allow
        ${allow_from_all}
        DirectoryIndex index.php index.html index.htm
    </Directory>
    ErrorLog /data/wwwlog/${main_domain}/error.log
    CustomLog /data/wwwlog/${main_domain}/access.log combined
</VirtualHost>
EOF

    _info "Created virtual host [$(_green ${main_domain})] success"
    _info "Website root directory is: $(_green ${website_root})"

    if [ "$create" = "y" ]; then
        mysql -uroot -p${mysqlroot_passwd} <<EOF
CREATE DATABASE IF NOT EXISTS \`${dbname}\` CHARACTER SET utf8 COLLATE utf8_general_ci;
GRANT ALL PRIVILEGES ON \`${dbname}\` . * TO '${dbname}'@'localhost' IDENTIFIED BY '${mysqlpwd}';
GRANT ALL PRIVILEGES ON \`${dbname}\` . * TO '${dbname}'@'127.0.0.1' IDENTIFIED BY '${mysqlpwd}';
FLUSH PRIVILEGES;
EOF
        _info "Created database [$(_green ${dbname})] and user [$(_green ${dbname})] success"
    fi

    _info "Reloading the Apache config file..."
    if ${apache_location}/bin/apachectl -t >/dev/null 2>&1; then
        /etc/init.d/httpd restart
        _info "Reload success"
    else
        _error "Error: Reload failed. Apache config file had an error, please fixed it and try again"
    fi

    read -p "[$(date)] Do you want to add a SSL certificate? [y/n]: " create_ssl
    if [ "${create_ssl}" = "y" ] || [ "${create_ssl}" = "Y" ]; then
        add_ssl_memu
        add_ssl_cert
        _info "Reloading the Apache config file..."
        if ${apache_location}/bin/apachectl -t >/dev/null 2>&1; then
            /etc/init.d/httpd restart
            _info "Reload success"
        else
            _error "Error: Reload failed. Apache config file had an error, please fixed it and try again"
        fi
    else
        _info "You chosen do not add a SSL certificate"
    fi
    chown -R apache:apache /data/wwwlog/${main_domain} ${website_root}
    _info "All done"
}

add_ssl_memu() {
    _info "$(_green 1). Use your own SSL Certificate and Key"
    _info "$(_green 2). Use Let's Encrypt CA to create SSL Certificate and Key"
    _info "$(_green 3). Use Buypass.com CA to create SSL Certificate and Key"

    while true; do
        read -p "[$(date)] Please enter 1 or 2 or 3: " ssl_pick
        if [ "${ssl_pick}" = "1" ]; then
            while true; do
                read -p "[$(date)] Please enter full path to SSL Certificate file: " ssl_certificate
                if [ -z "${ssl_certificate}" ]; then
                    _info "$(_red "Error: SSL Certificate file can not be empty")"
                elif [ -s "${ssl_certificate}" ]; then
                    break
                else
                    _info "$(_red "Error: ${ssl_certificate} does not exist or is not a file")"
                fi
            done

            while true; do
                read -p "[$(date)] Please enter full path to SSL Certificate Key file: " ssl_certificate_key
                if [ -z "${ssl_certificate_key}" ]; then
                    _info "$(_red "Error: SSL Certificate Key file can not be empty")"
                elif [ -s "${ssl_certificate_key}" ]; then
                    break
                else
                    _info "$(_red "Error: ${ssl_certificate_key} does not exist or is not a file")"
                fi
            done
            break
        elif [ "${ssl_pick}" = "2" ]; then
            _info "You chosen Let's Encrypt CA, and it will be processed automatically"
            break
        elif [ "${ssl_pick}" = "3" ]; then
            _info "You chosen Buypass.com CA, and it will be processed automatically"
            break
        else
            _info "$(_red "Error: Please only enter 1 or 2 or 3")"
        fi
    done

    read -p "[$(date)] Do you want force redirection from HTTP to HTTPS? [y/n]: " force_ssl
    if [ "${force_ssl}" = "y" ] || [ "${force_ssl}" = "Y" ]; then
        _info "You chosen force redirection from HTTP to HTTPS, and it will be processed automatically"
    else
        _info "Do not force redirection from HTTP to HTTPS"
    fi
}

create_ssl_config() {
    sed -i 's@#Include conf/extra/httpd-ssl.conf@Include conf/extra/httpd-ssl.conf@g' ${apache_location}/conf/httpd.conf
    cat >>${apache_location}/conf/vhost/${main_domain}.conf <<EOF
<VirtualHost *:443>
    ServerAdmin ${email}
    ${php_admin_value}
    DocumentRoot ${website_root}
    ServerName ${main_domain}
    ServerAlias ${server_names}
    SSLEngine on
    SSLCertificateFile ${ssl_certificate}
    SSLCertificateKeyFile ${ssl_certificate_key}
    <Directory ${website_root}>
        SetOutputFilter DEFLATE
        Options FollowSymLinks
        AllowOverride All
        Order Deny,Allow
        ${allow_from_all}
        DirectoryIndex index.php index.html index.htm
    </Directory>
    Header always set Strict-Transport-Security "max-age=31536000; preload"
    Header always edit Set-Cookie ^(.*)$ $1;HttpOnly;Secure
    Header always set X-Content-Type-Options nosniff
    Header always set X-Frame-Options SAMEORIGIN
    ErrorLog  /data/wwwlog/${main_domain}/ssl_error.log
    CustomLog  /data/wwwlog/${main_domain}/ssl_access.log combined
</VirtualHost>
EOF
}

create_ssl_htaccess() {
    cat >${website_root}/.htaccess <<EOF
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [R,L]
</IfModule>
EOF
}

add_letsencrypt() {
    if [ -d "${apache_location}/conf/ssl/${main_domain}" ]; then
        _info "Removing exist domain certificate..."
        rm -rf ${apache_location}/conf/ssl/${main_domain}
    fi
    _info "Starting create Let's Encrypt SSL Certificate..."
    . /usr/local/acme.sh/acme.sh.env
    /usr/local/acme.sh/acme.sh --issue --server letsencrypt ${letsdomain} -w ${website_root}
    if [ $? -eq 0 ]; then
        if [ -s "${apache_location}/conf/ssl/${main_domain}/fullchain.cer" ]; then
            ssl_certificate="${apache_location}/conf/ssl/${main_domain}/fullchain.cer"
            ssl_certificate_key="${apache_location}/conf/ssl/${main_domain}/${main_domain}.key"
        fi
        if [ -s "${apache_location}/conf/ssl/${main_domain}_ecc/fullchain.cer" ]; then
            ssl_certificate="${apache_location}/conf/ssl/${main_domain}_ecc/fullchain.cer"
            ssl_certificate_key="${apache_location}/conf/ssl/${main_domain}_ecc/${main_domain}.key"
        fi
        _info "Created Let's Encrypt SSL Certificate success"
    else
        _error "Error: Create Let's Encrypt SSL Certificate failed"
    fi
}

add_buypass() {
    if [ -d "${apache_location}/conf/ssl/${main_domain}" ]; then
        _info "Removing exist domain certificate..."
        rm -rf ${apache_location}/conf/ssl/${main_domain}
    fi
    _info "Starting create Buypass.com SSL Certificate..."
    . /usr/local/acme.sh/acme.sh.env
    /usr/local/acme.sh/acme.sh -m ${email} --issue --server buypass ${letsdomain} -w ${website_root} --days 170
    if [ $? -eq 0 ]; then
        if [ -s "${apache_location}/conf/ssl/${main_domain}/fullchain.cer" ]; then
            ssl_certificate="${apache_location}/conf/ssl/${main_domain}/fullchain.cer"
            ssl_certificate_key="${apache_location}/conf/ssl/${main_domain}/${main_domain}.key"
        fi
        if [ -s "${apache_location}/conf/ssl/${main_domain}_ecc/fullchain.cer" ]; then
            ssl_certificate="${apache_location}/conf/ssl/${main_domain}_ecc/fullchain.cer"
            ssl_certificate_key="${apache_location}/conf/ssl/${main_domain}_ecc/${main_domain}.key"
        fi
        _info "Created Buypass.com SSL Certificate success"
    else
        _error "Error: Create Buypass.com SSL Certificate failed"
    fi
}

install_check_acme() {
    if [ -s "/usr/local/acme.sh/acme.sh" ]; then
        _info "/usr/local/acme.sh/acme.sh [found]"
    else
        wget --no-check-certificate -qO- https://github.com/acmesh-official/acme.sh/tarball/master | tar xz
        cd acmesh-* || _error "Error: Download acme.sh failed, Please check it and try again"
        ./acme.sh --install --log --home /usr/local/acme.sh --certhome /usr/local/apache/conf/ssl
        cd .. && rm -rf acmesh-*
        sed -i 's/cat "\$CERT_PATH"$/#cat "\$CERT_PATH"/g' /usr/local/acme.sh/acme.sh
        cat >/usr/local/acme.sh/upgrade.sh <<EOF
#!/bin/bash

. /usr/local/acme.sh/acme.sh.env
/usr/local/acme.sh/acme.sh --upgrade
sed -i 's/cat "\\\$CERT_PATH"\$/#cat "\\\$CERT_PATH"/g' /usr/local/acme.sh/acme.sh
EOF
        chmod +x /usr/local/acme.sh/upgrade.sh
        if crontab -l | grep -q "/usr/local/acme.sh/upgrade.sh"; then
            _info "acme.sh upgrade crontab rule is existed"
        else
            (crontab -l ; echo '0 3 */7 * * /usr/local/acme.sh/upgrade.sh') | crontab -
            _info "create cron job for automatic upgrade acme.sh success"
        fi
    fi
    [ ! -d "${apache_location}/conf/ssl" ] && mkdir -p "${apache_location}/conf/ssl"
}

add_ssl_cert() {
    if [ -z "${email}" ] || [ -z "${website_root}" ]; then
        _error "Error: parameters must be specified"
    fi
    if [ ! -d "${website_root}" ]; then
        _error "Error: ${website_root} does not exist or is not a directory"
    fi
    letsdomain=""
    for i in ${server_names}; do
        letsdomain="${letsdomain} -d ${i}"
    done

    install_check_acme

    if [ "${ssl_pick}" = "2" ]; then
        add_letsencrypt
    elif [ "${ssl_pick}" = "3" ]; then
        add_buypass
    fi

    create_ssl_config
    [ "${force_ssl}" = "y" -o "${force_ssl}" = "Y" ] && create_ssl_htaccess
    _info "Added SSL certificate for virtual host [$(_green ${main_domain})] success"
}

vhost_list() {
    if [ $(ls ${apache_location}/conf/vhost/ | grep ".conf$" | grep -v "none" | grep -v "default" | wc -l) -gt 0 ]; then
        printf "%s\n" "ServerName"
        printf "%s\n" "----------"
        ls ${apache_location}/conf/vhost/ | grep ".conf$" | grep -v "none" | grep -v "default" | sed 's/.conf//g'
    else
        echo "Apache virtual host not found. You can create a new Apache virtual host with command: $(_green "lamp add")"
    fi
}

vhost_del() {
    read -p "[$(date)] Please enter a domain you want to delete it (for example: www.lamp.sh): " domain
    [ -z "${domain}" ] && _error "Error: domain can not be empty"
    if ! apache_vhost_is_exist "${domain}"; then
        _error "Error: virtual host [${domain}] not found"
    else
        local dir=$(grep -w "DocumentRoot" ${apache_location}/conf/vhost/${domain}.conf | head -1 | awk '{print $2}')
        rm -f ${apache_location}/conf/vhost/${domain}.conf
        _info "Virtual host [$(_green ${domain})] has been deleted"
        _info "But website root directory [$(_green ${dir})] still exists"
        _info "You can delete it manually if necessary"
        _info "Reloading the Apache config file..."
        if ${apache_location}/bin/apachectl -t >/dev/null 2>&1; then
            /etc/init.d/httpd restart
            _info "Reload success"
        else
            _error "Error: Reload failed. Apache config file had an error, please fixed it and try again"
        fi
    fi
}

apache_vhost_is_exist() {
    if [ -f "${apache_location}/conf/vhost/$1.conf" ]; then
        return 0
    else
        return 1
    fi
}

display_usage() {
    printf "
Usage  : $(basename $0) [Options]
Options:
        add      Create a new Apache virtual host
        del      Delete a Apache virtual host
        list     List all of Apache virtual hosts
        version  Print version and exit

"
}

display_version() {
    echo "Version: $(_green 20231115)"
}

#Run it
[ ${EUID} -ne 0 ] && _red "Error: This script must be run as root" && exit 1
[ $# -ne 1 ] && display_usage && exit 1
action=$1
case "${action}" in
add)
    vhost ${action}
    ;;
del)
    vhost ${action}
    ;;
list)
    vhost ${action}
    ;;
version)
    display_version
    ;;
*)
    display_usage
    ;;
esac
