agent v0.3.8: fix crash UI notifications, auto Podman machine DNS, WordPress 7.0.0 ready template

This commit is contained in:
EduBox Dev
2026-06-26 15:24:21 +00:00
parent a414f03a59
commit cf8b66340a
15 changed files with 590 additions and 35 deletions
+67 -8
View File
@@ -18,6 +18,7 @@ type WSMessage struct {
Type string `json:"type,omitempty"`
Port int `json:"port,omitempty"`
ComposeConfig string `json:"composeConfig,omitempty"`
InitScript string `json:"initScript,omitempty"`
StudentId string `json:"studentId,omitempty"`
StudentName string `json:"studentName,omitempty"`
Error string `json:"error,omitempty"`
@@ -101,7 +102,14 @@ func notifyUI(msg map[string]interface{}) {
log.Printf("notifyUI: broadcasting to %d UI clients", len(notifiers))
for _, fn := range notifiers {
go fn(msg)
go func(notify uiNotifier) {
defer func() {
if r := recover(); r != nil {
log.Printf("PANIC in notifyUI goroutine: %v", r)
}
}()
notify(msg)
}(fn)
}
}
@@ -192,6 +200,12 @@ func startWebSocket(serverAddr, nodeID, dataDir, headscaleURL, headscaleAuthKey
}
func handleMessage(conn *websocket.Conn, msg WSMessage, dataDir, nodeID string) {
defer func() {
if r := recover(); r != nil {
log.Printf("PANIC in handleMessage (action=%s): %v", msg.Action, r)
}
}()
switch msg.Action {
case "set_token":
if msg.Token != "" {
@@ -251,10 +265,15 @@ func handleMessage(conn *websocket.Conn, msg WSMessage, dataDir, nodeID string)
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"})
go sendMessage(WSMessage{Action: "vpn_error", NodeID: nodeID, Error: "headscale config missing"})
return
}
go func() {
defer func() {
if r := recover(); r != nil {
log.Printf("PANIC in start_vpn goroutine: %v", r)
}
}()
ip, err := startTailscale(dataDir, nodeID, hsURL, hsKey)
if err != nil {
log.Printf("start_vpn error: %v", err)
@@ -282,7 +301,14 @@ func handleMessage(conn *websocket.Conn, msg WSMessage, dataDir, nodeID string)
case "stop_vpn":
log.Printf("Server requested VPN stop")
stopTailscale()
sendMessage(WSMessage{Action: "vpn_stopped", NodeID: nodeID})
go func() {
defer func() {
if r := recover(); r != nil {
log.Printf("PANIC in stop_vpn goroutine: %v", r)
}
}()
sendMessage(WSMessage{Action: "vpn_stopped", NodeID: nodeID})
}()
case "activation_failed":
log.Printf("handleMessage: activation_failed received, error=%s", msg.Error)
notifyUI(map[string]interface{}{
@@ -291,19 +317,27 @@ func handleMessage(conn *websocket.Conn, msg WSMessage, dataDir, nodeID string)
})
case "start":
log.Printf("Start instance %s on port %d", msg.InstanceID, msg.Port)
go handleStartInstance(dataDir, nodeID, msg.InstanceID, msg.Type, msg.ComposeConfig, msg.Port)
go func() {
defer func() {
if r := recover(); r != nil {
log.Printf("PANIC in start goroutine instance=%s: %v", msg.InstanceID, r)
}
}()
handleStartInstance(dataDir, nodeID, msg.InstanceID, msg.Type, msg.ComposeConfig, msg.InitScript, msg.Port)
}()
case "stop":
log.Printf("Stop instance %s", msg.InstanceID)
if inst, _ := loadInstances(dataDir); inst[msg.InstanceID] != nil {
removeTailscaleServe(inst[msg.InstanceID].Port)
}
if err := dockerComposeDown(dataDir, msg.InstanceID); err != nil {
log.Printf("dockerComposeDown error: %v", err)
if err := dockerComposeStop(dataDir, msg.InstanceID); err != nil {
log.Printf("dockerComposeStop error: %v", err)
}
if inst, _ := loadInstances(dataDir); inst[msg.InstanceID] != nil {
inst[msg.InstanceID].Status = "stopped"
_ = saveInstances(dataDir, inst)
}
go sendMessage(WSMessage{Action: "instance_stopped", InstanceID: msg.InstanceID})
notifyUI(map[string]interface{}{"action": "instances_updated"})
case "delete":
log.Printf("Delete instance %s", msg.InstanceID)
@@ -312,17 +346,37 @@ func handleMessage(conn *websocket.Conn, msg WSMessage, dataDir, nodeID string)
}
dockerComposeRm(dataDir, msg.InstanceID)
removeInstance(dataDir, msg.InstanceID)
go sendMessage(WSMessage{Action: "instance_deleted", InstanceID: msg.InstanceID})
notifyUI(map[string]interface{}{"action": "instances_updated"})
case "reset":
log.Printf("Reset instance %s", msg.InstanceID)
dockerComposeRm(dataDir, msg.InstanceID)
go handleStartInstance(dataDir, nodeID, msg.InstanceID, msg.Type, msg.ComposeConfig, msg.Port)
go func() {
defer func() {
if r := recover(); r != nil {
log.Printf("PANIC in reset goroutine instance=%s: %v", msg.InstanceID, r)
}
}()
handleStartInstance(dataDir, nodeID, msg.InstanceID, msg.Type, msg.ComposeConfig, msg.InitScript, msg.Port)
}()
default:
log.Printf("Unknown action: %s", msg.Action)
}
}
func handleStartInstance(dataDir, nodeID, instanceID, instanceType, composeConfig string, port int) {
func handleStartInstance(dataDir, nodeID, instanceID, instanceType, composeConfig, initScript string, port int) {
defer func() {
if r := recover(); r != nil {
log.Printf("PANIC in handleStartInstance instance=%s: %v", instanceID, r)
_ = upsertInstance(dataDir, &InstanceInfo{ID: instanceID, TemplateName: instanceType, Port: port, Status: "error"})
sendMessage(WSMessage{Action: "instance_error", InstanceID: instanceID, Error: fmt.Sprintf("internal panic: %v", r)})
sendInstanceProgress(instanceID, "start", "0", "Erreur interne")
notifyUI(map[string]interface{}{"action": "instances_updated"})
}
}()
log.Printf("handleStartInstance begin: instance=%s type=%s port=%d dataDir=%s initScriptLen=%d", instanceID, instanceType, port, dataDir, len(initScript))
notifyInstanceProgress := func(percent, message string) {
sendInstanceProgress(instanceID, "start", percent, message)
}
@@ -344,6 +398,11 @@ func handleStartInstance(dataDir, nodeID, instanceID, instanceType, composeConfi
notifyUI(map[string]interface{}{"action": "instances_updated"})
return
}
if initScript != "" {
if err := writeInitScript(dataDir, instanceID, initScript); err != nil {
log.Printf("writeInitScript error: %v", err)
}
}
notifyInstanceProgress("30", "Configuration de l'application...")
if err := dockerComposeUp(dataDir, instanceID); err != nil {