-
Notifications
You must be signed in to change notification settings - Fork 200
Expand file tree
/
Copy pathdata_paths.py
More file actions
106 lines (91 loc) · 3.63 KB
/
Copy pathdata_paths.py
File metadata and controls
106 lines (91 loc) · 3.63 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
"""Resolve a writable directory for ha-mcp persistent data.
Single source of truth for "where does ha-mcp write its files?" — used
by both ``settings_ui`` (tool config) and ``usage_logger`` (rolling
JSONL).
"""
from __future__ import annotations
import functools
import logging
import os
import tempfile
from pathlib import Path
from .._version import is_running_in_addon
logger = logging.getLogger(__name__)
@functools.lru_cache(maxsize=1)
def get_data_dir() -> Path:
"""Return a writable directory for ha-mcp persistent data (memoized).
Resolution order:
1. ``HA_MCP_CONFIG_DIR`` env var — explicit override, e.g. for hardened
Docker setups bind-mounting a writable volume into a
``read_only: true`` container.
2. ``/data`` — Home Assistant add-on (``SUPERVISOR_TOKEN`` set; writable
supervisor data dir).
3. ``~/.ha-mcp`` — standard install. Skipped when ``HA_MCP_CONFIG_DIR``
was set but failed: an explicit override means "use this exact
location", and silently writing to ``$HOME`` instead would surprise
users who chose the override deliberately.
4. ``<tempdir>/ha-mcp`` — last-resort fallback when the previously
chosen step fails (read-only filesystem; ``HOME`` unset so
``Path.home()`` resolves to ``/``; or ``HA_MCP_CONFIG_DIR`` set but
its mkdir raises). Loses persistence across restarts but lets the
server start; users wanting persistence should set
``HA_MCP_CONFIG_DIR`` to a writable path.
Memoized so the fallback warning emits once at startup rather than on
every save/load HTTP request. Tests reset via
``get_data_dir.cache_clear()``.
"""
return _resolve_data_dir()
def _resolve_data_dir() -> Path:
"""Resolve the data directory (uncached); see ``get_data_dir`` for priority."""
config_dir_env = os.environ.get("HA_MCP_CONFIG_DIR")
preferred: Path | None = None
if config_dir_env:
custom_dir = Path(config_dir_env)
try:
custom_dir.mkdir(parents=True, exist_ok=True)
except OSError as e:
logger.warning(
"HA_MCP_CONFIG_DIR=%s could not be prepared (%s: %s); "
"falling back to a tmpdir.",
custom_dir,
type(e).__name__,
e,
)
preferred = custom_dir
else:
return custom_dir
if is_running_in_addon():
return Path("/data")
if preferred is None:
home_dir = Path.home() / ".ha-mcp"
try:
home_dir.mkdir(parents=True, exist_ok=True)
except OSError:
preferred = home_dir
else:
return home_dir
fallback = Path(tempfile.gettempdir()) / "ha-mcp"
try:
fallback.mkdir(parents=True, exist_ok=True)
except OSError as e:
# Even the tmpdir is unwritable. Return the path anyway: callers
# that wrap writes in try/except OSError can degrade gracefully
# (no persistence, but the server still starts).
logger.warning(
"Cannot write ha-mcp data to %s or fallback %s (%s: %s); "
"persistence is disabled. "
"Set HA_MCP_CONFIG_DIR to a writable path for persistence.",
preferred,
fallback,
type(e).__name__,
e,
)
else:
logger.warning(
"Cannot write ha-mcp data to %s (read-only filesystem or HOME unset). "
"Falling back to %s — data will NOT persist across restarts. "
"Set HA_MCP_CONFIG_DIR to a writable path for persistence.",
preferred,
fallback,
)
return fallback