2024-09-15 10:24:36 +00:00
|
|
|
package config
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"os"
|
|
|
|
|
|
|
|
"github.com/BurntSushi/toml"
|
|
|
|
"github.com/rs/zerolog/log"
|
2024-12-18 14:24:56 +00:00
|
|
|
"git.mstar.dev/mstar/goutils/other"
|
2024-09-15 10:24:36 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
type ConfigSSL struct {
|
|
|
|
HandleSSL bool `toml:"handle_ssl"` // Whether Linstrom should handle SSL encryption itself
|
|
|
|
// If Linstrom is to handle SSL, whether it should use LetsEncrypt for certificates
|
|
|
|
UseLetsEncrypt *bool `toml:"use_lets_encrypt"`
|
|
|
|
// Path to the certificate if Linstrom is to handle SSL while not using LetsEncrypt
|
|
|
|
CertificateFile *string `toml:"certificate_file"`
|
|
|
|
// Mail adress to use in case of using LetsEncrypt
|
|
|
|
AdminMail *string `toml:"admin_mail"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type ConfigGeneral struct {
|
|
|
|
Protocol string `toml:"protocol"` // The protocol with which to access the server publicly (http/https)
|
|
|
|
// The subdomain under which the server lives (example: "linstrom" if the full domain is linstrom.example.com)
|
|
|
|
Subdomain *string `toml:"subdomain"`
|
|
|
|
// The root domain under which the server lives (example: "example.com" if the full domain is linstrom.example.com)
|
|
|
|
Domain string `toml:"domain"`
|
|
|
|
// The port on which the server runs on
|
|
|
|
PrivatePort int `toml:"private_port"`
|
|
|
|
// The port under which the public can reach the server (useful if running behind a reverse proxy)
|
|
|
|
PublicPort *int `toml:"public_port"`
|
|
|
|
// File to write structured logs to (structured being formatted as json)
|
|
|
|
// If not set, Linstrom won't write structured logs
|
|
|
|
StructuredLogFile *string
|
|
|
|
}
|
|
|
|
|
|
|
|
type ConfigWebAuthn struct {
|
|
|
|
DisplayName string `toml:"display_name"`
|
|
|
|
HashingSecret string `toml:"hashing_secret"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type ConfigAdmin struct {
|
|
|
|
// Name of the server's root admin account
|
|
|
|
Username string `toml:"username"`
|
|
|
|
// A one time password used to verify account access to the root admin
|
|
|
|
// after a server has been created and before the account could be linked to a passkey
|
|
|
|
FirstTimeSetupOTP string `toml:"first_time_setup_otp"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type ConfigStorage struct {
|
|
|
|
// Url to the database to use
|
|
|
|
// If DbIsPostgres is either not set or false, the url is expected to be a path to a sqlite file
|
|
|
|
// Otherwise, it's expected to be an url to a postgres server
|
|
|
|
DatabaseUrl string `toml:"database_url"`
|
|
|
|
// Whether the target of the database url is a postgres server
|
|
|
|
DbIsPostgres *bool `toml:"db_is_postgres,omitempty"`
|
|
|
|
// Url to redis server. If empty, no redis is used
|
|
|
|
RedisUrl *string `toml:"redis_url,omitempty"`
|
|
|
|
// The maximum size of the in-memory cache in bytes
|
|
|
|
MaxInMemoryCacheSize int64 `toml:"max_in_memory_cache_size"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type Config struct {
|
|
|
|
General ConfigGeneral `toml:"general"`
|
|
|
|
SSL ConfigSSL `toml:"ssl"`
|
|
|
|
Admin ConfigAdmin `toml:"admin"`
|
|
|
|
Webauthn ConfigWebAuthn `toml:"webauthn"`
|
|
|
|
Storage ConfigStorage `toml:"storage"`
|
|
|
|
}
|
|
|
|
|
|
|
|
var GlobalConfig Config
|
|
|
|
|
|
|
|
var defaultConfig Config = Config{
|
|
|
|
General: ConfigGeneral{
|
|
|
|
Protocol: "http",
|
|
|
|
Subdomain: nil,
|
|
|
|
Domain: "localhost",
|
|
|
|
PrivatePort: 8080,
|
|
|
|
PublicPort: nil,
|
|
|
|
},
|
|
|
|
SSL: ConfigSSL{
|
|
|
|
HandleSSL: false,
|
|
|
|
UseLetsEncrypt: nil,
|
|
|
|
CertificateFile: nil,
|
|
|
|
AdminMail: nil,
|
|
|
|
},
|
|
|
|
Admin: ConfigAdmin{
|
|
|
|
Username: "server-admin",
|
|
|
|
FirstTimeSetupOTP: "Example otp password",
|
|
|
|
},
|
|
|
|
Webauthn: ConfigWebAuthn{
|
|
|
|
DisplayName: "Linstrom",
|
|
|
|
HashingSecret: "some super secure secret that should never be changed or else password storage breaks",
|
|
|
|
},
|
|
|
|
Storage: ConfigStorage{
|
|
|
|
DatabaseUrl: "db.sqlite",
|
|
|
|
DbIsPostgres: other.IntoPointer(false),
|
|
|
|
RedisUrl: nil,
|
|
|
|
MaxInMemoryCacheSize: 1e6, // 1 Megabyte
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
func (gc *ConfigGeneral) GetFullDomain() string {
|
|
|
|
if gc.Subdomain != nil {
|
|
|
|
return *gc.Subdomain + gc.Domain
|
|
|
|
}
|
|
|
|
return gc.Domain
|
|
|
|
}
|
|
|
|
|
|
|
|
func (gc *ConfigGeneral) GetFullPublicUrl() string {
|
|
|
|
str := gc.Protocol + gc.GetFullDomain()
|
|
|
|
if gc.PublicPort != nil {
|
|
|
|
str += fmt.Sprint(*gc.PublicPort)
|
|
|
|
} else {
|
|
|
|
str += fmt.Sprint(gc.PrivatePort)
|
|
|
|
}
|
|
|
|
return str
|
|
|
|
}
|
|
|
|
|
|
|
|
func WriteDefaultConfig(toFile string) error {
|
|
|
|
log.Trace().Caller().Send()
|
|
|
|
log.Info().Str("config-file", toFile).Msg("Writing default config to file")
|
|
|
|
file, err := os.Create(toFile)
|
|
|
|
if err != nil {
|
|
|
|
log.Error().
|
|
|
|
Err(err).
|
|
|
|
Str("config-file", toFile).
|
|
|
|
Msg("Failed to create file for default config")
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer file.Close()
|
|
|
|
|
|
|
|
data, err := toml.Marshal(&defaultConfig)
|
|
|
|
if err != nil {
|
|
|
|
log.Error().Err(err).Msg("Failed to marshal default config to toml")
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
_, err = file.Write(data)
|
|
|
|
if err != nil {
|
|
|
|
log.Error().Err(err).Str("config-file", toFile).Msg("Failed to write default config")
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
log.Info().Str("config-file", toFile).Msg("Wrote default config")
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func ReadAndWriteToGlobal(fileName string) error {
|
|
|
|
log.Trace().Caller().Send()
|
|
|
|
log.Debug().Str("config-file", fileName).Msg("Attempting to read config file")
|
|
|
|
data, err := os.ReadFile(fileName)
|
|
|
|
if err != nil {
|
|
|
|
log.Warn().
|
|
|
|
Str("config-file", fileName).
|
|
|
|
Err(err).
|
|
|
|
Msg("Failed to read config file, attempting to write default config")
|
|
|
|
err = WriteDefaultConfig(fileName)
|
|
|
|
if err != nil {
|
|
|
|
log.Error().
|
|
|
|
Err(err).
|
|
|
|
Str("config-file", fileName).
|
|
|
|
Msg("Failed to create default config file")
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
GlobalConfig = defaultConfig
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
config := Config{}
|
|
|
|
log.Debug().Str("config-file", fileName).Msg("Read config file, attempting to unmarshal")
|
|
|
|
err = toml.Unmarshal(data, &config)
|
|
|
|
if err != nil {
|
|
|
|
log.Error().Err(err).Bytes("config-data-raw", data).Msg("Failed to unmarshal config file")
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
GlobalConfig = config
|
|
|
|
log.Info().Str("config-file", fileName).Msg("Read and applied config file")
|
|
|
|
return nil
|
|
|
|
}
|