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
Showing 4 changed files+1352-0
▼.circleci/config.yml
+222-0
| @@ -0,0 +1,222 @@ | ||
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
| @@ -0,0 +1,566 @@ | ||
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
| @@ -0,0 +1,563 @@ | ||
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,6 +78,7 @@ Best practices and workflows: | ||
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 | |