Add a manual, workflow_dispatch-only CD workflow and close the infra binding-wiring gap (#9) so the deployed Worker is actually functional. Deliverable 1 - .github/workflows/deploy.yml: - workflow_dispatch only, with a `stack` choice input (default prod). - Gated to the `production` GitHub Actions environment and to the main branch (guard step fails otherwise); no push/PR trigger. - Reuses ci.yml's Go 1.26 + TinyGo + pnpm setup and the TinyGo net/http patch, builds the Wasm Worker (pnpm run build), then runs pulumi up over infra/ via pulumi/actions, injecting the built ../build/worker.mjs as the workerScriptPath config. Provider/backend creds come from environment secrets (nothing committed). All actions pinned by commit SHA; actionlint clean. Deliverable 2 - infra/deploy.go binding wiring (#9): - WorkersScript now carries the R2 bucket binding (REPORTS_BUCKET), the four Secrets Store bindings (BUGREPORT_ENC_KEYRING, ADMIN_TOKEN, GITHUB_TOKEN, OTEL_EXPORTER_OTLP_HEADERS), and the plain vars (GITHUB_REPO, OTEL_*), matching wrangler.jsonc and the Worker runtime contract. - New WorkersCronTrigger resource registers the two Friday UTC crons (#13), bound to the Worker. - Real built artifact wired via ContentFile + computed ContentSha256 when the workerScriptPath config is set; documented placeholder otherwise (keeps the program testable without the artifact, as #2 did). - secretsStoreId is a new required config; git rate-limit ruleset insertion point (#6/#7) kept reserved. - Extended the WithMocks tests to assert the R2 + Secrets Store + var bindings, the crons, and the artifact ContentFile/ContentSha256 path. - Updated Pulumi.<stack>.yaml and infra/README.md with the full secret/config list and how a maintainer triggers the deploy. Verified in infra/: go build, go vet, go test all green; actionlint clean on deploy.yml. No deploy/preview/provision was run. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
LibreMail Bug Report Ingest
Server-side infrastructure for LibreMail's debug bug-report pipeline. This repo is intentionally separate from the Android app repo — it owns the Cloudflare Worker and infrastructure-as-code, not the client.
What this is
Per JMR-dev/LibreMail#11:
- The LibreMail app lets a user opt in to submitting a debug report (LibreMail#33).
- A Cloudflare Worker in this repo receives the report over HTTPS, best-effort scrubs PII, and stores it encrypted in a Cloudflare R2 bucket (#34).
- Every Friday at 17:00 (Central Time, DST-aware), a scheduled job publishes any report not manually removed as a GitHub issue on the LibreMail repo (#35).
Stack
- Worker: Go, compiled to WebAssembly with TinyGo and served through the
syumai/workersruntime adapter - Infrastructure as code: Pulumi (Go)
- Deployment: GitHub Actions
- Secrets/key custody: Cloudflare Secret Manager
- DNS: Google Cloud DNS
Build & run locally
The request-handling logic lives in internal/handler as plain, build-tag-free
Go, so it is unit-tested and run locally with the standard Go toolchain — no
TinyGo needed. Only the actual Wasm Worker build requires TinyGo.
Layout:
internal/handler/— the corehttp.Handler(health/hello endpoints). No build tags; all request logic and its tests live here.cmd/devserver/— a plainnet/httpserver that mounts the core handler for local dev without TinyGo.worker/— the Cloudflare Workers (Wasm) entrypoint, guarded by//go:build js && wasm, wiring the same core handler into the Workers runtime. Excluded from host builds and tests.
Test
go vet ./...
go test ./...
Run locally (no TinyGo)
go run ./cmd/devserver # listens on :8787; override with ADDR, e.g. ADDR=:9000
Then, from another shell:
$ curl -s localhost:8787/
{"service":"libremail-bug-report-ingest","status":"ok","message":"hello from the LibreMail bug-report ingest Worker"}
$ curl -s localhost:8787/healthz
{"status":"ok"}
This runs the exact handler the deployed Worker uses, minus the Workers runtime.
Build & run the real Worker (requires TinyGo)
Node tooling is managed with pnpm; wrangler is a dev dependency. The Wasm build uses TinyGo 0.41.1 on the Go 1.26 toolchain.
Temporary toolchain patch. TinyGo 0.41.1 and earlier vendor a
net/httpjs/wasm overlay (tinygo-org/net@e54965e) that fails to compile against Go 1.25+/1.26 witht.roundTrip undefined(see tinygo-org/tinygo#5467). CI applies the exact upstream fix (tinygo-org/net@1026408a, checked in as.ci/tinygo-net-roundtrip.patch) to the installed TinyGo before building. Building locally on Go 1.26 needs the same one-file patch until a TinyGo release later than 0.41.1 ships it, at which point the patch and the CI step are removed (tracked in #26).
pnpm install # install wrangler
pnpm run build # workers-assets-gen + TinyGo -> ./build/app.wasm + ./build/worker.mjs
pnpm exec wrangler dev # serve the Wasm Worker locally on :8787
pnpm exec wrangler deploy # deploy (CI only)
pnpm run build runs, verbatim:
go run github.com/syumai/workers/cmd/workers-assets-gen && tinygo build -o ./build/app.wasm -target wasm -no-debug ./worker
TinyGo is not required for tests or the dev server; it is needed only for the
Wasm build above and is installed in CI. The generated ./build/ output is
git-ignored.
Why TinyGo + syumai/workers
Cloudflare Workers execute WebAssembly, not native binaries, so Go must be
compiled to Wasm. Of the two options — the standard compiler's
GOOS=js GOARCH=wasm output or TinyGo — TinyGo emits far smaller modules that
sit comfortably inside the Worker size limit, which is why it is the standard
path for Go on Workers. The syumai/workers
package adapts Go's net/http handler model to the Workers fetch event, so a
single http.Handler runs unchanged on the dev server and in the deployed
Worker.
Status
Early bootstrap. See the project board and open issues for the current breakdown of work.