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.
372 lines
9 KiB
Go
372 lines
9 KiB
Go
/*
|
|
* Copyright © 2018 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 config
|
|
|
|
import (
|
|
"fmt"
|
|
"github.com/fatih/color"
|
|
"github.com/manifoldco/promptui"
|
|
"github.com/mitchellh/go-wordwrap"
|
|
"github.com/writeas/web-core/auth"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
type SetupData struct {
|
|
User *UserCreation
|
|
Config *Config
|
|
}
|
|
|
|
func Configure(fname string, configSections string) (*SetupData, error) {
|
|
data := &SetupData{}
|
|
var err error
|
|
if fname == "" {
|
|
fname = FileName
|
|
}
|
|
|
|
data.Config, err = Load(fname)
|
|
var action string
|
|
isNewCfg := false
|
|
if err != nil {
|
|
fmt.Printf("No %s configuration yet. Creating new.\n", fname)
|
|
data.Config = New()
|
|
action = "generate"
|
|
isNewCfg = true
|
|
} else {
|
|
fmt.Printf("Loaded configuration %s.\n", fname)
|
|
action = "update"
|
|
}
|
|
title := color.New(color.Bold, color.BgGreen).PrintFunc()
|
|
|
|
intro := color.New(color.Bold, color.FgWhite).PrintlnFunc()
|
|
fmt.Println()
|
|
intro(" ✍ WriteFreely Configuration ✍")
|
|
fmt.Println()
|
|
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()
|
|
|
|
tmpls := &promptui.PromptTemplates{
|
|
Success: "{{ . | bold | faint }}: ",
|
|
}
|
|
selTmpls := &promptui.SelectTemplates{
|
|
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 prompt promptui.Prompt
|
|
|
|
if strings.Contains(configSections, "server"){
|
|
|
|
title(" Server setup ")
|
|
fmt.Println()
|
|
|
|
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 strings.Contains(configSections, "db"){
|
|
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()
|
|
}
|
|
if strings.Contains(configSections, "app"){
|
|
|
|
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"
|
|
|
|
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)
|
|
}
|