from __future__ import annotations import os import pwd import tempfile from dataclasses import dataclass from pathlib import Path @dataclass(frozen=True) class AppPaths: config_dir: Path state_dir: Path log_dir: Path report_dir: Path scenario_file: Path app_log_file: Path def _invoking_user() -> tuple[str, Path]: sudo_user = os.environ.get("SUDO_USER") if sudo_user: user_info = pwd.getpwnam(sudo_user) return sudo_user, Path(user_info.pw_dir) user = os.environ.get("USER", "root") home = Path.home() return user, home def build_paths() -> AppPaths: _, user_home = _invoking_user() config_home = Path(os.environ.get("XDG_CONFIG_HOME", user_home / ".config")) state_home = Path(os.environ.get("XDG_STATE_HOME", user_home / ".local" / "state")) config_dir = _select_writable_dir( [ config_home / "securecheck", Path.cwd() / ".securecheck-runtime" / "config", Path(tempfile.gettempdir()) / "securecheck" / "config", ] ) state_dir = _select_writable_dir( [ state_home / "securecheck", Path.cwd() / ".securecheck-runtime" / "state", Path(tempfile.gettempdir()) / "securecheck" / "state", ] ) if os.geteuid() == 0 and _is_path_writable(Path("/var/log")): log_dir = Path("/var/log/securecheck") else: log_dir = _select_writable_dir( [ state_dir / "logs", Path.cwd() / ".securecheck-runtime" / "logs", Path(tempfile.gettempdir()) / "securecheck" / "logs", ] ) report_dir = log_dir / "reports" scenario_file = config_dir / "scenarios.json" app_log_file = log_dir / "securecheck.log" return AppPaths( config_dir=config_dir, state_dir=state_dir, log_dir=log_dir, report_dir=report_dir, scenario_file=scenario_file, app_log_file=app_log_file, ) def ensure_app_dirs(paths: AppPaths) -> None: for directory in (paths.config_dir, paths.state_dir, paths.log_dir, paths.report_dir): directory.mkdir(parents=True, exist_ok=True) def _is_path_writable(path: Path) -> bool: target = path if path.exists() else path.parent return os.access(target, os.W_OK) def _select_writable_dir(candidates: list[Path]) -> Path: for candidate in candidates: try: candidate.mkdir(parents=True, exist_ok=True) probe = candidate / ".write-test" with probe.open("w", encoding="utf-8") as handle: handle.write("ok") probe.unlink() return candidate except OSError: continue raise OSError("Aucun emplacement inscriptible disponible pour SecureCheck")