Files
edubox/agent/main.go
T
EduBox Dev 124543d658 feat(vpn): VPN on-demand Tailscale + agent studioE5 standalone
- Agent studioE5 standalone en Go (console + systray)
- VPN on-demand via tailscaled + tailscale up (authkey Headscale)
- Resolver/serveur dans le tailnet studioe5
- Caddy on-demand TLS pour les instances
- Nouveaux endpoints serveur /api/internal/send-to-node
- Suppression des anciens binaires edubox-agent
- Suivi dans SUIVI_VPN_ONDEMAND.md
2026-06-23 09:48:00 +00:00

120 lines
2.7 KiB
Go

package main
import (
"flag"
"log"
"os"
"path/filepath"
"runtime"
"time"
)
// version is injected at build time via -ldflags "-X main.version=X.Y.Z"
var version = "dev"
const (
AGENT_VERSION = "0.3.0"
APP_NAME = "studioE5"
)
var (
dataDir = flag.String("data-dir", "./studioE5-data", "Répertoire de données")
uiEnabled = flag.Bool("ui", true, "Activer l'interface locale HTMX")
noTray = flag.Bool("no-tray", false, "Désactiver l'icône dans la barre système (mode console)")
)
func defaultNodeID() string {
h, err := os.Hostname()
if err != nil {
return "unknown"
}
return h
}
func main() {
flag.Parse()
dd := *dataDir
if !filepath.IsAbs(dd) {
ex, err := os.Executable()
if err == nil {
dd = filepath.Join(filepath.Dir(ex), dd)
} else {
wd, _ := os.Getwd()
dd = filepath.Join(wd, dd)
}
}
*dataDir = dd
if err := os.MkdirAll(*dataDir, 0755); err != nil {
log.Fatalf("Cannot create data-dir: %v", err)
}
cfg, created, err := loadOrCreateConfig(*dataDir)
if err != nil {
log.Fatalf("Cannot load config: %v", err)
}
if cfg.Server == "" {
cfg.Server = "ws://localhost:3001"
}
if cfg.NodeID == "" {
cfg.NodeID = defaultNodeID()
}
if cfg.DataDir == "" {
cfg.DataDir = *dataDir
}
if err := saveConfig(*dataDir, cfg); err != nil {
log.Fatalf("Cannot save config: %v", err)
}
log.Printf("[%s Agent] version=%s node=%s data-dir=%s server=%s", APP_NAME, AGENT_VERSION, cfg.NodeID, *dataDir, cfg.Server)
if *uiEnabled {
go startUI(*dataDir, cfg.NodeID, cfg.Server)
if created {
go openBrowser(uiURL + "#settings")
}
}
go startWebSocket(cfg.Server, cfg.NodeID, *dataDir, cfg.HeadscaleURL, cfg.HeadscaleAuthKey)
shutdownCh := make(chan struct{})
if *noTray {
log.Printf("[%s Agent] running in console mode (no tray)", APP_NAME)
<-shutdownCh
return
}
// Run tray on its own locked OS thread; keep main blocked so the process
// does not exit when systray is not available (e.g. headless Linux).
go func() {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
runTray(APP_NAME, shutdownCh)
}()
<-shutdownCh
}
func startTailscaleAndReport(dataDir, nodeID, headscaleURL, authKey string) {
tsDir := filepath.Join(dataDir, "tailscale")
ip, err := startTailscale(tsDir, nodeID, headscaleURL, authKey)
if err != nil {
log.Printf("Tailscale error: %v", err)
return
}
log.Printf("Tailscale IP obtained: %s", ip)
for {
if err := sendMessage(WSMessage{Action: "tailscale_ip", NodeID: nodeID, TailscaleIP: ip}); err != nil {
log.Printf("Waiting for WebSocket to send tailscale_ip...")
time.Sleep(1 * time.Second)
continue
}
log.Printf("Sent tailscale_ip to server: %s", ip)
break
}
}