config.go

  1package rtw
  2
  3import (
  4	"fmt"
  5	"io"
  6
  7	toml "github.com/pelletier/go-toml/v2"
  8)
  9
 10type LogLevel string
 11
 12const (
 13	DEBUG LogLevel = "DEBUG"
 14	INFO  LogLevel = "INFO"
 15	ERROR LogLevel = "ERROR"
 16)
 17
 18type Server struct {
 19	Host      string   `toml:"host" comment:"host bind address (IP or hostname, no scheme)"`
 20	Port      int      `toml:"port" comment:"listening port"`
 21	LogLevel  LogLevel `toml:"loglevel" comment:"one of DEBUG, INFO, ERROR"`
 22	PublicURL string   `toml:"public-url" comment:"public URL of server"`
 23	DataDir   string   `toml:"data-dir" comment:"base directory for server state (empty = current working directory, relative paths resolved against working directory)"`
 24}
 25
 26type YSweet struct {
 27	URL string `toml:"url" comment:"URL of the upstream jamsocket/ysweet server to persist documents"`
 28}
 29
 30type Routing struct {
 31	URL   string `toml:"url" comment:"URL of Graphhopper-compatible routing API (e.g., https://graphhopper.com/api/1)"`
 32	Token string `toml:"token,commented" comment:"authorization token"`
 33}
 34
 35type Geocoder struct {
 36	URL string `toml:"url" comment:"URL of Waldo geocoding API"`
 37}
 38
 39type Protomaps struct {
 40	APIKey string `toml:"api-key,commented" comment:"Protomaps API key (see https://protomaps.com)"`
 41}
 42
 43type User struct {
 44	Email          string `toml:"email,commented"`
 45	Handle         string `toml:"handle,commented"`
 46	HashedPassword string `toml:"hashed-password,commented" comment:"run 'echo <password> | rtw hash' to quickly compute the hashed password"`
 47}
 48
 49type Config struct {
 50	Server      Server    `toml:"server"`
 51	YSweet      YSweet    `toml:"ysweet"`
 52	Routing     Routing   `toml:"routing" comment:"configuration for Graphhopper routing API (local or hosted)"`
 53	Geocoder    Geocoder  `toml:"geocoder" comment:"configuration for a Waldo geocoder (see https://git.kilimanjaro.io/waldo)"`
 54	Protomaps   Protomaps `toml:"protomaps,commented" comment:"configuration for Protomaps map tiles"`
 55	SuperUsers  []User    `toml:"superusers,commented" comment:"users added with superuser permissions at app startup"`
 56}
 57
 58func (c *Config) Validate() error {
 59	if c.Server.Host == "" {
 60		return fmt.Errorf("config error: server.host is required")
 61	}
 62	if c.Server.Port == 0 {
 63		return fmt.Errorf("config error: server.port is required")
 64	}
 65	if c.Server.LogLevel == "" {
 66		return fmt.Errorf("config error: server.loglevel is required")
 67	}
 68	switch c.Server.LogLevel {
 69	case DEBUG, INFO, ERROR:
 70	default:
 71		return fmt.Errorf("config error: server.loglevel must be DEBUG, INFO, or ERROR")
 72	}
 73	if c.Server.PublicURL == "" {
 74		return fmt.Errorf("config error: server.public-url is required")
 75	}
 76	if c.Server.DataDir == "" {
 77		return fmt.Errorf("config error: server.data-dir is required")
 78	}
 79	if c.YSweet.URL == "" {
 80		return fmt.Errorf("config error: ysweet.url is required")
 81	}
 82	if c.Routing.URL == "" {
 83		return fmt.Errorf("config error: routing.url is required")
 84	}
 85	if c.Geocoder.URL == "" {
 86		return fmt.Errorf("config error: geocoder.url is required")
 87	}
 88	return nil
 89}
 90
 91func ReadConfig(r io.Reader) (*Config, error) {
 92	var cfg Config
 93	b, err := io.ReadAll(r)
 94	if err != nil {
 95		return nil, fmt.Errorf("error reading config file: %w", err)
 96	}
 97	if err := toml.Unmarshal(b, &cfg); err != nil {
 98		return nil, fmt.Errorf("config error: %w", err)
 99	}
100	return &cfg, nil
101}
102
103func WriteInitConfig(w io.Writer) error {
104	example := Config{
105		Server: Server{
106			Host:      "127.0.0.1",
107			Port:      8080,
108			LogLevel:  "INFO",
109			PublicURL: "https://rtw.run",
110			DataDir:   "/var/rtw",
111		},
112		YSweet: YSweet{
113			URL: "http://127.0.0.1:9000",
114		},
115		Routing: Routing{
116			URL:   "http://127.0.0.1:8989",
117			Token: "<enter-token-if-required>",
118		},
119		Geocoder: Geocoder{
120			URL: "http://127.0.0.1:8999",
121		},
122		Protomaps: Protomaps{
123			APIKey: "<your-protomaps-api-key>",
124		},
125		SuperUsers: []User{
126			{
127				Email:          "[email protected]",
128				Handle:         "bt",
129				HashedPassword: "use rtw hash to get this value",
130			},
131		},
132	}
133	b, err := toml.Marshal(example)
134	if err != nil {
135		return fmt.Errorf("error marshaling example config: %w", err)
136	}
137	if _, err := w.Write(b); err != nil {
138		return fmt.Errorf("error writing example config: %w", err)
139	}
140	return nil
141}