2018-12-24 17:45:15 +00:00
/ *
2022-11-11 04:49:16 +00:00
* Copyright © 2018 Musing Studio LLC .
2018-12-24 17:45:15 +00:00
*
* 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 .
* /
2018-12-31 06:05:26 +00:00
2018-10-25 13:15:10 +00:00
package config
import (
"fmt"
"github.com/fatih/color"
"github.com/manifoldco/promptui"
"github.com/mitchellh/go-wordwrap"
2018-11-10 03:16:13 +00:00
"github.com/writeas/web-core/auth"
2018-10-25 13:15:10 +00:00
"strconv"
2019-06-20 20:41:03 +00:00
"strings"
2018-10-25 13:15:10 +00:00
)
2018-11-10 03:16:13 +00:00
type SetupData struct {
User * UserCreation
Config * Config
}
2019-06-20 20:41:03 +00:00
func Configure ( fname string , configSections string ) ( * SetupData , error ) {
2018-11-10 03:16:13 +00:00
data := & SetupData { }
var err error
2018-12-08 22:49:19 +00:00
if fname == "" {
fname = FileName
}
2018-11-10 03:16:13 +00:00
2018-12-08 22:49:19 +00:00
data . Config , err = Load ( fname )
2018-11-08 06:31:01 +00:00
var action string
2018-12-08 18:36:51 +00:00
isNewCfg := false
2018-10-25 13:15:10 +00:00
if err != nil {
2018-12-08 22:49:19 +00:00
fmt . Printf ( "No %s configuration yet. Creating new.\n" , fname )
2018-11-10 03:16:13 +00:00
data . Config = New ( )
2018-11-08 06:31:01 +00:00
action = "generate"
2018-12-08 18:36:51 +00:00
isNewCfg = true
2018-10-25 13:15:10 +00:00
} else {
2018-12-08 22:49:19 +00:00
fmt . Printf ( "Loaded configuration %s.\n" , fname )
2018-11-08 06:31:01 +00:00
action = "update"
2018-10-25 13:15:10 +00:00
}
2018-11-11 02:44:41 +00:00
title := color . New ( color . Bold , color . BgGreen ) . PrintFunc ( )
2018-10-25 13:15:10 +00:00
intro := color . New ( color . Bold , color . FgWhite ) . PrintlnFunc ( )
fmt . Println ( )
2019-04-12 01:33:33 +00:00
intro ( " ✍ WriteFreely Configuration ✍" )
2018-10-25 13:15:10 +00:00
fmt . Println ( )
2018-12-08 22:49:19 +00:00
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 ) )
2018-10-25 13:15:10 +00:00
fmt . Println ( )
2018-11-08 03:06:34 +00:00
tmpls := & promptui . PromptTemplates {
Success : "{{ . | bold | faint }}: " ,
2018-10-25 13:15:10 +00:00
}
2018-11-08 03:06:34 +00:00
selTmpls := & promptui . SelectTemplates {
2018-11-08 17:16:52 +00:00
Selected : fmt . Sprintf ( ` {{ .Label }} {{ . | faint }} ` ) ,
2018-10-25 13:15:10 +00:00
}
2019-07-01 18:00:56 +00:00
var selPrompt promptui . Select
2018-11-21 23:26:19 +00:00
var prompt promptui . Prompt
2019-06-20 20:41:03 +00:00
2019-07-01 17:21:41 +00:00
if strings . Contains ( configSections , "server" ) {
2019-06-20 20:41:03 +00:00
title ( " Server setup " )
fmt . Println ( )
2019-07-01 17:21:41 +00:00
2019-07-01 18:00:56 +00:00
// 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
2019-06-20 20:41:03 +00:00
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
2018-11-21 23:26:19 +00:00
}
2019-07-01 17:21:41 +00:00
2019-06-20 20:41:03 +00:00
if isStandalone {
selPrompt = promptui . Select {
Templates : selTmpls ,
Label : "Web server mode" ,
2019-07-21 01:46:10 +00:00
Items : [ ] string { "Insecure (port 80)" , "Secure (port 443), manual certificate" , "Secure (port 443), auto certificate" } ,
2019-06-20 20:41:03 +00:00
}
sel , _ , err := selPrompt . Run ( )
if err != nil {
return data , err
}
if sel == 0 {
2019-07-21 01:46:10 +00:00
data . Config . Server . Autocert = false
2019-06-20 20:41:03 +00:00
data . Config . Server . Port = 80
data . Config . Server . TLSCertPath = ""
data . Config . Server . TLSKeyPath = ""
2019-07-21 01:46:10 +00:00
} else if sel == 1 || sel == 2 {
2019-06-20 20:41:03 +00:00
data . Config . Server . Port = 443
2019-07-21 01:46:10 +00:00
data . Config . Server . Autocert = sel == 2
if sel == 1 {
// Manual certificate configuration
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 {
// Automatic certificate
data . Config . Server . TLSCertPath = "certs"
data . Config . Server . TLSKeyPath = "certs"
2019-06-20 20:41:03 +00:00
}
}
} else {
data . Config . Server . TLSCertPath = ""
data . Config . Server . TLSKeyPath = ""
2018-11-21 23:26:19 +00:00
}
2019-07-01 17:21:41 +00:00
2019-06-20 20:41:03 +00:00
fmt . Println ( )
2018-11-21 23:26:19 +00:00
}
2019-07-01 17:21:41 +00:00
if strings . Contains ( configSections , "db" ) {
2019-06-20 20:41:03 +00:00
title ( " Database setup " )
fmt . Println ( )
2019-07-01 17:21:41 +00:00
2018-11-21 23:26:19 +00:00
selPrompt = promptui . Select {
Templates : selTmpls ,
2019-06-20 20:41:03 +00:00
Label : "Database driver" ,
Items : [ ] string { "MySQL" , "SQLite" } ,
2018-11-21 23:26:19 +00:00
}
sel , _ , err := selPrompt . Run ( )
if err != nil {
return data , err
}
2019-07-01 17:21:41 +00:00
2018-11-21 23:26:19 +00:00
if sel == 0 {
2019-06-20 20:41:03 +00:00
// Configure for MySQL
data . Config . UseMySQL ( isNewCfg )
2019-07-01 17:21:41 +00:00
2018-11-21 23:26:19 +00:00
prompt = promptui . Prompt {
Templates : tmpls ,
2019-06-20 20:41:03 +00:00
Label : "Username" ,
2018-11-21 23:26:19 +00:00
Validate : validateNonEmpty ,
2019-06-20 20:41:03 +00:00
Default : data . Config . Database . User ,
2018-11-21 23:26:19 +00:00
}
2019-06-20 20:41:03 +00:00
data . Config . Database . User , err = prompt . Run ( )
2018-11-21 23:26:19 +00:00
if err != nil {
return data , err
}
2019-07-01 17:21:41 +00:00
2018-11-21 23:26:19 +00:00
prompt = promptui . Prompt {
Templates : tmpls ,
2019-06-20 20:41:03 +00:00
Label : "Password" ,
2018-11-21 23:26:19 +00:00
Validate : validateNonEmpty ,
2019-06-20 20:41:03 +00:00
Default : data . Config . Database . Password ,
Mask : '*' ,
2018-11-21 23:26:19 +00:00
}
2019-06-20 20:41:03 +00:00
data . Config . Database . Password , err = prompt . Run ( )
if err != nil {
return data , err
}
2019-07-01 17:21:41 +00:00
2019-06-20 20:41:03 +00:00
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
}
2019-07-01 17:21:41 +00:00
2019-06-20 20:41:03 +00:00
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
}
2019-07-01 17:21:41 +00:00
2019-06-20 20:41:03 +00:00
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 )
2019-07-01 17:21:41 +00:00
2019-06-20 20:41:03 +00:00
prompt = promptui . Prompt {
Templates : tmpls ,
Label : "Filename" ,
Validate : validateNonEmpty ,
Default : data . Config . Database . FileName ,
}
data . Config . Database . FileName , err = prompt . Run ( )
2018-11-21 23:26:19 +00:00
if err != nil {
return data , err
}
}
2019-07-01 17:21:41 +00:00
2019-06-20 20:41:03 +00:00
fmt . Println ( )
2018-10-25 13:15:10 +00:00
}
2019-07-01 17:21:41 +00:00
if strings . Contains ( configSections , "app" ) {
2019-06-20 20:41:03 +00:00
title ( " App setup " )
fmt . Println ( )
2019-07-01 17:21:41 +00:00
2019-06-20 20:41:03 +00:00
selPrompt = promptui . Select {
Templates : selTmpls ,
Label : "Site type" ,
Items : [ ] string { "Single user blog" , "Multi-user instance" } ,
2018-12-08 18:36:51 +00:00
}
2019-06-20 20:41:03 +00:00
_ , usersType , err := selPrompt . Run ( )
2018-12-08 18:36:51 +00:00
if err != nil {
return data , err
}
2019-06-20 20:41:03 +00:00
data . Config . App . SingleUser = usersType == "Single user blog"
2019-07-01 17:21:41 +00:00
2019-06-20 20:41:03 +00:00
if data . Config . App . SingleUser {
data . User = & UserCreation { }
2019-07-01 17:21:41 +00:00
2019-06-20 20:41:03 +00:00
// 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
}
2019-07-01 17:21:41 +00:00
2019-06-20 20:41:03 +00:00
// prompt for password
prompt = promptui . Prompt {
Templates : tmpls ,
Label : "Admin password" ,
Validate : validateNonEmpty ,
}
newUserPass , err := prompt . Run ( )
if err != nil {
return data , err
}
2019-07-01 17:21:41 +00:00
2019-06-20 20:41:03 +00:00
data . User . HashedPass , err = auth . HashPass ( [ ] byte ( newUserPass ) )
if err != nil {
return data , err
}
2018-12-08 18:36:51 +00:00
}
2019-07-01 17:21:41 +00:00
2019-06-20 20:41:03 +00:00
siteNameLabel := "Instance name"
if data . Config . App . SingleUser {
siteNameLabel = "Blog name"
2018-12-08 18:36:51 +00:00
}
2018-11-10 03:16:13 +00:00
prompt = promptui . Prompt {
Templates : tmpls ,
2019-06-20 20:41:03 +00:00
Label : siteNameLabel ,
2018-11-10 03:16:13 +00:00
Validate : validateNonEmpty ,
2019-06-20 20:41:03 +00:00
Default : data . Config . App . SiteName ,
2018-11-10 03:16:13 +00:00
}
2019-06-20 20:41:03 +00:00
data . Config . App . SiteName , err = prompt . Run ( )
2018-11-10 03:16:13 +00:00
if err != nil {
return data , err
}
2019-07-01 17:21:41 +00:00
2018-11-10 03:16:13 +00:00
prompt = promptui . Prompt {
Templates : tmpls ,
2019-06-20 20:41:03 +00:00
Label : "Public URL" ,
Validate : validateDomain ,
Default : data . Config . App . Host ,
2018-11-10 03:16:13 +00:00
}
2019-06-20 20:41:03 +00:00
data . Config . App . Host , err = prompt . Run ( )
2018-11-10 03:16:13 +00:00
if err != nil {
return data , err
}
2019-07-01 17:21:41 +00:00
2019-06-20 20:41:03 +00:00
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"
2019-07-01 17:21:41 +00:00
2019-06-20 20:41:03 +00:00
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
2018-11-08 03:06:34 +00:00
}
2019-07-01 17:21:41 +00:00
2018-10-25 13:15:10 +00:00
selPrompt = promptui . Select {
2018-11-08 03:06:34 +00:00
Templates : selTmpls ,
2019-06-20 20:41:03 +00:00
Label : "Federation" ,
Items : [ ] string { "Enabled" , "Disabled" } ,
2018-10-25 13:15:10 +00:00
}
2019-06-20 20:41:03 +00:00
_ , fedType , err := selPrompt . Run ( )
2018-10-25 13:15:10 +00:00
if err != nil {
2018-11-10 03:16:13 +00:00
return data , err
2018-10-25 13:15:10 +00:00
}
2019-06-20 20:41:03 +00:00
data . Config . App . Federation = fedType == "Enabled"
2019-07-01 17:21:41 +00:00
2019-06-20 20:41:03 +00:00
if data . Config . App . Federation {
selPrompt = promptui . Select {
Templates : selTmpls ,
2020-04-21 17:01:39 +00:00
Label : "Usage stats (active users, posts)" ,
2019-06-20 20:41:03 +00:00
Items : [ ] string { "Public" , "Private" } ,
}
_ , fedStatsType , err := selPrompt . Run ( )
if err != nil {
return data , err
}
data . Config . App . PublicStats = fedStatsType == "Public"
2019-07-01 17:21:41 +00:00
2019-06-20 20:41:03 +00:00
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"
2018-10-25 13:15:10 +00:00
}
}
2018-12-08 22:49:19 +00:00
return data , Save ( data . Config , fname )
2018-10-25 13:15:10 +00:00
}