package main import ( "bytes" "crypto/rand" "encoding/base64" "errors" "io" "os" "github.com/goccy/go-yaml" ) type EnvTokens struct { Secret string `json:"secret"` OpenRouter string `json:"openrouter"` Exa string `json:"exa"` GitHub string `json:"github"` } type EnvSettings struct { CleanContent bool `json:"cleanup"` TitleModel string `json:"title-model"` } type EnvUser struct { Username string `json:"username"` Password string `json:"password"` } type EnvAuthentication struct { lookup map[string]*EnvUser Enabled bool `json:"enabled"` Users []*EnvUser `json:"users"` } type Environment struct { Debug bool `json:"debug"` Tokens EnvTokens `json:"tokens"` Settings EnvSettings `json:"settings"` Authentication EnvAuthentication `json:"authentication"` } var env = Environment{ // defaults Settings: EnvSettings{ CleanContent: true, }, } func init() { file, err := os.OpenFile("config.yml", os.O_RDONLY, 0) log.MustFail(err) defer file.Close() err = yaml.NewDecoder(file).Decode(&env) log.MustFail(err) log.MustFail(env.Init()) } func (e *Environment) Init() error { // print if debug is enabled if e.Debug { log.Warnln("Debug mode enabled") } // check if server secret is set if e.Tokens.Secret == "" { log.Warnln("Missing tokens.secret, generating new...") key := make([]byte, 32) _, err := io.ReadFull(rand.Reader, key) if err != nil { return err } e.Tokens.Secret = base64.StdEncoding.EncodeToString(key) err = e.Store() if err != nil { return err } log.Println("Stored new tokens.secret") } // check if openrouter token is set if e.Tokens.OpenRouter == "" { return errors.New("missing tokens.openrouter") } // check if exa token is set if e.Tokens.Exa == "" { log.Warnln("Missing token.exa, web search unavailable") } // check if github token is set if e.Tokens.GitHub == "" { log.Warnln("Missing token.github, limited api requests") } // default title model if e.Settings.TitleModel == "" { e.Settings.TitleModel = "google/gemini-2.5-flash-lite" } // create user lookup map e.Authentication.lookup = make(map[string]*EnvUser) for _, user := range e.Authentication.Users { e.Authentication.lookup[user.Username] = user } return nil } func (e *Environment) Store() error { var ( buffer bytes.Buffer comments = yaml.CommentMap{ "$.debug": {yaml.HeadComment(" enable verbose logging and diagnostics")}, "$.tokens": {yaml.HeadComment("")}, "$.settings": {yaml.HeadComment("")}, "$.authentication": {yaml.HeadComment("")}, "$.tokens.secret": {yaml.HeadComment(" server secret for signing auth tokens; auto-generated if empty")}, "$.tokens.openrouter": {yaml.HeadComment(" openrouter.ai api token (required)")}, "$.tokens.exa": {yaml.HeadComment(" exa search api token (optional; used by search tools)")}, "$.tokens.github": {yaml.HeadComment(" github api token (optional; used by search tools)")}, "$.settings.cleanup": {yaml.HeadComment(" normalize unicode in assistant output (optional; default: true)")}, "$.settings.title-model": {yaml.HeadComment(" model used to generate titles (needs to have structured output support; default: google/gemini-2.5-flash-lite)")}, "$.authentication.enabled": {yaml.HeadComment(" require login with username and password")}, "$.authentication.users": {yaml.HeadComment(" list of users with bcrypt password hashes")}, } ) err := yaml.NewEncoder(&buffer, yaml.WithComment(comments)).Encode(e) if err != nil { return err } body := bytes.ReplaceAll(buffer.Bytes(), []byte("#\n"), []byte("\n")) return os.WriteFile("config.yml", body, 0644) }