{ pkgs, ... }: { services.postgresql = { enable = true; ensureDatabases = [ "hass" ]; ensureUsers = [{ name = "hass"; ensureDBOwnership = true; }]; }; services.home-assistant = { enable = true; extraPackages = ps: with ps; [ psycopg2 ]; extraComponents = [ "esphome" "met" "roku" "vesync" ]; # https://nathan.gs/2023/12/28/home-assistant-add-a-custom-component-in-nixos-revisited/ customComponents = [ ( pkgs.buildHomeAssistantComponent rec { owner = "BeryJu"; domain = "auth_header"; version = "1.10"; src = pkgs.fetchFromGitHub { inherit owner; repo = "hass-auth-header"; rev = "refs/tags/v${version}"; hash = "sha256-dSmY3d8Kx0pXl+20dTGAYgjSH6OhNh53jPX7VLCZs7Y="; }; dontBuild = true; } ) ( pkgs.buildHomeAssistantComponent rec { owner = "make-all"; domain = "tuya_local"; version = "2024.2.0"; src = pkgs.fetchFromGitHub { inherit owner; repo = "tuya-local"; rev = "refs/tags/${version}"; hash = "sha256-wNdATRXJNHusVO2fMUXqSz0EZRDpodORSuFRXL6ohUs="; }; propagatedBuildInputs = with pkgs.home-assistant.python.pkgs; [ ( buildPythonPackage rec { pname = "tinytuya"; version = "1.13.1"; format = "wheel"; src = pkgs.fetchPypi { inherit pname version format; hash = "sha256-j7t4P4U9iuVHyb6HASkf7LmBheHN32IjdKE60HUbjIE="; }; propagatedBuildInputs = [ colorama cryptography requests ]; } ) ]; dontBuild = true; } ) ]; config = { default_config = { }; http = { server_host = "::1"; trusted_proxies = [ "::1" ]; use_x_forwarded_for = true; }; recorder.db_url = "postgresql://@/hass"; auth_header = { }; #binary_sensor: # - platform: template # sensors: # rain_today: # friendly_name: Rain today # value_template: "{{ state_attr('weather.knyc_daynight', 'forecast')[1]['precipitation_probability']|float > 0 or state_attr('weather.knyc_daynight', 'forecast')[2]['precipitation_probability']|float > 0 }}" # #value_template: "{{ states('sensor.dark_sky_precip_probability_0d')|float > 5 }}" #switch: # - platform: template # switches: # heater: # value_template: "{{ state_attr('climate.sensi', 'temperature')|float > 80 }}" # availability_template: "{{ not is_state('climate.sensi', 'unavailable') }}" # turn_on: # service: climate.set_temperature # data: # entity_id: climate.sensi # temperature: 85 # turn_off: # service: climate.set_temperature # data: # entity_id: climate.sensi # temperature: 60 # cooler: # value_template: "{{ is_state('climate.sensi', 'cool') and state_attr('climate.sensi', 'temperature')|float < 72 }}" # availability_template: "{{ not is_state('climate.sensi', 'unavailable') }}" # turn_on: # - service: climate.set_hvac_mode # data: # entity_id: climate.sensi # hvac_mode: cool # - service: climate.set_temperature # data: # entity_id: climate.sensi # temperature: 70 # turn_off: # - service: climate.set_temperature # data: # entity_id: climate.sensi # temperature: 75 # - service: climate.turn_off # data: # entity_id: climate.sensi input_number.target_panel_brightness = { name = "Target panel brightness"; min = 0; max = 100; icon = "mdi:lightbulb-on-outline"; }; climate = [ { platform = "generic_thermostat"; name = "Bedroom"; heater = "switch.thermostat_heat"; target_sensor = "sensor.bedroom_temperature"; min_cycle_duration.minutes = 2; # Remember HVAC mode and periodically explicitly sync heater. # initial_hvac_mode = "off" keep_alive.minutes = 5; } ]; # - id: b43fdad4b27046b0a8688742daf89ba2 # alias: Departure # description: '' # trigger: # - platform: zone # entity_id: person.joe # zone: zone.home # event: leave # condition: [] # action: # - service: light.turn_off # data: {} # target: # entity_id: # - light.panel_light # mode: single # - alias: Sound machine switch # description: '' # trigger: # - device_id: 6abc7b035b2e42e6a620eb66ccc8de74 # domain: binary_sensor # entity_id: binary_sensor.button_2 # platform: device # type: turned_on # condition: [] # action: # - entity_id: switch.pinky # service: switch.toggle # id: f6405dfa252e4121b51ab015c7d5a136 # - alias: Light relay # description: '' # trigger: # - above: '.15' # entity_id: sensor.photoresistor # platform: numeric_state # condition: # - after: sunset # condition: sun # action: # - entity_id: switch.wakko # service: switch.turn_on # id: ce759a29adb74a6090dcb0d1c1ef704c # - alias: Homebound # description: '' # trigger: # - event_data: # actionName: Homebound # event_type: ios.action_fired # platform: event # condition: [] # action: # - data: # temperature: 72 # entity_id: climate.bedroom # service: climate.set_temperature # - data: # hvac_mode: heat # entity_id: climate.bedroom # service: climate.set_hvac_mode # id: f039799e56cc48509e2855974c69a3e5 # - alias: Bedroom thermostat off # description: '' # trigger: # - entity_id: climate.bedroom # platform: state # to: 'off' # condition: [] # action: # - entity_id: light.d5_led # service: light.turn_on # id: 3616bf440fe84c76bbfed07fa336272b # - alias: Bedroom thermostat on # description: '' # trigger: # - entity_id: climate.bedroom # from: 'off' # platform: state # condition: [] # action: # - entity_id: light.d5_led # service: light.turn_off # mode: single # id: 117ab3e24e624680aa265efa8b0986d2 # - alias: Rain light on # description: '' # trigger: # - entity_id: binary_sensor.rain_today # platform: state # to: 'on' # condition: [] # action: # - data: {} # entity_id: light.nodemcu_led # service: light.turn_on # id: 68401f3337c546e38521d77da2bd4040 # - alias: Rain light off # description: '' # trigger: # - entity_id: binary_sensor.rain_today # platform: state # to: 'off' # condition: [] # action: # - data: {} # entity_id: light.nodemcu_led # service: light.turn_off # id: ba3266d2ebfe4b4cabbc5ef36a63412d # - alias: Windows webhook # description: '' # trigger: # - platform: webhook # webhook_id: -5ykPDB93EXDiNKFAnaJ6_aZi # condition: [] # action: # - choose: # - conditions: # - condition: template # value_template: '{{ trigger.data.action == ''on'' }}' # sequence: # - repeat: # while: # - condition: template # value_template: '{{ state_attr(''media_player.55_tcl_roku_tv'', ''app_id'') # != ''tvinput.hdmi1'' }} # ' # - condition: template # value_template: '{{ repeat.index < 2 }}' # sequence: # - alias: Turn on TV to speed up switching source # service: media_player.turn_on # target: # entity_id: media_player.55_tcl_roku_tv # continue_on_error: true # - service: media_player.select_source # data: # source: tvinput.hdmi1 # target: # entity_id: media_player.55_tcl_roku_tv # continue_on_error: true # - wait_template: '{{ state_attr(''media_player.55_tcl_roku_tv'', ''app_id'') # == ''tvinput.hdmi1'' }}' # timeout: 10 # alias: Retry while (checks before) TV input not on Windows # - conditions: # - condition: template # value_template: '{{ trigger.data.action == ''off'' }}' # sequence: # - service: media_player.turn_off # data: {} # target: # entity_id: media_player.55_tcl_roku_tv # mode: restart # id: c759fbc37e4f46e0a834d95f40a78427 # - id: c51d746e68384b7586a83236ff46b0f6 # alias: Grow light update # description: '' # trigger: # - platform: state # entity_id: # - input_number.target_panel_brightness # condition: [] # action: # - if: # - condition: template # value_template: '{{ states(''input_number.target_panel_brightness'') == ''0.0'' # }}' # then: # - service: light.turn_off # data: {} # target: # entity_id: light.grow_light # - service: switch.turn_off # data: {} # target: # entity_id: switch.dot # else: # - service: light.turn_on # data: # brightness_pct: '{{ states(''input_number.target_panel_brightness'') }}' # target: # entity_id: light.grow_light # - service: switch.turn_on # data: {} # target: # entity_id: switch.dot # mode: single # - alias: Barrier webhook # description: '' # trigger: # - platform: webhook # webhook_id: barrier-webhook-GyQhMV4gPNhSfYbYFx4DNVUc # condition: [] # action: # - choose: # - conditions: # - condition: template # value_template: '{{ trigger.data.action == ''on'' }}' # sequence: # - repeat: # while: # - condition: template # value_template: '{{ state_attr(''media_player.55_tcl_roku_tv'', ''app_id'') # != ''tvinput.hdmi1'' }} # ' # - condition: template # value_template: '{{ repeat.index < 2 }}' # sequence: # - alias: Turn on TV to speed up switching source # service: media_player.turn_on # target: # entity_id: media_player.55_tcl_roku_tv # continue_on_error: true # - service: media_player.select_source # data: # source: tvinput.hdmi1 # target: # entity_id: media_player.55_tcl_roku_tv # continue_on_error: true # - wait_template: '{{ state_attr(''media_player.55_tcl_roku_tv'', ''app_id'') # == ''tvinput.hdmi1'' }}' # timeout: 10 # alias: Retry while (checks before) TV input not on Windows # - conditions: # - condition: template # value_template: '{{ trigger.data.action == ''hdmi2'' }}' # sequence: # - repeat: # while: # - condition: template # value_template: '{{ state_attr(''media_player.55_tcl_roku_tv'', ''app_id'') # != ''tvinput.hdmi2'' }} # ' # - condition: template # value_template: '{{ repeat.index < 2 }}' # sequence: # - alias: Turn on TV to speed up switching source # service: media_player.turn_on # target: # entity_id: media_player.55_tcl_roku_tv # continue_on_error: true # - service: media_player.select_source # data: # source: tvinput.hdmi2 # target: # entity_id: media_player.55_tcl_roku_tv # continue_on_error: true # - wait_template: '{{ state_attr(''media_player.55_tcl_roku_tv'', ''app_id'') # == ''tvinput.hdmi2'' }}' # timeout: 10 # alias: Retry while (checks before) TV input not on HDMI 2 # - conditions: # - condition: template # value_template: '{{ trigger.data.action == ''off'' }}' # sequence: # - service: media_player.turn_off # data: {} # target: # entity_id: media_player.55_tcl_roku_tv # mode: restart # id: a86bdf5640e34e5d9b4864f7b58d4bae # - id: '1675140424295' # alias: Bedroom toggle grow light # description: '' # trigger: # - platform: state # entity_id: # - binary_sensor.bedroom_button_1 # to: 'on' # condition: [] # action: # - service: switch.toggle # data: {} # target: # entity_id: switch.dot # mode: single # - id: '1675140647798' # alias: Panel light reinitialize # description: '' # trigger: # - platform: state # entity_id: # - light.panel_light # to: unavailable # condition: [] # action: # - repeat: # while: # - condition: state # entity_id: light.panel_light # state: unavailable # sequence: # - service: switch.turn_off # data: {} # target: # entity_id: switch.wakko # - delay: # hours: 0 # minutes: 0 # seconds: 10 # milliseconds: 0 # - service: switch.turn_on # data: {} # target: # entity_id: switch.wakko # mode: single # - id: '1678928586230' # alias: Spotify WIP # description: '' # trigger: # - platform: time # at: 08:55:00 # condition: # - condition: zone # entity_id: person.joe # zone: zone.home # action: # - repeat: # until: # - condition: or # conditions: # - condition: state # entity_id: media_player.55_tcl_roku_tv # attribute: source # state: Spotify Music # - condition: template # value_template: '{{ repeat.index >= 5 }}' # sequence: # - alias: Turn on TV to speed up switching source # service: media_player.turn_on # target: # entity_id: media_player.55_tcl_roku_tv # continue_on_error: true # - service: media_player.select_source # data: # source: Spotify Music # target: # entity_id: media_player.55_tcl_roku_tv # continue_on_error: true # - delay: # hours: 0 # minutes: 0 # seconds: 15 # milliseconds: 0 # - wait_template: '{{ state_attr(''media_player.spotify_yoyoyojomo'', ''source_list'') # | default([], true) | contains(''55" TCL Roku TV'') }}' # continue_on_timeout: true # timeout: '15:00' # - service: media_player.select_source # data: # source: 55" TCL Roku TV # target: # entity_id: media_player.spotify_yoyoyojomo # enabled: false # - service: media_player.play_media # data: # media_content_id: spotify:playlist:37i9dQZF1E355kum8XgZAW # media_content_type: playlist # target: # entity_id: media_player.spotify_yoyoyojomo # enabled: false # - stop: Power cycle TV for SPDIF glitch? # enabled: false # - repeat: # until: # - condition: or # conditions: # - condition: device # device_id: 41de89da6b761d29f8542ab039951a1b # domain: media_player # entity_id: media_player.spotify_yoyoyojomo # type: is_playing # - condition: template # value_template: '{{ repeat.index >= 5 }}' # sequence: # - service: media_player.select_source # data: # source: 55" TCL Roku TV # target: # entity_id: media_player.spotify_yoyoyojomo # - service: media_player.play_media # data: # media_content_id: spotify:playlist:37i9dQZEVXbgyUT3Ewvdog # media_content_type: playlist # target: # entity_id: media_player.spotify_yoyoyojomo # continue_on_error: true # - delay: # hours: 0 # minutes: 0 # seconds: 20 # milliseconds: 0 # - stop: 'Release Radar (updates Fridays): 37i9dQZEVXbgyUT3Ewvdog' # mode: single automation = [ { alias = "Panel update"; trigger = [ { platform = "state"; entity_id = "input_number.target_panel_brightness"; } # { # platform = "zone"; # entity_id = "person.joe"; # zone: "zone.home"; # event: "enter"; # } ]; # condition = [ # { # condition = "zone"; # entity_id = "person.joe"; # zone: "zone.home"; # } # ]; action = [ { service = "light.turn_on"; target.entity_id = "light.panel_light"; data.brightness_pct = "{{ states('input_number.target_panel_brightness')|float * 0.9 + 10 }}"; } ]; } { alias = "Sunrise"; trigger = { platform = "sun"; event = "sunrise"; }; action = [ { service = "input_number.set_value"; target.entity_id = "input_number.target_panel_brightness"; data.value = 15; } # { # service = "switch.turn_on"; # target.entity_id = "switch.dot"; # } ]; } { alias = "Sunrise after"; trigger = { platform = "sun"; event = "sunrise"; offset = "1:00"; }; action = [ { service = "input_number.set_value"; target.entity_id = "input_number.target_panel_brightness"; data.value = 100; } ]; } { alias = "Sunset before"; trigger = { platform = "sun"; event = "sunset"; offset = "-1:00"; }; action = [ { service = "input_number.set_value"; target.entity_id = "input_number.target_panel_brightness"; data.value = 15; } ]; } { alias = "Sunset"; trigger = { platform = "sun"; event = "sunset"; }; action = [ { service = "input_number.set_value"; target.entity_id = "input_number.target_panel_brightness"; data.value = 0; } # { # service = "switch.turn_off"; # target.entity_id = "switch.dot"; # } ]; } { alias = "Bedroom toggle panel light"; trigger = { platform = "state"; entity_id = "binary_sensor.bedroom_button_2"; to = "on"; }; action = [ { service = "light.toggle"; target.entity_id = "light.panel_light"; } ]; } { alias = "Thermostat morning"; trigger = { platform = "time"; at = "06:00"; }; action = [ { service = "climate.set_temperature"; target.entity_id = "climate.bedroom"; data.temperature = 71; } ]; } { alias = "Thermostat night"; trigger = { platform = "time"; at = "23:00"; }; action = [ { service = "climate.set_temperature"; target.entity_id = "climate.bedroom"; data.temperature = 69; } ]; } ]; }; }; services.nginx.virtualHosts."ha.elmo.mou.fo" = { enableACME = true; forceSSL = true; locations."/" = { proxyPass = "http://[::1]:8123"; proxyWebsockets = true; extraConfig = '' # This is frequently used in examples but without clear explanation. It # might help with WebSockets. proxy_buffering off; # oauth2_proxy NixOS module sets some non-standard headers, but we need # the preferred_username claim. auth_request_set $preferred_username $upstream_http_x_auth_request_preferred_username; proxy_set_header X-Forwarded-Preferred-Username $preferred_username; ''; }; # Duplicate relevant parts of root route to skip oauth2-proxy module magic. locations."/api/" = { proxyPass = "http://[::1]:8123"; proxyWebsockets = true; extraConfig = '' proxy_buffering off; ''; }; # Disable service worker caching that works improperly with reverse proxy. # https://github.com/home-assistant/frontend/issues/14836 # https://community.home-assistant.io/t/disabling-service-worker-reverse-proxy-auth-causes-issues/167082 locations."/service_worker.js" = { return = ''410 "Service worker disabled: https://github.com/home-assistant/frontend/issues/14836"''; }; }; services.oauth2_proxy.nginx.virtualHosts = [ "ha.elmo.mou.fo" ]; }