diff --git a/app.go b/app.go index 86351ed..7312e83 100644 --- a/app.go +++ b/app.go @@ -36,6 +36,7 @@ var shttp = http.NewServeMux() func Serve() { createConfig := flag.Bool("create-config", false, "Creates a basic configuration and exits") + doConfig := flag.Bool("config", false, "Run the configuration process") flag.Parse() if *createConfig { @@ -48,6 +49,13 @@ func Serve() { os.Exit(1) } 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...") diff --git a/config/config.go b/config/config.go index 77a1f58..5ca1358 100644 --- a/config/config.go +++ b/config/config.go @@ -5,7 +5,7 @@ import ( ) const ( - configFile = "config.ini" + FileName = "config.ini" ) type ( @@ -24,18 +24,22 @@ type ( } AppCfg struct { - MultiUser bool `ini:"multiuser"` - OpenSignups bool `ini:"open_signups"` - Federation bool `ini:"federation"` - PublicStats bool `ini:"public_stats"` - Private bool `ini:"private"` + SiteName string `ini:"site_name"` - Name string `ini:"site_name"` + // Site appearance + Theme string `ini:"theme"` + JSDisabled bool `ini:"disable_js"` + WebFonts bool `ini:"webfonts"` - JSDisabled bool `ini:"disable_js"` + // Users + SingleUser bool `ini:"single_user"` + OpenRegistration bool `ini:"open_registration"` + MinUsernameLen int `ini:"min_username_len"` - // User registration - MinUsernameLen int `ini:"min_username_len"` + // Federation + Federation bool `ini:"federation"` + PublicStats bool `ini:"public_stats"` + Private bool `ini:"private"` } Config struct { @@ -57,15 +61,18 @@ func New() *Config { Port: 3306, }, App: AppCfg{ + Theme: "write", + WebFonts: true, + SingleUser: true, + MinUsernameLen: 3, Federation: true, PublicStats: true, - MinUsernameLen: 3, }, } } func Load() (*Config, error) { - cfg, err := ini.Load(configFile) + cfg, err := ini.Load(FileName) if err != nil { return nil, err } @@ -86,5 +93,5 @@ func Save(uc *Config) error { return err } - return cfg.SaveTo(configFile) + return cfg.SaveTo(FileName) } diff --git a/config/setup.go b/config/setup.go new file mode 100644 index 0000000..3eabce2 --- /dev/null +++ b/config/setup.go @@ -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) +} diff --git a/config/validation.go b/config/validation.go new file mode 100644 index 0000000..cdbe101 --- /dev/null +++ b/config/validation.go @@ -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 +}