A clean, Markdown-based publishing platform made for writers. Write together, and build a community. https://writefreely.org
25개 이상의 토픽을 선택하실 수 없습니다. Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

812 lines
21 KiB

  1. /*
  2. * Copyright © 2018-2019 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 writefreely
  11. import (
  12. "crypto/tls"
  13. "database/sql"
  14. "fmt"
  15. "html/template"
  16. "io/ioutil"
  17. "net/http"
  18. "net/url"
  19. "os"
  20. "os/signal"
  21. "path/filepath"
  22. "regexp"
  23. "strings"
  24. "syscall"
  25. "time"
  26. "github.com/gorilla/mux"
  27. "github.com/gorilla/schema"
  28. "github.com/gorilla/sessions"
  29. "github.com/manifoldco/promptui"
  30. "github.com/writeas/go-strip-markdown"
  31. "github.com/writeas/impart"
  32. "github.com/writeas/web-core/auth"
  33. "github.com/writeas/web-core/converter"
  34. "github.com/writeas/web-core/log"
  35. "github.com/writeas/writefreely/author"
  36. "github.com/writeas/writefreely/config"
  37. "github.com/writeas/writefreely/key"
  38. "github.com/writeas/writefreely/migrations"
  39. "github.com/writeas/writefreely/page"
  40. "golang.org/x/crypto/acme/autocert"
  41. )
  42. const (
  43. staticDir = "static"
  44. assumedTitleLen = 80
  45. postsPerPage = 10
  46. serverSoftware = "WriteFreely"
  47. softwareURL = "https://writefreely.org"
  48. )
  49. var (
  50. debugging bool
  51. // Software version can be set from git env using -ldflags
  52. softwareVer = "0.10.0"
  53. // DEPRECATED VARS
  54. isSingleUser bool
  55. )
  56. // App holds data and configuration for an individual WriteFreely instance.
  57. type App struct {
  58. router *mux.Router
  59. shttp *http.ServeMux
  60. db *datastore
  61. cfg *config.Config
  62. cfgFile string
  63. keys *key.Keychain
  64. sessionStore *sessions.CookieStore
  65. formDecoder *schema.Decoder
  66. timeline *localTimeline
  67. }
  68. // DB returns the App's datastore
  69. func (app *App) DB() *datastore {
  70. return app.db
  71. }
  72. // Router returns the App's router
  73. func (app *App) Router() *mux.Router {
  74. return app.router
  75. }
  76. // Config returns the App's current configuration.
  77. func (app *App) Config() *config.Config {
  78. return app.cfg
  79. }
  80. // SetConfig updates the App's Config to the given value.
  81. func (app *App) SetConfig(cfg *config.Config) {
  82. app.cfg = cfg
  83. }
  84. // SetKeys updates the App's Keychain to the given value.
  85. func (app *App) SetKeys(k *key.Keychain) {
  86. app.keys = k
  87. }
  88. // Apper is the interface for getting data into and out of a WriteFreely
  89. // instance (or "App").
  90. //
  91. // App returns the App for the current instance.
  92. //
  93. // LoadConfig reads an app configuration into the App, returning any error
  94. // encountered.
  95. //
  96. // SaveConfig persists the current App configuration.
  97. //
  98. // LoadKeys reads the App's encryption keys and loads them into its
  99. // key.Keychain.
  100. type Apper interface {
  101. App() *App
  102. LoadConfig() error
  103. SaveConfig(*config.Config) error
  104. LoadKeys() error
  105. ReqLog(r *http.Request, status int, timeSince time.Duration) string
  106. }
  107. // App returns the App
  108. func (app *App) App() *App {
  109. return app
  110. }
  111. // LoadConfig loads and parses a config file.
  112. func (app *App) LoadConfig() error {
  113. log.Info("Loading %s configuration...", app.cfgFile)
  114. cfg, err := config.Load(app.cfgFile)
  115. if err != nil {
  116. log.Error("Unable to load configuration: %v", err)
  117. os.Exit(1)
  118. return err
  119. }
  120. app.cfg = cfg
  121. return nil
  122. }
  123. // SaveConfig saves the given Config to disk -- namely, to the App's cfgFile.
  124. func (app *App) SaveConfig(c *config.Config) error {
  125. return config.Save(c, app.cfgFile)
  126. }
  127. // LoadKeys reads all needed keys from disk into the App. In order to use the
  128. // configured `Server.KeysParentDir`, you must call initKeyPaths(App) before
  129. // this.
  130. func (app *App) LoadKeys() error {
  131. var err error
  132. app.keys = &key.Keychain{}
  133. if debugging {
  134. log.Info(" %s", emailKeyPath)
  135. }
  136. app.keys.EmailKey, err = ioutil.ReadFile(emailKeyPath)
  137. if err != nil {
  138. return err
  139. }
  140. if debugging {
  141. log.Info(" %s", cookieAuthKeyPath)
  142. }
  143. app.keys.CookieAuthKey, err = ioutil.ReadFile(cookieAuthKeyPath)
  144. if err != nil {
  145. return err
  146. }
  147. if debugging {
  148. log.Info(" %s", cookieKeyPath)
  149. }
  150. app.keys.CookieKey, err = ioutil.ReadFile(cookieKeyPath)
  151. if err != nil {
  152. return err
  153. }
  154. return nil
  155. }
  156. func (app *App) ReqLog(r *http.Request, status int, timeSince time.Duration) string {
  157. return fmt.Sprintf("\"%s %s\" %d %s \"%s\"", r.Method, r.RequestURI, status, timeSince, r.UserAgent())
  158. }
  159. // handleViewHome shows page at root path. Will be the Pad if logged in and the
  160. // catch-all landing page otherwise.
  161. func handleViewHome(app *App, w http.ResponseWriter, r *http.Request) error {
  162. if app.cfg.App.SingleUser {
  163. // Render blog index
  164. return handleViewCollection(app, w, r)
  165. }
  166. // Multi-user instance
  167. forceLanding := r.FormValue("landing") == "1"
  168. if !forceLanding {
  169. // Show correct page based on user auth status and configured landing path
  170. u := getUserSession(app, r)
  171. if u != nil {
  172. // User is logged in, so show the Pad or Blogs page, depending on config
  173. if app.cfg.App.SimpleNav {
  174. // Simple nav, so home page is Blogs page
  175. return viewCollections(app, u, w, r)
  176. }
  177. // Default config, so home page is editor
  178. return handleViewPad(app, w, r)
  179. }
  180. if land := app.cfg.App.LandingPath(); land != "/" {
  181. return impart.HTTPError{http.StatusFound, land}
  182. }
  183. }
  184. p := struct {
  185. page.StaticPage
  186. Flashes []template.HTML
  187. Banner template.HTML
  188. Content template.HTML
  189. ForcedLanding bool
  190. }{
  191. StaticPage: pageForReq(app, r),
  192. ForcedLanding: forceLanding,
  193. }
  194. banner, err := getLandingBanner(app)
  195. if err != nil {
  196. log.Error("unable to get landing banner: %v", err)
  197. return impart.HTTPError{http.StatusInternalServerError, fmt.Sprintf("Could not get banner: %v", err)}
  198. }
  199. p.Banner = template.HTML(applyMarkdown([]byte(banner.Content), "", app.cfg))
  200. content, err := getLandingBody(app)
  201. if err != nil {
  202. log.Error("unable to get landing content: %v", err)
  203. return impart.HTTPError{http.StatusInternalServerError, fmt.Sprintf("Could not get content: %v", err)}
  204. }
  205. p.Content = template.HTML(applyMarkdown([]byte(content.Content), "", app.cfg))
  206. // Get error messages
  207. session, err := app.sessionStore.Get(r, cookieName)
  208. if err != nil {
  209. // Ignore this
  210. log.Error("Unable to get session in handleViewHome; ignoring: %v", err)
  211. }
  212. flashes, _ := getSessionFlashes(app, w, r, session)
  213. for _, flash := range flashes {
  214. p.Flashes = append(p.Flashes, template.HTML(flash))
  215. }
  216. // Show landing page
  217. return renderPage(w, "landing.tmpl", p)
  218. }
  219. func handleTemplatedPage(app *App, w http.ResponseWriter, r *http.Request, t *template.Template) error {
  220. p := struct {
  221. page.StaticPage
  222. ContentTitle string
  223. Content template.HTML
  224. PlainContent string
  225. Updated string
  226. AboutStats *InstanceStats
  227. }{
  228. StaticPage: pageForReq(app, r),
  229. }
  230. if r.URL.Path == "/about" || r.URL.Path == "/privacy" {
  231. var c *instanceContent
  232. var err error
  233. if r.URL.Path == "/about" {
  234. c, err = getAboutPage(app)
  235. // Fetch stats
  236. p.AboutStats = &InstanceStats{}
  237. p.AboutStats.NumPosts, _ = app.db.GetTotalPosts()
  238. p.AboutStats.NumBlogs, _ = app.db.GetTotalCollections()
  239. } else {
  240. c, err = getPrivacyPage(app)
  241. }
  242. if err != nil {
  243. return err
  244. }
  245. p.ContentTitle = c.Title.String
  246. p.Content = template.HTML(applyMarkdown([]byte(c.Content), "", app.cfg))
  247. p.PlainContent = shortPostDescription(stripmd.Strip(c.Content))
  248. if !c.Updated.IsZero() {
  249. p.Updated = c.Updated.Format("January 2, 2006")
  250. }
  251. }
  252. // Serve templated page
  253. err := t.ExecuteTemplate(w, "base", p)
  254. if err != nil {
  255. log.Error("Unable to render page: %v", err)
  256. }
  257. return nil
  258. }
  259. func pageForReq(app *App, r *http.Request) page.StaticPage {
  260. p := page.StaticPage{
  261. AppCfg: app.cfg.App,
  262. Path: r.URL.Path,
  263. Version: "v" + softwareVer,
  264. }
  265. // Add user information, if given
  266. var u *User
  267. accessToken := r.FormValue("t")
  268. if accessToken != "" {
  269. userID := app.db.GetUserID(accessToken)
  270. if userID != -1 {
  271. var err error
  272. u, err = app.db.GetUserByID(userID)
  273. if err == nil {
  274. p.Username = u.Username
  275. }
  276. }
  277. } else {
  278. u = getUserSession(app, r)
  279. if u != nil {
  280. p.Username = u.Username
  281. p.IsAdmin = u != nil && u.IsAdmin()
  282. p.CanInvite = canUserInvite(app.cfg, p.IsAdmin)
  283. }
  284. }
  285. p.CanViewReader = !app.cfg.App.Private || u != nil
  286. return p
  287. }
  288. var fileRegex = regexp.MustCompile("/([^/]*\\.[^/]*)$")
  289. // Initialize loads the app configuration and initializes templates, keys,
  290. // session, route handlers, and the database connection.
  291. func Initialize(apper Apper, debug bool) (*App, error) {
  292. debugging = debug
  293. apper.LoadConfig()
  294. // Load templates
  295. err := InitTemplates(apper.App().Config())
  296. if err != nil {
  297. return nil, fmt.Errorf("load templates: %s", err)
  298. }
  299. // Load keys and set up session
  300. initKeyPaths(apper.App()) // TODO: find a better way to do this, since it's unneeded in all Apper implementations
  301. err = InitKeys(apper)
  302. if err != nil {
  303. return nil, fmt.Errorf("init keys: %s", err)
  304. }
  305. apper.App().InitSession()
  306. apper.App().InitDecoder()
  307. err = ConnectToDatabase(apper.App())
  308. if err != nil {
  309. return nil, fmt.Errorf("connect to DB: %s", err)
  310. }
  311. // Handle local timeline, if enabled
  312. if apper.App().cfg.App.LocalTimeline {
  313. log.Info("Initializing local timeline...")
  314. initLocalTimeline(apper.App())
  315. }
  316. return apper.App(), nil
  317. }
  318. func Serve(app *App, r *mux.Router) {
  319. log.Info("Going to serve...")
  320. isSingleUser = app.cfg.App.SingleUser
  321. app.cfg.Server.Dev = debugging
  322. // Handle shutdown
  323. c := make(chan os.Signal, 2)
  324. signal.Notify(c, os.Interrupt, syscall.SIGTERM)
  325. go func() {
  326. <-c
  327. log.Info("Shutting down...")
  328. shutdown(app)
  329. log.Info("Done.")
  330. os.Exit(0)
  331. }()
  332. // Start web application server
  333. var bindAddress = app.cfg.Server.Bind
  334. if bindAddress == "" {
  335. bindAddress = "localhost"
  336. }
  337. var err error
  338. if app.cfg.IsSecureStandalone() {
  339. if app.cfg.Server.Autocert {
  340. m := &autocert.Manager{
  341. Prompt: autocert.AcceptTOS,
  342. Cache: autocert.DirCache(app.cfg.Server.TLSCertPath),
  343. }
  344. host, err := url.Parse(app.cfg.App.Host)
  345. if err != nil {
  346. log.Error("[WARNING] Unable to parse configured host! %s", err)
  347. log.Error(`[WARNING] ALL hosts are allowed, which can open you to an attack where
  348. clients connect to a server by IP address and pretend to be asking for an
  349. incorrect host name, and cause you to reach the CA's rate limit for certificate
  350. requests. We recommend supplying a valid host name.`)
  351. log.Info("Using autocert on ANY host")
  352. } else {
  353. log.Info("Using autocert on host %s", host.Host)
  354. m.HostPolicy = autocert.HostWhitelist(host.Host)
  355. }
  356. s := &http.Server{
  357. Addr: ":https",
  358. Handler: r,
  359. TLSConfig: &tls.Config{
  360. GetCertificate: m.GetCertificate,
  361. },
  362. }
  363. s.SetKeepAlivesEnabled(false)
  364. go func() {
  365. log.Info("Serving redirects on http://%s:80", bindAddress)
  366. err = http.ListenAndServe(":80", m.HTTPHandler(nil))
  367. log.Error("Unable to start redirect server: %v", err)
  368. }()
  369. log.Info("Serving on https://%s:443", bindAddress)
  370. log.Info("---")
  371. err = s.ListenAndServeTLS("", "")
  372. } else {
  373. go func() {
  374. log.Info("Serving redirects on http://%s:80", bindAddress)
  375. err = http.ListenAndServe(fmt.Sprintf("%s:80", bindAddress), http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  376. http.Redirect(w, r, app.cfg.App.Host, http.StatusMovedPermanently)
  377. }))
  378. log.Error("Unable to start redirect server: %v", err)
  379. }()
  380. log.Info("Serving on https://%s:443", bindAddress)
  381. log.Info("Using manual certificates")
  382. log.Info("---")
  383. err = http.ListenAndServeTLS(fmt.Sprintf("%s:443", bindAddress), app.cfg.Server.TLSCertPath, app.cfg.Server.TLSKeyPath, r)
  384. }
  385. } else {
  386. log.Info("Serving on http://%s:%d\n", bindAddress, app.cfg.Server.Port)
  387. log.Info("---")
  388. err = http.ListenAndServe(fmt.Sprintf("%s:%d", bindAddress, app.cfg.Server.Port), r)
  389. }
  390. if err != nil {
  391. log.Error("Unable to start: %v", err)
  392. os.Exit(1)
  393. }
  394. }
  395. func (app *App) InitDecoder() {
  396. // TODO: do this at the package level, instead of the App level
  397. // Initialize modules
  398. app.formDecoder = schema.NewDecoder()
  399. app.formDecoder.RegisterConverter(converter.NullJSONString{}, converter.ConvertJSONNullString)
  400. app.formDecoder.RegisterConverter(converter.NullJSONBool{}, converter.ConvertJSONNullBool)
  401. app.formDecoder.RegisterConverter(sql.NullString{}, converter.ConvertSQLNullString)
  402. app.formDecoder.RegisterConverter(sql.NullBool{}, converter.ConvertSQLNullBool)
  403. app.formDecoder.RegisterConverter(sql.NullInt64{}, converter.ConvertSQLNullInt64)
  404. app.formDecoder.RegisterConverter(sql.NullFloat64{}, converter.ConvertSQLNullFloat64)
  405. }
  406. // ConnectToDatabase validates and connects to the configured database, then
  407. // tests the connection.
  408. func ConnectToDatabase(app *App) error {
  409. // Check database configuration
  410. if app.cfg.Database.Type == driverMySQL && (app.cfg.Database.User == "" || app.cfg.Database.Password == "") {
  411. return fmt.Errorf("Database user or password not set.")
  412. }
  413. if app.cfg.Database.Host == "" {
  414. app.cfg.Database.Host = "localhost"
  415. }
  416. if app.cfg.Database.Database == "" {
  417. app.cfg.Database.Database = "writefreely"
  418. }
  419. // TODO: check err
  420. connectToDatabase(app)
  421. // Test database connection
  422. err := app.db.Ping()
  423. if err != nil {
  424. return fmt.Errorf("Database ping failed: %s", err)
  425. }
  426. return nil
  427. }
  428. // OutputVersion prints out the version of the application.
  429. func OutputVersion() {
  430. fmt.Println(serverSoftware + " " + softwareVer)
  431. }
  432. // NewApp creates a new app instance.
  433. func NewApp(cfgFile string) *App {
  434. return &App{
  435. cfgFile: cfgFile,
  436. }
  437. }
  438. // CreateConfig creates a default configuration and saves it to the app's cfgFile.
  439. func CreateConfig(app *App) error {
  440. log.Info("Creating configuration...")
  441. c := config.New()
  442. log.Info("Saving configuration %s...", app.cfgFile)
  443. err := config.Save(c, app.cfgFile)
  444. if err != nil {
  445. return fmt.Errorf("Unable to save configuration: %v", err)
  446. }
  447. return nil
  448. }
  449. // DoConfig runs the interactive configuration process.
  450. func DoConfig(app *App, configSections string) {
  451. if configSections == "" {
  452. configSections = "server db app"
  453. }
  454. // let's check there aren't any garbage in the list
  455. configSectionsArray := strings.Split(configSections, " ")
  456. for _, element := range configSectionsArray {
  457. if element != "server" && element != "db" && element != "app" {
  458. log.Error("Invalid argument to --sections. Valid arguments are only \"server\", \"db\" and \"app\"")
  459. os.Exit(1)
  460. }
  461. }
  462. d, err := config.Configure(app.cfgFile, configSections)
  463. if err != nil {
  464. log.Error("Unable to configure: %v", err)
  465. os.Exit(1)
  466. }
  467. app.cfg = d.Config
  468. connectToDatabase(app)
  469. defer shutdown(app)
  470. if !app.db.DatabaseInitialized() {
  471. err = adminInitDatabase(app)
  472. if err != nil {
  473. log.Error(err.Error())
  474. os.Exit(1)
  475. }
  476. } else {
  477. log.Info("Database already initialized.")
  478. }
  479. if d.User != nil {
  480. u := &User{
  481. Username: d.User.Username,
  482. HashedPass: d.User.HashedPass,
  483. Created: time.Now().Truncate(time.Second).UTC(),
  484. }
  485. // Create blog
  486. log.Info("Creating user %s...\n", u.Username)
  487. err = app.db.CreateUser(app.cfg, u, app.cfg.App.SiteName)
  488. if err != nil {
  489. log.Error("Unable to create user: %s", err)
  490. os.Exit(1)
  491. }
  492. log.Info("Done!")
  493. }
  494. os.Exit(0)
  495. }
  496. // GenerateKeyFiles creates app encryption keys and saves them into the configured KeysParentDir.
  497. func GenerateKeyFiles(app *App) error {
  498. // Read keys path from config
  499. app.LoadConfig()
  500. // Create keys dir if it doesn't exist yet
  501. fullKeysDir := filepath.Join(app.cfg.Server.KeysParentDir, keysDir)
  502. if _, err := os.Stat(fullKeysDir); os.IsNotExist(err) {
  503. err = os.Mkdir(fullKeysDir, 0700)
  504. if err != nil {
  505. return err
  506. }
  507. }
  508. // Generate keys
  509. initKeyPaths(app)
  510. // TODO: use something like https://github.com/hashicorp/go-multierror to return errors
  511. var keyErrs error
  512. err := generateKey(emailKeyPath)
  513. if err != nil {
  514. keyErrs = err
  515. }
  516. err = generateKey(cookieAuthKeyPath)
  517. if err != nil {
  518. keyErrs = err
  519. }
  520. err = generateKey(cookieKeyPath)
  521. if err != nil {
  522. keyErrs = err
  523. }
  524. return keyErrs
  525. }
  526. // CreateSchema creates all database tables needed for the application.
  527. func CreateSchema(apper Apper) error {
  528. apper.LoadConfig()
  529. connectToDatabase(apper.App())
  530. defer shutdown(apper.App())
  531. err := adminInitDatabase(apper.App())
  532. if err != nil {
  533. return err
  534. }
  535. return nil
  536. }
  537. // Migrate runs all necessary database migrations.
  538. func Migrate(apper Apper) error {
  539. apper.LoadConfig()
  540. connectToDatabase(apper.App())
  541. defer shutdown(apper.App())
  542. err := migrations.Migrate(migrations.NewDatastore(apper.App().db.DB, apper.App().db.driverName))
  543. if err != nil {
  544. return fmt.Errorf("migrate: %s", err)
  545. }
  546. return nil
  547. }
  548. // ResetPassword runs the interactive password reset process.
  549. func ResetPassword(apper Apper, username string) error {
  550. // Connect to the database
  551. apper.LoadConfig()
  552. connectToDatabase(apper.App())
  553. defer shutdown(apper.App())
  554. // Fetch user
  555. u, err := apper.App().db.GetUserForAuth(username)
  556. if err != nil {
  557. log.Error("Get user: %s", err)
  558. os.Exit(1)
  559. }
  560. // Prompt for new password
  561. prompt := promptui.Prompt{
  562. Templates: &promptui.PromptTemplates{
  563. Success: "{{ . | bold | faint }}: ",
  564. },
  565. Label: "New password",
  566. Mask: '*',
  567. }
  568. newPass, err := prompt.Run()
  569. if err != nil {
  570. log.Error("%s", err)
  571. os.Exit(1)
  572. }
  573. // Do the update
  574. log.Info("Updating...")
  575. err = adminResetPassword(apper.App(), u, newPass)
  576. if err != nil {
  577. log.Error("%s", err)
  578. os.Exit(1)
  579. }
  580. log.Info("Success.")
  581. return nil
  582. }
  583. func connectToDatabase(app *App) {
  584. log.Info("Connecting to %s database...", app.cfg.Database.Type)
  585. var db *sql.DB
  586. var err error
  587. if app.cfg.Database.Type == driverMySQL {
  588. db, err = sql.Open(app.cfg.Database.Type, fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=true&loc=%s", app.cfg.Database.User, app.cfg.Database.Password, app.cfg.Database.Host, app.cfg.Database.Port, app.cfg.Database.Database, url.QueryEscape(time.Local.String())))
  589. db.SetMaxOpenConns(50)
  590. } else if app.cfg.Database.Type == driverSQLite {
  591. if !SQLiteEnabled {
  592. log.Error("Invalid database type '%s'. Binary wasn't compiled with SQLite3 support.", app.cfg.Database.Type)
  593. os.Exit(1)
  594. }
  595. if app.cfg.Database.FileName == "" {
  596. log.Error("SQLite database filename value in config.ini is empty.")
  597. os.Exit(1)
  598. }
  599. db, err = sql.Open("sqlite3_with_regex", app.cfg.Database.FileName+"?parseTime=true&cached=shared")
  600. db.SetMaxOpenConns(1)
  601. } else {
  602. log.Error("Invalid database type '%s'. Only 'mysql' and 'sqlite3' are supported right now.", app.cfg.Database.Type)
  603. os.Exit(1)
  604. }
  605. if err != nil {
  606. log.Error("%s", err)
  607. os.Exit(1)
  608. }
  609. app.db = &datastore{db, app.cfg.Database.Type}
  610. }
  611. func shutdown(app *App) {
  612. log.Info("Closing database connection...")
  613. app.db.Close()
  614. }
  615. // CreateUser creates a new admin or normal user from the given credentials.
  616. func CreateUser(apper Apper, username, password string, isAdmin bool) error {
  617. // Create an admin user with --create-admin
  618. apper.LoadConfig()
  619. connectToDatabase(apper.App())
  620. defer shutdown(apper.App())
  621. // Ensure an admin / first user doesn't already exist
  622. firstUser, _ := apper.App().db.GetUserByID(1)
  623. if isAdmin {
  624. // Abort if trying to create admin user, but one already exists
  625. if firstUser != nil {
  626. return fmt.Errorf("Admin user already exists (%s). Create a regular user with: writefreely --create-user", firstUser.Username)
  627. }
  628. } else {
  629. // Abort if trying to create regular user, but no admin exists yet
  630. if firstUser == nil {
  631. return fmt.Errorf("No admin user exists yet. Create an admin first with: writefreely --create-admin")
  632. }
  633. }
  634. // Create the user
  635. // Normalize and validate username
  636. desiredUsername := username
  637. username = getSlug(username, "")
  638. usernameDesc := username
  639. if username != desiredUsername {
  640. usernameDesc += " (originally: " + desiredUsername + ")"
  641. }
  642. if !author.IsValidUsername(apper.App().cfg, username) {
  643. return fmt.Errorf("Username %s is invalid, reserved, or shorter than configured minimum length (%d characters).", usernameDesc, apper.App().cfg.App.MinUsernameLen)
  644. }
  645. // Hash the password
  646. hashedPass, err := auth.HashPass([]byte(password))
  647. if err != nil {
  648. return fmt.Errorf("Unable to hash password: %v", err)
  649. }
  650. u := &User{
  651. Username: username,
  652. HashedPass: hashedPass,
  653. Created: time.Now().Truncate(time.Second).UTC(),
  654. }
  655. userType := "user"
  656. if isAdmin {
  657. userType = "admin"
  658. }
  659. log.Info("Creating %s %s...", userType, usernameDesc)
  660. err = apper.App().db.CreateUser(apper.App().Config(), u, desiredUsername)
  661. if err != nil {
  662. return fmt.Errorf("Unable to create user: %s", err)
  663. }
  664. log.Info("Done!")
  665. return nil
  666. }
  667. func adminInitDatabase(app *App) error {
  668. schemaFileName := "schema.sql"
  669. if app.cfg.Database.Type == driverSQLite {
  670. schemaFileName = "sqlite.sql"
  671. }
  672. schema, err := Asset(schemaFileName)
  673. if err != nil {
  674. return fmt.Errorf("Unable to load schema file: %v", err)
  675. }
  676. tblReg := regexp.MustCompile("CREATE TABLE (IF NOT EXISTS )?`([a-z_]+)`")
  677. queries := strings.Split(string(schema), ";\n")
  678. for _, q := range queries {
  679. if strings.TrimSpace(q) == "" {
  680. continue
  681. }
  682. parts := tblReg.FindStringSubmatch(q)
  683. if len(parts) >= 3 {
  684. log.Info("Creating table %s...", parts[2])
  685. } else {
  686. log.Info("Creating table ??? (Weird query) No match in: %v", parts)
  687. }
  688. _, err = app.db.Exec(q)
  689. if err != nil {
  690. log.Error("%s", err)
  691. } else {
  692. log.Info("Created.")
  693. }
  694. }
  695. // Set up migrations table
  696. log.Info("Initializing appmigrations table...")
  697. err = migrations.SetInitialMigrations(migrations.NewDatastore(app.db.DB, app.db.driverName))
  698. if err != nil {
  699. return fmt.Errorf("Unable to set initial migrations: %v", err)
  700. }
  701. log.Info("Running migrations...")
  702. err = migrations.Migrate(migrations.NewDatastore(app.db.DB, app.db.driverName))
  703. if err != nil {
  704. return fmt.Errorf("migrate: %s", err)
  705. }
  706. log.Info("Done.")
  707. return nil
  708. }