The Flake For Your House - Automating With Nix

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.

Project GitHub

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.

:white_cane::duck:

12 Likes

I sincerely hope this supplants Home Assistant someday. The lead dev and his anti-determinism, anti-nix drama have always left a bad taste in my mouth… particularly now that HA is ubiquitous.