Просмотр исходного кода

feat: add Caddy reverse proxy to stack + fix browser upload CORS

- Add Caddy as a Docker service for internal routing
- Catch-all :80 site block proxies /api/* → api, rest → frontend
- Add explicit OPTIONS handlers in assets route for preflight requests
- Expand ALLOWED_ORIGINS to include local IP addresses
- NEXT_PUBLIC_API_URL stays as public HTTPS URL for external access

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Claude Dev 1 месяц назад
Родитель
Сommit
74c70deb36
3 измененных файлов с 57 добавлено и 6 удалено
  1. 18 0
      Caddyfile
  2. 23 6
      docker-compose.yml
  3. 16 0
      packages/api/src/routes/assets.ts

+ 18 - 0
Caddyfile

@@ -0,0 +1,18 @@
+{
+    # Debug: enable request logging
+    admin off
+}
+
+# Catch-all: Synology forwards decrypted HTTP traffic to this port.
+# Route based on path regardless of Host header.
+:80 {
+    @api path /api/*
+    handle @api {
+        reverse_proxy api:3001 {
+            header_down -Origin
+        }
+    }
+    handle {
+        reverse_proxy frontend:3000
+    }
+}

+ 23 - 6
docker-compose.yml

@@ -57,7 +57,7 @@ services:
       NODE_ENV: production
       NODE_ENV: production
       UPLOAD_DIR: /app/uploads
       UPLOAD_DIR: /app/uploads
       MAX_FILE_SIZE_MB: 500
       MAX_FILE_SIZE_MB: 500
-      ALLOWED_ORIGINS: 'https://vid.k9tech.space,http://vid.k9tech.space'
+      ALLOWED_ORIGINS: 'https://vid.k9tech.space,http://vid.k9tech.space,http://10.147.17.128,http://192.168.1.31'
       FRONTEND_URL: https://vid.k9tech.space
       FRONTEND_URL: https://vid.k9tech.space
       RESEND_API_KEY: ${RESEND_API_KEY:-}
       RESEND_API_KEY: ${RESEND_API_KEY:-}
     ports:
     ports:
@@ -74,8 +74,6 @@ services:
       retries: 5
       retries: 5
 
 
   # ── Transcode Worker ──────────────────────────────────────────────────────
   # ── Transcode Worker ──────────────────────────────────────────────────────
-  # Standalone Node.js process that polls the DB for pending transcode jobs.
-  # Runs FFmpeg off the main API thread — uploads never block.
   worker:
   worker:
     build:
     build:
       context: .
       context: .
@@ -94,16 +92,33 @@ services:
       - uploads:/app/uploads
       - uploads:/app/uploads
     restart: unless-stopped
     restart: unless-stopped
 
 
+  # ── Caddy Reverse Proxy ───────────────────────────────────────────────────
+  # Receives HTTP traffic forwarded by Synology reverse proxy.
+  # Routes /api/* → api container, everything else → frontend.
+  caddy:
+    image: caddy:2-alpine
+    container_name: vidreview-caddy
+    ports:
+      - '80:80'
+      - '443:443'
+    volumes:
+      - ./Caddyfile:/etc/caddy/Caddyfile:ro
+      - caddy_data:/data
+      - caddy_config:/config
+    depends_on:
+      - frontend
+      - api
+
   frontend:
   frontend:
     build:
     build:
       context: .
       context: .
       dockerfile: Dockerfile.frontend
       dockerfile: Dockerfile.frontend
     container_name: vidreview-frontend
     container_name: vidreview-frontend
     environment:
     environment:
-      NEXT_PUBLIC_API_URL: http://api:3001
+      NEXT_PUBLIC_API_URL: https://vid.k9tech.space/api
       NODE_ENV: production
       NODE_ENV: production
-    ports:
-      - '3000:3000'
+    expose:
+      - '3000'
     depends_on:
     depends_on:
       api:
       api:
         condition: service_healthy
         condition: service_healthy
@@ -112,3 +127,5 @@ volumes:
   postgres_data:
   postgres_data:
   uploads:
   uploads:
   seed_output:
   seed_output:
+  caddy_data:
+  caddy_config:

+ 16 - 0
packages/api/src/routes/assets.ts

@@ -8,6 +8,22 @@ import { authMiddleware } from '../lib/auth';
 import { startTranscodeJob } from '../worker/dispatcher';
 import { startTranscodeJob } from '../worker/dispatcher';
 
 
 const router = Router();
 const router = Router();
+
+// ── CORS preflight (must be before authMiddleware — OPTIONS carries no auth token)
+router.options('/', (_req, res) => {
+  res.setHeader('Access-Control-Allow-Origin', '*');
+  res.setHeader('Access-Control-Allow-Methods', 'GET,POST,PUT,DELETE,PATCH,OPTIONS');
+  res.setHeader('Access-Control-Allow-Headers', 'Authorization,Content-Type,X-Requested-With');
+  res.sendStatus(200);
+});
+
+router.options('/upload', (_req, res) => {
+  res.setHeader('Access-Control-Allow-Origin', '*');
+  res.setHeader('Access-Control-Allow-Methods', 'GET,POST,PUT,DELETE,PATCH,OPTIONS');
+  res.setHeader('Access-Control-Allow-Headers', 'Authorization,Content-Type,X-Requested-With');
+  res.sendStatus(200);
+});
+
 router.use(authMiddleware);
 router.use(authMiddleware);
 
 
 const str = (v: string | string[] | undefined): string => Array.isArray(v) ? v[0] ?? '' : (v ?? '');
 const str = (v: string | string[] | undefined): string => Array.isArray(v) ? v[0] ?? '' : (v ?? '');