diff options
Diffstat (limited to 'cmd/gotosocial')
28 files changed, 1415 insertions, 925 deletions
diff --git a/cmd/gotosocial/accountsflags.go b/cmd/gotosocial/accountsflags.go deleted file mode 100644 index 9f9be266d..000000000 --- a/cmd/gotosocial/accountsflags.go +++ /dev/null @@ -1,47 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see <http://www.gnu.org/licenses/>. -*/ - -package main - -import ( - "github.com/superseriousbusiness/gotosocial/internal/config" - "github.com/urfave/cli/v2" -) - -func accountsFlags(flagNames, envNames config.Flags, defaults config.Defaults) []cli.Flag { - return []cli.Flag{ - &cli.BoolFlag{ - Name: flagNames.AccountsOpenRegistration, - Usage: "Allow anyone to submit an account signup request. If false, server will be invite-only.", - Value: defaults.AccountsOpenRegistration, - EnvVars: []string{envNames.AccountsOpenRegistration}, - }, - &cli.BoolFlag{ - Name: flagNames.AccountsApprovalRequired, - Usage: "Do account signups require approval by an admin or moderator before user can log in? If false, new registrations will be automatically approved.", - Value: defaults.AccountsRequireApproval, - EnvVars: []string{envNames.AccountsApprovalRequired}, - }, - &cli.BoolFlag{ - Name: flagNames.AccountsReasonRequired, - Usage: "Do new account signups require a reason to be submitted on registration?", - Value: defaults.AccountsReasonRequired, - EnvVars: []string{envNames.AccountsReasonRequired}, - }, - } -} diff --git a/cmd/gotosocial/commands.go b/cmd/gotosocial/action/action.go index 9b61a66ec..315b30de6 100644 --- a/cmd/gotosocial/commands.go +++ b/cmd/gotosocial/action/action.go @@ -16,22 +16,13 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. */ -package main +package action import ( - "github.com/urfave/cli/v2" + "context" ) -func getCommands(allFlags []cli.Flag) []*cli.Command { - commands := []*cli.Command{} - commandSets := [][]*cli.Command{ - serverCommands(allFlags), - adminCommands(allFlags), - testrigCommands(allFlags), - } - for _, cs := range commandSets { - commands = append(commands, cs...) - } - - return commands -} +// GTSAction defines one *action* that can be taken by the gotosocial cli command. +// This can be either a long-running action (like server start) or something +// shorter like db init or db inspect. +type GTSAction func(context.Context) error diff --git a/cmd/gotosocial/action/admin/account/account.go b/cmd/gotosocial/action/admin/account/account.go new file mode 100644 index 000000000..6b1029712 --- /dev/null +++ b/cmd/gotosocial/action/admin/account/account.go @@ -0,0 +1,258 @@ +/* + GoToSocial + Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +package account + +import ( + "context" + "errors" + "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" + "github.com/superseriousbusiness/gotosocial/internal/db/bundb" + "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" + "github.com/superseriousbusiness/gotosocial/internal/validate" + "golang.org/x/crypto/bcrypt" +) + +// Create creates a new account in the database using the provided flags. +var Create action.GTSAction = func(ctx context.Context) error { + dbConn, err := bundb.NewBunDBService(ctx) + if err != nil { + return fmt.Errorf("error creating dbservice: %s", err) + } + + username := viper.GetString(config.Keys.AdminAccountUsername) + if username == "" { + return errors.New("no username set") + } + if err := validate.Username(username); err != nil { + return err + } + + email := viper.GetString(config.Keys.AdminAccountEmail) + if email == "" { + return errors.New("no email set") + } + if err := validate.Email(email); err != nil { + return err + } + + password := viper.GetString(config.Keys.AdminAccountPassword) + if password == "" { + return errors.New("no password set") + } + if err := validate.NewPassword(password); err != nil { + return err + } + + _, err = dbConn.NewSignup(ctx, username, "", false, email, password, nil, "", "", false, false) + if err != nil { + return err + } + + return dbConn.Stop(ctx) +} + +// Confirm sets a user to Approved, sets Email to the current UnconfirmedEmail value, and sets ConfirmedAt to now. +var Confirm action.GTSAction = func(ctx context.Context) error { + dbConn, err := bundb.NewBunDBService(ctx) + if err != nil { + return fmt.Errorf("error creating dbservice: %s", err) + } + + username := viper.GetString(config.Keys.AdminAccountUsername) + if username == "" { + return errors.New("no username set") + } + if err := validate.Username(username); err != nil { + return err + } + + a, err := dbConn.GetLocalAccountByUsername(ctx, username) + if err != nil { + return err + } + + u := >smodel.User{} + if err := dbConn.GetWhere(ctx, []db.Where{{Key: "account_id", Value: a.ID}}, u); err != nil { + return err + } + + u.Approved = true + u.Email = u.UnconfirmedEmail + u.ConfirmedAt = time.Now() + if err := dbConn.UpdateByPrimaryKey(ctx, u); err != nil { + return err + } + + return dbConn.Stop(ctx) +} + +// Promote sets a user to admin. +var Promote action.GTSAction = func(ctx context.Context) error { + dbConn, err := bundb.NewBunDBService(ctx) + if err != nil { + return fmt.Errorf("error creating dbservice: %s", err) + } + + username := viper.GetString(config.Keys.AdminAccountUsername) + if username == "" { + return errors.New("no username set") + } + if err := validate.Username(username); err != nil { + return err + } + + a, err := dbConn.GetLocalAccountByUsername(ctx, username) + if err != nil { + return err + } + + u := >smodel.User{} + if err := dbConn.GetWhere(ctx, []db.Where{{Key: "account_id", Value: a.ID}}, u); err != nil { + return err + } + u.Admin = true + if err := dbConn.UpdateByPrimaryKey(ctx, u); err != nil { + return err + } + + return dbConn.Stop(ctx) +} + +// Demote sets admin on a user to false. +var Demote action.GTSAction = func(ctx context.Context) error { + dbConn, err := bundb.NewBunDBService(ctx) + if err != nil { + return fmt.Errorf("error creating dbservice: %s", err) + } + + username := viper.GetString(config.Keys.AdminAccountUsername) + if username == "" { + return errors.New("no username set") + } + if err := validate.Username(username); err != nil { + return err + } + + a, err := dbConn.GetLocalAccountByUsername(ctx, username) + if err != nil { + return err + } + + u := >smodel.User{} + if err := dbConn.GetWhere(ctx, []db.Where{{Key: "account_id", Value: a.ID}}, u); err != nil { + return err + } + u.Admin = false + if err := dbConn.UpdateByPrimaryKey(ctx, u); err != nil { + return err + } + + return dbConn.Stop(ctx) +} + +// Disable sets Disabled to true on a user. +var Disable action.GTSAction = func(ctx context.Context) error { + dbConn, err := bundb.NewBunDBService(ctx) + if err != nil { + return fmt.Errorf("error creating dbservice: %s", err) + } + + username := viper.GetString(config.Keys.AdminAccountUsername) + if username == "" { + return errors.New("no username set") + } + if err := validate.Username(username); err != nil { + return err + } + + a, err := dbConn.GetLocalAccountByUsername(ctx, username) + if err != nil { + return err + } + + u := >smodel.User{} + if err := dbConn.GetWhere(ctx, []db.Where{{Key: "account_id", Value: a.ID}}, u); err != nil { + return err + } + u.Disabled = true + if err := dbConn.UpdateByPrimaryKey(ctx, u); err != nil { + return err + } + + return dbConn.Stop(ctx) +} + +// Suspend suspends the target account, cleanly removing all of its media, followers, following, likes, statuses, etc. +var Suspend action.GTSAction = func(ctx context.Context) error { + // TODO + return nil +} + +// Password sets the password of target account. +var Password action.GTSAction = func(ctx context.Context) error { + dbConn, err := bundb.NewBunDBService(ctx) + if err != nil { + return fmt.Errorf("error creating dbservice: %s", err) + } + + username := viper.GetString(config.Keys.AdminAccountUsername) + if username == "" { + return errors.New("no username set") + } + if err := validate.Username(username); err != nil { + return err + } + + password := viper.GetString(config.Keys.AdminAccountPassword) + if password == "" { + return errors.New("no password set") + } + if err := validate.NewPassword(password); err != nil { + return err + } + + a, err := dbConn.GetLocalAccountByUsername(ctx, username) + if err != nil { + return err + } + + u := >smodel.User{} + if err := dbConn.GetWhere(ctx, []db.Where{{Key: "account_id", Value: a.ID}}, u); err != nil { + return err + } + + pw, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) + if err != nil { + return fmt.Errorf("error hashing password: %s", err) + } + + u.EncryptedPassword = string(pw) + + if err := dbConn.UpdateByPrimaryKey(ctx, u); err != nil { + return err + } + + return nil +} diff --git a/cmd/gotosocial/action/admin/trans/export.go b/cmd/gotosocial/action/admin/trans/export.go new file mode 100644 index 000000000..615ba72f0 --- /dev/null +++ b/cmd/gotosocial/action/admin/trans/export.go @@ -0,0 +1,52 @@ +/* + GoToSocial + Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +package trans + +import ( + "context" + "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" + "github.com/superseriousbusiness/gotosocial/internal/trans" +) + +// Export exports info from the database into a file +var Export action.GTSAction = func(ctx context.Context) error { + dbConn, err := bundb.NewBunDBService(ctx) + if err != nil { + return fmt.Errorf("error creating dbservice: %s", err) + } + + exporter := trans.NewExporter(dbConn) + + path := viper.GetString(config.Keys.AdminTransPath) + if path == "" { + return errors.New("no path set") + } + + if err := exporter.ExportMinimal(ctx, path); err != nil { + return err + } + + return dbConn.Stop(ctx) +} diff --git a/cmd/gotosocial/action/admin/trans/import.go b/cmd/gotosocial/action/admin/trans/import.go new file mode 100644 index 000000000..688083130 --- /dev/null +++ b/cmd/gotosocial/action/admin/trans/import.go @@ -0,0 +1,52 @@ +/* + GoToSocial + Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +package trans + +import ( + "context" + "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" + "github.com/superseriousbusiness/gotosocial/internal/trans" +) + +// Import imports info from a file into the database +var Import action.GTSAction = func(ctx context.Context) error { + dbConn, err := bundb.NewBunDBService(ctx) + if err != nil { + return fmt.Errorf("error creating dbservice: %s", err) + } + + importer := trans.NewImporter(dbConn) + + path := viper.GetString(config.Keys.AdminTransPath) + if path == "" { + return errors.New("no path set") + } + + if err := importer.Import(ctx, path); err != nil { + return err + } + + return dbConn.Stop(ctx) +} diff --git a/cmd/gotosocial/testrigcommands.go b/cmd/gotosocial/action/debug/config/config.go index aabe04267..749ea124d 100644 --- a/cmd/gotosocial/testrigcommands.go +++ b/cmd/gotosocial/action/debug/config/config.go @@ -16,27 +16,24 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. */ -package main +package config import ( - "github.com/superseriousbusiness/gotosocial/internal/cliactions/testrig" - "github.com/urfave/cli/v2" + "context" + "encoding/json" + "fmt" + + "github.com/spf13/viper" + "github.com/superseriousbusiness/gotosocial/cmd/gotosocial/action" ) -func testrigCommands(allFlags []cli.Flag) []*cli.Command { - return []*cli.Command{ - { - Name: "testrig", - Usage: "gotosocial testrig tasks", - Subcommands: []*cli.Command{ - { - Name: "start", - Usage: "start the gotosocial testrig", - Action: func(c *cli.Context) error { - return runAction(c, allFlags, testrig.Start) - }, - }, - }, - }, +// 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) + if err != nil { + return err } + fmt.Println(string(b)) + return nil } diff --git a/cmd/gotosocial/action/server/server.go b/cmd/gotosocial/action/server/server.go new file mode 100644 index 000000000..57384ac6f --- /dev/null +++ b/cmd/gotosocial/action/server/server.go @@ -0,0 +1,225 @@ +/* + GoToSocial + Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +package server + +import ( + "context" + "fmt" + "net/http" + "os" + "os/signal" + "syscall" + + "codeberg.org/gruf/go-store/kv" + "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" + "github.com/superseriousbusiness/gotosocial/internal/api/client/admin" + "github.com/superseriousbusiness/gotosocial/internal/api/client/app" + "github.com/superseriousbusiness/gotosocial/internal/api/client/auth" + "github.com/superseriousbusiness/gotosocial/internal/api/client/blocks" + "github.com/superseriousbusiness/gotosocial/internal/api/client/emoji" + "github.com/superseriousbusiness/gotosocial/internal/api/client/favourites" + "github.com/superseriousbusiness/gotosocial/internal/api/client/fileserver" + "github.com/superseriousbusiness/gotosocial/internal/api/client/filter" + "github.com/superseriousbusiness/gotosocial/internal/api/client/followrequest" + "github.com/superseriousbusiness/gotosocial/internal/api/client/instance" + "github.com/superseriousbusiness/gotosocial/internal/api/client/list" + mediaModule "github.com/superseriousbusiness/gotosocial/internal/api/client/media" + "github.com/superseriousbusiness/gotosocial/internal/api/client/notification" + "github.com/superseriousbusiness/gotosocial/internal/api/client/search" + "github.com/superseriousbusiness/gotosocial/internal/api/client/status" + "github.com/superseriousbusiness/gotosocial/internal/api/client/streaming" + "github.com/superseriousbusiness/gotosocial/internal/api/client/timeline" + userClient "github.com/superseriousbusiness/gotosocial/internal/api/client/user" + "github.com/superseriousbusiness/gotosocial/internal/api/s2s/nodeinfo" + "github.com/superseriousbusiness/gotosocial/internal/api/s2s/user" + "github.com/superseriousbusiness/gotosocial/internal/api/s2s/webfinger" + "github.com/superseriousbusiness/gotosocial/internal/api/security" + "github.com/superseriousbusiness/gotosocial/internal/config" + "github.com/superseriousbusiness/gotosocial/internal/db/bundb" + "github.com/superseriousbusiness/gotosocial/internal/email" + "github.com/superseriousbusiness/gotosocial/internal/federation" + "github.com/superseriousbusiness/gotosocial/internal/federation/federatingdb" + "github.com/superseriousbusiness/gotosocial/internal/gotosocial" + "github.com/superseriousbusiness/gotosocial/internal/media" + "github.com/superseriousbusiness/gotosocial/internal/oauth" + "github.com/superseriousbusiness/gotosocial/internal/oidc" + "github.com/superseriousbusiness/gotosocial/internal/processing" + "github.com/superseriousbusiness/gotosocial/internal/router" + timelineprocessing "github.com/superseriousbusiness/gotosocial/internal/timeline" + "github.com/superseriousbusiness/gotosocial/internal/transport" + "github.com/superseriousbusiness/gotosocial/internal/typeutils" + "github.com/superseriousbusiness/gotosocial/internal/web" +) + +// Start creates and starts a gotosocial server +var Start action.GTSAction = func(ctx context.Context) error { + dbService, err := bundb.NewBunDBService(ctx) + if err != nil { + return fmt.Errorf("error creating dbservice: %s", err) + } + + if err := dbService.CreateInstanceAccount(ctx); err != nil { + return fmt.Errorf("error creating instance account: %s", err) + } + + if err := dbService.CreateInstanceInstance(ctx); err != nil { + return fmt.Errorf("error creating instance instance: %s", err) + } + + federatingDB := federatingdb.New(dbService) + + router, err := router.New(ctx, dbService) + if err != nil { + return fmt.Errorf("error creating router: %s", err) + } + + // build converters and util + typeConverter := typeutils.NewConverter(dbService) + timelineManager := timelineprocessing.NewManager(dbService, typeConverter) + + // Open the storage backend + storageBasePath := viper.GetString(config.Keys.StorageBasePath) + storage, err := kv.OpenFile(storageBasePath, nil) + if err != nil { + return fmt.Errorf("error creating storage backend: %s", err) + } + + // build backend handlers + mediaHandler := media.New(dbService, storage) + oauthServer := oauth.New(ctx, dbService) + transportController := transport.NewController(dbService, &federation.Clock{}, http.DefaultClient) + federator := federation.NewFederator(dbService, federatingDB, transportController, typeConverter, mediaHandler) + + // 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 != "" { + // host is defined so create a proper sender + emailSender, err = email.NewSender() + if err != nil { + return fmt.Errorf("error creating email sender: %s", err) + } + } else { + // no host is defined so create a noop sender + emailSender, err = email.NewNoopSender(nil) + if err != nil { + return fmt.Errorf("error creating noop email sender: %s", err) + } + } + + // create and start the message processor using the other services we've created so far + processor := processing.NewProcessor(typeConverter, federator, oauthServer, mediaHandler, storage, timelineManager, dbService, emailSender) + if err := processor.Start(ctx); err != nil { + return fmt.Errorf("error starting processor: %s", err) + } + + idp, err := oidc.NewIDP(ctx) + if err != nil { + return fmt.Errorf("error creating oidc idp: %s", err) + } + + // build client api modules + authModule := auth.New(dbService, oauthServer, idp) + accountModule := account.New(processor) + instanceModule := instance.New(processor) + appsModule := app.New(processor) + followRequestsModule := followrequest.New(processor) + webfingerModule := webfinger.New(processor) + nodeInfoModule := nodeinfo.New(processor) + webBaseModule := web.New(processor) + usersModule := user.New(processor) + timelineModule := timeline.New(processor) + notificationModule := notification.New(processor) + searchModule := search.New(processor) + filtersModule := filter.New(processor) + emojiModule := emoji.New(processor) + listsModule := list.New(processor) + mm := mediaModule.New(processor) + fileServerModule := fileserver.New(processor) + adminModule := admin.New(processor) + statusModule := status.New(processor) + securityModule := security.New(dbService, oauthServer) + streamingModule := streaming.New(processor) + favouritesModule := favourites.New(processor) + blocksModule := blocks.New(processor) + userClientModule := userClient.New(processor) + + apis := []api.ClientModule{ + // modules with middleware go first + securityModule, + authModule, + + // now everything else + webBaseModule, + accountModule, + instanceModule, + appsModule, + followRequestsModule, + mm, + fileServerModule, + adminModule, + statusModule, + webfingerModule, + nodeInfoModule, + usersModule, + timelineModule, + notificationModule, + searchModule, + filtersModule, + emojiModule, + listsModule, + streamingModule, + favouritesModule, + blocksModule, + userClientModule, + } + + for _, m := range apis { + if err := m.Route(router); err != nil { + return fmt.Errorf("routing error: %s", err) + } + } + + gts, err := gotosocial.NewServer(dbService, router, federator) + if err != nil { + return fmt.Errorf("error creating gotosocial service: %s", err) + } + + if err := gts.Start(ctx); err != nil { + return fmt.Errorf("error starting gotosocial service: %s", err) + } + + // catch shutdown signals from the operating system + sigs := make(chan os.Signal, 1) + signal.Notify(sigs, os.Interrupt, syscall.SIGTERM) + sig := <-sigs + logrus.Infof("received signal %s, shutting down", sig) + + // close down all running services in order + if err := gts.Stop(ctx); err != nil { + return fmt.Errorf("error closing gotosocial service: %s", err) + } + + logrus.Info("done! exiting...") + return nil +} diff --git a/cmd/gotosocial/action/testrig/testrig.go b/cmd/gotosocial/action/testrig/testrig.go new file mode 100644 index 000000000..626a22781 --- /dev/null +++ b/cmd/gotosocial/action/testrig/testrig.go @@ -0,0 +1,184 @@ +/* + GoToSocial + Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +package testrig + +import ( + "bytes" + "context" + "fmt" + "io/ioutil" + "net/http" + "os" + "os/signal" + "syscall" + + "github.com/sirupsen/logrus" + "github.com/superseriousbusiness/gotosocial/cmd/gotosocial/action" + "github.com/superseriousbusiness/gotosocial/internal/api" + "github.com/superseriousbusiness/gotosocial/internal/api/client/account" + "github.com/superseriousbusiness/gotosocial/internal/api/client/admin" + "github.com/superseriousbusiness/gotosocial/internal/api/client/app" + "github.com/superseriousbusiness/gotosocial/internal/api/client/auth" + "github.com/superseriousbusiness/gotosocial/internal/api/client/blocks" + "github.com/superseriousbusiness/gotosocial/internal/api/client/emoji" + "github.com/superseriousbusiness/gotosocial/internal/api/client/favourites" + "github.com/superseriousbusiness/gotosocial/internal/api/client/fileserver" + "github.com/superseriousbusiness/gotosocial/internal/api/client/filter" + "github.com/superseriousbusiness/gotosocial/internal/api/client/followrequest" + "github.com/superseriousbusiness/gotosocial/internal/api/client/instance" + "github.com/superseriousbusiness/gotosocial/internal/api/client/list" + mediaModule "github.com/superseriousbusiness/gotosocial/internal/api/client/media" + "github.com/superseriousbusiness/gotosocial/internal/api/client/notification" + "github.com/superseriousbusiness/gotosocial/internal/api/client/search" + "github.com/superseriousbusiness/gotosocial/internal/api/client/status" + "github.com/superseriousbusiness/gotosocial/internal/api/client/streaming" + "github.com/superseriousbusiness/gotosocial/internal/api/client/timeline" + userClient "github.com/superseriousbusiness/gotosocial/internal/api/client/user" + "github.com/superseriousbusiness/gotosocial/internal/api/s2s/nodeinfo" + "github.com/superseriousbusiness/gotosocial/internal/api/s2s/user" + "github.com/superseriousbusiness/gotosocial/internal/api/s2s/webfinger" + "github.com/superseriousbusiness/gotosocial/internal/api/security" + "github.com/superseriousbusiness/gotosocial/internal/gotosocial" + "github.com/superseriousbusiness/gotosocial/internal/oidc" + "github.com/superseriousbusiness/gotosocial/internal/web" + "github.com/superseriousbusiness/gotosocial/testrig" +) + +// Start creates and starts a gotosocial testrig server +var Start action.GTSAction = func(ctx context.Context) error { + testrig.InitTestConfig() + testrig.InitTestLog() + + dbService := testrig.NewTestDB() + testrig.StandardDBSetup(dbService, nil) + router := testrig.NewTestRouter(dbService) + storageBackend := testrig.NewTestStorage() + testrig.StandardStorageSetup(storageBackend, "./testrig/media") + + // build backend handlers + oauthServer := testrig.NewTestOauthServer(dbService) + transportController := testrig.NewTestTransportController(testrig.NewMockHTTPClient(func(req *http.Request) (*http.Response, error) { + r := ioutil.NopCloser(bytes.NewReader([]byte{})) + return &http.Response{ + StatusCode: 200, + Body: r, + }, nil + }), dbService) + federator := testrig.NewTestFederator(dbService, transportController, storageBackend) + + emailSender := testrig.NewEmailSender("./web/template/", nil) + + processor := testrig.NewTestProcessor(dbService, storageBackend, federator, emailSender) + if err := processor.Start(ctx); err != nil { + return fmt.Errorf("error starting processor: %s", err) + } + + idp, err := oidc.NewIDP(ctx) + if err != nil { + return fmt.Errorf("error creating oidc idp: %s", err) + } + + // build client api modules + authModule := auth.New(dbService, oauthServer, idp) + accountModule := account.New(processor) + instanceModule := instance.New(processor) + appsModule := app.New(processor) + followRequestsModule := followrequest.New(processor) + webfingerModule := webfinger.New(processor) + nodeInfoModule := nodeinfo.New(processor) + webBaseModule := web.New(processor) + usersModule := user.New(processor) + timelineModule := timeline.New(processor) + notificationModule := notification.New(processor) + searchModule := search.New(processor) + filtersModule := filter.New(processor) + emojiModule := emoji.New(processor) + listsModule := list.New(processor) + mm := mediaModule.New(processor) + fileServerModule := fileserver.New(processor) + adminModule := admin.New(processor) + statusModule := status.New(processor) + securityModule := security.New(dbService, oauthServer) + streamingModule := streaming.New(processor) + favouritesModule := favourites.New(processor) + blocksModule := blocks.New(processor) + userClientModule := userClient.New(processor) + + apis := []api.ClientModule{ + // modules with middleware go first + securityModule, + authModule, + + // now everything else + webBaseModule, + accountModule, + instanceModule, + appsModule, + followRequestsModule, + mm, + fileServerModule, + adminModule, + statusModule, + webfingerModule, + nodeInfoModule, + usersModule, + timelineModule, + notificationModule, + searchModule, + filtersModule, + emojiModule, + listsModule, + streamingModule, + favouritesModule, + blocksModule, + userClientModule, + } + + for _, m := range apis { + if err := m.Route(router); err != nil { + return fmt.Errorf("routing error: %s", err) + } + } + + gts, err := gotosocial.NewServer(dbService, router, federator) + if err != nil { + return fmt.Errorf("error creating gotosocial service: %s", err) + } + + if err := gts.Start(ctx); err != nil { + return fmt.Errorf("error starting gotosocial service: %s", err) + } + + // catch shutdown signals from the operating system + sigs := make(chan os.Signal, 1) + signal.Notify(sigs, os.Interrupt, syscall.SIGTERM) + sig := <-sigs + logrus.Infof("received signal %s, shutting down", sig) + + testrig.StandardDBTeardown(dbService) + testrig.StandardStorageTeardown(storageBackend) + + // close down all running services in order + if err := gts.Stop(ctx); err != nil { + return fmt.Errorf("error closing gotosocial service: %s", err) + } + + logrus.Info("done! exiting...") + return nil +} diff --git a/cmd/gotosocial/admin.go b/cmd/gotosocial/admin.go new file mode 100644 index 000000000..9ee4461ea --- /dev/null +++ b/cmd/gotosocial/admin.go @@ -0,0 +1,169 @@ +/* + GoToSocial + Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +package main + +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" +) + +func adminCommands() *cobra.Command { + adminCmd := &cobra.Command{ + Use: "admin", + Short: "gotosocial admin-related tasks", + } + + /* + ADMIN ACCOUNT COMMANDS + */ + + adminAccountCmd := &cobra.Command{ + Use: "account", + Short: "admin commands related to accounts", + } + flag.AdminAccount(adminAccountCmd, config.Defaults) + + adminAccountCreateCmd := &cobra.Command{ + Use: "create", + Short: "create a new account", + PreRunE: func(cmd *cobra.Command, args []string) error { + return preRun(cmd) + }, + RunE: func(cmd *cobra.Command, args []string) error { + return run(cmd.Context(), account.Create) + }, + } + flag.AdminAccountCreate(adminAccountCreateCmd, config.Defaults) + adminAccountCmd.AddCommand(adminAccountCreateCmd) + + adminAccountConfirmCmd := &cobra.Command{ + Use: "confirm", + Short: "confirm an existing account manually, thereby skipping email confirmation", + PreRunE: func(cmd *cobra.Command, args []string) error { + return preRun(cmd) + }, + RunE: func(cmd *cobra.Command, args []string) error { + return run(cmd.Context(), account.Confirm) + }, + } + flag.AdminAccount(adminAccountConfirmCmd, config.Defaults) + adminAccountCmd.AddCommand(adminAccountConfirmCmd) + + adminAccountPromoteCmd := &cobra.Command{ + Use: "promote", + Short: "promote an account to admin", + PreRunE: func(cmd *cobra.Command, args []string) error { + return preRun(cmd) + }, + RunE: func(cmd *cobra.Command, args []string) error { + return run(cmd.Context(), account.Promote) + }, + } + flag.AdminAccount(adminAccountPromoteCmd, config.Defaults) + adminAccountCmd.AddCommand(adminAccountPromoteCmd) + + adminAccountDemoteCmd := &cobra.Command{ + Use: "demote", + Short: "demote an account from admin to normal user", + PreRunE: func(cmd *cobra.Command, args []string) error { + return preRun(cmd) + }, + RunE: func(cmd *cobra.Command, args []string) error { + return run(cmd.Context(), account.Demote) + }, + } + flag.AdminAccount(adminAccountDemoteCmd, config.Defaults) + adminAccountCmd.AddCommand(adminAccountDemoteCmd) + + adminAccountDisableCmd := &cobra.Command{ + Use: "disable", + Short: "prevent an account from signing in or posting etc, but don't delete anything", + PreRunE: func(cmd *cobra.Command, args []string) error { + return preRun(cmd) + }, + RunE: func(cmd *cobra.Command, args []string) error { + return run(cmd.Context(), account.Disable) + }, + } + flag.AdminAccount(adminAccountDisableCmd, config.Defaults) + adminAccountCmd.AddCommand(adminAccountDisableCmd) + + adminAccountSuspendCmd := &cobra.Command{ + Use: "suspend", + Short: "completely remove an account and all of its posts, media, etc", + PreRunE: func(cmd *cobra.Command, args []string) error { + return preRun(cmd) + }, + RunE: func(cmd *cobra.Command, args []string) error { + return run(cmd.Context(), account.Suspend) + }, + } + flag.AdminAccount(adminAccountSuspendCmd, config.Defaults) + adminAccountCmd.AddCommand(adminAccountSuspendCmd) + + adminAccountPasswordCmd := &cobra.Command{ + Use: "password", + Short: "set a new password for the given account", + PreRunE: func(cmd *cobra.Command, args []string) error { + return preRun(cmd) + }, + RunE: func(cmd *cobra.Command, args []string) error { + return run(cmd.Context(), account.Password) + }, + } + flag.AdminAccountPassword(adminAccountPasswordCmd, config.Defaults) + adminAccountCmd.AddCommand(adminAccountPasswordCmd) + + adminCmd.AddCommand(adminAccountCmd) + + /* + ADMIN IMPORT/EXPORT COMMANDS + */ + + adminExportCmd := &cobra.Command{ + Use: "export", + Short: "export data from the database to file at the given path", + PreRunE: func(cmd *cobra.Command, args []string) error { + return preRun(cmd) + }, + RunE: func(cmd *cobra.Command, args []string) error { + return run(cmd.Context(), trans.Export) + }, + } + flag.AdminTrans(adminExportCmd, config.Defaults) + adminCmd.AddCommand(adminExportCmd) + + adminImportCmd := &cobra.Command{ + Use: "import", + Short: "import data from a file into the database", + PreRunE: func(cmd *cobra.Command, args []string) error { + return preRun(cmd) + }, + RunE: func(cmd *cobra.Command, args []string) error { + return run(cmd.Context(), trans.Import) + }, + } + flag.AdminTrans(adminImportCmd, config.Defaults) + adminCmd.AddCommand(adminImportCmd) + + return adminCmd +} diff --git a/cmd/gotosocial/admincommands.go b/cmd/gotosocial/admincommands.go deleted file mode 100644 index a70693b2c..000000000 --- a/cmd/gotosocial/admincommands.go +++ /dev/null @@ -1,184 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see <http://www.gnu.org/licenses/>. -*/ - -package main - -import ( - "github.com/superseriousbusiness/gotosocial/internal/cliactions/admin/account" - "github.com/superseriousbusiness/gotosocial/internal/cliactions/admin/trans" - "github.com/superseriousbusiness/gotosocial/internal/config" - "github.com/urfave/cli/v2" -) - -func adminCommands(allFlags []cli.Flag) []*cli.Command { - return []*cli.Command{ - { - Name: "admin", - Usage: "gotosocial admin-related tasks", - Subcommands: []*cli.Command{ - { - Name: "account", - Usage: "admin commands related to accounts", - Subcommands: []*cli.Command{ - { - Name: "create", - Usage: "create a new account", - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: config.UsernameFlag, - Usage: config.UsernameUsage, - Required: true, - }, - &cli.StringFlag{ - Name: config.EmailFlag, - Usage: config.EmailUsage, - Required: true, - }, - &cli.StringFlag{ - Name: config.PasswordFlag, - Usage: config.PasswordUsage, - Required: true, - }, - }, - Action: func(c *cli.Context) error { - return runAction(c, allFlags, account.Create) - }, - }, - { - Name: "confirm", - Usage: "confirm an existing account manually, thereby skipping email confirmation", - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: config.UsernameFlag, - Usage: config.UsernameUsage, - Required: true, - }, - }, - Action: func(c *cli.Context) error { - return runAction(c, allFlags, account.Confirm) - }, - }, - { - Name: "promote", - Usage: "promote an account to admin", - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: config.UsernameFlag, - Usage: config.UsernameUsage, - Required: true, - }, - }, - Action: func(c *cli.Context) error { - return runAction(c, allFlags, account.Promote) - }, - }, - { - Name: "demote", - Usage: "demote an account from admin to normal user", - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: config.UsernameFlag, - Usage: config.UsernameUsage, - Required: true, - }, - }, - Action: func(c *cli.Context) error { - return runAction(c, allFlags, account.Demote) - }, - }, - { - Name: "disable", - Usage: "prevent an account from signing in or posting etc, but don't delete anything", - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: config.UsernameFlag, - Usage: config.UsernameUsage, - Required: true, - }, - }, - Action: func(c *cli.Context) error { - return runAction(c, allFlags, account.Disable) - }, - }, - { - Name: "suspend", - Usage: "completely remove an account and all of its posts, media, etc", - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: config.UsernameFlag, - Usage: config.UsernameUsage, - Required: true, - }, - }, - Action: func(c *cli.Context) error { - return runAction(c, allFlags, account.Suspend) - }, - }, - { - Name: "password", - Usage: "set a new password for the given account", - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: config.UsernameFlag, - Usage: config.UsernameUsage, - Required: true, - }, - &cli.StringFlag{ - Name: config.PasswordFlag, - Usage: config.PasswordUsage, - Required: true, - }, - }, - Action: func(c *cli.Context) error { - return runAction(c, allFlags, account.Password) - }, - }, - }, - }, - { - Name: "export", - Usage: "export data from the database to file at the given path", - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: config.TransPathFlag, - Usage: config.TransPathUsage, - Required: true, - }, - }, - Action: func(c *cli.Context) error { - return runAction(c, allFlags, trans.Export) - }, - }, - { - Name: "import", - Usage: "import data from a file into the database", - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: config.TransPathFlag, - Usage: config.TransPathUsage, - Required: true, - }, - }, - Action: func(c *cli.Context) error { - return runAction(c, allFlags, trans.Import) - }, - }, - }, - }, - } -} diff --git a/cmd/gotosocial/common.go b/cmd/gotosocial/common.go new file mode 100644 index 000000000..7b5a42652 --- /dev/null +++ b/cmd/gotosocial/common.go @@ -0,0 +1,64 @@ +/* + GoToSocial + Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +package main + +import ( + "context" + "fmt" + + "github.com/spf13/cobra" + "github.com/spf13/viper" + "github.com/superseriousbusiness/gotosocial/cmd/gotosocial/action" + "github.com/superseriousbusiness/gotosocial/internal/config" + "github.com/superseriousbusiness/gotosocial/internal/log" +) + +// preRun should be run in the pre-run stage of every cobra command. +// The goal here is to initialize the viper config store, and also read in +// the config file (if present). +// +// The order of these is important: the init-config function reads the location +// of the config file from the viper store so that it can be picked up by either +// env vars or cli flag. +func preRun(cmd *cobra.Command) error { + if err := config.InitViper(cmd.Flags()); err != nil { + return fmt.Errorf("error initializing viper: %s", err) + } + + if err := config.ReadFromFile(); err != nil { + return fmt.Errorf("error initializing config: %s", err) + } + + return nil +} + +// run should be used during the run stage of every cobra command. +// The idea here is to take a GTSAction and run it with the given +// context, after initializing any last-minute things like loggers etc. +func run(ctx context.Context, action action.GTSAction) error { + // if log level has been set... + if logLevel := viper.GetString(config.Keys.LogLevel); logLevel != "" { + // then try to initialize the logger to that level + if err := log.Initialize(logLevel); err != nil { + return fmt.Errorf("error initializing log: %s", err) + } + } + + return action(ctx) +} diff --git a/cmd/gotosocial/databaseflags.go b/cmd/gotosocial/databaseflags.go deleted file mode 100644 index 7abe64f1b..000000000 --- a/cmd/gotosocial/databaseflags.go +++ /dev/null @@ -1,77 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see <http://www.gnu.org/licenses/>. -*/ - -package main - -import ( - "github.com/superseriousbusiness/gotosocial/internal/config" - "github.com/urfave/cli/v2" -) - -func databaseFlags(flagNames, envNames config.Flags, defaults config.Defaults) []cli.Flag { - return []cli.Flag{ - &cli.StringFlag{ - Name: flagNames.DbType, - Usage: "Database type: eg., postgres", - Value: defaults.DbType, - EnvVars: []string{envNames.DbType}, - }, - &cli.StringFlag{ - Name: flagNames.DbAddress, - Usage: "Database ipv4 address or hostname", - Value: defaults.DbAddress, - EnvVars: []string{envNames.DbAddress}, - }, - &cli.IntFlag{ - Name: flagNames.DbPort, - Usage: "Database port", - Value: defaults.DbPort, - EnvVars: []string{envNames.DbPort}, - }, - &cli.StringFlag{ - Name: flagNames.DbUser, - Usage: "Database username", - Value: defaults.DbUser, - EnvVars: []string{envNames.DbUser}, - }, - &cli.StringFlag{ - Name: flagNames.DbPassword, - Usage: "Database password", - Value: defaults.DbPassword, - EnvVars: []string{envNames.DbPassword}, - }, - &cli.StringFlag{ - Name: flagNames.DbDatabase, - Usage: "Database name", - Value: defaults.DbDatabase, - EnvVars: []string{envNames.DbDatabase}, - }, - &cli.StringFlag{ - Name: flagNames.DbTLSMode, - Usage: "Database tls mode", - Value: defaults.DBTlsMode, - EnvVars: []string{envNames.DbTLSMode}, - }, - &cli.StringFlag{ - Name: flagNames.DbTLSCACert, - Usage: "Path to CA cert for db tls connection", - Value: defaults.DBTlsCACert, - EnvVars: []string{envNames.DbTLSCACert}, - }, - } -} diff --git a/cmd/gotosocial/flags.go b/cmd/gotosocial/debug.go index 8e162f6c7..f66d5ac0c 100644 --- a/cmd/gotosocial/flags.go +++ b/cmd/gotosocial/debug.go @@ -19,31 +19,30 @@ 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" - "github.com/urfave/cli/v2" ) -func getFlags() []cli.Flag { - flagNames := config.GetFlagNames() - envNames := config.GetEnvNames() - defaults := config.GetDefaults() - - flags := []cli.Flag{} - flagSets := [][]cli.Flag{ - generalFlags(flagNames, envNames, defaults), - databaseFlags(flagNames, envNames, defaults), - templateFlags(flagNames, envNames, defaults), - accountsFlags(flagNames, envNames, defaults), - mediaFlags(flagNames, envNames, defaults), - storageFlags(flagNames, envNames, defaults), - statusesFlags(flagNames, envNames, defaults), - letsEncryptFlags(flagNames, envNames, defaults), - oidcFlags(flagNames, envNames, defaults), - smtpFlags(flagNames, envNames, defaults), +func debugCommands() *cobra.Command { + debugCmd := &cobra.Command{ + Use: "debug", + Short: "gotosocial debug-related tasks", } - for _, fs := range flagSets { - flags = append(flags, fs...) + + debugConfigCmd := &cobra.Command{ + Use: "config", + Short: "print the collated config (derived from env, flag, and config file) to stdout", + PreRunE: func(cmd *cobra.Command, args []string) error { + return preRun(cmd) + }, + RunE: func(cmd *cobra.Command, args []string) error { + return run(cmd.Context(), configaction.Config) + }, } + flag.Server(debugConfigCmd, config.Defaults) - return flags + debugCmd.AddCommand(debugConfigCmd) + return debugCmd } diff --git a/cmd/gotosocial/flag/admin.go b/cmd/gotosocial/flag/admin.go new file mode 100644 index 000000000..b087071bb --- /dev/null +++ b/cmd/gotosocial/flag/admin.go @@ -0,0 +1,62 @@ +/* + GoToSocial + Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see <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 new file mode 100644 index 000000000..b7fb03e17 --- /dev/null +++ b/cmd/gotosocial/flag/global.go @@ -0,0 +1,45 @@ +/* + GoToSocial + Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see <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().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 new file mode 100644 index 000000000..3d3e946f3 --- /dev/null +++ b/cmd/gotosocial/flag/server.go @@ -0,0 +1,111 @@ +/* + GoToSocial + Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see <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) +} + +// 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) +} + +// 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.StorageBasePath, values.StorageBasePath, usage.StorageBasePath) + cmd.Flags().String(config.Keys.StorageServeProtocol, values.StorageServeProtocol, usage.StorageServeProtocol) + cmd.Flags().String(config.Keys.StorageServeHost, values.StorageServeHost, usage.StorageServeHost) + cmd.Flags().String(config.Keys.StorageServeBasePath, values.StorageServeBasePath, usage.StorageServeBasePath) +} + +// 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) +} diff --git a/cmd/gotosocial/flag/usage.go b/cmd/gotosocial/flag/usage.go new file mode 100644 index 000000000..aa57048c1 --- /dev/null +++ b/cmd/gotosocial/flag/usage.go @@ -0,0 +1,80 @@ +/* + GoToSocial + Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see <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]", + 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). 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 (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", + StorageBackend: "Storage backend to use for media attachments", + StorageBasePath: "Full path to an already-created directory where gts should store/retrieve media files. Subfolders will be created within this dir.", + StorageServeProtocol: "Protocol to use for serving media attachments (use https if storage is local)", + StorageServeHost: "Hostname to serve media attachments from (use the same value as host if storage is local)", + StorageServeBasePath: "Path to append to protocol and hostname to create the base path from which media files will be served (default will mostly be fine)", + 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'", + 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/generalflags.go b/cmd/gotosocial/generalflags.go deleted file mode 100644 index b7e15ab20..000000000 --- a/cmd/gotosocial/generalflags.go +++ /dev/null @@ -1,84 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see <http://www.gnu.org/licenses/>. -*/ - -package main - -import ( - "github.com/superseriousbusiness/gotosocial/internal/config" - "github.com/urfave/cli/v2" -) - -func generalFlags(flagNames, envNames config.Flags, defaults config.Defaults) []cli.Flag { - return []cli.Flag{ - &cli.StringFlag{ - Name: flagNames.LogLevel, - Usage: "Log level to run at: debug, info, warn, fatal", - Value: defaults.LogLevel, - EnvVars: []string{envNames.LogLevel}, - }, - &cli.StringFlag{ - Name: flagNames.ApplicationName, - Usage: "Name of the application, used in various places internally", - Value: defaults.ApplicationName, - EnvVars: []string{envNames.ApplicationName}, - Hidden: true, - }, - &cli.StringFlag{ - Name: flagNames.ConfigPath, - Usage: "Path to a yaml file containing gotosocial configuration. Values set in this file will be overwritten by values set as env vars or arguments", - Value: defaults.ConfigPath, - EnvVars: []string{envNames.ConfigPath}, - }, - &cli.StringFlag{ - Name: flagNames.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!", - Value: defaults.Host, - EnvVars: []string{envNames.Host}, - }, - &cli.StringFlag{ - Name: flagNames.AccountDomain, - 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!", - Value: defaults.AccountDomain, - EnvVars: []string{envNames.AccountDomain}, - }, - &cli.StringFlag{ - Name: flagNames.Protocol, - Usage: "Protocol to use for the REST api of the server (only use http for debugging and tests!)", - Value: defaults.Protocol, - EnvVars: []string{envNames.Protocol}, - }, - &cli.StringFlag{ - Name: flagNames.BindAddress, - 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.", - Value: defaults.BindAddress, - EnvVars: []string{envNames.BindAddress}, - }, - &cli.IntFlag{ - Name: flagNames.Port, - Usage: "Port to use for GoToSocial. Change this to 443 if you're running the binary directly on the host machine.", - Value: defaults.Port, - EnvVars: []string{envNames.Port}, - }, - &cli.StringSliceFlag{ - Name: flagNames.TrustedProxies, - Usage: "Proxies to trust when parsing x-forwarded headers into real IPs.", - Value: cli.NewStringSlice(defaults.TrustedProxies...), - EnvVars: []string{envNames.TrustedProxies}, - }, - } -} diff --git a/cmd/gotosocial/letsencryptflags.go b/cmd/gotosocial/letsencryptflags.go deleted file mode 100644 index 86079c015..000000000 --- a/cmd/gotosocial/letsencryptflags.go +++ /dev/null @@ -1,53 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see <http://www.gnu.org/licenses/>. -*/ - -package main - -import ( - "github.com/superseriousbusiness/gotosocial/internal/config" - "github.com/urfave/cli/v2" -) - -func letsEncryptFlags(flagNames, envNames config.Flags, defaults config.Defaults) []cli.Flag { - return []cli.Flag{ - &cli.BoolFlag{ - Name: flagNames.LetsEncryptEnabled, - Usage: "Enable letsencrypt TLS certs for this server. If set to true, then cert dir also needs to be set (or take the default).", - Value: defaults.LetsEncryptEnabled, - EnvVars: []string{envNames.LetsEncryptEnabled}, - }, - &cli.IntFlag{ - Name: flagNames.LetsEncryptPort, - Usage: "Port to listen on for letsencrypt certificate challenges. Must not be the same as the GtS webserver/API port.", - Value: defaults.LetsEncryptPort, - EnvVars: []string{envNames.LetsEncryptPort}, - }, - &cli.StringFlag{ - Name: flagNames.LetsEncryptCertDir, - Usage: "Directory to store acquired letsencrypt certificates.", - Value: defaults.LetsEncryptCertDir, - EnvVars: []string{envNames.LetsEncryptCertDir}, - }, - &cli.StringFlag{ - Name: flagNames.LetsEncryptEmailAddress, - Usage: "Email address to use when requesting letsencrypt certs. Will receive updates on cert expiry etc.", - Value: defaults.LetsEncryptEmailAddress, - EnvVars: []string{envNames.LetsEncryptEmailAddress}, - }, - } -} diff --git a/cmd/gotosocial/main.go b/cmd/gotosocial/main.go index 87c44487c..a0b8b1046 100644 --- a/cmd/gotosocial/main.go +++ b/cmd/gotosocial/main.go @@ -19,38 +19,59 @@ package main import ( - "os" - "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/urfave/cli/v2" + "github.com/superseriousbusiness/gotosocial/internal/config" ) -// Version is the software version of GtS being used +// Version is the software version of GtS being used. var Version string -// Commit is the git commit of GtS being used +// Commit is the git commit of GtS being used. var Commit string //go:generate swagger generate spec func main() { var v string - if Commit == "" { + if len(Commit) < 7 { v = Version } else { v = Version + " " + Commit[:7] } - flagsSlice := getFlags() - app := &cli.App{ - Version: v, - Usage: "a fediverse social media server", - Flags: flagsSlice, - Commands: getCommands(flagsSlice), + // override software version in viper store + viper.Set(config.Keys.SoftwareVersion, v) + + // 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: v, + 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) } - if err := app.Run(os.Args); err != nil { - logrus.Fatal(err) + // add subcommands + rootCmd.AddCommand(serverCommands()) + rootCmd.AddCommand(testrigCommands()) + rootCmd.AddCommand(debugCommands()) + rootCmd.AddCommand(adminCommands()) + + // run + if err := rootCmd.Execute(); err != nil { + logrus.Fatalf("error executing command: %s", err) } } diff --git a/cmd/gotosocial/mediaflags.go b/cmd/gotosocial/mediaflags.go deleted file mode 100644 index abf009a89..000000000 --- a/cmd/gotosocial/mediaflags.go +++ /dev/null @@ -1,53 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see <http://www.gnu.org/licenses/>. -*/ - -package main - -import ( - "github.com/superseriousbusiness/gotosocial/internal/config" - "github.com/urfave/cli/v2" -) - -func mediaFlags(flagNames, envNames config.Flags, defaults config.Defaults) []cli.Flag { - return []cli.Flag{ - &cli.IntFlag{ - Name: flagNames.MediaMaxImageSize, - Usage: "Max size of accepted images in bytes", - Value: defaults.MediaMaxImageSize, - EnvVars: []string{envNames.MediaMaxImageSize}, - }, - &cli.IntFlag{ - Name: flagNames.MediaMaxVideoSize, - Usage: "Max size of accepted videos in bytes", - Value: defaults.MediaMaxVideoSize, - EnvVars: []string{envNames.MediaMaxVideoSize}, - }, - &cli.IntFlag{ - Name: flagNames.MediaMinDescriptionChars, - Usage: "Min required chars for an image description", - Value: defaults.MediaMinDescriptionChars, - EnvVars: []string{envNames.MediaMinDescriptionChars}, - }, - &cli.IntFlag{ - Name: flagNames.MediaMaxDescriptionChars, - Usage: "Max permitted chars for an image description", - Value: defaults.MediaMaxDescriptionChars, - EnvVars: []string{envNames.MediaMaxDescriptionChars}, - }, - } -} diff --git a/cmd/gotosocial/oidcflags.go b/cmd/gotosocial/oidcflags.go deleted file mode 100644 index 93b86b166..000000000 --- a/cmd/gotosocial/oidcflags.go +++ /dev/null @@ -1,71 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see <http://www.gnu.org/licenses/>. -*/ - -package main - -import ( - "github.com/superseriousbusiness/gotosocial/internal/config" - "github.com/urfave/cli/v2" -) - -func oidcFlags(flagNames, envNames config.Flags, defaults config.Defaults) []cli.Flag { - return []cli.Flag{ - &cli.BoolFlag{ - Name: flagNames.OIDCEnabled, - Usage: "Enabled OIDC authorization for this instance. If set to true, then the other OIDC flags must also be set.", - Value: defaults.OIDCEnabled, - EnvVars: []string{envNames.OIDCEnabled}, - }, - &cli.StringFlag{ - Name: flagNames.OIDCIdpName, - Usage: "Name of the OIDC identity provider. Will be shown to the user when logging in.", - Value: defaults.OIDCIdpName, - EnvVars: []string{envNames.OIDCIdpName}, - }, - &cli.BoolFlag{ - Name: flagNames.OIDCSkipVerification, - Usage: "Skip verification of tokens returned by the OIDC provider. Should only be set to 'true' for testing purposes, never in a production environment!", - Value: defaults.OIDCSkipVerification, - EnvVars: []string{envNames.OIDCSkipVerification}, - }, - &cli.StringFlag{ - Name: flagNames.OIDCIssuer, - 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'", - Value: defaults.OIDCIssuer, - EnvVars: []string{envNames.OIDCIssuer}, - }, - &cli.StringFlag{ - Name: flagNames.OIDCClientID, - Usage: "ClientID of GoToSocial, as registered with the OIDC provider.", - Value: defaults.OIDCClientID, - EnvVars: []string{envNames.OIDCClientID}, - }, - &cli.StringFlag{ - Name: flagNames.OIDCClientSecret, - Usage: "ClientSecret of GoToSocial, as registered with the OIDC provider.", - Value: defaults.OIDCClientSecret, - EnvVars: []string{envNames.OIDCClientSecret}, - }, - &cli.StringSliceFlag{ - Name: flagNames.OIDCScopes, - Usage: "ClientSecret of GoToSocial, as registered with the OIDC provider.", - Value: cli.NewStringSlice(defaults.OIDCScopes...), - EnvVars: []string{envNames.OIDCScopes}, - }, - } -} diff --git a/cmd/gotosocial/runaction.go b/cmd/gotosocial/runaction.go deleted file mode 100644 index 96c4edaf6..000000000 --- a/cmd/gotosocial/runaction.go +++ /dev/null @@ -1,82 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see <http://www.gnu.org/licenses/>. -*/ - -package main - -import ( - "fmt" - - "github.com/superseriousbusiness/gotosocial/internal/cliactions" - "github.com/superseriousbusiness/gotosocial/internal/config" - "github.com/superseriousbusiness/gotosocial/internal/log" - "github.com/urfave/cli/v2" -) - -type MonkeyPatchedCLIContext struct { - CLIContext *cli.Context - AllFlags []cli.Flag -} - -func (f MonkeyPatchedCLIContext) Bool(k string) bool { return f.CLIContext.Bool(k) } -func (f MonkeyPatchedCLIContext) String(k string) string { return f.CLIContext.String(k) } -func (f MonkeyPatchedCLIContext) StringSlice(k string) []string { return f.CLIContext.StringSlice(k) } -func (f MonkeyPatchedCLIContext) Int(k string) int { return f.CLIContext.Int(k) } -func (f MonkeyPatchedCLIContext) IsSet(k string) bool { - for _, flag := range f.AllFlags { - flagNames := flag.Names() - for _, name := range flagNames { - if name == k { - return flag.IsSet() - } - } - - } - return false -} - -// runAction builds up the config and logger necessary for any -// gotosocial action, and then executes the action. -func runAction(c *cli.Context, allFlags []cli.Flag, a cliactions.GTSAction) error { - - // create a new *config.Config based on the config path provided... - conf, err := config.FromFile(c.String(config.GetFlagNames().ConfigPath)) - if err != nil { - return fmt.Errorf("error creating config: %s", err) - } - - // ... and the flags set on the *cli.Context by urfave - // - // The IsSet function on the cli.Context object `c` here appears to have some issues right now, it always returns false in my tests. - // However we can re-create the behaviour we want by simply referencing the flag objects we created previously - // https://picopublish.sequentialread.com/files/chatlog_2021_11_18.txt - monkeyPatchedCLIContext := MonkeyPatchedCLIContext{ - CLIContext: c, - AllFlags: allFlags, - } - if err := conf.ParseCLIFlags(monkeyPatchedCLIContext, c.App.Version); err != nil { - return fmt.Errorf("error parsing config: %s", err) - } - - // initialize the global logger to the log level, with formatting and output splitter already set - err = log.Initialize(conf.LogLevel) - if err != nil { - return fmt.Errorf("error creating logger: %s", err) - } - - return a(c.Context, conf) -} diff --git a/cmd/gotosocial/templateflags.go b/cmd/gotosocial/server.go index c7bf93bc0..742a4f505 100644 --- a/cmd/gotosocial/templateflags.go +++ b/cmd/gotosocial/server.go @@ -19,23 +19,31 @@ 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" - "github.com/urfave/cli/v2" ) -func templateFlags(flagNames, envNames config.Flags, defaults config.Defaults) []cli.Flag { - return []cli.Flag{ - &cli.StringFlag{ - Name: flagNames.TemplateBaseDir, - Usage: "Basedir for html templating files for rendering pages and composing emails.", - Value: defaults.TemplateBaseDir, - EnvVars: []string{envNames.TemplateBaseDir}, +// serverCommands returns the 'server' subcommand +func serverCommands() *cobra.Command { + serverCmd := &cobra.Command{ + Use: "server", + Short: "gotosocial server-related tasks", + } + + serverStartCmd := &cobra.Command{ + Use: "start", + Short: "start the gotosocial server", + PreRunE: func(cmd *cobra.Command, args []string) error { + return preRun(cmd) }, - &cli.StringFlag{ - Name: flagNames.AssetBaseDir, - Usage: "Directory to serve static assets from, accessible at example.com/assets/", - Value: defaults.AssetBaseDir, - EnvVars: []string{envNames.AssetBaseDir}, + RunE: func(cmd *cobra.Command, args []string) error { + return run(cmd.Context(), server.Start) }, } + flag.Server(serverStartCmd, config.Defaults) + + serverCmd.AddCommand(serverStartCmd) + return serverCmd } diff --git a/cmd/gotosocial/smtpflags.go b/cmd/gotosocial/smtpflags.go deleted file mode 100644 index 5c790ef7e..000000000 --- a/cmd/gotosocial/smtpflags.go +++ /dev/null @@ -1,59 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see <http://www.gnu.org/licenses/>. -*/ - -package main - -import ( - "github.com/superseriousbusiness/gotosocial/internal/config" - "github.com/urfave/cli/v2" -) - -func smtpFlags(flagNames, envNames config.Flags, defaults config.Defaults) []cli.Flag { - return []cli.Flag{ - &cli.StringFlag{ - Name: flagNames.SMTPHost, - Usage: "Host of the smtp server. Eg., 'smtp.eu.mailgun.org'", - Value: defaults.SMTPHost, - EnvVars: []string{envNames.SMTPHost}, - }, - &cli.IntFlag{ - Name: flagNames.SMTPPort, - Usage: "Port of the smtp server. Eg., 587", - Value: defaults.SMTPPort, - EnvVars: []string{envNames.SMTPPort}, - }, - &cli.StringFlag{ - Name: flagNames.SMTPUsername, - Usage: "Username to authenticate with the smtp server as. Eg., 'postmaster@mail.example.org'", - Value: defaults.SMTPUsername, - EnvVars: []string{envNames.SMTPUsername}, - }, - &cli.StringFlag{ - Name: flagNames.SMTPPassword, - Usage: "Password to pass to the smtp server.", - Value: defaults.SMTPPassword, - EnvVars: []string{envNames.SMTPPassword}, - }, - &cli.StringFlag{ - Name: flagNames.SMTPFrom, - Usage: "Address to use as the 'from' field of the email. Eg., 'gotosocial@example.org'", - Value: defaults.SMTPFrom, - EnvVars: []string{envNames.SMTPFrom}, - }, - } -} diff --git a/cmd/gotosocial/statusesflags.go b/cmd/gotosocial/statusesflags.go deleted file mode 100644 index af52dc0ae..000000000 --- a/cmd/gotosocial/statusesflags.go +++ /dev/null @@ -1,59 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see <http://www.gnu.org/licenses/>. -*/ - -package main - -import ( - "github.com/superseriousbusiness/gotosocial/internal/config" - "github.com/urfave/cli/v2" -) - -func statusesFlags(flagNames, envNames config.Flags, defaults config.Defaults) []cli.Flag { - return []cli.Flag{ - &cli.IntFlag{ - Name: flagNames.StatusesMaxChars, - Usage: "Max permitted characters for posted statuses", - Value: defaults.StatusesMaxChars, - EnvVars: []string{envNames.StatusesMaxChars}, - }, - &cli.IntFlag{ - Name: flagNames.StatusesCWMaxChars, - Usage: "Max permitted characters for content/spoiler warnings on statuses", - Value: defaults.StatusesCWMaxChars, - EnvVars: []string{envNames.StatusesCWMaxChars}, - }, - &cli.IntFlag{ - Name: flagNames.StatusesPollMaxOptions, - Usage: "Max amount of options permitted on a poll", - Value: defaults.StatusesPollMaxOptions, - EnvVars: []string{envNames.StatusesPollMaxOptions}, - }, - &cli.IntFlag{ - Name: flagNames.StatusesPollOptionMaxChars, - Usage: "Max amount of characters for a poll option", - Value: defaults.StatusesPollOptionMaxChars, - EnvVars: []string{envNames.StatusesPollOptionMaxChars}, - }, - &cli.IntFlag{ - Name: flagNames.StatusesMaxMediaFiles, - Usage: "Maximum number of media files/attachments per status", - Value: defaults.StatusesMaxMediaFiles, - EnvVars: []string{envNames.StatusesMaxMediaFiles}, - }, - } -} diff --git a/cmd/gotosocial/storageflags.go b/cmd/gotosocial/storageflags.go deleted file mode 100644 index 326e1fae5..000000000 --- a/cmd/gotosocial/storageflags.go +++ /dev/null @@ -1,59 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see <http://www.gnu.org/licenses/>. -*/ - -package main - -import ( - "github.com/superseriousbusiness/gotosocial/internal/config" - "github.com/urfave/cli/v2" -) - -func storageFlags(flagNames, envNames config.Flags, defaults config.Defaults) []cli.Flag { - return []cli.Flag{ - &cli.StringFlag{ - Name: flagNames.StorageBackend, - Usage: "Storage backend to use for media attachments", - Value: defaults.StorageBackend, - EnvVars: []string{envNames.StorageBackend}, - }, - &cli.StringFlag{ - Name: flagNames.StorageBasePath, - Usage: "Full path to an already-created directory where gts should store/retrieve media files. Subfolders will be created within this dir.", - Value: defaults.StorageBasePath, - EnvVars: []string{envNames.StorageBasePath}, - }, - &cli.StringFlag{ - Name: flagNames.StorageServeProtocol, - Usage: "Protocol to use for serving media attachments (use https if storage is local)", - Value: defaults.StorageServeProtocol, - EnvVars: []string{envNames.StorageServeProtocol}, - }, - &cli.StringFlag{ - Name: flagNames.StorageServeHost, - Usage: "Hostname to serve media attachments from (use the same value as host if storage is local)", - Value: defaults.StorageServeHost, - EnvVars: []string{envNames.StorageServeHost}, - }, - &cli.StringFlag{ - Name: flagNames.StorageServeBasePath, - Usage: "Path to append to protocol and hostname to create the base path from which media files will be served (default will mostly be fine)", - Value: defaults.StorageServeBasePath, - EnvVars: []string{envNames.StorageServeBasePath}, - }, - } -} diff --git a/cmd/gotosocial/servercommands.go b/cmd/gotosocial/testrig.go index fb6574216..b78996475 100644 --- a/cmd/gotosocial/servercommands.go +++ b/cmd/gotosocial/testrig.go @@ -19,24 +19,24 @@ package main import ( - "github.com/superseriousbusiness/gotosocial/internal/cliactions/server" - "github.com/urfave/cli/v2" + "github.com/spf13/cobra" + "github.com/superseriousbusiness/gotosocial/cmd/gotosocial/action/testrig" ) -func serverCommands(allFlags []cli.Flag) []*cli.Command { - return []*cli.Command{ - { - Name: "server", - Usage: "gotosocial server-related tasks", - Subcommands: []*cli.Command{ - { - Name: "start", - Usage: "start the gotosocial server", - Action: func(c *cli.Context) error { - return runAction(c, allFlags, server.Start) - }, - }, - }, +func testrigCommands() *cobra.Command { + testrigCmd := &cobra.Command{ + Use: "testrig", + Short: "gotosocial testrig-related tasks", + } + + testrigStartCmd := &cobra.Command{ + Use: "start", + Short: "start the gotosocial testrig server", + RunE: func(cmd *cobra.Command, args []string) error { + return run(cmd.Context(), testrig.Start) }, } + + testrigCmd.AddCommand(testrigStartCmd) + return testrigCmd } |