package app import ( "path/filepath" transportcfgpkg "selective-vpn-api/app/transportcfg" ) type singBoxSecretsPatchResult struct { HasSecrets bool Masked map[string]string Changed bool Rollback func() } func applySingBoxSecretsPatch(profileID string, clear bool, updates map[string]string) (singBoxSecretsPatchResult, error) { id := sanitizeID(profileID) if id == "" { return singBoxSecretsPatchResult{}, errSingBoxProfileID() } prev, err := readSingBoxSecrets(id) if err != nil { return singBoxSecretsPatchResult{}, err } next := map[string]string{} if !clear { next = transportcfgpkg.CloneStringMap(prev) if next == nil { next = map[string]string{} } } for key, val := range transportcfgpkg.NormalizeSecretUpdates(updates) { if val == "" { delete(next, key) continue } next[key] = val } changed := !transportcfgpkg.EqualStringMap(prev, next) if changed { if err := writeSingBoxSecrets(id, next); err != nil { return singBoxSecretsPatchResult{}, err } } res := singBoxSecretsPatchResult{ HasSecrets: len(next) > 0, Masked: transportcfgpkg.MaskStringMap(next, "******"), Changed: changed, } if changed { rollbackPrev := transportcfgpkg.CloneStringMap(prev) res.Rollback = func() { _ = writeSingBoxSecrets(id, rollbackPrev) } } if !res.HasSecrets { res.Masked = nil } return res, nil } func readSingBoxSecrets(profileID string) (map[string]string, error) { id := sanitizeID(profileID) if id == "" { return nil, errSingBoxProfileID() } path := singBoxSecretsPath(id) return transportcfgpkg.ReadStringMapJSON(path) } func writeSingBoxSecrets(profileID string, secrets map[string]string) error { id := sanitizeID(profileID) if id == "" { return errSingBoxProfileID() } path := singBoxSecretsPath(id) return transportcfgpkg.WriteStringMapJSON(path, secrets, 0o700, 0o600) } func singBoxSecretsPath(profileID string) string { id := sanitizeID(profileID) if id == "" { return filepath.Join(singBoxSecretsRootDir, "invalid.json") } return filepath.Join(singBoxSecretsRootDir, id+".json") }