diff options
author | 2023-11-17 11:35:28 +0100 | |
---|---|---|
committer | 2023-11-17 11:35:28 +0100 | |
commit | fc02d3c6f7db5a7794448f31fd9d6d81d3d224eb (patch) | |
tree | f792f799abadf784e493933af597d8f2292ab776 /internal/config | |
parent | [bugfix] process account delete side effects in serial, not in parallel (#2360) (diff) | |
download | gotosocial-fc02d3c6f7db5a7794448f31fd9d6d81d3d224eb.tar.xz |
[feature] Set/show instance language(s); show post language on frontend (#2362)
* update go text, include text/display
* [feature] Set instance langs, show post lang on frontend
* go fmt
* WebGet
* set language for whole article, don't use FA icon
* mention instance languages + other optional config vars
* little tweak
* put languages in config properly
* warn log language parse
* change some naming around
* tidy up validate a bit
* lint
* rename LanguageTmpl in template
Diffstat (limited to 'internal/config')
-rw-r--r-- | internal/config/config.go | 16 | ||||
-rw-r--r-- | internal/config/defaults.go | 2 | ||||
-rw-r--r-- | internal/config/flags.go | 1 | ||||
-rw-r--r-- | internal/config/gen/gen.go | 1 | ||||
-rw-r--r-- | internal/config/helpers.gen.go | 26 | ||||
-rw-r--r-- | internal/config/validate.go | 128 | ||||
-rw-r--r-- | internal/config/validate_test.go | 8 |
7 files changed, 130 insertions, 52 deletions
diff --git a/internal/config/config.go b/internal/config/config.go index 7cb31d0a1..b7d2eff36 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -23,6 +23,7 @@ import ( "codeberg.org/gruf/go-bytesize" "github.com/mitchellh/mapstructure" + "github.com/superseriousbusiness/gotosocial/internal/language" ) // cfgtype is the reflected type information of Configuration{}. @@ -76,13 +77,14 @@ type Configuration struct { WebTemplateBaseDir string `name:"web-template-base-dir" usage:"Basedir for html templating files for rendering pages and composing emails."` WebAssetBaseDir string `name:"web-asset-base-dir" usage:"Directory to serve static assets from, accessible at example.org/assets/"` - InstanceFederationMode string `name:"instance-federation-mode" usage:"Set instance federation mode."` - InstanceExposePeers bool `name:"instance-expose-peers" usage:"Allow unauthenticated users to query /api/v1/instance/peers?filter=open"` - InstanceExposeSuspended bool `name:"instance-expose-suspended" usage:"Expose suspended instances via web UI, and allow unauthenticated users to query /api/v1/instance/peers?filter=suspended"` - InstanceExposeSuspendedWeb bool `name:"instance-expose-suspended-web" usage:"Expose list of suspended instances as webpage on /about/suspended"` - InstanceExposePublicTimeline bool `name:"instance-expose-public-timeline" usage:"Allow unauthenticated users to query /api/v1/timelines/public"` - InstanceDeliverToSharedInboxes bool `name:"instance-deliver-to-shared-inboxes" usage:"Deliver federated messages to shared inboxes, if they're available."` - InstanceInjectMastodonVersion bool `name:"instance-inject-mastodon-version" usage:"This injects a Mastodon compatible version in /api/v1/instance to help Mastodon clients that use that version for feature detection"` + InstanceFederationMode string `name:"instance-federation-mode" usage:"Set instance federation mode."` + InstanceExposePeers bool `name:"instance-expose-peers" usage:"Allow unauthenticated users to query /api/v1/instance/peers?filter=open"` + InstanceExposeSuspended bool `name:"instance-expose-suspended" usage:"Expose suspended instances via web UI, and allow unauthenticated users to query /api/v1/instance/peers?filter=suspended"` + InstanceExposeSuspendedWeb bool `name:"instance-expose-suspended-web" usage:"Expose list of suspended instances as webpage on /about/suspended"` + InstanceExposePublicTimeline bool `name:"instance-expose-public-timeline" usage:"Allow unauthenticated users to query /api/v1/timelines/public"` + InstanceDeliverToSharedInboxes bool `name:"instance-deliver-to-shared-inboxes" usage:"Deliver federated messages to shared inboxes, if they're available."` + InstanceInjectMastodonVersion bool `name:"instance-inject-mastodon-version" usage:"This injects a Mastodon compatible version in /api/v1/instance to help Mastodon clients that use that version for feature detection"` + InstanceLanguages language.Languages `name:"instance-languages" usage:"BCP47 language tags for the instance. Used to indicate the preferred languages of instance residents (in order from most-preferred to least-preferred)."` AccountsRegistrationOpen bool `name:"accounts-registration-open" usage:"Allow anyone to submit an account signup request. If false, server will be invite-only."` AccountsApprovalRequired bool `name:"accounts-approval-required" usage:"Do account signups require approval by an admin or moderator before user can log in? If false, new registrations will be automatically approved."` diff --git a/internal/config/defaults.go b/internal/config/defaults.go index 2a7c6f9db..3da489501 100644 --- a/internal/config/defaults.go +++ b/internal/config/defaults.go @@ -22,6 +22,7 @@ import ( "codeberg.org/gruf/go-bytesize" "github.com/coreos/go-oidc/v3/oidc" + "github.com/superseriousbusiness/gotosocial/internal/language" ) // Defaults contains a populated Configuration with reasonable defaults. Note that @@ -62,6 +63,7 @@ var Defaults = Configuration{ InstanceExposeSuspended: false, InstanceExposeSuspendedWeb: false, InstanceDeliverToSharedInboxes: true, + InstanceLanguages: make(language.Languages, 0), AccountsRegistrationOpen: true, AccountsApprovalRequired: true, diff --git a/internal/config/flags.go b/internal/config/flags.go index b29d0fe04..2dc583abc 100644 --- a/internal/config/flags.go +++ b/internal/config/flags.go @@ -88,6 +88,7 @@ func (s *ConfigState) AddServerFlags(cmd *cobra.Command) { cmd.Flags().Bool(InstanceExposeSuspendedFlag(), cfg.InstanceExposeSuspended, fieldtag("InstanceExposeSuspended", "usage")) cmd.Flags().Bool(InstanceExposeSuspendedWebFlag(), cfg.InstanceExposeSuspendedWeb, fieldtag("InstanceExposeSuspendedWeb", "usage")) cmd.Flags().Bool(InstanceDeliverToSharedInboxesFlag(), cfg.InstanceDeliverToSharedInboxes, fieldtag("InstanceDeliverToSharedInboxes", "usage")) + // cmd.Flags().StringSlice(InstanceLanguagesFlag(), cfg.InstanceLanguages.TagStrs(), fieldtag("InstanceLanguages", "usage")) // Accounts cmd.Flags().Bool(AccountsRegistrationOpenFlag(), cfg.AccountsRegistrationOpen, fieldtag("AccountsRegistrationOpen", "usage")) diff --git a/internal/config/gen/gen.go b/internal/config/gen/gen.go index d9fb30904..96e036d98 100644 --- a/internal/config/gen/gen.go +++ b/internal/config/gen/gen.go @@ -67,6 +67,7 @@ func main() { fmt.Fprint(output, "import (\n") fmt.Fprint(output, "\t\"time\"\n\n") fmt.Fprint(output, "\t\"codeberg.org/gruf/go-bytesize\"\n") + fmt.Fprint(output, "\t\"github.com/superseriousbusiness/gotosocial/internal/langs\"\n") fmt.Fprint(output, ")\n\n") generateFields(output, nil, reflect.TypeOf(config.Configuration{})) _ = output.Close() diff --git a/internal/config/helpers.gen.go b/internal/config/helpers.gen.go index 6e9b71812..a008092d3 100644 --- a/internal/config/helpers.gen.go +++ b/internal/config/helpers.gen.go @@ -22,6 +22,7 @@ import ( "time" "codeberg.org/gruf/go-bytesize" + "github.com/superseriousbusiness/gotosocial/internal/language" ) // GetLogLevel safely fetches the Configuration value for state's 'LogLevel' field @@ -924,6 +925,31 @@ func GetInstanceInjectMastodonVersion() bool { return global.GetInstanceInjectMa // SetInstanceInjectMastodonVersion safely sets the value for global configuration 'InstanceInjectMastodonVersion' field func SetInstanceInjectMastodonVersion(v bool) { global.SetInstanceInjectMastodonVersion(v) } +// GetInstanceLanguages safely fetches the Configuration value for state's 'InstanceLanguages' field +func (st *ConfigState) GetInstanceLanguages() (v language.Languages) { + st.mutex.RLock() + v = st.config.InstanceLanguages + st.mutex.RUnlock() + return +} + +// SetInstanceLanguages safely sets the Configuration value for state's 'InstanceLanguages' field +func (st *ConfigState) SetInstanceLanguages(v language.Languages) { + st.mutex.Lock() + defer st.mutex.Unlock() + st.config.InstanceLanguages = v + st.reloadToViper() +} + +// InstanceLanguagesFlag returns the flag name for the 'InstanceLanguages' field +func InstanceLanguagesFlag() string { return "instance-languages" } + +// GetInstanceLanguages safely fetches the value for global configuration 'InstanceLanguages' field +func GetInstanceLanguages() language.Languages { return global.GetInstanceLanguages() } + +// SetInstanceLanguages safely sets the value for global configuration 'InstanceLanguages' field +func SetInstanceLanguages(v language.Languages) { global.SetInstanceLanguages(v) } + // GetAccountsRegistrationOpen safely fetches the Configuration value for state's 'AccountsRegistrationOpen' field func (st *ConfigState) GetAccountsRegistrationOpen() (v bool) { st.mutex.RLock() diff --git a/internal/config/validate.go b/internal/config/validate.go index 45cdc4eee..d79d83b9d 100644 --- a/internal/config/validate.go +++ b/internal/config/validate.go @@ -18,85 +18,131 @@ package config import ( - "errors" "fmt" - "strings" "github.com/miekg/dns" + "github.com/superseriousbusiness/gotosocial/internal/gtserror" + "github.com/superseriousbusiness/gotosocial/internal/language" "github.com/superseriousbusiness/gotosocial/internal/log" ) -// Validate validates global config settings which don't have defaults, to make sure they are set sensibly. +// Validate validates global config settings. func Validate() error { - errs := []error{} + // Gather all validation errors in + // easily readable format for admins. + var ( + errs gtserror.MultiError + errf = func(format string, a ...any) { + errs = append(errs, fmt.Errorf(format, a...)) + } + ) - // host + // `host` host := GetHost() if host == "" { - errs = append(errs, fmt.Errorf("%s must be set", HostFlag())) + errf("%s must be set", HostFlag()) } - // accountDomain; only check if host was set, otherwise there's no point + // If `account-domain` and `host` + // are set, `host` must be a valid + // subdomain of `account-domain`. if host != "" { - switch ad := GetAccountDomain(); ad { - case "": + ad := GetAccountDomain() + if ad == "" { + // `account-domain` not set, fall + // back by setting it to `host`. SetAccountDomain(GetHost()) - default: - if !dns.IsSubDomain(ad, host) { - errs = append(errs, fmt.Errorf("%s was %s and %s was %s, but %s is not a valid subdomain of %s", HostFlag(), host, AccountDomainFlag(), ad, host, ad)) - } + } else if !dns.IsSubDomain(ad, host) { + errf( + "%s %s is not a valid subdomain of %s %s", + AccountDomainFlag(), ad, HostFlag(), host, + ) } } - // protocol + // Ensure `protocol` sensibly set. switch proto := GetProtocol(); proto { case "https": - // no problem - break + // No problem. + case "http": - log.Warnf(nil, "%s was set to 'http'; this should *only* be used for debugging and tests!", ProtocolFlag()) + log.Warnf( + nil, + "%s was set to 'http'; this should *only* be used for debugging and tests!", + ProtocolFlag(), + ) + case "": - errs = append(errs, fmt.Errorf("%s must be set", ProtocolFlag())) + errf("%s must be set", ProtocolFlag()) + default: - errs = append(errs, fmt.Errorf("%s must be set to either http or https, provided value was %s", ProtocolFlag(), proto)) + errf( + "%s must be set to either http or https, provided value was %s", + ProtocolFlag(), proto, + ) } - // federation mode - switch federationMode := GetInstanceFederationMode(); federationMode { + // `federation-mode` should be + // "blocklist" or "allowlist". + switch fediMode := GetInstanceFederationMode(); fediMode { case InstanceFederationModeBlocklist, InstanceFederationModeAllowlist: - // no problem - break + // No problem. + case "": - errs = append(errs, fmt.Errorf("%s must be set", InstanceFederationModeFlag())) + errf("%s must be set", InstanceFederationModeFlag()) + default: - errs = append(errs, fmt.Errorf("%s must be set to either blocklist or allowlist, provided value was %s", InstanceFederationModeFlag(), federationMode)) + errf( + "%s must be set to either blocklist or allowlist, provided value was %s", + InstanceFederationModeFlag(), fediMode, + ) } + // Parse `instance-languages`, and + // set enriched version into config. + parsedLangs, err := language.InitLangs(GetInstanceLanguages().TagStrs()) + if err != nil { + errf( + "%s could not be parsed as an array of valid BCP47 language tags: %v", + InstanceLanguagesFlag(), err, + ) + } else { + // Parsed successfully, put enriched + // versions in config immediately. + SetInstanceLanguages(parsedLangs) + } + + // `web-assets-base-dir`. webAssetsBaseDir := GetWebAssetBaseDir() if webAssetsBaseDir == "" { - errs = append(errs, fmt.Errorf("%s must be set", WebAssetBaseDirFlag())) + errf("%s must be set", WebAssetBaseDirFlag()) } - tlsChain := GetTLSCertificateChain() - tlsKey := GetTLSCertificateKey() - tlsChainFlag := TLSCertificateChainFlag() - tlsKeyFlag := TLSCertificateKeyFlag() + // Custom / LE TLS settings. + // + // Only one of custom certs or LE can be set, + // and if using custom certs then all relevant + // values must be provided. + var ( + tlsChain = GetTLSCertificateChain() + tlsKey = GetTLSCertificateKey() + tlsChainFlag = TLSCertificateChainFlag() + tlsKeyFlag = TLSCertificateKeyFlag() + ) if GetLetsEncryptEnabled() && (tlsChain != "" || tlsKey != "") { - errs = append(errs, fmt.Errorf("%s cannot be enabled when %s and/or %s are also set", LetsEncryptEnabledFlag(), tlsChainFlag, tlsKeyFlag)) + errf( + "%s cannot be true when %s and/or %s are also set", + LetsEncryptEnabledFlag(), tlsChainFlag, tlsKeyFlag, + ) } if (tlsChain != "" && tlsKey == "") || (tlsChain == "" && tlsKey != "") { - errs = append(errs, fmt.Errorf("%s and %s need to both be set or unset", tlsChainFlag, tlsKeyFlag)) - } - - if len(errs) > 0 { - errStrings := []string{} - for _, err := range errs { - errStrings = append(errStrings, err.Error()) - } - return errors.New(strings.Join(errStrings, "; ")) + errf( + "%s and %s need to both be set or unset", + tlsChainFlag, tlsKeyFlag, + ) } - return nil + return errs.Combine() } diff --git a/internal/config/validate_test.go b/internal/config/validate_test.go index 72c268c16..1ae3d6c78 100644 --- a/internal/config/validate_test.go +++ b/internal/config/validate_test.go @@ -80,7 +80,7 @@ func (suite *ConfigValidateTestSuite) TestValidateAccountDomainNotSubdomain1() { config.SetAccountDomain("example.com") err := config.Validate() - suite.EqualError(err, "host was gts.example.org and account-domain was example.com, but gts.example.org is not a valid subdomain of example.com") + suite.EqualError(err, "account-domain example.com is not a valid subdomain of host gts.example.org") } func (suite *ConfigValidateTestSuite) TestValidateAccountDomainNotSubdomain2() { @@ -90,7 +90,7 @@ func (suite *ConfigValidateTestSuite) TestValidateAccountDomainNotSubdomain2() { config.SetAccountDomain("gts.example.org") err := config.Validate() - suite.EqualError(err, "host was example.org and account-domain was gts.example.org, but example.org is not a valid subdomain of gts.example.org") + suite.EqualError(err, "account-domain gts.example.org is not a valid subdomain of host example.org") } func (suite *ConfigValidateTestSuite) TestValidateConfigNoProtocol() { @@ -118,7 +118,7 @@ func (suite *ConfigValidateTestSuite) TestValidateConfigNoProtocolOrHost() { config.SetProtocol("") err := config.Validate() - suite.EqualError(err, "host must be set; protocol must be set") + suite.EqualError(err, "host must be set\nprotocol must be set") } func (suite *ConfigValidateTestSuite) TestValidateConfigBadProtocol() { @@ -137,7 +137,7 @@ func (suite *ConfigValidateTestSuite) TestValidateConfigBadProtocolNoHost() { config.SetProtocol("foo") err := config.Validate() - suite.EqualError(err, "host must be set; protocol must be set to either http or https, provided value was foo") + suite.EqualError(err, "host must be set\nprotocol must be set to either http or https, provided value was foo") } func TestConfigValidateTestSuite(t *testing.T) { |