fix(server, client): auto-resolve db, cert, state and config paths to ProgramData when running in read-only directories
This commit is contained in:
+186
-122
@@ -384,11 +384,45 @@ def generate_server_keypair(server_name: str):
|
|||||||
return private_key_armored, public_key_armored, fingerprint
|
return private_key_armored, public_key_armored, fingerprint
|
||||||
|
|
||||||
|
|
||||||
|
def get_default_data_dir(app_name: str = "LOGAR-Server") -> 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-Server", default_filename: str = "logar_state.db") -> 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 load_or_init_config(config_path: str = CONFIG_FILE_NAME) -> Dict[str, Any]:
|
def load_or_init_config(config_path: str = CONFIG_FILE_NAME) -> Dict[str, Any]:
|
||||||
"""Loads existing server_config.json or creates a new one on first run."""
|
"""Loads existing server_config.json or creates a new one on first run."""
|
||||||
if os.path.exists(config_path):
|
actual_config_path = config_path
|
||||||
print(f"[*] Loading server configuration from: {os.path.abspath(config_path)}")
|
if not os.path.isabs(actual_config_path) and not os.path.exists(actual_config_path):
|
||||||
with open(config_path, "r", encoding="utf-8") as f:
|
data_dir_cfg = os.path.join(get_default_data_dir("LOGAR-Server"), os.path.basename(config_path))
|
||||||
|
if os.path.exists(data_dir_cfg):
|
||||||
|
actual_config_path = data_dir_cfg
|
||||||
|
|
||||||
|
if os.path.exists(actual_config_path):
|
||||||
|
print(f"[*] Loading server configuration from: {os.path.abspath(actual_config_path)}")
|
||||||
|
with open(actual_config_path, "r", encoding="utf-8") as f:
|
||||||
config = json.load(f)
|
config = json.load(f)
|
||||||
if "enrollment_secret" not in config:
|
if "enrollment_secret" not in config:
|
||||||
config["enrollment_secret"] = secrets.token_hex(24)
|
config["enrollment_secret"] = secrets.token_hex(24)
|
||||||
@@ -404,11 +438,22 @@ def load_or_init_config(config_path: str = CONFIG_FILE_NAME) -> Dict[str, Any]:
|
|||||||
config["admin_user"] = DEFAULT_ADMIN_USER
|
config["admin_user"] = DEFAULT_ADMIN_USER
|
||||||
if "session_secret" not in config:
|
if "session_secret" not in config:
|
||||||
config["session_secret"] = secrets.token_hex(32)
|
config["session_secret"] = secrets.token_hex(32)
|
||||||
|
if "db_path" not in config:
|
||||||
|
config["db_path"] = DEFAULT_DB_FILE
|
||||||
|
|
||||||
|
# Resolve relative db_path and cert_dir to writable paths
|
||||||
|
config["db_path"] = resolve_writable_path(config["db_path"], "LOGAR-Server", DEFAULT_DB_FILE)
|
||||||
|
config["cert_dir"] = resolve_writable_path(config["cert_dir"], "LOGAR-Server", "certs")
|
||||||
|
|
||||||
global SESSION_SECRET
|
global SESSION_SECRET
|
||||||
if not os.environ.get("LOGAR_SESSION_SECRET"):
|
if not os.environ.get("LOGAR_SESSION_SECRET"):
|
||||||
SESSION_SECRET = config["session_secret"]
|
SESSION_SECRET = config["session_secret"]
|
||||||
return config
|
return config
|
||||||
|
|
||||||
|
# Fallback to ProgramData if CWD is read-only / Program Files
|
||||||
|
if not os.path.isabs(config_path) and ("Program Files" in os.getcwd() or not os.access(os.getcwd(), os.W_OK)):
|
||||||
|
config_path = os.path.join(get_default_data_dir("LOGAR-Server"), os.path.basename(config_path))
|
||||||
|
|
||||||
print(f"[!] Config '{config_path}' not found. Initializing first-run configuration...")
|
print(f"[!] Config '{config_path}' not found. Initializing first-run configuration...")
|
||||||
server_name = "LOGAR-Cloud-Hub"
|
server_name = "LOGAR-Cloud-Hub"
|
||||||
private_key, public_key, fingerprint = generate_server_keypair(server_name)
|
private_key, public_key, fingerprint = generate_server_keypair(server_name)
|
||||||
@@ -416,6 +461,9 @@ def load_or_init_config(config_path: str = CONFIG_FILE_NAME) -> Dict[str, Any]:
|
|||||||
enrollment_secret = secrets.token_hex(24)
|
enrollment_secret = secrets.token_hex(24)
|
||||||
session_sec = secrets.token_hex(32)
|
session_sec = secrets.token_hex(32)
|
||||||
|
|
||||||
|
db_path = resolve_writable_path(DEFAULT_DB_FILE, "LOGAR-Server", DEFAULT_DB_FILE)
|
||||||
|
cert_dir = resolve_writable_path("certs", "LOGAR-Server", "certs")
|
||||||
|
|
||||||
config = {
|
config = {
|
||||||
"server_name": server_name,
|
"server_name": server_name,
|
||||||
"tcp_host": "0.0.0.0",
|
"tcp_host": "0.0.0.0",
|
||||||
@@ -428,9 +476,9 @@ def load_or_init_config(config_path: str = CONFIG_FILE_NAME) -> Dict[str, Any]:
|
|||||||
"session_secret": session_sec,
|
"session_secret": session_sec,
|
||||||
"max_seats": 10,
|
"max_seats": 10,
|
||||||
"license_server_url": "https://logar.eibl.tech",
|
"license_server_url": "https://logar.eibl.tech",
|
||||||
"cert_dir": "certs",
|
"cert_dir": cert_dir,
|
||||||
"tls_enabled": True,
|
"tls_enabled": True,
|
||||||
"db_path": DEFAULT_DB_FILE,
|
"db_path": db_path,
|
||||||
"evaluation_window_hours": EVALUATION_WINDOW_HOURS,
|
"evaluation_window_hours": EVALUATION_WINDOW_HOURS,
|
||||||
"min_persistence_runs": RUN_THRESHOLD,
|
"min_persistence_runs": RUN_THRESHOLD,
|
||||||
"server_fingerprint": fingerprint,
|
"server_fingerprint": fingerprint,
|
||||||
@@ -440,6 +488,7 @@ def load_or_init_config(config_path: str = CONFIG_FILE_NAME) -> Dict[str, Any]:
|
|||||||
if not os.environ.get("LOGAR_SESSION_SECRET"):
|
if not os.environ.get("LOGAR_SESSION_SECRET"):
|
||||||
SESSION_SECRET = session_sec
|
SESSION_SECRET = session_sec
|
||||||
|
|
||||||
|
os.makedirs(os.path.dirname(os.path.abspath(config_path)), exist_ok=True)
|
||||||
with open(config_path, "w", encoding="utf-8") as f:
|
with open(config_path, "w", encoding="utf-8") as f:
|
||||||
json.dump(config, f, indent=2)
|
json.dump(config, f, indent=2)
|
||||||
|
|
||||||
@@ -554,126 +603,141 @@ def format_relative_time(dt_str: Optional[str]) -> str:
|
|||||||
return dt_str[:16] if dt_str else "N/A"
|
return dt_str[:16] if dt_str else "N/A"
|
||||||
|
|
||||||
|
|
||||||
def init_db(db_path: str, enrollment_secret: Optional[str] = None, max_seats: int = 10):
|
def init_db(db_path: str, enrollment_secret: Optional[str] = None, max_seats: int = 10) -> str:
|
||||||
"""Initializes the SQLite schema for multi-run temporal tracking, client tracking, users, and license quota."""
|
"""Initializes the SQLite schema for multi-run temporal tracking, client tracking, users, and license quota."""
|
||||||
conn = sqlite3.connect(db_path)
|
def _execute_schema(target_path: str):
|
||||||
|
target_dir = os.path.dirname(os.path.abspath(target_path))
|
||||||
|
if target_dir:
|
||||||
|
os.makedirs(target_dir, exist_ok=True)
|
||||||
|
conn = sqlite3.connect(target_path)
|
||||||
|
|
||||||
# 1. Active Issues
|
# 1. Active Issues
|
||||||
conn.execute("""
|
|
||||||
CREATE TABLE IF NOT EXISTS active_issues (
|
|
||||||
fingerprint TEXT PRIMARY KEY,
|
|
||||||
site_name TEXT,
|
|
||||||
server TEXT,
|
|
||||||
signature TEXT,
|
|
||||||
severity TEXT,
|
|
||||||
message TEXT,
|
|
||||||
os_type TEXT,
|
|
||||||
first_seen TEXT,
|
|
||||||
last_seen TEXT,
|
|
||||||
run_count INTEGER,
|
|
||||||
status TEXT,
|
|
||||||
last_run_id TEXT
|
|
||||||
)
|
|
||||||
""")
|
|
||||||
|
|
||||||
# 2. Ingest Runs
|
|
||||||
conn.execute("""
|
|
||||||
CREATE TABLE IF NOT EXISTS ingest_runs (
|
|
||||||
run_id TEXT PRIMARY KEY,
|
|
||||||
site_name TEXT,
|
|
||||||
server TEXT,
|
|
||||||
timestamp TEXT,
|
|
||||||
log_count INTEGER
|
|
||||||
)
|
|
||||||
""")
|
|
||||||
|
|
||||||
# 3. License Config
|
|
||||||
conn.execute("""
|
|
||||||
CREATE TABLE IF NOT EXISTS license_config (
|
|
||||||
id INTEGER PRIMARY KEY CHECK (id = 1),
|
|
||||||
max_seats INTEGER NOT NULL DEFAULT 10,
|
|
||||||
enrollment_secret TEXT NOT NULL,
|
|
||||||
license_id TEXT,
|
|
||||||
license_server_url TEXT,
|
|
||||||
issued_at TEXT,
|
|
||||||
expires_at TEXT,
|
|
||||||
license_status TEXT DEFAULT 'active'
|
|
||||||
)
|
|
||||||
""")
|
|
||||||
|
|
||||||
# Migrations for license_config
|
|
||||||
for col, col_def in [
|
|
||||||
("license_id", "TEXT"),
|
|
||||||
("license_server_url", "TEXT"),
|
|
||||||
("issued_at", "TEXT"),
|
|
||||||
("expires_at", "TEXT"),
|
|
||||||
("license_status", "TEXT DEFAULT 'active'")
|
|
||||||
]:
|
|
||||||
try:
|
|
||||||
conn.execute(f"ALTER TABLE license_config ADD COLUMN {col} {col_def}")
|
|
||||||
except sqlite3.OperationalError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# 4. Clients Table
|
|
||||||
conn.execute("""
|
|
||||||
CREATE TABLE IF NOT EXISTS clients (
|
|
||||||
client_id TEXT PRIMARY KEY,
|
|
||||||
hostname TEXT NOT NULL,
|
|
||||||
os_type TEXT NOT NULL,
|
|
||||||
cert_fingerprint TEXT NOT NULL,
|
|
||||||
status TEXT DEFAULT 'active',
|
|
||||||
first_seen TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
last_seen TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
||||||
)
|
|
||||||
""")
|
|
||||||
|
|
||||||
# 5. Users Table
|
|
||||||
conn.execute("""
|
|
||||||
CREATE TABLE IF NOT EXISTS users (
|
|
||||||
username TEXT PRIMARY KEY,
|
|
||||||
password_hash TEXT NOT NULL,
|
|
||||||
salt TEXT NOT NULL,
|
|
||||||
role TEXT NOT NULL DEFAULT 'admin',
|
|
||||||
created_at TEXT NOT NULL
|
|
||||||
)
|
|
||||||
""")
|
|
||||||
|
|
||||||
# Ensure default admin user exists
|
|
||||||
c = conn.cursor()
|
|
||||||
c.execute("SELECT COUNT(*) FROM users")
|
|
||||||
if c.fetchone()[0] == 0:
|
|
||||||
now_iso = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S UTC")
|
|
||||||
pwd_hash, salt = hash_password(DEFAULT_ADMIN_PASS)
|
|
||||||
conn.execute("INSERT INTO users (username, password_hash, salt, role, created_at) VALUES (?, ?, ?, 'admin', ?)",
|
|
||||||
(DEFAULT_ADMIN_USER, pwd_hash, salt, now_iso))
|
|
||||||
|
|
||||||
# 6. Hermes Tokens Table
|
|
||||||
conn.execute("""
|
|
||||||
CREATE TABLE IF NOT EXISTS hermes_tokens (
|
|
||||||
token TEXT PRIMARY KEY,
|
|
||||||
name TEXT NOT NULL,
|
|
||||||
created_at TEXT NOT NULL,
|
|
||||||
last_used TEXT,
|
|
||||||
status TEXT NOT NULL DEFAULT 'active'
|
|
||||||
)
|
|
||||||
""")
|
|
||||||
|
|
||||||
# Auto-initialize initial Hermes token if table is empty
|
|
||||||
c.execute("SELECT COUNT(*) FROM hermes_tokens")
|
|
||||||
if c.fetchone()[0] == 0:
|
|
||||||
initial_token = f"hermes_live_{secrets.token_hex(20)}"
|
|
||||||
now_iso = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S UTC")
|
|
||||||
conn.execute("INSERT INTO hermes_tokens (token, name, created_at, status) VALUES (?, 'Default Hermes Agent', ?, 'active')",
|
|
||||||
(initial_token, now_iso))
|
|
||||||
|
|
||||||
if enrollment_secret:
|
|
||||||
conn.execute("""
|
conn.execute("""
|
||||||
INSERT OR IGNORE INTO license_config (id, max_seats, enrollment_secret)
|
CREATE TABLE IF NOT EXISTS active_issues (
|
||||||
VALUES (1, ?, ?)
|
fingerprint TEXT PRIMARY KEY,
|
||||||
""", (max_seats, enrollment_secret))
|
site_name TEXT,
|
||||||
|
server TEXT,
|
||||||
|
signature TEXT,
|
||||||
|
severity TEXT,
|
||||||
|
message TEXT,
|
||||||
|
os_type TEXT,
|
||||||
|
first_seen TEXT,
|
||||||
|
last_seen TEXT,
|
||||||
|
run_count INTEGER,
|
||||||
|
status TEXT,
|
||||||
|
last_run_id TEXT
|
||||||
|
)
|
||||||
|
""")
|
||||||
|
|
||||||
conn.commit()
|
# 2. Ingest Runs
|
||||||
conn.close()
|
conn.execute("""
|
||||||
|
CREATE TABLE IF NOT EXISTS ingest_runs (
|
||||||
|
run_id TEXT PRIMARY KEY,
|
||||||
|
site_name TEXT,
|
||||||
|
server TEXT,
|
||||||
|
timestamp TEXT,
|
||||||
|
log_count INTEGER
|
||||||
|
)
|
||||||
|
""")
|
||||||
|
|
||||||
|
# 3. License Config
|
||||||
|
conn.execute("""
|
||||||
|
CREATE TABLE IF NOT EXISTS license_config (
|
||||||
|
id INTEGER PRIMARY KEY CHECK (id = 1),
|
||||||
|
max_seats INTEGER NOT NULL DEFAULT 10,
|
||||||
|
enrollment_secret TEXT NOT NULL,
|
||||||
|
license_id TEXT,
|
||||||
|
license_server_url TEXT,
|
||||||
|
issued_at TEXT,
|
||||||
|
expires_at TEXT,
|
||||||
|
license_status TEXT DEFAULT 'active'
|
||||||
|
)
|
||||||
|
""")
|
||||||
|
|
||||||
|
# Migrations for license_config
|
||||||
|
for col, col_def in [
|
||||||
|
("license_id", "TEXT"),
|
||||||
|
("license_server_url", "TEXT"),
|
||||||
|
("issued_at", "TEXT"),
|
||||||
|
("expires_at", "TEXT"),
|
||||||
|
("license_status", "TEXT DEFAULT 'active'")
|
||||||
|
]:
|
||||||
|
try:
|
||||||
|
conn.execute(f"ALTER TABLE license_config ADD COLUMN {col} {col_def}")
|
||||||
|
except sqlite3.OperationalError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# 4. Clients Table
|
||||||
|
conn.execute("""
|
||||||
|
CREATE TABLE IF NOT EXISTS clients (
|
||||||
|
client_id TEXT PRIMARY KEY,
|
||||||
|
hostname TEXT NOT NULL,
|
||||||
|
os_type TEXT NOT NULL,
|
||||||
|
cert_fingerprint TEXT NOT NULL,
|
||||||
|
status TEXT DEFAULT 'active',
|
||||||
|
first_seen TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
last_seen TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
|
)
|
||||||
|
""")
|
||||||
|
|
||||||
|
# 5. Users Table
|
||||||
|
conn.execute("""
|
||||||
|
CREATE TABLE IF NOT EXISTS users (
|
||||||
|
username TEXT PRIMARY KEY,
|
||||||
|
password_hash TEXT NOT NULL,
|
||||||
|
salt TEXT NOT NULL,
|
||||||
|
role TEXT NOT NULL DEFAULT 'admin',
|
||||||
|
created_at TEXT NOT NULL
|
||||||
|
)
|
||||||
|
""")
|
||||||
|
|
||||||
|
# Ensure default admin user exists
|
||||||
|
c = conn.cursor()
|
||||||
|
c.execute("SELECT COUNT(*) FROM users")
|
||||||
|
if c.fetchone()[0] == 0:
|
||||||
|
now_iso = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S UTC")
|
||||||
|
pwd_hash, salt = hash_password(DEFAULT_ADMIN_PASS)
|
||||||
|
conn.execute("INSERT INTO users (username, password_hash, salt, role, created_at) VALUES (?, ?, ?, 'admin', ?)",
|
||||||
|
(DEFAULT_ADMIN_USER, pwd_hash, salt, now_iso))
|
||||||
|
|
||||||
|
# 6. Hermes Tokens Table
|
||||||
|
conn.execute("""
|
||||||
|
CREATE TABLE IF NOT EXISTS hermes_tokens (
|
||||||
|
token TEXT PRIMARY KEY,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
created_at TEXT NOT NULL,
|
||||||
|
last_used TEXT,
|
||||||
|
status TEXT NOT NULL DEFAULT 'active'
|
||||||
|
)
|
||||||
|
""")
|
||||||
|
|
||||||
|
# Auto-initialize initial Hermes token if table is empty
|
||||||
|
c.execute("SELECT COUNT(*) FROM hermes_tokens")
|
||||||
|
if c.fetchone()[0] == 0:
|
||||||
|
initial_token = f"hermes_live_{secrets.token_hex(20)}"
|
||||||
|
now_iso = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S UTC")
|
||||||
|
conn.execute("INSERT INTO hermes_tokens (token, name, created_at, status) VALUES (?, 'Default Hermes Agent', ?, 'active')",
|
||||||
|
(initial_token, now_iso))
|
||||||
|
|
||||||
|
if enrollment_secret:
|
||||||
|
conn.execute("""
|
||||||
|
INSERT OR IGNORE INTO license_config (id, max_seats, enrollment_secret)
|
||||||
|
VALUES (1, ?, ?)
|
||||||
|
""", (max_seats, enrollment_secret))
|
||||||
|
|
||||||
|
conn.commit()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
try:
|
||||||
|
_execute_schema(db_path)
|
||||||
|
return db_path
|
||||||
|
except (sqlite3.OperationalError, PermissionError, OSError) as e:
|
||||||
|
if "readonly" in str(e).lower() or "permission" in str(e).lower() or isinstance(e, PermissionError):
|
||||||
|
fallback_path = os.path.join(get_default_data_dir("LOGAR-Server"), "logar_state.db")
|
||||||
|
print(f"[!] Warning: Database path '{db_path}' is not writable ({e}). Falling back to '{fallback_path}'...")
|
||||||
|
_execute_schema(fallback_path)
|
||||||
|
return fallback_path
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
def export_license_request_lic(config: Dict[str, Any]) -> Tuple[Dict[str, Any], str]:
|
def export_license_request_lic(config: Dict[str, Any]) -> Tuple[Dict[str, Any], str]:
|
||||||
@@ -2601,7 +2665,7 @@ def main():
|
|||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
config = load_or_init_config(args.config)
|
config = load_or_init_config(args.config)
|
||||||
init_db(
|
config["db_path"] = init_db(
|
||||||
config["db_path"],
|
config["db_path"],
|
||||||
enrollment_secret=config.get("enrollment_secret"),
|
enrollment_secret=config.get("enrollment_secret"),
|
||||||
max_seats=config.get("max_seats", 10)
|
max_seats=config.get("max_seats", 10)
|
||||||
|
|||||||
+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)
|
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:
|
def get_state_path(config_path: str, custom_state_path: Optional[str] = None) -> str:
|
||||||
if custom_state_path:
|
if custom_state_path:
|
||||||
return custom_state_path
|
return custom_state_path
|
||||||
config_dir = os.path.dirname(os.path.abspath(config_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)
|
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):
|
def save_state(state_path: str, state: dict):
|
||||||
try:
|
try:
|
||||||
|
os.makedirs(os.path.dirname(os.path.abspath(state_path)), exist_ok=True)
|
||||||
temp_path = f"{state_path}.tmp"
|
temp_path = f"{state_path}.tmp"
|
||||||
with open(temp_path, "w", encoding="utf-8") as f:
|
with open(temp_path, "w", encoding="utf-8") as f:
|
||||||
json.dump(state, f, indent=2)
|
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):
|
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(
|
raise FileNotFoundError(
|
||||||
f"Client configuration file not found at: {config_path}\n"
|
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}"
|
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:
|
with open(actual_config_path, "r", encoding="utf-8") as f:
|
||||||
return json.load(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:
|
def get_machine_identifier() -> str:
|
||||||
|
|||||||
Reference in New Issue
Block a user