gpaste/cmd/server/actions/actions.go
Torjus Håkestad c6eb147e2c
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Use configured store
2022-01-25 01:57:28 +01:00

216 lines
5.2 KiB
Go

package actions
import (
"context"
"io"
"net/http"
"os"
"os/signal"
"path/filepath"
"strings"
"time"
"git.t-juice.club/torjus/gpaste"
"git.t-juice.club/torjus/gpaste/api"
"git.t-juice.club/torjus/gpaste/files"
"git.t-juice.club/torjus/gpaste/users"
"github.com/google/uuid"
"github.com/urfave/cli/v2"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
func ActionServe(c *cli.Context) error {
configPath := "gpaste-server.toml"
if c.IsSet("config") {
configPath = c.String("config")
}
var (
cfg *gpaste.ServerConfig
r io.ReadCloser
)
r, err := os.Open(configPath)
if err != nil {
cfg = &gpaste.ServerConfig{
LogLevel: "INFO",
URL: "localhost:8080",
ListenAddr: ":8080",
SigningSecret: "TODO: CHANGE THIS LOL",
Store: &gpaste.ServerStoreConfig{
Type: "memory",
},
}
} else {
defer r.Close()
cfg, err = gpaste.ServerConfigFromReader(r)
if err != nil {
if err != nil {
return cli.Exit(err, 1)
}
}
}
// Setup loggers
rootLogger := getRootLogger(cfg.LogLevel)
serverLogger := rootLogger.Named("SERV")
accessLogger := rootLogger.Named("ACCS")
// Setup contexts for clean shutdown
rootCtx, rootCancel := signal.NotifyContext(context.Background(), os.Interrupt)
defer rootCancel()
httpCtx, httpCancel := context.WithCancel(rootCtx)
defer httpCancel()
httpShutdownCtx, httpShutdownCancel := context.WithCancel(context.Background())
defer httpShutdownCancel()
// Setup stores
// Files
fileStore, fileClose, err := getFileStore(cfg)
if err != nil {
return err
}
defer fileClose() // nolint: errcheck
// Users
userStore, userClose, err := getUserStore(cfg)
if err != nil {
return err
}
defer userClose() // nolint: errcheck
if userList, err := userStore.List(); err != nil {
serverLogger.Panicw("Error checking userstore for users.", "error", err)
} else if len(userList) < 1 {
admin := users.User{
Username: "admin",
Role: users.RoleAdmin,
}
password := uuid.NewString()
if err := admin.SetPassword(password); err != nil {
serverLogger.DPanic("Error setting admin-user password.", "error", err)
}
serverLogger.Warnw("Created admin-user.", "username", admin.Username, "password", password)
}
// Auth
auth := gpaste.NewAuthService(userStore, []byte(cfg.SigningSecret))
go func() {
srv := api.NewHTTPServer(cfg)
srv.Users = userStore
srv.Files = fileStore
srv.Addr = cfg.ListenAddr
srv.Logger = serverLogger
srv.AccessLogger = accessLogger
srv.Auth = auth
// Wait for cancel
go func() {
<-httpCtx.Done()
timeoutCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second) // nolint: gomnd
defer cancel()
_ = srv.Shutdown(timeoutCtx)
}()
serverLogger.Infow("Starting HTTP server.", "addr", cfg.ListenAddr)
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
serverLogger.Errorw("Error during shutdown.", "error", err)
}
serverLogger.Infow("HTTP server shutdown complete.", "addr", cfg.ListenAddr)
httpShutdownCancel()
}()
<-httpShutdownCtx.Done()
return nil
}
func getRootLogger(level string) *zap.SugaredLogger {
logEncoderConfig := zap.NewProductionEncoderConfig()
logEncoderConfig.EncodeCaller = zapcore.ShortCallerEncoder
logEncoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder
logEncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
logEncoderConfig.EncodeDuration = zapcore.StringDurationEncoder
rootLoggerConfig := &zap.Config{
Level: zap.NewAtomicLevelAt(zap.DebugLevel),
OutputPaths: []string{"stdout"},
ErrorOutputPaths: []string{"stdout"},
Encoding: "console",
EncoderConfig: logEncoderConfig,
DisableCaller: true,
}
switch strings.ToUpper(level) {
case "DEBUG":
rootLoggerConfig.DisableCaller = false
rootLoggerConfig.Level = zap.NewAtomicLevelAt(zap.DebugLevel)
case "INFO":
rootLoggerConfig.Level = zap.NewAtomicLevelAt(zap.InfoLevel)
case "WARN", "WARNING":
rootLoggerConfig.Level = zap.NewAtomicLevelAt(zap.WarnLevel)
case "ERR", "ERROR":
rootLoggerConfig.Level = zap.NewAtomicLevelAt(zap.ErrorLevel)
}
rootLogger, err := rootLoggerConfig.Build()
if err != nil {
panic(err)
}
return rootLogger.Sugar()
}
// nolint: ireturn
func getUserStore(cfg *gpaste.ServerConfig) (users.UserStore, func() error, error) {
closer := func() error { return nil }
switch cfg.Store.Type {
case "memory":
return users.NewMemoryUserStore(), closer, nil
case "fs":
path := filepath.Join(cfg.Store.FS.Dir, "gpaste-users.db")
bs, err := users.NewBoltUserStore(path)
if err != nil {
return nil, closer, cli.Exit("error setting up user store", 1)
}
return bs, bs.Close, nil
default:
return nil, closer, cli.Exit("no userstore configured", 1)
}
}
// nolint: ireturn
func getFileStore(cfg *gpaste.ServerConfig) (files.FileStore, func() error, error) {
closer := func() error { return nil }
switch cfg.Store.Type {
case "memory":
return files.NewMemoryFileStore(), closer, nil
case "fs":
var err error
s, err := files.NewFSFileStore(cfg.Store.FS.Dir)
if err != nil {
return nil, closer, cli.Exit("error setting up filestore", 1)
}
return s, closer, nil
default:
return nil, closer, cli.Exit("No store configured", 1)
}
}