Charlie/project-charlie: Build image / config (push) Successful in 1s
Charlie/project-charlie: Deploy runners / config (push) Successful in 1s
Charlie/project-charlie: Deploy stack / config (push) Successful in 1s
Charlie/project-charlie: Deploy runners / deploy-morgott (push) Skipped
Charlie/project-charlie: Deploy runners / deploy-melina (push) Skipped
Charlie/project-charlie: Deploy stack / deploy (push) Skipped
Charlie/project-charlie: Build image / build (push) Successful in 1m26s
jarvis receives Grafana alert webhooks, runs a headless GitHub Copilot CLI agent grounded in the cloned Charlie repositories to produce an SRE triage per alert, and serves the results as an authenticated Atom feed for miniflux. - internal/alert: Grafana webhook payload model - internal/store: SQLite (pure-Go modernc) feed persistence, dedupe by id - internal/diagnose: Copilot CLI diagnoser (+ stub fallback) - internal/server: Bearer-auth webhook, serial worker, basic-auth Atom feed - cmd/jarvis: env config, graceful shutdown - Dockerfile + entrypoint: Go build + node/Copilot CLI runtime, clones Charlie repos into /context for agent grounding - .charlie/build.yml: build on melina Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: b1482a00-8151-4442-ab70-de276dec2182
61 lines
1.9 KiB
Go
61 lines
1.9 KiB
Go
// Package server wires jarvis's HTTP surface: the Grafana webhook
|
|
// receiver, a background diagnostic worker, and the authenticated Atom feed.
|
|
package server
|
|
|
|
import (
|
|
"crypto/subtle"
|
|
"net/http"
|
|
)
|
|
|
|
// Config holds the server's runtime configuration.
|
|
type Config struct {
|
|
// WebhookToken authenticates Grafana -> jarvis on POST /webhook/grafana.
|
|
// Sent by Grafana as a Bearer token (Authorization: Bearer <token>).
|
|
WebhookToken string
|
|
// FeedUser / FeedPassword gate the Atom feed with HTTP Basic auth
|
|
// (what miniflux connects with).
|
|
FeedUser string
|
|
FeedPassword string
|
|
// FeedTitle / FeedID / BaseURL describe the feed document.
|
|
FeedTitle string
|
|
FeedID string
|
|
BaseURL string
|
|
// FeedLimit caps how many entries the feed serves.
|
|
FeedLimit int
|
|
}
|
|
|
|
// bearerAuth wraps h, requiring "Authorization: Bearer <token>". If token is
|
|
// empty the endpoint is left open (useful for local dev only).
|
|
func bearerAuth(token string, h http.HandlerFunc) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
if token != "" {
|
|
const prefix = "Bearer "
|
|
got := r.Header.Get("Authorization")
|
|
if len(got) <= len(prefix) ||
|
|
subtle.ConstantTimeCompare([]byte(got[len(prefix):]), []byte(token)) != 1 {
|
|
http.Error(w, "unauthorized", http.StatusUnauthorized)
|
|
return
|
|
}
|
|
}
|
|
h(w, r)
|
|
}
|
|
}
|
|
|
|
// basicAuth wraps h with HTTP Basic auth. If user is empty the endpoint is
|
|
// left open (local dev only).
|
|
func basicAuth(user, pass string, h http.HandlerFunc) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
if user != "" {
|
|
u, p, ok := r.BasicAuth()
|
|
userOK := subtle.ConstantTimeCompare([]byte(u), []byte(user)) == 1
|
|
passOK := subtle.ConstantTimeCompare([]byte(p), []byte(pass)) == 1
|
|
if !ok || !userOK || !passOK {
|
|
w.Header().Set("WWW-Authenticate", `Basic realm="jarvis"`)
|
|
http.Error(w, "unauthorized", http.StatusUnauthorized)
|
|
return
|
|
}
|
|
}
|
|
h(w, r)
|
|
}
|
|
}
|