f
This commit is contained in:
@@ -187,6 +187,21 @@ def _apply_conf_block(
|
||||
tool[tool_name] = dict(block)
|
||||
return
|
||||
|
||||
if kind_l == "networking":
|
||||
net_name = str(subtype).strip().lower()
|
||||
if not net_name:
|
||||
return
|
||||
net = config.setdefault("networking", {})
|
||||
if not isinstance(net, dict):
|
||||
config["networking"] = {}
|
||||
net = config["networking"]
|
||||
existing = net.get(net_name)
|
||||
if isinstance(existing, dict):
|
||||
_merge_dict_inplace(existing, block)
|
||||
else:
|
||||
net[net_name] = dict(block)
|
||||
return
|
||||
|
||||
|
||||
def parse_conf_text(text: str, *, base: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
||||
"""Parse a lightweight .conf format into the app's config dict.
|
||||
@@ -284,7 +299,7 @@ def _serialize_conf(config: Dict[str, Any]) -> str:
|
||||
|
||||
# Top-level scalars first
|
||||
for key in sorted(config.keys()):
|
||||
if key in {"store", "provider", "tool"}:
|
||||
if key in {"store", "provider", "tool", "networking"}:
|
||||
continue
|
||||
value = config.get(key)
|
||||
if isinstance(value, dict):
|
||||
@@ -351,6 +366,24 @@ def _serialize_conf(config: Dict[str, Any]) -> str:
|
||||
seen_keys.add(k_upper)
|
||||
lines.append(f"{k}={_format_conf_value(block.get(k))}")
|
||||
|
||||
# Networking blocks
|
||||
networking = config.get("networking")
|
||||
if isinstance(networking, dict):
|
||||
for name in sorted(networking.keys()):
|
||||
block = networking.get(name)
|
||||
if not isinstance(block, dict):
|
||||
continue
|
||||
lines.append("")
|
||||
lines.append(f"[networking={name}]")
|
||||
|
||||
seen_keys = set()
|
||||
for k in sorted(block.keys()):
|
||||
k_upper = k.upper()
|
||||
if k_upper in seen_keys:
|
||||
continue
|
||||
seen_keys.add(k_upper)
|
||||
lines.append(f"{k}={_format_conf_value(block.get(k))}")
|
||||
|
||||
return "\n".join(lines).rstrip() + "\n"
|
||||
|
||||
|
||||
|
||||
@@ -48,6 +48,13 @@ _PROVIDER_DEPENDENCIES: Dict[str, List[Tuple[str, str]]] = {
|
||||
"soulseek": [("aioslsk", "aioslsk>=1.6.0")],
|
||||
}
|
||||
|
||||
# Dependencies required when ZeroTier features are configured (auto-install when enabled)
|
||||
_ZEROTIER_DEPENDENCIES: List[Tuple[str, str]] = [
|
||||
("flask", "flask>=2.3.0"),
|
||||
("flask_cors", "flask-cors>=3.0.1"),
|
||||
("werkzeug", "werkzeug>=2.3.0"),
|
||||
]
|
||||
|
||||
|
||||
def florencevision_missing_modules() -> List[str]:
|
||||
return [
|
||||
@@ -144,5 +151,29 @@ def maybe_auto_install_configured_tools(config: Dict[str, Any]) -> None:
|
||||
label = f"{provider_name.title()} provider"
|
||||
_install_requirements(label, requirements)
|
||||
|
||||
# ZeroTier: if a zerotier section is present OR a zerotier store is configured,
|
||||
# optionally auto-install Flask-based remote server dependencies so the
|
||||
# `remote_storage_server.py` and CLI helper will run out-of-the-box.
|
||||
try:
|
||||
zerotier_cfg = (config or {}).get("zerotier")
|
||||
store_cfg = (config or {}).get("store") if isinstance(config, dict) else {}
|
||||
store_has_zerotier = isinstance(store_cfg, dict) and bool(store_cfg.get("zerotier"))
|
||||
|
||||
if (isinstance(zerotier_cfg, dict) and zerotier_cfg) or store_has_zerotier:
|
||||
auto_install = True
|
||||
if isinstance(zerotier_cfg, dict) and "auto_install" in zerotier_cfg:
|
||||
auto_install = _as_bool(zerotier_cfg.get("auto_install"), True)
|
||||
if auto_install:
|
||||
missing = [
|
||||
requirement
|
||||
for import_name, requirement in _ZEROTIER_DEPENDENCIES
|
||||
if not _try_import(import_name)
|
||||
]
|
||||
if missing:
|
||||
_install_requirements("ZeroTier", missing)
|
||||
except Exception:
|
||||
# Don't let optional-dep logic raise at startup
|
||||
pass
|
||||
|
||||
|
||||
__all__ = ["maybe_auto_install_configured_tools", "florencevision_missing_modules"]
|
||||
|
||||
Reference in New Issue
Block a user