From 9023d4eb3f0311fd40f3f146544b3464b6b0c76f Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Fri, 10 May 2019 11:40:35 -0400 Subject: [PATCH] Move flag parsing to main package This exposes setup and admin functions in the writefreely package, and uses them in the main application, initialized by the flag parsing that now happens there. This is the first step towards making `writefreely` usable as a standalone package. --- app.go | 345 +++++++++++++++++++--------------------- cmd/writefreely/main.go | 84 +++++++++- 2 files changed, 246 insertions(+), 183 deletions(-) diff --git a/app.go b/app.go index 540b496..154943a 100644 --- a/app.go +++ b/app.go @@ -12,7 +12,6 @@ package writefreely import ( "database/sql" - "flag" "fmt" "html/template" "net/http" @@ -190,184 +189,8 @@ func pageForReq(app *app, r *http.Request) page.StaticPage { var shttp = http.NewServeMux() var fileRegex = regexp.MustCompile("/([^/]*\\.[^/]*)$") -func Serve() { - // 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") - 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") - resetPassUser := flag.String("reset-pass", "", "Reset the given user's password") - outputVersion := flag.Bool("v", false, "Output the current version") - flag.Parse() - - debugging = *debugPtr - - app := &app{ - cfgFile: *configFile, - } - - if *outputVersion { - fmt.Println(serverSoftware + " " + softwareVer) - os.Exit(0) - } else if *createConfig { - log.Info("Creating configuration...") - c := config.New() - log.Info("Saving configuration %s...", app.cfgFile) - err := config.Save(c, app.cfgFile) - if err != nil { - log.Error("Unable to save configuration: %v", err) - os.Exit(1) - } - os.Exit(0) - } else if *doConfig { - d, err := config.Configure(app.cfgFile) - if err != nil { - log.Error("Unable to configure: %v", err) - os.Exit(1) - } - if d.User != nil { - app.cfg = d.Config - connectToDatabase(app) - defer shutdown(app) - - if !app.db.DatabaseInitialized() { - err = adminInitDatabase(app) - if err != nil { - log.Error(err.Error()) - os.Exit(1) - } - } - - u := &User{ - Username: d.User.Username, - HashedPass: d.User.HashedPass, - Created: time.Now().Truncate(time.Second).UTC(), - } - - // Create blog - log.Info("Creating user %s...\n", u.Username) - err = app.db.CreateUser(u, app.cfg.App.SiteName) - if err != nil { - log.Error("Unable to create user: %s", err) - os.Exit(1) - } - log.Info("Done!") - } - os.Exit(0) - } else if *genKeys { - errStatus := 0 - - // Read keys path from config - loadConfig(app) - - // Create keys dir if it doesn't exist yet - fullKeysDir := filepath.Join(app.cfg.Server.KeysParentDir, keysDir) - if _, err := os.Stat(fullKeysDir); os.IsNotExist(err) { - err = os.Mkdir(fullKeysDir, 0700) - if err != nil { - log.Error("%s", err) - os.Exit(1) - } - } - - // Generate keys - initKeyPaths(app) - err := generateKey(emailKeyPath) - if err != nil { - errStatus = 1 - } - err = generateKey(cookieAuthKeyPath) - if err != nil { - errStatus = 1 - } - err = generateKey(cookieKeyPath) - if err != nil { - errStatus = 1 - } - - os.Exit(errStatus) - } else if *createSchema { - loadConfig(app) - connectToDatabase(app) - defer shutdown(app) - err := adminInitDatabase(app) - if err != nil { - log.Error(err.Error()) - os.Exit(1) - } - os.Exit(0) - } else if *createAdmin != "" { - err := adminCreateUser(app, *createAdmin, true) - if err != nil { - log.Error(err.Error()) - os.Exit(1) - } - os.Exit(0) - } else if *createUser != "" { - err := adminCreateUser(app, *createUser, false) - if err != nil { - log.Error(err.Error()) - os.Exit(1) - } - os.Exit(0) - } else if *resetPassUser != "" { - // Connect to the database - loadConfig(app) - connectToDatabase(app) - defer shutdown(app) - - // Fetch user - u, err := app.db.GetUserForAuth(*resetPassUser) - if err != nil { - log.Error("Get user: %s", err) - os.Exit(1) - } - - // Prompt for new password - prompt := promptui.Prompt{ - Templates: &promptui.PromptTemplates{ - Success: "{{ . | bold | faint }}: ", - }, - Label: "New password", - Mask: '*', - } - newPass, err := prompt.Run() - if err != nil { - log.Error("%s", err) - os.Exit(1) - } - - // Do the update - log.Info("Updating...") - err = adminResetPassword(app, u, newPass) - if err != nil { - log.Error("%s", err) - os.Exit(1) - } - log.Info("Success.") - os.Exit(0) - } else if *migrate { - loadConfig(app) - connectToDatabase(app) - defer shutdown(app) - - err := migrations.Migrate(migrations.NewDatastore(app.db.DB, app.db.driverName)) - if err != nil { - log.Error("migrate: %s", err) - os.Exit(1) - } - - os.Exit(0) - } +func Serve(app *app, debug bool) { + debugging = debug log.Info("Initializing...") @@ -375,7 +198,7 @@ func Serve() { hostName = app.cfg.App.Host isSingleUser = app.cfg.App.SingleUser - app.cfg.Server.Dev = *debugPtr + app.cfg.Server.Dev = debugging err := initTemplates(app.cfg) if err != nil { @@ -488,6 +311,165 @@ func Serve() { } } +// OutputVersion prints out the version of the application. +func OutputVersion() { + fmt.Println(serverSoftware + " " + softwareVer) +} + +// NewApp creates a new app instance. +func NewApp(cfgFile string) *app { + return &app{ + cfgFile: cfgFile, + } +} + +// CreateConfig creates a default configuration and saves it to the app's cfgFile. +func CreateConfig(app *app) error { + log.Info("Creating configuration...") + c := config.New() + log.Info("Saving configuration %s...", app.cfgFile) + err := config.Save(c, app.cfgFile) + if err != nil { + return fmt.Errorf("Unable to save configuration: %v", err) + } + return nil +} + +// DoConfig runs the interactive configuration process. +func DoConfig(app *app) { + d, err := config.Configure(app.cfgFile) + if err != nil { + log.Error("Unable to configure: %v", err) + os.Exit(1) + } + if d.User != nil { + app.cfg = d.Config + connectToDatabase(app) + defer shutdown(app) + + if !app.db.DatabaseInitialized() { + err = adminInitDatabase(app) + if err != nil { + log.Error(err.Error()) + os.Exit(1) + } + } + + u := &User{ + Username: d.User.Username, + HashedPass: d.User.HashedPass, + Created: time.Now().Truncate(time.Second).UTC(), + } + + // Create blog + log.Info("Creating user %s...\n", u.Username) + err = app.db.CreateUser(u, app.cfg.App.SiteName) + if err != nil { + log.Error("Unable to create user: %s", err) + os.Exit(1) + } + log.Info("Done!") + } + os.Exit(0) +} + +// GenerateKeys creates app encryption keys and saves them into the configured KeysParentDir. +func GenerateKeys(app *app) error { + // Read keys path from config + loadConfig(app) + + // Create keys dir if it doesn't exist yet + fullKeysDir := filepath.Join(app.cfg.Server.KeysParentDir, keysDir) + if _, err := os.Stat(fullKeysDir); os.IsNotExist(err) { + err = os.Mkdir(fullKeysDir, 0700) + if err != nil { + return err + } + } + + // Generate keys + initKeyPaths(app) + var keyErrs error + err := generateKey(emailKeyPath) + if err != nil { + keyErrs = err + } + err = generateKey(cookieAuthKeyPath) + if err != nil { + keyErrs = err + } + err = generateKey(cookieKeyPath) + if err != nil { + keyErrs = err + } + + return keyErrs +} + +// CreateSchema creates all database tables needed for the application. +func CreateSchema(app *app) error { + loadConfig(app) + connectToDatabase(app) + defer shutdown(app) + err := adminInitDatabase(app) + if err != nil { + return err + } + return nil +} + +// Migrate runs all necessary database migrations. +func Migrate(app *app) error { + loadConfig(app) + connectToDatabase(app) + defer shutdown(app) + + err := migrations.Migrate(migrations.NewDatastore(app.db.DB, app.db.driverName)) + if err != nil { + return fmt.Errorf("migrate: %s", err) + } + return nil +} + +// ResetPassword runs the interactive password reset process. +func ResetPassword(app *app, username string) error { + // Connect to the database + loadConfig(app) + connectToDatabase(app) + defer shutdown(app) + + // Fetch user + u, err := app.db.GetUserForAuth(username) + if err != nil { + log.Error("Get user: %s", err) + os.Exit(1) + } + + // Prompt for new password + prompt := promptui.Prompt{ + Templates: &promptui.PromptTemplates{ + Success: "{{ . | bold | faint }}: ", + }, + Label: "New password", + Mask: '*', + } + newPass, err := prompt.Run() + if err != nil { + log.Error("%s", err) + os.Exit(1) + } + + // Do the update + log.Info("Updating...") + err = adminResetPassword(app, u, newPass) + if err != nil { + log.Error("%s", err) + os.Exit(1) + } + log.Info("Success.") + return nil +} + func loadConfig(app *app) { log.Info("Loading %s configuration...", app.cfgFile) cfg, err := config.Load(app.cfgFile) @@ -533,7 +515,8 @@ func shutdown(app *app) { app.db.Close() } -func adminCreateUser(app *app, credStr string, isAdmin bool) error { +// CreateUser creates a new admin or normal user from the given username:password string. +func CreateUser(app *app, credStr string, isAdmin bool) error { // Create an admin user with --create-admin creds := strings.Split(credStr, ":") if len(creds) != 2 { diff --git a/cmd/writefreely/main.go b/cmd/writefreely/main.go index e0a293c..73866cb 100644 --- a/cmd/writefreely/main.go +++ b/cmd/writefreely/main.go @@ -1,5 +1,5 @@ /* - * Copyright © 2018 A Bunch Tell LLC. + * Copyright © 2018-2019 A Bunch Tell LLC. * * This file is part of WriteFreely. * @@ -11,9 +11,89 @@ package main import ( + "flag" + "github.com/writeas/web-core/log" "github.com/writeas/writefreely" + "os" ) func main() { - writefreely.Serve() + // 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") + 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") + 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) + os.Exit(0) + } else if *genKeys { + err := writefreely.GenerateKeys(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 != "" { + err := writefreely.CreateUser(app, *createAdmin, true) + if err != nil { + log.Error(err.Error()) + os.Exit(1) + } + os.Exit(0) + } else if *createUser != "" { + err := writefreely.CreateUser(app, *createUser, 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 *migrate { + err := writefreely.Migrate(app) + if err != nil { + log.Error(err.Error()) + os.Exit(1) + } + os.Exit(0) + } + + writefreely.Serve(app, *debugPtr) }