#!/bin/sh
# The moonraker service, as s6 runs it. Exec'd by s6-supervise, never by hand.
#
# NO start-stop-daemon AND NO `-b`: the process this script turns into IS the
# service, so a fork-and-return would have s6 supervise the process that
# exited and restart it for ever while the real moonraker served :7125
# supervised by nobody. AND `exec`: without it s6 supervises this shell, and
# every signal it sends goes to a shell that ignores it.
#
# THIS FILE DOES NOT LOOK AT MOD_WEB: a `run` that exits because it disagrees
# is a respawn loop with extra steps. There is no `down` file either --
# MEASURED: s6-rc-compile accepts one and SILENTLY DROPS it. The gate is the
# argument list; svc_rc_enabled reads MOD_WEB at runtime.
MODDIR=/usr/data/anvil

# Read HERE, not inherited: s6-supervise starts this from the SCANNER's
# environment, which is firmwareExe's at boot but whatever an ssh session had
# when the service is restarted by hand.
[ -f $MODDIR/anvil-env.sh ] && . $MODDIR/anvil-env.sh

# One moonraker, on the DATA partition -- /usr/prog is what a stock flash
# overwrites.
MOONRAKER_MAIN=$MODDIR/moonraker/moonraker.py
MOONRAKER_DATA=/usr/data
MOONRAKER_TMP=/usr/data/tmp
MOONRAKER_CONF=$MOONRAKER_DATA/config/moonraker.conf

# TMPDIR: /tmp is a ramdisk here, so a large upload without this fills memory
# instead of the data partition. Set in the run script because s6 restarts
# this service without going near any shell of ours. The wipe needs no
# "is it running" guard -- s6-supervise execs this only after the previous
# copy was reaped.
rm -rf $MOONRAKER_TMP
mkdir -p $MOONRAKER_TMP $MOONRAKER_DATA/logs
export TMPDIR=$MOONRAKER_TMP

# The prerequisites. A run script cannot decline to run -- s6 respawns it -- so
# a missing one is reported and the sleep keeps it to one line every 5 seconds.
if [ ! -x "$FF_PYTHON" ]; then
    echo "moonraker: !! no interpreter at ${FF_PYTHON:-/usr/prog/Python-3.8.2/bin/python3}"
    sleep 5
    exit 1
fi
if [ ! -f "$MOONRAKER_MAIN" ]; then
    echo "moonraker: !! nothing at $MOONRAKER_MAIN -- the install did not complete"
    sleep 5
    exit 1
fi
# THE UPGRADE PATH: a printer on the pre-s6 payload has a start-stop-daemon'd
# moonraker whose pid is in this file. Delete both once no pre-s6 install can
# be upgraded in the field.
rm -f /run/moonraker.pid

# A moonraker already holding :7125 makes this service fail to bind, exit, get
# respawned, and loop for as long as the printer is on, with s6-svstat saying
# "up" while an unsupervised leftover answers Mainsail.
#
# /proc rather than `ps | grep`: busybox ps truncates to the terminal width.
# The match is the absolute path of the entry point, which appears in a
# moonraker's command line and in no other process here.
for _mr_proc in /proc/[0-9]*; do
    [ -r "$_mr_proc/cmdline" ] || continue
    [ "${_mr_proc#/proc/}" = "$$" ] && continue
    _mr_args=`tr '\0' ' ' < "$_mr_proc/cmdline" 2>/dev/null`
    case "$_mr_args" in
        *"$MOONRAKER_MAIN"*) kill -9 "${_mr_proc#/proc/}" 2>/dev/null ;;
    esac
done
unset _mr_proc _mr_args

# ---- readiness ------------------------------------------------------------
#
# `notification-fd` holds "3": the service is READY once a line is written to
# fd 3, and ff-startup depends on that meaning something a caller can use.
#
# READY IS "THE HTTP API IS LISTENING", not "the process was forked" (component
# imports fail seconds later) and NOT "klippy_state is ready" -- moonraker
# reporting klippy_state=error IS moonraker doing its job, so that definition
# would never fire on precisely the broken printer whose UI somebody needs.
#
# /proc/net/tcp because this busybox has no nc applet: field 2 is
# LOCAL_ADDRESS:PORT in uppercase hex, field 4 the state, 0A is TCP_LISTEN.
# The address half is not matched, so host: 127.0.0.1 still counts.
#
# The port is read from moonraker's own config because it is user-editable and
# a probe watching the wrong port would never fire. The awk is section-aware.
MOONRAKER_PORT=`awk '
    /^[[:space:]]*\[/ { section = $0 }
    section ~ /\[[[:space:]]*server[[:space:]]*\]/ && /^[[:space:]]*port[[:space:]]*:/ {
        sub(/^[^:]*:[[:space:]]*/, "");
        gsub(/[^0-9]/, "");
        if (length($0)) { print; exit }
    }' "$MOONRAKER_CONF" 2>/dev/null`
[ -n "$MOONRAKER_PORT" ] || MOONRAKER_PORT=7125

# A subshell, because the last thing this script does is exec and an exec'd
# moonraker cannot report on itself. The exec below closes fd 3 in MOONRAKER
# (3>&-) so the only process holding the notification pipe is the one with
# something to say.
#
# 180s: moonraker imports its whole component tree on a 1GHz mipsel box off
# eMMC, and slower again on the qemu replica.
(
    _mr_n=0
    while [ $_mr_n -lt 180 ]; do
        if awk -v p=":`printf '%04X' "$MOONRAKER_PORT"`" \
               '$2 ~ p"$" && $4 == "0A" { found = 1 }
                END { exit !found }' /proc/net/tcp 2>/dev/null
        then
            echo ready >&3
            exit 0
        fi
        sleep 1
        _mr_n=$((_mr_n + 1))
    done
) &

# `-d /usr/data` is moonraker's data path. Not optional: moonraker defaults to
# ~/printer_data, a home directory that does not exist here.
#
# stderr goes wherever the scanner's does ($SVC_S6_LOG), which is where a
# moonraker that dies before its own logging is configured explains itself.
#
# `nice` sets its own priority and then execs in place, so this is still one
# process. The nice value protects klippy from it, and is modest on purpose:
# this is the print path, so starving it delays the UI, not just background
# work.
exec nice -n 5 "$FF_PYTHON" "$MOONRAKER_MAIN" -d "$MOONRAKER_DATA" 3>&-
