Skip to content

feat(serve): run a daemon across machines: TLS, reconnect, restarts, per-client tokens - #197

Merged
fuseraft merged 2 commits into
mainfrom
feature/serve-remote-hardening
Nov 1, 2026
Merged

fuseraft merged 2 commits into
mainfrom
feature/serve-remote-hardening

Conversation

@fuseraft

@fuseraft fuseraft commented Nov 1, 2026

Copy link
Copy Markdown
Owner

Makes fuseraft serve usable from another machine, and keeps its work through dropped connections, restarts on either side, and clients with different rights.

New

  • TLS. --tls-cert/--tls-key (PEM, or .pfx with FUSERAFT_SERVE_TLS_PASSWORD) serve /mcp, /attach and the A2A endpoints over https:///wss://. The daemon prints the certificate's SHA-256 fingerprint and warns when it serves plain HTTP beyond loopback.
  • Reconnecting clients. A WebSocket client that sends Fuseraft-Client-Id keeps its tasks across connections: a pending approval is asked again when it reconnects within --reconnect-grace (default 60 s), and results that finished while it was away are sent on return. hello gains clientId, readOnly, inFlight and pendingResults; result gains task. Pings every 15 s notice peers that vanish.
  • Durable queue. Unfinished tasks and uncollected results are saved to ~/.fuseraft/state/<project>/serve-queue.json (mode 600). After a crash or restart, the running task resumes from its saved session (broadcast as task_resumed) and the queued ones follow, each still owned by its client. A task interrupted more than 3 times is failed. SIGTERM now shuts down like Ctrl+C.
  • Per-client tokens. --tokens-file holds name token [full|read] lines (chmod 600 enforced), re-read on change. Removing or rescoping a token closes its connections within seconds, and client ids are scoped per token. A read token can watch over /attach and call get_status/get_result; dispatch_task is hidden and refused.
  • fuseraft attach --url takes --cert-sha256 to pin a self-signed certificate and --client-id to pick a running task and its approval back up. With a read token, it only watches.

Fixed

  • A stale pidfile naming the daemon's own PID (always the case for PID 1 in a container) blocked every restart after a crash.
  • Shutting down while a task waited for approval hung, since the wait ignored cancellation. A stopping daemon now denies the pending approval, whatever --unattended-policy says; the task asks again when it resumes.
  • A connection replaced by a newer one with the same client id was never closed, because disposing a server WebSocket didn't wake its pending receive.

Docs

serve.md, cli-reference.md, protocol.md and security.md cover the new flags, events and trust model.

Testing

New tests for the attach clients, the queue journal, tokens (including scope and revocation over a real WebSocket and real MCP), TLS loading and pinning, and the pidfile. Also run live against a daemon in a container with real model calls: SIGKILL and SIGTERM mid-task followed by a resume, client pickup after a detach, token revocation, read-only tokens over attach and MCP, and fingerprint pinning.

…per-client tokens

fuseraft serve can now be attached to from another machine and survive the
things that happen there: dropped connections, restarts on either side, and
more than one client with different rights.

- --tls-cert/--tls-key serve /mcp, /attach and A2A over https/wss, printing
  the certificate's SHA-256 fingerprint; plain HTTP beyond loopback warns.
- A Fuseraft-Client-Id header makes a client outlive its connection: a
  pending approval is asked again on reconnect within --reconnect-grace
  (default 60 s), and results that finished meanwhile are delivered.
  WebSocket keepalive notices peers that vanish without closing.
- The queue is saved to ~/.fuseraft/state/<project>/serve-queue.json. After
  a crash or restart the running task resumes from its saved session and
  the queued ones follow; a task interrupted more than 3 times fails.
  SIGTERM shuts down like Ctrl+C, and a stopping daemon denies a pending
  approval instead of waiting on it.
- --tokens-file gives each client a named token, re-read on change; removing
  or rescoping one closes its connections. Client ids are kept per token.
  A `read` token watches over /attach and can call get_status/get_result,
  but can't dispatch.
- fuseraft attach --url takes --cert-sha256 to pin a self-signed
  certificate and --client-id to pick a running task back up.

Fixes found along the way: a pidfile naming the daemon's own PID (PID 1 in
a container) blocked every restart after a crash, and a replaced WebSocket
connection was never actually closed.
@fuseraft
fuseraft merged commit 271df49 into main Nov 1, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant