cmd.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550
  1. package cmd
  2. import (
  3. "errors"
  4. "fmt"
  5. "log"
  6. "os"
  7. "strings"
  8. "github.com/dutchcoders/transfer.sh/server/storage"
  9. "github.com/dutchcoders/transfer.sh/server"
  10. "github.com/fatih/color"
  11. "github.com/urfave/cli/v2"
  12. "google.golang.org/api/googleapi"
  13. )
  14. // Version is inject at build time
  15. var Version = "0.0.0"
  16. var helpTemplate = `NAME:
  17. {{.Name}} - {{.Usage}}
  18. DESCRIPTION:
  19. {{.Description}}
  20. USAGE:
  21. {{.Name}} {{if .Flags}}[flags] {{end}}command{{if .Flags}}{{end}} [arguments...]
  22. COMMANDS:
  23. {{range .Commands}}{{join .Names ", "}}{{ "\t" }}{{.Usage}}
  24. {{end}}{{if .Flags}}
  25. FLAGS:
  26. {{range .Flags}}{{.}}
  27. {{end}}{{end}}
  28. VERSION:
  29. ` + Version +
  30. `{{ "\n"}}`
  31. var globalFlags = []cli.Flag{
  32. &cli.StringFlag{
  33. Name: "listener",
  34. Usage: "127.0.0.1:8080",
  35. Value: "127.0.0.1:8080",
  36. EnvVars: []string{"LISTENER"},
  37. },
  38. // redirect to https?
  39. // hostnames
  40. &cli.StringFlag{
  41. Name: "profile-listener",
  42. Usage: "127.0.0.1:6060",
  43. Value: "",
  44. EnvVars: []string{"PROFILE_LISTENER"},
  45. },
  46. &cli.BoolFlag{
  47. Name: "force-https",
  48. Usage: "",
  49. EnvVars: []string{"FORCE_HTTPS"},
  50. },
  51. &cli.StringFlag{
  52. Name: "tls-listener",
  53. Usage: "127.0.0.1:8443",
  54. Value: "",
  55. EnvVars: []string{"TLS_LISTENER"},
  56. },
  57. &cli.BoolFlag{
  58. Name: "tls-listener-only",
  59. Usage: "",
  60. EnvVars: []string{"TLS_LISTENER_ONLY"},
  61. },
  62. &cli.StringFlag{
  63. Name: "tls-cert-file",
  64. Value: "",
  65. EnvVars: []string{"TLS_CERT_FILE"},
  66. },
  67. &cli.StringFlag{
  68. Name: "tls-private-key",
  69. Value: "",
  70. EnvVars: []string{"TLS_PRIVATE_KEY"},
  71. },
  72. &cli.StringFlag{
  73. Name: "temp-path",
  74. Usage: "path to temp files",
  75. Value: os.TempDir(),
  76. EnvVars: []string{"TEMP_PATH"},
  77. },
  78. &cli.StringFlag{
  79. Name: "web-path",
  80. Usage: "path to static web files",
  81. Value: "",
  82. EnvVars: []string{"WEB_PATH"},
  83. },
  84. &cli.StringFlag{
  85. Name: "proxy-path",
  86. Usage: "path prefix when service is run behind a proxy",
  87. Value: "",
  88. EnvVars: []string{"PROXY_PATH"},
  89. },
  90. &cli.StringFlag{
  91. Name: "proxy-port",
  92. Usage: "port of the proxy when the service is run behind a proxy",
  93. Value: "",
  94. EnvVars: []string{"PROXY_PORT"},
  95. },
  96. &cli.StringFlag{
  97. Name: "email-contact",
  98. Usage: "email address to link in Contact Us (front end)",
  99. Value: "",
  100. EnvVars: []string{"EMAIL_CONTACT"},
  101. },
  102. &cli.StringFlag{
  103. Name: "ga-key",
  104. Usage: "key for google analytics (front end)",
  105. Value: "",
  106. EnvVars: []string{"GA_KEY"},
  107. },
  108. &cli.StringFlag{
  109. Name: "uservoice-key",
  110. Usage: "key for user voice (front end)",
  111. Value: "",
  112. EnvVars: []string{"USERVOICE_KEY"},
  113. },
  114. &cli.StringFlag{
  115. Name: "provider",
  116. Usage: "s3|gdrive|local",
  117. Value: "",
  118. EnvVars: []string{"PROVIDER"},
  119. },
  120. &cli.StringFlag{
  121. Name: "s3-endpoint",
  122. Usage: "",
  123. Value: "",
  124. EnvVars: []string{"S3_ENDPOINT"},
  125. },
  126. &cli.StringFlag{
  127. Name: "s3-region",
  128. Usage: "",
  129. Value: "eu-west-1",
  130. EnvVars: []string{"S3_REGION"},
  131. },
  132. &cli.StringFlag{
  133. Name: "aws-access-key",
  134. Usage: "",
  135. Value: "",
  136. EnvVars: []string{"AWS_ACCESS_KEY"},
  137. },
  138. &cli.StringFlag{
  139. Name: "aws-secret-key",
  140. Usage: "",
  141. Value: "",
  142. EnvVars: []string{"AWS_SECRET_KEY"},
  143. },
  144. &cli.StringFlag{
  145. Name: "bucket",
  146. Usage: "",
  147. Value: "",
  148. EnvVars: []string{"BUCKET"},
  149. },
  150. &cli.BoolFlag{
  151. Name: "s3-no-multipart",
  152. Usage: "Disables S3 Multipart Puts",
  153. EnvVars: []string{"S3_NO_MULTIPART"},
  154. },
  155. &cli.BoolFlag{
  156. Name: "s3-path-style",
  157. Usage: "Forces path style URLs, required for Minio.",
  158. EnvVars: []string{"S3_PATH_STYLE"},
  159. },
  160. &cli.StringFlag{
  161. Name: "gdrive-client-json-filepath",
  162. Usage: "",
  163. Value: "",
  164. EnvVars: []string{"GDRIVE_CLIENT_JSON_FILEPATH"},
  165. },
  166. &cli.StringFlag{
  167. Name: "gdrive-local-config-path",
  168. Usage: "",
  169. Value: "",
  170. EnvVars: []string{"GDRIVE_LOCAL_CONFIG_PATH"},
  171. },
  172. &cli.IntFlag{
  173. Name: "gdrive-chunk-size",
  174. Usage: "",
  175. Value: googleapi.DefaultUploadChunkSize / 1024 / 1024,
  176. EnvVars: []string{"GDRIVE_CHUNK_SIZE"},
  177. },
  178. &cli.StringFlag{
  179. Name: "storj-access",
  180. Usage: "Access for the project",
  181. Value: "",
  182. EnvVars: []string{"STORJ_ACCESS"},
  183. },
  184. &cli.StringFlag{
  185. Name: "storj-bucket",
  186. Usage: "Bucket to use within the project",
  187. Value: "",
  188. EnvVars: []string{"STORJ_BUCKET"},
  189. },
  190. &cli.IntFlag{
  191. Name: "rate-limit",
  192. Usage: "requests per minute",
  193. Value: 0,
  194. EnvVars: []string{"RATE_LIMIT"},
  195. },
  196. &cli.IntFlag{
  197. Name: "purge-days",
  198. Usage: "number of days after uploads are purged automatically",
  199. Value: 0,
  200. EnvVars: []string{"PURGE_DAYS"},
  201. },
  202. &cli.IntFlag{
  203. Name: "purge-interval",
  204. Usage: "interval in hours to run the automatic purge for",
  205. Value: 0,
  206. EnvVars: []string{"PURGE_INTERVAL"},
  207. },
  208. &cli.Int64Flag{
  209. Name: "max-upload-size",
  210. Usage: "max limit for upload, in kilobytes",
  211. Value: 0,
  212. EnvVars: []string{"MAX_UPLOAD_SIZE"},
  213. },
  214. &cli.StringFlag{
  215. Name: "lets-encrypt-hosts",
  216. Usage: "host1, host2",
  217. Value: "",
  218. EnvVars: []string{"HOSTS"},
  219. },
  220. &cli.StringFlag{
  221. Name: "log",
  222. Usage: "/var/log/transfersh.log",
  223. Value: "",
  224. EnvVars: []string{"LOG"},
  225. },
  226. &cli.StringFlag{
  227. Name: "basedir",
  228. Usage: "path to storage",
  229. Value: "",
  230. EnvVars: []string{"BASEDIR"},
  231. },
  232. &cli.StringFlag{
  233. Name: "clamav-host",
  234. Usage: "clamav-host",
  235. Value: "",
  236. EnvVars: []string{"CLAMAV_HOST"},
  237. },
  238. &cli.BoolFlag{
  239. Name: "perform-clamav-prescan",
  240. Usage: "perform-clamav-prescan",
  241. EnvVars: []string{"PERFORM_CLAMAV_PRESCAN"},
  242. },
  243. &cli.StringFlag{
  244. Name: "virustotal-key",
  245. Usage: "virustotal-key",
  246. Value: "",
  247. EnvVars: []string{"VIRUSTOTAL_KEY"},
  248. },
  249. &cli.BoolFlag{
  250. Name: "profiler",
  251. Usage: "enable profiling",
  252. EnvVars: []string{"PROFILER"},
  253. },
  254. &cli.StringFlag{
  255. Name: "http-auth-user",
  256. Usage: "user for http basic auth",
  257. Value: "",
  258. EnvVars: []string{"HTTP_AUTH_USER"},
  259. },
  260. &cli.StringFlag{
  261. Name: "http-auth-pass",
  262. Usage: "pass for http basic auth",
  263. Value: "",
  264. EnvVars: []string{"HTTP_AUTH_PASS"},
  265. },
  266. &cli.StringFlag{
  267. Name: "http-auth-htpasswd",
  268. Usage: "htpasswd file http basic auth",
  269. Value: "",
  270. EnvVars: []string{"HTTP_AUTH_HTPASSWD"},
  271. },
  272. &cli.StringFlag{
  273. Name: "http-auth-ip-whitelist",
  274. Usage: "comma separated list of ips allowed to upload without being challenged an http auth",
  275. Value: "",
  276. EnvVars: []string{"HTTP_AUTH_IP_WHITELIST"},
  277. },
  278. &cli.StringFlag{
  279. Name: "ip-whitelist",
  280. Usage: "comma separated list of ips allowed to connect to the service",
  281. Value: "",
  282. EnvVars: []string{"IP_WHITELIST"},
  283. },
  284. &cli.StringFlag{
  285. Name: "ip-blacklist",
  286. Usage: "comma separated list of ips not allowed to connect to the service",
  287. Value: "",
  288. EnvVars: []string{"IP_BLACKLIST"},
  289. },
  290. &cli.StringFlag{
  291. Name: "cors-domains",
  292. Usage: "comma separated list of domains allowed for CORS requests",
  293. Value: "",
  294. EnvVars: []string{"CORS_DOMAINS"},
  295. },
  296. &cli.IntFlag{
  297. Name: "random-token-length",
  298. Usage: "",
  299. Value: 10,
  300. EnvVars: []string{"RANDOM_TOKEN_LENGTH"},
  301. },
  302. }
  303. // Cmd wraps cli.app
  304. type Cmd struct {
  305. *cli.App
  306. }
  307. func versionCommand(_ *cli.Context) error {
  308. fmt.Println(color.YellowString("transfer.sh %s: Easy file sharing from the command line", Version))
  309. return nil
  310. }
  311. // New is the factory for transfer.sh
  312. func New() *Cmd {
  313. logger := log.New(os.Stdout, "[transfer.sh]", log.LstdFlags)
  314. app := cli.NewApp()
  315. app.Name = "transfer.sh"
  316. app.Authors = []*cli.Author{}
  317. app.Usage = "transfer.sh"
  318. app.Description = `Easy file sharing from the command line`
  319. app.Version = Version
  320. app.Flags = globalFlags
  321. app.CustomAppHelpTemplate = helpTemplate
  322. app.Commands = []*cli.Command{
  323. {
  324. Name: "version",
  325. Action: versionCommand,
  326. },
  327. }
  328. app.Before = func(c *cli.Context) error {
  329. return nil
  330. }
  331. app.Action = func(c *cli.Context) error {
  332. var options []server.OptionFn
  333. if v := c.String("listener"); v != "" {
  334. options = append(options, server.Listener(v))
  335. }
  336. if v := c.String("cors-domains"); v != "" {
  337. options = append(options, server.CorsDomains(v))
  338. }
  339. if v := c.String("tls-listener"); v == "" {
  340. } else if c.Bool("tls-listener-only") {
  341. options = append(options, server.TLSListener(v, true))
  342. } else {
  343. options = append(options, server.TLSListener(v, false))
  344. }
  345. if v := c.String("profile-listener"); v != "" {
  346. options = append(options, server.ProfileListener(v))
  347. }
  348. if v := c.String("web-path"); v != "" {
  349. options = append(options, server.WebPath(v))
  350. }
  351. if v := c.String("proxy-path"); v != "" {
  352. options = append(options, server.ProxyPath(v))
  353. }
  354. if v := c.String("proxy-port"); v != "" {
  355. options = append(options, server.ProxyPort(v))
  356. }
  357. if v := c.String("email-contact"); v != "" {
  358. options = append(options, server.EmailContact(v))
  359. }
  360. if v := c.String("ga-key"); v != "" {
  361. options = append(options, server.GoogleAnalytics(v))
  362. }
  363. if v := c.String("uservoice-key"); v != "" {
  364. options = append(options, server.UserVoice(v))
  365. }
  366. if v := c.String("temp-path"); v != "" {
  367. options = append(options, server.TempPath(v))
  368. }
  369. if v := c.String("log"); v != "" {
  370. options = append(options, server.LogFile(logger, v))
  371. } else {
  372. options = append(options, server.Logger(logger))
  373. }
  374. if v := c.String("lets-encrypt-hosts"); v != "" {
  375. options = append(options, server.UseLetsEncrypt(strings.Split(v, ",")))
  376. }
  377. if v := c.String("virustotal-key"); v != "" {
  378. options = append(options, server.VirustotalKey(v))
  379. }
  380. if v := c.String("clamav-host"); v != "" {
  381. options = append(options, server.ClamavHost(v))
  382. }
  383. if v := c.Bool("perform-clamav-prescan"); v {
  384. if c.String("clamav-host") == "" {
  385. return errors.New("clamav-host not set")
  386. }
  387. options = append(options, server.PerformClamavPrescan(v))
  388. }
  389. if v := c.Int64("max-upload-size"); v > 0 {
  390. options = append(options, server.MaxUploadSize(v))
  391. }
  392. if v := c.Int("rate-limit"); v > 0 {
  393. options = append(options, server.RateLimit(v))
  394. }
  395. v := c.Int("random-token-length")
  396. options = append(options, server.RandomTokenLength(v))
  397. purgeDays := c.Int("purge-days")
  398. purgeInterval := c.Int("purge-interval")
  399. if purgeDays > 0 && purgeInterval > 0 {
  400. options = append(options, server.Purge(purgeDays, purgeInterval))
  401. }
  402. if cert := c.String("tls-cert-file"); cert == "" {
  403. } else if pk := c.String("tls-private-key"); pk == "" {
  404. } else {
  405. options = append(options, server.TLSConfig(cert, pk))
  406. }
  407. if c.Bool("profiler") {
  408. options = append(options, server.EnableProfiler())
  409. }
  410. if c.Bool("force-https") {
  411. options = append(options, server.ForceHTTPS())
  412. }
  413. if httpAuthUser := c.String("http-auth-user"); httpAuthUser == "" {
  414. } else if httpAuthPass := c.String("http-auth-pass"); httpAuthPass == "" {
  415. } else {
  416. options = append(options, server.HTTPAuthCredentials(httpAuthUser, httpAuthPass))
  417. }
  418. if httpAuthHtpasswd := c.String("http-auth-htpasswd"); httpAuthHtpasswd != "" {
  419. options = append(options, server.HTTPAuthHtpasswd(httpAuthHtpasswd))
  420. }
  421. if httpAuthIPWhitelist := c.String("http-auth-ip-whitelist"); httpAuthIPWhitelist != "" {
  422. ipFilterOptions := server.IPFilterOptions{}
  423. ipFilterOptions.AllowedIPs = strings.Split(httpAuthIPWhitelist, ",")
  424. ipFilterOptions.BlockByDefault = false
  425. options = append(options, server.HTTPAUTHFilterOptions(ipFilterOptions))
  426. }
  427. applyIPFilter := false
  428. ipFilterOptions := server.IPFilterOptions{}
  429. if ipWhitelist := c.String("ip-whitelist"); ipWhitelist != "" {
  430. applyIPFilter = true
  431. ipFilterOptions.AllowedIPs = strings.Split(ipWhitelist, ",")
  432. ipFilterOptions.BlockByDefault = true
  433. }
  434. if ipBlacklist := c.String("ip-blacklist"); ipBlacklist != "" {
  435. applyIPFilter = true
  436. ipFilterOptions.BlockedIPs = strings.Split(ipBlacklist, ",")
  437. }
  438. if applyIPFilter {
  439. options = append(options, server.FilterOptions(ipFilterOptions))
  440. }
  441. switch provider := c.String("provider"); provider {
  442. case "s3":
  443. if accessKey := c.String("aws-access-key"); accessKey == "" {
  444. return errors.New("access-key not set.")
  445. } else if secretKey := c.String("aws-secret-key"); secretKey == "" {
  446. return errors.New("secret-key not set.")
  447. } else if bucket := c.String("bucket"); bucket == "" {
  448. return errors.New("bucket not set.")
  449. } else if store, err := storage.NewS3Storage(c.Context, accessKey, secretKey, bucket, purgeDays, c.String("s3-region"), c.String("s3-endpoint"), c.Bool("s3-no-multipart"), c.Bool("s3-path-style"), logger); err != nil {
  450. return err
  451. } else {
  452. options = append(options, server.UseStorage(store))
  453. }
  454. case "gdrive":
  455. chunkSize := c.Int("gdrive-chunk-size") * 1024 * 1024
  456. if clientJSONFilepath := c.String("gdrive-client-json-filepath"); clientJSONFilepath == "" {
  457. return errors.New("gdrive-client-json-filepath not set.")
  458. } else if localConfigPath := c.String("gdrive-local-config-path"); localConfigPath == "" {
  459. return errors.New("gdrive-local-config-path not set.")
  460. } else if basedir := c.String("basedir"); basedir == "" {
  461. return errors.New("basedir not set.")
  462. } else if store, err := storage.NewGDriveStorage(c.Context, clientJSONFilepath, localConfigPath, basedir, chunkSize, logger); err != nil {
  463. return err
  464. } else {
  465. options = append(options, server.UseStorage(store))
  466. }
  467. case "storj":
  468. if access := c.String("storj-access"); access == "" {
  469. return errors.New("storj-access not set.")
  470. } else if bucket := c.String("storj-bucket"); bucket == "" {
  471. return errors.New("storj-bucket not set.")
  472. } else if store, err := storage.NewStorjStorage(c.Context, access, bucket, purgeDays, logger); err != nil {
  473. return err
  474. } else {
  475. options = append(options, server.UseStorage(store))
  476. }
  477. case "local":
  478. if v := c.String("basedir"); v == "" {
  479. return errors.New("basedir not set.")
  480. } else if store, err := storage.NewLocalStorage(v, logger); err != nil {
  481. return err
  482. } else {
  483. options = append(options, server.UseStorage(store))
  484. }
  485. default:
  486. return errors.New("Provider not set or invalid.")
  487. }
  488. srvr, err := server.New(
  489. options...,
  490. )
  491. if err != nil {
  492. logger.Println(color.RedString("Error starting server: %s", err.Error()))
  493. return err
  494. }
  495. srvr.Run()
  496. return nil
  497. }
  498. return &Cmd{
  499. App: app,
  500. }
  501. }