SPB Git

Repository Public

cluster-integration.md 8.1 KB

# MacLustr cluster integration

Claude Remote runs on one node (M5M36) and touches the rest of the cluster through four narrow, explicitly authorised channels. Inventory: infrastructure-inventory.md.

Channel Direction Authorisation Used for
agentd telemetry API M5M36 → agentd node (LAN, HTTP, bearer token) token held server-side only Cluster page: read-only inventory and metrics
SSH to compute nodes M5M36 → hosts in CR_EXEC_HOSTS a key generated on M5M36, authorised on M3U96a and M3U96b remote background tasks
Git server (gitsrv) M5M36 → gateway M1M32 the same key, restricted to git-shell (no interactive shell) clone / push of repositories
mld + tunnel gateway → M5M36 cluster orchestrator deployment, health, public route (see deployment.md)

Claude Remote does not run mld, does not deploy applications, and has no sudo.

# 1. Cluster telemetry (read-only)

Source: apps/server/src/modules/cluster/{service,mapping,index}.ts, page apps/web/src/app/(app)/cluster.

  • When CR_AGENTD_URL is set, the server polls agentd's /api/cluster every 20 s and maps the payload to ClusterOverview (packages/shared/src/dto.ts).
  • The mapping is defensive: unknown fields are ignored, missing values become null, and nothing that identifies the network or the operator is copied through — no IP addresses, public IP, LAN map, usernames, hostnames or serial numbers. Display strings that could carry an address are dropped.
  • Freshness: a node whose metrics are older than 90 s is stale with health UNKNOWN — never ONLINE. When agentd stops answering, the last good snapshot is kept with its timestamp and ages into UNKNOWN.
  • Without agentd (source: "none") the overview contains only the host, measured by the runtime (GET /host/metrics), and the cluster health is UNKNOWN.
  • Persistence: inventory upserts into cluster_nodes and at most one downsampled row per node per minute into node_metrics (kept 14 days). GET /cluster/nodes/:id returns a node with its recorded metrics for the last 24 hours; GET /cluster/metrics returns the recorded series.
  • Topology edges are limited to two facts: lan (host → every other Mac in the inventory) and tunnel (host → the public gateway, only when agentd lists it).
  • isHost marks the node whose alias equals CR_NODE_ALIAS (or the runtime's short hostname).
  • execAllowed is true for aliases listed in CR_EXEC_HOSTS.

The System page reports the agentd link as a component: ONLINE when fresh, UNKNOWN when only old data exists or agentd is not configured, OFFLINE when it never answered.

# 2. Remote tasks over SSH

Source: apps/server/src/modules/tasks/index.ts, apps/runtime/src/core-task-manager.ts, core-task-script.ts.

  • A task has a host: local or a node alias. A non-local host must match ^[A-Za-z0-9._-]+$ and be listed in CR_EXEC_HOSTS; the check is done by the server (403 host_not_allowed, audited) and again by the runtime.

  • Production value: CR_EXEC_HOSTS=M3U96a,M3U96b. An empty list means local tasks only.

  • The runner (a static POSIX script copied to tasks/<id>/run.sh, executed in tmux session crt-<uuid>) runs:

    text
    ssh -o BatchMode=yes -o ConnectTimeout=10 <host> -- "<command>"

    The command text reaches the script through a file, the host through an environment variable — never by interpolation into the script. stdout and stderr go to tasks/<id>/log on M5M36 (capped, 256 MiB by default); the ssh exit code is the task's exit code.

  • Timeout, cancel (TERM to the runner's process group, KILL after 10 s) and state tracking are the same as for local tasks. CPU time and peak memory are reported for local tasks only (/usr/bin/time -l); for remote tasks they stay null.

  • The task's working directory setting applies to local tasks. For a remote task the command runs in the remote account's default directory; put a cd in the command if needed.

  • Artifacts (artifactGlobs) are collected from the local working directory through the file API; files produced on a remote node are not collected.

  • ssh resolves the alias through the node account's own SSH configuration; Claude Remote does not manage ~/.ssh/config.

From a terminal session the administrator can of course run ssh by hand to any host the node key is authorised on; that is outside the task allowlist and is a property of the shell, not of the application.

# 3. Git server

  • "Clone" mode of project creation (POST /api/v1/projects, mode: "clone") accepts only https://…, ssh://…, git@host:path and gitsrv:path URLs, never a string starting with - (validated by the server and by the runtime). The runtime runs git clone with GIT_TERMINAL_PROMPT=0, a 10-minute timeout, and never forwards credentials or host paths in errors. Credentials embedded in a remote URL are stripped before it is stored or shown.
  • Cluster repositories are cloned as gitsrv:srv/git/<name>.git (the SSH alias present on every node).
  • The node key is authorised on the git server with a git-shell restriction: it can fetch and push, it cannot open an interactive shell on the gateway.
  • Project pages show branch, ahead/behind, dirty state and diffs (git status, git diff through the runtime, with fsmonitor and hooks disabled so that inspecting a repository never runs repository code). Commits and pushes are done in the terminal.

# 4. Local models

GET /cluster/models lists the endpoints declared in CR_LOCAL_MODELS (a JSON array of {id, name, host, engine, endpoint}), each with a health probed by a real GET <endpoint>/v1/models (5 s timeout, cached 30 s). POST /cluster/models/:id/test sends one small chat completion and reports latency, and tokens per second only when the endpoint returns usage. Endpoint URLs are reduced to a host label before reaching the browser.

No model is ever invented: the cluster currently serves no local model (the former gateway was dismantled in June 2026), CR_LOCAL_MODELS is unset, and the panel is empty by design.

# 5. Skills registry

GET /skills/registry/search?q= is an anonymous, read-only search adapter for the cluster's skills registry (CR_SKILLS_REGISTRY_URL). If the registry is unreachable the answer is {connected: false}. Installing from the registry is not implemented: POST /skills/op with a registry source answers 501 and downloads nothing. Skills can be installed only from a directory inside a project.

# What was migrated to the node

Summary (details and classification in migration.md): Claude Code CLI; a sanitized global CLAUDE.md (sudo password removed); the jof250 skill, 7 agents and 10 jof-* commands; 5 MCP server scripts without API keys; settings limited to model and fast mode. Not copied: 20 credential-bearing memory notes (the other 251 were copied on 2026-11-10), MCP API keys, the laptop's auto-approve PreToolUse hook, the cluster slash commands.

# Limitations / not verified

  • Remote tasks were tested in the application only with a fake ssh on PATH (apps/runtime/src/tasks.test.ts). The node key was verified by hand on M3U96a and M3U96b; no remote task has been launched from the production application.
  • Whether cancelling a remote task stops the command on the remote node (and not only the local ssh client) has not been verified.
  • Project "clone" mode was not tested against the real git server from the application; git clone with the node key works by hand.
  • execAllowed reflects the allowlist only; it does not test SSH reachability, although the DTO comment says "reachable with the runtime key".
  • The Cluster page is read-only: no node control, no deployment, no mld action.
  • Notification kinds node.offline and storage.low exist in the schema but nothing emits them.
  • The three iPhone "mobile nodes" of the cluster are out of scope.
  • The cluster slash commands were not migrated, so Claude Code on the node has no built-in cluster tooling beyond what the sanitized CLAUDE.md describes and what ssh to the two allow-listed nodes permits.
Files main
Menu

Theme