gtsocial-umbx

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

commit 43ac0cdb9c4eea9d3c5eceb2c11b9e5b98b87b00
parent ae5402ada6aa794a69de455f745934b27e030d3d
Author: kim <89579420+NyaaaWhatsUpDoc@users.noreply.github.com>
Date:   Mon, 30 May 2022 13:41:24 +0100

[chore] Global server configuration overhaul (#575)

* move config flag names and usage to config package, rewrite config package to use global Configuration{} struct

Signed-off-by: kim <grufwub@gmail.com>

* improved code comment

Signed-off-by: kim <grufwub@gmail.com>

* linter

Signed-off-by: kim <grufwub@gmail.com>

* fix unmarshaling

Signed-off-by: kim <grufwub@gmail.com>

* remove kim's custom go compiler changes

Signed-off-by: kim <grufwub@gmail.com>

* generate setter and flag-name functions, implement these in codebase

Signed-off-by: kim <grufwub@gmail.com>

* update deps

Signed-off-by: kim <grufwub@gmail.com>

* small change

Signed-off-by: kim <grufwub@gmail.com>

* appease the linter...

Signed-off-by: kim <grufwub@gmail.com>

* move configuration into ConfigState structure, ensure reloading to/from viper settings to keep in sync

Signed-off-by: kim <grufwub@gmail.com>

* lint

Signed-off-by: kim <grufwub@gmail.com>

* update code comments

Signed-off-by: kim <grufwub@gmail.com>

* fix merge issue

Signed-off-by: kim <grufwub@gmail.com>

* fix merge issue

Signed-off-by: kim <grufwub@gmail.com>

* improved version string (removes time + go version)

Signed-off-by: kim <grufwub@gmail.com>

* fix version string build to pass test script + consolidate logic in func

Signed-off-by: kim <grufwub@gmail.com>

* add license text, update config.Defaults comment

Signed-off-by: kim <grufwub@gmail.com>

* add license text to generated config helpers file

Signed-off-by: kim <grufwub@gmail.com>

* defer unlock on config.Set___(), to ensure unlocked on panic

Signed-off-by: kim <grufwub@gmail.com>

* make it more obvious which cmd flags are being attached

Signed-off-by: kim <grufwub@gmail.com>
Diffstat:
Mcmd/gotosocial/action/admin/account/account.go | 19+++++++++----------
Mcmd/gotosocial/action/admin/trans/export.go | 3+--
Mcmd/gotosocial/action/admin/trans/import.go | 3+--
Mcmd/gotosocial/action/debug/config/config.go | 22+++++++++++++++++-----
Mcmd/gotosocial/action/server/server.go | 8+++-----
Mcmd/gotosocial/admin.go | 22+++++++++++-----------
Mcmd/gotosocial/common.go | 8++++----
Mcmd/gotosocial/debug.go | 4+---
Dcmd/gotosocial/flag/admin.go | 62--------------------------------------------------------------
Dcmd/gotosocial/flag/global.go | 46----------------------------------------------
Dcmd/gotosocial/flag/server.go | 117-------------------------------------------------------------------------------
Dcmd/gotosocial/flag/usage.go | 82-------------------------------------------------------------------------------
Mcmd/gotosocial/main.go | 92++++++++++++++++++++++++++++++++++++++++++++++++-------------------------------
Mcmd/gotosocial/server.go | 5+----
Mgo.mod | 2+-
Minternal/api/client/account/account_test.go | 5++---
Minternal/api/client/account/accountcreate.go | 7++-----
Minternal/api/client/admin/admin_test.go | 5++---
Minternal/api/client/admin/mediacleanup.go | 3+--
Minternal/api/client/auth/auth_test.go | 5++---
Minternal/api/client/followrequest/followrequest_test.go | 5++---
Minternal/api/client/instance/instance_test.go | 5++---
Minternal/api/client/instance/instanceget.go | 3+--
Minternal/api/client/media/mediacreate.go | 10++++------
Minternal/api/client/media/mediacreate_test.go | 3+--
Minternal/api/client/media/mediaupdate.go | 6++----
Minternal/api/client/media/mediaupdate_test.go | 3+--
Minternal/api/client/status/statuscreate.go | 12+++++-------
Minternal/api/s2s/webfinger/webfingerget.go | 5++---
Minternal/api/s2s/webfinger/webfingerget_test.go | 19++++++++++---------
Ainternal/config/config.go | 139+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Minternal/config/defaults.go | 6+++---
Dinternal/config/file.go | 38--------------------------------------
Ainternal/config/flags.go | 157+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ainternal/config/gen/gen.go | 112+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ainternal/config/global.go | 53+++++++++++++++++++++++++++++++++++++++++++++++++++++
Ainternal/config/helpers.gen.go | 1494+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dinternal/config/keys.go | 182-------------------------------------------------------------------------------
Ainternal/config/state.go | 136+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Minternal/config/validate.go | 14++++++--------
Minternal/config/validate_test.go | 15+++++++--------
Dinternal/config/values.go | 93-------------------------------------------------------------------------------
Dinternal/config/viper.go | 42------------------------------------------
Minternal/db/bundb/account.go | 4+---
Minternal/db/bundb/admin.go | 7+++----
Minternal/db/bundb/bundb.go | 56++++++++++++++++++++++++--------------------------------
Minternal/db/bundb/bundbnew_test.go | 3+--
Minternal/db/bundb/domain.go | 3+--
Minternal/db/bundb/instance.go | 10+++-------
Minternal/email/confirm.go | 3+--
Minternal/email/noopsender.go | 3+--
Minternal/email/sender.go | 15++++++---------
Minternal/federation/authenticate.go | 4+---
Minternal/federation/dereferencing/thread.go | 13++++---------
Minternal/federation/federatingdb/inbox.go | 3+--
Minternal/federation/federatingdb/owns.go | 4+---
Minternal/federation/federatingdb/update.go | 4+---
Minternal/federation/federatingdb/util.go | 5+----
Minternal/log/log.go | 14+++++---------
Minternal/log/syslog_test.go | 15++++++++-------
Minternal/media/manager.go | 3+--
Minternal/oidc/idp.go | 23+++++++++--------------
Minternal/processing/account/create.go | 6++----
Minternal/processing/account/update.go | 5++---
Minternal/processing/blocks.go | 5++---
Minternal/processing/federation/getnodeinfo.go | 9++++-----
Minternal/processing/federation/getwebfinger.go | 5++---
Minternal/processing/instance.go | 3+--
Minternal/processing/search.go | 4+---
Minternal/processing/statustimeline.go | 9++++-----
Minternal/processing/user/emailconfirm.go | 7++-----
Minternal/router/router.go | 35++++++++++++++---------------------
Minternal/router/session.go | 15+++++++--------
Minternal/router/session_test.go | 27+++++++++++++--------------
Minternal/router/template.go | 5++---
Minternal/transport/controller.go | 14+++++---------
Minternal/transport/deliver.go | 3+--
Minternal/transport/dereference.go | 3+--
Minternal/typeutils/internaltoas.go | 5++---
Minternal/typeutils/internaltofrontend.go | 13+++++--------
Minternal/uris/uri.go | 35++++++++++++++++-------------------
Minternal/web/base.go | 9++++-----
Minternal/web/confirmemail.go | 3+--
Minternal/web/profile.go | 3+--
Minternal/web/thread.go | 3+--
Mtest/cliparsing.sh | 22+++++++++++-----------
Mtestrig/config.go | 36+++++-------------------------------
Mtestrig/db.go | 13+++++++++----
Mtestrig/email.go | 5+++--
Mtestrig/router.go | 9+++++----
90 files changed, 2450 insertions(+), 1125 deletions(-)

diff --git a/cmd/gotosocial/action/admin/account/account.go b/cmd/gotosocial/action/admin/account/account.go @@ -24,7 +24,6 @@ import ( "fmt" "time" - "github.com/spf13/viper" "github.com/superseriousbusiness/gotosocial/cmd/gotosocial/action" "github.com/superseriousbusiness/gotosocial/internal/config" "github.com/superseriousbusiness/gotosocial/internal/db" @@ -41,7 +40,7 @@ var Create action.GTSAction = func(ctx context.Context) error { return fmt.Errorf("error creating dbservice: %s", err) } - username := viper.GetString(config.Keys.AdminAccountUsername) + username := config.GetAdminAccountUsername() if username == "" { return errors.New("no username set") } @@ -49,7 +48,7 @@ var Create action.GTSAction = func(ctx context.Context) error { return err } - email := viper.GetString(config.Keys.AdminAccountEmail) + email := config.GetAdminAccountEmail() if email == "" { return errors.New("no email set") } @@ -57,7 +56,7 @@ var Create action.GTSAction = func(ctx context.Context) error { return err } - password := viper.GetString(config.Keys.AdminAccountPassword) + password := config.GetAdminAccountPassword() if password == "" { return errors.New("no password set") } @@ -80,7 +79,7 @@ var Confirm action.GTSAction = func(ctx context.Context) error { return fmt.Errorf("error creating dbservice: %s", err) } - username := viper.GetString(config.Keys.AdminAccountUsername) + username := config.GetAdminAccountUsername() if username == "" { return errors.New("no username set") } @@ -115,7 +114,7 @@ var Promote action.GTSAction = func(ctx context.Context) error { return fmt.Errorf("error creating dbservice: %s", err) } - username := viper.GetString(config.Keys.AdminAccountUsername) + username := config.GetAdminAccountUsername() if username == "" { return errors.New("no username set") } @@ -147,7 +146,7 @@ var Demote action.GTSAction = func(ctx context.Context) error { return fmt.Errorf("error creating dbservice: %s", err) } - username := viper.GetString(config.Keys.AdminAccountUsername) + username := config.GetAdminAccountUsername() if username == "" { return errors.New("no username set") } @@ -179,7 +178,7 @@ var Disable action.GTSAction = func(ctx context.Context) error { return fmt.Errorf("error creating dbservice: %s", err) } - username := viper.GetString(config.Keys.AdminAccountUsername) + username := config.GetAdminAccountUsername() if username == "" { return errors.New("no username set") } @@ -217,7 +216,7 @@ var Password action.GTSAction = func(ctx context.Context) error { return fmt.Errorf("error creating dbservice: %s", err) } - username := viper.GetString(config.Keys.AdminAccountUsername) + username := config.GetAdminAccountUsername() if username == "" { return errors.New("no username set") } @@ -225,7 +224,7 @@ var Password action.GTSAction = func(ctx context.Context) error { return err } - password := viper.GetString(config.Keys.AdminAccountPassword) + password := config.GetAdminAccountPassword() if password == "" { return errors.New("no password set") } diff --git a/cmd/gotosocial/action/admin/trans/export.go b/cmd/gotosocial/action/admin/trans/export.go @@ -23,7 +23,6 @@ import ( "errors" "fmt" - "github.com/spf13/viper" "github.com/superseriousbusiness/gotosocial/cmd/gotosocial/action" "github.com/superseriousbusiness/gotosocial/internal/config" "github.com/superseriousbusiness/gotosocial/internal/db/bundb" @@ -39,7 +38,7 @@ var Export action.GTSAction = func(ctx context.Context) error { exporter := trans.NewExporter(dbConn) - path := viper.GetString(config.Keys.AdminTransPath) + path := config.GetAdminTransPath() if path == "" { return errors.New("no path set") } diff --git a/cmd/gotosocial/action/admin/trans/import.go b/cmd/gotosocial/action/admin/trans/import.go @@ -23,7 +23,6 @@ import ( "errors" "fmt" - "github.com/spf13/viper" "github.com/superseriousbusiness/gotosocial/cmd/gotosocial/action" "github.com/superseriousbusiness/gotosocial/internal/config" "github.com/superseriousbusiness/gotosocial/internal/db/bundb" @@ -39,7 +38,7 @@ var Import action.GTSAction = func(ctx context.Context) error { importer := trans.NewImporter(dbConn) - path := viper.GetString(config.Keys.AdminTransPath) + path := config.GetAdminTransPath() if path == "" { return errors.New("no path set") } diff --git a/cmd/gotosocial/action/debug/config/config.go b/cmd/gotosocial/action/debug/config/config.go @@ -23,17 +23,29 @@ import ( "encoding/json" "fmt" - "github.com/spf13/viper" "github.com/superseriousbusiness/gotosocial/cmd/gotosocial/action" + "github.com/superseriousbusiness/gotosocial/internal/config" ) // Config just prints the collated config out to stdout as json. -var Config action.GTSAction = func(ctx context.Context) error { - allSettings := viper.AllSettings() - b, err := json.Marshal(&allSettings) +var Config action.GTSAction = func(ctx context.Context) (err error) { + var raw map[string]interface{} + + // Marshal configuration to a raw JSON map + config.Config(func(cfg *config.Configuration) { + raw, err = cfg.MarshalMap() + }) + if err != nil { + return err + } + + // Marshal map to JSON + b, err := json.Marshal(raw) if err != nil { return err } - fmt.Println(string(b)) + + // Print to stdout + fmt.Printf("%s\n", b) return nil } diff --git a/cmd/gotosocial/action/server/server.go b/cmd/gotosocial/action/server/server.go @@ -29,7 +29,6 @@ import ( "codeberg.org/gruf/go-store/kv" "codeberg.org/gruf/go-store/storage" "github.com/sirupsen/logrus" - "github.com/spf13/viper" "github.com/superseriousbusiness/gotosocial/cmd/gotosocial/action" "github.com/superseriousbusiness/gotosocial/internal/api" "github.com/superseriousbusiness/gotosocial/internal/api/client/account" @@ -107,7 +106,7 @@ var Start action.GTSAction = func(ctx context.Context) error { typeConverter := typeutils.NewConverter(dbService) // Open the storage backend - storageBasePath := viper.GetString(config.Keys.StorageLocalBasePath) + storageBasePath := config.GetStorageLocalBasePath() storage, err := kv.OpenFile(storageBasePath, &storage.DiskConfig{ // Put the store lockfile in the storage dir itself. // Normally this would not be safe, since we could end up @@ -134,8 +133,7 @@ var Start action.GTSAction = func(ctx context.Context) error { // decide whether to create a noop email sender (won't send emails) or a real one var emailSender email.Sender - smtpHost := viper.GetString(config.Keys.SMTPHost) - if smtpHost != "" { + if smtpHost := config.GetSMTPHost(); smtpHost != "" { // host is defined so create a proper sender emailSender, err = email.NewSender() if err != nil { @@ -239,7 +237,7 @@ var Start action.GTSAction = func(ctx context.Context) error { } // perform initial media prune in case value of MediaRemoteCacheDays changed - if err := processor.AdminMediaPrune(ctx, viper.GetInt(config.Keys.MediaRemoteCacheDays)); err != nil { + if err := processor.AdminMediaPrune(ctx, config.GetMediaRemoteCacheDays()); err != nil { return fmt.Errorf("error during initial media prune: %s", err) } diff --git a/cmd/gotosocial/admin.go b/cmd/gotosocial/admin.go @@ -22,7 +22,6 @@ import ( "github.com/spf13/cobra" "github.com/superseriousbusiness/gotosocial/cmd/gotosocial/action/admin/account" "github.com/superseriousbusiness/gotosocial/cmd/gotosocial/action/admin/trans" - "github.com/superseriousbusiness/gotosocial/cmd/gotosocial/flag" "github.com/superseriousbusiness/gotosocial/internal/config" ) @@ -40,7 +39,7 @@ func adminCommands() *cobra.Command { Use: "account", Short: "admin commands related to accounts", } - flag.AdminAccount(adminAccountCmd, config.Defaults) + config.AddAdminAccount(adminAccountCmd) adminAccountCreateCmd := &cobra.Command{ Use: "create", @@ -52,7 +51,7 @@ func adminCommands() *cobra.Command { return run(cmd.Context(), account.Create) }, } - flag.AdminAccountCreate(adminAccountCreateCmd, config.Defaults) + config.AddAdminAccountCreate(adminAccountCreateCmd) adminAccountCmd.AddCommand(adminAccountCreateCmd) adminAccountConfirmCmd := &cobra.Command{ @@ -65,7 +64,7 @@ func adminCommands() *cobra.Command { return run(cmd.Context(), account.Confirm) }, } - flag.AdminAccount(adminAccountConfirmCmd, config.Defaults) + config.AddAdminAccount(adminAccountConfirmCmd) adminAccountCmd.AddCommand(adminAccountConfirmCmd) adminAccountPromoteCmd := &cobra.Command{ @@ -78,7 +77,7 @@ func adminCommands() *cobra.Command { return run(cmd.Context(), account.Promote) }, } - flag.AdminAccount(adminAccountPromoteCmd, config.Defaults) + config.AddAdminAccount(adminAccountPromoteCmd) adminAccountCmd.AddCommand(adminAccountPromoteCmd) adminAccountDemoteCmd := &cobra.Command{ @@ -91,7 +90,7 @@ func adminCommands() *cobra.Command { return run(cmd.Context(), account.Demote) }, } - flag.AdminAccount(adminAccountDemoteCmd, config.Defaults) + config.AddAdminAccount(adminAccountDemoteCmd) adminAccountCmd.AddCommand(adminAccountDemoteCmd) adminAccountDisableCmd := &cobra.Command{ @@ -104,7 +103,7 @@ func adminCommands() *cobra.Command { return run(cmd.Context(), account.Disable) }, } - flag.AdminAccount(adminAccountDisableCmd, config.Defaults) + config.AddAdminAccount(adminAccountDisableCmd) adminAccountCmd.AddCommand(adminAccountDisableCmd) adminAccountSuspendCmd := &cobra.Command{ @@ -117,7 +116,7 @@ func adminCommands() *cobra.Command { return run(cmd.Context(), account.Suspend) }, } - flag.AdminAccount(adminAccountSuspendCmd, config.Defaults) + config.AddAdminAccount(adminAccountSuspendCmd) adminAccountCmd.AddCommand(adminAccountSuspendCmd) adminAccountPasswordCmd := &cobra.Command{ @@ -130,7 +129,8 @@ func adminCommands() *cobra.Command { return run(cmd.Context(), account.Password) }, } - flag.AdminAccountPassword(adminAccountPasswordCmd, config.Defaults) + config.AddAdminAccount(adminAccountPasswordCmd) + config.AddAdminAccountPassword(adminAccountPasswordCmd) adminAccountCmd.AddCommand(adminAccountPasswordCmd) adminCmd.AddCommand(adminAccountCmd) @@ -149,7 +149,7 @@ func adminCommands() *cobra.Command { return run(cmd.Context(), trans.Export) }, } - flag.AdminTrans(adminExportCmd, config.Defaults) + config.AddAdminTrans(adminExportCmd) adminCmd.AddCommand(adminExportCmd) adminImportCmd := &cobra.Command{ @@ -162,7 +162,7 @@ func adminCommands() *cobra.Command { return run(cmd.Context(), trans.Import) }, } - flag.AdminTrans(adminImportCmd, config.Defaults) + config.AddAdminTrans(adminImportCmd) adminCmd.AddCommand(adminImportCmd) return adminCmd diff --git a/cmd/gotosocial/common.go b/cmd/gotosocial/common.go @@ -43,12 +43,12 @@ type preRunArgs struct { // of the config file from the viper store so that it can be picked up by either // env vars or cli flag. func preRun(a preRunArgs) error { - if err := config.InitViper(a.cmd.Flags()); err != nil { - return fmt.Errorf("error initializing viper: %s", err) + if err := config.BindFlags(a.cmd); err != nil { + return fmt.Errorf("error binding flags: %s", err) } - if err := config.ReadFromFile(); err != nil { - return fmt.Errorf("error initializing config: %s", err) + if err := config.Reload(); err != nil { + return fmt.Errorf("error reloading config: %s", err) } if !a.skipValidation { diff --git a/cmd/gotosocial/debug.go b/cmd/gotosocial/debug.go @@ -21,7 +21,6 @@ package main import ( "github.com/spf13/cobra" configaction "github.com/superseriousbusiness/gotosocial/cmd/gotosocial/action/debug/config" - "github.com/superseriousbusiness/gotosocial/cmd/gotosocial/flag" "github.com/superseriousbusiness/gotosocial/internal/config" ) @@ -41,8 +40,7 @@ func debugCommands() *cobra.Command { return run(cmd.Context(), configaction.Config) }, } - flag.Server(debugConfigCmd, config.Defaults) - + config.AddServerFlags(debugConfigCmd) debugCmd.AddCommand(debugConfigCmd) return debugCmd } diff --git a/cmd/gotosocial/flag/admin.go b/cmd/gotosocial/flag/admin.go @@ -1,62 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021-2022 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 <http://www.gnu.org/licenses/>. -*/ - -package flag - -import ( - "github.com/spf13/cobra" - "github.com/superseriousbusiness/gotosocial/internal/config" -) - -// AdminAccount attaches flags pertaining to admin account actions. -func AdminAccount(cmd *cobra.Command, values config.Values) { - cmd.Flags().String(config.Keys.AdminAccountUsername, "", usage.AdminAccountUsername) // REQUIRED - if err := cmd.MarkFlagRequired(config.Keys.AdminAccountUsername); err != nil { - panic(err) - } -} - -// AdminAccountPassword attaches flags pertaining to admin account password reset. -func AdminAccountPassword(cmd *cobra.Command, values config.Values) { - AdminAccount(cmd, values) - cmd.Flags().String(config.Keys.AdminAccountPassword, "", usage.AdminAccountPassword) // REQUIRED - if err := cmd.MarkFlagRequired(config.Keys.AdminAccountPassword); err != nil { - panic(err) - } -} - -// AdminAccountCreate attaches flags pertaining to admin account creation. -func AdminAccountCreate(cmd *cobra.Command, values config.Values) { - AdminAccount(cmd, values) - cmd.Flags().String(config.Keys.AdminAccountPassword, "", usage.AdminAccountPassword) // REQUIRED - if err := cmd.MarkFlagRequired(config.Keys.AdminAccountPassword); err != nil { - panic(err) - } - cmd.Flags().String(config.Keys.AdminAccountEmail, "", usage.AdminAccountEmail) // REQUIRED - if err := cmd.MarkFlagRequired(config.Keys.AdminAccountEmail); err != nil { - panic(err) - } -} - -// AdminTrans attaches flags pertaining to import/export commands. -func AdminTrans(cmd *cobra.Command, values config.Values) { - cmd.Flags().String(config.Keys.AdminTransPath, "", usage.AdminTransPath) // REQUIRED - if err := cmd.MarkFlagRequired(config.Keys.AdminTransPath); err != nil { - panic(err) - } -} diff --git a/cmd/gotosocial/flag/global.go b/cmd/gotosocial/flag/global.go @@ -1,46 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021-2022 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 <http://www.gnu.org/licenses/>. -*/ - -package flag - -import ( - "github.com/spf13/cobra" - "github.com/superseriousbusiness/gotosocial/internal/config" -) - -// Global attaches flags that are common to all commands, aka persistent commands. -func Global(cmd *cobra.Command, values config.Values) { - // general stuff - cmd.PersistentFlags().String(config.Keys.ApplicationName, values.ApplicationName, usage.ApplicationName) - cmd.PersistentFlags().String(config.Keys.Host, values.Host, usage.Host) - cmd.PersistentFlags().String(config.Keys.AccountDomain, values.AccountDomain, usage.AccountDomain) - cmd.PersistentFlags().String(config.Keys.Protocol, values.Protocol, usage.Protocol) - cmd.PersistentFlags().String(config.Keys.LogLevel, values.LogLevel, usage.LogLevel) - cmd.PersistentFlags().Bool(config.Keys.LogDbQueries, values.LogDbQueries, usage.LogDbQueries) - cmd.PersistentFlags().String(config.Keys.ConfigPath, values.ConfigPath, usage.ConfigPath) - - // database stuff - cmd.PersistentFlags().String(config.Keys.DbType, values.DbType, usage.DbType) - cmd.PersistentFlags().String(config.Keys.DbAddress, values.DbAddress, usage.DbAddress) - cmd.PersistentFlags().Int(config.Keys.DbPort, values.DbPort, usage.DbPort) - cmd.PersistentFlags().String(config.Keys.DbUser, values.DbUser, usage.DbUser) - cmd.PersistentFlags().String(config.Keys.DbPassword, values.DbPassword, usage.DbPassword) - cmd.PersistentFlags().String(config.Keys.DbDatabase, values.DbDatabase, usage.DbDatabase) - cmd.PersistentFlags().String(config.Keys.DbTLSMode, values.DbTLSMode, usage.DbTLSMode) - cmd.PersistentFlags().String(config.Keys.DbTLSCACert, values.DbTLSCACert, usage.DbTLSCACert) -} diff --git a/cmd/gotosocial/flag/server.go b/cmd/gotosocial/flag/server.go @@ -1,117 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021-2022 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 <http://www.gnu.org/licenses/>. -*/ - -package flag - -import ( - "github.com/spf13/cobra" - "github.com/superseriousbusiness/gotosocial/internal/config" -) - -// Server attaches all flags pertaining to running the GtS server or testrig. -func Server(cmd *cobra.Command, values config.Values) { - Template(cmd, values) - Accounts(cmd, values) - Media(cmd, values) - Storage(cmd, values) - Statuses(cmd, values) - LetsEncrypt(cmd, values) - OIDC(cmd, values) - SMTP(cmd, values) - Router(cmd, values) - Syslog(cmd, values) -} - -// Router attaches flags pertaining to the gin router. -func Router(cmd *cobra.Command, values config.Values) { - cmd.PersistentFlags().String(config.Keys.BindAddress, values.BindAddress, usage.BindAddress) - cmd.PersistentFlags().Int(config.Keys.Port, values.Port, usage.Port) - cmd.PersistentFlags().StringSlice(config.Keys.TrustedProxies, values.TrustedProxies, usage.TrustedProxies) -} - -// Template attaches flags pertaining to templating config. -func Template(cmd *cobra.Command, values config.Values) { - cmd.Flags().String(config.Keys.WebTemplateBaseDir, values.WebTemplateBaseDir, usage.WebTemplateBaseDir) - cmd.Flags().String(config.Keys.WebAssetBaseDir, values.WebAssetBaseDir, usage.WebAssetBaseDir) -} - -// Accounts attaches flags pertaining to account config. -func Accounts(cmd *cobra.Command, values config.Values) { - cmd.Flags().Bool(config.Keys.AccountsRegistrationOpen, values.AccountsRegistrationOpen, usage.AccountsRegistrationOpen) - cmd.Flags().Bool(config.Keys.AccountsApprovalRequired, values.AccountsApprovalRequired, usage.AccountsApprovalRequired) - cmd.Flags().Bool(config.Keys.AccountsReasonRequired, values.AccountsReasonRequired, usage.AccountsReasonRequired) -} - -// Media attaches flags pertaining to media config. -func Media(cmd *cobra.Command, values config.Values) { - cmd.Flags().Int(config.Keys.MediaImageMaxSize, values.MediaImageMaxSize, usage.MediaImageMaxSize) - cmd.Flags().Int(config.Keys.MediaVideoMaxSize, values.MediaVideoMaxSize, usage.MediaVideoMaxSize) - cmd.Flags().Int(config.Keys.MediaDescriptionMinChars, values.MediaDescriptionMinChars, usage.MediaDescriptionMinChars) - cmd.Flags().Int(config.Keys.MediaDescriptionMaxChars, values.MediaDescriptionMaxChars, usage.MediaDescriptionMaxChars) - cmd.Flags().Int(config.Keys.MediaRemoteCacheDays, values.MediaRemoteCacheDays, usage.MediaRemoteCacheDays) -} - -// Storage attaches flags pertaining to storage config. -func Storage(cmd *cobra.Command, values config.Values) { - cmd.Flags().String(config.Keys.StorageBackend, values.StorageBackend, usage.StorageBackend) - cmd.Flags().String(config.Keys.StorageLocalBasePath, values.StorageLocalBasePath, usage.StorageLocalBasePath) -} - -// Statuses attaches flags pertaining to statuses config. -func Statuses(cmd *cobra.Command, values config.Values) { - cmd.Flags().Int(config.Keys.StatusesMaxChars, values.StatusesMaxChars, usage.StatusesMaxChars) - cmd.Flags().Int(config.Keys.StatusesCWMaxChars, values.StatusesCWMaxChars, usage.StatusesCWMaxChars) - cmd.Flags().Int(config.Keys.StatusesPollMaxOptions, values.StatusesPollMaxOptions, usage.StatusesPollMaxOptions) - cmd.Flags().Int(config.Keys.StatusesPollOptionMaxChars, values.StatusesPollOptionMaxChars, usage.StatusesPollOptionMaxChars) - cmd.Flags().Int(config.Keys.StatusesMediaMaxFiles, values.StatusesMediaMaxFiles, usage.StatusesMediaMaxFiles) -} - -// LetsEncrypt attaches flags pertaining to letsencrypt config. -func LetsEncrypt(cmd *cobra.Command, values config.Values) { - cmd.Flags().Bool(config.Keys.LetsEncryptEnabled, values.LetsEncryptEnabled, usage.LetsEncryptEnabled) - cmd.Flags().Int(config.Keys.LetsEncryptPort, values.LetsEncryptPort, usage.LetsEncryptPort) - cmd.Flags().String(config.Keys.LetsEncryptCertDir, values.LetsEncryptCertDir, usage.LetsEncryptCertDir) - cmd.Flags().String(config.Keys.LetsEncryptEmailAddress, values.LetsEncryptEmailAddress, usage.LetsEncryptEmailAddress) -} - -// OIDC attaches flags pertaining to oidc config. -func OIDC(cmd *cobra.Command, values config.Values) { - cmd.Flags().Bool(config.Keys.OIDCEnabled, values.OIDCEnabled, usage.OIDCEnabled) - cmd.Flags().String(config.Keys.OIDCIdpName, values.OIDCIdpName, usage.OIDCIdpName) - cmd.Flags().Bool(config.Keys.OIDCSkipVerification, values.OIDCSkipVerification, usage.OIDCSkipVerification) - cmd.Flags().String(config.Keys.OIDCIssuer, values.OIDCIssuer, usage.OIDCIssuer) - cmd.Flags().String(config.Keys.OIDCClientID, values.OIDCClientID, usage.OIDCClientID) - cmd.Flags().String(config.Keys.OIDCClientSecret, values.OIDCClientSecret, usage.OIDCClientSecret) - cmd.Flags().StringSlice(config.Keys.OIDCScopes, values.OIDCScopes, usage.OIDCScopes) -} - -// SMTP attaches flags pertaining to smtp/email config. -func SMTP(cmd *cobra.Command, values config.Values) { - cmd.Flags().String(config.Keys.SMTPHost, values.SMTPHost, usage.SMTPHost) - cmd.Flags().Int(config.Keys.SMTPPort, values.SMTPPort, usage.SMTPPort) - cmd.Flags().String(config.Keys.SMTPUsername, values.SMTPUsername, usage.SMTPUsername) - cmd.Flags().String(config.Keys.SMTPPassword, values.SMTPPassword, usage.SMTPPassword) - cmd.Flags().String(config.Keys.SMTPFrom, values.SMTPFrom, usage.SMTPFrom) -} - -// Syslog attaches flags pertaining to syslog config. -func Syslog(cmd *cobra.Command, values config.Values) { - cmd.Flags().Bool(config.Keys.SyslogEnabled, values.SyslogEnabled, usage.SyslogEnabled) - cmd.Flags().String(config.Keys.SyslogProtocol, values.SyslogProtocol, usage.SyslogProtocol) - cmd.Flags().String(config.Keys.SyslogAddress, values.SyslogAddress, usage.SyslogAddress) -} diff --git a/cmd/gotosocial/flag/usage.go b/cmd/gotosocial/flag/usage.go @@ -1,82 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021-2022 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 <http://www.gnu.org/licenses/>. -*/ - -package flag - -import "github.com/superseriousbusiness/gotosocial/internal/config" - -var usage = config.KeyNames{ - LogLevel: "Log level to run at: [trace, debug, info, warn, fatal]", - LogDbQueries: "Log database queries verbosely when log-level is trace or debug", - ApplicationName: "Name of the application, used in various places internally", - ConfigPath: "Path to a file containing gotosocial configuration. Values set in this file will be overwritten by values set as env vars or arguments", - Host: "Hostname to use for the server (eg., example.org, gotosocial.whatever.com). This value must be set. DO NOT change this on a server that's already run!", - AccountDomain: "Domain to use in account names (eg., example.org, whatever.com). If not set, will default to the setting for host. DO NOT change this on a server that's already run!", - Protocol: "Protocol to use for the REST api of the server. This value must be set to one of http or https; only use http for debugging and tests!", - BindAddress: "Bind address to use for the GoToSocial server (eg., 0.0.0.0, 172.138.0.9, [::], localhost). For ipv6, enclose the address in square brackets, eg [2001:db8::fed1]. Default binds to all interfaces.", - Port: "Port to use for GoToSocial. Change this to 443 if you're running the binary directly on the host machine.", - TrustedProxies: "Proxies to trust when parsing x-forwarded headers into real IPs.", - DbType: "Database type: eg., postgres", - DbAddress: "Database ipv4 address, hostname, or filename", - DbPort: "Database port", - DbUser: "Database username", - DbPassword: "Database password", - DbDatabase: "Database name", - DbTLSMode: "Database tls mode", - DbTLSCACert: "Path to CA cert for db tls connection", - WebTemplateBaseDir: "Basedir for html templating files for rendering pages and composing emails.", - WebAssetBaseDir: "Directory to serve static assets from, accessible at example.org/assets/", - AccountsRegistrationOpen: "Allow anyone to submit an account signup request. If false, server will be invite-only.", - AccountsApprovalRequired: "Do account signups require approval by an admin or moderator before user can log in? If false, new registrations will be automatically approved.", - AccountsReasonRequired: "Do new account signups require a reason to be submitted on registration?", - MediaImageMaxSize: "Max size of accepted images in bytes", - MediaVideoMaxSize: "Max size of accepted videos in bytes", - MediaDescriptionMinChars: "Min required chars for an image description", - MediaDescriptionMaxChars: "Max permitted chars for an image description", - MediaRemoteCacheDays: "Number of days to locally cache media from remote instances. If set to 0, remote media will be kept indefinitely.", - StorageBackend: "Storage backend to use for media attachments", - StorageLocalBasePath: "Full path to an already-created directory where gts should store/retrieve media files. Subfolders will be created within this dir.", - StatusesMaxChars: "Max permitted characters for posted statuses", - StatusesCWMaxChars: "Max permitted characters for content/spoiler warnings on statuses", - StatusesPollMaxOptions: "Max amount of options permitted on a poll", - StatusesPollOptionMaxChars: "Max amount of characters for a poll option", - StatusesMediaMaxFiles: "Maximum number of media files/attachments per status", - LetsEncryptEnabled: "Enable letsencrypt TLS certs for this server. If set to true, then cert dir also needs to be set (or take the default).", - LetsEncryptPort: "Port to listen on for letsencrypt certificate challenges. Must not be the same as the GtS webserver/API port.", - LetsEncryptCertDir: "Directory to store acquired letsencrypt certificates.", - LetsEncryptEmailAddress: "Email address to use when requesting letsencrypt certs. Will receive updates on cert expiry etc.", - OIDCEnabled: "Enabled OIDC authorization for this instance. If set to true, then the other OIDC flags must also be set.", - OIDCIdpName: "Name of the OIDC identity provider. Will be shown to the user when logging in.", - OIDCSkipVerification: "Skip verification of tokens returned by the OIDC provider. Should only be set to 'true' for testing purposes, never in a production environment!", - OIDCIssuer: "Address of the OIDC issuer. Should be the web address, including protocol, at which the issuer can be reached. Eg., 'https://example.org/auth'", - OIDCClientID: "ClientID of GoToSocial, as registered with the OIDC provider.", - OIDCClientSecret: "ClientSecret of GoToSocial, as registered with the OIDC provider.", - OIDCScopes: "OIDC scopes.", - SMTPHost: "Host of the smtp server. Eg., 'smtp.eu.mailgun.org'", - SMTPPort: "Port of the smtp server. Eg., 587", - SMTPUsername: "Username to authenticate with the smtp server as. Eg., 'postmaster@mail.example.org'", - SMTPPassword: "Password to pass to the smtp server.", - SMTPFrom: "Address to use as the 'from' field of the email. Eg., 'gotosocial@example.org'", - SyslogEnabled: "Enable the syslog logging hook. Logs will be mirrored to the configured destination.", - SyslogProtocol: "Protocol to use when directing logs to syslog. Leave empty to connect to local syslog.", - SyslogAddress: "Address:port to send syslog logs to. Leave empty to connect to local syslog.", - AdminAccountUsername: "the username to create/delete/etc", - AdminAccountEmail: "the email address of this account", - AdminAccountPassword: "the password to set for this account", - AdminTransPath: "the path of the file to import from/export to", -} diff --git a/cmd/gotosocial/main.go b/cmd/gotosocial/main.go @@ -19,14 +19,12 @@ package main import ( - "fmt" "runtime/debug" + "strings" "github.com/sirupsen/logrus" "github.com/spf13/cobra" - "github.com/spf13/viper" - "github.com/superseriousbusiness/gotosocial/cmd/gotosocial/flag" _ "github.com/superseriousbusiness/gotosocial/docs" "github.com/superseriousbusiness/gotosocial/internal/config" ) @@ -37,48 +35,28 @@ var Version string //go:generate swagger generate spec func main() { - buildInfo, ok := debug.ReadBuildInfo() - if !ok { - panic("could not read buildinfo") - } - - goVersion := buildInfo.GoVersion - var commit string - var time string - for _, s := range buildInfo.Settings { - if s.Key == "vcs.revision" { - commit = s.Value[:7] - } - if s.Key == "vcs.time" { - time = s.Value - } - } - - var versionString string - if Version != "" { - versionString = fmt.Sprintf("%s %s %s [%s]", Version, commit, time, goVersion) - } + // Load version string + version := version() - // override software version in viper store - viper.Set(config.Keys.SoftwareVersion, versionString) + // override version in config store + config.SetSoftwareVersion(version) // instantiate the root command rootCmd := &cobra.Command{ - Use: "gotosocial", - Short: "GoToSocial - a fediverse social media server", - Long: "GoToSocial - a fediverse social media server\n\nFor help, see: https://docs.gotosocial.org.\n\nCode: https://github.com/superseriousbusiness/gotosocial", - Version: versionString, + Use: "gotosocial", + Short: "GoToSocial - a fediverse social media server", + Long: "GoToSocial - a fediverse social media server\n\nFor help, see: https://docs.gotosocial.org.\n\nCode: https://github.com/superseriousbusiness/gotosocial", + Version: version, + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + // before running any other cmd funcs, we must load config-path + return config.LoadEarlyFlags(cmd) + }, SilenceErrors: true, SilenceUsage: true, } // attach global flags to the root command so that they can be accessed from any subcommand - flag.Global(rootCmd, config.Defaults) - - // bind the config-path flag to viper early so that we can call it in the pre-run of following commands - if err := viper.BindPFlag(config.Keys.ConfigPath, rootCmd.PersistentFlags().Lookup(config.Keys.ConfigPath)); err != nil { - logrus.Fatalf("error attaching config flag: %s", err) - } + config.AddGlobalFlags(rootCmd) // add subcommands rootCmd.AddCommand(serverCommands()) @@ -91,3 +69,45 @@ func main() { logrus.Fatalf("error executing command: %s", err) } } + +// version will build a version string from binary's stored build information. +func version() string { + // Read build information from binary + build, ok := debug.ReadBuildInfo() + if !ok { + return "" + } + + // Define easy getter to fetch build settings + getSetting := func(key string) string { + for i := 0; i < len(build.Settings); i++ { + if build.Settings[i].Key == key { + return build.Settings[i].Value + } + } + return "" + } + + var info []string + + if Version != "" { + // Append version if set + info = append(info, Version) + } + + if vcs := getSetting("vcs"); vcs != "" { + // A VCS type was set (99.9% probably git) + + if commit := getSetting("vcs.revision"); commit != "" { + if len(commit) > 7 { + // Truncate commit + commit = commit[:7] + } + + // Append VCS + commit if set + info = append(info, vcs+"-"+commit) + } + } + + return strings.Join(info, " ") +} diff --git a/cmd/gotosocial/server.go b/cmd/gotosocial/server.go @@ -21,7 +21,6 @@ package main import ( "github.com/spf13/cobra" "github.com/superseriousbusiness/gotosocial/cmd/gotosocial/action/server" - "github.com/superseriousbusiness/gotosocial/cmd/gotosocial/flag" "github.com/superseriousbusiness/gotosocial/internal/config" ) @@ -31,7 +30,6 @@ func serverCommands() *cobra.Command { Use: "server", Short: "gotosocial server-related tasks", } - serverStartCmd := &cobra.Command{ Use: "start", Short: "start the gotosocial server", @@ -42,8 +40,7 @@ func serverCommands() *cobra.Command { return run(cmd.Context(), server.Start) }, } - flag.Server(serverStartCmd, config.Defaults) - + config.AddServerFlags(serverStartCmd) serverCmd.AddCommand(serverStartCmd) return serverCmd } diff --git a/go.mod b/go.mod @@ -32,7 +32,6 @@ require ( github.com/russross/blackfriday/v2 v2.1.0 github.com/sirupsen/logrus v1.8.1 github.com/spf13/cobra v1.4.0 - github.com/spf13/pflag v1.0.5 github.com/spf13/viper v1.11.0 github.com/stretchr/testify v1.7.1 github.com/superseriousbusiness/activity v1.1.0-gts @@ -105,6 +104,7 @@ require ( github.com/spf13/afero v1.8.2 // indirect github.com/spf13/cast v1.4.1 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect github.com/subosito/gotenv v1.2.0 // indirect github.com/superseriousbusiness/go-jpeg-image-structure/v2 v2.0.0-20220321154430-d89a106fdabe // indirect github.com/tdewolff/parse/v2 v2.5.29 // indirect diff --git a/internal/api/client/account/account_test.go b/internal/api/client/account/account_test.go @@ -8,7 +8,6 @@ import ( "codeberg.org/gruf/go-store/kv" "github.com/gin-gonic/gin" - "github.com/spf13/viper" "github.com/stretchr/testify/suite" "github.com/superseriousbusiness/gotosocial/internal/api/client/account" "github.com/superseriousbusiness/gotosocial/internal/concurrency" @@ -90,8 +89,8 @@ func (suite *AccountStandardTestSuite) newContext(recorder *httptest.ResponseRec ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"]) ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"]) - protocol := viper.GetString(config.Keys.Protocol) - host := viper.GetString(config.Keys.Host) + protocol := config.GetProtocol() + host := config.GetHost() baseURI := fmt.Sprintf("%s://%s", protocol, host) requestURI := fmt.Sprintf("%s/%s", baseURI, requestPath) diff --git a/internal/api/client/account/accountcreate.go b/internal/api/client/account/accountcreate.go @@ -24,7 +24,6 @@ import ( "net/http" "github.com/sirupsen/logrus" - "github.com/spf13/viper" "github.com/gin-gonic/gin" "github.com/superseriousbusiness/gotosocial/internal/api" @@ -123,9 +122,7 @@ func (m *Module) AccountCreatePOSTHandler(c *gin.Context) { // validateCreateAccount checks through all the necessary prerequisites for creating a new account, // according to the provided account create request. If the account isn't eligible, an error will be returned. func validateCreateAccount(form *model.AccountCreateRequest) error { - keys := config.Keys - - if !viper.GetBool(keys.AccountsRegistrationOpen) { + if !config.GetAccountsRegistrationOpen() { return errors.New("registration is not open for this server") } @@ -149,7 +146,7 @@ func validateCreateAccount(form *model.AccountCreateRequest) error { return err } - if err := validate.SignUpReason(form.Reason, viper.GetBool(keys.AccountsReasonRequired)); err != nil { + if err := validate.SignUpReason(form.Reason, config.GetAccountsReasonRequired()); err != nil { return err } diff --git a/internal/api/client/admin/admin_test.go b/internal/api/client/admin/admin_test.go @@ -26,7 +26,6 @@ import ( "codeberg.org/gruf/go-store/kv" "github.com/gin-gonic/gin" - "github.com/spf13/viper" "github.com/stretchr/testify/suite" "github.com/superseriousbusiness/gotosocial/internal/api/client/admin" "github.com/superseriousbusiness/gotosocial/internal/concurrency" @@ -108,8 +107,8 @@ func (suite *AdminStandardTestSuite) newContext(recorder *httptest.ResponseRecor ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["admin_account"]) ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["admin_account"]) - protocol := viper.GetString(config.Keys.Protocol) - host := viper.GetString(config.Keys.Host) + protocol := config.GetProtocol() + host := config.GetHost() baseURI := fmt.Sprintf("%s://%s", protocol, host) requestURI := fmt.Sprintf("%s/%s", baseURI, requestPath) diff --git a/internal/api/client/admin/mediacleanup.go b/internal/api/client/admin/mediacleanup.go @@ -24,7 +24,6 @@ import ( "github.com/gin-gonic/gin" "github.com/sirupsen/logrus" - "github.com/spf13/viper" "github.com/superseriousbusiness/gotosocial/internal/api/model" "github.com/superseriousbusiness/gotosocial/internal/config" "github.com/superseriousbusiness/gotosocial/internal/oauth" @@ -93,7 +92,7 @@ func (m *Module) MediaCleanupPOSTHandler(c *gin.Context) { var remoteCacheDays int if form.RemoteCacheDays == nil { - remoteCacheDays = viper.GetInt(config.Keys.MediaRemoteCacheDays) + remoteCacheDays = config.GetMediaRemoteCacheDays() } else { remoteCacheDays = *form.RemoteCacheDays } diff --git a/internal/api/client/auth/auth_test.go b/internal/api/client/auth/auth_test.go @@ -26,7 +26,6 @@ import ( "github.com/gin-contrib/sessions" "github.com/gin-contrib/sessions/memstore" "github.com/gin-gonic/gin" - "github.com/spf13/viper" "github.com/stretchr/testify/suite" "github.com/superseriousbusiness/gotosocial/internal/api/client/auth" "github.com/superseriousbusiness/gotosocial/internal/config" @@ -96,8 +95,8 @@ func (suite *AuthStandardTestSuite) newContext(requestMethod string, requestPath testrig.ConfigureTemplatesWithGin(engine) // create the request - protocol := viper.GetString(config.Keys.Protocol) - host := viper.GetString(config.Keys.Host) + protocol := config.GetProtocol() + host := config.GetHost() baseURI := fmt.Sprintf("%s://%s", protocol, host) requestURI := fmt.Sprintf("%s/%s", baseURI, requestPath) ctx.Request = httptest.NewRequest(requestMethod, requestURI, nil) // the endpoint we're hitting diff --git a/internal/api/client/followrequest/followrequest_test.go b/internal/api/client/followrequest/followrequest_test.go @@ -25,7 +25,6 @@ import ( "codeberg.org/gruf/go-store/kv" "github.com/gin-gonic/gin" - "github.com/spf13/viper" "github.com/stretchr/testify/suite" "github.com/superseriousbusiness/gotosocial/internal/api/client/followrequest" "github.com/superseriousbusiness/gotosocial/internal/concurrency" @@ -104,8 +103,8 @@ func (suite *FollowRequestStandardTestSuite) newContext(recorder *httptest.Respo ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"]) ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"]) - protocol := viper.GetString(config.Keys.Protocol) - host := viper.GetString(config.Keys.Host) + protocol := config.GetProtocol() + host := config.GetHost() baseURI := fmt.Sprintf("%s://%s", protocol, host) requestURI := fmt.Sprintf("%s/%s", baseURI, requestPath) diff --git a/internal/api/client/instance/instance_test.go b/internal/api/client/instance/instance_test.go @@ -26,7 +26,6 @@ import ( "codeberg.org/gruf/go-store/kv" "github.com/gin-gonic/gin" - "github.com/spf13/viper" "github.com/stretchr/testify/suite" "github.com/superseriousbusiness/gotosocial/internal/api/client/instance" "github.com/superseriousbusiness/gotosocial/internal/concurrency" @@ -108,8 +107,8 @@ func (suite *InstanceStandardTestSuite) newContext(recorder *httptest.ResponseRe ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["admin_account"]) ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["admin_account"]) - protocol := viper.GetString(config.Keys.Protocol) - host := viper.GetString(config.Keys.Host) + protocol := config.GetProtocol() + host := config.GetHost() baseURI := fmt.Sprintf("%s://%s", protocol, host) requestURI := fmt.Sprintf("%s/%s", baseURI, requestPath) diff --git a/internal/api/client/instance/instanceget.go b/internal/api/client/instance/instanceget.go @@ -4,7 +4,6 @@ import ( "net/http" "github.com/sirupsen/logrus" - "github.com/spf13/viper" "github.com/superseriousbusiness/gotosocial/internal/api" "github.com/superseriousbusiness/gotosocial/internal/config" @@ -41,7 +40,7 @@ func (m *Module) InstanceInformationGETHandler(c *gin.Context) { return } - host := viper.GetString(config.Keys.Host) + host := config.GetHost() instance, err := m.processor.InstanceGet(c.Request.Context(), host) if err != nil { diff --git a/internal/api/client/media/mediacreate.go b/internal/api/client/media/mediacreate.go @@ -24,7 +24,6 @@ import ( "net/http" "github.com/sirupsen/logrus" - "github.com/spf13/viper" "github.com/gin-gonic/gin" "github.com/superseriousbusiness/gotosocial/internal/api" @@ -133,11 +132,10 @@ func validateCreateMedia(form *model.AttachmentRequest) error { return errors.New("no attachment given") } - keys := config.Keys - maxVideoSize := viper.GetInt(keys.MediaVideoMaxSize) - maxImageSize := viper.GetInt(keys.MediaImageMaxSize) - minDescriptionChars := viper.GetInt(keys.MediaDescriptionMinChars) - maxDescriptionChars := viper.GetInt(keys.MediaDescriptionMaxChars) + maxVideoSize := config.GetMediaVideoMaxSize() + maxImageSize := config.GetMediaImageMaxSize() + minDescriptionChars := config.GetMediaDescriptionMinChars() + maxDescriptionChars := config.GetMediaDescriptionMaxChars() // a very superficial check to see if no size limits are exceeded // we still don't actually know which media types we're dealing with but the other handlers will go into more detail there diff --git a/internal/api/client/media/mediacreate_test.go b/internal/api/client/media/mediacreate_test.go @@ -33,7 +33,6 @@ import ( "codeberg.org/gruf/go-store/kv" "github.com/gin-gonic/gin" "github.com/sirupsen/logrus" - "github.com/spf13/viper" "github.com/stretchr/testify/suite" mediamodule "github.com/superseriousbusiness/gotosocial/internal/api/client/media" "github.com/superseriousbusiness/gotosocial/internal/api/model" @@ -261,7 +260,7 @@ func (suite *MediaCreateTestSuite) TestMediaCreateLongDescription() { func (suite *MediaCreateTestSuite) TestMediaCreateTooShortDescription() { // set the min description length - viper.Set(config.Keys.MediaDescriptionMinChars, 500) + config.SetMediaDescriptionMinChars(500) // set up the context for the request t := suite.testTokens["local_account_1"] diff --git a/internal/api/client/media/mediaupdate.go b/internal/api/client/media/mediaupdate.go @@ -24,7 +24,6 @@ import ( "net/http" "github.com/sirupsen/logrus" - "github.com/spf13/viper" "github.com/gin-gonic/gin" "github.com/superseriousbusiness/gotosocial/internal/api" @@ -141,9 +140,8 @@ func (m *Module) MediaPUTHandler(c *gin.Context) { } func validateUpdateMedia(form *model.AttachmentUpdateRequest) error { - keys := config.Keys - minDescriptionChars := viper.GetInt(keys.MediaDescriptionMinChars) - maxDescriptionChars := viper.GetInt(keys.MediaDescriptionMaxChars) + minDescriptionChars := config.GetMediaDescriptionMinChars() + maxDescriptionChars := config.GetMediaDescriptionMaxChars() if form.Description != nil { if len(*form.Description) < minDescriptionChars || len(*form.Description) > maxDescriptionChars { diff --git a/internal/api/client/media/mediaupdate_test.go b/internal/api/client/media/mediaupdate_test.go @@ -31,7 +31,6 @@ import ( "codeberg.org/gruf/go-store/kv" "github.com/gin-gonic/gin" "github.com/sirupsen/logrus" - "github.com/spf13/viper" "github.com/stretchr/testify/suite" mediamodule "github.com/superseriousbusiness/gotosocial/internal/api/client/media" "github.com/superseriousbusiness/gotosocial/internal/api/model" @@ -188,7 +187,7 @@ func (suite *MediaUpdateTestSuite) TestUpdateImage() { func (suite *MediaUpdateTestSuite) TestUpdateImageShortDescription() { // set the min description length - viper.Set(config.Keys.MediaDescriptionMinChars, 50) + config.SetMediaDescriptionMinChars(50) toUpdate := suite.testAttachments["local_account_1_unattached_1"] diff --git a/internal/api/client/status/statuscreate.go b/internal/api/client/status/statuscreate.go @@ -24,7 +24,6 @@ import ( "net/http" "github.com/sirupsen/logrus" - "github.com/spf13/viper" "github.com/gin-gonic/gin" "github.com/superseriousbusiness/gotosocial/internal/api" @@ -130,12 +129,11 @@ func validateCreateStatus(form *model.AdvancedStatusCreateForm) error { return errors.New("can't post media + poll in same status") } - keys := config.Keys - maxChars := viper.GetInt(keys.StatusesMaxChars) - maxMediaFiles := viper.GetInt(keys.StatusesMediaMaxFiles) - maxPollOptions := viper.GetInt(keys.StatusesPollMaxOptions) - maxPollChars := viper.GetInt(keys.StatusesPollOptionMaxChars) - maxCwChars := viper.GetInt(keys.StatusesCWMaxChars) + maxChars := config.GetStatusesMaxChars() + maxMediaFiles := config.GetStatusesMediaMaxFiles() + maxPollOptions := config.GetStatusesPollMaxOptions() + maxPollChars := config.GetStatusesPollOptionMaxChars() + maxCwChars := config.GetStatusesCWMaxChars() // validate status if form.Status != "" { diff --git a/internal/api/s2s/webfinger/webfingerget.go b/internal/api/s2s/webfinger/webfingerget.go @@ -26,7 +26,6 @@ import ( "github.com/gin-gonic/gin" "github.com/sirupsen/logrus" - "github.com/spf13/viper" "github.com/superseriousbusiness/gotosocial/internal/ap" "github.com/superseriousbusiness/gotosocial/internal/config" ) @@ -90,8 +89,8 @@ func (m *Module) WebfingerGETRequest(c *gin.Context) { return } - accountDomain := viper.GetString(config.Keys.AccountDomain) - host := viper.GetString(config.Keys.Host) + accountDomain := config.GetAccountDomain() + host := config.GetHost() if requestedAccountDomain != accountDomain && requestedAccountDomain != host { l.Debugf("aborting request because accountDomain %s does not belong to this instance", requestedAccountDomain) diff --git a/internal/api/s2s/webfinger/webfingerget_test.go b/internal/api/s2s/webfinger/webfingerget_test.go @@ -27,7 +27,6 @@ import ( "testing" "github.com/gin-gonic/gin" - "github.com/spf13/viper" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/suite" "github.com/superseriousbusiness/gotosocial/internal/api/s2s/webfinger" @@ -46,7 +45,7 @@ func (suite *WebfingerGetTestSuite) TestFingerUser() { targetAccount := suite.testAccounts["local_account_1"] // setup request - host := viper.GetString(config.Keys.Host) + host := config.GetHost() requestPath := fmt.Sprintf("/%s?resource=acct:%s@%s", webfinger.WebfingerBasePath, targetAccount.Username, host) recorder := httptest.NewRecorder() @@ -69,8 +68,9 @@ func (suite *WebfingerGetTestSuite) TestFingerUser() { } func (suite *WebfingerGetTestSuite) TestFingerUserWithDifferentAccountDomainByHost() { - viper.Set(config.Keys.Host, "gts.example.org") - viper.Set(config.Keys.AccountDomain, "example.org") + config.SetHost("gts.example.org") + config.SetAccountDomain("example.org") + clientWorker := concurrency.NewWorkerPool[messages.FromClientAPI](-1, -1) fedWorker := concurrency.NewWorkerPool[messages.FromFederator](-1, -1) suite.processor = processing.NewProcessor(suite.tc, suite.federator, testrig.NewTestOauthServer(suite.db), testrig.NewTestMediaManager(suite.db, suite.storage), suite.storage, suite.db, suite.emailSender, clientWorker, fedWorker) @@ -82,7 +82,7 @@ func (suite *WebfingerGetTestSuite) TestFingerUserWithDifferentAccountDomainByHo } // setup request - host := viper.GetString(config.Keys.Host) + host := config.GetHost() requestPath := fmt.Sprintf("/%s?resource=acct:%s@%s", webfinger.WebfingerBasePath, targetAccount.Username, host) recorder := httptest.NewRecorder() @@ -105,8 +105,9 @@ func (suite *WebfingerGetTestSuite) TestFingerUserWithDifferentAccountDomainByHo } func (suite *WebfingerGetTestSuite) TestFingerUserWithDifferentAccountDomainByAccountDomain() { - viper.Set(config.Keys.Host, "gts.example.org") - viper.Set(config.Keys.AccountDomain, "example.org") + config.SetHost("gts.example.org") + config.SetAccountDomain("example.org") + clientWorker := concurrency.NewWorkerPool[messages.FromClientAPI](-1, -1) fedWorker := concurrency.NewWorkerPool[messages.FromFederator](-1, -1) suite.processor = processing.NewProcessor(suite.tc, suite.federator, testrig.NewTestOauthServer(suite.db), testrig.NewTestMediaManager(suite.db, suite.storage), suite.storage, suite.db, suite.emailSender, clientWorker, fedWorker) @@ -118,7 +119,7 @@ func (suite *WebfingerGetTestSuite) TestFingerUserWithDifferentAccountDomainByAc } // setup request - accountDomain := viper.GetString(config.Keys.AccountDomain) + accountDomain := config.GetAccountDomain() requestPath := fmt.Sprintf("/%s?resource=acct:%s@%s", webfinger.WebfingerBasePath, targetAccount.Username, accountDomain) recorder := httptest.NewRecorder() @@ -144,7 +145,7 @@ func (suite *WebfingerGetTestSuite) TestFingerUserWithoutAcct() { targetAccount := suite.testAccounts["local_account_1"] // setup request -- leave out the 'acct:' prefix, which is prettymuch what pixelfed currently does - host := viper.GetString(config.Keys.Host) + host := config.GetHost() requestPath := fmt.Sprintf("/%s?resource=%s@%s", webfinger.WebfingerBasePath, targetAccount.Username, host) recorder := httptest.NewRecorder() diff --git a/internal/config/config.go b/internal/config/config.go @@ -0,0 +1,139 @@ +/* + GoToSocial + Copyright (C) 2021-2022 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 <http://www.gnu.org/licenses/>. +*/ + +package config + +import ( + "reflect" + + "github.com/mitchellh/mapstructure" +) + +// cfgtype is the reflected type information of Configuration{}. +var cfgtype = reflect.TypeOf(Configuration{}) + +// fieldtag will fetch the string value for the given tag name +// on the given field name in the Configuration{} struct. +func fieldtag(field, tag string) string { + sfield, ok := cfgtype.FieldByName(field) + if !ok { + panic("unknown struct field") + } + return sfield.Tag.Get(tag) +} + +// Configuration represents global GTS server runtime configuration. +// +// Please note that if you update this struct's fields or tags, you +// will need to regenerate the global Getter/Setter helpers by running: +// `go run ./internal/config/gen/ -out ./internal/config/helpers.gen.go` +type Configuration struct { + LogLevel string `name:"log-level" usage:"Log level to run at: [trace, debug, info, warn, fatal]"` + LogDbQueries bool `name:"log-db-queries" usage:"Log database queries verbosely when log-level is trace or debug"` + ApplicationName string `name:"application-name" usage:"Name of the application, used in various places internally"` + ConfigPath string `name:"config-path" usage:"Path to a file containing gotosocial configuration. Values set in this file will be overwritten by values set as env vars or arguments"` + Host string `name:"host" usage:"Hostname to use for the server (eg., example.org, gotosocial.whatever.com). DO NOT change this on a server that's already run!"` + AccountDomain string `name:"account-domain" usage:"Domain to use in account names (eg., example.org, whatever.com). If not set, will default to the setting for host. DO NOT change this on a server that's already run!"` + Protocol string `name:"protocol" usage:"Protocol to use for the REST api of the server (only use http if you are debugging or behind a reverse proxy!)"` + BindAddress string `name:"bind-address" usage:"Bind address to use for the GoToSocial server (eg., 0.0.0.0, 172.138.0.9, [::], localhost). For ipv6, enclose the address in square brackets, eg [2001:db8::fed1]. Default binds to all interfaces."` + Port int `name:"port" usage:"Port to use for GoToSocial. Change this to 443 if you're running the binary directly on the host machine."` + TrustedProxies []string `name:"trusted-proxies" usage:"Proxies to trust when parsing x-forwarded headers into real IPs."` + SoftwareVersion string `name:"software-version" usage:""` + + DbType string `name:"db-type" usage:"Database type: eg., postgres"` + DbAddress string `name:"db-address" usage:"Database ipv4 address, hostname, or filename"` + DbPort int `name:"db-port" usage:"Database port"` + DbUser string `name:"db-user" usage:"Database username"` + DbPassword string `name:"db-password" usage:"Database password"` + DbDatabase string `name:"db-database" usage:"Database name"` + DbTLSMode string `name:"db-tls-mode" usage:"Database tls mode"` + DbTLSCACert string `name:"db-tls-ca-cert" usage:"Path to CA cert for db tls connection"` + + 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/"` + + 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."` + AccountsReasonRequired bool `name:"accounts-reason-required" usage:"Do new account signups require a reason to be submitted on registration?"` + + MediaImageMaxSize int `name:"media-image-max-size" usage:"Max size of accepted images in bytes"` + MediaVideoMaxSize int `name:"media-video-max-size" usage:"Max size of accepted videos in bytes"` + MediaDescriptionMinChars int `name:"media-description-min-chars" usage:"Min required chars for an image description"` + MediaDescriptionMaxChars int `name:"media-description-max-chars" usage:"Max permitted chars for an image description"` + MediaRemoteCacheDays int `name:"media-remote-cache-days" usage:"Number of days to locally cache media from remote instances. If set to 0, remote media will be kept indefinitely."` + + StorageBackend string `name:"storage-backend" usage:"Storage backend to use for media attachments"` + StorageLocalBasePath string `name:"storage-local-base-path" usage:"Full path to an already-created directory where gts should store/retrieve media files. Subfolders will be created within this dir."` + + StatusesMaxChars int `name:"statuses-max-chars" usage:"Max permitted characters for posted statuses"` + StatusesCWMaxChars int `name:"statuses-cw-max-chars" usage:"Max permitted characters for content/spoiler warnings on statuses"` + StatusesPollMaxOptions int `name:"statuses-poll-max-options" usage:"Max amount of options permitted on a poll"` + StatusesPollOptionMaxChars int `name:"statuses-poll-option-max-chars" usage:"Max amount of characters for a poll option"` + StatusesMediaMaxFiles int `name:"statuses-media-max-files" usage:"Maximum number of media files/attachments per status"` + + LetsEncryptEnabled bool `name:"letsencrypt-enabled" usage:"Enable letsencrypt TLS certs for this server. If set to true, then cert dir also needs to be set (or take the default)."` + LetsEncryptPort int `name:"letsencrypt-port" usage:"Port to listen on for letsencrypt certificate challenges. Must not be the same as the GtS webserver/API port."` + LetsEncryptCertDir string `name:"letsencrypt-cert-dir" usage:"Directory to store acquired letsencrypt certificates."` + LetsEncryptEmailAddress string `name:"letsencrypt-email-address" usage:"Email address to use when requesting letsencrypt certs. Will receive updates on cert expiry etc."` + + OIDCEnabled bool `name:"oidc-enabled" usage:"Enabled OIDC authorization for this instance. If set to true, then the other OIDC flags must also be set."` + OIDCIdpName string `name:"oidc-idp-name" usage:"Name of the OIDC identity provider. Will be shown to the user when logging in."` + OIDCSkipVerification bool `name:"oidc-skip-verification" usage:"Skip verification of tokens returned by the OIDC provider. Should only be set to 'true' for testing purposes, never in a production environment!"` + OIDCIssuer string `name:"oidc-issuer" usage:"Address of the OIDC issuer. Should be the web address, including protocol, at which the issuer can be reached. Eg., 'https://example.org/auth'"` + OIDCClientID string `name:"oidc-client-id" usage:"ClientID of GoToSocial, as registered with the OIDC provider."` + OIDCClientSecret string `name:"oidc-client-secret" usage:"ClientSecret of GoToSocial, as registered with the OIDC provider."` + OIDCScopes []string `name:"oidc-scopes" usage:"OIDC scopes."` + + SMTPHost string `name:"smtp-host" usage:"Host of the smtp server. Eg., 'smtp.eu.mailgun.org'"` + SMTPPort int `name:"smtp-port" usage:"Port of the smtp server. Eg., 587"` + SMTPUsername string `name:"smtp-username" usage:"Username to authenticate with the smtp server as. Eg., 'postmaster@mail.example.org'"` + SMTPPassword string `name:"smtp-password" usage:"Password to pass to the smtp server."` + SMTPFrom string `name:"smtp-from" usage:"Address to use as the 'from' field of the email. Eg., 'gotosocial@example.org'"` + + SyslogEnabled bool `name:"syslog-enabled" usage:"Enable the syslog logging hook. Logs will be mirrored to the configured destination."` + SyslogProtocol string `name:"syslog-protocol" usage:"Protocol to use when directing logs to syslog. Leave empty to connect to local syslog."` + SyslogAddress string `name:"syslog-address" usage:"Address:port to send syslog logs to. Leave empty to connect to local syslog."` + + // TODO: move these elsewhere, these are more ephemeral vs long-running flags like above + AdminAccountUsername string `name:"username" usage:"the username to create/delete/etc"` + AdminAccountEmail string `name:"email" usage:"the email address of this account"` + AdminAccountPassword string `name:"password" usage:"the password to set for this account"` + AdminTransPath string `name:"path" usage:"the path of the file to import from/export to"` +} + +// MarshalMap will marshal current Configuration into a map structure (useful for JSON). +func (cfg *Configuration) MarshalMap() (map[string]interface{}, error) { + var dst map[string]interface{} + dec, _ := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ + TagName: "name", + Result: &dst, + }) + if err := dec.Decode(cfg); err != nil { + return nil, err + } + return dst, nil +} + +// UnmarshalMap will unmarshal a map structure into the receiving Configuration. +func (cfg *Configuration) UnmarshalMap(src map[string]interface{}) error { + dec, _ := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ + TagName: "name", + Result: cfg, + }) + return dec.Decode(src) +} diff --git a/internal/config/defaults.go b/internal/config/defaults.go @@ -20,9 +20,9 @@ package config import "github.com/coreos/go-oidc/v3/oidc" -// Defaults returns a populated Values struct with most of the values set to reasonable defaults. -// Note that if you use this, you still need to set Host and, if desired, ConfigPath. -var Defaults = Values{ +// Defaults contains a populated Configuration with reasonable defaults. Note that +// if you use this, you will still need to set Host, and, if desired, ConfigPath. +var Defaults = Configuration{ LogLevel: "info", LogDbQueries: false, ApplicationName: "gotosocial", diff --git a/internal/config/file.go b/internal/config/file.go @@ -1,38 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021-2022 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 <http://www.gnu.org/licenses/>. -*/ - -package config - -import ( - "github.com/spf13/viper" -) - -// ReadFromFile checks if there's already a path to the config file set in viper. -// If there is, it will attempt to read the config file into viper. -func ReadFromFile() error { - // config file stuff - // check if we have a config path set (either by cli arg or env var) - if configPath := viper.GetString(Keys.ConfigPath); configPath != "" { - viper.SetConfigFile(configPath) - if err := viper.ReadInConfig(); err != nil { - return err - } - } - - return nil -} diff --git a/internal/config/flags.go b/internal/config/flags.go @@ -0,0 +1,157 @@ +/* + GoToSocial + Copyright (C) 2021-2022 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 <http://www.gnu.org/licenses/>. +*/ + +package config + +import ( + "github.com/spf13/cobra" +) + +// TODO: consolidate these methods into the Configuration{} or ConfigState{} structs. + +// AddGlobalFlags will attach global configuration flags to given cobra command, loading defaults from global config. +func AddGlobalFlags(cmd *cobra.Command) { + Config(func(cfg *Configuration) { + // General + cmd.PersistentFlags().String(ApplicationNameFlag(), cfg.ApplicationName, fieldtag("ApplicationName", "usage")) + cmd.PersistentFlags().String(HostFlag(), cfg.Host, fieldtag("Host", "usage")) + cmd.PersistentFlags().String(AccountDomainFlag(), cfg.AccountDomain, fieldtag("AccountDomain", "usage")) + cmd.PersistentFlags().String(ProtocolFlag(), cfg.Protocol, fieldtag("Protocol", "usage")) + cmd.PersistentFlags().String(LogLevelFlag(), cfg.LogLevel, fieldtag("LogLevel", "usage")) + cmd.PersistentFlags().Bool(LogDbQueriesFlag(), cfg.LogDbQueries, fieldtag("LogDbQueries", "usage")) + cmd.PersistentFlags().String(ConfigPathFlag(), cfg.ConfigPath, fieldtag("ConfigPath", "usage")) + + // Database + cmd.PersistentFlags().String(DbTypeFlag(), cfg.DbType, fieldtag("DbType", "usage")) + cmd.PersistentFlags().String(DbAddressFlag(), cfg.DbAddress, fieldtag("DbAddress", "usage")) + cmd.PersistentFlags().Int(DbPortFlag(), cfg.DbPort, fieldtag("DbPort", "usage")) + cmd.PersistentFlags().String(DbUserFlag(), cfg.DbUser, fieldtag("DbUser", "usage")) + cmd.PersistentFlags().String(DbPasswordFlag(), cfg.DbPassword, fieldtag("DbPassword", "usage")) + cmd.PersistentFlags().String(DbDatabaseFlag(), cfg.DbDatabase, fieldtag("DbDatabase", "usage")) + cmd.PersistentFlags().String(DbTLSModeFlag(), cfg.DbTLSMode, fieldtag("DbTLSMode", "usage")) + cmd.PersistentFlags().String(DbTLSCACertFlag(), cfg.DbTLSCACert, fieldtag("DbTLSCACert", "usage")) + }) +} + +// AddServerFlags will attach server configuration flags to given cobra command, loading defaults from global config. +func AddServerFlags(cmd *cobra.Command) { + Config(func(cfg *Configuration) { + // Router + cmd.PersistentFlags().String(BindAddressFlag(), cfg.BindAddress, fieldtag("BindAddress", "usage")) + cmd.PersistentFlags().Int(PortFlag(), cfg.Port, fieldtag("Port", "usage")) + cmd.PersistentFlags().StringSlice(TrustedProxiesFlag(), cfg.TrustedProxies, fieldtag("TrustedProxies", "usage")) + + // Template + cmd.Flags().String(WebTemplateBaseDirFlag(), cfg.WebTemplateBaseDir, fieldtag("WebTemplateBaseDir", "usage")) + cmd.Flags().String(WebAssetBaseDirFlag(), cfg.WebAssetBaseDir, fieldtag("WebAssetBaseDir", "usage")) + + // Accounts + cmd.Flags().Bool(AccountsRegistrationOpenFlag(), cfg.AccountsRegistrationOpen, fieldtag("AccountsRegistrationOpen", "usage")) + cmd.Flags().Bool(AccountsApprovalRequiredFlag(), cfg.AccountsApprovalRequired, fieldtag("AccountsApprovalRequired", "usage")) + cmd.Flags().Bool(AccountsReasonRequiredFlag(), cfg.AccountsReasonRequired, fieldtag("AccountsReasonRequired", "usage")) + + // Media + cmd.Flags().Int(MediaImageMaxSizeFlag(), cfg.MediaImageMaxSize, fieldtag("MediaImageMaxSize", "usage")) + cmd.Flags().Int(MediaVideoMaxSizeFlag(), cfg.MediaVideoMaxSize, fieldtag("MediaVideoMaxSize", "usage")) + cmd.Flags().Int(MediaDescriptionMinCharsFlag(), cfg.MediaDescriptionMinChars, fieldtag("MediaDescriptionMinChars", "usage")) + cmd.Flags().Int(MediaDescriptionMaxCharsFlag(), cfg.MediaDescriptionMaxChars, fieldtag("MediaDescriptionMaxChars", "usage")) + cmd.Flags().Int(MediaRemoteCacheDaysFlag(), cfg.MediaRemoteCacheDays, fieldtag("MediaRemoteCacheDays", "usage")) + + // Storage + cmd.Flags().String(StorageBackendFlag(), cfg.StorageBackend, fieldtag("StorageBackend", "usage")) + cmd.Flags().String(StorageLocalBasePathFlag(), cfg.StorageLocalBasePath, fieldtag("StorageLocalBasePath", "usage")) + + // Statuses + cmd.Flags().Int(StatusesMaxCharsFlag(), cfg.StatusesMaxChars, fieldtag("StatusesMaxChars", "usage")) + cmd.Flags().Int(StatusesCWMaxCharsFlag(), cfg.StatusesCWMaxChars, fieldtag("StatusesCWMaxChars", "usage")) + cmd.Flags().Int(StatusesPollMaxOptionsFlag(), cfg.StatusesPollMaxOptions, fieldtag("StatusesPollMaxOptions", "usage")) + cmd.Flags().Int(StatusesPollOptionMaxCharsFlag(), cfg.StatusesPollOptionMaxChars, fieldtag("StatusesPollOptionMaxChars", "usage")) + cmd.Flags().Int(StatusesMediaMaxFilesFlag(), cfg.StatusesMediaMaxFiles, fieldtag("StatusesMediaMaxFiles", "usage")) + + // LetsEncrypt + cmd.Flags().Bool(LetsEncryptEnabledFlag(), cfg.LetsEncryptEnabled, fieldtag("LetsEncryptEnabled", "usage")) + cmd.Flags().Int(LetsEncryptPortFlag(), cfg.LetsEncryptPort, fieldtag("LetsEncryptPort", "usage")) + cmd.Flags().String(LetsEncryptCertDirFlag(), cfg.LetsEncryptCertDir, fieldtag("LetsEncryptCertDir", "usage")) + cmd.Flags().String(LetsEncryptEmailAddressFlag(), cfg.LetsEncryptEmailAddress, fieldtag("LetsEncryptEmailAddress", "usage")) + + // OIDC + cmd.Flags().Bool(OIDCEnabledFlag(), cfg.OIDCEnabled, fieldtag("OIDCEnabled", "usage")) + cmd.Flags().String(OIDCIdpNameFlag(), cfg.OIDCIdpName, fieldtag("OIDCIdpName", "usage")) + cmd.Flags().Bool(OIDCSkipVerificationFlag(), cfg.OIDCSkipVerification, fieldtag("OIDCSkipVerification", "usage")) + cmd.Flags().String(OIDCIssuerFlag(), cfg.OIDCIssuer, fieldtag("OIDCIssuer", "usage")) + cmd.Flags().String(OIDCClientIDFlag(), cfg.OIDCClientID, fieldtag("OIDCClientID", "usage")) + cmd.Flags().String(OIDCClientSecretFlag(), cfg.OIDCClientSecret, fieldtag("OIDCClientSecret", "usage")) + cmd.Flags().StringSlice(OIDCScopesFlag(), cfg.OIDCScopes, fieldtag("OIDCScopes", "usage")) + + // SMTP + cmd.Flags().String(SMTPHostFlag(), cfg.SMTPHost, fieldtag("SMTPHost", "usage")) + cmd.Flags().Int(SMTPPortFlag(), cfg.SMTPPort, fieldtag("SMTPPort", "usage")) + cmd.Flags().String(SMTPUsernameFlag(), cfg.SMTPUsername, fieldtag("SMTPUsername", "usage")) + cmd.Flags().String(SMTPPasswordFlag(), cfg.SMTPPassword, fieldtag("SMTPPassword", "usage")) + cmd.Flags().String(SMTPFromFlag(), cfg.SMTPFrom, fieldtag("SMTPFrom", "usage")) + + // Syslog + cmd.Flags().Bool(SyslogEnabledFlag(), cfg.SyslogEnabled, fieldtag("SyslogEnabled", "usage")) + cmd.Flags().String(SyslogProtocolFlag(), cfg.SyslogProtocol, fieldtag("SyslogProtocol", "usage")) + cmd.Flags().String(SyslogAddressFlag(), cfg.SyslogAddress, fieldtag("SyslogAddress", "usage")) + }) +} + +// AddAdminAccount attaches flags pertaining to admin account actions. +func AddAdminAccount(cmd *cobra.Command) { + name := AdminAccountUsernameFlag() + usage := fieldtag("AdminAccountUsername", "usage") + cmd.Flags().String(name, "", usage) // REQUIRED + if err := cmd.MarkFlagRequired(name); err != nil { + panic(err) + } +} + +// AddAdminAccountPassword attaches flags pertaining to admin account password reset. +func AddAdminAccountPassword(cmd *cobra.Command) { + name := AdminAccountPasswordFlag() + usage := fieldtag("AdminAccountPassword", "usage") + cmd.Flags().String(name, "", usage) // REQUIRED + if err := cmd.MarkFlagRequired(name); err != nil { + panic(err) + } +} + +// AddAdminAccountCreate attaches flags pertaining to admin account creation. +func AddAdminAccountCreate(cmd *cobra.Command) { + // Requires both account and password + AddAdminAccount(cmd) + AddAdminAccountPassword(cmd) + + name := AdminAccountEmailFlag() + usage := fieldtag("AdminAccountEmail", "usage") + cmd.Flags().String(name, "", usage) // REQUIRED + if err := cmd.MarkFlagRequired(name); err != nil { + panic(err) + } +} + +// AddAdminTrans attaches flags pertaining to import/export commands. +func AddAdminTrans(cmd *cobra.Command) { + name := AdminTransPathFlag() + usage := fieldtag("AdminTransPath", "usage") + cmd.Flags().String(name, "", usage) // REQUIRED + if err := cmd.MarkFlagRequired(name); err != nil { + panic(err) + } +} diff --git a/internal/config/gen/gen.go b/internal/config/gen/gen.go @@ -0,0 +1,112 @@ +/* + GoToSocial + Copyright (C) 2021-2022 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 <http://www.gnu.org/licenses/>. +*/ + +package main + +import ( + "flag" + "fmt" + "os" + "os/exec" + "reflect" + + "github.com/superseriousbusiness/gotosocial/internal/config" +) + +const license = `/* + GoToSocial + Copyright (C) 2021-2022 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 <http://www.gnu.org/licenses/>. +*/ +` + +func main() { + var ( + out string + gen string + ) + + // Load runtime config flags + flag.StringVar(&out, "out", "", "Generated file output path") + flag.StringVar(&gen, "gen", "helpers", "Type of file to generate (helpers)") + flag.Parse() + + // Open output file path + output, err := os.OpenFile(out, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0o644) + if err != nil { + panic(err) + } + + switch gen { + // Generate config field helper methods + case "helpers": + fmt.Fprint(output, "// THIS IS A GENERATED FILE, DO NOT EDIT BY HAND\n") + fmt.Fprint(output, license) + fmt.Fprint(output, "package config\n\n") + t := reflect.TypeOf(config.Configuration{}) + for i := 0; i < t.NumField(); i++ { + field := t.Field(i) + + // ConfigState structure helper methods + fmt.Fprintf(output, "// Get%s safely fetches the Configuration value for state's '%s' field\n", field.Name, field.Name) + fmt.Fprintf(output, "func (st *ConfigState) Get%s() (v %s) {\n", field.Name, field.Type.String()) + fmt.Fprintf(output, "\tst.mutex.Lock()\n") + fmt.Fprintf(output, "\tv = st.config.%s\n", field.Name) + fmt.Fprintf(output, "\tst.mutex.Unlock()\n") + fmt.Fprintf(output, "\treturn\n") + fmt.Fprintf(output, "}\n\n") + fmt.Fprintf(output, "// Set%s safely sets the Configuration value for state's '%s' field\n", field.Name, field.Name) + fmt.Fprintf(output, "func (st *ConfigState) Set%s(v %s) {\n", field.Name, field.Type.String()) + fmt.Fprintf(output, "\tst.mutex.Lock()\n") + fmt.Fprintf(output, "\tdefer st.mutex.Unlock()\n") + fmt.Fprintf(output, "\tst.config.%s = v\n", field.Name) + fmt.Fprintf(output, "\tst.reloadToViper()\n") + fmt.Fprintf(output, "}\n\n") + + // Global ConfigState helper methods + // TODO: remove when we pass around a ConfigState{} + fmt.Fprintf(output, "// %sFlag returns the flag name for the '%s' field\n", field.Name, field.Name) + fmt.Fprintf(output, "func %sFlag() string { return \"%s\" }\n\n", field.Name, field.Tag.Get("name")) + fmt.Fprintf(output, "// Get%s safely fetches the value for global configuration '%s' field\n", field.Name, field.Name) + fmt.Fprintf(output, "func Get%[1]s() %[2]s { return global.Get%[1]s() }\n\n", field.Name, field.Type.String()) + fmt.Fprintf(output, "// Set%s safely sets the value for global configuration '%s' field\n", field.Name, field.Name) + fmt.Fprintf(output, "func Set%[1]s(v %[2]s) { global.Set%[1]s(v) }\n\n", field.Name, field.Type.String()) + } + _ = output.Close() + _ = exec.Command("gofmt", "-w", out).Run() + + // The plain here is that eventually we might be able + // to generate an example configuration from struct tags + + // Unknown type + default: + panic("unknown generation type: " + gen) + } +} diff --git a/internal/config/global.go b/internal/config/global.go @@ -0,0 +1,53 @@ +/* + GoToSocial + Copyright (C) 2021-2022 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 <http://www.gnu.org/licenses/>. +*/ + +package config + +import "github.com/spf13/cobra" + +var global *ConfigState + +func init() { + // init global state + global = NewState() +} + +// TODO: in the future we should move away from using globals in this config +// package, and instead pass the ConfigState round in a global gts state. + +// Config provides you safe access to the global configuration. +func Config(fn func(cfg *Configuration)) { + global.Config(fn) +} + +// Reload will reload the current configuration values from file. +func Reload() error { + return global.Reload() +} + +// LoadEarlyFlags will bind specific flags from given Cobra command to global viper +// instance, and load the current configuration values. This is useful for flags like +// .ConfigPath which have to parsed first in order to perform early configuration load. +func LoadEarlyFlags(cmd *cobra.Command) error { + return global.LoadEarlyFlags(cmd) +} + +// BindFlags binds given command's pflags to the global viper instance. +func BindFlags(cmd *cobra.Command) error { + return global.BindFlags(cmd) +} diff --git a/internal/config/helpers.gen.go b/internal/config/helpers.gen.go @@ -0,0 +1,1494 @@ +// THIS IS A GENERATED FILE, DO NOT EDIT BY HAND +/* + GoToSocial + Copyright (C) 2021-2022 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 <http://www.gnu.org/licenses/>. +*/ +package config + +// GetLogLevel safely fetches the Configuration value for state's 'LogLevel' field +func (st *ConfigState) GetLogLevel() (v string) { + st.mutex.Lock() + v = st.config.LogLevel + st.mutex.Unlock() + return +} + +// SetLogLevel safely sets the Configuration value for state's 'LogLevel' field +func (st *ConfigState) SetLogLevel(v string) { + st.mutex.Lock() + defer st.mutex.Unlock() + st.config.LogLevel = v + st.reloadToViper() +} + +// LogLevelFlag returns the flag name for the 'LogLevel' field +func LogLevelFlag() string { return "log-level" } + +// GetLogLevel safely fetches the value for global configuration 'LogLevel' field +func GetLogLevel() string { return global.GetLogLevel() } + +// SetLogLevel safely sets the value for global configuration 'LogLevel' field +func SetLogLevel(v string) { global.SetLogLevel(v) } + +// GetLogDbQueries safely fetches the Configuration value for state's 'LogDbQueries' field +func (st *ConfigState) GetLogDbQueries() (v bool) { + st.mutex.Lock() + v = st.config.LogDbQueries + st.mutex.Unlock() + return +} + +// SetLogDbQueries safely sets the Configuration value for state's 'LogDbQueries' field +func (st *ConfigState) SetLogDbQueries(v bool) { + st.mutex.Lock() + defer st.mutex.Unlock() + st.config.LogDbQueries = v + st.reloadToViper() +} + +// LogDbQueriesFlag returns the flag name for the 'LogDbQueries' field +func LogDbQueriesFlag() string { return "log-db-queries" } + +// GetLogDbQueries safely fetches the value for global configuration 'LogDbQueries' field +func GetLogDbQueries() bool { return global.GetLogDbQueries() } + +// SetLogDbQueries safely sets the value for global configuration 'LogDbQueries' field +func SetLogDbQueries(v bool) { global.SetLogDbQueries(v) } + +// GetApplicationName safely fetches the Configuration value for state's 'ApplicationName' field +func (st *ConfigState) GetApplicationName() (v string) { + st.mutex.Lock() + v = st.config.ApplicationName + st.mutex.Unlock() + return +} + +// SetApplicationName safely sets the Configuration value for state's 'ApplicationName' field +func (st *ConfigState) SetApplicationName(v string) { + st.mutex.Lock() + defer st.mutex.Unlock() + st.config.ApplicationName = v + st.reloadToViper() +} + +// ApplicationNameFlag returns the flag name for the 'ApplicationName' field +func ApplicationNameFlag() string { return "application-name" } + +// GetApplicationName safely fetches the value for global configuration 'ApplicationName' field +func GetApplicationName() string { return global.GetApplicationName() } + +// SetApplicationName safely sets the value for global configuration 'ApplicationName' field +func SetApplicationName(v string) { global.SetApplicationName(v) } + +// GetConfigPath safely fetches the Configuration value for state's 'ConfigPath' field +func (st *ConfigState) GetConfigPath() (v string) { + st.mutex.Lock() + v = st.config.ConfigPath + st.mutex.Unlock() + return +} + +// SetConfigPath safely sets the Configuration value for state's 'ConfigPath' field +func (st *ConfigState) SetConfigPath(v string) { + st.mutex.Lock() + defer st.mutex.Unlock() + st.config.ConfigPath = v + st.reloadToViper() +} + +// ConfigPathFlag returns the flag name for the 'ConfigPath' field +func ConfigPathFlag() string { return "config-path" } + +// GetConfigPath safely fetches the value for global configuration 'ConfigPath' field +func GetConfigPath() string { return global.GetConfigPath() } + +// SetConfigPath safely sets the value for global configuration 'ConfigPath' field +func SetConfigPath(v string) { global.SetConfigPath(v) } + +// GetHost safely fetches the Configuration value for state's 'Host' field +func (st *ConfigState) GetHost() (v string) { + st.mutex.Lock() + v = st.config.Host + st.mutex.Unlock() + return +} + +// SetHost safely sets the Configuration value for state's 'Host' field +func (st *ConfigState) SetHost(v string) { + st.mutex.Lock() + defer st.mutex.Unlock() + st.config.Host = v + st.reloadToViper() +} + +// HostFlag returns the flag name for the 'Host' field +func HostFlag() string { return "host" } + +// GetHost safely fetches the value for global configuration 'Host' field +func GetHost() string { return global.GetHost() } + +// SetHost safely sets the value for global configuration 'Host' field +func SetHost(v string) { global.SetHost(v) } + +// GetAccountDomain safely fetches the Configuration value for state's 'AccountDomain' field +func (st *ConfigState) GetAccountDomain() (v string) { + st.mutex.Lock() + v = st.config.AccountDomain + st.mutex.Unlock() + return +} + +// SetAccountDomain safely sets the Configuration value for state's 'AccountDomain' field +func (st *ConfigState) SetAccountDomain(v string) { + st.mutex.Lock() + defer st.mutex.Unlock() + st.config.AccountDomain = v + st.reloadToViper() +} + +// AccountDomainFlag returns the flag name for the 'AccountDomain' field +func AccountDomainFlag() string { return "account-domain" } + +// GetAccountDomain safely fetches the value for global configuration 'AccountDomain' field +func GetAccountDomain() string { return global.GetAccountDomain() } + +// SetAccountDomain safely sets the value for global configuration 'AccountDomain' field +func SetAccountDomain(v string) { global.SetAccountDomain(v) } + +// GetProtocol safely fetches the Configuration value for state's 'Protocol' field +func (st *ConfigState) GetProtocol() (v string) { + st.mutex.Lock() + v = st.config.Protocol + st.mutex.Unlock() + return +} + +// SetProtocol safely sets the Configuration value for state's 'Protocol' field +func (st *ConfigState) SetProtocol(v string) { + st.mutex.Lock() + defer st.mutex.Unlock() + st.config.Protocol = v + st.reloadToViper() +} + +// ProtocolFlag returns the flag name for the 'Protocol' field +func ProtocolFlag() string { return "protocol" } + +// GetProtocol safely fetches the value for global configuration 'Protocol' field +func GetProtocol() string { return global.GetProtocol() } + +// SetProtocol safely sets the value for global configuration 'Protocol' field +func SetProtocol(v string) { global.SetProtocol(v) } + +// GetBindAddress safely fetches the Configuration value for state's 'BindAddress' field +func (st *ConfigState) GetBindAddress() (v string) { + st.mutex.Lock() + v = st.config.BindAddress + st.mutex.Unlock() + return +} + +// SetBindAddress safely sets the Configuration value for state's 'BindAddress' field +func (st *ConfigState) SetBindAddress(v string) { + st.mutex.Lock() + defer st.mutex.Unlock() + st.config.BindAddress = v + st.reloadToViper() +} + +// BindAddressFlag returns the flag name for the 'BindAddress' field +func BindAddressFlag() string { return "bind-address" } + +// GetBindAddress safely fetches the value for global configuration 'BindAddress' field +func GetBindAddress() string { return global.GetBindAddress() } + +// SetBindAddress safely sets the value for global configuration 'BindAddress' field +func SetBindAddress(v string) { global.SetBindAddress(v) } + +// GetPort safely fetches the Configuration value for state's 'Port' field +func (st *ConfigState) GetPort() (v int) { + st.mutex.Lock() + v = st.config.Port + st.mutex.Unlock() + return +} + +// SetPort safely sets the Configuration value for state's 'Port' field +func (st *ConfigState) SetPort(v int) { + st.mutex.Lock() + defer st.mutex.Unlock() + st.config.Port = v + st.reloadToViper() +} + +// PortFlag returns the flag name for the 'Port' field +func PortFlag() string { return "port" } + +// GetPort safely fetches the value for global configuration 'Port' field +func GetPort() int { return global.GetPort() } + +// SetPort safely sets the value for global configuration 'Port' field +func SetPort(v int) { global.SetPort(v) } + +// GetTrustedProxies safely fetches the Configuration value for state's 'TrustedProxies' field +func (st *ConfigState) GetTrustedProxies() (v []string) { + st.mutex.Lock() + v = st.config.TrustedProxies + st.mutex.Unlock() + return +} + +// SetTrustedProxies safely sets the Configuration value for state's 'TrustedProxies' field +func (st *ConfigState) SetTrustedProxies(v []string) { + st.mutex.Lock() + defer st.mutex.Unlock() + st.config.TrustedProxies = v + st.reloadToViper() +} + +// TrustedProxiesFlag returns the flag name for the 'TrustedProxies' field +func TrustedProxiesFlag() string { return "trusted-proxies" } + +// GetTrustedProxies safely fetches the value for global configuration 'TrustedProxies' field +func GetTrustedProxies() []string { return global.GetTrustedProxies() } + +// SetTrustedProxies safely sets the value for global configuration 'TrustedProxies' field +func SetTrustedProxies(v []string) { global.SetTrustedProxies(v) } + +// GetSoftwareVersion safely fetches the Configuration value for state's 'SoftwareVersion' field +func (st *ConfigState) GetSoftwareVersion() (v string) { + st.mutex.Lock() + v = st.config.SoftwareVersion + st.mutex.Unlock() + return +} + +// SetSoftwareVersion safely sets the Configuration value for state's 'SoftwareVersion' field +func (st *ConfigState) SetSoftwareVersion(v string) { + st.mutex.Lock() + defer st.mutex.Unlock() + st.config.SoftwareVersion = v + st.reloadToViper() +} + +// SoftwareVersionFlag returns the flag name for the 'SoftwareVersion' field +func SoftwareVersionFlag() string { return "software-version" } + +// GetSoftwareVersion safely fetches the value for global configuration 'SoftwareVersion' field +func GetSoftwareVersion() string { return global.GetSoftwareVersion() } + +// SetSoftwareVersion safely sets the value for global configuration 'SoftwareVersion' field +func SetSoftwareVersion(v string) { global.SetSoftwareVersion(v) } + +// GetDbType safely fetches the Configuration value for state's 'DbType' field +func (st *ConfigState) GetDbType() (v string) { + st.mutex.Lock() + v = st.config.DbType + st.mutex.Unlock() + return +} + +// SetDbType safely sets the Configuration value for state's 'DbType' field +func (st *ConfigState) SetDbType(v string) { + st.mutex.Lock() + defer st.mutex.Unlock() + st.config.DbType = v + st.reloadToViper() +} + +// DbTypeFlag returns the flag name for the 'DbType' field +func DbTypeFlag() string { return "db-type" } + +// GetDbType safely fetches the value for global configuration 'DbType' field +func GetDbType() string { return global.GetDbType() } + +// SetDbType safely sets the value for global configuration 'DbType' field +func SetDbType(v string) { global.SetDbType(v) } + +// GetDbAddress safely fetches the Configuration value for state's 'DbAddress' field +func (st *ConfigState) GetDbAddress() (v string) { + st.mutex.Lock() + v = st.config.DbAddress + st.mutex.Unlock() + return +} + +// SetDbAddress safely sets the Configuration value for state's 'DbAddress' field +func (st *ConfigState) SetDbAddress(v string) { + st.mutex.Lock() + defer st.mutex.Unlock() + st.config.DbAddress = v + st.reloadToViper() +} + +// DbAddressFlag returns the flag name for the 'DbAddress' field +func DbAddressFlag() string { return "db-address" } + +// GetDbAddress safely fetches the value for global configuration 'DbAddress' field +func GetDbAddress() string { return global.GetDbAddress() } + +// SetDbAddress safely sets the value for global configuration 'DbAddress' field +func SetDbAddress(v string) { global.SetDbAddress(v) } + +// GetDbPort safely fetches the Configuration value for state's 'DbPort' field +func (st *ConfigState) GetDbPort() (v int) { + st.mutex.Lock() + v = st.config.DbPort + st.mutex.Unlock() + return +} + +// SetDbPort safely sets the Configuration value for state's 'DbPort' field +func (st *ConfigState) SetDbPort(v int) { + st.mutex.Lock() + defer st.mutex.Unlock() + st.config.DbPort = v + st.reloadToViper() +} + +// DbPortFlag returns the flag name for the 'DbPort' field +func DbPortFlag() string { return "db-port" } + +// GetDbPort safely fetches the value for global configuration 'DbPort' field +func GetDbPort() int { return global.GetDbPort() } + +// SetDbPort safely sets the value for global configuration 'DbPort' field +func SetDbPort(v int) { global.SetDbPort(v) } + +// GetDbUser safely fetches the Configuration value for state's 'DbUser' field +func (st *ConfigState) GetDbUser() (v string) { + st.mutex.Lock() + v = st.config.DbUser + st.mutex.Unlock() + return +} + +// SetDbUser safely sets the Configuration value for state's 'DbUser' field +func (st *ConfigState) SetDbUser(v string) { + st.mutex.Lock() + defer st.mutex.Unlock() + st.config.DbUser = v + st.reloadToViper() +} + +// DbUserFlag returns the flag name for the 'DbUser' field +func DbUserFlag() string { return "db-user" } + +// GetDbUser safely fetches the value for global configuration 'DbUser' field +func GetDbUser() string { return global.GetDbUser() } + +// SetDbUser safely sets the value for global configuration 'DbUser' field +func SetDbUser(v string) { global.SetDbUser(v) } + +// GetDbPassword safely fetches the Configuration value for state's 'DbPassword' field +func (st *ConfigState) GetDbPassword() (v string) { + st.mutex.Lock() + v = st.config.DbPassword + st.mutex.Unlock() + return +} + +// SetDbPassword safely sets the Configuration value for state's 'DbPassword' field +func (st *ConfigState) SetDbPassword(v string) { + st.mutex.Lock() + defer st.mutex.Unlock() + st.config.DbPassword = v + st.reloadToViper() +} + +// DbPasswordFlag returns the flag name for the 'DbPassword' field +func DbPasswordFlag() string { return "db-password" } + +// GetDbPassword safely fetches the value for global configuration 'DbPassword' field +func GetDbPassword() string { return global.GetDbPassword() } + +// SetDbPassword safely sets the value for global configuration 'DbPassword' field +func SetDbPassword(v string) { global.SetDbPassword(v) } + +// GetDbDatabase safely fetches the Configuration value for state's 'DbDatabase' field +func (st *ConfigState) GetDbDatabase() (v string) { + st.mutex.Lock() + v = st.config.DbDatabase + st.mutex.Unlock() + return +} + +// SetDbDatabase safely sets the Configuration value for state's 'DbDatabase' field +func (st *ConfigState) SetDbDatabase(v string) { + st.mutex.Lock() + defer st.mutex.Unlock() + st.config.DbDatabase = v + st.reloadToViper() +} + +// DbDatabaseFlag returns the flag name for the 'DbDatabase' field +func DbDatabaseFlag() string { return "db-database" } + +// GetDbDatabase safely fetches the value for global configuration 'DbDatabase' field +func GetDbDatabase() string { return global.GetDbDatabase() } + +// SetDbDatabase safely sets the value for global configuration 'DbDatabase' field +func SetDbDatabase(v string) { global.SetDbDatabase(v) } + +// GetDbTLSMode safely fetches the Configuration value for state's 'DbTLSMode' field +func (st *ConfigState) GetDbTLSMode() (v string) { + st.mutex.Lock() + v = st.config.DbTLSMode + st.mutex.Unlock() + return +} + +// SetDbTLSMode safely sets the Configuration value for state's 'DbTLSMode' field +func (st *ConfigState) SetDbTLSMode(v string) { + st.mutex.Lock() + defer st.mutex.Unlock() + st.config.DbTLSMode = v + st.reloadToViper() +} + +// DbTLSModeFlag returns the flag name for the 'DbTLSMode' field +func DbTLSModeFlag() string { return "db-tls-mode" } + +// GetDbTLSMode safely fetches the value for global configuration 'DbTLSMode' field +func GetDbTLSMode() string { return global.GetDbTLSMode() } + +// SetDbTLSMode safely sets the value for global configuration 'DbTLSMode' field +func SetDbTLSMode(v string) { global.SetDbTLSMode(v) } + +// GetDbTLSCACert safely fetches the Configuration value for state's 'DbTLSCACert' field +func (st *ConfigState) GetDbTLSCACert() (v string) { + st.mutex.Lock() + v = st.config.DbTLSCACert + st.mutex.Unlock() + return +} + +// SetDbTLSCACert safely sets the Configuration value for state's 'DbTLSCACert' field +func (st *ConfigState) SetDbTLSCACert(v string) { + st.mutex.Lock() + defer st.mutex.Unlock() + st.config.DbTLSCACert = v + st.reloadToViper() +} + +// DbTLSCACertFlag returns the flag name for the 'DbTLSCACert' field +func DbTLSCACertFlag() string { return "db-tls-ca-cert" } + +// GetDbTLSCACert safely fetches the value for global configuration 'DbTLSCACert' field +func GetDbTLSCACert() string { return global.GetDbTLSCACert() } + +// SetDbTLSCACert safely sets the value for global configuration 'DbTLSCACert' field +func SetDbTLSCACert(v string) { global.SetDbTLSCACert(v) } + +// GetWebTemplateBaseDir safely fetches the Configuration value for state's 'WebTemplateBaseDir' field +func (st *ConfigState) GetWebTemplateBaseDir() (v string) { + st.mutex.Lock() + v = st.config.WebTemplateBaseDir + st.mutex.Unlock() + return +} + +// SetWebTemplateBaseDir safely sets the Configuration value for state's 'WebTemplateBaseDir' field +func (st *ConfigState) SetWebTemplateBaseDir(v string) { + st.mutex.Lock() + defer st.mutex.Unlock() + st.config.WebTemplateBaseDir = v + st.reloadToViper() +} + +// WebTemplateBaseDirFlag returns the flag name for the 'WebTemplateBaseDir' field +func WebTemplateBaseDirFlag() string { return "web-template-base-dir" } + +// GetWebTemplateBaseDir safely fetches the value for global configuration 'WebTemplateBaseDir' field +func GetWebTemplateBaseDir() string { return global.GetWebTemplateBaseDir() } + +// SetWebTemplateBaseDir safely sets the value for global configuration 'WebTemplateBaseDir' field +func SetWebTemplateBaseDir(v string) { global.SetWebTemplateBaseDir(v) } + +// GetWebAssetBaseDir safely fetches the Configuration value for state's 'WebAssetBaseDir' field +func (st *ConfigState) GetWebAssetBaseDir() (v string) { + st.mutex.Lock() + v = st.config.WebAssetBaseDir + st.mutex.Unlock() + return +} + +// SetWebAssetBaseDir safely sets the Configuration value for state's 'WebAssetBaseDir' field +func (st *ConfigState) SetWebAssetBaseDir(v string) { + st.mutex.Lock() + defer st.mutex.Unlock() + st.config.WebAssetBaseDir = v + st.reloadToViper() +} + +// WebAssetBaseDirFlag returns the flag name for the 'WebAssetBaseDir' field +func WebAssetBaseDirFlag() string { return "web-asset-base-dir" } + +// GetWebAssetBaseDir safely fetches the value for global configuration 'WebAssetBaseDir' field +func GetWebAssetBaseDir() string { return global.GetWebAssetBaseDir() } + +// SetWebAssetBaseDir safely sets the value for global configuration 'WebAssetBaseDir' field +func SetWebAssetBaseDir(v string) { global.SetWebAssetBaseDir(v) } + +// GetAccountsRegistrationOpen safely fetches the Configuration value for state's 'AccountsRegistrationOpen' field +func (st *ConfigState) GetAccountsRegistrationOpen() (v bool) { + st.mutex.Lock() + v = st.config.AccountsRegistrationOpen + st.mutex.Unlock() + return +} + +// SetAccountsRegistrationOpen safely sets the Configuration value for state's 'AccountsRegistrationOpen' field +func (st *ConfigState) SetAccountsRegistrationOpen(v bool) { + st.mutex.Lock() + defer st.mutex.Unlock() + st.config.AccountsRegistrationOpen = v + st.reloadToViper() +} + +// AccountsRegistrationOpenFlag returns the flag name for the 'AccountsRegistrationOpen' field +func AccountsRegistrationOpenFlag() string { return "accounts-registration-open" } + +// GetAccountsRegistrationOpen safely fetches the value for global configuration 'AccountsRegistrationOpen' field +func GetAccountsRegistrationOpen() bool { return global.GetAccountsRegistrationOpen() } + +// SetAccountsRegistrationOpen safely sets the value for global configuration 'AccountsRegistrationOpen' field +func SetAccountsRegistrationOpen(v bool) { global.SetAccountsRegistrationOpen(v) } + +// GetAccountsApprovalRequired safely fetches the Configuration value for state's 'AccountsApprovalRequired' field +func (st *ConfigState) GetAccountsApprovalRequired() (v bool) { + st.mutex.Lock() + v = st.config.AccountsApprovalRequired + st.mutex.Unlock() + return +} + +// SetAccountsApprovalRequired safely sets the Configuration value for state's 'AccountsApprovalRequired' field +func (st *ConfigState) SetAccountsApprovalRequired(v bool) { + st.mutex.Lock() + defer st.mutex.Unlock() + st.config.AccountsApprovalRequired = v + st.reloadToViper() +} + +// AccountsApprovalRequiredFlag returns the flag name for the 'AccountsApprovalRequired' field +func AccountsApprovalRequiredFlag() string { return "accounts-approval-required" } + +// GetAccountsApprovalRequired safely fetches the value for global configuration 'AccountsApprovalRequired' field +func GetAccountsApprovalRequired() bool { return global.GetAccountsApprovalRequired() } + +// SetAccountsApprovalRequired safely sets the value for global configuration 'AccountsApprovalRequired' field +func SetAccountsApprovalRequired(v bool) { global.SetAccountsApprovalRequired(v) } + +// GetAccountsReasonRequired safely fetches the Configuration value for state's 'AccountsReasonRequired' field +func (st *ConfigState) GetAccountsReasonRequired() (v bool) { + st.mutex.Lock() + v = st.config.AccountsReasonRequired + st.mutex.Unlock() + return +} + +// SetAccountsReasonRequired safely sets the Configuration value for state's 'AccountsReasonRequired' field +func (st *ConfigState) SetAccountsReasonRequired(v bool) { + st.mutex.Lock() + defer st.mutex.Unlock() + st.config.AccountsReasonRequired = v + st.reloadToViper() +} + +// AccountsReasonRequiredFlag returns the flag name for the 'AccountsReasonRequired' field +func AccountsReasonRequiredFlag() string { return "accounts-reason-required" } + +// GetAccountsReasonRequired safely fetches the value for global configuration 'AccountsReasonRequired' field +func GetAccountsReasonRequired() bool { return global.GetAccountsReasonRequired() } + +// SetAccountsReasonRequired safely sets the value for global configuration 'AccountsReasonRequired' field +func SetAccountsReasonRequired(v bool) { global.SetAccountsReasonRequired(v) } + +// GetMediaImageMaxSize safely fetches the Configuration value for state's 'MediaImageMaxSize' field +func (st *ConfigState) GetMediaImageMaxSize() (v int) { + st.mutex.Lock() + v = st.config.MediaImageMaxSize + st.mutex.Unlock() + return +} + +// SetMediaImageMaxSize safely sets the Configuration value for state's 'MediaImageMaxSize' field +func (st *ConfigState) SetMediaImageMaxSize(v int) { + st.mutex.Lock() + defer st.mutex.Unlock() + st.config.MediaImageMaxSize = v + st.reloadToViper() +} + +// MediaImageMaxSizeFlag returns the flag name for the 'MediaImageMaxSize' field +func MediaImageMaxSizeFlag() string { return "media-image-max-size" } + +// GetMediaImageMaxSize safely fetches the value for global configuration 'MediaImageMaxSize' field +func GetMediaImageMaxSize() int { return global.GetMediaImageMaxSize() } + +// SetMediaImageMaxSize safely sets the value for global configuration 'MediaImageMaxSize' field +func SetMediaImageMaxSize(v int) { global.SetMediaImageMaxSize(v) } + +// GetMediaVideoMaxSize safely fetches the Configuration value for state's 'MediaVideoMaxSize' field +func (st *ConfigState) GetMediaVideoMaxSize() (v int) { + st.mutex.Lock() + v = st.config.MediaVideoMaxSize + st.mutex.Unlock() + return +} + +// SetMediaVideoMaxSize safely sets the Configuration value for state's 'MediaVideoMaxSize' field +func (st *ConfigState) SetMediaVideoMaxSize(v int) { + st.mutex.Lock() + defer st.mutex.Unlock() + st.config.MediaVideoMaxSize = v + st.reloadToViper() +} + +// MediaVideoMaxSizeFlag returns the flag name for the 'MediaVideoMaxSize' field +func MediaVideoMaxSizeFlag() string { return "media-video-max-size" } + +// GetMediaVideoMaxSize safely fetches the value for global configuration 'MediaVideoMaxSize' field +func GetMediaVideoMaxSize() int { return global.GetMediaVideoMaxSize() } + +// SetMediaVideoMaxSize safely sets the value for global configuration 'MediaVideoMaxSize' field +func SetMediaVideoMaxSize(v int) { global.SetMediaVideoMaxSize(v) } + +// GetMediaDescriptionMinChars safely fetches the Configuration value for state's 'MediaDescriptionMinChars' field +func (st *ConfigState) GetMediaDescriptionMinChars() (v int) { + st.mutex.Lock() + v = st.config.MediaDescriptionMinChars + st.mutex.Unlock() + return +} + +// SetMediaDescriptionMinChars safely sets the Configuration value for state's 'MediaDescriptionMinChars' field +func (st *ConfigState) SetMediaDescriptionMinChars(v int) { + st.mutex.Lock() + defer st.mutex.Unlock() + st.config.MediaDescriptionMinChars = v + st.reloadToViper() +} + +// MediaDescriptionMinCharsFlag returns the flag name for the 'MediaDescriptionMinChars' field +func MediaDescriptionMinCharsFlag() string { return "media-description-min-chars" } + +// GetMediaDescriptionMinChars safely fetches the value for global configuration 'MediaDescriptionMinChars' field +func GetMediaDescriptionMinChars() int { return global.GetMediaDescriptionMinChars() } + +// SetMediaDescriptionMinChars safely sets the value for global configuration 'MediaDescriptionMinChars' field +func SetMediaDescriptionMinChars(v int) { global.SetMediaDescriptionMinChars(v) } + +// GetMediaDescriptionMaxChars safely fetches the Configuration value for state's 'MediaDescriptionMaxChars' field +func (st *ConfigState) GetMediaDescriptionMaxChars() (v int) { + st.mutex.Lock() + v = st.config.MediaDescriptionMaxChars + st.mutex.Unlock() + return +} + +// SetMediaDescriptionMaxChars safely sets the Configuration value for state's 'MediaDescriptionMaxChars' field +func (st *ConfigState) SetMediaDescriptionMaxChars(v int) { + st.mutex.Lock() + defer st.mutex.Unlock() + st.config.MediaDescriptionMaxChars = v + st.reloadToViper() +} + +// MediaDescriptionMaxCharsFlag returns the flag name for the 'MediaDescriptionMaxChars' field +func MediaDescriptionMaxCharsFlag() string { return "media-description-max-chars" } + +// GetMediaDescriptionMaxChars safely fetches the value for global configuration 'MediaDescriptionMaxChars' field +func GetMediaDescriptionMaxChars() int { return global.GetMediaDescriptionMaxChars() } + +// SetMediaDescriptionMaxChars safely sets the value for global configuration 'MediaDescriptionMaxChars' field +func SetMediaDescriptionMaxChars(v int) { global.SetMediaDescriptionMaxChars(v) } + +// GetMediaRemoteCacheDays safely fetches the Configuration value for state's 'MediaRemoteCacheDays' field +func (st *ConfigState) GetMediaRemoteCacheDays() (v int) { + st.mutex.Lock() + v = st.config.MediaRemoteCacheDays + st.mutex.Unlock() + return +} + +// SetMediaRemoteCacheDays safely sets the Configuration value for state's 'MediaRemoteCacheDays' field +func (st *ConfigState) SetMediaRemoteCacheDays(v int) { + st.mutex.Lock() + defer st.mutex.Unlock() + st.config.MediaRemoteCacheDays = v + st.reloadToViper() +} + +// MediaRemoteCacheDaysFlag returns the flag name for the 'MediaRemoteCacheDays' field +func MediaRemoteCacheDaysFlag() string { return "media-remote-cache-days" } + +// GetMediaRemoteCacheDays safely fetches the value for global configuration 'MediaRemoteCacheDays' field +func GetMediaRemoteCacheDays() int { return global.GetMediaRemoteCacheDays() } + +// SetMediaRemoteCacheDays safely sets the value for global configuration 'MediaRemoteCacheDays' field +func SetMediaRemoteCacheDays(v int) { global.SetMediaRemoteCacheDays(v) } + +// GetStorageBackend safely fetches the Configuration value for state's 'StorageBackend' field +func (st *ConfigState) GetStorageBackend() (v string) { + st.mutex.Lock() + v = st.config.StorageBackend + st.mutex.Unlock() + return +} + +// SetStorageBackend safely sets the Configuration value for state's 'StorageBackend' field +func (st *ConfigState) SetStorageBackend(v string) { + st.mutex.Lock() + defer st.mutex.Unlock() + st.config.StorageBackend = v + st.reloadToViper() +} + +// StorageBackendFlag returns the flag name for the 'StorageBackend' field +func StorageBackendFlag() string { return "storage-backend" } + +// GetStorageBackend safely fetches the value for global configuration 'StorageBackend' field +func GetStorageBackend() string { return global.GetStorageBackend() } + +// SetStorageBackend safely sets the value for global configuration 'StorageBackend' field +func SetStorageBackend(v string) { global.SetStorageBackend(v) } + +// GetStorageLocalBasePath safely fetches the Configuration value for state's 'StorageLocalBasePath' field +func (st *ConfigState) GetStorageLocalBasePath() (v string) { + st.mutex.Lock() + v = st.config.StorageLocalBasePath + st.mutex.Unlock() + return +} + +// SetStorageLocalBasePath safely sets the Configuration value for state's 'StorageLocalBasePath' field +func (st *ConfigState) SetStorageLocalBasePath(v string) { + st.mutex.Lock() + defer st.mutex.Unlock() + st.config.StorageLocalBasePath = v + st.reloadToViper() +} + +// StorageLocalBasePathFlag returns the flag name for the 'StorageLocalBasePath' field +func StorageLocalBasePathFlag() string { return "storage-local-base-path" } + +// GetStorageLocalBasePath safely fetches the value for global configuration 'StorageLocalBasePath' field +func GetStorageLocalBasePath() string { return global.GetStorageLocalBasePath() } + +// SetStorageLocalBasePath safely sets the value for global configuration 'StorageLocalBasePath' field +func SetStorageLocalBasePath(v string) { global.SetStorageLocalBasePath(v) } + +// GetStatusesMaxChars safely fetches the Configuration value for state's 'StatusesMaxChars' field +func (st *ConfigState) GetStatusesMaxChars() (v int) { + st.mutex.Lock() + v = st.config.StatusesMaxChars + st.mutex.Unlock() + return +} + +// SetStatusesMaxChars safely sets the Configuration value for state's 'StatusesMaxChars' field +func (st *ConfigState) SetStatusesMaxChars(v int) { + st.mutex.Lock() + defer st.mutex.Unlock() + st.config.StatusesMaxChars = v + st.reloadToViper() +} + +// StatusesMaxCharsFlag returns the flag name for the 'StatusesMaxChars' field +func StatusesMaxCharsFlag() string { return "statuses-max-chars" } + +// GetStatusesMaxChars safely fetches the value for global configuration 'StatusesMaxChars' field +func GetStatusesMaxChars() int { return global.GetStatusesMaxChars() } + +// SetStatusesMaxChars safely sets the value for global configuration 'StatusesMaxChars' field +func SetStatusesMaxChars(v int) { global.SetStatusesMaxChars(v) } + +// GetStatusesCWMaxChars safely fetches the Configuration value for state's 'StatusesCWMaxChars' field +func (st *ConfigState) GetStatusesCWMaxChars() (v int) { + st.mutex.Lock() + v = st.config.StatusesCWMaxChars + st.mutex.Unlock() + return +} + +// SetStatusesCWMaxChars safely sets the Configuration value for state's 'StatusesCWMaxChars' field +func (st *ConfigState) SetStatusesCWMaxChars(v int) { + st.mutex.Lock() + defer st.mutex.Unlock() + st.config.StatusesCWMaxChars = v + st.reloadToViper() +} + +// StatusesCWMaxCharsFlag returns the flag name for the 'StatusesCWMaxChars' field +func StatusesCWMaxCharsFlag() string { return "statuses-cw-max-chars" } + +// GetStatusesCWMaxChars safely fetches the value for global configuration 'StatusesCWMaxChars' field +func GetStatusesCWMaxChars() int { return global.GetStatusesCWMaxChars() } + +// SetStatusesCWMaxChars safely sets the value for global configuration 'StatusesCWMaxChars' field +func SetStatusesCWMaxChars(v int) { global.SetStatusesCWMaxChars(v) } + +// GetStatusesPollMaxOptions safely fetches the Configuration value for state's 'StatusesPollMaxOptions' field +func (st *ConfigState) GetStatusesPollMaxOptions() (v int) { + st.mutex.Lock() + v = st.config.StatusesPollMaxOptions + st.mutex.Unlock() + return +} + +// SetStatusesPollMaxOptions safely sets the Configuration value for state's 'StatusesPollMaxOptions' field +func (st *ConfigState) SetStatusesPollMaxOptions(v int) { + st.mutex.Lock() + defer st.mutex.Unlock() + st.config.StatusesPollMaxOptions = v + st.reloadToViper() +} + +// StatusesPollMaxOptionsFlag returns the flag name for the 'StatusesPollMaxOptions' field +func StatusesPollMaxOptionsFlag() string { return "statuses-poll-max-options" } + +// GetStatusesPollMaxOptions safely fetches the value for global configuration 'StatusesPollMaxOptions' field +func GetStatusesPollMaxOptions() int { return global.GetStatusesPollMaxOptions() } + +// SetStatusesPollMaxOptions safely sets the value for global configuration 'StatusesPollMaxOptions' field +func SetStatusesPollMaxOptions(v int) { global.SetStatusesPollMaxOptions(v) } + +// GetStatusesPollOptionMaxChars safely fetches the Configuration value for state's 'StatusesPollOptionMaxChars' field +func (st *ConfigState) GetStatusesPollOptionMaxChars() (v int) { + st.mutex.Lock() + v = st.config.StatusesPollOptionMaxChars + st.mutex.Unlock() + return +} + +// SetStatusesPollOptionMaxChars safely sets the Configuration value for state's 'StatusesPollOptionMaxChars' field +func (st *ConfigState) SetStatusesPollOptionMaxChars(v int) { + st.mutex.Lock() + defer st.mutex.Unlock() + st.config.StatusesPollOptionMaxChars = v + st.reloadToViper() +} + +// StatusesPollOptionMaxCharsFlag returns the flag name for the 'StatusesPollOptionMaxChars' field +func StatusesPollOptionMaxCharsFlag() string { return "statuses-poll-option-max-chars" } + +// GetStatusesPollOptionMaxChars safely fetches the value for global configuration 'StatusesPollOptionMaxChars' field +func GetStatusesPollOptionMaxChars() int { return global.GetStatusesPollOptionMaxChars() } + +// SetStatusesPollOptionMaxChars safely sets the value for global configuration 'StatusesPollOptionMaxChars' field +func SetStatusesPollOptionMaxChars(v int) { global.SetStatusesPollOptionMaxChars(v) } + +// GetStatusesMediaMaxFiles safely fetches the Configuration value for state's 'StatusesMediaMaxFiles' field +func (st *ConfigState) GetStatusesMediaMaxFiles() (v int) { + st.mutex.Lock() + v = st.config.StatusesMediaMaxFiles + st.mutex.Unlock() + return +} + +// SetStatusesMediaMaxFiles safely sets the Configuration value for state's 'StatusesMediaMaxFiles' field +func (st *ConfigState) SetStatusesMediaMaxFiles(v int) { + st.mutex.Lock() + defer st.mutex.Unlock() + st.config.StatusesMediaMaxFiles = v + st.reloadToViper() +} + +// StatusesMediaMaxFilesFlag returns the flag name for the 'StatusesMediaMaxFiles' field +func StatusesMediaMaxFilesFlag() string { return "statuses-media-max-files" } + +// GetStatusesMediaMaxFiles safely fetches the value for global configuration 'StatusesMediaMaxFiles' field +func GetStatusesMediaMaxFiles() int { return global.GetStatusesMediaMaxFiles() } + +// SetStatusesMediaMaxFiles safely sets the value for global configuration 'StatusesMediaMaxFiles' field +func SetStatusesMediaMaxFiles(v int) { global.SetStatusesMediaMaxFiles(v) } + +// GetLetsEncryptEnabled safely fetches the Configuration value for state's 'LetsEncryptEnabled' field +func (st *ConfigState) GetLetsEncryptEnabled() (v bool) { + st.mutex.Lock() + v = st.config.LetsEncryptEnabled + st.mutex.Unlock() + return +} + +// SetLetsEncryptEnabled safely sets the Configuration value for state's 'LetsEncryptEnabled' field +func (st *ConfigState) SetLetsEncryptEnabled(v bool) { + st.mutex.Lock() + defer st.mutex.Unlock() + st.config.LetsEncryptEnabled = v + st.reloadToViper() +} + +// LetsEncryptEnabledFlag returns the flag name for the 'LetsEncryptEnabled' field +func LetsEncryptEnabledFlag() string { return "letsencrypt-enabled" } + +// GetLetsEncryptEnabled safely fetches the value for global configuration 'LetsEncryptEnabled' field +func GetLetsEncryptEnabled() bool { return global.GetLetsEncryptEnabled() } + +// SetLetsEncryptEnabled safely sets the value for global configuration 'LetsEncryptEnabled' field +func SetLetsEncryptEnabled(v bool) { global.SetLetsEncryptEnabled(v) } + +// GetLetsEncryptPort safely fetches the Configuration value for state's 'LetsEncryptPort' field +func (st *ConfigState) GetLetsEncryptPort() (v int) { + st.mutex.Lock() + v = st.config.LetsEncryptPort + st.mutex.Unlock() + return +} + +// SetLetsEncryptPort safely sets the Configuration value for state's 'LetsEncryptPort' field +func (st *ConfigState) SetLetsEncryptPort(v int) { + st.mutex.Lock() + defer st.mutex.Unlock() + st.config.LetsEncryptPort = v + st.reloadToViper() +} + +// LetsEncryptPortFlag returns the flag name for the 'LetsEncryptPort' field +func LetsEncryptPortFlag() string { return "letsencrypt-port" } + +// GetLetsEncryptPort safely fetches the value for global configuration 'LetsEncryptPort' field +func GetLetsEncryptPort() int { return global.GetLetsEncryptPort() } + +// SetLetsEncryptPort safely sets the value for global configuration 'LetsEncryptPort' field +func SetLetsEncryptPort(v int) { global.SetLetsEncryptPort(v) } + +// GetLetsEncryptCertDir safely fetches the Configuration value for state's 'LetsEncryptCertDir' field +func (st *ConfigState) GetLetsEncryptCertDir() (v string) { + st.mutex.Lock() + v = st.config.LetsEncryptCertDir + st.mutex.Unlock() + return +} + +// SetLetsEncryptCertDir safely sets the Configuration value for state's 'LetsEncryptCertDir' field +func (st *ConfigState) SetLetsEncryptCertDir(v string) { + st.mutex.Lock() + defer st.mutex.Unlock() + st.config.LetsEncryptCertDir = v + st.reloadToViper() +} + +// LetsEncryptCertDirFlag returns the flag name for the 'LetsEncryptCertDir' field +func LetsEncryptCertDirFlag() string { return "letsencrypt-cert-dir" } + +// GetLetsEncryptCertDir safely fetches the value for global configuration 'LetsEncryptCertDir' field +func GetLetsEncryptCertDir() string { return global.GetLetsEncryptCertDir() } + +// SetLetsEncryptCertDir safely sets the value for global configuration 'LetsEncryptCertDir' field +func SetLetsEncryptCertDir(v string) { global.SetLetsEncryptCertDir(v) } + +// GetLetsEncryptEmailAddress safely fetches the Configuration value for state's 'LetsEncryptEmailAddress' field +func (st *ConfigState) GetLetsEncryptEmailAddress() (v string) { + st.mutex.Lock() + v = st.config.LetsEncryptEmailAddress + st.mutex.Unlock() + return +} + +// SetLetsEncryptEmailAddress safely sets the Configuration value for state's 'LetsEncryptEmailAddress' field +func (st *ConfigState) SetLetsEncryptEmailAddress(v string) { + st.mutex.Lock() + defer st.mutex.Unlock() + st.config.LetsEncryptEmailAddress = v + st.reloadToViper() +} + +// LetsEncryptEmailAddressFlag returns the flag name for the 'LetsEncryptEmailAddress' field +func LetsEncryptEmailAddressFlag() string { return "letsencrypt-email-address" } + +// GetLetsEncryptEmailAddress safely fetches the value for global configuration 'LetsEncryptEmailAddress' field +func GetLetsEncryptEmailAddress() string { return global.GetLetsEncryptEmailAddress() } + +// SetLetsEncryptEmailAddress safely sets the value for global configuration 'LetsEncryptEmailAddress' field +func SetLetsEncryptEmailAddress(v string) { global.SetLetsEncryptEmailAddress(v) } + +// GetOIDCEnabled safely fetches the Configuration value for state's 'OIDCEnabled' field +func (st *ConfigState) GetOIDCEnabled() (v bool) { + st.mutex.Lock() + v = st.config.OIDCEnabled + st.mutex.Unlock() + return +} + +// SetOIDCEnabled safely sets the Configuration value for state's 'OIDCEnabled' field +func (st *ConfigState) SetOIDCEnabled(v bool) { + st.mutex.Lock() + defer st.mutex.Unlock() + st.config.OIDCEnabled = v + st.reloadToViper() +} + +// OIDCEnabledFlag returns the flag name for the 'OIDCEnabled' field +func OIDCEnabledFlag() string { return "oidc-enabled" } + +// GetOIDCEnabled safely fetches the value for global configuration 'OIDCEnabled' field +func GetOIDCEnabled() bool { return global.GetOIDCEnabled() } + +// SetOIDCEnabled safely sets the value for global configuration 'OIDCEnabled' field +func SetOIDCEnabled(v bool) { global.SetOIDCEnabled(v) } + +// GetOIDCIdpName safely fetches the Configuration value for state's 'OIDCIdpName' field +func (st *ConfigState) GetOIDCIdpName() (v string) { + st.mutex.Lock() + v = st.config.OIDCIdpName + st.mutex.Unlock() + return +} + +// SetOIDCIdpName safely sets the Configuration value for state's 'OIDCIdpName' field +func (st *ConfigState) SetOIDCIdpName(v string) { + st.mutex.Lock() + defer st.mutex.Unlock() + st.config.OIDCIdpName = v + st.reloadToViper() +} + +// OIDCIdpNameFlag returns the flag name for the 'OIDCIdpName' field +func OIDCIdpNameFlag() string { return "oidc-idp-name" } + +// GetOIDCIdpName safely fetches the value for global configuration 'OIDCIdpName' field +func GetOIDCIdpName() string { return global.GetOIDCIdpName() } + +// SetOIDCIdpName safely sets the value for global configuration 'OIDCIdpName' field +func SetOIDCIdpName(v string) { global.SetOIDCIdpName(v) } + +// GetOIDCSkipVerification safely fetches the Configuration value for state's 'OIDCSkipVerification' field +func (st *ConfigState) GetOIDCSkipVerification() (v bool) { + st.mutex.Lock() + v = st.config.OIDCSkipVerification + st.mutex.Unlock() + return +} + +// SetOIDCSkipVerification safely sets the Configuration value for state's 'OIDCSkipVerification' field +func (st *ConfigState) SetOIDCSkipVerification(v bool) { + st.mutex.Lock() + defer st.mutex.Unlock() + st.config.OIDCSkipVerification = v + st.reloadToViper() +} + +// OIDCSkipVerificationFlag returns the flag name for the 'OIDCSkipVerification' field +func OIDCSkipVerificationFlag() string { return "oidc-skip-verification" } + +// GetOIDCSkipVerification safely fetches the value for global configuration 'OIDCSkipVerification' field +func GetOIDCSkipVerification() bool { return global.GetOIDCSkipVerification() } + +// SetOIDCSkipVerification safely sets the value for global configuration 'OIDCSkipVerification' field +func SetOIDCSkipVerification(v bool) { global.SetOIDCSkipVerification(v) } + +// GetOIDCIssuer safely fetches the Configuration value for state's 'OIDCIssuer' field +func (st *ConfigState) GetOIDCIssuer() (v string) { + st.mutex.Lock() + v = st.config.OIDCIssuer + st.mutex.Unlock() + return +} + +// SetOIDCIssuer safely sets the Configuration value for state's 'OIDCIssuer' field +func (st *ConfigState) SetOIDCIssuer(v string) { + st.mutex.Lock() + defer st.mutex.Unlock() + st.config.OIDCIssuer = v + st.reloadToViper() +} + +// OIDCIssuerFlag returns the flag name for the 'OIDCIssuer' field +func OIDCIssuerFlag() string { return "oidc-issuer" } + +// GetOIDCIssuer safely fetches the value for global configuration 'OIDCIssuer' field +func GetOIDCIssuer() string { return global.GetOIDCIssuer() } + +// SetOIDCIssuer safely sets the value for global configuration 'OIDCIssuer' field +func SetOIDCIssuer(v string) { global.SetOIDCIssuer(v) } + +// GetOIDCClientID safely fetches the Configuration value for state's 'OIDCClientID' field +func (st *ConfigState) GetOIDCClientID() (v string) { + st.mutex.Lock() + v = st.config.OIDCClientID + st.mutex.Unlock() + return +} + +// SetOIDCClientID safely sets the Configuration value for state's 'OIDCClientID' field +func (st *ConfigState) SetOIDCClientID(v string) { + st.mutex.Lock() + defer st.mutex.Unlock() + st.config.OIDCClientID = v + st.reloadToViper() +} + +// OIDCClientIDFlag returns the flag name for the 'OIDCClientID' field +func OIDCClientIDFlag() string { return "oidc-client-id" } + +// GetOIDCClientID safely fetches the value for global configuration 'OIDCClientID' field +func GetOIDCClientID() string { return global.GetOIDCClientID() } + +// SetOIDCClientID safely sets the value for global configuration 'OIDCClientID' field +func SetOIDCClientID(v string) { global.SetOIDCClientID(v) } + +// GetOIDCClientSecret safely fetches the Configuration value for state's 'OIDCClientSecret' field +func (st *ConfigState) GetOIDCClientSecret() (v string) { + st.mutex.Lock() + v = st.config.OIDCClientSecret + st.mutex.Unlock() + return +} + +// SetOIDCClientSecret safely sets the Configuration value for state's 'OIDCClientSecret' field +func (st *ConfigState) SetOIDCClientSecret(v string) { + st.mutex.Lock() + defer st.mutex.Unlock() + st.config.OIDCClientSecret = v + st.reloadToViper() +} + +// OIDCClientSecretFlag returns the flag name for the 'OIDCClientSecret' field +func OIDCClientSecretFlag() string { return "oidc-client-secret" } + +// GetOIDCClientSecret safely fetches the value for global configuration 'OIDCClientSecret' field +func GetOIDCClientSecret() string { return global.GetOIDCClientSecret() } + +// SetOIDCClientSecret safely sets the value for global configuration 'OIDCClientSecret' field +func SetOIDCClientSecret(v string) { global.SetOIDCClientSecret(v) } + +// GetOIDCScopes safely fetches the Configuration value for state's 'OIDCScopes' field +func (st *ConfigState) GetOIDCScopes() (v []string) { + st.mutex.Lock() + v = st.config.OIDCScopes + st.mutex.Unlock() + return +} + +// SetOIDCScopes safely sets the Configuration value for state's 'OIDCScopes' field +func (st *ConfigState) SetOIDCScopes(v []string) { + st.mutex.Lock() + defer st.mutex.Unlock() + st.config.OIDCScopes = v + st.reloadToViper() +} + +// OIDCScopesFlag returns the flag name for the 'OIDCScopes' field +func OIDCScopesFlag() string { return "oidc-scopes" } + +// GetOIDCScopes safely fetches the value for global configuration 'OIDCScopes' field +func GetOIDCScopes() []string { return global.GetOIDCScopes() } + +// SetOIDCScopes safely sets the value for global configuration 'OIDCScopes' field +func SetOIDCScopes(v []string) { global.SetOIDCScopes(v) } + +// GetSMTPHost safely fetches the Configuration value for state's 'SMTPHost' field +func (st *ConfigState) GetSMTPHost() (v string) { + st.mutex.Lock() + v = st.config.SMTPHost + st.mutex.Unlock() + return +} + +// SetSMTPHost safely sets the Configuration value for state's 'SMTPHost' field +func (st *ConfigState) SetSMTPHost(v string) { + st.mutex.Lock() + defer st.mutex.Unlock() + st.config.SMTPHost = v + st.reloadToViper() +} + +// SMTPHostFlag returns the flag name for the 'SMTPHost' field +func SMTPHostFlag() string { return "smtp-host" } + +// GetSMTPHost safely fetches the value for global configuration 'SMTPHost' field +func GetSMTPHost() string { return global.GetSMTPHost() } + +// SetSMTPHost safely sets the value for global configuration 'SMTPHost' field +func SetSMTPHost(v string) { global.SetSMTPHost(v) } + +// GetSMTPPort safely fetches the Configuration value for state's 'SMTPPort' field +func (st *ConfigState) GetSMTPPort() (v int) { + st.mutex.Lock() + v = st.config.SMTPPort + st.mutex.Unlock() + return +} + +// SetSMTPPort safely sets the Configuration value for state's 'SMTPPort' field +func (st *ConfigState) SetSMTPPort(v int) { + st.mutex.Lock() + defer st.mutex.Unlock() + st.config.SMTPPort = v + st.reloadToViper() +} + +// SMTPPortFlag returns the flag name for the 'SMTPPort' field +func SMTPPortFlag() string { return "smtp-port" } + +// GetSMTPPort safely fetches the value for global configuration 'SMTPPort' field +func GetSMTPPort() int { return global.GetSMTPPort() } + +// SetSMTPPort safely sets the value for global configuration 'SMTPPort' field +func SetSMTPPort(v int) { global.SetSMTPPort(v) } + +// GetSMTPUsername safely fetches the Configuration value for state's 'SMTPUsername' field +func (st *ConfigState) GetSMTPUsername() (v string) { + st.mutex.Lock() + v = st.config.SMTPUsername + st.mutex.Unlock() + return +} + +// SetSMTPUsername safely sets the Configuration value for state's 'SMTPUsername' field +func (st *ConfigState) SetSMTPUsername(v string) { + st.mutex.Lock() + defer st.mutex.Unlock() + st.config.SMTPUsername = v + st.reloadToViper() +} + +// SMTPUsernameFlag returns the flag name for the 'SMTPUsername' field +func SMTPUsernameFlag() string { return "smtp-username" } + +// GetSMTPUsername safely fetches the value for global configuration 'SMTPUsername' field +func GetSMTPUsername() string { return global.GetSMTPUsername() } + +// SetSMTPUsername safely sets the value for global configuration 'SMTPUsername' field +func SetSMTPUsername(v string) { global.SetSMTPUsername(v) } + +// GetSMTPPassword safely fetches the Configuration value for state's 'SMTPPassword' field +func (st *ConfigState) GetSMTPPassword() (v string) { + st.mutex.Lock() + v = st.config.SMTPPassword + st.mutex.Unlock() + return +} + +// SetSMTPPassword safely sets the Configuration value for state's 'SMTPPassword' field +func (st *ConfigState) SetSMTPPassword(v string) { + st.mutex.Lock() + defer st.mutex.Unlock() + st.config.SMTPPassword = v + st.reloadToViper() +} + +// SMTPPasswordFlag returns the flag name for the 'SMTPPassword' field +func SMTPPasswordFlag() string { return "smtp-password" } + +// GetSMTPPassword safely fetches the value for global configuration 'SMTPPassword' field +func GetSMTPPassword() string { return global.GetSMTPPassword() } + +// SetSMTPPassword safely sets the value for global configuration 'SMTPPassword' field +func SetSMTPPassword(v string) { global.SetSMTPPassword(v) } + +// GetSMTPFrom safely fetches the Configuration value for state's 'SMTPFrom' field +func (st *ConfigState) GetSMTPFrom() (v string) { + st.mutex.Lock() + v = st.config.SMTPFrom + st.mutex.Unlock() + return +} + +// SetSMTPFrom safely sets the Configuration value for state's 'SMTPFrom' field +func (st *ConfigState) SetSMTPFrom(v string) { + st.mutex.Lock() + defer st.mutex.Unlock() + st.config.SMTPFrom = v + st.reloadToViper() +} + +// SMTPFromFlag returns the flag name for the 'SMTPFrom' field +func SMTPFromFlag() string { return "smtp-from" } + +// GetSMTPFrom safely fetches the value for global configuration 'SMTPFrom' field +func GetSMTPFrom() string { return global.GetSMTPFrom() } + +// SetSMTPFrom safely sets the value for global configuration 'SMTPFrom' field +func SetSMTPFrom(v string) { global.SetSMTPFrom(v) } + +// GetSyslogEnabled safely fetches the Configuration value for state's 'SyslogEnabled' field +func (st *ConfigState) GetSyslogEnabled() (v bool) { + st.mutex.Lock() + v = st.config.SyslogEnabled + st.mutex.Unlock() + return +} + +// SetSyslogEnabled safely sets the Configuration value for state's 'SyslogEnabled' field +func (st *ConfigState) SetSyslogEnabled(v bool) { + st.mutex.Lock() + defer st.mutex.Unlock() + st.config.SyslogEnabled = v + st.reloadToViper() +} + +// SyslogEnabledFlag returns the flag name for the 'SyslogEnabled' field +func SyslogEnabledFlag() string { return "syslog-enabled" } + +// GetSyslogEnabled safely fetches the value for global configuration 'SyslogEnabled' field +func GetSyslogEnabled() bool { return global.GetSyslogEnabled() } + +// SetSyslogEnabled safely sets the value for global configuration 'SyslogEnabled' field +func SetSyslogEnabled(v bool) { global.SetSyslogEnabled(v) } + +// GetSyslogProtocol safely fetches the Configuration value for state's 'SyslogProtocol' field +func (st *ConfigState) GetSyslogProtocol() (v string) { + st.mutex.Lock() + v = st.config.SyslogProtocol + st.mutex.Unlock() + return +} + +// SetSyslogProtocol safely sets the Configuration value for state's 'SyslogProtocol' field +func (st *ConfigState) SetSyslogProtocol(v string) { + st.mutex.Lock() + defer st.mutex.Unlock() + st.config.SyslogProtocol = v + st.reloadToViper() +} + +// SyslogProtocolFlag returns the flag name for the 'SyslogProtocol' field +func SyslogProtocolFlag() string { return "syslog-protocol" } + +// GetSyslogProtocol safely fetches the value for global configuration 'SyslogProtocol' field +func GetSyslogProtocol() string { return global.GetSyslogProtocol() } + +// SetSyslogProtocol safely sets the value for global configuration 'SyslogProtocol' field +func SetSyslogProtocol(v string) { global.SetSyslogProtocol(v) } + +// GetSyslogAddress safely fetches the Configuration value for state's 'SyslogAddress' field +func (st *ConfigState) GetSyslogAddress() (v string) { + st.mutex.Lock() + v = st.config.SyslogAddress + st.mutex.Unlock() + return +} + +// SetSyslogAddress safely sets the Configuration value for state's 'SyslogAddress' field +func (st *ConfigState) SetSyslogAddress(v string) { + st.mutex.Lock() + defer st.mutex.Unlock() + st.config.SyslogAddress = v + st.reloadToViper() +} + +// SyslogAddressFlag returns the flag name for the 'SyslogAddress' field +func SyslogAddressFlag() string { return "syslog-address" } + +// GetSyslogAddress safely fetches the value for global configuration 'SyslogAddress' field +func GetSyslogAddress() string { return global.GetSyslogAddress() } + +// SetSyslogAddress safely sets the value for global configuration 'SyslogAddress' field +func SetSyslogAddress(v string) { global.SetSyslogAddress(v) } + +// GetAdminAccountUsername safely fetches the Configuration value for state's 'AdminAccountUsername' field +func (st *ConfigState) GetAdminAccountUsername() (v string) { + st.mutex.Lock() + v = st.config.AdminAccountUsername + st.mutex.Unlock() + return +} + +// SetAdminAccountUsername safely sets the Configuration value for state's 'AdminAccountUsername' field +func (st *ConfigState) SetAdminAccountUsername(v string) { + st.mutex.Lock() + defer st.mutex.Unlock() + st.config.AdminAccountUsername = v + st.reloadToViper() +} + +// AdminAccountUsernameFlag returns the flag name for the 'AdminAccountUsername' field +func AdminAccountUsernameFlag() string { return "username" } + +// GetAdminAccountUsername safely fetches the value for global configuration 'AdminAccountUsername' field +func GetAdminAccountUsername() string { return global.GetAdminAccountUsername() } + +// SetAdminAccountUsername safely sets the value for global configuration 'AdminAccountUsername' field +func SetAdminAccountUsername(v string) { global.SetAdminAccountUsername(v) } + +// GetAdminAccountEmail safely fetches the Configuration value for state's 'AdminAccountEmail' field +func (st *ConfigState) GetAdminAccountEmail() (v string) { + st.mutex.Lock() + v = st.config.AdminAccountEmail + st.mutex.Unlock() + return +} + +// SetAdminAccountEmail safely sets the Configuration value for state's 'AdminAccountEmail' field +func (st *ConfigState) SetAdminAccountEmail(v string) { + st.mutex.Lock() + defer st.mutex.Unlock() + st.config.AdminAccountEmail = v + st.reloadToViper() +} + +// AdminAccountEmailFlag returns the flag name for the 'AdminAccountEmail' field +func AdminAccountEmailFlag() string { return "email" } + +// GetAdminAccountEmail safely fetches the value for global configuration 'AdminAccountEmail' field +func GetAdminAccountEmail() string { return global.GetAdminAccountEmail() } + +// SetAdminAccountEmail safely sets the value for global configuration 'AdminAccountEmail' field +func SetAdminAccountEmail(v string) { global.SetAdminAccountEmail(v) } + +// GetAdminAccountPassword safely fetches the Configuration value for state's 'AdminAccountPassword' field +func (st *ConfigState) GetAdminAccountPassword() (v string) { + st.mutex.Lock() + v = st.config.AdminAccountPassword + st.mutex.Unlock() + return +} + +// SetAdminAccountPassword safely sets the Configuration value for state's 'AdminAccountPassword' field +func (st *ConfigState) SetAdminAccountPassword(v string) { + st.mutex.Lock() + defer st.mutex.Unlock() + st.config.AdminAccountPassword = v + st.reloadToViper() +} + +// AdminAccountPasswordFlag returns the flag name for the 'AdminAccountPassword' field +func AdminAccountPasswordFlag() string { return "password" } + +// GetAdminAccountPassword safely fetches the value for global configuration 'AdminAccountPassword' field +func GetAdminAccountPassword() string { return global.GetAdminAccountPassword() } + +// SetAdminAccountPassword safely sets the value for global configuration 'AdminAccountPassword' field +func SetAdminAccountPassword(v string) { global.SetAdminAccountPassword(v) } + +// GetAdminTransPath safely fetches the Configuration value for state's 'AdminTransPath' field +func (st *ConfigState) GetAdminTransPath() (v string) { + st.mutex.Lock() + v = st.config.AdminTransPath + st.mutex.Unlock() + return +} + +// SetAdminTransPath safely sets the Configuration value for state's 'AdminTransPath' field +func (st *ConfigState) SetAdminTransPath(v string) { + st.mutex.Lock() + defer st.mutex.Unlock() + st.config.AdminTransPath = v + st.reloadToViper() +} + +// AdminTransPathFlag returns the flag name for the 'AdminTransPath' field +func AdminTransPathFlag() string { return "path" } + +// GetAdminTransPath safely fetches the value for global configuration 'AdminTransPath' field +func GetAdminTransPath() string { return global.GetAdminTransPath() } + +// SetAdminTransPath safely sets the value for global configuration 'AdminTransPath' field +func SetAdminTransPath(v string) { global.SetAdminTransPath(v) } diff --git a/internal/config/keys.go b/internal/config/keys.go @@ -1,182 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021-2022 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 <http://www.gnu.org/licenses/>. -*/ - -package config - -// KeyNames is a struct that just contains the names of configuration keys. -type KeyNames struct { - // root - LogLevel string - LogDbQueries string - ConfigPath string - - // general - ApplicationName string - Host string - AccountDomain string - Protocol string - BindAddress string - Port string - TrustedProxies string - SoftwareVersion string - - // database - DbType string - DbAddress string - DbPort string - DbUser string - DbPassword string - DbDatabase string - DbTLSMode string - DbTLSCACert string - - // template - WebTemplateBaseDir string - WebAssetBaseDir string - - // accounts - AccountsRegistrationOpen string - AccountsApprovalRequired string - AccountsReasonRequired string - - // media - MediaImageMaxSize string - MediaVideoMaxSize string - MediaDescriptionMinChars string - MediaDescriptionMaxChars string - MediaRemoteCacheDays string - - // storage - StorageBackend string - StorageLocalBasePath string - - // statuses - StatusesMaxChars string - StatusesCWMaxChars string - StatusesPollMaxOptions string - StatusesPollOptionMaxChars string - StatusesMediaMaxFiles string - - // letsencrypt - LetsEncryptEnabled string - LetsEncryptCertDir string - LetsEncryptEmailAddress string - LetsEncryptPort string - - // oidc - OIDCEnabled string - OIDCIdpName string - OIDCSkipVerification string - OIDCIssuer string - OIDCClientID string - OIDCClientSecret string - OIDCScopes string - - // smtp - SMTPHost string - SMTPPort string - SMTPUsername string - SMTPPassword string - SMTPFrom string - - // syslog - SyslogEnabled string - SyslogProtocol string - SyslogAddress string - - // admin - AdminAccountUsername string - AdminAccountEmail string - AdminAccountPassword string - AdminTransPath string -} - -// Keys contains the names of the various keys used for initializing and storing flag variables, -// and retrieving values from the viper config store. -var Keys = KeyNames{ - LogLevel: "log-level", - LogDbQueries: "log-db-queries", - ApplicationName: "application-name", - ConfigPath: "config-path", - Host: "host", - AccountDomain: "account-domain", - Protocol: "protocol", - BindAddress: "bind-address", - Port: "port", - TrustedProxies: "trusted-proxies", - SoftwareVersion: "software-version", - - DbType: "db-type", - DbAddress: "db-address", - DbPort: "db-port", - DbUser: "db-user", - DbPassword: "db-password", - DbDatabase: "db-database", - DbTLSMode: "db-tls-mode", - DbTLSCACert: "db-tls-ca-cert", - - WebTemplateBaseDir: "web-template-base-dir", - WebAssetBaseDir: "web-asset-base-dir", - - AccountsRegistrationOpen: "accounts-registration-open", - AccountsApprovalRequired: "accounts-approval-required", - AccountsReasonRequired: "accounts-reason-required", - - MediaImageMaxSize: "media-image-max-size", - MediaVideoMaxSize: "media-video-max-size", - MediaDescriptionMinChars: "media-description-min-chars", - MediaDescriptionMaxChars: "media-description-max-chars", - MediaRemoteCacheDays: "media-remote-cache-days", - - StorageBackend: "storage-backend", - StorageLocalBasePath: "storage-local-base-path", - - StatusesMaxChars: "statuses-max-chars", - StatusesCWMaxChars: "statuses-cw-max-chars", - StatusesPollMaxOptions: "statuses-poll-max-options", - StatusesPollOptionMaxChars: "statuses-poll-option-max-chars", - StatusesMediaMaxFiles: "statuses-media-max-files", - - LetsEncryptEnabled: "letsencrypt-enabled", - LetsEncryptPort: "letsencrypt-port", - LetsEncryptCertDir: "letsencrypt-cert-dir", - LetsEncryptEmailAddress: "letsencrypt-email-address", - - OIDCEnabled: "oidc-enabled", - OIDCIdpName: "oidc-idp-name", - OIDCSkipVerification: "oidc-skip-verification", - OIDCIssuer: "oidc-issuer", - OIDCClientID: "oidc-client-id", - OIDCClientSecret: "oidc-client-secret", - OIDCScopes: "oidc-scopes", - - SMTPHost: "smtp-host", - SMTPPort: "smtp-port", - SMTPUsername: "smtp-username", - SMTPPassword: "smtp-password", - SMTPFrom: "smtp-from", - - SyslogEnabled: "syslog-enabled", - SyslogProtocol: "syslog-protocol", - SyslogAddress: "syslog-address", - - AdminAccountUsername: "username", - AdminAccountEmail: "email", - AdminAccountPassword: "password", - AdminTransPath: "path", -} diff --git a/internal/config/state.go b/internal/config/state.go @@ -0,0 +1,136 @@ +/* + GoToSocial + Copyright (C) 2021-2022 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 <http://www.gnu.org/licenses/>. +*/ + +package config + +import ( + "strings" + "sync" + + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +// ConfigState manages safe concurrent access to Configuration{} values, +// and provides ease of linking them (including reloading) via viper to +// environment, CLI and configuration file variables. +type ConfigState struct { //nolint + viper *viper.Viper + config Configuration + mutex sync.Mutex +} + +// NewState returns a new initialized ConfigState instance. +func NewState() *ConfigState { + viper := viper.New() + + // Flag 'some-flag-name' becomes env var 'GTS_SOME_FLAG_NAME' + viper.SetEnvPrefix("gts") + viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_")) + + // Load appropriate named vals from env + viper.AutomaticEnv() + + // Create new ConfigState with defaults + state := &ConfigState{ + viper: viper, + config: Defaults, + } + + // Perform initial load into viper + state.reloadToViper() + + return state +} + +// Config provides safe access to the ConfigState's contained Configuration, +// and will reload the current Configuration back into viper settings. +func (st *ConfigState) Config(fn func(*Configuration)) { + st.mutex.Lock() + defer func() { + st.reloadToViper() + st.mutex.Unlock() + }() + fn(&st.config) +} + +// Viper provides safe access to the ConfigState's contained viper instance, +// and will reload the current viper setting state back into Configuration. +func (st *ConfigState) Viper(fn func(*viper.Viper)) { + st.mutex.Lock() + defer func() { + st.reloadFromViper() + st.mutex.Unlock() + }() + fn(st.viper) +} + +// LoadEarlyFlags will bind specific flags from given Cobra command to ConfigState's viper +// instance, and load the current configuration values. This is useful for flags like +// .ConfigPath which have to parsed first in order to perform early configuration load. +func (st *ConfigState) LoadEarlyFlags(cmd *cobra.Command) (err error) { + name := ConfigPathFlag() + flag := cmd.Flags().Lookup(name) + st.Viper(func(v *viper.Viper) { + err = v.BindPFlag(name, flag) + }) + return +} + +// BindFlags will bind given Cobra command's pflags to this ConfigState's viper instance. +func (st *ConfigState) BindFlags(cmd *cobra.Command) (err error) { + st.Viper(func(v *viper.Viper) { + err = v.BindPFlags(cmd.Flags()) + }) + return +} + +// Reload will reload the Configuration values from ConfigState's viper instance, and from file if set. +func (st *ConfigState) Reload() (err error) { + st.Viper(func(v *viper.Viper) { + if st.config.ConfigPath != "" { + // Ensure configuration path is set + v.SetConfigFile(st.config.ConfigPath) + + // Read in configuration from file + if err = v.ReadInConfig(); err != nil { + return + } + } + }) + return +} + +// reloadToViper will reload Configuration{} values into viper. +func (st *ConfigState) reloadToViper() { + raw, err := st.config.MarshalMap() + if err != nil { + panic(err) + } + if err := st.viper.MergeConfigMap(raw); err != nil { + panic(err) + } +} + +// reloadFromViper will reload Configuration{} values from viper. +func (st *ConfigState) reloadFromViper() { + err := st.config.UnmarshalMap(st.viper.AllSettings()) + if err != nil { + panic(err) + } +} diff --git a/internal/config/validate.go b/internal/config/validate.go @@ -24,7 +24,6 @@ import ( "strings" "github.com/sirupsen/logrus" - "github.com/spf13/viper" ) // Validate validates global config settings which don't have defaults, to make sure they are set sensibly. @@ -32,22 +31,21 @@ func Validate() error { errs := []error{} // host - if viper.GetString(Keys.Host) == "" { - errs = append(errs, fmt.Errorf("%s must be set", Keys.Host)) + if GetHost() == "" { + errs = append(errs, fmt.Errorf("%s must be set", HostFlag())) } // protocol - protocol := viper.GetString(Keys.Protocol) - switch protocol { + switch proto := GetProtocol(); proto { case "https": // no problem break case "http": - logrus.Warnf("%s was set to 'http'; this should *only* be used for debugging and tests!", Keys.Protocol) + logrus.Warnf("%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", Keys.Protocol)) + errs = append(errs, fmt.Errorf("%s must be set", ProtocolFlag())) default: - errs = append(errs, fmt.Errorf("%s must be set to either http or https, provided value was %s", Keys.Protocol, protocol)) + errs = append(errs, fmt.Errorf("%s must be set to either http or https, provided value was %s", ProtocolFlag(), proto)) } if len(errs) > 0 { diff --git a/internal/config/validate_test.go b/internal/config/validate_test.go @@ -21,7 +21,6 @@ package config_test import ( "testing" - "github.com/spf13/viper" "github.com/stretchr/testify/suite" "github.com/superseriousbusiness/gotosocial/internal/config" "github.com/superseriousbusiness/gotosocial/testrig" @@ -41,7 +40,7 @@ func (suite *ConfigValidateTestSuite) TestValidateConfigOK() { func (suite *ConfigValidateTestSuite) TestValidateConfigNoHost() { testrig.InitTestConfig() - viper.Set(config.Keys.Host, "") + config.SetHost("") err := config.Validate() suite.EqualError(err, "host must be set") @@ -50,7 +49,7 @@ func (suite *ConfigValidateTestSuite) TestValidateConfigNoHost() { func (suite *ConfigValidateTestSuite) TestValidateConfigNoProtocol() { testrig.InitTestConfig() - viper.Set(config.Keys.Protocol, "") + config.SetProtocol("") err := config.Validate() suite.EqualError(err, "protocol must be set") @@ -59,8 +58,8 @@ func (suite *ConfigValidateTestSuite) TestValidateConfigNoProtocol() { func (suite *ConfigValidateTestSuite) TestValidateConfigNoProtocolOrHost() { testrig.InitTestConfig() - viper.Set(config.Keys.Host, "") - viper.Set(config.Keys.Protocol, "") + config.SetHost("") + config.SetProtocol("") err := config.Validate() suite.EqualError(err, "host must be set; protocol must be set") @@ -69,7 +68,7 @@ func (suite *ConfigValidateTestSuite) TestValidateConfigNoProtocolOrHost() { func (suite *ConfigValidateTestSuite) TestValidateConfigBadProtocol() { testrig.InitTestConfig() - viper.Set(config.Keys.Protocol, "foo") + config.SetProtocol("foo") err := config.Validate() suite.EqualError(err, "protocol must be set to either http or https, provided value was foo") @@ -78,8 +77,8 @@ func (suite *ConfigValidateTestSuite) TestValidateConfigBadProtocol() { func (suite *ConfigValidateTestSuite) TestValidateConfigBadProtocolNoHost() { testrig.InitTestConfig() - viper.Set(config.Keys.Host, "") - viper.Set(config.Keys.Protocol, "foo") + config.SetHost("") + 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") diff --git a/internal/config/values.go b/internal/config/values.go @@ -1,93 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021-2022 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 <http://www.gnu.org/licenses/>. -*/ - -package config - -// Values contains contains the type of each configuration value. -type Values struct { - LogLevel string - LogDbQueries bool - ApplicationName string - ConfigPath string - Host string - AccountDomain string - Protocol string - BindAddress string - Port int - TrustedProxies []string - SoftwareVersion string - - DbType string - DbAddress string - DbPort int - DbUser string - DbPassword string - DbDatabase string - DbTLSMode string - DbTLSCACert string - - WebTemplateBaseDir string - WebAssetBaseDir string - - AccountsRegistrationOpen bool - AccountsApprovalRequired bool - AccountsReasonRequired bool - - MediaImageMaxSize int - MediaVideoMaxSize int - MediaDescriptionMinChars int - MediaDescriptionMaxChars int - MediaRemoteCacheDays int - - StorageBackend string - StorageLocalBasePath string - - StatusesMaxChars int - StatusesCWMaxChars int - StatusesPollMaxOptions int - StatusesPollOptionMaxChars int - StatusesMediaMaxFiles int - - LetsEncryptEnabled bool - LetsEncryptCertDir string - LetsEncryptEmailAddress string - LetsEncryptPort int - - OIDCEnabled bool - OIDCIdpName string - OIDCSkipVerification bool - OIDCIssuer string - OIDCClientID string - OIDCClientSecret string - OIDCScopes []string - - SMTPHost string - SMTPPort int - SMTPUsername string - SMTPPassword string - SMTPFrom string - - SyslogEnabled bool - SyslogProtocol string - SyslogAddress string - - AdminAccountUsername string - AdminAccountEmail string - AdminAccountPassword string - AdminTransPath string -} diff --git a/internal/config/viper.go b/internal/config/viper.go @@ -1,42 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021-2022 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 <http://www.gnu.org/licenses/>. -*/ - -package config - -import ( - "strings" - - "github.com/spf13/pflag" - "github.com/spf13/viper" -) - -func InitViper(f *pflag.FlagSet) error { - // environment variable stuff - // flag 'some-flag-name' becomes env var 'GTS_SOME_FLAG_NAME' - viper.SetEnvPrefix("gts") - viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_")) - viper.AutomaticEnv() - - // flag stuff - // bind all of the flags in flagset to viper so that we can retrieve their values from the viper store - if err := viper.BindPFlags(f); err != nil { - return err - } - - return nil -} diff --git a/internal/db/bundb/account.go b/internal/db/bundb/account.go @@ -25,7 +25,6 @@ import ( "strings" "time" - "github.com/spf13/viper" "github.com/superseriousbusiness/gotosocial/internal/cache" "github.com/superseriousbusiness/gotosocial/internal/config" "github.com/superseriousbusiness/gotosocial/internal/db" @@ -133,9 +132,8 @@ func (a *accountDB) GetInstanceAccount(ctx context.Context, domain string) (*gts Where("account.username = ?", domain). Where("account.domain = ?", domain) } else { - host := viper.GetString(config.Keys.Host) q = q. - Where("account.username = ?", host). + Where("account.username = ?", config.GetHost()). WhereGroup(" AND ", whereEmptyOrNull("domain")) } diff --git a/internal/db/bundb/admin.go b/internal/db/bundb/admin.go @@ -30,7 +30,6 @@ import ( "time" "github.com/sirupsen/logrus" - "github.com/spf13/viper" "github.com/superseriousbusiness/gotosocial/internal/ap" "github.com/superseriousbusiness/gotosocial/internal/config" @@ -178,7 +177,7 @@ func (a *adminDB) NewSignup(ctx context.Context, username string, reason string, } func (a *adminDB) CreateInstanceAccount(ctx context.Context) db.Error { - username := viper.GetString(config.Keys.Host) + username := config.GetHost() q := a.conn. NewSelect(). @@ -237,8 +236,8 @@ func (a *adminDB) CreateInstanceAccount(ctx context.Context) db.Error { } func (a *adminDB) CreateInstanceInstance(ctx context.Context) db.Error { - protocol := viper.GetString(config.Keys.Protocol) - host := viper.GetString(config.Keys.Host) + protocol := config.GetProtocol() + host := config.GetHost() // check if instance entry already exists q := a.conn. diff --git a/internal/db/bundb/bundb.go b/internal/db/bundb/bundb.go @@ -35,7 +35,6 @@ import ( "github.com/jackc/pgx/v4" "github.com/jackc/pgx/v4/stdlib" "github.com/sirupsen/logrus" - "github.com/spf13/viper" "github.com/superseriousbusiness/gotosocial/internal/cache" "github.com/superseriousbusiness/gotosocial/internal/config" "github.com/superseriousbusiness/gotosocial/internal/db" @@ -120,7 +119,7 @@ func doMigration(ctx context.Context, db *bun.DB) error { func NewBunDBService(ctx context.Context) (db.DB, error) { var conn *DBConn var err error - dbType := strings.ToLower(viper.GetString(config.Keys.DbType)) + dbType := strings.ToLower(config.GetDbType()) switch dbType { case dbTypePostgres: @@ -139,7 +138,7 @@ func NewBunDBService(ctx context.Context) (db.DB, error) { // add a hook to log queries and the time they take // only do this for logging where performance isn't 1st concern - if logrus.GetLevel() >= logrus.DebugLevel && viper.GetBool(config.Keys.LogDbQueries) { + if logrus.GetLevel() >= logrus.DebugLevel && config.GetLogDbQueries() { conn.DB.AddQueryHook(newDebugQueryHook()) } @@ -209,9 +208,9 @@ func NewBunDBService(ctx context.Context) (db.DB, error) { func sqliteConn(ctx context.Context) (*DBConn, error) { // validate db address has actually been set - dbAddress := viper.GetString(config.Keys.DbAddress) + dbAddress := config.GetDbAddress() if dbAddress == "" { - return nil, fmt.Errorf("'%s' was not set when attempting to start sqlite", config.Keys.DbAddress) + return nil, fmt.Errorf("'%s' was not set when attempting to start sqlite", config.DbAddressFlag()) } // Drop anything fancy from DB address @@ -282,27 +281,21 @@ func pgConn(ctx context.Context) (*DBConn, error) { // deriveBunDBPGOptions takes an application config and returns either a ready-to-use set of options // with sensible defaults, or an error if it's not satisfied by the provided config. func deriveBunDBPGOptions() (*pgx.ConnConfig, error) { - keys := config.Keys - - if strings.ToUpper(viper.GetString(keys.DbType)) != db.DBTypePostgres { - return nil, fmt.Errorf("expected db type of %s but got %s", db.DBTypePostgres, viper.GetString(keys.DbType)) + if strings.ToUpper(config.GetDbType()) != db.DBTypePostgres { + return nil, fmt.Errorf("expected db type of %s but got %s", db.DBTypePostgres, config.DbTypeFlag()) } // these are all optional, the db adapter figures out defaults - port := viper.GetInt(keys.DbPort) - address := viper.GetString(keys.DbAddress) - username := viper.GetString(keys.DbUser) - password := viper.GetString(keys.DbPassword) + address := config.GetDbAddress() // validate database - database := viper.GetString(keys.DbDatabase) + database := config.GetDbDatabase() if database == "" { return nil, errors.New("no database set") } var tlsConfig *tls.Config - tlsMode := viper.GetString(keys.DbTLSMode) - switch tlsMode { + switch config.GetDbTLSMode() { case dbTLSModeDisable, dbTLSModeUnset: break // nothing to do case dbTLSModeEnable: @@ -313,13 +306,12 @@ func deriveBunDBPGOptions() (*pgx.ConnConfig, error) { case dbTLSModeRequire: tlsConfig = &tls.Config{ InsecureSkipVerify: false, - ServerName: viper.GetString(keys.DbAddress), + ServerName: address, MinVersion: tls.VersionTLS12, } } - caCertPath := viper.GetString(keys.DbTLSCACert) - if tlsConfig != nil && caCertPath != "" { + if certPath := config.GetDbTLSCACert(); tlsConfig != nil && certPath != "" { // load the system cert pool first -- we'll append the given CA cert to this certPool, err := x509.SystemCertPool() if err != nil { @@ -327,24 +319,24 @@ func deriveBunDBPGOptions() (*pgx.ConnConfig, error) { } // open the file itself and make sure there's something in it - caCertBytes, err := os.ReadFile(caCertPath) + caCertBytes, err := os.ReadFile(certPath) if err != nil { - return nil, fmt.Errorf("error opening CA certificate at %s: %s", caCertPath, err) + return nil, fmt.Errorf("error opening CA certificate at %s: %s", certPath, err) } if len(caCertBytes) == 0 { - return nil, fmt.Errorf("ca cert at %s was empty", caCertPath) + return nil, fmt.Errorf("ca cert at %s was empty", certPath) } // make sure we have a PEM block caPem, _ := pem.Decode(caCertBytes) if caPem == nil { - return nil, fmt.Errorf("could not parse cert at %s into PEM", caCertPath) + return nil, fmt.Errorf("could not parse cert at %s into PEM", certPath) } // parse the PEM block into the certificate caCert, err := x509.ParseCertificate(caPem.Bytes) if err != nil { - return nil, fmt.Errorf("could not parse cert at %s into x509 certificate: %s", caCertPath, err) + return nil, fmt.Errorf("could not parse cert at %s into x509 certificate: %s", certPath, err) } // we're happy, add it to the existing pool and then use this pool in our tls config @@ -356,21 +348,21 @@ func deriveBunDBPGOptions() (*pgx.ConnConfig, error) { if address != "" { cfg.Host = address } - if port > 0 { + if port := config.GetPort(); port > 0 { cfg.Port = uint16(port) } - if username != "" { - cfg.User = username + if u := config.GetDbUser(); u != "" { + cfg.User = u } - if password != "" { - cfg.Password = password + if p := config.GetDbPassword(); p != "" { + cfg.Password = p } if tlsConfig != nil { cfg.TLSConfig = tlsConfig } cfg.Database = database cfg.PreferSimpleProtocol = true - cfg.RuntimeParams["application_name"] = viper.GetString(keys.ApplicationName) + cfg.RuntimeParams["application_name"] = config.GetApplicationName() return cfg, nil } @@ -387,8 +379,8 @@ func tweakConnectionValues(sqldb *sql.DB) { */ func (ps *bunDBService) TagStringsToTags(ctx context.Context, tags []string, originAccountID string) ([]*gtsmodel.Tag, error) { - protocol := viper.GetString(config.Keys.Protocol) - host := viper.GetString(config.Keys.Host) + protocol := config.GetProtocol() + host := config.GetHost() newTags := []*gtsmodel.Tag{} for _, t := range tags { diff --git a/internal/db/bundb/bundbnew_test.go b/internal/db/bundb/bundbnew_test.go @@ -22,7 +22,6 @@ import ( "context" "testing" - "github.com/spf13/viper" "github.com/stretchr/testify/suite" "github.com/superseriousbusiness/gotosocial/internal/config" "github.com/superseriousbusiness/gotosocial/internal/db/bundb" @@ -41,7 +40,7 @@ func (suite *BundbNewTestSuite) TestCreateNewDB() { func (suite *BundbNewTestSuite) TestCreateNewSqliteDBNoAddress() { // create a new db with no address specified - viper.Set(config.Keys.DbAddress, "") + config.SetDbAddress("") db, err := bundb.NewBunDBService(context.Background()) suite.EqualError(err, "'db-address' was not set when attempting to start sqlite") suite.Nil(db) diff --git a/internal/db/bundb/domain.go b/internal/db/bundb/domain.go @@ -23,7 +23,6 @@ import ( "net/url" "strings" - "github.com/spf13/viper" "github.com/superseriousbusiness/gotosocial/internal/config" "github.com/superseriousbusiness/gotosocial/internal/db" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" @@ -35,7 +34,7 @@ type domainDB struct { } func (d *domainDB) IsDomainBlocked(ctx context.Context, domain string) (bool, db.Error) { - if domain == "" || domain == viper.GetString(config.Keys.Host) { + if domain == "" || domain == config.GetHost() { return false, nil } diff --git a/internal/db/bundb/instance.go b/internal/db/bundb/instance.go @@ -22,7 +22,6 @@ import ( "context" "github.com/sirupsen/logrus" - "github.com/spf13/viper" "github.com/superseriousbusiness/gotosocial/internal/config" "github.com/superseriousbusiness/gotosocial/internal/db" @@ -41,8 +40,7 @@ func (i *instanceDB) CountInstanceUsers(ctx context.Context, domain string) (int Where("username != ?", domain). Where("? IS NULL", bun.Ident("suspended_at")) - host := viper.GetString(config.Keys.Host) - if domain == host { + if domain == config.GetHost() { // if the domain is *this* domain, just count where the domain field is null q = q.WhereGroup(" AND ", whereEmptyOrNull("domain")) } else { @@ -61,8 +59,7 @@ func (i *instanceDB) CountInstanceStatuses(ctx context.Context, domain string) ( NewSelect(). Model(&[]*gtsmodel.Status{}) - host := viper.GetString(config.Keys.Host) - if domain == host { + if domain == config.GetHost() { // if the domain is *this* domain, just count where local is true q = q.Where("local = ?", true) } else { @@ -83,8 +80,7 @@ func (i *instanceDB) CountInstanceDomains(ctx context.Context, domain string) (i NewSelect(). Model(&[]*gtsmodel.Instance{}) - host := viper.GetString(config.Keys.Host) - if domain == host { + if domain == config.GetHost() { // if the domain is *this* domain, just count other instances it knows about // exclude domains that are blocked q = q. diff --git a/internal/email/confirm.go b/internal/email/confirm.go @@ -23,7 +23,6 @@ import ( "net/smtp" "github.com/sirupsen/logrus" - "github.com/spf13/viper" "github.com/superseriousbusiness/gotosocial/internal/config" ) @@ -43,7 +42,7 @@ func (s *sender) SendConfirmEmail(toAddress string, data ConfirmData) error { if err != nil { return err } - logrus.WithField("func", "SendConfirmEmail").Trace(s.hostAddress + "\n" + viper.GetString(config.Keys.SMTPUsername) + ":password" + "\n" + s.from + "\n" + toAddress + "\n\n" + string(msg) + "\n") + logrus.WithField("func", "SendConfirmEmail").Trace(s.hostAddress + "\n" + config.GetSMTPUsername() + ":password" + "\n" + s.from + "\n" + toAddress + "\n\n" + string(msg) + "\n") return smtp.SendMail(s.hostAddress, s.auth, s.from, []string{toAddress}, msg) } diff --git a/internal/email/noopsender.go b/internal/email/noopsender.go @@ -23,7 +23,6 @@ import ( "text/template" "github.com/sirupsen/logrus" - "github.com/spf13/viper" "github.com/superseriousbusiness/gotosocial/internal/config" ) @@ -32,7 +31,7 @@ import ( // // Passing a nil function is also acceptable, in which case the send functions will just return nil. func NewNoopSender(sendCallback func(toAddress string, message string)) (Sender, error) { - templateBaseDir := viper.GetString(config.Keys.WebTemplateBaseDir) + templateBaseDir := config.GetWebTemplateBaseDir() t, err := loadTemplates(templateBaseDir) if err != nil { diff --git a/internal/email/sender.go b/internal/email/sender.go @@ -23,7 +23,6 @@ import ( "net/smtp" "text/template" - "github.com/spf13/viper" "github.com/superseriousbusiness/gotosocial/internal/config" ) @@ -38,19 +37,17 @@ type Sender interface { // NewSender returns a new email Sender interface with the given configuration, or an error if something goes wrong. func NewSender() (Sender, error) { - keys := config.Keys - - templateBaseDir := viper.GetString(keys.WebTemplateBaseDir) + templateBaseDir := config.GetWebTemplateBaseDir() t, err := loadTemplates(templateBaseDir) if err != nil { return nil, err } - username := viper.GetString(keys.SMTPUsername) - password := viper.GetString(keys.SMTPPassword) - host := viper.GetString(keys.SMTPHost) - port := viper.GetInt(keys.SMTPPort) - from := viper.GetString(keys.SMTPFrom) + username := config.GetSMTPUsername() + password := config.GetSMTPPassword() + host := config.GetSMTPHost() + port := config.GetSMTPPort() + from := config.GetSMTPFrom() return &sender{ hostAddress: fmt.Sprintf("%s:%d", host, port), diff --git a/internal/federation/authenticate.go b/internal/federation/authenticate.go @@ -29,7 +29,6 @@ import ( "strings" "github.com/sirupsen/logrus" - "github.com/spf13/viper" "github.com/go-fed/httpsig" "github.com/superseriousbusiness/activity/pub" @@ -168,8 +167,7 @@ func (f *federator) AuthenticateFederatedRequest(ctx context.Context, requestedU requestingRemoteAccount := &gtsmodel.Account{} requestingLocalAccount := &gtsmodel.Account{} requestingHost := requestingPublicKeyID.Host - host := viper.GetString(config.Keys.Host) - if strings.EqualFold(requestingHost, host) { + if host := config.GetHost(); strings.EqualFold(requestingHost, host) { // LOCAL ACCOUNT REQUEST // the request is coming from INSIDE THE HOUSE so skip the remote dereferencing l.Tracef("proceeding without dereference for local public key %s", requestingPublicKeyID) diff --git a/internal/federation/dereferencing/thread.go b/internal/federation/dereferencing/thread.go @@ -24,7 +24,6 @@ import ( "net/url" "github.com/sirupsen/logrus" - "github.com/spf13/viper" "github.com/superseriousbusiness/gotosocial/internal/ap" "github.com/superseriousbusiness/gotosocial/internal/config" "github.com/superseriousbusiness/gotosocial/internal/uris" @@ -45,8 +44,7 @@ func (d *deref) DereferenceThread(ctx context.Context, username string, statusIR l.Debug("entering DereferenceThread") // if it's our status we already have everything stashed so we can bail early - host := viper.GetString(config.Keys.Host) - if statusIRI.Host == host { + if statusIRI.Host == config.GetHost() { l.Debug("iri belongs to us, bailing") return nil } @@ -80,8 +78,7 @@ func (d *deref) iterateAncestors(ctx context.Context, username string, statusIRI l.Debug("entering iterateAncestors") // if it's our status we don't need to dereference anything so we can immediately move up the chain - host := viper.GetString(config.Keys.Host) - if statusIRI.Host == host { + if statusIRI.Host == config.GetHost() { l.Debug("iri belongs to us, moving up to next ancestor") // since this is our status, we know we can extract the id from the status path @@ -132,8 +129,7 @@ func (d *deref) iterateDescendants(ctx context.Context, username string, statusI l.Debug("entering iterateDescendants") // if it's our status we already have descendants stashed so we can bail early - host := viper.GetString(config.Keys.Host) - if statusIRI.Host == host { + if statusIRI.Host == config.GetHost() { l.Debug("iri belongs to us, bailing") return nil } @@ -209,8 +205,7 @@ pageLoop: continue } - host := viper.GetString(config.Keys.Host) - if itemURI.Host == host { + if itemURI.Host == config.GetHost() { // skip if the reply is from us -- we already have it then continue } diff --git a/internal/federation/federatingdb/inbox.go b/internal/federation/federatingdb/inbox.go @@ -23,7 +23,6 @@ import ( "fmt" "net/url" - "github.com/spf13/viper" "github.com/superseriousbusiness/activity/streams" "github.com/superseriousbusiness/activity/streams/vocab" "github.com/superseriousbusiness/gotosocial/internal/config" @@ -80,7 +79,7 @@ func (f *federatingDB) SetInbox(c context.Context, inbox vocab.ActivityStreamsOr // The library makes this call only after acquiring a lock first. func (f *federatingDB) InboxesForIRI(c context.Context, iri *url.URL) (inboxIRIs []*url.URL, err error) { // check if this is a followers collection iri for a local account... - if iri.Host == viper.GetString(config.Keys.Host) && uris.IsFollowersPath(iri) { + if iri.Host == config.GetHost() && uris.IsFollowersPath(iri) { localAccountUsername, err := uris.ParseFollowersPath(iri) if err != nil { return nil, fmt.Errorf("couldn't extract local account username from uri %s: %s", iri, err) diff --git a/internal/federation/federatingdb/owns.go b/internal/federation/federatingdb/owns.go @@ -24,7 +24,6 @@ import ( "net/url" "github.com/sirupsen/logrus" - "github.com/spf13/viper" "github.com/superseriousbusiness/gotosocial/internal/config" "github.com/superseriousbusiness/gotosocial/internal/db" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" @@ -44,8 +43,7 @@ func (f *federatingDB) Owns(ctx context.Context, id *url.URL) (bool, error) { l.Debug("entering Owns") // if the id host isn't this instance host, we don't own this IRI - host := viper.GetString(config.Keys.Host) - if id.Host != host { + if host := config.GetHost(); id.Host != host { l.Tracef("we DO NOT own activity because the host is %s not %s", id.Host, host) return false, nil } diff --git a/internal/federation/federatingdb/update.go b/internal/federation/federatingdb/update.go @@ -24,7 +24,6 @@ import ( "fmt" "github.com/sirupsen/logrus" - "github.com/spf13/viper" "github.com/superseriousbusiness/activity/streams/vocab" "github.com/superseriousbusiness/gotosocial/internal/ap" "github.com/superseriousbusiness/gotosocial/internal/config" @@ -125,8 +124,7 @@ func (f *federatingDB) Update(ctx context.Context, asType vocab.Type) error { return fmt.Errorf("UPDATE: error converting to account: %s", err) } - host := viper.GetString(config.Keys.Host) - if updatedAcct.Domain == host { + if updatedAcct.Domain == config.GetHost() { // no need to update local accounts // in fact, if we do this will break the shit out of things so do NOT return nil diff --git a/internal/federation/federatingdb/util.go b/internal/federation/federatingdb/util.go @@ -26,7 +26,6 @@ import ( "net/url" "github.com/sirupsen/logrus" - "github.com/spf13/viper" "github.com/superseriousbusiness/activity/streams" "github.com/superseriousbusiness/activity/streams/vocab" "github.com/superseriousbusiness/gotosocial/internal/ap" @@ -209,9 +208,7 @@ func (f *federatingDB) NewID(ctx context.Context, t vocab.Type) (idURL *url.URL, return nil, err } - protocol := viper.GetString(config.Keys.Protocol) - host := viper.GetString(config.Keys.Host) - return url.Parse(fmt.Sprintf("%s://%s/%s", protocol, host, newID)) + return url.Parse(fmt.Sprintf("%s://%s/%s", config.GetProtocol(), config.GetHost(), newID)) } // ActorForOutbox fetches the actor's IRI for the given outbox IRI. diff --git a/internal/log/log.go b/internal/log/log.go @@ -26,7 +26,6 @@ import ( "github.com/sirupsen/logrus" lSyslog "github.com/sirupsen/logrus/hooks/syslog" - "github.com/spf13/viper" "github.com/superseriousbusiness/gotosocial/internal/config" ) @@ -48,12 +47,9 @@ func Initialize() error { FullTimestamp: true, }) - keys := config.Keys - // check if a desired log level has been set - logLevel := viper.GetString(keys.LogLevel) - if logLevel != "" { - level, err := logrus.ParseLevel(logLevel) + if lvl := config.GetLogLevel(); lvl != "" { + level, err := logrus.ParseLevel(lvl) if err != nil { return err } @@ -65,9 +61,9 @@ func Initialize() error { } // check if syslog has been enabled, and configure it if so - if syslogEnabled := viper.GetBool(keys.SyslogEnabled); syslogEnabled { - protocol := viper.GetString(keys.SyslogProtocol) - address := viper.GetString(keys.SyslogAddress) + if config.GetSyslogEnabled() { + protocol := config.GetSyslogProtocol() + address := config.GetSyslogAddress() hook, err := lSyslog.NewSyslogHook(protocol, address, syslog.LOG_INFO, "") if err != nil { diff --git a/internal/log/syslog_test.go b/internal/log/syslog_test.go @@ -26,7 +26,6 @@ import ( "github.com/google/uuid" "github.com/sirupsen/logrus" - "github.com/spf13/viper" "github.com/stretchr/testify/suite" "github.com/superseriousbusiness/gotosocial/internal/config" "github.com/superseriousbusiness/gotosocial/testrig" @@ -45,9 +44,10 @@ type SyslogTestSuite struct { func (suite *SyslogTestSuite) SetupTest() { testrig.InitTestConfig() - viper.Set(config.Keys.SyslogEnabled, true) - viper.Set(config.Keys.SyslogProtocol, "udp") - viper.Set(config.Keys.SyslogAddress, "127.0.0.1:42069") + config.SetSyslogEnabled(true) + config.SetSyslogProtocol("udp") + config.SetSyslogAddress("127.0.0.1:42069") + server, channel, err := testrig.InitTestSyslog() if err != nil { panic(err) @@ -93,9 +93,10 @@ func (suite *SyslogTestSuite) TestSyslogLongMessageUnixgram() { syslogServer := server syslogChannel := channel - viper.Set(config.Keys.SyslogEnabled, true) - viper.Set(config.Keys.SyslogProtocol, "unixgram") - viper.Set(config.Keys.SyslogAddress, socketPath) + config.SetSyslogEnabled(true) + config.SetSyslogProtocol("unixgram") + config.SetSyslogAddress(socketPath) + testrig.InitTestLog() logrus.Warn(longMessage) diff --git a/internal/media/manager.go b/internal/media/manager.go @@ -26,7 +26,6 @@ import ( "codeberg.org/gruf/go-store/kv" "github.com/robfig/cron/v3" "github.com/sirupsen/logrus" - "github.com/spf13/viper" "github.com/superseriousbusiness/gotosocial/internal/concurrency" "github.com/superseriousbusiness/gotosocial/internal/config" "github.com/superseriousbusiness/gotosocial/internal/db" @@ -212,7 +211,7 @@ func scheduleCleanupJobs(m *manager) error { } // start remote cache cleanup cronjob if configured - if mediaRemoteCacheDays := viper.GetInt(config.Keys.MediaRemoteCacheDays); mediaRemoteCacheDays > 0 { + if mediaRemoteCacheDays := config.GetMediaRemoteCacheDays(); mediaRemoteCacheDays > 0 { if _, err := c.AddFunc("@midnight", func() { begin := time.Now() pruned, err := m.PruneAllRemote(pruneCtx, mediaRemoteCacheDays) diff --git a/internal/oidc/idp.go b/internal/oidc/idp.go @@ -23,7 +23,6 @@ import ( "fmt" "github.com/coreos/go-oidc/v3/oidc" - "github.com/spf13/viper" "github.com/superseriousbusiness/gotosocial/internal/config" "golang.org/x/oauth2" ) @@ -56,36 +55,33 @@ type idp struct { // is set to false, then nil, nil will be returned. If OIDCConfig.Enabled is true, // then the other OIDC config fields must also be set. func NewIDP(ctx context.Context) (IDP, error) { - keys := config.Keys - - oidcEnabled := viper.GetBool(keys.OIDCEnabled) - if !oidcEnabled { + if !config.GetOIDCEnabled() { // oidc isn't enabled so we don't need to do anything return nil, nil } // validate config fields - idpName := viper.GetString(keys.OIDCIdpName) + idpName := config.GetOIDCIdpName() if idpName == "" { return nil, fmt.Errorf("not set: IDPName") } - issuer := viper.GetString(keys.OIDCIssuer) + issuer := config.GetOIDCIssuer() if issuer == "" { return nil, fmt.Errorf("not set: Issuer") } - clientID := viper.GetString(keys.OIDCClientID) + clientID := config.GetOIDCClientID() if clientID == "" { return nil, fmt.Errorf("not set: ClientID") } - clientSecret := viper.GetString(keys.OIDCClientSecret) + clientSecret := config.GetOIDCClientSecret() if clientSecret == "" { return nil, fmt.Errorf("not set: ClientSecret") } - scopes := viper.GetStringSlice(keys.OIDCScopes) + scopes := config.GetOIDCScopes() if len(scopes) == 0 { return nil, fmt.Errorf("not set: Scopes") } @@ -95,8 +91,8 @@ func NewIDP(ctx context.Context) (IDP, error) { return nil, err } - protocol := viper.GetString(keys.Protocol) - host := viper.GetString(keys.Host) + protocol := config.GetProtocol() + host := config.GetHost() oauth2Config := oauth2.Config{ // client_id and client_secret of the client. @@ -120,8 +116,7 @@ func NewIDP(ctx context.Context) (IDP, error) { ClientID: clientID, } - skipVerification := viper.GetBool(keys.OIDCSkipVerification) - if skipVerification { + if config.GetOIDCSkipVerification() { oidcConf.SkipClientIDCheck = true oidcConf.SkipExpiryCheck = true oidcConf.SkipIssuerCheck = true diff --git a/internal/processing/account/create.go b/internal/processing/account/create.go @@ -23,7 +23,6 @@ import ( "fmt" "github.com/sirupsen/logrus" - "github.com/spf13/viper" "github.com/superseriousbusiness/gotosocial/internal/ap" apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" @@ -53,9 +52,8 @@ func (p *processor) Create(ctx context.Context, applicationToken oauth2.TokenInf return nil, fmt.Errorf("username %s in use", form.Username) } - keys := config.Keys - reasonRequired := viper.GetBool(keys.AccountsReasonRequired) - approvalRequired := viper.GetBool(keys.AccountsApprovalRequired) + reasonRequired := config.GetAccountsReasonRequired() + approvalRequired := config.GetAccountsApprovalRequired() // don't store a reason if we don't require one reason := form.Reason diff --git a/internal/processing/account/update.go b/internal/processing/account/update.go @@ -25,7 +25,6 @@ import ( "mime/multipart" "github.com/sirupsen/logrus" - "github.com/spf13/viper" "github.com/superseriousbusiness/gotosocial/internal/ap" apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" @@ -142,7 +141,7 @@ func (p *processor) Update(ctx context.Context, account *gtsmodel.Account, form // parsing and checking the image, and doing the necessary updates in the database for this to become // the account's new avatar image. func (p *processor) UpdateAvatar(ctx context.Context, avatar *multipart.FileHeader, accountID string) (*gtsmodel.MediaAttachment, error) { - maxImageSize := viper.GetInt(config.Keys.MediaImageMaxSize) + maxImageSize := config.GetMediaImageMaxSize() if int(avatar.Size) > maxImageSize { return nil, fmt.Errorf("UpdateAvatar: avatar with size %d exceeded max image size of %d bytes", avatar.Size, maxImageSize) } @@ -169,7 +168,7 @@ func (p *processor) UpdateAvatar(ctx context.Context, avatar *multipart.FileHead // parsing and checking the image, and doing the necessary updates in the database for this to become // the account's new header image. func (p *processor) UpdateHeader(ctx context.Context, header *multipart.FileHeader, accountID string) (*gtsmodel.MediaAttachment, error) { - maxImageSize := viper.GetInt(config.Keys.MediaImageMaxSize) + maxImageSize := config.GetMediaImageMaxSize() if int(header.Size) > maxImageSize { return nil, fmt.Errorf("UpdateHeader: header with size %d exceeded max image size of %d bytes", header.Size, maxImageSize) } diff --git a/internal/processing/blocks.go b/internal/processing/blocks.go @@ -23,7 +23,6 @@ import ( "fmt" "net/url" - "github.com/spf13/viper" apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" "github.com/superseriousbusiness/gotosocial/internal/config" "github.com/superseriousbusiness/gotosocial/internal/db" @@ -64,8 +63,8 @@ func (p *processor) packageBlocksResponse(accounts []*apimodel.Account, path str // prepare the next and previous links if len(accounts) != 0 { - protocol := viper.GetString(config.Keys.Protocol) - host := viper.GetString(config.Keys.Host) + protocol := config.GetProtocol() + host := config.GetHost() nextLink := &url.URL{ Scheme: protocol, diff --git a/internal/processing/federation/getnodeinfo.go b/internal/processing/federation/getnodeinfo.go @@ -23,7 +23,6 @@ import ( "fmt" "net/http" - "github.com/spf13/viper" apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" "github.com/superseriousbusiness/gotosocial/internal/config" "github.com/superseriousbusiness/gotosocial/internal/gtserror" @@ -40,8 +39,8 @@ var ( ) func (p *processor) GetNodeInfoRel(ctx context.Context, request *http.Request) (*apimodel.WellKnownResponse, gtserror.WithCode) { - protocol := viper.GetString(config.Keys.Protocol) - host := viper.GetString(config.Keys.Host) + protocol := config.GetProtocol() + host := config.GetHost() return &apimodel.WellKnownResponse{ Links: []apimodel.Link{ @@ -54,8 +53,8 @@ func (p *processor) GetNodeInfoRel(ctx context.Context, request *http.Request) ( } func (p *processor) GetNodeInfo(ctx context.Context, request *http.Request) (*apimodel.Nodeinfo, gtserror.WithCode) { - openRegistration := viper.GetBool(config.Keys.AccountsRegistrationOpen) - softwareVersion := viper.GetString(config.Keys.SoftwareVersion) + openRegistration := config.GetAccountsRegistrationOpen() + softwareVersion := config.GetSoftwareVersion() return &apimodel.Nodeinfo{ Version: nodeInfoVersion, diff --git a/internal/processing/federation/getwebfinger.go b/internal/processing/federation/getwebfinger.go @@ -22,7 +22,6 @@ import ( "context" "fmt" - "github.com/spf13/viper" apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" "github.com/superseriousbusiness/gotosocial/internal/config" "github.com/superseriousbusiness/gotosocial/internal/gtserror" @@ -43,9 +42,9 @@ func (p *processor) GetWebfingerAccount(ctx context.Context, requestedUsername s return nil, gtserror.NewErrorNotFound(fmt.Errorf("database error getting account with username %s: %s", requestedUsername, err)) } - accountDomain := viper.GetString(config.Keys.AccountDomain) + accountDomain := config.GetAccountDomain() if accountDomain == "" { - accountDomain = viper.GetString(config.Keys.Host) + accountDomain = config.GetHost() } // return the webfinger representation diff --git a/internal/processing/instance.go b/internal/processing/instance.go @@ -22,7 +22,6 @@ import ( "context" "fmt" - "github.com/spf13/viper" apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" "github.com/superseriousbusiness/gotosocial/internal/config" "github.com/superseriousbusiness/gotosocial/internal/db" @@ -49,7 +48,7 @@ func (p *processor) InstanceGet(ctx context.Context, domain string) (*apimodel.I func (p *processor) InstancePatch(ctx context.Context, form *apimodel.InstanceSettingsUpdateRequest) (*apimodel.Instance, gtserror.WithCode) { // fetch the instance entry from the db for processing i := &gtsmodel.Instance{} - host := viper.GetString(config.Keys.Host) + host := config.GetHost() if err := p.db.GetWhere(ctx, []db.Where{{Key: "domain", Value: host}}, i); err != nil { return nil, gtserror.NewErrorInternalError(fmt.Errorf("db error fetching instance %s: %s", host, err)) } diff --git a/internal/processing/search.go b/internal/processing/search.go @@ -25,7 +25,6 @@ import ( "strings" "github.com/sirupsen/logrus" - "github.com/spf13/viper" apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" "github.com/superseriousbusiness/gotosocial/internal/config" "github.com/superseriousbusiness/gotosocial/internal/db" @@ -166,8 +165,7 @@ func (p *processor) searchAccountByMention(ctx context.Context, authed *oauth.Au // if it's a local account we can skip a whole bunch of stuff maybeAcct := &gtsmodel.Account{} - host := viper.GetString(config.Keys.Host) - if domain == host { + if domain == config.GetHost() { maybeAcct, err = p.db.GetLocalAccountByUsername(ctx, username) if err != nil { return nil, fmt.Errorf("searchAccountByMention: error getting local account by username: %s", err) diff --git a/internal/processing/statustimeline.go b/internal/processing/statustimeline.go @@ -25,7 +25,6 @@ import ( "net/url" "github.com/sirupsen/logrus" - "github.com/spf13/viper" apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" "github.com/superseriousbusiness/gotosocial/internal/config" @@ -111,8 +110,8 @@ func StatusSkipInsertFunction() timeline.SkipInsertFunction { nextItemAccountID string, nextItemBoostOfID string, nextItemBoostOfAccountID string, - depth int) (bool, error) { - + depth int, + ) (bool, error) { // make sure we don't insert a duplicate if newItemID == nextItemID { return true, nil @@ -148,8 +147,8 @@ func (p *processor) packageStatusResponse(statuses []*apimodel.Status, path stri // prepare the next and previous links if len(statuses) != 0 { - protocol := viper.GetString(config.Keys.Protocol) - host := viper.GetString(config.Keys.Host) + protocol := config.GetProtocol() + host := config.GetHost() nextLink := &url.URL{ Scheme: protocol, diff --git a/internal/processing/user/emailconfirm.go b/internal/processing/user/emailconfirm.go @@ -25,7 +25,6 @@ import ( "time" "github.com/google/uuid" - "github.com/spf13/viper" "github.com/superseriousbusiness/gotosocial/internal/config" "github.com/superseriousbusiness/gotosocial/internal/db" "github.com/superseriousbusiness/gotosocial/internal/email" @@ -34,9 +33,7 @@ import ( "github.com/superseriousbusiness/gotosocial/internal/uris" ) -var ( - oneWeek = 168 * time.Hour -) +var oneWeek = 168 * time.Hour func (p *processor) SendConfirmEmail(ctx context.Context, user *gtsmodel.User, username string) error { if user.UnconfirmedEmail == "" || user.UnconfirmedEmail == user.Email { @@ -57,7 +54,7 @@ func (p *processor) SendConfirmEmail(ctx context.Context, user *gtsmodel.User, u // pull our instance entry from the database so we can greet the user nicely in the email instance := &gtsmodel.Instance{} - host := viper.GetString(config.Keys.Host) + host := config.GetHost() if err := p.db.GetWhere(ctx, []db.Where{{Key: "domain", Value: host}}, instance); err != nil { return fmt.Errorf("SendConfirmEmail: error getting instance: %s", err) } diff --git a/internal/router/router.go b/internal/router/router.go @@ -27,7 +27,6 @@ import ( "codeberg.org/gruf/go-debug" "github.com/gin-gonic/gin" "github.com/sirupsen/logrus" - "github.com/spf13/viper" "github.com/superseriousbusiness/gotosocial/internal/config" "github.com/superseriousbusiness/gotosocial/internal/db" "golang.org/x/crypto/acme/autocert" @@ -70,16 +69,12 @@ func (r *router) AttachStaticFS(relativePath string, fs http.FileSystem) { // Start starts the router nicely. It will serve two handlers if letsencrypt is enabled, and only the web/API handler if letsencrypt is not enabled. func (r *router) Start() { - var ( - keys = config.Keys + // listen is the server start function, by + // default pointing to regular HTTP listener, + // but updated to TLS if LetsEncrypt is enabled. + listen := r.srv.ListenAndServe - // listen is the server start function, by - // default pointing to regular HTTP listener, - // but updated to TLS if LetsEncrypt is enabled. - listen = r.srv.ListenAndServe - ) - - if viper.GetBool(keys.LetsEncryptEnabled) { + if config.GetLetsEncryptEnabled() { // LetsEncrypt support is enabled // Prepare an HTTPS-redirect handler for LetsEncrypt fallback @@ -97,8 +92,8 @@ func (r *router) Start() { srv := (*r.srv) //nolint srv.Handler = r.certManager.HTTPHandler(redirect) srv.Addr = fmt.Sprintf("%s:%d", - viper.GetString(keys.BindAddress), - viper.GetInt(keys.LetsEncryptPort), + config.GetBindAddress(), + config.GetLetsEncryptPort(), ) // Start the LetsEncrypt autocert manager HTTP server. @@ -144,8 +139,6 @@ func (r *router) Stop(ctx context.Context) error { // The given DB is only used in the New function for parsing config values, and is not otherwise // pinned to the router. func New(ctx context.Context, db db.DB) (Router, error) { - keys := config.Keys - gin.SetMode(gin.ReleaseMode) // create the actual engine here -- this is the core request routing handler for gts @@ -158,7 +151,7 @@ func New(ctx context.Context, db db.DB) (Router, error) { engine.MaxMultipartMemory = 8 << 20 // set up IP forwarding via x-forward-* headers. - trustedProxies := viper.GetStringSlice(keys.TrustedProxies) + trustedProxies := config.GetTrustedProxies() if err := engine.SetTrustedProxies(trustedProxies); err != nil { return nil, err } @@ -187,8 +180,8 @@ func New(ctx context.Context, db db.DB) (Router, error) { } // create the http server here, passing the gin engine as handler - bindAddress := viper.GetString(keys.BindAddress) - port := viper.GetInt(keys.Port) + bindAddress := config.GetBindAddress() + port := config.GetPort() listen := fmt.Sprintf("%s:%d", bindAddress, port) s := &http.Server{ Addr: listen, @@ -201,14 +194,14 @@ func New(ctx context.Context, db db.DB) (Router, error) { // We need to spawn the underlying server slightly differently depending on whether lets encrypt is enabled or not. // In either case, the gin engine will still be used for routing requests. - leEnabled := viper.GetBool(keys.LetsEncryptEnabled) + leEnabled := config.GetLetsEncryptEnabled() var m *autocert.Manager if leEnabled { // le IS enabled, so roll up an autocert manager for handling letsencrypt requests - host := viper.GetString(keys.Host) - leCertDir := viper.GetString(keys.LetsEncryptCertDir) - leEmailAddress := viper.GetString(keys.LetsEncryptEmailAddress) + host := config.GetHost() + leCertDir := config.GetLetsEncryptCertDir() + leEmailAddress := config.GetLetsEncryptEmailAddress() m = &autocert.Manager{ Prompt: autocert.AcceptTOS, HostPolicy: autocert.HostWhitelist(host), diff --git a/internal/router/session.go b/internal/router/session.go @@ -28,7 +28,6 @@ import ( "github.com/gin-contrib/sessions" "github.com/gin-contrib/sessions/memstore" "github.com/gin-gonic/gin" - "github.com/spf13/viper" "github.com/superseriousbusiness/gotosocial/internal/config" "github.com/superseriousbusiness/gotosocial/internal/db" "golang.org/x/net/idna" @@ -38,19 +37,19 @@ import ( func SessionOptions() sessions.Options { return sessions.Options{ Path: "/", - Domain: viper.GetString(config.Keys.Host), - MaxAge: 120, // 2 minutes - Secure: viper.GetString(config.Keys.Protocol) == "https", // only use cookie over https - HttpOnly: true, // exclude javascript from inspecting cookie - SameSite: http.SameSiteStrictMode, // https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-cookie-same-site-00#section-4.1.1 + Domain: config.GetHost(), + MaxAge: 120, // 2 minutes + Secure: config.GetProtocol() == "https", // only use cookie over https + HttpOnly: true, // exclude javascript from inspecting cookie + SameSite: http.SameSiteStrictMode, // https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-cookie-same-site-00#section-4.1.1 } } // SessionName is a utility function that derives an appropriate session name from the hostname. func SessionName() (string, error) { // parse the protocol + host - protocol := viper.GetString(config.Keys.Protocol) - host := viper.GetString(config.Keys.Host) + protocol := config.GetProtocol() + host := config.GetHost() u, err := url.Parse(fmt.Sprintf("%s://%s", protocol, host)) if err != nil { return "", err diff --git a/internal/router/session_test.go b/internal/router/session_test.go @@ -21,7 +21,6 @@ package router_test import ( "testing" - "github.com/spf13/viper" "github.com/stretchr/testify/suite" "github.com/superseriousbusiness/gotosocial/internal/config" "github.com/superseriousbusiness/gotosocial/internal/router" @@ -37,8 +36,8 @@ func (suite *SessionTestSuite) SetupTest() { } func (suite *SessionTestSuite) TestDeriveSessionNameLocalhostWithPort() { - viper.Set(config.Keys.Protocol, "http") - viper.Set(config.Keys.Host, "localhost:8080") + config.SetProtocol("http") + config.SetHost("localhost:8080") sessionName, err := router.SessionName() suite.NoError(err) @@ -46,8 +45,8 @@ func (suite *SessionTestSuite) TestDeriveSessionNameLocalhostWithPort() { } func (suite *SessionTestSuite) TestDeriveSessionNameLocalhost() { - viper.Set(config.Keys.Protocol, "http") - viper.Set(config.Keys.Host, "localhost") + config.SetProtocol("http") + config.SetHost("localhost") sessionName, err := router.SessionName() suite.NoError(err) @@ -55,8 +54,8 @@ func (suite *SessionTestSuite) TestDeriveSessionNameLocalhost() { } func (suite *SessionTestSuite) TestDeriveSessionNoProtocol() { - viper.Set(config.Keys.Protocol, "") - viper.Set(config.Keys.Host, "localhost") + config.SetProtocol("") + config.SetHost("localhost") sessionName, err := router.SessionName() suite.EqualError(err, "parse \"://localhost\": missing protocol scheme") @@ -64,9 +63,9 @@ func (suite *SessionTestSuite) TestDeriveSessionNoProtocol() { } func (suite *SessionTestSuite) TestDeriveSessionNoHost() { - viper.Set(config.Keys.Protocol, "https") - viper.Set(config.Keys.Host, "") - viper.Set(config.Keys.Port, 0) + config.SetProtocol("https") + config.SetHost("") + config.SetPort(0) sessionName, err := router.SessionName() suite.EqualError(err, "could not derive hostname without port from https://") @@ -74,8 +73,8 @@ func (suite *SessionTestSuite) TestDeriveSessionNoHost() { } func (suite *SessionTestSuite) TestDeriveSessionOK() { - viper.Set(config.Keys.Protocol, "https") - viper.Set(config.Keys.Host, "example.org") + config.SetProtocol("https") + config.SetHost("example.org") sessionName, err := router.SessionName() suite.NoError(err) @@ -83,8 +82,8 @@ func (suite *SessionTestSuite) TestDeriveSessionOK() { } func (suite *SessionTestSuite) TestDeriveSessionIDNOK() { - viper.Set(config.Keys.Protocol, "https") - viper.Set(config.Keys.Host, "fóid.org") + config.SetProtocol("https") + config.SetHost("fóid.org") sessionName, err := router.SessionName() suite.NoError(err) diff --git a/internal/router/template.go b/internal/router/template.go @@ -26,16 +26,15 @@ import ( "time" "github.com/gin-gonic/gin" - "github.com/spf13/viper" "github.com/superseriousbusiness/gotosocial/internal/api/model" "github.com/superseriousbusiness/gotosocial/internal/config" ) // LoadTemplates loads html templates for use by the given engine func loadTemplates(engine *gin.Engine) error { - templateBaseDir := viper.GetString(config.Keys.WebTemplateBaseDir) + templateBaseDir := config.GetWebTemplateBaseDir() if templateBaseDir == "" { - return fmt.Errorf("%s cannot be empty and must be a relative or absolute path", config.Keys.WebTemplateBaseDir) + return fmt.Errorf("%s cannot be empty and must be a relative or absolute path", config.WebTemplateBaseDirFlag()) } templateBaseDir, err := filepath.Abs(templateBaseDir) diff --git a/internal/transport/controller.go b/internal/transport/controller.go @@ -25,13 +25,11 @@ import ( "encoding/json" "fmt" "net/url" - "runtime/debug" "time" "codeberg.org/gruf/go-byteutil" "codeberg.org/gruf/go-cache/v2" "github.com/sirupsen/logrus" - "github.com/spf13/viper" "github.com/superseriousbusiness/activity/pub" "github.com/superseriousbusiness/activity/streams" "github.com/superseriousbusiness/gotosocial/internal/config" @@ -59,11 +57,9 @@ type controller struct { // NewController returns an implementation of the Controller interface for creating new transports func NewController(db db.DB, federatingDB federatingdb.DB, clock pub.Clock, client pub.HttpClient) Controller { - applicationName := viper.GetString(config.Keys.ApplicationName) - host := viper.GetString(config.Keys.Host) - - // Determine build information - build, _ := debug.ReadBuildInfo() + applicationName := config.GetApplicationName() + host := config.GetHost() + version := config.GetSoftwareVersion() c := &controller{ db: db, @@ -71,7 +67,7 @@ func NewController(db db.DB, federatingDB federatingdb.DB, clock pub.Clock, clie clock: clock, client: client, cache: cache.New[string, *transport](), - userAgent: fmt.Sprintf("%s; %s (gofed/activity gotosocial-%s)", applicationName, host, build.Main.Version), + userAgent: fmt.Sprintf("%s; %s (gofed/activity gotosocial-%s)", applicationName, host, version), } // Transport cache has TTL=1hr freq=1m @@ -128,7 +124,7 @@ func (c *controller) NewTransportForUsername(ctx context.Context, username strin // Otherwise, we can take the instance account and use those credentials to make the request. var u string if username == "" { - u = viper.GetString(config.Keys.Host) + u = config.GetHost() } else { u = username } diff --git a/internal/transport/deliver.go b/internal/transport/deliver.go @@ -27,7 +27,6 @@ import ( "strings" "sync" - "github.com/spf13/viper" "github.com/superseriousbusiness/gotosocial/internal/config" ) @@ -69,7 +68,7 @@ outer: func (t *transport) Deliver(ctx context.Context, b []byte, to *url.URL) error { // if the 'to' host is our own, just skip this delivery since we by definition already have the message! - if to.Host == viper.GetString(config.Keys.Host) || to.Host == viper.GetString(config.Keys.AccountDomain) { + if to.Host == config.GetHost() || to.Host == config.GetAccountDomain() { return nil } diff --git a/internal/transport/dereference.go b/internal/transport/dereference.go @@ -25,7 +25,6 @@ import ( "net/http" "net/url" - "github.com/spf13/viper" "github.com/superseriousbusiness/gotosocial/internal/config" "github.com/superseriousbusiness/gotosocial/internal/uris" ) @@ -33,7 +32,7 @@ import ( func (t *transport) Dereference(ctx context.Context, iri *url.URL) ([]byte, error) { // if the request is to us, we can shortcut for certain URIs rather than going through // the normal request flow, thereby saving time and energy - if iri.Host == viper.GetString(config.Keys.Host) { + if iri.Host == config.GetHost() { if uris.IsFollowersPath(iri) { // the request is for followers of one of our accounts, which we can shortcut return t.controller.dereferenceLocalFollowers(ctx, iri) diff --git a/internal/typeutils/internaltoas.go b/internal/typeutils/internaltoas.go @@ -26,7 +26,6 @@ import ( "net/url" "github.com/sirupsen/logrus" - "github.com/spf13/viper" "github.com/superseriousbusiness/activity/pub" "github.com/superseriousbusiness/activity/streams" "github.com/superseriousbusiness/activity/streams/vocab" @@ -629,9 +628,9 @@ func (c *converter) MentionToAS(ctx context.Context, m *gtsmodel.Mention) (vocab // name -- this should be the namestring of the mentioned user, something like @whatever@example.org var domain string if m.TargetAccount.Domain == "" { - accountDomain := viper.GetString(config.Keys.AccountDomain) + accountDomain := config.GetAccountDomain() if accountDomain == "" { - accountDomain = viper.GetString(config.Keys.Host) + accountDomain = config.GetHost() } domain = accountDomain } else { diff --git a/internal/typeutils/internaltofrontend.go b/internal/typeutils/internaltofrontend.go @@ -24,7 +24,6 @@ import ( "strings" "github.com/sirupsen/logrus" - "github.com/spf13/viper" "github.com/superseriousbusiness/gotosocial/internal/api/model" "github.com/superseriousbusiness/gotosocial/internal/config" @@ -575,9 +574,7 @@ func (c *converter) InstanceToAPIInstance(ctx context.Context, i *gtsmodel.Insta } // if the requested instance is *this* instance, we can add some extra information - keys := config.Keys - host := viper.GetString(keys.Host) - if i.Domain == host { + if host := config.GetHost(); i.Domain == host { userCount, err := c.db.CountInstanceUsers(ctx, host) if err == nil { mi.Stats["user_count"] = userCount @@ -593,14 +590,14 @@ func (c *converter) InstanceToAPIInstance(ctx context.Context, i *gtsmodel.Insta mi.Stats["domain_count"] = domainCount } - mi.Registrations = viper.GetBool(keys.AccountsRegistrationOpen) - mi.ApprovalRequired = viper.GetBool(keys.AccountsApprovalRequired) + mi.Registrations = config.GetAccountsRegistrationOpen() + mi.ApprovalRequired = config.GetAccountsApprovalRequired() mi.InvitesEnabled = false // TODO - mi.MaxTootChars = uint(viper.GetInt(keys.StatusesMaxChars)) + mi.MaxTootChars = uint(config.GetStatusesMaxChars()) mi.URLS = &model.InstanceURLs{ StreamingAPI: fmt.Sprintf("wss://%s", host), } - mi.Version = viper.GetString(keys.SoftwareVersion) + mi.Version = config.GetSoftwareVersion() } // get the instance account if it exists and just skip if it doesn't diff --git a/internal/uris/uri.go b/internal/uris/uri.go @@ -22,7 +22,6 @@ import ( "fmt" "net/url" - "github.com/spf13/viper" "github.com/superseriousbusiness/gotosocial/internal/config" "github.com/superseriousbusiness/gotosocial/internal/regexes" ) @@ -79,47 +78,47 @@ type UserURIs struct { // GenerateURIForFollow returns the AP URI for a new follow -- something like: // https://example.org/users/whatever_user/follow/01F7XTH1QGBAPMGF49WJZ91XGC func GenerateURIForFollow(username string, thisFollowID string) string { - protocol := viper.GetString(config.Keys.Protocol) - host := viper.GetString(config.Keys.Host) + protocol := config.GetProtocol() + host := config.GetHost() return fmt.Sprintf("%s://%s/%s/%s/%s/%s", protocol, host, UsersPath, username, FollowPath, thisFollowID) } // GenerateURIForLike returns the AP URI for a new like/fave -- something like: // https://example.org/users/whatever_user/liked/01F7XTH1QGBAPMGF49WJZ91XGC func GenerateURIForLike(username string, thisFavedID string) string { - protocol := viper.GetString(config.Keys.Protocol) - host := viper.GetString(config.Keys.Host) + protocol := config.GetProtocol() + host := config.GetHost() return fmt.Sprintf("%s://%s/%s/%s/%s/%s", protocol, host, UsersPath, username, LikedPath, thisFavedID) } // GenerateURIForUpdate returns the AP URI for a new update activity -- something like: // https://example.org/users/whatever_user#updates/01F7XTH1QGBAPMGF49WJZ91XGC func GenerateURIForUpdate(username string, thisUpdateID string) string { - protocol := viper.GetString(config.Keys.Protocol) - host := viper.GetString(config.Keys.Host) + protocol := config.GetProtocol() + host := config.GetHost() return fmt.Sprintf("%s://%s/%s/%s#%s/%s", protocol, host, UsersPath, username, UpdatePath, thisUpdateID) } // GenerateURIForBlock returns the AP URI for a new block activity -- something like: // https://example.org/users/whatever_user/blocks/01F7XTH1QGBAPMGF49WJZ91XGC func GenerateURIForBlock(username string, thisBlockID string) string { - protocol := viper.GetString(config.Keys.Protocol) - host := viper.GetString(config.Keys.Host) + protocol := config.GetProtocol() + host := config.GetHost() return fmt.Sprintf("%s://%s/%s/%s/%s/%s", protocol, host, UsersPath, username, BlocksPath, thisBlockID) } // GenerateURIForEmailConfirm returns a link for email confirmation -- something like: // https://example.org/confirm_email?token=490e337c-0162-454f-ac48-4b22bb92a205 func GenerateURIForEmailConfirm(token string) string { - protocol := viper.GetString(config.Keys.Protocol) - host := viper.GetString(config.Keys.Host) + protocol := config.GetProtocol() + host := config.GetHost() return fmt.Sprintf("%s://%s/%s?token=%s", protocol, host, ConfirmEmailPath, token) } // GenerateURIsForAccount throws together a bunch of URIs for the given username, with the given protocol and host. func GenerateURIsForAccount(username string) *UserURIs { - protocol := viper.GetString(config.Keys.Protocol) - host := viper.GetString(config.Keys.Host) + protocol := config.GetProtocol() + host := config.GetHost() // The below URLs are used for serving web requests hostURL := fmt.Sprintf("%s://%s", protocol, host) @@ -157,17 +156,15 @@ func GenerateURIsForAccount(username string) *UserURIs { // GenerateURIForAttachment generates a URI for an attachment/emoji/header etc. // Will produced something like https://example.org/fileserver/01FPST95B8FC3HG3AGCDKPQNQ2/attachment/original/01FPST9QK4V5XWS3F9Z4F2G1X7.gif func GenerateURIForAttachment(accountID string, mediaType string, mediaSize string, mediaID string, extension string) string { - protocol := viper.GetString(config.Keys.Protocol) - host := viper.GetString(config.Keys.Host) - + protocol := config.GetProtocol() + host := config.GetHost() return fmt.Sprintf("%s://%s/%s/%s/%s/%s/%s.%s", protocol, host, FileserverPath, accountID, mediaType, mediaSize, mediaID, extension) } // GenerateURIForEmoji generates an activitypub uri for a new emoji. func GenerateURIForEmoji(emojiID string) string { - protocol := viper.GetString(config.Keys.Protocol) - host := viper.GetString(config.Keys.Host) - + protocol := config.GetProtocol() + host := config.GetHost() return fmt.Sprintf("%s://%s/%s/%s", protocol, host, EmojiPath, emojiID) } diff --git a/internal/web/base.go b/internal/web/base.go @@ -27,7 +27,6 @@ import ( "github.com/gin-gonic/gin" "github.com/sirupsen/logrus" - "github.com/spf13/viper" "github.com/superseriousbusiness/gotosocial/internal/api" "github.com/superseriousbusiness/gotosocial/internal/config" "github.com/superseriousbusiness/gotosocial/internal/processing" @@ -54,9 +53,9 @@ type Module struct { // New returns a new api.ClientModule for web pages. func New(processor processing.Processor) (api.ClientModule, error) { - assetsBaseDir := viper.GetString(config.Keys.WebAssetBaseDir) + assetsBaseDir := config.GetWebAssetBaseDir() if assetsBaseDir == "" { - return nil, fmt.Errorf("%s cannot be empty and must be a relative or absolute path", config.Keys.WebAssetBaseDir) + return nil, fmt.Errorf("%s cannot be empty and must be a relative or absolute path", config.WebAssetBaseDirFlag()) } assetsPath, err := filepath.Abs(assetsBaseDir) @@ -106,7 +105,7 @@ func (m *Module) baseHandler(c *gin.Context) { l := logrus.WithField("func", "BaseGETHandler") l.Trace("serving index html") - host := viper.GetString(config.Keys.Host) + host := config.GetHost() instance, err := m.processor.InstanceGet(c.Request.Context(), host) if err != nil { l.Debugf("error getting instance from processor: %s", err) @@ -124,7 +123,7 @@ func (m *Module) NotFoundHandler(c *gin.Context) { l := logrus.WithField("func", "404") l.Trace("serving 404 html") - host := viper.GetString(config.Keys.Host) + host := config.GetHost() instance, err := m.processor.InstanceGet(c.Request.Context(), host) if err != nil { l.Debugf("error getting instance from processor: %s", err) diff --git a/internal/web/confirmemail.go b/internal/web/confirmemail.go @@ -23,7 +23,6 @@ import ( "github.com/gin-gonic/gin" "github.com/sirupsen/logrus" - "github.com/spf13/viper" "github.com/superseriousbusiness/gotosocial/internal/config" ) @@ -45,7 +44,7 @@ func (m *Module) confirmEmailGETHandler(c *gin.Context) { return } - host := viper.GetString(config.Keys.Host) + host := config.GetHost() instance, err := m.processor.InstanceGet(ctx, host) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) diff --git a/internal/web/profile.go b/internal/web/profile.go @@ -27,7 +27,6 @@ import ( "github.com/gin-gonic/gin" "github.com/sirupsen/logrus" - "github.com/spf13/viper" "github.com/superseriousbusiness/gotosocial/internal/ap" "github.com/superseriousbusiness/gotosocial/internal/api" "github.com/superseriousbusiness/gotosocial/internal/config" @@ -52,7 +51,7 @@ func (m *Module) profileTemplateHandler(c *gin.Context) { return } - instance, errWithCode := m.processor.InstanceGet(ctx, viper.GetString(config.Keys.Host)) + instance, errWithCode := m.processor.InstanceGet(ctx, config.GetHost()) if errWithCode != nil { l.Debugf("error getting instance from processor: %s", errWithCode.Error()) c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()}) diff --git a/internal/web/thread.go b/internal/web/thread.go @@ -23,7 +23,6 @@ import ( "strings" "github.com/sirupsen/logrus" - "github.com/spf13/viper" "github.com/gin-gonic/gin" "github.com/superseriousbusiness/gotosocial/internal/config" @@ -57,7 +56,7 @@ func (m *Module) threadTemplateHandler(c *gin.Context) { return } - host := viper.GetString(config.Keys.Host) + host := config.GetHost() instance, err := m.processor.InstanceGet(ctx, host) if err != nil { l.Debugf("error getting instance from processor: %s", err) diff --git a/test/cliparsing.sh b/test/cliparsing.sh @@ -5,7 +5,7 @@ set -e echo "STARTING CLI TESTS" echo "TEST_1 Make sure defaults are set correctly." -TEST_1_EXPECTED='{"account-domain":"","accounts-approval-required":true,"accounts-reason-required":true,"accounts-registration-open":true,"application-name":"gotosocial","bind-address":"0.0.0.0","config-path":"","db-address":"","db-database":"gotosocial","db-password":"","db-port":5432,"db-tls-ca-cert":"","db-tls-mode":"disable","db-type":"postgres","db-user":"","help":false,"host":"","letsencrypt-cert-dir":"/gotosocial/storage/certs","letsencrypt-email-address":"","letsencrypt-enabled":false,"letsencrypt-port":80,"log-db-queries":false,"log-level":"info","media-description-max-chars":500,"media-description-min-chars":0,"media-image-max-size":2097152,"media-remote-cache-days":30,"media-video-max-size":10485760,"oidc-client-id":"","oidc-client-secret":"","oidc-enabled":false,"oidc-idp-name":"","oidc-issuer":"","oidc-scopes":["openid","profile","email","groups"],"oidc-skip-verification":false,"port":8080,"protocol":"https","smtp-from":"GoToSocial","smtp-host":"","smtp-password":"","smtp-port":0,"smtp-username":"","software-version":"","statuses-cw-max-chars":100,"statuses-max-chars":5000,"statuses-media-max-files":6,"statuses-poll-max-options":6,"statuses-poll-option-max-chars":50,"storage-backend":"local","storage-local-base-path":"/gotosocial/storage","syslog-address":"localhost:514","syslog-enabled":false,"syslog-protocol":"udp","trusted-proxies":["127.0.0.1/32"],"web-asset-base-dir":"./web/assets/","web-template-base-dir":"./web/template/"}' +TEST_1_EXPECTED='{"account-domain":"","accounts-approval-required":true,"accounts-reason-required":true,"accounts-registration-open":true,"application-name":"gotosocial","bind-address":"0.0.0.0","config-path":"","db-address":"","db-database":"gotosocial","db-password":"","db-port":5432,"db-tls-ca-cert":"","db-tls-mode":"disable","db-type":"postgres","db-user":"","email":"","host":"","letsencrypt-cert-dir":"/gotosocial/storage/certs","letsencrypt-email-address":"","letsencrypt-enabled":false,"letsencrypt-port":80,"log-db-queries":false,"log-level":"info","media-description-max-chars":500,"media-description-min-chars":0,"media-image-max-size":2097152,"media-remote-cache-days":30,"media-video-max-size":10485760,"oidc-client-id":"","oidc-client-secret":"","oidc-enabled":false,"oidc-idp-name":"","oidc-issuer":"","oidc-scopes":["openid","profile","email","groups"],"oidc-skip-verification":false,"password":"","path":"","port":8080,"protocol":"https","smtp-from":"GoToSocial","smtp-host":"","smtp-password":"","smtp-port":0,"smtp-username":"","software-version":"","statuses-cw-max-chars":100,"statuses-max-chars":5000,"statuses-media-max-files":6,"statuses-poll-max-options":6,"statuses-poll-option-max-chars":50,"storage-backend":"local","storage-local-base-path":"/gotosocial/storage","syslog-address":"localhost:514","syslog-enabled":false,"syslog-protocol":"udp","trusted-proxies":["127.0.0.1/32"],"username":"","web-asset-base-dir":"./web/assets/","web-template-base-dir":"./web/template/"}' TEST_1="$(go run ./cmd/gotosocial/... debug config)" if [ "${TEST_1}" != "${TEST_1_EXPECTED}" ]; then echo "TEST_1 not equal TEST_1_EXPECTED" @@ -15,7 +15,7 @@ else fi echo "TEST_2 Override db-address from default using cli flag." -TEST_2_EXPECTED='{"account-domain":"","accounts-approval-required":true,"accounts-reason-required":true,"accounts-registration-open":true,"application-name":"gotosocial","bind-address":"0.0.0.0","config-path":"","db-address":"some.db.address","db-database":"gotosocial","db-password":"","db-port":5432,"db-tls-ca-cert":"","db-tls-mode":"disable","db-type":"postgres","db-user":"","help":false,"host":"","letsencrypt-cert-dir":"/gotosocial/storage/certs","letsencrypt-email-address":"","letsencrypt-enabled":false,"letsencrypt-port":80,"log-db-queries":false,"log-level":"info","media-description-max-chars":500,"media-description-min-chars":0,"media-image-max-size":2097152,"media-remote-cache-days":30,"media-video-max-size":10485760,"oidc-client-id":"","oidc-client-secret":"","oidc-enabled":false,"oidc-idp-name":"","oidc-issuer":"","oidc-scopes":["openid","profile","email","groups"],"oidc-skip-verification":false,"port":8080,"protocol":"https","smtp-from":"GoToSocial","smtp-host":"","smtp-password":"","smtp-port":0,"smtp-username":"","software-version":"","statuses-cw-max-chars":100,"statuses-max-chars":5000,"statuses-media-max-files":6,"statuses-poll-max-options":6,"statuses-poll-option-max-chars":50,"storage-backend":"local","storage-local-base-path":"/gotosocial/storage","syslog-address":"localhost:514","syslog-enabled":false,"syslog-protocol":"udp","trusted-proxies":["127.0.0.1/32"],"web-asset-base-dir":"./web/assets/","web-template-base-dir":"./web/template/"}' +TEST_2_EXPECTED='{"account-domain":"","accounts-approval-required":true,"accounts-reason-required":true,"accounts-registration-open":true,"application-name":"gotosocial","bind-address":"0.0.0.0","config-path":"","db-address":"some.db.address","db-database":"gotosocial","db-password":"","db-port":5432,"db-tls-ca-cert":"","db-tls-mode":"disable","db-type":"postgres","db-user":"","email":"","host":"","letsencrypt-cert-dir":"/gotosocial/storage/certs","letsencrypt-email-address":"","letsencrypt-enabled":false,"letsencrypt-port":80,"log-db-queries":false,"log-level":"info","media-description-max-chars":500,"media-description-min-chars":0,"media-image-max-size":2097152,"media-remote-cache-days":30,"media-video-max-size":10485760,"oidc-client-id":"","oidc-client-secret":"","oidc-enabled":false,"oidc-idp-name":"","oidc-issuer":"","oidc-scopes":["openid","profile","email","groups"],"oidc-skip-verification":false,"password":"","path":"","port":8080,"protocol":"https","smtp-from":"GoToSocial","smtp-host":"","smtp-password":"","smtp-port":0,"smtp-username":"","software-version":"","statuses-cw-max-chars":100,"statuses-max-chars":5000,"statuses-media-max-files":6,"statuses-poll-max-options":6,"statuses-poll-option-max-chars":50,"storage-backend":"local","storage-local-base-path":"/gotosocial/storage","syslog-address":"localhost:514","syslog-enabled":false,"syslog-protocol":"udp","trusted-proxies":["127.0.0.1/32"],"username":"","web-asset-base-dir":"./web/assets/","web-template-base-dir":"./web/template/"}' TEST_2="$(go run ./cmd/gotosocial/... --db-address some.db.address debug config)" if [ "${TEST_2}" != "${TEST_2_EXPECTED}" ]; then echo "TEST_2 not equal TEST_2_EXPECTED" @@ -25,7 +25,7 @@ else fi echo "TEST_3 Override db-address from default using env var." -TEST_3_EXPECTED='{"account-domain":"","accounts-approval-required":true,"accounts-reason-required":true,"accounts-registration-open":true,"application-name":"gotosocial","bind-address":"0.0.0.0","config-path":"","db-address":"some.db.address","db-database":"gotosocial","db-password":"","db-port":5432,"db-tls-ca-cert":"","db-tls-mode":"disable","db-type":"postgres","db-user":"","help":false,"host":"","letsencrypt-cert-dir":"/gotosocial/storage/certs","letsencrypt-email-address":"","letsencrypt-enabled":false,"letsencrypt-port":80,"log-db-queries":false,"log-level":"info","media-description-max-chars":500,"media-description-min-chars":0,"media-image-max-size":2097152,"media-remote-cache-days":30,"media-video-max-size":10485760,"oidc-client-id":"","oidc-client-secret":"","oidc-enabled":false,"oidc-idp-name":"","oidc-issuer":"","oidc-scopes":["openid","profile","email","groups"],"oidc-skip-verification":false,"port":8080,"protocol":"https","smtp-from":"GoToSocial","smtp-host":"","smtp-password":"","smtp-port":0,"smtp-username":"","software-version":"","statuses-cw-max-chars":100,"statuses-max-chars":5000,"statuses-media-max-files":6,"statuses-poll-max-options":6,"statuses-poll-option-max-chars":50,"storage-backend":"local","storage-local-base-path":"/gotosocial/storage","syslog-address":"localhost:514","syslog-enabled":false,"syslog-protocol":"udp","trusted-proxies":["127.0.0.1/32"],"web-asset-base-dir":"./web/assets/","web-template-base-dir":"./web/template/"}' +TEST_3_EXPECTED='{"account-domain":"","accounts-approval-required":true,"accounts-reason-required":true,"accounts-registration-open":true,"application-name":"gotosocial","bind-address":"0.0.0.0","config-path":"","db-address":"some.db.address","db-database":"gotosocial","db-password":"","db-port":5432,"db-tls-ca-cert":"","db-tls-mode":"disable","db-type":"postgres","db-user":"","email":"","host":"","letsencrypt-cert-dir":"/gotosocial/storage/certs","letsencrypt-email-address":"","letsencrypt-enabled":false,"letsencrypt-port":80,"log-db-queries":false,"log-level":"info","media-description-max-chars":500,"media-description-min-chars":0,"media-image-max-size":2097152,"media-remote-cache-days":30,"media-video-max-size":10485760,"oidc-client-id":"","oidc-client-secret":"","oidc-enabled":false,"oidc-idp-name":"","oidc-issuer":"","oidc-scopes":["openid","profile","email","groups"],"oidc-skip-verification":false,"password":"","path":"","port":8080,"protocol":"https","smtp-from":"GoToSocial","smtp-host":"","smtp-password":"","smtp-port":0,"smtp-username":"","software-version":"","statuses-cw-max-chars":100,"statuses-max-chars":5000,"statuses-media-max-files":6,"statuses-poll-max-options":6,"statuses-poll-option-max-chars":50,"storage-backend":"local","storage-local-base-path":"/gotosocial/storage","syslog-address":"localhost:514","syslog-enabled":false,"syslog-protocol":"udp","trusted-proxies":["127.0.0.1/32"],"username":"","web-asset-base-dir":"./web/assets/","web-template-base-dir":"./web/template/"}' TEST_3="$(GTS_DB_ADDRESS=some.db.address go run ./cmd/gotosocial/... debug config)" if [ "${TEST_3}" != "${TEST_3_EXPECTED}" ]; then echo "TEST_3 not equal TEST_3_EXPECTED" @@ -35,7 +35,7 @@ else fi echo "TEST_4 Override db-address from default using both env var and cli flag. The cli flag should take priority." -TEST_4_EXPECTED='{"account-domain":"","accounts-approval-required":true,"accounts-reason-required":true,"accounts-registration-open":true,"application-name":"gotosocial","bind-address":"0.0.0.0","config-path":"","db-address":"some.other.db.address","db-database":"gotosocial","db-password":"","db-port":5432,"db-tls-ca-cert":"","db-tls-mode":"disable","db-type":"postgres","db-user":"","help":false,"host":"","letsencrypt-cert-dir":"/gotosocial/storage/certs","letsencrypt-email-address":"","letsencrypt-enabled":false,"letsencrypt-port":80,"log-db-queries":false,"log-level":"info","media-description-max-chars":500,"media-description-min-chars":0,"media-image-max-size":2097152,"media-remote-cache-days":30,"media-video-max-size":10485760,"oidc-client-id":"","oidc-client-secret":"","oidc-enabled":false,"oidc-idp-name":"","oidc-issuer":"","oidc-scopes":["openid","profile","email","groups"],"oidc-skip-verification":false,"port":8080,"protocol":"https","smtp-from":"GoToSocial","smtp-host":"","smtp-password":"","smtp-port":0,"smtp-username":"","software-version":"","statuses-cw-max-chars":100,"statuses-max-chars":5000,"statuses-media-max-files":6,"statuses-poll-max-options":6,"statuses-poll-option-max-chars":50,"storage-backend":"local","storage-local-base-path":"/gotosocial/storage","syslog-address":"localhost:514","syslog-enabled":false,"syslog-protocol":"udp","trusted-proxies":["127.0.0.1/32"],"web-asset-base-dir":"./web/assets/","web-template-base-dir":"./web/template/"}' +TEST_4_EXPECTED='{"account-domain":"","accounts-approval-required":true,"accounts-reason-required":true,"accounts-registration-open":true,"application-name":"gotosocial","bind-address":"0.0.0.0","config-path":"","db-address":"some.other.db.address","db-database":"gotosocial","db-password":"","db-port":5432,"db-tls-ca-cert":"","db-tls-mode":"disable","db-type":"postgres","db-user":"","email":"","host":"","letsencrypt-cert-dir":"/gotosocial/storage/certs","letsencrypt-email-address":"","letsencrypt-enabled":false,"letsencrypt-port":80,"log-db-queries":false,"log-level":"info","media-description-max-chars":500,"media-description-min-chars":0,"media-image-max-size":2097152,"media-remote-cache-days":30,"media-video-max-size":10485760,"oidc-client-id":"","oidc-client-secret":"","oidc-enabled":false,"oidc-idp-name":"","oidc-issuer":"","oidc-scopes":["openid","profile","email","groups"],"oidc-skip-verification":false,"password":"","path":"","port":8080,"protocol":"https","smtp-from":"GoToSocial","smtp-host":"","smtp-password":"","smtp-port":0,"smtp-username":"","software-version":"","statuses-cw-max-chars":100,"statuses-max-chars":5000,"statuses-media-max-files":6,"statuses-poll-max-options":6,"statuses-poll-option-max-chars":50,"storage-backend":"local","storage-local-base-path":"/gotosocial/storage","syslog-address":"localhost:514","syslog-enabled":false,"syslog-protocol":"udp","trusted-proxies":["127.0.0.1/32"],"username":"","web-asset-base-dir":"./web/assets/","web-template-base-dir":"./web/template/"}' TEST_4="$(GTS_DB_ADDRESS=some.db.address go run ./cmd/gotosocial/... --db-address some.other.db.address debug config)" if [ "${TEST_4}" != "${TEST_4_EXPECTED}" ]; then echo "TEST_4 not equal TEST_4_EXPECTED" @@ -45,7 +45,7 @@ else fi echo "TEST_5 Test loading a config file by passing an env var." -TEST_5_EXPECTED='{"account-domain":"example.org","accounts-approval-required":true,"accounts-reason-required":true,"accounts-registration-open":true,"application-name":"gotosocial","bind-address":"0.0.0.0","config-path":"./test/test.yaml","db-address":"127.0.0.1","db-database":"postgres","db-password":"postgres","db-port":5432,"db-tls-ca-cert":"","db-tls-mode":"disable","db-type":"postgres","db-user":"postgres","help":false,"host":"gts.example.org","letsencrypt-cert-dir":"/gotosocial/storage/certs","letsencrypt-email-address":"","letsencrypt-enabled":true,"letsencrypt-port":80,"log-db-queries":false,"log-level":"info","media-description-max-chars":500,"media-description-min-chars":0,"media-image-max-size":2097152,"media-remote-cache-days":30,"media-video-max-size":10485760,"oidc-client-id":"","oidc-client-secret":"","oidc-enabled":false,"oidc-idp-name":"","oidc-issuer":"","oidc-scopes":["openid","email","profile","groups"],"oidc-skip-verification":false,"port":8080,"protocol":"https","smtp-from":"someone@example.org","smtp-host":"verycoolemailhost.mail","smtp-password":"smtp-password","smtp-port":8888,"smtp-username":"smtp-username","software-version":"","statuses-cw-max-chars":100,"statuses-max-chars":5000,"statuses-media-max-files":6,"statuses-poll-max-options":6,"statuses-poll-option-max-chars":50,"storage-backend":"local","storage-local-base-path":"/gotosocial/storage","syslog-address":"localhost:514","syslog-enabled":false,"syslog-protocol":"udp","trusted-proxies":["127.0.0.1/32","0.0.0.0/0"],"web-asset-base-dir":"./web/assets/","web-template-base-dir":"./web/template/"}' +TEST_5_EXPECTED='{"account-domain":"example.org","accounts-approval-required":true,"accounts-reason-required":true,"accounts-registration-open":true,"application-name":"gotosocial","bind-address":"0.0.0.0","config-path":"./test/test.yaml","db-address":"127.0.0.1","db-database":"postgres","db-password":"postgres","db-port":5432,"db-tls-ca-cert":"","db-tls-mode":"disable","db-type":"postgres","db-user":"postgres","email":"","host":"gts.example.org","letsencrypt-cert-dir":"/gotosocial/storage/certs","letsencrypt-email-address":"","letsencrypt-enabled":true,"letsencrypt-port":80,"log-db-queries":false,"log-level":"info","media-description-max-chars":500,"media-description-min-chars":0,"media-image-max-size":2097152,"media-remote-cache-days":30,"media-video-max-size":10485760,"oidc-client-id":"","oidc-client-secret":"","oidc-enabled":false,"oidc-idp-name":"","oidc-issuer":"","oidc-scopes":["openid","email","profile","groups"],"oidc-skip-verification":false,"password":"","path":"","port":8080,"protocol":"https","smtp-from":"someone@example.org","smtp-host":"verycoolemailhost.mail","smtp-password":"smtp-password","smtp-port":8888,"smtp-username":"smtp-username","software-version":"","statuses-cw-max-chars":100,"statuses-max-chars":5000,"statuses-media-max-files":6,"statuses-poll-max-options":6,"statuses-poll-option-max-chars":50,"storage-backend":"local","storage-local-base-path":"/gotosocial/storage","syslog-address":"localhost:514","syslog-enabled":false,"syslog-protocol":"udp","trusted-proxies":["127.0.0.1/32","0.0.0.0/0"],"username":"","web-asset-base-dir":"./web/assets/","web-template-base-dir":"./web/template/"}' TEST_5="$(GTS_CONFIG_PATH=./test/test.yaml go run ./cmd/gotosocial/... debug config)" if [ "${TEST_5}" != "${TEST_5_EXPECTED}" ]; then echo "TEST_5 not equal TEST_5_EXPECTED" @@ -55,7 +55,7 @@ else fi echo "TEST_6 Test loading a config file by passing cli flag." -TEST_6_EXPECTED='{"account-domain":"example.org","accounts-approval-required":true,"accounts-reason-required":true,"accounts-registration-open":true,"application-name":"gotosocial","bind-address":"0.0.0.0","config-path":"./test/test.yaml","db-address":"127.0.0.1","db-database":"postgres","db-password":"postgres","db-port":5432,"db-tls-ca-cert":"","db-tls-mode":"disable","db-type":"postgres","db-user":"postgres","help":false,"host":"gts.example.org","letsencrypt-cert-dir":"/gotosocial/storage/certs","letsencrypt-email-address":"","letsencrypt-enabled":true,"letsencrypt-port":80,"log-db-queries":false,"log-level":"info","media-description-max-chars":500,"media-description-min-chars":0,"media-image-max-size":2097152,"media-remote-cache-days":30,"media-video-max-size":10485760,"oidc-client-id":"","oidc-client-secret":"","oidc-enabled":false,"oidc-idp-name":"","oidc-issuer":"","oidc-scopes":["openid","email","profile","groups"],"oidc-skip-verification":false,"port":8080,"protocol":"https","smtp-from":"someone@example.org","smtp-host":"verycoolemailhost.mail","smtp-password":"smtp-password","smtp-port":8888,"smtp-username":"smtp-username","software-version":"","statuses-cw-max-chars":100,"statuses-max-chars":5000,"statuses-media-max-files":6,"statuses-poll-max-options":6,"statuses-poll-option-max-chars":50,"storage-backend":"local","storage-local-base-path":"/gotosocial/storage","syslog-address":"localhost:514","syslog-enabled":false,"syslog-protocol":"udp","trusted-proxies":["127.0.0.1/32","0.0.0.0/0"],"web-asset-base-dir":"./web/assets/","web-template-base-dir":"./web/template/"}' +TEST_6_EXPECTED='{"account-domain":"example.org","accounts-approval-required":true,"accounts-reason-required":true,"accounts-registration-open":true,"application-name":"gotosocial","bind-address":"0.0.0.0","config-path":"./test/test.yaml","db-address":"127.0.0.1","db-database":"postgres","db-password":"postgres","db-port":5432,"db-tls-ca-cert":"","db-tls-mode":"disable","db-type":"postgres","db-user":"postgres","email":"","host":"gts.example.org","letsencrypt-cert-dir":"/gotosocial/storage/certs","letsencrypt-email-address":"","letsencrypt-enabled":true,"letsencrypt-port":80,"log-db-queries":false,"log-level":"info","media-description-max-chars":500,"media-description-min-chars":0,"media-image-max-size":2097152,"media-remote-cache-days":30,"media-video-max-size":10485760,"oidc-client-id":"","oidc-client-secret":"","oidc-enabled":false,"oidc-idp-name":"","oidc-issuer":"","oidc-scopes":["openid","email","profile","groups"],"oidc-skip-verification":false,"password":"","path":"","port":8080,"protocol":"https","smtp-from":"someone@example.org","smtp-host":"verycoolemailhost.mail","smtp-password":"smtp-password","smtp-port":8888,"smtp-username":"smtp-username","software-version":"","statuses-cw-max-chars":100,"statuses-max-chars":5000,"statuses-media-max-files":6,"statuses-poll-max-options":6,"statuses-poll-option-max-chars":50,"storage-backend":"local","storage-local-base-path":"/gotosocial/storage","syslog-address":"localhost:514","syslog-enabled":false,"syslog-protocol":"udp","trusted-proxies":["127.0.0.1/32","0.0.0.0/0"],"username":"","web-asset-base-dir":"./web/assets/","web-template-base-dir":"./web/template/"}' TEST_6="$(go run ./cmd/gotosocial/... --config-path ./test/test.yaml debug config)" if [ "${TEST_6}" != "${TEST_6_EXPECTED}" ]; then echo "TEST_6 not equal TEST_6_EXPECTED" @@ -65,7 +65,7 @@ else fi echo "TEST_7 Test loading a config file and overriding one of the variables with a cli flag." -TEST_7_EXPECTED='{"account-domain":"","accounts-approval-required":true,"accounts-reason-required":true,"accounts-registration-open":true,"application-name":"gotosocial","bind-address":"0.0.0.0","config-path":"./test/test.yaml","db-address":"127.0.0.1","db-database":"postgres","db-password":"postgres","db-port":5432,"db-tls-ca-cert":"","db-tls-mode":"disable","db-type":"postgres","db-user":"postgres","help":false,"host":"gts.example.org","letsencrypt-cert-dir":"/gotosocial/storage/certs","letsencrypt-email-address":"","letsencrypt-enabled":true,"letsencrypt-port":80,"log-db-queries":false,"log-level":"info","media-description-max-chars":500,"media-description-min-chars":0,"media-image-max-size":2097152,"media-remote-cache-days":30,"media-video-max-size":10485760,"oidc-client-id":"","oidc-client-secret":"","oidc-enabled":false,"oidc-idp-name":"","oidc-issuer":"","oidc-scopes":["openid","email","profile","groups"],"oidc-skip-verification":false,"port":8080,"protocol":"https","smtp-from":"someone@example.org","smtp-host":"verycoolemailhost.mail","smtp-password":"smtp-password","smtp-port":8888,"smtp-username":"smtp-username","software-version":"","statuses-cw-max-chars":100,"statuses-max-chars":5000,"statuses-media-max-files":6,"statuses-poll-max-options":6,"statuses-poll-option-max-chars":50,"storage-backend":"local","storage-local-base-path":"/gotosocial/storage","syslog-address":"localhost:514","syslog-enabled":false,"syslog-protocol":"udp","trusted-proxies":["127.0.0.1/32","0.0.0.0/0"],"web-asset-base-dir":"./web/assets/","web-template-base-dir":"./web/template/"}' +TEST_7_EXPECTED='{"account-domain":"","accounts-approval-required":true,"accounts-reason-required":true,"accounts-registration-open":true,"application-name":"gotosocial","bind-address":"0.0.0.0","config-path":"./test/test.yaml","db-address":"127.0.0.1","db-database":"postgres","db-password":"postgres","db-port":5432,"db-tls-ca-cert":"","db-tls-mode":"disable","db-type":"postgres","db-user":"postgres","email":"","host":"gts.example.org","letsencrypt-cert-dir":"/gotosocial/storage/certs","letsencrypt-email-address":"","letsencrypt-enabled":true,"letsencrypt-port":80,"log-db-queries":false,"log-level":"info","media-description-max-chars":500,"media-description-min-chars":0,"media-image-max-size":2097152,"media-remote-cache-days":30,"media-video-max-size":10485760,"oidc-client-id":"","oidc-client-secret":"","oidc-enabled":false,"oidc-idp-name":"","oidc-issuer":"","oidc-scopes":["openid","email","profile","groups"],"oidc-skip-verification":false,"password":"","path":"","port":8080,"protocol":"https","smtp-from":"someone@example.org","smtp-host":"verycoolemailhost.mail","smtp-password":"smtp-password","smtp-port":8888,"smtp-username":"smtp-username","software-version":"","statuses-cw-max-chars":100,"statuses-max-chars":5000,"statuses-media-max-files":6,"statuses-poll-max-options":6,"statuses-poll-option-max-chars":50,"storage-backend":"local","storage-local-base-path":"/gotosocial/storage","syslog-address":"localhost:514","syslog-enabled":false,"syslog-protocol":"udp","trusted-proxies":["127.0.0.1/32","0.0.0.0/0"],"username":"","web-asset-base-dir":"./web/assets/","web-template-base-dir":"./web/template/"}' TEST_7="$(go run ./cmd/gotosocial/... --config-path ./test/test.yaml --account-domain '' debug config)" if [ "${TEST_7}" != "${TEST_7_EXPECTED}" ]; then echo "TEST_7 not equal TEST_7_EXPECTED" @@ -75,7 +75,7 @@ else fi echo "TEST_8 Test loading a config file and overriding one of the variables with an env var." -TEST_8_EXPECTED='{"account-domain":"peepee","accounts-approval-required":true,"accounts-reason-required":true,"accounts-registration-open":true,"application-name":"gotosocial","bind-address":"0.0.0.0","config-path":"./test/test.yaml","db-address":"127.0.0.1","db-database":"postgres","db-password":"postgres","db-port":5432,"db-tls-ca-cert":"","db-tls-mode":"disable","db-type":"postgres","db-user":"postgres","help":false,"host":"gts.example.org","letsencrypt-cert-dir":"/gotosocial/storage/certs","letsencrypt-email-address":"","letsencrypt-enabled":true,"letsencrypt-port":80,"log-db-queries":false,"log-level":"info","media-description-max-chars":500,"media-description-min-chars":0,"media-image-max-size":2097152,"media-remote-cache-days":30,"media-video-max-size":10485760,"oidc-client-id":"","oidc-client-secret":"","oidc-enabled":false,"oidc-idp-name":"","oidc-issuer":"","oidc-scopes":["openid","email","profile","groups"],"oidc-skip-verification":false,"port":8080,"protocol":"https","smtp-from":"someone@example.org","smtp-host":"verycoolemailhost.mail","smtp-password":"smtp-password","smtp-port":8888,"smtp-username":"smtp-username","software-version":"","statuses-cw-max-chars":100,"statuses-max-chars":5000,"statuses-media-max-files":6,"statuses-poll-max-options":6,"statuses-poll-option-max-chars":50,"storage-backend":"local","storage-local-base-path":"/gotosocial/storage","syslog-address":"localhost:514","syslog-enabled":false,"syslog-protocol":"udp","trusted-proxies":["127.0.0.1/32","0.0.0.0/0"],"web-asset-base-dir":"./web/assets/","web-template-base-dir":"./web/template/"}' +TEST_8_EXPECTED='{"account-domain":"peepee","accounts-approval-required":true,"accounts-reason-required":true,"accounts-registration-open":true,"application-name":"gotosocial","bind-address":"0.0.0.0","config-path":"./test/test.yaml","db-address":"127.0.0.1","db-database":"postgres","db-password":"postgres","db-port":5432,"db-tls-ca-cert":"","db-tls-mode":"disable","db-type":"postgres","db-user":"postgres","email":"","host":"gts.example.org","letsencrypt-cert-dir":"/gotosocial/storage/certs","letsencrypt-email-address":"","letsencrypt-enabled":true,"letsencrypt-port":80,"log-db-queries":false,"log-level":"info","media-description-max-chars":500,"media-description-min-chars":0,"media-image-max-size":2097152,"media-remote-cache-days":30,"media-video-max-size":10485760,"oidc-client-id":"","oidc-client-secret":"","oidc-enabled":false,"oidc-idp-name":"","oidc-issuer":"","oidc-scopes":["openid","email","profile","groups"],"oidc-skip-verification":false,"password":"","path":"","port":8080,"protocol":"https","smtp-from":"someone@example.org","smtp-host":"verycoolemailhost.mail","smtp-password":"smtp-password","smtp-port":8888,"smtp-username":"smtp-username","software-version":"","statuses-cw-max-chars":100,"statuses-max-chars":5000,"statuses-media-max-files":6,"statuses-poll-max-options":6,"statuses-poll-option-max-chars":50,"storage-backend":"local","storage-local-base-path":"/gotosocial/storage","syslog-address":"localhost:514","syslog-enabled":false,"syslog-protocol":"udp","trusted-proxies":["127.0.0.1/32","0.0.0.0/0"],"username":"","web-asset-base-dir":"./web/assets/","web-template-base-dir":"./web/template/"}' TEST_8="$(GTS_ACCOUNT_DOMAIN='peepee' go run ./cmd/gotosocial/... --config-path ./test/test.yaml debug config)" if [ "${TEST_8}" != "${TEST_8_EXPECTED}" ]; then echo "TEST_8 not equal TEST_8_EXPECTED" @@ -85,7 +85,7 @@ else fi echo "TEST_9 Test loading a config file and overriding one of the variables with both an env var and a cli flag. The cli flag should have priority." -TEST_9_EXPECTED='{"account-domain":"","accounts-approval-required":true,"accounts-reason-required":true,"accounts-registration-open":true,"application-name":"gotosocial","bind-address":"0.0.0.0","config-path":"./test/test.yaml","db-address":"127.0.0.1","db-database":"postgres","db-password":"postgres","db-port":5432,"db-tls-ca-cert":"","db-tls-mode":"disable","db-type":"postgres","db-user":"postgres","help":false,"host":"gts.example.org","letsencrypt-cert-dir":"/gotosocial/storage/certs","letsencrypt-email-address":"","letsencrypt-enabled":true,"letsencrypt-port":80,"log-db-queries":false,"log-level":"info","media-description-max-chars":500,"media-description-min-chars":0,"media-image-max-size":2097152,"media-remote-cache-days":30,"media-video-max-size":10485760,"oidc-client-id":"","oidc-client-secret":"","oidc-enabled":false,"oidc-idp-name":"","oidc-issuer":"","oidc-scopes":["openid","email","profile","groups"],"oidc-skip-verification":false,"port":8080,"protocol":"https","smtp-from":"someone@example.org","smtp-host":"verycoolemailhost.mail","smtp-password":"smtp-password","smtp-port":8888,"smtp-username":"smtp-username","software-version":"","statuses-cw-max-chars":100,"statuses-max-chars":5000,"statuses-media-max-files":6,"statuses-poll-max-options":6,"statuses-poll-option-max-chars":50,"storage-backend":"local","storage-local-base-path":"/gotosocial/storage","syslog-address":"localhost:514","syslog-enabled":false,"syslog-protocol":"udp","trusted-proxies":["127.0.0.1/32","0.0.0.0/0"],"web-asset-base-dir":"./web/assets/","web-template-base-dir":"./web/template/"}' +TEST_9_EXPECTED='{"account-domain":"","accounts-approval-required":true,"accounts-reason-required":true,"accounts-registration-open":true,"application-name":"gotosocial","bind-address":"0.0.0.0","config-path":"./test/test.yaml","db-address":"127.0.0.1","db-database":"postgres","db-password":"postgres","db-port":5432,"db-tls-ca-cert":"","db-tls-mode":"disable","db-type":"postgres","db-user":"postgres","email":"","host":"gts.example.org","letsencrypt-cert-dir":"/gotosocial/storage/certs","letsencrypt-email-address":"","letsencrypt-enabled":true,"letsencrypt-port":80,"log-db-queries":false,"log-level":"info","media-description-max-chars":500,"media-description-min-chars":0,"media-image-max-size":2097152,"media-remote-cache-days":30,"media-video-max-size":10485760,"oidc-client-id":"","oidc-client-secret":"","oidc-enabled":false,"oidc-idp-name":"","oidc-issuer":"","oidc-scopes":["openid","email","profile","groups"],"oidc-skip-verification":false,"password":"","path":"","port":8080,"protocol":"https","smtp-from":"someone@example.org","smtp-host":"verycoolemailhost.mail","smtp-password":"smtp-password","smtp-port":8888,"smtp-username":"smtp-username","software-version":"","statuses-cw-max-chars":100,"statuses-max-chars":5000,"statuses-media-max-files":6,"statuses-poll-max-options":6,"statuses-poll-option-max-chars":50,"storage-backend":"local","storage-local-base-path":"/gotosocial/storage","syslog-address":"localhost:514","syslog-enabled":false,"syslog-protocol":"udp","trusted-proxies":["127.0.0.1/32","0.0.0.0/0"],"username":"","web-asset-base-dir":"./web/assets/","web-template-base-dir":"./web/template/"}' TEST_9="$(GTS_ACCOUNT_DOMAIN='peepee' go run ./cmd/gotosocial/... --config-path ./test/test.yaml --account-domain '' debug config)" if [ "${TEST_9}" != "${TEST_9_EXPECTED}" ]; then echo "TEST_9 not equal TEST_9_EXPECTED" @@ -95,7 +95,7 @@ else fi echo "TEST_10 Test loading a config file from json." -TEST_10_EXPECTED='{"account-domain":"example.org","accounts-approval-required":true,"accounts-reason-required":true,"accounts-registration-open":true,"application-name":"gotosocial","bind-address":"0.0.0.0","config-path":"./test/test.json","db-address":"127.0.0.1","db-database":"postgres","db-password":"postgres","db-port":5432,"db-tls-ca-cert":"","db-tls-mode":"disable","db-type":"postgres","db-user":"postgres","help":false,"host":"gts.example.org","letsencrypt-cert-dir":"/gotosocial/storage/certs","letsencrypt-email-address":"","letsencrypt-enabled":true,"letsencrypt-port":80,"log-db-queries":false,"log-level":"info","media-description-max-chars":500,"media-description-min-chars":0,"media-image-max-size":2097152,"media-remote-cache-days":30,"media-video-max-size":10485760,"oidc-client-id":"","oidc-client-secret":"","oidc-enabled":false,"oidc-idp-name":"","oidc-issuer":"","oidc-scopes":["openid","email","profile","groups"],"oidc-skip-verification":false,"port":8080,"protocol":"https","smtp-from":"someone@example.org","smtp-host":"verycoolemailhost.mail","smtp-password":"smtp-password","smtp-port":8888,"smtp-username":"smtp-username","software-version":"","statuses-cw-max-chars":100,"statuses-max-chars":5000,"statuses-media-max-files":6,"statuses-poll-max-options":6,"statuses-poll-option-max-chars":50,"storage-backend":"local","storage-local-base-path":"/gotosocial/storage","syslog-address":"localhost:514","syslog-enabled":false,"syslog-protocol":"udp","trusted-proxies":["127.0.0.1/32","0.0.0.0/0"],"web-asset-base-dir":"./web/assets/","web-template-base-dir":"./web/template/"}' +TEST_10_EXPECTED='{"account-domain":"example.org","accounts-approval-required":true,"accounts-reason-required":true,"accounts-registration-open":true,"application-name":"gotosocial","bind-address":"0.0.0.0","config-path":"./test/test.json","db-address":"127.0.0.1","db-database":"postgres","db-password":"postgres","db-port":5432,"db-tls-ca-cert":"","db-tls-mode":"disable","db-type":"postgres","db-user":"postgres","email":"","host":"gts.example.org","letsencrypt-cert-dir":"/gotosocial/storage/certs","letsencrypt-email-address":"","letsencrypt-enabled":true,"letsencrypt-port":80,"log-db-queries":false,"log-level":"info","media-description-max-chars":500,"media-description-min-chars":0,"media-image-max-size":2097152,"media-remote-cache-days":30,"media-video-max-size":10485760,"oidc-client-id":"","oidc-client-secret":"","oidc-enabled":false,"oidc-idp-name":"","oidc-issuer":"","oidc-scopes":["openid","email","profile","groups"],"oidc-skip-verification":false,"password":"","path":"","port":8080,"protocol":"https","smtp-from":"someone@example.org","smtp-host":"verycoolemailhost.mail","smtp-password":"smtp-password","smtp-port":8888,"smtp-username":"smtp-username","software-version":"","statuses-cw-max-chars":100,"statuses-max-chars":5000,"statuses-media-max-files":6,"statuses-poll-max-options":6,"statuses-poll-option-max-chars":50,"storage-backend":"local","storage-local-base-path":"/gotosocial/storage","syslog-address":"localhost:514","syslog-enabled":false,"syslog-protocol":"udp","trusted-proxies":["127.0.0.1/32","0.0.0.0/0"],"username":"","web-asset-base-dir":"./web/assets/","web-template-base-dir":"./web/template/"}' TEST_10="$(go run ./cmd/gotosocial/... --config-path ./test/test.json debug config)" if [ "${TEST_10}" != "${TEST_10_EXPECTED}" ]; then echo "TEST_10 not equal TEST_10_EXPECTED" @@ -105,7 +105,7 @@ else fi echo "TEST_11 Test loading a partial config file. Default values should be used apart from those set in the config file." -TEST_11_EXPECTED='{"account-domain":"peepee.poopoo","accounts-approval-required":true,"accounts-reason-required":true,"accounts-registration-open":true,"application-name":"gotosocial","bind-address":"0.0.0.0","config-path":"./test/test2.yaml","db-address":"","db-database":"gotosocial","db-password":"","db-port":5432,"db-tls-ca-cert":"","db-tls-mode":"disable","db-type":"postgres","db-user":"","help":false,"host":"","letsencrypt-cert-dir":"/gotosocial/storage/certs","letsencrypt-email-address":"","letsencrypt-enabled":false,"letsencrypt-port":80,"log-db-queries":false,"log-level":"trace","media-description-max-chars":500,"media-description-min-chars":0,"media-image-max-size":2097152,"media-remote-cache-days":30,"media-video-max-size":10485760,"oidc-client-id":"","oidc-client-secret":"","oidc-enabled":false,"oidc-idp-name":"","oidc-issuer":"","oidc-scopes":["openid","profile","email","groups"],"oidc-skip-verification":false,"port":8080,"protocol":"https","smtp-from":"GoToSocial","smtp-host":"","smtp-password":"","smtp-port":0,"smtp-username":"","software-version":"","statuses-cw-max-chars":100,"statuses-max-chars":5000,"statuses-media-max-files":6,"statuses-poll-max-options":6,"statuses-poll-option-max-chars":50,"storage-backend":"local","storage-local-base-path":"/gotosocial/storage","syslog-address":"localhost:514","syslog-enabled":false,"syslog-protocol":"udp","trusted-proxies":["127.0.0.1/32"],"web-asset-base-dir":"./web/assets/","web-template-base-dir":"./web/template/"}' +TEST_11_EXPECTED='{"account-domain":"peepee.poopoo","accounts-approval-required":true,"accounts-reason-required":true,"accounts-registration-open":true,"application-name":"gotosocial","bind-address":"0.0.0.0","config-path":"./test/test2.yaml","db-address":"","db-database":"gotosocial","db-password":"","db-port":5432,"db-tls-ca-cert":"","db-tls-mode":"disable","db-type":"postgres","db-user":"","email":"","host":"","letsencrypt-cert-dir":"/gotosocial/storage/certs","letsencrypt-email-address":"","letsencrypt-enabled":false,"letsencrypt-port":80,"log-db-queries":false,"log-level":"trace","media-description-max-chars":500,"media-description-min-chars":0,"media-image-max-size":2097152,"media-remote-cache-days":30,"media-video-max-size":10485760,"oidc-client-id":"","oidc-client-secret":"","oidc-enabled":false,"oidc-idp-name":"","oidc-issuer":"","oidc-scopes":["openid","profile","email","groups"],"oidc-skip-verification":false,"password":"","path":"","port":8080,"protocol":"https","smtp-from":"GoToSocial","smtp-host":"","smtp-password":"","smtp-port":0,"smtp-username":"","software-version":"","statuses-cw-max-chars":100,"statuses-max-chars":5000,"statuses-media-max-files":6,"statuses-poll-max-options":6,"statuses-poll-option-max-chars":50,"storage-backend":"local","storage-local-base-path":"/gotosocial/storage","syslog-address":"localhost:514","syslog-enabled":false,"syslog-protocol":"udp","trusted-proxies":["127.0.0.1/32"],"username":"","web-asset-base-dir":"./web/assets/","web-template-base-dir":"./web/template/"}' TEST_11="$(go run ./cmd/gotosocial/... --config-path ./test/test2.yaml debug config)" if [ "${TEST_11}" != "${TEST_11_EXPECTED}" ]; then echo "TEST_11 not equal TEST_11_EXPECTED" diff --git a/testrig/config.go b/testrig/config.go @@ -19,45 +19,19 @@ package testrig import ( - "reflect" - "github.com/coreos/go-oidc/v3/oidc" - "github.com/spf13/viper" "github.com/superseriousbusiness/gotosocial/internal/config" ) -// InitTestConfig resets + initializes the viper configuration with test defaults. +// InitTestConfig initializes viper configuration with test defaults. func InitTestConfig() { - // reset viper to an empty state - viper.Reset() - - // get the field names of config.Keys - keyFields := reflect.VisibleFields(reflect.TypeOf(config.Keys)) - - // get the field values of config.Keys - keyValues := reflect.ValueOf(config.Keys) - - // get the field values of TestDefaults - testDefaults := reflect.ValueOf(TestDefaults) - - // for each config field... - for _, field := range keyFields { - // the viper config key should be the value of the key - key, ok := keyValues.FieldByName(field.Name).Interface().(string) - if !ok { - panic("could not convert config.Keys value to string") - } - - // the value should be the test default corresponding to the given fieldName - value := testDefaults.FieldByName(field.Name).Interface() - - // actually set the value in viper -- this will override everything - viper.Set(key, value) - } + config.Config(func(cfg *config.Configuration) { + *cfg = TestDefaults + }) } // TestDefaults returns a Values struct with values set that are suitable for local testing. -var TestDefaults = config.Values{ +var TestDefaults = config.Configuration{ LogLevel: "trace", LogDbQueries: true, ApplicationName: "gotosocial", diff --git a/testrig/db.go b/testrig/db.go @@ -24,7 +24,6 @@ import ( "strconv" "github.com/sirupsen/logrus" - "github.com/spf13/viper" "github.com/superseriousbusiness/gotosocial/internal/config" "github.com/superseriousbusiness/gotosocial/internal/db" "github.com/superseriousbusiness/gotosocial/internal/db/bundb" @@ -69,11 +68,15 @@ var testModels = []interface{}{ // value as the port instead. func NewTestDB() db.DB { if alternateAddress := os.Getenv("GTS_DB_ADDRESS"); alternateAddress != "" { - viper.Set(config.Keys.DbAddress, alternateAddress) + config.Config(func(cfg *config.Configuration) { + cfg.DbAddress = alternateAddress + }) } if alternateDBType := os.Getenv("GTS_DB_TYPE"); alternateDBType != "" { - viper.Set(config.Keys.DbType, alternateDBType) + config.Config(func(cfg *config.Configuration) { + cfg.DbType = alternateDBType + }) } if alternateDBPort := os.Getenv("GTS_DB_PORT"); alternateDBPort != "" { @@ -81,7 +84,9 @@ func NewTestDB() db.DB { if err != nil { panic(err) } - viper.Set(config.Keys.DbPort, port) + config.Config(func(cfg *config.Configuration) { + cfg.DbPort = int(port) + }) } testDB, err := bundb.NewBunDBService(context.Background()) diff --git a/testrig/email.go b/testrig/email.go @@ -19,7 +19,6 @@ package testrig import ( - "github.com/spf13/viper" "github.com/superseriousbusiness/gotosocial/internal/config" "github.com/superseriousbusiness/gotosocial/internal/email" ) @@ -30,7 +29,9 @@ import ( // the map, with email address of the recipient as the key, and the value as the // parsed email message as it would have been sent. func NewEmailSender(templateBaseDir string, sentEmails map[string]string) email.Sender { - viper.Set(config.Keys.WebTemplateBaseDir, templateBaseDir) + config.Config(func(cfg *config.Configuration) { + cfg.WebTemplateBaseDir = templateBaseDir + }) var sendCallback func(toAddress string, message string) diff --git a/testrig/router.go b/testrig/router.go @@ -26,7 +26,6 @@ import ( "runtime" "github.com/gin-gonic/gin" - "github.com/spf13/viper" "github.com/superseriousbusiness/gotosocial/internal/config" "github.com/superseriousbusiness/gotosocial/internal/db" "github.com/superseriousbusiness/gotosocial/internal/router" @@ -38,11 +37,13 @@ import ( // value as the template base directory instead. func NewTestRouter(db db.DB) router.Router { if alternativeTemplateBaseDir := os.Getenv("GTS_WEB_TEMPLATE_BASE_DIR"); alternativeTemplateBaseDir != "" { - viper.Set(config.Keys.WebTemplateBaseDir, alternativeTemplateBaseDir) + config.Config(func(cfg *config.Configuration) { + cfg.WebTemplateBaseDir = alternativeTemplateBaseDir + }) } if alternativeBindAddress := os.Getenv("GTS_BIND_ADDRESS"); alternativeBindAddress != "" { - viper.Set(config.Keys.BindAddress, alternativeBindAddress) + config.SetBindAddress(alternativeBindAddress) } r, err := router.New(context.Background(), db) @@ -56,7 +57,7 @@ func NewTestRouter(db db.DB) router.Router { func ConfigureTemplatesWithGin(engine *gin.Engine) { router.LoadTemplateFunctions(engine) - templateBaseDir := viper.GetString(config.Keys.WebTemplateBaseDir) + templateBaseDir := config.GetWebTemplateBaseDir() if !filepath.IsAbs(templateBaseDir) { // https://stackoverflow.com/questions/31873396/is-it-possible-to-get-the-current-root-of-package-structure-as-a-string-in-golan