Files
internet_use_control/README.md
Sam Rolfe 7c88d3f52b Phase 2: device registry, dashboard cards, manual lock/unlock, audit
- Device registry: add/edit name, child and platform, persisted in devices.
- Stable per-device topic derived from name + id, unchanged by renames.
- Desired state persisted in state; timed locks expire and are audited.
- Dashboard cards: name, child, platform, status dot, state and time left.
- Manual lock/unlock forms with CSRF; every change appends to audit (FR-15).
- Append-only activity list on the dashboard. No hard deletes (rule 10).
- Store and handler tests; version bumped to 0.2.0-phase2.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-10-13 14:18:02 +11:00

139 lines
5.5 KiB
Markdown

# Internet Use Control — Control Service
The **Control Service** is the brain and the single source of truth for the
Internet Use Control project (`SYSTEM_MAP.md`). It holds schedules, blocklists,
per-device state and usage, and serves the web dashboard. Only it publishes
device config.
This repository currently contains **Phase 2 — Devices and state**
(AGENTS.md §11), on top of the Phase 1 skeleton:
- Go service with its own image (`Dockerfile`) and compose file.
- SQLite schema for the whole data model (§7), applied through versioned
migrations.
- Configuration from the process environment (`environment.d`); no secrets in
the repo.
- A separate session login for Sam.
- `GET /health`.
- HTMX + server-rendered dashboard styled with the Family Home Lab design
system (`tokens.css` copied in).
- **Device registry**: add and edit a device, assign it to a child, choose its
platform. Persisted in `devices`.
- **Dashboard cards**: name, child, platform, status, current state, time left.
- **Manual lock/unlock** from the dashboard, persisted in `state` and written
to the append-only `audit` log (FR-15).
Enforcement, MQTT, schedules, blocklists and the clients arrive in later
phases. Nothing here touches the router, Pi-hole, or DNS.
## Quick start (on `.13`)
```bash
cd /home/sam/paseo/projects/internet_use_control
# 1. Create the env file from the template and set real values.
cp environment.d/control-service.env.example environment.d/control-service.env
$EDITOR environment.d/control-service.env
# 2. Build and start. Own image, own container, port 8791.
docker compose up -d --build
# 3. Prove it.
curl -s http://127.0.0.1:8791/health
# {"status":"ok","service":"control-service","version":"0.2.0-phase2",...}
```
Open <http://127.0.0.1:8791/> in a browser. It redirects to `/login`. Sign in
with `CONTROL_ADMIN_USERNAME` and `CONTROL_ADMIN_PASSWORD`.
## Configuration
All configuration is environment variables. Docker Compose loads them from
`environment.d/control-service.env` (gitignored). The variables can also be
exported from `~/.config/environment.d/10-secrets.conf` instead; the service
reads only the process environment.
| Variable | Default | Purpose |
|---|---|---|
| `CONTROL_ADDR` | `:8791` | Listen address |
| `CONTROL_DB_PATH` | `/data/control.db` | SQLite file |
| `CONTROL_SESSION_SECRET` | — (required) | Signs the session cookie, ≥ 16 chars |
| `CONTROL_SESSION_TTL` | `12h` | Session lifetime |
| `CONTROL_COOKIE_SECURE` | `true` | Secure cookie flag; keep true behind HTTPS |
| `CONTROL_ADMIN_USERNAME` | `sam` | Dashboard login |
| `CONTROL_ADMIN_PASSWORD` | — | Plaintext password (option A) |
| `CONTROL_ADMIN_PASSWORD_HASH` | — | bcrypt hash (option B, preferred) |
| `CONTROL_APP_ENV` | `production` | Label shown in the footer |
| `CONTROL_LOG_LEVEL` | `info` | `debug`/`info`/`warn`/`error` |
Generate a bcrypt hash without host Go:
```bash
docker run --rm --entrypoint /app/control webrestrict/control:0.2.0-phase2 \
hash-password 'your-password'
```
## Endpoints (Phase 2)
| Method | Path | Notes |
|---|---|---|
| `GET` | `/health` | JSON. `status: ok`, database check. No auth. |
| `GET` | `/login` | Session login form. |
| `POST` | `/login` | Verifies credentials, sets a signed session cookie. |
| `POST` | `/logout` | Clears the session. |
| `GET` | `/` | Dashboard: service status, device cards, recent activity. |
| `GET` | `/partials/status` | HTMX fragment, polled every 10 s. |
| `GET` | `/devices/new` | Add-device form. |
| `POST` | `/devices` | Creates a device. |
| `GET` | `/devices/{id}/edit` | Edit-device form. |
| `POST` | `/devices/{id}` | Updates name, child, platform. |
| `POST` | `/devices/{id}/lock` | Locks now; optional `minutes` (0 = until unlocked). |
| `POST` | `/devices/{id}/unlock` | Unlocks now. |
Every form POST needs the `csrf` field from the session cookie. State changes
append to the `audit` table. Devices are never hard-deleted (§10 rule 10).
## Development
There is no host Go toolchain on `.13`; use the cached builder image.
```bash
make test # go test ./... in golang:1.25-alpine
make vet # go vet ./...
make build # build the container image
make up # docker compose up -d --build
make logs # follow container logs
```
## Layout
```
cmd/control/ service entry point (+ hash-password helper)
internal/config/ environment configuration
internal/store/ SQLite open, migrations, audit, devices + state
internal/store/migrations/001_init.sql
internal/auth/ signed-cookie session login + CSRF
internal/web/ HTTP handlers, templates, static assets
devices.go device registry + manual lock/unlock handlers
templates/ login, dashboard, device form, HTMX partials
static/ tokens.css (Family Home Lab), app.css, htmx, Inter fonts
Dockerfile own image (multi-stage, CGO-free)
docker-compose.yml own container, port 8791
environment.d/ control-service.env.example (real file gitignored)
deploy/caddy/ Caddy vhost for webrestrict.lab.audasmedia.com.au
```
## Caddy
`deploy/caddy/webrestrict.caddy` holds the vhost for
`webrestrict.lab.audasmedia.com.au`. The terminating Caddy runs on `.35`
(sam-Ubuntu1); see that file for the two proxy patterns and the apply commands.
## Non-goals (Phase 2)
- No MQTT publishing (Phase 3).
- No schedules, blocklists or allowlists (Phase 6).
- No telemetry or usage screens (Phase 7).
- No admin API (Phase 8).
- No Hub contact (Phase 9).