Merge pull request #127 from writeas/shorter-config-process
Shorter config process
This commit is contained in:
commit
18bafadc43
15
app.go
15
app.go
|
@ -443,8 +443,19 @@ func CreateConfig(app *App) error {
|
|||
}
|
||||
|
||||
// DoConfig runs the interactive configuration process.
|
||||
func DoConfig(app *App) {
|
||||
d, err := config.Configure(app.cfgFile)
|
||||
func DoConfig(app *App, configSections string) {
|
||||
if configSections == "" {
|
||||
configSections = "server db app"
|
||||
}
|
||||
// let's check there aren't any garbage in the list
|
||||
configSectionsArray := strings.Split(configSections, " ")
|
||||
for _, element := range configSectionsArray {
|
||||
if element != "server" && element != "db" && element != "app" {
|
||||
log.Error("Invalid argument to --sections. Valid arguments are only \"server\", \"db\" and \"app\"")
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
d, err := config.Configure(app.cfgFile, configSections)
|
||||
if err != nil {
|
||||
log.Error("Unable to configure: %v", err)
|
||||
os.Exit(1)
|
||||
|
|
|
@ -28,6 +28,9 @@ func main() {
|
|||
// 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")
|
||||
|
@ -52,7 +55,7 @@ func main() {
|
|||
}
|
||||
os.Exit(0)
|
||||
} else if *doConfig {
|
||||
writefreely.DoConfig(app)
|
||||
writefreely.DoConfig(app, *configSections)
|
||||
os.Exit(0)
|
||||
} else if *genKeys {
|
||||
err := writefreely.GenerateKeyFiles(app)
|
||||
|
|
539
config/setup.go
539
config/setup.go
|
@ -17,6 +17,7 @@ import (
|
|||
"github.com/mitchellh/go-wordwrap"
|
||||
"github.com/writeas/web-core/auth"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type SetupData struct {
|
||||
|
@ -24,7 +25,7 @@ type SetupData struct {
|
|||
Config *Config
|
||||
}
|
||||
|
||||
func Configure(fname string) (*SetupData, error) {
|
||||
func Configure(fname string, configSections string) (*SetupData, error) {
|
||||
data := &SetupData{}
|
||||
var err error
|
||||
if fname == "" {
|
||||
|
@ -52,9 +53,6 @@ func Configure(fname 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()
|
||||
|
||||
title(" Server setup ")
|
||||
fmt.Println()
|
||||
|
||||
tmpls := &promptui.PromptTemplates{
|
||||
Success: "{{ . | bold | faint }}: ",
|
||||
}
|
||||
|
@ -62,300 +60,313 @@ func Configure(fname string) (*SetupData, error) {
|
|||
Selected: fmt.Sprintf(`{{.Label}} {{ . | faint }}`),
|
||||
}
|
||||
|
||||
// Environment selection
|
||||
selPrompt := promptui.Select{
|
||||
Templates: selTmpls,
|
||||
Label: "Environment",
|
||||
Items: []string{"Development", "Production, standalone", "Production, behind reverse proxy"},
|
||||
}
|
||||
_, envType, err := selPrompt.Run()
|
||||
if err != nil {
|
||||
return data, err
|
||||
}
|
||||
isDevEnv := envType == "Development"
|
||||
isStandalone := envType == "Production, standalone"
|
||||
|
||||
data.Config.Server.Dev = isDevEnv
|
||||
|
||||
var selPrompt promptui.Select
|
||||
var prompt promptui.Prompt
|
||||
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),
|
||||
|
||||
if strings.Contains(configSections, "server") {
|
||||
title(" Server setup ")
|
||||
fmt.Println()
|
||||
|
||||
// Environment selection
|
||||
selPrompt = promptui.Select{
|
||||
Templates: selTmpls,
|
||||
Label: "Environment",
|
||||
Items: []string{"Development", "Production, standalone", "Production, behind reverse proxy"},
|
||||
}
|
||||
port, err := prompt.Run()
|
||||
_, envType, err := selPrompt.Run()
|
||||
if err != nil {
|
||||
return data, err
|
||||
}
|
||||
data.Config.Server.Port, _ = strconv.Atoi(port) // Ignore error, as we've already validated number
|
||||
isDevEnv := envType == "Development"
|
||||
isStandalone := envType == "Production, standalone"
|
||||
|
||||
data.Config.Server.Dev = isDevEnv
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
if isStandalone {
|
||||
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 = ""
|
||||
}
|
||||
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
if isStandalone {
|
||||
if strings.Contains(configSections, "db") {
|
||||
title(" Database setup ")
|
||||
fmt.Println()
|
||||
|
||||
selPrompt = promptui.Select{
|
||||
Templates: selTmpls,
|
||||
Label: "Web server mode",
|
||||
Items: []string{"Insecure (port 80)", "Secure (port 443)"},
|
||||
Label: "Database driver",
|
||||
Items: []string{"MySQL", "SQLite"},
|
||||
}
|
||||
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 = ""
|
||||
// Configure for MySQL
|
||||
data.Config.UseMySQL(isNewCfg)
|
||||
|
||||
prompt = promptui.Prompt{
|
||||
Templates: tmpls,
|
||||
Label: "Username",
|
||||
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()
|
||||
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 {
|
||||
data.Config.Server.Port = 443
|
||||
// Configure for SQLite
|
||||
data.Config.UseSQLite(isNewCfg)
|
||||
|
||||
prompt = promptui.Prompt{
|
||||
Templates: tmpls,
|
||||
Label: "Certificate path",
|
||||
Label: "Filename",
|
||||
Validate: validateNonEmpty,
|
||||
Default: data.Config.Server.TLSCertPath,
|
||||
Default: data.Config.Database.FileName,
|
||||
}
|
||||
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()
|
||||
data.Config.Database.FileName, err = prompt.Run()
|
||||
if err != nil {
|
||||
return data, err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
data.Config.Server.TLSCertPath = ""
|
||||
data.Config.Server.TLSKeyPath = ""
|
||||
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
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 sel == 0 {
|
||||
// Configure for MySQL
|
||||
data.Config.UseMySQL(isNewCfg)
|
||||
|
||||
prompt = promptui.Prompt{
|
||||
Templates: tmpls,
|
||||
Label: "Username",
|
||||
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()
|
||||
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 {
|
||||
return data, err
|
||||
}
|
||||
}
|
||||
|
||||
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{
|
||||
Templates: tmpls,
|
||||
Label: "Admin username",
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
siteNameLabel := "Instance name"
|
||||
if data.Config.App.SingleUser {
|
||||
siteNameLabel = "Blog name"
|
||||
}
|
||||
prompt = promptui.Prompt{
|
||||
Templates: tmpls,
|
||||
Label: siteNameLabel,
|
||||
Validate: validateNonEmpty,
|
||||
Default: data.Config.App.SiteName,
|
||||
}
|
||||
data.Config.App.SiteName, err = prompt.Run()
|
||||
if err != nil {
|
||||
return data, err
|
||||
}
|
||||
|
||||
prompt = promptui.Prompt{
|
||||
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{
|
||||
Templates: selTmpls,
|
||||
Label: "Federation usage stats",
|
||||
Items: []string{"Public", "Private"},
|
||||
}
|
||||
_, fedStatsType, err := selPrompt.Run()
|
||||
if err != nil {
|
||||
return data, err
|
||||
}
|
||||
data.Config.App.PublicStats = fedStatsType == "Public"
|
||||
if strings.Contains(configSections, "app") {
|
||||
title(" App setup ")
|
||||
fmt.Println()
|
||||
|
||||
selPrompt = promptui.Select{
|
||||
Templates: selTmpls,
|
||||
Label: "Instance metadata privacy",
|
||||
Items: []string{"Public", "Private"},
|
||||
Label: "Site type",
|
||||
Items: []string{"Single user blog", "Multi-user instance"},
|
||||
}
|
||||
_, fedStatsType, err = selPrompt.Run()
|
||||
_, usersType, err := selPrompt.Run()
|
||||
if err != nil {
|
||||
return data, err
|
||||
}
|
||||
data.Config.App.Private = fedStatsType == "Private"
|
||||
data.Config.App.SingleUser = usersType == "Single user blog"
|
||||
|
||||
if data.Config.App.SingleUser {
|
||||
data.User = &UserCreation{}
|
||||
|
||||
// prompt for username
|
||||
prompt = promptui.Prompt{
|
||||
Templates: tmpls,
|
||||
Label: "Admin username",
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
siteNameLabel := "Instance name"
|
||||
if data.Config.App.SingleUser {
|
||||
siteNameLabel = "Blog name"
|
||||
}
|
||||
prompt = promptui.Prompt{
|
||||
Templates: tmpls,
|
||||
Label: siteNameLabel,
|
||||
Validate: validateNonEmpty,
|
||||
Default: data.Config.App.SiteName,
|
||||
}
|
||||
data.Config.App.SiteName, err = prompt.Run()
|
||||
if err != nil {
|
||||
return data, err
|
||||
}
|
||||
|
||||
prompt = promptui.Prompt{
|
||||
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{
|
||||
Templates: selTmpls,
|
||||
Label: "Federation usage stats",
|
||||
Items: []string{"Public", "Private"},
|
||||
}
|
||||
_, fedStatsType, err := selPrompt.Run()
|
||||
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"
|
||||
}
|
||||
}
|
||||
|
||||
return data, Save(data.Config, fname)
|
||||
|
|
Loading…
Reference in New Issue