wp on webui
This commit is contained in:
@@ -6,6 +6,7 @@ Each session gets its own isolated container with a dedicated working directory.
|
||||
"""
|
||||
|
||||
import os
|
||||
import re
|
||||
import uuid
|
||||
import json
|
||||
import asyncio
|
||||
@@ -15,6 +16,7 @@ from datetime import datetime, timedelta
|
||||
from pathlib import Path
|
||||
from typing import Dict, Optional, List
|
||||
from contextlib import asynccontextmanager
|
||||
from urllib.parse import urlparse
|
||||
|
||||
import docker
|
||||
from docker.errors import DockerException, NotFound
|
||||
@@ -358,7 +360,7 @@ class SessionManager:
|
||||
return session
|
||||
|
||||
async def _start_container_async(self, session: SessionData):
|
||||
"""Start the OpenCode container asynchronously using aiodeocker"""
|
||||
"""Start the OpenCode container asynchronously using aiodocker"""
|
||||
try:
|
||||
# Get and validate resource limits
|
||||
resource_limits = get_resource_limits()
|
||||
@@ -378,8 +380,8 @@ class SessionManager:
|
||||
"OPENAI_API_KEY": os.getenv("OPENAI_API_KEY", ""),
|
||||
"ANTHROPIC_API_KEY": os.getenv("ANTHROPIC_API_KEY", ""),
|
||||
"GOOGLE_API_KEY": os.getenv("GOOGLE_API_KEY", ""),
|
||||
# Secure authentication for OpenCode server
|
||||
"OPENCODE_SERVER_PASSWORD": session.auth_token or "",
|
||||
# Auth disabled for development - will be added later
|
||||
# "OPENCODE_SERVER_PASSWORD": session.auth_token or "",
|
||||
"SESSION_AUTH_TOKEN": session.auth_token or "",
|
||||
"SESSION_ID": session.session_id,
|
||||
},
|
||||
@@ -395,10 +397,8 @@ class SessionManager:
|
||||
},
|
||||
)
|
||||
|
||||
# For async mode, containers are already started during creation
|
||||
# For sync mode, we need to explicitly start them
|
||||
if not self.docker_service.use_async:
|
||||
await self.docker_service.start_container(container_info.container_id)
|
||||
# Containers need to be explicitly started after creation
|
||||
await self.docker_service.start_container(container_info.container_id)
|
||||
|
||||
session.container_id = container_info.container_id
|
||||
session.status = "running"
|
||||
@@ -458,8 +458,8 @@ class SessionManager:
|
||||
"OPENAI_API_KEY": os.getenv("OPENAI_API_KEY", ""),
|
||||
"ANTHROPIC_API_KEY": os.getenv("ANTHROPIC_API_KEY", ""),
|
||||
"GOOGLE_API_KEY": os.getenv("GOOGLE_API_KEY", ""),
|
||||
# Secure authentication for OpenCode server
|
||||
"OPENCODE_SERVER_PASSWORD": session.auth_token or "",
|
||||
# Auth disabled for development - will be added later
|
||||
# "OPENCODE_SERVER_PASSWORD": session.auth_token or "",
|
||||
"SESSION_AUTH_TOKEN": session.auth_token or "",
|
||||
"SESSION_ID": session.session_id,
|
||||
},
|
||||
@@ -883,6 +883,118 @@ async def get_session_container_health(session_id: str):
|
||||
}
|
||||
|
||||
|
||||
# Routes for OpenCode SPA runtime requests
|
||||
# These paths are hardcoded in the compiled JS and need cookie-based session routing
|
||||
@app.api_route(
|
||||
"/global/{path:path}",
|
||||
methods=["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD"],
|
||||
)
|
||||
async def proxy_global_to_session(request: Request, path: str):
|
||||
"""Proxy /global/* requests to the current session based on cookie"""
|
||||
session_id = request.cookies.get("lovdata_session")
|
||||
if not session_id:
|
||||
raise HTTPException(status_code=400, detail="No active session - please access via /session/{id}/ first")
|
||||
# Redirect to session-prefixed path
|
||||
return await proxy_to_session(request, session_id, f"global/{path}")
|
||||
|
||||
|
||||
@app.api_route(
|
||||
"/assets/{path:path}",
|
||||
methods=["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD"],
|
||||
)
|
||||
async def proxy_assets_to_session(request: Request, path: str):
|
||||
"""Proxy /assets/* requests to the current session based on cookie"""
|
||||
session_id = request.cookies.get("lovdata_session")
|
||||
if not session_id:
|
||||
raise HTTPException(status_code=400, detail="No active session - please access via /session/{id}/ first")
|
||||
# Redirect to session-prefixed path
|
||||
return await proxy_to_session(request, session_id, f"assets/{path}")
|
||||
|
||||
|
||||
@app.api_route(
|
||||
"/provider/{path:path}",
|
||||
methods=["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD"],
|
||||
)
|
||||
async def proxy_provider_path_to_session(request: Request, path: str):
|
||||
"""Proxy /provider/* requests to the current session based on cookie"""
|
||||
session_id = request.cookies.get("lovdata_session")
|
||||
if not session_id:
|
||||
raise HTTPException(status_code=400, detail="No active session - please access via /session/{id}/ first")
|
||||
return await proxy_to_session(request, session_id, f"provider/{path}")
|
||||
|
||||
|
||||
@app.api_route(
|
||||
"/provider",
|
||||
methods=["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD"],
|
||||
)
|
||||
async def proxy_provider_to_session(request: Request):
|
||||
"""Proxy /provider requests to the current session based on cookie"""
|
||||
session_id = request.cookies.get("lovdata_session")
|
||||
if not session_id:
|
||||
raise HTTPException(status_code=400, detail="No active session - please access via /session/{id}/ first")
|
||||
return await proxy_to_session(request, session_id, "provider")
|
||||
|
||||
|
||||
@app.api_route(
|
||||
"/project",
|
||||
methods=["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD"],
|
||||
)
|
||||
async def proxy_project_to_session(request: Request):
|
||||
"""Proxy /project requests to the current session based on cookie"""
|
||||
session_id = request.cookies.get("lovdata_session")
|
||||
if not session_id:
|
||||
raise HTTPException(status_code=400, detail="No active session - please access via /session/{id}/ first")
|
||||
return await proxy_to_session(request, session_id, "project")
|
||||
|
||||
|
||||
@app.api_route(
|
||||
"/path",
|
||||
methods=["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD"],
|
||||
)
|
||||
async def proxy_path_to_session(request: Request):
|
||||
"""Proxy /path requests to the current session based on cookie"""
|
||||
session_id = request.cookies.get("lovdata_session")
|
||||
if not session_id:
|
||||
raise HTTPException(status_code=400, detail="No active session - please access via /session/{id}/ first")
|
||||
return await proxy_to_session(request, session_id, "path")
|
||||
|
||||
|
||||
@app.api_route(
|
||||
"/find/{path:path}",
|
||||
methods=["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD"],
|
||||
)
|
||||
async def proxy_find_to_session(request: Request, path: str):
|
||||
"""Proxy /find/* requests to the current session based on cookie"""
|
||||
session_id = request.cookies.get("lovdata_session")
|
||||
if not session_id:
|
||||
raise HTTPException(status_code=400, detail="No active session - please access via /session/{id}/ first")
|
||||
return await proxy_to_session(request, session_id, f"find/{path}")
|
||||
|
||||
|
||||
@app.api_route(
|
||||
"/file",
|
||||
methods=["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD"],
|
||||
)
|
||||
async def proxy_file_to_session(request: Request):
|
||||
"""Proxy /file requests to the current session based on cookie"""
|
||||
session_id = request.cookies.get("lovdata_session")
|
||||
if not session_id:
|
||||
raise HTTPException(status_code=400, detail="No active session - please access via /session/{id}/ first")
|
||||
return await proxy_to_session(request, session_id, "file")
|
||||
|
||||
|
||||
@app.api_route(
|
||||
"/file/{path:path}",
|
||||
methods=["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD"],
|
||||
)
|
||||
async def proxy_file_path_to_session(request: Request, path: str):
|
||||
"""Proxy /file/* requests to the current session based on cookie"""
|
||||
session_id = request.cookies.get("lovdata_session")
|
||||
if not session_id:
|
||||
raise HTTPException(status_code=400, detail="No active session - please access via /session/{id}/ first")
|
||||
return await proxy_to_session(request, session_id, f"file/{path}")
|
||||
|
||||
|
||||
@app.api_route(
|
||||
"/session/{session_id}/{path:path}",
|
||||
methods=["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD"],
|
||||
@@ -916,40 +1028,14 @@ async def proxy_to_session(request: Request, session_id: str, path: str):
|
||||
status_code=404, detail="Session not found or not running"
|
||||
)
|
||||
|
||||
# Dynamically detect the Docker host IP from container perspective
|
||||
# This supports multiple Docker environments (Docker Desktop, Linux, cloud, etc.)
|
||||
try:
|
||||
host_ip = await async_get_host_ip()
|
||||
logger.info(f"Using detected host IP for proxy: {host_ip}")
|
||||
except RuntimeError as e:
|
||||
# Fallback to environment variable or common defaults
|
||||
host_ip = os.getenv("HOST_IP")
|
||||
if not host_ip:
|
||||
# Try common Docker gateway IPs as final fallback
|
||||
common_gateways = ["172.17.0.1", "192.168.65.1", "host.docker.internal"]
|
||||
for gateway in common_gateways:
|
||||
try:
|
||||
# Test connectivity to gateway
|
||||
import socket
|
||||
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
sock.settimeout(1.0)
|
||||
result = sock.connect_ex((gateway, 22))
|
||||
sock.close()
|
||||
if result == 0:
|
||||
host_ip = gateway
|
||||
logger.warning(f"Using fallback gateway IP: {host_ip}")
|
||||
break
|
||||
except Exception:
|
||||
continue
|
||||
else:
|
||||
logger.error(f"Host IP detection failed: {e}")
|
||||
raise HTTPException(
|
||||
status_code=500,
|
||||
detail="Could not determine Docker host IP for proxy routing",
|
||||
)
|
||||
|
||||
container_url = f"http://{host_ip}:{session.port}"
|
||||
# For DinD architecture: containers run inside docker-daemon with ports mapped
|
||||
# to docker-daemon's interface, so we proxy through docker-daemon hostname
|
||||
docker_host = os.getenv("DOCKER_HOST", "http://docker-daemon:2375")
|
||||
# Extract hostname from DOCKER_HOST (e.g., "http://docker-daemon:2375" -> "docker-daemon")
|
||||
parsed = urlparse(docker_host)
|
||||
container_host = parsed.hostname or "docker-daemon"
|
||||
|
||||
container_url = f"http://{container_host}:{session.port}"
|
||||
|
||||
# Prepare the request URL
|
||||
url = f"{container_url}/{path}"
|
||||
@@ -1002,12 +1088,46 @@ async def proxy_to_session(request: Request, session_id: str, path: str):
|
||||
status_code=response.status_code,
|
||||
)
|
||||
|
||||
# Return the response
|
||||
return Response(
|
||||
content=response.content,
|
||||
# Return the response - inject base tag for HTML to fix asset paths
|
||||
content = response.content
|
||||
response_headers = dict(response.headers)
|
||||
content_type = response.headers.get("content-type", "")
|
||||
|
||||
# For HTML responses, rewrite root-relative paths to include session prefix
|
||||
# OpenCode uses paths like /assets/, /favicon.ico which bypass <base> tag
|
||||
# We need to prepend the session path to make them work
|
||||
if "text/html" in content_type:
|
||||
try:
|
||||
html = content.decode("utf-8")
|
||||
session_prefix = f"/session/{session_id}"
|
||||
|
||||
# Rewrite src="/..." to src="/session/{id}/..."
|
||||
html = re.sub(r'src="/', f'src="{session_prefix}/', html)
|
||||
# Rewrite href="/..." to href="/session/{id}/..."
|
||||
html = re.sub(r'href="/', f'href="{session_prefix}/', html)
|
||||
# Rewrite content="/..." for meta tags
|
||||
html = re.sub(r'content="/', f'content="{session_prefix}/', html)
|
||||
|
||||
content = html.encode("utf-8")
|
||||
response_headers["content-length"] = str(len(content))
|
||||
except UnicodeDecodeError:
|
||||
pass # Not valid UTF-8, skip rewriting
|
||||
|
||||
# Create response with session tracking cookie
|
||||
resp = Response(
|
||||
content=content,
|
||||
status_code=response.status_code,
|
||||
headers=dict(response.headers),
|
||||
headers=response_headers,
|
||||
)
|
||||
# Set cookie to track current session for /global/* and /assets/* routing
|
||||
resp.set_cookie(
|
||||
key="lovdata_session",
|
||||
value=session_id,
|
||||
httponly=True,
|
||||
samesite="lax",
|
||||
max_age=86400, # 24 hours
|
||||
)
|
||||
return resp
|
||||
|
||||
except httpx.TimeoutException as e:
|
||||
duration_ms = (time.time() - start_time) * 1000
|
||||
|
||||
Reference in New Issue
Block a user