Commit Graph

4 Commits (c10d803a1264971b7432515162c62cda535fbbcd)

Author SHA1 Message Date
Amir Alexander Abdelbaki c10d803a12 Per-person digests, RCI sources, agendas, archive and TV power
digest-engine grows from a single household digest into four per-person
sections, and gains the memory and inputs to make them worth reading.

Per-person sections. identity owns a digest_sections column per person,
edited in the admin panel and read by digest-engine at the start of every
run (GET /digest-preferences). A run generates the union the household
asked for: a section nobody wants costs no LLM call and no ingestion of
its sources. Each surface then filters to the person HA resolved. The
display half is a filter, not an access control, and says so.

Network is its own section, split out of household so the two can be
wanted separately.

Political section rebuilt around four questions (global class struggle,
organising in Vorarlberg, mid-term consequences, the International and
comrades' reports). ~30 international feeds added, each carrying owner
and bias, with a symmetric ownership analysis rather than a
reliability ranking; Zionist outlets get an explicit zero-trust rule that
is not inversion. RCI social/podcast ingestion (YouTube Atom, podcast RSS,
public Telegram via the existing session) feeds a watch-later window.
Globe markers carry summaries with fold-out sources; counter_run drops
citations whose URLs are not in the context.

Meeting agendas: a Tagesordnung arriving by mail or WhatsApp is matched to
its calendar event, read with pypdf, and its points extracted
mechanically. The political section owns the contents and derives
"Political todos"; the household section is told only that an agenda
exists, enforced structurally.

The archive keeps every ingested item and measured number across runs, so
trends may finally be stated with figures and dates attached.

ntfy push after each run, assembled from existing narrations, gated by the
same per-person sections.

identity gains GET /speaker: automatic recognition for the voice path from
BLE plus recent face sightings. Unresolved means show less, never ask, and
nothing displays a digest because someone walked past a screen.

OPNsense credentials move into CoreSystemConfig.json; thin clients gain a
Display switch (HDMI-CEC, DPMS fallback) so an empty room stops powering a
TV.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NpKsMV1Q2EHJ5cZVoLvK9M
2026-08-06 13:30:47 +02:00
Amir Alexander Abdelbaki 00991b9864 Add an HTTPS reverse proxy, and auto-generate empty service tokens
TLS front door (Caddy) for this repo's own services: one hostname, a permanent
HTTP->HTTPS redirect, and the API on the same origin as the page. Home Assistant,
Grocy, Frigate and the rest keep their own ports — fronting HA brings its own
auth and websocket concerns and none of the problems below need it.

Three concrete reasons, not hygiene:

1. The admin panel's URL carries IDENTITY_TOKEN, which grants administrative
   access to the person registry AND to the device grants that decide whether a
   smart lock opens. On plain HTTP that is readable by anything on the smart-home
   VLAN — a segment deliberately full of cheap IoT hardware.
2. getUserMedia requires a secure context, so register.html's camera cannot work
   over http://192.168.x.x at all. It also failed SILENTLY: the call site used
   `navigator.mediaDevices?.getUserMedia(...).then().catch()`, and optional
   chaining short-circuits the whole chain — so neither handler ran, no "Camera
   unavailable" message appeared, and registration proceeded photo-less with no
   explanation. Verified in node. register.js now checks explicitly and says
   which of the two cases it is; HTTPS is what actually fixes it.
3. Serving the page over HTTPS while ?api= still pointed at http://...:8097 would
   have every call blocked as mixed content, so the API has to be proxied too.

tls: internal runs Caddy's own CA (no external dependency, works with no WAN at
all) with tools/export-proxy-ca.sh to fetch the root; tls: custom takes an
existing cert, which is how you'd use a real one from a DNS-01 challenge without
exposing anything. HSTS is deliberately not set — with an internal CA it would
turn a dismissible warning into a hard failure. Kiosks stay on plain HTTP for
now: a full-screen cert interstitial is not dismissible on a device with no
keyboard, so moving them is documented as a follow-up rather than done blind.

Empty service tokens now fill themselves in on the first build and land in
tokens.txt with what each is for. They are written BACK to the config, which is
the part that matters: a token is only useful because two machines agree on it,
so generating fresh randomness per build would produce a door panel that cannot
talk to the service it was built for. Blanks are filled once and never
overwritten. ha_token, mqtt_password, admin_password_hash and ssh_authorized_key
are deliberately not invented — tokens.txt lists them with the reason, so an
empty field is never a mystery.

32 new checks: token generation and stability across runs, disabled services
skipped, tokens.txt contents, config still valid after the rewrite, Caddyfile
routes and redirect, conditional pantry route, both TLS modes, and that the
derived admin URL keeps page and API on one origin. Nothing has been run against
a real Caddy — see proxy/README.md.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 14:04:02 +02:00
Amir Alexander Abdelbaki 2ae5cb3449 Add a floorplan editor to the admin panel
Closes the oldest deferred item in the plan (open decision #22). /presence has
reported a best-effort `room` since Phase 6, but there was nothing to plot it
on: no floor plan, no room list, no coordinate format. The reason it stayed
deferred was sound — nothing here knows the shape of this flat, and inventing a
coordinate format against a guess would have been the wrong kind of work. So the
plan is DRAWN, by hand, in a new Floorplan tab.

Levels hold rooms; rooms are polygons (real rooms aren't rectangles) with an
optional background image to trace over. Click corners to draw, drag handles to
adjust. Each room maps to the HA area string /presence already reported, and the
editor offers a pick-list of the areas HA is actually reporting rather than
asking anyone to retype an area_id from Developer Tools — same anti-typo
reasoning as CoreSystemConfig.json. Tick "Show who's home" and occupied rooms
light up.

Coordinates are normalised 0-1 rather than pixels: the plan has to render on a
laptop now and possibly a wall panel later, and pixels would be right on exactly
one of them. The tradeoff is documented — replacing a background with one of a
different aspect ratio distorts existing rooms.

Two choices about failure worth calling out. A room with no HA area is legal and
labelled as such on the plan, because drawing the flat and wiring up presence are
separate jobs. And anyone who can't be placed — home with no room resolved, or
reporting an area no room claims — is listed under the plan, with unclaimed areas
named so you know what's left to draw. A floor plan that quietly lost people
would be worse than no floor plan.

The whole feature is still gated on AREA_ATTRIBUTE being right, which remains an
unconfirmed guess at what Bermuda publishes; every room can be drawn correctly
and still never light up. The editor makes that diagnosable rather than
mysterious, but can't fix it. Added to identity's verification list.

38 backend checks: level and room CRUD, polygon validation (too few points, out
of range, non-numeric, over the vertex cap), duplicate-area refusal, image
round-trip, the presence join including both unplaceable cases, area suggestions
degrading when HA is down, and cascade on level delete. The editor UI itself has
not been opened in a browser.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 13:45:45 +02:00
Amir Alexander Abdelbaki ea82ee70ad Add tools/ and CoreSystemConfig.json — one source of truth for every build
Installation was six scripts each carrying its own copy of the container host's
IP, three that had to agree on IDENTITY_TOKEN, and every service URL typed by
hand with a port in it. Any one could be wrong, and the symptom was always the
same and always late: an image that boots fine and then can't reach something,
found after a 40-minute build and a reboot.

Two properties fix that class of bug:

- Nothing is written twice. No script in tools/ contains an IP, port or token.
- Anything derivable is derived. You give the subnet prefix once and one last
  octet per host; every address and service URL is computed from those.

THE TWINNED PAIR. container_host.ip_last_octet 12 and llm_host 13 mean the
container host's OLLAMA_HOST *is* http://<prefix>.13:11434 — computed in the
same build, not typed into two files and kept in sync. Move the LLM host to .21
and the container host's Ollama URL follows; change the subnet and both halves
move along with every kiosk's URLs. Neither image can be built pointing at an
address the other isn't using. Both carry the same SMARTHOME_PAIR_ID (a hash of
the config's meaning, not its bytes) so two USB sticks can be checked against
each other later.

validate-config.py runs before every build and refuses to start on an error, so
a mistake costs seconds not an hour. It catches duplicate ports (including the
music_assistant/pantry_vision 8095 clash that Compose can't see because MA runs
network_mode:host — open decision #31), both hosts on one address, duplicate
hostnames across kiosks and audio endpoints, placeholder tokens (checked before
the length check, so padding "changeme" to 32 chars doesn't pass), a private key
pasted where the public one goes, and a kiosk pointed at a disabled service.

build-all.sh is the normal entry point — the images are a set that has to agree
with itself, so building one is the exception. It builds the core pair, every
kiosk, and every audio endpoint including both architectures (amd64 live-build
ISO and arm64 rpi-image-gen img are different toolchains, not one image).

The two new host ISOs install unattended with everything burnt in, including
service env files generated from derived values — which permanently removes the
class of bug that had chores.env shipping IDENTITY_URL=http://127.0.0.1:8097.
setup-container-host.sh and setup-llm-host.sh now read every config value as
${VAR:-default} so the images configure them without editing.

That also makes every ISO a credential: Wi-Fi PSK, tokens, MQTT and HA
credentials are readable by anyone holding the stick. .gitignore covers the
filled-in CoreSystemConfig.json and build-output/.

Tested: 43 config validation/derivation checks and 44 builder checks against the
real code paths with only `lb` stubbed — every generated env file, preseed,
network config, first-boot unit and build stamp is verified, including that a
port collision refuses the build before writing anything. No ISO has been built;
`lb build` needs live-build, root and a long fetch. tools/README.md says so.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 13:22:25 +02:00