Initial spec: AGENTS.md build spec + archived original notes
This commit is contained in:
36
.gitignore
vendored
Normal file
36
.gitignore
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
# Secrets
|
||||
.env
|
||||
.env.*
|
||||
*.local
|
||||
*.keystore
|
||||
|
||||
# Go
|
||||
bin/
|
||||
*.exe
|
||||
*.test
|
||||
*.out
|
||||
|
||||
# Python
|
||||
__pycache__/
|
||||
*.pyc
|
||||
.venv/
|
||||
venv/
|
||||
|
||||
# Node / web
|
||||
node_modules/
|
||||
dist/
|
||||
*.visual-check.*
|
||||
|
||||
# Android / Kotlin
|
||||
.gradle/
|
||||
build/
|
||||
local.properties
|
||||
*.apk
|
||||
|
||||
# OS / editors
|
||||
.DS_Store
|
||||
*.swp
|
||||
|
||||
|
||||
# Pi agent generated memory (local state, not source)
|
||||
.pi/memory-vault/
|
||||
16
.pi/settings.json
Normal file
16
.pi/settings.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"packages": [
|
||||
{
|
||||
"source": "/home/sam/.agents",
|
||||
"extensions": [
|
||||
"extensions/pi-config/index.ts",
|
||||
"extensions/filechanges/index.ts",
|
||||
"extensions/ask-user-question/index.ts"
|
||||
],
|
||||
"skills": [
|
||||
"skills/obsidian-cli",
|
||||
"skills/npm-security"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
233
AGENTS.md
Normal file
233
AGENTS.md
Normal file
@@ -0,0 +1,233 @@
|
||||
# Internet Use Control
|
||||
|
||||
> Build spec for Paperclip. Follow the phases in section 11 in order. Do not skip a
|
||||
> phase's Definition of Done. When something is unclear, ask Sam — do not guess.
|
||||
>
|
||||
> **Read `SYSTEM_MAP.md` and `TRANSPORT_DESIGN.md` in this folder first.** They are
|
||||
> the component picture and the MQTT design. This file is the build spec.
|
||||
|
||||
---
|
||||
|
||||
## 1. Purpose and outcome
|
||||
|
||||
A system that limits the kids' screen use on their own devices — Android phones and
|
||||
Windows computers. It applies schedules, blocklists and allowlists on the device,
|
||||
collects usage, and lets Sam override.
|
||||
|
||||
**Outcome (the single success statement):**
|
||||
> Harry's phone and Finn's computer follow their schedule without anyone touching
|
||||
> them, Sam can see their usage, and Sam can change a limit from his phone in
|
||||
> seconds.
|
||||
|
||||
## 2. Scope
|
||||
|
||||
**In scope**
|
||||
- Per-device weekly schedules.
|
||||
- A blocklist of apps (Android package, Windows process) and sites (hostname).
|
||||
- An allowlist during a block (for example, Spotify at night, school sites).
|
||||
- Usage telemetry per device, app, and domain.
|
||||
- Push state to devices; collect presence and telemetry.
|
||||
- On-device warnings and a status view for the kid.
|
||||
- An admin override (keycode on device; remote from the admin app).
|
||||
- Alerts to Sam when a limit is hit.
|
||||
|
||||
**Out of scope**
|
||||
- Anything house-wide. **No router change. No Pi-hole. No DNS server change.**
|
||||
- The kids' Android **client** is this project. The **admin app** is not — it lives
|
||||
in `kotlin_phone_app_admin`.
|
||||
- Chores, quests, or earning screen time. Removed by decision. Do not build it.
|
||||
|
||||
## 3. Users and roles
|
||||
|
||||
| User | Can do |
|
||||
|---|---|
|
||||
| **Sam** | Everything. Session login to the dashboard. Uses the admin app to override. |
|
||||
| **Jo** | Read-only (optional, later). |
|
||||
| **Harry / Finn** | See their own status on their device. Cannot change rules. |
|
||||
|
||||
## 4. Functional requirements
|
||||
|
||||
| ID | Requirement |
|
||||
|---|---|
|
||||
| FR-1 | Register devices: name each one, assign it to a child, choose its platform. |
|
||||
| FR-2 | Weekly schedules: independent on/off windows per weekday, per device. |
|
||||
| FR-3 | Blocklist: apps (Android package name, Windows process name) and sites (hostname). |
|
||||
| FR-4 | Allowlist: things that stay available during a block (Spotify, school sites). |
|
||||
| FR-5 | Publish each device's desired state to MQTT, **retained**, on its own topic. |
|
||||
| FR-6 | Presence: know if a device is online, using the broker's Last Will and Testament. |
|
||||
| FR-7 | Telemetry: receive usage by device, app, and domain, and store a rolling log. |
|
||||
| FR-8 | Web dashboard: devices, schedules, blocklists, allowlists, and usage. |
|
||||
| FR-9 | Kids' app view: what is blocked now, until when, why, and an alert when a block starts. |
|
||||
| FR-10 | Override: a keycode on the device, and remote control from the admin app. |
|
||||
| FR-11 | Admin app API: lock, unlock, grant time, and query state. |
|
||||
| FR-12 | Hub API: `GET /capabilities`, `POST /ask` (read-only), `POST /notify`. |
|
||||
| FR-13 | Alerts via Apprise when a limit is hit or a device goes offline too long. |
|
||||
| FR-14 | Fail safe: when a device cannot reach the broker, it keeps its last state. |
|
||||
| FR-15 | Audit log: every state change, who made it, and when. |
|
||||
|
||||
## 5. Screens and UX flow
|
||||
|
||||
**Screens (web dashboard)**
|
||||
1. **Login** — session form.
|
||||
2. **Dashboard** — every device as a card: name, child, platform, status dot, current state, time left.
|
||||
3. **Schedule editor** — a weekly grid per device.
|
||||
4. **Blocklist / allowlist editor** — one list, tagged by platform.
|
||||
5. **Usage** — per device: top apps, top domains, by day and week.
|
||||
6. **Alerts** — recent limit hits and warnings.
|
||||
|
||||
**Kids' device view (in the kids' client app)**
|
||||
- What is blocked now.
|
||||
- How long until it lifts.
|
||||
- Why (schedule, limit reached).
|
||||
- An alert when a block starts.
|
||||
|
||||
**Main flow**
|
||||
```
|
||||
Sam opens dashboard → sees Harry's phone "locked, 42 min left"
|
||||
→ grants 30 min → the phone unlocks within one second
|
||||
```
|
||||
|
||||
## 6. Look and feel
|
||||
|
||||
Reuse the Family Home Lab design system.
|
||||
|
||||
- Source: `/home/sam/Docker/Containers/family-home-lab/DESIGN.md`
|
||||
- Tokens: `/home/sam/Docker/Containers/family-home-lab/portal/static/tokens.css`
|
||||
- Copy `tokens.css` into this project.
|
||||
|
||||
Canvas `#f6f5f4`, one action blue `#0075de`, Inter, 8px grid, pill CTAs, hairline
|
||||
borders. Include dark mode. Works on a phone browser. Touch targets ≥44px.
|
||||
|
||||
## 7. Data model
|
||||
|
||||
SQLite inside the Control Service. Suggested tables:
|
||||
|
||||
| Table | Holds |
|
||||
|---|---|
|
||||
| `devices` | id, name, child, platform, topic, online, last-seen |
|
||||
| `schedules` | device id, weekday, start, end |
|
||||
| `blocklist` | device id (or group), kind (`app`, `site`), value |
|
||||
| `allowlist` | device id (or group), kind, value |
|
||||
| `state` | device id, locked, until, allow (JSON), updated-at |
|
||||
| `telemetry` | device id, app, domain, seconds, day |
|
||||
| `overrides` | device id, actor, action, until, created-at |
|
||||
| `audit` | actor, action, device id, detail, created-at |
|
||||
|
||||
Retention: telemetry rolls up daily and is kept long. The audit log is append-only
|
||||
and size-capped.
|
||||
|
||||
## 8. Integrations
|
||||
|
||||
| System | Use |
|
||||
|---|---|
|
||||
| **MQTT broker** | The **existing Mosquitto on `.13`**. Per-device credentials and topic ACLs. |
|
||||
| **Apprise** | Alerts to Sam. Running on `.35:8210`. |
|
||||
| **Home Assistant** | Optional: publish device state with MQTT discovery, for dashboards. |
|
||||
| **Hub** | `POST /ask` (read-only), `POST /notify`, `GET /capabilities`. |
|
||||
| **Admin app** | Separate project. Calls this service's API directly. |
|
||||
|
||||
The Hub interface is frozen in `central_ai_llm_master/docs/ask-contract.md`.
|
||||
|
||||
**Control never goes through the Hub.** The admin app calls this service directly.
|
||||
|
||||
## 9. Technology (locked)
|
||||
|
||||
| Item | Value |
|
||||
|---|---|
|
||||
| Backend | **Go** |
|
||||
| UI | **HTMX** + server-rendered HTML |
|
||||
| Database | **SQLite** |
|
||||
| Transport | **MQTT** (existing Mosquitto) + HTTP for setup and queries |
|
||||
| Design | Family Home Lab `tokens.css` |
|
||||
| Host | `.13`, Docker |
|
||||
| Container | **This project's own** image and compose file. |
|
||||
| Port | Allocate one (suggest **8791**). Record it once chosen. |
|
||||
| URL | `webrestrict.lab.audasmedia.com.au` via Caddy on `.35` |
|
||||
| Auth | **Separate session login.** |
|
||||
| Kids' phone client | **Kotlin**, Android Device Owner |
|
||||
| Kids' computer client | **Python** (`psutil`, `pystray`) |
|
||||
|
||||
## 10. Rules and constraints
|
||||
|
||||
1. **Enforcement is device-side and kids-only.** No router, no Pi-hole, no
|
||||
house-wide change.
|
||||
2. **Fail safe is "keep last state".** The device caches its last config.
|
||||
3. **One admin keycode**, shared by every device.
|
||||
4. **No mobile profile.** Away from home behaves the same as at home.
|
||||
5. Only the **Control Service** publishes config. Everything else writes to it over
|
||||
HTTP.
|
||||
6. MQTT uses TLS and per-device credentials with topic ACLs. A device may read only
|
||||
its own config and write only its own status.
|
||||
7. The kids' app never changes its own rules. It only obeys and reports.
|
||||
8. Models go through **OmniRoute**. Secrets come from `environment.d`.
|
||||
9. `/ask` is read-only.
|
||||
10. **Never hard-delete** a device or its history.
|
||||
|
||||
## 11. Build order
|
||||
|
||||
**Phase 1 — Skeleton**
|
||||
Go service, SQLite, config, session login, Dockerfile, compose, Caddy route.
|
||||
*DoD:* the dashboard runs on `.13`, login works, `/health` returns ok.
|
||||
|
||||
**Phase 2 — Devices and state**
|
||||
Device registry, dashboard cards, manual lock/unlock in the UI, state in SQLite.
|
||||
*DoD:* a device can be added and its state changed from the dashboard.
|
||||
|
||||
**Phase 3 — MQTT**
|
||||
Publish retained state per device. Subscribe to status and telemetry. Presence via LWT.
|
||||
*DoD:* a test subscriber receives the state, and a dropped subscriber shows offline.
|
||||
|
||||
**Phase 4 — Computer client**
|
||||
Python client: subscribe to config, apply process blocking, report telemetry, overlay
|
||||
warning, keep last state.
|
||||
*DoD:* a real Windows machine blocks a listed process during a scheduled window.
|
||||
|
||||
**Phase 5 — Phone client**
|
||||
Kotlin Device Owner app: suspend listed packages, overlay, status view, telemetry.
|
||||
*DoD:* a real Android phone suspends a listed app during a window.
|
||||
|
||||
**Phase 6 — Schedules, blocklists, allowlists**
|
||||
Editors in the dashboard. Allowlist honoured during a block.
|
||||
*DoD:* Spotify stays available while other apps are blocked.
|
||||
|
||||
**Phase 7 — Telemetry and usage**
|
||||
Store and display usage by device, app, and domain.
|
||||
*DoD:* the dashboard shows a week of real usage for one device.
|
||||
|
||||
**Phase 8 — Overrides**
|
||||
Device keycode and the admin app API (lock, unlock, grant).
|
||||
*DoD:* the admin app locks a device and it locks in under one second.
|
||||
|
||||
**Phase 9 — Hub and alerts**
|
||||
`/capabilities`, `/ask`, `/notify`, and Apprise alerts.
|
||||
*DoD:* the Hub asks "how long have the kids been on screens today?" and gets a correct answer.
|
||||
|
||||
**Phase 10 — Documentation**
|
||||
*DoD:* project-docs run; Gitea, Obsidian, Outline, Vikunja, and the map are updated.
|
||||
|
||||
## 12. Acceptance criteria
|
||||
|
||||
- A real device follows its weekly schedule without intervention.
|
||||
- A listed app is blocked on the real phone and computer.
|
||||
- Spotify stays available during a block, when allowlisted.
|
||||
- The admin app locks a device in under one second.
|
||||
- Usage is visible per device for at least a week.
|
||||
- The Hub answers a screen-time question correctly.
|
||||
- When the broker is unreachable, the device keeps its last state.
|
||||
- Everything runs in its own container on its own port, behind Caddy.
|
||||
|
||||
## 13. Decisions and remaining questions
|
||||
|
||||
Resolved:
|
||||
1. **Device-side only**, kids only. No router, no Pi-hole.
|
||||
2. **MQTT** with retained messages and LWT presence. `TRANSPORT_DESIGN.md`.
|
||||
3. **Keep last state.** One admin keycode. No mobile profile.
|
||||
4. **Go** for the service. **Kotlin** for the kids' phone client. **Python** for the computer client.
|
||||
5. **Admin app** is a separate project (`kotlin_phone_app_admin`); it calls this service.
|
||||
6. **Chores to unlock time** removed. Not in scope.
|
||||
|
||||
Remaining:
|
||||
1. Pass-through apps during a lock: confirm the list (Spotify, school sites).
|
||||
2. Remote shutdown or sleep for a kid's computer, or blocking only?
|
||||
3. Windows client: a service, or a scheduled task?
|
||||
4. Telemetry retention: how long to keep daily rollups?
|
||||
38
ORIGINAL_NOTES.md
Normal file
38
ORIGINAL_NOTES.md
Normal file
@@ -0,0 +1,38 @@
|
||||
# Internet Use Control — Original Notes (archived)
|
||||
|
||||
> Historical pre-planning notes. **Superseded by `AGENTS.md`.** Do not build from this.
|
||||
|
||||
|
||||
> Kept for provenance. The sections above replace these. Do not build from this.
|
||||
|
||||
```
|
||||
Project Goal & Philosophy
|
||||
- A cross-platform screening system (Android + Windows) that helps kids
|
||||
self-regulate evening screen usage. A gentle enforcement layer, a reminder
|
||||
system (overlays, warnings, sounds), and a notifier via Apprise.
|
||||
- A centralized Docker dashboard for scheduling, logging, and remote management.
|
||||
|
||||
Components (original)
|
||||
- Central Orchestrator (Go + HTMX + Tailwind), SQLite, Docker, behind Caddy at
|
||||
webrestrict.lab.audasmedia.com.au. Session login.
|
||||
- Windows Client (Python + psutil + pystray): background loop, warning overlays,
|
||||
.wav countdowns, process termination, domain logging, ACPI sleep/shutdown,
|
||||
4-digit parent PIN.
|
||||
- Android Agent (Kotlin, Device Owner): ForegroundService, setPackagesSuspended,
|
||||
WindowManager overlay, usage reporting.
|
||||
|
||||
Original phases
|
||||
1. Go + HTMX Docker environment; SQLite tables for schedules and blocklists.
|
||||
2. Client telemetry API (POST /api/log, GET /api/client-config); Apprise triggers.
|
||||
3. Windows monitor script.
|
||||
4. Kotlin Device Owner workspace.
|
||||
5. LAN control integration.
|
||||
|
||||
Ideas (kept)
|
||||
- Usage stats for kids: total time, top apps, by day and week.
|
||||
- Some apps allowed through: Spotify, school sites and timetables.
|
||||
|
||||
Superceded by the decisions in this spec: the 30-second poll is replaced by MQTT;
|
||||
the router/DNS and "network hooks" ideas are dropped in favour of device-side
|
||||
enforcement; the Rust idea is dropped; the chores idea is removed.
|
||||
```
|
||||
144
SYSTEM_MAP.md
Normal file
144
SYSTEM_MAP.md
Normal file
@@ -0,0 +1,144 @@
|
||||
# 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
|
||||
|
||||
```mermaid
|
||||
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.
|
||||
157
TRANSPORT_DESIGN.md
Normal file
157
TRANSPORT_DESIGN.md
Normal file
@@ -0,0 +1,157 @@
|
||||
# Internet Control — transport design (MQTT vs polling)
|
||||
|
||||
**Date:** 2026-10-10
|
||||
**Question from Sam:** can the internet controls use a broadcast mechanism
|
||||
(Mosquitto / MQTT) as the source of truth? Devices check in, apply state, and
|
||||
block on device. Then Harry's phone can be off while his desktop stays on.
|
||||
|
||||
---
|
||||
|
||||
## Short answer
|
||||
|
||||
**Yes — and it is better than polling. But do not make the broker the database.**
|
||||
|
||||
Use both, each for its own job:
|
||||
|
||||
| Layer | Job |
|
||||
|---|---|
|
||||
| **MQTT (Mosquitto)** | Push state, presence, and telemetry. Instant. |
|
||||
| **HTTP API** | Setup and queries: schedules, blocklists, the portal, the Hub. |
|
||||
| **The service (Go) + SQLite** | The **source of truth**. MQTT is transport, not storage. |
|
||||
|
||||
Mosquitto is already installed. Home Assistant already speaks MQTT. This is proven
|
||||
ground.
|
||||
|
||||
---
|
||||
|
||||
## Why MQTT beats the 30-second poll
|
||||
|
||||
| Property | Poll every 30 s | MQTT |
|
||||
|---|---|---|
|
||||
| Enforcement delay | up to 30 s | instant |
|
||||
| Presence (online/offline) | must be inferred | free, via Last Will and Testament |
|
||||
| Reconnect state | next poll | **retained** message delivers it at once |
|
||||
| Per-device state | possible | natural (one topic per device) |
|
||||
| Device discovery | service must know addresses | device announces itself |
|
||||
|
||||
---
|
||||
|
||||
## Topic design
|
||||
|
||||
| Topic | Direction | Retained | Payload |
|
||||
|---|---|---|---|
|
||||
| `restrict/<device>/config` | service → device | **yes** | desired state |
|
||||
| `restrict/<device>/status` | device → service | yes | online, app, usage |
|
||||
| `restrict/<device>/telemetry` | device → service | no | periodic usage |
|
||||
| `restrict/<device>/cmd` | service → device | no | one-shot command |
|
||||
| `restrict/<device>/ack` | device → service | no | command result |
|
||||
|
||||
### Example — the "phone off, desktop on" case
|
||||
|
||||
```
|
||||
restrict/harry_phone/config {"locked": true, "until": "2026-10-10T21:00Z", "allow": ["spotify"]}
|
||||
restrict/harry_desktop/config {"locked": false}
|
||||
restrict/finn_phone/config {"locked": true, "until": "2026-10-10T21:00Z"}
|
||||
```
|
||||
|
||||
Each device subscribes only to its own config. Retained means a phone that
|
||||
reconnects is locked again **immediately**, with no waiting.
|
||||
|
||||
---
|
||||
|
||||
## Security (do not skip this)
|
||||
|
||||
- TLS, plus a per-device username/password or a client certificate.
|
||||
- **Topic ACLs.** A device may read only **its own** config and write only **its
|
||||
own** status. Without this, a child can publish a fake "unlocked".
|
||||
- Rotate device credentials when a device is removed.
|
||||
|
||||
---
|
||||
|
||||
## Keep the HTTP API
|
||||
|
||||
- Admin portal: schedules, blocklists, usage history.
|
||||
- The Hub: `/ask` (*"how long have the kids been on screens?"*), `/capabilities`.
|
||||
- Approval callbacks.
|
||||
|
||||
Setup and questions are request/response. That is what HTTP is for. Do not force
|
||||
them through MQTT.
|
||||
|
||||
---
|
||||
|
||||
## Enforcement: device only, kids only (corrected)
|
||||
|
||||
Rules apply to the **kids' devices only**. Nothing house-wide. No router change.
|
||||
No Pi-hole. So filtering happens **on the device**:
|
||||
|
||||
| Target | How (device-local) |
|
||||
|---|---|
|
||||
| An app (YouTube, a game) | Block the app. Android Device Owner `setPackagesSuspended`. Windows process kill. |
|
||||
| A website by name | A **device-local** filter. Android: a local `VpnService`. Windows: hosts file or a firewall rule. |
|
||||
| Everything except Spotify | Block all, allow a short list — on the device. |
|
||||
|
||||
MQTT carries the commands. The device applies them to itself.
|
||||
|
||||
---
|
||||
|
||||
## Anti-bypass (honest)
|
||||
|
||||
The client runs on the device, so a determined child can interfere. Reasonable
|
||||
steps, not a fortress:
|
||||
|
||||
- **Android Device Owner** — the app cannot be uninstalled or force-stopped.
|
||||
- **Windows service** with auto-restart and a watchdog.
|
||||
- A child can buy another SIM. That is accepted. Aim for **reasonable friction**,
|
||||
not a lockdown.
|
||||
|
||||
---
|
||||
|
||||
## Fail safe (when a device cannot reach the broker)
|
||||
|
||||
The device follows instructions. If it cannot hear the broker, what should it do?
|
||||
|
||||
- **Fail-open:** assume "not locked". A child turns off Wi-Fi and plays games.
|
||||
- **Fail-closed:** assume "locked". A server reboot locks everyone out.
|
||||
- **Keep last state:** keep following the last instruction. An outage changes
|
||||
nothing.
|
||||
|
||||
**Recommended: keep last state.** Use a **retained** MQTT message, so a
|
||||
reconnecting device gets the current instruction at once. Add an **admin
|
||||
override** for real exceptions (travel, holidays).
|
||||
|
||||
The open question: after a **very long** outage, does the device still keep the
|
||||
last state, or does it lock?
|
||||
|
||||
---
|
||||
|
||||
## The broker
|
||||
|
||||
Use the **existing Mosquitto on `.13`**. Do not add a second broker. Isolation is
|
||||
done with **per-device credentials and topic ACLs**. A second broker is optional
|
||||
hardening only.
|
||||
|
||||
---
|
||||
|
||||
## Who publishes config
|
||||
|
||||
See `SYSTEM_MAP.md`. Short answer: **the Control Service, and only the Control
|
||||
Service.** Everything else writes to it over HTTP.
|
||||
|
||||
---
|
||||
|
||||
## Recommendation
|
||||
|
||||
1. SQLite in the service stays the **source of truth**.
|
||||
2. The service publishes desired state to MQTT (**retained**).
|
||||
3. Devices subscribe, apply **instantly**, and block locally.
|
||||
4. Telemetry returns over MQTT; the service writes it to SQLite.
|
||||
5. HTTP stays for setup and Hub queries.
|
||||
6. **Do not** make the broker the database.
|
||||
7. Use a **per-device** topic so state is per device, not per family.
|
||||
|
||||
## Open questions
|
||||
|
||||
1. Fail safe: when the broker is unreachable, lock or keep the last state?
|
||||
2. One broker only, or one broker per VLAN? (Mosquitto is single today.)
|
||||
3. Should device state also appear in Home Assistant (MQTT discovery)?
|
||||
4. Who publishes config — only the Go service, or also the admin app directly?
|
||||
Reference in New Issue
Block a user