Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
425c242ae7 | ||
|
|
f3a2a1f099 |
No files matched your search
+1
-1
@@ -42,7 +42,7 @@ All through environment variables; nothing about where it runs lives in this rep
|
||||
| --- | --- |
|
||||
| `PANEL_URL` | The panel the worker takes jobs from |
|
||||
| `BUILDER_TOKEN` | Shared secret the panel checks on `/api/v1/builder/**` (same value in the panel's env) |
|
||||
| `GITEA_TOKEN` | The bot account's token (repository read/write on this repo): pushes draft branches, opens PRs |
|
||||
| `GITEA_TOKEN` | The bot account's token (repository read/write; the account needs Write on this repo): pushes draft branches, opens PRs as that account. Checked when the worker starts |
|
||||
| `OLLAMA_URL` | Ollama for the model (or `MODEL_PROVIDER=gemini` with `GEMINI_API_KEY`) |
|
||||
| `SEARXNG_URL`, `READER_URL` | A SearXNG instance (JSON API) and a page-to-text reader (`READER_URL/<page url>`) |
|
||||
| `MODEL`, `POLL_INTERVAL`, `WORKER_NAME`, `WORK_DIR` | Optional: model name, poll interval (20s), name shown in the panel, work folder |
|
||||
|
||||
+52
-2
@@ -49,6 +49,53 @@ type workerConfig struct {
|
||||
workDir, name string
|
||||
poll time.Duration
|
||||
provider, model string
|
||||
botLogin, botEmail string // the token's account, used as the commit author
|
||||
}
|
||||
|
||||
// checkBot reads which account GITEA_TOKEN belongs to and makes sure it can push to the games repo, so a
|
||||
// wrong token or missing permission shows when the worker starts, not at the end of a 30-minute build.
|
||||
func (c *workerConfig) checkBot() error {
|
||||
var user struct {
|
||||
Login string `json:"login"`
|
||||
Email string `json:"email"`
|
||||
}
|
||||
if err := c.giteaGet("/user", &user); err != nil {
|
||||
return fmt.Errorf("GITEA_TOKEN: %w", err)
|
||||
}
|
||||
var repo struct {
|
||||
Permissions struct {
|
||||
Push bool `json:"push"`
|
||||
} `json:"permissions"`
|
||||
}
|
||||
if err := c.giteaGet("/repos/"+c.repo, &repo); err != nil {
|
||||
return fmt.Errorf("%s: %w", c.repo, err)
|
||||
}
|
||||
if !repo.Permissions.Push {
|
||||
return fmt.Errorf("the account %s can't push to %s: give it Write access there", user.Login, c.repo)
|
||||
}
|
||||
c.botLogin, c.botEmail = user.Login, user.Email
|
||||
if c.botEmail == "" {
|
||||
c.botEmail = user.Login + "@users.noreply"
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *workerConfig) giteaGet(p string, out any) error {
|
||||
req, err := http.NewRequest(http.MethodGet, c.gitea+"/api/v1"+p, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Header.Set("Authorization", "token "+c.botToken)
|
||||
resp, err := (&http.Client{Timeout: 30 * time.Second}).Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode >= 300 {
|
||||
raw, _ := io.ReadAll(resp.Body)
|
||||
return fmt.Errorf("HTTP %d: %.200s", resp.StatusCode, raw)
|
||||
}
|
||||
return json.NewDecoder(resp.Body).Decode(out)
|
||||
}
|
||||
|
||||
func runWorker(args []string) error {
|
||||
@@ -71,7 +118,10 @@ func runWorker(args []string) error {
|
||||
return fmt.Errorf("set %s", v.name)
|
||||
}
|
||||
}
|
||||
fmt.Printf("[worker] %s polling %s every %s\n", c.name, c.panel, c.poll)
|
||||
if err := c.checkBot(); err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("[worker] %s polling %s every %s; pull requests as %s\n", c.name, c.panel, c.poll, c.botLogin)
|
||||
for {
|
||||
job, err := c.claim()
|
||||
if err != nil {
|
||||
@@ -313,7 +363,7 @@ func (c *workerConfig) openPR(dir string, job *builderJob, res *buildResult) (st
|
||||
}{
|
||||
{"checkout", []string{"checkout", "-q", "-b", branch}},
|
||||
{"add", []string{"add", filepath.ToSlash(res.Dir)}},
|
||||
{"commit", []string{"-c", "user.name=gbr-bot", "-c", "user.email=gbr-bot@users.noreply.gitea.gbreed.com", "commit", "-q", "-m",
|
||||
{"commit", []string{"-c", "user.name=" + c.botLogin, "-c", "user.email=" + c.botEmail, "commit", "-q", "-m",
|
||||
fmt.Sprintf("feat(games): draft %s\n\nDrafted and boot-tested by the GBR builder (job %s).", job.GameName, job.ID)}},
|
||||
{"push", []string{"-c", auth, "push", "-q", "origin", branch}},
|
||||
}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCheckBotNeedsPushAccess(t *testing.T) {
|
||||
push := false
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Header.Get("Authorization") != "token secret" {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
switch r.URL.Path {
|
||||
case "/api/v1/user":
|
||||
w.Write([]byte(`{"login":"git_ai_bot","email":"bot@example.com"}`))
|
||||
case "/api/v1/repos/org/games":
|
||||
if push {
|
||||
w.Write([]byte(`{"permissions":{"push":true}}`))
|
||||
} else {
|
||||
w.Write([]byte(`{"permissions":{"push":false}}`))
|
||||
}
|
||||
default:
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
}
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
c := &workerConfig{gitea: srv.URL, repo: "org/games", botToken: "secret"}
|
||||
if err := c.checkBot(); err == nil || !strings.Contains(err.Error(), "git_ai_bot can't push to org/games") {
|
||||
t.Errorf("read-only bot: got %v", err)
|
||||
}
|
||||
push = true
|
||||
if err := c.checkBot(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if c.botLogin != "git_ai_bot" || c.botEmail != "bot@example.com" {
|
||||
t.Errorf("identity = %s <%s>", c.botLogin, c.botEmail)
|
||||
}
|
||||
c.botToken = "wrong"
|
||||
if err := c.checkBot(); err == nil || !strings.Contains(err.Error(), "GITEA_TOKEN") {
|
||||
t.Errorf("bad token: got %v", err)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user