#!/bin/sh
# Imprion PoS - why the till's power menu does or does not offer Restart and
# Power Off (F80).
#
# READ-ONLY. It starts nothing, stops nothing, changes no setting and takes no
# power action. It is safe to run on a terminal that is selling.
#
# The one write it makes is its own pid into gnome-shell's `cgroup.procs`, and
# it is there for a reason worth knowing: a probe run as `sudo -u pos` from an
# ssh shell is in the SSH session, which is neither local nor active, so logind
# answers "challenge" on a perfectly healthy terminal. Joining the session's
# cgroup is the only way to ask the question the till itself is asking. It
# affects nothing once the probe exits.
#
# HOW TO READ IT. The sections are in the order the answer is decided, so the
# FIRST section whose verdict is not the expected one names the cause and the
# ones after it are context:
#
#   1  gnome-session CanShutdown   expect "true"   <- the gate that hid the menu
#   2  disable-log-out             expect "false"  <- the key section 1 reads
#   3  logind CanPowerOff/CanReboot expect "yes"   <- polkit's answer, in situ
#   4  pkcheck for the seat0 session               <- polkit, subject-accurate
#   5  pkaction implicit policy                    <- what stock policy allows
#   6  polkitd's own log                           <- a JS error disables a file
#   7  logind sessions                             <- who else holds one
#   8  inhibitors                                  <- who is blocking shutdown
#   9  the shell extension                         <- ours, and it edits the menu
#  10  the policy files as installed
#  11  gnome-shell's own gating source, off this image
#
# MEASURED, 2026-09-24, on a lab terminal carrying the shipped 1.0.0-14
# platform: sections 3, 4, 5 and 6 were all perfect - polkit ALLOWED all four
# login1 power actions for the seat0 `pos` session and logind answered "yes" -
# while section 1 answered `false` and the menu offered Suspend and nothing
# else. `disable-log-out` is gnome-session's gate on the WHOLE end of session
# (gsm_manager_can_shutdown), not just on the Log Out entry, and gnome-shell
# takes Restart and Power Off straight from that one D-Bus answer. That is why
# this script asks gnome-session first and polkit fourth.
#
# Run as root: sections 1, 3 and 4 have to ask about the operator's graphical
# session, and only root can step into it or query polkit about it.
#
#   ssh root@<terminal> /usr/lib/imprion/power-diagnostics
#   ssh root@<terminal> /usr/lib/imprion/power-diagnostics > power.txt

set -u

POS_USER=pos
ACTIONS='org.freedesktop.login1.power-off
org.freedesktop.login1.power-off-multiple-sessions
org.freedesktop.login1.reboot
org.freedesktop.login1.reboot-multiple-sessions'

section() {
	echo
	echo "=============================================================="
	echo "$*"
	echo "=============================================================="
}

note() {
	echo "  ! $*"
}

# have <binary> - is it on this terminal at all.
have() {
	command -v "$1" >/dev/null 2>&1
}

POS_UID="$(id -u "${POS_USER}" 2>/dev/null || echo '')"
SHELL_PID="$(pgrep -u "${POS_USER}" -x gnome-shell 2>/dev/null | head -1)"
SEAT0_SESSION="$(loginctl --no-legend list-sessions 2>/dev/null |
	awk -v u="${POS_USER}" '$3 == u && $4 == "seat0" { print $1; exit }')"

# in_session <command...> - run a command as `pos`, INSIDE the operator's
# graphical session.
#
# THIS IS NOT FUSSINESS AND GETTING IT WRONG INVENTS A BUG. logind and polkit
# decide from the CALLING process's session, and a `sudo -u pos` or `su - pos`
# launched from an ssh shell is in the ssh session: not local, not on seat0, so
# `CanPowerOff` answers "challenge" and a perfectly working terminal reads as
# broken. Joining the shell's own cgroup puts the probe in session
# ${SEAT0_SESSION}, which is the session the person at the till is pressing a
# button in.
in_session() {
	if [ -z "${SHELL_PID}" ]
	then
		echo "(no gnome-shell for ${POS_USER}: no graphical session to ask)"
		return 1
	fi

	cgroup="/sys/fs/cgroup$(awk -F: '/^0::/ { print $3 }' "/proc/${SHELL_PID}/cgroup" 2>/dev/null)"

	sh -c '
		echo $$ > "$1/cgroup.procs" 2>/dev/null || exit 97
		shift
		exec setpriv --reuid="$0" --regid="$0" --init-groups \
			env XDG_RUNTIME_DIR="/run/user/$0" \
			    DBUS_SESSION_BUS_ADDRESS="unix:path=/run/user/$0/bus" \
			    "$@"
	' "${POS_UID}" "${cgroup}" "$@" 2>&1
}

echo "Imprion PoS - power menu diagnostics (F80)"
echo "host:           $(hostname 2>/dev/null)"
echo "date:           $(date -Is 2>/dev/null)"
echo "uid:            $(id -u) ($(id -un))"
echo "pos uid:        ${POS_UID:-<no pos account>}"
echo "gnome-shell:    $(gnome-shell --version 2>/dev/null || echo '<not installed>') (pid ${SHELL_PID:-none})"
echo "gnome-session:  $(dpkg-query -W -f='${Version}' gnome-session-bin 2>/dev/null || echo '?')"
echo "seat0 session:  ${SEAT0_SESSION:-<none>}"
sed -n 's/^IMPRION_RELEASE=/release:        /p;s/^IMPRION_BUILD_ID=/build:          /p' \
	/etc/imprion-release 2>/dev/null

[ "$(id -u)" -eq 0 ] || note "NOT ROOT - sections 1, 3, 4 and 6 will be wrong or empty."

section "1. gnome-session: org.gnome.SessionManager.CanShutdown   EXPECT true"
echo "   gnome-shell's Restart and Power Off entries are this answer and"
echo "   nothing else (misc/systemActions.js _updateHaveShutdown)."
in_session gdbus call --session \
	-d org.gnome.SessionManager \
	-o /org/gnome/SessionManager \
	-m org.gnome.SessionManager.CanShutdown

section "2. org.gnome.desktop.lockdown   EXPECT disable-log-out false, locked"
echo "   gnome-session's gsm_manager_can_shutdown() is"
echo "     !disable-log-out && (can_stop || can_restart || can_suspend || can_hibernate)"
echo "   so a true here turns section 1 false whatever logind and polkit say,"
echo "   and leaves Suspend as the only entry in the menu."
for key in disable-log-out disable-user-switching disable-lock-screen
do
	printf '   %-24s value=%-8s writable=%s\n' "${key}" \
		"$(in_session gsettings get org.gnome.desktop.lockdown "${key}")" \
		"$(in_session gsettings writable org.gnome.desktop.lockdown "${key}")"
done
echo "   --- dconf dump /org/gnome/desktop/lockdown/"
in_session dconf dump /org/gnome/desktop/lockdown/
echo "   --- the system database fragments that produce it"
grep -rn 'disable-log-out' /etc/dconf/db/local.d/ 2>/dev/null

section "3. logind, asked from inside the seat0 session   EXPECT yes"
for method in CanPowerOff CanReboot CanSuspend
do
	printf '   %-12s ' "${method}"
	in_session busctl call org.freedesktop.login1 /org/freedesktop/login1 \
		org.freedesktop.login1.Manager "${method}"
done
echo '   "challenge" here means polkit wants an administrator; "no" means the'
echo '   action is not possible at all. "yes" means polkit is not the problem.'

section "4. polkit, asked about the seat0 session   EXPECT ALLOWED"
if [ -n "${SHELL_PID}" ] && have pkcheck
then
	echo "   subject: pid ${SHELL_PID} (${POS_USER}'s gnome-shell, session ${SEAT0_SESSION:-?})"
	for action in ${ACTIONS}
	do
		printf '   %-58s ' "${action}"

		if pkcheck --action-id "${action}" --process "${SHELL_PID}" >/dev/null 2>&1
		then
			echo ALLOWED
		else
			echo 'NOT ALLOWED (auth needed, or refused)'
		fi
	done
else
	echo "   (no gnome-shell, or pkcheck is not installed)"
fi

section "5. polkit implicit policy for those four actions"
if have pkaction
then
	for action in ${ACTIONS}
	do
		pkaction --verbose --action-id "${action}" 2>&1 |
			sed -n '1p;/implicit/p'
	done
else
	echo "   (pkaction is not installed)"
fi

section "6. polkitd's own log   EXPECT the rules directory loaded, no errors"
echo "   A JavaScript error in ANY file disables THAT FILE, silently."
journalctl -u polkit -b --no-pager 2>&1 |
	grep -iE 'Loading rules|Finished loading|Error|polkitd version' |
	grep -v 'No such file or directory' |
	tail -15

section "7. logind sessions"
echo "   More than one user with a session makes logind ask about the"
echo "   *-multiple-sessions actions instead of the plain ones."
loginctl list-sessions 2>&1
for session in $(loginctl --no-legend list-sessions 2>/dev/null | awk '{ print $1 }')
do
	printf '   session %-6s ' "${session}"
	loginctl show-session "${session}" \
		-p Name -p Seat -p Type -p Class -p Active -p State -p Remote -p Leader 2>/dev/null |
		tr '\n' ' '
	echo
done

section "8. inhibitors"
echo "   A 'shutdown' inhibitor in *block* mode makes logind refuse unless the"
echo "   caller may also *-ignore-inhibit, which this terminal deliberately"
echo "   does not grant (49-imprion-power.rules says why)."
systemd-inhibit --list 2>&1

section "9. the Imprion shell extension"
echo "   It edits the quick-settings menu (it removes the a11y indicator and,"
echo "   since F80's second cut, the Log Out entry). If the power entries are"
echo "   missing and section 1 says true, look here."
in_session gnome-extensions info 'imprion-shell@imprion' 2>&1 |
	grep -E 'Name|Version|Enabled|State|Path'
journalctl -b --no-pager 2>&1 | grep -i 'imprion-shell:' | tail -5

section "10. the policy files as installed"
ls -l /usr/share/polkit-1/rules.d/ /etc/polkit-1/rules.d/ 2>&1
echo "   --- owner of the power rule"
dpkg -S /usr/share/polkit-1/rules.d/49-imprion-power.rules 2>&1
echo "   --- dconf database and its fragments"
ls -l /etc/dconf/db/local /etc/dconf/db/local.d/ /etc/dconf/db/local.d/locks/ 2>&1

section "11. gnome-shell's own gating code, read off this image"
echo "   Not from memory: the shell's JS is in a gresource inside libshell."
for library in /usr/lib/gnome-shell/libshell-*.so
do
	[ -e "${library}" ] || continue
	gresource list "${library}" 2>/dev/null | grep -q systemActions.js || continue
	echo "   --- ${library} : misc/systemActions.js"
	gresource extract "${library}" /org/gnome/shell/misc/systemActions.js 2>/dev/null |
		grep -nE '_updateHaveShutdown|CanShutdownAsync|_canHavePowerOff|POWER_OFF_ACTION_ID\).available|RESTART_ACTION_ID\).available|SUSPEND_ACTION_ID\).available|canSuspend\(\)'
	break
done

echo
echo "=============================================================="
echo "end"
