package app import ( "encoding/json" "errors" "fmt" "os" "path/filepath" "strings" ) func transportMaybeMigrateSingBoxDNSConfig(client TransportClient) (string, error) { if client.Kind != TransportClientSingBox { return "", nil } if !transportSingBoxDNSMigrationEnabled(client) { return "", nil } path := transportSingBoxConfigPath(client) if path == "" { return "", nil } data, err := os.ReadFile(path) if err != nil { return "", fmt.Errorf("read singbox config failed: %w", err) } var root map[string]any if err := json.Unmarshal(data, &root); err != nil { return "", fmt.Errorf("parse singbox config failed: %w", err) } changed, warnings := transportMigrateSingBoxDNSConfigMap(root) if !changed { if len(warnings) == 0 { return "", nil } return "", errors.New(strings.Join(warnings, "; ")) } backup := path + ".legacy-dns.bak" if _, err := os.Stat(backup); os.IsNotExist(err) { if writeErr := os.WriteFile(backup, data, 0o644); writeErr != nil { return "", fmt.Errorf("write backup failed: %w", writeErr) } } out, err := json.MarshalIndent(root, "", " ") if err != nil { return "", fmt.Errorf("marshal migrated config failed: %w", err) } tmp := path + ".tmp" if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil { return "", fmt.Errorf("ensure config dir failed: %w", err) } if err := os.WriteFile(tmp, append(out, '\n'), 0o644); err != nil { return "", fmt.Errorf("write migrated config failed: %w", err) } if err := os.Rename(tmp, path); err != nil { return "", fmt.Errorf("replace migrated config failed: %w", err) } msg := "singbox dns migrated to new server format" if len(warnings) > 0 { return msg, errors.New(strings.Join(warnings, "; ")) } return msg, nil }