add version info
This commit is contained in:
parent
2aca4479a5
commit
4f4489d3f1
16 changed files with 160 additions and 29 deletions
20
README.md
20
README.md
|
@ -40,6 +40,17 @@ $ go get -u github.com/drakkan/sftpgo
|
|||
|
||||
Make sure [Git is installed](https://git-scm.com/downloads) on your machine and in your system's `PATH`.
|
||||
|
||||
Version info can be embedded populating the following variables at build time:
|
||||
|
||||
- `github.com/drakkan/sftpgo/utils.commit`
|
||||
- `github.com/drakkan/sftpgo/utils.date`
|
||||
|
||||
For example on Linux you can build using the following ldflags:
|
||||
|
||||
```bash
|
||||
-ldflags "-s -w -X github.com/drakkan/sftpgo/utils.commit=`git describe --tags --always --dirty` -X github.com/drakkan/sftpgo/utils.date=`date --utc +%FT%TZ`"
|
||||
```
|
||||
|
||||
A systemd sample [service](https://github.com/drakkan/sftpgo/tree/master/init/sftpgo.service "systemd service") can be found inside the source tree.
|
||||
|
||||
Alternately you can use distro packages:
|
||||
|
@ -195,7 +206,7 @@ For each account the following properties can be configured:
|
|||
|
||||
- `username`
|
||||
- `password` used for password authentication. For users created using SFTPGo REST API the password will be stored using argon2id hashing algo. SFTPGo supports checking passwords stored with bcrypt too. Currently, as fallback, there is a clear text password checking but you should not store passwords as clear text and this support could be removed at any time, so please don't depend on it.
|
||||
- `public_key` array of public keys. At least one public key or the password is mandatory.
|
||||
- `public_keys` array of public keys. At least one public key or the password is mandatory.
|
||||
- `home_dir` The user cannot upload or download files outside this directory. Must be an absolute path
|
||||
- `uid`, `gid`. If sftpgo runs as root system user then the created files and directories will be assigned to this system uid/gid. Ignored on windows and if sftpgo runs as non root user: in this case files and directories for all SFTP users will be owned by the system user that runs sftpgo.
|
||||
- `max_sessions` maximum concurrent sessions. 0 means unlimited
|
||||
|
@ -223,7 +234,7 @@ If quota tracking is enabled in `sftpgo` configuration file, then the used size
|
|||
|
||||
REST API is designed to run on localhost or on a trusted network, if you need HTTPS or authentication you can setup a reverse proxy using an HTTP Server such as Apache or NGNIX.
|
||||
|
||||
For example you can setup a reverse proxy using apache this way:
|
||||
For example you can keep SFTPGo listening on localhost and expose it externally configuring a reverse proxy using Apache HTTP Server this way:
|
||||
|
||||
```
|
||||
ProxyPass /api/v1 http://127.0.0.1:8080/api/v1
|
||||
|
@ -236,7 +247,8 @@ and you can add authentication with something like this:
|
|||
<Location /api/v1>
|
||||
AuthType Digest
|
||||
AuthName "Private"
|
||||
AuthBasicProvider file
|
||||
AuthDigestDomain "/api/v1"
|
||||
AuthDigestProvider file
|
||||
AuthUserFile "/etc/httpd/conf/auth_digest"
|
||||
Require valid-user
|
||||
</Location>
|
||||
|
@ -248,7 +260,7 @@ The OpenAPI 3 schema for the exposed API can be found inside the source tree: [o
|
|||
|
||||
A sample CLI client for the REST API can be found inside the source tree [scripts](https://github.com/drakkan/sftpgo/tree/master/scripts "scripts") directory.
|
||||
|
||||
You can also generate your own REST client using an OpenAPI generator such as [swagger-codegen](https://github.com/swagger-api/swagger-codegen) or [OpenAPI Generator](https://openapi-generator.tech/)
|
||||
You can also generate your own REST client, in your preferred programming language or even bash scripts, using an OpenAPI generator such as [swagger-codegen](https://github.com/swagger-api/swagger-codegen) or [OpenAPI Generator](https://openapi-generator.tech/)
|
||||
|
||||
## Logs
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@ const (
|
|||
activeConnectionsPath = "/api/v1/sftp_connection"
|
||||
quotaScanPath = "/api/v1/quota_scan"
|
||||
userPath = "/api/v1/user"
|
||||
versionPath = "/api/v1/version"
|
||||
)
|
||||
|
||||
var (
|
||||
|
|
|
@ -35,6 +35,7 @@ const (
|
|||
userPath = "/api/v1/user"
|
||||
activeConnectionsPath = "/api/v1/sftp_connection"
|
||||
quotaScanPath = "/api/v1/quota_scan"
|
||||
versionPath = "/api/v1/version"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -393,6 +394,17 @@ func TestStartQuotaScan(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestGetVersion(t *testing.T) {
|
||||
_, _, err := api.GetVersion(http.StatusOK)
|
||||
if err != nil {
|
||||
t.Errorf("unable to get sftp version: %v", err)
|
||||
}
|
||||
_, _, err = api.GetVersion(http.StatusInternalServerError)
|
||||
if err == nil {
|
||||
t.Errorf("get version request must succeed, we requested to check a wrong status code")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetSFTPConnections(t *testing.T) {
|
||||
_, _, err := api.GetSFTPConnections(http.StatusOK)
|
||||
if err != nil {
|
||||
|
@ -668,6 +680,12 @@ func TestStartQuotaScanNonExistentUserMock(t *testing.T) {
|
|||
checkResponseCode(t, http.StatusBadRequest, rr.Code)
|
||||
}
|
||||
|
||||
func TestGetVersionMock(t *testing.T) {
|
||||
req, _ := http.NewRequest(http.MethodGet, versionPath, nil)
|
||||
rr := executeRequest(req)
|
||||
checkResponseCode(t, http.StatusOK, rr.Code)
|
||||
}
|
||||
|
||||
func TestGetSFTPConnectionsMock(t *testing.T) {
|
||||
req, _ := http.NewRequest(http.MethodGet, activeConnectionsPath, nil)
|
||||
rr := executeRequest(req)
|
||||
|
|
|
@ -242,6 +242,24 @@ func CloseSFTPConnection(connectionID string, expectedStatusCode int) ([]byte, e
|
|||
return body, err
|
||||
}
|
||||
|
||||
// GetVersion returns version details
|
||||
func GetVersion(expectedStatusCode int) (utils.VersionInfo, []byte, error) {
|
||||
var version utils.VersionInfo
|
||||
var body []byte
|
||||
resp, err := getHTTPClient().Get(buildURLRelativeToBase(versionPath))
|
||||
if err != nil {
|
||||
return version, body, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
err = checkResponse(resp.StatusCode, expectedStatusCode)
|
||||
if err == nil && expectedStatusCode == http.StatusOK {
|
||||
err = render.DecodeJSON(resp.Body, &version)
|
||||
} else {
|
||||
body, _ = getResponseBody(resp)
|
||||
}
|
||||
return version, body, err
|
||||
}
|
||||
|
||||
func checkResponse(actual int, expected int) error {
|
||||
if expected != actual {
|
||||
return fmt.Errorf("wrong status code: got %v want %v", actual, expected)
|
||||
|
|
|
@ -205,5 +205,9 @@ func TestApiCallToNotListeningServer(t *testing.T) {
|
|||
if err == nil {
|
||||
t.Errorf("request to an inactive URL must fail")
|
||||
}
|
||||
_, _, err = GetVersion(http.StatusOK)
|
||||
if err == nil {
|
||||
t.Errorf("request to an inactive URL must fail")
|
||||
}
|
||||
SetBaseURL(oldBaseURL)
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
|
||||
"github.com/drakkan/sftpgo/logger"
|
||||
"github.com/drakkan/sftpgo/sftpd"
|
||||
"github.com/drakkan/sftpgo/utils"
|
||||
"github.com/go-chi/chi"
|
||||
"github.com/go-chi/chi/middleware"
|
||||
"github.com/go-chi/render"
|
||||
|
@ -30,6 +31,10 @@ func initializeRouter() {
|
|||
sendAPIResponse(w, r, nil, "Method not allowed", http.StatusMethodNotAllowed)
|
||||
}))
|
||||
|
||||
router.Get(versionPath, func(w http.ResponseWriter, r *http.Request) {
|
||||
render.JSON(w, r, utils.GetAppVersion())
|
||||
})
|
||||
|
||||
router.Get(activeConnectionsPath, func(w http.ResponseWriter, r *http.Request) {
|
||||
render.JSON(w, r, sftpd.GetConnectionsStats())
|
||||
})
|
||||
|
|
|
@ -7,6 +7,21 @@ info:
|
|||
servers:
|
||||
- url: /api/v1
|
||||
paths:
|
||||
/version:
|
||||
get:
|
||||
tags:
|
||||
- version
|
||||
summary: Get version details
|
||||
operationId: get_version
|
||||
responses:
|
||||
200:
|
||||
description: successful operation
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref : '#/components/schemas/VersionInfo'
|
||||
/sftp_connection:
|
||||
get:
|
||||
tags:
|
||||
|
@ -654,3 +669,13 @@ components:
|
|||
type: string
|
||||
nullable: true
|
||||
description: error description if any
|
||||
VersionInfo:
|
||||
type: object
|
||||
properties:
|
||||
version:
|
||||
type: string
|
||||
build_date:
|
||||
type: string
|
||||
commit_hash:
|
||||
type: string
|
||||
|
|
@ -20,9 +20,10 @@ var (
|
|||
)
|
||||
|
||||
func init() {
|
||||
version := utils.GetAppVersion()
|
||||
rootCmd.Flags().BoolP("version", "v", false, "")
|
||||
rootCmd.Version = utils.GetAppVersion()
|
||||
rootCmd.SetVersionTemplate(`{{printf "SFTPGo version "}}{{printf "%s" .Version}}
|
||||
rootCmd.Version = version.GetVersionAsString()
|
||||
rootCmd.SetVersionTemplate(`{{printf "SFTPGo version: "}}{{printf "%s" .Version}}
|
||||
`)
|
||||
}
|
||||
|
||||
|
|
|
@ -7,7 +7,6 @@ package config
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/drakkan/sftpgo/api"
|
||||
|
@ -79,10 +78,7 @@ func init() {
|
|||
replacer := strings.NewReplacer(".", "__")
|
||||
viper.SetEnvKeyReplacer(replacer)
|
||||
viper.SetConfigName(DefaultConfigName)
|
||||
if runtime.GOOS == "linux" {
|
||||
viper.AddConfigPath("$HOME/.config/sftpgo")
|
||||
viper.AddConfigPath("/etc/sftpgo")
|
||||
}
|
||||
setViperAdditionalConfigPaths()
|
||||
viper.AddConfigPath(".")
|
||||
viper.AutomaticEnv()
|
||||
}
|
||||
|
|
11
config/config_linux.go
Normal file
11
config/config_linux.go
Normal file
|
@ -0,0 +1,11 @@
|
|||
// +build linux
|
||||
|
||||
package config
|
||||
|
||||
import "github.com/spf13/viper"
|
||||
|
||||
// linux specific config search path
|
||||
func setViperAdditionalConfigPaths() {
|
||||
viper.AddConfigPath("$HOME/.config/sftpgo")
|
||||
viper.AddConfigPath("/etc/sftpgo")
|
||||
}
|
7
config/config_nolinux.go
Normal file
7
config/config_nolinux.go
Normal file
|
@ -0,0 +1,7 @@
|
|||
// +build !linux
|
||||
|
||||
package config
|
||||
|
||||
func setViperAdditionalConfigPaths() {
|
||||
|
||||
}
|
|
@ -16,6 +16,7 @@ class SFTPGoApiRequests:
|
|||
self.userPath = urlparse.urljoin(baseUrl, "/api/v1/user")
|
||||
self.quotaScanPath = urlparse.urljoin(baseUrl, "/api/v1/quota_scan")
|
||||
self.activeConnectionsPath = urlparse.urljoin(baseUrl, "/api/v1/sftp_connection")
|
||||
self.versionPath = urlparse.urljoin(baseUrl, "/api/v1/version")
|
||||
self.debug = debug
|
||||
if authType == "basic":
|
||||
self.auth = requests.auth.HTTPBasicAuth(authUser, authPassword)
|
||||
|
@ -98,6 +99,10 @@ class SFTPGoApiRequests:
|
|||
r = requests.post(self.quotaScanPath, json=u, auth=self.auth, verify=self.verify)
|
||||
self.printResponse(r)
|
||||
|
||||
def getVersion(self):
|
||||
r = requests.get(self.versionPath, auth=self.auth, verify=self.verify)
|
||||
self.printResponse(r)
|
||||
|
||||
|
||||
def addCommonUserArguments(parser):
|
||||
parser.add_argument('username', type=str)
|
||||
|
@ -165,6 +170,8 @@ if __name__ == '__main__':
|
|||
parserStartQuotaScans = subparsers.add_parser("start_quota_scan", help="Start a new quota scan")
|
||||
addCommonUserArguments(parserStartQuotaScans)
|
||||
|
||||
parserGetVersion = subparsers.add_parser("get_version", help="Get version details")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
api = SFTPGoApiRequests(args.debug, args.base_url, args.auth_type, args.auth_user, args.auth_password, args.verify)
|
||||
|
@ -191,4 +198,6 @@ if __name__ == '__main__':
|
|||
api.getQuotaScans()
|
||||
elif args.command == "start_quota_scan":
|
||||
api.startQuotaScan(args.username)
|
||||
elif args.command == "get_version":
|
||||
api.getVersion()
|
||||
|
||||
|
|
|
@ -193,7 +193,7 @@ func GetConnectionsStats() []ConnectionStatus {
|
|||
}
|
||||
for _, t := range activeTransfers {
|
||||
if t.connectionID == c.ID {
|
||||
if utils.GetTimeAsMsSinceEpoch(t.lastActivity) > conn.LastActivity {
|
||||
if t.lastActivity.UnixNano() > c.lastActivity.UnixNano() {
|
||||
conn.LastActivity = utils.GetTimeAsMsSinceEpoch(t.lastActivity)
|
||||
}
|
||||
var operationType string
|
||||
|
|
|
@ -96,6 +96,7 @@ func TestMain(m *testing.M) {
|
|||
sftpdConf := config.GetSFTPDConfig()
|
||||
httpdConf := config.GetHTTPDConfig()
|
||||
router := api.GetHTTPRouter()
|
||||
sftpdConf.BindPort = 2022
|
||||
// we run the test cases with UploadMode atomic. The non atomic code path
|
||||
// simply does not execute some code so if it works in atomic mode will
|
||||
// work in non atomic mode too
|
||||
|
@ -105,7 +106,7 @@ func TestMain(m *testing.M) {
|
|||
} else {
|
||||
homeBasePath = "/tmp"
|
||||
sftpdConf.Actions.ExecuteOn = []string{"download", "upload", "rename", "delete"}
|
||||
sftpdConf.Actions.Command = "/bin/true"
|
||||
sftpdConf.Actions.Command = "/usr/bin/true"
|
||||
sftpdConf.Actions.HTTPNotificationURL = "http://127.0.0.1:8080/"
|
||||
}
|
||||
|
||||
|
@ -145,6 +146,7 @@ func TestInitialization(t *testing.T) {
|
|||
config.LoadConfig(configDir, "")
|
||||
sftpdConf := config.GetSFTPDConfig()
|
||||
sftpdConf.Umask = "invalid umask"
|
||||
sftpdConf.BindPort = 2022
|
||||
err := sftpdConf.Initialize(configDir)
|
||||
if err == nil {
|
||||
t.Errorf("Inizialize must fail, a SFTP server should be already running")
|
||||
|
|
|
@ -12,12 +12,6 @@ import (
|
|||
|
||||
const logSender = "utils"
|
||||
|
||||
var (
|
||||
version = "dev"
|
||||
commit = ""
|
||||
date = ""
|
||||
)
|
||||
|
||||
// IsStringInSlice searches a string in a slice and returns true if the string is found
|
||||
func IsStringInSlice(obj string, list []string) bool {
|
||||
for _, v := range list {
|
||||
|
@ -76,14 +70,7 @@ func SetPathPermissions(path string, uid int, gid int) {
|
|||
}
|
||||
}
|
||||
|
||||
// GetAppVersion returns the app version
|
||||
func GetAppVersion() string {
|
||||
v := version
|
||||
if len(commit) > 0 {
|
||||
v += "-" + commit
|
||||
}
|
||||
if len(date) > 0 {
|
||||
v += "-" + date
|
||||
}
|
||||
return v
|
||||
// GetAppVersion returns VersionInfo struct
|
||||
func GetAppVersion() VersionInfo {
|
||||
return versionInfo
|
||||
}
|
||||
|
|
35
utils/version.go
Normal file
35
utils/version.go
Normal file
|
@ -0,0 +1,35 @@
|
|||
package utils
|
||||
|
||||
var (
|
||||
version = "0.9.0-dev"
|
||||
commit = ""
|
||||
date = ""
|
||||
versionInfo VersionInfo
|
||||
)
|
||||
|
||||
// VersionInfo defines version details
|
||||
type VersionInfo struct {
|
||||
Version string `json:"version"`
|
||||
BuildDate string `json:"build_date"`
|
||||
CommitHash string `json:"commit_hash"`
|
||||
}
|
||||
|
||||
// GetVersionAsString returns the string representation of the VersionInfo struct
|
||||
func (v *VersionInfo) GetVersionAsString() string {
|
||||
versionString := v.Version
|
||||
if len(v.CommitHash) > 0 {
|
||||
versionString += "-" + v.CommitHash
|
||||
}
|
||||
if len(v.BuildDate) > 0 {
|
||||
versionString += "-" + v.BuildDate
|
||||
}
|
||||
return versionString
|
||||
}
|
||||
|
||||
func init() {
|
||||
versionInfo = VersionInfo{
|
||||
Version: version,
|
||||
CommitHash: commit,
|
||||
BuildDate: date,
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue