Configuration

Four layers, each with a clear job: environment variables, the git-backed store, Spring Boot overrides, and per-entity settings.

The four layers

LayerLives inHoldsChanges take effect
Environment variables The container / process environment Bootstrap secrets: database URL and credentials, the JWT signing key, where the configuration repository is. On restart
Configuration repository A git repo read by ConfigProvider Everything runtime: MQTT, CORS, feature flags, integration credentials, dashboard bindings. Within one configSync interval, or immediately when edited in the UI
Spring Boot overrides ./config/application.yml next to the JAR Deployment facts the API must not be able to rewrite — trusted proxies, LAN ranges, job toggles. On restart
Per-entity configuration The Configuration table, edited in the UI Settings attached to a single device, port, peripheral or zone — auto-off timeouts, voice aliases, device tokens. Immediately
Why is the security allowlist not in the git store?

The appConfigUpdate GraphQL mutation can rewrite arbitrary configuration keys at runtime. Putting myhab.security.trustedProxies and lanCidrs there would make the allowlist editable through the API it exists to protect. They stay in application.yml.

Environment variables

VariableRequiredPurpose
GRAILS_ENVyesproduction, development or demo. Selects the environment block in application.yml.
DB_URLyesJDBC URL, e.g. jdbc:postgresql://db:5432/myhab. ?TimeZone=UTC is appended automatically if absent.
DB_USERNAMEyesDatabase user (defaults to myhab outside production).
DB_PASSWORDyesDatabase password.
JWT_SECRETyesHS256 signing key for access tokens. Use a long random string; changing it invalidates every issued token.
CFG_REPO_URIyesConfiguration repository — an https:// remote or a file:// bare repo.
CFG_USERNAMEnoGit username. Leave empty for file://.
CFG_PASSWORDnoGit password or access token.
TZrecommendedSet to UTC. See Installation.
ANTHROPIC_API_KEY / OPENAI_API_KEYnoFallback for the voice assistant's LLM key when it is not in the configuration store.
GOOGLE_TTS_API_KEYnoFallback for the neural TTS service-account JSON.

The branch ConfigProvider reads defaults to prod in the production environment and dev elsewhere; override it with the myhab.config.branch property.

The configuration repository

One YAML document, versioned in git, cloned by the application at start and re-read by the configSync job. It is also editable in the UI under Settings → App configuration, which commits back to the repository.

MQTT

mqtt:
  hostname: mosquitto
  port: 1883
  username: myhab
  password: change-me
  topics: myhab/#        # subscription filter

The outbound topic prefix (myhab) is set separately in application.yml under mqtt.topic.prefix.

CORS

Only needed when the client is served from a different origin than the API — for example during development, or if you host the PWA separately.

cors:
  allowedOrigin:
    - https://home.example.com
    - http://localhost:10002

UI bindings

These tell the dashboard which entities to wire its fixed tiles to. Only keys under specialDevices., specialZones., ui., grafana. and surveillance. are exposed to the browser — the rest of the store never leaves the server for non-admin users.

specialZones:
  int:    { id: 1001 }   # "Interior" quick tile
  ext:    { id: 1003 }   # "Exterior" quick tile
  parter: { id: 1001 }   # ground floor
  etaj:   { id: 1007 }   # upper floor
  lan:    { id: 1005 }   # lawn irrigation
  garden: { id: 1006 }   # garden irrigation

specialDevices:
  doorLockMain:
    peripheral:
      id: 1234           # the gate the dashboard card operates

ui:
  meteo:
    locationName: My Town

Two optional integrations are wired the same way — an embedded Grafana dashboard for long-range charts, and a link to whatever surveillance UI you run:

grafana:
  url: https://grafana.example.com
  dashboard:
    solar:       { id: abc123 }
    temperature: { id: def456, panelId: 4 }

surveillance:
  url: https://cameras.example.com

Given https://grafana.example.com/d/abc123/solar-plant, set grafana.url to the origin and grafana.dashboard.solar.id to abc123. Without them the Solar Reports page explains what is missing instead of rendering a broken frame.

Widgets know what they need.

Each dashboard widget declares the configuration keys it cannot work without. Settings will not offer a widget whose keys are missing, and the dashboard renders a “not configured” placeholder — with an inline picker for admins — instead of mounting a broken card.

Heating

heat:
  thermostat:
    enabled: true
  temp:
    allDay: 21           # default setpoint in °C

Auto-import

admin:
  devices:
    autoimport: false    # create Device rows from unknown MQTT announcements
  ports:
    autoimport: false    # create DevicePort rows from unknown port topics

Useful when bringing a new controller online; turn it back off afterwards so a misbehaving device cannot spawn hundreds of rows.

Notifications

telegram:
  token: "<bot token>"
  chanelId: "<channel id>"
  bot1x1ChannelId: "<direct chat id>"

push:
  vapid:
    publicKey: "<vapid public key>"
    privateKey: "<vapid private key>"
    subject: "mailto:you@example.com"

opsgenie:
  url: https://api.opsgenie.com
  apiKey: "<key>"
  team: "<team>"
  recipientId: "<id>"
  alias: myhab
  email: alerts@example.com

All three degrade cleanly when absent: the Telegram bot stays off (it also has its own myhab.telegram.enabled switch), web push logs one warning and stays inactive, and OpsGenie keys are only read when an alert is actually raised.

Feature flags

KeyDefaultPurpose
feature.voice.enabledfalseMaster switch for the voice assistant.
feature.voice.llm.provideranthropicanthropic or openai.
feature.voice.llm.modelprovider defaultModel id override.
feature.voice.llm.apikeyLLM key; falls back to the provider's environment variable.
feature.voice.tts.enabledfalseServer-side neural speech for replies.
feature.voice.tts.providergoogleCurrently Google Cloud TTS only.
feature.voice.tts.apikeyA service-account JSON (inline or a file path) — not an API key.
feature.voice.tts.voice.ro / .enGoogle defaultSpecific voice names, e.g. ro-RO-Wavenet-A.
feature.qr.enabledfalseEmbed QR codes on printed labels.
feature.qr.content.templatemyhab://TYPE/IDWhat the QR encodes. Deliberately base-URL-free.
feature.qr.position / feature.qr.sizePlacement and size on the label.

Quartz job intervals

Every scheduled job can be enabled, disabled and re-timed from quartz.jobs.* without touching code. These live in application.yml and are typically overridden per deployment in ./config/application.yml.

JobDefaultIntervalDoes
deviceControllerStateSyncon60 sHTTP reachability + state reconciliation for controllers that support it.
portValueSyncTriggeron60 sAsks devices to re-publish their port values over MQTT.
configSyncon60 sPulls the configuration repository.
switchOffOnTimeouton30 sTurns off anything past its auto-off timeout.
meteoStationSyncon1800 sOpen-Meteo current conditions and forecast.
nibeInfoSyncon300 sHeat pump telemetry. Inert without credentials.
nibeTokenRefreshon120 sRefreshes the myUplink OAuth token before expiry.
heatingControloff120 sThermostat automation from room temperature and degree-minutes.
huaweiInfoSyncoff300 sFusionSolar inverter and meter poll.
navimowInfoSyncoff30 sMower state; auto-creates ports and raises notifications.
navimowTokenRefreshoff600 sSpends the stored refresh token. Enable after the OAuth flow has run once.
eventLogReaderoff60 sDrains the event log queue.
electricityMetricStatistics1H / 24H / 1Monthoff1 h / 24 h / 30 dEnergy statistics rollups.
randomColors, rainbowRGBoff5 s / 80 sRGB effects — testing and parties.
# ./config/application.yml — enable the integrations you actually own
quartz:
  jobs:
    huaweiInfoSync:   { enabled: true,  interval: 300 }
    heatingControl:   { enabled: true,  interval: 120 }
    eventLogReader:   { enabled: true,  interval: 60 }
    nibeInfoSync:     { enabled: false }

Security settings

myhab:
  security:
    # Reverse proxies whose X-Real-IP we believe. Must be the proxy's own address.
    # Empty means no request arriving with forwarding headers is trusted.
    trustedProxies: []
    # Networks the anonymous device endpoints may be called from.
    lanCidrs:
      - 192.168.0.0/16
      - 10.0.0.0/8
      - 172.16.0.0/12
      - 127.0.0.0/8
      - ::1/128
forward-headers-strategy must stay unset.

Enabling it rewrites request.remoteAddr from X-Forwarded-For, which is caller-supplied. myHAB deliberately distinguishes the proxy from the caller itself via ClientIpService.

Per-entity configuration

Many entities carry their own key/value sidecar, edited from that entity's screen rather than globally.

KeyAttached toPurpose
key.on.timeoutPeripheralAuto-off after N seconds. Also shown to guests on a shared link.
feature.voice.aliasPeripheral, ZoneComma-separated alternate names fed to the voice agent.
cfg.key.device.http.sync.supportedDeviceInclude this controller in the HTTP state sweep.
cfg.key.device.mqtt.sync.supportedDeviceInclude it in the MQTT value-read trigger.
cfg.key.device.admin.port.autoimportDevicePer-device port auto-import override.
cfg.key.device.oauth.access_user / .access_passwdDeviceCredentials for a cloud-backed device.
cfg.key.device.oauth.access_token / .refresh_tokenDeviceTokens written by the OAuth flow and refreshed by a job.
cfg.key.device.navimow.api.base_urlDeviceRegional Segway API endpoint.
cfg.key.device.navimow.device.idDeviceThe mower id returned by the Segway account listing.

Settings in the app

Some things are per-user rather than per-installation, and live in the profile: