package config import ( "cmp" "errors" "log/slog" "os" "os/exec" "strconv" "strings" ) var log = slog.New(slog.NewJSONHandler(os.Stdout, nil)) type Config struct { AddrPort string `json:"addr_port"` Keys map[string]string `json:"keys"` Speaker string `json:"speaker"` Conn string `json:"conn"` SessionLengthMax int `json:"session_length_max"` IntervalImmediate int `json:"interval_immediate"` IntervalInvestigate int `json:"interval_investigate"` SkipVoicing bool `json:"skip_voicing"` SeedAlert bool `json:"seed_alert"` } func die(err error, str string) { if err == nil { return } log.Error(str, "err", err) os.Exit(1) } func Must() Config { cfg := Config{ AddrPort: os.Getenv("CICADA_ADDRPORT"), Speaker: cmp.Or(os.Getenv("CICADA_SPEAKER"), "espeak"), Conn: os.Getenv("CICADA_CONN"), SkipVoicing: os.Getenv("CICADA_SKIP_VOICING") == "true", SeedAlert: os.Getenv("CICADA_SEED_ALERT") == "true", SessionLengthMax: 86400 * 365, IntervalImmediate: 60 * 5, IntervalInvestigate: 60 * 60, } if !cfg.SkipVoicing { die(exec.Command("which", cfg.Speaker).Run(), "could not find speech command") } keysEnv := os.Getenv("CICADA_KEYS") if keysEnv == "" { die(errors.New("must pass CICADA_KEYS"), "bad config") } keys := strings.Split(keysEnv, ",") keysMap := map[string]string{} foundAdmin := false for i, key := range keys { username, _, hasUsername := strings.Cut(key, "_") if !hasUsername { die(errors.New("key must have username"), "bad username in position "+strconv.Itoa(i)) } keysMap[key] = username if username == "admin" { foundAdmin = true if !foundAdmin { } } } cfg.Keys = keysMap if !foundAdmin { die(errors.New("no admin found"), "must supply an admin in keys") } return cfg }