docs(ci): add CircleCI worker guide + config — 3 approaches for running background workers

Swadhin Biswas committed 5 months ago
f58315a
1 parent f5bade1
commit f58315a
4 files changed
+1352 -0
▼.circleci/config.yml
+222-0
1
+version: 2.1
2
+
3
+# ──────────────────────────────────────────────────────────────
4
+# OpenCodeHub Worker - CircleCI Configuration
5
+# Run background workers (merge queue, mirror sync, cleanup)
6
+# using CircleCI infrastructure
7
+# ──────────────────────────────────────────────────────────────
8
+
9
+executors:
10
+ bun-executor:
11
+ docker:
12
+ - image: oven/bun:1
13
+ resource_class: medium
14
+
15
+ # Self-hosted runner executor (recommended for production)
16
+ # Replace 'your-namespace' with your actual CircleCI namespace
17
+ och-runner:
18
+ machine:
19
+ image: ubuntu-2204:current
20
+ resource_class: your-namespace/opencodehub-runner
21
+
22
+# ── Jobs ─────────────────────────────────────────────────────
23
+
24
+jobs:
25
+ # ──────────────────────────────────────────────
26
+ # Continuous Background Worker (Self-Hosted Runner)
27
+ # ──────────────────────────────────────────────
28
+ worker-continuous:
29
+ executor: och-runner
30
+ environment:
31
+ NODE_ENV: production
32
+ WORKER_INTERVAL: 5000
33
+ WORKER_HEALTH_PORT: 9090
34
+ WORKER_STALE_TIMEOUT: 300000
35
+ steps:
36
+ - checkout
37
+
38
+ - run:
39
+ name: Install Bun
40
+ command: |
41
+ if ! command -v bun &> /dev/null; then
42
+ curl -fsSL https://bun.sh/install | bash
43
+ echo 'export PATH="$HOME/.bun/bin:$PATH"' >> $BASH_ENV
44
+ fi
45
+ source $BASH_ENV
46
+ bun --version
47
+
48
+ - run:
49
+ name: Install dependencies
50
+ command: |
51
+ for i in 1 2 3; do
52
+ bun install --frozen-lockfile && exit 0
53
+ echo "bun install attempt $i failed, retrying in 10s..."
54
+ sleep 10
55
+ done
56
+ echo "::error::bun install failed after 3 attempts"
57
+ exit 1
58
+
59
+ - run:
60
+ name: Start OpenCodeHub Worker
61
+ command: |
62
+ echo "Starting OpenCodeHub background worker..."
63
+ bun run scripts/worker.ts &
64
+ WORKER_PID=$!
65
+ echo "Worker PID: $WORKER_PID"
66
+
67
+ # Wait for health check
68
+ for i in $(seq 1 30); do
69
+ if curl -sf http://localhost:9090/health; then
70
+ echo "✅ Worker is healthy"
71
+ break
72
+ fi
73
+ echo "Waiting for worker to start... ($i/30)"
74
+ sleep 2
75
+ done
76
+
77
+ # Keep alive
78
+ wait $WORKER_PID
79
+ no_output_timeout: 24h
80
+
81
+ # ──────────────────────────────────────────────
82
+ # Merge Queue Worker (Scheduled / Docker)
83
+ # ──────────────────────────────────────────────
84
+ merge-queue:
85
+ executor: bun-executor
86
+ environment:
87
+ NODE_ENV: production
88
+ WORKER_INTERVAL: 5000
89
+ WORKER_STALE_TIMEOUT: 300000
90
+ steps:
91
+ - checkout
92
+
93
+ - run:
94
+ name: Install dependencies
95
+ command: |
96
+ for i in 1 2 3; do
97
+ bun install --frozen-lockfile && exit 0
98
+ echo "bun install attempt $i failed, retrying in 10s..."
99
+ sleep 10
100
+ done
101
+ echo "::error::bun install failed after 3 attempts"
102
+ exit 1
103
+
104
+ - run:
105
+ name: Process Merge Queue
106
+ command: |
107
+ echo "Running merge queue processor for 4 hours..."
108
+ timeout 14400 bun run scripts/worker.ts || true
109
+ echo "Merge queue cycle complete."
110
+ no_output_timeout: 4h
111
+
112
+ # ──────────────────────────────────────────────
113
+ # Mirror Sync (Scheduled)
114
+ # ──────────────────────────────────────────────
115
+ mirror-sync:
116
+ executor: bun-executor
117
+ environment:
118
+ NODE_ENV: production
119
+ steps:
120
+ - checkout
121
+
122
+ - run:
123
+ name: Install dependencies
124
+ command: bun install --frozen-lockfile
125
+
126
+ - run:
127
+ name: Sync Mirrors
128
+ command: bun run scripts/worker.ts --task=mirror-sync || true
129
+
130
+ # ──────────────────────────────────────────────
131
+ # Cleanup (Scheduled)
132
+ # ──────────────────────────────────────────────
133
+ cleanup:
134
+ executor: bun-executor
135
+ environment:
136
+ NODE_ENV: production
137
+ steps:
138
+ - checkout
139
+
140
+ - run:
141
+ name: Install dependencies
142
+ command: bun install --frozen-lockfile
143
+
144
+ - run:
145
+ name: Run Cleanup Tasks
146
+ command: bun run scripts/worker.ts --task=cleanup || true
147
+
148
+ # ──────────────────────────────────────────────
149
+ # Worker Health Check
150
+ # ──────────────────────────────────────────────
151
+ health-check:
152
+ executor: bun-executor
153
+ steps:
154
+ - run:
155
+ name: Check Worker Health
156
+ command: |
157
+ curl -sf http://your-worker-host:9090/health || exit 1
158
+ echo "✅ Worker is healthy"
159
+
160
+# ── Workflows ────────────────────────────────────────────────
161
+
162
+workflows:
163
+ version: 2
164
+
165
+ # OPTION A: Continuous worker (requires self-hosted runner)
166
+ # Uncomment this if you have a self-hosted runner
167
+ # worker-continuous:
168
+ # jobs:
169
+ # - worker-continuous:
170
+ # filters:
171
+ # branches:
172
+ # only:
173
+ # - main
174
+
175
+ # OPTION B: Scheduled worker tasks (no self-hosted runner needed)
176
+ # Runs merge queue every 4 hours
177
+ scheduled-merge-queue:
178
+ triggers:
179
+ - schedule:
180
+ cron: "0 */4 * * *"
181
+ filters:
182
+ branches:
183
+ only:
184
+ - main
185
+ jobs:
186
+ - merge-queue
187
+
188
+ # Runs mirror sync every hour
189
+ scheduled-mirror-sync:
190
+ triggers:
191
+ - schedule:
192
+ cron: "0 * * * *"
193
+ filters:
194
+ branches:
195
+ only:
196
+ - main
197
+ jobs:
198
+ - mirror-sync
199
+
200
+ # Runs cleanup daily at 3 AM UTC
201
+ scheduled-cleanup:
202
+ triggers:
203
+ - schedule:
204
+ cron: "0 3 * * *"
205
+ filters:
206
+ branches:
207
+ only:
208
+ - main
209
+ jobs:
210
+ - cleanup
211
+
212
+ # Health check every hour
213
+ scheduled-health:
214
+ triggers:
215
+ - schedule:
216
+ cron: "0 * * * *"
217
+ filters:
218
+ branches:
219
+ only:
220
+ - main
221
+ jobs:
222
+ - health-check
0
223
▼docs-site/src/content/docs/administration/circleci-worker.md
+566-0
1
+---
2
+title: "Run Workers on CircleCI"
3
+description: "Run OpenCodeHub background workers using CircleCI instead of GitHub Actions"
4
+---
5
+
6
+> **Goal:** Run OpenCodeHub background workers using CircleCI instead of GitHub Actions.
7
+
8
+OpenCodeHub's worker (`scripts/worker.ts`) is a **long-running background process** that continuously processes:
9
+- Merge queue items
10
+- Mirror sync jobs
11
+- Cleanup tasks
12
+- Webhook deliveries
13
+
14
+Since CircleCI is primarily a CI/CD platform (not a background process host), there are **three approaches** to run the worker on CircleCI infrastructure.
15
+
16
+---
17
+
18
+## Approach 1: Self-Hosted CircleCI Runner (Recommended)
19
+
20
+Use CircleCI's [self-hosted runner](https://circleci.com/docs/runner-overview/) on your own VPS. The runner stays online and executes the worker process continuously.
21
+
22
+### Why This Works Best
23
+- The runner is a persistent agent on your server
24
+- Can run long-lived processes like the OpenCodeHub worker
25
+- You control the machine (RAM, disk, Docker access)
26
+- Can also run CI/CD jobs for repositories
27
+
28
+### Step 1: Install CircleCI Runner on Your VPS
29
+
30
+```bash
31
+# Download and install the runner
32
+curl -sSL https://runner.circleci.com/install.sh | bash
33
+
34
+# Configure the runner
35
+sudo mkdir -p /var/lib/circleci-runner
36
+cat > /var/lib/circleci-runner/launch-agent-config.yaml << 'EOF'
37
+api:
38
+ auth_token: "${CIRCLECI_RUNNER_TOKEN}"
39
+runner:
40
+ name: opencodehub-worker-1
41
+ working_directory: /var/lib/circleci-runner/workdir
42
+ cleanup_working_directory: true
43
+EOF
44
+
45
+# Start the runner service
46
+sudo circleci-runner service install
47
+sudo circleci-runner service start
48
+```
49
+
50
+### Step 2: Create `.circleci/config.yml` in OpenCodeHub Repo
51
+
52
+```yaml
53
+version: 2.1
54
+
55
+executors:
56
+ och-worker:
57
+ machine:
58
+ image: ubuntu-2204:current
59
+ resource_class: your-namespace/opencodehub-runner # Your self-hosted runner
60
+
61
+jobs:
62
+ # ──────────────────────────────────────────────
63
+ # Job 1: Run Background Worker (Continuous)
64
+ # ──────────────────────────────────────────────
65
+ worker:
66
+ executor: och-worker
67
+ environment:
68
+ NODE_ENV: production
69
+ DATABASE_URL: ${DATABASE_URL}
70
+ REDIS_URL: ${REDIS_URL}
71
+ JWT_SECRET: ${JWT_SECRET}
72
+ WORKER_INTERVAL: 5000
73
+ WORKER_HEALTH_PORT: 9090
74
+ steps:
75
+ - checkout
76
+
77
+ - run:
78
+ name: Install Bun
79
+ command: |
80
+ curl -fsSL https://bun.sh/install | bash
81
+ echo 'export PATH="$HOME/.bun/bin:$PATH"' >> $BASH_ENV
82
+ source $BASH_ENV
83
+
84
+ - run:
85
+ name: Install dependencies
86
+ command: |
87
+ for i in 1 2 3; do
88
+ bun install --frozen-lockfile && break
89
+ echo "Attempt $i failed, retrying..."
90
+ sleep 10
91
+ done
92
+
93
+ - run:
94
+ name: Start OpenCodeHub Worker
95
+ command: |
96
+ echo "Starting OpenCodeHub background worker..."
97
+ bun run scripts/worker.ts &
98
+ WORKER_PID=$!
99
+ echo "Worker PID: $WORKER_PID"
100
+
101
+ # Wait for health check to pass
102
+ for i in $(seq 1 30); do
103
+ if curl -sf http://localhost:9090/health; then
104
+ echo "✅ Worker is healthy"
105
+ break
106
+ fi
107
+ echo "Waiting for worker to start... ($i/30)"
108
+ sleep 2
109
+ done
110
+
111
+ # Keep the job alive (CircleCI will show it as "Running")
112
+ echo "Worker running. Press Ctrl+C to stop."
113
+ wait $WORKER_PID
114
+ no_output_timeout: 24h # Keep worker running for up to 24 hours
115
+
116
+ - run:
117
+ name: Worker crashed - restart logic
118
+ command: |
119
+ echo "Worker exited. Checking logs..."
120
+ # CircleCI will automatically retry based on job settings
121
+ when: on_fail
122
+
123
+ # ──────────────────────────────────────────────
124
+ # Job 2: Run Worker Tasks On-Demand
125
+ # ──────────────────────────────────────────────
126
+ worker-tasks:
127
+ executor: och-worker
128
+ environment:
129
+ NODE_ENV: production
130
+ DATABASE_URL: ${DATABASE_URL}
131
+ REDIS_URL: ${REDIS_URL}
132
+ steps:
133
+ - checkout
134
+
135
+ - run:
136
+ name: Install Bun
137
+ command: |
138
+ curl -fsSL https://bun.sh/install | bash
139
+ echo 'export PATH="$HOME/.bun/bin:$PATH"' >> $BASH_ENV
140
+ source $BASH_ENV
141
+
142
+ - run:
143
+ name: Install dependencies
144
+ command: |
145
+ for i in 1 2 3; do
146
+ bun install --frozen-lockfile && break
147
+ echo "Attempt $i failed, retrying..."
148
+ sleep 10
149
+ done
150
+
151
+ - run:
152
+ name: Run Merge Queue Processor
153
+ command: |
154
+ echo "Processing merge queues..."
155
+ bun run scripts/worker.ts --once # If you add a --once flag
156
+ # OR run a specific task script
157
+ # bun run scripts/process-merge-queue.ts
158
+
159
+ - run:
160
+ name: Run Mirror Sync
161
+ command: |
162
+ echo "Syncing mirrors..."
163
+ # bun run scripts/sync-mirrors.ts
164
+
165
+ - run:
166
+ name: Run Cleanup
167
+ command: |
168
+ echo "Running cleanup tasks..."
169
+ # bun run scripts/cleanup-repos.ts
170
+
171
+ # ──────────────────────────────────────────────
172
+ # Job 3: Health Check (Scheduled)
173
+ # ──────────────────────────────────────────────
174
+ worker-health-check:
175
+ executor: och-worker
176
+ steps:
177
+ - run:
178
+ name: Check Worker Health
179
+ command: |
180
+ curl -sf http://localhost:9090/health || exit 1
181
+ echo "✅ Worker is healthy"
182
+
183
+workflows:
184
+ version: 2
185
+
186
+ # Continuous worker (always running)
187
+ worker-continuous:
188
+ jobs:
189
+ - worker:
190
+ filters:
191
+ branches:
192
+ only:
193
+ - main
194
+
195
+ # Scheduled tasks (every 5 minutes)
196
+ worker-scheduled:
197
+ triggers:
198
+ - schedule:
199
+ cron: "*/5 * * * *"
200
+ filters:
201
+ branches:
202
+ only:
203
+ - main
204
+ jobs:
205
+ - worker-tasks
206
+
207
+ # Health check every hour
208
+ worker-health:
209
+ triggers:
210
+ - schedule:
211
+ cron: "0 * * * *"
212
+ filters:
213
+ branches:
214
+ only:
215
+ - main
216
+ jobs:
217
+ - worker-health-check
218
+```
219
+
220
+### Step 3: Set Environment Variables in CircleCI
221
+
222
+Go to **Project Settings** → **Environment Variables**:
223
+
224
+| Variable | Value | Description |
225
+|----------|-------|-------------|
226
+| `DATABASE_URL` | `postgresql://...` | Your PostgreSQL connection string |
227
+| `REDIS_URL` | `redis://...` | Your Redis connection string |
228
+| `JWT_SECRET` | `...` | JWT secret |
229
+| `SESSION_SECRET` | `...` | Session secret |
230
+| `WORKFLOW_SECRET_ENCRYPTION_KEY` | `...` | For CI secrets |
231
+| `CIRCLECI_RUNNER_TOKEN` | `...` | Runner registration token |
232
+
233
+---
234
+
235
+## Approach 2: CircleCI Scheduled Pipelines (No Self-Hosted Runner)
236
+
237
+If you don't want to manage a self-hosted runner, use CircleCI's [scheduled pipelines](https://circleci.com/docs/scheduled-pipelines/) to run the worker periodically.
238
+
239
+### Limitations
240
+- Jobs have a maximum runtime (5 hours on free plan, longer on paid)
241
+- Worker will restart every 5 hours
242
+- Not truly "continuous" but works for light workloads
243
+
244
+### `.circleci/config.yml`
245
+
246
+```yaml
247
+version: 2.1
248
+
249
+executors:
250
+ bun-executor:
251
+ docker:
252
+ - image: oven/bun:1
253
+ resource_class: medium
254
+
255
+jobs:
256
+ merge-queue-worker:
257
+ executor: bun-executor
258
+ environment:
259
+ NODE_ENV: production
260
+ DATABASE_URL: ${DATABASE_URL}
261
+ REDIS_URL: ${REDIS_URL}
262
+ WORKER_INTERVAL: 5000
263
+ WORKER_STALE_TIMEOUT: 300000
264
+ steps:
265
+ - checkout
266
+
267
+ - run:
268
+ name: Install dependencies
269
+ command: |
270
+ for i in 1 2 3; do
271
+ bun install --frozen-lockfile && break
272
+ echo "Attempt $i failed, retrying..."
273
+ sleep 10
274
+ done
275
+
276
+ - run:
277
+ name: Run Merge Queue Worker
278
+ command: |
279
+ echo "Starting worker for 4.5 hours..."
280
+ timeout 16200 bun run scripts/worker.ts || true
281
+ echo "Worker cycle complete. Will restart on next schedule."
282
+ no_output_timeout: 4.5h
283
+
284
+ mirror-sync:
285
+ executor: bun-executor
286
+ environment:
287
+ NODE_ENV: production
288
+ DATABASE_URL: ${DATABASE_URL}
289
+ steps:
290
+ - checkout
291
+
292
+ - run:
293
+ name: Install dependencies
294
+ command: bun install --frozen-lockfile
295
+
296
+ - run:
297
+ name: Sync Mirrors
298
+ command: bun run scripts/worker.ts --task=mirror-sync
299
+
300
+ cleanup:
301
+ executor: bun-executor
302
+ environment:
303
+ NODE_ENV: production
304
+ DATABASE_URL: ${DATABASE_URL}
305
+ steps:
306
+ - checkout
307
+
308
+ - run:
309
+ name: Install dependencies
310
+ command: bun install --frozen-lockfile
311
+
312
+ - run:
313
+ name: Run Cleanup
314
+ command: bun run scripts/worker.ts --task=cleanup
315
+
316
+workflows:
317
+ # Run merge queue worker every 5 hours
318
+ merge-queue-schedule:
319
+ triggers:
320
+ - schedule:
321
+ cron: "0 */5 * * *"
322
+ filters:
323
+ branches:
324
+ only: main
325
+ jobs:
326
+ - merge-queue-worker
327
+
328
+ # Run mirror sync every hour
329
+ mirror-sync-schedule:
330
+ triggers:
331
+ - schedule:
332
+ cron: "0 * * * *"
333
+ filters:
334
+ branches:
335
+ only: main
336
+ jobs:
337
+ - mirror-sync
338
+
339
+ # Run cleanup daily at 3 AM
340
+ cleanup-schedule:
341
+ triggers:
342
+ - schedule:
343
+ cron: "0 3 * * *"
344
+ filters:
345
+ branches:
346
+ only: main
347
+ jobs:
348
+ - cleanup
349
+```
350
+
351
+---
352
+
353
+## Approach 3: CircleCI Webhook-Triggered Worker
354
+
355
+Trigger the worker via API/webhook when specific events happen (push, PR created, merge queue updated).
356
+
357
+### API Triggered Workflow
358
+
359
+```yaml
360
+version: 2.1
361
+
362
+executors:
363
+ bun-executor:
364
+ docker:
365
+ - image: oven/bun:1
366
+
367
+parameters:
368
+ event_type:
369
+ type: string
370
+ default: "merge-queue"
371
+ repo_id:
372
+ type: string
373
+ default: ""
374
+ pr_id:
375
+ type: string
376
+ default: ""
377
+
378
+jobs:
379
+ process-event:
380
+ executor: bun-executor
381
+ environment:
382
+ NODE_ENV: production
383
+ DATABASE_URL: ${DATABASE_URL}
384
+ REDIS_URL: ${REDIS_URL}
385
+ steps:
386
+ - checkout
387
+
388
+ - run:
389
+ name: Install dependencies
390
+ command: bun install --frozen-lockfile
391
+
392
+ - run:
393
+ name: Process Event
394
+ command: |
395
+ echo "Processing event: << pipeline.parameters.event_type >>"
396
+ case "<< pipeline.parameters.event_type >>" in
397
+ merge-queue)
398
+ bun run scripts/worker.ts --task=merge-queue --repo-id=<< pipeline.parameters.repo_id >>
399
+ ;;
400
+ mirror-sync)
401
+ bun run scripts/worker.ts --task=mirror-sync
402
+ ;;
403
+ cleanup)
404
+ bun run scripts/worker.ts --task=cleanup
405
+ ;;
406
+ *)
407
+ echo "Unknown event type"
408
+ exit 1
409
+ ;;
410
+ esac
411
+
412
+workflows:
413
+ event-driven-worker:
414
+ when:
415
+ equal: [ webhook, << pipeline.trigger_source >> ]
416
+ jobs:
417
+ - process-event
418
+```
419
+
420
+### Trigger via API from OpenCodeHub
421
+
422
+In your OpenCodeHub webhook handler, trigger CircleCI:
423
+
424
+```typescript
425
+// When merge queue is updated
426
+transportToCircleCI({
427
+ event_type: "merge-queue",
428
+ repo_id: repository.id,
429
+ pr_id: pullRequest.id,
430
+});
431
+
432
+async function transportToCircleCI(params: { event_type: string; repo_id: string; pr_id?: string }) {
433
+ const response = await fetch(
434
+ `https://circleci.com/api/v2/project/gh/${org}/${repo}/pipeline`,
435
+ {
436
+ method: "POST",
437
+ headers: {
438
+ "Circle-Token": process.env.CIRCLECI_API_TOKEN!,
439
+ "Content-Type": "application/json",
440
+ },
441
+ body: JSON.stringify({
442
+ branch: "main",
443
+ parameters: params,
444
+ }),
445
+ }
446
+ );
447
+ return response.json();
448
+}
449
+```
450
+
451
+---
452
+
453
+## Approach Comparison
454
+
455
+| Feature | Self-Hosted Runner | Scheduled Pipelines | Webhook-Triggered |
456
+|---------|-------------------|---------------------|-------------------|
457
+| **Continuous?** | ✅ Yes | ⚠️ Every 5 hours | ✅ Event-driven |
458
+| **Setup Complexity** | Medium (VPS needed) | Low | Medium |
459
+| **Cost** | VPS cost only | CircleCI compute minutes | CircleCI compute minutes |
460
+| **Best For** | Production workloads | Light workloads | Event-specific tasks |
461
+| **Docker Support** | ✅ Full | ✅ Yes | ✅ Yes |
462
+| **Persistence** | ✅ Local disk | ❌ Ephemeral | ❌ Ephemeral |
463
+
464
+---
465
+
466
+## Recommended Setup for Production
467
+
468
+### Architecture
469
+
470
+```
471
+VPS (e.g., Hetzner/DigitalOcean)
472
+├── OpenCodeHub Main App (port 4321)
473
+├── PostgreSQL (port 5432)
474
+├── Redis (port 6379)
475
+└── CircleCI Self-Hosted Runner
476
+ └── Runs: bun run scripts/worker.ts
477
+```
478
+
479
+### Why Self-Hosted Runner Wins
480
+1. **Persistence** — Runner stays on your VPS, has access to local git repos
481
+2. **Network** — Can reach PostgreSQL/Redis on `localhost`
482
+3. **Cost** — No CircleCI compute minutes used
483
+4. **Control** — You manage the machine
484
+
485
+### Quick Start Script
486
+
487
+```bash
488
+#!/bin/bash
489
+# setup-circleci-worker.sh
490
+
491
+# 1. Install CircleCI runner
492
+curl -sSL https://runner.circleci.com/install.sh | bash
493
+
494
+# 2. Create runner config
495
+mkdir -p /var/lib/circleci-runner
496
+cat > /var/lib/circleci-runner/launch-agent-config.yaml << EOF
497
+api:
498
+ auth_token: "$CIRCLECI_RUNNER_TOKEN"
499
+runner:
500
+ name: "opencodehub-worker-$(hostname)"
501
+ working_directory: /var/lib/circleci-runner/workdir
502
+ cleanup_working_directory: true
503
+EOF
504
+
505
+# 3. Install and start service
506
+circleci-runner service install
507
+circleci-runner service start
508
+
509
+# 4. Verify
510
+systemctl status circleci-runner
511
+```
512
+
513
+---
514
+
515
+## Monitoring
516
+
517
+### Worker Health Check
518
+
519
+```bash
520
+# Check if worker is running
521
+curl http://localhost:9090/health
522
+
523
+# Expected output
524
+{
525
+ "status": "healthy",
526
+ "uptime": 86400,
527
+ "lastQueueRun": "2026-05-02T12:00:00.000Z",
528
+ "consecutiveErrors": 0,
529
+ "shuttingDown": false
530
+}
531
+```
532
+
533
+### CircleCI Dashboard
534
+
535
+Monitor runner status at:
536
+```
537
+https://app.circleci.com/settings/runner
538
+```
539
+
540
+### Alerts
541
+
542
+Set up alerts in CircleCI when:
543
+- Runner goes offline
544
+- Job fails more than 3 times
545
+- Worker health check returns `unhealthy`
546
+
547
+---
548
+
549
+## Troubleshooting
550
+
551
+| Problem | Solution |
552
+|---------|----------|
553
+| **"Runner not picking up jobs"** | Check runner token is correct. Verify runner is online in CircleCI dashboard. |
554
+| **"Database connection refused"** | Ensure DATABASE_URL uses correct host. For self-hosted runner on same VPS, use `localhost`. |
555
+| **"Worker exits after 5 hours"** | Scheduled pipeline limit. Use self-hosted runner for continuous operation. |
556
+| **"No output for 10 minutes"** | CircleCI kills idle jobs. The worker logs regularly, but add `no_output_timeout: 24h`. |
557
+| **"Cannot find module"** | Make sure `bun install` ran successfully. Check `node_modules` exists. |
558
+| **"Permission denied on repos"** | Runner user needs read/write access to OpenCodeHub's repo storage directory. |
559
+
560
+---
561
+
562
+## Next Steps
563
+
564
+- [CircleCI Integration Guide](/guides/circleci-integration/) — For CI/CD pipelines
565
+- [Deployment Guide](/administration/deployment/) — Production setup
566
+- [Merge Queue](/features/merge-queue/) — How merge queue works
0
567
▼docs/administration/circleci-worker.md
+563-0
1
+# OpenCodeHub Worker on CircleCI
2
+
3
+> **Goal:** Run OpenCodeHub background workers using CircleCI instead of GitHub Actions.
4
+
5
+OpenCodeHub's worker (`scripts/worker.ts`) is a **long-running background process** that continuously processes:
6
+- Merge queue items
7
+- Mirror sync jobs
8
+- Cleanup tasks
9
+- Webhook deliveries
10
+
11
+Since CircleCI is primarily a CI/CD platform (not a background process host), there are **three approaches** to run the worker on CircleCI infrastructure.
12
+
13
+---
14
+
15
+## Approach 1: Self-Hosted CircleCI Runner (Recommended)
16
+
17
+Use CircleCI's [self-hosted runner](https://circleci.com/docs/runner-overview/) on your own VPS. The runner stays online and executes the worker process continuously.
18
+
19
+### Why This Works Best
20
+- The runner is a persistent agent on your server
21
+- Can run long-lived processes like the OpenCodeHub worker
22
+- You control the machine (RAM, disk, Docker access)
23
+- Can also run CI/CD jobs for repositories
24
+
25
+### Step 1: Install CircleCI Runner on Your VPS
26
+
27
+```bash
28
+# Download and install the runner
29
+curl -sSL https://runner.circleci.com/install.sh | bash
30
+
31
+# Configure the runner
32
+sudo mkdir -p /var/lib/circleci-runner
33
+cat > /var/lib/circleci-runner/launch-agent-config.yaml << 'EOF'
34
+api:
35
+ auth_token: "${CIRCLECI_RUNNER_TOKEN}"
36
+runner:
37
+ name: opencodehub-worker-1
38
+ working_directory: /var/lib/circleci-runner/workdir
39
+ cleanup_working_directory: true
40
+EOF
41
+
42
+# Start the runner service
43
+sudo circleci-runner service install
44
+sudo circleci-runner service start
45
+```
46
+
47
+### Step 2: Create `.circleci/config.yml` in OpenCodeHub Repo
48
+
49
+```yaml
50
+version: 2.1
51
+
52
+executors:
53
+ och-worker:
54
+ machine:
55
+ image: ubuntu-2204:current
56
+ resource_class: your-namespace/opencodehub-runner # Your self-hosted runner
57
+
58
+jobs:
59
+ # ──────────────────────────────────────────────
60
+ # Job 1: Run Background Worker (Continuous)
61
+ # ──────────────────────────────────────────────
62
+ worker:
63
+ executor: och-worker
64
+ environment:
65
+ NODE_ENV: production
66
+ DATABASE_URL: ${DATABASE_URL}
67
+ REDIS_URL: ${REDIS_URL}
68
+ JWT_SECRET: ${JWT_SECRET}
69
+ WORKER_INTERVAL: 5000
70
+ WORKER_HEALTH_PORT: 9090
71
+ steps:
72
+ - checkout
73
+
74
+ - run:
75
+ name: Install Bun
76
+ command: |
77
+ curl -fsSL https://bun.sh/install | bash
78
+ echo 'export PATH="$HOME/.bun/bin:$PATH"' >> $BASH_ENV
79
+ source $BASH_ENV
80
+
81
+ - run:
82
+ name: Install dependencies
83
+ command: |
84
+ for i in 1 2 3; do
85
+ bun install --frozen-lockfile && break
86
+ echo "Attempt $i failed, retrying..."
87
+ sleep 10
88
+ done
89
+
90
+ - run:
91
+ name: Start OpenCodeHub Worker
92
+ command: |
93
+ echo "Starting OpenCodeHub background worker..."
94
+ bun run scripts/worker.ts &
95
+ WORKER_PID=$!
96
+ echo "Worker PID: $WORKER_PID"
97
+
98
+ # Wait for health check to pass
99
+ for i in $(seq 1 30); do
100
+ if curl -sf http://localhost:9090/health; then
101
+ echo "✅ Worker is healthy"
102
+ break
103
+ fi
104
+ echo "Waiting for worker to start... ($i/30)"
105
+ sleep 2
106
+ done
107
+
108
+ # Keep the job alive (CircleCI will show it as "Running")
109
+ echo "Worker running. Press Ctrl+C to stop."
110
+ wait $WORKER_PID
111
+ no_output_timeout: 24h # Keep worker running for up to 24 hours
112
+
113
+ - run:
114
+ name: Worker crashed - restart logic
115
+ command: |
116
+ echo "Worker exited. Checking logs..."
117
+ # CircleCI will automatically retry based on job settings
118
+ when: on_fail
119
+
120
+ # ──────────────────────────────────────────────
121
+ # Job 2: Run Worker Tasks On-Demand
122
+ # ──────────────────────────────────────────────
123
+ worker-tasks:
124
+ executor: och-worker
125
+ environment:
126
+ NODE_ENV: production
127
+ DATABASE_URL: ${DATABASE_URL}
128
+ REDIS_URL: ${REDIS_URL}
129
+ steps:
130
+ - checkout
131
+
132
+ - run:
133
+ name: Install Bun
134
+ command: |
135
+ curl -fsSL https://bun.sh/install | bash
136
+ echo 'export PATH="$HOME/.bun/bin:$PATH"' >> $BASH_ENV
137
+ source $BASH_ENV
138
+
139
+ - run:
140
+ name: Install dependencies
141
+ command: |
142
+ for i in 1 2 3; do
143
+ bun install --frozen-lockfile && break
144
+ echo "Attempt $i failed, retrying..."
145
+ sleep 10
146
+ done
147
+
148
+ - run:
149
+ name: Run Merge Queue Processor
150
+ command: |
151
+ echo "Processing merge queues..."
152
+ bun run scripts/worker.ts --once # If you add a --once flag
153
+ # OR run a specific task script
154
+ # bun run scripts/process-merge-queue.ts
155
+
156
+ - run:
157
+ name: Run Mirror Sync
158
+ command: |
159
+ echo "Syncing mirrors..."
160
+ # bun run scripts/sync-mirrors.ts
161
+
162
+ - run:
163
+ name: Run Cleanup
164
+ command: |
165
+ echo "Running cleanup tasks..."
166
+ # bun run scripts/cleanup-repos.ts
167
+
168
+ # ──────────────────────────────────────────────
169
+ # Job 3: Health Check (Scheduled)
170
+ # ──────────────────────────────────────────────
171
+ worker-health-check:
172
+ executor: och-worker
173
+ steps:
174
+ - run:
175
+ name: Check Worker Health
176
+ command: |
177
+ curl -sf http://localhost:9090/health || exit 1
178
+ echo "✅ Worker is healthy"
179
+
180
+workflows:
181
+ version: 2
182
+
183
+ # Continuous worker (always running)
184
+ worker-continuous:
185
+ jobs:
186
+ - worker:
187
+ filters:
188
+ branches:
189
+ only:
190
+ - main
191
+
192
+ # Scheduled tasks (every 5 minutes)
193
+ worker-scheduled:
194
+ triggers:
195
+ - schedule:
196
+ cron: "*/5 * * * *"
197
+ filters:
198
+ branches:
199
+ only:
200
+ - main
201
+ jobs:
202
+ - worker-tasks
203
+
204
+ # Health check every hour
205
+ worker-health:
206
+ triggers:
207
+ - schedule:
208
+ cron: "0 * * * *"
209
+ filters:
210
+ branches:
211
+ only:
212
+ - main
213
+ jobs:
214
+ - worker-health-check
215
+```
216
+
217
+### Step 3: Set Environment Variables in CircleCI
218
+
219
+Go to **Project Settings** → **Environment Variables**:
220
+
221
+| Variable | Value | Description |
222
+|----------|-------|-------------|
223
+| `DATABASE_URL` | `postgresql://...` | Your PostgreSQL connection string |
224
+| `REDIS_URL` | `redis://...` | Your Redis connection string |
225
+| `JWT_SECRET` | `...` | JWT secret |
226
+| `SESSION_SECRET` | `...` | Session secret |
227
+| `WORKFLOW_SECRET_ENCRYPTION_KEY` | `...` | For CI secrets |
228
+| `CIRCLECI_RUNNER_TOKEN` | `...` | Runner registration token |
229
+
230
+---
231
+
232
+## Approach 2: CircleCI Scheduled Pipelines (No Self-Hosted Runner)
233
+
234
+If you don't want to manage a self-hosted runner, use CircleCI's [scheduled pipelines](https://circleci.com/docs/scheduled-pipelines/) to run the worker periodically.
235
+
236
+### Limitations
237
+- Jobs have a maximum runtime (5 hours on free plan, longer on paid)
238
+- Worker will restart every 5 hours
239
+- Not truly "continuous" but works for light workloads
240
+
241
+### `.circleci/config.yml`
242
+
243
+```yaml
244
+version: 2.1
245
+
246
+executors:
247
+ bun-executor:
248
+ docker:
249
+ - image: oven/bun:1
250
+ resource_class: medium
251
+
252
+jobs:
253
+ merge-queue-worker:
254
+ executor: bun-executor
255
+ environment:
256
+ NODE_ENV: production
257
+ DATABASE_URL: ${DATABASE_URL}
258
+ REDIS_URL: ${REDIS_URL}
259
+ WORKER_INTERVAL: 5000
260
+ WORKER_STALE_TIMEOUT: 300000
261
+ steps:
262
+ - checkout
263
+
264
+ - run:
265
+ name: Install dependencies
266
+ command: |
267
+ for i in 1 2 3; do
268
+ bun install --frozen-lockfile && break
269
+ echo "Attempt $i failed, retrying..."
270
+ sleep 10
271
+ done
272
+
273
+ - run:
274
+ name: Run Merge Queue Worker
275
+ command: |
276
+ echo "Starting worker for 4.5 hours..."
277
+ timeout 16200 bun run scripts/worker.ts || true
278
+ echo "Worker cycle complete. Will restart on next schedule."
279
+ no_output_timeout: 4.5h
280
+
281
+ mirror-sync:
282
+ executor: bun-executor
283
+ environment:
284
+ NODE_ENV: production
285
+ DATABASE_URL: ${DATABASE_URL}
286
+ steps:
287
+ - checkout
288
+
289
+ - run:
290
+ name: Install dependencies
291
+ command: bun install --frozen-lockfile
292
+
293
+ - run:
294
+ name: Sync Mirrors
295
+ command: bun run scripts/worker.ts --task=mirror-sync
296
+
297
+ cleanup:
298
+ executor: bun-executor
299
+ environment:
300
+ NODE_ENV: production
301
+ DATABASE_URL: ${DATABASE_URL}
302
+ steps:
303
+ - checkout
304
+
305
+ - run:
306
+ name: Install dependencies
307
+ command: bun install --frozen-lockfile
308
+
309
+ - run:
310
+ name: Run Cleanup
311
+ command: bun run scripts/worker.ts --task=cleanup
312
+
313
+workflows:
314
+ # Run merge queue worker every 5 hours
315
+ merge-queue-schedule:
316
+ triggers:
317
+ - schedule:
318
+ cron: "0 */5 * * *"
319
+ filters:
320
+ branches:
321
+ only: main
322
+ jobs:
323
+ - merge-queue-worker
324
+
325
+ # Run mirror sync every hour
326
+ mirror-sync-schedule:
327
+ triggers:
328
+ - schedule:
329
+ cron: "0 * * * *"
330
+ filters:
331
+ branches:
332
+ only: main
333
+ jobs:
334
+ - mirror-sync
335
+
336
+ # Run cleanup daily at 3 AM
337
+ cleanup-schedule:
338
+ triggers:
339
+ - schedule:
340
+ cron: "0 3 * * *"
341
+ filters:
342
+ branches:
343
+ only: main
344
+ jobs:
345
+ - cleanup
346
+```
347
+
348
+---
349
+
350
+## Approach 3: CircleCI Webhook-Triggered Worker
351
+
352
+Trigger the worker via API/webhook when specific events happen (push, PR created, merge queue updated).
353
+
354
+### API Triggered Workflow
355
+
356
+```yaml
357
+version: 2.1
358
+
359
+executors:
360
+ bun-executor:
361
+ docker:
362
+ - image: oven/bun:1
363
+
364
+parameters:
365
+ event_type:
366
+ type: string
367
+ default: "merge-queue"
368
+ repo_id:
369
+ type: string
370
+ default: ""
371
+ pr_id:
372
+ type: string
373
+ default: ""
374
+
375
+jobs:
376
+ process-event:
377
+ executor: bun-executor
378
+ environment:
379
+ NODE_ENV: production
380
+ DATABASE_URL: ${DATABASE_URL}
381
+ REDIS_URL: ${REDIS_URL}
382
+ steps:
383
+ - checkout
384
+
385
+ - run:
386
+ name: Install dependencies
387
+ command: bun install --frozen-lockfile
388
+
389
+ - run:
390
+ name: Process Event
391
+ command: |
392
+ echo "Processing event: << pipeline.parameters.event_type >>"
393
+ case "<< pipeline.parameters.event_type >>" in
394
+ merge-queue)
395
+ bun run scripts/worker.ts --task=merge-queue --repo-id=<< pipeline.parameters.repo_id >>
396
+ ;;
397
+ mirror-sync)
398
+ bun run scripts/worker.ts --task=mirror-sync
399
+ ;;
400
+ cleanup)
401
+ bun run scripts/worker.ts --task=cleanup
402
+ ;;
403
+ *)
404
+ echo "Unknown event type"
405
+ exit 1
406
+ ;;
407
+ esac
408
+
409
+workflows:
410
+ event-driven-worker:
411
+ when:
412
+ equal: [ webhook, << pipeline.trigger_source >> ]
413
+ jobs:
414
+ - process-event
415
+```
416
+
417
+### Trigger via API from OpenCodeHub
418
+
419
+In your OpenCodeHub webhook handler, trigger CircleCI:
420
+
421
+```typescript
422
+// When merge queue is updated
423
+transportToCircleCI({
424
+ event_type: "merge-queue",
425
+ repo_id: repository.id,
426
+ pr_id: pullRequest.id,
427
+});
428
+
429
+async function transportToCircleCI(params: { event_type: string; repo_id: string; pr_id?: string }) {
430
+ const response = await fetch(
431
+ `https://circleci.com/api/v2/project/gh/${org}/${repo}/pipeline`,
432
+ {
433
+ method: "POST",
434
+ headers: {
435
+ "Circle-Token": process.env.CIRCLECI_API_TOKEN!,
436
+ "Content-Type": "application/json",
437
+ },
438
+ body: JSON.stringify({
439
+ branch: "main",
440
+ parameters: params,
441
+ }),
442
+ }
443
+ );
444
+ return response.json();
445
+}
446
+```
447
+
448
+---
449
+
450
+## Approach Comparison
451
+
452
+| Feature | Self-Hosted Runner | Scheduled Pipelines | Webhook-Triggered |
453
+|---------|-------------------|---------------------|-------------------|
454
+| **Continuous?** | ✅ Yes | ⚠️ Every 5 hours | ✅ Event-driven |
455
+| **Setup Complexity** | Medium (VPS needed) | Low | Medium |
456
+| **Cost** | VPS cost only | CircleCI compute minutes | CircleCI compute minutes |
457
+| **Best For** | Production workloads | Light workloads | Event-specific tasks |
458
+| **Docker Support** | ✅ Full | ✅ Yes | ✅ Yes |
459
+| **Persistence** | ✅ Local disk | ❌ Ephemeral | ❌ Ephemeral |
460
+
461
+---
462
+
463
+## Recommended Setup for Production
464
+
465
+### Architecture
466
+
467
+```
468
+VPS (e.g., Hetzner/DigitalOcean)
469
+├── OpenCodeHub Main App (port 4321)
470
+├── PostgreSQL (port 5432)
471
+├── Redis (port 6379)
472
+└── CircleCI Self-Hosted Runner
473
+ └── Runs: bun run scripts/worker.ts
474
+```
475
+
476
+### Why Self-Hosted Runner Wins
477
+1. **Persistence** — Runner stays on your VPS, has access to local git repos
478
+2. **Network** — Can reach PostgreSQL/Redis on `localhost`
479
+3. **Cost** — No CircleCI compute minutes used
480
+4. **Control** — You manage the machine
481
+
482
+### Quick Start Script
483
+
484
+```bash
485
+#!/bin/bash
486
+# setup-circleci-worker.sh
487
+
488
+# 1. Install CircleCI runner
489
+curl -sSL https://runner.circleci.com/install.sh | bash
490
+
491
+# 2. Create runner config
492
+mkdir -p /var/lib/circleci-runner
493
+cat > /var/lib/circleci-runner/launch-agent-config.yaml << EOF
494
+api:
495
+ auth_token: "$CIRCLECI_RUNNER_TOKEN"
496
+runner:
497
+ name: "opencodehub-worker-$(hostname)"
498
+ working_directory: /var/lib/circleci-runner/workdir
499
+ cleanup_working_directory: true
500
+EOF
501
+
502
+# 3. Install and start service
503
+circleci-runner service install
504
+circleci-runner service start
505
+
506
+# 4. Verify
507
+systemctl status circleci-runner
508
+```
509
+
510
+---
511
+
512
+## Monitoring
513
+
514
+### Worker Health Check
515
+
516
+```bash
517
+# Check if worker is running
518
+curl http://localhost:9090/health
519
+
520
+# Expected output
521
+{
522
+ "status": "healthy",
523
+ "uptime": 86400,
524
+ "lastQueueRun": "2026-05-02T12:00:00.000Z",
525
+ "consecutiveErrors": 0,
526
+ "shuttingDown": false
527
+}
528
+```
529
+
530
+### CircleCI Dashboard
531
+
532
+Monitor runner status at:
533
+```
534
+https://app.circleci.com/settings/runner
535
+```
536
+
537
+### Alerts
538
+
539
+Set up alerts in CircleCI when:
540
+- Runner goes offline
541
+- Job fails more than 3 times
542
+- Worker health check returns `unhealthy`
543
+
544
+---
545
+
546
+## Troubleshooting
547
+
548
+| Problem | Solution |
549
+|---------|----------|
550
+| **"Runner not picking up jobs"** | Check runner token is correct. Verify runner is online in CircleCI dashboard. |
551
+| **"Database connection refused"** | Ensure DATABASE_URL uses correct host. For self-hosted runner on same VPS, use `localhost`. |
552
+| **"Worker exits after 5 hours"** | Scheduled pipeline limit. Use self-hosted runner for continuous operation. |
553
+| **"No output for 10 minutes"** | CircleCI kills idle jobs. The worker logs regularly, but add `no_output_timeout: 24h`. |
554
+| **"Cannot find module"** | Make sure `bun install` ran successfully. Check `node_modules` exists. |
555
+| **"Permission denied on repos"** | Runner user needs read/write access to OpenCodeHub's repo storage directory. |
556
+
557
+---
558
+
559
+## Next Steps
560
+
561
+- [CircleCI Integration Guide](../guides/circleci-integration.md) — For CI/CD pipelines
562
+- [Deployment Guide](../administration/deployment.md) — Production setup
563
+- [Merge Queue](../features/merge-queue.md) — How merge queue works
0
564
▼docs/index.md
+1-0
78
78
- **[Webhooks](guides/webhooks.md)** — Integrate with external services
79
79
- **[Storage Adapters](guides/storage-adapters.md)** — Configure S3, GCS, or local storage
80
80
- **[CircleCI Integration](guides/circleci-integration.md)** — Use CircleCI as your CI/CD provider
81
+- **[Run Workers on CircleCI](administration/circleci-worker.md)** — Background workers via CircleCI
81
82
82
83
### API Reference
83
84
84
85