package main import ( "context" "fmt" "os" "os/signal" "runtime" "syscall" "github.com/fosrl/newt/logger" "github.com/fosrl/newt/updates" "github.com/fosrl/olm/olm" ) func main() { // Check if we're running as a Windows service if isWindowsService() { runService("OlmWireguardService", false, os.Args[1:]) fmt.Println("Running as Windows service") return } // Handle service management commands on Windows if runtime.GOOS == "windows" { var command string if len(os.Args) > 1 { command = os.Args[1] } else { command = "default" } switch command { case "install": err := installService() if err != nil { fmt.Printf("Failed to install service: %v\n", err) os.Exit(1) } fmt.Println("Service installed successfully") return case "remove", "uninstall": err := removeService() if err != nil { fmt.Printf("Failed to remove service: %v\n", err) os.Exit(1) } fmt.Println("Service removed successfully") return case "start": // Pass the remaining arguments (after "start") to the service serviceArgs := os.Args[2:] err := startService(serviceArgs) if err != nil { fmt.Printf("Failed to start service: %v\n", err) os.Exit(1) } fmt.Println("Service started successfully") return case "stop": err := stopService() if err != nil { fmt.Printf("Failed to stop service: %v\n", err) os.Exit(1) } fmt.Println("Service stopped successfully") return case "status": status, err := getServiceStatus() if err != nil { fmt.Printf("Failed to get service status: %v\n", err) os.Exit(1) } fmt.Printf("Service status: %s\n", status) return case "debug": // get the status and if it is Not Installed then install it first status, err := getServiceStatus() if err != nil { fmt.Printf("Failed to get service status: %v\n", err) os.Exit(1) } if status == "Not Installed" { err := installService() if err != nil { fmt.Printf("Failed to install service: %v\n", err) os.Exit(1) } fmt.Println("Service installed successfully, now running in debug mode") } // Pass the remaining arguments (after "debug") to the service serviceArgs := os.Args[2:] err = debugService(serviceArgs) if err != nil { fmt.Printf("Failed to debug service: %v\n", err) os.Exit(1) } return case "logs": err := watchLogFile(false) if err != nil { fmt.Printf("Failed to watch log file: %v\n", err) os.Exit(1) } return case "config": if runtime.GOOS == "windows" { showServiceConfig() } else { fmt.Println("Service configuration is only available on Windows") } return case "help", "--help", "-h": fmt.Println("Olm WireGuard VPN Client") fmt.Println("\nWindows Service Management:") fmt.Println(" install Install the service") fmt.Println(" remove Remove the service") fmt.Println(" start [args] Start the service with optional arguments") fmt.Println(" stop Stop the service") fmt.Println(" status Show service status") fmt.Println(" debug [args] Run service in debug mode with optional arguments") fmt.Println(" logs Tail the service log file") fmt.Println(" config Show current service configuration") fmt.Println("\nExamples:") fmt.Println(" olm start --enable-http --http-addr :9452") fmt.Println(" olm debug --endpoint https://example.com --id myid --secret mysecret") fmt.Println("\nFor console mode, run without arguments or with standard flags.") return default: // get the status and if it is Not Installed then install it first status, err := getServiceStatus() if err != nil { fmt.Printf("Failed to get service status: %v\n", err) os.Exit(1) } if status == "Not Installed" { err := installService() if err != nil { fmt.Printf("Failed to install service: %v\n", err) os.Exit(1) } fmt.Println("Service installed successfully, now running") } // Pass the remaining arguments (after "debug") to the service serviceArgs := os.Args[1:] err = debugService(serviceArgs) if err != nil { fmt.Printf("Failed to debug service: %v\n", err) os.Exit(1) } return } } // Create a context that will be cancelled on interrupt signals signalCtx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) defer stop() // Create a separate context for programmatic shutdown (e.g., via API exit) ctx, cancel := context.WithCancel(context.Background()) defer cancel() // Run in console mode runOlmMainWithArgs(ctx, cancel, signalCtx, os.Args[1:]) } func runOlmMainWithArgs(ctx context.Context, cancel context.CancelFunc, signalCtx context.Context, args []string) { // Setup Windows event logging if on Windows if runtime.GOOS == "windows" { setupWindowsEventLog() } else { // Initialize logger for non-Windows platforms logger.Init(nil) } // Load configuration from file, env vars, and CLI args // Priority: CLI args > Env vars > Config file > Defaults // Use the passed args parameter instead of os.Args[1:] to support Windows service mode config, showVersion, showConfig, err := LoadConfig(args) if err != nil { fmt.Printf("Failed to load configuration: %v\n", err) return } // Handle --show-config flag if showConfig { config.ShowConfig() os.Exit(0) } olmVersion := "version_replaceme" if showVersion { fmt.Println("Olm version " + olmVersion) os.Exit(0) } logger.Info("Olm version %s", olmVersion) config.Version = olmVersion if err := SaveConfig(config); err != nil { logger.Error("Failed to save full olm config: %v", err) } else { logger.Debug("Saved full olm config with all options") } if err := updates.CheckForUpdate("fosrl", "olm", config.Version); err != nil { logger.Debug("Failed to check for updates: %v", err) } // Create a new olm.Config struct and copy values from the main config olmConfig := olm.GlobalConfig{ LogLevel: config.LogLevel, EnableAPI: config.EnableAPI, HTTPAddr: config.HTTPAddr, SocketPath: config.SocketPath, Version: config.Version, Agent: "Olm CLI", OnExit: cancel, // Pass cancel function directly to trigger shutdown OnTerminated: cancel, } olm.Init(ctx, olmConfig) if err := olm.StartApi(); err != nil { logger.Fatal("Failed to start API server: %v", err) } if config.ID != "" && config.Secret != "" && config.Endpoint != "" { tunnelConfig := olm.TunnelConfig{ Endpoint: config.Endpoint, ID: config.ID, Secret: config.Secret, UserToken: config.UserToken, MTU: config.MTU, DNS: config.DNS, UpstreamDNS: config.UpstreamDNS, InterfaceName: config.InterfaceName, Holepunch: !config.DisableHolepunch, TlsClientCert: config.TlsClientCert, PingIntervalDuration: config.PingIntervalDuration, PingTimeoutDuration: config.PingTimeoutDuration, OrgID: config.OrgID, OverrideDNS: config.OverrideDNS, DisableRelay: config.DisableRelay, EnableUAPI: true, } go olm.StartTunnel(tunnelConfig) } else { logger.Info("Incomplete tunnel configuration, not starting tunnel") } // Wait for either signal or programmatic shutdown select { case <-signalCtx.Done(): logger.Info("Shutdown signal received, cleaning up...") case <-ctx.Done(): logger.Info("Shutdown requested via API, cleaning up...") } // Clean up resources olm.Close() logger.Info("Shutdown complete") }