#!/bin/bash
# Copyright (c) 2015 SUSE LLC
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

set -e

TEXTDOMAIN='jeos-firstboot'

. /etc/os-release

# Read the optional configuration file
[ -f /usr/share/defaults/jeos-firstboot.conf ] && . /usr/share/defaults/jeos-firstboot.conf
[ -f /etc/jeos-firstboot.conf ] && . /etc/jeos-firstboot.conf

stty_size() {
	set -- `stty size`; LINES=$1; COLUMNS=$2
	# stty size can return zero when not ready or
	# its a serial console
	if [ "$COLUMNS" = "0" -o "$LINES" = "0" ]; then
		LINES=24
		COLUMNS=80
	fi
}
stty_size

# for testing we may run as non root
if [ -w /run ]; then
	export TMPDIR=/run
	# debugging
	if [ -n "$FIRSTBOOT_DEBUG" ]; then
		set -x
		exec 2>/var/log/firstboot-debug
	fi
else
	dry=1
fi

if [ -n "$dry" ]; then
	run() {
		echo "$@"
	}
else
	run() {
		"$@"
	}
fi

modules=()

call_module_hook() {
	local hook="$1"
	for module in "${modules[@]}"; do
		hook_function="${module}_${hook}"
		[ "$(type -t "${hook_function}")" = "function" ] || continue
		"${hook_function}" && true # To not trigger errexit
		ret=$?
		[ $ret -eq 0 ] || return $ret
	done
	return 0
}

if pushd "/usr/share/jeos-firstboot" &>/dev/null; then
	for module in *; do
		if [ -f "${module}" ] && source "${module}"; then
			modules+=("${module}")
		fi
	done
	popd &>/dev/null
fi

dialog_out=`mktemp -qt 'firstboot-XXXXXX'`
cleanup() {
	call_module_hook cleanup
	echo .oOo.oOo.oOo. > $dialog_out
	rm -f "$dialog_out"
	# reenable systemd and kernel logs
	run kill -s SIGRTMAX-10 1
	run setterm -msg on 2>/dev/null || true
}
trap cleanup EXIT

# avoid kernel messages spamming our console
run setterm -msg off 2>/dev/null || true
# Avoid systemd messages spamming our console
run kill -s SIGRTMAX-9 1
# sleep to avoid systemd bug, bsc#1119382
sleep 1

systemd_firstboot_args=('--setup-machine-id')

# If the configuration is not loaded and we are in the first terminal
# instance, make sure that the variables are declared.
JEOS_LOCALE=${JEOS_LOCALE-}
JEOS_KEYTABLE=${JEOS_KEYTABLE-}

result=
list=
password=''

let dh_menu=LINES-15
let dh_text=LINES-5

d(){
	retval=
	while true
	do
		retval=0
		dialog --backtitle "$PRETTY_NAME" --output-fd 3 "$@" 3>"${dialog_out}" || retval=$?
		case $retval in
		  0)
			# need || true as dialog doesn't write newlines
			read result < $dialog_out || true
			return 0
			;;
		  1)
			dialog --yesno $"Do you really want to quit?" 0 0 && exit 1
			continue
			;;
		  255)
			# xargs to remove whitespaces
			result_error="$(xargs -a "$dialog_out")"
			if [ -z "$result_error" ]; then
				dialog --yesno $"Do you really want to quit?" 0 0 && exit 1
				continue
			fi
			logger -p err -t jeos-firstboot "$result_error"
			dialog --msgbox $"Exiting due to error, please check the system log" 0 0
			exit 2
			;;
		esac
	done
}

warn(){
	d --title $"Warning" --msgbox "$1" 6 40
}

menulist()
{
	list=()
	local line
	while read line; do
		list+=("$line" '')
	done < <("$@"||true)
	[ -n "$list" ]
}

# localectl --no-pager list-keymaps does not list aliases (symlinks), but those are used
# by YaST/langset.sh, so we need to show them.
findkeymaps()
{
	list=()
	local line
	while read line; do
		list+=("${line%.map.gz}" '')
	done < <(find /usr/share/kbd/keymaps -name '*.map.gz' -printf "%f\n" | sort -u)
	[ -n "$list" ]
}

findlocales()
{
	list=()
	local l locale
	# List only locales which are both in live-langset-data and glibc-locale(-base)
	for l in /usr/share/langset/*; do
		locale="${l#/usr/share/langset/}"
		[ -d "/usr/lib/locale/${locale}.utf8" ] || continue
		list+=("${locale}" '')
	done
	[ -n "$list" ]
}

if [ -z "$JEOS_LOCALE" ]; then
	default="en_US"
	[ -f /etc/locale.conf ] && locale_lang="$(awk -F= '$1 == "LANG" { split($2,fs,"."); print fs[1]; exit }' /etc/locale.conf)"
	[ -n "$locale_lang" ] && default="$locale_lang"

	list=() # Set by findlocales
	newlocale="$default"
	if ! findlocales; then
		d --msgbox $"No locales found" 0 0
	elif [ "${#list[@]}" -eq 2 ]; then
		newlocale="${list[0]}"
		d --msgbox $"Locale set to $newlocale.\nTo change to a different one, install glibc-locale and use\n'localectl set-locale LANG=ex_AMPLE.UTF-8'." 8 50
	else
		d --default-item "$default" --menu $"Select system locale" 0 0 $dh_menu "${list[@]}"
		newlocale="${result}"
	fi

	JEOS_LOCALE="${newlocale}.UTF-8"
fi

run langset.sh $JEOS_LOCALE || warn $"Setting the locale failed"
systemd_firstboot_args+=("--locale=$JEOS_LOCALE")

if [ -z "$JEOS_KEYTABLE" ]; then
	default="us"
	[ -f /etc/vconsole.conf ] && vconsole_keymap="$(awk -F= '$1 == "KEYMAP" { split($2,fs,"."); print fs[1]; exit }' /etc/vconsole.conf)"
	[ -n "$vconsole_keymap" ] && default="$vconsole_keymap"

	if findkeymaps \
		&& d --default-item "$default" --menu  $"Select keyboard layout" 0 0 $dh_menu "${list[@]}"; then
		if [ -n "$result" ]; then
			JEOS_KEYTABLE="$result"
		fi
	else
		d --msgbox $"Error setting keyboard" 0 0
	fi
fi 

if [ ! -z "$JEOS_LOCALE" -a ! -z "$JEOS_KEYTABLE" ]; then
	# Activate the selected keyboard layout
	run langset.sh "$JEOS_LOCALE" "$JEOS_KEYTABLE" || warn $"Setting the keyboard layout failed"
fi

[ -n "$JEOS_LOCALE" ] && language="${JEOS_LOCALE%%_*}" || language="en"
force_english_license=0
export LANG="$JEOS_LOCALE"

kmscon_available() {
        # kmscon itself is installed
        kmscon --help >/dev/null 2>&1 || return 1
        # At least one monospace font is available
        [ -n "$(fc-match "monospace" 2>/dev/null)" ] || return 1

        return 0
}

fbiterm_available() {
        # fbiterm itself is installed
        fbiterm --help >/dev/null 2>&1 || return 1
        # fbiterm comes with its own fallback font

        return 0
}

if [[ "$(ps h -o tty -p $$)" = tty[0-9]* ]]; then
	# Those languages can't be displayed in the console
	declare -A start_kmscon
	start_kmscon["cs"]=1
	start_kmscon["ja"]=1
	start_kmscon["zh"]=1
	start_kmscon["ko"]=1

	# Relay those settings to the nested instance
	export JEOS_LOCALE JEOS_KEYTABLE
	if [ -n "$JEOS_LOCALE" -a -n "${start_kmscon[${language}]+_}" ]; then
		if kmscon_available; then
			ret_file="$(mktemp)"
			kmscon --silent --font-size 10 --palette vga --no-reset-env -l -- /bin/sh -c "$0; echo \$? > $ret_file; kill \$PPID"
			exit $(cat "$ret_file"; rm -f "$ret_file")
		elif fbiterm_available; then
			exec fbiterm -- "$0"
		else
			# No kmscon or fbiterm, fall back to english
			export LANG="en_US.UTF-8"
			force_english_license=1
		fi
	fi
fi

if [ -z "$JEOS_EULA_ALREADY_AGREED" ]; then
	# Find the location of the EULA
	# An EULA in /etc takes precedence
	EULA_FILE=/etc/YaST2/licenses/base/license.txt
	[ -e "${EULA_FILE}" ] || EULA_FILE=/usr/share/licenses/product/base/license.txt

	# Failsafe: If no license found, quit.
	if ! [ -e "$EULA_FILE" ]; then
		d --msgbox $"No license found - cannot continue" 6 40
		exit 1
	fi

	if [ "$force_english_license" = "0" ]; then
		for i in "${EULA_FILE%.txt}.${JEOS_LOCALE}.txt" \
				"${EULA_FILE%.txt}.${JEOS_LOCALE%%.UTF-8}.txt" \
				"${EULA_FILE%.txt}.${language}.txt"; do
			if [ -e "$i" ]; then
				EULA_FILE="$i"
				break
			fi
		done
	fi

	while ! dialog --backtitle "$PRETTY_NAME" --textbox "$EULA_FILE" $dh_text 85 --and-widget --yesno $"Do you agree with the terms of the license?" 0 0; do
		d --msgbox $"Can not continue without agreement" 6 40
	done
fi

default="$(readlink -f /etc/localtime)"
default="${default##/usr/share/zoneinfo/}"

if [ -z "$JEOS_TIMEZONE" ]; then
	# timedatectl doesn't work as dbus is not up yet
	# menulist timedatectl --no-pager list-timezones
	if menulist awk \
		'BEGIN{print "UTC"; sort="sort"}/^#/{next;}{print $3|sort}END{close(sort)}' \
		/usr/share/zoneinfo/zone.tab \
		&& d --default-item "$default" --menu $"Select time zone" 0 0 $dh_menu "${list[@]}"; then
	if [ -n "$result" ]; then
		JEOS_TIMEZONE="$result"
	fi
	else
		d --msgbox $"Error setting timezone" 0 0
	fi
fi
systemd_firstboot_args+=("--timezone=$JEOS_TIMEZONE")

# systemd-firstboot does not set the timezone if it exists, langset.sh created it
run rm -f /etc/localtime
run systemd-firstboot "${systemd_firstboot_args[@]}"

if [ -z "$JEOS_PASSWORD_ALREADY_SET" ]; then
	while true; do
		d --insecure --passwordbox  $"Enter root password" 0 0
		password="$result"
		d --insecure --passwordbox  $"Confirm root password" 0 0
		if [ "$password" != "$result" ]; then
			d --msgbox $"Entered passwords don't match" 5 40
			continue
		fi
		# don't use that one as we need to switch locale
		#systemd_firstboot_args+=("--root-password-file=$dialog_out")
		if [ -z "$password" ]; then
			warn $"Warning: No root password set.

You cannot log in that way. A debug shell will be started on tty9 just this time. Use it to e.g. import your ssh key." 0 0 || true
		run systemctl start debug-shell.service
		fi
		break
	done
fi

# Do not show the register on non SLE based distributions or if is
# globally disabled
if [ -x /usr/bin/SUSEConnect -a -z "${ID##sle*}" -a -z "${JEOS_HIDE_SUSECONNECT}" ]; then
	d --msgbox $"Please register this image using your existing SUSE entitlement.

As \"root\" use the following command:

 SUSEConnect -e company@example.com -r YOUR_CODE

to register the instance with SCC

Without registration this instance does not have access to updates and
security fixes." 0 0 || true
fi

d --infobox $"Collecting network info ..." 3 33
## Configure initial network settings
#
shopt -s nullglob

for net_path in /sys/class/net/* ; do
	test -f "$net_path" && continue # skip bonding_masters file

	# Only devices having ID_NET_NAME.* attrs
	# Ignore errors if udev not available
	udevadm info -q property -p "$net_path" 2>/dev/null | grep -qs ID_NET_NAME || continue
	# But don't touch WLAN interfaces
	udevadm info -q property -p "$net_path" | grep -qs "DEVTYPE=wlan" && continue

	net_device=${net_path##*/}

	unset IPADDR
	eval `wicked test dhcp4 "$net_device" 2>/dev/null | grep -E "^IPADDR="`
	ip link set down "$net_device" # set link down after probe once done

	# Create a configuration file for each interface that provides
	# an IPADDR
	if [ -n "$IPADDR" ]; then
		printf "STARTMODE=auto\nBOOTPROTO=dhcp\n" \
			> "/etc/sysconfig/network/ifcfg-$net_device"
	fi
done

run sed -i -E 's/^DHCLIENT(6?)_SET_HOSTNAME=.*$/DHCLIENT\1_SET_HOSTNAME=yes/' /etc/sysconfig/network/dhcp

call_module_hook systemd_firstboot

d --infobox $"Applying firstboot settings ..." 3 40 || true
# FIXME: systemd-firstboot doesn't set password if shadow present
if [ -n "$password" ]; then
	run echo "root:$password" | run /usr/sbin/chpasswd
fi

# Look for EFI dir to see if the machine is booted in UEFI mode
EFI_SYSTAB="/sys/firmware/efi/systab"
# modprobe and efivars are not available everywhere, just ignore those cases
run modprobe efivars &>/dev/null || true
if ! [ -f "$EFI_SYSTAB" ]; then
	if [ -f /etc/sysconfig/bootloader ]; then
		run sed -i -e "s/LOADER_TYPE=.*/LOADER_TYPE=grub2/g" /etc/sysconfig/bootloader
	fi
fi

# Test if snapper is available
if [ -x /usr/bin/snapper -a "$(stat --format=%T -f /)" = "btrfs" ]; then
	if ! btrfs qgroup show / &>/dev/null; then
		# Run snapper to setup quota for btrfs
		run /usr/bin/snapper --no-dbus setup-quota || warn $"Could not setup quota for btrfs"
	fi
fi

call_module_hook post
