From ad53429cf18bd4e75124aa7fa137a0a70e66f011 Mon Sep 17 00:00:00 2001 From: Nicola Murino Date: Sat, 23 May 2020 11:58:05 +0200 Subject: [PATCH] add support for build tag to allow to disable some features The following build tags are available: - "nogcs", disable Google Cloud Storage backend - "nos3", disable S3 Compabible Object Storage backends - "nobolt", disable Bolt data provider - "nomysql", disable MySQL data provider - "nopgsql", disable PostgreSQL data provider - "nosqlite", disable SQLite data provider - "noportable", disable portable mode --- cmd/portable.go | 5 ++ cmd/portable_disabled.go | 9 +++ cmd/root.go | 2 +- dataprovider/bolt.go | 6 ++ dataprovider/bolt_disabled.go | 17 +++++ dataprovider/mysql.go | 10 +++ dataprovider/mysql_disabled.go | 17 +++++ dataprovider/pgsql.go | 10 +++ dataprovider/pgsql_disabled.go | 17 +++++ dataprovider/sqlite.go | 9 +++ dataprovider/sqlite_disabled.go | 17 +++++ docker/sftpgo/alpine/Dockerfile | 9 +-- docker/sftpgo/alpine/README.md | 5 +- docker/sftpgo/debian/Dockerfile | 8 +- docker/sftpgo/debian/README.md | 13 +++- docs/build-from-source.md | 24 ++++-- httpd/schema/openapi.yaml | 7 +- main.go | 8 +- service/service.go | 112 ---------------------------- service/service_portable.go | 126 ++++++++++++++++++++++++++++++++ utils/version.go | 29 ++++++-- vfs/gcsfs.go | 23 ++---- vfs/gcsfs_disabled.go | 18 +++++ vfs/s3fs.go | 33 ++------- vfs/s3fs_disabled.go | 18 +++++ vfs/vfs.go | 43 +++++++++++ 26 files changed, 406 insertions(+), 189 deletions(-) create mode 100644 cmd/portable_disabled.go create mode 100644 dataprovider/bolt_disabled.go create mode 100644 dataprovider/mysql_disabled.go create mode 100644 dataprovider/pgsql_disabled.go create mode 100644 dataprovider/sqlite_disabled.go create mode 100644 service/service_portable.go create mode 100644 vfs/gcsfs_disabled.go create mode 100644 vfs/s3fs_disabled.go diff --git a/cmd/portable.go b/cmd/portable.go index 92bd8f93..bfc634c1 100644 --- a/cmd/portable.go +++ b/cmd/portable.go @@ -1,3 +1,5 @@ +// +build !noportable + package cmd import ( @@ -14,6 +16,7 @@ import ( "github.com/drakkan/sftpgo/dataprovider" "github.com/drakkan/sftpgo/service" "github.com/drakkan/sftpgo/sftpd" + "github.com/drakkan/sftpgo/utils" "github.com/drakkan/sftpgo/vfs" ) @@ -138,6 +141,8 @@ Please take a look at the usage below to customize the serving parameters`, ) func init() { + utils.AddFeature("+portable") + portableCmd.Flags().StringVarP(&directoryToServe, "directory", "d", ".", "Path to the directory to serve. This can be an absolute path or a path relative to the current directory") portableCmd.Flags().IntVarP(&portableSFTPDPort, "sftpd-port", "s", 0, "0 means a random non privileged port") diff --git a/cmd/portable_disabled.go b/cmd/portable_disabled.go new file mode 100644 index 00000000..c374cae3 --- /dev/null +++ b/cmd/portable_disabled.go @@ -0,0 +1,9 @@ +// +build noportable + +package cmd + +import "github.com/drakkan/sftpgo/utils" + +func init() { + utils.AddFeature("-portable") +} diff --git a/cmd/root.go b/cmd/root.go index 7b9bd617..f1007eae 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -63,7 +63,7 @@ func init() { version := utils.GetAppVersion() rootCmd.Flags().BoolP("version", "v", false, "") rootCmd.Version = version.GetVersionAsString() - rootCmd.SetVersionTemplate(`{{printf "SFTPGo version: "}}{{printf "%s" .Version}} + rootCmd.SetVersionTemplate(`{{printf "SFTPGo "}}{{printf "%s" .Version}} `) } diff --git a/dataprovider/bolt.go b/dataprovider/bolt.go index d93cba28..8097c4aa 100644 --- a/dataprovider/bolt.go +++ b/dataprovider/bolt.go @@ -1,3 +1,5 @@ +// +build !nobolt + package dataprovider import ( @@ -52,6 +54,10 @@ type compatUserV2 struct { Status int `json:"status"` } +func init() { + utils.AddFeature("+bolt") +} + func initializeBoltProvider(basePath string) error { var err error logSender = fmt.Sprintf("dataprovider_%v", BoltDataProviderName) diff --git a/dataprovider/bolt_disabled.go b/dataprovider/bolt_disabled.go new file mode 100644 index 00000000..a84260e2 --- /dev/null +++ b/dataprovider/bolt_disabled.go @@ -0,0 +1,17 @@ +// +build nobolt + +package dataprovider + +import ( + "errors" + + "github.com/drakkan/sftpgo/utils" +) + +func init() { + utils.AddFeature("-bolt") +} + +func initializeBoltProvider(basePath string) error { + return errors.New("bolt disabled at build time") +} diff --git a/dataprovider/mysql.go b/dataprovider/mysql.go index b02d71bc..edfb2bf1 100644 --- a/dataprovider/mysql.go +++ b/dataprovider/mysql.go @@ -1,3 +1,5 @@ +// +build !nomysql + package dataprovider import ( @@ -6,7 +8,11 @@ import ( "strings" "time" + // we import go-sql-driver/mysql here to be able to disable MySQL support using a build tag + _ "github.com/go-sql-driver/mysql" + "github.com/drakkan/sftpgo/logger" + "github.com/drakkan/sftpgo/utils" ) const ( @@ -28,6 +34,10 @@ type MySQLProvider struct { dbHandle *sql.DB } +func init() { + utils.AddFeature("+mysql") +} + func initializeMySQLProvider() error { var err error logSender = fmt.Sprintf("dataprovider_%v", MySQLDataProviderName) diff --git a/dataprovider/mysql_disabled.go b/dataprovider/mysql_disabled.go new file mode 100644 index 00000000..e004be9f --- /dev/null +++ b/dataprovider/mysql_disabled.go @@ -0,0 +1,17 @@ +// +build nomysql + +package dataprovider + +import ( + "errors" + + "github.com/drakkan/sftpgo/utils" +) + +func init() { + utils.AddFeature("-mysql") +} + +func initializeMySQLProvider() error { + return errors.New("MySQL disabled at build time") +} diff --git a/dataprovider/pgsql.go b/dataprovider/pgsql.go index 2793569a..b5ef445e 100644 --- a/dataprovider/pgsql.go +++ b/dataprovider/pgsql.go @@ -1,3 +1,5 @@ +// +build !nopgsql + package dataprovider import ( @@ -5,7 +7,11 @@ import ( "fmt" "strings" + // we import lib/pq here to be able to disable PostgreSQL support using a build tag + _ "github.com/lib/pq" + "github.com/drakkan/sftpgo/logger" + "github.com/drakkan/sftpgo/utils" ) const ( @@ -26,6 +32,10 @@ type PGSQLProvider struct { dbHandle *sql.DB } +func init() { + utils.AddFeature("+pgsql") +} + func initializePGSQLProvider() error { var err error logSender = fmt.Sprintf("dataprovider_%v", PGSQLDataProviderName) diff --git a/dataprovider/pgsql_disabled.go b/dataprovider/pgsql_disabled.go new file mode 100644 index 00000000..c9aeee32 --- /dev/null +++ b/dataprovider/pgsql_disabled.go @@ -0,0 +1,17 @@ +// +build nopgsql + +package dataprovider + +import ( + "errors" + + "github.com/drakkan/sftpgo/utils" +) + +func init() { + utils.AddFeature("-pgsql") +} + +func initializePGSQLProvider() error { + return errors.New("PostgreSQL disabled at build time") +} diff --git a/dataprovider/sqlite.go b/dataprovider/sqlite.go index 16f86904..2556e08d 100644 --- a/dataprovider/sqlite.go +++ b/dataprovider/sqlite.go @@ -1,3 +1,5 @@ +// +build !nosqlite + package dataprovider import ( @@ -6,6 +8,9 @@ import ( "path/filepath" "strings" + // we import go-sqlite3 here to be able to disable SQLite support using a build tag + _ "github.com/mattn/go-sqlite3" + "github.com/drakkan/sftpgo/logger" "github.com/drakkan/sftpgo/utils" ) @@ -41,6 +46,10 @@ type SQLiteProvider struct { dbHandle *sql.DB } +func init() { + utils.AddFeature("+sqlite") +} + func initializeSQLiteProvider(basePath string) error { var err error var connectionString string diff --git a/dataprovider/sqlite_disabled.go b/dataprovider/sqlite_disabled.go new file mode 100644 index 00000000..430f2a72 --- /dev/null +++ b/dataprovider/sqlite_disabled.go @@ -0,0 +1,17 @@ +// +build nosqlite + +package dataprovider + +import ( + "errors" + + "github.com/drakkan/sftpgo/utils" +) + +func init() { + utils.AddFeature("-sqlite") +} + +func initializeSQLiteProvider(basePath string) error { + return errors.New("SQLite disabled at build time") +} diff --git a/docker/sftpgo/alpine/Dockerfile b/docker/sftpgo/alpine/Dockerfile index 46952469..77a5cdd6 100644 --- a/docker/sftpgo/alpine/Dockerfile +++ b/docker/sftpgo/alpine/Dockerfile @@ -4,19 +4,18 @@ RUN apk add --no-cache git gcc g++ ca-certificates \ && go get -d github.com/drakkan/sftpgo WORKDIR /go/src/github.com/drakkan/sftpgo ARG TAG +ARG FEATURES # Use --build-arg TAG=LATEST for latest tag. Use e.g. --build-arg TAG=0.9.6 for a specific tag/commit. Otherwise HEAD (master) is built. RUN git checkout $(if [ "${TAG}" = LATEST ]; then echo `git rev-list --tags --max-count=1`; elif [ -n "${TAG}" ]; then echo "${TAG}"; else echo HEAD; fi) -RUN go build -i -ldflags "-s -w -X github.com/drakkan/sftpgo/utils.commit=`git describe --always --dirty` -X github.com/drakkan/sftpgo/utils.date=`date -u +%FT%TZ`" -o /go/bin/sftpgo +RUN go build -i $(if [ -n "${FEATURES}" ]; then echo "-tags ${FEATURES}"; fi) -ldflags "-s -w -X github.com/drakkan/sftpgo/utils.commit=`git describe --always --dirty` -X github.com/drakkan/sftpgo/utils.date=`date -u +%FT%TZ`" -o /go/bin/sftpgo FROM alpine:latest RUN apk add --no-cache ca-certificates su-exec \ && mkdir -p /data /etc/sftpgo /srv/sftpgo/config /srv/sftpgo/web /srv/sftpgo/backups -# ca-certificates is needed for Cloud Storage Support and to expose the REST API over HTTPS. -# If you install git then ca-certificates will be automatically installed as dependency. -# git, rsync and ca-certificates are optional, uncomment the next line to add support for them if needed. -#RUN apk add --no-cache git rsync ca-certificates +# git and rsync are optional, uncomment the next line to add support for them if needed. +#RUN apk add --no-cache git rsync COPY --from=builder /go/bin/sftpgo /bin/ COPY --from=builder /go/src/github.com/drakkan/sftpgo/sftpgo.json /etc/sftpgo/sftpgo.json diff --git a/docker/sftpgo/alpine/README.md b/docker/sftpgo/alpine/README.md index 603ff373..2a955ce9 100644 --- a/docker/sftpgo/alpine/README.md +++ b/docker/sftpgo/alpine/README.md @@ -13,7 +13,10 @@ sudo groupadd -g 1003 sftpgrp && \ # Edit sftpgo.json as you need -# Get and build SFTPGo image (add --build-arg TAG=LATEST to build the latest tag or e.g. TAG=0.9.6 for a specific tag/commit). +# Get and build SFTPGo image. +# Add --build-arg TAG=LATEST to build the latest tag or e.g. TAG=0.9.6 for a specific tag/commit. +# Add --build-arg FEATURES= to disable some feature. +# Please take a look at the [build from source](./../../../docs/build-from-source.md) documentation for the complete list of the features that can be disabled. git clone https://github.com/drakkan/sftpgo.git && \ cd sftpgo && \ sudo docker build -t sftpgo docker/sftpgo/alpine/ diff --git a/docker/sftpgo/debian/Dockerfile b/docker/sftpgo/debian/Dockerfile index 5aac420f..5e6ef93e 100644 --- a/docker/sftpgo/debian/Dockerfile +++ b/docker/sftpgo/debian/Dockerfile @@ -4,16 +4,18 @@ LABEL maintainer="nicola.murino@gmail.com" RUN go get -d github.com/drakkan/sftpgo WORKDIR /go/src/github.com/drakkan/sftpgo ARG TAG +ARG FEATURES # Use --build-arg TAG=LATEST for latest tag. Use e.g. --build-arg TAG=0.9.6 for a specific tag/commit. Otherwise HEAD (master) is built. RUN git checkout $(if [ "${TAG}" = LATEST ]; then echo `git rev-list --tags --max-count=1`; elif [ -n "${TAG}" ]; then echo "${TAG}"; else echo HEAD; fi) -RUN go build -i -ldflags "-s -w -X github.com/drakkan/sftpgo/utils.commit=`git describe --always --dirty` -X github.com/drakkan/sftpgo/utils.date=`date -u +%FT%TZ`" -o sftpgo +RUN go build -i $(if [ -n "${FEATURES}" ]; then echo "-tags ${FEATURES}"; fi) -ldflags "-s -w -X github.com/drakkan/sftpgo/utils.commit=`git describe --always --dirty` -X github.com/drakkan/sftpgo/utils.date=`date -u +%FT%TZ`" -o sftpgo # now define the run environment FROM debian:latest # ca-certificates is needed for Cloud Storage Support and to expose the REST API over HTTPS. -# If you install git then ca-certificates will be automatically installed as dependency. -# git, rsync and ca-certificates are optional, uncomment the next line to add support for them if needed. +RUN apt-get update && apt-get install -y ca-certificates + +# git and rsync are optional, uncomment the next line to add support for them if needed. #RUN apt-get update && apt-get install -y git rsync ca-certificates ARG BASE_DIR=/app diff --git a/docker/sftpgo/debian/README.md b/docker/sftpgo/debian/README.md index 65c0325a..7ea154b6 100644 --- a/docker/sftpgo/debian/README.md +++ b/docker/sftpgo/debian/README.md @@ -8,13 +8,22 @@ You can build the container image using `docker build`, for example: docker build -t="drakkan/sftpgo" . ``` -This will build master of github.com/drakkan/sftpgo. To build the latest tag you can add `--build-arg TAG=LATEST` -and to build a specific tag/commit you can use for example `TAG=0.9.6`, like this: +This will build master of github.com/drakkan/sftpgo. + +To build the latest tag you can add `--build-arg TAG=LATEST` and to build a specific tag/commit you can use for example `TAG=0.9.6`, like this: ```bash docker build -t="drakkan/sftpgo" --build-arg TAG=0.9.6 . ``` +To disable some features you can add `--build-arg FEATURES=`. For example you can disable SQLite support like this: + +```bash +docker build -t="drakkan/sftpgo" --build-arg FEATURES=nosqlite . +``` + +Please take a look at the [build from source](./../../../docs/build-from-source.md) documentation for the complete list of the features that can be disabled. + Now create the required folders on the host system, for example: ```bash diff --git a/docs/build-from-source.md b/docs/build-from-source.md index ad26d97c..6e2a46d7 100644 --- a/docs/build-from-source.md +++ b/docs/build-from-source.md @@ -8,13 +8,23 @@ go get -u github.com/drakkan/sftpgo Make sure [Git](https://git-scm.com/downloads) is installed on your machine and in your system's `PATH`. -SFTPGo depends on [go-sqlite3](https://github.com/mattn/go-sqlite3) which is a CGO package and so it requires a `C` compiler at build time. +The following build tags are available to disable some features: + +- `nogcs`, disable Google Cloud Storage backend +- `nos3`, disable S3 Compabible Object Storage backends +- `nobolt`, disable Bolt data provider +- `nomysql`, disable MySQL data provider +- `nopgsql`, disable PostgreSQL data provider +- `nosqlite`, disable SQLite data provider +- `noportable`, disable portable mode + +If no build tag is specified all the features will be included. + +The optional [SQLite driver](https://github.com/mattn/go-sqlite3 "go-sqlite3") is a `CGO` package and so it requires a `C` compiler at build time. On Linux and macOS, a compiler is easy to install or already installed. On Windows, you need to download [MinGW-w64](https://sourceforge.net/projects/mingw-w64/files/) and build SFTPGo from its command prompt. The compiler is a build time only dependency. It is not required at runtime. -If you don't need SQLite, you can also get/build SFTPGo setting the environment variable `GCO_ENABLED` to 0. This way, SQLite support will be disabled and PostgreSQL, MySQL, bbolt and memory data providers will keep working. In this way, you don't need a `C` compiler for building. - Version info, such as git commit and build date, can be embedded setting the following string variables at build time: - `github.com/drakkan/sftpgo/utils.commit` @@ -23,12 +33,12 @@ Version info, such as git commit and build date, can be embedded setting the fol For example, you can build using the following command: ```bash -go build -i -ldflags "-s -w -X github.com/drakkan/sftpgo/utils.commit=`git describe --always --dirty` -X github.com/drakkan/sftpgo/utils.date=`date -u +%FT%TZ`" -o sftpgo +go build -i -tags nogcs,nos3,nosqlite -ldflags "-s -w -X github.com/drakkan/sftpgo/utils.commit=`git describe --always --dirty` -X github.com/drakkan/sftpgo/utils.date=`date -u +%FT%TZ`" -o sftpgo ``` -You should get a version that includes git commit and build date like this one: +You should get a version that includes git commit, build date and available features like this one: ```bash -$ sftpgo -v -SFTPGo version: 0.9.0-dev-90607d4-dirty-2019-08-08T19:28:36Z +$ ./sftpgo -v +SFTPGo 0.9.6-dev-15298b0-dirty-2020-05-22T21:25:51Z -gcs -s3 +bolt +mysql +pgsql -sqlite +portable ``` \ No newline at end of file diff --git a/httpd/schema/openapi.yaml b/httpd/schema/openapi.yaml index dcc2f7c4..eb695799 100644 --- a/httpd/schema/openapi.yaml +++ b/httpd/schema/openapi.yaml @@ -2,7 +2,7 @@ openapi: 3.0.1 info: title: SFTPGo description: 'SFTPGo REST API' - version: 1.8.5 + version: 1.8.6 servers: - url: /api/v1 @@ -1278,6 +1278,11 @@ components: type: string commit_hash: type: string + features: + type: array + items: + type: string + description: Features for the current build. Available features are "portable", "bolt", "mysql", "sqlite", "pgsql", "s3", "gcs". If a feature is available it has a "+" prefix, otherwise a "-" prefix securitySchemes: BasicAuth: type: http diff --git a/main.go b/main.go index 2144df49..444edef3 100644 --- a/main.go +++ b/main.go @@ -3,13 +3,7 @@ // https://github.com/drakkan/sftpgo/blob/master/README.md package main // import "github.com/drakkan/sftpgo" -import ( - _ "github.com/go-sql-driver/mysql" - _ "github.com/lib/pq" - _ "github.com/mattn/go-sqlite3" - - "github.com/drakkan/sftpgo/cmd" -) +import "github.com/drakkan/sftpgo/cmd" func main() { cmd.Execute() diff --git a/service/service.go b/service/service.go index 923ecf6b..ccc770f4 100644 --- a/service/service.go +++ b/service/service.go @@ -2,16 +2,8 @@ package service import ( - "fmt" - "math/rand" - "os" - "os/signal" "path/filepath" - "strings" - "syscall" - "time" - "github.com/grandcat/zeroconf" "github.com/rs/zerolog" "github.com/drakkan/sftpgo/config" @@ -141,107 +133,3 @@ func (s *Service) Stop() { close(s.Shutdown) logger.Debug(logSender, "", "Service stopped") } - -// StartPortableMode starts the service in portable mode -func (s *Service) StartPortableMode(sftpdPort int, enabledSSHCommands []string, advertiseService, advertiseCredentials bool) error { - if s.PortableMode != 1 { - return fmt.Errorf("service is not configured for portable mode") - } - var err error - rand.Seed(time.Now().UnixNano()) - if len(s.PortableUser.Username) == 0 { - s.PortableUser.Username = "user" - } - if len(s.PortableUser.PublicKeys) == 0 && len(s.PortableUser.Password) == 0 { - var b strings.Builder - for i := 0; i < 8; i++ { - b.WriteRune(chars[rand.Intn(len(chars))]) - } - s.PortableUser.Password = b.String() - } - dataProviderConf := config.GetProviderConf() - dataProviderConf.Driver = dataprovider.MemoryDataProviderName - dataProviderConf.Name = "" - dataProviderConf.CredentialsPath = filepath.Join(os.TempDir(), "credentials") - config.SetProviderConf(dataProviderConf) - httpdConf := config.GetHTTPDConfig() - httpdConf.BindPort = 0 - config.SetHTTPDConfig(httpdConf) - sftpdConf := config.GetSFTPDConfig() - sftpdConf.MaxAuthTries = 12 - if sftpdPort > 0 { - sftpdConf.BindPort = sftpdPort - } else { - // dynamic ports starts from 49152 - sftpdConf.BindPort = 49152 + rand.Intn(15000) - } - if utils.IsStringInSlice("*", enabledSSHCommands) { - sftpdConf.EnabledSSHCommands = sftpd.GetSupportedSSHCommands() - } else { - sftpdConf.EnabledSSHCommands = enabledSSHCommands - } - config.SetSFTPDConfig(sftpdConf) - - err = s.Start() - if err != nil { - return err - } - var mDNSService *zeroconf.Server - if advertiseService { - version := utils.GetAppVersion() - meta := []string{ - fmt.Sprintf("version=%v", version.GetVersionAsString()), - } - if advertiseCredentials { - logger.InfoToConsole("Advertising credentials via multicast DNS") - meta = append(meta, fmt.Sprintf("user=%v", s.PortableUser.Username)) - if len(s.PortableUser.Password) > 0 { - meta = append(meta, fmt.Sprintf("password=%v", s.PortableUser.Password)) - } else { - logger.InfoToConsole("Unable to advertise key based credentials via multicast DNS, we don't have the private key") - } - } - mDNSService, err = zeroconf.Register( - fmt.Sprintf("SFTPGo portable %v", sftpdConf.BindPort), // service instance name - "_sftp-ssh._tcp", // service type and protocol - "local.", // service domain - sftpdConf.BindPort, // service port - meta, // service metadata - nil, // register on all network interfaces - ) - if err != nil { - mDNSService = nil - logger.WarnToConsole("Unable to advertise SFTP service via multicast DNS: %v", err) - } else { - logger.InfoToConsole("SFTP service advertised via multicast DNS") - } - } - sig := make(chan os.Signal, 1) - signal.Notify(sig, os.Interrupt, syscall.SIGTERM) - go func() { - <-sig - if mDNSService != nil { - logger.InfoToConsole("unregistering multicast DNS service") - mDNSService.Shutdown() - } - s.Stop() - }() - - logger.InfoToConsole("Portable mode ready, SFTP port: %v, user: %#v, password: %#v, public keys: %v, directory: %#v, "+ - "permissions: %+v, enabled ssh commands: %v file extensions filters: %+v", sftpdConf.BindPort, s.PortableUser.Username, - s.PortableUser.Password, s.PortableUser.PublicKeys, s.getPortableDirToServe(), s.PortableUser.Permissions, - sftpdConf.EnabledSSHCommands, s.PortableUser.Filters.FileExtensions) - return nil -} - -func (s *Service) getPortableDirToServe() string { - var dirToServe string - if s.PortableUser.FsConfig.Provider == 1 { - dirToServe = s.PortableUser.FsConfig.S3Config.KeyPrefix - } else if s.PortableUser.FsConfig.Provider == 2 { - dirToServe = s.PortableUser.FsConfig.GCSConfig.KeyPrefix - } else { - dirToServe = s.PortableUser.HomeDir - } - return dirToServe -} diff --git a/service/service_portable.go b/service/service_portable.go new file mode 100644 index 00000000..b05b50f6 --- /dev/null +++ b/service/service_portable.go @@ -0,0 +1,126 @@ +// +build !noportable + +package service + +import ( + "fmt" + "math/rand" + "os" + "os/signal" + "path/filepath" + "strings" + "syscall" + "time" + + "github.com/grandcat/zeroconf" + + "github.com/drakkan/sftpgo/config" + "github.com/drakkan/sftpgo/dataprovider" + "github.com/drakkan/sftpgo/logger" + "github.com/drakkan/sftpgo/sftpd" + "github.com/drakkan/sftpgo/utils" +) + +// StartPortableMode starts the service in portable mode +func (s *Service) StartPortableMode(sftpdPort int, enabledSSHCommands []string, advertiseService, advertiseCredentials bool) error { + if s.PortableMode != 1 { + return fmt.Errorf("service is not configured for portable mode") + } + var err error + rand.Seed(time.Now().UnixNano()) + if len(s.PortableUser.Username) == 0 { + s.PortableUser.Username = "user" + } + if len(s.PortableUser.PublicKeys) == 0 && len(s.PortableUser.Password) == 0 { + var b strings.Builder + for i := 0; i < 8; i++ { + b.WriteRune(chars[rand.Intn(len(chars))]) + } + s.PortableUser.Password = b.String() + } + dataProviderConf := config.GetProviderConf() + dataProviderConf.Driver = dataprovider.MemoryDataProviderName + dataProviderConf.Name = "" + dataProviderConf.CredentialsPath = filepath.Join(os.TempDir(), "credentials") + config.SetProviderConf(dataProviderConf) + httpdConf := config.GetHTTPDConfig() + httpdConf.BindPort = 0 + config.SetHTTPDConfig(httpdConf) + sftpdConf := config.GetSFTPDConfig() + sftpdConf.MaxAuthTries = 12 + if sftpdPort > 0 { + sftpdConf.BindPort = sftpdPort + } else { + // dynamic ports starts from 49152 + sftpdConf.BindPort = 49152 + rand.Intn(15000) + } + if utils.IsStringInSlice("*", enabledSSHCommands) { + sftpdConf.EnabledSSHCommands = sftpd.GetSupportedSSHCommands() + } else { + sftpdConf.EnabledSSHCommands = enabledSSHCommands + } + config.SetSFTPDConfig(sftpdConf) + + err = s.Start() + if err != nil { + return err + } + var mDNSService *zeroconf.Server + if advertiseService { + version := utils.GetAppVersion() + meta := []string{ + fmt.Sprintf("version=%v", version.GetVersionAsString()), + } + if advertiseCredentials { + logger.InfoToConsole("Advertising credentials via multicast DNS") + meta = append(meta, fmt.Sprintf("user=%v", s.PortableUser.Username)) + if len(s.PortableUser.Password) > 0 { + meta = append(meta, fmt.Sprintf("password=%v", s.PortableUser.Password)) + } else { + logger.InfoToConsole("Unable to advertise key based credentials via multicast DNS, we don't have the private key") + } + } + mDNSService, err = zeroconf.Register( + fmt.Sprintf("SFTPGo portable %v", sftpdConf.BindPort), // service instance name + "_sftp-ssh._tcp", // service type and protocol + "local.", // service domain + sftpdConf.BindPort, // service port + meta, // service metadata + nil, // register on all network interfaces + ) + if err != nil { + mDNSService = nil + logger.WarnToConsole("Unable to advertise SFTP service via multicast DNS: %v", err) + } else { + logger.InfoToConsole("SFTP service advertised via multicast DNS") + } + } + sig := make(chan os.Signal, 1) + signal.Notify(sig, os.Interrupt, syscall.SIGTERM) + go func() { + <-sig + if mDNSService != nil { + logger.InfoToConsole("unregistering multicast DNS service") + mDNSService.Shutdown() + } + s.Stop() + }() + + logger.InfoToConsole("Portable mode ready, SFTP port: %v, user: %#v, password: %#v, public keys: %v, directory: %#v, "+ + "permissions: %+v, enabled ssh commands: %v file extensions filters: %+v", sftpdConf.BindPort, s.PortableUser.Username, + s.PortableUser.Password, s.PortableUser.PublicKeys, s.getPortableDirToServe(), s.PortableUser.Permissions, + sftpdConf.EnabledSSHCommands, s.PortableUser.Filters.FileExtensions) + return nil +} + +func (s *Service) getPortableDirToServe() string { + var dirToServe string + if s.PortableUser.FsConfig.Provider == 1 { + dirToServe = s.PortableUser.FsConfig.S3Config.KeyPrefix + } else if s.PortableUser.FsConfig.Provider == 2 { + dirToServe = s.PortableUser.FsConfig.GCSConfig.KeyPrefix + } else { + dirToServe = s.PortableUser.HomeDir + } + return dirToServe +} diff --git a/utils/version.go b/utils/version.go index 57f385bf..e475ccb4 100644 --- a/utils/version.go +++ b/utils/version.go @@ -1,5 +1,7 @@ package utils +import "strings" + const version = "0.9.6-dev" var ( @@ -10,21 +12,34 @@ var ( // VersionInfo defines version details type VersionInfo struct { - Version string `json:"version"` - BuildDate string `json:"build_date"` - CommitHash string `json:"commit_hash"` + Version string `json:"version"` + BuildDate string `json:"build_date"` + CommitHash string `json:"commit_hash"` + Features []string `json:"features"` } // GetVersionAsString returns the string representation of the VersionInfo struct func (v *VersionInfo) GetVersionAsString() string { - versionString := v.Version + var sb strings.Builder + sb.WriteString(v.Version) if len(v.CommitHash) > 0 { - versionString += "-" + v.CommitHash + sb.WriteString("-") + sb.WriteString(v.CommitHash) } if len(v.BuildDate) > 0 { - versionString += "-" + v.BuildDate + sb.WriteString("-") + sb.WriteString(v.BuildDate) } - return versionString + if len(v.Features) > 0 { + sb.WriteString(" ") + sb.WriteString(strings.Join(v.Features, " ")) + } + return sb.String() +} + +// AddFeature adds a feature description +func AddFeature(feature string) { + versionInfo.Features = append(versionInfo.Features, feature) } func init() { diff --git a/vfs/gcsfs.go b/vfs/gcsfs.go index cd4d22ad..dcb7a4d3 100644 --- a/vfs/gcsfs.go +++ b/vfs/gcsfs.go @@ -1,3 +1,5 @@ +// +build !nogcs + package vfs import ( @@ -19,6 +21,7 @@ import ( "github.com/drakkan/sftpgo/logger" "github.com/drakkan/sftpgo/metrics" + "github.com/drakkan/sftpgo/utils" ) var ( @@ -27,22 +30,6 @@ var ( gcsDefaultFieldsSelection = []string{"Name", "Size", "Deleted", "Updated"} ) -// GCSFsConfig defines the configuration for Google Cloud Storage based filesystem -type GCSFsConfig struct { - Bucket string `json:"bucket,omitempty"` - // KeyPrefix is similar to a chroot directory for local filesystem. - // If specified the SFTP user will only see objects that starts with - // this prefix and so you can restrict access to a specific virtual - // folder. The prefix, if not empty, must not start with "/" and must - // end with "/". - // If empty the whole bucket contents will be available - KeyPrefix string `json:"key_prefix,omitempty"` - CredentialFile string `json:"-"` - Credentials string `json:"credentials,omitempty"` - AutomaticCredentials int `json:"automatic_credentials,omitempty"` - StorageClass string `json:"storage_class,omitempty"` -} - // GCSFs is a Fs implementation for Google Cloud Storage. type GCSFs struct { connectionID string @@ -53,6 +40,10 @@ type GCSFs struct { ctxLongTimeout time.Duration } +func init() { + utils.AddFeature("+gcs") +} + // NewGCSFs returns an GCSFs object that allows to interact with Google Cloud Storage func NewGCSFs(connectionID, localTempDir string, config GCSFsConfig) (Fs, error) { var err error diff --git a/vfs/gcsfs_disabled.go b/vfs/gcsfs_disabled.go new file mode 100644 index 00000000..f1515e2b --- /dev/null +++ b/vfs/gcsfs_disabled.go @@ -0,0 +1,18 @@ +// +build nogcs + +package vfs + +import ( + "errors" + + "github.com/drakkan/sftpgo/utils" +) + +func init() { + utils.AddFeature("-gcs") +} + +// NewGCSFs returns an error, GCS is disabled +func NewGCSFs(connectionID, localTempDir string, config GCSFsConfig) (Fs, error) { + return nil, errors.New("Google Cloud Storage disabled at build time") +} diff --git a/vfs/s3fs.go b/vfs/s3fs.go index 63cf2bb1..be9467f3 100644 --- a/vfs/s3fs.go +++ b/vfs/s3fs.go @@ -1,3 +1,5 @@ +// +build !nos3 + package vfs import ( @@ -22,33 +24,6 @@ import ( "github.com/drakkan/sftpgo/utils" ) -// S3FsConfig defines the configuration for S3 based filesystem -type S3FsConfig struct { - Bucket string `json:"bucket,omitempty"` - // KeyPrefix is similar to a chroot directory for local filesystem. - // If specified the SFTP user will only see objects that starts with - // this prefix and so you can restrict access to a specific virtual - // folder. The prefix, if not empty, must not start with "/" and must - // end with "/". - // If empty the whole bucket contents will be available - KeyPrefix string `json:"key_prefix,omitempty"` - Region string `json:"region,omitempty"` - AccessKey string `json:"access_key,omitempty"` - AccessSecret string `json:"access_secret,omitempty"` - Endpoint string `json:"endpoint,omitempty"` - StorageClass string `json:"storage_class,omitempty"` - // The buffer size (in MB) to use for multipart uploads. The minimum allowed part size is 5MB, - // and if this value is set to zero, the default value (5MB) for the AWS SDK will be used. - // The minimum allowed value is 5. - // Please note that if the upload bandwidth between the SFTP client and SFTPGo is greater than - // the upload bandwidth between SFTPGo and S3 then the SFTP client have to wait for the upload - // of the last parts to S3 after it ends the file upload to SFTPGo, and it may time out. - // Keep this in mind if you customize these parameters. - UploadPartSize int64 `json:"upload_part_size,omitempty"` - // How many parts are uploaded in parallel - UploadConcurrency int `json:"upload_concurrency,omitempty"` -} - // S3Fs is a Fs implementation for Amazon S3 compatible object storage. type S3Fs struct { connectionID string @@ -59,6 +34,10 @@ type S3Fs struct { ctxLongTimeout time.Duration } +func init() { + utils.AddFeature("+s3") +} + // NewS3Fs returns an S3Fs object that allows to interact with an s3 compatible // object storage func NewS3Fs(connectionID, localTempDir string, config S3FsConfig) (Fs, error) { diff --git a/vfs/s3fs_disabled.go b/vfs/s3fs_disabled.go new file mode 100644 index 00000000..2e39d39b --- /dev/null +++ b/vfs/s3fs_disabled.go @@ -0,0 +1,18 @@ +// +build nos3 + +package vfs + +import ( + "errors" + + "github.com/drakkan/sftpgo/utils" +) + +func init() { + utils.AddFeature("-s3") +} + +// NewS3Fs returns an error, S3 is disabled +func NewS3Fs(connectionID, localTempDir string, config S3FsConfig) (Fs, error) { + return nil, errors.New("S3 disabled at build time") +} diff --git a/vfs/vfs.go b/vfs/vfs.go index 96a76b02..7432101c 100644 --- a/vfs/vfs.go +++ b/vfs/vfs.go @@ -44,6 +44,49 @@ type Fs interface { Join(elem ...string) string } +// S3FsConfig defines the configuration for S3 based filesystem +type S3FsConfig struct { + Bucket string `json:"bucket,omitempty"` + // KeyPrefix is similar to a chroot directory for local filesystem. + // If specified the SFTP user will only see objects that starts with + // this prefix and so you can restrict access to a specific virtual + // folder. The prefix, if not empty, must not start with "/" and must + // end with "/". + // If empty the whole bucket contents will be available + KeyPrefix string `json:"key_prefix,omitempty"` + Region string `json:"region,omitempty"` + AccessKey string `json:"access_key,omitempty"` + AccessSecret string `json:"access_secret,omitempty"` + Endpoint string `json:"endpoint,omitempty"` + StorageClass string `json:"storage_class,omitempty"` + // The buffer size (in MB) to use for multipart uploads. The minimum allowed part size is 5MB, + // and if this value is set to zero, the default value (5MB) for the AWS SDK will be used. + // The minimum allowed value is 5. + // Please note that if the upload bandwidth between the SFTP client and SFTPGo is greater than + // the upload bandwidth between SFTPGo and S3 then the SFTP client have to wait for the upload + // of the last parts to S3 after it ends the file upload to SFTPGo, and it may time out. + // Keep this in mind if you customize these parameters. + UploadPartSize int64 `json:"upload_part_size,omitempty"` + // How many parts are uploaded in parallel + UploadConcurrency int `json:"upload_concurrency,omitempty"` +} + +// GCSFsConfig defines the configuration for Google Cloud Storage based filesystem +type GCSFsConfig struct { + Bucket string `json:"bucket,omitempty"` + // KeyPrefix is similar to a chroot directory for local filesystem. + // If specified the SFTP user will only see objects that starts with + // this prefix and so you can restrict access to a specific virtual + // folder. The prefix, if not empty, must not start with "/" and must + // end with "/". + // If empty the whole bucket contents will be available + KeyPrefix string `json:"key_prefix,omitempty"` + CredentialFile string `json:"-"` + Credentials string `json:"credentials,omitempty"` + AutomaticCredentials int `json:"automatic_credentials,omitempty"` + StorageClass string `json:"storage_class,omitempty"` +} + // PipeWriter defines a wrapper for pipeat.PipeWriterAt. type PipeWriter struct { writer *pipeat.PipeWriterAt