package config import ( "fmt" "net/url" "os" "os/exec" "slices" "strings" "gopkg.in/yaml.v3" ) type Access struct { User string `yaml:"user,omitempty"` Users []string `yaml:"users,omitempty"` Group string `yaml:"group,omitempty"` Groups string `yaml:"groups,omitempty"` Mode string `yaml:"mode"` // "read-only" or "read-write" ICS string `yaml:"ics,omitempty"` } type Calendar struct { ID string `yaml:"id"` Owner string `yaml:"owner"` Color string `yaml:"color,omitempty"` Access []Access `yaml:"access,omitempty"` } type AddressBook struct { ID string `yaml:"id"` Owner string `yaml:"owner"` Access []Access `yaml:"access,omitempty"` } type Aggregate struct { ID string `yaml:"id"` Owner string `yaml:"owner"` Color string `yaml:"color,omitempty"` Sources []string `yaml:"sources"` // Calendar IDs Access []Access `yaml:"access,omitempty"` } type User struct { Name string `yaml:"name"` Password string `yaml:"password"` PasswordCmd string `yaml:"password_cmd"` Groups []string `yaml:"groups,omitempty"` } type DatabaseConfig struct { URL string `yaml:"url"` } type ServerConfig struct { BindAddress string `yaml:"bind_address"` PublicURL string `yaml:"public_url"` EmailDomain string `yaml:"email_domain"` Redaction string `yaml:"redaction_text"` // "[-]" DefaultClass string `yaml:"default_class"` // CONFIDENTIAL/PRIVATE/PUBLIC } func (s ServerConfig) BasePath() string { u, err := url.Parse(s.PublicURL) if err != nil { return "" } return strings.TrimSuffix(u.Path, "/") } type SMTPConfig struct { Host string `yaml:"host"` Port int `yaml:"port"` User string `yaml:"user"` Password string `yaml:"password"` PasswordCmd string `yaml:"password_cmd"` } type Config struct { Server ServerConfig `yaml:"server"` Database DatabaseConfig `yaml:"database"` SMTP SMTPConfig `yaml:"smtp"` Users []User `yaml:"users"` Calendars []Calendar `yaml:"calendars"` AddressBooks []AddressBook `yaml:"address_books"` Aggregates []Aggregate `yaml:"aggregates"` } func ResolvePassword(password, passwordCmd string) (string, error) { if passwordCmd != "" { cmd := exec.Command("sh", "-c", passwordCmd) out, err := cmd.Output() if err != nil { return "", fmt.Errorf("failed to run password command: %v", err) } return strings.TrimSpace(string(out)), nil } return password, nil } func (c *Config) setDefaults() { if c.Server.BindAddress == "" { c.Server.BindAddress = ":8080" } if c.Server.Redaction == "" { c.Server.Redaction = "Busy" } if c.Server.DefaultClass == "" { c.Server.DefaultClass = "CONFIDENTIAL" } if c.Server.EmailDomain == "" { c.Server.EmailDomain = "nx2.site" } if c.Database.URL == "" { c.Database.URL = "postgres://nxcaldav@localhost:5432/nxcaldav?sslmode=disable" } if c.SMTP.Host == "" { c.SMTP.Host = "localhost" } if c.SMTP.Port == 0 { c.SMTP.Port = 25 } } func (c *Config) checkConfig() { if !(slices.Contains([]string{"PUBLIC", "PRIVATE", "CONFIDENTIAL"}, c.Server.DefaultClass)) { panic("Invaldi Config, default_class") } } func Load(path string) (*Config, error) { f, err := os.Open(path) if err != nil { return nil, err } defer f.Close() var cfg Config err = yaml.NewDecoder(f).Decode(&cfg) if err != nil { return nil, err } cfg.checkConfig() cfg.setDefaults() return &cfg, nil }