#!/bin/sh
# The clock. Exec'd by s6-supervise, never by hand.
#
# THIS PRINTER HAS NO RTC AND NO BATTERY. The kernel starts every boot at
# whatever was compiled into it, and without this service it stays there for
# as long as the machine is on: a print started on Tuesday is filed under
# 1970, Moonraker's job history sorts by a timestamp that never moves, and
# anything that checks a TLS certificate's validity dates decides the
# certificate is not valid yet. `date -s` over ssh fixes it until the next
# power cut, which is not a fix.
#
# A LONGRUN, NOT A ONESHOT AFTER WIFI, and the difference is the whole design:
#
#   * A time sync hung off the end of `wifi` would fire while wlan0 was
#     still associating, fail, and never be tried again -- and would not fire
#     at all for the printer whose owner joins a network from the HelixScreen
#     panel an hour after boot.
#   * A clock is not set once. There is no RTC to hold the answer, so the
#     correction has to be maintained for as long as the printer is up.
#
# The binary does both by design: with the network down it logs "will try
# again later" and keeps its schedule, rather than exiting, so THIS SERVICE
# NEEDS NO WIFI DEPENDENCY -- and would gain nothing from one, because `wifi`
# up means a supplicant is running, not that anything is routable. It syncs
# whenever the network turns up, from whichever direction.
#
# IT IS EXEC'D AS ntpclient, WHICH IS THE ONLY NAME THAT SETS THE CLOCK. Both
# names are the same binary and it chooses its argument parser from argv[0].
# The step lives behind `if (!dry && ntpc->set_clock)` at sntpd.c:485, and
# set_clock is raised in one place only: `case 's'` in the ntpclient parser,
# sntpd.c:1459. Under the sntpd name that field is initialised to zero with no
# option to raise it -- its -s means "log to syslog" -- and the daemon
# disciplines the clock FREQUENCY through adjtimex instead. That is the right
# behaviour for a machine whose clock is roughly right and drifting. It is
# useless here: there is no RTC, every boot starts in 1970, and a correction
# in parts per million never closes fifty-six years. Run as sntpd this service
# resolved, connected, took valid stratum-1 readings and left the clock in
# 1970 for as long as the printer was up (measured 2026-08-31).
MODDIR=/usr/data/anvil

# anvil-env.sh is read HERE, not inherited. s6-supervise starts this script
# 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

# The ntpclient name of the anvil-sntpd binary. Both names are one file; this
# is the one whose argument parser can set the clock.
NTPCLIENT=$MODDIR/sbin/ntpclient
LOG=/usr/data/logs/anvil-ntp.log

# ---- the whole policy, which is an argument list -------------------------
#
# ONE POOL ENTRY IS ENOUGH. pool.ntp.org resolves to several addresses and
# sntpd rotates through them itself when one stops answering, so a list of
# named servers here would buy nothing the pool does not already give.
#
# iburst: eight packets two seconds apart once a server is unreachable. It is
# what turns "wifi just came up" into a corrected clock in seconds instead of
# on the next poll.
SERVERS="pool.ntp.org,iburst"

# THE POLL INTERVAL IS ALSO THE RETRY CEILING, which is why it is 300 and not
# upstream's 600. A server that has missed a probe is retried on an
# exponential backoff CAPPED AT THIS NUMBER (peer_spacing() in the sntpd
# source), so it doubles as the longest a printer that joined wifi late can
# sit on the wrong time. 300s is inside what pool.ntp.org asks of clients;
# halving the worst case is worth one extra query every ten minutes.
INTERVAL=300

# The same place every other service of ours logs, appending rather than
# truncating so a respawn cannot erase the reason for the respawn. sntpd is
# quiet: at the default level it writes when it corrects the clock and when
# the network comes and goes, not once per poll.
mkdir -p /usr/data/logs
exec >>"$LOG" 2>&1

# The prerequisite. anvil-core deliberately Depends on nothing it can start
# (see its pkg.conf), so `opkg remove anvil-sntpd` is a thing a printer owner
# may do -- and then this service must still say, in a log, that the clock is
# unattended, rather than failing an exec once a second. The sleep is a minute
# rather than the five seconds the other services use because a removed
# package is a permanent condition, not a race.
if [ ! -x "$NTPCLIENT" ]; then
    echo "ntp: !! no $NTPCLIENT -- anvil-sntpd is not installed, the clock stays wrong"
    sleep 60
    exit 1
fi

# THE OPTIONS BELOW ARE THE ntpclient SET, NOT sntpd's, and the two disagree
# about the same letters. Read them against sntpd.c:1416, not against
# `sntpd -h`:
#
# -s  Set the clock. The whole reason this name is exec'd; see above.
#
# -l  Live: keep polling and keep disciplining the frequency between steps,
#     rather than exiting after the first reading. With -g and -c both unset
#     the "stop setting the clock once it is good" branch at sntpd.c:1229
#     never fires, so a printer that drifts is corrected again, for ever.
#
# -h  The server. Repeatable; one pool entry is enough, for the reason above.
#
# NO -n AND NO -p HERE. This parser has no -n at all -- an unknown option is a
# usage error and a service that exits at once -- and it does not need one:
# ntpclient mode does not fork and logs to stdout, which is the redirection
# above. Its -p is the LOCAL port to send from, not sntpd's server-mode port;
# server mode is already off, because only the sntpd parser turns it on.
#
# And `exec`, so the SIGTERM behind `s6-rc -d change ntp` reaches the daemon
# rather than a shell that ignores it.
exec "$NTPCLIENT" -s -l -i "$INTERVAL" -h "$SERVERS"
