From 5fb7736be2fb75ade62a228d5ea7ea98b32e8198 Mon Sep 17 00:00:00 2001 From: Greg Date: Sat, 24 Oct 2026 17:12:50 -0400 Subject: [PATCH] ci: publish templates to the control panel on main Adds `gbrgames export`, which builds the games_published payload from every game.yaml, and a CI step that posts it to the panel webhook after validation on main. Unlike the node-agent notify, a failed publish fails the run so stale templates in the panel never go unnoticed. Co-Authored-By: Claude Opus 5.5 --- .gitea/workflows/validate.yml | 23 ++++++++++++++ cmd/gbrgames/export.go | 58 +++++++++++++++++++++++++++++++++++ cmd/gbrgames/main.go | 5 ++- 3 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 cmd/gbrgames/export.go diff --git a/.gitea/workflows/validate.yml b/.gitea/workflows/validate.yml index ecc4314..5257242 100644 --- a/.gitea/workflows/validate.yml +++ b/.gitea/workflows/validate.yml @@ -7,6 +7,9 @@ on: push: workflow_dispatch: +env: + PANEL_WEBHOOK_URL: https://api.gbrhosting.com/api/v1/webhooks/gitea + jobs: validate: runs-on: ubuntu-latest @@ -21,3 +24,23 @@ jobs: - name: Validate run: go run ./cmd/gbrgames validate + + # main is the source of truth: send every template to the panel, which upserts them + # (new games arrive disabled). Unlike the node-agent notify, a failure here fails the run, + # because the panel would otherwise keep serving the previous templates silently. + - name: Publish to Control Panel + if: github.ref == 'refs/heads/main' + env: + PANEL_WEBHOOK_SECRET: ${{ secrets.PANEL_WEBHOOK_SECRET }} + run: | + if [ -z "$PANEL_WEBHOOK_SECRET" ]; then + echo "::error::PANEL_WEBHOOK_SECRET is not set; templates were not published" + exit 1 + fi + go run ./cmd/gbrgames export -repo "${{ github.repository }}" -commit "${{ github.sha }}" -out payload.json + curl -sS --fail-with-body --max-time 30 --retry 3 --retry-delay 10 \ + -X POST "${{ env.PANEL_WEBHOOK_URL }}" \ + -H "Content-Type: application/json" \ + -H "X-Gitea-Token: $PANEL_WEBHOOK_SECRET" \ + -H "X-GBR-Event: games_published" \ + --data-binary @payload.json diff --git a/cmd/gbrgames/export.go b/cmd/gbrgames/export.go new file mode 100644 index 0000000..e5f8f27 --- /dev/null +++ b/cmd/gbrgames/export.go @@ -0,0 +1,58 @@ +package main + +import ( + "encoding/json" + "errors" + "flag" + "fmt" + "os" + "path/filepath" +) + +// runExport writes the games_published webhook payload: every game.yaml as JSON, ready for the panel. +func runExport(args []string) error { + fs := flag.NewFlagSet("export", flag.ExitOnError) + gamesDir := fs.String("games", "games", "directory holding one folder per game") + repo := fs.String("repo", "", "repository full name, e.g. gbreed-public/gbr-games") + commit := fs.String("commit", "", "commit SHA the templates come from") + out := fs.String("out", "", "file to write (default stdout)") + fs.Parse(args) + + entries, err := os.ReadDir(*gamesDir) + if err != nil { + return err + } + games := []any{} + for _, e := range entries { + if !e.IsDir() { + continue + } + raw, err := os.ReadFile(filepath.Join(*gamesDir, e.Name(), "game.yaml")) + if err != nil { + return fmt.Errorf("%s: %w", e.Name(), err) + } + game, err := loadYAML(raw) + if err != nil { + return fmt.Errorf("%s/game.yaml: %w", e.Name(), err) + } + games = append(games, game) + } + if len(games) == 0 { + return errors.New("no games to export") + } + + payload := map[string]any{ + "repository": map[string]any{"full_name": *repo}, + "after": *commit, + "games": games, + } + b, err := json.Marshal(payload) + if err != nil { + return err + } + if *out == "" { + _, err = os.Stdout.Write(b) + return err + } + return os.WriteFile(*out, b, 0o644) +} diff --git a/cmd/gbrgames/main.go b/cmd/gbrgames/main.go index 41c23b9..3ebca68 100644 --- a/cmd/gbrgames/main.go +++ b/cmd/gbrgames/main.go @@ -2,6 +2,7 @@ // // go run ./cmd/gbrgames validate # check every games//game.yaml (+ test.yaml) // go run ./cmd/gbrgames import export.json # write games// from GET /api/v1/admin/games +// go run ./cmd/gbrgames export -out p.json # build the games_published payload CI sends to the panel package main import ( @@ -19,6 +20,8 @@ func main() { err = runValidate(os.Args[2:]) case "import": err = runImport(os.Args[2:]) + case "export": + err = runExport(os.Args[2:]) default: usage() } @@ -29,6 +32,6 @@ func main() { } func usage() { - fmt.Fprintln(os.Stderr, "usage: gbrgames validate [-games DIR] | import [-games DIR] [-all] EXPORT.json") + fmt.Fprintln(os.Stderr, "usage: gbrgames validate [-games DIR] | import [-games DIR] [-all] EXPORT.json | export [-repo R] [-commit SHA] [-out FILE]") os.Exit(2) }