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.
This commit is contained in:
Matt Baer 2019-05-10 11:40:35 -04:00
parent 788713116f
commit 9023d4eb3f
2 changed files with 246 additions and 183 deletions

345
app.go
View file

@ -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 {

View file

@ -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)
}