fix(linux): bind deb Service recovery to the removing transaction

Reinstall a Service removed by a previous prerm only when the old prerm
uninstalls unconditionally, this dpkg run completed a stop job for it,
and its group comes from that same invocation's stdout journal entries.
Hand the evidence to the postinst through a single-use file tied to the
old version and cleared by every preinst.

Refs #8515

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
Tunglies
2026-11-09 18:21:08 +08:00
co-authored by Copilot
parent 63a16a8272
commit 3ca0c27a8e
2 changed files with 38 additions and 16 deletions
+11 -4
View File
@@ -7,6 +7,8 @@ SERVICE_UNIT="clash-verge-service.service"
SERVICE_UNIT_FILE="/etc/systemd/system/$SERVICE_UNIT"
# Read by the app, which offers a reinstall when the package could not keep the Service.
UPGRADE_MARKER="/run/clash-verge-service-upgrade"
# Written by the preinst for this transaction only.
UPGRADE_EVIDENCE="/run/clash-verge-service-upgrade-evidence"
write_upgrade_marker() {
(umask 022 && printf 'gid=%s\nid=%s\n' "$1" "$(date +%s%N)" >"$UPGRADE_MARKER")
@@ -61,15 +63,20 @@ if [ "$1" = "configure" ] && [ -n "$2" ] && [ -d /run/systemd/system ]; then
else
write_upgrade_marker "$gid"
fi
elif [ -f "$UPGRADE_MARKER" ]; then
# The preinst found that the previous package's prerm removed a running Service.
gid="$(sed -n 's/^gid=//p' "$UPGRADE_MARKER")"
if [ -z "$gid" ]; then
elif [ -f "$UPGRADE_EVIDENCE" ]; then
evidence="$(cat "$UPGRADE_EVIDENCE")"
rm -f "$UPGRADE_EVIDENCE"
gid="$(printf '%s\n' "$evidence" | sed -n 's/^gid=//p')"
if [ "$(printf '%s\n' "$evidence" | sed -n 's/^old=//p')" != "$2" ]; then
echo "Warning: ignoring Clash Verge Service evidence from another upgrade." >&2
elif [ -z "$gid" ]; then
echo "Warning: the previous package removed Clash Verge Service and its group is unknown; Clash Verge will offer to reinstall it." >&2
write_upgrade_marker ""
elif CLASH_VERGE_SERVICE_GID="$gid" /usr/bin/clash-verge-service-install --install-service; then
rm -f "$UPGRADE_MARKER"
else
echo "Warning: reinstalling Clash Verge Service failed; Clash Verge will offer to reinstall it." >&2
write_upgrade_marker "$gid"
fi
fi
fi
+27 -12
View File
@@ -1,10 +1,10 @@
#!/bin/bash
# Previous packages' prerm uninstalls the Service before this script runs, deleting the unit that names
# its group. The journal still shows whether this dpkg run stopped a running Service and which group it ran with;
# the postinst reinstalls from that evidence, and without it nothing is installed.
# its group. The journal still shows whether this dpkg run stopped a running Service and which group that
# invocation ran with; the postinst reinstalls only from that evidence.
SERVICE_UNIT="clash-verge-service.service"
UPGRADE_MARKER="/run/clash-verge-service-upgrade"
# systemd's "unit stopped" catalog entry.
UPGRADE_EVIDENCE="/run/clash-verge-service-upgrade-evidence"
# systemd's "unit stopped" catalog entry; restart jobs share it, so JOB_TYPE is required too.
UNIT_STOPPED_MESSAGE_ID="9d1aaa27d60140bd96365438aad20286"
dpkg_start_epoch() {
@@ -17,18 +17,33 @@ dpkg_start_epoch() {
echo $((btime + start / ticks))
}
if [ "$1" = "upgrade" ] && [ ! -e "/etc/systemd/system/$SERVICE_UNIT" ] && [ -d /run/systemd/system ] &&
[ "$(cat "/proc/$PPID/comm" 2>/dev/null)" = "dpkg" ] && command -v journalctl >/dev/null; then
# Only a prerm that uninstalls on every invocation explains a missing unit during an upgrade.
old_prerm_uninstalls() {
local prerm="${DPKG_ADMINDIR:-/var/lib/dpkg}/info/$DPKG_MAINTSCRIPT_PACKAGE.prerm"
[ -n "$DPKG_MAINTSCRIPT_PACKAGE" ] && [ -f "$prerm" ] &&
grep -qx '/usr/bin/clash-verge-service-uninstall' "$prerm" && ! grep -q 'upgrade' "$prerm"
}
rm -f "$UPGRADE_EVIDENCE"
if [ "$1" = "upgrade" ] && [ -n "$2" ] && [ ! -e "/etc/systemd/system/$SERVICE_UNIT" ] &&
[ -d /run/systemd/system ] && [ "$(cat "/proc/$PPID/comm" 2>/dev/null)" = "dpkg" ] &&
command -v journalctl >/dev/null && old_prerm_uninstalls; then
since="$(dpkg_start_epoch)"
if [ -n "$since" ] &&
[ -n "$(journalctl -q --no-pager --since "@$since" _PID=1 UNIT="$SERVICE_UNIT" MESSAGE_ID="$UNIT_STOPPED_MESSAGE_ID" -o cat 2>/dev/null)" ]; then
# The truncated process name excludes cores the Service spawned in its cgroup.
gid="$(journalctl -q --no-pager -b _SYSTEMD_UNIT="$SERVICE_UNIT" _COMM=clash-verge-ser -n 1 -o export 2>/dev/null |
sed -n 's/^_GID=//p')"
invocation=""
if [ -n "$since" ]; then
invocation="$(journalctl -q --no-pager --since "@$since" _PID=1 UNIT="$SERVICE_UNIT" \
MESSAGE_ID="$UNIT_STOPPED_MESSAGE_ID" JOB_TYPE=stop JOB_RESULT=done -n 1 -o export 2>/dev/null |
sed -n 's/^INVOCATION_ID=//p')"
fi
if printf '%s' "$invocation" | grep -qx '[0-9a-f]\{32\}'; then
# Stdout entries carry the credentials systemd connected the stream with, which are the unit's Group=.
gid="$(journalctl -q --no-pager _SYSTEMD_INVOCATION_ID="$invocation" _SYSTEMD_UNIT="$SERVICE_UNIT" \
_TRANSPORT=stdout -n 1 -o export 2>/dev/null | sed -n 's/^_GID=//p')"
if [ -z "$gid" ] || [ "$gid" = "0" ] || ! getent group "$gid" >/dev/null; then
gid=""
fi
(umask 022 && printf 'gid=%s\nid=%s\n' "$gid" "$(date +%s%N)" >"$UPGRADE_MARKER")
echo "Clash Verge Service invocation $invocation was removed by the previous package; it will be reinstalled."
(umask 022 && printf 'old=%s\ngid=%s\n' "$2" "$gid" >"$UPGRADE_EVIDENCE")
fi
fi
exit 0