mirror of
https://github.com/haiwen/seafile-server.git
synced 2025-09-23 20:17:19 +00:00
* Don't check database type when use go fileserver * Improve load fileserver config * Add LoadDBOption in option pkg --------- Co-authored-by: Heran Yang <heran.yang@seafile.com>
459 lines
11 KiB
Go
459 lines
11 KiB
Go
package option
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
log "github.com/sirupsen/logrus"
|
|
"gopkg.in/ini.v1"
|
|
)
|
|
|
|
// InfiniteQuota indicates that the quota is unlimited.
|
|
const InfiniteQuota = -2
|
|
|
|
// Storage unit.
|
|
const (
|
|
KB = 1000
|
|
MB = 1000000
|
|
GB = 1000000000
|
|
TB = 1000000000000
|
|
)
|
|
|
|
var (
|
|
// fileserver options
|
|
Host string
|
|
Port uint32
|
|
MaxUploadSize uint64
|
|
FsIdListRequestTimeout int64
|
|
// Block size for indexing uploaded files
|
|
FixedBlockSize uint64
|
|
// Maximum number of goroutines to index uploaded files
|
|
MaxIndexingThreads uint32
|
|
WebTokenExpireTime uint32
|
|
// File mode for temp files
|
|
ClusterSharedTempFileMode uint32
|
|
WindowsEncoding string
|
|
SkipBlockHash bool
|
|
FsCacheLimit int64
|
|
VerifyClientBlocks bool
|
|
|
|
// general options
|
|
CloudMode bool
|
|
|
|
// notification server
|
|
EnableNotification bool
|
|
NotificationURL string
|
|
|
|
// GROUP options
|
|
GroupTableName string
|
|
|
|
// quota options
|
|
DefaultQuota int64
|
|
|
|
// redis options
|
|
HasRedisOptions bool
|
|
RedisHost string
|
|
RedisPasswd string
|
|
RedisPort uint32
|
|
RedisExpiry uint32
|
|
RedisMaxConn uint32
|
|
RedisTimeout time.Duration
|
|
|
|
// Profile password
|
|
ProfilePassword string
|
|
EnableProfiling bool
|
|
|
|
// Go log level
|
|
LogLevel string
|
|
|
|
// DB default timeout
|
|
DBOpTimeout time.Duration
|
|
|
|
// database
|
|
DBType string
|
|
|
|
// seahub
|
|
SeahubURL string
|
|
JWTPrivateKey string
|
|
)
|
|
|
|
type DBOption struct {
|
|
User string
|
|
Password string
|
|
Host string
|
|
Port int
|
|
CcnetDbName string
|
|
SeafileDbName string
|
|
CaPath string
|
|
UseTLS bool
|
|
SkipVerify bool
|
|
Charset string
|
|
DBEngine string
|
|
}
|
|
|
|
func initDefaultOptions() {
|
|
Host = "0.0.0.0"
|
|
Port = 8082
|
|
FixedBlockSize = 1 << 23
|
|
MaxIndexingThreads = 1
|
|
WebTokenExpireTime = 7200
|
|
ClusterSharedTempFileMode = 0600
|
|
DefaultQuota = InfiniteQuota
|
|
FsCacheLimit = 4 << 30
|
|
VerifyClientBlocks = true
|
|
FsIdListRequestTimeout = -1
|
|
DBOpTimeout = 60 * time.Second
|
|
RedisHost = "127.0.0.1"
|
|
RedisPort = 6379
|
|
RedisExpiry = 24 * 3600
|
|
RedisMaxConn = 100
|
|
RedisTimeout = 1 * time.Second
|
|
}
|
|
|
|
func LoadFileServerOptions(centralDir string) {
|
|
initDefaultOptions()
|
|
|
|
seafileConfPath := filepath.Join(centralDir, "seafile.conf")
|
|
|
|
opts := ini.LoadOptions{}
|
|
opts.SpaceBeforeInlineComment = true
|
|
config, err := ini.LoadSources(opts, seafileConfPath)
|
|
if err != nil {
|
|
log.Fatalf("Failed to load seafile.conf: %v", err)
|
|
}
|
|
CloudMode = false
|
|
if section, err := config.GetSection("general"); err == nil {
|
|
if key, err := section.GetKey("cloud_mode"); err == nil {
|
|
CloudMode, _ = key.Bool()
|
|
}
|
|
}
|
|
|
|
notifServer := os.Getenv("INNER_NOTIFICATION_SERVER_URL")
|
|
enableNotifServer := os.Getenv("ENABLE_NOTIFICATION_SERVER")
|
|
if notifServer != "" && enableNotifServer == "true" {
|
|
NotificationURL = notifServer
|
|
EnableNotification = true
|
|
}
|
|
|
|
if section, err := config.GetSection("httpserver"); err == nil {
|
|
parseFileServerSection(section)
|
|
}
|
|
if section, err := config.GetSection("fileserver"); err == nil {
|
|
parseFileServerSection(section)
|
|
}
|
|
|
|
if section, err := config.GetSection("quota"); err == nil {
|
|
if key, err := section.GetKey("default"); err == nil {
|
|
quotaStr := key.String()
|
|
DefaultQuota = parseQuota(quotaStr)
|
|
}
|
|
}
|
|
|
|
loadCacheOptionFromEnv()
|
|
|
|
GroupTableName = os.Getenv("SEAFILE_MYSQL_DB_GROUP_TABLE_NAME")
|
|
if GroupTableName == "" {
|
|
GroupTableName = "Group"
|
|
}
|
|
}
|
|
|
|
func parseFileServerSection(section *ini.Section) {
|
|
if key, err := section.GetKey("host"); err == nil {
|
|
Host = key.String()
|
|
}
|
|
if key, err := section.GetKey("port"); err == nil {
|
|
port, err := key.Uint()
|
|
if err == nil {
|
|
Port = uint32(port)
|
|
}
|
|
}
|
|
if key, err := section.GetKey("max_upload_size"); err == nil {
|
|
size, err := key.Uint()
|
|
if err == nil {
|
|
MaxUploadSize = uint64(size) * 1000000
|
|
}
|
|
}
|
|
if key, err := section.GetKey("max_indexing_threads"); err == nil {
|
|
threads, err := key.Uint()
|
|
if err == nil {
|
|
MaxIndexingThreads = uint32(threads)
|
|
}
|
|
}
|
|
if key, err := section.GetKey("fixed_block_size"); err == nil {
|
|
blkSize, err := key.Uint64()
|
|
if err == nil {
|
|
FixedBlockSize = blkSize * (1 << 20)
|
|
}
|
|
}
|
|
if key, err := section.GetKey("web_token_expire_time"); err == nil {
|
|
expire, err := key.Uint()
|
|
if err == nil {
|
|
WebTokenExpireTime = uint32(expire)
|
|
}
|
|
}
|
|
if key, err := section.GetKey("cluster_shared_temp_file_mode"); err == nil {
|
|
fileMode, err := key.Uint()
|
|
if err == nil {
|
|
ClusterSharedTempFileMode = uint32(fileMode)
|
|
}
|
|
}
|
|
if key, err := section.GetKey("enable_profiling"); err == nil {
|
|
EnableProfiling, _ = key.Bool()
|
|
}
|
|
if EnableProfiling {
|
|
if key, err := section.GetKey("profile_password"); err == nil {
|
|
ProfilePassword = key.String()
|
|
} else {
|
|
log.Fatal("password of profiling must be specified.")
|
|
}
|
|
}
|
|
if key, err := section.GetKey("go_log_level"); err == nil {
|
|
LogLevel = key.String()
|
|
}
|
|
if key, err := section.GetKey("fs_cache_limit"); err == nil {
|
|
fsCacheLimit, err := key.Int64()
|
|
if err == nil {
|
|
FsCacheLimit = fsCacheLimit * 1024 * 1024
|
|
}
|
|
}
|
|
// The ratio of physical memory consumption and fs objects is about 4:1,
|
|
// and this part of memory is generally not subject to GC. So the value is
|
|
// divided by 4.
|
|
FsCacheLimit = FsCacheLimit / 4
|
|
if key, err := section.GetKey("fs_id_list_request_timeout"); err == nil {
|
|
fsIdListRequestTimeout, err := key.Int64()
|
|
if err == nil {
|
|
FsIdListRequestTimeout = fsIdListRequestTimeout
|
|
}
|
|
}
|
|
if key, err := section.GetKey("verify_client_blocks_after_sync"); err == nil {
|
|
VerifyClientBlocks, _ = key.Bool()
|
|
}
|
|
}
|
|
|
|
func parseQuota(quotaStr string) int64 {
|
|
var quota int64
|
|
var multiplier int64 = GB
|
|
if end := strings.Index(quotaStr, "kb"); end > 0 {
|
|
multiplier = KB
|
|
quotaInt, err := strconv.ParseInt(quotaStr[:end], 10, 0)
|
|
if err != nil {
|
|
return InfiniteQuota
|
|
}
|
|
quota = quotaInt * multiplier
|
|
} else if end := strings.Index(quotaStr, "mb"); end > 0 {
|
|
multiplier = MB
|
|
quotaInt, err := strconv.ParseInt(quotaStr[:end], 10, 0)
|
|
if err != nil {
|
|
return InfiniteQuota
|
|
}
|
|
quota = quotaInt * multiplier
|
|
} else if end := strings.Index(quotaStr, "gb"); end > 0 {
|
|
multiplier = GB
|
|
quotaInt, err := strconv.ParseInt(quotaStr[:end], 10, 0)
|
|
if err != nil {
|
|
return InfiniteQuota
|
|
}
|
|
quota = quotaInt * multiplier
|
|
} else if end := strings.Index(quotaStr, "tb"); end > 0 {
|
|
multiplier = TB
|
|
quotaInt, err := strconv.ParseInt(quotaStr[:end], 10, 0)
|
|
if err != nil {
|
|
return InfiniteQuota
|
|
}
|
|
quota = quotaInt * multiplier
|
|
} else {
|
|
quotaInt, err := strconv.ParseInt(quotaStr, 10, 0)
|
|
if err != nil {
|
|
return InfiniteQuota
|
|
}
|
|
quota = quotaInt * multiplier
|
|
}
|
|
|
|
return quota
|
|
}
|
|
|
|
func loadCacheOptionFromEnv() {
|
|
cacheProvider := os.Getenv("CACHE_PROVIDER")
|
|
if cacheProvider != "redis" {
|
|
return
|
|
}
|
|
|
|
HasRedisOptions = true
|
|
|
|
redisHost := os.Getenv("REDIS_HOST")
|
|
if redisHost != "" {
|
|
RedisHost = redisHost
|
|
}
|
|
redisPort := os.Getenv("REDIS_PORT")
|
|
if redisPort != "" {
|
|
port, err := strconv.ParseUint(redisPort, 10, 32)
|
|
if err != nil {
|
|
RedisPort = uint32(port)
|
|
}
|
|
}
|
|
redisPasswd := os.Getenv("REDIS_PASSWORD")
|
|
if redisPasswd != "" {
|
|
RedisPasswd = redisPasswd
|
|
}
|
|
redisMaxConn := os.Getenv("REDIS_MAX_CONNECTIONS")
|
|
if redisMaxConn != "" {
|
|
maxConn, err := strconv.ParseUint(redisMaxConn, 10, 32)
|
|
if err != nil {
|
|
RedisMaxConn = uint32(maxConn)
|
|
}
|
|
}
|
|
redisExpiry := os.Getenv("REDIS_EXPIRY")
|
|
if redisExpiry != "" {
|
|
expiry, err := strconv.ParseUint(redisExpiry, 10, 32)
|
|
if err != nil {
|
|
RedisExpiry = uint32(expiry)
|
|
}
|
|
}
|
|
}
|
|
|
|
func LoadSeahubConfig() error {
|
|
JWTPrivateKey = os.Getenv("JWT_PRIVATE_KEY")
|
|
if JWTPrivateKey == "" {
|
|
return fmt.Errorf("failed to read JWT_PRIVATE_KEY")
|
|
}
|
|
|
|
siteRoot := os.Getenv("SITE_ROOT")
|
|
if siteRoot != "" {
|
|
SeahubURL = fmt.Sprintf("http://127.0.0.1:8000%sapi/v2.1/internal", siteRoot)
|
|
} else {
|
|
SeahubURL = "http://127.0.0.1:8000/api/v2.1/internal"
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func LoadDBOption(centralDir string) (*DBOption, error) {
|
|
dbOpt, err := loadDBOptionFromFile(centralDir)
|
|
if err != nil {
|
|
log.Warnf("failed to load database config: %v", err)
|
|
}
|
|
dbOpt = loadDBOptionFromEnv(dbOpt)
|
|
|
|
if dbOpt.Host == "" {
|
|
return nil, fmt.Errorf("no database host in seafile.conf.")
|
|
}
|
|
if dbOpt.User == "" {
|
|
return nil, fmt.Errorf("no database user in seafile.conf.")
|
|
}
|
|
if dbOpt.Password == "" {
|
|
return nil, fmt.Errorf("no database password in seafile.conf.")
|
|
}
|
|
|
|
DBType = dbOpt.DBEngine
|
|
|
|
return dbOpt, nil
|
|
}
|
|
|
|
func loadDBOptionFromFile(centralDir string) (*DBOption, error) {
|
|
dbOpt := new(DBOption)
|
|
dbOpt.DBEngine = "mysql"
|
|
|
|
seafileConfPath := filepath.Join(centralDir, "seafile.conf")
|
|
opts := ini.LoadOptions{}
|
|
opts.SpaceBeforeInlineComment = true
|
|
config, err := ini.LoadSources(opts, seafileConfPath)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to load seafile.conf: %v", err)
|
|
}
|
|
|
|
section, err := config.GetSection("database")
|
|
if err != nil {
|
|
return dbOpt, nil
|
|
}
|
|
|
|
dbEngine := "mysql"
|
|
key, err := section.GetKey("type")
|
|
if err == nil {
|
|
dbEngine = key.String()
|
|
}
|
|
if dbEngine != "mysql" {
|
|
return nil, fmt.Errorf("unsupported database %s.", dbEngine)
|
|
}
|
|
dbOpt.DBEngine = dbEngine
|
|
if key, err = section.GetKey("host"); err == nil {
|
|
dbOpt.Host = key.String()
|
|
}
|
|
// user is required.
|
|
if key, err = section.GetKey("user"); err == nil {
|
|
dbOpt.User = key.String()
|
|
}
|
|
|
|
if key, err = section.GetKey("password"); err == nil {
|
|
dbOpt.Password = key.String()
|
|
}
|
|
|
|
if key, err = section.GetKey("db_name"); err == nil {
|
|
dbOpt.SeafileDbName = key.String()
|
|
}
|
|
port := 3306
|
|
if key, err = section.GetKey("port"); err == nil {
|
|
port, _ = key.Int()
|
|
}
|
|
dbOpt.Port = port
|
|
useTLS := false
|
|
if key, err = section.GetKey("use_ssl"); err == nil {
|
|
useTLS, _ = key.Bool()
|
|
}
|
|
dbOpt.UseTLS = useTLS
|
|
skipVerify := false
|
|
if key, err = section.GetKey("skip_verify"); err == nil {
|
|
skipVerify, _ = key.Bool()
|
|
}
|
|
dbOpt.SkipVerify = skipVerify
|
|
if key, err = section.GetKey("ca_path"); err == nil {
|
|
dbOpt.CaPath = key.String()
|
|
}
|
|
if key, err = section.GetKey("connection_charset"); err == nil {
|
|
dbOpt.Charset = key.String()
|
|
}
|
|
|
|
return dbOpt, nil
|
|
}
|
|
|
|
func loadDBOptionFromEnv(dbOpt *DBOption) *DBOption {
|
|
user := os.Getenv("SEAFILE_MYSQL_DB_USER")
|
|
password := os.Getenv("SEAFILE_MYSQL_DB_PASSWORD")
|
|
host := os.Getenv("SEAFILE_MYSQL_DB_HOST")
|
|
ccnetDbName := os.Getenv("SEAFILE_MYSQL_DB_CCNET_DB_NAME")
|
|
seafileDbName := os.Getenv("SEAFILE_MYSQL_DB_SEAFILE_DB_NAME")
|
|
|
|
if dbOpt == nil {
|
|
dbOpt = new(DBOption)
|
|
}
|
|
if user != "" {
|
|
dbOpt.User = user
|
|
}
|
|
if password != "" {
|
|
dbOpt.Password = password
|
|
}
|
|
if host != "" {
|
|
dbOpt.Host = host
|
|
}
|
|
if dbOpt.Port == 0 {
|
|
dbOpt.Port = 3306
|
|
}
|
|
if ccnetDbName != "" {
|
|
dbOpt.CcnetDbName = ccnetDbName
|
|
} else if dbOpt.CcnetDbName == "" {
|
|
dbOpt.CcnetDbName = "ccnet_db"
|
|
log.Infof("Failed to read SEAFILE_MYSQL_DB_CCNET_DB_NAME, use ccnet_db by default")
|
|
}
|
|
if seafileDbName != "" {
|
|
dbOpt.SeafileDbName = seafileDbName
|
|
} else if dbOpt.SeafileDbName == "" {
|
|
dbOpt.SeafileDbName = "seafile_db"
|
|
log.Infof("Failed to read SEAFILE_MYSQL_DB_SEAFILE_DB_NAME, use seafile_db by default")
|
|
}
|
|
return dbOpt
|
|
}
|