initial
This commit is contained in:
@@ -0,0 +1,141 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
ListenAddr string
|
||||
WorkspaceRoot string // mkdtemp parent. empty = os.TempDir()
|
||||
JobTimeout time.Duration // max wall-clock for a single Claude session
|
||||
}
|
||||
|
||||
func loadConfig() Config {
|
||||
cfg := Config{
|
||||
ListenAddr: envOr("KLAUS_LISTEN", ":8020"),
|
||||
WorkspaceRoot: os.Getenv("KLAUS_WORKSPACE"),
|
||||
JobTimeout: 20 * time.Minute,
|
||||
}
|
||||
if v := os.Getenv("KLAUS_JOB_TIMEOUT"); v != "" {
|
||||
if d, err := time.ParseDuration(v); err == nil {
|
||||
cfg.JobTimeout = d
|
||||
} else {
|
||||
log.Printf("ignoring invalid KLAUS_JOB_TIMEOUT=%q: %v", v, err)
|
||||
}
|
||||
}
|
||||
if cfg.WorkspaceRoot != "" {
|
||||
if err := os.MkdirAll(cfg.WorkspaceRoot, 0o755); err != nil {
|
||||
log.Fatalf("create workspace root %s: %v", cfg.WorkspaceRoot, err)
|
||||
}
|
||||
}
|
||||
return cfg
|
||||
}
|
||||
|
||||
func envOr(k, def string) string {
|
||||
if v := os.Getenv(k); v != "" {
|
||||
return v
|
||||
}
|
||||
return def
|
||||
}
|
||||
|
||||
func main() {
|
||||
cfg := loadConfig()
|
||||
worker := NewWorker(cfg)
|
||||
|
||||
http.HandleFunc("/gitea-webhook", func(w http.ResponseWriter, r *http.Request) {
|
||||
eventType := r.Header.Get("X-Gitea-Event")
|
||||
if eventType == "" {
|
||||
eventType = r.Header.Get("X-Gogs-Event")
|
||||
}
|
||||
log.Printf("%s %s from %s event=%s", r.Method, r.URL.Path, r.RemoteAddr, eventType)
|
||||
|
||||
if r.Method != http.MethodPost {
|
||||
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
body, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
defer r.Body.Close()
|
||||
|
||||
if !json.Valid(body) {
|
||||
http.Error(w, "invalid JSON", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
event, trigger, err := DecideTrigger(eventType, body)
|
||||
if err != nil {
|
||||
log.Printf("decode webhook: %v", err)
|
||||
http.Error(w, "bad payload", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if trigger == nil {
|
||||
log.Printf("[skip] %s(action=%s) on %s by @%s — no klaus trigger; %s",
|
||||
eventType, event.Action, event.Repository.FullName, event.Sender.Name(),
|
||||
skipHint(eventType, event))
|
||||
w.WriteHeader(http.StatusOK)
|
||||
return
|
||||
}
|
||||
log.Printf("[queue] %s on %s#%d — reason=%s (by @%s)",
|
||||
eventType, event.Repository.FullName, trigger.Number, trigger.Reason, event.Sender.Name())
|
||||
|
||||
worker.Enqueue(Job{
|
||||
EventType: eventType,
|
||||
Event: event,
|
||||
Trigger: trigger,
|
||||
})
|
||||
w.WriteHeader(http.StatusAccepted)
|
||||
})
|
||||
|
||||
log.Printf("klaus-bot listening on %s (workspace=%s, timeout=%s)",
|
||||
cfg.ListenAddr, displayPath(cfg.WorkspaceRoot), cfg.JobTimeout)
|
||||
log.Fatal(http.ListenAndServe(cfg.ListenAddr, nil))
|
||||
}
|
||||
|
||||
func displayPath(p string) string {
|
||||
if p == "" {
|
||||
return os.TempDir() + " (default)"
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
// skipHint produces a short diagnostic suffix for the "[skip]" log line so
|
||||
// operators can tell at a glance why a payload didn't trigger Klaus.
|
||||
func skipHint(eventType string, ev *GiteaEvent) string {
|
||||
snippet := func(s string) string {
|
||||
s = strings.TrimSpace(s)
|
||||
s = strings.ReplaceAll(s, "\n", " ")
|
||||
if len(s) > 120 {
|
||||
s = s[:120] + "…"
|
||||
}
|
||||
return s
|
||||
}
|
||||
switch eventType {
|
||||
case "issue_comment":
|
||||
if ev.Comment != nil {
|
||||
return fmt.Sprintf("comment=%q", snippet(ev.Comment.Body))
|
||||
}
|
||||
case "issues":
|
||||
if ev.Issue != nil {
|
||||
return fmt.Sprintf("issue=%q", snippet(ev.Issue.Body))
|
||||
}
|
||||
case "pull_request":
|
||||
if ev.PullRequest != nil {
|
||||
return fmt.Sprintf("pr=%q", snippet(ev.PullRequest.Body))
|
||||
}
|
||||
case "pull_request_review":
|
||||
if ev.Review != nil {
|
||||
return fmt.Sprintf("review=%q", snippet(ev.Review.Body))
|
||||
}
|
||||
}
|
||||
return "(no body)"
|
||||
}
|
||||
Reference in New Issue
Block a user