Files
internet_use_control/docker-compose.yml
Sam Rolfe 4f233a8012 Phase 8: overrides (shared keycode + versioned admin lock/unlock/grant API)
Build on Phases 1–7. Adds the two override paths from AGENTS.md §4 FR-10/FR-11:
the shared device keycode and the versioned admin HTTP API the separate Kotlin
admin app will call.

- store: migration 004 adds state.grant_until and a settings table. New
  overrides.go records every override in `overrides` and verifies the shared
  keycode (bcrypt; one code for every device, §10 rule 3). ExpireLocks also
  clears an expired grant, and NextScheduleBoundary includes the grant end so a
  device re-locks on time.
- effective state: a grant unlocks a device inside a schedule window; a manual
  lock clears a grant and a grant clears a manual lock. The retained config
  payload gains grant_until (additive, v stays 1).
- web: /api/v1/admin/... (list, state, lock, unlock, grant) guarded by
  CONTROL_ADMIN_API_TOKEN, separate from the session login, plus the on-device
  keycode endpoint POST /api/v1/device/override with per-address throttling.
  A state change is published retained before the API responds.
- cmd/control: `keycode` subcommand and start-up seeding from
  CONTROL_DEVICE_KEYCODE_HASH.
- deploy/verify-phase8.sh + `make verify-phase8` prove the definition of done:
  an API lock reaches a real broker subscriber in well under a second.
- Version 0.6.0-phase8 (compose, Makefile, docs).

Measured on .13: the lock reached the TLS broker subscriber in 7 ms (handler
1 ms, request to retained publish accepted).

Tests: go vet ./... and go test -count=1 ./... pass in golang:1.25-alpine.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-10-13 16:27:46 +11:00

47 lines
1.5 KiB
YAML

# Internet Use Control — Control Service (own image, own container, own port).
# Deploy on .13: cd /home/sam/paseo/projects/internet_use_control && docker compose up -d --build
# The service is the single source of truth (SYSTEM_MAP.md). Only it publishes config.
#
# MQTT is transport, not storage: the service publishes retained config to the
# shared Mosquitto on `.13:1883` and writes what devices report into SQLite.
name: webrestrict
services:
control:
build:
context: .
args:
VERSION: "0.6.0-phase8"
image: webrestrict/control:0.6.0-phase8
container_name: webrestrict-control
restart: unless-stopped
env_file:
- environment.d/control-service.env
ports:
- "8791:8791"
# The broker is the Mosquitto container on the host. No host IP is baked
# in: the gateway is resolved at start, so CONTROL_MQTT_BROKER can stay
# "tcp://host.docker.internal:1883".
extra_hosts:
- "host.docker.internal:host-gateway"
volumes:
- webrestrict-data:/data
# The lab CA for broker TLS verification. Read-only and optional: the
# file is absent until the TLS listener exists.
- ./deploy/mosquitto/tls:/etc/webrestrict/tls:ro
healthcheck:
test: ["CMD", "wget", "-qO-", "http://127.0.0.1:8791/health"]
interval: 30s
timeout: 5s
retries: 3
start_period: 10s
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
volumes:
webrestrict-data: