Systemd Deployment — External Mosquitto
End-to-end recipe for running HomeCore as a native systemd service, paired with an external Mosquitto broker that enforces per-topic ACLs (the embedded rumqttd does not). If you're new to the trade-off between the embedded broker and an external one, read the broker guide first.
If you prefer Docker, the compose variant is in the core repo at
core/docker/docker-compose.external-broker.yml and the flow is
equivalent — same generated files, just different packaging.
This guide assumes a Debian/Ubuntu-style distribution with
systemd and apt; Red Hat / Arch / NixOS users will need to adapt
package + path conventions.
Layout
By the end you'll have:
/etc/homecore/
├── homecore.toml # HomeCore config (broker.external_url set)
/etc/mosquitto/
├── mosquitto.conf # generated by hc-cli
├── acl # generated aclfile
├── passwd # hashed from passwd.setup.sh
└── mosquitto.conf.d/ # standard drop-in dir (unused here)
/var/lib/homecore/ # state.redb, history.db, audit.db,
# jwt_secret (0600)
/var/log/homecore/ # rolling tracing log (if configured)
/etc/systemd/system/
├── homecore.service # HomeCore unit
└── mosquitto.service.d/override.conf # optional: tighten Mosquitto
1. Install Mosquitto
sudo apt install -y mosquitto mosquitto-clients
sudo systemctl stop mosquitto
We stop it immediately — the default config is anonymous-allowed, which we'll replace.
2. Install HomeCore
If you build from source:
cd homeCore/core
cargo build --release
sudo install -m 0755 target/release/homecore /usr/local/bin/
sudo install -m 0755 target/release/hc-cli /usr/local/bin/
If you use a prebuilt tarball, extract it under /opt/homecore and
copy the binaries the same way.
Create the service user and directories:
sudo useradd --system --home-dir /var/lib/homecore --shell /usr/sbin/nologin homecore
sudo install -d -o homecore -g homecore -m 0750 /var/lib/homecore
sudo install -d -o homecore -g homecore -m 0750 /var/log/homecore
sudo install -d -o root -g homecore -m 0750 /etc/homecore
3. Author homecore.toml
Drop your config at /etc/homecore/homecore.toml. The two lines that
matter for this deployment:
[broker]
external_url = "mqtt://127.0.0.1:1883"
# The [[broker.clients]] entries below are *not* used by the embedded
# broker (because external_url is set) — they're consumed by hc-cli to
# generate the Mosquitto ACL + password files.
[[broker.clients]]
id = "internal.core"
password = "<strong plaintext password for homeCore itself>"
allow_pub = ["homecore/#"]
allow_sub = ["homecore/#"]
[[broker.clients]]
id = "plugin.thermostat"
password = "<strong plaintext password>"
allow_pub = ["homecore/devices/thermostat_+/state",
"homecore/plugins/plugin.thermostat/+",
"homecore/devices/+/cmd"]
allow_sub = ["homecore/devices/thermostat_+/cmd",
"homecore/devices/+/state",
"homecore/devices/+/state/partial"]
# ...one [[broker.clients]] block per plugin.
Lock it down:
sudo chown root:homecore /etc/homecore/homecore.toml
sudo chmod 0640 /etc/homecore/homecore.toml
4. Generate Mosquitto config from homecore.toml
sudo -u homecore hc-cli broker generate-mosquitto-config \
--config /etc/homecore/homecore.toml \
--out-dir /tmp/mosquitto-config
This produces three files under /tmp/mosquitto-config/:
mosquitto.conf— listener + references to/mosquitto/config/acland/mosquitto/config/passwdaclfile— per-user topic rules derived fromallow_pub/allow_subpasswd.setup.sh— helper script withmosquitto_passwdcommands
Adjust paths for a native (non-Docker) Mosquitto
The generated mosquitto.conf assumes Docker's /mosquitto/config/...
layout. For a native systemd install we want /etc/mosquitto/...:
sed -i 's#/mosquitto/config/#/etc/mosquitto/#g' /tmp/mosquitto-config/mosquitto.conf
sed -i 's#/mosquitto/data/#/var/lib/mosquitto/#g' /tmp/mosquitto-config/mosquitto.conf
Populate real passwords
Edit passwd.setup.sh and replace each CHANGE_ME_<ID> with the same
plaintext you put in the matching [[broker.clients]].password field.
Run the script — Mosquitto's mosquitto_passwd hashes the entries with
PBKDF2-SHA512:
cd /tmp/mosquitto-config
bash passwd.setup.sh # produces `passwd` next to the script
Install the Mosquitto files
sudo install -m 0644 /tmp/mosquitto-config/mosquitto.conf /etc/mosquitto/
sudo install -m 0640 -g mosquitto /tmp/mosquitto-config/aclfile /etc/mosquitto/acl
sudo install -m 0600 -g mosquitto /tmp/mosquitto-config/passwd /etc/mosquitto/passwd
rm -rf /tmp/mosquitto-config
acl and passwd must be readable by the mosquitto user; not
world-readable (passwd contains hashed creds).
5. Install the HomeCore systemd unit
A template lives at core/scripts/service-templates/homecore.service.
Fill in the placeholders for a systemd install:
# /etc/systemd/system/homecore.service
[Unit]
Description=HomeCore Home Automation Server
Documentation=https://github.com/homeCore-io/homeCore
After=network-online.target mosquitto.service
Wants=network-online.target
Requires=mosquitto.service
[Service]
Type=simple
User=homecore
Group=homecore
Environment="HOMECORE_HOME=/var/lib/homecore"
ExecStart=/usr/local/bin/homecore --config /etc/homecore/homecore.toml
ExecReload=/bin/kill -HUP $MAINPID
Restart=on-failure
RestartSec=5
StartLimitIntervalSec=60
StartLimitBurst=5
KillMode=control-group
KillSignal=SIGINT
TimeoutStopSec=30
StandardOutput=journal
StandardError=journal
SyslogIdentifier=homecore
# Hardening — safe defaults for a system service.
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=read-only
ReadWritePaths=/var/lib/homecore /var/log/homecore
PrivateTmp=true
ProtectControlGroups=true
ProtectKernelModules=true
ProtectKernelTunables=true
RestrictRealtime=true
RestrictNamespaces=true
LockPersonality=true
MemoryDenyWriteExecute=true
[Install]
WantedBy=multi-user.target
Key bits:
Requires=mosquitto.service+After=mosquitto.service— HomeCore will not start until Mosquitto is up, and it restarts when Mosquitto restarts.ReadWritePaths=limits writable directories afterProtectSystem=strict. If you enable log pruning or backups, add those paths here.HOMECORE_HOMEtells HomeCore where to resolve relative paths inhomecore.toml. Absolute paths in config are safer; the env var is a fallback.
6. Start everything
sudo systemctl daemon-reload
sudo systemctl enable --now mosquitto
sudo systemctl enable --now homecore
7. Verify
Mosquitto is up and authenticating
journalctl -u mosquitto -f --since "2 min ago"
Expect a line like mosquitto version 2.x starting. A New connection from 127.0.0.1 line (with no username) followed by rejected is
expected — the homecore client presents creds on its next retry.
Try a denied action to prove the ACL is live:
mosquitto_pub -h 127.0.0.1 -u plugin.thermostat \
-P "<thermostat plaintext password>" \
-t "homecore/devices/zwave_37/cmd" -m '{"on":true}' -d
Since plugin.thermostat does have homecore/devices/+/cmd in its
allow_pub, this should succeed. Now try a topic outside its ACL:
mosquitto_pub -h 127.0.0.1 -u plugin.thermostat \
-P "<password>" \
-t "homecore/plugins/plugin.zigbee/register" -m '{}' -d
Expect Mosquitto to log ACL denying access … and the publish to be
dropped. That's the difference vs. the embedded broker: enforcement.
HomeCore is up
journalctl -u homecore -f --since "2 min ago"
curl http://127.0.0.1:8080/api/v1/health
Expect {"status":"ok","version":"..."}.
Plugins connect
Each plugin logs its Mosquitto connect at INFO. Tail their logs or, from Mosquitto's side:
journalctl -u mosquitto -g 'New client connected'
Each entry should show the plugin's client_id (e.g. plugin.lutron).
If you see Client rejected — not authorised in Mosquitto's log, the
plugin is presenting the wrong password — check that its own config
file carries the same plaintext you put in passwd.setup.sh.
Operations
Rotating a plugin password
- Generate a new plaintext secret.
- Update the plugin's own config file with it.
- Update the matching
[[broker.clients]].passwordin/etc/homecore/homecore.toml. - Regenerate + re-install the Mosquitto password file:
sudo -u homecore hc-cli broker generate-mosquitto-config \
--config /etc/homecore/homecore.toml \
--out-dir /tmp/mc --force
# Edit passwd.setup.sh, replace placeholders, run it, copy `passwd`.
sudo install -m 0600 -g mosquitto /tmp/mc/passwd /etc/mosquitto/passwd sudo systemctl reload mosquitto— picks up the newpasswdwithout disconnecting already-authenticated clients. The new password takes effect on the next CONNECT. If you want to force an immediate re-auth,sudo systemctl restart mosquittoinstead.
Changing a plugin's allowed topics
Edit the allow_pub / allow_sub arrays in [[broker.clients]].
Regenerate only the ACL file:
sudo -u homecore hc-cli broker generate-mosquitto-config \
--config /etc/homecore/homecore.toml \
--out-dir /tmp/mc --force
sudo install -m 0640 -g mosquitto /tmp/mc/aclfile /etc/mosquitto/acl
sudo systemctl reload mosquitto
Mosquitto re-reads the ACL on SIGHUP (what systemctl reload sends).
Adding a new plugin
- Add a new
[[broker.clients]]block inhomecore.toml. - Regenerate everything (
generate-mosquitto-config). - Update the
passwd(runpasswd.setup.shwith the newmosquitto_passwd -b passwd <new-plugin-id> <password>line, or just run the full script with-cremoved from the first line so it appends rather than clobbers). - Install the updated
aclandpasswdfiles;systemctl reload mosquitto. - Install the plugin binary + its own config (pointing at the same plaintext password).
Troubleshooting
HomeCore refuses to start — broker external_url unreachable.
Mosquitto didn't start or is listening on a different port. Check
ss -tlnp | grep 1883.
Plugins connect but "Publish denied" fills the Mosquitto log.
The plugin is trying a topic outside its ACL. Either the ACL is too
narrow (fix [[broker.clients]].allow_pub), or the plugin is
misbehaving. Cross-reference against the plugin's documented topic
contract.
Plugin can authenticate but homeCore never sees its state.
The plugin's allow_pub doesn't cover its state topic. Very common
pitfall after renaming a device ID — the ACL patterns stayed narrow.
Mosquitto's log is empty.
Default Mosquitto log level is high. Add log_type debug to
mosquitto.conf temporarily when debugging.
Next steps
- TLS for the broker — move plugins off plain-text MQTT.
- Backup / restore — the audit DB, state DB, and
jwt_secretall live under/var/lib/homecoreand should be in your backup set. - Deployment checklist — broader production hardening list.