#!/bin/bash
#
# Copyright (C) 2021-2026 Soleta Networks <opengnsys@soleta.eu>
#
# This program is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the
# Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#

check_exitcode()
{
	local exitcode=$1
	local errormsg=$2

	if [ "$exitcode" -ne 0 ]; then
		echo -e "$errormsg"
		exit 1
	fi

	return 0
}

error_report()
{
cat <<-EOF
	An unexpected error occured during the installation process.

	Please send a report with a copy of the output from the installation
	process to opengnsys@soleta.eu.
EOF
exit 1
}

trap 'echo "$BASH_SOURCE:$LINENO $BASH_COMMAND" && error_report' ERR
trap 'echo Exiting OpenGnsys Enterprise installer.' EXIT

SOLETA_APT_LIST_PATH="/etc/apt/sources.list.d/soleta.list"
DEB_OGSERVER="ogserver"
DEB_OGCP="ogcp"
DEB_OGCLI="ogcli"
DEB_OGCLIENT="ogclient"
DEB_OPENGNSYS_EXTRA="opengnsys-extra"
DEB_OGE_MEMTEST="oge-memtest"

# SHELL COLORS
YELLOW='\033[1;33m'
GREEN='\033[1;32m'
CYAN='\033[1;36m'
RED='\033[1;31m'
WHITE='\033[0m'

OGSERVER_TOKEN=""

validate_ip ()
{
	if [ -z "$1" ]; then
		return 1
	fi

	OFS=$IFS
	IFS=.

	# shellcheck disable=SC2086
	set -- $1

	IFS=$OFS

	if [ $# -ne 4 ]; then
		return 1
	fi

	for i; do
		# not a number
		case "$i" in
			''|*[!0-9]*)
				return 1
				;;
		esac
		# not in range 0-255
		if [ "$i" -lt 0 ] || [ "$i" -gt 255 ]; then
			return 1
		fi
	done
}

check_root()
{
if [ "$EUID" -ne 0 ]; then
	echo "Please run as root"
	exit 1
fi
}

check_ubuntu()
{
if [ ! "$(lsb_release -s -r)" == "22.04" ]; then
	echo "OpenGnsys Enterprise requires Ubuntu 22.04"
	echo "You are running:" "$(lsb_release -s -r)" "$(lsb_release -s -c)"
	exit 1
fi
}

check_previous_installation()
{
INSTALL_DIRECTORY="/opt/opengnsys"
if [ -d "$INSTALL_DIRECTORY" ]; then
	echo "OpenGnsys directory exists at $INSTALL_DIRECTORY"
	shopt -s nullglob
	for subdir in "$INSTALL_DIRECTORY"/*; do
		echo "An OpenGnsys installation seems to exist at:" "$subdir"
	done
	shopt -u nullglob
	echo "Please, uninstall OpenGnsys and try again."
	exit 1
fi
}

check_dependencies()
{
	JQ="$(which jq)"
	if [ ! -x "$JQ" ]; then
		echo "missing jq binary, hint: apt install jq"
		exit 1
	fi

	CURL="$(which curl)"
	if [ ! -x "$CURL" ]; then
		echo "missing curl binary, hint: apt install curl"
		exit 1
	fi
}


prepare_apt()
{
	[[ ! -f $SOLETA_APT_LIST_PATH ]] && echo "deb [trusted=yes] https://opengnsys.soleta.eu/ubuntu-jammy/ /" > $SOLETA_APT_LIST_PATH
	apt update
}

define_credentials()
{
	local -a device_array
	local -a address_array

	while true
	do
		read -r -p "Specify your ogCP user name: " OGCP_USER
		if [[ -z "$OGCP_USER" || "${OGCP_USER}" =~ [^A-Za-z0-9] ]]; then
			echo "Invalid user name, use alphanumeric only."
		else
			break
		fi
	done

	while true
	do
		read -r -p "Specify your ogCP password: " OGCP_PASS
		if [ -z "$OGCP_PASS" ]; then
			echo "Invalid empty password."
		else
			break
		fi
	done
	echo

	while true
	do
		read -r -p "Specify MySQL database new user (to be used by ogServer): " DB_USER
		if [[ -z "$DB_USER" || "${DB_USER}" =~ [^A-Za-z0-9] ]]; then
			echo "Invalid user name, use alphanumeric only."
		else
			break
		fi
	done

	while true
	do
		read -r -p "Specify your MySQL database password: " DB_PASS
		if [[ -z "$DB_PASS" || "${DB_PASS}" =~ [^A-Za-z0-9] ]]; then
			echo "Invalid password, use alphanumeric only."
		else
			break
		fi
	done
	echo

	while true
	do
		read -r -p "Specify password for your SAMBA server: " SAMBAPWD
		if [[ -z "$SAMBAPWD" || "${SAMBAPWD}" =~ [^A-Za-z0-9] ]]; then
			echo "Invalid password, use alphanumeric only."
		else
			break
		fi
	done
	echo

	mapfile -t device_array < <(ip -o link | awk '!/loopback/ {sub(/:.*/,"",$2); print $2}')
	if [ -z "${device_array[*]}" ]; then
		echo -e "${RED}Unable to detect network devices in this system. Aborting installation.${WHITE}"
		exit 1
	else
		echo -e "${CYAN}Detected network interfaces: ${device_array[*]}${WHITE}"
	fi

	read -r -p "Introduce the default network interface: " dev
	while ! ip -o -4 a show dev "$dev" &>/dev/null; do
		echo "Specified interface is invalid or has no address configured"
		read -r -p "Enter a valid interface (detected: ${device_array[*]}): " dev
	done
	export OGE_DEFAULT_NETWORK_DEVICE="$dev"

	IFS=" " read -r -a address_array <<< "$(ip -j -o -4 a show dev "$dev" | jq -r ".[] | .addr_info[].local")"
	echo -e "${CYAN}Configured addresses in network device $dev: ${address_array[*]} ${WHITE}"
	read -r -p "Introduce your default OpenGnsys server IPv4 address: " SERVERIP
	while ! validate_ip "$SERVERIP"; do
		echo "Specified IPv4 address is invalid."
		read -r -p "Enter a valid IPv4 address for your OpenGnsys server: " SERVERIP
	done
}

show_config()
{
	echo -e "${YELLOW}"
	echo -e "Please, verify that your configuration below is correct."
	echo -e "${WHITE}"
	echo -e "==============================="
	echo -e "- ogCP user name: " "$OGCP_USER"
	echo -e "- ogCP password: " "$OGCP_PASS"
	echo -e "- MySQL database user name: " "$DB_USER"
	echo -e "- MySQL database password: " "$DB_PASS"
	echo -e "- SAMBA server password: " "$SAMBAPWD"
	echo -e "- Default network device: " "$OGE_DEFAULT_NETWORK_DEVICE"
	echo -e "- Server IP address: " "$SERVERIP"
	echo -e ""

	read -p "Would you like to proceed with this installation of OpenGnsys Enterprise (y/n)?: " -n 1 -r
	echo
	if [[ $REPLY =~ ^[Yy]$ ]]
	then
		echo -e "${YELLOW}"
		echo -e "Starting the OpenGnsys Enterprise installation process. Please *DO NOT INTERRUPT* this process."
		echo -e "${WHITE}"
		sleep 5
	else
		echo -e "${RED}"
		echo -e "User aborted the installation. Exiting..."
		echo -e "${WHITE}"
		exit 1
	fi
}

create_populate_sql()
{
	OGADMSQLPATH="/usr/share/opengnsys/ogserver/ogAdmBD.sql"

	if [ -n "$SERVERIP" ];
	then
		sed -e "s/SERVERIP/$SERVERIP/g" \
			-e "s/DBUSER/$DB_USER/g" \
			-e "s/DBPASSWORD/$DB_PASS/g" \
			"$OGADMSQLPATH" > /tmp/foobar.sql
	else
		echo "Bug during installation, server IP was not set"
		exit 1
	fi
}

random_string()
{
	local result

	set -o pipefail
	if ! result=$(head -c 10000 /dev/urandom | tr -dc 'a-z0-9' | fold -w "${1:-32}" | head -n 1); then
		echo -e "${RED}Error while generating a random number${WHITE}"
		exit 1
	fi
	set +o pipefail

	if [ -z "$result" ]; then
		echo -e "${RED}BUG: Empty random number generated${WHITE}"
		exit 1
	fi

	echo "$result"
}

function install_sql()
{
	echo "## Configuring SQL..."
	echo "Creating mysqld-og.cnf"
	echo "[mysqld]
event_scheduler = ON
character-set-server=utf8
collation-server=utf8_general_ci
group_concat_max_len = 8192" > /etc/mysql/mysql.conf.d/mysqld-og.cnf
	systemctl restart mysql
	echo "Creating ogAdmBD database"
	mysqladmin --user=root create ogAdmBD
	mysql -u root -e "SET GLOBAL sql_mode=(SELECT TRIM(BOTH ',' FROM REPLACE(@@sql_mode,'ONLY_FULL_GROUP_BY','')));"
	echo "Creating and granting user $DB_USER"
	mysql -u root <<-EOF
		CREATE USER '$DB_USER'@'localhost' IDENTIFIED BY '$DB_PASS';
		GRANT ALL PRIVILEGES ON ogAdmBD.* TO '$DB_USER'@'localhost' WITH GRANT OPTION;
		FLUSH PRIVILEGES;
		EOF
	echo "Populating database..."
	create_populate_sql
	mysql -u root --default-character-set=utf8 "ogAdmBD" < /tmp/foobar.sql
	rm /tmp/foobar.sql

	systemctl enable --now ogserver
}

function install_ogcli()
{
	local tmp_ogcli_conf

	echo "## Installing ogCLI"
	if ! apt install -qy $DEB_OGCLI; then
		echo -e "${RED}Error installing ogCLI. \"apt install $DEB_OGCLI\" failed${WHITE}"
		exit 1
	fi

	echo "## Configuring ogCLI"
	tmp_ogcli_conf=$(mktemp)
	if [ ! -f "$tmp_ogcli_conf" ]; then
		echo -e "${RED}Error creating temporal ogCLI configuration file. \"mktemp\" failed.${WHITE}"
		exit 1
	fi

	if ! cp /opt/opengnsys/etc/ogcli.json "$tmp_ogcli_conf"; then
		echo -e "${RED}Error copying ogCLI configuration file${WHITE}"
		echo -e "${RED}\"cp /opt/opengnsys/etc/ogcli.json $tmp_ogcli_conf\" failed${WHITE}"
		exit 1
	fi

	if ! jq ".api_token = \"$OGSERVER_TOKEN\"" "$tmp_ogcli_conf" > /opt/opengnsys/etc/ogcli.json; then
		echo -e "${RED}Error updating ogCLI configuration file${WHITE}"
		echo -e "${RED}\"jq\" redirection to /opt/opengnsys/etc/ogcli.json failed${WHITE}"
		exit 1
	fi
}

function install_ogcp()
{
	local tmp_ogcp_conf

	echo "## Installing ogCP"
	if ! apt install -qy $DEB_OGCP; then
		echo -e "${RED}Error installing ogCP. \"apt install $DEB_OGCP\" failed${WHITE}"
		exit 1
	fi

	echo "## Configuring ogCP"
	if ! tmp_ogcp_conf=$(mktemp); then
		echo -e "${RED}Error creating temporal ogCP configuration file. \"mktemp\" failed.${WHITE}"
		exit 1
	fi

	if ! cp /opt/opengnsys/ogcp/ogcp/cfg/ogcp.json.template "$tmp_ogcp_conf"; then
		echo -e "${RED}Error copying ogCP configuration file${WHITE}"
		echo -e "${RED}\"cp /opt/opengnsys/ogcp/ogcp/cfg/ogcp.json.template $tmp_ogcp_conf\" failed${WHITE}"
		exit 1
	fi

	local PASS_HASH
	PASS_HASH=$(echo -n "$OGCP_PASS" | sha512sum | cut -d ' ' -f 1)
	if ! jq ".API_TOKEN = \"$OGSERVER_TOKEN\" | \
		.SERVERS = [{ \"NAME\": \"OpenGnsys\", \
		\"IP\": \"127.0.0.1\", \
		\"PORT\": 8888, \
		\"API_TOKEN\": \"$OGSERVER_TOKEN\" }] | \
		.USERS = [{ \"USER\": \"$OGCP_USER\", \
		\"PASS\": \"$PASS_HASH\", \
		\"ADMIN\": true, \
		\"SCOPES\": [ ] }]" \
		"$tmp_ogcp_conf" > /opt/opengnsys/ogcp/ogcp/cfg/ogcp.json; then
			echo -e "${RED}Error updating ogCP configuration file${WHITE}"
			echo -e "${RED}\"jq\" redirection to /opt/opengnsys/ogcp/ogcp/cfg/ogcp.json failed${WHITE}"
			exit 1
	fi

	if ! systemctl enable --now ogcp; then
		echo -e "${RED}Error enabling ogCP service. \"systemctl enable --now ogcp\" failed${WHITE}"
		exit 1
	fi
}

function install_ogserver()
{
	echo "## Installing ogserver"
	apt install -qy $DEB_OGSERVER
	OGSERVER_TOKEN=$(head -c 64 /dev/urandom | md5sum | cut -d ' ' -f1)
	echo "Updating ogserver.json..."
	jq --arg DB_USER "$DB_USER" --arg DB_PASS "$DB_PASS" --arg API_KEY "$OGSERVER_TOKEN" --arg SAMBAPWD "$SAMBAPWD" '.database.user = $DB_USER | .database.pass = $DB_PASS | .database.name = "ogAdmBD" | .rest.api_token = $API_KEY | .samba.pass = $SAMBAPWD' \
		/usr/share/opengnsys/etc/ogserver.json \
		> /opt/opengnsys/etc/ogserver.json
	check_exitcode $? "Error creating the ogServer configuration file"
}

function install_client()
{
	local repokey
	repokey=$(random_string)

	echo "## Installing client related software"
	apt install -qy $DEB_OGCLIENT $DEB_OPENGNSYS_EXTRA
	check_exitcode $? "Error installing packages $DEB_OGCLIENT, $DEB_OPENGNSYS_EXTRA"

	rm -f /opt/opengnsys/etc/ogAdmRepo.cfg

	echo "Setting up SAMBA server password..."
	echo -ne "$SAMBAPWD\n$SAMBAPWD\n" | smbpasswd -s opengnsys

	echo "Configuring ogClient (/opt/opengnsys/client/ogClient/cfg/ogclient.json)"
	jq --arg ip "$SERVERIP" --arg url "https://$SERVERIP/opengnsys/varios/menubrowser.php" --arg pass "$SAMBAPWD" \
		'.opengnsys += { $ip, $url } | .samba.pass = $pass' \
		/opt/opengnsys/client/ogClient/cfg/ogclient.json \
		> /tmp/ogclient.json
	check_exitcode $? "Error creating the default ogClient configuration file."

	mv /tmp/ogclient.json /opt/opengnsys/client/ogClient/cfg/ogclient.json
	check_exitcode $? "Error moving the default ogClient configuration file to /opt/opengnsys/client/ogClient/cfg/."

	echo "Configuring Tiptorrent server"
	if ! systemctl enable --now tiptorrent@opt-opengnsys-images.service; then
		echo -e "${RED}Error enabling Tiptorrent service${WHITE}"
		echo -e "${RED}\"systemctl enable --now tiptorrent@opt-opengnsys-images.service\" failed${WHITE}"
		exit 1
	fi
}

function configure_netboot()
{
	echo "## Configuring netboot"
	echo "Installing necessary efi packages"
	apt install -qy shim-signed grub-efi-amd64-signed grub-pc-bin oge-netboot

	ln -fs /var/lib/tftpboot /opt/opengnsys/tftpboot
	check_exitcode $? "Error creating /opt/opengnsys/tftpboot link to /var/lib/tftpboot"

	echo "Changing default TFTP directory /srv/tftp to /var/lib/tftpboot. To change TFTP directory edit /etc/default/tftpd-hpa"
	sed -i 's/\/srv\/tftp/\/var\/lib\/tftpboot/g' /etc/default/tftpd-hpa
	check_exitcode $? "Error modifying the TFTP server configuration file"

	systemctl enable tftpd-hpa.service
	systemctl restart tftpd-hpa.service
	check_exitcode $? "Error restarting the TFTP service"
	echo "Restarted TFTP service."

	echo "Creating extra folders needed when mounting samba..."
	mkdir -m 775 /opt/opengnsys/images
	check_exitcode $? "Error creating the OpenGnsys images directory"
	chown opengnsys:opengnsys /opt/opengnsys/images
	check_exitcode $? "Error setting permitions for the OpenGnsys images directory"
	# shellcheck disable=SC2174
	mkdir -m 755 -p /opt/opengnsys/log
	check_exitcode $? "Error creating the OpenGnsys log directory"
	# shellcheck disable=SC2174
	mkdir -m 775 -p /opt/opengnsys/log/clients
	check_exitcode $? "Error creating the OpenGnsys clients log directory"
	chown root:opengnsys /opt/opengnsys/log/clients
	check_exitcode $? "Error setting permitions for the OpenGnsys clients log directory"
	mkdir /opt/opengnsys/client/{images,log}
	check_exitcode $? "Error creating samba directories for the clients"

	echo "Creating grub.cfg in tftpboot..."
echo 'configfile=$prefix/01-$net_default_mac
source "$configfile"

if [ "$timeout" == "" -a "$default" == "" ]; then
    source "$prefix/default"
fi' > /opt/opengnsys/tftpboot/grub/grub.cfg


	echo "Creating default grub2 file in tftpboot..."
	echo 'set default=0
set timeout=240

menuentry "OpenGnsys Enterprise cannot find any OS in this computer" {
        reboot
}' > /opt/opengnsys/tftpboot/grub/default

	echo "Downloading and installing an ogReLive image ..."
	ogcli install live --name 6.1.0-43-amd64
	check_exitcode $? "Error downloading ogReLive"
}

function install_memtest()
{
	echo "## Installing memtest boot mode"
	if ! apt install -qy $DEB_OGE_MEMTEST; then
		echo -e "${RED}Error installing memtest boot mode. \"apt install $$DEB_OGE_MEMTEST\" failed${WHITE}"
		exit 1
	fi
}

function setup_ogrelive()
{
	sed -i 's|filename "grldr";|filename "grub/i386-pc/core.0";|' /etc/dhcp/dhcpd*.conf
	apt install -qy apache2
	a2enmod ssl
	a2ensite default-ssl
	systemctl enable --now apache2

	ln -s /var/lib/tftpboot/ogrelive /var/www/html/ogrelive
	check_exitcode $? "Error creating /var/www/html/ogrelive link to /var/lib/tftpboot/ogrelive"

	systemctl disable --now nmbd
}

check_root
check_ubuntu
check_previous_installation
check_dependencies
define_credentials
show_config
prepare_apt
install_ogserver
install_sql
install_ogcp
install_ogcli
install_client
configure_netboot
install_memtest
setup_ogrelive

echo -e "${GREEN}Installation ended successfully!${WHITE}"
echo -e "${GREEN}Now you can access to ogCP with http://$SERVERIP:5000/${WHITE}"
