flags.go 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426
  1. package engine
  2. import (
  3. "flag"
  4. "os"
  5. "path/filepath"
  6. "runtime"
  7. "strconv"
  8. "strings"
  9. log "github.com/sirupsen/logrus"
  10. "github.com/xyproto/algernon/cachemode"
  11. "github.com/xyproto/algernon/themes"
  12. "github.com/xyproto/datablock"
  13. "github.com/xyproto/env/v2"
  14. "github.com/xyproto/files"
  15. )
  16. // Parse the flags, return the default hostname
  17. func (ac *Config) handleFlags(serverTempDir string) {
  18. var (
  19. // The short version of some flags
  20. serveJustHTTPShort, autoRefreshShort, productionModeShort,
  21. debugModeShort, serverModeShort, useBoltShort, devModeShort,
  22. showVersionShort, quietModeShort, cacheFileStatShort, simpleModeShort,
  23. noBannerShort, quitAfterFirstRequestShort, verboseModeShort,
  24. serveJustQUICShort, onlyLuaModeShort, redirectShort bool
  25. // Used when setting the cache mode
  26. cacheModeString string
  27. // Used if disabling cache compression
  28. rawCache bool
  29. // Used if disabling the database backend
  30. noDatabase bool
  31. )
  32. // The usage function that provides more help (for --help or -h)
  33. flag.Usage = generateUsageFunction(ac)
  34. // The default for running the redis server on Windows is to listen
  35. // to "localhost:port", but not just ":port".
  36. host := ""
  37. if runtime.GOOS == "windows" {
  38. host = "localhost"
  39. // Default Bolt database file
  40. ac.defaultBoltFilename = filepath.Join(serverTempDir, "algernon.db")
  41. // Default log file
  42. ac.defaultLogFile = filepath.Join(serverTempDir, "algernon.log")
  43. }
  44. // Commandline flag configuration
  45. flag.StringVar(&ac.serverDirOrFilename, "dir", ".", "Server directory")
  46. flag.StringVar(&ac.serverAddr, "addr", "", "Server [host][:port] (ie \":443\")")
  47. flag.StringVar(&ac.serverCert, "cert", "cert.pem", "Server certificate")
  48. flag.StringVar(&ac.serverKey, "key", "key.pem", "Server key")
  49. flag.StringVar(&ac.redisAddr, "redis", "", "Redis [host][:port] (ie \""+ac.defaultRedisColonPort+"\")")
  50. flag.IntVar(&ac.redisDBindex, "dbindex", 0, "Redis database index")
  51. flag.StringVar(&ac.serverConfScript, "conf", "serverconf.lua", "Server configuration written in Lua")
  52. flag.StringVar(&ac.serverLogFile, "log", "", "Server log file")
  53. flag.StringVar(&ac.internalLogFilename, "internal", os.DevNull, "Internal log file")
  54. flag.BoolVar(&ac.serveJustHTTP2, "http2only", false, "Serve HTTP/2, not HTTPS + HTTP/2")
  55. flag.BoolVar(&ac.serveJustHTTP, "httponly", false, "Serve plain old HTTP")
  56. flag.BoolVar(&ac.productionMode, "prod", false, "Production mode (when running as a system service)")
  57. flag.BoolVar(&ac.debugMode, "debug", false, "Debug mode")
  58. flag.BoolVar(&ac.verboseMode, "verbose", false, "Verbose logging")
  59. flag.BoolVar(&ac.redirectHTTP, "redirect", false, "Redirect HTTP traffic to HTTPS if both are enabled")
  60. flag.BoolVar(&ac.autoRefresh, "autorefresh", false, "Enable the auto-refresh feature")
  61. flag.StringVar(&ac.autoRefreshDir, "watchdir", "", "Directory to watch (also enables auto-refresh)")
  62. flag.StringVar(&ac.eventAddr, "eventserver", "", "SSE [host][:port] (ie \""+ac.defaultEventColonPort+"\")")
  63. flag.StringVar(&ac.eventRefresh, "eventrefresh", ac.defaultEventRefresh, "Event refresh interval (ie \""+ac.defaultEventRefresh+"\")")
  64. flag.BoolVar(&ac.serverMode, "server", false, "Server mode (disable interactive mode)")
  65. flag.StringVar(&ac.mariadbDSN, "maria", "", "MariaDB/MySQL connection string (DSN)")
  66. flag.StringVar(&ac.mariaDatabase, "mariadb", "", "MariaDB/MySQL database name")
  67. flag.StringVar(&ac.postgresDSN, "postgres", "", "PostgreSQL connection string (DSN)")
  68. flag.StringVar(&ac.postgresDatabase, "postgresdb", "", "PostgreSQL database name")
  69. flag.BoolVar(&ac.useBolt, "bolt", false, "Use the default Bolt filename")
  70. flag.StringVar(&ac.boltFilename, "boltdb", "", "Bolt database filename")
  71. flag.Int64Var(&ac.limitRequests, "limit", ac.defaultLimit, "Limit clients to a number of requests per second")
  72. flag.BoolVar(&ac.disableRateLimiting, "nolimit", false, "Disable rate limiting")
  73. flag.BoolVar(&ac.devMode, "dev", false, "Development mode")
  74. flag.BoolVar(&ac.showVersion, "version", false, "Version")
  75. flag.StringVar(&cacheModeString, "cache", "", "Cache everything but Amber, Lua, GCSS and Markdown")
  76. flag.Uint64Var(&ac.cacheSize, "cachesize", ac.defaultCacheSize, "Cache size, in bytes")
  77. flag.Uint64Var(&ac.largeFileSize, "largesize", ac.defaultLargeFileSize, "Threshold for not reading static files into memory, in bytes")
  78. flag.Uint64Var(&ac.writeTimeout, "timeout", 10, "Timeout when writing to a client, in seconds")
  79. flag.BoolVar(&ac.quietMode, "quiet", false, "Quiet")
  80. flag.BoolVar(&rawCache, "rawcache", false, "Disable cache compression")
  81. flag.StringVar(&ac.serverHeaderName, "servername", ac.versionString, "Server header name")
  82. flag.BoolVar(&ac.cacheFileStat, "statcache", false, "Cache os.Stat")
  83. flag.BoolVar(&ac.serverAddDomain, "domain", false, "Look for files in the directory named the same as the hostname")
  84. flag.BoolVar(&ac.simpleMode, "simple", false, "Serve a directory of files over HTTP")
  85. flag.StringVar(&ac.openExecutable, "open", "", "Open URL after serving, with an application")
  86. flag.BoolVar(&ac.quitAfterFirstRequest, "quit", false, "Quit after the first request")
  87. flag.BoolVar(&ac.noCache, "nocache", false, "Disable caching")
  88. flag.BoolVar(&ac.noHeaders, "noheaders", false, "Don't set any HTTP headers by default")
  89. flag.BoolVar(&ac.stricterHeaders, "stricter", false, "Stricter HTTP headers")
  90. flag.StringVar(&ac.defaultTheme, "theme", themes.DefaultTheme, "Theme for Markdown and directory listings")
  91. flag.BoolVar(&ac.noBanner, "nobanner", false, "Don't show a banner at start")
  92. flag.BoolVar(&ac.ctrldTwice, "ctrld", false, "Press ctrl-d twice to exit")
  93. if quicEnabled {
  94. flag.BoolVar(&ac.serveJustQUIC, "quic", false, "Serve just QUIC")
  95. }
  96. flag.BoolVar(&noDatabase, "nodb", false, "No database backend")
  97. flag.BoolVar(&ac.onlyLuaMode, "lua", false, "Only present the Lua REPL")
  98. flag.StringVar(&ac.combinedAccessLogFilename, "accesslog", "", "Combined access log filename")
  99. flag.StringVar(&ac.commonAccessLogFilename, "ncsa", "", "NCSA access log filename")
  100. flag.BoolVar(&ac.clearDefaultPathPrefixes, "clear", false, "Clear the default URI prefixes for handling permissions")
  101. flag.StringVar(&ac.cookieSecret, "cookiesecret", "", "Secret to be used when setting and getting login cookies")
  102. flag.BoolVar(&ac.useCertMagic, "letsencrypt", false, "Use Let's Encrypt for all served domains and serve regular HTTPS")
  103. flag.StringVar(&ac.dirBaseURL, "dirbaseurl", "", "Base URL for the directory listing (optional)")
  104. // The short versions of some flags
  105. flag.BoolVar(&serveJustHTTPShort, "t", false, "Serve plain old HTTP")
  106. flag.BoolVar(&autoRefreshShort, "a", false, "Enable the auto-refresh feature")
  107. flag.BoolVar(&serverModeShort, "s", false, "Server mode (non-interactive)")
  108. flag.BoolVar(&useBoltShort, "b", false, "Use the default Bolt filename")
  109. flag.BoolVar(&productionModeShort, "p", false, "Production mode (when running as a system service)")
  110. flag.BoolVar(&debugModeShort, "d", false, "Debug mode")
  111. flag.BoolVar(&devModeShort, "e", false, "Development mode")
  112. flag.BoolVar(&showVersionShort, "v", false, "Version")
  113. flag.BoolVar(&verboseModeShort, "V", false, "Verbose")
  114. flag.BoolVar(&quietModeShort, "q", false, "Quiet")
  115. flag.BoolVar(&cacheFileStatShort, "c", false, "Cache os.Stat")
  116. flag.BoolVar(&simpleModeShort, "x", false, "Simple mode")
  117. flag.BoolVar(&ac.openURLAfterServing, "o", false, "Open URL after serving")
  118. flag.BoolVar(&quitAfterFirstRequestShort, "z", false, "Quit after the first request")
  119. flag.BoolVar(&ac.markdownMode, "m", false, "Markdown mode")
  120. flag.BoolVar(&noBannerShort, "n", false, "Don't show a banner at start")
  121. if quicEnabled {
  122. flag.BoolVar(&serveJustQUICShort, "u", false, "Serve just QUIC")
  123. }
  124. flag.BoolVar(&onlyLuaModeShort, "l", false, "Only present the Lua REPL")
  125. flag.BoolVar(&redirectShort, "r", false, "Redirect HTTP traffic to HTTPS, if both are enabled")
  126. flag.Parse()
  127. // Accept both long and short versions of some flags
  128. ac.serveJustHTTP = ac.serveJustHTTP || serveJustHTTPShort
  129. ac.autoRefresh = ac.autoRefresh || autoRefreshShort
  130. ac.debugMode = ac.debugMode || debugModeShort
  131. ac.serverMode = ac.serverMode || serverModeShort
  132. ac.useBolt = ac.useBolt || useBoltShort
  133. ac.productionMode = ac.productionMode || productionModeShort
  134. ac.devMode = ac.devMode || devModeShort
  135. ac.showVersion = ac.showVersion || showVersionShort
  136. ac.quietMode = ac.quietMode || quietModeShort
  137. ac.cacheFileStat = ac.cacheFileStat || cacheFileStatShort
  138. ac.simpleMode = ac.simpleMode || simpleModeShort
  139. ac.openURLAfterServing = ac.openURLAfterServing || (ac.openExecutable != "")
  140. ac.quitAfterFirstRequest = ac.quitAfterFirstRequest || quitAfterFirstRequestShort
  141. ac.verboseMode = ac.verboseMode || verboseModeShort
  142. ac.noBanner = ac.noBanner || noBannerShort
  143. if quicEnabled {
  144. ac.serveJustQUIC = ac.serveJustQUIC || serveJustQUICShort
  145. }
  146. ac.onlyLuaMode = ac.onlyLuaMode || onlyLuaModeShort
  147. ac.redirectHTTP = ac.redirectHTTP || redirectShort
  148. // Serve a single Markdown file once, and open it in the browser
  149. if ac.markdownMode {
  150. ac.quietMode = true
  151. ac.openURLAfterServing = true
  152. ac.quitAfterFirstRequest = true
  153. }
  154. // If only using the Lua REPL, don't include the banner, and don't serve anything
  155. if ac.onlyLuaMode {
  156. ac.noBanner = true
  157. ac.debugMode = true
  158. ac.serverConfScript = ""
  159. }
  160. // Check if IGNOREEOF is set
  161. if ignoreEOF := env.Int("IGNOREEOF", 0); ignoreEOF > 1 {
  162. ac.ctrldTwice = true
  163. }
  164. // Disable verbose mode if quiet mode has been enabled
  165. if ac.quietMode {
  166. ac.verboseMode = false
  167. }
  168. // Enable cache compression unless raw cache is specified
  169. ac.cacheCompression = !rawCache
  170. ac.redisAddrSpecified = ac.redisAddr != ""
  171. if ac.redisAddr == "" {
  172. // The default host and port
  173. ac.redisAddr = host + ac.defaultRedisColonPort
  174. }
  175. // May be overridden by devMode
  176. if ac.serverMode {
  177. ac.debugMode = false
  178. }
  179. if noDatabase {
  180. ac.boltFilename = os.DevNull
  181. }
  182. // TODO: If flags are set in addition to -p or -e, don't override those
  183. // when -p or -e is set.
  184. // Change several defaults if production mode is enabled
  185. switch {
  186. case ac.productionMode:
  187. // Use system directories
  188. ac.serverDirOrFilename = "/srv/algernon"
  189. ac.serverCert = "/etc/algernon/cert.pem"
  190. ac.serverKey = "/etc/algernon/key.pem"
  191. ac.cacheMode = cachemode.Production
  192. ac.serverMode = true
  193. case ac.devMode:
  194. // Change several defaults if development mode is enabled
  195. ac.serveJustHTTP = true
  196. // serverLogFile = defaultLogFile
  197. ac.debugMode = true
  198. // TODO: Make it possible to set --limit to the default limit also when -e is used
  199. if ac.limitRequests == ac.defaultLimit {
  200. ac.limitRequests = 700 // Increase the rate limit considerably
  201. }
  202. ac.cacheMode = cachemode.Development
  203. case ac.simpleMode:
  204. ac.useBolt = true
  205. ac.boltFilename = os.DevNull
  206. ac.serveJustHTTP = true
  207. ac.serverMode = true
  208. ac.cacheMode = cachemode.Off
  209. ac.noCache = true
  210. ac.disableRateLimiting = true
  211. ac.clearDefaultPathPrefixes = true
  212. ac.noHeaders = true
  213. ac.writeTimeout = 3600 * 24
  214. }
  215. if ac.onlyLuaMode {
  216. // Use a random database, so that several lua REPLs can be started without colliding,
  217. // but only if the current default bolt database file can not be opened.
  218. if ac.boltFilename != os.DevNull && !files.CanRead(ac.boltFilename) {
  219. tempFile, err := os.CreateTemp("", "algernon_repl*.db")
  220. if err == nil { // no issue
  221. ac.boltFilename = tempFile.Name()
  222. }
  223. }
  224. }
  225. // If a watch directory is given, enable the auto refresh feature
  226. if ac.autoRefreshDir != "" {
  227. ac.autoRefresh = true
  228. }
  229. // If nocache is given, disable the cache
  230. if ac.noCache {
  231. ac.cacheMode = cachemode.Off
  232. ac.cacheFileStat = false
  233. }
  234. // Convert the request limit to a string
  235. ac.limitRequestsString = strconv.FormatInt(ac.limitRequests, 10)
  236. // If auto-refresh is enabled, change the caching
  237. if ac.autoRefresh {
  238. if cacheModeString == "" {
  239. // Disable caching by default, when auto-refresh is enabled
  240. ac.cacheMode = cachemode.Off
  241. ac.cacheFileStat = false
  242. }
  243. }
  244. // The cache flag overrides the settings from the other modes
  245. if cacheModeString != "" {
  246. ac.cacheMode = cachemode.New(cacheModeString)
  247. }
  248. // Disable cache entirely if cacheSize is set to 0
  249. if ac.cacheSize == 0 {
  250. ac.cacheMode = cachemode.Off
  251. }
  252. // Set cacheSize to 0 if the cache is disabled
  253. if ac.cacheMode == cachemode.Off {
  254. ac.cacheSize = 0
  255. }
  256. // If cache mode is unset, use the dev mode
  257. if ac.cacheMode == cachemode.Unset {
  258. ac.cacheMode = cachemode.Default
  259. }
  260. if ac.cacheMode == cachemode.Small {
  261. ac.cacheMaxEntitySize = ac.defaultCacheMaxEntitySize
  262. }
  263. // For backward compatibility with previous versions of Algernon
  264. // TODO: Remove, in favor of a better config/flag system
  265. serverAddrChanged := false
  266. if len(flag.Args()) >= 1 {
  267. // Only override the default server directory if Algernon can find it
  268. firstArg := flag.Args()[0]
  269. fs := datablock.NewFileStat(ac.cacheFileStat, ac.defaultStatCacheRefresh)
  270. // Interpret as a file or directory
  271. if fs.IsDir(firstArg) || fs.Exists(firstArg) {
  272. if strings.HasSuffix(firstArg, string(os.PathSeparator)) {
  273. ac.serverDirOrFilename = firstArg[:len(firstArg)-1]
  274. } else {
  275. ac.serverDirOrFilename = firstArg
  276. }
  277. } else if strings.Contains(firstArg, ":") {
  278. // Interpret as the server address
  279. ac.serverAddr = firstArg
  280. serverAddrChanged = true
  281. } else if _, err := strconv.Atoi(firstArg); err == nil { // no error
  282. // Is a number. Interpret as the server address
  283. ac.serverAddr = ":" + firstArg
  284. serverAddrChanged = true
  285. }
  286. }
  287. // Clean up path in ac.serverDirOrFilename
  288. // .Rel calls .Clean on the result.
  289. if pwd, err := os.Getwd(); err == nil { // no error
  290. if cleanPath, err := filepath.Rel(pwd, ac.serverDirOrFilename); err == nil { // no error
  291. ac.serverDirOrFilename = cleanPath
  292. }
  293. }
  294. // TODO: Replace the code below with a good config/flag package.
  295. shift := 0
  296. if serverAddrChanged {
  297. shift = 1
  298. }
  299. if len(flag.Args()) >= 2 {
  300. secondArg := flag.Args()[1]
  301. if strings.Contains(secondArg, ":") {
  302. ac.serverAddr = secondArg
  303. } else if _, err := strconv.Atoi(secondArg); err == nil { // no error
  304. // Is a number. Interpret as the server address.
  305. ac.serverAddr = ":" + secondArg
  306. } else if len(flag.Args()) >= 3-shift {
  307. ac.serverCert = flag.Args()[2-shift]
  308. }
  309. }
  310. if len(flag.Args()) >= 4-shift {
  311. ac.serverKey = flag.Args()[3-shift]
  312. }
  313. if len(flag.Args()) >= 5-shift {
  314. ac.redisAddr = flag.Args()[4-shift]
  315. ac.redisAddrSpecified = true
  316. }
  317. if len(flag.Args()) >= 6-shift {
  318. // Convert the dbindex from string to int
  319. DBindex, err := strconv.Atoi(flag.Args()[5-shift])
  320. if err != nil {
  321. ac.redisDBindex = DBindex
  322. }
  323. }
  324. // Use the default openExecutable if none is set
  325. if ac.openURLAfterServing && ac.openExecutable == "" {
  326. ac.openExecutable = ac.defaultOpenExecutable
  327. }
  328. // Add the serverConfScript to the list of configuration scripts to be read and executed
  329. if ac.serverConfScript != "" && ac.serverConfScript != os.DevNull {
  330. ac.serverConfigurationFilenames = append(ac.serverConfigurationFilenames, ac.serverConfScript, filepath.Join(ac.serverDirOrFilename, ac.serverConfScript))
  331. }
  332. ac.serverHost = host
  333. // CertMagic and Let's Encrypt
  334. if ac.useCertMagic {
  335. log.Info("Use Cert Magic")
  336. if dirEntries, err := os.ReadDir(ac.serverDirOrFilename); err != nil {
  337. log.Error("Could not use Cert Magic:" + err.Error())
  338. ac.useCertMagic = false
  339. } else {
  340. // log.Infof("Looping over %v files", len(files))
  341. for _, dirEntry := range dirEntries {
  342. basename := filepath.Base(dirEntry.Name())
  343. dirOrSymlink := dirEntry.IsDir() || ((dirEntry.Type() & os.ModeSymlink) == os.ModeSymlink)
  344. // TODO: Confirm that the symlink is a symlink to a directory, if it's a symlink
  345. if dirOrSymlink && strings.Contains(basename, ".") && !strings.HasPrefix(basename, ".") && !strings.HasSuffix(basename, ".old") {
  346. ac.certMagicDomains = append(ac.certMagicDomains, basename)
  347. }
  348. }
  349. // Using Let's Encrypt implies --domain, to search for suitable directories in the directory to be served
  350. ac.serverAddDomain = true
  351. }
  352. }
  353. }
  354. // Set the values that has not been set by flags nor scripts (and can be set by both)
  355. // Returns true if a "ready function" has been run.
  356. func (ac *Config) finalConfiguration(host string) bool {
  357. // Set the server host and port (commandline flags overrides Lua configuration)
  358. if ac.serverAddr == "" {
  359. if ac.serverAddrLua != "" {
  360. ac.serverAddr = ac.serverAddrLua
  361. } else {
  362. ac.serverAddr = host + ac.defaultWebColonPort
  363. }
  364. }
  365. // Set the event server host and port
  366. if ac.eventAddr == "" {
  367. ac.eventAddr = host + ac.defaultEventColonPort
  368. }
  369. // Turn off debug mode if production mode is enabled
  370. if ac.productionMode {
  371. // Turn off debug mode
  372. ac.debugMode = false
  373. }
  374. hasReadyFunction := ac.serverReadyFunctionLua != nil
  375. // Run the Lua function specified with the OnReady function, if available
  376. if hasReadyFunction {
  377. // Useful for outputting configuration information after both
  378. // configuration scripts have been run and flags have been parsed
  379. ac.serverReadyFunctionLua()
  380. }
  381. return hasReadyFunction
  382. }