diff --git a/config.go b/config.go new file mode 100644 index 0000000..d45328f --- /dev/null +++ b/config.go @@ -0,0 +1,492 @@ +package main + +import ( + "encoding/json" + "flag" + "fmt" + "os" + "path/filepath" + "runtime" + "time" +) + +// OlmConfig holds all configuration options for the Olm client +type OlmConfig struct { + // Connection settings + Endpoint string `json:"endpoint"` + ID string `json:"id"` + Secret string `json:"secret"` + + // Network settings + MTU string `json:"mtu"` + DNS string `json:"dns"` + InterfaceName string `json:"interface"` + + // Logging + LogLevel string `json:"logLevel"` + + // HTTP server + EnableHTTP bool `json:"enableHttp"` + HTTPAddr string `json:"httpAddr"` + + // Ping settings + PingInterval string `json:"pingInterval"` + PingTimeout string `json:"pingTimeout"` + + // Advanced + Holepunch bool `json:"holepunch"` + TlsClientCert string `json:"tlsClientCert"` + + // Parsed values (not in JSON) + PingIntervalDuration time.Duration `json:"-"` + PingTimeoutDuration time.Duration `json:"-"` + + // Source tracking (not in JSON) + sources map[string]string `json:"-"` +} + +// ConfigSource tracks where each config value came from +type ConfigSource string + +const ( + SourceDefault ConfigSource = "default" + SourceFile ConfigSource = "file" + SourceEnv ConfigSource = "environment" + SourceCLI ConfigSource = "cli" +) + +// DefaultConfig returns a config with default values +func DefaultConfig() *OlmConfig { + config := &OlmConfig{ + MTU: "1280", + DNS: "8.8.8.8", + LogLevel: "INFO", + InterfaceName: "olm", + EnableHTTP: false, + HTTPAddr: ":9452", + PingInterval: "3s", + PingTimeout: "5s", + Holepunch: false, + sources: make(map[string]string), + } + + // Track default sources + config.sources["mtu"] = string(SourceDefault) + config.sources["dns"] = string(SourceDefault) + config.sources["logLevel"] = string(SourceDefault) + config.sources["interface"] = string(SourceDefault) + config.sources["enableHttp"] = string(SourceDefault) + config.sources["httpAddr"] = string(SourceDefault) + config.sources["pingInterval"] = string(SourceDefault) + config.sources["pingTimeout"] = string(SourceDefault) + config.sources["holepunch"] = string(SourceDefault) + + return config +} + +// getOlmConfigPath returns the path to the olm config file +func getOlmConfigPath() string { + configFile := os.Getenv("CONFIG_FILE") + if configFile != "" { + return configFile + } + + var configDir string + switch runtime.GOOS { + case "darwin": + configDir = filepath.Join(os.Getenv("HOME"), "Library", "Application Support", "olm-client") + case "windows": + configDir = filepath.Join(os.Getenv("PROGRAMDATA"), "olm", "olm-client") + default: // linux and others + configDir = filepath.Join(os.Getenv("HOME"), ".config", "olm-client") + } + + if err := os.MkdirAll(configDir, 0755); err != nil { + fmt.Printf("Warning: Failed to create config directory: %v\n", err) + } + + return filepath.Join(configDir, "config.json") +} + +// LoadConfig loads configuration from file, env vars, and CLI args +// Priority: CLI args > Env vars > Config file > Defaults +// Returns: (config, showVersion, showConfig, error) +func LoadConfig(args []string) (*OlmConfig, bool, bool, error) { + // Start with defaults + config := DefaultConfig() + + // Load from config file (if exists) + fileConfig, err := loadConfigFromFile() + if err != nil { + return nil, false, false, fmt.Errorf("failed to load config file: %w", err) + } + if fileConfig != nil { + mergeConfigs(config, fileConfig) + } + + // Override with environment variables + loadConfigFromEnv(config) + + // Override with CLI arguments + showVersion, showConfig, err := loadConfigFromCLI(config, args) + if err != nil { + return nil, false, false, err + } + + // Parse duration strings + if err := config.parseDurations(); err != nil { + return nil, false, false, err + } + + return config, showVersion, showConfig, nil +} + +// loadConfigFromFile loads configuration from the JSON config file +func loadConfigFromFile() (*OlmConfig, error) { + configPath := getOlmConfigPath() + data, err := os.ReadFile(configPath) + if err != nil { + if os.IsNotExist(err) { + return nil, nil // File doesn't exist, not an error + } + return nil, err + } + + var config OlmConfig + if err := json.Unmarshal(data, &config); err != nil { + return nil, fmt.Errorf("failed to parse config file: %w", err) + } + + return &config, nil +} + +// loadConfigFromEnv loads configuration from environment variables +func loadConfigFromEnv(config *OlmConfig) { + if val := os.Getenv("PANGOLIN_ENDPOINT"); val != "" { + config.Endpoint = val + config.sources["endpoint"] = string(SourceEnv) + } + if val := os.Getenv("OLM_ID"); val != "" { + config.ID = val + config.sources["id"] = string(SourceEnv) + } + if val := os.Getenv("OLM_SECRET"); val != "" { + config.Secret = val + config.sources["secret"] = string(SourceEnv) + } + if val := os.Getenv("MTU"); val != "" { + config.MTU = val + config.sources["mtu"] = string(SourceEnv) + } + if val := os.Getenv("DNS"); val != "" { + config.DNS = val + config.sources["dns"] = string(SourceEnv) + } + if val := os.Getenv("LOG_LEVEL"); val != "" { + config.LogLevel = val + config.sources["logLevel"] = string(SourceEnv) + } + if val := os.Getenv("INTERFACE"); val != "" { + config.InterfaceName = val + config.sources["interface"] = string(SourceEnv) + } + if val := os.Getenv("HTTP_ADDR"); val != "" { + config.HTTPAddr = val + config.sources["httpAddr"] = string(SourceEnv) + } + if val := os.Getenv("PING_INTERVAL"); val != "" { + config.PingInterval = val + config.sources["pingInterval"] = string(SourceEnv) + } + if val := os.Getenv("PING_TIMEOUT"); val != "" { + config.PingTimeout = val + config.sources["pingTimeout"] = string(SourceEnv) + } + if val := os.Getenv("ENABLE_HTTP"); val == "true" { + config.EnableHTTP = true + config.sources["enableHttp"] = string(SourceEnv) + } + if val := os.Getenv("HOLEPUNCH"); val == "true" { + config.Holepunch = true + config.sources["holepunch"] = string(SourceEnv) + } +} + +// loadConfigFromCLI loads configuration from command-line arguments +func loadConfigFromCLI(config *OlmConfig, args []string) (bool, bool, error) { + serviceFlags := flag.NewFlagSet("service", flag.ContinueOnError) + + // Store original values to detect changes + origValues := map[string]interface{}{ + "endpoint": config.Endpoint, + "id": config.ID, + "secret": config.Secret, + "mtu": config.MTU, + "dns": config.DNS, + "logLevel": config.LogLevel, + "interface": config.InterfaceName, + "httpAddr": config.HTTPAddr, + "pingInterval": config.PingInterval, + "pingTimeout": config.PingTimeout, + "enableHttp": config.EnableHTTP, + "holepunch": config.Holepunch, + } + + // Define flags + serviceFlags.StringVar(&config.Endpoint, "endpoint", config.Endpoint, "Endpoint of your Pangolin server") + serviceFlags.StringVar(&config.ID, "id", config.ID, "Olm ID") + serviceFlags.StringVar(&config.Secret, "secret", config.Secret, "Olm secret") + serviceFlags.StringVar(&config.MTU, "mtu", config.MTU, "MTU to use") + serviceFlags.StringVar(&config.DNS, "dns", config.DNS, "DNS server to use") + serviceFlags.StringVar(&config.LogLevel, "log-level", config.LogLevel, "Log level (DEBUG, INFO, WARN, ERROR, FATAL)") + serviceFlags.StringVar(&config.InterfaceName, "interface", config.InterfaceName, "Name of the WireGuard interface") + serviceFlags.StringVar(&config.HTTPAddr, "http-addr", config.HTTPAddr, "HTTP server address (e.g., ':9452')") + serviceFlags.StringVar(&config.PingInterval, "ping-interval", config.PingInterval, "Interval for pinging the server") + serviceFlags.StringVar(&config.PingTimeout, "ping-timeout", config.PingTimeout, "Timeout for each ping") + serviceFlags.BoolVar(&config.EnableHTTP, "enable-http", config.EnableHTTP, "Enable HTTP server for receiving connection requests") + serviceFlags.BoolVar(&config.Holepunch, "holepunch", config.Holepunch, "Enable hole punching") + + version := serviceFlags.Bool("version", false, "Print the version") + showConfig := serviceFlags.Bool("show-config", false, "Show configuration sources and exit") + + // Parse the arguments + if err := serviceFlags.Parse(args); err != nil { + return false, false, err + } + + // Track which values were changed by CLI args + if config.Endpoint != origValues["endpoint"].(string) { + config.sources["endpoint"] = string(SourceCLI) + } + if config.ID != origValues["id"].(string) { + config.sources["id"] = string(SourceCLI) + } + if config.Secret != origValues["secret"].(string) { + config.sources["secret"] = string(SourceCLI) + } + if config.MTU != origValues["mtu"].(string) { + config.sources["mtu"] = string(SourceCLI) + } + if config.DNS != origValues["dns"].(string) { + config.sources["dns"] = string(SourceCLI) + } + if config.LogLevel != origValues["logLevel"].(string) { + config.sources["logLevel"] = string(SourceCLI) + } + if config.InterfaceName != origValues["interface"].(string) { + config.sources["interface"] = string(SourceCLI) + } + if config.HTTPAddr != origValues["httpAddr"].(string) { + config.sources["httpAddr"] = string(SourceCLI) + } + if config.PingInterval != origValues["pingInterval"].(string) { + config.sources["pingInterval"] = string(SourceCLI) + } + if config.PingTimeout != origValues["pingTimeout"].(string) { + config.sources["pingTimeout"] = string(SourceCLI) + } + if config.EnableHTTP != origValues["enableHttp"].(bool) { + config.sources["enableHttp"] = string(SourceCLI) + } + if config.Holepunch != origValues["holepunch"].(bool) { + config.sources["holepunch"] = string(SourceCLI) + } + + return *version, *showConfig, nil +} + +// parseDurations parses the duration strings into time.Duration +func (c *OlmConfig) parseDurations() error { + var err error + + // Parse ping interval + if c.PingInterval != "" { + c.PingIntervalDuration, err = time.ParseDuration(c.PingInterval) + if err != nil { + fmt.Printf("Invalid PING_INTERVAL value: %s, using default 3 seconds\n", c.PingInterval) + c.PingIntervalDuration = 3 * time.Second + c.PingInterval = "3s" + } + } else { + c.PingIntervalDuration = 3 * time.Second + c.PingInterval = "3s" + } + + // Parse ping timeout + if c.PingTimeout != "" { + c.PingTimeoutDuration, err = time.ParseDuration(c.PingTimeout) + if err != nil { + fmt.Printf("Invalid PING_TIMEOUT value: %s, using default 5 seconds\n", c.PingTimeout) + c.PingTimeoutDuration = 5 * time.Second + c.PingTimeout = "5s" + } + } else { + c.PingTimeoutDuration = 5 * time.Second + c.PingTimeout = "5s" + } + + return nil +} + +// mergeConfigs merges source config into destination (only non-empty values) +// Also tracks that these values came from a file +func mergeConfigs(dest, src *OlmConfig) { + if src.Endpoint != "" { + dest.Endpoint = src.Endpoint + dest.sources["endpoint"] = string(SourceFile) + } + if src.ID != "" { + dest.ID = src.ID + dest.sources["id"] = string(SourceFile) + } + if src.Secret != "" { + dest.Secret = src.Secret + dest.sources["secret"] = string(SourceFile) + } + if src.MTU != "" && src.MTU != "1280" { + dest.MTU = src.MTU + dest.sources["mtu"] = string(SourceFile) + } + if src.DNS != "" && src.DNS != "8.8.8.8" { + dest.DNS = src.DNS + dest.sources["dns"] = string(SourceFile) + } + if src.LogLevel != "" && src.LogLevel != "INFO" { + dest.LogLevel = src.LogLevel + dest.sources["logLevel"] = string(SourceFile) + } + if src.InterfaceName != "" && src.InterfaceName != "olm" { + dest.InterfaceName = src.InterfaceName + dest.sources["interface"] = string(SourceFile) + } + if src.HTTPAddr != "" && src.HTTPAddr != ":9452" { + dest.HTTPAddr = src.HTTPAddr + dest.sources["httpAddr"] = string(SourceFile) + } + if src.PingInterval != "" && src.PingInterval != "3s" { + dest.PingInterval = src.PingInterval + dest.sources["pingInterval"] = string(SourceFile) + } + if src.PingTimeout != "" && src.PingTimeout != "5s" { + dest.PingTimeout = src.PingTimeout + dest.sources["pingTimeout"] = string(SourceFile) + } + if src.TlsClientCert != "" { + dest.TlsClientCert = src.TlsClientCert + dest.sources["tlsClientCert"] = string(SourceFile) + } + // For booleans, we always take the source value if explicitly set + if src.EnableHTTP { + dest.EnableHTTP = src.EnableHTTP + dest.sources["enableHttp"] = string(SourceFile) + } + if src.Holepunch { + dest.Holepunch = src.Holepunch + dest.sources["holepunch"] = string(SourceFile) + } +} + +// SaveConfig saves the current configuration to the config file +func SaveConfig(config *OlmConfig) error { + configPath := getOlmConfigPath() + data, err := json.MarshalIndent(config, "", " ") + if err != nil { + return fmt.Errorf("failed to marshal config: %w", err) + } + return os.WriteFile(configPath, data, 0644) +} + +// UpdateFromWebsocket updates config with values received from websocket client +func (c *OlmConfig) UpdateFromWebsocket(id, secret, endpoint string) { + if id != "" { + c.ID = id + } + if secret != "" { + c.Secret = secret + } + if endpoint != "" { + c.Endpoint = endpoint + } +} + +// ShowConfig prints the configuration and the source of each value +func (c *OlmConfig) ShowConfig() { + configPath := getOlmConfigPath() + + fmt.Println("\n=== Olm Configuration ===\n") + fmt.Printf("Config File: %s\n", configPath) + + // Check if config file exists + if _, err := os.Stat(configPath); err == nil { + fmt.Printf("Config File Status: ✓ exists\n") + } else { + fmt.Printf("Config File Status: ✗ not found\n") + } + + fmt.Println("\n--- Configuration Values ---") + fmt.Println("(Format: Setting = Value [source])\n") + + // Helper to get source or default + getSource := func(key string) string { + if source, ok := c.sources[key]; ok { + return source + } + return string(SourceDefault) + } + + // Helper to format value (mask secrets) + formatValue := func(key, value string) string { + if key == "secret" && value != "" { + if len(value) > 8 { + return value[:4] + "****" + value[len(value)-4:] + } + return "****" + } + if value == "" { + return "(not set)" + } + return value + } + + // Connection settings + fmt.Println("Connection:") + fmt.Printf(" endpoint = %s [%s]\n", formatValue("endpoint", c.Endpoint), getSource("endpoint")) + fmt.Printf(" id = %s [%s]\n", formatValue("id", c.ID), getSource("id")) + fmt.Printf(" secret = %s [%s]\n", formatValue("secret", c.Secret), getSource("secret")) + + // Network settings + fmt.Println("\nNetwork:") + fmt.Printf(" mtu = %s [%s]\n", c.MTU, getSource("mtu")) + fmt.Printf(" dns = %s [%s]\n", c.DNS, getSource("dns")) + fmt.Printf(" interface = %s [%s]\n", c.InterfaceName, getSource("interface")) + + // Logging + fmt.Println("\nLogging:") + fmt.Printf(" log-level = %s [%s]\n", c.LogLevel, getSource("logLevel")) + + // HTTP server + fmt.Println("\nHTTP Server:") + fmt.Printf(" enable-http = %v [%s]\n", c.EnableHTTP, getSource("enableHttp")) + fmt.Printf(" http-addr = %s [%s]\n", c.HTTPAddr, getSource("httpAddr")) + + // Timing + fmt.Println("\nTiming:") + fmt.Printf(" ping-interval = %s [%s]\n", c.PingInterval, getSource("pingInterval")) + fmt.Printf(" ping-timeout = %s [%s]\n", c.PingTimeout, getSource("pingTimeout")) + + // Advanced + fmt.Println("\nAdvanced:") + fmt.Printf(" holepunch = %v [%s]\n", c.Holepunch, getSource("holepunch")) + if c.TlsClientCert != "" { + fmt.Printf(" tls-cert = %s [%s]\n", c.TlsClientCert, getSource("tlsClientCert")) + } + + // Source legend + fmt.Println("\n--- Source Legend ---") + fmt.Println(" default = Built-in default value") + fmt.Println(" file = Loaded from config file") + fmt.Println(" environment = Set via environment variable") + fmt.Println(" cli = Provided as command-line argument") + fmt.Println("\nPriority: cli > environment > file > default") + fmt.Println() +} diff --git a/main.go b/main.go index 0401fba..05a23f7 100644 --- a/main.go +++ b/main.go @@ -3,7 +3,6 @@ package main import ( "context" "encoding/json" - "flag" "fmt" "net" "os" @@ -19,7 +18,6 @@ import ( "github.com/fosrl/newt/websocket" "github.com/fosrl/olm/httpserver" "github.com/fosrl/olm/peermonitor" - "github.com/fosrl/olm/wgtester" "golang.zx2c4.com/wireguard/device" "golang.zx2c4.com/wireguard/tun" @@ -204,122 +202,41 @@ func runOlmMain(ctx context.Context) { } func runOlmMainWithArgs(ctx context.Context, args []string) { - // Log that we've entered the main function - // fmt.Printf("runOlmMainWithArgs() called with args: %v\n", args) + // Load configuration from file, env vars, and CLI args + // Priority: CLI args > Env vars > Config file > Defaults + config, showVersion, showConfig, err := LoadConfig(args) + if err != nil { + fmt.Printf("Failed to load configuration: %v\n", err) + return + } - // Create a new FlagSet for parsing service arguments - serviceFlags := flag.NewFlagSet("service", flag.ContinueOnError) + // Handle --show-config flag + if showConfig { + config.ShowConfig() + os.Exit(0) + } + // Extract commonly used values from config for convenience var ( - endpoint string - id string - secret string - mtu string + endpoint = config.Endpoint + id = config.ID + secret = config.Secret + mtu = config.MTU mtuInt int - dns string + logLevel = config.LogLevel + interfaceName = config.InterfaceName + enableHTTP = config.EnableHTTP + httpAddr = config.HTTPAddr + pingInterval = config.PingIntervalDuration + pingTimeout = config.PingTimeoutDuration + doHolepunch = config.Holepunch privateKey wgtypes.Key - err error - logLevel string - interfaceName string - enableHTTP bool - httpAddr string - testMode bool // Add this var for the test flag - testTarget string // Add this var for test target - pingInterval time.Duration - pingTimeout time.Duration - doHolepunch bool connected bool ) stopHolepunch = make(chan struct{}) stopPing = make(chan struct{}) - // if PANGOLIN_ENDPOINT, OLM_ID, and OLM_SECRET are set as environment variables, they will be used as default values - endpoint = os.Getenv("PANGOLIN_ENDPOINT") - id = os.Getenv("OLM_ID") - secret = os.Getenv("OLM_SECRET") - mtu = os.Getenv("MTU") - dns = os.Getenv("DNS") - logLevel = os.Getenv("LOG_LEVEL") - interfaceName = os.Getenv("INTERFACE") - httpAddr = os.Getenv("HTTP_ADDR") - pingIntervalStr := os.Getenv("PING_INTERVAL") - pingTimeoutStr := os.Getenv("PING_TIMEOUT") - enableHTTPEnv := os.Getenv("ENABLE_HTTP") - holepunchEnv := os.Getenv("HOLEPUNCH") - - enableHTTP = enableHTTPEnv == "true" - doHolepunch = holepunchEnv == "true" - - if endpoint == "" { - serviceFlags.StringVar(&endpoint, "endpoint", "", "Endpoint of your Pangolin server") - } - if id == "" { - serviceFlags.StringVar(&id, "id", "", "Olm ID") - } - if secret == "" { - serviceFlags.StringVar(&secret, "secret", "", "Olm secret") - } - if mtu == "" { - serviceFlags.StringVar(&mtu, "mtu", "1280", "MTU to use") - } - if dns == "" { - serviceFlags.StringVar(&dns, "dns", "8.8.8.8", "DNS server to use") - } - if logLevel == "" { - serviceFlags.StringVar(&logLevel, "log-level", "INFO", "Log level (DEBUG, INFO, WARN, ERROR, FATAL)") - } - if interfaceName == "" { - serviceFlags.StringVar(&interfaceName, "interface", "olm", "Name of the WireGuard interface") - } - if httpAddr == "" { - serviceFlags.StringVar(&httpAddr, "http-addr", ":9452", "HTTP server address (e.g., ':9452')") - } - if pingIntervalStr == "" { - serviceFlags.StringVar(&pingIntervalStr, "ping-interval", "3s", "Interval for pinging the server (default 3s)") - } - if pingTimeoutStr == "" { - serviceFlags.StringVar(&pingTimeoutStr, "ping-timeout", "5s", " Timeout for each ping (default 3s)") - } - if enableHTTPEnv == "" { - serviceFlags.BoolVar(&enableHTTP, "enable-http", false, "Enable HTT server for receiving connection requests") - } - if holepunchEnv == "" { - serviceFlags.BoolVar(&doHolepunch, "holepunch", false, "Enable hole punching (default false)") - } - - version := serviceFlags.Bool("version", false, "Print the version") - - // Parse the service arguments - if err := serviceFlags.Parse(args); err != nil { - fmt.Printf("Error parsing service arguments: %v\n", err) - return - } - - // Debug: Print final values after flag parsing - // fmt.Printf("After flag parsing: endpoint='%s', id='%s', secret='%s'\n", endpoint, id, secret) - - // Parse ping intervals - if pingIntervalStr != "" { - pingInterval, err = time.ParseDuration(pingIntervalStr) - if err != nil { - fmt.Printf("Invalid PING_INTERVAL value: %s, using default 3 seconds\n", pingIntervalStr) - pingInterval = 3 * time.Second - } - } else { - pingInterval = 3 * time.Second - } - - if pingTimeoutStr != "" { - pingTimeout, err = time.ParseDuration(pingTimeoutStr) - if err != nil { - fmt.Printf("Invalid PING_TIMEOUT value: %s, using default 5 seconds\n", pingTimeoutStr) - pingTimeout = 5 * time.Second - } - } else { - pingTimeout = 5 * time.Second - } - // Setup Windows event logging if on Windows if runtime.GOOS == "windows" { setupWindowsEventLog() @@ -331,12 +248,11 @@ func runOlmMainWithArgs(ctx context.Context, args []string) { logger.GetLogger().SetLevel(parseLogLevel(logLevel)) olmVersion := "version_replaceme" - if *version { + if showVersion { fmt.Println("Olm version " + olmVersion) os.Exit(0) - } else { - logger.Info("Olm version " + olmVersion) } + logger.Info("Olm version " + olmVersion) if err := updates.CheckForUpdate("fosrl", "olm", olmVersion); err != nil { logger.Debug("Failed to check for updates: %v", err) @@ -351,35 +267,6 @@ func runOlmMainWithArgs(ctx context.Context, args []string) { logger.Warn("Hole punching is enabled. This is EXPERIMENTAL and may not work in all environments.") } - // Handle test mode - if testMode { - if testTarget == "" { - logger.Fatal("Test mode requires -test-target to be set to a server:port") - } - - logger.Info("Running in test mode, connecting to %s", testTarget) - - // Create a new tester client - tester, err := wgtester.NewClient(testTarget) - if err != nil { - logger.Fatal("Failed to create tester client: %v", err) - } - defer tester.Close() - - // Test connection with a 2-second timeout - connected, rtt := tester.TestConnectionWithTimeout(2 * time.Second) - - if connected { - logger.Info("Connection test successful! RTT: %v", rtt) - fmt.Printf("Connection test successful! RTT: %v\n", rtt) - os.Exit(0) - } else { - logger.Error("Connection test failed - no response received") - fmt.Println("Connection test failed - no response received") - os.Exit(1) - } - } - var httpServer *httpserver.HTTPServer if enableHTTP { httpServer = httpserver.NewHTTPServer(httpAddr) @@ -437,9 +324,15 @@ func runOlmMainWithArgs(ctx context.Context, args []string) { if err != nil { logger.Fatal("Failed to create olm: %v", err) } - endpoint = olm.GetConfig().Endpoint // Update endpoint from config - id = olm.GetConfig().ID // Update ID from config - secret = olm.GetConfig().Secret // Update secret from config + // Update config with values from websocket client (which may have loaded from its config file) + config.UpdateFromWebsocket( + olm.GetConfig().ID, + olm.GetConfig().Secret, + olm.GetConfig().Endpoint, + ) + endpoint = config.Endpoint + id = config.ID + secret = config.Secret // wait until we have a client id and secret and endpoint waitCount := 0 @@ -974,6 +867,14 @@ func runOlmMainWithArgs(ctx context.Context, args []string) { httpServer.SetConnectionStatus(true) } + // CRITICAL: Save our full config AFTER websocket saves its limited config + // This ensures all 13 fields are preserved, not just the 4 that websocket saves + 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 connected { logger.Debug("Already connected, skipping registration") return nil