2025-08-05 03:56:23 +02:00
package main
import (
2025-08-16 16:03:36 +02:00
"bytes"
"crypto/rand"
"encoding/base64"
2025-08-05 03:56:23 +02:00
"errors"
2025-08-16 16:03:36 +02:00
"io"
2025-08-05 03:56:23 +02:00
"os"
2025-08-16 15:15:06 +02:00
"github.com/goccy/go-yaml"
2025-08-05 03:56:23 +02:00
)
2025-08-16 15:15:06 +02:00
type EnvTokens struct {
2025-08-16 16:03:36 +02:00
Secret string ` json:"secret" `
2025-08-16 15:15:06 +02:00
OpenRouter string ` json:"openrouter" `
Exa string ` json:"exa" `
2025-08-25 18:37:30 +02:00
GitHub string ` json:"github" `
2025-08-16 15:15:06 +02:00
}
2025-08-14 17:08:45 +02:00
2025-08-16 15:15:06 +02:00
type EnvSettings struct {
2025-08-25 22:45:03 +02:00
CleanContent bool ` json:"cleanup" `
TitleModel string ` json:"title-model" `
2025-08-16 15:15:06 +02:00
}
2025-08-14 17:08:45 +02:00
2025-08-16 16:03:36 +02:00
type EnvUser struct {
Username string ` json:"username" `
Password string ` json:"password" `
}
type EnvAuthentication struct {
2025-08-16 17:18:48 +02:00
lookup map [ string ] * EnvUser
Enabled bool ` json:"enabled" `
Users [ ] * EnvUser ` json:"users" `
2025-08-16 16:03:36 +02:00
}
2025-08-16 15:15:06 +02:00
type Environment struct {
2025-08-16 16:03:36 +02:00
Debug bool ` json:"debug" `
Tokens EnvTokens ` json:"tokens" `
Settings EnvSettings ` json:"settings" `
Authentication EnvAuthentication ` json:"authentication" `
2025-08-16 15:15:06 +02:00
}
2025-08-18 05:15:48 +02:00
var env = Environment {
// defaults
Settings : EnvSettings {
2025-08-23 15:19:43 +02:00
CleanContent : true ,
2025-08-18 05:15:48 +02:00
} ,
}
2025-08-05 03:56:23 +02:00
func init ( ) {
2025-08-16 15:15:06 +02:00
file , err := os . OpenFile ( "config.yml" , os . O_RDONLY , 0 )
log . MustPanic ( err )
2025-08-05 03:56:23 +02:00
2025-08-16 15:15:06 +02:00
defer file . Close ( )
2025-08-14 03:53:14 +02:00
2025-08-16 15:15:06 +02:00
err = yaml . NewDecoder ( file ) . Decode ( & env )
log . MustPanic ( err )
2025-08-14 17:08:45 +02:00
2025-08-16 15:15:06 +02:00
log . MustPanic ( env . Init ( ) )
}
2025-08-14 17:08:45 +02:00
2025-08-16 15:15:06 +02:00
func ( e * Environment ) Init ( ) error {
// print if debug is enabled
if e . Debug {
log . Warning ( "Debug mode enabled" )
}
2025-08-14 03:53:14 +02:00
2025-08-16 16:03:36 +02:00
// check if server secret is set
if e . Tokens . Secret == "" {
log . Warning ( "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 . Info ( "Stored new tokens.secret" )
}
2025-08-16 15:15:06 +02:00
// check if openrouter token is set
if e . Tokens . OpenRouter == "" {
return errors . New ( "missing tokens.openrouter" )
2025-08-14 03:53:14 +02:00
}
2025-08-11 01:16:52 +02:00
2025-08-16 15:15:06 +02:00
// check if exa token is set
if e . Tokens . Exa == "" {
2025-08-16 16:03:36 +02:00
log . Warning ( "Missing token.exa, web search unavailable" )
2025-08-05 03:56:23 +02:00
}
2025-08-11 01:16:52 +02:00
2025-08-25 22:45:03 +02:00
// check if github token is set
if e . Tokens . GitHub == "" {
log . Warning ( "Missing token.github, limited api requests" )
}
// default title model
if e . Settings . TitleModel == "" {
e . Settings . TitleModel = "google/gemini-2.5-flash-lite"
}
2025-08-16 17:18:48 +02:00
// create user lookup map
e . Authentication . lookup = make ( map [ string ] * EnvUser )
2025-08-16 16:03:36 +02:00
for _ , user := range e . Authentication . Users {
2025-08-16 17:18:48 +02:00
e . Authentication . lookup [ user . Username ] = user
2025-08-16 16:03:36 +02:00
}
2025-08-16 17:18:48 +02:00
return nil
2025-08-16 16:03:36 +02:00
}
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)" ) } ,
2025-08-25 18:37:30 +02:00
"$.tokens.github" : { yaml . HeadComment ( " github api token (optional; used by search tools)" ) } ,
2025-08-16 16:03:36 +02:00
2025-08-25 22:45:03 +02:00
"$.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)" ) } ,
2025-08-16 16:03:36 +02:00
"$.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 )
}