feat: cluster Phase 4 — remote backup browse, download, restore & delete #5

Merged
james merged 1 commit from feat/clustering-phase4 into main 2026-07-24 20:44:06 +00:00
Owner

Completes the clustering vision: from the main dashboard you can now browse, download, restore, and delete a node's backup files — all over the node-initiated channel (main still never opens a connection into a node).

The mechanism gap this closes

Phase 2's command channel is fire-and-forget (good for actions). Phase 4 adds the two missing shapes:

  • Synchronous RPC for request→response ops (browse): dispatch_command enqueues a command and blocks on a per-command-id waiter that the node's result event signals (timeout → CommandTimeout). Delivery still rides the existing long-poll, so it's near-instant.
  • Download relay for large binaries that can't ride the JSON event channel: main opens an OS pipe, tells the node to stream the file into it (node-initiated POST /api/cluster/nodes/:id/files/:token), and streams the pipe out to the browser. Two Puma threads rendezvous on the pipe.

What's included

  • Node file-op commands (ClusterAgent): list_backups, backup_details, list_contents, delete_backup, restore (fire-and-forget + pollable status), upload_file (streams via Net::HTTP body_stream, application/octet-stream so Rack doesn't parse the body as params).
  • Main routes (session-authed, main only; self-node short-circuits to local BackupManagementService): GET /api/nodes/:id/files[/:date/:filename[/contents]], DELETE, POST …/restore, GET …/download, GET …/commands/:cid.
  • Concurrency safety: download relays capped by BT_MAX_TRANSFERS (503 when exceeded); each holds 2 Puma threads, so BT_HEALTH_MAX_THREADS default raised to 24. Keep it ≳ BT_MAX_LONGPOLL + 2×BT_MAX_TRANSFERS + UI headroom.
  • UI: node detail gains a backup-file browser (download/restore/delete + restore-status polling).

Testing

Full suite 1078 examples, 0 failures. Added coordinator RPC + transfer-relay specs, node file-op execution specs (webmock), and endpoint request specs. Plus an in-process E2E proving remote browse and a 200 KB file streamed node→main→browser byte-for-byte, and the no-responder → 504 path.

Depends on nothing outside main; safe to merge after review. Downloads are still gated by the node's existing MAX_DOWNLOAD_SIZE.

🤖 Generated with Claude Code

Completes the clustering vision: from the **main** dashboard you can now browse, **download**, restore, and delete a **node's** backup files — all over the node-initiated channel (main still never opens a connection into a node). ### The mechanism gap this closes Phase 2's command channel is fire-and-forget (good for *actions*). Phase 4 adds the two missing shapes: - **Synchronous RPC** for request→response ops (browse): `dispatch_command` enqueues a command and blocks on a per-command-id waiter that the node's result event signals (timeout → `CommandTimeout`). Delivery still rides the existing long-poll, so it's near-instant. - **Download relay** for large binaries that can't ride the JSON event channel: main opens an OS pipe, tells the node to stream the file into it (node-initiated `POST /api/cluster/nodes/:id/files/:token`), and streams the pipe out to the browser. Two Puma threads rendezvous on the pipe. ### What's included - **Node file-op commands** (`ClusterAgent`): `list_backups`, `backup_details`, `list_contents`, `delete_backup`, `restore` (fire-and-forget + pollable status), `upload_file` (streams via `Net::HTTP` `body_stream`, `application/octet-stream` so Rack doesn't parse the body as params). - **Main routes** (session-authed, main only; self-node short-circuits to local `BackupManagementService`): `GET /api/nodes/:id/files[/:date/:filename[/contents]]`, `DELETE`, `POST …/restore`, `GET …/download`, `GET …/commands/:cid`. - **Concurrency safety**: download relays capped by `BT_MAX_TRANSFERS` (503 when exceeded); each holds 2 Puma threads, so `BT_HEALTH_MAX_THREADS` default raised to 24. Keep it ≳ `BT_MAX_LONGPOLL + 2×BT_MAX_TRANSFERS` + UI headroom. - **UI**: node detail gains a backup-file browser (download/restore/delete + restore-status polling). ### Testing Full suite **1078 examples, 0 failures**. Added coordinator RPC + transfer-relay specs, node file-op execution specs (webmock), and endpoint request specs. Plus an in-process E2E proving remote browse and a **200 KB file streamed node→main→browser byte-for-byte**, and the no-responder → 504 path. Depends on nothing outside main; safe to merge after review. Downloads are still gated by the node's existing `MAX_DOWNLOAD_SIZE`. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Main can now operate on a node's backup files from its dashboard, over the
node-initiated channel (main still never connects into a node).

- Synchronous command RPC: ClusterCoordinator#dispatch_command enqueues a
  command and blocks on a per-command-id waiter that record_command_result
  signals (with timeout → CommandTimeout). Reuses the Phase 2 long-poll delivery,
  so request/response file ops arrive near-instantly. NodeCommandRepository gains
  find_command for status polling.
- Node executes file-op commands (ClusterAgent): list_backups, backup_details,
  list_contents, delete_backup, restore (via BackupManagementService), each
  reporting its result. restore is fire-and-forget (slow) with a pollable status.
- Download relay (4c): a download can't ride the JSON event channel, so main
  opens an OS pipe, tells the node to stream the file into it (node-initiated
  POST /api/cluster/nodes/:id/files/:token), and streams the pipe out to the
  browser. Two Puma threads rendezvous on the pipe; concurrency is capped by a
  semaphore (BT_MAX_TRANSFERS, 503 when exceeded) and BT_HEALTH_MAX_THREADS
  default raised to 24. The node streams via Net::HTTP body_stream (no buffering)
  with application/octet-stream so Rack doesn't parse the body as params.
- Main UI routes (session-authed, main only; self-node short-circuits to local):
  GET /api/nodes/:id/files[/:date/:filename[/contents]], DELETE, POST .../restore,
  GET .../download (self=send_file, remote=relay), GET .../commands/:cid.
- UI: node detail gains a backup-file browser with download/restore/delete and
  restore-status polling; adds the shared confirm modal to the nodes page.
- Tests: coordinator RPC + transfer-relay specs, node file-op execution specs,
  endpoint request specs (full suite 1078 examples, 0 failures) and an in-process
  E2E proving remote browse + a 200KB download streamed node→main→browser + the
  no-responder 504.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
james merged commit 1cb8ee9072 into main 2026-07-24 20:44:06 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
james/baktainer!5
No description provided.