Merge branch 'main' of gitlab.com:mstarongitlab/linstrom

also delete server-old with all its problems
This commit is contained in:
Melody Becker 2024-09-13 20:41:47 +02:00
commit d99efca667
Signed by: mstar
SSH key fingerprint: SHA256:vkXfS9FG2pVNVfvDrzd1VW9n8VJzqqdKQGljxxX8uK8
47 changed files with 2200 additions and 694 deletions

View file

@ -1,11 +1,12 @@
package config
import (
"errors"
"fmt"
"os"
"github.com/BurntSushi/toml"
"github.com/rs/zerolog/log"
"gitlab.com/mstarongitlab/goutils/other"
)
type ConfigSSL struct {
@ -19,25 +20,44 @@ type ConfigSSL struct {
}
type ConfigGeneral struct {
Domain string `toml:"domain"` // The domain this server operates under
FullDomain string `toml:"full_domain"` // The full url the server operates under (without port), eg "http://localhost", the public port will be appended as needed
PublicPort int `toml:"public_port"` // The public facing port, usually either 80 or 443
PrivatePort int `toml:"private_port"` // The port the server should launch at
// Explanation:
// The public port is the port to connect to from outside the server to access it.
// The private port is where the server will open for itself on launch
// Important for reverse proxies like nginx or traeffik
// Where you the public port would be either 80 (http) or 443 (https), but the private one could be 3000 for example
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 {
Username string `toml:"username"`
PasswordHash string `toml:"password_hash"`
// 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 {
IsPostgres bool `toml:"is_postgres"`
Uri string `toml:"uri"`
// 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 ConfigMail struct {
@ -53,27 +73,23 @@ type ConfigMail struct {
}
type Config struct {
General ConfigGeneral `toml:"general"`
SSL ConfigSSL `toml:"ssl"`
Admin ConfigAdmin `toml:"admin"`
Storage ConfigStorage `toml:"storage"`
Mail ConfigMail `toml:"mail"`
General ConfigGeneral `toml:"general"`
SSL ConfigSSL `toml:"ssl"`
Admin ConfigAdmin `toml:"admin"`
Webauthn ConfigWebAuthn `toml:"webauthn"`
Storage ConfigStorage `toml:"storage"`
Mail ConfigMail `toml:"mail"`
}
const DEFAULT_CONFIG_FILE_PATH = "config.toml"
var GlobalConfig Config
// "Global" variable for accessing the config
// If nil, no config has been loaded yet
var Global *Config
var ErrGlobalConfigNotSet = errors.New("global config not set")
// The default config is for a local debug environment
var defaultConfig = Config{
var defaultConfig Config = Config{
General: ConfigGeneral{
Protocol: "http",
Subdomain: nil,
Domain: "localhost",
PublicPort: 8080,
PrivatePort: 8080,
PublicPort: nil,
},
SSL: ConfigSSL{
HandleSSL: false,
@ -82,54 +98,94 @@ var defaultConfig = Config{
AdminMail: nil,
},
Admin: ConfigAdmin{
Username: "admin",
PasswordHash: "", // No password
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{
IsPostgres: false,
Uri: "db.sqlite",
DatabaseUrl: "db.sqlite",
DbIsPostgres: other.IntoPointer(false),
RedisUrl: nil,
MaxInMemoryCacheSize: 1e6, // 1 Megabyte
},
}
func LoadConfigFromFile(file string, tryDefaultPath, saveIntoGlobal bool) (*Config, error) {
conf := &Config{}
data, err := os.ReadFile(file)
if err != nil && !tryDefaultPath {
return nil, fmt.Errorf("failed to read config %s: %w", file, err)
func (gc *ConfigGeneral) GetFullDomain() string {
if gc.Subdomain != nil {
return *gc.Subdomain + gc.Domain
}
if err != nil && tryDefaultPath {
conf, err = loadFromDefaultPath()
if err != nil {
return nil, fmt.Errorf("failed to read custom and default config: %w", err)
}
}
err = toml.Unmarshal(data, &conf)
if err != nil {
return nil, fmt.Errorf("failed to convert from toml: %w", err)
}
if saveIntoGlobal {
Global = conf
}
return conf, nil
return gc.Domain
}
func loadFromDefaultPath() (*Config, error) {
data, err := os.ReadFile(DEFAULT_CONFIG_FILE_PATH)
if err != nil {
return nil, fmt.Errorf(
"failed to load default config path %s: %w",
DEFAULT_CONFIG_FILE_PATH,
err,
)
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)
}
conf := Config{}
err = toml.Unmarshal(data, &conf)
if err != nil {
return nil, fmt.Errorf(
"failed to parse file content of %s as toml: %w",
DEFAULT_CONFIG_FILE_PATH,
err,
)
}
return &conf, nil
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
}