Go to file
HomeOS Bootstrap da249022cd fix(shelly): round-trip light brightness/rgb; don't hijack a reused IP (R15)
Codex R15 on the local transport:
- [P2] A light command set brightness/rgb but the normalizer stored only `on`,
  so the poll dropped them and the UI/automations went stale. The light state
  now carries brightness + rgb when the device reports them.
- [P2] ShellyLocalOnboarder matched an existing row by IP BEFORE the Shelly id,
  so a DHCP-reassigned IP could let one Shelly overwrite an unrelated device.
  Now matches by stable Shelly id first and reuses an IP row only when it's the
  same (or an unidentified) device.

+3 tests (light attrs, housekeeping dropped, IP-reuse no hijack). Suite 71 green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 09:51:56 +02:00
app fix(shelly): round-trip light brightness/rgb; don't hijack a reused IP (R15) 2026-07-18 09:51:56 +02:00
bootstrap Phase 1: bootstrap — Docker stack, Laravel 13, design system, auth 2026-07-17 21:06:20 +02:00
config feat(devices): auto-detect host IP, discovery dedup, delete + rescan 2026-07-18 09:34:07 +02:00
database fix: harden MQTT sharing + input roles + dedup (adversarial review) 2026-07-18 08:59:12 +02:00
docker feat(devices): auto-detect host IP, discovery dedup, delete + rescan 2026-07-18 09:34:07 +02:00
lang feat(shelly): local HTTP/RPC transport — add by IP like Home Assistant 2026-07-18 09:48:35 +02:00
public fix(pwa): await service-worker cache write (R15) 2026-07-18 01:27:41 +02:00
resources feat(shelly): local HTTP/RPC transport — add by IP like Home Assistant 2026-07-18 09:48:35 +02:00
routes feat(shelly): local HTTP/RPC transport — add by IP like Home Assistant 2026-07-18 09:48:35 +02:00
sidecar feat(devices): auto-detect host IP, discovery dedup, delete + rescan 2026-07-18 09:34:07 +02:00
storage Phase 1: bootstrap — Docker stack, Laravel 13, design system, auth 2026-07-17 21:06:20 +02:00
tests fix(shelly): round-trip light brightness/rgb; don't hijack a reused IP (R15) 2026-07-18 09:51:56 +02:00
.editorconfig Phase 1: bootstrap — Docker stack, Laravel 13, design system, auth 2026-07-17 21:06:20 +02:00
.env.example feat(mqtt): Home-Assistant-style onboarding — shared account + auto-create 2026-07-18 08:20:57 +02:00
.gitattributes Phase 1: bootstrap — Docker stack, Laravel 13, design system, auth 2026-07-17 21:06:20 +02:00
.gitignore feat(addons): Ring backend bridge — ring-mqtt ingest, cloud devices, badge 2026-07-18 01:58:40 +02:00
.npmrc Phase 1: bootstrap — Docker stack, Laravel 13, design system, auth 2026-07-17 21:06:20 +02:00
CLAUDE.md Phase 1: bootstrap — Docker stack, Laravel 13, design system, auth 2026-07-17 21:06:20 +02:00
README.md docs: README — Ring add-on setup, PWA install, full services table 2026-07-18 02:10:47 +02:00
artisan Phase 1: bootstrap — Docker stack, Laravel 13, design system, auth 2026-07-17 21:06:20 +02:00
composer.json Phase 5: UniFi presence — poll, person↔client mapping 2026-07-18 00:31:32 +02:00
composer.lock Phase 5: UniFi presence — poll, person↔client mapping 2026-07-18 00:31:32 +02:00
design-mockup.html Phase 1: bootstrap — Docker stack, Laravel 13, design system, auth 2026-07-17 21:06:20 +02:00
docker-compose.yml feat(addons): Ring backend bridge — ring-mqtt ingest, cloud devices, badge 2026-07-18 01:58:40 +02:00
handoff.md Phase 1: bootstrap — Docker stack, Laravel 13, design system, auth 2026-07-17 21:06:20 +02:00
package-lock.json Tablet control panel: touch tiles, drag-reorder, light colour modal 2026-07-18 00:58:24 +02:00
package.json Tablet control panel: touch tiles, drag-reorder, light colour modal 2026-07-18 00:58:24 +02:00
phpunit.xml Phase 1: bootstrap — Docker stack, Laravel 13, design system, auth 2026-07-17 21:06:20 +02:00
rules.md Phase 1: bootstrap — Docker stack, Laravel 13, design system, auth 2026-07-17 21:06:20 +02:00
vite.config.js Phase 1: bootstrap — Docker stack, Laravel 13, design system, auth 2026-07-17 21:06:20 +02:00

README.md

HomeOS

Self-built smart-home control plane — single household, self-hosted, LAN-first. Laravel 13 · Livewire v3 · Tailwind v4 · PostgreSQL 17 + TimescaleDB · Redis/Horizon · Reverb.

Everything runs in Docker; the host needs only Docker (no PHP/Composer/Node). Authoritative spec: handoff.md · conventions: rules.md · guide: CLAUDE.md.

First-run setup

cp .env.example .env                                        # dev defaults work as-is
docker compose build                                        # build the app image
docker compose run --rm app bash docker/app/bootstrap.sh    # deps + key + assets + migrate/seed
bash docker/mosquitto/gen-passwd.sh                         # broker credentials (fills empty MQTT_*_PASSWORD)
docker compose up -d                                        # start the full stack

docker compose up alone is not the first command — vendor/, node_modules, public/build and the Mosquitto passwd file are not committed, so the steps above must run first.

Then open http://localhost and sign in with the seeded dev admin:

Production

.env.example ships development defaults. For a real deployment you must:

  • set APP_ENV=production and APP_DEBUG=false (otherwise exceptions are exposed and the demo household is seeded into your real database);
  • set strong values for DB_PASSWORD, the REVERB_APP_* keys and HOMEOS_ADMIN_PASSWORD (seeding aborts if the admin password is unset outside local/testing), and run php artisan key:generate.

The demo seeder (DemoHomeSeeder) only runs in local/testing.

Everyday commands (in-container, R8)

docker compose exec app php artisan …    # artisan
docker compose exec app composer …       # composer
docker compose exec app npm run build    # rebuild assets (or `npm run dev` for HMR)
docker compose exec app php artisan test # test suite
docker compose logs -f app               # logs

Add-ons

On-demand integrations, managed under Add-ons in the sidebar.

Ring (cloud, opt-in)

Ring has no local API, so HomeOS bridges it via ring-mqtt, which runs as an opt-in container (it never starts on a Ring-less setup):

bash docker/mosquitto/gen-passwd.sh              # seeds the least-privileged `ring` broker account
docker compose --profile addons up -d ring-mqtt  # start the bridge

Then:

  1. Open http://:${RING_UI_PORT:-55123} (ring-mqtt's own web UI) and sign in with your Ring account (email + password + 2FA). It mints and stores the refresh token — HomeOS never handles Ring's OAuth itself.
  2. In HomeOS → Add-ons → Ring → Install, then Connect account.
  3. Ring devices (doorbell, cameras, sensors) appear automatically under Devices, tagged Cloud.

Install as an app (PWA)

HomeOS ships a web manifest + service worker, so it installs to a tablet/phone home screen ("Add to Home Screen") and runs full-screen. Use the Steuerung (/panel) tab as the tablet control surface. Install requires HTTPS in production (localhost is exempt for testing).

Services & ports

Service Role Host port
app php-fpm + nginx + supervisor ${APP_PORT:-80}
reverb websockets (proxied same-origin via nginx /app) ${REVERB_HOST_PORT:-6001}
horizon · scheduler queue workers · cron (presence, metrics, automations)
mqtt-listener subscribes the bus, dispatches ingest jobs
mosquitto MQTT broker (auth + per-client ACLs) ${MQTT_HOST_PORT:-1883}
discovery mDNS/SSDP sidecar (host network)
ring-mqtt Ring bridge — opt-in (--profile addons) ${RING_UI_PORT:-55123}
db PostgreSQL 17 + TimescaleDB 127.0.0.1:${DB_HOST_PORT:-5432}
redis cache / queue

All ports and HOST_UID/HOST_GID are env-driven in .env.