# #cloud-config UserData Functions
# vim:set filetype=sh:
# shellcheck shell=sh

# NOTE:  This is only a subset of what cloud-init supports!

INIT_ACTIONS_MAIN="$(insert_before create_default_user userdata_user $INIT_ACTIONS_MAIN)"
INIT_ACTIONS_MAIN="$(insert_after set_hostname \
	"userdata_bootcmd userdata_groups userdata_users userdata_write_files userdata_ntp userdata_package_update userdata_package_upgrade userdata_packages" \
	$INIT_ACTIONS_MAIN)"
INIT_ACTIONS_MAIN="$(insert_after set_ssh_keys ssh_authorized_keys $INIT_ACTIONS_MAIN)"
INIT_ACTIONS_FINAL="$INIT_ACTIONS_FINAL userdata_runcmd"

get_userdata() {
	IFS="/"
	yx -f "$TINY_CLOUD_VAR/user-data" $1 2>/dev/null
	unset IFS
}

init__userdata_user() {
	local name="$(get_userdata user/name)"
	if [ -z "$name" ]; then
		name="$(get_userdata user)"
		if [ -n "$(get_userdata user/$name)" ]; then
			log -s err "user/name is required"
			return
		fi
	fi
	if get_userdata | grep -q -x users; then
		local default_user="$(get_userdata users/1)"
		if [ "$default_user" != "default" ]; then
			CLOUD_USER="$(get_userdata users/1/name)"
			CLOUD_USER_HOMEDIR="$(get_userdata users/1/homedir)"
			CLOUD_USER_SHELL="$(get_userdata users/1/shell)"
			CLOUD_USER_PRIMARY_GROUP="$(get_userdata users/1/primary_group)"
			CLOUD_USER_GECOS="$(get_userdata users/1/gecos)"
			return 0
		fi
	fi

	local homedir="$(get_userdata user/homedir)"
	local shell="$(get_userdata user/shell)"
	local primary_group="$(get_userdata user/primary_group)"
	local gecos="$(get_userdata user/gecos)"

	CLOUD_USER="${name:-$CLOUD_USER}"
	CLOUD_USER_HOMEDIR="${homedir:-$CLOUD_USER_HOMEDIR}"
	CLOUD_USER_SHELL="${shell:-$CLOUD_USER_SHELL}"
	CLOUD_USER_PRIMARY_GROUP="${primary_group:-$CLOUD_USER_PRIMARY_GROUP}"
	CLOUD_USER_GECOS="${gecos:-$CLOUD_USER_GECOS}"
}

set_ssh_authorized_keys_for() {
	local user="$1"
	local userdata_path="$2"
	local sshkeys="$(get_userdata $userdata_path)"
	if [ -z "$sshkeys" ]; then
		return
	fi

	local pwent="$(getent passwd "$user")"
	if [ -z "$pwent" ]; then
		log -i -t "$phase" err "$ACTION: failed to find user $user"
		return 1
	fi
	local group=$(echo "$pwent" | cut -d: -f4)
	local ssh_dir="${ROOT}$(echo "$pwent" | cut -d: -f6)/.ssh"
	local keys_file="$ssh_dir/authorized_keys"

	if [ ! -d "$ssh_dir" ]; then
		mkdir -p "$ssh_dir"
		chmod 700 "$ssh_dir"
	fi

	touch "$keys_file"
	chmod 600 "$keys_file"
	$MOCK chown -R "$user:$group" "$ssh_dir"
	for i in $sshkeys; do
		local key="$(get_userdata $userdata_path/$i)"
		if [ -n "$key" ]; then
			echo "$key" >> "$keys_file"
		fi
	done
}

init__ssh_authorized_keys() {
	if [ -z "$CLOUD_USER" ]; then
		return
	fi
	set_ssh_authorized_keys_for "$CLOUD_USER" ssh_authorized_keys
}

init__userdata_bootcmd() {
	# run bootcmd
	local bootcmds="$(get_userdata bootcmd)"
	for i in $bootcmds; do
		local cmd="$(get_userdata bootcmd/"$i")"
		sh -c "$cmd"
	done
}

# write_file <path> <mode> <owner> <encoding> <append>
write_file() {
	# Defaults used are the same as for full cloud-init "spec":
	# https://cloudinit.readthedocs.io/en/latest/reference/modules.html#write-files
	local path="$1"
	local mode="${2:-0644}"
	local owner="${3:-root:root}"
	local encoding="${4:-text/plain}"
	local append="${5:-false}"

	if [ "$append" != "true" ] && [ "$append" != "false" ]; then
		log err "append must be true or false"
		return
	fi

	local tmpfile="$(mktemp $TINY_CLOUD_VAR/user-data.write_files.XXXXXX)"

	case "$encoding" in
		gzip|gz|gz+base64|gzip+base64|gz+b64|gzip+b64)
			base64 -d | gzip -d > "$tmpfile"
			;;
		base64|b64)
			base64 -d > "$tmpfile"
			;;
		text/plain)
			cat > "$tmpfile"
			;;
	esac

	if [ "$append" = "true" ]; then
		cat "$tmpfile" >> "$path"
	else
		cat "$tmpfile" > "$path"
	fi
	rm -f "$tmpfile"

	chmod "$mode" "$path"
	# mocked as we do not know which users we could use in testing
	# this way we can check the proper invocation at least
	$MOCK chown "$owner" "$path"
}

init__userdata_write_files() {
	local files="$(get_userdata write_files)"

	for i in $files; do
		local path="$(get_userdata write_files/$i/path)"
		if [ -z "$path" ]; then
			continue
		fi

		mkdir -p "$(dirname "$ROOT/$path")"
		get_userdata write_files/$i/content | write_file "$ROOT/$path" \
			"$(get_userdata write_files/$i/permissions)" \
			"$(get_userdata write_files/$i/owner)" \
			"$(get_userdata write_files/$i/encoding)" \
			"$(get_userdata write_files/$i/append)"
	done
}

init__userdata_ntp() {
	local ntp_enabled="$(get_userdata ntp/enabled)"
	if [ "$ntp_enabled" != "yes" ] && [ "$ntp_enabled" != "true" ]; then
		return
	fi
	local ntp_client="$(get_userdata ntp/ntp_client)"
	local svc= pkg=
	case "$ntp_client" in
		busybox)
			svc=ntpd
			;;
		chrony|"")
			pkg=chrony
			svc=chronyd
			;;
		openntpd)
			pkg=openntpd
			svc=openntpd
			;;
	esac
	if [ -n "$pkg" ]; then
		$MOCK apk add "$pkg"
	fi
	if [ -n "$svc" ]; then
		$MOCK rc-update add "$svc" default
		$MOCK rc-service "$svc" start
	fi
}

init__userdata_package_update() {
	local update="$(get_userdata package_update)"
	if [ "$update" = "true" ]; then
		$MOCK apk update
	fi
}

init__userdata_package_upgrade() {
	local upgrade="$(get_userdata package_upgrade)"
	if [ "$upgrade" = "true" ]; then
		$MOCK apk upgrade
	fi
}

init__userdata_packages() {
	local packages="$(get_userdata packages)"
	local pkgs=
	for i in $packages; do
		pkgs="$pkgs $(get_userdata packages/$i)"
	done
	if [ -n "$pkgs" ]; then
		$MOCK apk add $pkgs
	fi
}

init__userdata_runcmd() {
	local runcmds="$(get_userdata runcmd)"
	for i in $runcmds; do
		local cmd="$(get_userdata runcmd/$i)"
		sh -c "$cmd"
	done
}

init__userdata_groups() {
	local groups="$(get_userdata groups)"
	for i in $groups; do
		local group="$(get_userdata groups/$i)"
		$MOCK addgroup $group
	done
}

in_list() {
	local i needle="$1"
	shift
	for i in "$@"; do
		if [ "$i" = "$needle" ]; then
			return 0
		fi
	done
	return 1
}

init__userdata_users() {
	local users="$(get_userdata users)"
	for i in $users; do
		local name="" gecos="" homedir="" shell="" primary_group="" groups=""
		local system=false no_create_home=false lock_passwd=true
		local keys="$(get_userdata users/$i)"
		if [ "$i" = 1 ] && [ "$keys" = "default" ]; then
			continue
		fi
		if in_list name $keys; then
			name="$(get_userdata users/$i/name)"
		else
			continue
		fi
		if in_list gecos $keys; then
			gecos="$(get_userdata users/$i/gecos)"
		fi
		if in_list homedir $keys; then
			homedir="$(get_userdata users/$i/homedir)"
		fi
		if in_list shell $keys; then
			shell="$(get_userdata users/$i/shell)"
		fi
		if in_list primary_group $keys; then
			primary_group="$(get_userdata users/$i/primary_group)"
		fi
		if in_list system $keys; then
			system="$(get_userdata users/$i/system)"
		fi
		if in_list no_create_home $keys; then
			no_create_home="$(get_userdata users/$i/no_create_home)"
		fi

		if getent passwd "$user" >/dev/null; then
			log -i -t "$phase" info "$ACTION: user $user already exists"
		else
			if [ "$system" != "true" ]; then
				unset system
			fi
			if [ "$no_create_home" != "true" ]; then
				unset no_create_home
			fi
			$MOCK adduser -D ${gecos:+-g "$gecos"} ${homedir:+-h "$homedir"} ${shell:+-s "$shell"} ${primary_group:+-G "$primary_group"} ${system:+-S} ${no_create_home:+-H} "$name"
		fi

		if in_list lock_passwd $keys; then
			lock_passwd="$(get_userdata users/$i/lock_passwd)"
		fi

		if [ "$lock_passwd" != "false" ]; then
			echo "$name:*" | $MOCK chpasswd -e
		fi

		if in_list ssh_authorized_keys $keys; then
			set_ssh_authorized_keys_for "$name" users/$i/ssh_authorized_keys
		fi

		if in_list groups $keys; then
			groups="$(get_userdata users/$i/groups | tr ',' ' ')"
			local group
			for group in $groups; do
				$MOCK addgroup "$name" "$group"
			done
		fi
		if in_list doas $keys; then
			if [ -d "$ETC/doas.d" ]; then
				touch "$ETC/doas.d/$name.conf"
				chmod 640 "$ETC/doas.d/$name.conf"
			fi
			local j
			for j in $(get_userdata users/$i/doas); do
				local line="$(get_userdata users/$i/doas/$j)"
				if [ -d "$ETC/doas.d" ]; then
					echo "$line" >> "$ETC/doas.d/$name.conf"
				elif [ -f "$ETC/doas.conf" ]; then
					add_once "$ETC/doas.conf" "$line"
				fi
			done
		fi
	done
}
