From e998b1229aaa18d3189338b6b153d35238758ed3 Mon Sep 17 00:00:00 2001 From: Luis Pater Date: Wed, 31 Dec 2025 11:51:20 +0800 Subject: [PATCH] feat(updater): add fallback URL and logic for missing management asset --- internal/managementasset/updater.go | 54 +++++++++++++++++++++++++---- 1 file changed, 47 insertions(+), 7 deletions(-) diff --git a/internal/managementasset/updater.go b/internal/managementasset/updater.go index 420c110f..c941da02 100644 --- a/internal/managementasset/updater.go +++ b/internal/managementasset/updater.go @@ -24,10 +24,11 @@ import ( ) const ( - defaultManagementReleaseURL = "https://api.github.com/repos/router-for-me/Cli-Proxy-API-Management-Center/releases/latest" - managementAssetName = "management.html" - httpUserAgent = "CLIProxyAPI-management-updater" - updateCheckInterval = 3 * time.Hour + defaultManagementReleaseURL = "https://api.github.com/repos/router-for-me/Cli-Proxy-API-Management-Center/releases/latest" + defaultManagementFallbackURL = "https://cpamc.router-for.me/" + managementAssetName = "management.html" + httpUserAgent = "CLIProxyAPI-management-updater" + updateCheckInterval = 3 * time.Hour ) // ManagementFileName exposes the control panel asset filename. @@ -198,6 +199,16 @@ func EnsureLatestManagementHTML(ctx context.Context, staticDir string, proxyURL return } + localPath := filepath.Join(staticDir, managementAssetName) + localFileMissing := false + if _, errStat := os.Stat(localPath); errStat != nil { + if errors.Is(errStat, os.ErrNotExist) { + localFileMissing = true + } else { + log.WithError(errStat).Debug("failed to stat local management asset") + } + } + // Rate limiting: check only once every 3 hours lastUpdateCheckMu.Lock() now := time.Now() @@ -210,15 +221,14 @@ func EnsureLatestManagementHTML(ctx context.Context, staticDir string, proxyURL lastUpdateCheckTime = now lastUpdateCheckMu.Unlock() - if err := os.MkdirAll(staticDir, 0o755); err != nil { - log.WithError(err).Warn("failed to prepare static directory for management asset") + if errMkdirAll := os.MkdirAll(staticDir, 0o755); errMkdirAll != nil { + log.WithError(errMkdirAll).Warn("failed to prepare static directory for management asset") return } releaseURL := resolveReleaseURL(panelRepository) client := newHTTPClient(proxyURL) - localPath := filepath.Join(staticDir, managementAssetName) localHash, err := fileSHA256(localPath) if err != nil { if !errors.Is(err, os.ErrNotExist) { @@ -229,6 +239,13 @@ func EnsureLatestManagementHTML(ctx context.Context, staticDir string, proxyURL asset, remoteHash, err := fetchLatestAsset(ctx, client, releaseURL) if err != nil { + if localFileMissing { + log.WithError(err).Warn("failed to fetch latest management release information, trying fallback page") + if ensureFallbackManagementHTML(ctx, client, localPath) { + return + } + return + } log.WithError(err).Warn("failed to fetch latest management release information") return } @@ -240,6 +257,13 @@ func EnsureLatestManagementHTML(ctx context.Context, staticDir string, proxyURL data, downloadedHash, err := downloadAsset(ctx, client, asset.BrowserDownloadURL) if err != nil { + if localFileMissing { + log.WithError(err).Warn("failed to download management asset, trying fallback page") + if ensureFallbackManagementHTML(ctx, client, localPath) { + return + } + return + } log.WithError(err).Warn("failed to download management asset") return } @@ -256,6 +280,22 @@ func EnsureLatestManagementHTML(ctx context.Context, staticDir string, proxyURL log.Infof("management asset updated successfully (hash=%s)", downloadedHash) } +func ensureFallbackManagementHTML(ctx context.Context, client *http.Client, localPath string) bool { + data, downloadedHash, err := downloadAsset(ctx, client, defaultManagementFallbackURL) + if err != nil { + log.WithError(err).Warn("failed to download fallback management control panel page") + return false + } + + if err = atomicWriteFile(localPath, data); err != nil { + log.WithError(err).Warn("failed to persist fallback management control panel page") + return false + } + + log.Infof("management asset updated from fallback page successfully (hash=%s)", downloadedHash) + return true +} + func resolveReleaseURL(repo string) string { repo = strings.TrimSpace(repo) if repo == "" {