data provider: add a setting to prevent auto-update

This commit is contained in:
Nicola Murino 2020-10-05 19:42:33 +02:00
parent 0ef826c090
commit c992072286
No known key found for this signature in database
GPG key ID: 2F1FB59433D5A8CB
13 changed files with 71 additions and 47 deletions

View file

@ -66,7 +66,7 @@ jobs:
build-args: | build-args: |
COMMIT_SHA=${{ steps.info.outputs.sha }} COMMIT_SHA=${{ steps.info.outputs.sha }}
labels: | labels: |
org.opencontainers.image.title=${{ github.event.repository.name }} org.opencontainers.image.title=SFTPGo
org.opencontainers.image.description=Fully featured and highly configurable SFTP server with optional FTP/S and WebDAV support org.opencontainers.image.description=Fully featured and highly configurable SFTP server with optional FTP/S and WebDAV support
org.opencontainers.image.url=${{ github.event.repository.html_url }} org.opencontainers.image.url=${{ github.event.repository.html_url }}
org.opencontainers.image.source=${{ github.event.repository.clone_url }} org.opencontainers.image.source=${{ github.event.repository.clone_url }}

View file

@ -90,17 +90,17 @@ sftpgo serve
Check out [this documentation](./docs/service.md) if you want to run SFTPGo as a service. Check out [this documentation](./docs/service.md) if you want to run SFTPGo as a service.
### Data provider initialization ### Data provider initialization and update
Before starting the SFTPGo server, please ensure that the configured data provider is properly initialized. Before starting the SFTPGo server, please ensure that the configured data provider is properly initialized/updated.
SQL based data providers (SQLite, MySQL, PostgreSQL) require the creation of a database containing the required tables. Memory and bolt data providers do not require an initialization. SQL based data providers (SQLite, MySQL, PostgreSQL) require the creation of a database containing the required tables. Memory and bolt data providers do not require an initialization but they could require an update to the existing data after upgrading SFTPGo.
For PostgreSQL and MySQL providers, you need to create the configured database. For PostgreSQL and MySQL providers, you need to create the configured database.
SFTPGo will attempt to automatically detect if the data privider has been initialized and if not, initialize it on startup. SFTPGo will attempt to automatically detect if the data provider is initialized/updated and if not, will attempt to initialize/ update it on startup as needed.
Alternately, you can create the required data provider structure yourself using the `initprovider` command. Alternately, you can create/update the required data provider structures yourself using the `initprovider` command.
For example, you can simply execute the following command from the configuration directory: For example, you can simply execute the following command from the configuration directory:
@ -114,7 +114,7 @@ Take a look at the CLI usage to learn how to specify a different configuration f
sftpgo initprovider --help sftpgo initprovider --help
``` ```
After the first initialization (manual or automatic), the database structure will be automatically checked and updated, if required, at startup. You can also disable automatic data provider checks at startup setting the `update_mode` configuration key to `1`.
## Tutorials ## Tutorials

View file

@ -16,18 +16,20 @@ import (
var ( var (
initProviderCmd = &cobra.Command{ initProviderCmd = &cobra.Command{
Use: "initprovider", Use: "initprovider",
Short: "Initializes the configured data provider", Short: "Initializes and/or updates the configured data provider",
Long: `This command reads the data provider connection details from the specified Long: `This command reads the data provider connection details from the specified
configuration file and creates the initial structure. configuration file and creates the initial structure or update the existing one,
as needed.
Some data providers such as bolt and memory does not require an initialization. Some data providers such as bolt and memory does not require an initialization
but they could require an update to the existing data after upgrading SFTPGo.
For SQLite provider the database file will be auto created if missing. For SQLite/bolt providers the database file will be auto-created if missing.
For PostgreSQL and MySQL providers you need to create the configured database, For PostgreSQL and MySQL providers you need to create the configured database,
this command will create the required tables. this command will create/update the required tables as needed.
To initialize the data provider from the configuration directory simply use: To initialize/update the data provider from the configuration directory simply use:
$ sftpgo initprovider $ sftpgo initprovider
@ -42,14 +44,14 @@ Please take a look at the usage below to customize the options.`,
return return
} }
providerConf := config.GetProviderConf() providerConf := config.GetProviderConf()
logger.DebugToConsole("Initializing provider: %#v config file: %#v", providerConf.Driver, viper.ConfigFileUsed()) logger.InfoToConsole("Initializing provider: %#v config file: %#v", providerConf.Driver, viper.ConfigFileUsed())
err = dataprovider.InitializeDatabase(providerConf, configDir) err = dataprovider.InitializeDatabase(providerConf, configDir)
if err == nil { if err == nil {
logger.DebugToConsole("Data provider successfully initialized") logger.InfoToConsole("Data provider successfully initialized/updated")
} else if err == dataprovider.ErrNoInitRequired { } else if err == dataprovider.ErrNoInitRequired {
logger.DebugToConsole("%v", err.Error()) logger.InfoToConsole("%v", err.Error())
} else { } else {
logger.WarnToConsole("Unable to initialize data provider: %v", err) logger.WarnToConsole("Unable to initialize/update the data provider: %v", err)
os.Exit(1) os.Exit(1)
} }
}, },

View file

@ -141,6 +141,7 @@ func init() {
Parallelism: 2, Parallelism: 2,
}, },
}, },
UpdateMode: 0,
}, },
HTTPDConfig: httpd.Conf{ HTTPDConfig: httpd.Conf{
BindPort: 8080, BindPort: 8080,

View file

@ -712,8 +712,8 @@ func (p BoltProvider) migrateDatabase() error {
return err return err
} }
if dbVersion.Version == boltDatabaseVersion { if dbVersion.Version == boltDatabaseVersion {
providerLog(logger.LevelDebug, "bolt database is updated, current version: %v", dbVersion.Version) providerLog(logger.LevelDebug, "bolt database is up to date, current version: %v", dbVersion.Version)
return nil return ErrNoInitRequired
} }
switch dbVersion.Version { switch dbVersion.Version {
case 1: case 1:
@ -866,6 +866,7 @@ func getFolderBucket(tx *bolt.Tx) (*bolt.Bucket, error) {
} }
func updateDatabaseFrom1To2(dbHandle *bolt.DB) error { func updateDatabaseFrom1To2(dbHandle *bolt.DB) error {
logger.InfoToConsole("updating bolt database version: 1 -> 2")
providerLog(logger.LevelInfo, "updating bolt database version: 1 -> 2") providerLog(logger.LevelInfo, "updating bolt database version: 1 -> 2")
usernames, err := getBoltAvailableUsernames(dbHandle) usernames, err := getBoltAvailableUsernames(dbHandle)
if err != nil { if err != nil {
@ -887,6 +888,7 @@ func updateDatabaseFrom1To2(dbHandle *bolt.DB) error {
} }
func updateDatabaseFrom2To3(dbHandle *bolt.DB) error { func updateDatabaseFrom2To3(dbHandle *bolt.DB) error {
logger.InfoToConsole("updating bolt database version: 2 -> 3")
providerLog(logger.LevelInfo, "updating bolt database version: 2 -> 3") providerLog(logger.LevelInfo, "updating bolt database version: 2 -> 3")
users := []User{} users := []User{}
err := dbHandle.View(func(tx *bolt.Tx) error { err := dbHandle.View(func(tx *bolt.Tx) error {
@ -941,6 +943,7 @@ func updateDatabaseFrom2To3(dbHandle *bolt.DB) error {
} }
func updateDatabaseFrom3To4(dbHandle *bolt.DB) error { func updateDatabaseFrom3To4(dbHandle *bolt.DB) error {
logger.InfoToConsole("updating bolt database version: 3 -> 4")
providerLog(logger.LevelInfo, "updating bolt database version: 3 -> 4") providerLog(logger.LevelInfo, "updating bolt database version: 3 -> 4")
foldersToScan := []string{} foldersToScan := []string{}
users := []userCompactVFolders{} users := []userCompactVFolders{}

View file

@ -99,8 +99,8 @@ var (
ErrNoAuthTryed = errors.New("no auth tryed") ErrNoAuthTryed = errors.New("no auth tryed")
// ValidProtocols defines all the valid protcols // ValidProtocols defines all the valid protcols
ValidProtocols = []string{"SSH", "FTP", "DAV"} ValidProtocols = []string{"SSH", "FTP", "DAV"}
// ErrNoInitRequired defines the error returned by InitProvider if no inizialization is required // ErrNoInitRequired defines the error returned by InitProvider if no inizialization/update is required
ErrNoInitRequired = errors.New("Data provider initialization is not required") ErrNoInitRequired = errors.New("The data provider is already up to date")
// ErrInvalidCredentials defines the error to return if the supplied credentials are invalid // ErrInvalidCredentials defines the error to return if the supplied credentials are invalid
ErrInvalidCredentials = errors.New("Invalid credentials") ErrInvalidCredentials = errors.New("Invalid credentials")
webDAVUsersCache sync.Map webDAVUsersCache sync.Map
@ -251,6 +251,10 @@ type Config struct {
CheckPasswordScope int `json:"check_password_scope" mapstructure:"check_password_scope"` CheckPasswordScope int `json:"check_password_scope" mapstructure:"check_password_scope"`
// PasswordHashing defines the configuration for password hashing // PasswordHashing defines the configuration for password hashing
PasswordHashing PasswordHashing `json:"password_hashing" mapstructure:"password_hashing"` PasswordHashing PasswordHashing `json:"password_hashing" mapstructure:"password_hashing"`
// Defines how the database will be initialized/updated:
// - 0 means automatically
// - 1 means manually using the initprovider sub-command
UpdateMode int `json:"update_mode" mapstructure:"update_mode"`
} }
// BackupData defines the structure for the backup/restore files // BackupData defines the structure for the backup/restore files
@ -383,19 +387,23 @@ func Initialize(cnf Config, basePath string) error {
if err != nil { if err != nil {
return err return err
} }
err = provider.initializeDatabase() if cnf.UpdateMode == 0 {
if err != nil && err != ErrNoInitRequired { err = provider.initializeDatabase()
logger.WarnToConsole("Unable to initialize data provider: %v", err) if err != nil && err != ErrNoInitRequired {
providerLog(logger.LevelWarn, "Unable to initialize data provider: %v", err) logger.WarnToConsole("Unable to initialize data provider: %v", err)
return err providerLog(logger.LevelWarn, "Unable to initialize data provider: %v", err)
} return err
if err == nil { }
logger.DebugToConsole("Data provider successfully initialized") if err == nil {
} logger.DebugToConsole("Data provider successfully initialized")
err = provider.migrateDatabase() }
if err != nil { err = provider.migrateDatabase()
providerLog(logger.LevelWarn, "database migration error: %v", err) if err != nil && err != ErrNoInitRequired {
return err providerLog(logger.LevelWarn, "database migration error: %v", err)
return err
}
} else {
providerLog(logger.LevelInfo, "database initialization/migration skipped, manual mode is configured")
} }
argon2Params = &argon2id.Params{ argon2Params = &argon2id.Params{
Memory: cnf.PasswordHashing.Argon2Options.Memory, Memory: cnf.PasswordHashing.Argon2Options.Memory,
@ -458,14 +466,15 @@ func validateSQLTablesPrefix() error {
func InitializeDatabase(cnf Config, basePath string) error { func InitializeDatabase(cnf Config, basePath string) error {
config = cnf config = cnf
if config.Driver == BoltDataProviderName || config.Driver == MemoryDataProviderName {
return ErrNoInitRequired
}
err := createProvider(basePath) err := createProvider(basePath)
if err != nil { if err != nil {
return err return err
} }
return provider.initializeDatabase() err = provider.initializeDatabase()
if err != nil && err != ErrNoInitRequired {
return err
}
return provider.migrateDatabase()
} }
// CheckUserAndPass retrieves the SFTP user with the given username and password if a match is found or an error // CheckUserAndPass retrieves the SFTP user with the given username and password if a match is found or an error

View file

@ -671,5 +671,5 @@ func (p MemoryProvider) initializeDatabase() error {
} }
func (p MemoryProvider) migrateDatabase() error { func (p MemoryProvider) migrateDatabase() error {
return nil return ErrNoInitRequired
} }

View file

@ -205,8 +205,8 @@ func (p MySQLProvider) migrateDatabase() error {
return err return err
} }
if dbVersion.Version == sqlDatabaseVersion { if dbVersion.Version == sqlDatabaseVersion {
providerLog(logger.LevelDebug, "sql database is updated, current version: %v", dbVersion.Version) providerLog(logger.LevelDebug, "sql database is up to date, current version: %v", dbVersion.Version)
return nil return ErrNoInitRequired
} }
switch dbVersion.Version { switch dbVersion.Version {
case 1: case 1:
@ -233,12 +233,14 @@ func (p MySQLProvider) migrateDatabase() error {
} }
func updateMySQLDatabaseFrom1To2(dbHandle *sql.DB) error { func updateMySQLDatabaseFrom1To2(dbHandle *sql.DB) error {
logger.InfoToConsole("updating database version: 1 -> 2")
providerLog(logger.LevelInfo, "updating database version: 1 -> 2") providerLog(logger.LevelInfo, "updating database version: 1 -> 2")
sql := strings.Replace(mysqlV2SQL, "{{users}}", sqlTableUsers, 1) sql := strings.Replace(mysqlV2SQL, "{{users}}", sqlTableUsers, 1)
return sqlCommonExecSQLAndUpdateDBVersion(dbHandle, []string{sql}, 2) return sqlCommonExecSQLAndUpdateDBVersion(dbHandle, []string{sql}, 2)
} }
func updateMySQLDatabaseFrom2To3(dbHandle *sql.DB) error { func updateMySQLDatabaseFrom2To3(dbHandle *sql.DB) error {
logger.InfoToConsole("updating database version: 2 -> 3")
providerLog(logger.LevelInfo, "updating database version: 2 -> 3") providerLog(logger.LevelInfo, "updating database version: 2 -> 3")
sql := strings.Replace(mysqlV3SQL, "{{users}}", sqlTableUsers, 1) sql := strings.Replace(mysqlV3SQL, "{{users}}", sqlTableUsers, 1)
return sqlCommonExecSQLAndUpdateDBVersion(dbHandle, []string{sql}, 3) return sqlCommonExecSQLAndUpdateDBVersion(dbHandle, []string{sql}, 3)

View file

@ -204,8 +204,8 @@ func (p PGSQLProvider) migrateDatabase() error {
return err return err
} }
if dbVersion.Version == sqlDatabaseVersion { if dbVersion.Version == sqlDatabaseVersion {
providerLog(logger.LevelDebug, "sql database is updated, current version: %v", dbVersion.Version) providerLog(logger.LevelDebug, "sql database is up to date, current version: %v", dbVersion.Version)
return nil return ErrNoInitRequired
} }
switch dbVersion.Version { switch dbVersion.Version {
case 1: case 1:
@ -232,12 +232,14 @@ func (p PGSQLProvider) migrateDatabase() error {
} }
func updatePGSQLDatabaseFrom1To2(dbHandle *sql.DB) error { func updatePGSQLDatabaseFrom1To2(dbHandle *sql.DB) error {
logger.InfoToConsole("updating database version: 1 -> 2")
providerLog(logger.LevelInfo, "updating database version: 1 -> 2") providerLog(logger.LevelInfo, "updating database version: 1 -> 2")
sql := strings.Replace(pgsqlV2SQL, "{{users}}", sqlTableUsers, 1) sql := strings.Replace(pgsqlV2SQL, "{{users}}", sqlTableUsers, 1)
return sqlCommonExecSQLAndUpdateDBVersion(dbHandle, []string{sql}, 2) return sqlCommonExecSQLAndUpdateDBVersion(dbHandle, []string{sql}, 2)
} }
func updatePGSQLDatabaseFrom2To3(dbHandle *sql.DB) error { func updatePGSQLDatabaseFrom2To3(dbHandle *sql.DB) error {
logger.InfoToConsole("updating database version: 2 -> 3")
providerLog(logger.LevelInfo, "updating database version: 2 -> 3") providerLog(logger.LevelInfo, "updating database version: 2 -> 3")
sql := strings.Replace(pgsqlV3SQL, "{{users}}", sqlTableUsers, 1) sql := strings.Replace(pgsqlV3SQL, "{{users}}", sqlTableUsers, 1)
return sqlCommonExecSQLAndUpdateDBVersion(dbHandle, []string{sql}, 3) return sqlCommonExecSQLAndUpdateDBVersion(dbHandle, []string{sql}, 3)

View file

@ -899,6 +899,7 @@ func sqlCommonRestoreCompatVirtualFolders(ctx context.Context, users []userCompa
} }
func sqlCommonUpdateDatabaseFrom3To4(sqlV4 string, dbHandle *sql.DB) error { func sqlCommonUpdateDatabaseFrom3To4(sqlV4 string, dbHandle *sql.DB) error {
logger.InfoToConsole("updating database version: 3 -> 4")
providerLog(logger.LevelInfo, "updating database version: 3 -> 4") providerLog(logger.LevelInfo, "updating database version: 3 -> 4")
users, err := sqlCommonGetCompatVirtualFolders(dbHandle) users, err := sqlCommonGetCompatVirtualFolders(dbHandle)
if err != nil { if err != nil {

View file

@ -227,8 +227,8 @@ func (p SQLiteProvider) migrateDatabase() error {
return err return err
} }
if dbVersion.Version == sqlDatabaseVersion { if dbVersion.Version == sqlDatabaseVersion {
providerLog(logger.LevelDebug, "sql database is updated, current version: %v", dbVersion.Version) providerLog(logger.LevelDebug, "sql database is up to date, current version: %v", dbVersion.Version)
return nil return ErrNoInitRequired
} }
switch dbVersion.Version { switch dbVersion.Version {
case 1: case 1:
@ -255,12 +255,14 @@ func (p SQLiteProvider) migrateDatabase() error {
} }
func updateSQLiteDatabaseFrom1To2(dbHandle *sql.DB) error { func updateSQLiteDatabaseFrom1To2(dbHandle *sql.DB) error {
logger.InfoToConsole("updating database version: 1 -> 2")
providerLog(logger.LevelInfo, "updating database version: 1 -> 2") providerLog(logger.LevelInfo, "updating database version: 1 -> 2")
sql := strings.Replace(sqliteV2SQL, "{{users}}", sqlTableUsers, 1) sql := strings.Replace(sqliteV2SQL, "{{users}}", sqlTableUsers, 1)
return sqlCommonExecSQLAndUpdateDBVersion(dbHandle, []string{sql}, 2) return sqlCommonExecSQLAndUpdateDBVersion(dbHandle, []string{sql}, 2)
} }
func updateSQLiteDatabaseFrom2To3(dbHandle *sql.DB) error { func updateSQLiteDatabaseFrom2To3(dbHandle *sql.DB) error {
logger.InfoToConsole("updating database version: 2 -> 3")
providerLog(logger.LevelInfo, "updating database version: 2 -> 3") providerLog(logger.LevelInfo, "updating database version: 2 -> 3")
sql := strings.ReplaceAll(sqliteV3SQL, "{{users}}", sqlTableUsers) sql := strings.ReplaceAll(sqliteV3SQL, "{{users}}", sqlTableUsers)
return sqlCommonExecSQLAndUpdateDBVersion(dbHandle, []string{sql}, 3) return sqlCommonExecSQLAndUpdateDBVersion(dbHandle, []string{sql}, 3)

View file

@ -11,7 +11,7 @@ Usage:
Available Commands: Available Commands:
gen A collection of useful generators gen A collection of useful generators
help Help about any command help Help about any command
initprovider Initializes the configured data provider initprovider Initializes and/or updates the configured data provider
portable Serve a single directory portable Serve a single directory
serve Start the SFTP Server serve Start the SFTP Server
@ -143,6 +143,7 @@ The configuration file contains the following sections:
- `memory`, unsigned integer. The amount of memory used by the algorithm (in kibibytes). Default: 65536. - `memory`, unsigned integer. The amount of memory used by the algorithm (in kibibytes). Default: 65536.
- `iterations`, unsigned integer. The number of iterations over the memory. Default: 1. - `iterations`, unsigned integer. The number of iterations over the memory. Default: 1.
- `parallelism`. unsigned 8 bit integer. The number of threads (or lanes) used by the algorithm. Default: 2. - `parallelism`. unsigned 8 bit integer. The number of threads (or lanes) used by the algorithm. Default: 2.
- `update_mode`, integer. Defines how the database will be initialized/updated. 0 means automatically. 1 means manually using the initprovider sub-command.
- **"httpd"**, the configuration for the HTTP server used to serve REST API and to expose the built-in web interface - **"httpd"**, the configuration for the HTTP server used to serve REST API and to expose the built-in web interface
- `bind_port`, integer. The port used for serving HTTP requests. Set to 0 to disable HTTP server. Default: 8080 - `bind_port`, integer. The port used for serving HTTP requests. Set to 0 to disable HTTP server. Default: 8080
- `bind_address`, string. Leave blank to listen on all available network interfaces. Default: "127.0.0.1" - `bind_address`, string. Leave blank to listen on all available network interfaces. Default: "127.0.0.1"

View file

@ -99,7 +99,8 @@
"iterations": 1, "iterations": 1,
"parallelism": 2 "parallelism": 2
} }
} },
"update_mode": 0
}, },
"httpd": { "httpd": {
"bind_port": 8080, "bind_port": 8080,