Add interactive configuration
This adds a new --config flag and rearranges some config fields.
This commit is contained in:
parent
6bdb7a1c1c
commit
cc224db6e6
4 changed files with 250 additions and 13 deletions
8
app.go
8
app.go
|
@ -36,6 +36,7 @@ var shttp = http.NewServeMux()
|
||||||
|
|
||||||
func Serve() {
|
func Serve() {
|
||||||
createConfig := flag.Bool("create-config", false, "Creates a basic configuration and exits")
|
createConfig := flag.Bool("create-config", false, "Creates a basic configuration and exits")
|
||||||
|
doConfig := flag.Bool("config", false, "Run the configuration process")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
if *createConfig {
|
if *createConfig {
|
||||||
|
@ -48,6 +49,13 @@ func Serve() {
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
|
} else if *doConfig {
|
||||||
|
err := config.Configure()
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Unable to configure: %v", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Info("Initializing...")
|
log.Info("Initializing...")
|
||||||
|
|
|
@ -5,7 +5,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
configFile = "config.ini"
|
FileName = "config.ini"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
|
@ -24,18 +24,22 @@ type (
|
||||||
}
|
}
|
||||||
|
|
||||||
AppCfg struct {
|
AppCfg struct {
|
||||||
MultiUser bool `ini:"multiuser"`
|
SiteName string `ini:"site_name"`
|
||||||
OpenSignups bool `ini:"open_signups"`
|
|
||||||
|
// Site appearance
|
||||||
|
Theme string `ini:"theme"`
|
||||||
|
JSDisabled bool `ini:"disable_js"`
|
||||||
|
WebFonts bool `ini:"webfonts"`
|
||||||
|
|
||||||
|
// Users
|
||||||
|
SingleUser bool `ini:"single_user"`
|
||||||
|
OpenRegistration bool `ini:"open_registration"`
|
||||||
|
MinUsernameLen int `ini:"min_username_len"`
|
||||||
|
|
||||||
|
// Federation
|
||||||
Federation bool `ini:"federation"`
|
Federation bool `ini:"federation"`
|
||||||
PublicStats bool `ini:"public_stats"`
|
PublicStats bool `ini:"public_stats"`
|
||||||
Private bool `ini:"private"`
|
Private bool `ini:"private"`
|
||||||
|
|
||||||
Name string `ini:"site_name"`
|
|
||||||
|
|
||||||
JSDisabled bool `ini:"disable_js"`
|
|
||||||
|
|
||||||
// User registration
|
|
||||||
MinUsernameLen int `ini:"min_username_len"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Config struct {
|
Config struct {
|
||||||
|
@ -57,15 +61,18 @@ func New() *Config {
|
||||||
Port: 3306,
|
Port: 3306,
|
||||||
},
|
},
|
||||||
App: AppCfg{
|
App: AppCfg{
|
||||||
|
Theme: "write",
|
||||||
|
WebFonts: true,
|
||||||
|
SingleUser: true,
|
||||||
|
MinUsernameLen: 3,
|
||||||
Federation: true,
|
Federation: true,
|
||||||
PublicStats: true,
|
PublicStats: true,
|
||||||
MinUsernameLen: 3,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func Load() (*Config, error) {
|
func Load() (*Config, error) {
|
||||||
cfg, err := ini.Load(configFile)
|
cfg, err := ini.Load(FileName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -86,5 +93,5 @@ func Save(uc *Config) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return cfg.SaveTo(configFile)
|
return cfg.SaveTo(FileName)
|
||||||
}
|
}
|
||||||
|
|
181
config/setup.go
Normal file
181
config/setup.go
Normal file
|
@ -0,0 +1,181 @@
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/fatih/color"
|
||||||
|
"github.com/manifoldco/promptui"
|
||||||
|
"github.com/mitchellh/go-wordwrap"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Configure() error {
|
||||||
|
c, err := Load()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("No configuration yet. Creating new.")
|
||||||
|
c = New()
|
||||||
|
} else {
|
||||||
|
fmt.Println("Configuration loaded.")
|
||||||
|
}
|
||||||
|
title := color.New(color.Bold, color.BgGreen).PrintlnFunc()
|
||||||
|
|
||||||
|
intro := color.New(color.Bold, color.FgWhite).PrintlnFunc()
|
||||||
|
fmt.Println()
|
||||||
|
intro(" ✍ Write Freely Configuration ✍")
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Println(wordwrap.WrapString(" This quick configuration process will generate the application's config file, "+FileName+".\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()
|
||||||
|
|
||||||
|
prompt := promptui.Prompt{
|
||||||
|
Label: "Local port",
|
||||||
|
Validate: validatePort,
|
||||||
|
Default: fmt.Sprintf("%d", c.Server.Port),
|
||||||
|
}
|
||||||
|
port, err := prompt.Run()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
c.Server.Port, _ = strconv.Atoi(port) // Ignore error, as we've already validated number
|
||||||
|
|
||||||
|
prompt = promptui.Prompt{
|
||||||
|
Label: "Public-facing host",
|
||||||
|
Validate: validateDomain,
|
||||||
|
Default: c.Server.Host,
|
||||||
|
}
|
||||||
|
c.Server.Host, err = prompt.Run()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println()
|
||||||
|
title(" Database setup ")
|
||||||
|
fmt.Println()
|
||||||
|
|
||||||
|
prompt = promptui.Prompt{
|
||||||
|
Label: "Username",
|
||||||
|
Validate: validateNonEmpty,
|
||||||
|
Default: c.Database.User,
|
||||||
|
}
|
||||||
|
c.Database.User, err = prompt.Run()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
prompt = promptui.Prompt{
|
||||||
|
Label: "Password",
|
||||||
|
Validate: validateNonEmpty,
|
||||||
|
Default: c.Database.Password,
|
||||||
|
Mask: '*',
|
||||||
|
}
|
||||||
|
c.Database.Password, err = prompt.Run()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
prompt = promptui.Prompt{
|
||||||
|
Label: "Database name",
|
||||||
|
Validate: validateNonEmpty,
|
||||||
|
Default: c.Database.Database,
|
||||||
|
}
|
||||||
|
c.Database.Database, err = prompt.Run()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
prompt = promptui.Prompt{
|
||||||
|
Label: "Host",
|
||||||
|
Validate: validateNonEmpty,
|
||||||
|
Default: c.Database.Host,
|
||||||
|
}
|
||||||
|
c.Database.Host, err = prompt.Run()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
prompt = promptui.Prompt{
|
||||||
|
Label: "Port",
|
||||||
|
Validate: validatePort,
|
||||||
|
Default: fmt.Sprintf("%d", c.Database.Port),
|
||||||
|
}
|
||||||
|
dbPort, err := prompt.Run()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
c.Database.Port, _ = strconv.Atoi(dbPort) // Ignore error, as we've already validated number
|
||||||
|
|
||||||
|
fmt.Println()
|
||||||
|
title(" App setup ")
|
||||||
|
fmt.Println()
|
||||||
|
|
||||||
|
selPrompt := promptui.Select{
|
||||||
|
Label: "Site type",
|
||||||
|
Items: []string{"Single user", "Multiple users"},
|
||||||
|
}
|
||||||
|
_, usersType, err := selPrompt.Run()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
c.App.SingleUser = usersType == "Single user"
|
||||||
|
|
||||||
|
siteNameLabel := "Instance name"
|
||||||
|
if c.App.SingleUser {
|
||||||
|
siteNameLabel = "Blog name"
|
||||||
|
}
|
||||||
|
prompt = promptui.Prompt{
|
||||||
|
Label: siteNameLabel,
|
||||||
|
Validate: validateNonEmpty,
|
||||||
|
Default: c.App.SiteName,
|
||||||
|
}
|
||||||
|
c.App.SiteName, err = prompt.Run()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !c.App.SingleUser {
|
||||||
|
selPrompt = promptui.Select{
|
||||||
|
Label: "Registration",
|
||||||
|
Items: []string{"Open", "Closed"},
|
||||||
|
}
|
||||||
|
_, regType, err := selPrompt.Run()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
c.App.OpenRegistration = regType == "Open"
|
||||||
|
}
|
||||||
|
|
||||||
|
selPrompt = promptui.Select{
|
||||||
|
Label: "Federation",
|
||||||
|
Items: []string{"Enabled", "Disabled"},
|
||||||
|
}
|
||||||
|
_, fedType, err := selPrompt.Run()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
c.App.Federation = fedType == "Enabled"
|
||||||
|
|
||||||
|
if c.App.Federation {
|
||||||
|
selPrompt = promptui.Select{
|
||||||
|
Label: "Federation usage stats",
|
||||||
|
Items: []string{"Public", "Private"},
|
||||||
|
}
|
||||||
|
_, fedStatsType, err := selPrompt.Run()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
c.App.PublicStats = fedStatsType == "Public"
|
||||||
|
|
||||||
|
selPrompt = promptui.Select{
|
||||||
|
Label: "Instance metadata privacy",
|
||||||
|
Items: []string{"Public", "Private"},
|
||||||
|
}
|
||||||
|
_, fedStatsType, err = selPrompt.Run()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
c.App.Private = fedStatsType == "Private"
|
||||||
|
}
|
||||||
|
|
||||||
|
return Save(c)
|
||||||
|
}
|
41
config/validation.go
Normal file
41
config/validation.go
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
domainReg = regexp.MustCompile("^https?://")
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
minPort = 80
|
||||||
|
maxPort = 1<<16 - 1
|
||||||
|
)
|
||||||
|
|
||||||
|
func validateDomain(i string) error {
|
||||||
|
if !domainReg.MatchString(i) {
|
||||||
|
return fmt.Errorf("Domain must start with http:// or https://")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func validatePort(i string) error {
|
||||||
|
p, err := strconv.Atoi(i)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if p < minPort || p > maxPort {
|
||||||
|
return fmt.Errorf("Port must be a number %d - %d", minPort, maxPort)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateNonEmpty(i string) error {
|
||||||
|
if i == "" {
|
||||||
|
return fmt.Errorf("Must not be empty")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue