76d019f7e980.
Overview
Reefy uses three independent recovery layers:
| Layer | Watches | Trigger | Recovery |
|---|---|---|---|
| Hardware watchdog | PID 1, kernel progress, and shutdown | systemd stops petting the hardware watchdog | Hardware reset. |
| A/B boot watchdog | Trial firmware reaching control-plane health | A non-default boot is not confirmed within 360 seconds | Immediate sysrq reboot so the prior default slot can boot. |
| Infrastructure watchdog | Tailscale authentication and selected tunnel containers | Repeated service-specific failures | Restart only the affected tunnel unit or container, with cooldown. |
The layers do not share a timer or watchdog file descriptor. systemd owns the hardware watchdog for the full system lifetime. The A/B watchdog uses sysrq, and the infrastructure watchdog uses a systemd timer plus bounded service restarts.
Layer 1: systemd hardware watchdog
Configuration:
[Manager]
RuntimeWatchdogSec=120
RebootWatchdogSec=2min
When the hardware exposes /dev/watchdog0, systemd opens it and pets it at
half the runtime timeout. A kernel deadlock, PID 1 stall, or other failure that
stops those pings lets the device reset the machine. RebootWatchdogSec also
bounds a shutdown that never completes.
The kernel has its own production panic timeout, so a normal kernel panic can reboot before the hardware watchdog. The hardware timer remains the fallback for failures that prevent the kernel's recovery path from progressing.
Useful checks on a device:
systemctl show --property=RuntimeWatchdogUSec \
--property=RebootWatchdogUSec \
--property=WatchdogLastPingTimestamp
ls -l /dev/watchdog0
Hardware-watchdog panic tests can interrupt storage and active workloads and should be run only on disposable lab devices.
Layer 2: A/B boot watchdog
reefy-boot-watchdog.service activates its timer only when standard UEFI
BootCurrent differs from the first BootOrder entry. That state means Reefy
is testing a one-shot BootNext slot rather than booting the persistent
default.
The confirmation flow is:
- Firmware consumes
BootNextand boots the inactive slot. reefy-boot-confirmwaits up to 300 seconds.- It refuses confirmation immediately if
reefy-storageorreefy-controlenters the failed state. - When both are active, it runs
reefy-efi confirm. - Confirmation commits the current slot with a fresh pair of standard UEFI boot entries.
- Boot confirmation stops
reefy-boot-watchdog.service.
If confirmation does not stop the watchdog within 360 seconds, it writes b
to /proc/sysrq-trigger. That performs an immediate software reboot. Because
BootNext was one-shot and has already been consumed, firmware uses the prior
persistent default on the next boot.
The A/B watchdog does not open, close, or pet /dev/watchdog0. This avoids a
second owner fighting systemd and avoids accidentally leaving a hardware timer
armed after a file descriptor closes.
Docker and individual apps are not boot-confirmation requirements. Storage and the MQTT control plane are the minimum recovery surface needed to diagnose and repair application failures.
Layer 3: infrastructure watchdog
reefy-watchdog.timer starts two minutes after boot and runs every minute. It
launches a bounded oneshot service with a 50-second systemd timeout.
The shell entry point performs the Tailscale-specific log check and then runs
the Python liveness checks. State and cooldown files live in
/run/reefy-watchdog, so they reset on reboot.
Tailscale authentication recovery
Some expired or deleted ephemeral Tailscale nodes can leave tailscaled
reporting a running backend while repeatedly logging node not found.
The watchdog counts those messages over the previous two minutes. After at
least three errors, it restarts reefy-tunnel.service, which reruns the Reefy
tunnel setup and authentication. It deliberately does not restart
tailscaled, because killing that daemon would drop active Tailscale SSH and
tmux sessions. A three-minute cooldown prevents repeated reauthentication.
Tunnel-container liveness
The Python watchdog checks only optional containers that actually exist in the Compose project:
| Service | Probe | Healthy result |
|---|---|---|
cloudflared |
http://127.0.0.1:20241/ready |
JSON status 200 with at least one ready connection. |
reefy-proxy |
http://127.0.0.1:8080/ |
Any complete HTTP response, including the expected unauthenticated 403. |
An absent optional service is skipped and its failure count is cleared. One failed probe increments a per-service counter; a healthy probe clears it. After three consecutive failures, the watchdog restarts only the matching container using:
docker restart --time 5 <container>
The restart call is bounded to 15 seconds. Whether it succeeds or fails, a five-minute per-service cooldown prevents a restart storm. A successful restart clears the failure counter.
The infrastructure layer does not currently claim to monitor every app, Docker as a whole, MQTT reachability, or general Internet connectivity. Its scope is the known tunnel failure modes above.
Timing
boot
|-- systemd arms and continuously pets the hardware watchdog
|-- normal default-slot boot: A/B services exit without action
|
|-- trial slot boot
| |-- boot-confirm waits up to 300 seconds for storage + control
| |-- success: commit current slot and stop boot watchdog
| `-- no confirmation by 360 seconds: sysrq reboot
|
`-- 2 minutes after boot
`-- infrastructure watchdog every minute
|-- Tailscale log recovery, 3-minute cooldown
`-- cloudflared/proxy probes, 3 failures, 5-minute cooldown
Failure containment
- A container liveness failure cannot manipulate the hardware watchdog.
- A missing optional tunnel container does not trigger recovery.
- A transient failed HTTP probe must repeat across three timer runs.
- Container recovery does not restart unrelated user apps.
- Tailscale recovery preserves the long-running
tailscaledprocess. - The A/B timeout remains longer than the boot-confirm health window.
- All infrastructure restart subprocesses and the oneshot service are time bounded.
Implementation files
| File | Responsibility |
|---|---|
board/reefy/reefy/rootfs-overlay/etc/systemd/system.conf.d/watchdog.conf |
systemd hardware watchdog configuration. |
board/reefy/reefy/rootfs-overlay/usr/bin/reefy-boot-watchdog |
360-second A/B trial watchdog and sysrq reboot. |
board/reefy/reefy/rootfs-overlay/usr/bin/reefy-boot-confirm |
Storage and control health check plus slot confirmation. |
board/reefy/reefy/rootfs-overlay/usr/bin/reefy-watchdog.sh |
Timer entry point and Tailscale log recovery. |
board/reefy/reefy/rootfs-overlay/usr/lib/reefy/reefy/watchdog.py |
cloudflared and proxy probes, failure counts, and bounded restart. |
board/reefy/reefy/rootfs-overlay/usr/lib/systemd/system/reefy-watchdog.timer |
Two-minute delay and one-minute interval. |
board/reefy/reefy/rootfs-overlay/usr/lib/systemd/system/reefy-watchdog.service |
Bounded oneshot wrapper. |