mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-05-06 20:03:05 +08:00
315 lines
9.1 KiB
Go
315 lines
9.1 KiB
Go
package app
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
aiservice "GoNavi-Wails/internal/ai/service"
|
|
"GoNavi-Wails/internal/connection"
|
|
"GoNavi-Wails/internal/secretstore"
|
|
)
|
|
|
|
const (
|
|
securityUpdateAIConfigFileName = "ai_config.json"
|
|
securityUpdateAIProviderSecretKind = "ai-provider"
|
|
)
|
|
|
|
type securityUpdateSecretSnapshot struct {
|
|
Exists bool
|
|
Payload []byte
|
|
}
|
|
|
|
type securityUpdateCurrentAppRollbackSnapshot struct {
|
|
connectionsFileExists bool
|
|
connectionsFileData []byte
|
|
connectionSecrets map[string]securityUpdateSecretSnapshot
|
|
connectionCleanupRefs []string
|
|
|
|
globalProxyFileExists bool
|
|
globalProxyFileData []byte
|
|
globalProxySecretRef string
|
|
globalProxySecret securityUpdateSecretSnapshot
|
|
globalProxyCleanupRef string
|
|
|
|
aiConfigFileExists bool
|
|
aiConfigFileData []byte
|
|
aiProviderSecrets map[string]securityUpdateSecretSnapshot
|
|
aiProviderCleanupRefs []string
|
|
}
|
|
|
|
func captureSecurityUpdateCurrentAppRollbackSnapshot(a *App, source securityUpdateCurrentAppSource) (securityUpdateCurrentAppRollbackSnapshot, error) {
|
|
snapshot := securityUpdateCurrentAppRollbackSnapshot{
|
|
connectionSecrets: make(map[string]securityUpdateSecretSnapshot),
|
|
aiProviderSecrets: make(map[string]securityUpdateSecretSnapshot),
|
|
}
|
|
configDir := strings.TrimSpace(a.configDir)
|
|
if configDir == "" {
|
|
configDir = resolveAppConfigDir()
|
|
}
|
|
|
|
connectionRepo := a.savedConnectionRepository()
|
|
connectionFileData, connectionFileExists, err := readOptionalFile(connectionRepo.connectionsPath())
|
|
if err != nil {
|
|
return snapshot, err
|
|
}
|
|
snapshot.connectionsFileExists = connectionFileExists
|
|
snapshot.connectionsFileData = connectionFileData
|
|
|
|
existingConnections, err := connectionRepo.load()
|
|
if err != nil {
|
|
return snapshot, err
|
|
}
|
|
existingConnectionsByID := make(map[string]connection.SavedConnectionView, len(existingConnections))
|
|
for _, item := range existingConnections {
|
|
existingConnectionsByID[item.ID] = item
|
|
}
|
|
|
|
connectionCleanupSet := make(map[string]struct{})
|
|
for _, item := range source.Connections {
|
|
connectionID := strings.TrimSpace(item.ID)
|
|
if connectionID == "" {
|
|
connectionID = strings.TrimSpace(item.Config.ID)
|
|
}
|
|
if connectionID == "" {
|
|
continue
|
|
}
|
|
|
|
defaultRef, refErr := secretstore.BuildRef(savedConnectionSecretKind, connectionID)
|
|
if refErr == nil {
|
|
connectionCleanupSet[defaultRef] = struct{}{}
|
|
}
|
|
|
|
existing, ok := existingConnectionsByID[connectionID]
|
|
if !ok || !savedConnectionViewHasSecrets(existing) {
|
|
continue
|
|
}
|
|
|
|
ref := strings.TrimSpace(existing.SecretRef)
|
|
if ref == "" {
|
|
ref = defaultRef
|
|
}
|
|
if ref == "" {
|
|
continue
|
|
}
|
|
|
|
secretSnapshot, captureErr := captureSecurityUpdateSecretSnapshot(a.secretStore, ref)
|
|
if captureErr != nil {
|
|
return snapshot, captureErr
|
|
}
|
|
snapshot.connectionSecrets[ref] = secretSnapshot
|
|
connectionCleanupSet[ref] = struct{}{}
|
|
}
|
|
|
|
snapshot.connectionCleanupRefs = make([]string, 0, len(connectionCleanupSet))
|
|
for ref := range connectionCleanupSet {
|
|
snapshot.connectionCleanupRefs = append(snapshot.connectionCleanupRefs, ref)
|
|
}
|
|
|
|
if source.GlobalProxy != nil {
|
|
globalProxyFileData, globalProxyFileExists, err := readOptionalFile(globalProxyMetadataPath(configDir))
|
|
if err != nil {
|
|
return snapshot, err
|
|
}
|
|
snapshot.globalProxyFileExists = globalProxyFileExists
|
|
snapshot.globalProxyFileData = globalProxyFileData
|
|
|
|
defaultProxyRef, refErr := secretstore.BuildRef(globalProxySecretKind, globalProxySecretID)
|
|
if refErr == nil {
|
|
snapshot.globalProxyCleanupRef = defaultProxyRef
|
|
}
|
|
|
|
existingProxy, err := a.loadStoredGlobalProxyView()
|
|
if err != nil {
|
|
if !os.IsNotExist(err) {
|
|
return snapshot, err
|
|
}
|
|
} else if existingProxy.HasPassword {
|
|
ref := strings.TrimSpace(existingProxy.SecretRef)
|
|
if ref == "" {
|
|
ref = snapshot.globalProxyCleanupRef
|
|
}
|
|
if ref != "" {
|
|
secretSnapshot, captureErr := captureSecurityUpdateSecretSnapshot(a.secretStore, ref)
|
|
if captureErr != nil {
|
|
return snapshot, captureErr
|
|
}
|
|
snapshot.globalProxySecretRef = ref
|
|
snapshot.globalProxySecret = secretSnapshot
|
|
}
|
|
}
|
|
}
|
|
|
|
aiConfigPath := filepath.Join(configDir, securityUpdateAIConfigFileName)
|
|
aiConfigFileData, aiConfigFileExists, err := readOptionalFile(aiConfigPath)
|
|
if err != nil {
|
|
return snapshot, err
|
|
}
|
|
snapshot.aiConfigFileExists = aiConfigFileExists
|
|
snapshot.aiConfigFileData = aiConfigFileData
|
|
|
|
inspection, err := aiservice.NewProviderConfigStore(configDir, a.secretStore).Inspect()
|
|
if err != nil {
|
|
return snapshot, err
|
|
}
|
|
aiProviderCleanupSet := make(map[string]struct{})
|
|
for _, provider := range inspection.Snapshot.Providers {
|
|
providerID := strings.TrimSpace(provider.ID)
|
|
if providerID == "" {
|
|
continue
|
|
}
|
|
|
|
ref := strings.TrimSpace(provider.SecretRef)
|
|
if ref == "" && (provider.HasSecret || strings.TrimSpace(provider.APIKey) != "" || len(provider.Headers) > 0) {
|
|
builtRef, refErr := secretstore.BuildRef(securityUpdateAIProviderSecretKind, providerID)
|
|
if refErr == nil {
|
|
ref = builtRef
|
|
}
|
|
}
|
|
if ref == "" {
|
|
continue
|
|
}
|
|
|
|
secretSnapshot, captureErr := captureSecurityUpdateSecretSnapshot(a.secretStore, ref)
|
|
if captureErr != nil {
|
|
return snapshot, captureErr
|
|
}
|
|
snapshot.aiProviderSecrets[ref] = secretSnapshot
|
|
aiProviderCleanupSet[ref] = struct{}{}
|
|
}
|
|
snapshot.aiProviderCleanupRefs = make([]string, 0, len(aiProviderCleanupSet))
|
|
for ref := range aiProviderCleanupSet {
|
|
snapshot.aiProviderCleanupRefs = append(snapshot.aiProviderCleanupRefs, ref)
|
|
}
|
|
return snapshot, nil
|
|
}
|
|
|
|
func (s securityUpdateCurrentAppRollbackSnapshot) restore(a *App) error {
|
|
configDir := strings.TrimSpace(a.configDir)
|
|
if configDir == "" {
|
|
configDir = resolveAppConfigDir()
|
|
}
|
|
connectionRepo := a.savedConnectionRepository()
|
|
if err := restoreOptionalFile(connectionRepo.connectionsPath(), s.connectionsFileExists, s.connectionsFileData); err != nil {
|
|
return err
|
|
}
|
|
for ref, secretSnapshot := range s.connectionSecrets {
|
|
if err := restoreSecurityUpdateSecretSnapshot(a.secretStore, ref, secretSnapshot); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
for _, ref := range s.connectionCleanupRefs {
|
|
if _, alreadyRestored := s.connectionSecrets[ref]; alreadyRestored {
|
|
continue
|
|
}
|
|
if err := deleteSecurityUpdateSecretRef(a.secretStore, ref); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if err := restoreOptionalFile(globalProxyMetadataPath(configDir), s.globalProxyFileExists, s.globalProxyFileData); err != nil {
|
|
return err
|
|
}
|
|
if s.globalProxySecretRef != "" {
|
|
if err := restoreSecurityUpdateSecretSnapshot(a.secretStore, s.globalProxySecretRef, s.globalProxySecret); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if s.globalProxyCleanupRef != "" && s.globalProxyCleanupRef != s.globalProxySecretRef {
|
|
if err := deleteSecurityUpdateSecretRef(a.secretStore, s.globalProxyCleanupRef); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if err := restoreOptionalFile(filepath.Join(configDir, securityUpdateAIConfigFileName), s.aiConfigFileExists, s.aiConfigFileData); err != nil {
|
|
return err
|
|
}
|
|
for ref, secretSnapshot := range s.aiProviderSecrets {
|
|
if err := restoreSecurityUpdateSecretSnapshot(a.secretStore, ref, secretSnapshot); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
for _, ref := range s.aiProviderCleanupRefs {
|
|
if _, alreadyRestored := s.aiProviderSecrets[ref]; alreadyRestored {
|
|
continue
|
|
}
|
|
if err := deleteSecurityUpdateSecretRef(a.secretStore, ref); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if s.globalProxyFileExists {
|
|
a.loadPersistedGlobalProxy()
|
|
return nil
|
|
}
|
|
_, err := setGlobalProxyConfig(false, connection.ProxyConfig{})
|
|
return err
|
|
}
|
|
|
|
func readOptionalFile(path string) ([]byte, bool, error) {
|
|
data, err := os.ReadFile(path)
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
return nil, false, nil
|
|
}
|
|
return nil, false, err
|
|
}
|
|
return append([]byte(nil), data...), true, nil
|
|
}
|
|
|
|
func restoreOptionalFile(path string, exists bool, data []byte) error {
|
|
if !exists {
|
|
if err := os.Remove(path); err != nil && !os.IsNotExist(err) {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
return os.WriteFile(path, data, 0o644)
|
|
}
|
|
|
|
func captureSecurityUpdateSecretSnapshot(store secretstore.SecretStore, ref string) (securityUpdateSecretSnapshot, error) {
|
|
if store == nil || strings.TrimSpace(ref) == "" {
|
|
return securityUpdateSecretSnapshot{}, nil
|
|
}
|
|
payload, err := store.Get(ref)
|
|
if err != nil {
|
|
if os.IsNotExist(err) || secretstore.IsUnavailable(err) {
|
|
return securityUpdateSecretSnapshot{}, nil
|
|
}
|
|
return securityUpdateSecretSnapshot{}, err
|
|
}
|
|
return securityUpdateSecretSnapshot{
|
|
Exists: true,
|
|
Payload: append([]byte(nil), payload...),
|
|
}, nil
|
|
}
|
|
|
|
func restoreSecurityUpdateSecretSnapshot(store secretstore.SecretStore, ref string, snapshot securityUpdateSecretSnapshot) error {
|
|
if store == nil || strings.TrimSpace(ref) == "" {
|
|
return nil
|
|
}
|
|
if snapshot.Exists {
|
|
if err := store.Put(ref, snapshot.Payload); err != nil {
|
|
if secretstore.IsUnavailable(err) {
|
|
return nil
|
|
}
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
return deleteSecurityUpdateSecretRef(store, ref)
|
|
}
|
|
|
|
func deleteSecurityUpdateSecretRef(store secretstore.SecretStore, ref string) error {
|
|
if store == nil || strings.TrimSpace(ref) == "" {
|
|
return nil
|
|
}
|
|
if err := store.Delete(ref); err != nil {
|
|
if os.IsNotExist(err) || secretstore.IsUnavailable(err) {
|
|
return nil
|
|
}
|
|
return err
|
|
}
|
|
return nil
|
|
}
|