Files
obsidian-vault/200 projects/Tools Software WebUI/Internet Use Control — Kids' Screen Time Limits.md

5.4 KiB

title, type, status, created, updated, tags, aliases, skills, tools
title type status created updated tags aliases skills tools
Internet Use Control — Kids' Screen Time Limits project active 2026-10-14 12:12 2026-09-26
project
family
architecture
go
kids
time
internet-control
mqtt
android
sqlite
htmx
Internet Use Control
Kids Screen Time
Go backend development
HTMX server-rendered dashboard UI
SQLite data modelling and versioned migrations
MQTT transport with TLS and per-device topic ACLs
Android Accessibility Service and full-screen overlay (Kotlin)
Android VpnService for device-local site filtering
Versioned HTTP API design for a separate consumer
Docker and Caddy deployment
Go
SQLite
HTMX
Mosquitto
Docker
Caddy
Apprise
Kotlin
Android Accessibility Service
Android VpnService

Internet Use Control — Kids' Screen Time Limits

What is this project?

This project limits screen time on the children's own devices. Harry's phone and Finn's computer should follow their weekly schedule with nobody touching them. Sam sees their usage on one page, and Sam changes a limit from his phone in seconds.

The rules apply to the kids' devices only. Nothing changes for the rest of the house. There is no router change, no Pi-hole and no DNS change.

The main piece is the Control Service. It runs on .13 in Docker. It holds the schedules, the block lists, the allow lists and the usage in SQLite. It serves a small web dashboard. It is the one place that writes the rules.

A device gets its rules over MQTT. The Control Service publishes the wanted state for each device as a retained message, so a phone that reconnects is limited again at once. The device also sends its status and its usage back over MQTT. If a device cannot reach the broker, it keeps its last rules.

What it does

  • Weekly schedules. A grid per device, Monday to Sunday. A window means the rules are on. A window can cross midnight.
  • Block lists. An app (Android package name) or a site (hostname).
  • Allow lists. Spotify at night, school sites. The device blocks everything on the block list except the allow list.
  • Usage. The dashboard shows a week per device: the daily total, the top apps and the top sites.
  • Overrides. A versioned HTTP API for the admin app (lock, unlock, grant), plus one shared keycode that a child can type on the device for a short exemption.
  • Alerts. Apprise tells Sam when a limit is reached or a device is offline too long.
  • A Hub answer. The family Hub can ask "how long have the kids been on screens today?" and gets a real answer. That question path is read-only.

Why it matters (what it shows)

This is a full home-lab product, not a script. It shows a Go service with no heavy framework, an HTMX dashboard with no build step, a small SQLite store with versioned migrations, real MQTT with TLS, per-device accounts and topic access control, and a versioned API that another project consumes.

The Android side is the interesting part. Device Owner was rejected, because it needs a factory reset of the children's phones. Instead the phone blocks by interruption: an Accessibility Service watches the foreground app, and a full-screen overlay covers a blocked app. The child can disable it, and that is accepted. The real enforcement is the tamper alert: when the Accessibility Service stops, the app is force-stopped, or the overlay permission is removed, the phone reports it and Sam gets an alert. Site filtering uses a device-local VpnService. There is no wipe, no Device Owner and no managed profile.

Architecture

flowchart LR
    SAM["Sam<br/>browser dashboard"] --> CADDY["Caddy on .35"]
    CADDY --> CS["Control Service<br/>Go + SQLite :8791"]
    ADMIN["Admin app"] -->|"/api/v1"| CS
    HUB["Hub"] -->|"read-only /ask"| CS
    CS -->|"retained config"| MQ["Mosquitto on .13<br/>8883 TLS + topic ACLs"]
    MQ -->|"own config topic"| PHONE["Kids' Android client<br/>Kotlin"]
    PHONE -->|"status + telemetry"| MQ
    MQ -->|reports| CS
    CS --> APPRISE["Apprise<br/>alerts + tamper"]

A picture of the two MQTT planes: port 1883 stays plain and anonymous for legacy consumers, and port 8883 is TLS with per-device credentials and topic ACLs. A device on 8883 may read only its own config topic and write only its own status and telemetry topics. Without that rule a child could publish a fake "unlocked".

What is still missing (state it plainly)

The Phase 4 Windows computer client is deliberately deferred and is not built. There is no Python client and no process blocking on a computer. The project stands at 9 of 10 phases for that single reason. Everything else in the build spec is done and running.

Where it lives

Status

Built and deployed. Phases 1, 2, 3, 5, 6, 7, 8, 9 and 10 are done. The phones are limited by the Android client. Only the Windows client (Phase 4) remains, and it is deferred on purpose.