package main import ( "encoding/json" "errors" "fmt" "os" "path/filepath" "runtime" ) const defaultPort = 17680 type config struct { WikiRoot string `json:"wikiRoot"` AllowedOrigins []string `json:"allowedOrigins"` Port int `json:"port,omitempty"` OpenFileCommand string `json:"openFileCommand,omitempty"` OpenFolderCommand string `json:"openFolderCommand,omitempty"` } // configPath returns the platform-conventional config path. // // Windows: %APPDATA%\datascape\companion.json // Linux: $XDG_CONFIG_HOME/datascape/companion.json // (fallback ~/.config/datascape/companion.json) func configPath() (string, error) { if runtime.GOOS == "windows" { appData := os.Getenv("APPDATA") if appData == "" { return "", errors.New("APPDATA not set") } return filepath.Join(appData, "datascape", "companion.json"), nil } if x := os.Getenv("XDG_CONFIG_HOME"); x != "" { return filepath.Join(x, "datascape", "companion.json"), nil } home, err := os.UserHomeDir() if err != nil { return "", err } return filepath.Join(home, ".config", "datascape", "companion.json"), nil } // loadOrInitConfig reads the on-disk config, creating a default file if none // exists. Returns the resolved config (with port defaulted) and the path. func loadOrInitConfig() (*config, string, error) { p, err := configPath() if err != nil { return nil, "", err } data, err := os.ReadFile(p) if errors.Is(err, os.ErrNotExist) { cfg := &config{AllowedOrigins: []string{}, Port: defaultPort} if err := writeConfigFile(p, cfg); err != nil { return nil, p, err } return cfg, p, nil } if err != nil { return nil, p, err } var cfg config if err := json.Unmarshal(data, &cfg); err != nil { return nil, p, fmt.Errorf("parse config: %w", err) } if cfg.Port == 0 { cfg.Port = defaultPort } if cfg.AllowedOrigins == nil { cfg.AllowedOrigins = []string{} } return &cfg, p, nil } // writeConfigFile atomically writes cfg to p (write-temp + rename). func writeConfigFile(p string, cfg *config) error { if err := os.MkdirAll(filepath.Dir(p), 0755); err != nil { return err } out := *cfg if out.AllowedOrigins == nil { out.AllowedOrigins = []string{} } data, err := json.MarshalIndent(out, "", " ") if err != nil { return err } tmp := p + ".tmp" if err := os.WriteFile(tmp, data, 0644); err != nil { return err } return os.Rename(tmp, p) }