Commit Graph

7 Commits (main)

Author SHA1 Message Date
Amir Alexander Abdelbaki a948f4b375 Workshop assistant, fleet monitoring, infra health, and shared endpoint surfaces
Adds the workshop/office assistant and the plumbing several other features
were waiting on. The through-line: every new capability that could act on
its own proposes instead, and says out loud when it does not know something.

New service — workshop/
  Project notebook (workshop.db) plus a never-pruned knowledge store
  (workshop-knowledge.db): standing workflow instructions by activity,
  keyword facts, durable project learnings, and the household's ONE hardware
  inventory. GET /context returns everything applying right now in one call,
  so the assistant is told the standing considerations rather than reminded
  of them. Two databases because they have different lifetimes: rebuilding
  the project store must not take the note about how you solder with it.

  Hardware statuses distinguish reserved (still on the shelf) from in_use
  (installed and working) — "can I use this right now" has different answers
  for the two, and naming a project on an in_use item never silently demotes
  it.

  Gitea repos with append-only history: commit/push/branch yes, unattended;
  force-push/rebase/amend/reset/filter-repo never, enforced server-side by
  branch protection rather than only by this code refusing. When history
  genuinely must be scrubbed, /scrub-request prints the commands for a human
  to run — the manual step is the safety mechanism.

  Fleet scripts: one monitoring-agent script per kind of machine, fetched by
  each endpoint's fleet-bootstrap timer. Remote code execution by design, so
  the constraints are the design — upload is a draft, publishing is separate,
  scripts live in SQLite rather than on the writable share, every version is
  kept, and the endpoint verifies the checksum and reports pass or fail.
  Slots exist for the ESP32s and network appliances that cannot run a script
  at all, holding the CheckMK-server-side config instead.

Infrastructure health
  opnsense becomes a LIST of firewalls, each named, keyed by name rather than
  index. CheckMK joins it. Both are polled by workshop (always-on) and read
  by digest-engine, so the digest can say "critical since Tuesday" instead of
  quoting a six-hour-old snapshot. Three states, because "I could not ask" is
  not "nothing is wrong".

pantry-vision
  All four stock movements are camera-driven; stock counts individual units
  and folds brand-free via Grocy product groups. Door-sensor-triggered
  appliance cameras record sightings as hints with timestamps, never as
  stock — a camera at a door cannot tell in from out.

identity
  Per-person colour and settable profile picture, assigned to avoid collisions
  between people sharing an initial, on the 2-bit-per-channel lattice a colour
  Pebble renders natively.

render/ — shared, vendored, dependency-free
  media-visualiser: two-tier by necessity, since most endpoints have no local
  audio; the synthetic tier says on screen that it is not an analysis.
  floorplan-3d: canvas 2D rather than three.js — the scene is prisms on a
  plane, which an isometric projection draws in ~200 lines, predictably on
  weak panels, with the frontend still at zero dependencies.

Config and fleet plumbing
  Rooms are one vocabulary (an HA area_id) from CoreSystemConfig through the
  builders to suggested_area. Keycloak and FreeIPA are coupled as one
  decision with USR_HA_ group naming, declaration-only for now and validated
  as such. Immich alongside the photo share, read-only. Thin clients get the
  full media-key set for a wireless remote.

Docs: fridge-item-location, workshop-assistant, rooms-and-endpoints,
endpoint-surfaces, pebble-presence-watchface.

Testing is stubbed suites and headless unit checks only — no real Grocy,
camera, vision model, CheckMK, Gitea, Samba or browser has been involved.
The CheckMK API shape and Gitea's branch-protection payload are written from
documentation and have version-sensitive field names.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FanS1vyE2gLhGkqKq6HtYj
2026-08-10 14:54:37 +02:00
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 bbeabde97e Give every config array entry its own image, and build arm64 audio once
Follow-up to the tools/ build system: the kiosk and amd64 audio-endpoint
builders left their ISO in the live-build tree under live-build's fixed
filename, and never moved it to build-output/. Configure two thin clients and
build-all would build both into the same path — the second silently overwriting
the first, leaving one ISO carrying the second room's hostname and no sign the
first was ever produced.

core_publish_image() now moves each finished image to
build-output/smarthome-<hostname>-<pairid>.iso, so one array entry produces one
artifact. Verified: two thin clients now yield two distinct ISOs, and the
per-type builders still refuse with the real list when the hostname is omitted
or wrong.

The arm64 audio endpoint is the deliberate exception and is now handled as one:
Pi Imager sets hostname/Wi-Fi per unit at flash time, so a single generic .img
serves every room and build-all builds it once regardless of how many arm64
entries are listed. amd64 has no equivalent for a generic x86 ISO, so it bakes
the hostname in and does need one per room (project-plan Phase 15.5).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 13:25:38 +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
Amir Alexander Abdelbaki bfca1c7eb0 Add idle-gallery weather/clock overlay and headless audio endpoint images
Two additions:

- Thin client: an eww weather/clock overlay over the idle-gallery slideshow.
  Time/date come from the local clock, weather from a new retained MQTT topic
  (smarthome/weather/current) an HA automation has to publish — degrades to
  clock-only if that never happens. idle-gallery.sh owns the overlay's
  visibility, mirroring how fullscreen-watcher.sh already owns the
  now-playing widget's.

- hosts/audio-endpoint/: headless, per-room-independent Spotify Connect
  appliances for rooms without a thin client, no shared/synced stream. Two
  build pipelines sharing one architecture-independent systemd
  unit/wrapper: arm64 (Raspberry Pi + HiFiBerry Amp2) via rpi-image-gen
  producing a real flashable .img, and amd64 (mini PC + USB DAC/amp) reusing
  the thin client's own live-build toolchain stripped of its graphical/kiosk
  stack. Both reuse the thin client's existing apt-first/documented-fallback
  Spotify Connect install logic rather than reinventing it. rpi-image-gen's
  exact config schema is flagged explicitly as unverified — no ARM build
  environment was available to validate it against the real tool.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WgTwMqiho8DT6ue5S49WBP
2026-07-29 11:37:29 +02:00
Amir Alexander Abdelbaki 1d8b416ab9 Add Phase 13 admin canvas, Phase 14 voice/display satellite, and thin-client capture-card input
Three additions to the thin-client/container-host stack:

- admin-canvas + admin-web (Phase 13): an on-demand display surface for the
  sys-admin-llm, mirroring digest-engine/digest-web's write/read split but as
  a small always-on token-gated write API instead of a scheduled job. New
  "4:admin" thin-client workspace, MQTT button, and duplicated canvas-sdk with
  stat/image/video/chart window kinds.

- ESPHome firmware for the Waveshare ESP32-S3-Touch-LCD-1.85C-V2 (Phase 14):
  a voice satellite + round-display status screen, evaluated as a DIY
  alternative to Home Assistant Voice PE. Media/cover-art always takes
  priority over an idle weather/time/date cycle, with a voice-state
  visualizer overlaid on top. On-device wake word (micro_wake_word) feeding
  the existing Assist pipeline, per-room "media_player_entity_id" wiring
  documented explicitly to avoid cross-room bleed. Validated with ESPHome's
  own config validator; not yet flashed to real hardware.

- Capture-card ("receiver box") video source selection on the thin client: a
  new "5:capture" workspace and HA select that dynamically enumerates
  USB/PCIe capture cards (re-scanned periodically for hot-plug), launches
  mpv against the chosen one, and unconditionally excludes the gesture-
  control camera from the list to preserve its existing no-HA-entity
  invariant.

Also fixes a pre-existing bug found along the way: digest-browser's pkill
pattern matched any --kiosk mpv/firefox process rather than its own, which
would have let the new admin-browser/capture-view scripts kill each other's
windows.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WgTwMqiho8DT6ue5S49WBP
2026-07-29 11:03:11 +02:00
Amir Alexander Abdelbaki 035874bb2e Add Phase 11 thin-client ISO and Phase 12 quarter-daily LLM digest
Implements both new subsystems from the project plan end to end:

- hosts/thin-client/: Debian live-build Sway kiosk image — greetd autologin,
  wayvnc (replaces RDP, per open decision #1), thinclient-agent (HA MQTT
  discovery + swaymsg control, the sole LLM-reachable surface), mpv/mpv-mpris,
  spotifyd/Steam Link, minimal-chrome Firefox with uBlock Origin/SponsorBlock
  preinstalled, a fullscreen-aware now-playing widget, persistent audio-output
  selection, outbound RDP/VNC via Remmina, HA mobile-app text/pointer remote
  control via ydotool, an optional opt-in gesture-control camera (MediaPipe
  hand tracking, off by default at two independent gates), a maintenance-shell
  keybind, a German default keymap, and an idle-timeout photo slideshow from a
  new gallery SMB share on the container host.

- digest-engine/: quarter-daily LLM digest — email/Signal/Telegram/Discord/
  WhatsApp ingestion (WhatsApp via a headful-Chromium bridge, opt-in, highest
  risk), news (including RCI/marxist.com theoretical sources and a broad
  mainstream/state-affiliated outlet list), financial indicators, flight/naval
  traffic and an OPNsense Suricata-alerts summary as political-analysis
  evidence, Grocy/CalDAV household ingestion, an evening-only recipe and
  shopping-list suggestion from soon-to-expire stock, and a vendored
  digest-canvas SDK (globe/window/glow) rendering compact and full views. A
  digest that goes unviewed (no thin-client canvas display or voice playback)
  is merged into the next run instead of being discarded, tracked via a
  retained MQTT message on the shared broker.

Both are wired into hosts/container-host/scripts/setup-container-host.sh via
the existing ENABLE_X/X_BLOCK pattern, off by default. docs/project-plan.md and
README.md are updated to match; remaining open decisions and manual-
verification items are tracked in-repo (project-plan.md §4, digest-engine/
README.md, hosts/thin-client/README.md).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KPD1RhBsjdFKFLpMCLiVT6
2026-07-28 15:33:40 +02:00