#!/bin/sh
# Klippy, as s6 supervises it. Exec'd by s6-supervise, never by hand.
#
# This is FlashForge's klipperDaemon command line without the -b: without it
# busybox start-stop-daemon execs the program in place, so the process
# s6-supervise holds IS klippy rather than a shell that started one. The
# pidfile goes with it -- s6-supervise knows the pid.
#
# THE MCU BRING-UP DOES NOT HAPPEN HERE. It is the `mcu-bringup` oneshot, which
# klipper depends on, so s6-rc runs it before this service starts. Doing it
# here as well would hand the same serial ports to two processes at once.
MODDIR=/usr/data/anvil

# anvil-env.sh names the interpreter ($FF_PYTHON) and puts the mod's four
# library directories on LD_LIBRARY_PATH. s6-supervise hands this script the
# scanner's environment, which is already correct on the boot path -- but a
# service must not work only because the shell that started the tree happened
# to be set up right.
if [ -f $MODDIR/anvil-env.sh ]; then
    . $MODDIR/anvil-env.sh
else
    echo "klipper/run: !! no $MODDIR/anvil-env.sh -- klippy will not find its libraries"
fi

# BOTH HALVES ARE OURS AND BOTH ARE UNDER $MODDIR: the klippy tree anvil-klipper
# installs, on the CPython 3.13 anvil-python installs. There is no /usr/prog in
# this command line any more.
#
# The tree used to be read from /usr/prog/klipper/klippy -- a copy bin/payload.sh
# staged into the software component out of this very package, so that
# FlashForge's run.sh would place it on the firmware partition. Two copies of
# one tree, and the one that ran was the one a stock flash could overwrite.
# anvil-klipper is the only source now, which is also what lets this package
# declare the interpreter it actually needs: see pkgs/klipper/pkg.conf.
#
# THE INTERPRETER IS NOT FLASHFORGE'S 3.8.2 and has not been since $FF_PYTHON
# moved to 3.13. klippy reaches c_helper.so through _cffi_backend, which is why
# pkgs/3rdparty/python is a glibc build and not a musl one.
KLIPPY=$MODDIR/klipper/klippy/klippy.py
PRINTER_CFG=/usr/data/config/printer.cfg
PRINTER_LOG=/usr/data/logs/printer.log
UDS=/tmp/uds

# klippy does not unlink its socket on exit, and everything on this printer
# reads that socket as "klipper is running". Swept here rather than in a stop()
# because s6 restarts this service without going near one.
rm -f "$UDS"

# A klippy killed with SIGKILL -- an OOM -- leaves a process still holding
# /dev/ttyS4 while s6 starts a second one that cannot open the port and reports
# an MCU error for ever. s6-supervise runs one copy of this service at a time,
# so anything running klippy.py now is a leftover.
#
# /proc rather than `ps | grep`: busybox ps truncates to the terminal width, so
# the command line arrives cut off and the grep matches nothing. Our own pid is
# skipped.
for _kl_proc in /proc/[0-9]*; do
    [ -r "$_kl_proc/cmdline" ] || continue
    [ "${_kl_proc#/proc/}" = "$$" ] && continue
    case "`tr '\0' ' ' < "$_kl_proc/cmdline" 2>/dev/null`" in
        *klippy.py*) kill -9 "${_kl_proc#/proc/}" 2>/dev/null ;;
    esac
done
unset _kl_proc

# klipper_pri.sh puts klippy's main thread on SCHED_FIFO 50. It cannot run
# before the exec below -- there would be no klippy to find -- and this script
# has no "after", so it waits for the socket that says klippy is serving.
# Bounded, and nothing reads the result: a missing chrt is a slower printer.
if [ -x /usr/prog/klipper/klipper_pri.sh ]; then
    (   _kp=0
        while [ $_kp -lt 60 ] && [ ! -S "$UDS" ]; do
            sleep 1
            _kp=$((_kp + 1))
        done
        [ -S "$UDS" ] && sh /usr/prog/klipper/klipper_pri.sh
    ) >/dev/null 2>&1 &
fi

# NO NICE VALUE: klippy runs at normal priority, which is what FlashForge
# shipped. The number was briefly a NICE_KLIPPER knob in anvil.conf, and before
# that the installer grepped KLIPPER_NICENESS out of the stock klipperDaemon --
# which is what made that shim machine-specific and kept it from being a symlink
# like the other two files in prog/. Neither is true now.
#
# Renicing klippy is nearly always the wrong lever anyway: klipper_pri.sh puts
# its main thread on SCHED_FIFO 50, which outranks every nice value there is,
# and its serial reader thread calls os.nice(-20) itself. The two services that
# DO carry a nice value are the ones that yield to klippy -- moonraker/run and
# camera/run, each stating its own.
exec start-stop-daemon -S --exec "$FF_PYTHON" -- \
    "$KLIPPY" "$PRINTER_CFG" -l "$PRINTER_LOG" -a "$UDS"
