"""First-time machine setup: persist role + external paths + FERPA_DATA_ROOT. Writes two files: - paths.json (user-config dir, machine-scoped; OUTSIDE the repo) - .me-hub/local-state.json (in-repo, gitignored) The role declaration is explicit, never inferred from path existence. """ from __future__ import annotations import json import os import socket from pathlib import Path from typing import Optional from lib.local_state import SCHEMA_VERSION as LOCAL_STATE_SCHEMA_VERSION from lib.paths import default_paths_json_path, repo_root as _repo_root_real _PATHS_JSON_SCHEMA_VERSION = 1 class MissingMaintainerPathError(RuntimeError): pass class FerpaInsideRepoError(RuntimeError): pass def _paths_json_path() -> Path: return default_paths_json_path() def _local_state_path() -> Path: return _repo_root_real() / ".me-hub" / "local-state.json" def _repo_root() -> Path: return _repo_root_real() def configure( *, declared_role: str, box_togo_path: Optional[Path], tolk_technology_path: Optional[Path], ferpa_data_root: Path, machine: str, ) -> None: """Persist the configuration for this machine.""" if declared_role not in ("maintainer", "faculty"): raise ValueError( f"declared_role must be 'maintainer' or 'faculty', got {declared_role!r}" ) if declared_role == "maintainer": if box_togo_path is None or not box_togo_path.exists(): raise MissingMaintainerPathError("maintainer mode requires box_togo_path") if tolk_technology_path is None or not tolk_technology_path.exists(): raise MissingMaintainerPathError( "maintainer mode requires tolk_technology_path" ) repo = _repo_root() try: ferpa_data_root.resolve().relative_to(repo.resolve()) except ValueError: pass else: raise FerpaInsideRepoError( f"FERPA_DATA_ROOT must be OUTSIDE the working tree " f"(got {ferpa_data_root}, which is inside {repo})" ) ferpa_data_root.mkdir(parents=True, exist_ok=True) pj = _paths_json_path() pj.parent.mkdir(parents=True, exist_ok=True) pj.write_text( json.dumps( { "schema_version": _PATHS_JSON_SCHEMA_VERSION, "declared_role": declared_role, "box_togo_path": str(box_togo_path) if box_togo_path else None, "tolk_technology_path": str(tolk_technology_path) if tolk_technology_path else None, "ferpa_data_root": str(ferpa_data_root), }, indent=2, sort_keys=True, ), encoding="utf-8", ) ls = _local_state_path() ls.parent.mkdir(parents=True, exist_ok=True) ls.write_text( json.dumps( { "schema_version": LOCAL_STATE_SCHEMA_VERSION, "machine": machine, "declared_role": declared_role, "last_local_refresh_utc": None, "last_seen_state_commit": None, }, indent=2, sort_keys=True, ), encoding="utf-8", ) # Inform the maintainer about OPENAI_API_KEY (required for build-derived) if os.environ.get("OPENAI_API_KEY"): print("OK: OPENAI_API_KEY found in environment (will be used by build-derived).") else: print("") print("INFO: OPENAI_API_KEY not set. The build-derived pipeline requires it.") print(" - bash/zsh: export OPENAI_API_KEY=sk-...") print(" - powershell: $env:OPENAI_API_KEY = 'sk-...'") print(" - or set it in your shell profile so it persists across sessions.") # Inform the maintainer about ANTHROPIC_API_KEY (required for student-chat app) if os.environ.get("ANTHROPIC_API_KEY"): print("OK: ANTHROPIC_API_KEY found in environment (will be used by the chat app).") else: print("") print("INFO: ANTHROPIC_API_KEY not set. The student-chat application requires it.") print(" - bash/zsh: export ANTHROPIC_API_KEY=sk-ant-...") print(" - powershell: $env:ANTHROPIC_API_KEY = 'sk-ant-...'") print(" - or set it in your shell profile so it persists across sessions.") def detect_machine() -> str: """Return socket.gethostname(). Called by bootstrap.{ps1,sh} to populate `machine`.""" return socket.gethostname()