Nllhpho / Dockerfile
Mafia2008's picture
Create Dockerfile
917e3b7 verified
# syntax=docker/dockerfile:1
FROM ubuntu:22.04
ENV DEBIAN_FRONTEND=noninteractive
ENV HOSTNAME=Ubuntu
# ---- Base packages ----
RUN apt-get update && apt-get install -y --no-install-recommends \
ca-certificates \
curl \
wget \
git \
sudo \
docker.io \
htop \
btop \
neovim \
lsof \
qemu-system \
cloud-image-utils \
python3 \
python3-pip \
nginx \
supervisor \
&& rm -rf /var/lib/apt/lists/*
# ---- Install code-server ----
RUN curl -fsSL https://code-server.dev/install.sh | sh
# ---- Install API Dependencies ----
RUN pip3 install fastapi uvicorn pydantic huggingface_hub
# ---- Setup Directories ----
RUN mkdir -p /opt/api /workspace /var/log/supervisor
# ---- 1. Create the Python API (FastAPI) ----
COPY <<"EOF" /opt/api/api.py
from fastapi import FastAPI, HTTPException, BackgroundTasks
from pydantic import BaseModel
import os, subprocess, shutil, logging
from huggingface_hub import HfApi, snapshot_download
# --- Setup Logging to show in HF Space Logs ---
logging.basicConfig(level=logging.INFO, format="%(message)s")
logger = logging.getLogger("workspace_api")
app = FastAPI()
BASE_DIR = "/workspace"
# --- Request Models ---
class WriteRequest(BaseModel):
session_id: str = "default"
path: str
content: str
class ExecRequest(BaseModel):
session_id: str = "default"
command: str
class DeleteRequest(BaseModel):
session_id: str = "default"
path: str
# --- Helper Functions ---
def get_session_dir(session_id: str):
session_dir = os.path.abspath(os.path.join(BASE_DIR, session_id))
if not session_dir.startswith(BASE_DIR):
raise HTTPException(status_code=403, detail="Invalid session ID")
os.makedirs(session_dir, exist_ok=True)
return session_dir
def resolve_path(session_id: str, relative_path: str):
session_dir = get_session_dir(session_id)
if relative_path in [".", "/", ""]:
return session_dir
clean_path = relative_path.lstrip("/")
full_path = os.path.abspath(os.path.join(session_dir, clean_path))
if not full_path.startswith(session_dir):
raise HTTPException(status_code=403, detail="Path traversal detected")
return full_path
# --- Auto Sync on Startup ---
@app.on_event("startup")
def startup_sync():
token = os.getenv("HF_TOKEN")
dataset = os.getenv("HF_DATASET")
if token and dataset:
logger.info(f"Pulling dataset {dataset} on startup...")
try:
snapshot_download(repo_id=dataset, repo_type="dataset", local_dir=BASE_DIR, token=token)
logger.info("Successfully restored sessions from Hugging Face Dataset!")
except Exception as e:
logger.warning(f"Startup sync failed (If dataset is empty, this is normal): {e}")
# --- Endpoints ---
@app.get('/api/list')
def list_dir(session_id: str = "default", path: str = ""):
target = resolve_path(session_id, path)
try:
return {'files': os.listdir(target)}
except Exception as e:
raise HTTPException(status_code=400, detail=str(e))
@app.get('/api/read')
def read_file(session_id: str = "default", path: str = ""):
target = resolve_path(session_id, path)
try:
with open(target, 'r', encoding='utf-8') as f:
return {'content': f.read()}
except Exception as e:
raise HTTPException(status_code=400, detail=str(e))
@app.post('/api/write')
def write_file(req: WriteRequest):
target = resolve_path(req.session_id, req.path)
try:
os.makedirs(os.path.dirname(target), exist_ok=True)
with open(target, 'w', encoding='utf-8') as f:
f.write(req.content)
return {'status': 'success', 'saved_to': target}
except Exception as e:
raise HTTPException(status_code=400, detail=str(e))
@app.post('/api/exec')
def exec_cmd(req: ExecRequest):
session_dir = get_session_dir(req.session_id)
try:
# 1. Log the Input
logger.info(f"[Session {req.session_id} terminal input]: {req.command}")
result = subprocess.run(req.command, shell=True, cwd=session_dir, capture_output=True, text=True)
# 2. Log the Output (STDOUT and STDERR)
if result.stdout:
logger.info(f"[Session {req.session_id} output]:\n{result.stdout.strip()}")
if result.stderr:
logger.error(f"[Session {req.session_id} error]:\n{result.stderr.strip()}")
return {'stdout': result.stdout, 'stderr': result.stderr, 'returncode': result.returncode}
except Exception as e:
logger.error(f"[Session {req.session_id} critical error]: {str(e)}")
raise HTTPException(status_code=400, detail=str(e))
@app.post('/api/delete')
def delete_item(req: DeleteRequest):
target = resolve_path(req.session_id, req.path)
try:
if os.path.isdir(target):
shutil.rmtree(target)
else:
os.remove(target)
return {'status': 'success'}
except Exception as e:
raise HTTPException(status_code=400, detail=str(e))
# --- Dataset Sync Endpoints ---
@app.post('/api/sync/push')
def push_to_dataset(background_tasks: BackgroundTasks):
token = os.getenv("HF_TOKEN")
dataset = os.getenv("HF_DATASET")
if not token or not dataset:
raise HTTPException(status_code=400, detail="HF_TOKEN or HF_DATASET env missing")
def push_job():
try:
api = HfApi(token=token)
api.upload_folder(folder_path=BASE_DIR, repo_id=dataset, repo_type="dataset", commit_message="API Auto-sync")
logger.info("Successfully backed up to dataset.")
except Exception as e:
logger.error(f"Push failed: {e}")
background_tasks.add_task(push_job)
return {"status": "sync started in background"}
EOF
# ---- 2. Create Nginx Configuration ----
# Fixed the malformed proxy block and added dynamic port routing
COPY <<"EOF" /etc/nginx/nginx.conf
events {}
http {
server {
listen 7860;
# 1. API Route
location /api/ {
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
# 2. Dynamic Port Proxy Route (e.g., /proxy/3567/)
location ~ ^/proxy/(?<app_port>[0-9]+)(?<app_uri>.*)$ {
proxy_pass http://127.0.0.1:$app_port$app_uri;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
# 3. Main Code-Server Route
location / {
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Accept-Encoding gzip;
}
}
}
EOF
# ---- 3. Create Supervisor Configuration ----
# Added stdout/stderr routing so logs appear in Hugging Face UI
COPY <<"EOF" /etc/supervisor/conf.d/supervisord.conf
[supervisord]
nodaemon=true
user=root
logfile=/dev/null
logfile_maxbytes=0
[program:codeserver]
command=code-server --bind-addr 127.0.0.1:8080 --auth none
directory=/workspace
autorestart=true
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
[program:api]
command=uvicorn api:app --host 127.0.0.1 --port 8000
directory=/opt/api
autorestart=true
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
[program:nginx]
command=nginx -g "daemon off;"
autorestart=true
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
EOF
# ---- Workspace Setup ----
WORKDIR /workspace
EXPOSE 7860
CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/conf.d/supervisord.conf"]