feat: enable remote SDK and resilient device orchestration

This commit is contained in:
somew1nd committed 2026-08-30 00:41:07 -07:00
1 parent 3cc711df51
commit ddfa420945
110 files changed
+14450 -12954

No files matched your search

+58 -27
View File
@@ -12,11 +12,12 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import asyncio
from pathlib import Path
from fastapi import APIRouter, HTTPException
from fastapi.responses import FileResponse, HTMLResponse
from artemis.config import IMAGES_DIR, WORKSPACE_ROOT
from artemis.config import IMAGES_DIR, TRACES_PATH, WORKSPACE_ROOT
try:
from admin_console.database.repositories.session_repository import session_repo
@@ -27,6 +28,48 @@ except ImportError:
router = APIRouter(tags=["media"])
_VIDEO_MEDIA_TYPES = {
".mp4": "video/mp4",
".webm": "video/webm",
".mkv": "video/x-matroska",
}
def _allowed_media_roots() -> list[Path]:
"""Directories the media endpoints are permitted to serve from."""
roots = []
for root in (WORKSPACE_ROOT, TRACES_PATH):
try:
roots.append(Path(root).resolve())
except OSError:
continue
return roots
def _resolve_media_path(raw_path: str, allowed_suffixes: set[str]) -> Path:
"""Resolve a client-supplied path strictly inside the allowed media roots.
Every candidate is fully resolved (symlinks, `..`, mixed separators) and
then re-checked against the allowed roots and an extension allowlist, so a
crafted URL can never address source code, databases, or dotfiles.
"""
roots = _allowed_media_roots()
candidate = Path(raw_path)
attempts = [candidate] if candidate.is_absolute() else [root / candidate for root in roots]
for attempt in attempts:
try:
resolved = attempt.resolve(strict=True)
except (OSError, ValueError):
continue
if not resolved.is_file():
continue
if not any(resolved.is_relative_to(root) for root in roots):
continue
if resolved.suffix.lower() not in allowed_suffixes:
raise HTTPException(status_code=403, detail="File type is not served by the media API.")
return resolved
raise HTTPException(status_code=404, detail="Media file not found")
@router.get("/admin", response_class=HTMLResponse)
@router.get("/debug", response_class=HTMLResponse)
@@ -47,8 +90,12 @@ async def get_admin_index():
async def get_image(image_name: str):
if not image_name.endswith(".jpg"):
image_name += ".jpg"
image_path = IMAGES_DIR / image_name
if not image_path.exists():
try:
images_root = IMAGES_DIR.resolve()
image_path = (images_root / image_name).resolve(strict=True)
except (OSError, ValueError):
raise HTTPException(status_code=404, detail="Image not found")
if not image_path.is_file() or not image_path.is_relative_to(images_root):
raise HTTPException(status_code=404, detail="Image not found")
return FileResponse(image_path, media_type="image/jpeg")
@@ -56,34 +103,18 @@ async def get_image(image_name: str):
@router.get("/videos/{video_path:path}")
async def get_video(video_path: str):
path = Path(video_path)
if not path.exists():
if not path.is_absolute():
candidate = WORKSPACE_ROOT / path
if candidate.exists():
path = candidate
else:
candidate_root = Path("/" + video_path.lstrip("/"))
if candidate_root.exists():
path = candidate_root
else:
path = candidate
if not path.exists():
raise HTTPException(status_code=404, detail=f"Video not found at {path}")
suffix = path.suffix.lower()
media_type = "video/mp4"
if suffix == ".webm":
media_type = "video/webm"
elif suffix == ".mkv":
media_type = "video/x-matroska"
return FileResponse(path, media_type=media_type)
path = _resolve_media_path(video_path, set(_VIDEO_MEDIA_TYPES))
return FileResponse(path, media_type=_VIDEO_MEDIA_TYPES[path.suffix.lower()])
@router.get("/api/sessions/{session_id}/video")
async def get_session_video(session_id: str):
# Blocking work (sqlite, filesystem scan, possible ffmpeg conversion) runs
# off the event loop.
return await asyncio.to_thread(_get_session_video_sync, session_id)
def _get_session_video_sync(session_id: str):
video_rec_map = session_repo.get_video_recordings_map()
video_idx = media_service.build_video_index()
row = session_repo.get_session_by_id(session_id)