Architecture
This page describes the architecture of oonfeeWRT v0.1.8. For the implementation record and historical design decisions, see ARCHITECTURE.md and IMPLEMENTATION.md.
v0.1.8 patch, unchanged schema and authority
v0.1.8 keeps schema 25 while normalizing null LLDP package lists and preserving authoritative UCI radio section keys on shared-PHY devices. v0.1.7 introduced the Precision shell and navigation; v0.1.6 introduced Reports, Alerts, firmware-catalogue and integration readers, browser-local topology layout, an isolated demo, and mobile presentation. Schema 24 persists alerts; schema 25 persists encrypted AdGuard settings. The optional manually installed rpcd helper is an explicit extension to the historical no-controller-code-on-router rule. It does not replace ordinary agent-free adoption or add a daemon or firmware-write path.
The short version
oonfeeWRT is a self-hosted controller for stock OpenWrt. One Go process:
- serves an embedded React interface;
- stores controller state in SQLite;
- polls routers through OpenWrt's existing
rpcd/ubus interface; - turns read-only inspection into a bounded, shareable compatibility report;
- keeps device management mode separate from observed Gateway/AP/Switch functions;
- turns a site-level network model into per-device UCI changes; and
- previews, applies, verifies, and confirms those changes.
The controller runs on a Linux or macOS computer, NAS, mini-PC, or server. No oonfeeWRT executable is required on a router for ordinary adoption or polling. The separate experimental helper is optional and runs on demand through rpcd.
Browser
│ HTTP or reverse-proxied HTTPS
▼
oonfeewrtd
├─ embedded React UI
├─ REST API and WebSocket
├─ site model, renderer, and apply engine
├─ collector, topology inference, and event processing
├─ capability registry
└─ SQLite + keyring.json
│
├─ steady state: OpenWrt /ubus JSON-RPC
└─ bounded bootstrap/cleanup: SSH
│
▼
stock OpenWrt routers, APs, and switches2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Components and responsibilities
Controller process
oonfeewrtd owns the HTTP listener, database, keyring, router sessions, collection loops, background maintenance, and embedded UI. It is deliberately a single process: there is no separate database service or UI server to operate.
The release binary is static and the container image is built from scratch. The container contains no shell or package manager and runs as a non-root user by default.
Web interface
The React/TypeScript interface is built into ui/dist and embedded in the Go binary. It talks to the controller over same-origin REST and WebSocket connections, so a normal deployment has no cross-origin configuration.
The Precision sidebar introduced in v0.1.7 and retained in v0.1.8 groups Dashboard, Devices, Client Devices, Topology, Radios, Policy Engine, and Adopt a device under Workspace. Statistics, Reports, and Alerts form Insights. Firmware and Integrations are Settings tabs: canonical URLs use /settings?section=firmware and /settings?section=integrations, with legacy standalone aliases retained. Network is the default Settings tab; Diagnostics and Backup & Restore retain their role gates. Settings navigation never automatically runs an optional check. Settings, Accounts, and Logs form the Controller group, in that order, at the foot of the sidebar, followed by a compact profile shortcut to Accounts. Accounts opens My account for every signed-in role, with an additional Manage accounts tab for owners. These controls remain under Settings in v0.1.5; the move does not change their permissions. Statistics reads retained WAN, system, exact-interface, and stable-radio rollups without raising a device's polling tier. What appears in each workspace depends on measured device capabilities; unavailable evidence is not silently replaced with zeroes. Principal routes share one PageHeader/action pattern and have responsive light/dark browser coverage.
Browser preferences are not controller desired state. Theme, navigation expansion, inventory view, and topology arrangement live locally when storage is available. Navigation expansion and topology arrangement use an account-scoped key; Current and History maps have separate positions. These preferences do not synchronize to another browser or survive clearing site data, and they never change router links or permissions.
The isolated demo is built into ui/demo-dist, not the embedded ui/dist. Compile-time replacement excludes the real API, live channel, and service-worker registration; a connection-blocking policy and fail-closed adapters keep it synthetic and read-only. The installed-app metadata adds no offline API cache or push notification service.
Store and keyring
Controller state lives in a WAL-mode SQLite database named oonfeewrt.db. Router and Wi-Fi credentials are sealed with a random data key held by the adjacent keyring.json; the runtime passphrase unwraps that key.
These three items have different jobs:
| Item | Purpose | Can another item recreate it? |
|---|---|---|
oonfeewrt.db | Inventory, device management modes, desired state, policy sets, accounts, events, audit history, rollups, sealed credential records | No |
keyring.json | Wrapped random controller data key | No; neither the database nor passphrase can regenerate it |
| Runtime passphrase or passphrase file | Unlocks the keyring | No; it is not stored in the database or keyring |
Back up the database and matching keyring as one unit, and protect the runtime passphrase separately. See Data retention and the installation and recovery guide.
Router communication
Steady-state transport: ubus
Normal polling, capability reads, and configuration use OpenWrt's JSON-RPC ubus endpoint, usually /ubus through uhttpd. A dedicated, scoped oonfeewrt login is used instead of the router's administrator account.
The controller relies on stock OpenWrt objects such as session, uci, network, network.interface, network.device, wireless/hostapd objects, and capability-dependent iwinfo or luci-rpc methods. A missing object, method, or driver fact becomes an explicit capability gap.
Bounded exception: SSH
Stock rpcd cannot create its own scoped login or write an ACL file even when a caller signs in to ubus as root. After explicit operator approval, adoption therefore uses SSH to create at most:
- one scoped
oonfeewrtlogin in rpcd configuration; and /usr/share/rpcd/acl.d/oonfeewrt.json.
The supplied device-administrator credential is used for that transaction and is not stored. SSH is also used for approved cleanup, ACL refresh, and the separately authorized optional-package workflow. It is not the polling or Apply transport. See Safety model.
Capability-driven behavior
OpenWrt is a distribution across many kernels, drivers, switch designs, and package sets. oonfeeWRT does not assume that two devices expose the same facts. Adoption probes and stores evidence per device, including model, firmware, radio, interface, package-manager, topology, and supported-method information.
The important state distinction is:
- observed: the source answered with usable evidence;
- empty: the source answered successfully and reported no entries;
- unavailable/unsupported: the source could not provide the fact;
- stale: older evidence exists, but a current read did not refresh it; or
- unknown: the controller has not established the fact.
This prevents an unsupported driver counter from looking like a real 0, or a failed topology read from looking like an empty network. The exact v0.1.8 feature and evidence boundary is in Capabilities.
Shareable compatibility evidence
The ordinary Inspect result contains deployment-specific data and free-text notes, so it is not safe to serialize wholesale. Since v0.1.2, the server also builds a separate compatibility document from an explicit allowlist. Format version 1 contains bounded controller-version, board, firmware, radio, port, feature-state, and supported-function evidence. It excludes the management address, MAC address, site/router identity, credentials, network configuration, clients, live telemetry, timestamps, runtime radio/PHY identifiers, bridge members, and free-text notes.
Sanitization and size checks happen on the server. Evidence outside the strict bounds makes the report unavailable while the underlying read-only inspection can still succeed. The browser only pretty-prints that already-sanitized object and downloads it; it does not make another router call, persist the report in the controller, or upload it anywhere.
Site model and ownership
Configuration is authored as site intent—WLANs, groups, networks, zones, policies, reusable exact-MAC policy sets, device functions, uplinks, meshes, and bounded per-device overrides. The renderer turns that model into deterministic UCI sections for each selected managed device.
Each device also has a management mode. Managed devices may be selected by the renderer subject to capabilities and function ownership. Monitor only devices use the distinct read-only oonfeewrt-monitor ACL and remain in inventory, collection, events, and topology but are removed from desired/site rendering and the Preview/Apply fleet. Optional LLDP installation/ configuration/removal, wireless-neighbor mutation, and other package/config/ remove paths also refuse them; existing LLDP observation remains available. Scoped ACL maintenance and un-adoption remain available so the controller footprint can be maintained or removed deliberately. At most one managed device can hold Gateway authority; additional reachable routed routers may be monitor only.
A capability-proved RF scan is the intentional active-observation exception: Managed and Monitor only radios may run it only after the same separate disruption acknowledgement. It can take clients off-channel but has no intended persistent configuration effect.
Policy sets have stable database IDs, case-insensitively unique names, and canonical exact-MAC members. A firewall rule may reference one set ID rather than copy its member list. Validation, Master Table, and rendering resolve the current members; missing/empty references and mixed direct/set sources fail closed, and deletion is refused while any rule references a set.
MAC enforcement is additionally bound to proved local scope at the managed Gateway. The store rejects set members, active direct/set firewall rules, and client block/fixed-address intent when a client is Upstream/Unknown or lacks a stored local observation from the currently adopted Managed Gateway. Schema 23 keeps those observations source-relative by device and MAC; Monitor-only observations neither satisfy nor contaminate the proof, including after un-adoption. Missing proof gates set creation/update and MAC Object Manager compilation; active MAC intent becomes a site-level Preview error rather than an ineffective rendered rule. The clear operation for existing block/fixed-address intent remains available one client at a time.
oonfeeWRT coexists with LuCI by owning the sections it creates. Managed sections have an oowrt_ name and/or an oonfeewrt=1 marker, and the database also records ownership. Existing human-managed sections remain visible and ordinary rendering never rewrites or deletes them.
v0.1.4 has one narrow, non-owning exception. After an operator explicitly selects Prefix delegation or Disabled, Preview may contain allowlisted option patches for the exact existing management LAN interface, its single matching DHCP section, and supported conventional wan/wan6 sections. These patches cannot create, claim, rename, prune, or delete a foreign section. Ambiguous, wrong-type, or conflicting static-IPv6 targets block Preview rather than widening ownership.
Preview and Apply pipeline
Saving desired state changes the controller database only. It does not write a router. Router changes follow a separate workflow:
- Select: exclude monitor-only devices from the desired-configuration fleet.
- Render: build the complete per-device desired documents, resolving any policy-set references to concrete source MACs.
- Diff: compare desired state with current owned sections and any explicitly selected, allowlisted management-LAN IPv6 option patches.
- Preview: show exact creates, updates, deletes, gaps, and required acknowledgements.
- Fleet preflight: verify every selected managed device before the first write.
- Apply: stage the reviewed UCI changes with OpenWrt rollback enabled.
- Health verification: reconnect and read the expected runtime state.
- Confirm: cancel OpenWrt's rollback timer only after verification passes.
- Receipt: preserve the operation and per-device outcomes so a page reload does not retry the write.
Polling is quiesced around a write so collection cannot race the change. The engine stops before later devices after the first failure. If connectivity is lost, OpenWrt's own timer is the recovery mechanism. See Safety model for operator recovery steps.
Collection and live updates
The collector batches router reads for managed and monitor-only devices and uses a baseline cadence of about one poll per minute. A focused device view may temporarily use the six-per-minute focused cadence. Per-device overrides can make full-state polling slower, up to 15 minutes; lightweight router-log collection remains once per minute.
Raw metric samples stay in memory until a complete five-minute window can be written as one SQLite transaction. Older data is folded into hourly rollups. The WebSocket carries bounded live device.stats frames; durable history still comes from SQLite. See Data retention for the exact limits.
The post-v0.1.5 Statistics workspace reads that durable history through bounded series queries. It obtains available interface and stable-radio keys from the stored per-device series catalog instead of constructing names. Its WAN rate queries use only Dashboard's exact proved route-device key; missing timestamps are inserted as explicit chart gaps in the browser, not interpolated values.
Effective WAN route evidence
On the slower network/topology cycle, v0.1.3 observes the installed IPv4 route table and netifd's logical-interface dump in the same batch. It selects the one usable, lowest-metric default in the kernel main table, then requires that kernel device to map to exactly one active logical interface that also reports a default route. This maps layouts such as logical wan over runtime pppoe-wan without letting another netifd default candidate win by list order.
The two reads are one proof. A missing command result, malformed route, equal-metric route on a different device, ECMP/multipath route, or ambiguous or absent netifd mapping makes the observation unavailable. Partial evidence does not overwrite the last proved network scope or become an invented Internet edge.
The measured topology edge retains the kernel interface used by the route and, when different, the logical interface used for address scoping. Device Detail receives that proved kernel interface as its current chart candidate; the chart can remain empty until samples for the key exist. Dashboard promotes the route interface to WAN throughput only when that exact key is present in the durable RX/TX series catalog. Otherwise Dashboard throughput remains unavailable instead of falling back to a name such as wan or the first Ethernet interface. The baseline 15-minute topology cadence is not a failover monitor and does not model policy-routing-table selection, mwan3, ECMP, per-uplink health, or bond members.
Transitive wired-topology evidence
v0.1.4 keeps raw FDB/LLDP observations in topology history, but projects the live graph through a fresh source-aware multi-hop path when those observations prove that one managed device is reached through another. This prevents a downstream managed device from appearing as a second direct child of the gateway. The projection is not permanent truth: stale, failed, or ambiguous sources expose the ambiguity again instead of carrying a prior path forward.
Unchanged links do not churn history rows. Closed-history lookup uses the index introduced in schema 20, and browser refreshes cancel abandoned requests rather than stacking overlapping topology queries.
Deployment boundary
The default container mapping publishes port 8080 on host loopback. OONFEE_HTTP_BIND can deliberately select one host management IP or every IPv4 interface without changing the container's internal :8080 listener. Bridge networking supports normal L3 management and add-by-address adoption, but does not carry host LAN interfaces into the container's automatic discovery plan. Discovery is a bounded IPv4 TCP/HTTP scan with an unauthenticated /ubus object-list fingerprint; it implements neither ARP-table discovery nor mDNS. Linux host networking can expose eligible directly attached host LAN interfaces to that same scanner, but also exposes the listener according to the host's network and firewall configuration.
The HTTP listener has no native TLS. Keep it on loopback or a trusted, isolated management network and use a trusted reverse proxy for remote browser access. Review Requirements before deployment.
Deliberate non-components
The architecture excludes:
- a required controller-authored router daemon, listener, general command service, custom firmware, or package feed;
- a cloud relay, NAT traversal service, or multi-site broker;
- a separate database server;
- automatic package installation during adoption; and
- silent takeover of LuCI-managed configuration.
Those exclusions define the trust and support boundary; they are not missing boxes in the diagram.
The experimental optional helper is a narrow, explicitly installed read-only rpcd exception. Firmware catalogue results do not execute an upgrade. Integration checks do not configure DNS or VPNs, and Alerts does not perform automatic remediation.