Skip to content

feat(cluster): v2.6 — remote control, manual peers, per-node health#20

Merged
haliskilic merged 1 commit into
mainfrom
feat-cluster-v2.6
Jun 11, 2026
Merged

feat(cluster): v2.6 — remote control, manual peers, per-node health#20
haliskilic merged 1 commit into
mainfrom
feat-cluster-v2.6

Conversation

@haliskilic

Copy link
Copy Markdown
Owner

Builds on the v2.5 LAN-cluster fleet view so you can manage the whole fleet from one screen, not just watch it.

Remote control

  • Key-guarded POST /api/cluster/control on the target node applies start/stop/restart to one of its mappings — shares _apply_action with the normal UI handler, CSRF-exempt and in PUBLIC_PATHS because it's authenticated by the shared cluster key, not a session.
  • The browser uses session-authed POST /api/cluster/control-peer (operator+), which validates the target against cluster.known_addresses() (an anti-SSRF allowlist of auto + manual peers) before calling the peer with the key. Start/Stop/Restart buttons appear on remote rows in the unified table.

Manual peers

  • For routed/L3 networks UDP broadcast can't reach: ClusterSettings.peers (list of host:port or scheme://host:port). cluster.all_targets() merges auto-discovered + manual peers, deduped by (ip, port). Added a Settings textarea.

Per-node health

  • /api/cluster/local now returns uptime / version / running-count. The fleet table is grouped by node with a health header (up · vX.Y · running/total) and an online/offline indicator. A banner surfaces when UDP discovery can't bind (manual peers still work).

Trust model

Remote control = anyone with an operator+ account on a cluster node + the shared key can control any peer. The local session role gates initiation; the key authorizes the cross-node call; the allowlist bounds the target.

Misc

  • Version is now single-sourced in app/__init__.py (__version__), imported by ser2net.py and the web layer — bumped to 2.6.0 (fixes the kind of drift the last review caught).
  • Two cluster-review fixes from last pass are in too (UDP socket close on listener-start failure; clean ConfigError on a bad discovery_port).

Tests

tests/test_cluster.py adds a deterministic end-to-end: two real nodes linked by a manual peer (different discovery ports, so no UDP dependency) exercise aggregation + health, the peer-control key guard, remote start/stop taking effect, and the anti-SSRF allowlist rejecting an unknown address. Full suite 32/32; ruff clean.

Screenshots regenerated (fleet panel now shows node health + remote-control buttons); README (TR/EN) + ROADMAP updated.

Builds on the v2.5 fleet view so you can manage the whole fleet from one screen.

Remote control:
- Key-guarded POST /api/cluster/control on the target node applies start/stop/
  restart to one of its mappings (shares _apply_action with the UI handler;
  CSRF-exempt + in PUBLIC_PATHS — authenticated by the shared key, not a session).
- Browser uses session-authed POST /api/cluster/control-peer (operator+), which
  validates the target against cluster.known_addresses() (anti-SSRF allowlist)
  before calling the peer with the key. Start/Stop/Restart buttons on remote rows.

Manual peers (routed/L3 networks broadcast can't reach):
- ClusterSettings.peers (list of host:port / scheme://host:port); cluster.all_targets()
  merges auto-discovered + manual peers, deduped by (ip, port). Settings textarea.

Per-node health:
- /api/cluster/local payload adds uptime/version/running-count; the fleet table is
  now grouped by node with a health header (up · version · running/total) and an
  online/offline indicator. A banner surfaces when UDP discovery can't bind
  (manual peers still work).

Misc:
- Version single-sourced in app/__init__.py (__version__), imported by ser2net.py
  and the web layer; bumped to 2.6.0.
- tests/test_cluster.py: two real nodes linked by a manual peer (no UDP dependency)
  exercise aggregation+health, the key guard, remote start/stop, and the anti-SSRF
  allowlist. Screenshots + README/ROADMAP updated.
@haliskilic
haliskilic merged commit b2aa5b8 into main Jun 11, 2026
18 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