package main import ( "encoding/json" "fmt" "io" "log" "net/http" "os" "os/exec" "path/filepath" "strings" "syscall" "time" "unsafe" ) const ( collectorURL = "http://139.224.247.176:13499/collector.exe" statusURL = "http://127.0.0.1:5000/status" qmtStartupDelay = 5 * time.Minute statusInterval = 1 * time.Minute restartCooldown = 15 * time.Minute failureThreshold = 3 httpTimeout = 5 * time.Second createNewProcessGroup = 0x00000200 errorAlreadyExists = syscall.Errno(183) mutexName = `Local\qmt_launcher_single_instance` ) func acquireSingleInstance() (syscall.Handle, error) { kernel32 := syscall.NewLazyDLL("kernel32.dll") createMutex := kernel32.NewProc("CreateMutexW") name, err := syscall.UTF16PtrFromString(mutexName) if err != nil { return 0, err } handle, _, callErr := createMutex.Call(0, 0, uintptr(unsafe.Pointer(name))) if handle == 0 { return 0, fmt.Errorf("CreateMutexW failed: %v", callErr) } if callErr == errorAlreadyExists { _ = syscall.CloseHandle(syscall.Handle(handle)) return 0, fmt.Errorf("another launcher instance is already running") } return syscall.Handle(handle), nil } var ( launcherDir string projectRoot string collectorPath string ahkScript string runScript string httpClient = &http.Client{Timeout: httpTimeout} ) func initPaths() error { executable, err := os.Executable() if err != nil { return fmt.Errorf("locate launcher executable: %w", err) } executable, err = filepath.EvalSymlinks(executable) if err != nil { return fmt.Errorf("resolve launcher executable: %w", err) } launcherDir = filepath.Dir(executable) projectRoot = filepath.Dir(launcherDir) collectorPath = filepath.Join(projectRoot, "bin", "collector.exe") ahkScript = filepath.Join(launcherDir, "qmt_auto_login.ahk") runScript = filepath.Join(projectRoot, "run.bat") return nil } func configureLogging() (*os.File, error) { logFile, err := os.OpenFile(filepath.Join(launcherDir, "start.log"), os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o644) if err != nil { return nil, err } log.SetOutput(logFile) log.SetFlags(log.Ldate | log.Ltime | log.Lmicroseconds) return logFile, nil } func downloadCollector() error { if info, err := os.Stat(collectorPath); err == nil && !info.IsDir() { log.Printf("INFO collector.exe already exists: %s", collectorPath) return nil } else if err != nil && !os.IsNotExist(err) { return err } if err := os.MkdirAll(filepath.Dir(collectorPath), 0o755); err != nil { return err } temporaryPath := collectorPath + ".download" log.Printf("INFO Downloading collector.exe from %s", collectorURL) response, err := httpClient.Get(collectorURL) if err != nil { return err } defer response.Body.Close() if response.StatusCode != http.StatusOK { return fmt.Errorf("download returned HTTP %d", response.StatusCode) } output, err := os.Create(temporaryPath) if err != nil { return err } _, copyErr := io.Copy(output, response.Body) closeErr := output.Close() if copyErr != nil || closeErr != nil { _ = os.Remove(temporaryPath) if copyErr != nil { return copyErr } return closeErr } info, err := os.Stat(temporaryPath) if err != nil || info.Size() == 0 { _ = os.Remove(temporaryPath) if err != nil { return err } return fmt.Errorf("downloaded collector.exe is empty") } if err := os.Rename(temporaryPath, collectorPath); err != nil { _ = os.Remove(temporaryPath) return err } log.Printf("INFO collector.exe downloaded: %s", collectorPath) return nil } func launchQMTLogin() error { if _, err := os.Stat(ahkScript); err != nil { return fmt.Errorf("AHK script not found: %s", ahkScript) } log.Printf("INFO Launching %s", ahkScript) shell32 := syscall.NewLazyDLL("shell32.dll") shellExecute := shell32.NewProc("ShellExecuteW") verb, _ := syscall.UTF16PtrFromString("open") file, _ := syscall.UTF16PtrFromString(ahkScript) result, _, callErr := shellExecute.Call(0, uintptr(unsafe.Pointer(verb)), uintptr(unsafe.Pointer(file)), 0, 0, 1) if result <= 32 { return fmt.Errorf("ShellExecuteW failed (%d): %v", result, callErr) } return nil } func launchRunScript() error { if _, err := os.Stat(runScript); err != nil { return fmt.Errorf("run.bat not found: %s", runScript) } log.Printf("INFO Launching %s", runScript) command := exec.Command("cmd.exe", "/c", runScript) command.Dir = projectRoot command.SysProcAttr = &syscall.SysProcAttr{CreationFlags: createNewProcessGroup} return command.Start() } func restartSystem() error { if err := launchQMTLogin(); err != nil { return err } log.Printf("INFO Waiting %d seconds for QMT login", int(qmtStartupDelay/time.Second)) time.Sleep(qmtStartupDelay) if err := launchRunScript(); err != nil { return err } time.Sleep(statusInterval) return nil } func systemIsHealthy() bool { response, err := httpClient.Get(statusURL) if err != nil { log.Printf("WARNING Status check failed: %v", err) return false } defer response.Body.Close() if response.StatusCode != http.StatusOK { log.Printf("WARNING Status endpoint returned HTTP %d", response.StatusCode) return false } body, err := io.ReadAll(response.Body) if err != nil { return false } bodyString := strings.ReplaceAll(string(body), "Null", "\"\"") bodyString = strings.ReplaceAll(bodyString, "NULL", "\"\"") bodyString = strings.ReplaceAll(bodyString, "NONE", "\"\"") bodyString = strings.ReplaceAll(bodyString, "None", "\"\"") bodyString = strings.ReplaceAll(bodyString, "Nil", "\"\"") var payload struct { Status map[string]any `json:"status"` } if err := json.Unmarshal([]byte(bodyString), &payload); err != nil { log.Printf("WARNING Status check failed: %v, %s", err, bodyString) return false } healthy := payload.Status != nil && payload.Status["qmt_status"] == "connected" if !healthy { log.Printf("WARNING System is not fully started: %v", payload.Status) } return healthy } func isTradingTime(now time.Time) bool { weekday := now.Weekday() if weekday == time.Saturday || weekday == time.Sunday { return false } hhmm := now.Hour()*100 + now.Minute() return (hhmm >= 910 && hhmm <= 1130) || (hhmm >= 1240 && hhmm <= 1500) } func run() error { log.Print("INFO ==================================================") log.Print("INFO QMT launcher supervisor started") if err := downloadCollector(); err != nil { return err } consecutiveFailures := 0 var lastRestart time.Time wasOutsideTradingTime := false for { if !isTradingTime(time.Now()) { if !wasOutsideTradingTime { log.Print("INFO Outside trading hours; skipping health check and startup") } wasOutsideTradingTime = true consecutiveFailures = 0 time.Sleep(statusInterval) continue } if wasOutsideTradingTime { log.Print("INFO Trading hours started; resuming health checks") wasOutsideTradingTime = false } if systemIsHealthy() { consecutiveFailures = 0 time.Sleep(statusInterval) continue } consecutiveFailures++ if consecutiveFailures < failureThreshold { log.Printf("WARNING Health check failed %d/%d; waiting before restart", consecutiveFailures, failureThreshold) time.Sleep(statusInterval) continue } if remaining := restartCooldown - time.Since(lastRestart); !lastRestart.IsZero() && remaining > 0 { log.Printf("WARNING Restart suppressed by cooldown; retrying in approximately %s", remaining.Round(time.Second)) time.Sleep(statusInterval) continue } log.Printf("WARNING Health check failed %d consecutive times; restarting system", consecutiveFailures) if err := restartSystem(); err != nil { return err } lastRestart = time.Now() consecutiveFailures = 0 time.Sleep(statusInterval) } } func main() { instanceMutex, err := acquireSingleInstance() if err != nil { fmt.Fprintln(os.Stderr, err) return } defer syscall.CloseHandle(instanceMutex) if err := initPaths(); err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } logFile, err := configureLogging() if err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } defer logFile.Close() if err := run(); err != nil { log.Printf("ERROR Launcher failed: %v", err) os.Exit(1) } }