Initial scaffold: API service, K8s operator, and CRDs

Forgebot is a K8s operator + API service for dispatching AI agent
jobs from git forge commands. Includes:

- CRDs: AgentPool, AgentTask, ProviderQueue, RepositoryBinding
- API server with webhook handler, task queue, and comment proxy
- Operator controllers for task scheduling and job management
- Gitea provider with webhook parsing and signature verification
- PostgreSQL database with auto-migration
- Woodpecker CI pipelines and multi-stage Dockerfiles
This commit is contained in:
2026-06-08 22:49:18 +10:00
parent fd1a4956ed
commit 49d514c050
46 changed files with 3139 additions and 0 deletions
+18
View File
@@ -0,0 +1,18 @@
package gitea
import (
"code.gitea.io/sdk/gitea"
)
type Client struct {
api *gitea.Client
url string
}
func NewClient(url, token string) *Client {
client, _ := gitea.NewClient(url, gitea.SetToken(token))
return &Client{
api: client,
url: url,
}
}
+17
View File
@@ -0,0 +1,17 @@
package gitea
import (
sdk "code.gitea.io/sdk/gitea"
)
func (c *Client) PostComment(owner, repo string, issueOrPR int, body string) error {
_, _, err := c.api.CreateIssueComment(owner, repo, int64(issueOrPR), sdk.CreateIssueCommentOption{
Body: body,
})
return err
}
func (c *Client) AddReaction(owner, repo string, commentID int64, reaction string) error {
_, _, err := c.api.PostIssueCommentReaction(owner, repo, commentID, reaction)
return err
}
+89
View File
@@ -0,0 +1,89 @@
package gitea
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"git.unkin.net/unkin/forgebot/internal/provider"
)
type webhookPayload struct {
Action string `json:"action"`
Comment *commentPayload `json:"comment,omitempty"`
Issue *issuePayload `json:"issue,omitempty"`
Repository *repoPayload `json:"repository"`
PullRequest *prPayload `json:"pull_request,omitempty"`
}
type commentPayload struct {
ID int64 `json:"id"`
Body string `json:"body"`
User struct {
Login string `json:"login"`
} `json:"user"`
}
type issuePayload struct {
Number int `json:"number"`
PullRequest *struct{} `json:"pull_request,omitempty"`
}
type prPayload struct {
Number int `json:"number"`
Head struct {
Ref string `json:"ref"`
} `json:"head"`
}
type repoPayload struct {
FullName string `json:"full_name"`
DefaultBranch string `json:"default_branch"`
}
func (c *Client) ParseWebhook(body []byte, secret string) (*provider.WebhookEvent, error) {
var payload webhookPayload
if err := json.Unmarshal(body, &payload); err != nil {
return nil, fmt.Errorf("unmarshal webhook: %w", err)
}
if payload.Action != "created" || payload.Comment == nil {
return nil, nil
}
event := &provider.WebhookEvent{
Action: payload.Action,
Repository: payload.Repository.FullName,
CommentID: payload.Comment.ID,
Body: payload.Comment.Body,
Author: payload.Comment.User.Login,
}
if payload.Issue != nil {
if payload.Issue.PullRequest != nil {
event.Type = "pull_request_comment"
event.PRNum = payload.Issue.Number
if payload.PullRequest != nil {
event.Ref = payload.PullRequest.Head.Ref
}
} else {
event.Type = "issue_comment"
event.IssueNum = payload.Issue.Number
}
event.Ref = payload.Repository.DefaultBranch
}
return event, nil
}
func VerifySignature(body []byte, secret, signature string) bool {
if secret == "" {
return true
}
mac := hmac.New(sha256.New, []byte(secret))
mac.Write(body)
expected := hex.EncodeToString(mac.Sum(nil))
return hmac.Equal([]byte(expected), []byte(signature))
}
+19
View File
@@ -0,0 +1,19 @@
package provider
type WebhookEvent struct {
Type string
Action string
Repository string
Ref string
IssueNum int
PRNum int
CommentID int64
Body string
Author string
}
type Provider interface {
ParseWebhook(body []byte, secret string) (*WebhookEvent, error)
PostComment(owner, repo string, issueOrPR int, body string) error
AddReaction(owner, repo string, commentID int64, reaction string) error
}