-
Notifications
You must be signed in to change notification settings - Fork 444
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Crash HA on enabling integration #1508
Comments
Hello, I have this same issue! After I install the add on via HACS I reboot HA and then I am trying to configure it in Add integration the hole system freezes. |
Same here. Addon v1.3.9 does not work at all. Cards are visualized broken. |
Same here. |
Seems connected to the use of the There is also the recommended use of hass.async_add_executor_job() by the Home Assistant developers documentation. |
I tried to fix this last night with GPT 😄 but I give up. Since I don't want to recover backups on every try. |
What approach did you try? |
Also having this issue. Likely related to the HA Core update earlier this week? |
Same problem for me |
same problem, anyone solved it? |
Same issue here. Uninstalled and installed manually, crashes when I enable the community cards via github |
you dont need to restore backsups just use the home assistant dev enviroment or if you really want to dev on live then just Go to the cmd shell (connect a monitor and keyboard). Integrations are stored here: Using vi you can delete sections out of this file which removes the integration. (should be at the bottom if its the last integration you installed, make sure to remove the , from the close bracket of the entry before it since that becomes the last entry Some basics VI editor commands |
i tried this however this doesnt fix it, even with it set up for async jobs it crashes home assistant, im not entirely sure whats causing the crash this is the approach i tried to get it working if anyone has any inputs """Base UI Lovelace Minimalist class."""
from __future__ import annotations
import asyncio
from dataclasses import asdict, dataclass, field
import logging
import os
import pathlib
import shutil
from typing import Any, Awaitable, Callable
from aiogithubapi import (
GitHubAPI,
GitHubAuthenticationException,
GitHubException,
GitHubNotModifiedException,
GitHubRatelimitException,
)
from homeassistant.components.frontend import add_extra_js_url, async_remove_panel
from homeassistant.components.lovelace import _register_panel
from homeassistant.components.lovelace.dashboard import LovelaceYAML
from homeassistant.config_entries import ConfigEntry, ConfigEntryState
from homeassistant.core import HomeAssistant
from homeassistant.loader import Integration
from .const import (
COMMUNITY_CARDS_FOLDER,
DEFAULT_COMMUNITY_CARDS_ENABLED,
DEFAULT_INCLUDE_OTHER_CARDS,
DEFAULT_LANGUAGE,
DEFAULT_SIDEPANEL_ENABLED,
DEFAULT_SIDEPANEL_ICON,
DEFAULT_SIDEPANEL_TITLE,
DEFAULT_THEME,
DEFAULT_THEME_PATH,
DOMAIN,
GITHUB_REPO,
LANGUAGES,
TV,
)
from .enums import ConfigurationType, UlmDisabledReason
from .utils.decode import decode_content
_LOGGER: logging.Logger = logging.getLogger(__name__)
@dataclass
class UlmSystem:
"""ULM System info."""
disabled_reason: UlmDisabledReason | None = None
running: bool = False
@property
def disabled(self) -> bool:
"""Return if ULM is disabled."""
return self.disabled_reason is not None
@dataclass
class UlmConfiguration:
"""UlmConfiguration class."""
config: dict[str, Any] = field(default_factory=dict)
config_entry: ConfigEntry | None = None
config_type: ConfigurationType | None = None
sidepanel_enabled: bool = DEFAULT_SIDEPANEL_ENABLED
sidepanel_icon: str = DEFAULT_SIDEPANEL_ICON
sidepanel_title: str = DEFAULT_SIDEPANEL_TITLE
adaptive_ui_enabled: bool = DEFAULT_SIDEPANEL_ENABLED
adaptive_ui_icon: str = DEFAULT_SIDEPANEL_ICON
adaptive_ui_title: str = DEFAULT_SIDEPANEL_TITLE
theme_path: str = DEFAULT_THEME_PATH
theme: str = DEFAULT_THEME
plugin_path: str = "www/community/"
include_other_cards: bool = DEFAULT_INCLUDE_OTHER_CARDS
language: str = DEFAULT_LANGUAGE
community_cards_enabled = bool = DEFAULT_COMMUNITY_CARDS_ENABLED
community_cards: list = field(default_factory=list)
all_community_cards: list = field(default_factory=list)
token: str = None
def to_dict(self) -> dict:
"""Return Dict."""
return self.__dict__
def to_json(self) -> str:
"""Return a json string."""
return asdict(self)
def update_from_dict(self, data: dict) -> None:
"""Set attributes from dicts."""
if not isinstance(data, dict):
raise Exception("Configuration is not valid.")
for key in data:
self.__setattr__(key, data[key])
class UlmBase:
"""Base UI Lovelace Minimalist."""
integration: Integration | None = None
configuration = UlmConfiguration()
hass: HomeAssistant | None = None
log: logging.Logger = _LOGGER
githubapi: GitHubAPI | None = None
system = UlmSystem()
version: str | None = None
@property
def integration_dir(self) -> pathlib.Path:
"""Return the ULM integration dir."""
return self.integration.file_path
@property
def templates_dir(self) -> pathlib.Path:
"""Return the Button Cards Template dir."""
return pathlib.Path(f"{self.integration_dir}/__ui_minimalist__/ulm_templates")
@property
def community_cards_dir(self) -> pathlib.Path:
"""Return the Community cards dir inside Template dir."""
return pathlib.Path(f"{self.templates_dir}/community_cards")
def disable_ulm(self, reason: UlmDisabledReason) -> None:
"""Disable Ulm."""
if self.system.disabled_reason == reason:
return
self.system.disabled_reason = reason
if reason == UlmDisabledReason.INVALID_TOKEN:
self.configuration.config_entry.state = ConfigEntryState.SETUP_ERROR
self.configuration.config_entry.reason = "Authentication Failed"
self.hass.add_job(
self.configuration.config_entry.async_start_reauth, self.hass
)
def enable_ulm(self) -> None:
"""Enable Ulm."""
if self.system.disabled_reason is not None:
self.system.disabled_reason = None
self.log.info("ULM is enabled")
async def async_save_file(self, file_path: str, content: Any) -> bool:
"""Save a file."""
self.log.debug("Saving file: %s" % file_path)
def _write_file():
os.makedirs(os.path.dirname(file_path), exist_ok=True)
with open(
file_path,
mode="w" if isinstance(content, str) else "wb",
encoding="utf-8" if isinstance(content, str) else None,
errors="ignore" if isinstance(content, str) else None,
) as file_handler:
file_handler.write(content)
try:
await self.hass.async_add_executor_job(_write_file)
except BaseException as error: # lgtm [py/catch-base-exception] pylint: disable=broad-except
self.log.error(f"Could not write data to {file_path} - {error}")
return False
return os.path.exists(file_path)
async def async_github_get_file(self, filename: str) -> list:
"""Get the content of a file."""
self.log.debug("Fetching github file: %s" % filename)
response = await self.async_github_api_method(
method=self.githubapi.repos.contents.get,
repository=GITHUB_REPO,
path=filename,
)
if response is None:
return []
return decode_content(response.data.content)
async def async_github_get_tree(self, path: str) -> list:
"""Get the content of a directory."""
self.log.debug("Fetching github tree: %s" % path)
response = await self.async_github_api_method(
method=self.githubapi.repos.contents.get, repository=GITHUB_REPO, path=path
)
if response is None:
return []
return response.data
async def async_github_api_method(
self,
method: Callable[[], Awaitable[TV]],
*args,
raise_exception: bool = True,
**kwargs,
) -> TV | None:
"""Call a GitHub API method."""
_exception = None
try:
return await method(*args, **kwargs)
except GitHubAuthenticationException as exception:
self.disable_ulm(UlmDisabledReason.INVALID_TOKEN)
_exception = exception
except GitHubRatelimitException as exception:
_exception = exception
except GitHubNotModifiedException as exception:
raise exception
except GitHubException as exception:
_exception = exception
except BaseException as exception: # lgtm [py/catch-base-exception] pylint: disable=broad-except
self.log.exception(exception)
_exception = exception
if raise_exception and _exception is not None:
raise Exception(_exception)
return None
async def fetch_cards(self) -> None:
"""Fetch list of cards."""
response = await self.async_github_api_method(
method=self.githubapi.repos.contents.get,
repository=GITHUB_REPO,
path=COMMUNITY_CARDS_FOLDER,
)
if response is None:
return []
self.configuration.all_community_cards = [
c.name for c in response.data if c.type == "dir"
]
async def configure_community_cards(self) -> None:
"""Configure selected community cards."""
self.log.info("Configuring selected community cards")
language = LANGUAGES[self.configuration.language]
os.makedirs(self.community_cards_dir, exist_ok=True)
if (
not self.configuration.community_cards_enabled
or self.configuration.community_cards == []
):
await self.hass.async_add_executor_job(shutil.rmtree, f"{self.community_cards_dir}/", True)
elif self.configuration.community_cards_enabled:
existing_cards = [
f.path for f in os.scandir(self.community_cards_dir) if f.is_dir()
]
for e in existing_cards:
card_dir = os.path.basename(e)
# Delete unselected folders
if card_dir not in self.configuration.community_cards:
self.log.debug(
f"Deleting community card folder {card_dir}, not selected anymore."
)
await self.hass.async_add_executor_job(shutil.rmtree, e, True)
if card_dir not in self.configuration.all_community_cards:
self.log.debug(
f"Deleting community card folder {card_dir}, that is not existing anymore on Github."
)
await self.hass.async_add_executor_job(shutil.rmtree, e, True)
for card in self.configuration.community_cards:
if card not in self.configuration.all_community_cards:
self.configuration.community_cards.remove(card)
else:
card_files = await self.async_github_get_tree(
path=f"{COMMUNITY_CARDS_FOLDER}/{card}"
)
for f in card_files:
if f.type == "file":
card_file_path = (
f"{self.community_cards_dir}/{card}/{f.name}"
)
if (
not os.path.exists(card_file_path)
or os.path.getsize(card_file_path) != f.size
):
await self.async_save_file(
file_path=card_file_path,
content=await self.async_github_get_file(
filename=f.path
),
)
elif f.type == "dir" and f.name == "languages":
language_files = await self.async_github_get_tree(
path=f.path
)
for lang in language_files:
lang_file_path = f"{self.community_cards_dir}/{card}/languages/{lang.name}"
if pathlib.Path(lang.name).stem == language:
if (
not os.path.exists(lang_file_path)
or os.path.getsize(lang_file_path) != lang.size
):
await self.async_save_file(
file_path=lang_file_path,
content=await self.async_github_get_file(
filename=lang.path
),
)
async def configure_plugins(self) -> bool:
"""Configure the Plugins ULM depends on."""
self.log.debug("Checking Dependencies.")
try:
if not os.path.exists(
self.hass.config.path("custom_components/browser_mod")
):
self.log.error('HACS Integration repo "browser mod" is not installed!')
depenceny_resource_paths = [
"button-card",
"light-entity-card",
"lovelace-card-mod",
"lovelace-auto-entities",
"mini-graph-card",
"mini-media-player",
"my-cards",
"simple-weather-card",
"lovelace-layout-card",
"lovelace-state-switch",
"weather-radar-card",
]
for p in depenceny_resource_paths:
if not self.configuration.include_other_cards:
if not os.path.exists(self.hass.config.path(f"www/community/{p}")):
self.log.error(
f'HACS Frontend repo "{p}" is not installed, See Integration Configuration.'
)
else:
if os.path.exists(self.hass.config.path(f"www/community/{p}")):
_LOGGER.error(
f'HACS Frontend repo "{p}" is already installed, Remove it or disable include custom cards'
)
if self.configuration.include_other_cards:
for c in depenceny_resource_paths:
add_extra_js_url(
self.hass, f"/ui_lovelace_minimalist/cards/{c}/{c}.js"
)
# Register
self.hass.http.register_static_path(
"/ui_lovelace_minimalist/cards",
self.hass.config.path(f"{self.integration_dir}/cards"),
True,
)
except Exception as exception:
self.log.error(exception)
self.disable_ulm(UlmDisabledReason.LOAD_ULM)
return False
return True
async def configure_dashboard(self) -> bool:
"""Configure the ULM Dashboards."""
dashboard_url = "ui-lovelace-minimalist"
dashboard_config = {
"mode": "yaml",
"icon": self.configuration.sidepanel_icon,
"title": self.configuration.sidepanel_title,
"filename": "ui_lovelace_minimalist/dashboard/ui-lovelace.yaml",
"show_in_sidebar": True,
"require_admin": False,
}
adv_dashboard_url = "adaptive-dash"
adv_dashboard_config = {
"mode": "yaml",
"icon": self.configuration.adaptive_ui_icon,
"title": self.configuration.adaptive_ui_title,
"filename": "ui_lovelace_minimalist/dashboard/adaptive-dash/adaptive-ui.yaml",
"show_in_sidebar": True,
"require_admin": False,
}
# Optoinal override can be done with config_flow?
# if not dashboard_url in hass.data["lovelace"]["dashboards"]:
try:
if self.configuration.sidepanel_enabled:
self.hass.data["lovelace"]["dashboards"][dashboard_url] = LovelaceYAML(
self.hass, dashboard_url, dashboard_config
)
_register_panel(
self.hass, dashboard_url, "yaml", dashboard_config, True
)
else:
if dashboard_url in self.hass.data["lovelace"]["dashboards"]:
async_remove_panel(self.hass, "ui-lovelace-minimalist")
if self.configuration.adaptive_ui_enabled:
self.hass.data["lovelace"]["dashboards"][
adv_dashboard_url
] = LovelaceYAML(self.hass, adv_dashboard_url, adv_dashboard_config)
_register_panel(
self.hass, adv_dashboard_url, "yaml", adv_dashboard_config, True
)
else:
if adv_dashboard_url in self.hass.data["lovelace"]["dashboards"]:
async_remove_panel(self.hass, "adaptive-dash")
except Exception as exception:
self.log.error(exception)
self.disable_ulm(UlmDisabledReason.LOAD_ULM)
return False
return True
async def configure_ulm(self) -> bool:
"""Configure initial dashboard & cards directory."""
self.log.info("Setup ULM Configuration")
try:
# Cleanup
await self.hass.async_add_executor_job(shutil.rmtree, self.hass.config.path(f"{DOMAIN}/configs"), True)
await self.hass.async_add_executor_job(shutil.rmtree, self.hass.config.path(f"{DOMAIN}/addons"), True)
# Create config dir
os.makedirs(self.hass.config.path(f"{DOMAIN}/dashboard"), exist_ok=True)
os.makedirs(self.hass.config.path(f"{DOMAIN}/custom_cards"), exist_ok=True)
os.makedirs(self.hass.config.path(f"{DOMAIN}/custom_actions"), exist_ok=True)
if os.path.exists(self.hass.config.path(f"{DOMAIN}/dashboard")):
os.makedirs(self.templates_dir, exist_ok=True)
# Translations
language = LANGUAGES[self.configuration.language]
# Copy default language file over to config dir
await self.hass.async_add_executor_job(
shutil.copy2,
f"{self.integration_dir}/lovelace/translations/default.yaml",
f"{self.templates_dir}/default.yaml",
)
# Copy example dashboard file over to user config dir if not exists
if self.configuration.sidepanel_enabled:
if not os.path.exists(
self.hass.config.path(f"{DOMAIN}/dashboard/ui-lovelace.yaml")
):
await self.hass.async_add_executor_job(
shutil.copy2,
f"{self.integration_dir}/lovelace/ui-lovelace.yaml",
self.hass.config.path(
f"{DOMAIN}/dashboard/ui-lovelace.yaml"
),
)
# Copy adaptive dashboard if not exists and is selected as option
if self.configuration.adaptive_ui_enabled:
if not os.path.exists(
self.hass.config.path(f"{DOMAIN}/dashboard/adaptive-dash")
):
await self.hass.async_add_executor_job(
lambda: shutil.copytree(
f"{self.integration_dir}/lovelace/adaptive-dash",
self.hass.config.path(f"{DOMAIN}/dashboard/adaptive-dash"),
dirs_exist_ok=True,
)
)
# Copy example custom actions file over to user config dir if not exists
if not os.path.exists(
self.hass.config.path(
f"{DOMAIN}/custom_actions/custom_actions.yaml"
)
):
await self.hass.async_add_executor_job(
shutil.copy2,
f"{self.integration_dir}/lovelace/custom_actions.yaml",
self.hass.config.path(
f"{DOMAIN}/custom_actions/custom_actions.yaml"
),
)
# Copy chosen language file over to config dir
await self.hass.async_add_executor_job(
shutil.copy2,
f"{self.integration_dir}/lovelace/translations/{language}.yaml",
f"{self.templates_dir}/language.yaml",
)
# Copy over cards from integration
await self.hass.async_add_executor_job(
lambda: shutil.copytree(
f"{self.integration_dir}/lovelace/ulm_templates",
f"{self.templates_dir}",
dirs_exist_ok=True,
)
)
# Copy over manually installed custom_cards from user
await self.hass.async_add_executor_job(
lambda: shutil.copytree(
self.hass.config.path(f"{DOMAIN}/custom_cards"),
f"{self.templates_dir}/custom_cards",
dirs_exist_ok=True,
)
)
# Copy over manually installed custom_actions from user
await self.hass.async_add_executor_job(
lambda: shutil.copytree(
self.hass.config.path(f"{DOMAIN}/custom_actions"),
f"{self.templates_dir}/custom_actions",
dirs_exist_ok=True,
)
)
# Copy over themes to defined themes folder
await self.hass.async_add_executor_job(
lambda: shutil.copytree(
f"{self.integration_dir}/lovelace/themefiles",
self.hass.config.path(f"{self.configuration.theme_path}/"),
dirs_exist_ok=True,
)
)
self.hass.bus.async_fire("ui_lovelace_minimalist_reload")
async def handle_reload(call):
_LOGGER.debug("Reload UI Lovelace Minimalist Configuration")
self.reload_configuration()
# Register servcie ui_lovelace_minimalist.reload
self.hass.services.async_register(DOMAIN, "reload", handle_reload)
except Exception as exception:
self.log.error(exception)
self.disable_ulm(UlmDisabledReason.LOAD_ULM)
return False
return True
def reload_configuration(self):
"""Reload Configuration."""
if os.path.exists(self.hass.config.path(f"{DOMAIN}/custom_cards")):
# Copy over manually installed custom_cards from user
self.hass.async_add_executor_job(
lambda: shutil.copytree(
self.hass.config.path(f"{DOMAIN}/custom_cards"),
f"{self.templates_dir}/custom_cards",
dirs_exist_ok=True,
)
)
if os.path.exists(self.hass.config.path(f"{DOMAIN}/custom_actions")):
# Copy over manually installed custom_actions from user
self.hass.async_add_executor_job(
lambda: shutil.copytree(
self.hass.config.path(f"{DOMAIN}/custom_actions"),
f"{self.templates_dir}/custom_actions",
dirs_exist_ok=True,
)
)
self.hass.bus.async_fire("ui_lovelace_minimalist_reload") |
I tried that same approach on a fork and the thing still crashes. I think we need someone more versed in Home Assistant development to either work it out or at least gives pointers on where to look. |
I'm experiencing the same issue. Since i'm installing from scratch, it's unusable at the moment. |
It looks like the integration gets stuck in a loop. I enabled debug logging and it begins checking every half-second if the other front-end cards are installed and then reloading itself if they are not.
Within about five minutes, it has produced over 4,000 lines in my log file while completely locking Home Assistant up because it's an endless loop. |
Tried to use previous version (1.3.8 instead of 13.9) , still nothin changed. Looks like its a HA update problem... |
I was able to fix some issues related to 'Detected blocking' and hanging. See issue #1501. |
Experienced the same issue as others here, show stopping bug. Attempting to enable the integration will lock HA. It was particularly nasty for me, and ultimately required a fresh SD flash and manual backup restore (no fun.) |
Fixed hanging issues for (first-time) users not using the GitHub option. Update in https://github.com/wilbiev/UI/blob/main/custom_components/ui_lovelace_minimalist/__init__.py. @lyneld, @karelkryda this should solve your hanging issue |
Hi, |
Replace the code in init.py with https://github.com/wilbiev/UI/blob/main/custom_components/ui_lovelace_minimalist/__init__.py. |
I replaced that file and and could install the integration. Thank You for the tip. |
I can confirm that @wilbiev's fix works as well. |
Thanks @wilbiev your fix allowed me to save the configuration. However there are still issues related to the "Enable the ability to configure community cards with this integrations, Does require GitHub authentication!" setting. Not sure if this is all related. If it isn't I can create a new issue - sorry about that. But I never had that setting enabled. I am trying to enable it so I get automatic updates of the custom cards. Without your fix trying to enable it would hang my entire home assistant and force a reboot when I attempted to save the settings. With your fix in place I can now save the settings. However, after I save the settings and then go back in to select my custom cards, the custom cards selected do not save. Further if I restart home assistant with the custom cards setting enabled, UI Lovelace Minimalist fails to start. When I save:
When I restart:
I'm on v1.3.10 of Minimalist with your patch applied and Github integration enabled and connected. Home assistant core 2024.7.2. EDIT: My config does get updated though when I save. However the UI doesn't reflect that and I don't see the custom cards downloading. And if I restart home assistant IU Minimalist won't start.
|
@StarScream159 I see in the provided log you do not have an GitHub token configured. You need to enable the GitHub option when installing the integration. In your case you need to remove and install the integration again to enable this. |
It's still crashing at 2024.8.1 |
@wilbiev Not sure how I missed your reply. Thanks. I see you mentioned that the logs say I don't have a github token, but I do. I have the github integration configured and active. When I click on Settings -> Devices & Services -> Github -> Configure I get a little loader and then within a second I see my repos listed with a checkbox. So it's using my token/key to pull the repos list - it is configured correctly. I can also see a "HACS" and "Home Assistant GitHub Integration" under my https://github.com/settings/applications page. Both say used within the last week. |
Getting the same on a freshly installed HA - GitHub auth has been configured correctly but getting:
|
Just tried to install for the first time. Having the same issue. |
Same issue for me, just tried to install it, managed to uninstall it after few crashes. |
Describe the bug
After installing this form HACS and adding integration from the menu, the entire machine froze.
To Reproduce
Steps to reproduce the behavior:
Expected behavior
HA should work and not generate errors
Screenshots
If applicable, add screenshots to help explain your problem.
Additional context
Add any other context about the problem here.
Related Logs (can be found in the browser-console (F12))
The text was updated successfully, but these errors were encountered: