package main import ( "bufio" "os" "path/filepath" "strconv" "strings" ) type Config struct { TGToken string AllowedIDs []int64 WritePort int ControlPort int PreferIface string WriteDir string BlogDir string // parent of WriteDir, for hugo } func LoadConfig() *Config { c := &Config{ WritePort: 8016, ControlPort: 8017, PreferIface: "WLAN", } // Try .env next to executable first, then fallback to cwd exe, _ := os.Executable() envPath := filepath.Join(filepath.Dir(exe), ".env") if _, err := os.Stat(envPath); os.IsNotExist(err) { envPath = ".env" } f, err := os.Open(envPath) if err != nil { return c } defer f.Close() scanner := bufio.NewScanner(f) for scanner.Scan() { line := strings.TrimSpace(scanner.Text()) if line == "" || strings.HasPrefix(line, "#") { continue } parts := strings.SplitN(line, "=", 2) if len(parts) != 2 { continue } key := strings.TrimSpace(parts[0]) val := strings.TrimSpace(parts[1]) switch key { case "TG_BOT_TOKEN": c.TGToken = val case "TG_ALLOWED_CHAT_IDS": for _, s := range strings.Split(val, ",") { s = strings.TrimSpace(s) if id, err := strconv.ParseInt(s, 10, 64); err == nil { c.AllowedIDs = append(c.AllowedIDs, id) } } case "WRITE_PORT": if p, err := strconv.Atoi(val); err == nil { c.WritePort = p } case "BOT_PORT": if p, err := strconv.Atoi(val); err == nil { c.ControlPort = p } case "PREFER_IFACE": c.PreferIface = val } } // WriteDir and BlogDir derived from executable location exeDir := filepath.Dir(exe) writeDir := filepath.Join(exeDir, "..", "write") blogDir := filepath.Join(exeDir, "..") if abs, err := filepath.Abs(writeDir); err == nil { c.WriteDir = abs } if abs, err := filepath.Abs(blogDir); err == nil { c.BlogDir = abs } return c }