mirror of
https://github.com/dutchcoders/transfer.sh.git
synced 2024-11-26 14:10:18 +01:00
fc844ac341
* Upgrade aws-sdk-go to v2 `aws-sdk-go-v2` is the newer SDK version, replacing the one being used at the moment by the project. This change maintains full compatibility with existing flags and configurations, and only replaces the underlying library. * Simplify and isolate AWS config logic
550 lines
14 KiB
Go
550 lines
14 KiB
Go
package cmd
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"strings"
|
|
|
|
"github.com/dutchcoders/transfer.sh/server/storage"
|
|
|
|
"github.com/dutchcoders/transfer.sh/server"
|
|
"github.com/fatih/color"
|
|
"github.com/urfave/cli/v2"
|
|
"google.golang.org/api/googleapi"
|
|
)
|
|
|
|
// Version is inject at build time
|
|
var Version = "0.0.0"
|
|
var helpTemplate = `NAME:
|
|
{{.Name}} - {{.Usage}}
|
|
|
|
DESCRIPTION:
|
|
{{.Description}}
|
|
|
|
USAGE:
|
|
{{.Name}} {{if .Flags}}[flags] {{end}}command{{if .Flags}}{{end}} [arguments...]
|
|
|
|
COMMANDS:
|
|
{{range .Commands}}{{join .Names ", "}}{{ "\t" }}{{.Usage}}
|
|
{{end}}{{if .Flags}}
|
|
FLAGS:
|
|
{{range .Flags}}{{.}}
|
|
{{end}}{{end}}
|
|
VERSION:
|
|
` + Version +
|
|
`{{ "\n"}}`
|
|
|
|
var globalFlags = []cli.Flag{
|
|
&cli.StringFlag{
|
|
Name: "listener",
|
|
Usage: "127.0.0.1:8080",
|
|
Value: "127.0.0.1:8080",
|
|
EnvVars: []string{"LISTENER"},
|
|
},
|
|
// redirect to https?
|
|
// hostnames
|
|
&cli.StringFlag{
|
|
Name: "profile-listener",
|
|
Usage: "127.0.0.1:6060",
|
|
Value: "",
|
|
EnvVars: []string{"PROFILE_LISTENER"},
|
|
},
|
|
&cli.BoolFlag{
|
|
Name: "force-https",
|
|
Usage: "",
|
|
EnvVars: []string{"FORCE_HTTPS"},
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "tls-listener",
|
|
Usage: "127.0.0.1:8443",
|
|
Value: "",
|
|
EnvVars: []string{"TLS_LISTENER"},
|
|
},
|
|
&cli.BoolFlag{
|
|
Name: "tls-listener-only",
|
|
Usage: "",
|
|
EnvVars: []string{"TLS_LISTENER_ONLY"},
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "tls-cert-file",
|
|
Value: "",
|
|
EnvVars: []string{"TLS_CERT_FILE"},
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "tls-private-key",
|
|
Value: "",
|
|
EnvVars: []string{"TLS_PRIVATE_KEY"},
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "temp-path",
|
|
Usage: "path to temp files",
|
|
Value: os.TempDir(),
|
|
EnvVars: []string{"TEMP_PATH"},
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "web-path",
|
|
Usage: "path to static web files",
|
|
Value: "",
|
|
EnvVars: []string{"WEB_PATH"},
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "proxy-path",
|
|
Usage: "path prefix when service is run behind a proxy",
|
|
Value: "",
|
|
EnvVars: []string{"PROXY_PATH"},
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "proxy-port",
|
|
Usage: "port of the proxy when the service is run behind a proxy",
|
|
Value: "",
|
|
EnvVars: []string{"PROXY_PORT"},
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "email-contact",
|
|
Usage: "email address to link in Contact Us (front end)",
|
|
Value: "",
|
|
EnvVars: []string{"EMAIL_CONTACT"},
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "ga-key",
|
|
Usage: "key for google analytics (front end)",
|
|
Value: "",
|
|
EnvVars: []string{"GA_KEY"},
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "uservoice-key",
|
|
Usage: "key for user voice (front end)",
|
|
Value: "",
|
|
EnvVars: []string{"USERVOICE_KEY"},
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "provider",
|
|
Usage: "s3|gdrive|local",
|
|
Value: "",
|
|
EnvVars: []string{"PROVIDER"},
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "s3-endpoint",
|
|
Usage: "",
|
|
Value: "",
|
|
EnvVars: []string{"S3_ENDPOINT"},
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "s3-region",
|
|
Usage: "",
|
|
Value: "eu-west-1",
|
|
EnvVars: []string{"S3_REGION"},
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "aws-access-key",
|
|
Usage: "",
|
|
Value: "",
|
|
EnvVars: []string{"AWS_ACCESS_KEY"},
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "aws-secret-key",
|
|
Usage: "",
|
|
Value: "",
|
|
EnvVars: []string{"AWS_SECRET_KEY"},
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "bucket",
|
|
Usage: "",
|
|
Value: "",
|
|
EnvVars: []string{"BUCKET"},
|
|
},
|
|
&cli.BoolFlag{
|
|
Name: "s3-no-multipart",
|
|
Usage: "Disables S3 Multipart Puts",
|
|
EnvVars: []string{"S3_NO_MULTIPART"},
|
|
},
|
|
&cli.BoolFlag{
|
|
Name: "s3-path-style",
|
|
Usage: "Forces path style URLs, required for Minio.",
|
|
EnvVars: []string{"S3_PATH_STYLE"},
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "gdrive-client-json-filepath",
|
|
Usage: "",
|
|
Value: "",
|
|
EnvVars: []string{"GDRIVE_CLIENT_JSON_FILEPATH"},
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "gdrive-local-config-path",
|
|
Usage: "",
|
|
Value: "",
|
|
EnvVars: []string{"GDRIVE_LOCAL_CONFIG_PATH"},
|
|
},
|
|
&cli.IntFlag{
|
|
Name: "gdrive-chunk-size",
|
|
Usage: "",
|
|
Value: googleapi.DefaultUploadChunkSize / 1024 / 1024,
|
|
EnvVars: []string{"GDRIVE_CHUNK_SIZE"},
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "storj-access",
|
|
Usage: "Access for the project",
|
|
Value: "",
|
|
EnvVars: []string{"STORJ_ACCESS"},
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "storj-bucket",
|
|
Usage: "Bucket to use within the project",
|
|
Value: "",
|
|
EnvVars: []string{"STORJ_BUCKET"},
|
|
},
|
|
&cli.IntFlag{
|
|
Name: "rate-limit",
|
|
Usage: "requests per minute",
|
|
Value: 0,
|
|
EnvVars: []string{"RATE_LIMIT"},
|
|
},
|
|
&cli.IntFlag{
|
|
Name: "purge-days",
|
|
Usage: "number of days after uploads are purged automatically",
|
|
Value: 0,
|
|
EnvVars: []string{"PURGE_DAYS"},
|
|
},
|
|
&cli.IntFlag{
|
|
Name: "purge-interval",
|
|
Usage: "interval in hours to run the automatic purge for",
|
|
Value: 0,
|
|
EnvVars: []string{"PURGE_INTERVAL"},
|
|
},
|
|
&cli.Int64Flag{
|
|
Name: "max-upload-size",
|
|
Usage: "max limit for upload, in kilobytes",
|
|
Value: 0,
|
|
EnvVars: []string{"MAX_UPLOAD_SIZE"},
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "lets-encrypt-hosts",
|
|
Usage: "host1, host2",
|
|
Value: "",
|
|
EnvVars: []string{"HOSTS"},
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "log",
|
|
Usage: "/var/log/transfersh.log",
|
|
Value: "",
|
|
EnvVars: []string{"LOG"},
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "basedir",
|
|
Usage: "path to storage",
|
|
Value: "",
|
|
EnvVars: []string{"BASEDIR"},
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "clamav-host",
|
|
Usage: "clamav-host",
|
|
Value: "",
|
|
EnvVars: []string{"CLAMAV_HOST"},
|
|
},
|
|
&cli.BoolFlag{
|
|
Name: "perform-clamav-prescan",
|
|
Usage: "perform-clamav-prescan",
|
|
EnvVars: []string{"PERFORM_CLAMAV_PRESCAN"},
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "virustotal-key",
|
|
Usage: "virustotal-key",
|
|
Value: "",
|
|
EnvVars: []string{"VIRUSTOTAL_KEY"},
|
|
},
|
|
&cli.BoolFlag{
|
|
Name: "profiler",
|
|
Usage: "enable profiling",
|
|
EnvVars: []string{"PROFILER"},
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "http-auth-user",
|
|
Usage: "user for http basic auth",
|
|
Value: "",
|
|
EnvVars: []string{"HTTP_AUTH_USER"},
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "http-auth-pass",
|
|
Usage: "pass for http basic auth",
|
|
Value: "",
|
|
EnvVars: []string{"HTTP_AUTH_PASS"},
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "http-auth-htpasswd",
|
|
Usage: "htpasswd file http basic auth",
|
|
Value: "",
|
|
EnvVars: []string{"HTTP_AUTH_HTPASSWD"},
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "http-auth-ip-whitelist",
|
|
Usage: "comma separated list of ips allowed to upload without being challenged an http auth",
|
|
Value: "",
|
|
EnvVars: []string{"HTTP_AUTH_IP_WHITELIST"},
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "ip-whitelist",
|
|
Usage: "comma separated list of ips allowed to connect to the service",
|
|
Value: "",
|
|
EnvVars: []string{"IP_WHITELIST"},
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "ip-blacklist",
|
|
Usage: "comma separated list of ips not allowed to connect to the service",
|
|
Value: "",
|
|
EnvVars: []string{"IP_BLACKLIST"},
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "cors-domains",
|
|
Usage: "comma separated list of domains allowed for CORS requests",
|
|
Value: "",
|
|
EnvVars: []string{"CORS_DOMAINS"},
|
|
},
|
|
&cli.IntFlag{
|
|
Name: "random-token-length",
|
|
Usage: "",
|
|
Value: 10,
|
|
EnvVars: []string{"RANDOM_TOKEN_LENGTH"},
|
|
},
|
|
}
|
|
|
|
// Cmd wraps cli.app
|
|
type Cmd struct {
|
|
*cli.App
|
|
}
|
|
|
|
func versionCommand(_ *cli.Context) error {
|
|
fmt.Println(color.YellowString("transfer.sh %s: Easy file sharing from the command line", Version))
|
|
return nil
|
|
}
|
|
|
|
// New is the factory for transfer.sh
|
|
func New() *Cmd {
|
|
logger := log.New(os.Stdout, "[transfer.sh]", log.LstdFlags)
|
|
|
|
app := cli.NewApp()
|
|
app.Name = "transfer.sh"
|
|
app.Authors = []*cli.Author{}
|
|
app.Usage = "transfer.sh"
|
|
app.Description = `Easy file sharing from the command line`
|
|
app.Version = Version
|
|
app.Flags = globalFlags
|
|
app.CustomAppHelpTemplate = helpTemplate
|
|
app.Commands = []*cli.Command{
|
|
{
|
|
Name: "version",
|
|
Action: versionCommand,
|
|
},
|
|
}
|
|
|
|
app.Before = func(c *cli.Context) error {
|
|
return nil
|
|
}
|
|
|
|
app.Action = func(c *cli.Context) error {
|
|
var options []server.OptionFn
|
|
if v := c.String("listener"); v != "" {
|
|
options = append(options, server.Listener(v))
|
|
}
|
|
|
|
if v := c.String("cors-domains"); v != "" {
|
|
options = append(options, server.CorsDomains(v))
|
|
}
|
|
|
|
if v := c.String("tls-listener"); v == "" {
|
|
} else if c.Bool("tls-listener-only") {
|
|
options = append(options, server.TLSListener(v, true))
|
|
} else {
|
|
options = append(options, server.TLSListener(v, false))
|
|
}
|
|
|
|
if v := c.String("profile-listener"); v != "" {
|
|
options = append(options, server.ProfileListener(v))
|
|
}
|
|
|
|
if v := c.String("web-path"); v != "" {
|
|
options = append(options, server.WebPath(v))
|
|
}
|
|
|
|
if v := c.String("proxy-path"); v != "" {
|
|
options = append(options, server.ProxyPath(v))
|
|
}
|
|
|
|
if v := c.String("proxy-port"); v != "" {
|
|
options = append(options, server.ProxyPort(v))
|
|
}
|
|
|
|
if v := c.String("email-contact"); v != "" {
|
|
options = append(options, server.EmailContact(v))
|
|
}
|
|
|
|
if v := c.String("ga-key"); v != "" {
|
|
options = append(options, server.GoogleAnalytics(v))
|
|
}
|
|
|
|
if v := c.String("uservoice-key"); v != "" {
|
|
options = append(options, server.UserVoice(v))
|
|
}
|
|
|
|
if v := c.String("temp-path"); v != "" {
|
|
options = append(options, server.TempPath(v))
|
|
}
|
|
|
|
if v := c.String("log"); v != "" {
|
|
options = append(options, server.LogFile(logger, v))
|
|
} else {
|
|
options = append(options, server.Logger(logger))
|
|
}
|
|
|
|
if v := c.String("lets-encrypt-hosts"); v != "" {
|
|
options = append(options, server.UseLetsEncrypt(strings.Split(v, ",")))
|
|
}
|
|
|
|
if v := c.String("virustotal-key"); v != "" {
|
|
options = append(options, server.VirustotalKey(v))
|
|
}
|
|
|
|
if v := c.String("clamav-host"); v != "" {
|
|
options = append(options, server.ClamavHost(v))
|
|
}
|
|
|
|
if v := c.Bool("perform-clamav-prescan"); v {
|
|
if c.String("clamav-host") == "" {
|
|
return errors.New("clamav-host not set")
|
|
}
|
|
|
|
options = append(options, server.PerformClamavPrescan(v))
|
|
}
|
|
|
|
if v := c.Int64("max-upload-size"); v > 0 {
|
|
options = append(options, server.MaxUploadSize(v))
|
|
}
|
|
|
|
if v := c.Int("rate-limit"); v > 0 {
|
|
options = append(options, server.RateLimit(v))
|
|
}
|
|
|
|
v := c.Int("random-token-length")
|
|
options = append(options, server.RandomTokenLength(v))
|
|
|
|
purgeDays := c.Int("purge-days")
|
|
purgeInterval := c.Int("purge-interval")
|
|
if purgeDays > 0 && purgeInterval > 0 {
|
|
options = append(options, server.Purge(purgeDays, purgeInterval))
|
|
}
|
|
|
|
if cert := c.String("tls-cert-file"); cert == "" {
|
|
} else if pk := c.String("tls-private-key"); pk == "" {
|
|
} else {
|
|
options = append(options, server.TLSConfig(cert, pk))
|
|
}
|
|
|
|
if c.Bool("profiler") {
|
|
options = append(options, server.EnableProfiler())
|
|
}
|
|
|
|
if c.Bool("force-https") {
|
|
options = append(options, server.ForceHTTPS())
|
|
}
|
|
|
|
if httpAuthUser := c.String("http-auth-user"); httpAuthUser == "" {
|
|
} else if httpAuthPass := c.String("http-auth-pass"); httpAuthPass == "" {
|
|
} else {
|
|
options = append(options, server.HTTPAuthCredentials(httpAuthUser, httpAuthPass))
|
|
}
|
|
|
|
if httpAuthHtpasswd := c.String("http-auth-htpasswd"); httpAuthHtpasswd != "" {
|
|
options = append(options, server.HTTPAuthHtpasswd(httpAuthHtpasswd))
|
|
}
|
|
|
|
if httpAuthIPWhitelist := c.String("http-auth-ip-whitelist"); httpAuthIPWhitelist != "" {
|
|
ipFilterOptions := server.IPFilterOptions{}
|
|
ipFilterOptions.AllowedIPs = strings.Split(httpAuthIPWhitelist, ",")
|
|
ipFilterOptions.BlockByDefault = false
|
|
options = append(options, server.HTTPAUTHFilterOptions(ipFilterOptions))
|
|
}
|
|
|
|
applyIPFilter := false
|
|
ipFilterOptions := server.IPFilterOptions{}
|
|
if ipWhitelist := c.String("ip-whitelist"); ipWhitelist != "" {
|
|
applyIPFilter = true
|
|
ipFilterOptions.AllowedIPs = strings.Split(ipWhitelist, ",")
|
|
ipFilterOptions.BlockByDefault = true
|
|
}
|
|
|
|
if ipBlacklist := c.String("ip-blacklist"); ipBlacklist != "" {
|
|
applyIPFilter = true
|
|
ipFilterOptions.BlockedIPs = strings.Split(ipBlacklist, ",")
|
|
}
|
|
|
|
if applyIPFilter {
|
|
options = append(options, server.FilterOptions(ipFilterOptions))
|
|
}
|
|
|
|
switch provider := c.String("provider"); provider {
|
|
case "s3":
|
|
if accessKey := c.String("aws-access-key"); accessKey == "" {
|
|
return errors.New("access-key not set.")
|
|
} else if secretKey := c.String("aws-secret-key"); secretKey == "" {
|
|
return errors.New("secret-key not set.")
|
|
} else if bucket := c.String("bucket"); bucket == "" {
|
|
return errors.New("bucket not set.")
|
|
} 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 {
|
|
return err
|
|
} else {
|
|
options = append(options, server.UseStorage(store))
|
|
}
|
|
case "gdrive":
|
|
chunkSize := c.Int("gdrive-chunk-size") * 1024 * 1024
|
|
|
|
if clientJSONFilepath := c.String("gdrive-client-json-filepath"); clientJSONFilepath == "" {
|
|
return errors.New("gdrive-client-json-filepath not set.")
|
|
} else if localConfigPath := c.String("gdrive-local-config-path"); localConfigPath == "" {
|
|
return errors.New("gdrive-local-config-path not set.")
|
|
} else if basedir := c.String("basedir"); basedir == "" {
|
|
return errors.New("basedir not set.")
|
|
} else if store, err := storage.NewGDriveStorage(c.Context, clientJSONFilepath, localConfigPath, basedir, chunkSize, logger); err != nil {
|
|
return err
|
|
} else {
|
|
options = append(options, server.UseStorage(store))
|
|
}
|
|
case "storj":
|
|
if access := c.String("storj-access"); access == "" {
|
|
return errors.New("storj-access not set.")
|
|
} else if bucket := c.String("storj-bucket"); bucket == "" {
|
|
return errors.New("storj-bucket not set.")
|
|
} else if store, err := storage.NewStorjStorage(c.Context, access, bucket, purgeDays, logger); err != nil {
|
|
return err
|
|
} else {
|
|
options = append(options, server.UseStorage(store))
|
|
}
|
|
case "local":
|
|
if v := c.String("basedir"); v == "" {
|
|
return errors.New("basedir not set.")
|
|
} else if store, err := storage.NewLocalStorage(v, logger); err != nil {
|
|
return err
|
|
} else {
|
|
options = append(options, server.UseStorage(store))
|
|
}
|
|
default:
|
|
return errors.New("Provider not set or invalid.")
|
|
}
|
|
|
|
srvr, err := server.New(
|
|
options...,
|
|
)
|
|
|
|
if err != nil {
|
|
logger.Println(color.RedString("Error starting server: %s", err.Error()))
|
|
return err
|
|
}
|
|
|
|
srvr.Run()
|
|
return nil
|
|
}
|
|
|
|
return &Cmd{
|
|
App: app,
|
|
}
|
|
}
|