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 } }