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"})
|