From 242d87fbf74069516a77dc4316e4e65c15d79054 Mon Sep 17 00:00:00 2001 From: Jordan Holt Date: Sun, 19 Jan 2025 14:26:59 +0000 Subject: [PATCH] Add home-assistant module --- modules/nixos/default.nix | 1 + .../nixos/services/home-assistant/default.nix | 306 ++++++++++++++++++ .../home-assistant/floorplan/default.nix | 100 ++++++ .../home-assistant/floorplan/style.css | 27 ++ .../nixos/services/home-assistant/mqtt.nix | 93 ++++++ 5 files changed, 527 insertions(+) create mode 100644 modules/nixos/services/home-assistant/default.nix create mode 100644 modules/nixos/services/home-assistant/floorplan/default.nix create mode 100644 modules/nixos/services/home-assistant/floorplan/style.css create mode 100644 modules/nixos/services/home-assistant/mqtt.nix diff --git a/modules/nixos/default.nix b/modules/nixos/default.nix index 55f2231..cd55461 100644 --- a/modules/nixos/default.nix +++ b/modules/nixos/default.nix @@ -8,6 +8,7 @@ ./services/gitea.nix ./services/gitea-runner.nix ./services/headscale.nix + ./services/home-assistant ./services/mail.nix ./services/matrix.nix ./services/netbird.nix diff --git a/modules/nixos/services/home-assistant/default.nix b/modules/nixos/services/home-assistant/default.nix new file mode 100644 index 0000000..43a36b5 --- /dev/null +++ b/modules/nixos/services/home-assistant/default.nix @@ -0,0 +1,306 @@ +{ + config, + lib, + pkgs, + self, + ... +}: + +let + cfg = config.modules.services.home-assistant; +in +{ + imports = [ + ./floorplan/default.nix + ./mqtt.nix + ]; + + options.modules.services.home-assistant.enable = lib.mkEnableOption "home-assistant"; + + config = lib.mkIf cfg.enable { + + age.secrets."files/services/home-assistant/secrets.yaml" = { + file = "${self.inputs.secrets}/files/services/home-assistant/secrets.yaml.age"; + path = "${config.services.home-assistant.configDir}/secrets.yaml"; + owner = "hass"; + group = "hass"; + }; + + services.home-assistant = { + enable = true; + + config = { + automation = "!include automations.yaml"; + backup = { }; + binary_sensor = [ ]; + default_config = { }; + http = { + server_host = "::1"; + trusted_proxies = [ "::1" ]; + use_x_forwarded_for = true; + }; + ffmpeg = { }; + homeassistant = { + name = "Home"; + latitude = "!secret latitude"; + longitude = "!secret longitude"; + country = "GB"; + temperature_unit = "C"; + time_zone = config.time.timeZone; + unit_system = "metric"; + auth_providers = [ + { + type = "trusted_networks"; + trusted_networks = [ + "100.64.0.0/10" + "127.0.0.1" + ]; + allow_bypass_login = true; + } + { + type = "homeassistant"; + } + ]; + }; + logger = { + default = "info"; + logs = { }; + }; + lovelace = { + resources = [ + { + url = "/local/nixos-lovelace-modulels/mushroom.js"; + type = "module"; + } + ]; + }; + media_player = [ ]; + mobile_app = { }; + onkyo = { }; + open_meteo = { }; + recorder = { + purge_keep_days = 365; + }; + scene = "!include scenes.yaml"; + script = "!include scripts.yaml"; + sensor = [ ]; + system_health = { }; + zeroconf = { }; + }; + + configDir = "/etc/home-assistant"; + + extraComponents = [ + "air_quality" + "airly" + "alert" + "api" + "application_credentials" + "asuswrt" + "auth" + "automation" + "bayesian" + "binary_sensor" + # "blackbird" + "blueprint" + "bluetooth_adapters" + "bluetooth_le_tracker" + "button" + "calendar" + "camera" + "cast" + "cert_expiry" + "climate" + "co2signal" + "color_extractor" + "command_line" + "compensation" + "configurator" + "counter" + "cover" + "cpuspeed" + "default_config" + "demo" + "derivative" + "device_automation" + "device_sun_light_trigger" + "device_tracker" + "dlna_dmr" + "dlna_dms" + "dnsip" + "esphome" + "fail2ban" + "fan" + "feedreader" + "ffmpeg" + "file" + "file_upload" + "filesize" + "folder" + "folder_watcher" + "forecast_solar" + "frontend" + "gdacs" + "generic" + "generic_hygrostat" + "generic_thermostat" + "geo_json_events" + "geo_location" + "geo_rss_events" + "github" + "group" + "hardware" + "hdmi_cec" + "history_stats" + "homeassistant" + "homekit" + "homekit_controller" + "html5" + "http" + "humidifier" + "icloud" + "image_processing" + "input_boolean" + "input_button" + "input_datetime" + "input_number" + "input_select" + "input_text" + "integration" + "ios" + "jellyfin" + "light" + "local_calendar" + "local_file" + "local_ip" + "local_todo" + "lock" + "logentries" + "logger" + "lovelace" + "manual" + "manual_mqtt" + "matter" + "media_player" + "min_max" + "mjpeg" + "modern_forms" + "mold_indicator" + "moon" + "mysensors" + "network" + "nmap_tracker" + "notify" + "number" + "onboarding" + "onkyo" + "panel_custom" + "persistent_notification" + "person" + "ping" + "plant" + "prometheus" + "proximity" + "push" + "proximity" + "python_script" + "radio_browser" + "random" + "recorder" + "remote" + "repairs" + "rest" + "rest_command" + "rss_feed_template" + "scene" + "schedule" + "scrape" + "script" + "search" + "season" + "select" + "sense" + "sensor" + "sensorpush" + "shell_command" + "shopping_list" + "siren" + "smtp" + "snmp" + "sql" + "statistics" + "sun" + "switch" + "switch_as_x" + "system_health" + "system_log" + "systemmonitor" + "tag" + "tailscale" + "tcp" + "template" + "text" + "thread" + "threshold" + "time_date" + "timer" + "tod" + "todo" + "tomorrowio" + "trend" + "universal" + "upb" + "update" + "upnp" + "uptime" + "utility_meter" + "vacuum" + "vlc" + "vlc_telnet" + "wake_on_lan" + "water_heater" + "weather" + "websocket_api" + "wled" + "workday" + "worldclock" + "zone" + ]; + + extraPackages = + python3Packages: with python3Packages; [ + onkyo-eiscp + zeroconf + ]; + + customLovelaceModules = with pkgs.home-assistant-custom-lovelace-modules; [ + bubble-card + button-card + mushroom + sankey-chart + universal-remote-card + # zigbee2mqtt-networkmap + ]; + }; + + systemd.services.home-assistant.preStart = lib.mkForce ""; + + modules.services.borgmatic.directories = [ + config.services.home-assistant.configDir + ]; + + services.nginx = { + enable = true; + virtualHosts."home.mesh.vimium.net" = { + forceSSL = false; + extraConfig = '' + proxy_buffering off; + ''; + locations."/" = { + proxyPass = "http://[::1]:8123"; + proxyWebsockets = true; + }; + }; + }; + }; +} diff --git a/modules/nixos/services/home-assistant/floorplan/default.nix b/modules/nixos/services/home-assistant/floorplan/default.nix new file mode 100644 index 0000000..b536a83 --- /dev/null +++ b/modules/nixos/services/home-assistant/floorplan/default.nix @@ -0,0 +1,100 @@ +{ + config, + lib, + pkgs, + ... +}: + +let + cfg = config.modules.services.home-assistant; +in +{ + config = lib.mkIf cfg.enable { + services.home-assistant = { + config.lovelace.resources = [ + { + url = "/local/nixos-lovelace-modules/floorplan.js"; + type = "module"; + } + ]; + customLovelaceModules = [ + pkgs.ha-floorplan + ]; + }; + + environment.etc."home-assistant/www/floorplan/style.css".source = ./style.css; + environment.etc."home-assistant/www/floorplan/config.yaml".text = builtins.toJSON { + image = { + location = "/local/floorplan/beetham.svg"; + cache = false; + }; + stylesheet = { + location = "/local/floorplan/style.css"; + cache = false; + }; + + defaults = { + hover_action = "hover-info"; + hold_action = "toggle"; + tap_action = "more-info"; + }; + + rules = [ + { + name = "Rooms"; + entities = [ + { + entity = "light.bedroom_lamps"; + element = "area.bedroom"; + } + { + entity = "light.hallway_spots"; + element = "area.hallway"; + } + { + entity = "light.living_room_lamps"; + element = "area.livingroom"; + } + { + entity = "light.office_lamps"; + element = "area.office"; + } + ]; + tap_action = "light.toggle"; + state_action = { + service = "floorplan.class_set"; + service_data = '' + if (entity.state === "on") { + return "light-on"; + } + return "light-off"; + ''; + }; + } + { + name = "Temperature"; + entities = [ + "sensor.motion_sensor_temperature" + ]; + state_action = [ + { + service = "floorplan.text_set"; + service_data = '' + if (!isNaN(entity.state)) { + return Math.round(entity.state * 10) / 10 + "°"; + } + return "Unknown"; + ''; + } + { + service = "floorplan.class_set"; + service_data = { + class = "static-temp"; + }; + } + ]; + } + ]; + }; + }; +} diff --git a/modules/nixos/services/home-assistant/floorplan/style.css b/modules/nixos/services/home-assistant/floorplan/style.css new file mode 100644 index 0000000..a4e59ba --- /dev/null +++ b/modules/nixos/services/home-assistant/floorplan/style.css @@ -0,0 +1,27 @@ +#floorplan { + padding: 10px; +} + +svg, svg * { + vector-effect: non-scaling-stroke !important; + pointer-events: all !important; +} + +path[id*="area."].light-on { + opacity: 0 !important; +} + +path[id*="area."] { + opacity: 0.5 !important; + transition: opacity .25s; + -moz-transition: opacity .25s; + -webkit-transition: opacity .25s; +} + +svg tspan { + fill: var(--primary-text-color); +} + +.static-temp, .static-temp tspan { + fill: #ffffff; +} diff --git a/modules/nixos/services/home-assistant/mqtt.nix b/modules/nixos/services/home-assistant/mqtt.nix new file mode 100644 index 0000000..d21c51b --- /dev/null +++ b/modules/nixos/services/home-assistant/mqtt.nix @@ -0,0 +1,93 @@ +{ + config, + lib, + pkgs, + self, + ... +}: + +let + cfg = config.modules.services.home-assistant; +in +{ + config = lib.mkIf cfg.enable { + services.mosquitto = { + enable = true; + listeners = [ + { + acl = [ "pattern readwrite #" ]; + omitPasswordAuth = true; + port = 1883; + settings = { + allow_anonymous = true; + }; + } + ]; + }; + + age.secrets."files/services/zigbee2mqtt/secret.yaml" = { + file = "${self.inputs.secrets}/files/services/zigbee2mqtt/secret.yaml.age"; + path = "${config.services.zigbee2mqtt.dataDir}/secret.yaml"; + owner = "zigbee2mqtt"; + group = "zigbee2mqtt"; + }; + + services.zigbee2mqtt = { + package = pkgs.unstable.zigbee2mqtt; + enable = true; + dataDir = "/var/lib/zigbee2mqtt"; + settings = { + homeassistant = lib.optionalAttrs config.services.home-assistant.enable { + discovery_topic = "homeassistant"; + status_topic = "hass/status"; + legacy_entity_attributes = true; + legacy_triggers = true; + }; + availability = true; + frontend = true; + device_options = { + retain = true; + }; + serial = { + port = "/dev/serial/by-id/usb-Silicon_Labs_Sonoff_Zigbee_3.0_USB_Dongle_Plus_0001-if00-port0"; + }; + advanced = { + channel = 20; + network_key = "!secret.yaml network_key"; + pan_id = 13001; + ext_pan_id = [ + 79 + 1 + 73 + 47 + 250 + 136 + 124 + 222 + ]; + transmit_power = 20; + }; + mqtt = { + version = 5; + server = "mqtt://localhost:1883"; + }; + }; + }; + + modules.services.borgmatic.directories = [ + config.services.mosquitto.dataDir + config.services.zigbee2mqtt.dataDir + ]; + + services.home-assistant = { + config.mqtt = { }; + extraComponents = [ + "mqtt" + "mqtt_eventstream" + "mqtt_json" + "mqtt_room" + "mqtt_statestream" + ]; + }; + }; +}