config.go 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718
  1. // Package engine provides the server configuration struct and several functions for serving files over HTTP
  2. package engine
  3. import (
  4. "bytes"
  5. "errors"
  6. "fmt"
  7. "io"
  8. internallog "log"
  9. "net/http"
  10. "os"
  11. "path/filepath"
  12. "runtime"
  13. "strconv"
  14. "strings"
  15. "sync"
  16. "time"
  17. "github.com/evanw/esbuild/pkg/api"
  18. log "github.com/sirupsen/logrus"
  19. "github.com/xyproto/algernon/cachemode"
  20. "github.com/xyproto/algernon/lua/pool"
  21. "github.com/xyproto/algernon/platformdep"
  22. "github.com/xyproto/algernon/utils"
  23. "github.com/xyproto/datablock"
  24. "github.com/xyproto/env/v2"
  25. "github.com/xyproto/mime"
  26. "github.com/xyproto/pinterface"
  27. "github.com/xyproto/recwatch"
  28. "github.com/xyproto/textoutput"
  29. "github.com/xyproto/unzip"
  30. )
  31. const (
  32. // Version number. Stable API within major version numbers.
  33. Version = 2.0
  34. )
  35. // Config is the main structure for the Algernon server.
  36. // It contains all the state and settings.
  37. // The order of the fields has been decided by the "fieldalignment" utility.
  38. type Config struct {
  39. perm pinterface.IPermissions // the user state, for the permissions system
  40. mimereader *mime.Reader
  41. serverReadyFunctionLua func() // configuration that may only be set in the server configuration script(s)
  42. pongomutex *sync.RWMutex // workaround for rendering pongo2 pages without concurrency issues
  43. fs *datablock.FileStat // for checking if file exists, possibly in a cached way
  44. luapool *pool.LStatePool // a pool of Lua interpreters
  45. cache *datablock.FileCache
  46. reverseProxyConfig *ReverseProxyConfig
  47. redisAddr string
  48. defaultEventPath string
  49. defaultEventRefresh string
  50. description string // description of the current program
  51. versionString string // program name and version number
  52. defaultOpenExecutable string // default program for opening files and URLs in the current operating system
  53. serverHost string
  54. defaultEventColonPort string
  55. defaultLimitString string // default rate limit, as a string
  56. limitRequestsString string // store the request limit as a string for faster HTTP header creation later on
  57. defaultBoltFilename string // default bolt database file, for some operating systems
  58. defaultLogFile string // default log file, for some operating systems
  59. defaultLuaDataFilename string // default filename for a Lua script that provides data to a template
  60. defaultRedisColonPort string
  61. serverDirOrFilename string // exposed to the server configuration scripts(s)
  62. serverAddr string // exposed to the server configuration scripts(s)
  63. serverCert string // exposed to the server configuration scripts(s)
  64. serverKey string // exposed to the server configuration scripts(s)
  65. serverConfScript string // exposed to the server configuration scripts(s)
  66. defaultWebColonPort string
  67. serverLogFile string // exposed to the server configuration scripts(s)
  68. serverTempDir string // temporary directory
  69. cookieSecret string // secret to be used when setting and getting user login cookies
  70. defaultTheme string // theme for Markdown and error pages
  71. openExecutable string // open the URL after serving, with a specific executable
  72. serverAddrLua string // configuration that may only be set in the server configuration script(s)
  73. dbName string
  74. serverHeaderName string // used in the HTTP headers as the "Server" name
  75. eventAddr string // for the Server-Sent Event (SSE) server (host and port)
  76. eventRefresh string // for the Server-Sent Event (SSE) server (duration of an event cycle)
  77. luaServerFilename string // if a single Lua file is provided, or if Server() is used
  78. autoRefreshDir string // if only watching a single directory recursively
  79. combinedAccessLogFilename string // CLF access log
  80. commonAccessLogFilename string // NCSA access log
  81. boltFilename string
  82. internalLogFilename string // exposed to the server configuration scripts(s)
  83. mariadbDSN string // connection string
  84. mariaDatabase string // database name
  85. postgresDSN string // connection string
  86. postgresDatabase string // database name
  87. dirBaseURL string // optional Base URL, for the directory listings
  88. jsxOptions api.TransformOptions // JSX rendering options
  89. certMagicDomains []string
  90. serverConfigurationFilenames []string // list of configuration filenames to check
  91. cacheMaxGivenDataSize uint64
  92. largeFileSize uint64 // threshold for not reading large files into memory
  93. refreshDuration time.Duration // for the auto-refresh feature
  94. redisDBindex int
  95. cacheSize uint64
  96. cacheMode cachemode.Setting
  97. shutdownTimeout time.Duration
  98. cacheMaxEntitySize uint64
  99. defaultLimit int64
  100. defaultCacheMaxEntitySize uint64 // 64 KiB
  101. defaultLargeFileSize uint64 // 42 MiB: the default size for when a static file is large enough to not be read into memory
  102. limitRequests int64 // rate limit to this many requests per client per second
  103. writeTimeout uint64 // timeout when writing data to a client, in seconds
  104. defaultStatCacheRefresh time.Duration // refresh the stat cache, if the stat cache feature is enabled
  105. defaultCacheSize uint64 // 1 MiB
  106. defaultPermissions os.FileMode
  107. quietMode bool // no output to the command line
  108. autoRefresh bool // enable the event server and inject JavaScript to reload pages when sources change
  109. serverMode bool // server mode: non-interactive
  110. productionMode bool // server mode: non-interactive, assume the server is running as a system service
  111. verboseMode bool // server mode: be more verbose
  112. debugMode bool // server mode: enable debug features and better error messages
  113. cacheFileStat bool // assume files will not be removed from the served directories while Algernon is running, which allows caching of costly os.Stat calls
  114. serverAddDomain bool // look for files in the directory with the same name as the requested hostname
  115. stricterHeaders bool // stricter HTTP headers
  116. simpleMode bool // server mode: for serving a directory with files over regular HTTP, nothing more nothing less
  117. openURLAfterServing bool // open the URL after serving
  118. onlyLuaMode bool // if only using the Lua REPL, and not serving anything
  119. quitAfterFirstRequest bool // quit when the first request has been responded to?
  120. markdownMode bool
  121. serveJustQUIC bool // if only QUIC or HTTP/3
  122. serveJustHTTP bool // if only HTTP
  123. serveJustHTTP2 bool // if only HTTP/2
  124. ctrldTwice bool // require a double press of ctrl-d to exit the REPL
  125. noHeaders bool // HTTP headers
  126. redisAddrSpecified bool
  127. noCache bool
  128. showVersion bool
  129. curlSupport bool // support clients like "curl" that downloads uncompressed by default
  130. noBanner bool // don't display the ANSI-graphics banner at start
  131. cacheCompressionSpeed bool // compression speed over compactness
  132. cacheCompression bool
  133. singleFileMode bool // if only serving a single file, like a Lua script
  134. hyperApp bool // convert JSX to HyperApp JS, or React JS?
  135. devMode bool // server mode: aims to make it easy to get started
  136. clearDefaultPathPrefixes bool // clear default path prefixes like "/admin" from the permission system?
  137. disableRateLimiting bool
  138. redirectHTTP bool // redirect HTTP traffic to HTTPS?
  139. useCertMagic bool // use CertMagic and Let's Encrypt for all directories in the given directory that contains a "."
  140. useBolt bool
  141. useNoDatabase bool // don't use a database. There will be a loss of functionality.
  142. }
  143. // ErrVersion is returned when the initialization quits because all that is done
  144. // is showing version information
  145. var (
  146. ErrVersion = errors.New("only showing version information")
  147. ErrDatabase = errors.New("could not find a usable database backend")
  148. )
  149. // New creates a new server configuration based using the default values
  150. func New(versionString, description string) (*Config, error) {
  151. tmpdir := env.Str("TMPDIR", "/tmp")
  152. ac := &Config{
  153. curlSupport: true,
  154. shutdownTimeout: 10 * time.Second,
  155. defaultWebColonPort: ":3001",
  156. defaultRedisColonPort: ":6379",
  157. defaultEventColonPort: ":5553",
  158. defaultEventRefresh: "350ms",
  159. defaultEventPath: "/sse",
  160. defaultLimit: 10,
  161. defaultPermissions: 0o660,
  162. defaultCacheSize: 1 * utils.MiB, // 1 MiB
  163. defaultCacheMaxEntitySize: 64 * utils.KiB, // 64 KB
  164. defaultStatCacheRefresh: time.Minute * 1, // Refresh the stat cache, if the stat cache feature is enabled
  165. // When is a static file large enough to not read into memory when serving
  166. defaultLargeFileSize: 42 * utils.MiB, // 42 MiB
  167. // Default rate limit, as a string
  168. defaultLimitString: strconv.Itoa(10),
  169. // Default Bolt database file, for some operating systems
  170. defaultBoltFilename: filepath.Join(tmpdir, "algernon.db"),
  171. // Default log file, for some operating systems
  172. defaultLogFile: filepath.Join(tmpdir, "algernon.log"),
  173. // Default filename for a Lua script that provides data to a template
  174. defaultLuaDataFilename: "data.lua",
  175. // List of configuration filenames to check
  176. serverConfigurationFilenames: []string{"/etc/algernon/serverconf.lua", "/etc/algernon/server.lua"},
  177. // Compression speed over compactness
  178. cacheCompressionSpeed: true,
  179. // TODO: Make configurable
  180. // Maximum given file size for caching, 7 MiB
  181. cacheMaxGivenDataSize: 7 * utils.MiB,
  182. // Mutex for rendering Pongo2 pages
  183. pongomutex: &sync.RWMutex{},
  184. // Program for opening URLs
  185. defaultOpenExecutable: platformdep.DefaultOpenExecutable,
  186. // General information about Algernon
  187. versionString: versionString,
  188. description: description,
  189. // JSX rendering options
  190. jsxOptions: api.TransformOptions{
  191. Loader: api.LoaderJSX,
  192. MinifyWhitespace: true,
  193. MinifyIdentifiers: true,
  194. MinifySyntax: true,
  195. Charset: api.CharsetUTF8,
  196. },
  197. }
  198. if err := ac.initFilesAndCache(); err != nil {
  199. return nil, err
  200. }
  201. ac.initializeMime()
  202. ac.setupLogging()
  203. // File stat cache
  204. ac.fs = datablock.NewFileStat(ac.cacheFileStat, ac.defaultStatCacheRefresh)
  205. return ac, nil
  206. }
  207. // SetFileStatCache can be used to set a different FileStat cache than the default one
  208. func (ac *Config) SetFileStatCache(fs *datablock.FileStat) {
  209. ac.fs = fs
  210. }
  211. // Initialize a temporary directory, handle flags, output version and handle profiling
  212. func (ac *Config) initFilesAndCache() error {
  213. // Temporary directory that might be used for logging, databases or file extraction
  214. serverTempDir, err := os.MkdirTemp("", "algernon")
  215. if err != nil {
  216. return err
  217. }
  218. ac.serverTempDir = serverTempDir
  219. // Set several configuration variables, based on the given flags and arguments
  220. ac.handleFlags(ac.serverTempDir)
  221. // Version (--version)
  222. if ac.showVersion {
  223. if !ac.quietMode {
  224. fmt.Println(ac.versionString)
  225. }
  226. return ErrVersion
  227. }
  228. // CPU and memory profiling, if it is enabled at build time, and one of these
  229. // flags are provided (+ a filename): -cpuprofile, -memprofile, -fgtrace or -trace
  230. traceStart()
  231. // Touch the common access log, if specified
  232. if ac.commonAccessLogFilename != "" {
  233. // Create if missing
  234. f, err := os.OpenFile(ac.commonAccessLogFilename, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0o644)
  235. if err != nil {
  236. return err
  237. }
  238. f.Close()
  239. }
  240. // Touch the combined access log, if specified
  241. if ac.combinedAccessLogFilename != "" {
  242. // Create if missing
  243. f, err := os.OpenFile(ac.combinedAccessLogFilename, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0o644)
  244. if err != nil {
  245. return err
  246. }
  247. f.Close()
  248. }
  249. // Create a cache struct for reading files (contains functions that can
  250. // be used for reading files, also when caching is disabled).
  251. // The final argument is for compressing with "fast" instead of "best".
  252. ac.cache = datablock.NewFileCache(ac.cacheSize, ac.cacheCompression, ac.cacheMaxEntitySize, ac.cacheCompressionSpeed, ac.cacheMaxGivenDataSize)
  253. return nil
  254. }
  255. func (ac *Config) setupLogging() {
  256. // Log to a file as JSON, if a log file has been specified
  257. if ac.serverLogFile != "" {
  258. f, errJSONLog := os.OpenFile(ac.serverLogFile, os.O_WRONLY|os.O_CREATE|os.O_APPEND, ac.defaultPermissions)
  259. if errJSONLog != nil {
  260. log.Warnf("Could not log to %s: %s", ac.serverLogFile, errJSONLog)
  261. } else {
  262. // Log to the given log filename
  263. log.SetFormatter(&log.JSONFormatter{})
  264. log.SetOutput(f)
  265. }
  266. } else if ac.quietMode {
  267. // If quiet mode is enabled and no log file has been specified, disable logging
  268. log.SetOutput(io.Discard)
  269. }
  270. // Close stdout and stderr if quite mode has been enabled
  271. if ac.quietMode {
  272. os.Stdout.Close()
  273. os.Stderr.Close()
  274. }
  275. }
  276. // Close removes the temporary directory
  277. func (ac *Config) Close() {
  278. os.RemoveAll(ac.serverTempDir)
  279. }
  280. // Fatal exit
  281. func (ac *Config) fatalExit(err error) {
  282. // Log to file, if a log file is used
  283. if ac.serverLogFile != "" {
  284. log.Error(err)
  285. }
  286. // Then switch to stderr and log the message there as well
  287. log.SetOutput(os.Stderr)
  288. // Use the standard formatter
  289. log.SetFormatter(&log.TextFormatter{})
  290. // Log and exit
  291. log.Fatalln(err.Error())
  292. }
  293. // Abrupt exit
  294. func (ac *Config) abruptExit(msg string) {
  295. // Log to file, if a log file is used
  296. if ac.serverLogFile != "" {
  297. log.Info(msg)
  298. }
  299. // Then switch to stderr and log the message there as well
  300. log.SetOutput(os.Stderr)
  301. // Use the standard formatter
  302. log.SetFormatter(&log.TextFormatter{})
  303. // Log and exit
  304. log.Info(msg)
  305. os.Exit(0)
  306. }
  307. // Quit after a short duration
  308. func (ac *Config) quitSoon(msg string, soon time.Duration) {
  309. time.Sleep(soon)
  310. ac.abruptExit(msg)
  311. }
  312. // Return true of the given file type (extension) should be cached
  313. func (ac *Config) shouldCache(ext string) bool {
  314. switch ac.cacheMode {
  315. case cachemode.On:
  316. return true
  317. case cachemode.Production, cachemode.Small:
  318. switch ext {
  319. case ".amber", ".lua", ".tl", ".po2", ".tpl", ".pongo2":
  320. return false
  321. default:
  322. return true
  323. }
  324. case cachemode.Images:
  325. switch ext {
  326. case ".png", ".jpg", ".gif", ".svg", ".jpeg", ".ico", ".bmp", ".apng":
  327. return true
  328. default:
  329. return false
  330. }
  331. case cachemode.Off:
  332. return false
  333. case cachemode.Development, cachemode.Unset:
  334. fallthrough
  335. default:
  336. switch ext {
  337. case ".amber", ".lua", ".tl", ".md", ".gcss", ".jsx", ".po2", ".tpl", ".pongo2", ".happ", ".js", ".scss":
  338. return false
  339. default:
  340. return true
  341. }
  342. }
  343. }
  344. // hasHandlers checks if the given filename contains "handle(" or "handle ("
  345. func hasHandlers(fn string) bool {
  346. data, err := os.ReadFile(fn)
  347. return err == nil && (bytes.Contains(data, []byte("handle(")) || bytes.Contains(data, []byte("handle (")))
  348. }
  349. // has checks if a given slice of strings contains a given string
  350. func has(sl []string, e string) bool {
  351. for _, s := range sl {
  352. if e == s {
  353. return true
  354. }
  355. }
  356. return false
  357. }
  358. // repeat a string n number of times
  359. func repeat(s string, n int) string {
  360. var sb strings.Builder
  361. for i := 0; i < n; i++ {
  362. sb.WriteString(s)
  363. }
  364. return sb.String()
  365. }
  366. // unique removes all repeated elements from a slice of strings
  367. func unique(sl []string) []string {
  368. var nl []string
  369. for _, s := range sl {
  370. if !has(nl, s) {
  371. nl = append(nl, s)
  372. }
  373. }
  374. return nl
  375. }
  376. // MustServe sets up a server with handlers
  377. func (ac *Config) MustServe(mux *http.ServeMux) error {
  378. var err error
  379. defer ac.Close()
  380. // Output what we are attempting to access and serve
  381. if ac.verboseMode {
  382. log.Info("Accessing " + ac.serverDirOrFilename)
  383. }
  384. // Check if the given directory really is a directory
  385. if !ac.fs.IsDir(ac.serverDirOrFilename) {
  386. // It is not a directory
  387. serverFile := ac.serverDirOrFilename
  388. // Check if the file exists
  389. if ac.fs.Exists(serverFile) {
  390. if ac.markdownMode {
  391. // Serve the given Markdown file as a static HTTP server
  392. if serveErr := ac.ServeStaticFile(serverFile, ac.defaultWebColonPort); serveErr != nil {
  393. // Must serve
  394. ac.fatalExit(serveErr)
  395. }
  396. return nil
  397. }
  398. // Switch based on the lowercase filename extension
  399. switch strings.ToLower(filepath.Ext(serverFile)) {
  400. case ".md", ".markdown":
  401. // Serve the given Markdown file as a static HTTP server
  402. if serveErr := ac.ServeStaticFile(serverFile, ac.defaultWebColonPort); serveErr != nil {
  403. // Must serve
  404. ac.fatalExit(serveErr)
  405. }
  406. return nil
  407. case ".zip", ".alg":
  408. // Assume this to be a compressed Algernon application
  409. webApplicationExtractionDir := "/dev/shm" // extract to memory, if possible
  410. testfile := filepath.Join(webApplicationExtractionDir, "canary")
  411. if _, err := os.Create(testfile); err == nil { // success
  412. os.Remove(testfile)
  413. } else {
  414. // Could not create the test file
  415. // Use the server temp dir (typically /tmp) instead of /dev/shm
  416. webApplicationExtractionDir = ac.serverTempDir
  417. }
  418. // Extract the web application
  419. if extractErr := unzip.Extract(serverFile, webApplicationExtractionDir); extractErr != nil {
  420. return extractErr
  421. }
  422. // Use the directory where the file was extracted as the server directory
  423. ac.serverDirOrFilename = webApplicationExtractionDir
  424. // If there is only one directory there, assume it's the
  425. // directory of the newly extracted ZIP file.
  426. if filenames := utils.GetFilenames(ac.serverDirOrFilename); len(filenames) == 1 {
  427. fullPath := filepath.Join(ac.serverDirOrFilename, filenames[0])
  428. if ac.fs.IsDir(fullPath) {
  429. // Use this as the server directory instead
  430. ac.serverDirOrFilename = fullPath
  431. }
  432. }
  433. // If there are server configuration files in the extracted
  434. // directory, register them.
  435. for _, filename := range ac.serverConfigurationFilenames {
  436. configFilename := filepath.Join(ac.serverDirOrFilename, filename)
  437. ac.serverConfigurationFilenames = append(ac.serverConfigurationFilenames, configFilename)
  438. }
  439. // Disregard all configuration files from the current directory
  440. // (filenames without a path separator), since we are serving a
  441. // ZIP file.
  442. for i, filename := range ac.serverConfigurationFilenames {
  443. if strings.Count(filepath.ToSlash(filename), "/") == 0 {
  444. // Remove the filename from the slice
  445. ac.serverConfigurationFilenames = append(ac.serverConfigurationFilenames[:i], ac.serverConfigurationFilenames[i+1:]...)
  446. }
  447. }
  448. default:
  449. ac.singleFileMode = true
  450. }
  451. } else {
  452. return errors.New("File does not exist: " + serverFile)
  453. }
  454. }
  455. // Make a few changes to the defaults if we are serving a single file
  456. if ac.singleFileMode {
  457. ac.debugMode = true
  458. ac.serveJustHTTP = true
  459. }
  460. to := textoutput.NewTextOutput(runtime.GOOS != "windows", !ac.quietMode)
  461. // Console output
  462. if !ac.quietMode && !ac.singleFileMode && !ac.simpleMode && !ac.noBanner {
  463. // Output a colorful ansi logo if a proper terminal is available
  464. fmt.Println(platformdep.Banner(ac.versionString, ac.description))
  465. } else if !ac.quietMode {
  466. timestamp := time.Now().Format("2006-01-02 15:04")
  467. to.OutputTags("<cyan>" + ac.versionString + "<darkgray> - " + timestamp + "<off>")
  468. // colorstring.Println("[cyan]" + ac.versionString + "[dark_gray] - " + timestamp + "[reset]")
  469. }
  470. // Disable the database backend if the BoltDB filename is the /dev/null file (or OS equivalent)
  471. if ac.boltFilename == os.DevNull {
  472. ac.useNoDatabase = true
  473. }
  474. if !ac.useNoDatabase {
  475. // Connect to a database and retrieve a Permissions struct
  476. ac.perm, err = ac.DatabaseBackend()
  477. if err != nil {
  478. return ErrDatabase
  479. }
  480. }
  481. // Lua LState pool
  482. ac.luapool = pool.New()
  483. AtShutdown(func() {
  484. // TODO: Why not defer?
  485. ac.luapool.Shutdown()
  486. })
  487. // TODO: save repl history + close luapool + close logs ++ at shutdown
  488. if ac.singleFileMode && (filepath.Ext(ac.serverDirOrFilename) == ".lua" || ac.onlyLuaMode) {
  489. ac.luaServerFilename = ac.serverDirOrFilename
  490. if ac.luaServerFilename == "index.lua" || ac.luaServerFilename == "data.lua" {
  491. // Friendly message to new users
  492. if !hasHandlers(ac.luaServerFilename) {
  493. log.Warnf("Found no handlers in %s", ac.luaServerFilename)
  494. log.Info("How to implement \"Hello, World!\" in " + ac.luaServerFilename + " file:\n\nhandle(\"/\", function()\n print(\"Hello, World!\")\nend)\n")
  495. }
  496. }
  497. ac.serverDirOrFilename = filepath.Dir(ac.serverDirOrFilename)
  498. // Make it possible to read other files from the Lua script
  499. ac.singleFileMode = false
  500. }
  501. ac.serverConfigurationFilenames = unique(ac.serverConfigurationFilenames)
  502. // Color scheme
  503. arrowColor := "<lightblue>"
  504. filenameColor := "<white>"
  505. luaOutputColor := "<darkgray>"
  506. dashLineColor := "<red>"
  507. // Create a Colorize struct that will not reset colors after colorizing
  508. // strings meant for the terminal.
  509. // c := colorstring.Colorize{Colors: colorstring.DefaultColors, Reset: false}
  510. if (len(ac.serverConfigurationFilenames) > 0) && !ac.quietMode && !ac.onlyLuaMode {
  511. fmt.Println(to.Tags(dashLineColor + repeat("-", 49) + "<off>"))
  512. }
  513. // Read server configuration script, if present.
  514. // The scripts may change global variables.
  515. var ranConfigurationFilenames []string
  516. for _, filename := range unique(ac.serverConfigurationFilenames) {
  517. if ac.fs.Exists(filename) {
  518. // Dividing line between the banner and output from any of the configuration scripts
  519. if !ac.quietMode && !ac.onlyLuaMode {
  520. // Output the configuration filename
  521. to.Println(arrowColor + "-> " + filenameColor + filename + "<off>")
  522. fmt.Print(to.Tags(luaOutputColor))
  523. } else if ac.verboseMode {
  524. log.Info("Running Lua configuration file: " + filename)
  525. }
  526. withHandlerFunctions := true
  527. errConf := ac.RunConfiguration(filename, mux, withHandlerFunctions)
  528. if errConf != nil {
  529. if ac.perm != nil {
  530. log.Error("Could not use configuration script: " + filename)
  531. return errConf
  532. }
  533. if ac.verboseMode {
  534. log.Info("Skipping " + filename + " because the database backend is not in use.")
  535. }
  536. }
  537. ranConfigurationFilenames = append(ranConfigurationFilenames, filename)
  538. } else {
  539. if ac.verboseMode {
  540. log.Info("Looking for: " + filename)
  541. }
  542. }
  543. }
  544. // Only keep the active ones. Used when outputting server information.
  545. ac.serverConfigurationFilenames = ranConfigurationFilenames
  546. // Run the standalone Lua server, if specified
  547. if ac.luaServerFilename != "" {
  548. // Run the Lua server file and set up handlers
  549. if !ac.quietMode && !ac.onlyLuaMode {
  550. // Output the configuration filename
  551. to.Println(arrowColor + "-> " + filenameColor + ac.luaServerFilename + "<off>")
  552. fmt.Print(to.Tags(luaOutputColor))
  553. } else if ac.verboseMode {
  554. fmt.Println("Running Lua configuration file: " + ac.luaServerFilename)
  555. }
  556. withHandlerFunctions := true
  557. errLua := ac.RunConfiguration(ac.luaServerFilename, mux, withHandlerFunctions)
  558. if errLua != nil {
  559. log.Errorf("Error in %s (interpreted as a server script):\n%s\n", ac.luaServerFilename, errLua)
  560. return errLua
  561. }
  562. } else {
  563. // Register HTTP handler functions
  564. ac.RegisterHandlers(mux, "/", ac.serverDirOrFilename, ac.serverAddDomain)
  565. }
  566. // Set the values that has not been set by flags nor scripts
  567. // (and can be set by both)
  568. ranServerReadyFunction := ac.finalConfiguration(ac.serverHost)
  569. if !ac.quietMode && !ac.onlyLuaMode {
  570. to.Print("<off>")
  571. }
  572. // If no configuration files were being ran successfully,
  573. // output basic server information.
  574. if len(ac.serverConfigurationFilenames) == 0 {
  575. if !ac.quietMode && !ac.onlyLuaMode {
  576. fmt.Println(ac.Info())
  577. }
  578. ranServerReadyFunction = true
  579. }
  580. // Separator between the output of the configuration scripts and
  581. // the rest of the server output.
  582. if ranServerReadyFunction && (len(ac.serverConfigurationFilenames) > 0) && !ac.quietMode && !ac.onlyLuaMode {
  583. to.Tags(dashLineColor + repeat("-", 49) + "<off>")
  584. }
  585. // Direct internal logging elsewhere
  586. internalLogFile, err := os.Open(ac.internalLogFilename)
  587. if err != nil {
  588. // Could not open the internalLogFilename filename, try using another filename
  589. internalLogFile, err = os.OpenFile("internal.log", os.O_CREATE|os.O_APPEND|os.O_WRONLY, ac.defaultPermissions)
  590. AtShutdown(func() {
  591. // TODO This one is is special and should be closed after the other shutdown functions.
  592. // Set up a "done" channel instead of sleeping.
  593. time.Sleep(100 * time.Millisecond)
  594. internalLogFile.Close()
  595. })
  596. if err != nil {
  597. ac.fatalExit(fmt.Errorf("could not write to %s nor %s", ac.internalLogFilename, "internal.log"))
  598. }
  599. }
  600. defer internalLogFile.Close()
  601. internallog.SetOutput(internalLogFile)
  602. // Serve filesystem events in the background.
  603. // Used for reloading pages when the sources change.
  604. // Can also be used when serving a single file.
  605. if ac.autoRefresh {
  606. ac.refreshDuration, err = time.ParseDuration(ac.eventRefresh)
  607. if err != nil {
  608. log.Warnf("%s is an invalid duration. Using %s instead.", ac.eventRefresh, ac.defaultEventRefresh)
  609. // Ignore the error, since defaultEventRefresh is a constant and must be parseable
  610. ac.refreshDuration, _ = time.ParseDuration(ac.defaultEventRefresh)
  611. }
  612. recwatch.SetVerbose(ac.verboseMode)
  613. recwatch.LogError = func(err error) {
  614. log.Error(err)
  615. }
  616. recwatch.FatalExit = ac.fatalExit
  617. recwatch.Exists = ac.fs.Exists
  618. if ac.autoRefreshDir != "" {
  619. absdir, err := filepath.Abs(ac.autoRefreshDir)
  620. if err != nil {
  621. absdir = ac.autoRefreshDir
  622. }
  623. // Only watch the autoRefreshDir, recursively
  624. recwatch.EventServer(absdir, "*", ac.eventAddr, ac.defaultEventPath, ac.refreshDuration)
  625. } else {
  626. absdir, err := filepath.Abs(ac.serverDirOrFilename)
  627. if err != nil {
  628. absdir = ac.serverDirOrFilename
  629. }
  630. // Watch everything in the server directory, recursively
  631. recwatch.EventServer(absdir, "*", ac.eventAddr, ac.defaultEventPath, ac.refreshDuration)
  632. }
  633. }
  634. // For communicating to and from the REPL
  635. ready := make(chan bool) // for when the server is up and running
  636. done := make(chan bool) // for when the user wish to quit the server
  637. // The Lua REPL
  638. if !ac.serverMode {
  639. // If the REPL uses readline, the SIGWINCH signal is handled there
  640. go ac.REPL(ready, done)
  641. } else {
  642. // Ignore SIGWINCH if we are not going to use a REPL
  643. platformdep.IgnoreTerminalResizeSignal()
  644. }
  645. // Run the shutdown functions if graceful does not
  646. defer ac.GenerateShutdownFunction(nil)()
  647. // Serve HTTP, HTTP/2 and/or HTTPS
  648. return ac.Serve(mux, done, ready)
  649. }