This commit is contained in:
parent
7e221e8f57
commit
3d5c94e552
@ -149,6 +149,10 @@ func PlanLegacyMigration(root string, _ RuntimeValidationOptions) (LegacyMigrati
|
||||
return plan, nil
|
||||
}
|
||||
func MigrateLegacyConfig(root string, options RuntimeValidationOptions) (LegacyMigrationResult, error) {
|
||||
return migrateLegacyConfig(root, options, false)
|
||||
}
|
||||
|
||||
func migrateLegacyConfig(root string, options RuntimeValidationOptions, completeSetup bool) (LegacyMigrationResult, error) {
|
||||
plan, err := PlanLegacyMigration(root, options)
|
||||
if err != nil {
|
||||
return LegacyMigrationResult{}, err
|
||||
@ -168,6 +172,10 @@ func MigrateLegacyConfig(root string, options RuntimeValidationOptions) (LegacyM
|
||||
if err := json.Unmarshal(legacyData, &legacy); err != nil {
|
||||
return LegacyMigrationResult{}, err
|
||||
}
|
||||
if completeSetup {
|
||||
legacy.SetupState = SetupStateReady
|
||||
legacy.IsInit = true
|
||||
}
|
||||
runtime, manualFields, err := legacyRuntimeConfig(root, legacy)
|
||||
if err != nil {
|
||||
return LegacyMigrationResult{}, err
|
||||
|
||||
30
backend/config/setup_finalize.go
Normal file
30
backend/config/setup_finalize.go
Normal file
@ -0,0 +1,30 @@
|
||||
package config
|
||||
|
||||
// FinalizeSetupConfig 先保存可重试的安装状态,再以分区根文件发布就绪状态。
|
||||
// 只由完成安装的流程调用,不自动迁移已有的运行实例。
|
||||
func FinalizeSetupConfig(cfg *Config) error {
|
||||
legacyConfigMu.Lock()
|
||||
defer legacyConfigMu.Unlock()
|
||||
if cfg == nil {
|
||||
return ErrInvalidRuntimeConfig
|
||||
}
|
||||
|
||||
pending := *cfg
|
||||
pending.SetupState = SetupStateBootstrap
|
||||
pending.IsInit = false
|
||||
if err := writeConfig(&pending); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := migrateLegacyConfig(ROOT_PATH, RuntimeValidationOptions{
|
||||
HasSuperAdmin: true,
|
||||
Production: !DevelopmentMode,
|
||||
}, true); err != nil {
|
||||
return err
|
||||
}
|
||||
loaded, err := loadPartitionedCompatibility()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
PublishConfigSnapshot(loaded)
|
||||
return nil
|
||||
}
|
||||
@ -40,7 +40,7 @@ func Finish() error {
|
||||
cfg.LoginPolicy = "new_device"
|
||||
}
|
||||
|
||||
err = config.WriteConfig(cfg)
|
||||
err = config.FinalizeSetupConfig(cfg)
|
||||
if err != nil {
|
||||
return errors.Wrap(err)
|
||||
}
|
||||
|
||||
@ -20,6 +20,10 @@ func TestFinishPersistsServicePorts(t *testing.T) {
|
||||
if err := config.WriteConfig(&config.Config{
|
||||
SetupState: config.SetupStateBootstrap,
|
||||
HttpsEnabled: 1,
|
||||
Domain: "example.com",
|
||||
WebDomain: "mail.example.com",
|
||||
DbType: config.DBTypeSQLite,
|
||||
DbDSN: filepath.Join(config.ROOT_PATH, "data", "smail.db"),
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@ -40,6 +44,14 @@ func TestFinishPersistsServicePorts(t *testing.T) {
|
||||
if !loaded.VerificationEnabled || !loaded.ExternalRecoveryEnabled || loaded.LoginPolicy != "new_device" {
|
||||
t.Fatalf("security defaults = verification %t, recovery %t, policy %q", loaded.VerificationEnabled, loaded.ExternalRecoveryEnabled, loaded.LoginPolicy)
|
||||
}
|
||||
for _, name := range []string{"config.json", "email.json", "smtp.json", "ssl.json", "plugins.json"} {
|
||||
if _, err := os.Stat(filepath.Join(config.ROOT_PATH, "config", name)); err != nil {
|
||||
t.Fatalf("partition %s: %v", name, err)
|
||||
}
|
||||
}
|
||||
if err := Finish(); err != nil {
|
||||
t.Fatalf("repeated completion failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFinishPreservesExplicitlyDisabledSecurityOptions(t *testing.T) {
|
||||
@ -75,6 +87,10 @@ func TestFinishRepairsReadyConfigWithMissingPorts(t *testing.T) {
|
||||
SetupState: config.SetupStateReady,
|
||||
IsInit: true,
|
||||
HttpsEnabled: 1,
|
||||
Domain: "example.com",
|
||||
WebDomain: "mail.example.com",
|
||||
DbType: config.DBTypeSQLite,
|
||||
DbDSN: filepath.Join(config.ROOT_PATH, "data", "smail.db"),
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@ -101,6 +117,10 @@ func TestFinishKeepsInternalHTTPSDisabledInProxyMode(t *testing.T) {
|
||||
TLSMode: config.HTTPSTLSModeProxy,
|
||||
HttpsPort: 9443,
|
||||
HttpsEnabled: 1,
|
||||
Domain: "example.com",
|
||||
WebDomain: "mail.example.com",
|
||||
DbType: config.DBTypeSQLite,
|
||||
DbDSN: filepath.Join(config.ROOT_PATH, "data", "smail.db"),
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@ -117,6 +137,35 @@ func TestFinishKeepsInternalHTTPSDisabledInProxyMode(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestFinishRejectsInvalidConfigBeforeReady(t *testing.T) {
|
||||
prepareFinishConfigTest(t)
|
||||
if err := config.WriteConfig(&config.Config{
|
||||
SetupState: config.SetupStateBootstrap,
|
||||
DbType: config.DBTypeSQLite,
|
||||
DbDSN: filepath.Join(config.ROOT_PATH, "data", "smail.db"),
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := Finish(); err == nil {
|
||||
t.Fatal("missing domains must prevent setup completion")
|
||||
}
|
||||
loaded, err := config.ReadConfig()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if loaded.IsSetupReady() || loaded.IsInit || config.IsSetupReady() {
|
||||
t.Fatal("failed completion published ready state")
|
||||
}
|
||||
loaded.Domain = "example.com"
|
||||
loaded.WebDomain = "mail.example.com"
|
||||
if err := config.WriteConfig(loaded); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := Finish(); err != nil {
|
||||
t.Fatalf("corrected setup cannot retry: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func prepareFinishConfigTest(t *testing.T) {
|
||||
t.Helper()
|
||||
previousRoot := config.ROOT_PATH
|
||||
|
||||
Loading…
Reference in New Issue
Block a user