diff --git a/config.example.yaml b/config.example.yaml index 3718a07a..c78a2d75 100644 --- a/config.example.yaml +++ b/config.example.yaml @@ -25,6 +25,10 @@ remote-management: # Disable the bundled management control panel asset download and HTTP route when true. disable-control-panel: false + # Enable automatic periodic background updates of the management panel from GitHub (default: false). + # When disabled, the panel is only downloaded on first access if missing, and never auto-updated afterward. + # auto-update-panel: false + # GitHub repository for the management control panel. Accepts a repository URL or releases API URL. panel-github-repository: "https://github.com/router-for-me/Cli-Proxy-API-Management-Center" diff --git a/internal/config/config.go b/internal/config/config.go index a11c741e..b1772b1f 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -171,6 +171,9 @@ type RemoteManagement struct { SecretKey string `yaml:"secret-key"` // DisableControlPanel skips serving and syncing the bundled management UI when true. DisableControlPanel bool `yaml:"disable-control-panel"` + // AutoUpdatePanel enables automatic periodic background updates of the management panel asset from GitHub. + // When false (the default), the panel is only downloaded on first access if missing, and never auto-updated. + AutoUpdatePanel bool `yaml:"auto-update-panel"` // PanelGitHubRepository overrides the GitHub repository used to fetch the management panel asset. // Accepts either a repository URL (https://github.com/org/repo) or an API releases endpoint. PanelGitHubRepository string `yaml:"panel-github-repository"` diff --git a/internal/managementasset/updater.go b/internal/managementasset/updater.go index 7284b729..642dbf2f 100644 --- a/internal/managementasset/updater.go +++ b/internal/managementasset/updater.go @@ -31,6 +31,7 @@ const ( httpUserAgent = "CLIProxyAPI-management-updater" managementSyncMinInterval = 30 * time.Second updateCheckInterval = 3 * time.Hour + maxAssetDownloadSize = 10 << 20 // 10 MB safety limit for management asset downloads ) // ManagementFileName exposes the control panel asset filename. @@ -88,6 +89,10 @@ func runAutoUpdater(ctx context.Context) { log.Debug("management asset auto-updater skipped: control panel disabled") return } + if !cfg.RemoteManagement.AutoUpdatePanel { + log.Debug("management asset auto-updater skipped: auto-update-panel is disabled") + return + } configPath, _ := schedulerConfigPath.Load().(string) staticDir := StaticDir(configPath) @@ -259,7 +264,8 @@ func EnsureLatestManagementHTML(ctx context.Context, staticDir string, proxyURL } if remoteHash != "" && !strings.EqualFold(remoteHash, downloadedHash) { - log.Warnf("remote digest mismatch for management asset: expected %s got %s", remoteHash, downloadedHash) + log.Errorf("management asset digest mismatch: expected %s got %s — aborting update for safety", remoteHash, downloadedHash) + return nil, nil } if err = atomicWriteFile(localPath, data); err != nil { @@ -392,7 +398,7 @@ func downloadAsset(ctx context.Context, client *http.Client, downloadURL string) return nil, "", fmt.Errorf("unexpected download status %d: %s", resp.StatusCode, strings.TrimSpace(string(body))) } - data, err := io.ReadAll(resp.Body) + data, err := io.ReadAll(io.LimitReader(resp.Body, maxAssetDownloadSize)) if err != nil { return nil, "", fmt.Errorf("read download body: %w", err) }