├── README.md ├── apps └── check-config │ └── checkconfig.py ├── confignotify.py ├── info.md ├── lovelace-example.png ├── picture-elements-example.jpeg ├── result-error.png └── telegram.png /README.md: -------------------------------------------------------------------------------- 1 | # Check Config 2 | [![hacs_badge](https://img.shields.io/badge/HACS-Default-orange.svg?style=for-the-badge)](https://github.com/custom-components/hacs) 3 | Buy Me A Coffee 9 | 10 | ### Requires AppDaemon 11 | 12 | **As of version 2.0, I am only testing against AppDaemon 4.x. The app will likely continue to work with 13 | AppDaemon 3.x, but if you have an issue and are using AppDaemon 3.x, I will not be able to provide support.** 14 | 15 | _Allows you to check your Home Assistant configuration from within Lovelace, or 16 | automatically using when configuration.yaml is updated. 17 | Outputs the results to a sensor and can automatically restart HASS if 18 | configuration is valid._ 19 | 20 | ## Installation 21 | 22 | This app is best installed using 23 | [HACS](https://github.com/custom-components/hacs), so that you can easily track 24 | and download updates. 25 | 26 | Alternatively, you can download the `check-config` directory from inside the `apps` directory here to your 27 | local `apps` directory, then add the configuration to enable the `checkconfig` 28 | module. 29 | 30 | ## How it works 31 | 32 | The app will auto-generate an entity called `sensor.config_result`. It has an 33 | initial value of `-` until a configuration check is actually run. After running 34 | a check, it will change to `valid` or `invalid`. If it is invalid, the `detail` 35 | attribute on the sensor will include additional data about the specific failure. 36 | If it is valid, Home Assistant can restart automatically, or you can configure 37 | it to not restart until you do so manually. 38 | 39 | ## App configuration 40 | 41 | Add the following to your `apps.yaml` file: 42 | ```yaml 43 | check_config: 44 | module: checkconfig 45 | class: CheckConfig 46 | ``` 47 | 48 | key | optional | type | default | description 49 | -- | -- | -- | -- | -- 50 | `module` | False | string | | `checkconfig` 51 | `class` | False | string | | `CheckConfig` 52 | `restart` | True | boolean | True | By default, Home Assistant will be restarted if the configuration is valid. Setting this to false will allow you to restart at the time of your choosing. 53 | `folder_watcher` | True | boolean | False | If you are using the `folder_watcher` component (see "Using with Folder Watcher" below), the app can automatically monitor for changes to `configuration.yaml` and run the check any time the file is changed. 54 | 55 | ## Using With Lovelace 56 | 57 | You will need to create an entity called `script.check_config`, but this script 58 | can be blank and won't actually perform any steps. It simply allows us to call 59 | the config check from Lovelace. Copying and pasting the following into your 60 | `scripts.yaml` file will add the proper script. 61 | 62 | ```yaml 63 | check_config: 64 | sequence: [] 65 | alias: Check Configuration 66 | ``` 67 | 68 | The following is a basic example of a Lovelace example to run the check and show 69 | the results. 70 | 71 | ```yaml 72 | type: entities 73 | title: Check Configuration 74 | show_header_toggle: false 75 | entities: 76 | - script.check_config 77 | - sensor.config_result 78 | ``` 79 | 80 | ## Using with Folder Watcher 81 | _Note: Folder Watcher support currently does not support split configuration files, nor will it monitor changes to secrets.yaml. Support for secrets.yaml and split configurations is planned for the next release. In addition, when using the Folder Watcher configuration, the Lovelace script configuration above will no longer work (but the sensor will still update for you to optionally display in Lovelace)._ 82 | 83 | As of version 0.3.0, the [Folder 84 | Watcher](https://www.home-assistant.io/components/folder_watcher/) component can 85 | be integrated into this app. The app will then automatically trigger a 86 | configuration check when your `configuration.yaml` file is updated. You can then 87 | either have Home Assistant restart automatically, or continue to utilize the 88 | sensor updating as you see fit. For example, I have Telegram set up on the 89 | computer I perform my configuration updates on. So, I now receive a notification 90 | from Telegram on my desktop seconds after saving a change to 91 | `configuration.yaml`. If the config is invalid, the specific issue is called out 92 | in the notification. Otherwise, if it's valid, I have an inline keyboard button 93 | that will restart Home Assistant when pressed. 94 | 95 | The example code for this notification setup is included in `confignotify.py` in 96 | this repository. Since everyone's notification setup is different, I am not 97 | attempting to include it in the main app at this time, but you are free to use 98 | what I have created as a starting point. 99 | 100 | 102 | 103 | Here is the YAML I'm using to include `folder_watcher` in my setup to monitor 104 | `configuration.yaml`: 105 | ```yaml 106 | folder_watcher: 107 | - folder: /config 108 | patterns: 109 | - '*.yaml' 110 | ``` 111 | 112 | You also need your `config` directory under `whitelist_external_dirs` in the 113 | `homeassistant` section of `configuration.yaml`: 114 | ```yaml 115 | homeassistant: 116 | whitelist_external_dirs: 117 | - /config 118 | ``` 119 | 120 | ## Example Screenshots 121 | 122 | 124 | 125 | ## Advanced Lovelace Config Examples 126 | 127 | As a picture elements card with options to reload elements of your 128 | configuration, or a full restart: 129 | ```yaml 130 | type: custom:vertical-stack-in-card 131 | cards: 132 | - type: picture-elements 133 | image: /local/images/BG_blank_slim.png 134 | elements: 135 | 136 | - type: image 137 | image: /local/images/icons8-administrative-tools-80.png 138 | tap_action: 139 | action: call-service 140 | service: script.check_config 141 | style: 142 | top: 50% 143 | left: 10% 144 | width: 10% 145 | 146 | - type: image 147 | image: /local/images/icons8-restart-80.png 148 | tap_action: 149 | action: call-service 150 | service: homeassistant.restart 151 | style: 152 | top: 50% 153 | left: 26% 154 | width: 10% 155 | 156 | - type: image 157 | image: /local/images/icons8-source-code-80.png 158 | tap_action: 159 | action: call-service 160 | service: homeassistant.reload_core_config 161 | style: 162 | top: 50% 163 | left: 42% 164 | width: 10% 165 | 166 | - type: image 167 | image: /local/images/icons8-variation-80.png 168 | tap_action: 169 | action: call-service 170 | service: group.reload 171 | style: 172 | top: 50% 173 | left: 58% 174 | width: 10% 175 | 176 | - type: image 177 | image: /local/images/icons8-automation-80.png 178 | tap_action: 179 | action: call-service 180 | service: automation.reload 181 | style: 182 | top: 50% 183 | left: 74% 184 | width: 10% 185 | 186 | - type: image 187 | image: /local/images/icons8-note-80.png 188 | tap_action: 189 | action: call-service 190 | service: script.reload 191 | style: 192 | top: 50% 193 | left: 90% 194 | width: 10% 195 | 196 | 197 | - type: entities 198 | show_header_toggle: false 199 | entities: 200 | - sensor.config_result 201 | ``` 202 | 203 | 204 | ## Issues/Feature Requests 205 | 206 | Please log any issues or feature requests in this GitHub repository for me to review. 207 | -------------------------------------------------------------------------------- /apps/check-config/checkconfig.py: -------------------------------------------------------------------------------- 1 | import appdaemon.plugins.hass.hassapi as hass 2 | import requests 3 | import json 4 | 5 | 6 | # Check Home Assistant Configuration 7 | 8 | class CheckConfig(hass.Hass): 9 | 10 | def initialize(self): 11 | # is auto-restart set? 12 | if "restart" in self.args and self.args["restart"] == False: 13 | self.restart = False 14 | else: 15 | self.restart = True 16 | 17 | # is folder watcher set? 18 | if "folder_watcher" in self.args and self.args["folder_watcher"] == True: 19 | self.folder_watcher = True 20 | self.throttle_timer = None 21 | else: 22 | self.folder_watcher = False 23 | 24 | # create a sensor to track check result 25 | self.set_state("sensor.config_result", state="-", attributes = {"friendly_name": "Config Result", "detail": None}) 26 | 27 | # get HASS URL 28 | self.apiurl = "{}/api/config/core/check_config".format(self.config["plugins"]["HASS"]["ha_url"]) 29 | 30 | # token or key to authenticate 31 | if "token" in self.config["plugins"]["HASS"]: 32 | self.auth = "token" 33 | if self.folder_watcher == False: 34 | self.listen_state(self.check_config, "script.check_config", attribute="last_triggered") 35 | else: 36 | self.listen_event(self.config_throttle, "folder_watcher", file="configuration.yaml") 37 | elif "ha_key" in self.config["plugins"]["HASS"]: 38 | self.auth = "key" 39 | if self.folder_watcher == False: 40 | self.listen_state(self.check_config, "script.check_config", attribute="last_triggered") 41 | else: 42 | self.listen_event(self.config_throttle, "folder_watcher", file="configuration.yaml") 43 | else: 44 | self.log("AppDaemon config must use a token or key to authenticate with HASS") 45 | self.set_state("sensor.config_result", state="ERROR", attributes = {"friendly_name": "Config Result", "detail": "AppDaemon config must use a token or key to authenticate with HASS"}) 46 | 47 | def check_config(self, entity, attribute, old, new, kwargs): 48 | # reset sensor while check is in progress 49 | self.set_state("sensor.config_result", state="checking", attributes = {"detail": None}) 50 | # set headers for auth 51 | if self.auth == "token": 52 | self.headers = {'Authorization': "Bearer {}".format(self.config["plugins"]["HASS"]["token"])} 53 | else: #key 54 | self.headers = {'x-ha-access': self.config["plugins"]["HASS"]["ha_key"]} 55 | # make the request 56 | r = requests.post(self.apiurl, headers=self.headers) 57 | # evaluate result 58 | if json.loads(r.text)['result'] == "valid": 59 | self.set_state("sensor.config_result", state="valid", attributes = {"detail": None}) 60 | # restart if auto-restart is on 61 | if self.restart == True: 62 | self.call_service("homeassistant/restart") 63 | else: 64 | self.set_state("sensor.config_result", state="invalid", attributes = {"detail": json.loads(r.text)['errors']}) 65 | 66 | def config_throttle(self, event_name, data, kwargs): 67 | #throttle function to ensure that we don't call check multiple times 68 | self.cancel_timer(self.throttle_timer) 69 | self.throttle_timer = self.run_in(self.auto_check_config, 3) 70 | 71 | def auto_check_config(self, kwargs): 72 | # reset sensor while check is in progress 73 | self.set_state("sensor.config_result", state="checking", attributes = {"detail": None}) 74 | # set headers for auth 75 | if self.auth == "token": 76 | self.headers = {'Authorization': "Bearer {}".format(self.config["plugins"]["HASS"]["token"])} 77 | else: #key 78 | self.headers = {'x-ha-access': self.config["plugins"]["HASS"]["ha_key"]} 79 | # make the request 80 | r = requests.post(self.apiurl, headers=self.headers) 81 | # evaluate result 82 | if json.loads(r.text)['result'] == "valid": 83 | self.set_state("sensor.config_result", state="valid", attributes = {"detail": None}) 84 | # restart if auto-restart is on 85 | if self.restart == True: 86 | self.call_service("homeassistant/restart") 87 | else: 88 | self.set_state("sensor.config_result", state="invalid", attributes = {"detail": json.loads(r.text)['errors']}) -------------------------------------------------------------------------------- /confignotify.py: -------------------------------------------------------------------------------- 1 | import appdaemon.plugins.hass.hassapi as hass 2 | import globals 3 | 4 | # Notify after CheckConfig runs 5 | 6 | class ConfigNotify(hass.Hass): 7 | 8 | def initialize(self): 9 | # watch for valid config 10 | self.listen_state(self.valid_config, "sensor.config_result") 11 | 12 | # listen for telegram restart callback. only need to initialize this 13 | # listener once 14 | self.listen_event(self.hass_restart, "telegram_callback", data="/restart") 15 | 16 | def valid_config(self, entity, attribute, old, new, kwargs): 17 | if new == "valid": 18 | keyboard = [[("Restart Now", "/restart")]] 19 | self.call_service("telegram_bot/send_message", message = "Config is valid", target = globals.alex_low, inline_keyboard=keyboard) 20 | elif new == "invalid": 21 | error = self.entities.sensor.config_result.attributes.detail 22 | self.call_service("telegram_bot/send_message", message = "Config is invalid", target = globals.alex_low) 23 | self.call_service("telegram_bot/send_message", message = error, target = globals.alex_low) 24 | 25 | def hass_restart(self, event_name, data, kwargs): 26 | self.call_service("telegram_bot/send_message", message = "Home Assistant is restarting", target = globals.alex_low) 27 | self.call_service("homeassistant/restart") -------------------------------------------------------------------------------- /info.md: -------------------------------------------------------------------------------- 1 | Buy Me A Coffee 2 | 3 | **As of version 2.0, I am only testing against AppDaemon 4.x. The app will likely continue to work with 4 | AppDaemon 3.x, but if you have an issue and are using AppDaemon 3.x, I will not be able to provide support.** 5 | 6 | *In addition to configuring this app in `apps.yaml`, you will also need to add a 7 | few lines to `scripts.yaml` and your Lovelace configuration. Or, you will need 8 | to set up the `folder_watcher` component (see the readme for more detail if you 9 | choose that route).* 10 | 11 | ## App Configuration 12 | 13 | Add the following to your `apps.yaml` file: 14 | ```yaml 15 | check_config: 16 | module: checkconfig 17 | class: CheckConfig 18 | ``` 19 | 20 | key | optional | type | default | description 21 | -- | -- | -- | -- | -- 22 | `module` | False | string | | `checkconfig` 23 | `class` | False | string | | `CheckConfig` 24 | `restart` | True | boolean | True | By default, Home Assistant will be restarted if the configuration is valid. Setting this to false will allow you to restart at the time of your choosing. 25 | `folder_watcher` | True | boolean | False | If you are using the `folder_watcher` component (see "Using with Folder Watcher" in the README), the app can automatically monitor for changes to `configuration.yaml` and run the check any time the file is changed. 26 | 27 | ## `Scripts.yaml` Configuration 28 | 29 | ```yaml 30 | check_config: 31 | sequence: [] 32 | alias: Check Configuration 33 | ``` 34 | 35 | ## Sample Lovelace Configuration 36 | 37 | ```yaml 38 | type: entities 39 | title: Check Configuration 40 | show_header_toggle: false 41 | entities: 42 | - script.check_config 43 | - sensor.config_result 44 | ``` -------------------------------------------------------------------------------- /lovelace-example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apop880/config-check/e653011798d8032c47443705719f81e93e09727a/lovelace-example.png -------------------------------------------------------------------------------- /picture-elements-example.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apop880/config-check/e653011798d8032c47443705719f81e93e09727a/picture-elements-example.jpeg -------------------------------------------------------------------------------- /result-error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apop880/config-check/e653011798d8032c47443705719f81e93e09727a/result-error.png -------------------------------------------------------------------------------- /telegram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apop880/config-check/e653011798d8032c47443705719f81e93e09727a/telegram.png --------------------------------------------------------------------------------