Spaces:
Sleeping
Sleeping
juns commited on
Commit ·
7962d2c
1
Parent(s): b2f7e8c
Fix Railway deployment: encoding bug, healthcheck, deps pinning, Dockerfile optimization
Browse files- worker/index.js: Remove Content-Encoding forwarding (CF Workers auto-decompress)
- main.py: Add /health endpoint, make Playwright pre-warm non-fatal
- railway.toml: Add [deploy] with healthcheck, restart policy
- requirements.txt: Pin all dependency versions
- Dockerfile: Use playwright install --with-deps, optimize layer caching
- Dockerfile +8 -23
- main.py +11 -2
- railway.toml +6 -0
- requirements.txt +6 -6
- worker/index.js +7 -7
Dockerfile
CHANGED
|
@@ -1,37 +1,22 @@
|
|
| 1 |
FROM python:3.11-slim
|
| 2 |
|
| 3 |
-
# Install
|
| 4 |
RUN apt-get update && apt-get install -y --no-install-recommends \
|
| 5 |
-
wget \
|
| 6 |
ffmpeg \
|
| 7 |
-
libnss3 \
|
| 8 |
-
libnspr4 \
|
| 9 |
-
libatk1.0-0 \
|
| 10 |
-
libatk-bridge2.0-0 \
|
| 11 |
-
libcups2 \
|
| 12 |
-
libdrm2 \
|
| 13 |
-
libxkbcommon0 \
|
| 14 |
-
libxcomposite1 \
|
| 15 |
-
libxdamage1 \
|
| 16 |
-
libxfixes3 \
|
| 17 |
-
libxrandr2 \
|
| 18 |
-
libgbm1 \
|
| 19 |
-
libpango-1.0-0 \
|
| 20 |
-
libcairo2 \
|
| 21 |
-
libasound2t64 \
|
| 22 |
-
libatspi2.0-0 \
|
| 23 |
-
libwayland-client0 \
|
| 24 |
&& rm -rf /var/lib/apt/lists/*
|
| 25 |
|
| 26 |
WORKDIR /app
|
| 27 |
|
|
|
|
| 28 |
COPY requirements.txt .
|
| 29 |
RUN pip install --no-cache-dir -r requirements.txt
|
| 30 |
|
| 31 |
-
#
|
| 32 |
-
|
|
|
|
| 33 |
|
|
|
|
| 34 |
COPY . .
|
| 35 |
|
| 36 |
-
# Railway provides PORT env var
|
| 37 |
-
CMD uvicorn main:app --host 0.0.0.0 --port ${PORT:-8000}
|
|
|
|
| 1 |
FROM python:3.11-slim
|
| 2 |
|
| 3 |
+
# Install ffmpeg (not a Playwright dep, needed for Instagram audio extraction)
|
| 4 |
RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
|
|
| 5 |
ffmpeg \
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6 |
&& rm -rf /var/lib/apt/lists/*
|
| 7 |
|
| 8 |
WORKDIR /app
|
| 9 |
|
| 10 |
+
# Layer 1: Python dependencies (cached unless requirements.txt changes)
|
| 11 |
COPY requirements.txt .
|
| 12 |
RUN pip install --no-cache-dir -r requirements.txt
|
| 13 |
|
| 14 |
+
# Layer 2: Playwright Chromium + all system deps (auto-installs libnss3, libatk, etc.)
|
| 15 |
+
# Using --with-deps eliminates manual apt-get for ~15 Chromium libraries
|
| 16 |
+
RUN playwright install --with-deps chromium
|
| 17 |
|
| 18 |
+
# Layer 3: Application code (changes most frequently)
|
| 19 |
COPY . .
|
| 20 |
|
| 21 |
+
# Railway provides PORT env var; default to 8000 for local dev
|
| 22 |
+
CMD ["sh", "-c", "uvicorn main:app --host 0.0.0.0 --port ${PORT:-8000}"]
|
main.py
CHANGED
|
@@ -413,8 +413,11 @@ def _pw_init_browser():
|
|
| 413 |
return _ig_browser
|
| 414 |
|
| 415 |
|
| 416 |
-
# Pre-warm browser at import time
|
| 417 |
-
|
|
|
|
|
|
|
|
|
|
| 418 |
|
| 419 |
|
| 420 |
def _pw_extract_embed(shortcode):
|
|
@@ -884,3 +887,9 @@ async def submit_feedback(request: FeedbackRequest):
|
|
| 884 |
@app.get("/")
|
| 885 |
async def root():
|
| 886 |
return FileResponse("static/index.html")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 413 |
return _ig_browser
|
| 414 |
|
| 415 |
|
| 416 |
+
# Pre-warm browser at import time (fire-and-forget; failure is non-fatal)
|
| 417 |
+
try:
|
| 418 |
+
_pw_executor.submit(_pw_init_browser)
|
| 419 |
+
except Exception:
|
| 420 |
+
logger.warning("[instagram] Failed to submit browser pre-warm task")
|
| 421 |
|
| 422 |
|
| 423 |
def _pw_extract_embed(shortcode):
|
|
|
|
| 887 |
@app.get("/")
|
| 888 |
async def root():
|
| 889 |
return FileResponse("static/index.html")
|
| 890 |
+
|
| 891 |
+
|
| 892 |
+
@app.get("/health")
|
| 893 |
+
async def health_check():
|
| 894 |
+
"""Lightweight health check for Railway. No external dependencies."""
|
| 895 |
+
return {"status": "ok"}
|
railway.toml
CHANGED
|
@@ -1,3 +1,9 @@
|
|
| 1 |
[build]
|
| 2 |
builder = "DOCKERFILE"
|
| 3 |
dockerfilePath = "Dockerfile"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
[build]
|
| 2 |
builder = "DOCKERFILE"
|
| 3 |
dockerfilePath = "Dockerfile"
|
| 4 |
+
|
| 5 |
+
[deploy]
|
| 6 |
+
healthcheckPath = "/health"
|
| 7 |
+
healthcheckTimeout = 120
|
| 8 |
+
restartPolicyType = "ON_FAILURE"
|
| 9 |
+
restartPolicyMaxRetries = 3
|
requirements.txt
CHANGED
|
@@ -1,6 +1,6 @@
|
|
| 1 |
-
fastapi
|
| 2 |
-
uvicorn[standard]
|
| 3 |
-
youtube-transcript-api
|
| 4 |
-
playwright
|
| 5 |
-
groq
|
| 6 |
-
requests
|
|
|
|
| 1 |
+
fastapi==0.135.1
|
| 2 |
+
uvicorn[standard]==0.41.0
|
| 3 |
+
youtube-transcript-api==1.2.4
|
| 4 |
+
playwright==1.58.0
|
| 5 |
+
groq==1.1.1
|
| 6 |
+
requests==2.32.5
|
worker/index.js
CHANGED
|
@@ -25,8 +25,8 @@ export default {
|
|
| 25 |
headers.delete('cf-connecting-ip');
|
| 26 |
headers.delete('cf-ray');
|
| 27 |
headers.delete('cf-ipcountry');
|
| 28 |
-
// Force uncompressed response
|
| 29 |
-
// (
|
| 30 |
headers.set('Accept-Encoding', 'identity');
|
| 31 |
// Override User-Agent with a browser UA
|
| 32 |
headers.set('User-Agent', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36');
|
|
@@ -54,15 +54,15 @@ export default {
|
|
| 54 |
const response = await fetch(targetUrl, fetchOptions);
|
| 55 |
const body = await response.arrayBuffer();
|
| 56 |
|
| 57 |
-
//
|
|
|
|
|
|
|
|
|
|
|
|
|
| 58 |
const responseHeaders = {
|
| 59 |
'Content-Type': response.headers.get('Content-Type') || 'text/html',
|
| 60 |
'Access-Control-Allow-Origin': '*',
|
| 61 |
};
|
| 62 |
-
const contentEncoding = response.headers.get('Content-Encoding');
|
| 63 |
-
if (contentEncoding) {
|
| 64 |
-
responseHeaders['Content-Encoding'] = contentEncoding;
|
| 65 |
-
}
|
| 66 |
|
| 67 |
return new Response(body, {
|
| 68 |
status: response.status,
|
|
|
|
| 25 |
headers.delete('cf-connecting-ip');
|
| 26 |
headers.delete('cf-ray');
|
| 27 |
headers.delete('cf-ipcountry');
|
| 28 |
+
// Force uncompressed response — prevents encoding mismatch
|
| 29 |
+
// (CF Workers auto-decompress bodies, so forwarding Content-Encoding would lie about the actual encoding)
|
| 30 |
headers.set('Accept-Encoding', 'identity');
|
| 31 |
// Override User-Agent with a browser UA
|
| 32 |
headers.set('User-Agent', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36');
|
|
|
|
| 54 |
const response = await fetch(targetUrl, fetchOptions);
|
| 55 |
const body = await response.arrayBuffer();
|
| 56 |
|
| 57 |
+
// Build clean response headers
|
| 58 |
+
// CRITICAL: Do NOT forward Content-Encoding — CF Workers auto-decompress
|
| 59 |
+
// the body, so the raw bytes are always uncompressed regardless of what
|
| 60 |
+
// YouTube's Content-Encoding header says. Forwarding it would cause
|
| 61 |
+
// the downstream client to try decompressing already-decompressed data.
|
| 62 |
const responseHeaders = {
|
| 63 |
'Content-Type': response.headers.get('Content-Type') || 'text/html',
|
| 64 |
'Access-Control-Allow-Origin': '*',
|
| 65 |
};
|
|
|
|
|
|
|
|
|
|
|
|
|
| 66 |
|
| 67 |
return new Response(body, {
|
| 68 |
status: response.status,
|