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.
 
 
 
 
 

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