summaryrefslogtreecommitdiff
path: root/internal/config
diff options
context:
space:
mode:
authorLibravatar tobi <31960611+tsmethurst@users.noreply.github.com>2023-11-17 11:35:28 +0100
committerLibravatar GitHub <noreply@github.com>2023-11-17 11:35:28 +0100
commitfc02d3c6f7db5a7794448f31fd9d6d81d3d224eb (patch)
treef792f799abadf784e493933af597d8f2292ab776 /internal/config
parent[bugfix] process account delete side effects in serial, not in parallel (#2360) (diff)
downloadgotosocial-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.go16
-rw-r--r--internal/config/defaults.go2
-rw-r--r--internal/config/flags.go1
-rw-r--r--internal/config/gen/gen.go1
-rw-r--r--internal/config/helpers.gen.go26
-rw-r--r--internal/config/validate.go128
-rw-r--r--internal/config/validate_test.go8
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) {