Installation
Get a working myHAB in about twenty minutes — Docker for a real install, Gradle for development.
Don't install anything. demo.myhab.org is a complete, fully interactive installation with simulated devices.
Prerequisites
| Component | Version | Why |
|---|---|---|
| PostgreSQL | 14 or newer | Entities, partitioned time series, Quartz tables. |
| MQTT broker | Mosquitto 2.x or equivalent | Every device command and state echo. |
| A git repository | any host, or a bare repo on disk | Runtime configuration store, read by ConfigProvider. |
| Java | 17 (Temurin) | Only if you build or run from source; the Docker image bundles a JRE. |
| Node.js + Yarn | Node 18+ | Only for frontend development. |
Hardware: myHAB is comfortable on a small x86 box or a Raspberry Pi 4/5 class machine with 2 GB of RAM for the JVM. The database is the part that grows — port values accumulate quickly if you poll a lot of sensors.
Step 1 — Create the configuration repository
myHAB reads its runtime configuration from a git repository, not from a file inside the container. This is what lets you change MQTT credentials, feature flags or dashboard bindings without rebuilding or restarting anything, and gives you a history of every change.
Create a repository (private — it will hold credentials), with one branch per environment. The default
branch name in a production deployment is prod.
mkdir myhab-config && cd myhab-config
git init -b prod
cat > config.yaml <<'YAML'
mqtt:
hostname: mosquitto
port: 1883
username: myhab
password: change-me
topics: myhab/#
cors:
allowedOrigin:
- https://home.example.com
heat:
thermostat:
enabled: false
temp:
allDay: 21
admin:
devices:
autoimport: false
ports:
autoimport: false
ui:
meteo:
locationName: My Town
YAML
git add . && git commit -m "Initial myHAB configuration"
git push origin prodThe appConfig GraphQL query returns every key to any admin-authenticated
caller, and secrets committed to git stay in its history. Rotate by issuing a new credential and
revoking the old one — not by overwriting the value.
If you would rather not host a git server, a bare repository on a mounted volume works — point
CFG_REPO_URI at file:///config-repo/myhab-config.git. That is exactly what
the public demo does.
Step 2 — Run it with Docker Compose
The published image is kirpi4ik/myhab:<version> on Docker Hub, built from
eclipse-temurin:17-jre. It serves both the API and the compiled web client on
port 8181.
services:
db:
image: postgres:16
environment:
POSTGRES_DB: myhab
POSTGRES_USER: myhab
POSTGRES_PASSWORD: change-me
volumes: [ "pgdata:/var/lib/postgresql/data" ]
restart: unless-stopped
mosquitto:
image: eclipse-mosquitto:2
volumes:
- ./mosquitto.conf:/mosquitto/config/mosquitto.conf:ro
- "mqttdata:/mosquitto/data"
ports: [ "1883:1883" ] # devices on the LAN connect here
restart: unless-stopped
myhab:
image: kirpi4ik/myhab:2.8.12
depends_on: [ db, mosquitto ]
environment:
GRAILS_ENV: production
TZ: UTC
DB_URL: jdbc:postgresql://db:5432/myhab
DB_USERNAME: myhab
DB_PASSWORD: change-me
JWT_SECRET: "<a long random string>"
CFG_REPO_URI: https://git.example.com/you/myhab-config.git
CFG_USERNAME: myhab-bot
CFG_PASSWORD: "<token>"
ports: [ "8181:8181" ]
volumes:
- ./config:/app/config:ro # optional Spring Boot overrides, see below
restart: unless-stopped
volumes:
pgdata:
mqttdata:Run the container in UTC. myHAB stores every timestamp in UTC and converts for display using each user's timezone preference; running the JVM in a local zone produces charts that shift twice a year.
Bring it up and watch the first boot:
docker compose up -d
docker compose logs -f myhabGORM creates the schema on first start (dbCreate = update). The application answers on
http://<host>:8181/, and a health endpoint is available at
/actuator/healthcheck.
Step 3 — Create the first account
myHAB does not seed a default administrator — there is no well-known password to forget to change. Insert one directly, with a BCrypt hash of your chosen password.
# generate a hash (any bcrypt tool works)
htpasswd -bnBC 10 "" 'your-password' | tr -d ':\n'INSERT INTO sec_roles (id, version, authority) VALUES
(1, 0, 'ROLE_USER'),
(2, 0, 'ROLE_ADMIN')
ON CONFLICT DO NOTHING;
-- note: `users` has no version column
INSERT INTO users (id, username, password, first_name, last_name, email,
enabled, account_locked, account_expired, password_expired,
ts_created, ts_updated, en_type, language, timezone)
VALUES (1, 'admin', '<bcrypt hash>', 'Site', 'Admin', 'admin@example.com',
true, false, false, false, now(), now(), 'USER', 'en', 'UTC');
INSERT INTO sec_user_roles (user_id, role_id) VALUES (1, 1), (1, 2);sec_user_roles, not users_sec_roles.
Spring Security resolves authorities through the former. The similarly-named
users_sec_roles table is a legacy join; writing to it leaves the account with
ROLE_NO_ROLES and a login that succeeds but can do nothing.
Log in, then create the rest of your users from Infrastructure → Users.
Step 4 — Add your first device
- Infrastructure → Devices → Add device. Give it a code — this is what appears in its MQTT topics.
- Pick the model (ESP, MegaD, or a virtual model for a cloud integration) and assign it to a zone.
- Add ports for each I/O line, or let the controller's own announcements create them
if
admin.ports.autoimportis enabled in configuration. - Create a peripheral (“Terrace light”), connect it to the port, and place it in a zone. It now appears on the dashboard.
- Optionally document the cable between them and print a QR label.
Outbound command topics are built from a template, but inbound state topics are matched with
myhab/(\w+|_+)/(\w+|_+)/(\w+|_+)/state, and \w excludes -.
A hyphenated code publishes commands happily and then silently drops every echo: the control looks
dead, with nothing in the logs. Use esp_terrace, not esp-terrace.
Step 5 — Put it behind a reverse proxy
myHAB was written for a private LAN behind a router. If you expose it to the internet, terminate TLS at a proxy you control, and tell the application which proxy to believe:
# ./config/application.yml — layered over the image's defaults by Spring Boot
myhab:
security:
# The proxy's own address. Empty (the default) means no forwarded header is
# trusted at all — restrictive, not permissive.
trustedProxies:
- 172.18.0.5At the proxy itself:
- Refuse the anonymous device endpoints (
EventController) from outside — they assume a LAN. - Rate-limit
/api/login; the shared-link endpoints (/api/public/share/**) have no built-in rate limiting either. - Pass
X-Real-IPso the audit trail records the visitor rather than the proxy. - Proxy WebSocket upgrades — live state updates ride STOMP over WebSocket.
- Serve the whole thing over HTTPS. The browser Web Speech API used by voice control requires a secure context.
Building from source
The repository is a multi-module Gradle build. Java 17 is required; the Gradle wrapper is included.
git clone https://github.com/kirpi4ik/myhab.git
cd myhab
./gradlew bootRun # backend on :8181
./gradlew serve # Quasar dev server on :10002
./gradlew test # Spock test suite
./gradlew assembleServerAndClient # one JAR containing server + compiled client
./gradlew buildImage # local Docker imageFrontend work happens in client/web-vue3:
cd client/web-vue3
yarn install
yarn serve # dev server, port 10002
yarn pwa:build # production PWA build
yarn lint && yarn format| Module | Path | Contains |
|---|---|---|
:server-core | server/server-core | Controllers, GORM domain, services, Quartz jobs, GraphQL. |
:server-config | server/server-libs/server-config | ConfigProvider and the configuration-key constants. |
:server-rules | server/server-libs/server-rules | Heating and lighting rule facts. |
:web-vue3 | client/web-vue3 | Vue 3 + Quasar PWA. |
:demo-simulator | demo/simulator | MQTT device simulator used by the public demo. |
client/android/ has its own Gradle wrapper and is deliberately excluded from the root
settings.gradle — the Android Gradle Plugin and the Grails plugin do not coexist in one
build. Open only client/android in Android Studio.
See the voice documentation.
Running the demo sandbox locally
The demo environment is a self-contained dataset with simulated devices — the quickest way to see a populated installation without owning any hardware. No Docker required.
./gradlew demoSeedLocal # (re)build the myhab_demo database — destructive, idempotent
./gradlew demoSim # simulator + embedded MQTT broker on :1883
./gradlew demoRun # backend in the `demo` environment
./gradlew serve # Quasar dev server on :10002Open http://localhost:10002 and sign in with demo / demo.
Start demoSim before demoRun — the application connects to
the broker at boot and the simulator is hosting it.
More detail on the demo page.
Upgrading
- Back up the database.
dbCreate = updateadds columns and tables but never removes anything, so a rollback means restoring a dump. - Pull the new tag and recreate the application container. The schema migrates on start.
- Check the release notes for any manual SQL under
doc/migrations/in the repository. - Configuration lives in git, so it survives the upgrade untouched.
Troubleshooting first boot
| Symptom | Likely cause |
|---|---|
| Application starts, login page loads, credentials rejected | No user rows, or roles written to users_sec_roles instead of sec_user_roles. |
| Login succeeds but every screen is empty | The account has no ROLE_ADMIN. |
| Controls render but nothing switches | Nothing is answering on MQTT, or the device code contains a hyphen so state echoes never match. |
| Startup fails resolving configuration | CFG_REPO_URI unreachable, wrong credentials, or the branch does not exist (prod by default in production). |
| Browser console shows CORS errors | Add your origin to cors.allowedOrigin in the configuration repository. |
| Charts shifted by hours | The container is not running in UTC. |
| Audit shows the proxy's IP for every action | myhab.security.trustedProxies is unset. |