#!/bin/sh
# /usr/libexec/rpcd/shater — rpcd exec plugin backing luci-app-shater.
#
# Registers the ubus object "shater" (object name == this file's name) with two
# read-side methods the thin LuCI launcher calls over ubus:
#
#   status      -> passthrough of `shaterd status`
#   mint_token  -> passthrough of `shaterd mint-token` ({"token":"..."} | {"error":"..."})
#
# The status object is whatever apply.Status marshals (shater/apply/apply.go is the
# only definition; this script never parses or reshapes it), plus the one field
# `shaterd status` splices in itself. As of 2026-07-27 that is:
#
#   daemon_answered, running, engine_running, enabled, active, table, plane,
#   traffic, hash, kill_switch, panel_port, config_readable, config_error,
#   can_rollback, warnings, started_unix, uptime_seconds
#
# Three of those are load-bearing for the caller and easy to misread:
#
#   daemon_answered — WHERE THE OBJECT CAME FROM, and the only field that says so by
#       contract (cmd/shaterd/main.go, statusDaemonAnsweredKey). true: a running
#       daemon answered over the control socket. false: this is the OFFLINE STUB —
#       the apply.Status zero value plus a UCI and kernel read — and the fields only
#       a live daemon can know (running/engine_running/plane/traffic/hash/warnings/
#       uptime) are placeholders. dashboard.js keys "daemon: down" off this.
#   running / engine_running — the ENGINE's liveness, not the daemon's. `running`
#       was a hardcoded true until a8970b8ac (2026-07-26) and is now `engineUp`, so
#       a healthy daemon with a dead engine reports running=false. The daemon's own
#       liveness is not a field of apply.Status at all.
#   config_readable — whether the daemon could READ the configuration. When false,
#       enabled/kill_switch/panel_port are zero values and mean NOTHING. Note the
#       stub above never sets it, so its false is not a failed read either — a
#       consumer must check daemon_answered first. dashboard.js does.
#
# EXIT CODE: `shaterd status` now exits 1 when no daemon answered, and this script
# deliberately ignores that — command substitution below keeps only stdout. The stub
# is still worth relaying (it carries the real UCI and nft-table readings, and it
# says what it is), and dropping it would blank the LuCI page instead of degrading
# it. The wedged case is the one where the exit code matters, and it reports itself
# by printing NOTHING: the case below then emits the error object.
#
# Why shell out to shaterd instead of talking to /var/run/shaterd.ctl directly:
# a reliable AF_UNIX client is NOT guaranteed on stock OpenWrt (busybox `nc` is
# usually built without `-U`; socat/ucode-socket aren't in the base image). shaterd
# already carries the control-socket client, so a tiny `shaterd mint-token` verb is
# the robust, dependency-free bridge. See docs-shater/ARCHITECTURE.md §2.
#
# rpcd exec contract (see the openwrt-ubus-rpcd skill):
#   `<script> list`          -> print ONE JSON object mapping method -> arg signature
#   `<script> call <method>` -> args JSON on stdin (unused here); print JSON reply on stdout
# busybox ash only — no bashisms.

SHATERD=/usr/bin/shaterd

# emit <cmd...> output only if it looks like a JSON object, else a JSON error so the
# caller (LuCI rpc.declare) ALWAYS receives a parseable object, never a blank/garbage
# reply that would register as an empty ubus result.
emit_json() {
	out=$("$@" 2>/dev/null)
	case "$out" in
		'{'*) printf '%s\n' "$out" ;;
		*)    printf '{"error":"shaterd unavailable"}\n' ;;
	esac
}

case "$1" in
	list)
		# Both methods take no arguments.
		echo '{ "status": {}, "mint_token": {} }'
		;;
	call)
		case "$2" in
			status)
				emit_json "$SHATERD" status
				;;
			mint_token)
				emit_json "$SHATERD" mint-token
				;;
			*)
				echo '{"error":"unknown method"}'
				;;
		esac
		;;
esac
