#!/bin/bash
#
# This is an sdm plugin for: hotspot
#
# The plugin is called three times: for Phase 0, Phase 1, and post-install.
#
# https://www.raspberrypi.com/documentation/computers/configuration.html#host-a-wireless-network-from-your-raspberry-pi

function loadparams() {
    source $SDMPT/etc/sdm/sdm-readparams
}

#TODO
#  a pre-filled config file
#

function writehsconfig() {
    rm -f $assetdir/config
    IFS="|" read -r -a optlist <<< "${vldargs#|}"
    for a in "${optlist[@]}"
    do
	if [ "$a" != "config" ]
	then
	    av="${!a}"
	    [ "$av" != "" ] && printf "%s=%s\n" $a "$av" >> $assetdir/config
	fi
    done
}

function parsehsconfig() {
    local fhotspot="$1" sextglob okargs

    sextglob=$(shopt -p extglob)
    shopt -s extglob
    # Remove 'config' arg and leading/trailing vbar
    okargs=${vldargs//config|}
    okargs=${okargs#|}
    okargs=${okargs%|}
    okargs="@($okargs)"

    while IFS=":=" read -r rpifun value
    do
	if [[ ! $rpifun =~ ^\ *# && -n $rpifun ]] # skip comment and malformed lines
	then
	    value="${value%%\#*}"    # Del EOL comments
	    value="${value%"${value##*[^[:blank:]]}"}"  # Del trailing spaces/tabs
	    value=$(stripquotes "$value" qd)
	    case "${rpifun,,}" in
		$okargs)
		    printf -v "$rpifun" "%s" "$value"
		    ;;
		*)
		    logtobothex "? Plugin $pfx: Unrecognized hotspot option '$rpifun' in '$fhotspot'"
		    ;;
	    esac
	fi
    done < $fhotspot

    $sextglob
}

function enablerouting() {
    logtoboth "> Plugin $pfx: Enable routing and masquerading for WiFi clients"
    #logtoboth "> Plugin $pfx: Install iptables"
    #installpkgsif iptables
    logtoboth "> Plugin $pfx: Install nftables"
    installpkgsif nftables
    cat > /etc/sysctl.d/10-sdm-aprouted.conf <<EOF
# sdm hotspot routing configuration $(thisdate)
# https://www.raspberrypi.org/documentation/configuration/wireless/access-point-routed.md
# Enable IPv4 routing
net.ipv4.ip_forward=1
EOF
    logtoboth "> Plugin $pfx: Configure service sdm-hotspot-masquerade to enable nftables routing to '$ipforward'"
    cat > /etc/systemd/system/sdm-hotspot-masquerade.service <<EOF
[Unit]
Description=sdm-hotspot-masquerade
After=network.target

[Service]
#
# This service should be:
#  Enabled for a routed access point
#  Disabled for a local or bridged access point
#
ExecStartPre=/sbin/nft add table nat
ExecStartPre=/sbin/nft 'add chain nat postrouting { type nat hook postrouting priority 100 ; }'
ExecStart=/sbin/nft add rule nat postrouting masquerade
##NOTUSED#ExecStart=/sbin/iptables -t nat -A POSTROUTING -o $ipforward -j MASQUERADE
RemainAfterExit=true
Type=oneshot

[Install]
WantedBy=multi-user.target
EOF
    systemctl -q enable sdm-hotspot-masquerade
}

function dodefault() {
    #
    # $1: setting name
    # $2: value
    #
    local sname=$1 sval=$2
    logtoboth "$(printf "   %-60s [Default]" "$(printf "%s: %s" $sname $sval)")"
    printf -v "$sname" "%s" "$sval"
}

function fillupcfg() {
    #
    # Default any missing arguments
    #
    IFS="|" read -r -a optlist <<< "${vldargs#|}"
    for a in "${optlist[@]}"
    do
	if [ "${!a}" == "" ]
	then
	    case "$a" in
		#
		# Default for missing args
		#
		device)
		    dodefault device wlan0
		    ;;
		dhcpmode)
		    dodefault dhcpmode nm
		    ;;
		hsenable)
		    dodefault hsenable y
		    ;;
		hsname)
		    dodefault hsname Hotspot
		    ;;
		ipforward)
		    dodefault ipforward ""
		    ;;
		pskencrypt)
		    dodefault pskencrypt ""
		    ;;
		wifipassword)
		    dodefault wifipassword password
		    ;;
		wifissid)
		    dodefault wifissid MyPiNet
		    ;;
		type)
		    dodefault type routed
		    ;;
		wlanip)
		    dodefault wlanip ""
		    ;;
	    esac
	else
	    [ "$a" != "config" ] && logtoboth  "$(printf "   %-60s [Argument]" "${a}: ${!a}")"
	    [ "$a" == "hsname" ] && hsname=$(adjust_wifinmpsk "$(stripquotes "$hsname")")
	    if [ "$a" == "wifissid" ]
	    then
		wifissid="$(stripquotes "$wifissid")"
		owifissid="$wifissid"
		wifissid=$(adjust_wifinmpsk "$wifissid")
	    fi
	fi
    done
    if [[ "$wifipassword" != "" ]]
    then
	wifipassword="$(stripquotes "$wifipassword")"
	owifipassword="$wifipassword"
	wifipassword=$(adjust_wifinmpsk "$wifipassword")
	[[ "$pskencrypt" == "encrypt" ]] && wifipassword=$(encrypt_wifipsk "$owifipassword" "$owifissid")
    fi
}

function cfgnetman() {
    local fwdev ipad

    logtoboth "> Prefix $pfx: Configure hotspot '$hsname' with Network Manager"
    [ "$hsenable" == "y" ] && acsts="on" || acsts="off"
    if [ "$type" == "routed" ]
    then
	[ "$dhcpmode" == "nm" ] && ipv4method="shared" || ipv4method="manual"
	logtoboth "> Plugin $pfx: Write sdm First Boot script to configure routed hotspot '$hsname' with method=$ipv4method"
	[ "$wlanip" != "" ] && ipad="ipv4.addresses $wlanip/24" || ipad=""
	cat > $SDMPT/etc/sdm/0piboot/092-nm-hotspot-routed.sh <<EOF
#!/bin/bash
function getnwtype() {
    #
    # \$1: network device name
    #
    # Return value:
    #   ethernet if device is an ethernet device
    #   wifi     if device is a wifi device
    #   usb      if device is a usb (RNDIS) device
    #   unknown  no clue
    #
    local nwdev="\$1" nwtype
    if [ "\$nwdev" == "usb0" ]
    then
        echo "usb"
    else
        nwtype="\$(nmcli device show \$nwdev | grep GENERAL.TYPE | (IFS=": " read gt iftype ; echo "\$iftype"))"
        [ "\$nwtype" == "" ] && nwtype=unknown
        echo \$nwtype
    fi
}

logger "sdm FirstBoot: Configure routed NetworkManager-controlled hotspot"
nwtype=\$(getnwtype $device)
logger "sdm FirstBoot: Device: $device Network Type: \$nwtype"
[ "\$nwtype" == "wifi" ] && rfkill unblock wlan

# See if other connection exists that use this device
if grep -qs interface-name=$device /etc/NetworkManager/system-connections/*.nmconnection
then
    excname=\$(grep -l interface-name=$device /etc/NetworkManager/system-connections/*.nmconnection 2>/dev/null)
    [ "\$excname" != "" ] && excname="\$(basename \$excname)" && excname="\${excname%.nmconnection}"
else
    # rpi-imager preconfigured wifi check
    grep -qs id=preconfigured /etc/NetworkManager/system-connections/*.nmconnection && [[ "$device" == "wlan0" ]] && excname=preconfigured
fi

logger "sdm FirstBoot: Before configuration:"
logger "\$(nmcli c show)"
logger "sdm FirstBoot: Before configuration [active]:"
logger " \$(nmcli c show --active)"
nmcli device down $device >/dev/null 2>&1
if [ "\$nwtype" == "wifi" ]
then
    logger "sdm FirstBoot: Create wifi hotspot '$hsname' for device $device"
    nmcli device wifi hotspot ifname $device con-name $hsname ssid $wifissid band bg password $wifipassword
else
    logger "sdm FirstBoot: Create hotspot '$hsname' for device $device"
    nmcli c add ifname $device con-name $hsname type ethernet
fi
nmcli c modify $hsname ipv4.method $ipv4method ipv6.method disabled $ipad autoconnect $acsts connection.autoconnect-priority 150
nmcli c down $hsname >/dev/null 2>&1
[[ "\$excname" != "" ]] && [[ "$acsts" == "on" ]] && logger "sdm FirstBoot: Stop connection '\$excname'" && nmcli c down "\$excname" >/dev/null 2>&1
nmcli c reload
if [ "$acsts" == "on" ]
then
    logger "sdm FirstBoot: Enable IPV4 forwarding and nftables masquerade"
    [[ "$ipforward" != "" ]] && [[ "$type" == "routed" ]] && sysctl 'net.ipv4.ip_forward=1' >/dev/null && systemctl start -q sdm-hotspot-masquerade
    if [ "$device" != "usb0" ]
    then
        logger "sdm FirstBoot: Start hotspot '$hsname'"
        nmcli c up $hsname
    fi
fi

logger "sdm FirstBoot: After configuration:"
logger "\$(nmcli c show)"
logger "sdm FirstBoot: After configuration [active]:"
logger "\$(nmcli c show --active)"
EOF
    else
	logtoboth "> Plugin $pfx: Write sdm First Boot script to configure bridged hotspot '$hsname'"
	[ "$ipforward" == "" ] && fwdev="eth0" || fwdev="$ipforward"
	cat > $SDMPT/etc/sdm/0piboot/092-nm-hotspot-bridged.sh <<EOF
#!/bin/bash
function getnwtype() {
    #
    # \$1: network device name
    #
    # Return value:
    #   ethernet if device is an ethernet device
    #   wifi     if device is a wifi device
    #   usb      if device is a usb (RNDIS) device
    #   unknown  no clue
    #
    local nwdev="\$1" nwtype
    if [ "\$nwdev" == "usb0" ]
    then
        echo "usb"
    else
        nwtype="\$(nmcli device show \$nwdev | grep GENERAL.TYPE | (IFS=": " read gt iftype ; echo "\$iftype"))"
        [ "\$nwtype" == "" ] && nwtype=unknown
        echo \$nwtype
    fi
}

logger "sdm FirstBoot: Configure bridged NetworkManager-controlled hotspot"
nwtype=\$(getnwtype $device)
logger "sdm FirstBoot: Device: $device Network Type: \$nwtype"
[ "\$nwtype" == "wifi" ] && rfkill unblock wlan

logger "sdm FirstBoot: Before configuration:"
logger "\$(nmcli c show)"
logger "sdm FirstBoot: Before configuration [active]:"
logger " \$(nmcli c show --active)"

# Disable autoconnect on any connections for $fwdev that are not part of the hotspot
while read nmfile
do
    if grep -qs interface-name=$fwdev \$nmfile
    then
        IFS="=" read idx cname < <(grep ^id= \$nmfile)
        if ! [[ "\$cname" =~ $hsname ]]
        then
            logger "sdm FirstBoot: Disable connection '\$cname'"
            nmcli c down "\$cname"
            nmcli c modify "\$cname" autoconnect off
        fi
    fi
    if grep -qs interface-name=$device \$nmfile
    then
        IFS="=" read idx cname < <(grep ^id= \$nmfile)
        if ! [[ "\$cname" =~ $hsname ]]
        then
            logger "sdm FirstBoot: Disable connection '\$cname'"
            nmcli c down "\$cname"
            nmcli c modify "\$cname" autoconnect off
        fi
    fi
done < <(compgen -G "/etc/NetworkManager/system-connections/*.nmconnection")

logger "sdm FirstBoot: Stop 'Wired connnection 1'"
nmcli c down "Wired connection 1" >/dev/null 2>&1

logger "sdm FirstBoot: Create bridge '$hsname-br0'"
nmcli c add ifname "$hsname-br0" type bridge con-name "$hsname-br0" bridge.stp no ipv6.method disabled connection.autoconnect-slaves 1 autoconnect $acsts
#nmcli c show "$hsname-br0"  > /root/"$hsname-br0".nmcli
#nmcli c down "$hsname-br0"

logger "sdm FirstBoot: Create bridge slave '$hsname-bridge-slave-$fwdev'"
nmcli c add type bridge-slave ifname $fwdev con-name "$hsname-bridge-slave-$fwdev" master "$hsname-br0" autoconnect $acsts
#nmcli c show "$hsname-bridge-slave-$fwdev"  > /root/"$hsname-bridge-slave-$fwdev".nmcli

logger "sdm FirstBoot: Create bridge '$hsname-bridge-br0'"
nmcli c add type ethernet slave-type bridge con-name "$hsname-bridge-br0" ifname $fwdev master "$hsname-br0"
#nmcli c show "$hsname-bridge-br0"  > /root/"$hsname-bridge-br0".nmcli

#?needed? connection.autoconnect-slave yes
if [ "\$nwtype" == "wifi" ]
then
    logger "sdm FirstBoot: Create wifi hotspot '$hsname'"
    nmcli device wifi hotspot ifname $device con-name $hsname ssid $wifissid band bg
else
    logger "sdm FirstBoot: Create ethernet hotspot '$hsname'"
    nmcli c add ifname $device con-name $hsname type ethernet
fi
#nmcli c show $hsname  > /root/$hsname.nmcli
nmcli c down $hsname >/dev/null 2>&1
logger "sdm FirstBoot: Modify hotspot '$hsname' for bridge configuration"
if [ "\$nwtype" == "wifi" ]
then
    nmcli c modify $hsname slave-type bridge master "$hsname-br0" autoconnect $acsts wifi-sec.key-mgmt wpa-psk 802-11-wireless-security.psk $wifipassword
else
    nmcli c modify $hsname slave-type bridge master "$hsname-br0" autoconnect $acsts
fi
#nmcli c show $hsname  > /root/$hsname-after-modify.nmcli
# ipv4.addresses $wlanip/24

logger "sdm FirstBoot: After configuration:"
logger "\$(nmcli c show)"
logger "sdm FirstBoot: After configuration [active]:"
logger "\$(nmcli c show --active)"

if [ "$acsts" == "on" ]
then
    nmcli c reload
    if [ "\$nwtype" != "usb" ]
    then
        nmcli c up $hsname
        nmcli c up "$hsname-br0"
        nmcli c up "$hsname-bridge-slave-$fwdev"
fi
EOF
    fi
}

function converty() {
    [ -v hsenable ] && hsenable=y
    [ -v pskencrypt ] && pskencrypt=encrypt
}

# $1 is the phase: "0", "1", or "post-install"
# $2 is the argument list: arg1=val1|arg2=val2|arg3=val3| ...
#
# Main code for the Plugin
#
phase=$1
pfx="$(basename $0)"     #For messages
args="$2"
loadparams
vldargs="|config|device|dhcpmode|pskencrypt|hsenable|hsname|ipforward|type|wifipassword|wifissid|wlanip|"
rqdargs=""
assetdir="$SDMPT/etc/sdm/assets/hotspot"

if [ "$phase" == "0" ]
then
    #
    # In Phase 0 all references to directories in the image must be preceded by $SDMPT
    #
    logtoboth "* Plugin $pfx: Start Phase 0"
    unset domain # Unset sdm's domain setting
    plugin_getargs $pfx "$args" "$vldargs" "$rqdargs" || exit
    #
    # Print the keys found (example usage). plugin_getargs returns the list of found keys in $foundkeys
    #
    plugin_printkeys
    mkdir -p $assetdir
    converty
    if [ "$config" != "" ]
    then
	if [ -f $config ]
	then
	    logtoboth "> Plugin $pfx: Read hotspot config '$config' and update missing with defaults:"
	    parsehsconfig $config
	    fillupcfg
	    logtoboth "> Plugin $pfx: Write hotspot config to $assetdir/config"
	    writehsconfig
	else
	    logtoboth "% Plugin $pfx: Config file '$config' not found"
	    exit
	fi

    else
	# config file not provided. Make one from what we were given
	logtoboth "% Plugin $pfx: No 'config' provided; Generate config from arguments and defaults:"
	fillupcfg
	writehsconfig
    fi
    if [ "$dhcpmode" != "nm" ]
    then
	[ "$wlanip" == "" ] && logtobothex "? Plugin $pfx: Argument 'wlanip' required unless dhcpmode is 'nm'"
    fi
    logtoboth "* Plugin $pfx: Complete Phase 0"

elif [ "$phase" == "1" ]
then
    #
    # Phase 1 (in nspawn)
    #
    logtoboth "* Plugin $pfx: Start Phase 1"
    plugin_getargs $pfx "$args" "$vldargs" "$rqdargs"
    converty
    installpkgsif network-manager
    #! ispkginstalled network-manager && logtoboth "> Plugin $pfx: Install network-manager" && installpkgsif network-manager
    logtoboth "* Plugin $pfx: Complete Phase 1"
else
    #
    # Plugin Post-install edits
    #
    logtoboth "* Plugin $pfx: Start Phase post-install"
    plugin_getargs $pfx "$args" "$vldargs" "$rqdargs"
    converty
    if [ -f $assetdir/config ]
    then
	parsehsconfig $assetdir/config
	cfgnetman
	[[ "$ipforward" != "" ]] && [[ "$type" == "routed" ]] && enablerouting
    else
	logtoboth "? Plugin $pfx: Hotspot '$hsname' config file '$assetdir/config' not found"
    fi
    if [ "$device" == "usb0" ]
    then
	# set up to blacklist cdc_subset but don't enable
	logtoboth "> Plugin $pfx: Blacklist module cdc_subset may improve USB link detection reliability (but disabled for now)"
	logtoboth "  Undot /etc/modprobe.d/.blacklist-cdc-subset.conf if you're having issues"
	echo "blacklist cdc_subset" >| /etc/modprobe.d/.blacklist-cdc-subset.conf
	# This is neededd b/c cdc_subset and cdc_ether for some crazy reason detect the usb host gadget as eth1
	# Write hack /etc/systemd/network/10-usb.link
	logtoboth "> Plugin $pfx: Write /etc/systemd/network/10-usb.link to work around usb device detected as eth1"
	cat > /etc/systemd/network/10-usb.link <<EOF
[Match]
Driver=cdc_ether 

[Link]
Name=usb0
EOF
    else
	# WiFi connection; Delete WiFi disable if present
	rm -f /etc/sdm/0piboot/055-disable-wifi.sh
    fi

    logtoboth "* Plugin $pfx: Complete Phase post-install"
fi

exit
