Files
Alexandru MacocianandCopilot d29ef9e024
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
Initial jarvis service: alert -> AI diagnostic -> Atom feed
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
2026-07-29 11:24:42 +02:00

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)
}
}