Repository navigation
Expand file tree
/
Copy pathdocker-compose.yml
More file actions
156 lines (152 loc) · 6.88 KB
/
Copy pathdocker-compose.yml
File metadata and controls
156 lines (152 loc) · 6.88 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
# Dependent-audit service: web UI + HTTP API + crawler worker.
#
# browser ──▶ nginx (:8080) ── static SPA (built into the nginx image)
# │
# ├── /api/* ──▶ gunicorn/flask web API ──┐
# └── /api/jobs/*/events ─▶ (same, unbuffered SSE) ├── shared jobs volume
# worker (crawler) ────────┘
#
# POST /api/jobs returns a job id immediately; the worker runs the crawl and
# appends progress events to the job's events.jsonl, which the web tier
# streams to the browser over Server-Sent Events. Deep searches never hold a
# request thread for the crawl. See deploy/README.md for the API.
#
# Bring it up with: docker compose up --build then open http://localhost:8080
services:
audit:
build:
context: .
dockerfile: deploy/service/Dockerfile
image: dependent-audit-service
expose:
- "8000"
environment:
JOBS_DIR: /data/jobs
WEB_CONCURRENCY: "${WEB_CONCURRENCY:-2}"
# Each open live-progress stream holds a thread; see gunicorn.conf.py.
WEB_THREADS: "${WEB_THREADS:-16}"
SSE_MAX_STREAMS: "${SSE_MAX_STREAMS:-12}"
SSE_MAX_SECONDS: "${SSE_MAX_SECONDS:-60}"
GUNICORN_TIMEOUT: "${GUNICORN_TIMEOUT:-120}"
# --- Accounts (deploy/DEPLOY.md, "Accounts and sign-in") ---
# The site's public address: email links and OAuth callbacks use it.
PUBLIC_URL: "${PUBLIC_URL:-http://localhost:8080}"
ACCOUNTS_DIR: /data/accounts
# Encrypts users' saved tokens. Generate: python manage.py gen-key
TOKEN_ENCRYPTION_KEY: "${TOKEN_ENCRYPTION_KEY:-}"
ADMIN_EMAILS: "${ADMIN_EMAILS:-}"
ALLOWED_EMAIL_DOMAINS: "${ALLOWED_EMAIL_DOMAINS:-}"
SIGNUP_OPEN: "${SIGNUP_OPEN:-1}"
PASSWORD_SIGNUP: "${PASSWORD_SIGNUP:-1}"
SESSION_TTL_DAYS: "${SESSION_TTL_DAYS:-30}"
# Sign-in emails / password attempts per IP per hour (0 = unlimited).
MAIL_PER_IP_PER_HOUR: "${MAIL_PER_IP_PER_HOUR:-30}"
LOGINS_PER_IP_PER_HOUR: "${LOGINS_PER_IP_PER_HOUR:-100}"
# Email (sign-in links, confirmations, resets): smtp | console | none.
MAIL_BACKEND: "${MAIL_BACKEND:-}"
MAIL_FROM: "${MAIL_FROM:-Dependent Audit <no-reply@localhost>}"
SMTP_HOST: "${SMTP_HOST:-}"
SMTP_PORT: "${SMTP_PORT:-}"
SMTP_USER: "${SMTP_USER:-}"
SMTP_PASSWORD: "${SMTP_PASSWORD:-}"
SMTP_TLS: "${SMTP_TLS:-starttls}"
# Sign in with GitHub / Google: enabled when both id and secret are set.
GITHUB_CLIENT_ID: "${GITHUB_CLIENT_ID:-}"
GITHUB_CLIENT_SECRET: "${GITHUB_CLIENT_SECRET:-}"
GOOGLE_CLIENT_ID: "${GOOGLE_CLIENT_ID:-}"
GOOGLE_CLIENT_SECRET: "${GOOGLE_CLIENT_SECRET:-}"
# Users' own Sourcegraph/GitHub tokens are checked against those APIs
# when saved and at submit (0 skips), at most N checks per IP per hour.
VALIDATE_TOKENS: "${VALIDATE_TOKENS:-1}"
TOKEN_CHECKS_PER_HOUR: "${TOKEN_CHECKS_PER_HOUR:-30}"
# Reuse an identical request's result if it succeeded this recently (s);
# 0 disables. Sourcegraph re-syncs every repo at least every 8h.
RESULT_CACHE_SECONDS: "${RESULT_CACHE_SECONDS:-21600}"
# Abuse limits on new audits, per account (0 disables each).
SUBMITS_PER_HOUR: "${SUBMITS_PER_HOUR:-10}"
MAX_ACTIVE_PER_CLIENT: "${MAX_ACTIVE_PER_CLIENT:-3}"
MAX_QUEUED: "${MAX_QUEUED:-50}"
# Behind an SSO proxy that sets X-Forwarded-User (and -Email): that
# user is signed in. Only with a proxy that strips client-sent copies.
TRUST_FORWARDED_USER: "${TRUST_FORWARDED_USER:-0}"
LOG_LEVEL: "${LOG_LEVEL:-info}"
volumes:
- jobs:/data/jobs
# Accounts and saved tokens: web tier only, never the worker.
- accounts:/data/accounts
healthcheck:
test: ["CMD", "python", "-c",
"import urllib.request,sys; sys.exit(0 if urllib.request.urlopen('http://localhost:8000/health').status==200 else 1)"]
interval: 30s
timeout: 5s
retries: 3
start_period: 10s
restart: unless-stopped
worker:
# Reuses the image built by the `audit` service above (same Dockerfile).
# Only one service declares `build:` so `up --build` can't race two builds
# of the same tag.
image: dependent-audit-service
depends_on:
- audit
command: ["python", "worker.py"]
environment:
JOBS_DIR: /data/jobs
# No SG_TOKEN/GH_TOKEN here: every audit runs on the requesting user's
# own tokens (saved to their account; the worker drops any it inherits),
# so users don't share one rate limit.
AUDIT_EMAIL: "${AUDIT_EMAIL:-audit-bot@example.com}"
# OpenAlex: API key (secret; a request may bring its own) and
# worker-wide defaults for its knobs.
OPENALEX_API_KEY: "${OPENALEX_API_KEY:-}"
OPENALEX_DELAY: "${OPENALEX_DELAY:-0}"
CITATION_GRAPH_DEPTH: "${CITATION_GRAPH_DEPTH:-1}"
# Optional paper-relevance LLM tie-breaker (server-side only).
RELEVANCE_LLM_URL: "${RELEVANCE_LLM_URL:-}"
RELEVANCE_LLM_MODEL: "${RELEVANCE_LLM_MODEL:-local-model}"
RELEVANCE_LLM_TOKEN: "${RELEVANCE_LLM_TOKEN:-}"
WORKER_CONCURRENCY: "${WORKER_CONCURRENCY:-2}"
AUDIT_TIMEOUT: "${AUDIT_TIMEOUT:-3600}"
POLL_INTERVAL: "${POLL_INTERVAL:-2.0}"
# Retention: finished jobs are deleted after JOB_TTL seconds (30 days),
# and only the newest MAX_FINISHED_JOBS are kept.
JOB_TTL: "${JOB_TTL:-2592000}"
MAX_FINISHED_JOBS: "${MAX_FINISHED_JOBS:-2000}"
# Published audits instead live this long (0 = keep) and don't count
# toward MAX_FINISHED_JOBS.
PUBLISHED_JOB_TTL: "${PUBLISHED_JOB_TTL:-0}"
STALE_AFTER: "${STALE_AFTER:-300}"
LOG_LEVEL: "${LOG_LEVEL:-info}"
volumes:
- jobs:/data/jobs
healthcheck:
# Worker has no HTTP; check the heartbeat file it touches each loop.
test: ["CMD", "python", "-c",
"import os,time,sys; p='/tmp/worker.alive'; sys.exit(0 if os.path.isfile(p) and time.time()-os.path.getmtime(p) < 60 else 1)"]
interval: 30s
timeout: 5s
retries: 3
start_period: 10s
restart: unless-stopped
nginx:
# Multi-stage: builds frontend/ with node, then serves dist/ + nginx.conf.
build:
context: .
dockerfile: deploy/nginx/Dockerfile
image: dependent-audit-nginx
depends_on:
- audit
ports:
- "${HTTP_PORT:-8080}:80"
environment:
# Per-client request rate for /api (nginx syntax), burst allowance, and
# max concurrent live-progress streams per client.
API_RATE_LIMIT: "${API_RATE_LIMIT:-10r/s}"
API_RATE_BURST: "${API_RATE_BURST:-30}"
SSE_PER_CLIENT: "${SSE_PER_CLIENT:-6}"
# Address range of a proxy in front (so limits see real clients).
TRUSTED_PROXY_CIDR: "${TRUSTED_PROXY_CIDR:-127.0.0.1/32}"
restart: unless-stopped
volumes:
jobs:
accounts: