diff --git a/cmd/writefreely/config.go b/cmd/writefreely/config.go new file mode 100644 index 0000000..c5ff455 --- /dev/null +++ b/cmd/writefreely/config.go @@ -0,0 +1,61 @@ +/* + * Copyright © 2020 A Bunch Tell LLC. + * + * This file is part of WriteFreely. + * + * WriteFreely is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, included + * in the LICENSE file in this source code package. + */ + +package main + +import ( + "github.com/writeas/writefreely" + + "github.com/urfave/cli/v2" +) + +var ( + cmdConfig cli.Command = cli.Command{ + Name: "config", + Usage: "config management tools", + Subcommands: []*cli.Command{ + &cmdConfigGenerate, + &cmdConfigInteractive, + }, + } + + cmdConfigGenerate cli.Command = cli.Command{ + Name: "generate", + Aliases: []string{"gen"}, + Usage: "Generate a basic configuration", + Action: genConfigAction, + } + + cmdConfigInteractive cli.Command = cli.Command{ + Name: "start", + Usage: "Interactive configuration process", + Action: interactiveConfigAction, + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "sections", + Value: "server db app", + Usage: "Which sections of the configuration to go through\n" + + "valid values of sections flag are any combination of 'server', 'db' and 'app' \n" + + "example: writefreely config start --sections \"db app\"", + }, + }, + } +) + +func genConfigAction(c *cli.Context) error { + app := writefreely.NewApp(c.String("c")) + return writefreely.CreateConfig(app) +} + +func interactiveConfigAction(c *cli.Context) error { + app := writefreely.NewApp(c.String("c")) + writefreely.DoConfig(app, c.String("sections")) + return nil +} diff --git a/cmd/writefreely/db.go b/cmd/writefreely/db.go new file mode 100644 index 0000000..badc805 --- /dev/null +++ b/cmd/writefreely/db.go @@ -0,0 +1,50 @@ +/* + * Copyright © 2020 A Bunch Tell LLC. + * + * This file is part of WriteFreely. + * + * WriteFreely is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, included + * in the LICENSE file in this source code package. + */ + +package main + +import ( + "github.com/writeas/writefreely" + + "github.com/urfave/cli/v2" +) + +var ( + cmdDB cli.Command = cli.Command{ + Name: "db", + Usage: "db management tools", + Subcommands: []*cli.Command{ + &cmdDBInit, + &cmdDBMigrate, + }, + } + + cmdDBInit cli.Command = cli.Command{ + Name: "init", + Usage: "Initialize Database", + Action: initDBAction, + } + + cmdDBMigrate cli.Command = cli.Command{ + Name: "migrate", + Usage: "Migrate Database", + Action: migrateDBAction, + } +) + +func initDBAction(c *cli.Context) error { + app := writefreely.NewApp(c.String("c")) + return writefreely.CreateSchema(app) +} + +func migrateDBAction(c *cli.Context) error { + app := writefreely.NewApp(c.String("c")) + return writefreely.Migrate(app) +} diff --git a/cmd/writefreely/keys.go b/cmd/writefreely/keys.go new file mode 100644 index 0000000..9028f51 --- /dev/null +++ b/cmd/writefreely/keys.go @@ -0,0 +1,39 @@ +/* + * Copyright © 2020 A Bunch Tell LLC. + * + * This file is part of WriteFreely. + * + * WriteFreely is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, included + * in the LICENSE file in this source code package. + */ + +package main + +import ( + "github.com/writeas/writefreely" + + "github.com/urfave/cli/v2" +) + +var ( + cmdKeys cli.Command = cli.Command{ + Name: "keys", + Usage: "key management tools", + Subcommands: []*cli.Command{ + &cmdGenerateKeys, + }, + } + + cmdGenerateKeys cli.Command = cli.Command{ + Name: "generate", + Aliases: []string{"gen"}, + Usage: "Generate encryption and authentication keys", + Action: genKeysAction, + } +) + +func genKeysAction(c *cli.Context) error { + app := writefreely.NewApp(c.String("c")) + return writefreely.GenerateKeyFiles(app) +} diff --git a/cmd/writefreely/main.go b/cmd/writefreely/main.go index 7fc2342..45dfb80 100644 --- a/cmd/writefreely/main.go +++ b/cmd/writefreely/main.go @@ -1,5 +1,5 @@ /* - * Copyright © 2018-2019 A Bunch Tell LLC. + * Copyright © 2018-2020 A Bunch Tell LLC. * * This file is part of WriteFreely. * @@ -11,122 +11,157 @@ package main import ( - "flag" "fmt" "os" "strings" - "github.com/gorilla/mux" "github.com/writeas/web-core/log" "github.com/writeas/writefreely" + + "github.com/gorilla/mux" + "github.com/urfave/cli/v2" ) func main() { - // General options usable with other commands - debugPtr := flag.Bool("debug", false, "Enables debug logging.") - configFile := flag.String("c", "config.ini", "The configuration file to use") - - // Setup actions - createConfig := flag.Bool("create-config", false, "Creates a basic configuration and exits") - doConfig := flag.Bool("config", false, "Run the configuration process") - configSections := flag.String("sections", "server db app", "Which sections of the configuration to go through (requires --config), "+ - "valid values are any combination of 'server', 'db' and 'app' "+ - "example: writefreely --config --sections \"db app\"") - genKeys := flag.Bool("gen-keys", false, "Generate encryption and authentication keys") - createSchema := flag.Bool("init-db", false, "Initialize app database") - migrate := flag.Bool("migrate", false, "Migrate the database") - - // Admin actions - createAdmin := flag.String("create-admin", "", "Create an admin with the given username:password") - createUser := flag.String("create-user", "", "Create a regular user with the given username:password") - deleteUsername := flag.String("delete-user", "", "Delete a user with the given username") - resetPassUser := flag.String("reset-pass", "", "Reset the given user's password") - outputVersion := flag.Bool("v", false, "Output the current version") - flag.Parse() - - app := writefreely.NewApp(*configFile) - - if *outputVersion { - writefreely.OutputVersion() - os.Exit(0) - } else if *createConfig { - err := writefreely.CreateConfig(app) - if err != nil { - log.Error(err.Error()) - os.Exit(1) - } - os.Exit(0) - } else if *doConfig { - writefreely.DoConfig(app, *configSections) - os.Exit(0) - } else if *genKeys { - err := writefreely.GenerateKeyFiles(app) - if err != nil { - log.Error(err.Error()) - os.Exit(1) - } - os.Exit(0) - } else if *createSchema { - err := writefreely.CreateSchema(app) - if err != nil { - log.Error(err.Error()) - os.Exit(1) - } - os.Exit(0) - } else if *createAdmin != "" { - username, password, err := userPass(*createAdmin, true) - if err != nil { - log.Error(err.Error()) - os.Exit(1) - } - err = writefreely.CreateUser(app, username, password, true) - if err != nil { - log.Error(err.Error()) - os.Exit(1) - } - os.Exit(0) - } else if *createUser != "" { - username, password, err := userPass(*createUser, false) - if err != nil { - log.Error(err.Error()) - os.Exit(1) - } - err = writefreely.CreateUser(app, username, password, false) - if err != nil { - log.Error(err.Error()) - os.Exit(1) - } - os.Exit(0) - } else if *resetPassUser != "" { - err := writefreely.ResetPassword(app, *resetPassUser) - if err != nil { - log.Error(err.Error()) - os.Exit(1) - } - os.Exit(0) - } else if *deleteUsername != "" { - err := writefreely.DoDeleteAccount(app, *deleteUsername) + cli.VersionPrinter = func(c *cli.Context) { + fmt.Printf("%s\n", c.App.Version) + } + app := &cli.App{ + Name: "WriteFreely", + Usage: "A beautifully pared-down blogging platform", + Version: writefreely.FormatVersion(), + Action: legacyActions, // legacy due to use of flags for switching actions + Flags: []cli.Flag{ + &cli.BoolFlag{ + Name: "create-config", + Value: false, + Usage: "Generate a basic configuration", + Hidden: true, + }, + &cli.BoolFlag{ + Name: "config", + Value: false, + Usage: "Interactive configuration process", + Hidden: true, + }, + &cli.StringFlag{ + Name: "sections", + Value: "server db app", + Usage: "Which sections of the configuration to go through (requires --config)\n" + + "valid values are any combination of 'server', 'db' and 'app' \n" + + "example: writefreely --config --sections \"db app\"", + Hidden: true, + }, + &cli.BoolFlag{ + Name: "gen-keys", + Value: false, + Usage: "Generate encryption and authentication keys", + Hidden: true, + }, + &cli.BoolFlag{ + Name: "init-db", + Value: false, + Usage: "Initialize app database", + Hidden: true, + }, + &cli.BoolFlag{ + Name: "migrate", + Value: false, + Usage: "Migrate the database", + Hidden: true, + }, + &cli.StringFlag{ + Name: "create-admin", + Usage: "Create an admin with the given username:password", + Hidden: true, + }, + &cli.StringFlag{ + Name: "create-user", + Usage: "Create a regular user with the given username:password", + Hidden: true, + }, + &cli.StringFlag{ + Name: "delete-user", + Usage: "Delete a user with the given username", + Hidden: true, + }, + &cli.StringFlag{ + Name: "reset-pass", + Usage: "Reset the given user's password", + Hidden: true, + }, + }, // legacy flags (set to hidden to eventually switch to bash-complete compatible format) + } + + defaultFlags := []cli.Flag{ + &cli.StringFlag{ + Name: "c", + Value: "config.ini", + Usage: "Load configuration from `FILE`", + }, + &cli.BoolFlag{ + Name: "debug", + Value: false, + Usage: "Enables debug logging", + }, + } + + app.Flags = append(app.Flags, defaultFlags...) + + app.Commands = []*cli.Command{ + &cmdUser, + &cmdDB, + &cmdConfig, + &cmdKeys, + &cmdServe, + } + + err := app.Run(os.Args) + if err != nil { + log.Error(err.Error()) + os.Exit(1) + } +} + +func legacyActions(c *cli.Context) error { + app := writefreely.NewApp(c.String("c")) + + switch true { + case c.IsSet("create-config"): + return writefreely.CreateConfig(app) + case c.IsSet("config"): + writefreely.DoConfig(app, c.String("sections")) + return nil + case c.IsSet("gen-keys"): + return writefreely.GenerateKeyFiles(app) + case c.IsSet("init-db"): + return writefreely.CreateSchema(app) + case c.IsSet("migrate"): + return writefreely.Migrate(app) + case c.IsSet("create-admin"): + username, password, err := parseCredentials(c.String("create-admin")) if err != nil { - log.Error(err.Error()) - os.Exit(1) + return err } - os.Exit(0) - } else if *migrate { - err := writefreely.Migrate(app) + return writefreely.CreateUser(app, username, password, true) + case c.IsSet("create-user"): + username, password, err := parseCredentials(c.String("create-user")) if err != nil { - log.Error(err.Error()) - os.Exit(1) + return err } - os.Exit(0) + return writefreely.CreateUser(app, username, password, false) + case c.IsSet("delete-user"): + return writefreely.DoDeleteAccount(app, c.String("delete-user")) + case c.IsSet("reset-pass"): + return writefreely.ResetPassword(app, c.String("reset-pass")) } // Initialize the application var err error log.Info("Starting %s...", writefreely.FormatVersion()) - app, err = writefreely.Initialize(app, *debugPtr) + app, err = writefreely.Initialize(app, c.Bool("debug")) if err != nil { - log.Error("%s", err) - os.Exit(1) + return err } // Set app routes @@ -136,20 +171,14 @@ func main() { // Serve the application writefreely.Serve(app, r) + + return nil } -func userPass(credStr string, isAdmin bool) (user string, pass string, err error) { - creds := strings.Split(credStr, ":") +func parseCredentials(credentialString string) (string, string, error) { + creds := strings.Split(credentialString, ":") if len(creds) != 2 { - c := "user" - if isAdmin { - c = "admin" - } - err = fmt.Errorf("usage: writefreely --create-%s username:password", c) - return + return "", "", fmt.Errorf("invalid format for passed credentials, must be username:password") } - - user = creds[0] - pass = creds[1] - return + return creds[0], creds[1], nil } diff --git a/cmd/writefreely/user.go b/cmd/writefreely/user.go new file mode 100644 index 0000000..58ecbfb --- /dev/null +++ b/cmd/writefreely/user.go @@ -0,0 +1,97 @@ +/* + * Copyright © 2020 A Bunch Tell LLC. + * + * This file is part of WriteFreely. + * + * WriteFreely is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, included + * in the LICENSE file in this source code package. + */ + +package main + +import ( + "fmt" + + "github.com/writeas/writefreely" + + "github.com/urfave/cli/v2" +) + +var ( + cmdUser cli.Command = cli.Command{ + Name: "user", + Usage: "user management tools", + Subcommands: []*cli.Command{ + &cmdAddUser, + &cmdDelUser, + &cmdResetPass, + // TODO: possibly add a user list command + }, + } + + cmdAddUser cli.Command = cli.Command{ + Name: "create", + Usage: "Add new user", + Aliases: []string{"a", "add"}, + Flags: []cli.Flag{ + &cli.BoolFlag{ + Name: "admin", + Value: false, + Usage: "Create admin user", + }, + }, + Action: addUserAction, + } + + cmdDelUser cli.Command = cli.Command{ + Name: "delete", + Usage: "Delete user", + Aliases: []string{"del", "d"}, + Action: delUserAction, + } + + cmdResetPass cli.Command = cli.Command{ + Name: "reset-pass", + Usage: "Reset user's password", + Aliases: []string{"resetpass", "reset"}, + Action: resetPassAction, + } +) + +func addUserAction(c *cli.Context) error { + credentials := "" + if c.NArg() > 0 { + credentials = c.Args().Get(0) + } else { + return fmt.Errorf("No user passed. Example: writefreely user add [USER]:[PASSWORD]") + } + username, password, err := parseCredentials(credentials) + if err != nil { + return err + } + app := writefreely.NewApp(c.String("c")) + return writefreely.CreateUser(app, username, password, c.Bool("admin")) +} + +func delUserAction(c *cli.Context) error { + username := "" + if c.NArg() > 0 { + username = c.Args().Get(0) + } else { + return fmt.Errorf("No user passed. Example: writefreely user delete [USER]") + } + app := writefreely.NewApp(c.String("c")) + return writefreely.DoDeleteAccount(app, username) +} + +func resetPassAction(c *cli.Context) error { + username := "" + if c.NArg() > 0 { + username = c.Args().Get(0) + } else { + return fmt.Errorf("No user passed. Example: writefreely user reset-pass [USER]") + } + app := writefreely.NewApp(c.String("c")) + return writefreely.ResetPassword(app, username) +} diff --git a/cmd/writefreely/web.go b/cmd/writefreely/web.go new file mode 100644 index 0000000..a687548 --- /dev/null +++ b/cmd/writefreely/web.go @@ -0,0 +1,49 @@ +/* + * Copyright © 2020 A Bunch Tell LLC. + * + * This file is part of WriteFreely. + * + * WriteFreely is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, included + * in the LICENSE file in this source code package. + */ + +package main + +import ( + "github.com/writeas/web-core/log" + "github.com/writeas/writefreely" + + "github.com/gorilla/mux" + "github.com/urfave/cli/v2" +) + +var ( + cmdServe cli.Command = cli.Command{ + Name: "serve", + Aliases: []string{"web"}, + Usage: "Run web application", + Action: serveAction, + } +) + +func serveAction(c *cli.Context) error { + // Initialize the application + app := writefreely.NewApp(c.String("c")) + var err error + log.Info("Starting %s...", writefreely.FormatVersion()) + app, err = writefreely.Initialize(app, c.Bool("debug")) + if err != nil { + return err + } + + // Set app routes + r := mux.NewRouter() + writefreely.InitRoutes(app, r) + app.InitStaticRoutes(r) + + // Serve the application + writefreely.Serve(app, r) + + return nil +} diff --git a/go.mod b/go.mod index 5da3da4..fe5b548 100644 --- a/go.mod +++ b/go.mod @@ -1,7 +1,6 @@ module github.com/writeas/writefreely require ( - github.com/BurntSushi/toml v0.3.1 // indirect github.com/alecthomas/gometalinter v3.0.0+incompatible // indirect github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf // indirect github.com/captncraig/cors v0.0.0-20180620154129-376d45073b49 // indirect @@ -38,6 +37,7 @@ require ( github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304 // indirect github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c // indirect github.com/stretchr/testify v1.3.0 + github.com/urfave/cli/v2 v2.1.1 github.com/writeas/activity v0.1.2 github.com/writeas/activityserve v0.0.0-20191115095800-dd6d19cc8b89 github.com/writeas/go-strip-markdown v2.0.1+incompatible @@ -57,7 +57,6 @@ require ( google.golang.org/appengine v1.4.0 // indirect gopkg.in/alecthomas/kingpin.v3-unstable v3.0.0-20180810215634-df19058c872c // indirect gopkg.in/ini.v1 v1.41.0 - gopkg.in/yaml.v2 v2.2.2 // indirect src.techknowlogick.com/xgo v0.0.0-20200129005940-d0fae26e014b // indirect ) diff --git a/go.sum b/go.sum index 2d433ec..b0a423a 100644 --- a/go.sum +++ b/go.sum @@ -22,6 +22,8 @@ github.com/clbanning/mxj v1.8.4 h1:HuhwZtbyvyOw+3Z1AowPkU87JkJUSv751ELWaiTpj8I= github.com/clbanning/mxj v1.8.4/go.mod h1:BVjHeAH+rl9rs6f+QIpeRl0tfu10SXn1pUSa5PVGJng= github.com/client9/misspell v0.3.4 h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJI= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -112,6 +114,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rainycape/unidecode v0.0.0-20150907023854-cb7f23ec59be h1:ta7tUOvsPHVHGom5hKW5VXNc2xZIkfCKP8iaqOyYtUQ= github.com/rainycape/unidecode v0.0.0-20150907023854-cb7f23ec59be/go.mod h1:MIDFMn7db1kT65GmV94GzpX9Qdi7N/pQlwb+AN8wh+Q= +github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304 h1:Jpy1PXuP99tXNrhbq2BaPz9B+jNAvH1JPQQpG/9GCXY= @@ -124,12 +128,10 @@ github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/tsenart/deadcode v0.0.0-20160724212837-210d2dc333e9 h1:vY5WqiEon0ZSTGM3ayVVi+twaHKHDFUVloaQ/wug9/c= github.com/tsenart/deadcode v0.0.0-20160724212837-210d2dc333e9/go.mod h1:q+QjxYvZ+fpjMXqs+XEriussHjSYqeXVnAdSV1tkMYk= +github.com/urfave/cli/v2 v2.1.1 h1:Qt8FeAtxE/vfdrLmR3rxR6JRE0RoVmbXu8+6kZtYU4k= +github.com/urfave/cli/v2 v2.1.1/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ= github.com/writeas/activity v0.1.2 h1:Y12B5lIrabfqKE7e7HFCWiXrlfXljr9tlkFm2mp7DgY= github.com/writeas/activity v0.1.2/go.mod h1:mYYgiewmEM+8tlifirK/vl6tmB2EbjYaxwb+ndUw5T0= -github.com/writeas/activityserve v0.0.0-20191008122325-5fc3b48e70c5 h1:nG84xWpxBM8YU/FJchezJqg7yZH8ImSRow6NoYtbSII= -github.com/writeas/activityserve v0.0.0-20191008122325-5fc3b48e70c5/go.mod h1:Kz62mzYsCnrFTSTSFLXFj3fGYBQOntmBWTDDq57b46A= -github.com/writeas/activityserve v0.0.0-20191011072627-3a81f7784d5b h1:rd2wX/bTqD55hxtBjAhwLcUgaQE36c70KX3NzpDAwVI= -github.com/writeas/activityserve v0.0.0-20191011072627-3a81f7784d5b/go.mod h1:Kz62mzYsCnrFTSTSFLXFj3fGYBQOntmBWTDDq57b46A= github.com/writeas/activityserve v0.0.0-20191115095800-dd6d19cc8b89 h1:NJhzq9aTccL3SSSZMrcnYhkD6sObdY9otNZ1X6/ZKNE= github.com/writeas/activityserve v0.0.0-20191115095800-dd6d19cc8b89/go.mod h1:Kz62mzYsCnrFTSTSFLXFj3fGYBQOntmBWTDDq57b46A= github.com/writeas/go-strip-markdown v2.0.1+incompatible h1:IIqxTM5Jr7RzhigcL6FkrCNfXkvbR+Nbu1ls48pXYcw=