diff --git a/workplans/CUST-WP-0012-multi-user-onboarding.md b/workplans/CUST-WP-0012-multi-user-onboarding.md new file mode 100644 index 0000000..beb982d --- /dev/null +++ b/workplans/CUST-WP-0012-multi-user-onboarding.md @@ -0,0 +1,246 @@ +--- +id: CUST-WP-0012 +type: workplan +title: "Multi-User Onboarding and Environment Bootstrap" +domain: custodian +repo: the-custodian +status: active +owner: custodian +topic_slug: custodian +state_hub_workstream_id: "a28d9e29-4119-4b73-9469-f921920253ef" +created: "2026-03-11" +updated: "2026-03-11" +--- + +# Multi-User Onboarding and Environment Bootstrap + +## Goal + +Make the Custodian system accessible to collaborators beyond the primary +operator. A new user (or a new machine for the existing operator) should +be able to go from zero to a productive Claude Code session with full +State Hub MCP connectivity in a single session, without manual steps or +undocumented tribal knowledge. + +## Context + +Several friction points surfaced during the 2026-03-11 session: + +- No SSH key for Railiance01 on WSL2 → blocked `make tunnel-loop` +- No `~/.railiance_gitea.conf` → blocked repo creation script +- Token missing `read:user` scope → blocked org repo creation +- No `git credential.helper` → credentials required on every push +- MCP registration is manual and documented only in `CLAUDE.md` + +Each of these is a solved problem in isolation. This workstream collects +them into a repeatable, documented bootstrap experience. + +## Scope + +Two personas: + +| Persona | Access level | Typical machine | +|---------|-------------|-----------------| +| Primary operator | Full access, all domains | WSL2 workstation | +| Domain collaborator | Read + write to one domain | COULOMBCORE, remote laptop | + +## Tasks + +### T01 — Git credential.helper for Gitea access + +```task +id: CUST-WP-0012-T01 +state_hub_task_id: 71628269-9a75-4dae-a347-e64a86040322 +status: todo +priority: medium +``` + +Document and automate `git credential.helper` setup for Gitea +(`http://92.205.130.254:32166`). Recommend `libsecret` (keyring-backed) +on machines that support it; fall back to `credential.helper=store` +(persistent, plaintext `~/.git-credentials`) on headless servers. + +Include in bootstrap script (T04) and onboarding guide (T05). + +```bash +# Preferred: libsecret (GNOME keyring, WSL2 with keyring daemon) +sudo apt-get install -y libsecret-1-0 libsecret-1-dev +sudo make -C /usr/share/doc/git/contrib/credential/libsecret +git config --global credential.helper \ + /usr/share/doc/git/contrib/credential/libsecret/git-credential-libsecret + +# Fallback: store (plaintext, suitable for headless servers) +git config --global credential.helper store + +# Headless server alternative: cache (in-memory, 1h timeout) +git config --global credential.helper 'cache --timeout=3600' +``` + +**Done when:** included in bootstrap script; push to Gitea works without +re-entering credentials on second attempt. + +--- + +### T02 — SSH key generation and authorization automation + +```task +id: CUST-WP-0012-T02 +state_hub_task_id: fea965e9-8a8f-439c-9096-8f7756eb71ed +status: todo +priority: medium +``` + +Script or Ansible task that: +1. Generates an `ed25519` key pair on the new machine if none exists +2. Displays the public key with copy instructions +3. Authorizes it on all managed hosts (Railiance01, COULOMBCORE) via + `ssh-copy-id` or Ansible `authorized_key` module + +Surfaced by: RAIL-PL-WP-0001 T01 — no SSH key on WSL2 blocked +`make tunnel-loop HOST=tegwick@92.205.62.239`. + +```bash +# Generate if missing +[[ -f ~/.ssh/id_ed25519 ]] || ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519 -N "" + +# Authorize on a target host (requires existing access once) +ssh-copy-id -i ~/.ssh/id_ed25519.pub tegwick@92.205.62.239 +ssh-copy-id -i ~/.ssh/id_ed25519.pub tegwick@92.205.130.254 +``` + +**Done when:** included in bootstrap script; documented in onboarding guide. + +--- + +### T03 — Claude Code MCP registration automation + +```task +id: CUST-WP-0012-T03 +state_hub_task_id: 60318e9a-972e-45c8-afde-82ed0625f594 +status: todo +priority: medium +``` + +Automate the state-hub MCP server registration on a new machine. +Currently this is a multi-step manual process documented in +`~/.claude/CLAUDE.md`. It should be a single `make` target or script: + +```bash +# In the-custodian/state-hub/ +make register-mcp # idempotent; safe to re-run +``` + +The script should: +1. Detect whether `state-hub` is already in `~/.claude.json` +2. Extract the server config from `.mcp.json` +3. Run `claude mcp add-json -s user state-hub ` +4. Run `patch_mcp_cwd.py` to restore the cwd field +5. Print instructions to restart Claude Code + +Should also detect whether the state hub is reachable directly +(`http://127.0.0.1:8000`) or needs a tunnel (via ops-bridge), and emit +a warning if neither is available. + +**Done when:** `make register-mcp` works on a clean machine; documented +in onboarding guide. + +--- + +### T04 — Environment bootstrap script + +```task +id: CUST-WP-0012-T04 +state_hub_task_id: 84a94761-e424-4470-a9a2-64d9cabadb7f +status: todo +priority: high +``` + +Single idempotent script: `state-hub/scripts/bootstrap-env.sh` + +Checks/installs prerequisites and configures the environment: + +| Step | What | +|------|------| +| Prerequisites | git, sops, age, helm, kubectl, uv, claude CLI | +| Git credential | `credential.helper` (libsecret or store) | +| SSH key | Generate ed25519 if missing; display public key | +| MCP registration | `make register-mcp` (T03) | +| Gitea config | Prompt for token; write `~/.railiance_gitea.conf` | +| Health check | `curl /state/health`; warn if tunnel needed | + +Design constraints: +- Idempotent: safe to run on an already-configured machine +- No silent failures: each step prints ✓ / ✗ / ⚠ +- Minimal dependencies: bash + curl only to get started + +**Done when:** running the script on a clean Ubuntu 24.04 machine +produces a working Custodian environment with no additional manual steps. + +--- + +### T05 — Onboarding guide and user journey documentation + +```task +id: CUST-WP-0012-T05 +state_hub_task_id: b0839802-659a-475b-8b84-ab7341ea3d15 +status: todo +priority: medium +``` + +Write `docs/onboarding.md` in the-custodian covering the full journey +for both personas: + +**Primary operator (new machine):** +1. Prerequisites (git, SSH client) +2. Clone `the-custodian` +3. Run `make bootstrap-env` (T04) +4. Restart Claude Code → verify MCP is active +5. First session: `get_state_summary()` → orient → work + +**Domain collaborator (new person):** +1. Prerequisites + Gitea account +2. `ssh-copy-id` to get access to Railiance01 (or just COULOMBCORE) +3. Set up ops-bridge tunnel to reach state hub +4. Clone domain repo +5. First Claude Code session with MCP via tunnel +6. Contributing a workplan (ADR-001 convention) + +**Done when:** a new collaborator can follow the guide without +clarification from the primary operator. + +--- + +### T06 — State Hub multi-user model (deferred) + +```task +id: CUST-WP-0012-T06 +state_hub_task_id: d5df3302-67b9-4765-a8d8-ea2df53dff6e +status: todo +priority: low +``` + +Design a lightweight user/role model for the state hub: + +| Role | Permissions | +|------|-------------| +| Primary operator | Full read/write, all domains | +| Domain collaborator | Read all; write to own domain only | +| Observer | Read-only | + +Decision needed: enforce at API layer (HTTP Basic / token auth per +domain) or rely on Gitea repo permissions as the authoritative boundary +(simpler — the hub is a read model anyway). + +**Deferred until:** first external collaborator is actively onboarding. +Implement T01–T05 first; multi-user access control is only needed when +there is more than one user. + +--- + +## References + +- ops-bridge repo: `ops-bridge` (tunnel lifecycle management) +- MCP registration: `~/.claude/CLAUDE.md` (current manual procedure) +- Gitea repo creation: `railiance-cluster/tools/create_railiance_repo.sh` +- ADR-001: workplans as repo artefacts +- Surfaced by: RAIL-PL-WP-0001 T01 execution, 2026-03-11