Phase 1: Control Service skeleton (Go, SQLite, session login, Docker, Caddy)

- Go service with its own multi-stage Dockerfile and docker-compose.yml; runs
  as container webrestrict-control on port 8791.
- SQLite schema for the full AGENTS.md §7 data model via versioned migrations
  (PRAGMA user_version); audit log is append-only.
- Configuration from the process environment; the real env file is gitignored
  and a template is committed. No secrets in the repo.
- Separate session login (signed HMAC cookie + CSRF) for Sam.
- GET /health; HTMX + server-rendered dashboard styled with the Family Home Lab
  tokens.css (copied) and Inter fonts; dark mode; phone-friendly.
- Caddy vhost for webrestrict.lab.audasmedia.com.au applied on .35 (Let's
  Encrypt cert issued); route recorded in deploy/caddy/webrestrict.caddy.
- Unit tests for config, auth and store.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
2026-10-13 14:11:38 +11:00
parent 5368225f23
commit c83c9df0a3
29 changed files with 2119 additions and 0 deletions

121
README.md Normal file
View File

@@ -0,0 +1,121 @@
# 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 1 — Skeleton** (AGENTS.md §11):
- 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).
Enforcement, MQTT, devices, schedules 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.1.0-phase1",...}
```
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.1.0-phase1 \
hash-password 'your-password'
```
## Endpoints (Phase 1)
| 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. Redirects to `/login` when not signed in. |
| `GET` | `/partials/status` | HTMX fragment, polled every 10 s. |
## 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
internal/store/migrations/001_init.sql
internal/auth/ signed-cookie session login + CSRF
internal/web/ HTTP handlers, templates, static assets
templates/ login, dashboard, 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 1)
- No MQTT publishing (Phase 3).
- No device registry or lock/unlock UI (Phase 2).
- No admin API (Phase 8).
- No Hub contact (Phase 9).