feat(agent): activation zéro-config – config Headscale envoyée par le serveur
- Agent: URL serveur par défaut, node_id auto-généré, config Headscale vide par défaut - Serveur: lors de l’activation, renvoie headscaleUrl + headscaleAuthKey - Agent: sauvegarde la config reçue et démarre Tailscale automatiquement - docker-compose.yml: passe HEADSCALE_URL et HEADSCALE_AUTH_KEY au service server - Mise à jour du suivi avec le flow zéro-config
This commit is contained in:
+71
-20
@@ -10,17 +10,19 @@ import (
|
||||
)
|
||||
|
||||
type WSMessage struct {
|
||||
Action string `json:"action"`
|
||||
NodeID string `json:"nodeId,omitempty"`
|
||||
Code string `json:"code,omitempty"`
|
||||
InstanceID string `json:"instanceId,omitempty"`
|
||||
Type string `json:"type,omitempty"`
|
||||
Port int `json:"port,omitempty"`
|
||||
ComposeConfig string `json:"composeConfig,omitempty"`
|
||||
StudentId string `json:"studentId,omitempty"`
|
||||
StudentName string `json:"studentName,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
TailscaleIP string `json:"tailscaleIp,omitempty"`
|
||||
Action string `json:"action"`
|
||||
NodeID string `json:"nodeId,omitempty"`
|
||||
Code string `json:"code,omitempty"`
|
||||
InstanceID string `json:"instanceId,omitempty"`
|
||||
Type string `json:"type,omitempty"`
|
||||
Port int `json:"port,omitempty"`
|
||||
ComposeConfig string `json:"composeConfig,omitempty"`
|
||||
StudentId string `json:"studentId,omitempty"`
|
||||
StudentName string `json:"studentName,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
TailscaleIP string `json:"tailscaleIp,omitempty"`
|
||||
HeadscaleURL string `json:"headscaleUrl,omitempty"`
|
||||
HeadscaleAuthKey string `json:"headscaleAuthKey,omitempty"`
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -28,6 +30,27 @@ var (
|
||||
mainConnMu sync.Mutex
|
||||
)
|
||||
|
||||
// headscale config received from the server during activation.
|
||||
// These are mutable because activation may happen after the agent starts.
|
||||
var (
|
||||
currentHeadscaleURL string
|
||||
currentHeadscaleAuthKey string
|
||||
headscaleConfigMu sync.Mutex
|
||||
)
|
||||
|
||||
func setHeadscaleConfig(url, authKey string) {
|
||||
headscaleConfigMu.Lock()
|
||||
currentHeadscaleURL = url
|
||||
currentHeadscaleAuthKey = authKey
|
||||
headscaleConfigMu.Unlock()
|
||||
}
|
||||
|
||||
func getHeadscaleConfig() (string, string) {
|
||||
headscaleConfigMu.Lock()
|
||||
defer headscaleConfigMu.Unlock()
|
||||
return currentHeadscaleURL, currentHeadscaleAuthKey
|
||||
}
|
||||
|
||||
func sendMessage(msg WSMessage) error {
|
||||
mainConnMu.Lock()
|
||||
defer mainConnMu.Unlock()
|
||||
@@ -81,6 +104,8 @@ func notifyUI(msg map[string]interface{}) {
|
||||
}
|
||||
|
||||
func startWebSocket(serverAddr, nodeID, dataDir, headscaleURL, headscaleAuthKey string) {
|
||||
setHeadscaleConfig(headscaleURL, headscaleAuthKey)
|
||||
|
||||
for {
|
||||
conn, _, err := websocket.DefaultDialer.Dial(serverAddr, nil)
|
||||
if err != nil {
|
||||
@@ -111,6 +136,11 @@ func startWebSocket(serverAddr, nodeID, dataDir, headscaleURL, headscaleAuthKey
|
||||
log.Println("Waiting for activation...")
|
||||
} else {
|
||||
log.Printf("Already activated as %s", act.StudentName)
|
||||
// If already activated and we have credentials, ensure VPN is up.
|
||||
hsURL, hsKey := getHeadscaleConfig()
|
||||
if hsURL != "" && hsKey != "" {
|
||||
go startTailscaleAndReport(dataDir, nodeID, hsURL, hsKey)
|
||||
}
|
||||
}
|
||||
|
||||
// Heartbeat goroutine
|
||||
@@ -138,7 +168,7 @@ func startWebSocket(serverAddr, nodeID, dataDir, headscaleURL, headscaleAuthKey
|
||||
break
|
||||
}
|
||||
log.Printf("WS received from server: action=%s", msg.Action)
|
||||
handleMessage(conn, msg, dataDir, nodeID, headscaleURL, headscaleAuthKey)
|
||||
handleMessage(conn, msg, dataDir, nodeID)
|
||||
}
|
||||
|
||||
close(done)
|
||||
@@ -151,7 +181,7 @@ func startWebSocket(serverAddr, nodeID, dataDir, headscaleURL, headscaleAuthKey
|
||||
}
|
||||
}
|
||||
|
||||
func handleMessage(conn *websocket.Conn, msg WSMessage, dataDir, nodeID, headscaleURL, headscaleAuthKey string) {
|
||||
func handleMessage(conn *websocket.Conn, msg WSMessage, dataDir, nodeID string) {
|
||||
switch msg.Action {
|
||||
case "activated":
|
||||
log.Printf("handleMessage: activated received, student=%s", msg.StudentName)
|
||||
@@ -163,6 +193,25 @@ func handleMessage(conn *websocket.Conn, msg WSMessage, dataDir, nodeID, headsca
|
||||
log.Printf("Activated as %s", act.StudentName)
|
||||
}
|
||||
}
|
||||
|
||||
// The server also sends Headscale credentials on activation.
|
||||
if msg.HeadscaleURL != "" && msg.HeadscaleAuthKey != "" {
|
||||
setHeadscaleConfig(msg.HeadscaleURL, msg.HeadscaleAuthKey)
|
||||
cfg, _, err := loadOrCreateConfig(dataDir)
|
||||
if err != nil {
|
||||
log.Printf("loadOrCreateConfig error: %v", err)
|
||||
} else {
|
||||
cfg.HeadscaleURL = msg.HeadscaleURL
|
||||
cfg.HeadscaleAuthKey = msg.HeadscaleAuthKey
|
||||
if err := saveConfig(dataDir, cfg); err != nil {
|
||||
log.Printf("saveConfig error: %v", err)
|
||||
} else {
|
||||
log.Printf("Saved Headscale config received from server")
|
||||
}
|
||||
}
|
||||
go startTailscaleAndReport(dataDir, nodeID, msg.HeadscaleURL, msg.HeadscaleAuthKey)
|
||||
}
|
||||
|
||||
notifyUI(map[string]interface{}{
|
||||
"action": "activated",
|
||||
"studentName": msg.StudentName,
|
||||
@@ -172,13 +221,14 @@ func handleMessage(conn *websocket.Conn, msg WSMessage, dataDir, nodeID, headsca
|
||||
return
|
||||
case "start_vpn":
|
||||
log.Printf("Server requested VPN start")
|
||||
if headscaleURL == "" || headscaleAuthKey == "" {
|
||||
hsURL, hsKey := getHeadscaleConfig()
|
||||
if hsURL == "" || hsKey == "" {
|
||||
log.Printf("Cannot start VPN: headscale config missing")
|
||||
sendMessage(WSMessage{Action: "vpn_error", NodeID: nodeID, Error: "headscale config missing"})
|
||||
return
|
||||
}
|
||||
go func() {
|
||||
ip, err := startTailscale(dataDir, nodeID, headscaleURL, headscaleAuthKey)
|
||||
ip, err := startTailscale(dataDir, nodeID, hsURL, hsKey)
|
||||
if err != nil {
|
||||
log.Printf("start_vpn error: %v", err)
|
||||
sendMessage(WSMessage{Action: "vpn_error", NodeID: nodeID, Error: err.Error()})
|
||||
@@ -236,7 +286,7 @@ func handleMessage(conn *websocket.Conn, msg WSMessage, dataDir, nodeID, headsca
|
||||
}
|
||||
}()
|
||||
// Ensure Tailscale is running so the server can reach the node
|
||||
go ensureTailscale(dataDir, nodeID, headscaleURL, headscaleAuthKey, msg.Port)
|
||||
go ensureTailscale(dataDir, nodeID, msg.Port)
|
||||
|
||||
status := getInstanceStatus(dataDir, msg.InstanceID)
|
||||
_ = upsertInstance(dataDir, &InstanceInfo{ID: msg.InstanceID, TemplateName: msg.Type, Port: msg.Port, Status: status})
|
||||
@@ -282,7 +332,7 @@ func handleMessage(conn *websocket.Conn, msg WSMessage, dataDir, nodeID, headsca
|
||||
}
|
||||
}()
|
||||
// Ensure Tailscale is running so the server can reach the node
|
||||
go ensureTailscale(dataDir, nodeID, headscaleURL, headscaleAuthKey, msg.Port)
|
||||
go ensureTailscale(dataDir, nodeID, msg.Port)
|
||||
|
||||
status := getInstanceStatus(dataDir, msg.InstanceID)
|
||||
_ = upsertInstance(dataDir, &InstanceInfo{ID: msg.InstanceID, TemplateName: msg.Type, Port: msg.Port, Status: status})
|
||||
@@ -293,8 +343,9 @@ func handleMessage(conn *websocket.Conn, msg WSMessage, dataDir, nodeID, headsca
|
||||
}
|
||||
}
|
||||
|
||||
func ensureTailscale(dataDir, nodeID, headscaleURL, headscaleAuthKey string, port int) {
|
||||
if headscaleURL == "" || headscaleAuthKey == "" {
|
||||
func ensureTailscale(dataDir, nodeID string, port int) {
|
||||
hsURL, hsKey := getHeadscaleConfig()
|
||||
if hsURL == "" || hsKey == "" {
|
||||
log.Printf("Cannot ensure Tailscale: headscale config missing")
|
||||
return
|
||||
}
|
||||
@@ -302,7 +353,7 @@ func ensureTailscale(dataDir, nodeID, headscaleURL, headscaleAuthKey string, por
|
||||
return
|
||||
}
|
||||
log.Printf("Tailscale not running, starting it for instance port %d", port)
|
||||
ip, err := startTailscale(dataDir, nodeID, headscaleURL, headscaleAuthKey)
|
||||
ip, err := startTailscale(dataDir, nodeID, hsURL, hsKey)
|
||||
if err != nil {
|
||||
log.Printf("ensureTailscale start error: %v", err)
|
||||
return
|
||||
|
||||
Reference in New Issue
Block a user