diff --git a/cmd/gotosocial/main.go b/cmd/gotosocial/main.go index 78c7002..ffb7b9b 100644 --- a/cmd/gotosocial/main.go +++ b/cmd/gotosocial/main.go @@ -22,23 +22,69 @@ import ( "os" "github.com/gotosocial/gotosocial/cmd/server" + "github.com/gotosocial/gotosocial/internal/consts" "github.com/sirupsen/logrus" "github.com/urfave/cli/v2" ) func main() { + flagNames := consts.GetFlagNames() + envNames := consts.GetEnvNames() app := &cli.App{ Flags: []cli.Flag{ + // GENERAL FLAGS &cli.StringFlag{ - Name: "config", - Aliases: []string{"c"}, - Usage: "Load configuration from `FILE`", + Name: flagNames.LogLevel, + Usage: "Log level to run at: debug, info, warn, fatal", + Value: "info", + EnvVars: []string{"GTS_LOG_LEVEL"}, }, &cli.StringFlag{ - Name: "log-level", - Usage: "Log level to run at: debug, info, warn, fatal", - Value: "info", + Name: flagNames.ApplicationName, + Usage: "Name of the application, used in various places internally", + Value: "gotosocial", + EnvVars: []string{envNames.ApplicationName}, + Hidden: true, + }, + + // DATABASE FLAGS + &cli.StringFlag{ + Name: flagNames.DbType, + Usage: "Database type: eg., postgres", + Value: "postgres", + EnvVars: []string{envNames.DbType}, + }, + &cli.StringFlag{ + Name: flagNames.DbAddress, + Usage: "Database ipv4 address or hostname", + Value: "localhost", + EnvVars: []string{envNames.DbAddress}, + }, + &cli.IntFlag{ + Name: flagNames.DbPort, + Usage: "Database port", + Value: 5432, + EnvVars: []string{envNames.DbPort}, + }, + &cli.StringFlag{ + Name: flagNames.DbUser, + Usage: "Database username", + Value: "postgres", + EnvVars: []string{envNames.DbUser}, + }, + &cli.StringFlag{ + Name: flagNames.DbPassword, + Usage: "Database password", + Value: "postgres", + EnvVars: []string{envNames.DbPassword}, + FilePath: "./dbpass", + }, + &cli.StringFlag{ + Name: flagNames.DbDatabase, + Usage: "Database name", + Value: "postgres", + EnvVars: []string{envNames.DbDatabase}, }, }, Commands: []*cli.Command{ diff --git a/cmd/server/server.go b/cmd/server/server.go index 4f29699..bd16e1f 100644 --- a/cmd/server/server.go +++ b/cmd/server/server.go @@ -25,43 +25,28 @@ import ( "os/signal" "syscall" + "github.com/gotosocial/gotosocial/internal/config" "github.com/gotosocial/gotosocial/internal/db" - "github.com/sirupsen/logrus" + "github.com/gotosocial/gotosocial/internal/log" "github.com/urfave/cli/v2" ) -// getLog will try to set the logrus log level to the -// desired level specified by the user with the --log-level flag -func getLog(c *cli.Context) (*logrus.Logger, error) { - log := logrus.New() - logLevel, err := logrus.ParseLevel(c.String("log-level")) - if err != nil { - return nil, err - } - log.SetLevel(logLevel) - return log, nil -} - // Run starts the gotosocial server func Run(c *cli.Context) error { - log, err := getLog(c) + log, err := log.New(c.String("log-level")) if err != nil { return fmt.Errorf("error creating logger: %s", err) } - ctx := context.Background() - dbConfig := &db.Config{ - Type: "POSTGRES", - Address: "", - Port: 5432, - User: "", - Password: "whatever", - Database: "postgres", - ApplicationName: "gotosocial", + var gtsConfig *config.Config + if gtsConfig, err = config.New(c.String("config")); err != nil { + return fmt.Errorf("error creating config: %s", err) } - dbService, err := db.NewService(ctx, dbConfig, log) + + ctx := context.Background() + dbService, err := db.NewService(ctx, gtsConfig.DBConfig, log) if err != nil { - return err + return fmt.Errorf("error creating dbservice: %s", err) } // catch shutdown signals from the operating system diff --git a/go.mod b/go.mod index 65d4ccd..edf69c9 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,7 @@ require ( github.com/golang/protobuf v1.4.3 // indirect github.com/google/go-cmp v0.5.4 // indirect github.com/jinzhu/inflection v1.0.0 // indirect + github.com/namsral/flag v1.7.4-pre // indirect github.com/onsi/ginkgo v1.15.0 // indirect github.com/onsi/gomega v1.10.5 // indirect github.com/sirupsen/logrus v1.8.0 diff --git a/go.sum b/go.sum index b3350dc..0365c4f 100644 --- a/go.sum +++ b/go.sum @@ -77,6 +77,8 @@ github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OH github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/namsral/flag v1.7.4-pre h1:b2ScHhoCUkbsq0d2C15Mv+VU8bl8hAXV8arnWiOHNZs= +github.com/namsral/flag v1.7.4-pre/go.mod h1:OXldTctbM6SWH1K899kPZcf65KxJiD7MsceFUpB5yDo= github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= diff --git a/internal/config/config.go b/internal/config/config.go new file mode 100644 index 0000000..9b6935a --- /dev/null +++ b/internal/config/config.go @@ -0,0 +1,72 @@ +/* + GoToSocial + Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + +package config + +import ( + "encoding/json" + "fmt" + "os" + + "github.com/gotosocial/gotosocial/internal/db" +) + +// Config contains all the configuration needed to run gotosocial +type Config struct { + DBConfig *db.Config `json:"db,omitempty"` +} + +// New returns a new config, or an error if something goes amiss. +// The path parameter is optional, for loading a configuration json from the given path. +func New(path string) (*Config, error) { + var config *Config + if path != "" { + var err error + if config, err = loadFromFile(path); err != nil { + return nil, fmt.Errorf("error creating config: %s", err) + } + } + + return config, nil +} + +// loadFromFile takes a path to a .json file and attempts to load a Config object from it +func loadFromFile(path string) (*Config, error) { + bytes, err := os.ReadFile(path) + if err != nil { + return nil, fmt.Errorf("could not read file at path %s: %s", path, err) + } + + config := &Config{} + if err := json.Unmarshal(bytes, config); err != nil { + return nil, fmt.Errorf("could not unmarshal file at path %s: %s", path, err) + } + + return config, nil +} + +// WithFlags returns a copy of this config object with flags set using the provided flags object +func (c *Config) WithFlags(f Flags) *Config { + return c +} + +// Flags is a wrapper for any type that can store keyed flags and give them back +type Flags interface { + String(k string) string + Int(k string) int +} diff --git a/internal/consts/consts.go b/internal/consts/consts.go new file mode 100644 index 0000000..92c574a --- /dev/null +++ b/internal/consts/consts.go @@ -0,0 +1,77 @@ +/* + GoToSocial + Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + +// Package consts is where we shove any consts that don't really belong anywhere else in the code. +// Don't judge me. +package consts + +// FlagNames is used for storing the names of the various flags used for +// initializing and storing urfavecli flag variables. +type FlagNames struct { + LogLevel string + ApplicationName string + DbType string + DbAddress string + DbPort string + DbUser string + DbPassword string + DbDatabase string +} + +// GetFlagNames returns a struct containing the names of the various flags used for +// initializing and storing urfavecli flag variables. +func GetFlagNames() FlagNames { + return FlagNames{ + LogLevel: "log-level", + ApplicationName: "application-name", + DbType: "db-type", + DbAddress: "db-address", + DbPort: "db-port", + DbUser: "db-users", + DbPassword: "db-password", + DbDatabase: "db-database", + } +} + +// EnvNames is used for storing the environment variable keys used for +// initializing and storing urfavecli flag variables. +type EnvNames struct { + LogLevel string + ApplicationName string + DbType string + DbAddress string + DbPort string + DbUser string + DbPassword string + DbDatabase string +} + +// GetEnvNames returns a struct containing the names of the environment variable keys used for +// initializing and storing urfavecli flag variables. +func GetEnvNames() FlagNames { + return FlagNames{ + LogLevel: "GTS_LOG_LEVEL", + ApplicationName: "GTS_APPLICATION_NAME", + DbType: "GTS_DB_TYPE", + DbAddress: "GTS_DB_ADDRESS", + DbPort: "GTS_DB_PORT", + DbUser: "GTS_DB_USER", + DbPassword: "GTS_DB_PASSWORD", + DbDatabase: "GTS_DB_DATABASE", + } +} diff --git a/internal/db/postgres.go b/internal/db/postgres.go index f4cf474..14c8d3d 100644 --- a/internal/db/postgres.go +++ b/internal/db/postgres.go @@ -23,6 +23,7 @@ import ( "errors" "fmt" "net/url" + "strings" "time" "github.com/go-fed/activity/streams/vocab" @@ -95,7 +96,7 @@ func newPostgresService(ctx context.Context, config *Config, log *logrus.Entry) // derivePGOptions takes an application config and returns either a ready-to-use *pg.Options // with sensible defaults, or an error if it's not satisfied by the provided config. func derivePGOptions(config *Config) (*pg.Options, error) { - if config.Type != dbTypePostgres { + if strings.ToUpper(config.Type) != dbTypePostgres { return nil, fmt.Errorf("expected db type of %s but got %s", dbTypePostgres, config.Type) } diff --git a/internal/db/service.go b/internal/db/service.go index 9a1d3ce..6c73860 100644 --- a/internal/db/service.go +++ b/internal/db/service.go @@ -46,13 +46,14 @@ type Service interface { // Config provides configuration options for the database connection type Config struct { - Type string - Address string - Port int - User string - Password string - Database string - ApplicationName string + Type string `json:"type,omitempty"` + Address string `json:"address,omitempty"` + Port int `json:"port,omitempty"` + User string `json:"user,omitempty"` + Password string `json:"password,omitempty"` + PasswordFile string `json:"passwordFile,omitempty"` + Database string `json:"database,omitempty"` + ApplicationName string `json:"applicationName,omitempty"` } // NewService returns a new database service that satisfies the Service interface and, by extension, diff --git a/internal/log/log.go b/internal/log/log.go new file mode 100644 index 0000000..65b820c --- /dev/null +++ b/internal/log/log.go @@ -0,0 +1,58 @@ +/* + GoToSocial + Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + +package log + +import ( + "bytes" + "os" + + "github.com/sirupsen/logrus" +) + +// New returns a new logrus logger with the specified level, +// or an error if that level can't be parsed +func New(level string) (*logrus.Logger, error) { + log := logrus.New() + log.SetOutput(&outputSplitter{}) + return setLogLevel(level, log) +} + +// outputSplitter implements the io.Writer interface for use with Logrus, and simply +// splits logs between stdout and stderr depending on their severity. +// See: https://github.com/sirupsen/logrus/issues/403#issuecomment-346437512 +type outputSplitter struct{} + +func (splitter *outputSplitter) Write(p []byte) (n int, err error) { + if bytes.Contains(p, []byte("level=error")) { + return os.Stderr.Write(p) + } + return os.Stdout.Write(p) +} + +// setLogLevel will try to set the logrus log level to the +// desired level specified by the user with the --log-level flag +func setLogLevel(level string, logger *logrus.Logger) (*logrus.Logger, error) { + log := logrus.New() + logLevel, err := logrus.ParseLevel(level) + if err != nil { + return nil, err + } + log.SetLevel(logLevel) + return log, nil +}