/00 — boot sequence

Hello.

cd ../open-source

aiyu-ayaan/Domus

Domus

A local-first smart-home hub for your home server.

  • active
  • 2 stars
  • Rust
  • MIT
  • pushed today
Domus preview

// readmeaiyu-ayaan/Domus

Domus logo

Domus

Release Latest release License: MIT Last commit Docker pulls Docker image size

Rust SQLite React Electron Android MQTT Platforms Languages Encrypted

A local-first smart-home hub for your home server. Rust backend, SQLite, React web UI (with an Electron desktop shell), and pluggable integrations for Tuya / Smart Life (QR-code login, no developer account), TP-Link Tapo (local), Xiaomi Mi Home (local, tokens from your Mi account), Philips Hue (local bridge) and MQTT (Zigbee2MQTT, ESPHome and anything else that speaks Home Assistant discovery).

  • Automatic discovery of new devices, with an inbox or auto-add
  • Live dashboard: power draw, bill so far and projected bill, air quality, motion / door / leak / battery readings
  • Rooms, device groups, scenes and automations; triggers at a time, at sunrise or sunset (with an offset), on a device change, power level or going offline
  • Ask before switching: mark a main switch and every app, group, scene and manual automation run warns before it is turned on or off
  • Electricity billing with flat, slab and time-of-day tariffs in any currency
  • Multiple users with admin / member / guest roles and access per room or per device, each see-only, control or edit
  • Web, Electron desktop (Linux, Windows) and a native Android app, kept in sync live
  • 27 languages in the web, desktop and Android apps, following your system's language with a picker in Settings; right to left for Arabic and Urdu
  • An Updates page with the release notes (Markdown, tables and images) in every app
  • HTTPS by default; credentials and device keys encrypted at rest; one-file backup and restore

Architecture and diagrams: development/docs/ARCHITECTURE.md, summarised below. UI design profile: design-system/domus/MASTER.md. Progress: development/TODO.md.

How it fits together

flowchart LR
    subgraph Clients
        Web["React web UI"]
        Desk["Electron desktop"]
        And["Android app<br/>(Kotlin, Compose)"]
    end

    subgraph Server["Domus server: Rust (Axum, Tokio), one binary or Docker image"]
        direction TB
        TLS["HTTPS (rustls)"] --> Auth["JWT auth<br/>role and access read from the database"]
        Auth --> Modules["modules<br/>users · devices · rooms · scenes · automations<br/>energy · notifications · integrations"]
        Auth --> WS["live events (WebSocket)"]
        Modules --> Mgr["DeviceManager<br/>poll · discover · command"]
        Mgr --> Bus(("EventBus"))
        Bus --> Engine["AutomationEngine<br/>time · sun · state triggers"]
        Engine --> Mgr
        Bus --> WS
        Workers["workers<br/>poller · discovery · scheduler"] --> Mgr
        Workers --> Engine
        Mgr --> Vault["Vault<br/>AES-256-GCM"]
    end

    subgraph Plugins["integrations"]
        Tuya["Tuya / Smart Life<br/>cloud, QR login"]
        Tapo["Tapo<br/>local"]
        Xiaomi["Xiaomi<br/>local miIO"]
        Hue["Philips Hue<br/>local bridge"]
        Mqtt["MQTT<br/>Home Assistant discovery"]
    end

    DB[("SQLite<br/>data/domus.db")]
    Keys[["data/master.key · jwt.secret · tls/"]]
    Web & Desk & And -->|"HTTPS + WSS"| TLS
    Mgr --> Plugins
    Modules --> DB
    Vault -.-> Keys
    Modules -. "place search (name only)" .-> Geo(("Open-Meteo<br/>geocoding"))

Clients talk only to the server; the server talks to your devices (locally where the vendor allows it) and keeps everything in one SQLite file. The cloud is used only where a vendor offers no local control (Tuya) or to fetch device keys (Xiaomi), plus two optional lookups: place search for the home location and GitHub for update checks.

Data model

erDiagram
    users ||--o{ user_room_access : "rooms"
    rooms ||--o{ user_room_access : ""
    users ||--o{ user_device_access : "devices"
    devices ||--o{ user_device_access : ""
    rooms ||--o{ devices : contains
    integration_accounts ||--o{ devices : "linked via"
    integration_accounts ||--o{ discovered_devices : "found via"
    devices ||--o{ device_group_members : ""
    device_groups ||--o{ device_group_members : ""
    devices ||--o{ energy_metrics : samples
    devices ||--o{ air_metrics : samples
    users {
        text id PK
        text role
        bool all_rooms
        bool kid
        bool must_change_password
        text reset_code_hash
    }
    user_room_access {
        text user_id
        text room_id
        bool can_control
        bool can_edit
    }
    user_device_access {
        text user_id
        text device_id
        bool can_control
        bool can_edit
    }
    devices {
        text id PK
        text integration_type
        text state_json
        text secrets_enc
        bool is_online
        bool confirm_changes
    }
    discovered_devices {
        text id PK
        text status
        text secrets_enc
    }
    integration_accounts {
        text id PK
        text credentials_enc
        text status
    }
    energy_metrics {
        text device_id
        real power_watts
        real energy_kwh
        datetime recorded_at
    }
    air_metrics {
        text device_id
        real pm25
        datetime recorded_at
    }
    automations {
        text triggers_json
        text conditions_json
        text actions_json
        bool is_enabled
    }
    scenes {
        text actions_json
        text kind
    }
    electricity_settings {
        real rate_per_kwh
        text currency
        text slabs
        text time_of_use
    }
    notifications {
        text level
        text title
        bool is_read
        bool admin_only
    }
    app_settings {
        text key PK
        text value_json
    }

Credentials and device keys are stored sealed (*_enc) under data/master.key, which is not in the database. The full set of diagrams, one per feature, is in development/docs.

Screenshots

Domus walkthrough

Desktop (Electron and web)

Android

Android tablet (two columns from 840dp, Settings as list and detail)

Mobile web

All captures are in res/screenshots; the full-quality walkthrough is res/domus.mp4.

Download

Server (Docker)aiyuayaan/domus on Docker Hub, amd64 and arm64
AndroidDomus-<version>.apk from the latest release
Linux desktopthe one-line installer below, or the .deb / .AppImage from the release
Windows desktopDomus-<version>-Setup.exe from the release

Server

One file is all the host needs:

mkdir domus && cd domus
wget https://raw.githubusercontent.com/aiyu-ayaan/Domus-Rust/master/docker-compose.yml
docker compose up -d

Open https://<server-ip>:8443 and create the administrator account. docker compose logs domus shows the certificate fingerprint the apps ask you to confirm.

docker compose pull && docker compose up -d      # update
DOMUS_VERSION=beta docker compose up -d          # follow a channel: latest (default), beta, alpha, or 0.0.1
TZ=Asia/Kolkata DOMUS_PORT=9443 docker compose up -d
docker compose --profile mqtt up -d              # also start the optional Mosquitto broker

Put DOMUS_VERSION, TZ and DOMUS_PORT in a .env file next to the compose file to keep them.

Linux desktop app

Installs the newest release for the current user (x86_64, no root): the AppImage, a menu entry with the Domus icon and a domus command. The app then keeps itself up to date.

curl -fsSL https://raw.githubusercontent.com/aiyu-ayaan/Domus-Rust/master/scripts/install-linux.sh | sh
# ... | sh -s -- --channel beta     beta builds too (or alpha)
# ... | sh -s -- --uninstall        remove it; settings are kept

Android

Download the APK from the release page and open it. Later updates are offered when the app opens and in Settings → Updates, on the Stable, Beta or Alpha channel, and installed from there; Android asks once to allow Domus to install them.

The Updates page (Settings → Updates on Android, Settings → About → Updates on web and desktop) shows what you run, the newest release on your channel and its release notes, which are written in Markdown.

Releases, version numbers and the update channels: development/docs/RELEASE.md.

Requirements

  • Rust 1.80+ (cargo)
  • Node.js 18+ and pnpm

Run in development

pnpm install
npm run dev            # backend (https://127.0.0.1:8443) + web (http://localhost:3000)

npm run dev:backend    # backend only
npm run dev:web        # web only (proxies /api to the backend)
npm run dev:desktop    # Electron; pick the server on its connect page

Open http://localhost:3000. The first visit asks you to create the administrator account.

Run on the home server

pnpm build:web                     # builds apps/web/dist, served by the backend
APP_ENV=production cargo run --release --manifest-path backend/Cargo.toml

Then open https://<server-ip>:8443.

Or build the Docker image from this checkout (host networking, data in the domus_data volume):

docker compose -f docker-compose.local.yml up -d --build

Remote access through a Cloudflare Tunnel, the home/remote address the apps switch between, and the desktop packages (pnpm build:desktop) are described in development/docs/DEPLOYMENT.md.

On first start Domus creates data/ with:

FilePurpose
domus.dbSQLite database
master.keyencrypts integration credentials and device keys — back it up
jwt.secretsigns sessions
tls/self-signed certificate (replace via tls.cert_path / tls.key_path)

Settings live in config/default.toml; any key can be overridden with DOMUS__SECTION__KEY environment variables (see .env.example).

Backup and restore

Everything lives in data/. Back up the whole folder, or take the one-file backup:

Back up. Sign in as an administrator → Settings → Backup → Download backup. The file is a copy of the database with the master key stored inside, so the linked accounts in it can still be opened after a restore. It is made while the server runs. Keep it private: whoever holds it can open those accounts. The JWT secret and the TLS certificate are not in it.

Restore. Stop the server, then:

# Docker
docker compose cp domus-backup-2026-10-11.db domus:/app/data/restore.db
docker compose stop domus
docker compose run --rm domus domus-backend restore data/restore.db
docker compose start domus

# Without Docker, from the repo root (uses ./data)
domus-backend restore ~/Downloads/domus-backup-2026-10-11.db

What was there is kept next to it as *.before-restore. After a restore on a new machine people sign in again and the apps ask to trust the new certificate. If you set the key with DOMUS_MASTER_KEY, the backup holds no key and the restored server needs the same variable. More in development/docs/DEPLOYMENT.md.

Forgot the administrator password

Domus has no mail server, so nothing is sent by email. In order of effort:

  1. Another administrator opens Settings → People, turns on Reset code for you and tells you the 8-digit code (good for 30 minutes and 5 wrong tries). On the sign-in screen choose Forgot password?, enter your username and then the code.

  2. From the server, which works while Domus runs or is stopped and needs no sign-in:

    docker compose exec domus domus-backend reset-password <username>              # prints a new random password
    docker compose exec domus domus-backend reset-password <username> 'new-pass'   # or choose one (8+ characters)
    domus-backend reset-password <username>                                        # without Docker, from the repo root
    

    Sign in with it and change it in Settings → Password.

Five wrong passwords for a username lock that name for 15 minutes in production (the lock is lifted by restarting the server, and is off in development). Details and the rules for generated passwords and kid's accounts: development/docs/ACCOUNTS.md.

Languages

English, Hindi, Spanish, Portuguese (Brazil), Indonesian, Arabic, French, Russian, German, Turkish, Japanese, Korean, Italian, Vietnamese, Thai, Chinese (Simplified and Traditional), Polish, Bengali, Tamil, Telugu, Marathi, Gujarati, Kannada, Malayalam, Punjabi and Urdu. Arabic and Urdu are laid out right to left.

Each app follows the language of your browser or system, and shows English when it has no translation for it. To choose another one, open Settings > Language (Appearance on web and desktop); System default goes back to following the system. On Android 13 and later it also appears under the phone's Settings > Apps > Domus > Language.

The translations are machine-made and have not been reviewed by native speakers, so wording may be off. A fix is a one-line change in the language's catalog. How the languages work, how to add one and the rules for new text: development/docs/LOCALIZATION.md.

Connecting devices

BrandHow
Tuya / Smart LifeIntegrations → Link with QR code. Enter the User Code from Smart Life (Me → Settings → Account and Security → User Code), then scan the QR in the app.
TapoIntegrations → Link account with your TP-Link ID. Devices on the LAN are found automatically and controlled locally. An H100 hub brings its sensors, switches and thermostats with it. Cameras are listed but not supported yet.
XiaomiIntegrations → Link account with your Mi account and server region. If Xiaomi asks for two-step verification, enter the code it emails or texts you in the same panel. The account can be an email, a phone number with country code (+91…) or a Xiaomi ID. Tokens are fetched once; control is local. Devices can also be added with a token.
Philips HuePress the round button on the bridge, then Integrations → Philips Hue → Link within 30 seconds. Leave the address empty to find the bridge automatically. Lights and Hue smart plugs are controlled locally.
MQTTIntegrations → MQTT → Link with your broker's address, port and login. Devices that announce themselves through Home Assistant MQTT discovery (Zigbee2MQTT, ESPHome, …) appear under Discovered: lights, switches, plugs, fans and sensors. With the bundled broker (--profile mqtt) the address is mqtt, port 1883.

See development/docs/INTEGRATIONS.md for what each integration covers.

Electricity billing

Energy → Electricity plan: pick any currency, and choose a flat price per kWh or slabs for unit-based tariffs (e.g. Pune/MSEDCL: 0–100 units at one rate, 101–300 at the next, and so on). The bill is calculated per billing cycle, plus the fixed monthly charge.

Tariffs can also carry time-of-day windows: hours (on the home's clock, past midnight allowed) whose units cost a set amount more or less, for example a night rebate or a peak surcharge, on top of the flat or slab price.

New devices appear under Devices → Discovered. Turn on Add new devices automatically there to skip the inbox.

Sunrise and sunset

Set where the home is in Settings → Home location: use this phone's or browser's location, search for the town (the server asks the free Open-Meteo geocoder; only the name you type leaves the home), or type the latitude and longitude. The coordinates are rounded to about a kilometre and stay on your server. An automation can then run at sunrise or sunset, minutes before or after. Without a location, a sun trigger never fires.

Ask before switching

A main switch that cuts power to everything plugged into it is easy to turn off by mistake. Open the device and turn on Ask before switching: every app then asks before turning it on or off, and so do room All on / All off, device groups, scenes (including scenes inside scenes) and a manual Run of an automation that would switch it. The setting is kept on the server, so it is the same on web, desktop and Android. Automations that fire on their own cannot ask and run as written, and nothing outside Domus (a vendor app, a wall switch) is covered.

License

Domus is released under the MIT License. Third-party libraries keep their own licenses; they are listed under About → Acknowledgements in every app.

Tests

cargo test --workspace
pnpm --filter @domus/web exec tsc -b

// stack

  • Rust
  • TypeScript

// specs

status
Working
type
Open Source
year
2026
language
Rust
license
MIT
stars
2
branch
master

Repository data last synced