#!/bin/bash

#############################################################
# Author: Taryel Hlontsi, 2022
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>
#############################################################


#############################################################
#           SETUP SECTION, use "sudo su -- root"
#############################################################

# if set to true, ssh login only with ssh key which must(!) be configured upfront
NO_PASSWORD_SSH=true

# only if new user is required (root login won't be allowed anyway)
OS_USER="ubuntu"
OS_USER_PASSWORD="password_goes_here"

# change the IP. If domain is used then set it here as the server name
OS_SERVER_NAME="nextcloud"
OS_IP="127.0.0.1"

# database setup for the nexcloud web app
NC_DB="nextcloud"
NC_DB_USER="nextcloud"
NC_DB_PASSWORD="qrsE1001^00000"

# account to log on to the nexcloud web app
NC_ADMIN_USER="tar"
NC_ADMIN_PASSWORD="password_goes_here"

# link for nextcloud, country is for ssl certificate and fixing some warnings
NC_DOWNLOAD_URL="https://download.nextcloud.com/server/releases/latest.zip"
NC_COUNTRY_CODE="PL"

# optional, but removes some warnings in nextcloud
PHP_TIMEZONE="Europe/Warsaw"

# may be left as is
LOG_DIR="/home"
LOG="${LOG_DIR}/log.txt"

#############################################################
#          HELPER FUNCTIONS
#############################################################
trace() {
    PURPLE='\033[1;35m'
    NC='\033[0m'
    echo -e "${PURPLE}$1${NC}"
    echo -e "TRACE:\t$1" >> $LOG
}

info() {
    GREEN='\033[1;32m'
    NC='\033[0m'
    echo -e "${GREEN}$1${NC}"
    echo -e "INFO:\t$1" >> $LOG
}

warn() {
    YELLOW='\033[1;33m'
    NC='\033[0m'
    echo -e "${YELLOW}$1${NC}"
    echo -e "WARN:\t$1" >> $LOG
}

error() {
    RED='\033[1;31m'
    NC='\033[0m'
    echo -e "${RED}$1${NC}"
    echo -e "ERROR:\t$1" >> $LOG
}

not_set() {
    count=$(grep -Pcx "$1" "$2")
    code=$?
    if (( $code > 0 )) || (( $count == 0 )); then
	true
    else
	false
    fi
}

not_exist() {
    if test ! -f "$2"; then
	warn "file does not exist: $2"
	true
    else
	count=$(grep -Pc "$1" "$2")
	code=$?
	if (( $code > 0 )) || (( $count == 0 )); then
	    true
	else
	    false
	fi
    fi
}

configure() {
    if not_exist "$1" "$3"; then
	warn "setting \"$1\" is not in the file, thus won't be changed"
	false
    else
	if not_set "$2" "$3"; then	
	    sed -i "s~.*${1}.*~${2}~gi" "$3" &&
		trace "\"$1\" set to \"$2\"" &&
		true
	else
	    warn "\"$2\" already set in \"$3\""
	    false
	fi
    fi    
}

configured() {
    count=$(grep -Pcx "$1" "$2")
    if (( $? > 0 )) || (( $count == 0 )); then
	false
    else
	true
    fi    
}

insert_before() {
    if not_set "$2" "$3"; then
	sed -i "\~.*${1}.*~i ${2}" "$3" &&
	    trace "\"$1\" set to \"$2\"" &&
	    true
    else
	warn "\"$2\" already set in \"$3\""
	false
    fi
}

#############################################################
#          1 - ADD USER
#############################################################
add_user() {
    info "STEP 1 Adding a user"

    adduser --disabled-password --gecos "" $OS_USER
    chpasswd <<<"${OS_USER}:${OS_USER_PASSWORD}"
    usermod -aG sudo $OS_USER

    info "STEP 1 done"
}

#############################################################
#          2 - CHANGE SERVER NAME
#############################################################
change_servername() {
    info "STEP 2 Changing server name"

    echo $OS_SERVER_NAME > /etc/hostname
    hostentry="127.0.1.1\t${OS_SERVER_NAME}"
    if not_set "$hostentry" "/etc/hostname"; then
	echo -e "$hostentry" >> /etc/hosts
    else
	trace "\"$hostentry\" already set in \"/etc/hosts\""
    fi

    info "STEP 2 done"
}

#############################################################
#          3 - INSTALL MARIADB
#############################################################
install_mariadb() {
    info "STEP 3 Installing Maria DB"

    apt install -y mariadb-server
    systemctl --quiet is-active mariadb || systemctl start mariadb
    systemctl --quiet is-enabled mariadb || systemctl enable mariadb

    mysql -u root <<EOF
CREATE DATABASE IF NOT EXISTS ${NC_DB};
GRANT ALL PRIVILEGES ON ${NC_DB}.* TO '${NC_DB_USER}'@'localhost' IDENTIFIED BY '${NC_DB_PASSWORD}';
DROP DATABASE IF EXISTS test;
DELETE FROM mysql.db WHERE Db='test' OR Db='test\\_%';
DELETE FROM mysql.user WHERE User='';
DELETE FROM mysql.user WHERE User='root' AND Host NOT IN ('localhost', '127.0.0.1', '::1');
ALTER USER 'root'@'localhost' IDENTIFIED BY '${NC_DB_PASSWORD}';
FLUSH PRIVILEGES;
EOF

    test $? -eq 0 || error "Something went bananas. Maybe DB has already been set up"

    mysql -u root -p${NC_DB_PASSWORD} -h localhost <<EOF
SHOW DATABASES;
SELECT User FROM mysql.user;
EOF

    info "STEP 3 done"
}

#############################################################
#          4 - INSTALL PHP AND APACHE WEB SERVER
#############################################################
install_php_and_apache() {
    info "STEP 4 Installing PHP and Apache2"

    apt install -y apache2 php php-apcu php-bcmath php-cli php-common php-curl php-gd php-gmp php-imagick php-intl php-mbstring php-mysql php-zip php-xml
    systemctl --quiet is-active apache2 || systemctl start apache2
    systemctl --quiet is-enabled apache2 || systemctl enable apache2
    
    apt -y install libmagickcore-6.q16-6-extra

    info "STEP 4 done"
}

#############################################################
#          5 - SET UP PHP
#############################################################
setup_php() {
    info "STEP 5 Setting up PHP"

    phpenmod bcmath gmp imagick intl

    PHP_VERSION=$(php -r 'echo PHP_MAJOR_VERSION.".".PHP_MINOR_VERSION;')
    PHP_INI="/etc/php/${PHP_VERSION}/apache2/php.ini"

    trace "PHP version detected: ${PHP_VERSION}"
    trace "PHP timezone: ${PHP_TIMEZONE}"
    trace "configuring ${PHP_INI}"

    configure 'memory_limit =' 'memory_limit = 512M' $PHP_INI
    configure 'upload_max_filesize =' 'upload_max_filesize = 256M' $PHP_INI
    configure 'max_execution_time =' 'max_execution_time = 360' $PHP_INI
    configure 'post_max_size =' 'post_max_size = 256M' $PHP_INI
    configure 'date.timezone =' "date.timezone = ${PHP_TIMEZONE}" $PHP_INI
    configure 'opcache.enable=' 'opcache.enable=1' $PHP_INI
    configure 'opcache.interned_strings_buffer=' 'opcache.interned_strings_buffer=8' $PHP_INI
    configure 'opcache.max_accelerated_files=' 'opcache.max_accelerated_files=10000' $PHP_INI
    configure 'opcache.memory_consumption=' 'opcache.memory_consumption=128' $PHP_INI
    configure 'opcache.save_comments=' 'opcache.save_comments=1' $PHP_INI
    configure 'opcache.revalidate_freq=' 'opcache.revalidate_freq=3' $PHP_INI

    systemctl restart apache2

    info "STEP 5 done"
}

#############################################################
#          6 - DOWNLOAD NEXTCLOUD
#############################################################
download_nextcloud() {
    info "STEP 6 Downloading and installing NEXTCLOUD"

    apt install -y unzip
    apt install -y wget

    trace "Downloading nextcloud from ${NC_DOWNLOAD_URL}"
    wget --continue $NC_DOWNLOAD_URL --output-document nc.zip
    trace "Extracting zip archive..."
    unzip -qq nc.zip
    rm nc.zip
    sudo chown -R www-data:www-data nextcloud/

    trace "Moving extracted nextcloud to /var/www/"
    mv nextcloud/ /var/www/${OS_SERVER_NAME}

    trace "Disabling default apache site 000-default.conf"
    a2dissite 000-default.conf

    trace "Creating a new site config for apache"
    CFG_NAME="/etc/apache2/sites-available/${OS_SERVER_NAME}.conf"
    cat <<EOF > ${CFG_NAME}
<VirtualHost *:80>
    DocumentRoot "/var/www/${OS_SERVER_NAME}"
    ServerName ${OS_SERVER_NAME}

    <Directory "/var/www/${OS_SERVER_NAME}/">
        Options MultiViews FollowSymlinks
        AllowOverride All
        Order allow,deny
        Allow from all
   </Directory>

   TransferLog /var/log/apache2/${OS_SERVER_NAME}_access.log
   ErrorLog /var/log/apache2/${OS_SERVER_NAME}_error.log

</VirtualHost>
EOF

    trace "Apache config created: ${CFG_NAME}. Enabling it"
    a2ensite "${OS_SERVER_NAME}.conf"
    a2enmod dir env headers mime rewrite ssl
    systemctl restart apache2

    info "STEP 6 done"
}

#############################################################
#          7 - SET UP NEXTCLOUD
#############################################################
setup_nextcloud() {
    info "STEP 7 Setting up NEXTCLOUD"

    USER='www-data'
    NC_CONFIG="/var/www/${OS_SERVER_NAME}/config/config.php"

    trace "setting up ${NC_CONFIG}"

    cd "/var/www/${OS_SERVER_NAME}/"

    sudo -u www-data php occ  maintenance:install \
	 --database "mysql" \
	 --database-name "${NC_DB}" \
	 --database-user "${NC_DB_USER}" \
	 --database-pass "${NC_DB_PASSWORD}" \
	 --database-host "localhost" \
	 --data-dir "/var/www/${OS_SERVER_NAME}/data" \
	 --admin-user "${NC_ADMIN_USER}" \
	 --admin-pass "${NC_ADMIN_PASSWORD}"

    trace "Nextcloud config creation result (0 means OK): $?"

    configure ';' "  'memcache.local' => '\\\OC\\\Memcache\\\APCu'," $NC_CONFIG && \
	echo "  'default_phone_region' => '${NC_COUNTRY_CODE}'," >> $NC_CONFIG && \
	echo ');' >> $NC_CONFIG

    if configured "\t2 => '${OS_IP}'," $NC_CONFIG; then
	warn "Trusted domains were already configured"
    else
	configure "0 => 'localhost'," "\t0 => 'localhost',\n\t1 => '${OS_SERVER_NAME}',\n\t2 => '${OS_IP}'," $NC_CONFIG
	trace "Trusted domains were set in ${NC_CONFIG}"
    fi

    sudo chmod 660 $NC_CONFIG
    trace "Changed mod  of ${NC_CONFIG}"

    trace "Setting up a cron job for cron.php"

    cat <<EOF > /etc/systemd/system/nextcloudcron.service
[Unit]
Description=Nextcloud cron.php job

[Service]
User=${USER}
ExecStart=/usr/bin/php -f /var/www/${OS_SERVER_NAME}/cron.php --define apc.enable_cli=1
KillMode=process
EOF

    cat <<EOF > /etc/systemd/system/nextcloudcron.timer
[Unit]
Description=Run Nextcloud cron.php every 5 minutes

[Timer]
OnBootSec=5min
OnUnitActiveSec=5min
Unit=nextcloudcron.service

[Install]
WantedBy=timers.target
EOF

    systemctl enable --now nextcloudcron.timer

    # alternative approach is to setup a crontab:    
    #CRONTAB="/var/spool/cron/crontabs/${USER}"
    #echo "*/5  *  *  *  * php -f /var/www/${OS_SERVER_NAME}/cron.php" >> $CRONTAB
    #sudo chmod 600 $CRONTAB
    #sudo chown $USER:$USER $CRONTAB

    # to delete the crontab:
    # crontab -r -u www-data

    info "STEP 7 done"
}

#############################################################
#          8 - INSTALL SELF-SIGNED SSL CERTIFICATE
#############################################################
use_selfsigned_ssl_certificate() {
    info "STEP 8.1 Configuring a self-signed SSL certificate"

    KEY="/etc/ssl/private/apache.key"
    CRT="/etc/ssl/certs/apache.crt"
    APACHE_SSL_CFG="/etc/apache2/sites-available/default-ssl.conf"

    openssl req -x509 \
    -nodes \
    -days 2048 \
    -newkey rsa:2048 \
    -keyout $KEY \
    -out $CRT \
    -subj "/C=${NC_COUNTRY_CODE}/ST=${NC_COUNTRY_CODE}/L=${NC_COUNTRY_CODE}/O=E Corp/OU=IT/CN=${OS_SERVER_NAME}/emailAddress=info@example.com"

    trace "SSH key created at: ${KEY}"
    trace "Certificate created at: ${CRT}"
    
    CFG_NAME="/etc/apache2/sites-available/${OS_SERVER_NAME}-ssl.conf"
    cat <<EOF > ${CFG_NAME}
<IfModule mod_ssl.c>
<VirtualHost *:443>
    DocumentRoot "/var/www/${OS_SERVER_NAME}"
    ServerName $OS_SERVER_NAME
    SSLEngine on

    <IfModule mod_headers.c>
        Header always set Strict-Transport-Security "max-age=15552000; includeSubDomains"
    </IfModule>

    <Directory "/var/www/${OS_SERVER_NAME}/">
        Options MultiViews FollowSymlinks
        AllowOverride All
        Order allow,deny
        Allow from all
   </Directory>

   <FilesMatch "\.(cgi|shtml|phtml|php)$">
	    SSLOptions +StdEnvVars
   </FilesMatch>
   <Directory /usr/lib/cgi-bin>
	   SSLOptions +StdEnvVars
   </Directory>

   TransferLog /var/log/apache2/${OS_SERVER_NAME}_access.log
   ErrorLog /var/log/apache2/${OS_SERVER_NAME}_error.log

SSLCertificateFile    ${CRT}
SSLCertificateKeyFile ${KEY}

SSLProtocol -all +TLSv1.2 +TLSv1.3
SSLHonorCipherOrder on
SSLCipherSuite EECDH+AESGCM:EDH+AESGCM
SSLCompression off

</VirtualHost>
</IfModule>
EOF
    trace "New config for site created: ${CFG_NAME}. Enabling it"
    
    a2ensite "${OS_SERVER_NAME}-ssl.conf"

    trace "Setting up auto-redirect to HTTPS..."

    CFG_NAME="/etc/apache2/sites-available/${OS_SERVER_NAME}.conf"
    if configured '    RewriteEngine on' $CFG_NAME; then
	warn 'HTTPS redirection was already set up'
    else
	configure "ServerName ${OS_SERVER_NAME}" \
	  "    ServerName ${OS_SERVER_NAME}\n    RewriteEngine on\n    RewriteCond %{HTTPS}  !=on\n    RewriteRule ^(.*)$ https://%{HTTP_HOST} [R=301,L]" \
	  $CFG_NAME
	trace "Entries added to ${CFG_NAME} to redirect all requests to HTTPS"
    fi    

    systemctl restart apache2

    info "STEP 8.1 done"
}

#############################################################
#          9 - SET AUTOUPDATES FOR THE SERVER
#############################################################
set_autoupdates() {
    info "STEP 9 Setting up autoupdates (unattended-updates)"

    apt install -y unattended-upgrades
    export DEBIAN_FRONTEND=noninteractive DEBCONF_NONINTERACTIVE_SEEN=true
    dpkg-reconfigure --priority=low unattended-upgrades
    UNT_CFG="/etc/apt/apt.conf.d/50unattended-upgrades"
    UNT_CFG_BACK="${LOG_DIR}/50unattended-upgrades.backup"

    trace "Going to rewrite ${UNT_CFG}"
    trace "Backup will be here: ${UNT_CFG_BACK}"
    
    if test -f "$UNT_CFG_BACK"; then
	warn "unattended updates config backup is already there!"
    else
	cp $UNT_CFG $UNT_CFG_BACK
	cat <<'EOF' > ${UNT_CFG}
Unattended-Upgrade::Allowed-Origins {
	"${distro_id}:${distro_codename}";
	"${distro_id}:${distro_codename}-security";	
	"${distro_id}ESMApps:${distro_codename}-apps-security";
	"${distro_id}ESM:${distro_codename}-infra-security";
};

Unattended-Upgrade::Package-Blacklist {   
};

Unattended-Upgrade::DevRelease "auto";
Unattended-Upgrade::Remove-Unused-Kernel-Packages "true";
Unattended-Upgrade::Remove-New-Unused-Dependencies "true";
Unattended-Upgrade::Remove-Unused-Dependencies "true";
Unattended-Upgrade::Automatic-Reboot "true";
Unattended-Upgrade::Automatic-Reboot-Time "05:00";
EOF
    fi

    info "STEP 9 done"
}

#############################################################
#          10 - ADDITIONAL SECURITY FOR THE SERVER
#############################################################
secure_server() {
    info "STEP 10 Securing the server"

    trace "Installing openssh server..."
    apt install -y openssh-server
    systemctl --quiet is-active sshd || systemctl start sshd
    systemctl --quiet is-enabled sshd || systemctl enable sshd

    SSH_CFG="/etc/ssh/sshd_config"
    SSH_CFG_BACK="${LOG_DIR}/sshd_config.backup"

    trace "Going to rewrite ${SSH_CFG}"
    trace "Backup will be here: ${SSH_CFG_BACK}"

    if test -f "$SSH_CFG_BACK"; then
	warn "SSH config backup is already there!"
    else
	cp $SSH_CFG $SSH_CFG_BACK
	cat <<EOF > ${SSH_CFG}
Include /etc/ssh/sshd_config.d/*.conf
PermitRootLogin no
ChallengeResponseAuthentication no
UsePAM yes
X11Forwarding yes
PrintMotd no
AcceptEnv LANG LC_*
Subsystem	sftp	/usr/lib/openssh/sftp-server
EOF
	if $NO_PASSWORD_SSH; then
	    info "Disabling SSH with password completely!"
	    echo 'PasswordAuthentication no' >> $SSH_CFG
	fi    
    fi

    trace "Installing fail2ban..."
    apt install -y fail2ban
    systemctl --quiet is-active fail2ban || systemctl start fail2ban
    systemctl --quiet is-enabled fail2ban || systemctl enable fail2ban
    fail2ban-client status
    F2B_CFG_ORIG="/etc/fail2ban/jail.conf"
    F2B_CFG_COPY="/etc/fail2ban/jail.local"
    if test -f "$F2B_CFG_COPY"; then
	warn "Fail2ban was already set up"
    else
	cp $F2B_CFG_ORIG $F2B_CFG_COPY
	trace "Setting up ${F2B_CFG_COPY}"
	configure 'bantime  = 10m' 'bantime  = 60m' $F2B_CFG_COPY
	configure 'maxretry = 5' 'maxretry = 20' $F2B_CFG_COPY
    fi

    trace "Configuring ufw..."
    ufw --force enable
    ufw allow 'Apache Full'
    ufw allow 'OpenSSH'
    ufw status    
    
    info "STEP 10 done"
}

#############################################################
#          RUN SECTION
#############################################################
user=$(whoami)
if [ $user != root ]; then
	error "You are using a non-privileged account"
        exit 1
fi

echo $(date +'%Y-%m-%d %H:%M') > $LOG

info "Hiya!"

apt update
apt upgrade -y
#add_user
change_servername
install_mariadb
install_php_and_apache
setup_php
download_nextcloud
setup_nextcloud
use_selfsigned_ssl_certificate
set_autoupdates
secure_server

info "Don't forget to reboot"
info "Bye!"

exit 0

# if domain name is available, SSL cert can be verified by Lets encrypt:
# sudo snap install core; sudo snap refresh core
# sudo snap install --classic certbot
# sudo ln -s /snap/bin/certbot /usr/bin/certbot
# sudo certbot certonly --apache
# sudo certbot renew --dry-run
# disable current ssl site in apache
# create new ssl config based on old one, replace certificates and ServerName (here: /etc/apache2/sites-available/)
# optional: replace ServerName in the http config as well
# and a new domain name to the trusted_domains section in NC's config.php (here: /var/www/nextcloud/config/)
# sudo systemctl restart apache2

# do not change ownership of /var/www/nextcloud, it should be owned by www-data
# sometimes login page cannot be started after a fresh install
# then try to run again:
# sudo chown -R www-data:www-data /var/www/nextcloud 
# btw log is here: sudo vim /var/www/nextcloud/data/nextcloud.log