File size: 4,371 Bytes
a1d8f81
 
f8d6c3f
65d3b67
a1d8f81
 
65d3b67
a1d8f81
65d3b67
 
 
 
a1d8f81
f8d6c3f
 
a1d8f81
 
65d3b67
a1d8f81
 
 
 
 
65d3b67
 
a1d8f81
 
 
 
 
 
65d3b67
a1d8f81
65d3b67
a1d8f81
 
65d3b67
a1d8f81
 
 
 
 
 
 
65d3b67
a1d8f81
 
 
 
 
 
 
 
 
 
 
 
 
65d3b67
 
a1d8f81
65d3b67
a1d8f81
65d3b67
a1d8f81
65d3b67
 
 
 
 
a1d8f81
65d3b67
 
a1d8f81
 
 
 
 
 
 
 
 
 
 
65d3b67
a1d8f81
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
65d3b67
 
 
a1d8f81
 
65d3b67
a1d8f81
f8d6c3f
 
 
 
 
 
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
from fastapi import APIRouter, HTTPException, UploadFile, Form
from fastapi.responses import JSONResponse
from docker_client import docker_client
import uuid
import zipfile
import os
import time
from pyngrok import ngrok

router = APIRouter()

@router.post("/project")
async def deploy_code(file: UploadFile, app_name: str = Form(...)):
    if not docker_client:
        raise HTTPException(status_code=503, detail="Docker is not available")
    if not file.filename.endswith(".zip"):
        raise HTTPException(status_code=400, detail="Only .zip files are supported")
    
    # Create unique project directory
    project_id = str(uuid.uuid4())
    base_dir = os.path.dirname(os.path.abspath(__file__))  # /router/
    projects_dir = os.path.abspath(os.path.join(base_dir, "..", "projects"))
    os.makedirs(projects_dir, exist_ok=True)

    project_path = os.path.join(projects_dir, project_id)
    os.makedirs(project_path, exist_ok=True)

    # Save uploaded zip
    zip_path = os.path.join(project_path, file.filename)
    with open(zip_path, "wb") as f:
        f.write(await file.read())

    # Extract zip contents
    try:
        with zipfile.ZipFile(zip_path, 'r') as zip_ref:
            zip_ref.extractall(project_path)
    except Exception as e:
        raise HTTPException(status_code=400, detail=f"Invalid zip file: {str(e)}")

    # Debug: print all extracted files
    print(f"[DEBUG] Extracted files in: {project_path}")
    for dirpath, _, files in os.walk(project_path):
        for fname in files:
            print(" -", os.path.join(dirpath, fname))

    # Case-insensitive recursive search for file
    def find_file(filename, root):
        filename = filename.lower()
        for dirpath, _, files in os.walk(root):
            for file in files:
                if file.lower() == filename:
                    return os.path.join(dirpath, file)
        return None


    main_py = find_file("main.py", project_path)
    requirements_txt = find_file("requirements.txt", project_path)
    dockerfile = find_file("Dockerfile", project_path)  # lowercase f

    missing_files = []
    if not main_py:
        missing_files.append("main.py")
    if not requirements_txt:
        missing_files.append("requirements.txt")
    if not dockerfile:
        missing_files.append("Dockerfile")

    if missing_files:
        raise HTTPException(
            status_code=400,
            detail=f"Zip is missing required file(s): {', '.join(missing_files)} (case-insensitive)"
        )

    # Move Dockerfile to root if needed
    if os.path.dirname(dockerfile) != project_path:
        print(f"[DEBUG] Moving Dockerfile from {dockerfile} to project root")
        target_path = os.path.join(project_path, "Dockerfile")
        os.replace(dockerfile, target_path)
        dockerfile = target_path

    # Build and run Docker container
    image_name = f"{app_name.lower()}_{project_id[:8]}"
    container_name = image_name

    try:
        for c in docker_client.containers.list(all=True):
            if c.status in ["created", "exited"]:
                print(f"Removing leftover container {c.name} ({c.id})")
                c.remove(force=True)
                
        image, _ = docker_client.images.build(path=project_path, tag=image_name)

        container = docker_client.containers.run(
            image=image_name,
            ports={"8080/tcp": None},
            name=container_name,
            detach=True,
            mem_limit="512m",
            nano_cpus=1_000_000_000,
            read_only=True,
            tmpfs={"/tmp": ""},
            user="1001:1001"
        )

        time.sleep(2)

        port_info = docker_client.api.port(container.id, 8080)
        if not port_info:
            raise Exception("Port 8080 not exposed by container")
        port = port_info[0]['HostPort']

        tunnel = ngrok.connect(port, bind_tls=True)
        public_url = tunnel.public_url

        return JSONResponse({
            "project_id": project_id,
            "container_name": container_name,
            "preview_url": public_url
        })

    except Exception as e:
        return JSONResponse({"error": str(e)}, status_code=500)

@router.get("/project/{project_id}")
def get_project_status(project_id: str):
    # Placeholder for project status logic
    return JSONResponse({"project_id": project_id, "status": "running"})