Files
internet_use_control/SYSTEM_MAP.md

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)

  1. 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.
  2. 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)

  1. No mobile profile. Away from home behaves the same as at home: keep last state.
  2. One admin keycode, shared by every device.
  3. Keep last state indefinitely. The admin keycode overrides it.

These are settled. Do not re-open them unless something changes.