Use /data directory for .esphome folder when running as HA add-on (#5374)

This commit is contained in:
Jesse Hills 2023-09-12 09:26:48 +12:00 committed by GitHub
parent 10eee47b6b
commit fe81bcc003
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 53 additions and 43 deletions

View file

@ -35,11 +35,16 @@ if bashio::config.has_value 'default_compile_process_limit'; then
export ESPHOME_DEFAULT_COMPILE_PROCESS_LIMIT=$(bashio::config 'default_compile_process_limit') export ESPHOME_DEFAULT_COMPILE_PROCESS_LIMIT=$(bashio::config 'default_compile_process_limit')
else else
if grep -q 'Raspberry Pi 3' /proc/cpuinfo; then if grep -q 'Raspberry Pi 3' /proc/cpuinfo; then
export ESPHOME_DEFAULT_COMPILE_PROCESS_LIMIT=1; export ESPHOME_DEFAULT_COMPILE_PROCESS_LIMIT=1
fi fi
fi fi
mkdir -p "${pio_cache_base}" mkdir -p "${pio_cache_base}"
if bashio::fs.directory_exists '/config/esphome/.esphome'; then
bashio::log.info "Removing old .esphome directory..."
rm -rf /config/esphome/.esphome
fi
bashio::log.info "Starting ESPHome dashboard..." bashio::log.info "Starting ESPHome dashboard..."
exec esphome dashboard /config/esphome --socket /var/run/esphome.sock --ha-addon exec esphome dashboard /config/esphome --socket /var/run/esphome.sock --ha-addon

View file

@ -98,10 +98,9 @@ def validate_truetype_file(value):
def _compute_local_font_dir(name) -> Path: def _compute_local_font_dir(name) -> Path:
base_dir = Path(CORE.config_dir) / ".esphome" / DOMAIN
h = hashlib.new("sha256") h = hashlib.new("sha256")
h.update(name.encode()) h.update(name.encode())
return base_dir / h.hexdigest()[:8] return Path(CORE.data_dir) / DOMAIN / h.hexdigest()[:8]
def _compute_gfonts_local_path(value) -> Path: def _compute_gfonts_local_path(value) -> Path:

View file

@ -52,7 +52,7 @@ Image_ = image_ns.class_("Image")
def _compute_local_icon_path(value) -> Path: def _compute_local_icon_path(value) -> Path:
base_dir = Path(CORE.config_dir) / ".esphome" / DOMAIN / "mdi" base_dir = Path(CORE.data_dir) / DOMAIN / "mdi"
return base_dir / f"{value[CONF_ICON]}.svg" return base_dir / f"{value[CONF_ICON]}.svg"

View file

@ -87,12 +87,7 @@ def get_firmware(value):
url = value[CONF_URL] url = value[CONF_URL]
if CONF_SHA256 in value: # we have a hash, enable caching if CONF_SHA256 in value: # we have a hash, enable caching
path = ( path = Path(CORE.data_dir) / DOMAIN / (value[CONF_SHA256] + "_fw_stm.bin")
Path(CORE.config_dir)
/ ".esphome"
/ DOMAIN
/ (value[CONF_SHA256] + "_fw_stm.bin")
)
if not path.is_file(): if not path.is_file():
firmware_data, dl_hash = dl(url) firmware_data, dl_hash = dl(url)

View file

@ -554,6 +554,12 @@ class EsphomeCore:
def config_dir(self): def config_dir(self):
return os.path.dirname(self.config_path) return os.path.dirname(self.config_path)
@property
def data_dir(self):
if is_ha_addon():
return os.path.join("/data")
return self.relative_config_path(".esphome")
@property @property
def config_filename(self): def config_filename(self):
return os.path.basename(self.config_path) return os.path.basename(self.config_path)
@ -563,7 +569,7 @@ class EsphomeCore:
return os.path.join(self.config_dir, path_) return os.path.join(self.config_dir, path_)
def relative_internal_path(self, *path: str) -> str: def relative_internal_path(self, *path: str) -> str:
return self.relative_config_path(".esphome", *path) return os.path.join(self.data_dir, *path)
def relative_build_path(self, *path): def relative_build_path(self, *path):
path_ = os.path.expanduser(os.path.join(*path)) path_ = os.path.expanduser(os.path.join(*path))
@ -573,13 +579,9 @@ class EsphomeCore:
return self.relative_build_path("src", *path) return self.relative_build_path("src", *path)
def relative_pioenvs_path(self, *path): def relative_pioenvs_path(self, *path):
if is_ha_addon():
return os.path.join("/data", self.name, ".pioenvs", *path)
return self.relative_build_path(".pioenvs", *path) return self.relative_build_path(".pioenvs", *path)
def relative_piolibdeps_path(self, *path): def relative_piolibdeps_path(self, *path):
if is_ha_addon():
return os.path.join("/data", self.name, ".piolibdeps", *path)
return self.relative_build_path(".piolibdeps", *path) return self.relative_build_path(".piolibdeps", *path)
@property @property

View file

@ -198,8 +198,8 @@ def preload_core_config(config, result):
CORE.data[KEY_CORE] = {} CORE.data[KEY_CORE] = {}
if CONF_BUILD_PATH not in conf: if CONF_BUILD_PATH not in conf:
conf[CONF_BUILD_PATH] = f".esphome/build/{CORE.name}" conf[CONF_BUILD_PATH] = f"build/{CORE.name}"
CORE.build_path = CORE.relative_config_path(conf[CONF_BUILD_PATH]) CORE.build_path = CORE.relative_internal_path(conf[CONF_BUILD_PATH])
has_oldstyle = CONF_PLATFORM in conf has_oldstyle = CONF_PLATFORM in conf
newstyle_found = [key for key in TARGET_PLATFORMS if key in config] newstyle_found = [key for key in TARGET_PLATFORMS if key in config]

View file

@ -32,6 +32,7 @@ import yaml
from tornado.log import access_log from tornado.log import access_log
from esphome import const, platformio_api, util, yaml_util from esphome import const, platformio_api, util, yaml_util
from esphome.core import CORE
from esphome.helpers import get_bool_env, mkdir_p, run_system_command from esphome.helpers import get_bool_env, mkdir_p, run_system_command
from esphome.storage_json import ( from esphome.storage_json import (
EsphomeStorageJSON, EsphomeStorageJSON,
@ -70,6 +71,7 @@ class DashboardSettings:
self.password_hash = password_hash(password) self.password_hash = password_hash(password)
self.config_dir = args.configuration self.config_dir = args.configuration
self.absolute_config_dir = Path(self.config_dir).resolve() self.absolute_config_dir = Path(self.config_dir).resolve()
CORE.config_path = os.path.join(self.config_dir, ".")
@property @property
def relative_url(self): def relative_url(self):
@ -534,7 +536,7 @@ class DownloadListRequestHandler(BaseHandler):
@authenticated @authenticated
@bind_config @bind_config
def get(self, configuration=None): def get(self, configuration=None):
storage_path = ext_storage_path(settings.config_dir, configuration) storage_path = ext_storage_path(configuration)
storage_json = StorageJSON.load(storage_path) storage_json = StorageJSON.load(storage_path)
if storage_json is None: if storage_json is None:
self.send_error(404) self.send_error(404)
@ -577,7 +579,7 @@ class DownloadBinaryRequestHandler(BaseHandler):
def get(self, configuration=None): def get(self, configuration=None):
compressed = self.get_argument("compressed", "0") == "1" compressed = self.get_argument("compressed", "0") == "1"
storage_path = ext_storage_path(settings.config_dir, configuration) storage_path = ext_storage_path(configuration)
storage_json = StorageJSON.load(storage_path) storage_json = StorageJSON.load(storage_path)
if storage_json is None: if storage_json is None:
self.send_error(404) self.send_error(404)
@ -666,9 +668,7 @@ class DashboardEntry:
@property @property
def storage(self) -> Optional[StorageJSON]: def storage(self) -> Optional[StorageJSON]:
if not self._loaded_storage: if not self._loaded_storage:
self._storage = StorageJSON.load( self._storage = StorageJSON.load(ext_storage_path(self.filename))
ext_storage_path(settings.config_dir, self.filename)
)
self._loaded_storage = True self._loaded_storage = True
return self._storage return self._storage
@ -1044,9 +1044,9 @@ class DeleteRequestHandler(BaseHandler):
@bind_config @bind_config
def post(self, configuration=None): def post(self, configuration=None):
config_file = settings.rel_path(configuration) config_file = settings.rel_path(configuration)
storage_path = ext_storage_path(settings.config_dir, configuration) storage_path = ext_storage_path(configuration)
trash_path = trash_storage_path(settings.config_dir) trash_path = trash_storage_path()
mkdir_p(trash_path) mkdir_p(trash_path)
shutil.move(config_file, os.path.join(trash_path, configuration)) shutil.move(config_file, os.path.join(trash_path, configuration))
@ -1067,7 +1067,7 @@ class UndoDeleteRequestHandler(BaseHandler):
@bind_config @bind_config
def post(self, configuration=None): def post(self, configuration=None):
config_file = settings.rel_path(configuration) config_file = settings.rel_path(configuration)
trash_path = trash_storage_path(settings.config_dir) trash_path = trash_storage_path()
shutil.move(os.path.join(trash_path, configuration), config_file) shutil.move(os.path.join(trash_path, configuration), config_file)
@ -1325,10 +1325,9 @@ def make_app(debug=get_bool_env(ENV_DEV)):
def start_web_server(args): def start_web_server(args):
settings.parse_args(args) settings.parse_args(args)
mkdir_p(settings.rel_path(".esphome"))
if settings.using_auth: if settings.using_auth:
path = esphome_storage_path(settings.config_dir) path = esphome_storage_path()
storage = EsphomeStorageJSON.load(path) storage = EsphomeStorageJSON.load(path)
if storage is None: if storage is None:
storage = EsphomeStorageJSON.get_default() storage = EsphomeStorageJSON.get_default()

View file

@ -35,7 +35,7 @@ def run_git_command(cmd, cwd=None) -> str:
def _compute_destination_path(key: str, domain: str) -> Path: def _compute_destination_path(key: str, domain: str) -> Path:
base_dir = Path(CORE.config_dir) / ".esphome" / domain base_dir = Path(CORE.data_dir) / domain
h = hashlib.new("sha256") h = hashlib.new("sha256")
h.update(key.encode()) h.update(key.encode())
return base_dir / h.hexdigest()[:8] return base_dir / h.hexdigest()[:8]

View file

@ -22,19 +22,19 @@ _LOGGER = logging.getLogger(__name__)
def storage_path() -> str: def storage_path() -> str:
return CORE.relative_internal_path(f"{CORE.config_filename}.json") return os.path.join(CORE.data_dir, "storage", f"{CORE.config_filename}.json")
def ext_storage_path(base_path: str, config_filename: str) -> str: def ext_storage_path(config_filename: str) -> str:
return os.path.join(base_path, ".esphome", f"{config_filename}.json") return os.path.join(CORE.data_dir, "storage", f"{config_filename}.json")
def esphome_storage_path(base_path: str) -> str: def esphome_storage_path() -> str:
return os.path.join(base_path, ".esphome", "esphome.json") return os.path.join(CORE.data_dir, "esphome.json")
def trash_storage_path(base_path: str) -> str: def trash_storage_path() -> str:
return os.path.join(base_path, ".esphome", "trash") return CORE.relative_config_path("trash")
class StorageJSON: class StorageJSON:

View file

@ -6,12 +6,12 @@ import unicodedata
import voluptuous as vol import voluptuous as vol
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.const import ALLOWED_NAME_CHARS, ENV_QUICKWIZARD
from esphome.core import CORE
from esphome.helpers import get_bool_env, write_file from esphome.helpers import get_bool_env, write_file
from esphome.log import color, Fore from esphome.log import Fore, color
from esphome.storage_json import StorageJSON, ext_storage_path from esphome.storage_json import StorageJSON, ext_storage_path
from esphome.util import safe_print from esphome.util import safe_print
from esphome.const import ALLOWED_NAME_CHARS, ENV_QUICKWIZARD
CORE_BIG = r""" _____ ____ _____ ______ CORE_BIG = r""" _____ ____ _____ ______
/ ____/ __ \| __ \| ____| / ____/ __ \| __ \| ____|
@ -193,10 +193,10 @@ captive_portal:
def wizard_write(path, **kwargs): def wizard_write(path, **kwargs):
from esphome.components.esp8266 import boards as esp8266_boards
from esphome.components.esp32 import boards as esp32_boards
from esphome.components.rp2040 import boards as rp2040_boards
from esphome.components.bk72xx import boards as bk72xx_boards from esphome.components.bk72xx import boards as bk72xx_boards
from esphome.components.esp32 import boards as esp32_boards
from esphome.components.esp8266 import boards as esp8266_boards
from esphome.components.rp2040 import boards as rp2040_boards
from esphome.components.rtl87xx import boards as rtl87xx_boards from esphome.components.rtl87xx import boards as rtl87xx_boards
name = kwargs["name"] name = kwargs["name"]
@ -225,7 +225,7 @@ def wizard_write(path, **kwargs):
write_file(path, wizard_file(**kwargs)) write_file(path, wizard_file(**kwargs))
storage = StorageJSON.from_wizard(name, name, f"{name}.local", hardware) storage = StorageJSON.from_wizard(name, name, f"{name}.local", hardware)
storage_path = ext_storage_path(os.path.dirname(path), os.path.basename(path)) storage_path = ext_storage_path(os.path.basename(path))
storage.save(storage_path) storage.save(storage_path)
return True return True
@ -265,9 +265,9 @@ def strip_accents(value):
def wizard(path): def wizard(path):
from esphome.components.bk72xx import boards as bk72xx_boards
from esphome.components.esp32 import boards as esp32_boards from esphome.components.esp32 import boards as esp32_boards
from esphome.components.esp8266 import boards as esp8266_boards from esphome.components.esp8266 import boards as esp8266_boards
from esphome.components.bk72xx import boards as bk72xx_boards
from esphome.components.rtl87xx import boards as rtl87xx_boards from esphome.components.rtl87xx import boards as rtl87xx_boards
if not path.endswith(".yaml") and not path.endswith(".yml"): if not path.endswith(".yaml") and not path.endswith(".yml"):
@ -280,6 +280,9 @@ def wizard(path):
f"Uh oh, it seems like {color(Fore.CYAN, path)} already exists, please delete that file first or chose another configuration file." f"Uh oh, it seems like {color(Fore.CYAN, path)} already exists, please delete that file first or chose another configuration file."
) )
return 2 return 2
CORE.config_path = path
safe_print("Hi there!") safe_print("Hi there!")
sleep(1.5) sleep(1.5)
safe_print("I'm the wizard of ESPHome :)") safe_print("I'm the wizard of ESPHome :)")

View file

@ -1,7 +1,9 @@
"""Tests for the wizard.py file.""" """Tests for the wizard.py file."""
import os
import esphome.wizard as wz import esphome.wizard as wz
import pytest import pytest
from esphome.core import CORE
from esphome.components.esp8266.boards import ESP8266_BOARD_PINS from esphome.components.esp8266.boards import ESP8266_BOARD_PINS
from esphome.components.esp32.boards import ESP32_BOARD_PINS from esphome.components.esp32.boards import ESP32_BOARD_PINS
from esphome.components.bk72xx.boards import BK72XX_BOARD_PINS from esphome.components.bk72xx.boards import BK72XX_BOARD_PINS
@ -110,6 +112,7 @@ def test_wizard_write_sets_platform(default_config, tmp_path, monkeypatch):
# Given # Given
del default_config["platform"] del default_config["platform"]
monkeypatch.setattr(wz, "write_file", MagicMock()) monkeypatch.setattr(wz, "write_file", MagicMock())
monkeypatch.setattr(CORE, "config_path", os.path.dirname(tmp_path))
# When # When
wz.wizard_write(tmp_path, **default_config) wz.wizard_write(tmp_path, **default_config)
@ -130,6 +133,7 @@ def test_wizard_write_defaults_platform_from_board_esp8266(
default_config["board"] = [*ESP8266_BOARD_PINS][0] default_config["board"] = [*ESP8266_BOARD_PINS][0]
monkeypatch.setattr(wz, "write_file", MagicMock()) monkeypatch.setattr(wz, "write_file", MagicMock())
monkeypatch.setattr(CORE, "config_path", os.path.dirname(tmp_path))
# When # When
wz.wizard_write(tmp_path, **default_config) wz.wizard_write(tmp_path, **default_config)
@ -150,6 +154,7 @@ def test_wizard_write_defaults_platform_from_board_esp32(
default_config["board"] = [*ESP32_BOARD_PINS][0] default_config["board"] = [*ESP32_BOARD_PINS][0]
monkeypatch.setattr(wz, "write_file", MagicMock()) monkeypatch.setattr(wz, "write_file", MagicMock())
monkeypatch.setattr(CORE, "config_path", os.path.dirname(tmp_path))
# When # When
wz.wizard_write(tmp_path, **default_config) wz.wizard_write(tmp_path, **default_config)
@ -170,6 +175,7 @@ def test_wizard_write_defaults_platform_from_board_bk72xx(
default_config["board"] = [*BK72XX_BOARD_PINS][0] default_config["board"] = [*BK72XX_BOARD_PINS][0]
monkeypatch.setattr(wz, "write_file", MagicMock()) monkeypatch.setattr(wz, "write_file", MagicMock())
monkeypatch.setattr(CORE, "config_path", os.path.dirname(tmp_path))
# When # When
wz.wizard_write(tmp_path, **default_config) wz.wizard_write(tmp_path, **default_config)
@ -190,6 +196,7 @@ def test_wizard_write_defaults_platform_from_board_rtl87xx(
default_config["board"] = [*RTL87XX_BOARD_PINS][0] default_config["board"] = [*RTL87XX_BOARD_PINS][0]
monkeypatch.setattr(wz, "write_file", MagicMock()) monkeypatch.setattr(wz, "write_file", MagicMock())
monkeypatch.setattr(CORE, "config_path", os.path.dirname(tmp_path))
# When # When
wz.wizard_write(tmp_path, **default_config) wz.wizard_write(tmp_path, **default_config)