Page MenuHomeMusing Studio

No OneTemporary

diff --git a/app.go b/app.go
index 86351ed..7312e83 100644
--- a/app.go
+++ b/app.go
@@ -1,129 +1,137 @@
package writefreely
import (
"database/sql"
"flag"
"fmt"
_ "github.com/go-sql-driver/mysql"
"net/http"
"os"
"os/signal"
"syscall"
"github.com/gorilla/mux"
"github.com/gorilla/sessions"
"github.com/writeas/web-core/log"
"github.com/writeas/writefreely/config"
)
const (
staticDir = "static/"
serverSoftware = "Write Freely"
softwareURL = "https://writefreely.org"
softwareVer = "0.1"
)
type app struct {
router *mux.Router
db *datastore
cfg *config.Config
keys *keychain
sessionStore *sessions.CookieStore
}
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 {
log.Info("Creating configuration...")
c := config.New()
log.Info("Saving configuration...")
err := config.Save(c)
if err != nil {
log.Error("Unable to save configuration: %v", err)
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...")
log.Info("Loading configuration...")
cfg, err := config.Load()
if err != nil {
log.Error("Unable to load configuration: %v", err)
os.Exit(1)
}
app := &app{
cfg: cfg,
}
// Load keys
log.Info("Loading encryption keys...")
err = initKeys(app)
if err != nil {
log.Error("\n%s\n", err)
}
// Initialize modules
app.sessionStore = initSession(app)
// Check database configuration
if app.cfg.Database.User == "" || app.cfg.Database.Password == "" {
log.Error("Database user or password not set.")
os.Exit(1)
}
if app.cfg.Database.Host == "" {
app.cfg.Database.Host = "localhost"
}
if app.cfg.Database.Database == "" {
app.cfg.Database.Database = "writeas"
}
log.Info("Connecting to database...")
db, err := sql.Open("mysql", fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=true", app.cfg.Database.User, app.cfg.Database.Password, app.cfg.Database.Host, app.cfg.Database.Port, app.cfg.Database.Database))
if err != nil {
log.Error("\n%s\n", err)
os.Exit(1)
}
app.db = &datastore{db}
defer shutdown(app)
app.db.SetMaxOpenConns(50)
r := mux.NewRouter()
handler := NewHandler(app.sessionStore)
// Handle app routes
initRoutes(handler, r, app.cfg, app.db)
// Handle static files
fs := http.FileServer(http.Dir(staticDir))
shttp.Handle("/", fs)
r.PathPrefix("/").Handler(fs)
// Handle shutdown
c := make(chan os.Signal, 2)
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
go func() {
<-c
log.Info("Shutting down...")
shutdown(app)
log.Info("Done.")
os.Exit(0)
}()
// Start web application server
http.Handle("/", r)
log.Info("Serving on http://localhost:%d\n", app.cfg.Server.Port)
log.Info("---")
http.ListenAndServe(fmt.Sprintf(":%d", app.cfg.Server.Port), nil)
}
func shutdown(app *app) {
log.Info("Closing database connection...")
app.db.Close()
}
diff --git a/config/config.go b/config/config.go
index 77a1f58..5ca1358 100644
--- a/config/config.go
+++ b/config/config.go
@@ -1,90 +1,97 @@
package config
import (
"gopkg.in/ini.v1"
)
const (
- configFile = "config.ini"
+ FileName = "config.ini"
)
type (
ServerCfg struct {
Host string `ini:"host"`
Port int `ini:"port"`
}
DatabaseCfg struct {
Type string `ini:"type"`
User string `ini:"username"`
Password string `ini:"password"`
Database string `ini:"database"`
Host string `ini:"host"`
Port int `ini:"port"`
}
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 {
Server ServerCfg `ini:"server"`
Database DatabaseCfg `ini:"database"`
App AppCfg `ini:"app"`
}
)
func New() *Config {
return &Config{
Server: ServerCfg{
Host: "http://localhost:8080",
Port: 8080,
},
Database: DatabaseCfg{
Type: "mysql",
Host: "localhost",
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
}
// Parse INI file
uc := &Config{}
err = cfg.MapTo(uc)
if err != nil {
return nil, err
}
return uc, nil
}
func Save(uc *Config) error {
cfg := ini.Empty()
err := ini.ReflectFrom(cfg, uc)
if err != nil {
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
+}

File Metadata

Mime Type
text/x-diff
Expires
Sat, May 17, 8:46 AM (1 d, 14 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
3241240

Event Timeline