feat(selfhost): support an S3-compatible bucket for visual-capture storage - #4203
Conversation
…orage REVIEW_AUDIT (the visual-capture screenshot store) previously had only one self-host backend: a local filesystem directory (REVIEW_AUDIT_DIR), served back out through this instance's own /gittensory/shot proxy route. That means the resulting <img> URLs embedded in a public GitHub PR comment are only reachable through this instance's own PUBLIC_API_ORIGIN -- if an operator keeps their instance behind a private network (a VPN, a firewall, no public DNS), GitHub's own servers can never fetch them, and every screenshot renders as a broken image for every viewer. Adds REVIEW_AUDIT_S3_BUCKET/_ENDPOINT/_ACCESS_KEY_ID/_SECRET_ACCESS_KEY/ _REGION: an alternative backend (src/selfhost/s3-blob-store.ts, using aws4fetch to sign S3-compatible REST calls) that stores screenshots in an operator's own bucket -- their own Cloudflare R2 bucket, or any other S3-compatible provider -- instead of the local filesystem. Takes priority over REVIEW_AUDIT_DIR when both are configured. A bounded retry budget (3 attempts, not aws4fetch's resilience-first default of 10) keeps a persistently-misconfigured bucket from stalling the review pipeline for 30+ seconds on every best-effort read/write. REVIEW_AUDIT_S3_PUBLIC_URL additionally makes served links point DIRECTLY at the bucket's own public URL (an R2 public r2.dev bucket URL, or a custom domain) instead of proxying through /gittensory/shot -- genuinely decoupling "does my review pipeline run on my own infrastructure" from "are the resulting public-facing images reachable by anyone." Unset (the default) keeps today's proxy-based serving unchanged. Also adds a delete() method to the filesystem-backed store (blob-store.ts) for interface parity with the new S3 one -- actions-fallback.ts's dispatch- marker cleanup already calls .delete() and previously depended on that call silently throwing-and-being-caught on self-host, since the fs store never implemented it.
|
Superagent didn't find any vulnerabilities or security issues in this PR. |
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
gittensory-ui | f889fe7 | Commit Preview URL Branch Preview URL |
Jul 08 2026, 11:20 AM |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #4203 +/- ##
=======================================
Coverage 93.74% 93.75%
=======================================
Files 387 388 +1
Lines 36420 36443 +23
Branches 13341 13348 +7
=======================================
+ Hits 34142 34167 +25
Misses 1621 1621
+ Partials 657 655 -2
🚀 New features to boost your workflow:
|
|
Warning 🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨 ⏸️ Gittensory review result - manual review recommendedReview updated: 2026-07-08 17:15:36 UTC
⏸️ Suggested Action - Manual Review
Concerns raised — review before merging
Review context
Contributor next steps
Signal definitions
Visual preview
Click any thumbnail to open the full-size screenshot. Before = production · After = this PR's preview deploy. 🟩 Safe / merged · 🟦 Advisory · 🟨 Held for review · 🟥 Blocked / closed 💰 Earn for open-source contributions like this. Gittensor lets GitHub contributors earn for the work they already do — register to start earning →. Checked by Gittensory, a quiet PR intelligence layer for OSS maintainers.
|
…kAfterShot resolveShotUrl(env, key) || placeholder was only ever exercised with a truthy resolveShotUrl result (PUBLIC_API_ORIGIN or REVIEW_AUDIT_S3_PUBLIC_URL set), leaving the falsy/placeholder side of the fallback branch untested.
Summary
PUBLIC_API_ORIGINis a Tailscale-private hostname, unreachable by GitHub's own servers. fix(review): warn at boot when PUBLIC_API_ORIGIN/PUBLIC_SITE_ORIGIN look private #4185 (merged) added a boot-time advisory for this; its own "Remaining" section frames actually fixing reachability as an ops change outside this repo. This PR adds a genuine in-repo alternative: an operator's own publicly-readable S3-compatible bucket for screenshot storage, soPUBLIC_API_ORIGINitself never needs to be public at all.REVIEW_AUDIT(the visual-capture screenshot store) previously had one self-host backend: a local filesystem directory (REVIEW_AUDIT_DIR), always served back out through this instance's own/gittensory/shotproxy route.REVIEW_AUDIT_S3_BUCKET/_ENDPOINT/_ACCESS_KEY_ID/_SECRET_ACCESS_KEY/_REGION: an alternative backend (src/selfhost/s3-blob-store.ts, usingaws4fetchto sign S3-compatible REST calls) storing screenshots in an operator's own bucket — their own Cloudflare R2 bucket, or any other S3-compatible provider. Takes priority overREVIEW_AUDIT_DIRwhen both are configured.REVIEW_AUDIT_S3_PUBLIC_URLadditionally makes served links point directly at the bucket's own public URL instead of proxying through/gittensory/shot— decoupling "does my review pipeline run on my own infrastructure" from "are the resulting public-facing images reachable by anyone." Unset (default) keeps today's proxy-based serving unchanged.aws4fetch's resilience-first default of 10) keeps a persistently-misconfigured bucket from stalling the review pipeline for 30+ seconds on every best-effort read/write — caught this empirically in my own tests (a naive 500-response test took 30s to fail before I bounded it).delete()to the filesystem-backed store for interface parity —actions-fallback.ts's dispatch-marker cleanup already calls.delete()and previously depended on that call silently throwing-and-being-caught on self-host, since the fs store never implemented it.Scope
CONTRIBUTING.md.Validation
git diff --checknpm run test:ci(full local gate, green)npm audit --audit-level=moderate(0 vulnerabilities)npm run test:coverage— new/changedsrc/**lines covered, including both branches of every new conditional (S3-vs-fs backend priority, S3-public-url-vs-proxy URL construction, both configured-vs-unset combinations across all 4 call sites that construct a shot URL)npm run selfhost:env-reference— regenerated, the 5 newprocess.envreads picked upnpm run ui:openapi— confirmed no drift from this change (ran last, afterformat, per this repo's own known ordering gotcha)Safety
.env.example, the self-hosting configuration docs page, generated env reference.)Notes
src/env.d.ts/server.ts/selfhost/**) — no hosted-mode behavior changes.