Ever wanted your entire smart home to be as reproducible as your NixOS config?
I’m back after extracting from personal setup into a reusable flake, I’m happy to share Zigduck.
Zigduck is a fully reproducible home automation system for NixOS.
Define your rooms, devices, and automations once – deploy forever.
What you get:
- Smart defaults – lights, dimmers, and motion sensors work out‑of‑the‑box without writing any automations.
- Powerful automation engine - automate anything in Nix.
- Rust‑powered async runtime – fast, reliable, and responsive.
- Auto‑generated dashboard – beautiful scene images created from Nix configuration, also allows room/device control from any browser.
- CLI & REST API – powerful CLI/API that provides usage that ranges from alarms/timers to TV control.
- Version control – your whole smart home lives in Git, just like the rest of your NixOS system.
Below I’ll very briefly explain how automations can be defined.
Automation Types
Supports 7 automation types, each triggered by different events.
Below is a quick overview for each.
| Automation Type | Trigger | Example Use Case |
|---|---|---|
greeting |
No motion for a configured duration | Welcome you home when you return |
time_based |
Specific time or schedule | Morning alarm, evening routine |
mqtt_triggered |
Any MQTT topic/message | React to a doorbell, alarm, or external system |
room_actions |
Device events in a room (motion, contact) | Turn on fan when kitchen motion detected |
global_actions |
Global events (water leak, smoke) | Notify and flash all lights on smoke |
dimmer_actions |
Physical dimmer button presses | Customise what a dimmer switch does |
presence_based |
All motion sensors report no motion | Turn off everything when nobody is home |
Each automation type can use any of the automation action types described below.
For full details and all options, see the repository.
Automation Action Types
Every automation can mix and match 7 action types:
house.zigbee.automations {
mqtt_triggered = {
alarm_wakeup = {
enable = true;
description = "Time to wake up!";
topic = "zigbee2mqtt/alarm/triggered";
actions = [
# 1. snapshot – capture current state of all lights
{ type = "snapshot"; snapshot_name = "before_alarm"; }
# 2. scene – activate a predefined scene
{ type = "scene"; scene = "max"; }
# 3. mqtt – publish a raw MQTT message
{ type = "mqtt"; topic = "zigbee2mqtt/Robot Arm 3/set"; message = ''{"state":"OFF"}''; }
# 4. wait – pause for a number of seconds
{ type = "wait"; duration = 10; }
# 5. shell – run a shell command
{ type = "shell"; command = "curl http://192.168.1.13/api/settings/speaker/play/ding"; }
# 6. simple string – shortcut for shell (with `yo`, automate using natural language)
"yo do 'turn on all lights'"
# 7. restore – return lights to the snapshotted state
{ type = "restore"; snapshot_name = "before_alarm"; }
];
};
};
};
A more advanced example might look like:
house.zigbee.automations {
room_actions = {
kitchen = {
motion_not_detected = [
{
type = "shell";
command = ''
power=$(jq -r '."Fläkt".power' /var/lib/zigduck/state.json)
# if fan is on and no motion - start a countdown to turn off
if (( power > 20 )); then
zigduck-cli --publish --topic "zigduck/Fläkt/set" --payload '{"countdown": 120}'
fi
'';
}
{ type = "scene"; scene = "kitchenFadeOff"; }
];
motion_detected = [
{ type = "scene"; scene = "kitchenInstant"; }
{
type = "shell";
command = ''
# cancel any pending countdown
zigduck-cli --publish --topic "zigduck/Fläkt/set" --payload '{"countdown": 0}'
# if fan is off - start it
STATE=$(jq -r '."Fläkt".state' /var/lib/zigduck/state.json)
if [ "$STATE" = "OFF" ]; then
zigduck-cli --device "Fläkt" --state on
fi
'';
}
];
};
};
};
Perhaps others may find this project as useful and fun as I do.
Stay safe.
![]()
![]()
