1Features
- Rockchip RV1106G3 (Cortex-A7, 1.2 GHz) with 181 MB usable RAM and 256 MB SPI NAND
- DNS-level filtering of ~600,000 rules for every device, DoH upstreams queried in parallel
- Runs the network's DHCP, so every client picks it up as its resolver automatically
- Reachable as pure.local whatever the local subnet, through a standard-library mDNS responder
- Moves itself into an unknown subnet on first boot, and rolls back if the gateway goes quiet
- Watchdog on real HTTP health checks: restart the service, reboot only if DNS stays down
- Flash-wear aware: raw measurements stay in RAM, only hourly summaries reach the NAND
- Factory image hardened — telnet, Samba and adb closed, SSH key-only
2Specifications
- Board
- Luckfox Pico Max
- SoC
- RV1106G3 · Cortex-A7
- RAM
- 181 MB usable
- Storage
- 256 MB SPI NAND · UBIFS
- OS
- Buildroot 2023.02 · Linux 5.10
- Filter rules
- ~600,000
- Services
- 5, started at boot
- Power
- USB-C
3Figure 1 — On the router

4Device panel




5Description
PURE sits next to the home router and answers every DNS query on the network: requests for ad and tracker domains go nowhere, everything else resolves normally. It is a Luckfox Pico Max — a Rockchip RV1106G3 Cortex-A7 with 181 MB of usable RAM and 256 MB of SPI NAND — running Buildroot Linux, with the resolver cross-compiled for armv7 as a single static Go binary so the whole system fits on the NAND without a microSD card.
Most of the work was making a development image behave like a product. The stock image shipped with an empty CA store, so the encrypted upstreams failed silently while the service looked healthy; a launch script overwrote the static address with a DHCP lease; a boot race left the board without a default route; and the ownership of /root made sshd reject keys. Each is fixed in the image rather than by hand on every unit, and the init chain was extended so the box can find its own place on a network it has never seen.
Everything is sized for the hardware. Five services share the RAM, and the extra features — outage log, speed tests, new-device alerts, Telegram notifications — run in one Python process on one timer loop instead of five. The board runs 24/7 on flash with a finite write life, so raw data stays in RAM. Features the kernel cannot support — NAT, VPN, packet filtering — were measured and ruled out instead of promised.
Production is written down as a per-unit process around a golden NAND image: flash, verify, strip the development tooling, reset to the setup wizard so the customer sets their own password, and sign off an exit checklist per serial number.
6Boot sequence
- [ OK ]S35pureagfirst boot only: take a DHCP lease so the unit lands in the customer's subnet
- [ OK ]S40networkbring up eth0 with the saved static address
- [ OK ]S45pureaggateway gone + DHCP offer from another subnet → move there and save
- [ OK ]S46pureilkayarafter the setup wizard: apply filters, DoH upstreams and DHCP
- [ OK ]S99adguardhomestart the resolver; wait for LOWER_UP before adding the default route
- [ OK ]S99puremdnsannounce pure.local — A record, cache-flush bit, 120 s TTL
- [ OK ]S99puremonitoroutage log, hourly speed test, new-device alerts in one process
- [ OK ]S99purepanelweb panel behind the resolver's login
- [ OK ]S99purewatchdogHTTP health checks: restart first, reboot only for DNS
7Resources
- Resolver
- 87 MB
- Web panel
- 12.4 MB
- Monitor
- 10.8 MB
- mDNS
- 5.8 MB
- Watchdog
- <1 MB
- Kernel, cache & free
- 64 MB
Resident memory with all five services up. The resolver grows to ~117 MB after a heavy scan; about 45 MB stays free under load.