4.8 KiB
Internet Use Control — system map
Date: 2026-10-10 Purpose: name every piece once, clearly. No more "Go service" or "admin app" guessing.
The pieces
| Name | What it is | Where it runs | Language |
|---|---|---|---|
| Control Service | The brain and the source of truth. Holds schedules, blocklists, per-device state, usage. Serves the web dashboard. | .13, Docker |
Go + SQLite |
| Web Dashboard | Your setup screen in a browser. Schedules, blocklists, devices, usage. This is the desktop admin. | Served by the Control Service | HTMX |
| Broker | The message bus. Pushes state to devices. Collects their reports. | .13, Mosquitto (already running) |
— |
| Kids' phone app | Enforces limits on a kid's phone. Shows what is blocked, and for how long. | Kids' phones | Kotlin |
| Kids' computer client | Same job on a kid's laptop or desktop. | Kids' computers | Python |
| Admin app | Your override and approval surface. | Your phone | Kotlin |
| Hub | A peer. Asks questions only. Never controls. | .13 |
separate project |
There is no separate desktop admin app. The dashboard in a browser is the desktop admin. If you want a native desktop app later, that is a new decision.
Where the truth lives
SQLite inside the Control Service, on .13. Not MQTT, not the devices, not the
Hub.
The broker is only a delivery pipe. It is fast and it remembers the last message, but it is not the database.
Who writes what
| Action | First goes to | Then |
|---|---|---|
| Change a schedule | Control Service (HTTP) | saved in SQLite → published to MQTT |
| Lock or unlock now | Control Service (HTTP) | same |
| You enter the device keycode | Control Service (HTTP) | same |
| Device reports usage | Broker (MQTT) | Control Service writes it to SQLite |
| Hub asks "screen time today?" | Control Service (HTTP) | reads SQLite, answers |
Only the Control Service publishes config to devices. Everything else writes to the Control Service over HTTP.
That is the answer to "who publishes config": the Control Service, and only the Control Service. One writer. One truth.
"Config" means the desired state for one device: locked or not, until when, the allowed list, the blocked list.
Data flow
flowchart LR
WD["Web Dashboard<br/>(browser)"]
AA["Admin app<br/>(your phone)"]
HUB["Hub<br/>(peer)"]
CS["Control Service<br/>Go + SQLite<br/>on .13"]
MQ["Broker<br/>Mosquitto on .13"]
KP["Kids' phone app<br/>(Kotlin)"]
KC["Kids' computer client<br/>(Python)"]
WD -->|HTTP| CS
AA -->|HTTP| CS
HUB -->|HTTP /ask| CS
CS -->|publish state| MQ
MQ -->|deliver state| KP
MQ -->|deliver state| KC
KP -->|status + usage| MQ
KC -->|status + usage| MQ
MQ -->|reports| CS
Config carries per device
Each device has its own state. That is how "Harry's phone off, desktop on" works:
restrict/harry_phone/config {"locked": true, "until": "21:00", "allow": ["spotify"]}
restrict/harry_desktop/config {"locked": false}
The Control Service publishes each message, retained, so a device that reconnects gets its current state at once.
Fail state, and where the device is
The device must decide what to do when it cannot reach the broker. It also needs to know whether it is at home or away, because the two cases differ.
| Situation | How the device knows | Behaviour |
|---|---|---|
| At home, broker down | On the home Wi-Fi, broker unreachable | Keep last state |
| Away from home | Not on the home Wi-Fi | Keep last state |
| Long outage | No contact for N hours | Keep last state |
One rule: keep last state. An outage changes nothing. The child cannot escape by going offline, and a server restart does not lock them out.
The device caches its last config on itself, so the rule always has something to follow.
Overrides (two)
- On the device — one admin keycode, shared by every device. For a broken phone, a laptop, or when you are away. Stored hashed. Changeable in the Control Service.
- Admin app — remote lock, unlock, or grant time.
Honest limit
A child can turn off Wi-Fi and use mobile data to look "away". Accepted. The goal is reasonable friction, not a fortress.
Kids' app interface
A simple screen on the kid's device:
- What is blocked right now.
- How long until it lifts.
- Why (schedule, limit reached).
- An alert when a block starts.
Decisions (2026-10-10)
- No mobile profile. Away from home behaves the same as at home: keep last state.
- One admin keycode, shared by every device.
- Keep last state indefinitely. The admin keycode overrides it.
These are settled. Do not re-open them unless something changes.