"""Pair this Pi with the backend. Usage: python -m client.pair --backend https://backend.example.com [--code XYZ12345] Called from install.sh after it prompts the user for the code. """ import argparse import socket import requests from client.config import Config, save_config from client.log import get_logger _log = get_logger("pair") CLIENT_VERSION = "0.1.0" def pair(*, backend: str, code: str | None, hostname: str) -> None: backend = backend.rstrip("/") if not code: code = input("Pairing code: ").strip() if not code: raise SystemExit("no pairing code supplied") url = f"{backend}/api/pair" payload = {"code": code, "hostname": hostname, "client_version": CLIENT_VERSION} _log.info("POST %s code=%s hostname=%s", url, code, hostname) try: resp = requests.post(url, json=payload, timeout=30) except requests.RequestException as exc: raise SystemExit(f"pair request failed: {exc}") from exc if resp.status_code != 200: try: err = resp.json().get("error", resp.text) except ValueError: err = resp.text raise SystemExit(f"/api/pair returned {resp.status_code}: {err}") body = resp.json() cfg = Config( device_id=str(body["device_id"]), device_token=str(body["device_token"]), backend_ws=str(body["backend_ws"]), ) save_config(cfg) _log.info("paired device %s; config saved", cfg.device_id) def main() -> None: parser = argparse.ArgumentParser(description="Pair this Pi with the backend.") parser.add_argument("--backend", required=True, help="Backend base URL (https://...)") parser.add_argument("--code", default=None, help="Pairing code (prompted if omitted)") parser.add_argument( "--hostname", default=socket.gethostname(), help="Hostname reported to the backend (defaults to socket.gethostname())", ) args = parser.parse_args() pair(backend=args.backend, code=args.code, hostname=args.hostname) if __name__ == "__main__": main()