feat(M6): localOnly forward option (loopback→LAN rewrite for cluster, local direct), canvas diff-based create/revoke commands, revoke task semantics, topology derived canvas on sync nodes, LAN addr helper

This commit is contained in:
2026-08-18 09:44:38 +08:00
parent bf602016b4
commit 40980944e5
12 changed files with 364 additions and 13 deletions

View File

@ -49,6 +49,10 @@ type Task struct {
Remote store.Remote `json:"remote"`
Link store.Link `json:"link"`
Created int64 `json:"created"`
// Revoke marks a REVOCATION task: instead of creating the forward, the
// owning node cancels it (stop worker, drop from topology). Reuses the
// same publish channel as creation (round-1 inject, round-2 apply).
Revoke bool `json:"revoke,omitempty"`
}
// TopoEntry is an ESTABLISHED forward in the cluster topology: who (OwnerID)
@ -201,6 +205,25 @@ func (s *State) NextTaskID() string {
return fmt.Sprintf("t%d", s.Seq)
}
// AddRevoke publishes a REVOCATION task (same channel as creation): when a
// node wants to cancel an established forward, it injects a Revoke task;
// the owning node cancels the worker and drops it from topology.
func (s *State) AddRevoke(local store.Local, remote store.Remote, link store.Link) *Task {
t := &Task{
ID: s.NextTaskID(),
Local: local,
Remote: remote,
Link: link,
Created: time.Now().Unix(),
Revoke: true,
}
if s.PendingTasks == nil {
s.PendingTasks = map[string]*Task{}
}
s.PendingTasks[t.ID] = t
return t
}
// AddPending attaches a NEW forward request to the token (round-1 inject).
func (s *State) AddPending(local store.Local, remote store.Remote, link store.Link) *Task {
t := &Task{
@ -257,6 +280,18 @@ func (s *State) AddTopology(t *Task, ownerID string) *TopoEntry {
return e
}
// RemoveTopology drops the active forward for the given local/remote/port
// (returns true if removed — the owning node revokes it).
func (s *State) RemoveTopology(local, remote string, port int) bool {
for id, e := range s.Topology {
if e.Local.Name == local && e.Remote.Name == remote && e.Link.RemotePort == port {
delete(s.Topology, id)
return true
}
}
return false
}
// TopologyList returns active forwards, stable by task id.
func (s *State) TopologyList() []*TopoEntry {
out := make([]*TopoEntry, 0, len(s.Topology))