diff --git a/BUILDER.md b/BUILDER.md index d6ac700..f53a866 100644 --- a/BUILDER.md +++ b/BUILDER.md @@ -46,6 +46,7 @@ All through environment variables; nothing about where it runs lives in this rep | `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/`) | | `MODEL`, `POLL_INTERVAL`, `WORKER_NAME`, `WORK_DIR` | Optional: model name, poll interval (20s), name shown in the panel, work folder | +| `BOT_NAME` | Optional: commit author when the token can't read its own account (no `read:user` scope); default `gbr-bot` | ## Running a build by hand diff --git a/cmd/gbrgames/worker.go b/cmd/gbrgames/worker.go index d98c920..c8a62fd 100644 --- a/cmd/gbrgames/worker.go +++ b/cmd/gbrgames/worker.go @@ -60,7 +60,12 @@ func (c *workerConfig) checkBot() error { Email string `json:"email"` } if err := c.giteaGet("/user", &user); err != nil { - return fmt.Errorf("GITEA_TOKEN: %w", err) + if !strings.HasPrefix(err.Error(), "HTTP 403") { + return fmt.Errorf("GITEA_TOKEN: %w", err) + } + // A repository-only token (all the worker needs) may not read its own account: name the author from + // BOT_NAME instead. Whether it can push is still checked below. + user.Login = envOr("BOT_NAME", "gbr-bot") } var repo struct { Permissions struct { diff --git a/cmd/gbrgames/worker_test.go b/cmd/gbrgames/worker_test.go index c1259c5..895d5fe 100644 --- a/cmd/gbrgames/worker_test.go +++ b/cmd/gbrgames/worker_test.go @@ -45,3 +45,26 @@ func TestCheckBotNeedsPushAccess(t *testing.T) { t.Errorf("bad token: got %v", err) } } + +func TestCheckBotWithRepositoryOnlyToken(t *testing.T) { + // Gitea answers /user with 403 when the token only has repository scope + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch r.URL.Path { + case "/api/v1/user": + w.WriteHeader(http.StatusForbidden) + w.Write([]byte(`{"message":"token does not have at least one of required scope(s), required=[read:user]"}`)) + case "/api/v1/repos/org/games": + w.Write([]byte(`{"permissions":{"push":true}}`)) + } + })) + defer srv.Close() + + t.Setenv("BOT_NAME", "gbr-bot") + c := &workerConfig{gitea: srv.URL, repo: "org/games", botToken: "secret"} + if err := c.checkBot(); err != nil { + t.Fatal(err) + } + if c.botLogin != "gbr-bot" || c.botEmail != "gbr-bot@users.noreply" { + t.Errorf("identity = %s <%s>", c.botLogin, c.botEmail) + } +}