You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
253 lines
6.4 KiB
253 lines
6.4 KiB
package main
|
|
|
|
import (
|
|
"context"
|
|
"log"
|
|
"os"
|
|
"os/signal"
|
|
"sort"
|
|
"syscall"
|
|
"time"
|
|
|
|
"git.aiterp.net/stufflog/server/database"
|
|
"git.aiterp.net/stufflog/server/graph"
|
|
"git.aiterp.net/stufflog/server/internal/generate"
|
|
"git.aiterp.net/stufflog/server/internal/slerrors"
|
|
"git.aiterp.net/stufflog/server/internal/space"
|
|
"git.aiterp.net/stufflog/server/models"
|
|
"git.aiterp.net/stufflog/server/services"
|
|
"github.com/99designs/gqlgen/graphql/playground"
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/pkg/errors"
|
|
"github.com/pressly/goose"
|
|
"github.com/urfave/cli/v2"
|
|
)
|
|
|
|
var dbDriver string
|
|
var dbConnect string
|
|
var listenAddress string
|
|
var s3Host string
|
|
var s3AccessKey string
|
|
var s3SecretKey string
|
|
var s3BucketName string
|
|
var s3MaxFileSize int64
|
|
var s3Secure bool
|
|
var s3RootDirectory string
|
|
var s3UrlRoot string
|
|
|
|
func main() {
|
|
app := &cli.App{
|
|
Name: "stufflog",
|
|
Usage: "Issue tracker for your home and hobbies",
|
|
Flags: []cli.Flag{
|
|
&cli.StringFlag{
|
|
Name: "db-driver",
|
|
Value: "mysql",
|
|
Usage: "Database driver",
|
|
EnvVars: []string{"DATABASE_DRIVER"},
|
|
Destination: &dbDriver,
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "db-connect",
|
|
Value: "stufflog_user:stuff1234@(localhost:3306)/stufflog",
|
|
Usage: "Database connection string or path",
|
|
EnvVars: []string{"DATABASE_CONNECT"},
|
|
Destination: &dbConnect,
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "listenAddress",
|
|
Value: ":8000",
|
|
Usage: "Address to bind the server to.",
|
|
EnvVars: []string{"SERVER_LISTEN"},
|
|
Destination: &listenAddress,
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "s3-host",
|
|
Value: "localhost",
|
|
Usage: "S3 Host (without https://)",
|
|
EnvVars: []string{"S3_HOST"},
|
|
Destination: &s3Host,
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "s3-access-key",
|
|
Value: "",
|
|
Usage: "S3 access key (not the secret key)",
|
|
EnvVars: []string{"S3_ACCESS_KEY"},
|
|
Destination: &s3AccessKey,
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "s3-secret-key",
|
|
Value: "",
|
|
Usage: "S3 secret key",
|
|
EnvVars: []string{"S3_SECRET_KEY"},
|
|
Destination: &s3SecretKey,
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "s3-bucket-name",
|
|
Value: "stufflog",
|
|
Usage: "S3 bucket name",
|
|
EnvVars: []string{"S3_BUCKET"},
|
|
Destination: &s3BucketName,
|
|
},
|
|
&cli.Int64Flag{
|
|
Name: "s3-max-file-size",
|
|
Value: 1024 * 1024,
|
|
Usage: "S3 maximum file size",
|
|
EnvVars: []string{"S3_MAX_FILE_SIZE"},
|
|
Destination: &s3MaxFileSize,
|
|
},
|
|
&cli.BoolFlag{
|
|
Name: "s3-secure",
|
|
Usage: "Enable HTTPS for the s3 server",
|
|
EnvVars: []string{"S3_HTTPS", "S3_SECURE"},
|
|
Value: false,
|
|
Destination: &s3Secure,
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "s3-root-directory",
|
|
Value: "stufflog/userdata",
|
|
Usage: "Root directory under S3 server to use.",
|
|
EnvVars: []string{"S3_ROOT_DIRECTORY"},
|
|
Destination: &s3RootDirectory,
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "s3-url-root",
|
|
Value: "",
|
|
Usage: "The URL root for public URLs; includes the root directory. It's generated if blank.",
|
|
EnvVars: []string{"S3_URL_ROOT"},
|
|
Destination: &s3UrlRoot,
|
|
},
|
|
},
|
|
Commands: []*cli.Command{
|
|
{
|
|
Name: "reset-admin",
|
|
Usage: "Reset the admin user (or create it)",
|
|
Action: func(c *cli.Context) error {
|
|
db, err := database.Open(dbDriver, dbConnect)
|
|
if err != nil {
|
|
return errors.Wrap(err, "Failed to connect to database")
|
|
}
|
|
|
|
timeout, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
|
defer cancel()
|
|
|
|
newPass := generate.Generate(20, "")
|
|
|
|
admin, err := db.Users().Find(timeout, "Admin")
|
|
if slerrors.IsNotFound(err) {
|
|
admin = &models.User{
|
|
ID: "Admin",
|
|
Name: "Administrator",
|
|
Active: true,
|
|
Admin: true,
|
|
}
|
|
|
|
err = admin.SetPassword(newPass)
|
|
if err != nil {
|
|
return errors.Wrap(err, "Failed to set password")
|
|
}
|
|
|
|
_, err = db.Users().Insert(timeout, *admin)
|
|
if err != nil {
|
|
return errors.Wrap(err, "Failed to inset user")
|
|
}
|
|
} else if err != nil {
|
|
return err
|
|
} else {
|
|
err = admin.SetPassword(newPass)
|
|
if err != nil {
|
|
return errors.Wrap(err, "Failed to set password")
|
|
}
|
|
|
|
err = db.Users().Save(timeout, *admin)
|
|
if err != nil {
|
|
return errors.Wrap(err, "Failed to save user")
|
|
}
|
|
}
|
|
|
|
log.Println("Username:", admin.ID)
|
|
log.Println("Password:", newPass)
|
|
|
|
return nil
|
|
},
|
|
},
|
|
{
|
|
Name: "migrate",
|
|
Usage: "Migrate the configured database",
|
|
Action: func(c *cli.Context) error {
|
|
db, err := database.Open(dbDriver, dbConnect)
|
|
if err != nil {
|
|
return errors.Wrap(err, "Failed to connect to database")
|
|
}
|
|
|
|
err = db.Migrate()
|
|
if err == goose.ErrNoNextVersion {
|
|
log.Println("No more migrations to run")
|
|
} else if err != nil {
|
|
return errors.Wrap(err, "Failed to run migration")
|
|
}
|
|
|
|
log.Println("Migration succeeded")
|
|
|
|
return nil
|
|
},
|
|
},
|
|
{
|
|
Name: "server",
|
|
Usage: "Run the server",
|
|
Action: func(c *cli.Context) error {
|
|
db, err := database.Open(dbDriver, dbConnect)
|
|
if err != nil {
|
|
return errors.Wrap(err, "Failed to connect to database")
|
|
}
|
|
|
|
s3, err := space.Connect(
|
|
s3Host, s3AccessKey, s3SecretKey, s3BucketName,
|
|
s3Secure, s3MaxFileSize, s3RootDirectory, s3UrlRoot,
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
bundle := services.NewBundle(db, s3)
|
|
|
|
server := gin.New()
|
|
server.GET("/graphql", graph.Gin(bundle, db))
|
|
server.POST("/graphql", graph.Gin(bundle, db))
|
|
server.GET("/playground", gin.WrapH(playground.Handler("StuffLog GraphQL Playground", "/graphql")))
|
|
|
|
exitSignal := make(chan os.Signal)
|
|
signal.Notify(exitSignal, os.Interrupt, os.Kill, syscall.SIGTERM)
|
|
|
|
errCh := make(chan error)
|
|
go func() {
|
|
err := server.Run(listenAddress)
|
|
if err != nil {
|
|
errCh <- err
|
|
}
|
|
}()
|
|
|
|
select {
|
|
case sig := <-exitSignal:
|
|
{
|
|
log.Println("Received signal", sig)
|
|
return nil
|
|
}
|
|
case err := <-errCh:
|
|
{
|
|
return err
|
|
}
|
|
}
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
sort.Sort(cli.FlagsByName(app.Flags))
|
|
sort.Sort(cli.CommandsByName(app.Commands))
|
|
|
|
err := app.Run(os.Args)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
}
|