93 lines
2.4 KiB
Python
93 lines
2.4 KiB
Python
"""
|
|
Storage configuration loader.
|
|
|
|
Reads from /etc/fester/storage.json by default, or from
|
|
FESTER_STORAGE_CONFIG env var. Falls back to sensible defaults
|
|
if the file doesn't exist or isn't readable.
|
|
"""
|
|
|
|
import json
|
|
import os
|
|
from pathlib import Path
|
|
|
|
|
|
DEFAULT_CONFIG_PATH = Path(
|
|
os.environ.get("FESTER_STORAGE_CONFIG", "/etc/fester/storage.json")
|
|
)
|
|
|
|
|
|
DEFAULT_CONFIG = {
|
|
"tmpfs": True,
|
|
"btrfs": False,
|
|
"qcow2_freeze": False,
|
|
"btrfs_path": "/var/lib/fester/cas",
|
|
"tmpfs_path": "/dev/shm/fester-build",
|
|
"qcow2_path": "/var/lib/fester/snapshots",
|
|
}
|
|
|
|
|
|
def _writable_path(p: Path) -> bool:
|
|
"""Check if a path is writable (or can be created)."""
|
|
try:
|
|
p.mkdir(parents=True, exist_ok=True)
|
|
# touch a test file
|
|
test = p / ".fester_write_test"
|
|
test.write_text("")
|
|
test.unlink()
|
|
return True
|
|
except (OSError, PermissionError):
|
|
return False
|
|
|
|
|
|
def _resolve_paths(cfg: dict) -> dict:
|
|
"""If a configured path isn't writable, fall back to user/tmp paths."""
|
|
fallbacks = {
|
|
"btrfs_path": [
|
|
Path.home() / ".fester" / "cas",
|
|
Path("/tmp/fester/cas"),
|
|
],
|
|
"tmpfs_path": [
|
|
Path.home() / ".fester" / "tmpfs",
|
|
Path("/tmp/fester/tmpfs"),
|
|
],
|
|
"qcow2_path": [
|
|
Path.home() / ".fester" / "snapshots",
|
|
Path("/tmp/fester/snapshots"),
|
|
],
|
|
}
|
|
for key, alts in fallbacks.items():
|
|
p = Path(cfg[key])
|
|
if not _writable_path(p):
|
|
for alt in alts:
|
|
if _writable_path(alt):
|
|
cfg[key] = str(alt)
|
|
break
|
|
return cfg
|
|
|
|
|
|
def load_storage_config() -> dict:
|
|
"""Load storage config with graceful fallbacks."""
|
|
cfg = DEFAULT_CONFIG.copy()
|
|
|
|
if DEFAULT_CONFIG_PATH.exists():
|
|
try:
|
|
with open(DEFAULT_CONFIG_PATH) as f:
|
|
user_cfg = json.load(f)
|
|
cfg.update(user_cfg)
|
|
except (json.JSONDecodeError, OSError):
|
|
pass
|
|
|
|
cfg = _resolve_paths(cfg)
|
|
return cfg
|
|
|
|
|
|
def save_storage_config(cfg: dict) -> bool:
|
|
"""Persist config to disk. Returns True on success."""
|
|
try:
|
|
DEFAULT_CONFIG_PATH.parent.mkdir(parents=True, exist_ok=True)
|
|
with open(DEFAULT_CONFIG_PATH, "w") as f:
|
|
json.dump(cfg, f, indent=2)
|
|
return True
|
|
except (OSError, PermissionError):
|
|
return False
|