Skip to content

feat(selfhost): self-hostable Docker stack — gittensory's full Worker on Node (#979/#980) - #1157

Merged
JSONbored merged 26 commits into
mainfrom
feat/self-host-docker
Jun 24, 2026
Merged

feat(selfhost): self-hostable Docker stack — gittensory's full Worker on Node (#979/#980)#1157
JSONbored merged 26 commits into
mainfrom
feat/self-host-docker

Conversation

@JSONbored

@JSONbored JSONbored commented Jun 24, 2026

Copy link
Copy Markdown
Owner

Runs the same gittensory review engine on a plain Node container so operators can self-host next to their own GitHub App. docker compose up → webhooks, the deterministic gate, AI summaries, the maintain/sweep cron, and (optionally) full maintainer autonomy. The Cloudflare Worker (src/index.ts, wrangler) is untouched — this is a parallel Node entry.

Draft on purpose — do not merge yet (per owner). Mark ready when reviewed.

Foundation — runs the Worker on Node

D1 → node:sqlite shim (Drizzle + raw + all 56 migrations + atomic batch, byte-identical); Queue → durable queue; cron → timer; Hono via @hono/node-server; *_FILE secrets. Workers-only deps build-stubbed (visual off, /mcp→501).

Tier 0 — reliability

Durable queue (jobs persist; restart re-claims in-flight); /ready + /metrics (Prometheus); graceful SIGTERM; AI model-id fix (never leaks @cf/… to Ollama/claude/codex); CI Docker build+boot smoke test.

Tier 1 — AI power

BYOK fallback chain (AI_PROVIDER=anthropic,ollama); providers: ollama / openai-compatible / openai / native anthropic / claude-code / codex (subscription CLIs, scrubbed keys, fail-safe); CLI-in-image build arg; local RAG (SQLite vector store + /embeddings → no Vectorize needed).

Tier 2 — scale / HA (validated against real Postgres + Redis)

Postgres backend (DATABASE_URL) via a tested SQLite→PG dialect translator + async D1 adapter (all 56 migrations apply); Postgres queue with FOR UPDATE SKIP LOCKED (multi-instance-safe); Redis rate limiter (REDIS_URL, x-ratelimit-* headers). Real-infra testing caught 2 bugs (DO-stub fetch signature; int8→string). PG is beta (migrations + exercised paths validated; RAG is SQLite-only).

Tier 3 — packaging & onboarding

254 MB image (was 1.33 GB; self-contained bundle); GHCR multi-arch release pipeline (selfhost-v* tag → amd64+arm64 + provenance/SBOM + Release); Litestream continuous backup; GitHub App Manifest /setup wizard (one-click App creation, gated to first-run).

Tier 4 — production-grade compose profiles, observability, and 1-click deploy

Docker Compose profiles (#1199): 9 opt-in profiles via --profilepostgres, pgbouncer, redis, ollama, litestream, caddy, observability, tailscale, runners. Operators layer exactly the services they need.

Caddy reverse proxy (#1203): --profile caddy → HTTPS + HTTP/3 with auto-renewing Let's Encrypt certificates; zstd/gzip; HSTS + frame + content-type security headers.

Prometheus + Grafana (#1206): --profile observability → pre-provisioned datasource + dashboard (queue depth, dead-letter count, HTTP request rate, job throughput). Zero Grafana config needed.

Tailscale sidecar (#1204): --profile tailscale → instance visible on the operator's tailnet with no public firewall holes; persistent node identity volume.

Self-hosted GitHub Actions runner (#1205): --profile runners → registers a runner against any repo/org; mounts /var/run/docker.sock for in-runner Docker builds.

Worker concurrency (#1201): QUEUE_CONCURRENCY env var (default 1); both SQLite and Postgres queues support N concurrent pump loops; stop()/drain() await active === 0.

Structured audit log (#1202): logAudit() writes one JSON line per job lifecycle event (job_complete / job_dead / job_error) to stdout — level, ts, job_id, payload_type, latency_ms, attempts, error — captured by Docker's json-file driver with zero operator config.

Terraform (Hetzner) (#1209): terraform/ provisions a cx22 VPS + 20 GB volume + firewall; cloud-init installs Docker; terraform apply → SSH into a ready-to-clone host.

Railway 1-click template (#1210): railway.json — Dockerfile builder, /health check, ON_FAILURE restart policy — eligible for Railway Template Marketplace creator revenue.

Validated

  • Boots on Node + Docker on SQLite and Postgres+Redis; deep dep subtrees load (octokit/crypto, MCP/zod, drizzle/D1).
  • 83 self-host unit tests (includes new selfhost-audit.test.ts + selfhost-pg-queue.test.ts + concurrency branch tests) + a real-Postgres integration test (in CI via a postgres service) + a Docker build+boot smoke test. Typecheck clean, full gate green.
  • Multi-repo native (installation-driven webhook, per-installation tokens, per-repo keys).

Config = samples only

Every .env.example / compose / Dockerfile / litestream value is a placeholder; real .env gitignored. Full guide: docs/self-hosting.md.

Known limitations (documented)

Advances #979 #980 #981 #982 #1029 #1199 #1201 #1202 #1203 #1204 #1205 #1206 #1209 #1210

@JSONbored
JSONbored marked this pull request as ready for review June 24, 2026 03:52
@dosubot dosubot Bot added the size:XL label Jun 24, 2026
@codecov

codecov Bot commented Jun 24, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 99.12854% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 94.92%. Comparing base (2ea7ed1) to head (5f0a784).
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
src/selfhost/sqlite-queue.ts 97.14% 0 Missing and 2 partials ⚠️
src/selfhost/ai.ts 99.21% 0 Missing and 1 partial ⚠️
src/selfhost/health.ts 88.88% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1157      +/-   ##
==========================================
+ Coverage   94.82%   94.92%   +0.10%     
==========================================
  Files         158      173      +15     
  Lines       19157    19616     +459     
  Branches     6939     7050     +111     
==========================================
+ Hits        18165    18620     +455     
  Misses        396      396              
- Partials      596      600       +4     
Files with missing lines Coverage Δ
src/selfhost/audit.ts 100.00% <100.00%> (ø)
src/selfhost/d1-adapter.ts 100.00% <100.00%> (ø)
src/selfhost/mcp-server-node.ts 100.00% <100.00%> (ø)
src/selfhost/metrics.ts 100.00% <100.00%> (ø)
src/selfhost/migrate.ts 100.00% <100.00%> (ø)
src/selfhost/pg-dialect.ts 100.00% <100.00%> (ø)
src/selfhost/pg-vectorize.ts 100.00% <100.00%> (ø)
src/selfhost/qdrant-vectorize.ts 100.00% <100.00%> (ø)
src/selfhost/redis-cache.ts 100.00% <100.00%> (ø)
src/selfhost/redis-ratelimit.ts 100.00% <100.00%> (ø)
... and 5 more
🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@JSONbored JSONbored self-assigned this Jun 24, 2026
@ghost

ghost commented Jun 24, 2026

Copy link
Copy Markdown

Caution

🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥

🛑 Gittensory review — blocked

41 files · 1 AI reviewers · no blockers · readiness 93/100 · CI failing · unstable

🛑 Blocked

Review summary
The diff adds a self-hostable Docker stack for the gittensory Worker, enabling operators to run the same review engine on a plain Node container. The changes include new modules for AI providers, database adapters, queue implementations, and a setup wizard. The Dockerfile and docker-compose.yml are configured for easy deployment. The code is well-structured and follows existing conventions, with comprehensive tests and documentation.

Blockers

  • The `createOpenAiCompatibleAi` function in `src/selfhost/ai.ts` does not handle the case where `options.text` is an empty array, leading to a potential `ai_embed_http_400` error when the request body is empty.

Nits (5)

  • Add a type definition for `AiRunOptions` in `src/selfhost/ai.ts` to ensure all required fields are provided.
  • Consider adding error handling to the `createSelfHostAi` function in `src/selfhost/ai.ts` to handle cases where no AI provider is configured.
  • Verify that the `resolveModel` function in `src/selfhost/ai.ts` handles all edge cases, such as empty or malformed model IDs.
  • Add validation to the `createOpenAiCompatibleAi` function in `src/selfhost/ai.ts` to ensure that the `baseUrl` and `apiKey` are properly formatted.
  • Add defensive checks in `src/selfhost/ai.ts` to handle cases where the AI provider returns unexpected or malformed responses.

CI checks failing

  • validate
Signal Result Evidence
Code review ✅ No blockers 1 reviewers, synthesized
Linked issue ⚠️ Missing No linked issue or no-issue rationale found.
Related work ✅ No active overlap found No same-issue or scoped active PR overlap found.
Review load ✅ 20/20 Readiness component derived from cached public PR metadata and labels; size label size:XXL.
Validation evidence ✅ 25/25 PR body includes validation/test evidence.
Open PR queue ✅ 10/10 0 open PR(s), 0 likely reviewable.
Contributor context ✅ Confirmed Gittensor contributor JSONbored; Gittensor profile; 84 PR(s), 252 issue(s).
Gate result ✅ Passing No configured blocker found.
Nits — 1 non-blocking
  • No linked issue detected — If this PR is intended to solve an issue, link it explicitly in the PR body.
Review context
  • Author: JSONbored
  • Role context: owner (maintainer lane)
  • Public audience mode: oss maintainer
  • Lane context: Repository is configured for direct PR review.
  • Public profile languages: Python, TypeScript, JavaScript, Ruby, Go, Kotlin, MDX, Shell
  • Official Gittensor activity: 84 PR(s), 252 issue(s).
  • PR-specific overlap: none found.
Contributor next steps
  • Treat this as maintainer-lane context rather than normal contributor-lane activity.
  • Explain no-issue PR.
  • Link the issue being solved, or explicitly explain why this is a no-issue PR.
Signal definitions
  • Related work = same linked issue, overlapping active PRs, or title/path similarity.
  • Review load = cached public PR metadata such as size labels, changed paths, and preflight status.
  • Open PR queue = repo-wide review pressure; it is not a PR quality failure.
  • Contributor context = public GitHub/Gittensor identity context; non-Gittensor status is not a blocker.
Review details

Generated from public PR metadata and the diff. Advisory only; deterministic signals remain authoritative.

The diff adds a self-hostable Docker stack for the gittensory Worker, enabling operators to run the same review engine on a plain Node container. The changes include new modules for AI providers, database adapters, queue implementations, and a setup wizard. The Dockerfile and docker-compose.yml are configured for easy deployment. The code is well-structured and follows existing conventions, with comprehensive tests and documentation.

Blockers

  • The `createOpenAiCompatibleAi` function in `src/selfhost/ai.ts` does not handle the case where `options.text` is an empty array, leading to a potential `ai_embed_http_400` error when the request body is empty.

Nits (5)

  • Add a type definition for `AiRunOptions` in `src/selfhost/ai.ts` to ensure all required fields are provided.
  • Consider adding error handling to the `createSelfHostAi` function in `src/selfhost/ai.ts` to handle cases where no AI provider is configured.
  • Verify that the `resolveModel` function in `src/selfhost/ai.ts` handles all edge cases, such as empty or malformed model IDs.
  • Add validation to the `createOpenAiCompatibleAi` function in `src/selfhost/ai.ts` to ensure that the `baseUrl` and `apiKey` are properly formatted.
  • Add defensive checks in `src/selfhost/ai.ts` to handle cases where the AI provider returns unexpected or malformed responses.

🟩 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.

  • Re-run Gittensory review

@ghost ghost added gittensory:reviewed gittensor:feature Gittensor-scored feature linked to a feature issue — scores a 0.25x multiplier. labels Jun 24, 2026
@dosubot dosubot Bot added size:XXL and removed size:XL labels Jun 24, 2026
Comment thread .github/workflows/selfhost.yml Fixed

@superagent-security superagent-security Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Superagent found 3 security concern(s).

Comment thread .github/workflows/selfhost.yml Outdated
Comment thread .github/workflows/selfhost.yml
Comment thread .github/workflows/selfhost.yml
@superagent-security superagent-security Bot added the pr:flagged PR flagged for review by security analysis. label Jun 24, 2026
@dosubot dosubot Bot added the size:XXL label Jun 24, 2026
Comment thread src/selfhost/ai.ts Outdated

@superagent-security superagent-security Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Superagent found 2 security concern(s).

Comment thread .github/workflows/release-selfhost.yml
Comment thread .github/workflows/release-selfhost.yml

@superagent-security superagent-security Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Superagent found 2 security concern(s).

Comment thread .github/workflows/release-selfhost.yml Outdated
Comment thread .github/workflows/release-selfhost.yml Outdated

@superagent-security superagent-security Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Superagent found 2 security concern(s).

Comment thread .github/workflows/release-selfhost.yml
Comment thread Dockerfile Outdated
Comment thread .github/workflows/selfhost.yml Fixed
Comment thread .github/workflows/release-selfhost.yml
@JSONbored
JSONbored force-pushed the feat/self-host-docker branch from b3e8bbb to 7b2a48b Compare June 24, 2026 08:10
JSONbored added a commit that referenced this pull request Jun 24, 2026
…ch coverage

Completes the self-host feature parity gaps identified in PR #1157:

• MCP Node transport (mcp-server-node.ts): replaces the CF Agents SDK
  createMcpHandler (Durable-Object-only) with WebStandardStreamableHTTP
  from the MCP SDK — stateless, per-request, Node 18+ compatible.
  agents-mcp stub re-exports it so existing MCP routes work unchanged.

• pgvector RAG (pg-vectorize.ts): Vectorize adapter backed by pgvector's
  <=> cosine distance operator. initPgVectorize() issues DDL at startup;
  PGVECTOR_ENABLED=true env flag gates it (skips on plain Postgres).
  docker-compose.yml updated to pgvector/pgvector:pg16 image.

• Visual review (puppeteer stub): connects to an external Chrome sidecar
  via BROWSER_WS_ENDPOINT (e.g. browserless/chrome); puppeteer-core is an
  optional runtime dep installed with INSTALL_VISUAL_REVIEW=true build-arg.
  Dockerfile wires the optional install; server.ts injects BROWSER binding.

• Release workflow hardening: all action SHAs pinned to exact versions.

• restart: unless-stopped added to docker-compose gittensory service.

• 100% branch coverage on all new src/ files (mcp-server-node.ts,
  pg-vectorize.ts); stubs/** and server.ts remain Codecov-excluded.
  All 3709 tests pass; npm audit clean.
@JSONbored JSONbored added gittensor:priority Maintainer-selected Gittensor priority — scores a 1.5x multiplier. maintainer-only Owner-only work — yields no Gittensor points. and removed gittensor:feature Gittensor-scored feature linked to a feature issue — scores a 0.25x multiplier. labels Jun 24, 2026
@superagent-security superagent-security Bot removed the pr:flagged PR flagged for review by security analysis. label Jun 24, 2026

@superagent-security superagent-security Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Superagent found 2 security concern(s).

Comment thread Dockerfile Outdated
Comment thread Dockerfile
@superagent-security superagent-security Bot added the pr:flagged PR flagged for review by security analysis. label Jun 24, 2026
- Remove unit-test and typecheck steps (covered by main CI validate job)
- Add npm dependency caching via setup-node cache: 'npm'
- Add docker/setup-buildx-action + GHA layer caching: node:24-slim layer
  is served from cache on subsequent runs, avoiding Docker Hub transient failures
- Wrap Docker build in a 3-attempt retry loop for cold-cache runs
- Add test/integration/selfhost-pg* to path triggers
…te limits

Switch FROM node:24-slim to public.ecr.aws/docker/library/node:24-slim in the
Dockerfile (both build and runtime stages). ECR Public Gallery mirrors Docker
Official Images with no rate limits and no auth, eliminating 503s in CI and
operator builds. GHA BuildKit layer cache still applies for fast reruns.
… deploy options

Docker Compose profile system (#1199): 9 optional profiles activated via `--profile`
— postgres, pgbouncer, redis, ollama, litestream, caddy, observability, tailscale, runners.
Operators compose exactly the stack they need; the core service always runs profile-free.

Caddy (#1203): HTTPS reverse proxy with auto-TLS via Let's Encrypt; zstd/gzip compression;
security headers (HSTS, X-Frame-Options, X-Content-Type-Options, Referrer-Policy).

Prometheus + Grafana (#1206): pre-wired observability at --profile observability; 15s scrape
interval; pre-provisioned datasource and a Grafana dashboard (queue depth, dead-letter count,
HTTP request rate, job throughput by status).

Tailscale sidecar (#1204): --profile tailscale exposes the instance on an operator's tailnet
without any public firewall rules; TS_AUTHKEY + persistent state volume.

Self-hosted GitHub Actions runner (#1205): --profile runners registers a runner against any
repo/org; mounts /var/run/docker.sock for in-runner Docker builds.

Terraform (Hetzner) (#1209): cx22 VPS + 20 GB volume + firewall; cloud-init installs Docker
from the official apt repo; one terraform apply to a ready-to-clone host.

Railway template (#1210): railway.json with Dockerfile builder, /health check, ON_FAILURE
restart policy — one-click deploy, eligible for Railway Template Marketplace creator revenue.

Worker concurrency (#1201): QUEUE_CONCURRENCY env var (default 1) controls how many concurrent
pump() loops run per instance; both SQLite and Postgres queues updated; stop()/drain() wait
for active === 0.

Structured audit log (#1202): logAudit() writes one JSON line per job lifecycle event
(job_complete, job_dead, job_error) to stdout — level, ts, job_id, payload_type, latency_ms,
attempts, error — captured by Docker's json-file driver with zero operator configuration.

Coverage: new selfhost-audit.test.ts + selfhost-pg-queue.test.ts (fills the pre-existing gap
referenced by selfhost-pg-vectorize.test.ts); concurrency branch tests in
selfhost-sqlite-queue.test.ts; all gates green at 97.06% line / 94.81% branch.
@JSONbored
JSONbored force-pushed the feat/self-host-docker branch from 7a77b8f to 3ec6239 Compare June 24, 2026 09:08
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jun 24, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
gittensory-ui 3ec6239 Commit Preview URL

Branch Preview URL
Jun 24 2026, 09:10 AM

@superagent-security superagent-security Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Superagent found 3 security concern(s).

Comment thread docker-compose.yml Outdated
Comment thread docker-compose.yml Outdated
Comment thread docker-compose.yml Outdated
- Pin puppeteer-core to 22.13.1 in Dockerfile (was unpinned)
- Add CSRF state nonce to /setup → /setup/callback flow: generate a
  UUID per-visit, embed it in the manifest redirect_url, bind it to
  the browser via an HttpOnly SameSite=Lax cookie, and validate both
  match in the callback (prevents manifest code injection)
- Remove /var/run/docker.sock mount from runner service; add inline
  guidance for DinD alternative
- Pin runner image from :latest to :ubuntu-22.04
- Require GRAFANA_ADMIN_PASSWORD explicitly (fail-fast :? expansion)
@superagent-security superagent-security Bot removed the pr:flagged PR flagged for review by security analysis. label Jun 24, 2026
…ostgres readiness wait (#1217)

- Redis cache (redis-cache.ts): webhook delivery dedup — marks delivery IDs after
  a successful response so GitHub retries are handled idempotently; rate limiter now
  shares the same client connection as the cache
- Qdrant adapter (qdrant-vectorize.ts): optional RAG vector backend behind
  --profile qdrant; deterministic SHA-1 → UUID ID mapping, QDRANT_API_KEY auth,
  labeled error counters per operation, graceful-degrade on network errors
- waitForPostgres: exponential-backoff retry prevents crash loops when gittensory
  starts before Postgres is ready in compose stacks
- Grafana dashboard: full datasource-UID fix, System Health row, Qdrant panels,
  webhook dedup counter; all counter metrics pre-initialized to 0 at startup

@superagent-security superagent-security Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Superagent found 1 security concern(s).

@superagent-security superagent-security Bot added the pr:flagged PR flagged for review by security analysis. label Jun 24, 2026

@superagent-security superagent-security Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Superagent found 1 security concern(s).

Comment thread src/server.ts Outdated
… tests

All mock.calls[0] casts now use `as unknown as [...]` to satisfy the strict
mock type — was causing typecheck (lint CI job) to fail on the Docker PR.
@superagent-security

Copy link
Copy Markdown
Contributor

Superagent didn't find any vulnerabilities or security issues in this PR.

@superagent-security superagent-security Bot removed the pr:flagged PR flagged for review by security analysis. label Jun 24, 2026
…ost-header redirect attack

The setup wizard derived the App manifest origin from
`process.env.PUBLIC_API_ORIGIN ?? new URL(request.url).origin`.  When
PUBLIC_API_ORIGIN is unset, the fallback uses the Host header from the
incoming request.  An attacker who can craft a request with a spoofed
Host header (e.g. `evil.com`) causes the manifest's redirect_url to
point at an attacker-controlled domain.  GitHub follows that redirect on
App creation, allowing the attacker to exchange the one-time code for
the App's private key and webhook secret.

Fix: remove the request.url fallback entirely.  If PUBLIC_API_ORIGIN is
not set the wizard now returns 400 with a clear operator message.  The
origin used to build the manifest is always the operator-configured
value, never derived from the request.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment