351 lines
9.6 KiB
Go
351 lines
9.6 KiB
Go
package initialize
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
|
|
"kra/internal/config"
|
|
)
|
|
|
|
type configurationEnvelope struct {
|
|
Data json.RawMessage `json:"data"`
|
|
Admin json.RawMessage `json:"admin"`
|
|
Email json.RawMessage `json:"email"`
|
|
}
|
|
|
|
func (r *Repo) ConfigurationJSON() (json.RawMessage, error) {
|
|
current := r.backend.Config()
|
|
if current == nil {
|
|
current = &config.Config{}
|
|
}
|
|
safe := config.Clone(current)
|
|
maskConfigSecrets(safe)
|
|
return json.Marshal(map[string]any{"config": managementConfig(safe)})
|
|
}
|
|
|
|
func (r *Repo) SaveConfigurationJSON(ctx context.Context, raw json.RawMessage) error {
|
|
current := r.backend.Config()
|
|
if current == nil {
|
|
current = &config.Config{}
|
|
}
|
|
next := config.Clone(current)
|
|
var value configurationEnvelope
|
|
if err := json.Unmarshal(raw, &value); err != nil {
|
|
return err
|
|
}
|
|
if err := mergeJSON(value.Data, &next.Data); err != nil {
|
|
return err
|
|
}
|
|
if err := mergeJSON(value.Admin, &next.Admin); err != nil {
|
|
return err
|
|
}
|
|
if len(value.Email) > 0 && string(value.Email) != "null" {
|
|
if next.Admin == nil {
|
|
next.Admin = &config.Admin{}
|
|
}
|
|
if err := mergeJSON(value.Email, &next.Admin.Email); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
preserveConfigSecrets(next, current)
|
|
if next.Admin != nil && current.Admin != nil {
|
|
next.Admin.ConfigPath = current.Admin.ConfigPath
|
|
}
|
|
if err := refreshDatabaseSources(next.Data); err != nil {
|
|
return err
|
|
}
|
|
return r.PersistRuntimeConfig(ctx, next)
|
|
}
|
|
|
|
func (r *Repo) DiskMountPoints() []string {
|
|
current := r.backend.Config()
|
|
if current == nil || current.Admin == nil {
|
|
return nil
|
|
}
|
|
points := make([]string, 0, len(current.Admin.DiskList))
|
|
for _, item := range current.Admin.DiskList {
|
|
if item != nil && item.MountPoint != "" {
|
|
points = append(points, item.MountPoint)
|
|
}
|
|
}
|
|
return points
|
|
}
|
|
|
|
func mergeJSON(raw json.RawMessage, target any) error {
|
|
if len(raw) == 0 || string(raw) == "null" {
|
|
return nil
|
|
}
|
|
return json.Unmarshal(raw, target)
|
|
}
|
|
|
|
func managementConfig(value *config.Config) map[string]any {
|
|
result := map[string]any{"data": map[string]any{}, "admin": map[string]any{}, "email": map[string]any{}}
|
|
if value == nil {
|
|
return result
|
|
}
|
|
if value.Data != nil {
|
|
result["data"] = value.Data
|
|
}
|
|
if value.Admin != nil {
|
|
admin := value.Admin
|
|
result["admin"] = map[string]any{
|
|
"routerPrefix": admin.RouterPrefix,
|
|
"jwt": managementJWT(admin.JWT),
|
|
"captcha": managementCaptcha(admin.Captcha),
|
|
"local": managementLocal(admin.Local),
|
|
"media": managementMedia(admin.Media),
|
|
"system": managementSystem(admin.System),
|
|
"storage": admin.Storage,
|
|
"disk_list": admin.DiskList,
|
|
"zap": admin.Zap,
|
|
"cors": admin.CORS,
|
|
"app": admin.App,
|
|
}
|
|
if admin.Email != nil {
|
|
result["email"] = map[string]any{
|
|
"to": admin.Email.To, "from": admin.Email.From, "host": admin.Email.Host,
|
|
"secret": admin.Email.Secret, "nickname": admin.Email.Nickname, "port": admin.Email.Port,
|
|
"is-ssl": admin.Email.IsSSL, "is-loginauth": admin.Email.IsLoginAuth,
|
|
}
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
func managementJWT(value *config.JWT) any {
|
|
if value == nil {
|
|
return map[string]any{}
|
|
}
|
|
return map[string]any{"signingKey": value.SigningKey, "expiresTime": value.ExpiresTime.String(), "bufferTime": value.BufferTime.String(), "issuer": value.Issuer}
|
|
}
|
|
func managementCaptcha(value *config.Captcha) any {
|
|
if value == nil {
|
|
return map[string]any{}
|
|
}
|
|
return map[string]any{"keyLong": value.KeyLong, "imgWidth": value.ImgWidth, "imgHeight": value.ImgHeight, "storeExpiration": value.StoreExpiration.String()}
|
|
}
|
|
func managementLocal(value *config.Local) any {
|
|
if value == nil {
|
|
return map[string]any{}
|
|
}
|
|
return map[string]any{"storePath": value.StorePath, "pathPrefix": value.PathPrefix}
|
|
}
|
|
func managementMedia(value *config.Media) any {
|
|
if value == nil {
|
|
return map[string]any{}
|
|
}
|
|
chunkDir := value.ChunkDir
|
|
if chunkDir == "" {
|
|
chunkDir = "uploads/chunks"
|
|
}
|
|
return map[string]any{"sessionTtl": value.SessionTTL, "maxFileSize": value.MaxFileSize, "chunkDir": chunkDir}
|
|
}
|
|
func managementSystem(value *config.System) any {
|
|
if value == nil {
|
|
return map[string]any{}
|
|
}
|
|
return map[string]any{"useRedis": value.UseRedis, "useMultipoint": value.UseMultipoint, "useStrictAuth": value.UseStrictAuth, "disableAutoMigrate": value.DisableAutoMigrate, "useMongo": value.UseMongo, "addr": value.Addr, "iplimitCount": value.IplimitCount, "iplimitTime": value.IplimitTime}
|
|
}
|
|
|
|
func maskConfigSecrets(value *config.Config) {
|
|
if value == nil {
|
|
return
|
|
}
|
|
maskDataSecrets(value.Data)
|
|
if value.Admin == nil {
|
|
return
|
|
}
|
|
if value.Admin.JWT != nil && value.Admin.JWT.SigningKey != "" {
|
|
value.Admin.JWT.SigningKey = "******"
|
|
}
|
|
if value.Admin.Email != nil && value.Admin.Email.Secret != "" {
|
|
value.Admin.Email.Secret = "******"
|
|
}
|
|
maskStorageSecrets(value.Admin.Storage)
|
|
}
|
|
|
|
func maskDataSecrets(value *config.Data) {
|
|
if value == nil {
|
|
return
|
|
}
|
|
if value.Database != nil {
|
|
value.Database.Password = "******"
|
|
value.Database.Source = ""
|
|
}
|
|
if value.Redis != nil {
|
|
value.Redis.Password = "******"
|
|
}
|
|
if value.Mongo != nil {
|
|
value.Mongo.Password = "******"
|
|
}
|
|
for _, item := range value.DatabaseList {
|
|
if item != nil {
|
|
item.Password = "******"
|
|
item.Source = ""
|
|
}
|
|
}
|
|
for _, item := range value.RedisList {
|
|
if item != nil {
|
|
item.Password = "******"
|
|
}
|
|
}
|
|
}
|
|
|
|
func preserveConfigSecrets(next, current *config.Config) {
|
|
if next == nil || current == nil {
|
|
return
|
|
}
|
|
preserveDataSecrets(next.Data, current.Data)
|
|
if next.Admin == nil || current.Admin == nil {
|
|
return
|
|
}
|
|
if next.Admin.JWT != nil && current.Admin.JWT != nil && maskedSecret(next.Admin.JWT.SigningKey) {
|
|
next.Admin.JWT.SigningKey = current.Admin.JWT.SigningKey
|
|
}
|
|
if next.Admin.Email != nil && current.Admin.Email != nil && maskedSecret(next.Admin.Email.Secret) {
|
|
next.Admin.Email.Secret = current.Admin.Email.Secret
|
|
}
|
|
preserveStorageSecrets(next.Admin.Storage, current.Admin.Storage)
|
|
}
|
|
|
|
func preserveDataSecrets(next, current *config.Data) {
|
|
if next == nil || current == nil {
|
|
return
|
|
}
|
|
if next.Database != nil && current.Database != nil {
|
|
if maskedSecret(next.Database.Password) {
|
|
next.Database.Password = current.Database.Password
|
|
}
|
|
if next.Database.Source == "" {
|
|
next.Database.Source = current.Database.Source
|
|
}
|
|
}
|
|
if next.Redis != nil && current.Redis != nil && maskedSecret(next.Redis.Password) {
|
|
next.Redis.Password = current.Redis.Password
|
|
}
|
|
if next.Mongo != nil && current.Mongo != nil && maskedSecret(next.Mongo.Password) {
|
|
next.Mongo.Password = current.Mongo.Password
|
|
}
|
|
preserveDatabaseListSecrets(next.DatabaseList, current.DatabaseList)
|
|
preserveRedisListSecrets(next.RedisList, current.RedisList)
|
|
}
|
|
|
|
func preserveDatabaseListSecrets(next, current []*config.Database) {
|
|
byName := make(map[string]*config.Database, len(current))
|
|
for _, item := range current {
|
|
if item != nil && item.AliasName != "" {
|
|
byName[item.AliasName] = item
|
|
}
|
|
}
|
|
for index, item := range next {
|
|
if item == nil {
|
|
continue
|
|
}
|
|
previous := byName[item.AliasName]
|
|
if previous == nil && index < len(current) {
|
|
previous = current[index]
|
|
}
|
|
if previous == nil {
|
|
continue
|
|
}
|
|
if maskedSecret(item.Password) {
|
|
item.Password = previous.Password
|
|
}
|
|
if item.Source == "" {
|
|
item.Source = previous.Source
|
|
}
|
|
}
|
|
}
|
|
|
|
func preserveRedisListSecrets(next, current []*config.Redis) {
|
|
byName := make(map[string]*config.Redis, len(current))
|
|
for _, item := range current {
|
|
if item != nil && item.Name != "" {
|
|
byName[item.Name] = item
|
|
}
|
|
}
|
|
for index, item := range next {
|
|
if item == nil {
|
|
continue
|
|
}
|
|
previous := byName[item.Name]
|
|
if previous == nil && index < len(current) {
|
|
previous = current[index]
|
|
}
|
|
if previous != nil && maskedSecret(item.Password) {
|
|
item.Password = previous.Password
|
|
}
|
|
}
|
|
}
|
|
|
|
func maskedSecret(value string) bool { return value == "" || value == "******" }
|
|
|
|
func objectStores(value *config.Storage) []*config.ObjectStore {
|
|
if value == nil {
|
|
return nil
|
|
}
|
|
return []*config.ObjectStore{value.AliyunOSS, value.HuaweiOBS, value.TencentCOS, value.AWSS3, value.CloudflareR2, value.Minio}
|
|
}
|
|
func maskStorageSecrets(value *config.Storage) {
|
|
if value == nil {
|
|
return
|
|
}
|
|
if value.Qiniu != nil && value.Qiniu.SecretKey != "" {
|
|
value.Qiniu.SecretKey = "******"
|
|
}
|
|
for _, item := range objectStores(value) {
|
|
if item != nil && item.SecretKey != "" {
|
|
item.SecretKey = "******"
|
|
}
|
|
}
|
|
}
|
|
func preserveStorageSecrets(next, current *config.Storage) {
|
|
if next == nil || current == nil {
|
|
return
|
|
}
|
|
if next.Qiniu != nil && current.Qiniu != nil && maskedSecret(next.Qiniu.SecretKey) {
|
|
next.Qiniu.SecretKey = current.Qiniu.SecretKey
|
|
}
|
|
nextItems, currentItems := objectStores(next), objectStores(current)
|
|
for index := range nextItems {
|
|
if nextItems[index] != nil && currentItems[index] != nil && maskedSecret(nextItems[index].SecretKey) {
|
|
nextItems[index].SecretKey = currentItems[index].SecretKey
|
|
}
|
|
}
|
|
}
|
|
|
|
func refreshDatabaseSources(value *config.Data) error {
|
|
if value == nil {
|
|
return nil
|
|
}
|
|
if err := refreshDatabaseSource(value.Database); err != nil {
|
|
return err
|
|
}
|
|
for _, database := range value.DatabaseList {
|
|
if database == nil || database.Disable {
|
|
continue
|
|
}
|
|
if err := refreshDatabaseSource(database); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
func refreshDatabaseSource(database *config.Database) error {
|
|
if database == nil {
|
|
return nil
|
|
}
|
|
if database.Driver == "sqlite" && database.Path == "" {
|
|
return errors.New("sqlite database path is required")
|
|
}
|
|
if database.Driver == "sqlite" {
|
|
database.Source = database.Path
|
|
} else {
|
|
// Driver-specific DSN construction remains in data. Clearing Source
|
|
// makes the data backend rebuild it from structured values.
|
|
database.Source = ""
|
|
}
|
|
return nil
|
|
}
|