feat: M6 stage B - cluster heartbeat, new-node bootstrap auto-pull, peer-first install (e2e verified)

This commit is contained in:
2026-08-17 18:05:01 +08:00
parent 79e0b3da79
commit fd7604cb9c
4 changed files with 237 additions and 13 deletions

View File

@ -9,6 +9,7 @@ import (
"io/fs"
"net/http"
"os"
"path/filepath"
"strings"
"time"
@ -38,6 +39,9 @@ type Handler struct {
SyncWorkers func()
// Cluster is the peer-to-peer binary registry (M6). Nil disables M6 routes.
Cluster *cluster.Registry
// SelfAddr is this node's reachable listen address (from -addr), used for
// cluster discovery so peers can reach back for binary exchange.
SelfAddr string
}
const (
@ -266,21 +270,21 @@ func (h *Handler) handleClusterNodes(w http.ResponseWriter, r *http.Request) {
http.Error(w, "cluster registry not enabled", http.StatusNotFound)
return
}
nodes := h.Cluster.NodeList()
// Self node goes first so a querier can identify us by Nodes[0].
type nodeResp struct {
Addr string `json:"addr"`
Cache []string `json:"cache,omitempty"`
Version string `json:"version,omitempty"`
}
out := make([]nodeResp, 0, len(nodes))
for _, n := range nodes {
out := make([]nodeResp, 0, 1+len(h.Cluster.NodeList()))
ver := ""
if h.BinaryPath != nil {
ver = frpcVersionOf(h.BinaryPath())
}
out = append(out, nodeResp{Addr: h.SelfAddr, Cache: h.Cluster.CachedVersions(), Version: ver})
for _, n := range h.Cluster.NodeList() {
out = append(out, nodeResp{Addr: n.Addr, Version: n.Version, Cache: n.Cache})
}
out = append(out, nodeResp{
Addr: "self",
Cache: h.Cluster.CachedVersions(),
Version: currentVersion(),
})
writeJSON(w, http.StatusOK, map[string]any{"nodes": out})
}
@ -312,9 +316,15 @@ func (h *Handler) handleFrpcBinary(w http.ResponseWriter, r *http.Request) {
_, _ = io.Copy(w, f)
}
// currentVersion returns the app version for cluster node reporting.
func currentVersion() string {
return "0.1.0"
// frpcVersionOf extracts the frpc version from a binary path like
// .../bin/frpc-0.71.0/frpc. Empty when not a versioned cache path.
func frpcVersionOf(binPath string) string {
dir := filepath.Dir(binPath)
base := filepath.Base(dir)
if strings.HasPrefix(base, "frpc-") {
return strings.TrimPrefix(base, "frpc-")
}
return ""
}
// connStateOf derives the frps connection state from the local worker process