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> |
||
|---|---|---|
| app | ||
| bootstrap | ||
| config | ||
| database | ||
| docker | ||
| lang | ||
| public | ||
| resources | ||
| routes | ||
| sidecar | ||
| storage | ||
| tests | ||
| .editorconfig | ||
| .env.example | ||
| .gitattributes | ||
| .gitignore | ||
| .npmrc | ||
| CLAUDE.md | ||
| README.md | ||
| artisan | ||
| composer.json | ||
| composer.lock | ||
| design-mockup.html | ||
| docker-compose.yml | ||
| handoff.md | ||
| package-lock.json | ||
| package.json | ||
| phpunit.xml | ||
| rules.md | ||
| vite.config.js | ||
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 upalone is not the first command —vendor/,node_modules,public/buildand 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:
- admin@homeos.local / homeos-dev ← change this for anything but local dev.
Production
.env.example ships development defaults. For a real deployment you must:
- set
APP_ENV=productionandAPP_DEBUG=false(otherwise exceptions are exposed and the demo household is seeded into your real database); - set strong values for
DB_PASSWORD, theREVERB_APP_*keys andHOMEOS_ADMIN_PASSWORD(seeding aborts if the admin password is unset outside local/testing), and runphp 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:
- 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.
- In HomeOS → Add-ons → Ring → Install, then Connect account.
- 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.