feat(agent): v0.3.5 Windows inbound forwarding, UI actions, lifecycle
- Configure tailscale serve automatically for each instance on Windows userspace networking. - Add local UI buttons: start/stop/reset/delete instances (stop/start preserve volumes). - Clean shutdown: stop tailscaled and instances, notify server with instance_stopped. - Restart tailscaled on agent boot using persisted state when pre-auth key is absent. - Sync instance stopped/deleted status to dashboard (server/lib/websocket.ts). - Security: include prior authz/scoping changes across API routes, ephemeral pre-auth keys, ACL policy, internal API key. - Update SUIVI_VPN_ONDEMAND.md and docs/ONBOARDING_CLIENT.md. - Bump agent version to 0.3.5.
This commit is contained in:
+64
-1
@@ -2,10 +2,14 @@ package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -42,7 +46,7 @@ func main() {
|
||||
// Redirect agent logs to a file so the console can be hidden on Windows.
|
||||
agentLogPath := filepath.Join(*dataDir, "agent.log")
|
||||
if agentLogFile, err := os.OpenFile(agentLogPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644); err == nil {
|
||||
log.SetOutput(agentLogFile)
|
||||
log.SetOutput(io.MultiWriter(agentLogFile, uiLogWriter{}))
|
||||
} else {
|
||||
log.Printf("Cannot open agent log file %s: %v", agentLogPath, err)
|
||||
}
|
||||
@@ -65,9 +69,48 @@ func main() {
|
||||
go startWebSocket(cfg.Server, cfg.NodeID, *dataDir, cfg.HeadscaleURL, cfg.HeadscaleAuthKey)
|
||||
|
||||
shutdownCh := make(chan struct{})
|
||||
|
||||
// Capture Ctrl+C / SIGTERM so a console window close or service stop
|
||||
// triggers the same cleanup path as the tray "Quit" menu.
|
||||
go func() {
|
||||
sigCh := make(chan os.Signal, 1)
|
||||
signal.Notify(sigCh, os.Interrupt, syscall.SIGTERM)
|
||||
<-sigCh
|
||||
log.Println("Shutdown signal received")
|
||||
close(shutdownCh)
|
||||
}()
|
||||
|
||||
var cleanupWg sync.WaitGroup
|
||||
cleanupWg.Add(1)
|
||||
go func() {
|
||||
defer cleanupWg.Done()
|
||||
<-shutdownCh
|
||||
log.Println("Cleaning up before exit...")
|
||||
|
||||
// Stop Tailscale so the next agent start does not conflict on the
|
||||
// same socket/state.
|
||||
stopTailscale()
|
||||
|
||||
// Stop any running instances so containers are not left behind, but keep
|
||||
// their volumes intact so data survives the next agent start.
|
||||
if inst, err := loadInstances(*dataDir); err == nil {
|
||||
for id, info := range inst {
|
||||
if info.Status == "running" {
|
||||
log.Printf("Stopping instance %s", id)
|
||||
_ = dockerComposeStop(*dataDir, id)
|
||||
info.Status = "stopped"
|
||||
_ = sendMessage(WSMessage{Action: "instance_stopped", InstanceID: id})
|
||||
}
|
||||
}
|
||||
_ = saveInstances(*dataDir, inst)
|
||||
}
|
||||
log.Println("Cleanup complete")
|
||||
}()
|
||||
|
||||
if *noTray {
|
||||
log.Printf("[%s Agent] running in console mode (no tray)", APP_NAME)
|
||||
<-shutdownCh
|
||||
cleanupWg.Wait()
|
||||
return
|
||||
}
|
||||
|
||||
@@ -80,6 +123,7 @@ func main() {
|
||||
}()
|
||||
|
||||
<-shutdownCh
|
||||
cleanupWg.Wait()
|
||||
}
|
||||
|
||||
func startTailscaleAndReport(dataDir, nodeID, headscaleURL, authKey string) {
|
||||
@@ -99,4 +143,23 @@ func startTailscaleAndReport(dataDir, nodeID, headscaleURL, authKey string) {
|
||||
log.Printf("Sent tailscale_ip to server: %s", ip)
|
||||
break
|
||||
}
|
||||
|
||||
// Reconfigure tailscale serve for any instances that were left running
|
||||
// (e.g. after an agent restart while containers kept running).
|
||||
if inst, err := loadInstances(dataDir); err == nil {
|
||||
for id, info := range inst {
|
||||
if info.Status == "running" {
|
||||
log.Printf("Reconfiguring tailscale serve for running instance %s on port %d", id, info.Port)
|
||||
if err := setupTailscaleServe(info.Port); err != nil {
|
||||
log.Printf("setupTailscaleServe error for %s: %v", id, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Notify the local UI that the service status has changed.
|
||||
broadcastUI(map[string]interface{}{
|
||||
"action": "status",
|
||||
"status": buildUIStatus(dataDir),
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user