"""Persistent node configuration — stored in ~/.config/meshnet/config.json.""" from __future__ import annotations import json import os import stat from pathlib import Path _DEFAULT_CONFIG_DIR = Path.home() / ".config" / "meshnet" _DEFAULT_CONFIG_FILE = _DEFAULT_CONFIG_DIR / "config.json" # MESHNET_DOWNLOAD_DIR overrides the model store for every node on this # machine (all CLI flows fall back to this default; --download-dir still wins). _DEFAULT_DOWNLOAD_DIR = Path( os.environ.get("MESHNET_DOWNLOAD_DIR", str(Path.home() / ".meshnet" / "models")) ) _DEFAULT_TRACKER_URL = "http://localhost:8080" _DEFAULT_WALLET_PATH = str(Path.home() / ".config" / "meshnet" / "wallet.json") _DEFAULT_QUANTIZATION = "nf4" DEFAULTS = { "model_hf_repo": "", "model_name": "", "quantization": _DEFAULT_QUANTIZATION, "download_dir": str(_DEFAULT_DOWNLOAD_DIR), "tracker_url": _DEFAULT_TRACKER_URL, "wallet_path": _DEFAULT_WALLET_PATH, "shard_start": None, "shard_end": None, "port": 7000, "host": "0.0.0.0", "debug": False, } def config_path(override: Path | None = None) -> Path: return override or _DEFAULT_CONFIG_FILE def load_config(path: Path | None = None) -> dict | None: """Return parsed config dict, or None if no config file exists.""" p = config_path(path) if not p.exists(): return None try: cfg = json.loads(p.read_text()) if not isinstance(cfg, dict): return None return cfg except (json.JSONDecodeError, OSError): return None def save_config(cfg: dict, path: Path | None = None) -> None: """Write config to disk with restricted permissions (0o600).""" p = config_path(path) p.parent.mkdir(parents=True, exist_ok=True) p.write_text(json.dumps(cfg, indent=2)) try: os.chmod(p, stat.S_IRUSR | stat.S_IWUSR) except OSError: pass # Windows / some filesystems don't support chmod def delete_config(path: Path | None = None) -> None: p = config_path(path) if p.exists(): p.unlink() def merge_cli_overrides(cfg: dict, **cli_kwargs) -> dict: """Return a copy of cfg with any non-None CLI values applied on top.""" result = dict(cfg) for key, val in cli_kwargs.items(): if val is not None: result[key] = val return result