The Cuckoo Escapement: field report, kernel patch, dashboard
What the Raspberry Pi time-server guides get wrong on a Pi 4, with the measurements. The headline artifact is a four-line pps-gpio patch: PREEMPT_RT force-threads IRQ handlers, and pps-gpio takes its timestamp inside its handler, so the realtime kernel puts a scheduler between the electrical edge and the clock. IRQF_NO_THREAD takes RMS offset from 2468 ns to 199 ns. - kernel/ the patch - dashboard/ live status page (position hidden by default) - docs-site/ the write-up (Astro/Starlight, brass, no tutorial section)
This commit is contained in:
commit
6881489bf6
56 changed files with 10297 additions and 0 deletions
11
dashboard/deploy/Caddyfile.tmpl
Normal file
11
dashboard/deploy/Caddyfile.tmpl
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
# gps-ntp dashboard front door. `make caddy` renders {{DOMAIN}} from .env.
|
||||
#
|
||||
# The cert is issued OFF-BOX and synced to the Pi (see `make cert-sync`) rather
|
||||
# than obtained by Caddy itself. A time server usually lives on a LAN with no
|
||||
# inbound reachability, so HTTP-01 can't work; use DNS-01 wherever you already
|
||||
# run ACME and copy the result here.
|
||||
{{DOMAIN}} {
|
||||
tls /etc/caddy/certs/{{DOMAIN}}/fullchain.pem /etc/caddy/certs/{{DOMAIN}}/privkey.pem
|
||||
encode zstd gzip
|
||||
reverse_proxy 127.0.0.1:8080
|
||||
}
|
||||
10
dashboard/deploy/caddy-affinity.conf
Normal file
10
dashboard/deploy/caddy-affinity.conf
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
# systemd drop-in: /etc/systemd/system/caddy.service.d/affinity.conf
|
||||
#
|
||||
# Keep Caddy off cpu0. The PPS interrupt is handled there and cannot be moved
|
||||
# (Pi 4 GPIO IRQs are demuxed via pinctrl-bcm2835 and refuse an smp_affinity),
|
||||
# so any work scheduled on cpu0 adds jitter to the PPS timestamp directly.
|
||||
# Caddy is mostly idle, but on this box cpu0 belongs to the clock. See
|
||||
# TIMING-NOTES.md.
|
||||
[Service]
|
||||
CPUAffinity=1
|
||||
Nice=10
|
||||
37
dashboard/deploy/deploy.sh
Normal file
37
dashboard/deploy/deploy.sh
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
#!/usr/bin/env bash
|
||||
# Idempotent install/update of the gps-ntp dashboard. Run on the Pi as root
|
||||
# from the synced source tree (APPDIR). Safe to re-run.
|
||||
set -euo pipefail
|
||||
|
||||
APPDIR="${APPDIR:-/opt/gpsntp-dashboard}"
|
||||
SVC=gpsntp-dashboard
|
||||
|
||||
echo "==> dedicated system user"
|
||||
id -u gpsntp &>/dev/null || useradd --system --no-create-home --shell /usr/sbin/nologin gpsntp
|
||||
|
||||
echo "==> python venv + editable install"
|
||||
if [ ! -x "$APPDIR/.venv/bin/python" ]; then
|
||||
if ! python3 -m venv "$APPDIR/.venv" 2>/dev/null; then
|
||||
apt-get update -qq && apt-get install -y python3-venv python3-pip
|
||||
python3 -m venv "$APPDIR/.venv"
|
||||
fi
|
||||
fi
|
||||
"$APPDIR/.venv/bin/pip" install --quiet --upgrade pip
|
||||
"$APPDIR/.venv/bin/pip" install --quiet -e "$APPDIR"
|
||||
|
||||
echo "==> narrow sudoers rule (clients command only)"
|
||||
install -m 0440 "$APPDIR/deploy/gpsntp-dashboard.sudoers" /etc/sudoers.d/gpsntp-dashboard
|
||||
visudo -cf /etc/sudoers.d/gpsntp-dashboard
|
||||
|
||||
echo "==> systemd unit"
|
||||
install -m 0644 "$APPDIR/deploy/gpsntp-dashboard.service" "/etc/systemd/system/${SVC}.service"
|
||||
|
||||
echo "==> ownership"
|
||||
chown -R gpsntp:gpsntp "$APPDIR"
|
||||
|
||||
echo "==> enable + (re)start"
|
||||
systemctl daemon-reload
|
||||
systemctl enable "${SVC}.service" >/dev/null 2>&1 || true
|
||||
systemctl restart "${SVC}.service"
|
||||
sleep 2
|
||||
systemctl is-active "${SVC}.service" && echo "==> dashboard active on :8080"
|
||||
35
dashboard/deploy/gpsntp-dashboard.service
Normal file
35
dashboard/deploy/gpsntp-dashboard.service
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
[Unit]
|
||||
Description=gps-ntp status dashboard
|
||||
Documentation=https://git.supported.systems/time-pi
|
||||
After=network-online.target gpsd.service chrony.service
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=exec
|
||||
User=gpsntp
|
||||
Group=gpsntp
|
||||
ExecStart=/opt/gpsntp-dashboard/.venv/bin/gpsntp-dashboard
|
||||
Environment=GPSNTP_PORT=8080
|
||||
Environment=GPSNTP_HOST=0.0.0.0
|
||||
Restart=on-failure
|
||||
RestartSec=3
|
||||
|
||||
# Keep the dashboard OFF cpu0. The PPS interrupt is handled on cpu0 and cannot
|
||||
# be moved (Pi 4 GPIO IRQs are demuxed through pinctrl-bcm2835 and refuse an
|
||||
# smp_affinity), so any load there directly adds jitter to the timestamp.
|
||||
# Measured: the dashboard cost ~36% more PPS jitter before this. It is a
|
||||
# monitoring tool; it must never perturb the clock it is watching.
|
||||
CPUAffinity=1
|
||||
Nice=10
|
||||
|
||||
# Hardening. NoNewPrivileges is intentionally NOT set: the served-clients
|
||||
# panel shells `sudo -n chronyc -c clients`, allowed by a narrow sudoers rule.
|
||||
PrivateTmp=true
|
||||
ProtectHome=true
|
||||
ProtectControlGroups=true
|
||||
ProtectKernelTunables=true
|
||||
RestrictSUIDSGID=false
|
||||
LockPersonality=true
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
4
dashboard/deploy/gpsntp-dashboard.sudoers
Normal file
4
dashboard/deploy/gpsntp-dashboard.sudoers
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
# Least-privilege: the dashboard user may run ONLY this one read-only command.
|
||||
# `chronyc -c clients` is privileged (returns "501 Not authorised" otherwise),
|
||||
# and this is the served-client list the dashboard displays. Nothing else.
|
||||
gpsntp ALL=(root) NOPASSWD: /usr/bin/chronyc -c clients
|
||||
Loading…
Add table
Add a link
Reference in a new issue