package config import ( "bytes" "encoding/json" "fmt" "os" "code.nonshy.com/nonshy/website/pkg/encryption/coldstorage" "code.nonshy.com/nonshy/website/pkg/encryption/keygen" "code.nonshy.com/nonshy/website/pkg/log" "github.com/SherClockHolmes/webpush-go" "github.com/google/uuid" ) // Version of the config format - when new fields are added, it will attempt // to write the settings.toml to disk so new defaults populate. var currentVersion = 5 // Current loaded settings.json var Current = DefaultVariable() // Variable configuration attributes (loaded from settings.json). type Variable struct { Version int BaseURL string AdminEmail string CronAPIKey string Mail Mail Redis Redis Database Database BareRTC BareRTC Maintenance Maintenance Encryption Encryption SignedPhoto SignedPhoto WebPush WebPush Turnstile Turnstile UseXForwardedFor bool } // DefaultVariable returns the default settings.json data. func DefaultVariable() Variable { return Variable{ BaseURL: "http://localhost:8080", Mail: Mail{ Enabled: false, Host: "localhost", Port: 25, From: "no-reply@localhost", }, Redis: Redis{ Host: "localhost", Port: 6379, }, Database: Database{ SQLite: "database.sqlite", Postgres: "host=localhost user=nonshy password=nonshy dbname=nonshy port=5679 sslmode=disable TimeZone=America/Los_Angeles", }, CronAPIKey: uuid.New().String(), } } // LoadSettings loads the settings.json file or, if not existing, creates it with the default settings. func LoadSettings() { var writeSettings bool if _, err := os.Stat(SettingsPath); !os.IsNotExist(err) { log.Info("Loading settings from %s", SettingsPath) content, err := os.ReadFile(SettingsPath) if err != nil { panic(fmt.Sprintf("LoadSettings: couldn't read settings.json: %s", err)) } var v Variable err = json.Unmarshal(content, &v) if err != nil { panic(fmt.Sprintf("LoadSettings: couldn't parse settings.json: %s", err)) } Current = v } else { WriteSettings() log.Warn("NOTICE: Created default settings.json file - review it and configure mail servers and database!") } // If there is no DB configured, exit now. if !Current.Database.IsSQLite && !Current.Database.IsPostgres { log.Error("No database configured in settings.json. Choose SQLite or Postgres and update the DB connector string!") os.Exit(1) } // Initialize the AES encryption key. if len(Current.Encryption.AESKey) == 0 { log.Warn("NOTICE: rolling a random 32-byte (256-bit) AES encryption key for the settings file") aesKey, err := keygen.NewAESKey() if err != nil { log.Error("Couldn't generate AES key: %s", err) os.Exit(1) } Current.Encryption.AESKey = aesKey writeSettings = true } // Initialize the cold storage RSA keys. if len(Current.Encryption.ColdStorageRSAPublicKey) == 0 { x509publicKey, err := coldstorage.Initialize() if err != nil { log.Error("Initializing cold storage: %s", err) os.Exit(1) } // Store the public key in the settings.json. Current.Encryption.ColdStorageRSAPublicKey = x509publicKey writeSettings = true } // Initialize the VAPID keys for Web Push Notification. if len(Current.WebPush.VAPIDPublicKey) == 0 { privateKey, publicKey, err := webpush.GenerateVAPIDKeys() if err != nil { log.Error("Initializing VAPID keys for Web Push: %s", err) os.Exit(1) } Current.WebPush.VAPIDPrivateKey = privateKey Current.WebPush.VAPIDPublicKey = publicKey writeSettings = true } // Initialize JWT token for SignedPhoto feature. if Current.SignedPhoto.JWTSecret == "" { Current.SignedPhoto.JWTSecret = uuid.New().String() writeSettings = true } // Have we added new config fields? Save the settings.json. if Current.Version != currentVersion || writeSettings { log.Warn("New options are available for your settings.json file. Your settings will be re-saved now.") Current.Version = currentVersion if err := WriteSettings(); err != nil { log.Error("Couldn't write your settings.json file: %s", err) } } } // WriteSettings will commit the settings.json to disk. func WriteSettings() error { log.Error("Note: initial settings.json was written to disk.") var buf bytes.Buffer enc := json.NewEncoder(&buf) enc.SetIndent("", " ") err := enc.Encode(Current) if err != nil { panic(fmt.Sprintf("WriteSettings: couldn't marshal settings: %s", err)) } return os.WriteFile(SettingsPath, buf.Bytes(), 0600) } // Mail settings. type Mail struct { Enabled bool Host string // localhost Port int // 25 From string // noreply@localhost Username string // SMTP credentials Password string } // Redis settings. type Redis struct { Host string Port int DB int } // Database settings. type Database struct { IsSQLite bool IsPostgres bool SQLite string Postgres string } // BareRTC chat room settings. type BareRTC struct { JWTSecret string URL string } // Maintenance mode settings. type Maintenance struct { PauseSignup bool PauseLogin bool PauseChat bool PauseInteraction bool } // Encryption settings. type Encryption struct { AESKey []byte ColdStorageRSAPublicKey []byte } // SignedPhoto settings. type SignedPhoto struct { Enabled bool JWTSecret string } // WebPush settings. type WebPush struct { VAPIDPublicKey string VAPIDPrivateKey string } // Turnstile (Cloudflare CAPTCHA) settings. type Turnstile struct { Enabled bool SiteKey string SecretKey string }