215 lines
10 KiB
Plaintext
215 lines
10 KiB
Plaintext
{
|
|
"_README": [
|
|
"SmartestHome — the single source of truth for every build script in tools/.",
|
|
"",
|
|
"Copy this to CoreSystemConfig.json (that filename is gitignored), fill it in, then",
|
|
"run any builder in tools/. Nothing in tools/ has its own copy of an IP, a port, a",
|
|
"token or a URL — every build script reads this file, so a value can only ever be",
|
|
"wrong in one place instead of six.",
|
|
"",
|
|
"ANYTHING DERIVABLE IS DERIVED, NEVER TYPED. You give the subnet prefix once and one",
|
|
"last octet per host; the builders compute every address and every service URL from",
|
|
"those. That is what makes the container-host and llm-host ISOs a TWINNED pair: the",
|
|
"container host learns the LLM host's address because it was computed from the same",
|
|
"two numbers, not because someone typed it into two files and kept them in sync.",
|
|
"",
|
|
"THIS FILE WILL CONTAIN SECRETS once filled in (Wi-Fi PSK, service tokens, MQTT and",
|
|
"HA credentials) and those secrets get burnt into the ISOs the builders produce.",
|
|
"Treat both this file and the built ISOs as credentials: .gitignore covers them, but",
|
|
"an ISO on a USB stick in a drawer is still every token in this household.",
|
|
"",
|
|
"Run 'tools/validate-config.py' at any time to check it. Every builder runs it first",
|
|
"and refuses to build on an error."
|
|
],
|
|
|
|
"household": {
|
|
"_comment": "Baked into every image. timezone must be a real zoneinfo name; keyboard_layout an xkb layout ('localectl list-x11-keymap-layouts').",
|
|
"timezone": "Europe/Vienna",
|
|
"locale": "en_US.UTF-8",
|
|
"keyboard_layout": "de",
|
|
"debian_release": "bookworm"
|
|
},
|
|
|
|
"network": {
|
|
"_comment": "subnet_prefix is the first three octets of the smart-home VLAN, no trailing dot. Every host below places itself in it by last octet, which is also how the validator guarantees they can actually reach each other.",
|
|
"subnet_prefix": "192.168.30",
|
|
"netmask": "255.255.255.0",
|
|
"gateway_last_octet": 1,
|
|
"dns_servers": ["192.168.30.1"],
|
|
"wifi": {
|
|
"_comment": "Used by kiosk images that have no wired connection. Leave ssid empty if every device is wired; the validator will then not complain about an empty psk.",
|
|
"ssid": "",
|
|
"psk": ""
|
|
}
|
|
},
|
|
|
|
"container_host": {
|
|
"_comment": "The Phase 1 machine: Home Assistant, Mosquitto, Zigbee2MQTT, Frigate, Grocy and this repo's own services. Everything else in the household points at this address.",
|
|
"hostname": "smarthome-core",
|
|
"ip_last_octet": 12,
|
|
"install_disk": "/dev/sda",
|
|
"admin_username": "amir",
|
|
"enable": {
|
|
"_comment": "Mirrors setup-container-host.sh's ENABLE_* flags. Turning one off here also stops the builders from demanding the tokens it would have needed.",
|
|
"identity": true,
|
|
"pantry_vision": true,
|
|
"digest_engine": false,
|
|
"admin_canvas": false,
|
|
"trash_calendar": false,
|
|
"transit": false,
|
|
"trip_planning": false,
|
|
"chores": true,
|
|
"ntfy": true,
|
|
"node_red": true,
|
|
"netdata": true,
|
|
"homepage": true,
|
|
"portainer": true,
|
|
"mealie": false,
|
|
"gallery_smb": false,
|
|
"music_assistant": false,
|
|
"backups": false
|
|
}
|
|
},
|
|
|
|
"llm_host": {
|
|
"_comment": "The Phase 3 Ollama machine. Its address is what the container host's OLLAMA_HOST is derived from — change ip_last_octet here and every consumer follows automatically.",
|
|
"hostname": "smarthome-llm",
|
|
"ip_last_octet": 13,
|
|
"install_disk": "/dev/sda",
|
|
"admin_username": "amir",
|
|
"tier": "auto",
|
|
"text_model_gpu": "qwen2.5:14b-instruct",
|
|
"text_model_cpu": "qwen2.5:7b-instruct",
|
|
"vision_model": "llava",
|
|
"pull_vision_model": true,
|
|
"keep_alive": "30m",
|
|
"max_loaded_models": 1,
|
|
"num_parallel": 1
|
|
},
|
|
|
|
"ports": {
|
|
"_comment": "The whole published-port inventory, matching docs/network-integration.md §4. The validator FAILS on any duplicate here, which is the mechanism that keeps a collision from reaching a deployment. NOTE music_assistant: its own default is 8095, which collides with pantry_vision — and because it runs network_mode:host, Compose's own port-conflict check never fires (project-plan open decision #31). 8101 is set here to resolve that, but you must also configure Music Assistant itself to listen on it; this file cannot make it move.",
|
|
"home_assistant": 8123,
|
|
"mqtt": 1883,
|
|
"zigbee2mqtt": 8080,
|
|
"node_red": 1880,
|
|
"homepage": 3000,
|
|
"ntfy": 8090,
|
|
"portainer": 9000,
|
|
"gallery_smb": 445,
|
|
"mealie": 9925,
|
|
"frigate": 5000,
|
|
"grocy": 9283,
|
|
"digest_web": 8091,
|
|
"admin_web": 8094,
|
|
"pantry_vision": 8095,
|
|
"pantry_web": 8096,
|
|
"identity": 8097,
|
|
"identity_web": 8098,
|
|
"transit": 8099,
|
|
"otp": 8100,
|
|
"music_assistant": 8101,
|
|
"ollama": 11434,
|
|
"proxy_http": 80,
|
|
"proxy_https": 443
|
|
},
|
|
|
|
"secrets": {
|
|
"_comment": "Generate the tokens with: openssl rand -hex 32. Each is required only if the service that uses it is enabled above; the validator says which. ha_token is a Long-Lived Access Token from HA's own UI (profile -> Security) and cannot be generated ahead of time — leave it empty for the first build and re-run once HA is up. The opnsense_api_* pair is the same kind of thing: only OPNsense can mint it (System -> Access -> Users -> API keys), so it is never generated here either.",
|
|
"identity_token": "",
|
|
"pantry_vision_token": "",
|
|
"transit_token": "",
|
|
"mqtt_username": "",
|
|
"mqtt_password": "",
|
|
"ha_token": "",
|
|
"opnsense_api_key": "",
|
|
"opnsense_api_secret": "",
|
|
"ssh_authorized_key": "",
|
|
"kiosk_password": "",
|
|
"admin_password_hash": ""
|
|
},
|
|
|
|
"opnsense": {
|
|
"_comment": "The household's existing OPNsense firewall. Used by digest-engine's network digest, which pulls a Suricata intrusion-detection summary from it (GET /api/ids/service/status and POST /api/ids/service/query_alerts — two read endpoints, nothing else, ever). A build writes this block plus the secrets above into the container host's IDSconf.json; the long-form documentation for every field lives in digest-engine/IDSconf.json.example. Leave base_url empty to skip that file entirely. TWO THINGS THIS DOES NOT DO: it does not enable Suricata (do that at Services -> Intrusion Detection on the firewall, then download a ruleset), and it does not enable the ingest (set ENABLE_OPNSENSE_IDS_INGEST=true in digest-engine.env). SCOPE THE API KEY: give it its own OPNsense user with only the 'Services: Intrusion Detection' privilege — that privilege still covers api/ids/* including start/stop, because OPNsense ACLs are page-level, so the read-only guarantee comes from digest-engine calling exactly two endpoints and not from the firewall enforcing it.",
|
|
"base_url": "",
|
|
"verify_tls": true,
|
|
"interfaces": [],
|
|
"max_alerts_scanned": 5000,
|
|
"top_signatures": 8,
|
|
"top_hosts": 5,
|
|
"packet_capture_reference": ""
|
|
},
|
|
|
|
"proxy": {
|
|
"_comment": "One HTTPS front door for this repo's own services (Caddy), plus an HTTP->HTTPS redirect. Not a household-wide gateway — Home Assistant, Grocy and friends keep their own ports. hostname is what the certificate is issued for and what you type in the browser; it must resolve to the container host (a DNS override on OPNsense, a hosts entry, or just use the IP with tls: internal). tls 'internal' makes Caddy run its own CA — no external dependency, but browsers show a warning until you install its root (tools/export-proxy-ca.sh). tls 'custom' uses cert_file/key_file, which is how you'd use a real cert obtained via a DNS-01 challenge without exposing anything. See proxy/README.md.",
|
|
"enabled": true,
|
|
"hostname": "home.example.lan",
|
|
"tls": "internal",
|
|
"cert_file": "",
|
|
"key_file": ""
|
|
},
|
|
|
|
"voice": {
|
|
"_comment": "Defaults for any kiosk with voice_satellite enabled; a kiosk may override wake_word individually.",
|
|
"wake_word": "ok_nabu"
|
|
},
|
|
|
|
"kiosks": [
|
|
{
|
|
"_comment": "type must be one of: thin-client, touch-panel, door-panel, kitchen-display. hostname must be unique and a valid DNS label — it is what the HA device shows up as.",
|
|
"type": "door-panel",
|
|
"hostname": "door-panel",
|
|
"friendly_name": "Door panel",
|
|
"kiosk_username": "kiosk",
|
|
"voice_satellite": true,
|
|
"enable_installer": false
|
|
},
|
|
{
|
|
"type": "kitchen-display",
|
|
"hostname": "kitchen-display",
|
|
"friendly_name": "Kitchen fridge display",
|
|
"kiosk_username": "kiosk",
|
|
"voice_satellite": false,
|
|
"enable_installer": false
|
|
},
|
|
{
|
|
"type": "thin-client",
|
|
"hostname": "thin-client-living",
|
|
"friendly_name": "Living room thin client",
|
|
"kiosk_username": "kiosk",
|
|
"voice_satellite": false,
|
|
"enable_installer": false,
|
|
"enable_steam_link": true,
|
|
"enable_gesture_control": false
|
|
},
|
|
{
|
|
"type": "touch-panel",
|
|
"hostname": "touch-panel-kitchen",
|
|
"friendly_name": "Kitchen touch panel",
|
|
"kiosk_username": "kiosk",
|
|
"voice_satellite": false,
|
|
"enable_installer": false
|
|
}
|
|
],
|
|
|
|
"audio_endpoints": [
|
|
{
|
|
"_comment": "Headless Spotify Connect appliances for rooms with no thin client. arch picks the toolchain — and they are genuinely different toolchains producing different artifacts, not one image for both: 'amd64' is a mini PC + USB DAC built with live-build (an .iso), 'arm64' is a Raspberry Pi + HiFiBerry Amp2 built with rpi-image-gen (an .img). build-all.sh builds every entry here, so listing both architectures gets you both. hostname doubles as the Spotify Connect device name and must be unique across kiosks too — they're all devices on one network.",
|
|
"hostname": "audio-endpoint-livingroom",
|
|
"friendly_name": "Living room",
|
|
"arch": "amd64"
|
|
},
|
|
{
|
|
"hostname": "audio-endpoint-kitchen",
|
|
"friendly_name": "Kitchen",
|
|
"arch": "arm64"
|
|
}
|
|
],
|
|
|
|
"build": {
|
|
"_comment": "Where finished images land, relative to the repo root. Gitignored — see .gitignore — because these carry every secret in this file. arm64_prebake bakes each arm64 audio endpoint's hostname into its own image, so every device in the household is named by the build rather than typed into Raspberry Pi Imager afterwards; it costs one full rpi-image-gen run per room. Set it false to build a single generic .img and name each unit at flash time instead.",
|
|
"output_dir": "iso-out",
|
|
"arm64_prebake": true
|
|
}
|
|
}
|