homeassistant: Migrate to new HTTP configuration approach

- Without new HTTP configuration mechanism, proper configuration of HA fails.
This is because we don't setup the configuration before starting Home Assistant.
Home Assistant only migrates the old configuration to new configuration format
only on the first run. It also leaves a "repair" message to the user asking them
to remove the 'http' section from the configuration file.

- Write to the internal storage format of HA after verifying the version of the
format. Writing through API is hindered by the lack of an API token (which
apparently can't be obtained pragmatically).

Tests:

- Install without patches. configuration.yml contains http section. Home
Assistant not work as HTTP configuration is not set. Apply patches and restart
the service. The setup is run again. 'http' section in configuration.yml is
removed. /var/lib/home-assistant-freedombox/config/.storage/http contains the
required configuration keys with proper values. Home Assistant works (after
domain selection).

- Install freshly with patches. configuration.yml does not contain 'http'
section. /var/lib/home-assistant-freedombox/config/ contains the necessary
configuration changes. HA works after domain selection.

Signed-off-by: Sunil Mohan Adapa <sunil@medhas.org>
Reviewed-by: James Valleroy <jvalleroy@mailbox.org>
This commit is contained in:
Sunil Mohan Adapa 2026-10-08 11:32:10 -07:00 committed by James Valleroy
parent 27ff747cf0
commit 5e72f3944a
No known key found for this signature in database
GPG Key ID: 77C0C75E7B650808
2 changed files with 79 additions and 25 deletions

View File

@ -53,7 +53,7 @@ class HomeAssistnatApp(app_module.App):
app_id = 'homeassistant'
_version = 1
_version = 2
def __init__(self):
"""Create components for the app."""
@ -133,8 +133,8 @@ class HomeAssistnatApp(app_module.App):
self.get_component('shortcut-homeassistant').url = url
self.info.clients[0]['platforms'][0]['url'] = url
def setup(self, old_version):
def setup(self, old_version: int):
"""Install and configure the app."""
super().setup(old_version)
privileged.setup()
privileged.setup(old_version)

View File

@ -1,7 +1,9 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
"""Configure Home Assistant."""
import contextlib
import functools
import json
import pathlib
import time
import traceback
@ -9,10 +11,13 @@ from dataclasses import dataclass
import yaml
from freedombox import action_utils
from freedombox.actions import privileged
_settings_file = pathlib.Path(
_old_settings_file = pathlib.Path(
'/var/lib/home-assistant-freedombox/config/configuration.yaml')
_settings_file = pathlib.Path(
'/var/lib/home-assistant-freedombox/config/.storage/http')
@dataclass
@ -61,44 +66,93 @@ yaml_add_handlers()
@privileged
def setup() -> None:
def setup(old_version: int) -> None:
"""Setup basic Home Assistant configuration."""
pathlib.Path('/var/lib/home-assistant-freedombox/').chmod(0o700)
try:
_wait_for_configuration_file()
if old_version == 1:
_migrate_old_configuration()
settings = _read_settings()
if 'http' not in settings:
settings['http'] = {}
_wait_for_configuration_file(_settings_file)
settings['http']['server_host'] = '127.0.0.1'
settings['http']['use_x_forwarded_for'] = True
settings['http']['trusted_proxies'] = '127.0.0.1'
_write_settings(settings)
except Exception as exception:
raise Exception(
traceback.format_tb(exception.__traceback__) +
[_settings_file.read_text()])
with _ensure_stopped():
try:
settings = _read_settings()
version = settings.get('version', 0)
if version != 2:
raise Exception(
f'Configuration format not understood: {version}.')
settings.setdefault('data', {})
settings['data'].setdefault('stable', {})
settings['data']['stable']['server_host'] = '127.0.0.1'
settings['data']['stable']['use_x_forwarded_for'] = True
settings['data']['stable']['trusted_proxies'] = ['127.0.0.1']
_write_settings(settings)
except Exception as exception:
raise Exception(
traceback.format_tb(exception.__traceback__) +
[_settings_file.read_text()])
def _wait_for_configuration_file() -> None:
def _wait_for_configuration_file(settings_file: pathlib.Path) -> None:
"""Wait until the Home Assistant daemon creates a configuration file."""
start_time = time.time()
while time.time() < start_time + 300:
if _settings_file.exists():
if settings_file.exists():
break
time.sleep(1)
def _read_settings() -> dict:
"""Load settings as dictionary from YAML config file."""
with _settings_file.open('rb') as settings_file:
return yaml.load(settings_file, Loader=YAMLLoader)
"""Load internal storage for HTTP configuration."""
with _settings_file.open('rb') as file_handle:
return json.load(file_handle)
def _write_settings(settings: dict):
"""Write HTTP configuration into internal storage."""
with _settings_file.open('w') as file_handle:
return json.dump(settings, file_handle, indent=2)
def _migrate_old_configuration():
"""Migrate old configuration file."""
_wait_for_configuration_file(_old_settings_file)
try:
settings = _read_old_settings()
if 'http' in settings:
del settings['http']
_write_old_settings(settings)
except Exception as exception:
print('Unable to upgrade old configuration file.', exception)
def _read_old_settings() -> dict:
"""Load settings as dictionary from YAML config file."""
with _old_settings_file.open('rb') as file_handle:
return yaml.load(file_handle, Loader=YAMLLoader)
def _write_old_settings(settings: dict):
"""Write settings from dictionary to YAML config file."""
with _settings_file.open('w', encoding='utf-8') as settings_file:
yaml.dump(settings, settings_file, Dumper=YAMLDumper)
with _old_settings_file.open('w', encoding='utf-8') as file_handle:
yaml.dump(settings, file_handle, Dumper=YAMLDumper)
@contextlib.contextmanager
def _ensure_stopped():
"""Ensure that the service is stopped."""
name = 'home-assistant-freedombox'
starting_state = action_utils.service_is_running(name)
if starting_state:
action_utils.service_disable(name)
try:
yield starting_state
finally:
if starting_state:
action_utils.service_enable(name)