Documentation and inventory app for property JWS11 / apartment 4.2: devices, zones, maps, channel metadata, floor plan points, manuals, and handover docs.
It is a bilingual (English / German) Next.js site meant to run at home (e.g. on kelder next to Home Assistant) and optionally behind Cloudflare Access.
| Area | Description |
|---|---|
| Building overview | Home page with building diagram and ground-floor / basement overview images |
| Floor plan | Interactive SVG map: select rooms and device points, assign devices, edit notes/photos |
| Devices & zones | Browse installed devices and areas; channel documentation (switches, covers, etc.) |
| Admin | CRUD for devices, models, zones, media, and site copy (/admin) |
| Docs | Markdown handover / setup guides under /docs |
| Network / quickstart | Buyer-facing pages for LittleJerry + kelder handover |
Live edits are stored as YAML (and uploaded photos) on disk — no external database.
- Next.js 16 (App Router) + React 19
- Tailwind CSS + DaisyUI
- js-yaml for
data/*.yaml - Docker multi-stage build (
output: "standalone") - Optional publish to GHCR via GitHub Actions
npm install
npm run devOpen http://localhost:3000.
Other scripts:
npm run build # production build
npm start # run production build
npm run lint # oxlint
npm run format # oxfmtNode 20+ recommended (matches the Docker image).
app/ Next.js routes and UI
admin/ Admin console
floorplan/ Interactive floor plan
api/ REST handlers (devices, zones, maps, uploads, …)
docs/ Docs pages
content/docs/ Markdown source for handover docs
data/ Seed YAML (devices, zones, maps, …)
public/
maps/ map-floorplan.svg + overview overlays + PNG backgrounds
app-icons/ Favicons / PWA icons
uploads/ Uploaded photos (local/dev; prod uses a volume)
lib/ Data access, floor plan helpers, auth stubs
Dockerfile
docker-compose.yml Local / build-friendly compose
docker-compose.prod.yml Kelder / Dockge (image pull only)
| File | Role |
|---|---|
devices.yaml |
Device registry (IP, MAC, room, floor plan marker, …) |
zones.yaml |
Canonical clickable zones and points (merged topology model) |
maps.yaml |
Map metadata, overlays, legends, and zone bindings |
device_models.yaml |
Hardware model catalog + manuals |
device_types.yaml |
Functional categories |
channels.yaml |
Per-device switch/cover channels |
site.yaml |
Site copy / branding strings |
network.yaml |
Network / IP plan notes |
documents.yaml |
Document index |
Writes go to DATA_DIR (default runtime-data/). Seed files live in SEED_DATA_DIR (repo data/ or image /app/data).
On first read, if a YAML file is missing from runtime, it is copied once from seed. After that, runtime wins — redeploys do not overwrite live edits.
Image / git `data/` → seed defaults
runtime-data/ → live YAML (volume on kelder)
runtime-uploads/ → uploaded photos (volume on kelder)
- Source of truth for the interactive map:
public/maps/map-floorplan.svg - Zones:
zone_{code}(e.g.zone_ld,zone_ha) — basement zones are on Building maps, not floorplan - Points: group
points, markerszone_{code}-{suffix}(e.g.zone_ld-cp,zone_ba-li) - Human-readable names live in
zones.yaml, not in the SVG
After editing the SVG:
- Save/update
public/maps/map-floorplan.svg - Add/update point zones in
data/zones.yaml - Bind new area zones in
data/maps.yamlandapp/floorplan/FloorMapWorkspace.jsxif needed
Open /admin (gear icon in the header).
Admin write APIs are currently open to anyone who can reach the app (token gate removed). Protect production with Cloudflare Access (or similar) and avoid exposing port 8124 to the public internet unprotected.
docker compose up -d --buildApp: http://localhost:8124
(Host port 8124 sits next to Home Assistant’s 8123.)
In Dockge, create a stack (for example smarthome) and paste this as compose.yaml:
services:
smarthome:
image: ghcr.io/geertclaes/smarthome:latest
container_name: smarthome
restart: unless-stopped
ports:
- "8124:3000"
environment:
DATA_DIR: /app/runtime-data
SEED_DATA_DIR: /app/data
volumes:
- ./runtime-data:/app/runtime-data
- ./runtime-uploads:/app/public/uploadsBefore first start, create the volume folders in the stack directory:
mkdir -p runtime-data runtime-uploadsTypical stack path: /opt/stacks/smarthome/.
Update flow
- Push to
main→ GitHub Actions builds and pushesghcr.io/geertclaes/smarthome:latest - On kelder:
docker compose pull && docker compose up -d(or Dockge Update)
Because runtime writes live in runtime-data/, redeploying a new image does not overwrite YAML automatically.
Use the helper script to copy selected seed YAML files from the image (/app/data) into live runtime storage
(runtime-data) with an automatic timestamped backup.
# from this repo on kelder
STACK_DIR=/opt/stacks/smarthome scripts/kelder-sync-data.sh devices.yaml network.yaml
# sync all YAML seed files and restart service
STACK_DIR=/opt/stacks/smarthome scripts/kelder-sync-data.sh --all --restartBackups are created inside the container volume at:
/app/runtime-data/.backup-YYYYMMDD-HHMMSS
Recommended practice: sync only the files you explicitly changed (for example devices.yaml) instead of --all.
Private GHCR images require docker login ghcr.io on kelder (read:packages token).
Do not run docker compose down -v — that can wipe volumes.
The container runs as UID 1001. Host volumes must be writable by that user (or use the image entrypoint that chowns them on start):
sudo chown -R 1001:1001 runtime-data runtime-uploads| Variable | Purpose |
|---|---|
DATA_DIR |
Writable YAML directory (default /app/runtime-data) |
SEED_DATA_DIR |
Read-only seed YAML (default /app/data in the image) |
SMARTHOME_DATA_DIR |
Host path override for compose bind mount |
SMARTHOME_UPLOADS_DIR |
Host path override for uploads |
SMARTHOME_SEED_DATA_DIR |
Host path override for seed mount (dev compose) |
See .env.example.
UI strings: app/i18n.js (EN / DE).
Site/marketing copy: data/site.yaml via SiteContentProvider.
Language switcher is in the header.
| Service | Typical URL / port |
|---|---|
| Home Assistant | :8123 / home.kwokah.com |
| This docs app | :8124 / jws11.kwokah.com |
| Dockge | :5001 / docker.kwokah.com |
Cloudflare Tunnel routes should target http://127.0.0.1:<port> when cloudflared runs on the host. Prefer a single cloudflared connector (avoid duplicate token + local-config processes).
Private project for property documentation and handover.