1
0
mirror of https://github.com/haiwen/seafile-server.git synced 2025-09-24 04:29:52 +00:00
Files
seafile-server/fileserver/option/option.go
feiniks 6a7c1df693 Don't check database type when use go fileserver (#772)
* 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>
2025-08-25 17:49:42 +08:00

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
}