#!/bin/sh
# yonder-heal — clear what a crashed or killed daemon left behind.
#
# Runs as root from yonder.service (ExecStartPre and ExecStopPost). With
# KillMode=process only the daemon itself is signalled, so its capture/input
# helpers outlive it; a fresh daemon then finds /dev/dri or the virtual display
# taken. This reaps those helpers (matched by Yonder's own script names only),
# removes a stale virtual-display lock, and gives the state directories back to
# the owner. It never kills anything that is not Yonder's, and never fails the
# start: a healer that blocks the restart it exists for is worse than none.
#
# Generic version of deploy/riftlink-heal.sh. Left out on purpose: killing
# whatever holds the port (on a stranger's machine that may be their program).

UNIT="${YONDER_UNIT:-yonder.service}"
VDESK_DISPLAY="${RIFTLINK_VDESK_DISPLAY:-20}"

log() { printf '[yonder-heal] %s\n' "$*" >&2; }

owner="$(systemctl show -p User --value "$UNIT" 2>/dev/null || true)"
[ "$owner" = "root" ] && owner=""

# Signal every pid matching an extended regex against the full command line.
reap() {
    pids="$(pgrep -f "$1" 2>/dev/null || true)"
    [ -n "$pids" ] || return 0
    log "reaping leaked helper [$1]: $(echo "$pids" | tr '\n' ' ')"
    # shellcheck disable=SC2086
    kill -TERM $pids 2>/dev/null || true
    sleep 0.2
    pids="$(pgrep -f "$1" 2>/dev/null || true)"
    # shellcheck disable=SC2086
    [ -n "$pids" ] && kill -KILL $pids 2>/dev/null
    return 0
}

# One cheap scan for the common case (nothing leaked).
if pgrep -f "/usr/lib/yonder/(portal-capture\.sh|vdesk-capture\.sh|portal-input\.py|riftlink-vdesk\.sh)|Xvfb :$VDESK_DISPLAY|ffmpeg.*x11grab.*:$VDESK_DISPLAY|ffmpeg.*pipewiregrab" >/dev/null 2>&1; then
    reap '/usr/lib/yonder/portal-capture\.sh'
    reap '/usr/lib/yonder/vdesk-capture\.sh'
    reap '/usr/lib/yonder/portal-input\.py'
    reap "Xvfb :$VDESK_DISPLAY( |$)"
    reap "ffmpeg.*x11grab.*:$VDESK_DISPLAY"
    reap 'ffmpeg.*pipewiregrab'
fi

# Helpers that live as long as the daemon but are not Yonder scripts (the
# desktop-notification watcher) stay behind in this unit's cgroup, one more on
# every restart. Only this unit's own cgroup is searched and only their exact
# command lines matched, so the owner's shells (here when tmux got no scope of
# its own) are never touched.
cg="$(systemctl show -p ControlGroup --value "$UNIT" 2>/dev/null || true)"
if [ -n "$cg" ] && [ -r "/sys/fs/cgroup$cg/cgroup.procs" ]; then
    while read -r pid; do
        cmd="$(tr '\0' ' ' <"/proc/$pid/cmdline" 2>/dev/null)" || continue
        case "$cmd" in
            "dbus-monitor --session type='method_call',interface='org.freedesktop.Notifications'"*)
                log "reaping leaked helper $pid: $cmd"
                kill -TERM "$pid" 2>/dev/null || true
                ;;
        esac
    done <"/sys/fs/cgroup$cg/cgroup.procs"
fi

# A stale X lock keeps the virtual display from coming back on the same number.
xlock="/tmp/.X${VDESK_DISPLAY}-lock"
if [ -e "$xlock" ] && ! pgrep -f "Xvfb :$VDESK_DISPLAY( |$)" >/dev/null 2>&1; then
    log "removing stale X lock $xlock"
    rm -f "$xlock" "/tmp/.X11-unix/X$VDESK_DISPLAY" 2>/dev/null || true
fi

# State directories must belong to the owner: a root-owned one (left by running
# the daemon by hand as root) makes SQLite fail with "readonly database".
if [ -n "$owner" ]; then
    for d in /var/lib/riftlink /var/log/riftlink; do
        mkdir -p "$d" 2>/dev/null || true
        chown "$owner:" "$d" 2>/dev/null || true
    done
    for d in /tmp/riftlink-tmux /tmp/riftlink-share; do
        [ -d "$d" ] && [ ! -L "$d" ] && chown "$owner:" "$d" 2>/dev/null
    done
fi
exit 0
