"""Paths: project root, local .gpu-rent runtime, SSH key.""" from __future__ import annotations import os import shutil from pathlib import Path def detect_app_root() -> Path: override = (os.environ.get("GPU_RENT_ROOT") or "").strip() if override: return Path(override).expanduser().resolve() cwd = Path.cwd().resolve() for candidate in (cwd, *cwd.parents): if (candidate / "Models").is_dir() and (candidate / "docs").is_dir(): return candidate return cwd def app_root() -> Path: return detect_app_root() def runtime_dir() -> Path: """State, lock, SSH key — always inside the project (gitignored).""" return app_root() / ".gpu-rent" def local_lease_path() -> Path: return runtime_dir() / "local-lease.json" def local_watchdog_marker_path() -> Path: return runtime_dir() / "local-watchdog.json" # Back-compat alias used across the package. def home_dir() -> Path: return runtime_dir() def env_path() -> Path: return app_root() / ".env" def models_manifest_path() -> Path: return app_root() / "models.yaml" def ollama_models_manifest_path() -> Path: return app_root() / "ollama-models.yaml" def ollama_models_example_path() -> Path: return app_root() / "ollama-models.example.yaml" def extensions_manifest_path() -> Path: return app_root() / "extensions.yaml" def vars_path() -> Path: return app_root() / "gpu-rent.vars" def vars_example_path() -> Path: return app_root() / "gpu-rent.vars.example" def state_path() -> Path: return runtime_dir() / "state.json" def lock_path() -> Path: return runtime_dir() / "gpu-rent.lock" def default_ssh_key_path() -> Path: return runtime_dir() / "id_ed25519" def legacy_user_dir() -> Path: """Old location (~/.gpu-rent) — only for one-shot migrate.""" return Path.home() / ".gpu-rent" def migrate_legacy_if_needed() -> list[str]: """Copy ~/.gpu-rent leftovers into the project once. Returns log lines.""" legacy = legacy_user_dir() if not legacy.is_dir(): return [] root = app_root() runtime = runtime_dir() notes: list[str] = [] pairs = ( (legacy / ".env", env_path()), (legacy / "models.yaml", models_manifest_path()), (legacy / "extensions.yaml", extensions_manifest_path()), (legacy / "state.json", state_path()), (legacy / "id_ed25519", default_ssh_key_path()), (legacy / "id_ed25519.pub", default_ssh_key_path().with_suffix(".pub")), ) for src, dst in pairs: if not src.is_file() or dst.is_file(): continue dst.parent.mkdir(parents=True, exist_ok=True) shutil.copy2(src, dst) notes.append(f"перенёс {src} → {dst}") if notes: runtime.mkdir(parents=True, exist_ok=True) marker = runtime / ".migrated-from-user-home" if not marker.is_file(): marker.write_text( f"from {legacy}\n" + "\n".join(notes) + "\n", encoding="utf-8", ) return notes