mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-07-13 08:25:10 +08:00
- 更新检查前恢复持久化全局代理,避免重启后代理状态未进入运行时 - 将 DNS、EOF 和临时网络异常转为本地化错误,并对可重试网络错误做短重试 - 优先读取 GitHub asset digest,缺失时再回退 SHA256SUMS 校验文件 - 新增全局代理连接测试后端接口,支持草稿代理、已保存密码复用和清除密码意图 - 更新 Wails 绑定、浏览器 mock 与代理草稿序列化测试
228 lines
6.2 KiB
Go
228 lines
6.2 KiB
Go
package app
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"GoNavi-Wails/internal/connection"
|
|
"GoNavi-Wails/internal/logger"
|
|
"GoNavi-Wails/internal/secretstore"
|
|
)
|
|
|
|
const (
|
|
globalProxyFileName = "global_proxy.json"
|
|
globalProxySecretKind = "global-proxy"
|
|
globalProxySecretID = "default"
|
|
)
|
|
|
|
type globalProxySecretBundle struct {
|
|
Password string `json:"password,omitempty"`
|
|
}
|
|
|
|
func globalProxyMetadataPath(configDir string) string {
|
|
return filepath.Join(configDir, globalProxyFileName)
|
|
}
|
|
|
|
func (a *App) saveGlobalProxy(input connection.SaveGlobalProxyInput) (connection.GlobalProxyView, error) {
|
|
if strings.TrimSpace(a.configDir) == "" {
|
|
a.configDir = resolveAppConfigDir()
|
|
}
|
|
|
|
existing, err := a.loadStoredGlobalProxyView()
|
|
if err != nil && !os.IsNotExist(err) {
|
|
return connection.GlobalProxyView{}, err
|
|
}
|
|
|
|
view := connection.GlobalProxyView{
|
|
Enabled: input.Enabled,
|
|
Type: strings.TrimSpace(input.Type),
|
|
Host: strings.TrimSpace(input.Host),
|
|
Port: input.Port,
|
|
User: strings.TrimSpace(input.User),
|
|
}
|
|
|
|
bundle := globalProxySecretBundle{}
|
|
if strings.TrimSpace(input.Password) != "" {
|
|
bundle.Password = input.Password
|
|
} else if existing.HasPassword && !input.ClearPassword {
|
|
existingBundle, loadErr := a.loadGlobalProxySecretBundle(existing)
|
|
if loadErr != nil {
|
|
return connection.GlobalProxyView{}, loadErr
|
|
}
|
|
bundle = existingBundle
|
|
}
|
|
|
|
if strings.TrimSpace(bundle.Password) != "" {
|
|
if storeErr := a.dailySecretStore().PutGlobalProxy(toDailyGlobalProxyBundle(bundle)); storeErr != nil {
|
|
return connection.GlobalProxyView{}, storeErr
|
|
}
|
|
view.HasPassword = true
|
|
} else {
|
|
if deleteErr := a.dailySecretStore().DeleteGlobalProxy(); deleteErr != nil {
|
|
return connection.GlobalProxyView{}, deleteErr
|
|
}
|
|
view.HasPassword = false
|
|
}
|
|
view.SecretRef = ""
|
|
view.Password = ""
|
|
|
|
if err := a.persistGlobalProxyView(view); err != nil {
|
|
return connection.GlobalProxyView{}, err
|
|
}
|
|
if !view.Enabled {
|
|
if _, err := setGlobalProxyConfig(false, connection.ProxyConfig{}); err != nil {
|
|
return connection.GlobalProxyView{}, err
|
|
}
|
|
return sanitizeGlobalProxyView(view), nil
|
|
}
|
|
if _, err := setGlobalProxyConfig(true, connection.ProxyConfig{
|
|
Type: view.Type,
|
|
Host: view.Host,
|
|
Port: view.Port,
|
|
User: view.User,
|
|
Password: bundle.Password,
|
|
}); err != nil {
|
|
return connection.GlobalProxyView{}, err
|
|
}
|
|
return sanitizeGlobalProxyView(view), nil
|
|
}
|
|
|
|
func (a *App) persistGlobalProxyView(view connection.GlobalProxyView) error {
|
|
if err := os.MkdirAll(a.configDir, 0o755); err != nil {
|
|
return err
|
|
}
|
|
payload, err := json.MarshalIndent(view, "", " ")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return os.WriteFile(globalProxyMetadataPath(a.configDir), payload, 0o644)
|
|
}
|
|
|
|
func (a *App) loadStoredGlobalProxyView() (connection.GlobalProxyView, error) {
|
|
data, err := os.ReadFile(globalProxyMetadataPath(a.configDir))
|
|
if err != nil {
|
|
return connection.GlobalProxyView{}, err
|
|
}
|
|
var view connection.GlobalProxyView
|
|
if err := json.Unmarshal(data, &view); err != nil {
|
|
return connection.GlobalProxyView{}, err
|
|
}
|
|
return view, nil
|
|
}
|
|
|
|
func (a *App) loadGlobalProxySecretBundle(view connection.GlobalProxyView) (globalProxySecretBundle, error) {
|
|
inline := extractGlobalProxySecretBundle(view)
|
|
if strings.TrimSpace(inline.Password) != "" {
|
|
return inline, nil
|
|
}
|
|
if !view.HasPassword {
|
|
return globalProxySecretBundle{}, nil
|
|
}
|
|
bundle, ok, err := a.dailySecretStore().GetGlobalProxy()
|
|
if err != nil {
|
|
return globalProxySecretBundle{}, err
|
|
}
|
|
if ok {
|
|
return fromDailyGlobalProxyBundle(bundle), nil
|
|
}
|
|
return globalProxySecretBundle{}, os.ErrNotExist
|
|
}
|
|
|
|
func (a *App) resolveStoredGlobalProxyRuntimeConfig(view connection.GlobalProxyView) (connection.ProxyConfig, error) {
|
|
proxyConfig := connection.ProxyConfig{
|
|
Type: view.Type,
|
|
Host: view.Host,
|
|
Port: view.Port,
|
|
User: view.User,
|
|
}
|
|
if !view.HasPassword {
|
|
return proxyConfig, nil
|
|
}
|
|
bundle, err := a.loadGlobalProxySecretBundle(view)
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
logger.Warnf("全局代理标记了已保存密码,但密文不存在,将尝试按无认证代理恢复")
|
|
return proxyConfig, nil
|
|
}
|
|
return connection.ProxyConfig{}, err
|
|
}
|
|
proxyConfig.Password = bundle.Password
|
|
return proxyConfig, nil
|
|
}
|
|
|
|
func (a *App) loadGlobalProxySecretBundleFromStore(view connection.GlobalProxyView) (globalProxySecretBundle, error) {
|
|
if a.secretStore == nil {
|
|
return globalProxySecretBundle{}, fmt.Errorf("secret store unavailable")
|
|
}
|
|
ref := strings.TrimSpace(view.SecretRef)
|
|
if ref == "" {
|
|
var err error
|
|
ref, err = secretstore.BuildRef(globalProxySecretKind, globalProxySecretID)
|
|
if err != nil {
|
|
return globalProxySecretBundle{}, err
|
|
}
|
|
}
|
|
payload, err := a.secretStore.Get(ref)
|
|
if err != nil {
|
|
return globalProxySecretBundle{}, err
|
|
}
|
|
var bundle globalProxySecretBundle
|
|
if err := json.Unmarshal(payload, &bundle); err != nil {
|
|
return globalProxySecretBundle{}, err
|
|
}
|
|
return bundle, nil
|
|
}
|
|
|
|
func (a *App) storeGlobalProxySecret(existingRef string, bundle globalProxySecretBundle) (string, error) {
|
|
if a.secretStore == nil {
|
|
return "", fmt.Errorf("secret store unavailable")
|
|
}
|
|
if err := a.secretStore.HealthCheck(); err != nil {
|
|
return "", err
|
|
}
|
|
ref := strings.TrimSpace(existingRef)
|
|
if ref == "" {
|
|
var err error
|
|
ref, err = secretstore.BuildRef(globalProxySecretKind, globalProxySecretID)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
}
|
|
payload, err := json.Marshal(bundle)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if err := a.secretStore.Put(ref, payload); err != nil {
|
|
return "", err
|
|
}
|
|
return ref, nil
|
|
}
|
|
|
|
func (a *App) loadPersistedGlobalProxy() {
|
|
view, err := a.loadStoredGlobalProxyView()
|
|
if err != nil {
|
|
if !os.IsNotExist(err) {
|
|
logger.Error(err, "加载全局代理元数据失败")
|
|
}
|
|
return
|
|
}
|
|
if !view.Enabled {
|
|
if _, err := setGlobalProxyConfig(false, connection.ProxyConfig{}); err != nil {
|
|
logger.Error(err, "恢复全局代理关闭状态失败")
|
|
}
|
|
return
|
|
}
|
|
|
|
proxyConfig, err := a.resolveStoredGlobalProxyRuntimeConfig(view)
|
|
if err != nil {
|
|
logger.Error(err, "加载全局代理密码失败")
|
|
return
|
|
}
|
|
if _, err := setGlobalProxyConfig(view.Enabled, proxyConfig); err != nil {
|
|
logger.Error(err, "恢复全局代理配置失败")
|
|
}
|
|
}
|