A clean, Markdown-based publishing platform made for writers. Write together, and build a community. https://writefreely.org
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

373 lines
9.0 KiB

  1. /*
  2. * Copyright © 2018 A Bunch Tell LLC.
  3. *
  4. * This file is part of WriteFreely.
  5. *
  6. * WriteFreely is free software: you can redistribute it and/or modify
  7. * it under the terms of the GNU Affero General Public License, included
  8. * in the LICENSE file in this source code package.
  9. */
  10. package config
  11. import (
  12. "fmt"
  13. "github.com/fatih/color"
  14. "github.com/manifoldco/promptui"
  15. "github.com/mitchellh/go-wordwrap"
  16. "github.com/writeas/web-core/auth"
  17. "strconv"
  18. "strings"
  19. )
  20. type SetupData struct {
  21. User *UserCreation
  22. Config *Config
  23. }
  24. func Configure(fname string, configSections string) (*SetupData, error) {
  25. data := &SetupData{}
  26. var err error
  27. if fname == "" {
  28. fname = FileName
  29. }
  30. data.Config, err = Load(fname)
  31. var action string
  32. isNewCfg := false
  33. if err != nil {
  34. fmt.Printf("No %s configuration yet. Creating new.\n", fname)
  35. data.Config = New()
  36. action = "generate"
  37. isNewCfg = true
  38. } else {
  39. fmt.Printf("Loaded configuration %s.\n", fname)
  40. action = "update"
  41. }
  42. title := color.New(color.Bold, color.BgGreen).PrintFunc()
  43. intro := color.New(color.Bold, color.FgWhite).PrintlnFunc()
  44. fmt.Println()
  45. intro(" ✍ WriteFreely Configuration ✍")
  46. fmt.Println()
  47. 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))
  48. fmt.Println()
  49. tmpls := &promptui.PromptTemplates{
  50. Success: "{{ . | bold | faint }}: ",
  51. }
  52. selTmpls := &promptui.SelectTemplates{
  53. Selected: fmt.Sprintf(`{{.Label}} {{ . | faint }}`),
  54. }
  55. // Environment selection
  56. selPrompt := promptui.Select{
  57. Templates: selTmpls,
  58. Label: "Environment",
  59. Items: []string{"Development", "Production, standalone", "Production, behind reverse proxy"},
  60. }
  61. _, envType, err := selPrompt.Run()
  62. if err != nil {
  63. return data, err
  64. }
  65. isDevEnv := envType == "Development"
  66. isStandalone := envType == "Production, standalone"
  67. data.Config.Server.Dev = isDevEnv
  68. var prompt promptui.Prompt
  69. if strings.Contains(configSections, "server") {
  70. title(" Server setup ")
  71. fmt.Println()
  72. if isDevEnv || !isStandalone {
  73. // Running in dev environment or behind reverse proxy; ask for port
  74. prompt = promptui.Prompt{
  75. Templates: tmpls,
  76. Label: "Local port",
  77. Validate: validatePort,
  78. Default: fmt.Sprintf("%d", data.Config.Server.Port),
  79. }
  80. port, err := prompt.Run()
  81. if err != nil {
  82. return data, err
  83. }
  84. data.Config.Server.Port, _ = strconv.Atoi(port) // Ignore error, as we've already validated number
  85. }
  86. if isStandalone {
  87. selPrompt = promptui.Select{
  88. Templates: selTmpls,
  89. Label: "Web server mode",
  90. Items: []string{"Insecure (port 80)", "Secure (port 443)"},
  91. }
  92. sel, _, err := selPrompt.Run()
  93. if err != nil {
  94. return data, err
  95. }
  96. if sel == 0 {
  97. data.Config.Server.Port = 80
  98. data.Config.Server.TLSCertPath = ""
  99. data.Config.Server.TLSKeyPath = ""
  100. } else if sel == 1 {
  101. data.Config.Server.Port = 443
  102. prompt = promptui.Prompt{
  103. Templates: tmpls,
  104. Label: "Certificate path",
  105. Validate: validateNonEmpty,
  106. Default: data.Config.Server.TLSCertPath,
  107. }
  108. data.Config.Server.TLSCertPath, err = prompt.Run()
  109. if err != nil {
  110. return data, err
  111. }
  112. prompt = promptui.Prompt{
  113. Templates: tmpls,
  114. Label: "Key path",
  115. Validate: validateNonEmpty,
  116. Default: data.Config.Server.TLSKeyPath,
  117. }
  118. data.Config.Server.TLSKeyPath, err = prompt.Run()
  119. if err != nil {
  120. return data, err
  121. }
  122. }
  123. } else {
  124. data.Config.Server.TLSCertPath = ""
  125. data.Config.Server.TLSKeyPath = ""
  126. }
  127. fmt.Println()
  128. }
  129. if strings.Contains(configSections, "db") {
  130. title(" Database setup ")
  131. fmt.Println()
  132. selPrompt = promptui.Select{
  133. Templates: selTmpls,
  134. Label: "Database driver",
  135. Items: []string{"MySQL", "SQLite"},
  136. }
  137. sel, _, err := selPrompt.Run()
  138. if err != nil {
  139. return data, err
  140. }
  141. if sel == 0 {
  142. // Configure for MySQL
  143. data.Config.UseMySQL(isNewCfg)
  144. prompt = promptui.Prompt{
  145. Templates: tmpls,
  146. Label: "Username",
  147. Validate: validateNonEmpty,
  148. Default: data.Config.Database.User,
  149. }
  150. data.Config.Database.User, err = prompt.Run()
  151. if err != nil {
  152. return data, err
  153. }
  154. prompt = promptui.Prompt{
  155. Templates: tmpls,
  156. Label: "Password",
  157. Validate: validateNonEmpty,
  158. Default: data.Config.Database.Password,
  159. Mask: '*',
  160. }
  161. data.Config.Database.Password, err = prompt.Run()
  162. if err != nil {
  163. return data, err
  164. }
  165. prompt = promptui.Prompt{
  166. Templates: tmpls,
  167. Label: "Database name",
  168. Validate: validateNonEmpty,
  169. Default: data.Config.Database.Database,
  170. }
  171. data.Config.Database.Database, err = prompt.Run()
  172. if err != nil {
  173. return data, err
  174. }
  175. prompt = promptui.Prompt{
  176. Templates: tmpls,
  177. Label: "Host",
  178. Validate: validateNonEmpty,
  179. Default: data.Config.Database.Host,
  180. }
  181. data.Config.Database.Host, err = prompt.Run()
  182. if err != nil {
  183. return data, err
  184. }
  185. prompt = promptui.Prompt{
  186. Templates: tmpls,
  187. Label: "Port",
  188. Validate: validatePort,
  189. Default: fmt.Sprintf("%d", data.Config.Database.Port),
  190. }
  191. dbPort, err := prompt.Run()
  192. if err != nil {
  193. return data, err
  194. }
  195. data.Config.Database.Port, _ = strconv.Atoi(dbPort) // Ignore error, as we've already validated number
  196. } else if sel == 1 {
  197. // Configure for SQLite
  198. data.Config.UseSQLite(isNewCfg)
  199. prompt = promptui.Prompt{
  200. Templates: tmpls,
  201. Label: "Filename",
  202. Validate: validateNonEmpty,
  203. Default: data.Config.Database.FileName,
  204. }
  205. data.Config.Database.FileName, err = prompt.Run()
  206. if err != nil {
  207. return data, err
  208. }
  209. }
  210. fmt.Println()
  211. }
  212. if strings.Contains(configSections, "app") {
  213. title(" App setup ")
  214. fmt.Println()
  215. selPrompt = promptui.Select{
  216. Templates: selTmpls,
  217. Label: "Site type",
  218. Items: []string{"Single user blog", "Multi-user instance"},
  219. }
  220. _, usersType, err := selPrompt.Run()
  221. if err != nil {
  222. return data, err
  223. }
  224. data.Config.App.SingleUser = usersType == "Single user blog"
  225. if data.Config.App.SingleUser {
  226. data.User = &UserCreation{}
  227. // prompt for username
  228. prompt = promptui.Prompt{
  229. Templates: tmpls,
  230. Label: "Admin username",
  231. Validate: validateNonEmpty,
  232. }
  233. data.User.Username, err = prompt.Run()
  234. if err != nil {
  235. return data, err
  236. }
  237. // prompt for password
  238. prompt = promptui.Prompt{
  239. Templates: tmpls,
  240. Label: "Admin password",
  241. Validate: validateNonEmpty,
  242. }
  243. newUserPass, err := prompt.Run()
  244. if err != nil {
  245. return data, err
  246. }
  247. data.User.HashedPass, err = auth.HashPass([]byte(newUserPass))
  248. if err != nil {
  249. return data, err
  250. }
  251. }
  252. siteNameLabel := "Instance name"
  253. if data.Config.App.SingleUser {
  254. siteNameLabel = "Blog name"
  255. }
  256. prompt = promptui.Prompt{
  257. Templates: tmpls,
  258. Label: siteNameLabel,
  259. Validate: validateNonEmpty,
  260. Default: data.Config.App.SiteName,
  261. }
  262. data.Config.App.SiteName, err = prompt.Run()
  263. if err != nil {
  264. return data, err
  265. }
  266. prompt = promptui.Prompt{
  267. Templates: tmpls,
  268. Label: "Public URL",
  269. Validate: validateDomain,
  270. Default: data.Config.App.Host,
  271. }
  272. data.Config.App.Host, err = prompt.Run()
  273. if err != nil {
  274. return data, err
  275. }
  276. if !data.Config.App.SingleUser {
  277. selPrompt = promptui.Select{
  278. Templates: selTmpls,
  279. Label: "Registration",
  280. Items: []string{"Open", "Closed"},
  281. }
  282. _, regType, err := selPrompt.Run()
  283. if err != nil {
  284. return data, err
  285. }
  286. data.Config.App.OpenRegistration = regType == "Open"
  287. prompt = promptui.Prompt{
  288. Templates: tmpls,
  289. Label: "Max blogs per user",
  290. Default: fmt.Sprintf("%d", data.Config.App.MaxBlogs),
  291. }
  292. maxBlogs, err := prompt.Run()
  293. if err != nil {
  294. return data, err
  295. }
  296. data.Config.App.MaxBlogs, _ = strconv.Atoi(maxBlogs) // Ignore error, as we've already validated number
  297. }
  298. selPrompt = promptui.Select{
  299. Templates: selTmpls,
  300. Label: "Federation",
  301. Items: []string{"Enabled", "Disabled"},
  302. }
  303. _, fedType, err := selPrompt.Run()
  304. if err != nil {
  305. return data, err
  306. }
  307. data.Config.App.Federation = fedType == "Enabled"
  308. if data.Config.App.Federation {
  309. selPrompt = promptui.Select{
  310. Templates: selTmpls,
  311. Label: "Federation usage stats",
  312. Items: []string{"Public", "Private"},
  313. }
  314. _, fedStatsType, err := selPrompt.Run()
  315. if err != nil {
  316. return data, err
  317. }
  318. data.Config.App.PublicStats = fedStatsType == "Public"
  319. selPrompt = promptui.Select{
  320. Templates: selTmpls,
  321. Label: "Instance metadata privacy",
  322. Items: []string{"Public", "Private"},
  323. }
  324. _, fedStatsType, err = selPrompt.Run()
  325. if err != nil {
  326. return data, err
  327. }
  328. data.Config.App.Private = fedStatsType == "Private"
  329. }
  330. }
  331. return data, Save(data.Config, fname)
  332. }