Fix T657: add --sections argument to allow partial configuration.

Use the split argument list (slice) just for validation purposes
as it's substantially easier to do `.contains` in a string instead
of a slice. As such, pass the `configSections` arguments to
`Configure()` and check the existence of each one before showing
the options to the user.

An empty argument list is replaced by "server db app" so everything
is there negating the need to check anything else in `Configure()`.
In the same vein the default is "server db app".

The parsing is done in `app.go` alongside the other flags instead
of `main.go` as described in T657.
pull/127/head
Michael Demetriou 6 years ago
parent 1d5c396327
commit 07fe366c15
  1. 5
      app.go
  2. 464
      config/setup.go

@ -235,15 +235,14 @@ func Serve() {
if *configSections == "" { if *configSections == "" {
*configSections = "server db app" *configSections = "server db app"
} }
configSectionsArray := strings.Split(*configSections, " ")
// let's check there aren't any garbage in the list // let's check there aren't any garbage in the list
configSectionsArray := strings.Split(*configSections, " ")
for _, element := range configSectionsArray { for _, element := range configSectionsArray {
if element != "server" && element != "db" && element != "app" { if element != "server" && element != "db" && element != "app" {
log.Error("Invalid argument to --sections. Valid arguments are only \"server\", \"db\" and \"app\"") log.Error("Invalid argument to --sections. Valid arguments are only \"server\", \"db\" and \"app\"")
} }
} }
d, err := config.Configure(app.cfgFile, configSectionsArray) d, err := config.Configure(app.cfgFile, *configSections)
if err != nil { if err != nil {
log.Error("Unable to configure: %v", err) log.Error("Unable to configure: %v", err)
os.Exit(1) os.Exit(1)

@ -17,6 +17,7 @@ import (
"github.com/mitchellh/go-wordwrap" "github.com/mitchellh/go-wordwrap"
"github.com/writeas/web-core/auth" "github.com/writeas/web-core/auth"
"strconv" "strconv"
"strings"
) )
type SetupData struct { type SetupData struct {
@ -24,7 +25,7 @@ type SetupData struct {
Config *Config Config *Config
} }
func Configure(fname string, configSections []string) (*SetupData, error) { func Configure(fname string, configSections string) (*SetupData, error) {
data := &SetupData{} data := &SetupData{}
var err error var err error
if fname == "" { if fname == "" {
@ -52,9 +53,6 @@ func Configure(fname string, configSections []string) (*SetupData, error) {
fmt.Println(wordwrap.WrapString(" This quick configuration process will "+action+" the application's config file, "+fname+".\n\n It validates your input along the way, so you can be sure any future errors aren't caused by a bad configuration. If you'd rather configure your server manually, instead run: writefreely --create-config and edit that file.", 75)) fmt.Println(wordwrap.WrapString(" This quick configuration process will "+action+" the application's config file, "+fname+".\n\n It validates your input along the way, so you can be sure any future errors aren't caused by a bad configuration. If you'd rather configure your server manually, instead run: writefreely --create-config and edit that file.", 75))
fmt.Println() fmt.Println()
title(" Server setup ")
fmt.Println()
tmpls := &promptui.PromptTemplates{ tmpls := &promptui.PromptTemplates{
Success: "{{ . | bold | faint }}: ", Success: "{{ . | bold | faint }}: ",
} }
@ -78,284 +76,296 @@ func Configure(fname string, configSections []string) (*SetupData, error) {
data.Config.Server.Dev = isDevEnv data.Config.Server.Dev = isDevEnv
var prompt promptui.Prompt var prompt promptui.Prompt
if isDevEnv || !isStandalone {
// Running in dev environment or behind reverse proxy; ask for port if strings.Contains(configSections, "server"){
prompt = promptui.Prompt{
Templates: tmpls, title(" Server setup ")
Label: "Local port", fmt.Println()
Validate: validatePort,
Default: fmt.Sprintf("%d", data.Config.Server.Port), if isDevEnv || !isStandalone {
// Running in dev environment or behind reverse proxy; ask for port
prompt = promptui.Prompt{
Templates: tmpls,
Label: "Local port",
Validate: validatePort,
Default: fmt.Sprintf("%d", data.Config.Server.Port),
}
port, err := prompt.Run()
if err != nil {
return data, err
}
data.Config.Server.Port, _ = strconv.Atoi(port) // Ignore error, as we've already validated number
} }
port, err := prompt.Run()
if err != nil { if isStandalone {
return data, err selPrompt = promptui.Select{
Templates: selTmpls,
Label: "Web server mode",
Items: []string{"Insecure (port 80)", "Secure (port 443)"},
}
sel, _, err := selPrompt.Run()
if err != nil {
return data, err
}
if sel == 0 {
data.Config.Server.Port = 80
data.Config.Server.TLSCertPath = ""
data.Config.Server.TLSKeyPath = ""
} else if sel == 1 {
data.Config.Server.Port = 443
prompt = promptui.Prompt{
Templates: tmpls,
Label: "Certificate path",
Validate: validateNonEmpty,
Default: data.Config.Server.TLSCertPath,
}
data.Config.Server.TLSCertPath, err = prompt.Run()
if err != nil {
return data, err
}
prompt = promptui.Prompt{
Templates: tmpls,
Label: "Key path",
Validate: validateNonEmpty,
Default: data.Config.Server.TLSKeyPath,
}
data.Config.Server.TLSKeyPath, err = prompt.Run()
if err != nil {
return data, err
}
}
} else {
data.Config.Server.TLSCertPath = ""
data.Config.Server.TLSKeyPath = ""
} }
data.Config.Server.Port, _ = strconv.Atoi(port) // Ignore error, as we've already validated number
fmt.Println()
} }
if strings.Contains(configSections, "db"){
if isStandalone { title(" Database setup ")
fmt.Println()
selPrompt = promptui.Select{ selPrompt = promptui.Select{
Templates: selTmpls, Templates: selTmpls,
Label: "Web server mode", Label: "Database driver",
Items: []string{"Insecure (port 80)", "Secure (port 443)"}, Items: []string{"MySQL", "SQLite"},
} }
sel, _, err := selPrompt.Run() sel, _, err := selPrompt.Run()
if err != nil { if err != nil {
return data, err return data, err
} }
if sel == 0 { if sel == 0 {
data.Config.Server.Port = 80 // Configure for MySQL
data.Config.Server.TLSCertPath = "" data.Config.UseMySQL(isNewCfg)
data.Config.Server.TLSKeyPath = ""
} else if sel == 1 {
data.Config.Server.Port = 443
prompt = promptui.Prompt{ prompt = promptui.Prompt{
Templates: tmpls, Templates: tmpls,
Label: "Certificate path", Label: "Username",
Validate: validateNonEmpty, Validate: validateNonEmpty,
Default: data.Config.Server.TLSCertPath, Default: data.Config.Database.User,
} }
data.Config.Server.TLSCertPath, err = prompt.Run() data.Config.Database.User, err = prompt.Run()
if err != nil { if err != nil {
return data, err return data, err
} }
prompt = promptui.Prompt{ prompt = promptui.Prompt{
Templates: tmpls, Templates: tmpls,
Label: "Key path", Label: "Password",
Validate: validateNonEmpty, Validate: validateNonEmpty,
Default: data.Config.Server.TLSKeyPath, Default: data.Config.Database.Password,
Mask: '*',
} }
data.Config.Server.TLSKeyPath, err = prompt.Run() data.Config.Database.Password, err = prompt.Run()
if err != nil {
return data, err
}
prompt = promptui.Prompt{
Templates: tmpls,
Label: "Database name",
Validate: validateNonEmpty,
Default: data.Config.Database.Database,
}
data.Config.Database.Database, err = prompt.Run()
if err != nil {
return data, err
}
prompt = promptui.Prompt{
Templates: tmpls,
Label: "Host",
Validate: validateNonEmpty,
Default: data.Config.Database.Host,
}
data.Config.Database.Host, err = prompt.Run()
if err != nil {
return data, err
}
prompt = promptui.Prompt{
Templates: tmpls,
Label: "Port",
Validate: validatePort,
Default: fmt.Sprintf("%d", data.Config.Database.Port),
}
dbPort, err := prompt.Run()
if err != nil {
return data, err
}
data.Config.Database.Port, _ = strconv.Atoi(dbPort) // Ignore error, as we've already validated number
} else if sel == 1 {
// Configure for SQLite
data.Config.UseSQLite(isNewCfg)
prompt = promptui.Prompt{
Templates: tmpls,
Label: "Filename",
Validate: validateNonEmpty,
Default: data.Config.Database.FileName,
}
data.Config.Database.FileName, err = prompt.Run()
if err != nil { if err != nil {
return data, err return data, err
} }
} }
} else {
data.Config.Server.TLSCertPath = "" fmt.Println()
data.Config.Server.TLSKeyPath = ""
}
fmt.Println()
title(" Database setup ")
fmt.Println()
selPrompt = promptui.Select{
Templates: selTmpls,
Label: "Database driver",
Items: []string{"MySQL", "SQLite"},
}
sel, _, err := selPrompt.Run()
if err != nil {
return data, err
} }
if strings.Contains(configSections, "app"){
if sel == 0 { title(" App setup ")
// Configure for MySQL fmt.Println()
data.Config.UseMySQL(isNewCfg)
selPrompt = promptui.Select{
prompt = promptui.Prompt{ Templates: selTmpls,
Templates: tmpls, Label: "Site type",
Label: "Username", Items: []string{"Single user blog", "Multi-user instance"},
Validate: validateNonEmpty,
Default: data.Config.Database.User,
}
data.Config.Database.User, err = prompt.Run()
if err != nil {
return data, err
}
prompt = promptui.Prompt{
Templates: tmpls,
Label: "Password",
Validate: validateNonEmpty,
Default: data.Config.Database.Password,
Mask: '*',
}
data.Config.Database.Password, err = prompt.Run()
if err != nil {
return data, err
}
prompt = promptui.Prompt{
Templates: tmpls,
Label: "Database name",
Validate: validateNonEmpty,
Default: data.Config.Database.Database,
}
data.Config.Database.Database, err = prompt.Run()
if err != nil {
return data, err
}
prompt = promptui.Prompt{
Templates: tmpls,
Label: "Host",
Validate: validateNonEmpty,
Default: data.Config.Database.Host,
}
data.Config.Database.Host, err = prompt.Run()
if err != nil {
return data, err
}
prompt = promptui.Prompt{
Templates: tmpls,
Label: "Port",
Validate: validatePort,
Default: fmt.Sprintf("%d", data.Config.Database.Port),
} }
dbPort, err := prompt.Run() _, usersType, err := selPrompt.Run()
if err != nil { if err != nil {
return data, err return data, err
} }
data.Config.Database.Port, _ = strconv.Atoi(dbPort) // Ignore error, as we've already validated number data.Config.App.SingleUser = usersType == "Single user blog"
} else if sel == 1 {
// Configure for SQLite if data.Config.App.SingleUser {
data.Config.UseSQLite(isNewCfg) data.User = &UserCreation{}
prompt = promptui.Prompt{ // prompt for username
Templates: tmpls, prompt = promptui.Prompt{
Label: "Filename", Templates: tmpls,
Validate: validateNonEmpty, Label: "Admin username",
Default: data.Config.Database.FileName, Validate: validateNonEmpty,
}
data.User.Username, err = prompt.Run()
if err != nil {
return data, err
}
// prompt for password
prompt = promptui.Prompt{
Templates: tmpls,
Label: "Admin password",
Validate: validateNonEmpty,
}
newUserPass, err := prompt.Run()
if err != nil {
return data, err
}
data.User.HashedPass, err = auth.HashPass([]byte(newUserPass))
if err != nil {
return data, err
}
} }
data.Config.Database.FileName, err = prompt.Run()
if err != nil { siteNameLabel := "Instance name"
return data, err if data.Config.App.SingleUser {
siteNameLabel = "Blog name"
} }
}
fmt.Println()
title(" App setup ")
fmt.Println()
selPrompt = promptui.Select{
Templates: selTmpls,
Label: "Site type",
Items: []string{"Single user blog", "Multi-user instance"},
}
_, usersType, err := selPrompt.Run()
if err != nil {
return data, err
}
data.Config.App.SingleUser = usersType == "Single user blog"
if data.Config.App.SingleUser {
data.User = &UserCreation{}
// prompt for username
prompt = promptui.Prompt{ prompt = promptui.Prompt{
Templates: tmpls, Templates: tmpls,
Label: "Admin username", Label: siteNameLabel,
Validate: validateNonEmpty, Validate: validateNonEmpty,
Default: data.Config.App.SiteName,
} }
data.User.Username, err = prompt.Run() data.Config.App.SiteName, err = prompt.Run()
if err != nil { if err != nil {
return data, err return data, err
} }
// prompt for password
prompt = promptui.Prompt{ prompt = promptui.Prompt{
Templates: tmpls, Templates: tmpls,
Label: "Admin password", Label: "Public URL",
Validate: validateNonEmpty, Validate: validateDomain,
Default: data.Config.App.Host,
} }
newUserPass, err := prompt.Run() data.Config.App.Host, err = prompt.Run()
if err != nil { if err != nil {
return data, err return data, err
} }
data.User.HashedPass, err = auth.HashPass([]byte(newUserPass)) if !data.Config.App.SingleUser {
if err != nil { selPrompt = promptui.Select{
return data, err Templates: selTmpls,
} Label: "Registration",
} Items: []string{"Open", "Closed"},
}
siteNameLabel := "Instance name" _, regType, err := selPrompt.Run()
if data.Config.App.SingleUser { if err != nil {
siteNameLabel = "Blog name" return data, err
} }
prompt = promptui.Prompt{ data.Config.App.OpenRegistration = regType == "Open"
Templates: tmpls,
Label: siteNameLabel, prompt = promptui.Prompt{
Validate: validateNonEmpty, Templates: tmpls,
Default: data.Config.App.SiteName, Label: "Max blogs per user",
} Default: fmt.Sprintf("%d", data.Config.App.MaxBlogs),
data.Config.App.SiteName, err = prompt.Run() }
if err != nil { maxBlogs, err := prompt.Run()
return data, err if err != nil {
} return data, err
}
prompt = promptui.Prompt{ data.Config.App.MaxBlogs, _ = strconv.Atoi(maxBlogs) // Ignore error, as we've already validated number
Templates: tmpls,
Label: "Public URL",
Validate: validateDomain,
Default: data.Config.App.Host,
}
data.Config.App.Host, err = prompt.Run()
if err != nil {
return data, err
}
if !data.Config.App.SingleUser {
selPrompt = promptui.Select{
Templates: selTmpls,
Label: "Registration",
Items: []string{"Open", "Closed"},
}
_, regType, err := selPrompt.Run()
if err != nil {
return data, err
}
data.Config.App.OpenRegistration = regType == "Open"
prompt = promptui.Prompt{
Templates: tmpls,
Label: "Max blogs per user",
Default: fmt.Sprintf("%d", data.Config.App.MaxBlogs),
}
maxBlogs, err := prompt.Run()
if err != nil {
return data, err
} }
data.Config.App.MaxBlogs, _ = strconv.Atoi(maxBlogs) // Ignore error, as we've already validated number
}
selPrompt = promptui.Select{
Templates: selTmpls,
Label: "Federation",
Items: []string{"Enabled", "Disabled"},
}
_, fedType, err := selPrompt.Run()
if err != nil {
return data, err
}
data.Config.App.Federation = fedType == "Enabled"
if data.Config.App.Federation {
selPrompt = promptui.Select{ selPrompt = promptui.Select{
Templates: selTmpls, Templates: selTmpls,
Label: "Federation usage stats", Label: "Federation",
Items: []string{"Public", "Private"}, Items: []string{"Enabled", "Disabled"},
} }
_, fedStatsType, err := selPrompt.Run() _, fedType, err := selPrompt.Run()
if err != nil { if err != nil {
return data, err return data, err
} }
data.Config.App.PublicStats = fedStatsType == "Public" data.Config.App.Federation = fedType == "Enabled"
selPrompt = promptui.Select{ if data.Config.App.Federation {
Templates: selTmpls, selPrompt = promptui.Select{
Label: "Instance metadata privacy", Templates: selTmpls,
Items: []string{"Public", "Private"}, Label: "Federation usage stats",
} Items: []string{"Public", "Private"},
_, fedStatsType, err = selPrompt.Run() }
if err != nil { _, fedStatsType, err := selPrompt.Run()
return data, err if err != nil {
return data, err
}
data.Config.App.PublicStats = fedStatsType == "Public"
selPrompt = promptui.Select{
Templates: selTmpls,
Label: "Instance metadata privacy",
Items: []string{"Public", "Private"},
}
_, fedStatsType, err = selPrompt.Run()
if err != nil {
return data, err
}
data.Config.App.Private = fedStatsType == "Private"
} }
data.Config.App.Private = fedStatsType == "Private"
} }
return data, Save(data.Config, fname) return data, Save(data.Config, fname)

Loading…
Cancel
Save