#!/bin/sh
# Copyright (C) 2026 Aitor <aitor@genuen.org>
#
# Maintainer-script and admin helper for packages built with dh-66.
# Analogous to s6-helper / openrc-helper: does not Depend on 66 itself.

set -e

DEFAULT_TREE="${DEFAULT_TREE:-global}"
SERVICE_SYSTEM="${SERVICE_SYSTEM:-/usr/share/66/service}"
SERVICE_ADMIN="${SERVICE_ADMIN:-/etc/66/service}"
LIVEDIR="${LIVEDIR:-/run/66}"
SCANDIR="${SCANDIR:-${LIVEDIR}/scandir/0}"

have_66() {
	command -v 66 >/dev/null 2>&1
}

# True when 66's scandir is supervised (matches lib66 scandir_ok()).
# /run/66 alone is not enough: mkdir can exist without s6-svscan.
is_66_live() {
	local ctl="${SCANDIR}/.s6-svscan/control"

	[ -e "$ctl" ] || return 1
	# Open O_WRONLY without writing. Shell redirects are blocking on fifos, so
	# use a short timeout: ENXIO => not running; hang => treat as not live.
	# Avoid ": >fifo" which would inject data into svscan's control pipe.
	if command -v timeout >/dev/null 2>&1; then
		timeout 0.1 sh -c 'exec 3>"$1"' sh "$ctl" 2>/dev/null
	else
		# Best-effort without timeout(1): directory presence only.
		[ -d "${SCANDIR}/.s6-svscan" ]
	fi
}

# When genuen-rc-select has chosen s6-rc, skip runtime start/stop.
# Tree enable still runs so a later switch to 66 has membership ready.
is_66_rc_selected() {
	local target

	[ -L /etc/s6-linux-init/manager ] || return 0
	target=$(readlink -f /etc/s6-linux-init/manager 2>/dev/null || readlink /etc/s6-linux-init/manager 2>/dev/null || true)
	case "$target" in
		*/66/*) return 0 ;;
		*/s6-rc/*) return 1 ;;
		*) return 0 ;;
	esac
}

run_or_error() {
	if ! "$@"; then
		eval "${ERROR_HANDLER:-exit 1}"
	fi
}

# Soft variant for enable/start during dpkg: never break configure.
run_or_warn() {
	local what="$1"
	shift
	if ! "$@"; then
		echo "warning: 66-helper: ${what} failed for ${SCRIPT} (non-fatal)" >&2
		return 0
	fi
}

policy_allows() {
	local action="$1"

	[ -x /usr/sbin/policy-rc.d ] || return 0
	set +e
	/usr/sbin/policy-rc.d "${SCRIPT}" "${action}"
	local rc=$?
	set -e
	case $rc in
		0) return 0 ;;
		101)
			echo "66-helper: ${SCRIPT}: ${action} action denied by policy-rc.d" >&2
			return 1
			;;
		*)
			echo "66-helper: ${SCRIPT}: ${action} action denied by policy-rc.d" >&2
			return 1
			;;
	esac
}

service_frontend_exists() {
	local name="$1"

	[ -e "${SERVICE_SYSTEM}/${name}" ] || [ -e "${SERVICE_ADMIN}/${name}" ]
}

ensure_tree() {
	local tree="${1:-$DEFAULT_TREE}"

	have_66 || return 0
	# resolve succeeds if the tree already exists
	if 66 tree resolve "$tree" >/dev/null 2>&1; then
		return 0
	fi
	echo "66-helper: creating tree ${tree}" >&2
	66 tree create "$tree" >/dev/null 2>&1 || true
}

# Enable SCRIPT in TREE (TREE empty => skip enable, e.g. disable= in .66).
configure_tree() {
	if [ -z "${TREE:-}" ]; then
		return 0
	fi
	if ! have_66; then
		echo "warning: 66-helper: 66 not available, skip enable for ${SCRIPT}" >&2
		return 0
	fi
	if ! service_frontend_exists "$SCRIPT"; then
		echo "warning: 66-helper: frontend for ${SCRIPT} not under ${SERVICE_SYSTEM} or ${SERVICE_ADMIN}; skip enable" >&2
		return 0
	fi
	ensure_tree "$TREE"
	# Soft-fail: dependency frontends may arrive later in the same apt run
	# (66-policy / udev / … not configured yet), or parse stubs may be missing.
	run_or_warn "enable ${SCRIPT} in tree ${TREE}" 66 -t "$TREE" enable "$SCRIPT"
}

disable_service() {
	if ! have_66; then
		return 0
	fi
	66 disable "$SCRIPT" >/dev/null 2>&1 || true
}

start_service() {
	if ! have_66 || ! is_66_live || ! is_66_rc_selected; then
		return 0
	fi
	policy_allows start || return 0
	# Soft-fail: scandir may race or service may not be startable yet.
	run_or_warn "start ${SCRIPT}" 66 start "$SCRIPT"
}

stop_service() {
	if ! have_66 || ! is_66_live || ! is_66_rc_selected; then
		return 0
	fi
	policy_allows stop || return 0
	# Soft-fail: service may never have been started in a chroot image.
	66 stop "$SCRIPT" >/dev/null 2>&1 || true
}

restart_service() {
	if ! have_66 || ! is_66_live || ! is_66_rc_selected; then
		return 0
	fi
	policy_allows restart || return 0
	66 stop "$SCRIPT" >/dev/null 2>&1 || true
	run_or_warn "start ${SCRIPT}" 66 start "$SCRIPT"
}

preinst_chmod() {
	local action="${1}"

	if [ "$action" = install ] && [ -n "${2:-}" ] && [ -e "/etc/init.d/${SCRIPT}" ]; then
		chmod +x "/etc/init.d/${SCRIPT}" >/dev/null || true
	fi
}

preinst_stop() {
	local action="${1}"

	if [ "$action" = upgrade ]; then
		stop_service
	fi
}

postinst() {
	local action="${1}"

	case "$action" in
		configure|abort-upgrade|abort-deconfigure|abort-remove)
			;;
		*)
			return 0
			;;
	esac

	configure_tree
	start_service
}

postinst_restart() {
	local action="${1}"
	local previous="${2:-}"

	case "$action" in
		configure|abort-upgrade|abort-deconfigure|abort-remove)
			;;
		*)
			return 0
			;;
	esac

	configure_tree
	if [ -n "$previous" ]; then
		restart_service
	else
		start_service
	fi
}

postinst_nostart() {
	local action="${1}"

	case "$action" in
		configure|abort-upgrade|abort-deconfigure|abort-remove)
			;;
		*)
			return 0
			;;
	esac

	configure_tree
}

prerm_norestart() {
	local action="${1}"

	if [ "$action" = remove ]; then
		stop_service
	fi
}

prerm() {
	stop_service
}

postrm() {
	local action="${1}"

	if [ "$action" = remove ] && [ -x "/etc/init.d/${SCRIPT}" ]; then
		chmod -x "/etc/init.d/${SCRIPT}" >/dev/null || true
	fi

	if [ "$action" != remove ] && [ "$action" != purge ]; then
		return 0
	fi

	disable_service
	stop_service
}

# --- admin CLI ---

admin_enable() {
	SCRIPT="${1:?usage: 66-helper enable <service> [tree]}"
	TREE="${2:-$DEFAULT_TREE}"
	configure_tree
}

admin_disable() {
	SCRIPT="${1:?usage: 66-helper disable <service>}"
	disable_service
	stop_service
}

admin_start() {
	SCRIPT="${1:?usage: 66-helper start <service>}"
	start_service
}

admin_stop() {
	SCRIPT="${1:?usage: 66-helper stop <service>}"
	stop_service
}

usage() {
	cat >&2 <<EOF
Usage: 66-helper <action> [args]

Maintainer-script actions (env: SCRIPT, TREE, ERROR_HANDLER):
  preinst_chmod | preinst_stop
  postinst | postinst_restart | postinst_nostart
  prerm | prerm_norestart | postrm

Admin actions:
  enable <service> [tree]   (default tree: ${DEFAULT_TREE})
  disable <service>
  start <service>
  stop <service>
EOF
	exit 2
}

action="${1:-}"
[ -n "$action" ] || usage
shift

case "$action" in
	preinst_chmod|preinst_stop|postinst|postinst_restart|postinst_nostart|prerm|prerm_norestart|postrm)
		"$action" "$@"
		;;
	enable)
		admin_enable "$@"
		;;
	disable)
		admin_disable "$@"
		;;
	start)
		admin_start "$@"
		;;
	stop)
		admin_stop "$@"
		;;
	*)
		usage
		;;
esac
