|
|
""" |
|
|
Modal deployment script for Hue Portal Backend (Django) |
|
|
Based on flux-modal pattern: https://github.com/omarkortam/flux-modal |
|
|
""" |
|
|
import os |
|
|
import subprocess |
|
|
from pathlib import Path |
|
|
|
|
|
import modal |
|
|
|
|
|
|
|
|
hue_backend_image = ( |
|
|
modal.Image.debian_slim(python_version="3.11") |
|
|
.apt_install( |
|
|
"git", |
|
|
"build-essential", |
|
|
"tesseract-ocr", |
|
|
"tesseract-ocr-eng", |
|
|
"tesseract-ocr-vie", |
|
|
"libpoppler-cpp-dev", |
|
|
"pkg-config", |
|
|
"libgl1", |
|
|
) |
|
|
.pip_install( |
|
|
"Django==5.0.6", |
|
|
"djangorestframework==3.15.2", |
|
|
"django-cors-headers==4.4.0", |
|
|
"psycopg2-binary==2.9.9", |
|
|
"django-environ==0.11.2", |
|
|
"gunicorn==22.0.0", |
|
|
"whitenoise==6.6.0", |
|
|
"redis==5.0.6", |
|
|
"celery==5.4.0", |
|
|
"scikit-learn==1.3.2", |
|
|
"numpy==1.24.3", |
|
|
"scipy==1.11.4", |
|
|
"pydantic>=2.0.0,<3.0.0", |
|
|
"sentence-transformers>=2.2.0", |
|
|
"torch>=2.0.0", |
|
|
"faiss-cpu>=1.7.4", |
|
|
"python-docx==0.8.11", |
|
|
"PyMuPDF==1.24.3", |
|
|
"Pillow>=8.0.0,<12.0", |
|
|
"pytesseract==0.3.13", |
|
|
"requests>=2.31.0", |
|
|
) |
|
|
|
|
|
.copy_local_dir("backend", "/app") |
|
|
.run_commands( |
|
|
"mkdir -p /app/hue_portal/static /app/hue_portal/media", |
|
|
"chmod +x /app/hue_portal/manage.py", |
|
|
) |
|
|
) |
|
|
|
|
|
app = modal.App(name="hue-portal-backend", image=hue_backend_image) |
|
|
|
|
|
|
|
|
|
|
|
backend_mount = modal.Mount.from_local_dir("backend", remote_path="/app") |
|
|
|
|
|
@app.function( |
|
|
allow_concurrent_inputs=100, |
|
|
concurrency_limit=10, |
|
|
container_idle_timeout=300, |
|
|
timeout=600, |
|
|
cpu=2, |
|
|
memory=4096, |
|
|
secrets=[ |
|
|
modal.Secret.from_name("hue-portal-secrets", required=False), |
|
|
], |
|
|
mounts=[backend_mount], |
|
|
) |
|
|
@modal.web_server(8000, startup_timeout=180) |
|
|
def web(): |
|
|
"""Start Django application with Gunicorn.""" |
|
|
import os |
|
|
|
|
|
|
|
|
os.chdir("/app/hue_portal") |
|
|
|
|
|
|
|
|
print("[Modal] Running migrations...") |
|
|
migrate_result = subprocess.run( |
|
|
["python", "manage.py", "migrate", "--noinput"], |
|
|
capture_output=True, |
|
|
text=True, |
|
|
check=False, |
|
|
) |
|
|
if migrate_result.returncode != 0: |
|
|
print(f"[Modal] Migration warning: {migrate_result.stderr[:200]}") |
|
|
else: |
|
|
print("[Modal] Migrations completed") |
|
|
|
|
|
|
|
|
print("[Modal] Collecting static files...") |
|
|
collect_result = subprocess.run( |
|
|
["python", "manage.py", "collectstatic", "--noinput"], |
|
|
capture_output=True, |
|
|
text=True, |
|
|
check=False, |
|
|
) |
|
|
if collect_result.returncode != 0: |
|
|
print(f"[Modal] Collectstatic warning: {collect_result.stderr[:200]}") |
|
|
else: |
|
|
print("[Modal] Static files collected") |
|
|
|
|
|
|
|
|
print("[Modal] Starting Gunicorn on 0.0.0.0:8000...") |
|
|
gunicorn_process = subprocess.Popen( |
|
|
[ |
|
|
"gunicorn", |
|
|
"-b", "0.0.0.0:8000", |
|
|
"--workers", "2", |
|
|
"--timeout", "120", |
|
|
"--access-logfile", "-", |
|
|
"--error-logfile", "-", |
|
|
"hue_portal.wsgi:application", |
|
|
], |
|
|
cwd="/app/hue_portal", |
|
|
) |
|
|
|
|
|
print("[Modal] Gunicorn started, waiting...") |
|
|
|
|
|
|
|
|
import time |
|
|
try: |
|
|
while True: |
|
|
|
|
|
if gunicorn_process.poll() is not None: |
|
|
print(f"[Modal] Gunicorn exited with code {gunicorn_process.returncode}") |
|
|
break |
|
|
time.sleep(1) |
|
|
except KeyboardInterrupt: |
|
|
print("[Modal] Shutting down...") |
|
|
gunicorn_process.terminate() |
|
|
gunicorn_process.wait() |
|
|
|