153 lines
3.5 KiB
Go
153 lines
3.5 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/url"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
)
|
|
|
|
type ServerConfig struct {
|
|
URL string `json:"url"`
|
|
}
|
|
|
|
type Config struct {
|
|
Default string `json:"default"`
|
|
Servers map[string]ServerConfig `json:"servers"`
|
|
|
|
path string
|
|
fromEnv bool
|
|
}
|
|
|
|
const envServerName = "env"
|
|
|
|
func defaultConfigPath() string {
|
|
if p := os.Getenv("RB_SEARCH_CONFIG"); p != "" {
|
|
return p
|
|
}
|
|
if x := os.Getenv("XDG_CONFIG_HOME"); x != "" {
|
|
return filepath.Join(x, "rb-search", "config.json")
|
|
}
|
|
if h, err := os.UserHomeDir(); err == nil {
|
|
return filepath.Join(h, ".config", "rb-search", "config.json")
|
|
}
|
|
return "config.json"
|
|
}
|
|
|
|
func loadConfig(path string) (*Config, error) {
|
|
explicit := path != ""
|
|
if path == "" {
|
|
path = defaultConfigPath()
|
|
}
|
|
data, err := os.ReadFile(path)
|
|
if err != nil {
|
|
if !os.IsNotExist(err) {
|
|
return nil, err
|
|
}
|
|
if envCfg := configFromEnv(); envCfg != nil {
|
|
envCfg.path = path
|
|
return envCfg, nil
|
|
}
|
|
hint := "EVENTBUS_AMQP_HOST/USER/PASS env vars not set either"
|
|
if explicit {
|
|
return nil, fmt.Errorf("config not found at %s (%s)", path, hint)
|
|
}
|
|
return nil, fmt.Errorf("config not found at %s (%s; run `rb-search init` to create one)", path, hint)
|
|
}
|
|
var c Config
|
|
if err := json.Unmarshal(data, &c); err != nil {
|
|
return nil, fmt.Errorf("parse %s: %w", path, err)
|
|
}
|
|
if c.Servers == nil {
|
|
c.Servers = map[string]ServerConfig{}
|
|
}
|
|
c.path = path
|
|
return &c, nil
|
|
}
|
|
|
|
// configFromEnv builds a synthetic in-memory config from EVENTBUS_AMQP_*.
|
|
// Returns nil when EVENTBUS_AMQP_HOST is unset — host is the only required field.
|
|
func configFromEnv() *Config {
|
|
host := strings.TrimSpace(os.Getenv("EVENTBUS_AMQP_HOST"))
|
|
if host == "" {
|
|
return nil
|
|
}
|
|
port := os.Getenv("EVENTBUS_AMQP_PORT")
|
|
if port == "" {
|
|
port = "5672"
|
|
}
|
|
vhost := os.Getenv("EVENTBUS_AMQP_VHOST")
|
|
user := os.Getenv("EVENTBUS_AMQP_USER")
|
|
pass := os.Getenv("EVENTBUS_AMQP_PASS")
|
|
|
|
u := &url.URL{
|
|
Scheme: "amqp",
|
|
Host: host + ":" + port,
|
|
Path: "/" + strings.TrimPrefix(vhost, "/"),
|
|
}
|
|
if user != "" {
|
|
u.User = url.UserPassword(user, pass)
|
|
}
|
|
return &Config{
|
|
Default: envServerName,
|
|
Servers: map[string]ServerConfig{
|
|
envServerName: {URL: u.String()},
|
|
},
|
|
fromEnv: true,
|
|
}
|
|
}
|
|
|
|
func (c *Config) save() error {
|
|
if c.fromEnv {
|
|
return fmt.Errorf("config was synthesized from EVENTBUS_AMQP_* env vars; run `rb-search init` to materialize a file first")
|
|
}
|
|
if err := os.MkdirAll(filepath.Dir(c.path), 0o755); err != nil {
|
|
return err
|
|
}
|
|
data, err := json.MarshalIndent(c, "", " ")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
tmp := c.path + ".tmp"
|
|
if err := os.WriteFile(tmp, append(data, '\n'), 0o600); err != nil {
|
|
return err
|
|
}
|
|
return os.Rename(tmp, c.path)
|
|
}
|
|
|
|
func (c *Config) resolveServer(name string) (string, ServerConfig, error) {
|
|
if name == "" {
|
|
name = c.Default
|
|
}
|
|
if name == "" {
|
|
return "", ServerConfig{}, fmt.Errorf("no server specified and no default set")
|
|
}
|
|
srv, ok := c.Servers[name]
|
|
if !ok {
|
|
return "", ServerConfig{}, fmt.Errorf("unknown server %q", name)
|
|
}
|
|
if srv.URL == "" {
|
|
return "", ServerConfig{}, fmt.Errorf("server %q has empty url", name)
|
|
}
|
|
return name, srv, nil
|
|
}
|
|
|
|
func writeSampleConfig(path string) error {
|
|
if path == "" {
|
|
path = defaultConfigPath()
|
|
}
|
|
if _, err := os.Stat(path); err == nil {
|
|
return fmt.Errorf("%s already exists", path)
|
|
}
|
|
sample := Config{
|
|
Default: "local",
|
|
Servers: map[string]ServerConfig{
|
|
"local": {URL: "amqp://guest:guest@localhost:5672/"},
|
|
},
|
|
path: path,
|
|
}
|
|
return sample.save()
|
|
}
|