File size: 3,908 Bytes
519b145
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
"""
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

# Build Docker image with all dependencies
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 backend code
    .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)


# Mount backend directory
backend_mount = modal.Mount.from_local_dir("backend", remote_path="/app")

@app.function(
    allow_concurrent_inputs=100,
    concurrency_limit=10,
    container_idle_timeout=300,  # 5 minutes
    timeout=600,  # 10 minutes max request time
    cpu=2,  # 2 CPUs
    memory=4096,  # 4GB RAM
    secrets=[
        modal.Secret.from_name("hue-portal-secrets", required=False),  # Optional for now
    ],
    mounts=[backend_mount],
)
@modal.web_server(8000, startup_timeout=180)
def web():
    """Start Django application with Gunicorn."""
    import os
    
    # Set working directory
    os.chdir("/app/hue_portal")
    
    # Run migrations
    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")
    
    # Collect static files
    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")
    
    # Start Gunicorn
    print("[Modal] Starting Gunicorn on 0.0.0.0:8000...")
    gunicorn_process = subprocess.Popen(
        [
            "gunicorn",
            "-b", "0.0.0.0:8000",
            "--workers", "2",  # Reduced for Modal
            "--timeout", "120",
            "--access-logfile", "-",
            "--error-logfile", "-",
            "hue_portal.wsgi:application",
        ],
        cwd="/app/hue_portal",
    )
    
    print("[Modal] Gunicorn started, waiting...")
    
    # Keep process alive and monitor
    import time
    try:
        while True:
            # Check if gunicorn is still running
            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()