From cbcd097214119daa02907111f4ec5a9d4d931020 Mon Sep 17 00:00:00 2001 From: tegwick Date: Mon, 15 Jun 2026 09:02:02 +0200 Subject: [PATCH] Align naming with coulomb.social reuse-surface conventions Use reuse.coulomb.social, REUSE_SURFACE_URL/TOKEN env vars, reuse-surface image and reuse-surface-env secret. Replace reuse-surface-hub entrypoint with reuse-surface serve; CLI uses --base-url. --- AGENTS.md | 8 ++-- Dockerfile | 10 ++--- docs/deploy/hub-kubernetes.md | 32 --------------- docs/deploy/reuse-kubernetes.md | 39 +++++++++++++++++++ pyproject.toml | 1 - reuse_surface/cli.py | 30 +++++++++----- reuse_surface/hub/app.py | 14 +++---- reuse_surface/hub/compose.py | 2 +- reuse_surface/hub_client.py | 28 ++++++------- specs/FederationHubAPI.md | 25 ++++++------ tests/test_hub.py | 6 +-- tools/README.md | 6 +-- ...E-WP-0011-federation-hub-on-railiance01.md | 19 ++++----- 13 files changed, 120 insertions(+), 100 deletions(-) delete mode 100644 docs/deploy/hub-kubernetes.md create mode 100644 docs/deploy/reuse-kubernetes.md diff --git a/AGENTS.md b/AGENTS.md index bda144f..dd73eed 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -130,11 +130,11 @@ artifacts. .venv/bin/reuse-surface federation compose .venv/bin/reuse-surface graph --check -# Federation hub service (local) -# REUSE_SURFACE_HUB_TOKEN=dev-token reuse-surface-hub +# Federation service (local) +# REUSE_SURFACE_TOKEN=dev-token reuse-surface serve -# Hub CLI (against deployed or local hub) -# REUSE_SURFACE_HUB_URL=http://127.0.0.1:8000 reuse-surface hub status +# Hub CLI (against deployed or local service) +# REUSE_SURFACE_URL=http://127.0.0.1:8000 reuse-surface hub status # Automated tests .venv/bin/pytest -q diff --git a/Dockerfile b/Dockerfile index e7a8aab..5ba1a3d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,11 +8,11 @@ COPY schemas ./schemas RUN pip install --no-cache-dir . -ENV REUSE_SURFACE_HUB_HOST=0.0.0.0 -ENV REUSE_SURFACE_HUB_PORT=8000 -ENV REUSE_SURFACE_HUB_DB=/data/hub.db -ENV REUSE_SURFACE_HUB_CACHE_DIR=/data/cache +ENV REUSE_SURFACE_HOST=0.0.0.0 +ENV REUSE_SURFACE_PORT=8000 +ENV REUSE_SURFACE_DB=/data/reuse.db +ENV REUSE_SURFACE_CACHE_DIR=/data/cache EXPOSE 8000 -CMD ["reuse-surface-hub"] \ No newline at end of file +CMD ["reuse-surface", "serve"] \ No newline at end of file diff --git a/docs/deploy/hub-kubernetes.md b/docs/deploy/hub-kubernetes.md deleted file mode 100644 index 37964ec..0000000 --- a/docs/deploy/hub-kubernetes.md +++ /dev/null @@ -1,32 +0,0 @@ -# Federation Hub — Kubernetes Deployment - -Companion to **RAILIANCE-WP-0007** (`railiance-apps` Helm release). - -## Image - -```bash -docker build -t gitea.coulomb.social/coulomb/reuse-surface-hub: . -docker push gitea.coulomb.social/coulomb/reuse-surface-hub: -``` - -## Required environment - -| Variable | Purpose | -|---|---| -| `REUSE_SURFACE_HUB_TOKEN` | Bearer token for write API | -| `REUSE_SURFACE_HUB_DB` | SQLite path (default `/data/hub.db`) | -| `REUSE_SURFACE_HUB_CACHE_DIR` | Remote index cache (default `/data/cache`) | - -Mount a PVC at `/data` for persistence. - -## Probes - -- Liveness/readiness: `GET /health` on port `8000` - -## Client configuration - -```bash -export REUSE_SURFACE_HUB_URL=https://reuse-hub.whywhynot.de -export REUSE_SURFACE_HUB_TOKEN= -reuse-surface hub status -``` \ No newline at end of file diff --git a/docs/deploy/reuse-kubernetes.md b/docs/deploy/reuse-kubernetes.md new file mode 100644 index 0000000..fa58758 --- /dev/null +++ b/docs/deploy/reuse-kubernetes.md @@ -0,0 +1,39 @@ +# reuse-surface Service — Kubernetes Deployment + +Companion to **RAILIANCE-WP-0007** (`railiance-apps` Helm release). + +## Image + +Repository: `gitea.coulomb.social/coulomb/reuse-surface` (Gitea org `coulomb`, repo `reuse-surface`). + +```bash +docker build -t gitea.coulomb.social/coulomb/reuse-surface: . +docker push gitea.coulomb.social/coulomb/reuse-surface: +``` + +## Required environment + +| Variable | Purpose | +|---|---| +| `REUSE_SURFACE_TOKEN` | Bearer token for write API | +| `REUSE_SURFACE_DB` | SQLite path (default `/data/reuse.db`) | +| `REUSE_SURFACE_CACHE_DIR` | Remote index cache (default `/data/cache`) | + +Mount a PVC at `/data` for persistence. Inject secrets via Kubernetes Secret +`reuse-surface-env`. + +## Probes + +- Liveness/readiness: `GET /health` on port `8000` + +## Public URL + +`https://reuse.coulomb.social` + +## Client configuration + +```bash +export REUSE_SURFACE_URL=https://reuse.coulomb.social +export REUSE_SURFACE_TOKEN= +reuse-surface hub status +``` \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 1c0c586..50ead13 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,7 +23,6 @@ dev = [ [project.scripts] reuse-surface = "reuse_surface.cli:main" -reuse-surface-hub = "reuse_surface.hub.app:main" [tool.setuptools.packages.find] where = ["."] diff --git a/reuse_surface/cli.py b/reuse_surface/cli.py index 8bc46c1..e2e38c0 100644 --- a/reuse_surface/cli.py +++ b/reuse_surface/cli.py @@ -192,13 +192,20 @@ def cmd_catalog(args: argparse.Namespace) -> int: return 0 -def _hub_url(args: argparse.Namespace) -> str | None: - return getattr(args, "hub_url", None) +def _service_url(args: argparse.Namespace) -> str | None: + return getattr(args, "base_url", None) + + +def cmd_serve(args: argparse.Namespace) -> int: + from reuse_surface.hub.app import main as serve_main + + serve_main() + return 0 def cmd_hub_status(args: argparse.Namespace) -> int: try: - status, payload = hub_client.hub_status(_hub_url(args)) + status, payload = hub_client.hub_status(_service_url(args)) except ValueError as exc: print(f"error: {exc}", file=sys.stderr) return 1 @@ -211,7 +218,7 @@ def cmd_hub_status(args: argparse.Namespace) -> int: def cmd_hub_list(args: argparse.Namespace) -> int: try: - status, payload = hub_client.hub_list(_hub_url(args)) + status, payload = hub_client.hub_list(_service_url(args)) except ValueError as exc: print(f"error: {exc}", file=sys.stderr) return 1 @@ -227,7 +234,7 @@ def cmd_hub_list(args: argparse.Namespace) -> int: def cmd_hub_show(args: argparse.Namespace) -> int: try: - status, payload = hub_client.hub_show(args.repo, _hub_url(args)) + status, payload = hub_client.hub_show(args.repo, _service_url(args)) except ValueError as exc: print(f"error: {exc}", file=sys.stderr) return 1 @@ -249,7 +256,7 @@ def cmd_hub_register(args: argparse.Namespace) -> int: if args.description: body["description"] = args.description try: - status, payload = hub_client.hub_register(body, _hub_url(args)) + status, payload = hub_client.hub_register(body, _service_url(args)) except ValueError as exc: print(f"error: {exc}", file=sys.stderr) return 1 @@ -276,7 +283,7 @@ def cmd_hub_update(args: argparse.Namespace) -> int: print("error: no fields to update", file=sys.stderr) return 1 try: - status, payload = hub_client.hub_update(args.repo, body, _hub_url(args)) + status, payload = hub_client.hub_update(args.repo, body, _service_url(args)) except ValueError as exc: print(f"error: {exc}", file=sys.stderr) return 1 @@ -412,10 +419,13 @@ def main(argv: list[str] | None = None) -> int: ) graph.set_defaults(func=cmd_graph) - hub = subparsers.add_parser("hub", help="federation hub client") + serve = subparsers.add_parser("serve", help="run federation service API") + serve.set_defaults(func=cmd_serve) + + hub = subparsers.add_parser("hub", help="federation service client") hub.add_argument( - "--hub-url", - help="hub base URL (or set REUSE_SURFACE_HUB_URL)", + "--base-url", + help="service base URL (or set REUSE_SURFACE_URL)", ) hub_sub = hub.add_subparsers(dest="hub_command", required=True) diff --git a/reuse_surface/hub/app.py b/reuse_surface/hub/app.py index e4f6fbb..c98cbf2 100644 --- a/reuse_surface/hub/app.py +++ b/reuse_surface/hub/app.py @@ -15,15 +15,15 @@ HUB_VERSION = "0.1.0" def _db_path() -> Path: - return Path(os.environ.get("REUSE_SURFACE_HUB_DB", "/data/hub.db")) + return Path(os.environ.get("REUSE_SURFACE_DB", "/data/reuse.db")) def _cache_dir() -> Path: - return Path(os.environ.get("REUSE_SURFACE_HUB_CACHE_DIR", "/data/cache")) + return Path(os.environ.get("REUSE_SURFACE_CACHE_DIR", "/data/cache")) def _write_token() -> str: - return os.environ.get("REUSE_SURFACE_HUB_TOKEN", "") + return os.environ.get("REUSE_SURFACE_TOKEN", "") def _store() -> HubStore: @@ -41,7 +41,7 @@ def _require_auth(authorization: str | None = Header(default=None)) -> None: write_token = _write_token() if not write_token: raise _http_error( - 503, "misconfigured", "REUSE_SURFACE_HUB_TOKEN is not configured" + 503, "misconfigured", "REUSE_SURFACE_TOKEN is not configured" ) if not authorization or not authorization.startswith("Bearer "): raise _http_error(401, "unauthorized", "Bearer token required") @@ -56,7 +56,7 @@ def create_app() -> FastAPI: @app.get("/health") def health() -> dict[str, str]: - return {"status": "ok", "service": "reuse-surface-hub", "version": HUB_VERSION} + return {"status": "ok", "service": "reuse-surface", "version": HUB_VERSION} @app.get("/v1/repos") def list_repos() -> dict[str, Any]: @@ -138,6 +138,6 @@ def create_app() -> FastAPI: def main() -> None: import uvicorn - host = os.environ.get("REUSE_SURFACE_HUB_HOST", "0.0.0.0") - port = int(os.environ.get("REUSE_SURFACE_HUB_PORT", "8000")) + host = os.environ.get("REUSE_SURFACE_HOST", "0.0.0.0") + port = int(os.environ.get("REUSE_SURFACE_PORT", "8000")) uvicorn.run(create_app(), host=host, port=port, reload=False) \ No newline at end of file diff --git a/reuse_surface/hub/compose.py b/reuse_surface/hub/compose.py index 4e642ea..fda667e 100644 --- a/reuse_surface/hub/compose.py +++ b/reuse_surface/hub/compose.py @@ -7,7 +7,7 @@ from typing import Any from reuse_surface.federation import compose_federated_index from reuse_surface.hub.store import HubStore -DEFAULT_DOMAIN = os.environ.get("REUSE_SURFACE_HUB_DOMAIN", "helix_forge") +DEFAULT_DOMAIN = os.environ.get("REUSE_SURFACE_DOMAIN", "helix_forge") def registrations_to_manifest( diff --git a/reuse_surface/hub_client.py b/reuse_surface/hub_client.py index eee326b..7d900c3 100644 --- a/reuse_surface/hub_client.py +++ b/reuse_surface/hub_client.py @@ -7,17 +7,17 @@ import urllib.request from typing import Any -def hub_base_url(explicit: str | None = None) -> str: - base = (explicit or os.environ.get("REUSE_SURFACE_HUB_URL", "")).rstrip("/") +def service_base_url(explicit: str | None = None) -> str: + base = (explicit or os.environ.get("REUSE_SURFACE_URL", "")).rstrip("/") if not base: raise ValueError( - "hub URL not configured; set REUSE_SURFACE_HUB_URL or pass --hub-url" + "service URL not configured; set REUSE_SURFACE_URL or pass --base-url" ) return base -def hub_token() -> str | None: - return os.environ.get("REUSE_SURFACE_HUB_TOKEN") +def service_token() -> str | None: + return os.environ.get("REUSE_SURFACE_TOKEN") def _request( @@ -49,24 +49,24 @@ def _request( def hub_status(base_url: str | None = None) -> tuple[int, Any]: - return _request("GET", f"{hub_base_url(base_url)}/health") + return _request("GET", f"{service_base_url(base_url)}/health") def hub_list(base_url: str | None = None) -> tuple[int, Any]: - return _request("GET", f"{hub_base_url(base_url)}/v1/repos") + return _request("GET", f"{service_base_url(base_url)}/v1/repos") def hub_show(repo: str, base_url: str | None = None) -> tuple[int, Any]: - return _request("GET", f"{hub_base_url(base_url)}/v1/repos/{repo}") + return _request("GET", f"{service_base_url(base_url)}/v1/repos/{repo}") def hub_register(payload: dict[str, Any], base_url: str | None = None) -> tuple[int, Any]: - token = hub_token() + token = service_token() if not token: - raise ValueError("REUSE_SURFACE_HUB_TOKEN is required for register") + raise ValueError("REUSE_SURFACE_TOKEN is required for register") return _request( "POST", - f"{hub_base_url(base_url)}/v1/repos", + f"{service_base_url(base_url)}/v1/repos", token=token, body=payload, ) @@ -75,12 +75,12 @@ def hub_register(payload: dict[str, Any], base_url: str | None = None) -> tuple[ def hub_update( repo: str, payload: dict[str, Any], base_url: str | None = None ) -> tuple[int, Any]: - token = hub_token() + token = service_token() if not token: - raise ValueError("REUSE_SURFACE_HUB_TOKEN is required for update") + raise ValueError("REUSE_SURFACE_TOKEN is required for update") return _request( "PATCH", - f"{hub_base_url(base_url)}/v1/repos/{repo}", + f"{service_base_url(base_url)}/v1/repos/{repo}", token=token, body=payload, ) \ No newline at end of file diff --git a/specs/FederationHubAPI.md b/specs/FederationHubAPI.md index 7f63615..0367206 100644 --- a/specs/FederationHubAPI.md +++ b/specs/FederationHubAPI.md @@ -21,7 +21,7 @@ Companion deployment workplan: `railiance-apps` **RAILIANCE-WP-0007**. | Item | Value | |---|---| -| Default production URL | `https://reuse-hub.whywhynot.de` (confirm at deploy) | +| Default production URL | `https://reuse.coulomb.social` | | API prefix | `/v1` | | Read formats | JSON (default), YAML via `Accept: application/yaml` or `?format=yaml` | | Write content type | `application/json` | @@ -30,8 +30,8 @@ Environment variables for clients: | Variable | Purpose | |---|---| -| `REUSE_SURFACE_HUB_URL` | Hub base URL (no trailing slash) | -| `REUSE_SURFACE_HUB_TOKEN` | Bearer token for write operations | +| `REUSE_SURFACE_URL` | Service base URL (no trailing slash) | +| `REUSE_SURFACE_TOKEN` | Bearer token for write operations | --- @@ -45,7 +45,7 @@ Environment variables for clients: Write requests must include: ```http -Authorization: Bearer +Authorization: Bearer ``` Missing or invalid token → `401 Unauthorized`. @@ -88,7 +88,7 @@ Liveness/readiness probe. ```json { "status": "ok", - "service": "reuse-surface-hub", + "service": "reuse-surface", "version": "0.1.0" } ``` @@ -232,10 +232,10 @@ Non-2xx responses use: | Env var | Required | Purpose | |---|---|---| -| `REUSE_SURFACE_HUB_TOKEN` | yes | Write API bearer token | -| `REUSE_SURFACE_HUB_DB` | no | SQLite path (default `/data/hub.db`) | -| `REUSE_SURFACE_HUB_CACHE_DIR` | no | Remote index cache (default `/data/cache`) | -| `REUSE_SURFACE_HUB_DOMAIN` | no | Default federated `domain` (default `helix_forge`) | +| `REUSE_SURFACE_TOKEN` | yes | Write API bearer token | +| `REUSE_SURFACE_DB` | no | SQLite path (default `/data/reuse.db`) | +| `REUSE_SURFACE_CACHE_DIR` | no | Remote index cache (default `/data/cache`) | +| `REUSE_SURFACE_DOMAIN` | no | Default federated `domain` (default `helix_forge`) | --- @@ -249,13 +249,16 @@ Non-2xx responses use: | `reuse-surface hub register ...` | `POST /v1/repos` | | `reuse-surface hub update ...` | `PATCH /v1/repos/{repo}` | -Global flags: `--hub-url`, env `REUSE_SURFACE_HUB_URL`, `REUSE_SURFACE_HUB_TOKEN`. +Run locally: `reuse-surface serve`. Global client flags: `--base-url`, env +`REUSE_SURFACE_URL`, `REUSE_SURFACE_TOKEN`. --- ## 9. Deployment reference -- Image: `gitea.coulomb.social/coulomb/reuse-surface-hub:` +- Image: `gitea.coulomb.social/coulomb/reuse-surface:` +- Public URL: `https://reuse.coulomb.social` +- Secret: `reuse-surface-env` with `REUSE_SURFACE_TOKEN` - Probe path: `/health` - Persistence: PVC at `/data` (SQLite + fetch cache) - Helm release: `railiance-apps` RAILIANCE-WP-0007 \ No newline at end of file diff --git a/tests/test_hub.py b/tests/test_hub.py index c68d426..ce03c70 100644 --- a/tests/test_hub.py +++ b/tests/test_hub.py @@ -32,9 +32,9 @@ capabilities: def hub_client(tmp_path, monkeypatch): db_path = tmp_path / "hub.db" cache_dir = tmp_path / "cache" - monkeypatch.setenv("REUSE_SURFACE_HUB_TOKEN", "test-token") - monkeypatch.setenv("REUSE_SURFACE_HUB_DB", str(db_path)) - monkeypatch.setenv("REUSE_SURFACE_HUB_CACHE_DIR", str(cache_dir)) + monkeypatch.setenv("REUSE_SURFACE_TOKEN", "test-token") + monkeypatch.setenv("REUSE_SURFACE_DB", str(db_path)) + monkeypatch.setenv("REUSE_SURFACE_CACHE_DIR", str(cache_dir)) app = create_app() with TestClient(app) as client: yield client diff --git a/tools/README.md b/tools/README.md index 2341d88..eb50027 100644 --- a/tools/README.md +++ b/tools/README.md @@ -92,15 +92,15 @@ Writes `docs/graph/capability-graph.mmd` and `docs/graph/index.html`. Client for the federation hub service (REUSE-WP-0011). ```bash -export REUSE_SURFACE_HUB_URL=https://reuse-hub.whywhynot.de -export REUSE_SURFACE_HUB_TOKEN= +export REUSE_SURFACE_URL=https://reuse.coulomb.social +export REUSE_SURFACE_TOKEN= reuse-surface hub status reuse-surface hub list reuse-surface hub register --repo state-hub --url https://.../capabilities.yaml reuse-surface hub update --repo state-hub --enabled true ``` -Run the hub service locally: `reuse-surface-hub` (requires `REUSE_SURFACE_HUB_TOKEN`). +Run the service locally: `REUSE_SURFACE_TOKEN=dev-token reuse-surface serve` ## Export format diff --git a/workplans/REUSE-WP-0011-federation-hub-on-railiance01.md b/workplans/REUSE-WP-0011-federation-hub-on-railiance01.md index b7f1be6..f13f479 100644 --- a/workplans/REUSE-WP-0011-federation-hub-on-railiance01.md +++ b/workplans/REUSE-WP-0011-federation-hub-on-railiance01.md @@ -44,7 +44,7 @@ for discovery without cloning every repo. | Operations | Container image, deployment manifests, TLS ingress, documented runbook | | Dogfood | `reuse-surface` and at least one sibling repo registered via the hub | -**Proposed public URL (confirm in T05):** `https://reuse-hub.whywhynot.de` +**Public URL:** `https://reuse.coulomb.social` ## Design decisions (draft) @@ -59,7 +59,7 @@ for discovery without cloning every repo. `url` sources (reuse WP-0010 fetch/cache logic) and merges into `GET /v1/federated` output. Local-only `index` paths are **not** valid hub registrations unless expressed as published raw URLs. -- **Auth (MVP):** Token-based write access via `REUSE_SURFACE_HUB_TOKEN` / +- **Auth (MVP):** Token-based write access via `REUSE_SURFACE_TOKEN` / `Authorization: Bearer`; read endpoints public for agent discovery. - **Persistence (MVP):** SQLite on a PVC inside the hub container. Postgres via cnpg is a follow-up if multi-replica or backup requirements emerge. @@ -100,7 +100,7 @@ plus hub metadata (`registered_at`, `updated_at`, `registered_by`). ## CLI sketch (T03 refines) ```bash -# Configure hub endpoint (env REUSE_SURFACE_HUB_URL or --hub-url) +# Configure service URL (env REUSE_SURFACE_URL or --base-url) reuse-surface hub status reuse-surface hub list reuse-surface hub register --repo state-hub \ @@ -167,8 +167,9 @@ state_hub_task_id: "38fec6ce-23c0-4157-8350-7d112b9e8264" Extend `reuse-surface` CLI with `hub` subcommands: - `register`, `update`, `show`, `list`, `status` -- `--hub-url` flag and `REUSE_SURFACE_HUB_URL` env support -- `REUSE_SURFACE_HUB_TOKEN` for authenticated writes +- `--base-url` flag and `REUSE_SURFACE_URL` env support +- `REUSE_SURFACE_TOKEN` for authenticated writes +- `reuse-surface serve` to run the API locally or in container - Document in `tools/README.md` and `AGENTS.md` ## Containerize And Publish Deployment Artifacts @@ -183,8 +184,8 @@ state_hub_task_id: "24eec9ad-21fc-4f0b-8671-72d955b15e68" Provide: - `Dockerfile` for the hub service -- Example k8s manifests or Helm values template under `deploy/` or `docs/deploy/` -- Image naming convention: `gitea.coulomb.social/coulomb/reuse-surface-hub:` +- Example k8s manifests or Helm values template under `docs/deploy/` +- Image: `gitea.coulomb.social/coulomb/reuse-surface:` - CI job or documented build/push steps (coordinate with `railiance-forge` registry guidance) @@ -199,10 +200,10 @@ state_hub_task_id: "7f26a70f-7b7d-413d-8162-931c6dffef6a" Deploy the hub as a governed release on `railiance01`: -- Confirm hostname (default `reuse-hub.whywhynot.de`) and DNS A record +- Confirm DNS for `reuse.coulomb.social` (coulomb.social zone) - Traefik ingress + cert-manager TLS - PVC for SQLite data -- Inject hub write token via SOPS/sealed secret +- Inject `REUSE_SURFACE_TOKEN` via Secret `reuse-surface-env` (SOPS handoff) - Verify `GET /health` and `GET /v1/federated` from workstation and from cluster **Blocked on:** DNS decision, operator secret provisioning, and