#!/bin/sh

# SPDX-FileCopyrightText: Copyright 2022-2023, macmpi
# SPDX-License-Identifier: MIT

HDLSBSTRP_VERSION="1.2"

_apk() {
	local cmd="$1"
	local pkg="$2"

	case $cmd in
		add) # install only if not already present
			if ! apk info | grep -wq "${pkg}"; then
				apk add "$pkg" && printf '%s ' "${pkg}" >> /tmp/.trash/installed
			fi
		;;
		del) # delete only if previously installed
			if grep -wq "$pkg" /tmp/.trash/installed > /dev/null 2>&1; then
				apk del "$pkg" && sed -i 's/\b'"${pkg}"'\b//' /tmp/.trash/installed
			fi
		;;
		*)
			echo "only add/del: wrong usage"; exit
		;;
	esac
}

_preserve() {
# create a back-up of element (file, folder, symlink)
	[ -z "${1}" ] && return 1
	[ -e "${1}" ] && cp -a "${1}" "${1}".orig
}

_restore() {
# remove element (file, folder, symlink) and replace by
# previous back-up if available
	[ -z "${1}" ] && return 1
	rm -rf "${1}"
	[ -e "${1}".orig ] && mv -f "${1}".orig "${1}"
}

# shellcheck disable=SC2142  # known special case
alias _logger='logger -st "${0##*/}"'

##### End of part to be duplicated into headless_cleanup (do not alter!)

_prep_cleanup() {
## Prep for final headless_cleanup
# clears any installed packages and settings
# copy begininng of this file to keep functions
sed -n '/^#* End .*alter!)$/q;p' /usr/local/bin/headless_bootstrap > /tmp/.trash/headless_cleanup
cat <<-EOF >> /tmp/.trash/headless_cleanup
	# Redirect stdout and errors to console as service won't show messages
	exec 1>/dev/console 2>&1

	_logger "Cleaning-up..."
	_restore "/etc/ssh/sshd_config"
	_restore "/etc/conf.d/sshd"
	_apk del openssh-server
	_restore "/etc/wpa_supplicant/wpa_supplicant.conf"
	_apk del wpa_supplicant
	_restore "/etc/network/interfaces"
	_restore "/etc/hostname"
	rm -f /etc/modprobe.d/headless_gadget.conf

	# remove from boot service to avoid spurious openrc recalls from unattended script
	rm -f /etc/runlevels/default/headless_bootstrap
	rm -f /usr/local/bin/headless_bootstrap

	# Run unattended script if available
	install -m755 "${ovlpath}"/unattended.sh /tmp/headless_unattended > /dev/null 2>&1 && \
		_logger "Starting headless_unattended service" && \
		rc-service headless_unattended start

	rm -f /etc/init.d/headless_*
	_logger "Clean-up done, enjoy !"
	cat /tmp/.trash/banner > /dev/console
	if [ -n "${gdgt_term}" ]; then
		# Enabling terminal login into valid serial port
		# no choice than making permanent change to /etc/securetty (Alpine 3.19 already has ttyGS0)
		grep -q "${gdgt_term}" /etc/securetty || echo "${gdgt_term}" >> /etc/securetty
		/sbin/getty -L 115200 "${gdgt_term}" vt100 &
	fi
	exit 0
	EOF
chmod +x /tmp/.trash/headless_cleanup
}

_setup_sshd() {
## Setup temporary SSH server (root login, no password)
# We use some bundled (or optionaly provided) keys to avoid generation at startup and save time
_apk add openssh-server
_preserve "/etc/ssh/sshd_config"
_preserve "/etc/conf.d/sshd"

cat <<-EOF > /etc/ssh/sshd_config
	PermitRootLogin yes
	Banner /tmp/.trash/banner
	EOF

# Client authorized_keys or no authentication
if install -m600 "${ovlpath}"/authorized_keys /tmp/.trash/authorized_keys > /dev/null 2>&1; then
	_logger "Enabling public key SSH authentication..."
	cat <<-EOF >> /etc/ssh/sshd_config
		AuthenticationMethods publickey
		AuthorizedKeysFile /tmp/.trash/authorized_keys
		# relax strict mode as authorized_keys are inside /tmp
		StrictModes no
		EOF
else
	_logger "No SSH authentication."
	cat <<-EOF >> /etc/ssh/sshd_config
		AuthenticationMethods none
		PermitEmptyPasswords yes
		EOF
fi

# Server keys: inject optional custom keys, or generate new (might be stored),
# or use bundeled ones (not stored)
local keygen_stance="sshd_disable_keygen=yes"
if install -m600 "${ovlpath}"/ssh_host_*_key* /etc/ssh/ > /dev/null 2>&1; then
	# check for empty key within injected ones: if found, generate new keys
	if find /etc/ssh/ -maxdepth 1 -type f -name 'ssh_host_*_key*' -empty | grep -q .; then
		rm /etc/ssh/ssh_host_*_key*
		keygen_stance=""
		_logger "Will generate new SSH keys..."
	else
		chmod 644 /etc/ssh/ssh_host_*_key.pub
		_logger "Using injected SSH keys..."
	fi
else
	_logger "Using bundled ssh keys from RAM..."
	cat <<-EOF >> /etc/ssh/sshd_config
		HostKey /tmp/.trash/ssh_host_ed25519_key
		HostKey /tmp/.trash/ssh_host_rsa_key
		EOF
fi

echo "$keygen_stance" >> /etc/conf.d/sshd
rc-service sshd restart
}

_updt_apkovl() {
## update apkovl overlay file & eventually reboot
# URL redirects to apkovl file on github master: is.gd shortener provides basic analytics.
# Analytics are public and can be checked at https://is.gd/stats.php?url=apkovl_master
# Privacy policy: https://is.gd/privacy.php

local file_url="https://is.gd/apkovl_master"
local sha_url="https://github.com/macmpi/alpine-linux-headless-bootstrap/raw/main/headless.apkovl.tar.gz.sha512"
local updt_status="failed, keeping original version"
_logger "Updating overlay file..."

#	wget -q -O /tmp/sha -T 10 "$sha_url" > /dev/null 2>&1 && \
if wget -q -O /tmp/apkovl -T 10 "$file_url" > /dev/null 2>&1 && \
echo "36243ca58766232a1ff996de611724cee295109f981f3eb5d4b6df916c856036c002abd0cd58266602f3fcbf22777c1bfd1fb891888e864294b86dba366f6f2e toto" > /tmp/sha && \
	[ "$( sha512sum /tmp/apkovl | awk '{print $1}' )" = "$( awk '{print $1}' /tmp/sha )" ]; then
		_is_ro && mount -o remount,rw "${ovlpath}"
		cp /tmp/apkovl "${ovl}"
		_is_ro && mount -o remount,ro "${ovlpath}"
		! [ "$( sha512sum "${ovl}" | awk '{print $1}' )" = "$( awk '{print $1}' /tmp/sha )" ] && \
			_logger "Bad update: original apkovl file may be altered, please check!..." && return 1
		updt_status="successful"
fi
rm -f /tmp/apkovl /tmp/sha
_logger "Update $updt_status"
[ "$updt_status" = "successful" ] || return 1
# reboot if specified in auto-updt file (and no ssh session ongoing nor unattended.sh script available)
! pgrep -a -P "$( cat /run/sshd.pid 2>/dev/null )" 2>/dev/null | grep -q "sshd: root@pts" && \
	! [ -f "${ovlpath}"/unattended.sh ] && \
		grep -q "^reboot$" "${ovlpath}"/auto-updt && \
			_logger "Will reboot in 3sec..." && sleep 3 && reboot
exit 0
}

_tst_version() {
## Compare current version with latest online, notify & eventally calls for update
# URL redirects to github project page: is.gd shortener provides basic analytics.
# Analytics are public and can be checked at https://is.gd/stats.php?url=apkovl_run
# Privacy policy: https://is.gd/privacy.php
local new_vers=""
local ref="/macmpi/alpine-linux-headless-bootstrap/releases/tag/v"
local url="https://is.gd/apkovl_run"

if wget -q -O /tmp/homepg -T 10 "$url" > /dev/null 2>&1; then
	_logger "Internet access: success"
	ver="$( grep -o "$ref.*\"" /tmp/homepg | grep -Eo '[0-9]+[\.[0-9]+]*' )"
	rm -f /tmp/homepg
	if [ -n "$ver" ] && ! [ "$ver" = "$HDLSBSTRP_VERSION" ]; then
		new_vers="!! Version $ver is available on Github project page !!"
		_logger "$new_vers"
		printf '%s\n\n' "$new_vers" >> /tmp/.trash/banner
		# Optionally update apkovl if key-file allows it
		[ -f "${ovlpath}"/auto-updt ] && _updt_apkovl &
	fi
else
	_logger "Internet access: failed"
fi

}

_setup_networking() {
## Setup Network interfaces
local has_wifi wlan_lst
_has_wifi() { return "$has_wifi"; }
wlan_lst="$( find /sys/class/net/*/phy80211 -exec \
	sh -c 'printf %s\| "$( basename "$( dirname "$0" )" )"' {} \; 2>/dev/null )"
wlan_lst="${wlan_lst%\|}"
[ -n "$wlan_lst" ] && [ -f "${ovlpath}"/wpa_supplicant.conf ]
has_wifi=$?

_preserve "/etc/network/interfaces"
if ! install -m644 "${ovlpath}"/interfaces /etc/network/interfaces > /dev/null 2>&1; then
	_logger "No interfaces file supplied, building defaults..."
	cat <<-EOF > /etc/network/interfaces
		auto lo
		iface lo inet loopback

		EOF
	for dev in /sys/class/net/*; do
		# shellcheck disable=SC2034  # Unused IFINDEX while still sourced from uevent
		local DEVTYPE INTERFACE IFINDEX
		DEVTYPE=""
		# shellcheck source=/dev/null
		. "$dev"/uevent
		case ${INTERFACE%%[0-9]*} in
			lo)
				;;
			eth)
				cat <<-EOF >> /etc/network/interfaces
					auto $INTERFACE
					iface $INTERFACE inet dhcp

					EOF
				;;
			*)
				# According to below we could rely on DEVTYPE for wlan devices
				# https://lists.freedesktop.org/archives/systemd-devel/2014-January/015999.html
				# but...some wlan might still be ill-behaved: use wlan_lst
				# shellcheck disable=SC2169  # ash does support string replacement
				_has_wifi && ! [ "${wlan_lst/$INTERFACE/}" = "$wlan_lst" ] && \
					cat <<-EOF >> /etc/network/interfaces
						auto $INTERFACE
						iface $INTERFACE inet dhcp

						EOF
				# ensure considered gadget interface is actually connected over USB (may have several)
				[ "$DEVTYPE" = "gadget" ] && \
					! [ "$( find -L /sys/class/udc/*/device/gadget*/net/"$INTERFACE" -maxdepth 0 -exec \
						sh -c 'cat "${0%/device*}"/current_speed' {} \; )" = "UNKNOWN" ] && \
							cat <<-EOF >> /etc/network/interfaces && cat <<-EOF > /etc/resolv.conf
								auto $INTERFACE
								iface $INTERFACE inet static
								    address 10.42.0.2/24
								    gateway 10.42.0.1

								EOF
								nameserver 208.67.222.222
								nameserver 208.67.220.220

								EOF
				;;
		esac
	done
fi

echo "###################################"
echo "Using following network interfaces:"
cat /etc/network/interfaces
echo "###################################"

if _has_wifi && grep -qE "$wlan_lst" /etc/network/interfaces; then
	_logger "Configuring wifi..."
	_apk add wpa_supplicant
	_preserve "/etc/wpa_supplicant/wpa_supplicant.conf"
	install -m600 "${ovlpath}"/wpa_supplicant.conf /etc/wpa_supplicant/wpa_supplicant.conf
	rc-service wpa_supplicant restart
else
	_logger "No wifi interface or SSID/pass file supplied"
fi

_preserve "/etc/hostname"
echo "alpine-headless" > /etc/hostname
hostname -F /etc/hostname

rc-service networking restart
}

_setup_gadget() {
## load composite USB Serial/USB Ethernel driver & setup terminal
_logger "Enabling USB-gadget Serial and Ethernet ports"
# remove conflicting modules in case they were initially loaded (cmdline.txt)
modprobe -rq g_serial g_ether g_cdc
modprobe -q g_cdc

# look for valid serial port actually connected with USB cable
# (setting console to unconnect serial port would block boot)
gdgt_term="$( find /dev/ttyGS* -exec \
	sh -c 'timeout 1 echo "" > "$0" 2>/dev/null && echo "${0##*/}"' {} \; )"
if [ -n "$gdgt_term" ]; then
	# default serial config: xon/xoff flow control
	stty -g -F /dev/"$gdgt_term" >/dev/null 2>&1
	# notes to users willing to connect from Linux Ubuntu-based host terminal:
	# - user on host needs to be part of dialout group (reboot required), and
	# - disable spurious AT commands from ModemManager on host-side Gadget serial port
	# you may create a /etc/udev/rules.d/99-ttyacms-gadget.rules as per:
	# https://linux-tips.com/t/prevent-modem-manager-to-capture-usb-serial-devices/284/2
	# ATTRS{idVendor}=="0525" ATTRS{idProduct}=="a4aa", ENV{ID_MM_DEVICE_IGNORE}="1"

	setconsole /dev/"$gdgt_term"
fi
}


#############################################################################
## Main

# Redirect stdout and errors to console as service won't show messages
exec 1>/dev/console 2>&1
_logger "Alpine Linux headless bootstrap v$HDLSBSTRP_VERSION by macmpi"

# help randomness for wpa_supplicant and sshd (urandom until 3.16)
rc-service seedrng restart || rc-service urandom restart

# setup USB gadget ports if some ports are enabled in peripheral mode
# Note: we assume dwc2/dwc3 is pre-loaded, we just check mode
gdgt_term=""
[ "$( find -L /sys/class/udc/*/is_a_peripheral -print0 2>/dev/null | \
	xargs -0 cat 2>/dev/null | \
		grep -c "0" )" -ge "1" ] && \
			_setup_gadget

# Determine ovl file location
# grab used ovl filename from dmesg
ovl="$( dmesg | grep -o 'Loading user settings from .*:' | awk '{print $5}' | sed 's/:.*$//' )"
if [ -f "${ovl}" ]; then
	ovlpath="$( dirname "$ovl" )"
else
	# search path again as mountpoint have been changed later in the boot process...
	ovl="$( basename "${ovl}" )"
	ovlpath=$( find /media -maxdepth 2 -type d -path '*/.*' -prune -o -type f -name "${ovl}" -exec dirname {} \; | head -1 )
	ovl="${ovlpath}/${ovl}"
fi

# Create banner file
warn=""
grep -q "${ovlpath}.*[[:space:]]ro[[:space:],]" /proc/mounts; is_ro=$?
_is_ro() { return "$is_ro"; }

_is_ro && warn="(remount partition rw!)"
cat <<-EOF > /tmp/.trash/banner

	Alpine Linux headless bootstrap v$HDLSBSTRP_VERSION by macmpi
	
	You may want to delete/rename .apkovl file before reboot ${warn}:
	${ovl}
	(can be done automatically with unattended script - see sample snippet)


	EOF

_setup_networking

# Test latest available version online
# Can be skipped by creating a 'opt-out'-named dummy file aside apkovl file
[ -f "${ovlpath}"/opt-out ] || _tst_version &

# setup sshd unless unattended.sh script prevents it
grep -q "^#NO_SSH$" "${ovlpath}"/unattended.sh > /dev/null 2>&1 \
	|| _setup_sshd

_prep_cleanup
_logger "Initial setup done, handing-over to clean-up"
rc-service headless_cleanup start
exit 0