#!/bin/sh

PROGRAM=setup-interfaces
PREFIX=

PKGS=

for i in ./libalpine.sh $PREFIX/lib/libalpine.sh; do
	[ -e $i ] && . $i && break
done

unconfigured_add() {
	touch $1.noconf
}

unconfigured_detect() {
	local i=
	for i in ${INTERFACES:-$(available_ifaces)}; do
		if [ "$i" != "lo" ]; then
			unconfigured_add $i
		fi
	done
}

unconfigured_get_first() {
	ls *.noconf 2>/dev/null | head -n 1 | sed 's/.noconf//'
}

unconfigured_del() {
	rm -f $1.noconf
}

unconfigured_all_done() {
	local i=
	for i in *.noconf; do
		[ -e $i ] && return 1
	done
	return 0
}

unconfigured_list() {
	local list= i=
	for i in *.noconf; do
		[ -e "$i" ] || continue
		list="${list} ${i%.noconf}"
	done
	echo $list
}

unconfigured_isin() {
	[ -f $1.noconf ]
}

iface_exists() {
	test -e /sys/class/net/$1
}

get_default_addr() {
	# check if dhcpcd is running
	if pidof dhcpcd > /dev/null && [ -f "$ROOT/var/lib/dhcpc/dhcpcd-$1.info" ]; then
		echo dhcp
	elif iface_exists $1; then
		ip addr show $1 | awk '/inet / {print $2}' | head -n 1 | sed 's:/.*::'
	fi
}

get_default_mask() {
	if [ "$1" ] ; then
		ipcalc -m $1 | sed 's/.*=//'
	else
		echo "255.255.255.0"
	fi
}

get_default_gateway() {
	if iface_exists $1; then
		ip route show dev $1 | awk '/^default/ {print $3}'
	fi
}

ipaddr_help() {
	cat <<-__EOF__

		Select the ip address for this interface.

		dhcp				      Dynamic/automatic ip via DHCP
		none				      Do not add any address
		n.n.n.n		(ex: 192.168.0.1)     Static ip
		n.n.n.n/m	(ex: 192.168.0.1/24)  Static ip with mask
		br[0-9]+	(ex: br0)	      Add this interface to a bridge
		bridge[0-9]	(ex: bridge0)	      Add this interface to a bridge

		You will be prompted for netmask if not specified with the address.

	__EOF__
}

bridge_add_port() {
	local bridge=$1 iface=
	shift
	for iface; do
		echo $iface >> $bridge.bridge_ports
		unconfigured_add $bridge
		unconfigured_del $iface
	done
}

bridge_list_ports() {
	if [ -r $1.bridge_ports ]; then
		echo $(cat $1.bridge_ports)
	fi
}

is_bridge() {
	test -r $1.bridge_ports
}

is_wifi() {
	test -d /sys/class/net/$1/phy80211
}

find_essids() {
	local iface=$1
	export essids_list=wlans
	# Supports only open or PSK
	ip link set dev "$iface" up
	iwlist $iface scanning | awk -F: '/ESSID/ { printf "%s ",$2 } /Authentication Suites/ \
		{ printf ":%s\n",$2 }' | grep -v 802.1x | sort -u >$essids_list
	if [ -s $essids_list ]; then
		cat $essids_list
	else
		return 1
	fi
}

config_wpa_supp() {
	local iface=$1
	local essid=$2
	local psk=$3
	local conffile=/etc/wpa_supplicant/wpa_supplicant.conf
	if [ "$auth_type" == "WPA-PSK" ]; then
		cat << EOF >> $conffile
network={
	ssid="$essid"
	key_mgmt=$auth_type
	psk="$psk"
}
EOF
	else
		cat << EOF >> $conffile
network={
	ssid="$essid"
	key_mgmt=$auth_type
}
EOF
	fi

	wpa_supplicant -B -c $conffile -i $iface 2>/dev/null
}

wlan_is_not_open() {
	local iface=$1
	local essid=$2
	auth_type=$(awk -F: '/'"$essid"'/ { print $2 }' $essids_list)
	echo $auth_type | grep -q PSK
	if [ "$?" -eq "0" ]; then
		export auth_type="WPA-PSK"
		return 0
	else
		export auth_type="open"
		return 1
	fi
}

config_iface() {
	local iface=$1
	local prefix=$2
	local default_address=$3
	local address= netmask= gateway= bridge_ports=
	local bridge
	local conf=$prefix$iface.conf
	local answer=

	while [ -n "$ask_bridge" ] && ! is_bridge $iface; do
		ask "Do you want to bridge the interface $iface?" yes
		case "$resp" in
			yes|y) resp=yes; break;;
			no|n) break;;
		esac
	done

	if [ "$resp" = "yes" ]; then
		bridge="br$(echo $iface | sed 's/[^0-9]//g')"
		ask "Name of the bridge you want add $iface to:" $bridge
		bridge_add_port $resp $iface
		return
	fi

	if [ -r "$iface.bridge_ports" ]; then
		bridge_ports=$(echo $(cat $iface.bridge_ports))
		echo "bridge_ports=\"$bridge_ports\"" >> $conf
	fi
	if [ -r "$iface.bond_slaves" ]; then
		bond_slaves=$(echo $(cat $iface.bond_slaves))
		echo "bond_slaves=\"$bond_slaves\"" >> $conf
	fi
	if [ -r "$iface.raw_device" ]; then
		raw_device=$(cat $iface.raw_device)
		echo "raw_device=\"$raw_device\"" >> $conf
	fi
	if is_wifi $iface; then
		apk add --quiet --no-progress wireless-tools wpa_supplicant || prompt_for_interfaces
		echo "Available wireless networks (scanning):"
		if find_essids $iface; then
			ask "Type the wireless network name to connect to:"
			local essid=$resp
			if wlan_is_not_open $iface "$essid"; then
				ask "Type the \"$essid\" network Pre-Shared Key:"
				psk=$resp
			fi
			config_wpa_supp $iface "$essid" "$psk"
		else
			echo -e "\nNo available wireless networks\n"
			prompt_for_interfaces
		fi
	fi
	# use ipcalc to validate the address. we do accept /mask
	# we are no interested in the result, only error code, so
	# we send result to /dev/null
	while ! ipcalc -s -m $address >/dev/null 2>&1; do
		address=${default_address:-$(get_default_addr $iface)}
		[ -z "$address" ] && address="dhcp"
		ask "Ip address for $iface? (or 'dhcp', 'none', '?')" $address
		address=$resp
		case "$resp" in
		'?')	ipaddr_help;;
		"abort") return;;
		"dhcp")
			echo "type=dhcp" >> $conf
			unconfigured_del $iface
			return ;;
		"none")
			echo "type=manual" >> $conf
			unconfigured_del $iface
			return;;
		br[0-9]*|bridge[0-9]*)
			case "$iface" in
				# we dont allow bridge bridges
				br[0-9]*|bridge[0-9]*) continue;;
			esac
			bridge_add_port $resp $iface
			return ;;
		esac
	done

	# extract netmask if entered together with address
	if [ "$address" != "${address%%/*}" ]; then
		netmask=$(ipcalc -s -m $address | cut -d= -f2)
	fi

	# use ipcalc -m to validate netmask. we dont accept <addr>/mask suffix
	# so we pass on a dummy mask to ipcalc.
	while ! ipcalc -s -m $netmask/0 >/dev/null 2>&1; do
		netmask=$(get_default_mask $address)
		ask "Netmask?" $netmask
		netmask=$resp
		[ "$netmask" = "abort" ] && return
	done

	# use ipcalc -m to validate netmask. we dont accept <addr>/mask suffix
	# so we pass on a dummy mask to ipcalc.
	while ! ipcalc -s -m $gateway/0 >/dev/null 2>&1; do
		gateway=$(get_default_gateway $iface)
		[ -z "$gateway" ] && gateway=none
		ask "Gateway? (or 'none')" $gateway
		gateway=$resp
		[ "$gateway" = "abort" ] && return
		[ "$gateway" = "none" ] && gateway=""
		[ -z "$gateway" ] && break
	done

	echo "type=static" >> $conf
	if [ -n "$bridge_ports" ]; then
		echo "bridge_ports=$bridge_ports" >> $conf
	fi
	echo "address=${address%%/*}" >> $conf  #strip off /mask if there
	echo "netmask=$netmask" >> $conf
	echo "gateway=$gateway" >> $conf


	# print summary
	echo "Configuration for $iface:"
	sed 's/^/  /' $conf

	unconfigured_del $iface
}

is_bridge() {
	[ -e /sys/class/net/$1/bridge ] || [ -e $1.bridge_ports ]
}

is_bond_master() {
	[ -e $1.bond_slaves ]
}

unconfigured_available() {
	local local i= iflist=
	for i in $(unconfigured_list); do
		if ! is_bridge $i && ! is_bond_master $i; then
			iflist="${iflist}${iflist:+ }$i"
		fi
	done
	echo $iflist
}

unconfigured_all_are() {
	local i=
	for i; do
		unconfigured_isin $i || return 1
	done
	return 0
}

config_bridge() {
	local bridge=$1 iflist= i= ports=
	while ! unconfigured_all_done; do
		set -- $(unconfigured_available)
		[ $# -eq 0 ] && return 0;
		ports=$(bridge_list_ports $bridge)
		if [ -n "$ports" ]; then
			echo "Bridge ports in $bridge are: $ports"
		fi
		echo "Available bridge ports are: $@"
		ask "Which port(s) do you want add to bridge $bridge? (or 'done')" $1
		case $resp in
			'abort') return 1;;
			'done') return 0;;
		esac
		for i in $resp; do
			if unconfigured_isin $i; then
				bridge_add_port $bridge $i
			else
				echo "$i is not valid"
			fi
		done
	done
}

bond_add_slave() {
	local master=$1 slave=
	shift
	for slave; do
		echo $slave >> $master.bond_slaves
		unconfigured_add $master
		unconfigured_del $slave
	done
}

bond_list_slaves() {
	if [ -r $1.bond_slaves ]; then
		echo $(cat $1.bond_slaves)
	fi
}

config_bond() {
	local master=$1 slaves=
	while ! unconfigured_all_done; do
		set -- $(unconfigured_available)
		[ $# -eq 0 ] && return 0;
		slaves=$(bond_list_slaves $master)
		if [ -n "$slaves" ]; then
			echo "Bond slaves in $master are: $slaves"
		fi
		echo "Available bond slaves are: $@"
		ask "Which slave(s) do you want add to $master? (or 'done')" $1
		case $resp in
			'abort') return 1;;
			'done') return 0;;
		esac
		for i in $resp; do
			if unconfigured_isin $i; then
				bond_add_slave $master $i
			else
				echo "$i is not valid"
			fi
		done
	done
}

config_vlan() {
	local iface=$1 vid= raw_device=
	case $iface in
	*.*)	raw_device=${iface%.*}
		vid=${iface#*.}
		;;
	vlan*)	vid=${iface#vlan}
		ask_which "raw device" "do you want use for $iface" "$(unconfigured_list)"
		echo "$resp" > $iface.raw_device
		return 0
		;;
	esac
	if unconfigured_isin $raw_device || is_bond_master $raw_device; then
		return 0
	fi
	echo "$raw_device is not a valid raw device for $iface"
	return 1
}

usage() {
	cat <<-__EOF__
		usage: setup-interfaces [-bhi]

		Setup network interfaces

		options:
		 -b  Ask for bridging of interfaces
		 -h  Show this help
		 -i  Read new contents of ${ROOT}etc/network/interfaces from stdin
	__EOF__
	exit 1
}

iface_help() {
	cat <<-__EOF__

		Select the interface you wish to configure.

		For advanced configurations, you can also enter:
		br[0-9]+	(ex: br0)	bridge interface
		bridge[0-9]+	(ex: bridge0)	bridge interface
		bond[0-9]+	(ex: bond32)	bonded interface
		vlan[0-9]+	(ex: vlan371)	vlan interface
		eth?.[0-9]+	(ex: eth0.371)	vlan interface
		bond?.[0.9]+	(ex: bond0.371)	vlan interface

		You will be asked which physical interface(s) to
		be used for advanced configurations.

	__EOF__
}
prompt_for_interfaces() {
	init_tmpdir TMP

	cd $TMP
	unconfigured_detect

	index=1
	while ! unconfigured_all_done; do
		echo "Available interfaces are: $(unconfigured_list)."
		echo "Enter '?' for help on bridges, bonding and vlans."
		ask "Which one do you want to initialize? (or '?' or 'done')" \
			$(unconfigured_get_first)
		iface=$resp

		case "$iface" in
			"done") break;;
			'?') iface_help; continue;;
			br[0-9]*|bridge[0-9]*|virbr[0-9]*)
				config_bridge $iface || continue;;
			bond[0-9]*.[0-9]*)
				config_bond ${iface%.*} || continue
				config_iface ${iface%.*} $(printf "%.3d~" $index) none
				index=$(( $index + 1 ))
				config_vlan $iface || continue
				;;
			bond[0-9]*)
				config_bond $iface || continue;;
			*.[0-9]*|vlan[0-9]*)
				config_vlan $iface || continue;;
			*) unconfigured_isin $iface || continue;;
		esac
		config_iface $iface $(printf "%.3d~" $index)
		index=$(( $index + 1 ))
	done

	if [ "$(openrc --sys)" != "LXC" ] || ! ip addr show lo | grep -q 'inet.*127\.0'; then
		echo "type=loopback" > 000~lo.conf
		echo "" > interface
	fi
	hostname=$(cat $ROOT/etc/hostname 2>/dev/null)

	for i in *.conf ; do
		iface=$(basename $i .conf)
		iface=${iface#[0-9]*~}
		bridge_ports=
		bond_slaves=
		raw_device=
		address=
		type=
		gateway=
		. ./$i
		echo "auto $iface" >> interfaces
		echo "iface $iface inet $type" >> interfaces
		if [ -n "$bridge_ports" ]; then
			PKGS="$PKGS bridge"
			echo -e "\tbridge-ports $bridge_ports" >> interfaces
		fi
		if [ -n "$bond_slaves" ]; then
			PKGS="$PKGS bonding"
			echo -e "\tbond-slaves $bond_slaves" >> interfaces
		fi
		if [ -n "$raw_device" ]; then
			echo -e "\tvlan-raw-device $raw_device" >> interfaces
		fi
		case "$iface" in
			*.[0-9]*|vlan[0-9]*) PKGS="$PKGS vlan";;
		esac
		case $type in
		manual)
			echo -e "\tup ip link set \$IFACE up" >> interfaces
			echo -e "\tdown ip link set \$IFACE down" >> interfaces
			;;
		dhcp)
			[ -n "$hostname" ] \
				&& echo -e "\thostname $hostname" >> interfaces
			;;
		static)
			echo -e "\taddress $address" >> interfaces
			echo -e "\tnetmask $netmask" >> interfaces
			[ "$gateway" ] \
				&& echo -e "\tgateway $gateway" >> interfaces
			;;
		esac
		echo "" >> interfaces
	done

	while [ "$answer" != "yes" ] && [ "$answer" != "no" ] ; do
		ask "Do you want to do any manual network configuration?" no
		case $resp in
			y) answer=yes;;
			n) answer=no;;
			*) answer=$resp;;
		esac
	done

	if yesno "$answer"; then
		case "$EDITOR" in
			nano)	apk add nano;;
			vim)	apk add vim;;
		esac
		${EDITOR:-vi} interfaces
	fi

	if [ -n "$PKGS" ]; then
		apk add --quiet $PKGS
	fi

	mkdir -p $ROOT/etc/network
	cp interfaces $ROOT/etc/network/
}

auto_setup() {
	local iface
	local hostname=$(cat $ROOT/etc/hostname 2>/dev/null)
	for iface in $(available_ifaces); do
		if [ "$(cat /sys/class/net/$iface/operstate)" = "up" ]; then
			break
		fi
	done
	[ -z "$iface" ] && return 0
	cat >$ROOT/etc/network/interfaces <<-EOF
	auto lo
	iface lo inet loopback

	auto $iface
	iface $iface inet dhcp
	EOF
	if [ -n "$hostname" ]; then
		echo -e "\thostname $hostname" >> $ROOT/etc/network/interfaces
	fi
}

ask_bridge=
is_xen_dom0 && ask_bridge=1

while getopts "abhip:" opt; do
	case $opt in
		a) auto=1;;
		b) ask_bridge=1;;
		h) usage;;
		i) STDINPUT=1;;
		p) ROOT=$OPTARG;;
	esac
done

mkdir -p $ROOT/etc/network
if [ "$STDINPUT" = "1" ]; then
	cat > $ROOT/etc/network/interfaces
elif [ -n "$auto" ]; then
	auto_setup
else
	prompt_for_interfaces
fi
