fix(server, client): auto-resolve db, cert, state and config paths to ProgramData when running in read-only directories
This commit is contained in:
+44
-3
@@ -115,10 +115,40 @@ def get_tls_socket(hub_host: str, hub_port: int, cert_dir: str):
|
||||
return ctx.wrap_socket(raw_sock, server_hostname=hub_host)
|
||||
|
||||
|
||||
def get_default_data_dir(app_name: str = "LOGAR") -> str:
|
||||
"""Returns a suitable writable application data directory."""
|
||||
if sys.platform == "win32":
|
||||
prog_data = os.environ.get("ProgramData", r"C:\ProgramData")
|
||||
target = os.path.join(prog_data, app_name)
|
||||
cwd = os.getcwd()
|
||||
if os.path.exists(target) or "Program Files" in cwd or not os.access(cwd, os.W_OK):
|
||||
try:
|
||||
os.makedirs(target, exist_ok=True)
|
||||
return target
|
||||
except Exception:
|
||||
pass
|
||||
return os.getcwd()
|
||||
|
||||
|
||||
def resolve_writable_path(path: str, app_name: str = "LOGAR", default_filename: str = "client_state.json") -> str:
|
||||
"""Resolves a file or directory path, falling back to ProgramData if running in a read-only directory (e.g. Program Files)."""
|
||||
if not path:
|
||||
path = default_filename
|
||||
if os.path.isabs(path):
|
||||
return path
|
||||
cwd = os.getcwd()
|
||||
if sys.platform == "win32" and ("Program Files" in cwd or not os.access(cwd, os.W_OK)):
|
||||
data_dir = get_default_data_dir(app_name)
|
||||
return os.path.join(data_dir, path)
|
||||
return os.path.abspath(path)
|
||||
|
||||
|
||||
def get_state_path(config_path: str, custom_state_path: Optional[str] = None) -> str:
|
||||
if custom_state_path:
|
||||
return custom_state_path
|
||||
config_dir = os.path.dirname(os.path.abspath(config_path))
|
||||
if sys.platform == "win32" and ("Program Files" in config_dir or not os.access(config_dir, os.W_OK)):
|
||||
return os.path.join(get_default_data_dir("LOGAR"), STATE_FILE_NAME)
|
||||
return os.path.join(config_dir, STATE_FILE_NAME)
|
||||
|
||||
|
||||
@@ -135,6 +165,7 @@ def load_state(state_path: str) -> dict:
|
||||
|
||||
def save_state(state_path: str, state: dict):
|
||||
try:
|
||||
os.makedirs(os.path.dirname(os.path.abspath(state_path)), exist_ok=True)
|
||||
temp_path = f"{state_path}.tmp"
|
||||
with open(temp_path, "w", encoding="utf-8") as f:
|
||||
json.dump(state, f, indent=2)
|
||||
@@ -154,13 +185,23 @@ def commit_state(state: dict, state_path: str):
|
||||
|
||||
|
||||
def load_config(config_path: str = CONFIG_FILE_NAME):
|
||||
if not os.path.exists(config_path):
|
||||
actual_config_path = config_path
|
||||
if not os.path.exists(actual_config_path):
|
||||
data_dir_cfg = os.path.join(get_default_data_dir("LOGAR"), os.path.basename(config_path))
|
||||
if os.path.exists(data_dir_cfg):
|
||||
actual_config_path = data_dir_cfg
|
||||
|
||||
if not os.path.exists(actual_config_path):
|
||||
raise FileNotFoundError(
|
||||
f"Client configuration file not found at: {config_path}\n"
|
||||
f"Generate one from the server using: python Server.py --create-client-config --client-out {config_path}"
|
||||
)
|
||||
with open(config_path, "r", encoding="utf-8") as f:
|
||||
return json.load(f)
|
||||
with open(actual_config_path, "r", encoding="utf-8") as f:
|
||||
config = json.load(f)
|
||||
|
||||
if "cert_dir" in config:
|
||||
config["cert_dir"] = resolve_writable_path(config["cert_dir"], "LOGAR", "certs")
|
||||
return config
|
||||
|
||||
|
||||
def get_machine_identifier() -> str:
|
||||
|
||||
Reference in New Issue
Block a user