gabriel / musehub public
nginx-cf.conf
236 lines 10.9 KB
Raw
sha256:3ff9c9863a9891bdcde71b4a43228f66d0493e38b7cc1d09fe9eb7de774046b2 feat: add repair-commit wire endpoint (API parity with repa… Opus 4.8 minor ⚠ breaking 1 day ago
1 # /etc/nginx/sites-available/musehub
2 #
3 # Cloudflare Origin Certificate configuration.
4 # Cloudflare terminates SSL at the edge. This nginx instance accepts HTTPS
5 # connections using a Cloudflare Origin Certificate.
6 #
7 # SSL mode in Cloudflare dashboard MUST be set to "Full (Strict)".
8 # No Certbot or Let's Encrypt required — the Origin Certificate is valid for 15 years.
9 #
10 # IP restriction is enforced at the EC2 security group level (inbound port 443
11 # restricted to Cloudflare IP ranges). Do not duplicate that logic here —
12 # the real_ip module replaces $remote_addr with the true client IP before
13 # allow/deny runs, which would incorrectly block legitimate Cloudflare traffic.
14 #
15 # To generate the Origin Certificate:
16 # Cloudflare Dashboard → <domain> → SSL/TLS → Origin Server → Create Certificate
17 # Choose "Generate private key and CSR with Cloudflare" → RSA (2048) → 15 years
18 # Save certificate → /etc/ssl/cloudflare/origin.pem
19 # Save private key → /etc/ssl/cloudflare/origin.key
20 # chmod 640 /etc/ssl/cloudflare/origin.key
21
22 # Per-IP rate limiting zones — defined at http context level.
23 # Wire endpoints are exempt (MSign auth-gated). Healthz is exempt (fast-path).
24 # 'api' — general unauthenticated surface: 60 req/min with burst headroom
25 # 'auth' — tighter zone reserved for future auth endpoints: 10 req/min
26 limit_req_zone $binary_remote_addr zone=api:10m rate=60r/m;
27 limit_req_zone $binary_remote_addr zone=auth:10m rate=10r/m;
28
29 # Restore the real client IP from the Cloudflare connecting-IP header.
30 # Without this, every request appears to come from a Cloudflare edge node.
31 # Cloudflare IP ranges: https://www.cloudflare.com/ips/
32 real_ip_header CF-Connecting-IP;
33 real_ip_recursive on;
34
35 # Cloudflare IPv4 ranges
36 set_real_ip_from 173.245.48.0/20;
37 set_real_ip_from 103.21.244.0/22;
38 set_real_ip_from 103.22.200.0/22;
39 set_real_ip_from 103.31.4.0/22;
40 set_real_ip_from 141.101.64.0/18;
41 set_real_ip_from 108.162.192.0/18;
42 set_real_ip_from 190.93.240.0/20;
43 set_real_ip_from 188.114.96.0/20;
44 set_real_ip_from 197.234.240.0/22;
45 set_real_ip_from 198.41.128.0/17;
46 set_real_ip_from 162.158.0.0/15;
47 set_real_ip_from 104.16.0.0/13;
48 set_real_ip_from 104.24.0.0/14;
49 set_real_ip_from 172.64.0.0/13;
50 set_real_ip_from 131.0.72.0/22;
51
52 # Cloudflare IPv6 ranges
53 set_real_ip_from 2400:cb00::/32;
54 set_real_ip_from 2606:4700::/32;
55 set_real_ip_from 2803:f800::/32;
56 set_real_ip_from 2405:b500::/32;
57 set_real_ip_from 2405:8100::/32;
58 set_real_ip_from 2a06:98c0::/29;
59 set_real_ip_from 2c0f:f248::/32;
60
61 # Blue-green upstream: deploy.sh rewrites /etc/nginx/musehub-active-port
62 # and runs `nginx -s reload` to switch slots atomically.
63 #
64 # keepalive 32: nginx keeps up to 32 idle connections to uvicorn open,
65 # eliminating the TCP handshake cost on every request.
66 # keepalive_requests 1000: rotate the connection after 1000 requests so
67 # stale file descriptors don't accumulate.
68 # keepalive_timeout 60s: evict idle keepalive connections after 60s of
69 # disuse — matches uvicorn's own idle timeout.
70 upstream musehub_backend {
71 include /etc/nginx/musehub-active-port;
72 keepalive 32;
73 keepalive_requests 1000;
74 keepalive_timeout 60s;
75 }
76
77 # Redirect all plain-HTTP traffic to HTTPS — 301 (permanent, cacheable).
78 # In prod, Cloudflare enforces HTTPS at the edge, so this only fires for
79 # traffic that bypasses Cloudflare (e.g., direct-to-origin access during
80 # ops or monitoring). Belt-and-suspenders.
81 server {
82 listen 80;
83 listen [::]:80;
84 server_name DOMAIN_PLACEHOLDER;
85 return 301 https://$host$request_uri;
86 }
87
88 server {
89 listen 443 ssl http2;
90 listen [::]:443 ssl http2;
91 server_name DOMAIN_PLACEHOLDER;
92
93 ssl_certificate /etc/ssl/cloudflare/origin.pem;
94 ssl_certificate_key /etc/ssl/cloudflare/origin.key;
95
96 # Enforce TLS 1.2 minimum — disable TLS 1.0 and TLS 1.1 (both deprecated).
97 # TLS 1.3 is preferred; 1.2 retained for compatibility with older clients.
98 ssl_protocols TLSv1.2 TLSv1.3;
99 ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256;
100 ssl_prefer_server_ciphers off; # TLS 1.3 ignores this; keep off for 1.2 forward secrecy
101
102 client_max_body_size 500m;
103
104 gzip on;
105 gzip_comp_level 5;
106 gzip_vary on;
107 # Binary types (msgpack) are excluded: already compressed, gzip adds CPU with no size gain.
108 gzip_types text/plain text/css text/javascript application/javascript
109 application/json;
110
111 # Security headers — applied to all responses from this server block.
112 # 'always' ensures they are sent on error responses (4xx, 5xx) too.
113 # Wire location blocks that define their own add_header (MCP, SSE) break
114 # inheritance intentionally — programmatic clients don't need these.
115 add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
116 add_header X-Content-Type-Options "nosniff" always;
117 add_header X-Frame-Options "SAMEORIGIN" always;
118 add_header Referrer-Policy "strict-origin-when-cross-origin" always;
119
120 # ── Healthz — dedicated fast-path, short timeout, no interference ────────────
121 location = /healthz {
122 proxy_pass http://musehub_backend;
123 proxy_http_version 1.1;
124 proxy_set_header Connection "";
125 proxy_set_header Host $host;
126 proxy_set_header X-Real-IP $remote_addr;
127 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
128 proxy_set_header X-Forwarded-Proto $scheme;
129 proxy_read_timeout 5s;
130 proxy_send_timeout 5s;
131 }
132
133 # ── MCP — bidirectional Streamable HTTP, long-lived, never buffer ──────────
134 location ~ ^/mcp(/|$) {
135 proxy_pass http://musehub_backend;
136 proxy_http_version 1.1;
137 proxy_set_header Connection "";
138 proxy_set_header Host $host;
139 proxy_set_header X-Real-IP $remote_addr;
140 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
141 proxy_set_header X-Forwarded-Proto $scheme;
142 proxy_read_timeout 3600s;
143 proxy_send_timeout 3600s;
144 proxy_request_buffering off;
145 proxy_buffering off;
146 add_header X-Accel-Buffering no always;
147 }
148
149 # ── SSE — Server-Sent Events (social feed), long-lived, never buffer ───────
150 location ~ ^/api/social/[^/]+/stream$ {
151 proxy_pass http://musehub_backend;
152 proxy_http_version 1.1;
153 proxy_set_header Connection "";
154 proxy_set_header Host $host;
155 proxy_set_header X-Real-IP $remote_addr;
156 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
157 proxy_set_header X-Forwarded-Proto $scheme;
158 proxy_read_timeout 3600s;
159 proxy_send_timeout 3600s;
160 proxy_request_buffering off;
161 proxy_buffering off;
162 add_header X-Accel-Buffering no always;
163 }
164
165 # ── Wire push — large uploads, never buffer ───────────────────────────────
166 # /push/mpack-presign — negotiate S3 presigned upload URL
167 # /push/unpack-mpack — trigger S3→uvicorn unpack
168 location ~ ^/[^/]+/[^/]+/push(/mpack-presign|/unpack-mpack)?$ {
169 proxy_pass http://musehub_backend;
170 proxy_http_version 1.1;
171 proxy_set_header Connection "";
172 proxy_set_header Host $host;
173 proxy_set_header X-Real-IP $remote_addr;
174 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
175 proxy_set_header X-Forwarded-Proto $scheme;
176 proxy_read_timeout 300s;
177 proxy_send_timeout 300s;
178 proxy_request_buffering off;
179 proxy_buffering off;
180 client_max_body_size 0;
181 gzip off;
182 }
183
184 # ── Wire fetch — responses may be large, never buffer ─────────────────────
185 # /fetch/mpack — mpack download (inline bytes or presigned S3 URL)
186 # /fetch/objects — raw object bytes
187 # /fetch/presign — negotiate S3 presigned download URL
188 location ~ ^/[^/]+/[^/]+/fetch(/mpack|/objects|/presign)?$ {
189 proxy_pass http://musehub_backend;
190 proxy_http_version 1.1;
191 proxy_set_header Connection "";
192 proxy_set_header Host $host;
193 proxy_set_header X-Real-IP $remote_addr;
194 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
195 proxy_set_header X-Forwarded-Proto $scheme;
196 proxy_read_timeout 300s;
197 proxy_send_timeout 300s;
198 proxy_request_buffering off;
199 proxy_buffering off;
200 client_max_body_size 0;
201 gzip off;
202 }
203
204 # ── Wire misc — short-lived wire ops and raw object downloads ─────────────
205 # These are authenticated wire endpoints that need more than 60s: refs,
206 # repair, tags, releases, branches, coord, and the raw object download path /o/.
207 location ~ ^(/[^/]+/[^/]+/(refs|repair-object|repair-snapshot|repair-commit|tags|releases|branches|coord)|/o/) {
208 proxy_pass http://musehub_backend;
209 proxy_http_version 1.1;
210 proxy_set_header Connection "";
211 proxy_set_header Host $host;
212 proxy_set_header X-Real-IP $remote_addr;
213 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
214 proxy_set_header X-Forwarded-Proto $scheme;
215 proxy_read_timeout 120s;
216 proxy_send_timeout 120s;
217 gzip off;
218 }
219
220 # ── Default catch-all — UI pages and JSON API ─────────────────────────────
221 # All /api/*, /{owner}/{repo_slug} UI routes, /musehub/*, /explore, etc.
222 # Standard request/response, small payloads, 60s is ample.
223 # Rate-limited: unauthenticated endpoints here are the primary abuse surface.
224 # burst=20 nodelay: absorb short bursts without queuing; excess → 429 immediately.
225 location / {
226 limit_req zone=api burst=20 nodelay;
227 proxy_pass http://musehub_backend;
228 proxy_http_version 1.1;
229 proxy_set_header Connection "";
230 proxy_set_header Host $host;
231 proxy_set_header X-Real-IP $remote_addr;
232 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
233 proxy_set_header X-Forwarded-Proto $scheme;
234 proxy_read_timeout 60s;
235 }
236 }
File History 1 commit
sha256:3ff9c9863a9891bdcde71b4a43228f66d0493e38b7cc1d09fe9eb7de774046b2 feat: add repair-commit wire endpoint (API parity with repa… Opus 4.8 minor 1 day ago