/* GoToSocial Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ package config import ( "errors" "fmt" "os" "gopkg.in/yaml.v2" ) // Flags and usage strings for configuration. const ( UsernameFlag = "username" UsernameUsage = "the username to create/delete/etc" EmailFlag = "email" EmailUsage = "the email address of this account" PasswordFlag = "password" PasswordUsage = "the password to set for this account" ) // Config pulls together all the configuration needed to run gotosocial type Config struct { /* Parseable from .yaml configuration file. For long-running commands (server start etc). */ LogLevel string `yaml:"logLevel"` ApplicationName string `yaml:"applicationName"` Host string `yaml:"host"` Protocol string `yaml:"protocol"` DBConfig *DBConfig `yaml:"db"` TemplateConfig *TemplateConfig `yaml:"template"` AccountsConfig *AccountsConfig `yaml:"accounts"` MediaConfig *MediaConfig `yaml:"media"` StorageConfig *StorageConfig `yaml:"storage"` StatusesConfig *StatusesConfig `yaml:"statuses"` LetsEncryptConfig *LetsEncryptConfig `yaml:"letsEncrypt"` /* Not parsed from .yaml configuration file. */ AccountCLIFlags map[string]string SoftwareVersion string } // FromFile returns a new config from a file, or an error if something goes amiss. func FromFile(path string) (*Config, error) { if path != "" { c, err := loadFromFile(path) if err != nil { return nil, fmt.Errorf("error creating config: %s", err) } return c, nil } return Empty(), nil } // Empty just returns a new empty config func Empty() *Config { return &Config{ DBConfig: &DBConfig{}, TemplateConfig: &TemplateConfig{}, AccountsConfig: &AccountsConfig{}, MediaConfig: &MediaConfig{}, StorageConfig: &StorageConfig{}, StatusesConfig: &StatusesConfig{}, LetsEncryptConfig: &LetsEncryptConfig{}, AccountCLIFlags: make(map[string]string), } } // loadFromFile takes a path to a yaml file and attempts to load a Config object from it func loadFromFile(path string) (*Config, error) { bytes, err := os.ReadFile(path) if err != nil { return nil, fmt.Errorf("could not read file at path %s: %s", path, err) } config := &Config{} if err := yaml.Unmarshal(bytes, config); err != nil { return nil, fmt.Errorf("could not unmarshal file at path %s: %s", path, err) } return config, nil } // ParseCLIFlags sets flags on the config using the provided Flags object func (c *Config) ParseCLIFlags(f KeyedFlags, version string) error { fn := GetFlagNames() // For all of these flags, we only want to set them on the config if: // // a) They haven't been set at all in the config file we already parsed, // and so we take the default from the flags object. // // b) They may have been set in the config, but they've *also* been set explicitly // as a command-line argument or an env variable, which takes priority. // general flags if c.LogLevel == "" || f.IsSet(fn.LogLevel) { c.LogLevel = f.String(fn.LogLevel) } if c.ApplicationName == "" || f.IsSet(fn.ApplicationName) { c.ApplicationName = f.String(fn.ApplicationName) } if c.Host == "" || f.IsSet(fn.Host) { c.Host = f.String(fn.Host) } if c.Host == "" { return errors.New("host was not set") } if c.Protocol == "" || f.IsSet(fn.Protocol) { c.Protocol = f.String(fn.Protocol) } if c.Protocol == "" { return errors.New("protocol was not set") } // db flags if c.DBConfig.Type == "" || f.IsSet(fn.DbType) { c.DBConfig.Type = f.String(fn.DbType) } if c.DBConfig.Address == "" || f.IsSet(fn.DbAddress) { c.DBConfig.Address = f.String(fn.DbAddress) } if c.DBConfig.Port == 0 || f.IsSet(fn.DbPort) { c.DBConfig.Port = f.Int(fn.DbPort) } if c.DBConfig.User == "" || f.IsSet(fn.DbUser) { c.DBConfig.User = f.String(fn.DbUser) } if c.DBConfig.Password == "" || f.IsSet(fn.DbPassword) { c.DBConfig.Password = f.String(fn.DbPassword) } if c.DBConfig.Database == "" || f.IsSet(fn.DbDatabase) { c.DBConfig.Database = f.String(fn.DbDatabase) } // template flags if c.TemplateConfig.BaseDir == "" || f.IsSet(fn.TemplateBaseDir) { c.TemplateConfig.BaseDir = f.String(fn.TemplateBaseDir) } // template flags if c.TemplateConfig.AssetBaseDir == "" || f.IsSet(fn.AssetBaseDir) { c.TemplateConfig.AssetBaseDir = f.String(fn.AssetBaseDir) } // accounts flags if f.IsSet(fn.AccountsOpenRegistration) { c.AccountsConfig.OpenRegistration = f.Bool(fn.AccountsOpenRegistration) } if f.IsSet(fn.AccountsApprovalRequired) { c.AccountsConfig.RequireApproval = f.Bool(fn.AccountsApprovalRequired) } // media flags if c.MediaConfig.MaxImageSize == 0 || f.IsSet(fn.MediaMaxImageSize) { c.MediaConfig.MaxImageSize = f.Int(fn.MediaMaxImageSize) } if c.MediaConfig.MaxVideoSize == 0 || f.IsSet(fn.MediaMaxVideoSize) { c.MediaConfig.MaxVideoSize = f.Int(fn.MediaMaxVideoSize) } if c.MediaConfig.MinDescriptionChars == 0 || f.IsSet(fn.MediaMinDescriptionChars) { c.MediaConfig.MinDescriptionChars = f.Int(fn.MediaMinDescriptionChars) } if c.MediaConfig.MaxDescriptionChars == 0 || f.IsSet(fn.MediaMaxDescriptionChars) { c.MediaConfig.MaxDescriptionChars = f.Int(fn.MediaMaxDescriptionChars) } // storage flags if c.StorageConfig.Backend == "" || f.IsSet(fn.StorageBackend) { c.StorageConfig.Backend = f.String(fn.StorageBackend) } if c.StorageConfig.BasePath == "" || f.IsSet(fn.StorageBasePath) { c.StorageConfig.BasePath = f.String(fn.StorageBasePath) } if c.StorageConfig.ServeProtocol == "" || f.IsSet(fn.StorageServeProtocol) { c.StorageConfig.ServeProtocol = f.String(fn.StorageServeProtocol) } if c.StorageConfig.ServeHost == "" || f.IsSet(fn.StorageServeHost) { c.StorageConfig.ServeHost = f.String(fn.StorageServeHost) } if c.StorageConfig.ServeBasePath == "" || f.IsSet(fn.StorageServeBasePath) { c.StorageConfig.ServeBasePath = f.String(fn.StorageServeBasePath) } // statuses flags if c.StatusesConfig.MaxChars == 0 || f.IsSet(fn.StatusesMaxChars) { c.StatusesConfig.MaxChars = f.Int(fn.StatusesMaxChars) } if c.StatusesConfig.CWMaxChars == 0 || f.IsSet(fn.StatusesCWMaxChars) { c.StatusesConfig.CWMaxChars = f.Int(fn.StatusesCWMaxChars) } if c.StatusesConfig.PollMaxOptions == 0 || f.IsSet(fn.StatusesPollMaxOptions) { c.StatusesConfig.PollMaxOptions = f.Int(fn.StatusesPollMaxOptions) } if c.StatusesConfig.PollOptionMaxChars == 0 || f.IsSet(fn.StatusesPollOptionMaxChars) { c.StatusesConfig.PollOptionMaxChars = f.Int(fn.StatusesPollOptionMaxChars) } if c.StatusesConfig.MaxMediaFiles == 0 || f.IsSet(fn.StatusesMaxMediaFiles) { c.StatusesConfig.MaxMediaFiles = f.Int(fn.StatusesMaxMediaFiles) } // letsencrypt flags if f.IsSet(fn.LetsEncryptEnabled) { c.LetsEncryptConfig.Enabled = f.Bool(fn.LetsEncryptEnabled) } if c.LetsEncryptConfig.CertDir == "" || f.IsSet(fn.LetsEncryptCertDir) { c.LetsEncryptConfig.CertDir = f.String(fn.LetsEncryptCertDir) } if c.LetsEncryptConfig.EmailAddress == "" || f.IsSet(fn.LetsEncryptEmailAddress) { c.LetsEncryptConfig.EmailAddress = f.String(fn.LetsEncryptEmailAddress) } c.SoftwareVersion = GetDefaults().SoftwareVersion // command-specific flags // admin account CLI flags c.AccountCLIFlags[UsernameFlag] = f.String(UsernameFlag) c.AccountCLIFlags[EmailFlag] = f.String(EmailFlag) c.AccountCLIFlags[PasswordFlag] = f.String(PasswordFlag) c.SoftwareVersion = version return nil } // KeyedFlags is a wrapper for any type that can store keyed flags and give them back. // HINT: This works with a urfave cli context struct ;) type KeyedFlags interface { Bool(k string) bool String(k string) string Int(k string) int IsSet(k string) bool } // Flags is used for storing the names of the various flags used for // initializing and storing urfavecli flag variables. type Flags struct { LogLevel string ApplicationName string ConfigPath string Host string Protocol string DbType string DbAddress string DbPort string DbUser string DbPassword string DbDatabase string TemplateBaseDir string AssetBaseDir string AccountsOpenRegistration string AccountsApprovalRequired string AccountsReasonRequired string MediaMaxImageSize string MediaMaxVideoSize string MediaMinDescriptionChars string MediaMaxDescriptionChars string StorageBackend string StorageBasePath string StorageServeProtocol string StorageServeHost string StorageServeBasePath string StatusesMaxChars string StatusesCWMaxChars string StatusesPollMaxOptions string StatusesPollOptionMaxChars string StatusesMaxMediaFiles string LetsEncryptEnabled string LetsEncryptCertDir string LetsEncryptEmailAddress string } // Defaults contains all the default values for a gotosocial config type Defaults struct { LogLevel string ApplicationName string ConfigPath string Host string Protocol string SoftwareVersion string DbType string DbAddress string DbPort int DbUser string DbPassword string DbDatabase string TemplateBaseDir string AssetBaseDir string AccountsOpenRegistration bool AccountsRequireApproval bool AccountsReasonRequired bool MediaMaxImageSize int MediaMaxVideoSize int MediaMinDescriptionChars int MediaMaxDescriptionChars int StorageBackend string StorageBasePath string StorageServeProtocol string StorageServeHost string StorageServeBasePath string StatusesMaxChars int StatusesCWMaxChars int StatusesPollMaxOptions int StatusesPollOptionMaxChars int StatusesMaxMediaFiles int LetsEncryptEnabled bool LetsEncryptCertDir string LetsEncryptEmailAddress string } // GetFlagNames returns a struct containing the names of the various flags used for // initializing and storing urfavecli flag variables. func GetFlagNames() Flags { return Flags{ LogLevel: "log-level", ApplicationName: "application-name", ConfigPath: "config-path", Host: "host", Protocol: "protocol", DbType: "db-type", DbAddress: "db-address", DbPort: "db-port", DbUser: "db-user", DbPassword: "db-password", DbDatabase: "db-database", TemplateBaseDir: "template-basedir", AssetBaseDir: "asset-basedir", AccountsOpenRegistration: "accounts-open-registration", AccountsApprovalRequired: "accounts-approval-required", AccountsReasonRequired: "accounts-reason-required", MediaMaxImageSize: "media-max-image-size", MediaMaxVideoSize: "media-max-video-size", MediaMinDescriptionChars: "media-min-description-chars", MediaMaxDescriptionChars: "media-max-description-chars", StorageBackend: "storage-backend", StorageBasePath: "storage-base-path", StorageServeProtocol: "storage-serve-protocol", StorageServeHost: "storage-serve-host", StorageServeBasePath: "storage-serve-base-path", StatusesMaxChars: "statuses-max-chars", StatusesCWMaxChars: "statuses-cw-max-chars", StatusesPollMaxOptions: "statuses-poll-max-options", StatusesPollOptionMaxChars: "statuses-poll-option-max-chars", StatusesMaxMediaFiles: "statuses-max-media-files", LetsEncryptEnabled: "letsencrypt-enabled", LetsEncryptCertDir: "letsencrypt-cert-dir", LetsEncryptEmailAddress: "letsencrypt-email", } } // GetEnvNames returns a struct containing the names of the environment variable keys used for // initializing and storing urfavecli flag variables. func GetEnvNames() Flags { return Flags{ LogLevel: "GTS_LOG_LEVEL", ApplicationName: "GTS_APPLICATION_NAME", ConfigPath: "GTS_CONFIG_PATH", Host: "GTS_HOST", Protocol: "GTS_PROTOCOL", DbType: "GTS_DB_TYPE", DbAddress: "GTS_DB_ADDRESS", DbPort: "GTS_DB_PORT", DbUser: "GTS_DB_USER", DbPassword: "GTS_DB_PASSWORD", DbDatabase: "GTS_DB_DATABASE", TemplateBaseDir: "GTS_TEMPLATE_BASEDIR", AssetBaseDir: "GTS_ASSET_BASEDIR", AccountsOpenRegistration: "GTS_ACCOUNTS_OPEN_REGISTRATION", AccountsApprovalRequired: "GTS_ACCOUNTS_APPROVAL_REQUIRED", AccountsReasonRequired: "GTS_ACCOUNTS_REASON_REQUIRED", MediaMaxImageSize: "GTS_MEDIA_MAX_IMAGE_SIZE", MediaMaxVideoSize: "GTS_MEDIA_MAX_VIDEO_SIZE", MediaMinDescriptionChars: "GTS_MEDIA_MIN_DESCRIPTION_CHARS", MediaMaxDescriptionChars: "GTS_MEDIA_MAX_DESCRIPTION_CHARS", StorageBackend: "GTS_STORAGE_BACKEND", StorageBasePath: "GTS_STORAGE_BASE_PATH", StorageServeProtocol: "GTS_STORAGE_SERVE_PROTOCOL", StorageServeHost: "GTS_STORAGE_SERVE_HOST", StorageServeBasePath: "GTS_STORAGE_SERVE_BASE_PATH", StatusesMaxChars: "GTS_STATUSES_MAX_CHARS", StatusesCWMaxChars: "GTS_STATUSES_CW_MAX_CHARS", StatusesPollMaxOptions: "GTS_STATUSES_POLL_MAX_OPTIONS", StatusesPollOptionMaxChars: "GTS_STATUSES_POLL_OPTION_MAX_CHARS", StatusesMaxMediaFiles: "GTS_STATUSES_MAX_MEDIA_FILES", LetsEncryptEnabled: "GTS_LETSENCRYPT_ENABLED", LetsEncryptCertDir: "GTS_LETSENCRYPT_CERT_DIR", LetsEncryptEmailAddress: "GTS_LETSENCRYPT_EMAIL", } }