Contains the Concourse pipeline definition for building a line-server container
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.

322 lines
10 KiB

9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
  1. package main
  2. import (
  3. "flag"
  4. "log"
  5. "net"
  6. "net/http"
  7. "net/http/fcgi"
  8. "net/url"
  9. "os"
  10. "os/signal"
  11. "regexp"
  12. "strconv"
  13. "strings"
  14. "syscall"
  15. "time"
  16. "github.com/GeertJohan/go.rice"
  17. "github.com/andreimarcu/linx-server/backends"
  18. "github.com/andreimarcu/linx-server/backends/localfs"
  19. "github.com/andreimarcu/linx-server/backends/s3"
  20. "github.com/flosch/pongo2"
  21. "github.com/vharitonsky/iniflags"
  22. "github.com/zenazn/goji/graceful"
  23. "github.com/zenazn/goji/web"
  24. "github.com/zenazn/goji/web/middleware"
  25. )
  26. type headerList []string
  27. func (h *headerList) String() string {
  28. return strings.Join(*h, ",")
  29. }
  30. func (h *headerList) Set(value string) error {
  31. *h = append(*h, value)
  32. return nil
  33. }
  34. var Config struct {
  35. bind string
  36. filesDir string
  37. metaDir string
  38. siteName string
  39. siteURL string
  40. sitePath string
  41. selifPath string
  42. certFile string
  43. keyFile string
  44. contentSecurityPolicy string
  45. fileContentSecurityPolicy string
  46. referrerPolicy string
  47. fileReferrerPolicy string
  48. xFrameOptions string
  49. maxSize int64
  50. maxExpiry uint64
  51. realIp bool
  52. noLogs bool
  53. allowHotlink bool
  54. fastcgi bool
  55. remoteUploads bool
  56. authFile string
  57. remoteAuthFile string
  58. addHeaders headerList
  59. noDirectAgents bool
  60. s3Endpoint string
  61. s3Region string
  62. s3Bucket string
  63. s3ForcePathStyle bool
  64. forceRandomFilename bool
  65. }
  66. var Templates = make(map[string]*pongo2.Template)
  67. var TemplateSet *pongo2.TemplateSet
  68. var staticBox *rice.Box
  69. var timeStarted time.Time
  70. var timeStartedStr string
  71. var remoteAuthKeys []string
  72. var metaStorageBackend backends.MetaStorageBackend
  73. var storageBackend backends.StorageBackend
  74. func setup() *web.Mux {
  75. mux := web.New()
  76. // middleware
  77. mux.Use(middleware.RequestID)
  78. if Config.realIp {
  79. mux.Use(middleware.RealIP)
  80. }
  81. if !Config.noLogs {
  82. mux.Use(middleware.Logger)
  83. }
  84. mux.Use(middleware.Recoverer)
  85. mux.Use(middleware.AutomaticOptions)
  86. mux.Use(ContentSecurityPolicy(CSPOptions{
  87. policy: Config.contentSecurityPolicy,
  88. referrerPolicy: Config.referrerPolicy,
  89. frame: Config.xFrameOptions,
  90. }))
  91. mux.Use(AddHeaders(Config.addHeaders))
  92. if Config.authFile != "" {
  93. mux.Use(UploadAuth(AuthOptions{
  94. AuthFile: Config.authFile,
  95. UnauthMethods: []string{"GET", "HEAD", "OPTIONS", "TRACE"},
  96. }))
  97. }
  98. // make directories if needed
  99. err := os.MkdirAll(Config.filesDir, 0755)
  100. if err != nil {
  101. log.Fatal("Could not create files directory:", err)
  102. }
  103. err = os.MkdirAll(Config.metaDir, 0700)
  104. if err != nil {
  105. log.Fatal("Could not create metadata directory:", err)
  106. }
  107. if Config.siteURL != "" {
  108. // ensure siteURL ends wth '/'
  109. if lastChar := Config.siteURL[len(Config.siteURL)-1:]; lastChar != "/" {
  110. Config.siteURL = Config.siteURL + "/"
  111. }
  112. parsedUrl, err := url.Parse(Config.siteURL)
  113. if err != nil {
  114. log.Fatal("Could not parse siteurl:", err)
  115. }
  116. Config.sitePath = parsedUrl.Path
  117. } else {
  118. Config.sitePath = "/"
  119. }
  120. Config.selifPath = strings.TrimLeft(Config.selifPath, "/")
  121. if lastChar := Config.selifPath[len(Config.selifPath)-1:]; lastChar != "/" {
  122. Config.selifPath = Config.selifPath + "/"
  123. }
  124. if Config.s3Bucket != "" {
  125. storageBackend = s3.NewS3Backend(Config.s3Bucket, Config.s3Region, Config.s3Endpoint, Config.s3ForcePathStyle)
  126. } else {
  127. storageBackend = localfs.NewLocalfsBackend(Config.metaDir, Config.filesDir)
  128. }
  129. // Template setup
  130. p2l, err := NewPongo2TemplatesLoader()
  131. if err != nil {
  132. log.Fatal("Error: could not load templates", err)
  133. }
  134. TemplateSet := pongo2.NewSet("templates", p2l)
  135. err = populateTemplatesMap(TemplateSet, Templates)
  136. if err != nil {
  137. log.Fatal("Error: could not load templates", err)
  138. }
  139. staticBox = rice.MustFindBox("static")
  140. timeStarted = time.Now()
  141. timeStartedStr = strconv.FormatInt(timeStarted.Unix(), 10)
  142. // Routing setup
  143. nameRe := regexp.MustCompile("^" + Config.sitePath + `(?P<name>[a-z0-9-\.]+)$`)
  144. selifRe := regexp.MustCompile("^" + Config.sitePath + Config.selifPath + `(?P<name>[a-z0-9-\.]+)$`)
  145. selifIndexRe := regexp.MustCompile("^" + Config.sitePath + Config.selifPath + `$`)
  146. torrentRe := regexp.MustCompile("^" + Config.sitePath + `(?P<name>[a-z0-9-\.]+)/torrent$`)
  147. if Config.authFile == "" {
  148. mux.Get(Config.sitePath, indexHandler)
  149. mux.Get(Config.sitePath+"paste/", pasteHandler)
  150. } else {
  151. mux.Get(Config.sitePath, http.RedirectHandler(Config.sitePath+"API", 303))
  152. mux.Get(Config.sitePath+"paste/", http.RedirectHandler(Config.sitePath+"API/", 303))
  153. }
  154. mux.Get(Config.sitePath+"paste", http.RedirectHandler(Config.sitePath+"paste/", 301))
  155. mux.Get(Config.sitePath+"API/", apiDocHandler)
  156. mux.Get(Config.sitePath+"API", http.RedirectHandler(Config.sitePath+"API/", 301))
  157. if Config.remoteUploads {
  158. mux.Get(Config.sitePath+"upload", uploadRemote)
  159. mux.Get(Config.sitePath+"upload/", uploadRemote)
  160. if Config.remoteAuthFile != "" {
  161. remoteAuthKeys = readAuthKeys(Config.remoteAuthFile)
  162. }
  163. }
  164. mux.Post(Config.sitePath+"upload", uploadPostHandler)
  165. mux.Post(Config.sitePath+"upload/", uploadPostHandler)
  166. mux.Put(Config.sitePath+"upload", uploadPutHandler)
  167. mux.Put(Config.sitePath+"upload/", uploadPutHandler)
  168. mux.Put(Config.sitePath+"upload/:name", uploadPutHandler)
  169. mux.Delete(Config.sitePath+":name", deleteHandler)
  170. mux.Get(Config.sitePath+"static/*", staticHandler)
  171. mux.Get(Config.sitePath+"favicon.ico", staticHandler)
  172. mux.Get(Config.sitePath+"robots.txt", staticHandler)
  173. mux.Get(nameRe, fileDisplayHandler)
  174. mux.Get(selifRe, fileServeHandler)
  175. mux.Get(selifIndexRe, unauthorizedHandler)
  176. mux.Get(torrentRe, fileTorrentHandler)
  177. mux.NotFound(notFoundHandler)
  178. return mux
  179. }
  180. func main() {
  181. flag.StringVar(&Config.bind, "bind", "127.0.0.1:8080",
  182. "host to bind to (default: 127.0.0.1:8080)")
  183. flag.StringVar(&Config.filesDir, "filespath", "files/",
  184. "path to files directory")
  185. flag.StringVar(&Config.metaDir, "metapath", "meta/",
  186. "path to metadata directory")
  187. flag.BoolVar(&Config.noLogs, "nologs", false,
  188. "remove stdout output for each request")
  189. flag.BoolVar(&Config.allowHotlink, "allowhotlink", false,
  190. "Allow hotlinking of files")
  191. flag.StringVar(&Config.siteName, "sitename", "",
  192. "name of the site")
  193. flag.StringVar(&Config.siteURL, "siteurl", "",
  194. "site base url (including trailing slash)")
  195. flag.StringVar(&Config.selifPath, "selifpath", "selif",
  196. "path relative to site base url where files are accessed directly")
  197. flag.Int64Var(&Config.maxSize, "maxsize", 4*1024*1024*1024,
  198. "maximum upload file size in bytes (default 4GB)")
  199. flag.Uint64Var(&Config.maxExpiry, "maxexpiry", 0,
  200. "maximum expiration time in seconds (default is 0, which is no expiry)")
  201. flag.StringVar(&Config.certFile, "certfile", "",
  202. "path to ssl certificate (for https)")
  203. flag.StringVar(&Config.keyFile, "keyfile", "",
  204. "path to ssl key (for https)")
  205. flag.BoolVar(&Config.realIp, "realip", false,
  206. "use X-Real-IP/X-Forwarded-For headers as original host")
  207. flag.BoolVar(&Config.fastcgi, "fastcgi", false,
  208. "serve through fastcgi")
  209. flag.BoolVar(&Config.remoteUploads, "remoteuploads", false,
  210. "enable remote uploads")
  211. flag.StringVar(&Config.authFile, "authfile", "",
  212. "path to a file containing newline-separated scrypted auth keys")
  213. flag.StringVar(&Config.remoteAuthFile, "remoteauthfile", "",
  214. "path to a file containing newline-separated scrypted auth keys for remote uploads")
  215. flag.StringVar(&Config.contentSecurityPolicy, "contentsecuritypolicy",
  216. "default-src 'self'; img-src 'self' data:; style-src 'self' 'unsafe-inline'; frame-ancestors 'self';",
  217. "value of default Content-Security-Policy header")
  218. flag.StringVar(&Config.fileContentSecurityPolicy, "filecontentsecuritypolicy",
  219. "default-src 'none'; img-src 'self'; object-src 'self'; media-src 'self'; style-src 'self' 'unsafe-inline'; frame-ancestors 'self';",
  220. "value of Content-Security-Policy header for file access")
  221. flag.StringVar(&Config.referrerPolicy, "referrerpolicy",
  222. "same-origin",
  223. "value of default Referrer-Policy header")
  224. flag.StringVar(&Config.fileReferrerPolicy, "filereferrerpolicy",
  225. "same-origin",
  226. "value of Referrer-Policy header for file access")
  227. flag.StringVar(&Config.xFrameOptions, "xframeoptions", "SAMEORIGIN",
  228. "value of X-Frame-Options header")
  229. flag.Var(&Config.addHeaders, "addheader",
  230. "Add an arbitrary header to the response. This option can be used multiple times.")
  231. flag.BoolVar(&Config.noDirectAgents, "nodirectagents", false,
  232. "disable serving files directly for wget/curl user agents")
  233. flag.StringVar(&Config.s3Endpoint, "s3-endpoint", "",
  234. "S3 endpoint")
  235. flag.StringVar(&Config.s3Region, "s3-region", "",
  236. "S3 region")
  237. flag.StringVar(&Config.s3Bucket, "s3-bucket", "",
  238. "S3 bucket to use for files and metadata")
  239. flag.BoolVar(&Config.s3ForcePathStyle, "s3-force-path-style", false,
  240. "Force path-style addressing for S3 (e.g. https://s3.amazonaws.com/linx/example.txt)")
  241. flag.BoolVar(&Config.forceRandomFilename, "force-random-filename", false,
  242. "Force all uploads to use a random filename")
  243. iniflags.Parse()
  244. mux := setup()
  245. if Config.fastcgi {
  246. var listener net.Listener
  247. var err error
  248. if Config.bind[0] == '/' {
  249. // UNIX path
  250. listener, err = net.ListenUnix("unix", &net.UnixAddr{Name: Config.bind, Net: "unix"})
  251. cleanup := func() {
  252. log.Print("Removing FastCGI socket")
  253. os.Remove(Config.bind)
  254. }
  255. defer cleanup()
  256. sigs := make(chan os.Signal, 1)
  257. signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
  258. go func() {
  259. sig := <-sigs
  260. log.Print("Signal: ", sig)
  261. cleanup()
  262. os.Exit(0)
  263. }()
  264. } else {
  265. listener, err = net.Listen("tcp", Config.bind)
  266. }
  267. if err != nil {
  268. log.Fatal("Could not bind: ", err)
  269. }
  270. log.Printf("Serving over fastcgi, bound on %s", Config.bind)
  271. fcgi.Serve(listener, mux)
  272. } else if Config.certFile != "" {
  273. log.Printf("Serving over https, bound on %s", Config.bind)
  274. err := graceful.ListenAndServeTLS(Config.bind, Config.certFile, Config.keyFile, mux)
  275. if err != nil {
  276. log.Fatal(err)
  277. }
  278. } else {
  279. log.Printf("Serving over http, bound on %s", Config.bind)
  280. err := graceful.ListenAndServe(Config.bind, mux)
  281. if err != nil {
  282. log.Fatal(err)
  283. }
  284. }
  285. }