Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Add GET /api/stats, GET /api/system, and authenticated /metrics - #683

Merged
davidnewhall merged 6 commits into
dn2_http_authfrom
dn2_api_stats
Sep 7, 2026
Merged

Add GET /api/stats, GET /api/system, and authenticated /metrics#683
davidnewhall merged 6 commits into
dn2_http_authfrom
dn2_api_stats

Conversation

@davidnewhall

Copy link
Copy Markdown
Collaborator

Summary

  • GET /api/stats (permission read:system:stats) returns queue counters for a Homepage custom API widget. Closes Possible to query the api for gethomepage widget? #640.
  • GET /api/system (read:system:info) returns version, uptime, listen address, and auth mode.
  • /metrics now requires an API key with read:system:metrics via Authorization: Bearer or X-Api-Key. This is a breaking change for existing Prometheus scrapes.

Test plan

  • go test ./pkg/unpackerr/ ./pkg/configdef/
  • golangci-lint run ./pkg/unpackerr/ ./pkg/configdef/
  • Homepage customapi against /api/stats with a stats-only key
  • Stats-only key cannot hit /api/system or /metrics
  • Prometheus bearer_token scrapes /metrics successfully

Made with Cursor

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The stats race and incomplete API-key-only enforcement must be fixed before approval.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

Adds permission-protected system statistics and information APIs and authentication for Prometheus metrics.

Changes:

  • Adds /api/stats and /api/system.
  • Protects endpoints with permission middleware.
  • Documents /metrics API-key authentication and adds tests.
File summaries
File Review
pkg/unpackerr/webserver.go Registers the new APIs and protects metrics.
pkg/unpackerr/metrics.go Adds JSON fields and aggregate totals.
pkg/unpackerr/auth.go Moderate: Metrics authentication also accepts sessions and trusted proxies instead of enforcing API-key headers.
pkg/unpackerr/api.go Critical: Stats access can race with concurrent map updates and panic.
pkg/unpackerr/api_test.go Tests endpoint authentication and permissions.
pkg/configdef/definitions.yml Documents metrics authentication.
examples/unpackerr.conf.example Updates example configuration guidance.
Review details
  • Files reviewed: 7/7 changed files
  • Comments generated: 2
  • Review effort level: Balanced

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread pkg/unpackerr/api.go
}

func (u *Unpackerr) statsHandler(response http.ResponseWriter, _ *http.Request, _ httprouter.Params) {
writeJSON(response, http.StatusOK, u.stats())
Comment thread pkg/unpackerr/auth.go
Comment on lines +106 to +123
func (u *Unpackerr) requirePermHTTP(perm string, next http.Handler) http.Handler {
return http.HandlerFunc(func(response http.ResponseWriter, request *http.Request) {
info, ok := u.authenticate(request)
if !ok {
writeJSON(response, http.StatusUnauthorized, map[string]string{"error": "unauthorized"})

return
}

if !info.allows(perm) {
writeJSON(response, http.StatusForbidden, map[string]string{"error": "forbidden"})

return
}

next.ServeHTTP(response, request)
})
}

@qwen-pr-bot qwen-pr-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the full PR at 8cd25d1 against ae5fea7: the two new read-only endpoints, the permission middleware, the /metrics auth gate, the Stats JSON tags + Retries/Finished wiring, and the doc updates. Verdict: approve.

What I checked by reading:

  • requirePerm / requirePermHTTP (pkg/unpackerr/auth.go:83-122) — the deny paths are correct: unknown key → 401 via authenticate returning perms == nil; known key without the permission → 403; PermAll honored; session/webauth users get AllPermissions() as elsewhere. No bypass found in the X-Api-Key / Bearer extraction path.
  • Route registration with a non-root urlbase/base/api/stats + the existing /base/api/auth/* coexist in httprouter without a panic (verified live, not just by reading).
  • stats() is now reachable from HTTP handler goroutines in addition to the sync loop and Prometheus Collect. The unguarded u.Map / u.Retries / u.Finished reads are the same pre-existing exposure the scrape path always had — not introduced here, and race-detector runs came back clean.
  • systemInfo leaks nothing beyond version/uptime/listen addr/auth mode, all behind read:system:info. Fine.
  • Docs: examples/unpackerr.conf.example and definitions.yml both describe the new scrape requirement; the breaking /metrics change is called out in the description. One tiny non-blocking nit: the startup line in logWebserver still just says "Prometheus metrics enabled at /metrics" — worth mentioning the key requirement there too so operators aren't surprised by 401s in scrape logs.

Executed validation (at 8cd25d1, disposable checkout, nothing from the PR's non-test code executed beyond the compiler and the repo's own test binary):

  • go build ./... and go vet ./pkg/unpackerr/ ./pkg/configdef/ — clean.
  • go test -race -count=1 ./pkg/unpackerr/ ./pkg/configdef/ — both packages pass.
  • A throwaway harness I wrote (not committed) ran the real webRoutes() with Metrics: true and URLBase: /base/, then exercised the router directly: /metrics and /base/metrics → 401 unauthenticated, 403 with a stats-only key, 200 with the admin key via both X-Api-Key and Authorization: Bearer, and the response body is real promhttp output (unpackerr_gauges present). /base/api/stats → 200 with all 13 JSON keys; /base/api/system → 403 stats key / 200 admin. A password-login session cookie also reaches /metrics (200). One incident worth noting: my first harness run got 500s from /metrics because it skipped PollFolders(), leaving u.folders nil and panicking inside the pre-existing MetricsCollector.Collect — a harness artifact (production always initializes folders at startup), but it confirms Collect panics recover as a scrape error rather than killing the server.
  • Public CI fetched: gotest (ubuntu/macos/windows) and golangci-lint (linux/freebsd/windows/darwin) all completed success on this head.

Nothing blocking. Ship it.

qwen-pr-bot[bot]
qwen-pr-bot Bot previously approved these changes Sep 4, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

The breaking authentication change and broad concurrency updates require final human review.

Review details
  • Files reviewed: 19/19 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced

qwen-pr-bot[bot]
qwen-pr-bot Bot previously approved these changes Sep 4, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The inaccurate listen address and potential metrics-path data race are unresolved moderate issues.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details

Suppressed comments (1)

pkg/unpackerr/api_test.go:35

  • The stats endpoint test only verifies that the response can be unmarshaled into Stats; an empty or all-zero payload and incorrect/missing counter values still pass. Seed entries covering the queue statuses plus Retries/Finished, then assert the decoded values (and preferably the JSON field names), since those counters are the endpoint's primary contract.
	var stats Stats
	if err := json.Unmarshal(statsRec.Body.Bytes(), &stats); err != nil {
		t.Fatal(err)
  • Files reviewed: 19/19 changed files
  • Comments generated: 2
  • Review effort level: Balanced

Comment thread pkg/unpackerr/api.go Outdated
Comment thread pkg/unpackerr/auth.go Outdated
// cookies and trusted-proxy webauth/noauth are ignored.
func (u *Unpackerr) requirePermHTTP(perm string, next http.Handler) http.Handler {
return http.HandlerFunc(func(response http.ResponseWriter, request *http.Request) {
info, ok := u.authAPIKey(requestAPIKey(request))
qwen-pr-bot[bot]
qwen-pr-bot Bot previously approved these changes Sep 4, 2026
qwen-pr-bot[bot]
qwen-pr-bot Bot previously approved these changes Sep 4, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

There are confirmed functional bugs in the web routing (URLBase index handler) and in Starr extraction callbacks creating unintended Folder entries when map items are missing.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details

Suppressed comments (1)

pkg/unpackerr/handlers.go:342

  • After running remnant/metrics work outside the lock, handleXtractrCallback re-locks and updates status even if the Extract was removed; this again triggers updateQueueStatus's FolderString fallback for unknown names. Bail out early if the item is no longer present.
	u.lockHistory()
	defer u.unlockHistory()

	item = u.Map[resp.X.Name]

	switch {
	case remnants && remnantStatus == WAITING:
  • Files reviewed: 19/19 changed files
  • Comments generated: 2
  • Review effort level: Lite

Comment thread pkg/unpackerr/handlers.go
Comment on lines +294 to +299
u.lockHistory()

item := u.Map[resp.X.Name]
if resp.Done && item != nil {
u.updateMetrics(resp, item.App, item.URL)
} else if item != nil {
item.XProg.Archives = resp.Archives.Count() + resp.Extras.Count()
if !resp.Done {
if item != nil {
item.XProg.Archives = resp.Archives.Count() + resp.Extras.Count()
Comment thread pkg/unpackerr/webserver.go Outdated
qwen-pr-bot[bot]
qwen-pr-bot Bot previously approved these changes Sep 4, 2026
davidnewhall and others added 6 commits September 7, 2026 01:41
Homepage and Prometheus can share the same API keys; scrapes must send a key with read:system:metrics.

Co-authored-by: Cursor <[email protected]>
Holding it across RemoveAll blocked /api/stats and Prometheus scrapes for the duration of cleanup.

Co-authored-by: Cursor <[email protected]>
@davidnewhall
davidnewhall merged commit 1a140c2 into main Sep 7, 2026
22 checks passed
@davidnewhall
davidnewhall deleted the dn2_api_stats branch September 7, 2026 08:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

Possible to query the api for gethomepage widget?

2 participants