Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,7 @@ homeassistant/components/repetier/* @MTrab
homeassistant/components/rfxtrx/* @danielhiversen
homeassistant/components/rmvtransport/* @cgtobi
homeassistant/components/roomba/* @pschmitt
homeassistant/components/safe_mode/* @home-assistant/core
homeassistant/components/saj/* @fredericvl
homeassistant/components/samsungtv/* @escoand
homeassistant/components/scene/* @home-assistant/core
Expand Down
64 changes: 17 additions & 47 deletions homeassistant/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,10 @@
import subprocess
import sys
import threading
from typing import TYPE_CHECKING, Any, Dict, List
from typing import List

from homeassistant.const import REQUIRED_PYTHON_VER, RESTART_EXIT_CODE, __version__

if TYPE_CHECKING:
from homeassistant import core


def set_loop() -> None:
"""Attempt to use different loop."""
Expand Down Expand Up @@ -78,19 +75,6 @@ def ensure_config_path(config_dir: str) -> None:
sys.exit(1)


async def ensure_config_file(hass: "core.HomeAssistant", config_dir: str) -> str:
"""Ensure configuration file exists."""
import homeassistant.config as config_util

config_path = await config_util.async_ensure_config_exists(hass, config_dir)

if config_path is None:
print("Error getting configuration path")
sys.exit(1)

return config_path


def get_arguments() -> argparse.Namespace:
"""Get parsed passed in arguments."""
import homeassistant.config as config_util
Expand All @@ -107,7 +91,7 @@ def get_arguments() -> argparse.Namespace:
help="Directory that contains the Home Assistant configuration",
)
parser.add_argument(
"--demo-mode", action="store_true", help="Start Home Assistant in demo mode"
"--safe-mode", action="store_true", help="Start Home Assistant in safe mode"
)
parser.add_argument(
"--debug", action="store_true", help="Start Home Assistant in debug mode"
Expand Down Expand Up @@ -253,34 +237,20 @@ def cmdline() -> List[str]:

async def setup_and_run_hass(config_dir: str, args: argparse.Namespace) -> int:
"""Set up Home Assistant and run."""
from homeassistant import bootstrap, core

hass = core.HomeAssistant()

if args.demo_mode:
config: Dict[str, Any] = {"frontend": {}, "demo": {}}
bootstrap.async_from_config_dict(
config,
hass,
config_dir=config_dir,
verbose=args.verbose,
skip_pip=args.skip_pip,
log_rotate_days=args.log_rotate_days,
log_file=args.log_file,
log_no_color=args.log_no_color,
)
else:
config_file = await ensure_config_file(hass, config_dir)
print("Config directory:", config_dir)
await bootstrap.async_from_config_file(
config_file,
hass,
verbose=args.verbose,
skip_pip=args.skip_pip,
log_rotate_days=args.log_rotate_days,
log_file=args.log_file,
log_no_color=args.log_no_color,
)
from homeassistant import bootstrap

hass = await bootstrap.async_setup_hass(
config_dir=config_dir,
verbose=args.verbose,
log_rotate_days=args.log_rotate_days,
log_file=args.log_file,
log_no_color=args.log_no_color,
skip_pip=args.skip_pip,
safe_mode=args.safe_mode,
)

if hass is None:
return 1

if args.open_ui and hass.config.api is not None:
import webbrowser
Expand Down Expand Up @@ -358,7 +328,7 @@ def main() -> int:

return scripts.run(args.script)

config_dir = os.path.join(os.getcwd(), args.config)
config_dir = os.path.abspath(os.path.join(os.getcwd(), args.config))
ensure_config_path(config_dir)

# Daemon functions
Expand Down
132 changes: 64 additions & 68 deletions homeassistant/bootstrap.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
"""Provide methods to bootstrap a Home Assistant instance."""
import asyncio
from collections import OrderedDict
import logging
import logging.handlers
import os
Expand All @@ -11,6 +10,7 @@
import voluptuous as vol

from homeassistant import config as conf_util, config_entries, core, loader
from homeassistant.components import http
from homeassistant.const import (
EVENT_HOMEASSISTANT_CLOSE,
REQUIRED_NEXT_PYTHON_DATE,
Expand Down Expand Up @@ -42,33 +42,76 @@
}


async def async_from_config_dict(
config: Dict[str, Any],
hass: core.HomeAssistant,
config_dir: Optional[str] = None,
enable_log: bool = True,
verbose: bool = False,
skip_pip: bool = False,
log_rotate_days: Any = None,
log_file: Any = None,
log_no_color: bool = False,
async def async_setup_hass(
*,
config_dir: str,
verbose: bool,
log_rotate_days: int,
log_file: str,
log_no_color: bool,
skip_pip: bool,
safe_mode: bool,
) -> Optional[core.HomeAssistant]:
"""Try to configure Home Assistant from a configuration dictionary.

Dynamically loads required components and its dependencies.
This method is a coroutine.
"""
start = time()
"""Set up Home Assistant."""
hass = core.HomeAssistant()
hass.config.config_dir = config_dir

if enable_log:
async_enable_logging(hass, verbose, log_rotate_days, log_file, log_no_color)
async_enable_logging(hass, verbose, log_rotate_days, log_file, log_no_color)

hass.config.skip_pip = skip_pip
if skip_pip:
_LOGGER.warning(
"Skipping pip installation of required modules. This may cause issues"
)

if not await conf_util.async_ensure_config_exists(hass):
_LOGGER.error("Error getting configuration path")
return None

_LOGGER.info("Config directory: %s", config_dir)

config_dict = None

if not safe_mode:
await hass.async_add_executor_job(conf_util.process_ha_config_upgrade, hass)

try:
config_dict = await conf_util.async_hass_config_yaml(hass)
except HomeAssistantError as err:
_LOGGER.error(
"Failed to parse configuration.yaml: %s. Falling back to safe mode",
err,
)
else:
if not is_virtual_env():
await async_mount_local_lib_path(config_dir)

await async_from_config_dict(config_dict, hass)
finally:
clear_secret_cache()

if safe_mode or config_dict is None:
_LOGGER.info("Starting in safe mode")

http_conf = (await http.async_get_last_config(hass)) or {}

await async_from_config_dict(
{"safe_mode": {}, "http": http_conf}, hass,
)

return hass


async def async_from_config_dict(
config: Dict[str, Any], hass: core.HomeAssistant
) -> Optional[core.HomeAssistant]:
"""Try to configure Home Assistant from a configuration dictionary.

Dynamically loads required components and its dependencies.
This method is a coroutine.
"""
start = time()

core_config = config.get(core.DOMAIN, {})

try:
Expand All @@ -83,14 +126,6 @@ async def async_from_config_dict(
)
return None

# Make a copy because we are mutating it.
config = OrderedDict(config)

# Merge packages
await conf_util.merge_packages_config(
hass, config, core_config.get(conf_util.CONF_PACKAGES, {})
)

hass.config_entries = config_entries.ConfigEntries(hass, config)
await hass.config_entries.async_initialize()

Expand All @@ -116,46 +151,6 @@ async def async_from_config_dict(
return hass


async def async_from_config_file(
config_path: str,
hass: core.HomeAssistant,
verbose: bool = False,
skip_pip: bool = True,
log_rotate_days: Any = None,
log_file: Any = None,
log_no_color: bool = False,
) -> Optional[core.HomeAssistant]:
"""Read the configuration file and try to start all the functionality.

Will add functionality to 'hass' parameter.
This method is a coroutine.
"""
# Set config dir to directory holding config file
config_dir = os.path.abspath(os.path.dirname(config_path))
hass.config.config_dir = config_dir

if not is_virtual_env():
await async_mount_local_lib_path(config_dir)

async_enable_logging(hass, verbose, log_rotate_days, log_file, log_no_color)

await hass.async_add_executor_job(conf_util.process_ha_config_upgrade, hass)

try:
config_dict = await hass.async_add_executor_job(
conf_util.load_yaml_config_file, config_path
)
except HomeAssistantError as err:
_LOGGER.error("Error loading %s: %s", config_path, err)
return None
finally:
clear_secret_cache()

return await async_from_config_dict(
config_dict, hass, enable_log=False, skip_pip=skip_pip
)


@core.callback
def async_enable_logging(
hass: core.HomeAssistant,
Expand Down Expand Up @@ -269,7 +264,8 @@ def _get_domains(hass: core.HomeAssistant, config: Dict[str, Any]) -> Set[str]:
domains = set(key.split(" ")[0] for key in config.keys() if key != core.DOMAIN)

# Add config entry domains
domains.update(hass.config_entries.async_domains())
if "safe_mode" not in config:
domains.update(hass.config_entries.async_domains())

# Make sure the Hass.io component is loaded
if "HASSIO" in os.environ:
Expand Down
9 changes: 4 additions & 5 deletions homeassistant/components/frontend/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

from homeassistant.components import websocket_api
from homeassistant.components.http.view import HomeAssistantView
from homeassistant.config import find_config_file, load_yaml_config_file
from homeassistant.config import async_hass_config_yaml
from homeassistant.const import CONF_NAME, EVENT_THEMES_UPDATED
from homeassistant.core import callback
import homeassistant.helpers.config_validation as cv
Expand Down Expand Up @@ -362,11 +362,10 @@ def set_theme(call):
else:
_LOGGER.warning("Theme %s is not defined.", name)

@callback
def reload_themes(_):
async def reload_themes(_):
"""Reload themes."""
path = find_config_file(hass.config.config_dir)
new_themes = load_yaml_config_file(path)[DOMAIN].get(CONF_THEMES, {})
config = await async_hass_config_yaml(hass)
new_themes = config[DOMAIN].get(CONF_THEMES, {})
hass.data[DATA_THEMES] = new_themes
if hass.data[DATA_DEFAULT_THEME] not in new_themes:
hass.data[DATA_DEFAULT_THEME] = DEFAULT_THEME
Expand Down
19 changes: 18 additions & 1 deletion homeassistant/components/http/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import logging
import os
import ssl
from typing import Optional
from typing import Optional, cast

from aiohttp import web
from aiohttp.web_exceptions import HTTPMovedPermanently
Expand All @@ -14,7 +14,10 @@
EVENT_HOMEASSISTANT_STOP,
SERVER_PORT,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers import storage
import homeassistant.helpers.config_validation as cv
from homeassistant.loader import bind_hass
import homeassistant.util as hass_util
from homeassistant.util import ssl as ssl_util

Expand Down Expand Up @@ -56,6 +59,9 @@

MAX_CLIENT_SIZE: int = 1024 ** 2 * 16

STORAGE_KEY = DOMAIN
STORAGE_VERSION = 1


HTTP_SCHEMA = vol.Schema(
{
Expand Down Expand Up @@ -85,6 +91,13 @@
CONFIG_SCHEMA = vol.Schema({DOMAIN: HTTP_SCHEMA}, extra=vol.ALLOW_EXTRA)


@bind_hass
async def async_get_last_config(hass: HomeAssistant) -> Optional[dict]:
"""Return the last known working config."""
store = storage.Store(hass, STORAGE_VERSION, STORAGE_KEY)
return cast(Optional[dict], await store.async_load())


class ApiConfig:
"""Configuration settings for API server."""

Expand Down Expand Up @@ -151,6 +164,10 @@ async def start_server(event):
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, stop_server)
await server.start()

# If we are set up successful, we store the HTTP settings for safe mode.
store = storage.Store(hass, STORAGE_VERSION, STORAGE_KEY)
await store.async_save(conf)

hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, start_server)

hass.http = server
Expand Down
15 changes: 15 additions & 0 deletions homeassistant/components/safe_mode/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
"""The Safe Mode integration."""
from homeassistant.components import persistent_notification
from homeassistant.core import HomeAssistant

DOMAIN = "safe_mode"


async def async_setup(hass: HomeAssistant, config: dict):
"""Set up the Safe Mode component."""
persistent_notification.async_create(
hass,
"Home Assistant is running in safe mode. Check [the error log](/developer-tools/logs) to see what went wrong.",
"Safe Mode",
)
return True
12 changes: 12 additions & 0 deletions homeassistant/components/safe_mode/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"domain": "safe_mode",
"name": "Safe Mode",
"config_flow": false,
"documentation": "https://www.home-assistant.io/integrations/safe_mode",
"requirements": [],
"ssdp": [],
"zeroconf": [],
"homekit": {},
"dependencies": ["frontend", "config", "persistent_notification", "cloud"],
"codeowners": ["@home-assistant/core"]
}
Loading