#!/usr/bin/env bash
# opensim COMMAND [ARGUMENTS]: set up and manage OpenSimulator (see README.md). The commands are in libexec/.

set -e
here=$(dirname "$(realpath "${BASH_SOURCE[0]}" 2>/dev/null || echo "${BASH_SOURCE[0]}")")
libexec=$(cd "$here/../libexec" && pwd)

COMMANDOPTS=setup,status,ports,start,stop,restart,console,screen,enable,disable,web,next,ban,save,load,backup,rest,install-dotnet
HELP=$(
	cat <<USAGE

  setup           make a grid, a standalone or a simulator that joins a grid
  status          the instances of this machine, running or not
  ports           list instance ports, publishing options or firewall rules
  start           start grids and simulators
  stop            stop grids and simulators
  restart         stop, then start grids and simulators
  console|screen  attach to the console of a running instance
  enable|disable  enable or disable instances and grid features
  web             inspect grid services and print web-server snippets
  next            find free ports or region locations
  ban             prepare user cleanup and estate ban files
  save|load       save or restore IAR and OAR archives
  backup          back up grid configuration, data, and databases
  rest            use the remote OpenSim console client
  install-dotnet  install the .NET runtime OpenSimulator needs

System information
  --layout --format=env  print the resolved layout and identities as shell exports
USAGE
)
# Command help uses the command's own usage, before dispatch can prepare a setup or elevate privileges.
if [[ -n "${1:-}" && ",$COMMANDOPTS," == *",$1,"* && -x "$libexec/$1" ]]; then
	for argument in "${@:2}"; do
		case "$argument" in -h | --help) exec "$libexec/$1" "${@:2}" ;; esac
	done
fi
export PRESERVE_ENV=PATH,OPENSIM_API,CONTAINER_CONNECTION,DEFAULT_OWNER_NAME,DEFAULT_OWNER_PASSWORD,DOTNET_SYSTEM_NET_DISABLEIPV6,OPENSIM_DB_ADMIN_PASS,OPENSIM_DB_ADMIN_USER,OPENSIM_DB_HOST,OPENSIM_DB_NAME,OPENSIM_DB_PASS,OPENSIM_DB_PORT,OPENSIM_DB_USER,APT_SOURCE_DIR

# $COMMAND and other runtime values are detected by lib/common
. "$here/../src/lib/common" || die could not source opensim common helpers

if [[ "${1:-}" == --layout ]]; then
	shift
	format=
	while (($#)); do
		case "$1" in
		--format)
			(($# > 1)) || die "--format requires a value"
			format=$2
			shift 2
			;;
		--format=*)
			format=${1#*=}
			shift
			;;
		*) die "unexpected argument for --layout: $1" ;;
		esac
	done
	[[ "$format" == env ]] || die "--layout requires --format=env"
	layout=$(engine GET /layout) || die "could not read the OpenSim system layout"
	layout=$(php -r '
		$layout = json_decode(stream_get_contents(STDIN), true, flags: JSON_THROW_ON_ERROR);
		foreach (["etcRoot", "varRoot", "logsRoot"] as $key) {
			$path = realpath($layout["data"]["attributes"][$key] ?? "");
			if ($path === false) {
				fwrite(STDERR, "Layout path does not exist: " . ($layout["data"]["attributes"][$key] ?? "") . "\n");
				exit(1);
			}
			$layout["data"]["attributes"][$key . "Realpath"] = $path;
		}
		echo json_encode($layout, JSON_THROW_ON_ERROR);
	' <<<"$layout") || die "could not resolve the OpenSim layout paths"
	jq -er '
		.data | select(.type == "layout") | .attributes |
		[
			["currentUser", "CURRENT_USER"],
			["currentUid", "CURRENT_UID"],
			["currentGroup", "CURRENT_GROUP"],
			["currentGid", "CURRENT_GID"],
			["systemUser", "OPENSIM_SYSTEM_USER"],
			["systemUid", "OPENSIM_SYSTEM_UID"],
			["systemGroup", "OPENSIM_SYSTEM_GROUP"],
			["systemGid", "OPENSIM_SYSTEM_GID"],
			["etcRoot", "OPENSIM_ETC_ROOT"],
			["varRoot", "OPENSIM_VAR_ROOT"],
			["logsRoot", "OPENSIM_LOGS_ROOT"],
		    ["etcRootRealpath", "CANONICAL_ETC_ROOT"],
			["varRootRealpath", "CANONICAL_VAR_ROOT"],
			["logsRootRealpath", "CANONICAL_LOGS_ROOT"]
		] as $fields |
		. as $attributes |
		select(all($fields[]; .[0] as $key | $attributes | has($key) and .[$key] != null)) |
		$fields[] as [$key, $variable] |
		"\($variable)=\(.[$key] | tostring | @sh)"
	' <<<"$layout" || die "the OpenSim layout response is incomplete"
	exit 0
fi

system_user=$(run system-user 2>/dev/null)
debug_log system_user $system_user

case ${COMMAND:-} in
'')
	help
	end
	;;

setup)
	for argument in "$@"; do
		case "$argument" in
		--prepare | --prepare=*) exec "$libexec/setup" "$@" ;;
		esac
	done
	# A machine without the system layout gets it from the engine (the account, the folders, opensim.conf), which needs root
	if [[ "$(run needs-layout 2>/dev/null)" == "yes" ]]; then
		os=$(uname -s)
		case $os in
		Linux)
			debug_log prepare-layout as root
			as_root php "$here/../src/php/run.php" prepare-layout || exit 1
			;;
		Darwin)
			end $COMMAND not implemented for macOS
			;;
		*)
			end 1 $os operating system not supported
			;;
		esac
	else
		debug_log "prepare-layout not needed"
	fi
	# "$libexec/$COMMAND" "$@"
	run_setup "$@"
	# as_root "$libexec/$COMMAND" "$@"
	;;

install-dotnet)
	log install-dotnet as root
	# The runtime is installed for the whole system: as root, whoever calls
	as_root "$libexec/$COMMAND" "$@"
	;;

*)
	if [ -e "$libexec/$COMMAND" ]; then
		as_system_user "$libexec/$COMMAND" "$@"
		# as_root "$libexec/$COMMAND" "$@"
	else
		die "Unrecognized command $COMMAND"
	fi
	;;
esac
