Files
AgentLab/backend/app/web.py
T
ThearchyHelios 6af4e396b9 生产部署:prod.sh 直接运行,package-docker.sh 打 Docker 离线包(镜像 + 一键部署脚本 + 配置 + 说明)
- 服务端可以托管前端构建产物(AGENTLAB_WEB_DIST):页面路由回退到 index.html,/api 下不存在的路径照旧是 JSON 404,
  挡住路径穿越;index.html 不缓存,assets/ 带哈希的文件长缓存;没设时行为完全不变
- scripts/prod.sh:不用 Docker 的生产运行。按需构建前端,单进程、不热重载,默认只听本机,--host 开放到局域网并提示没有登录
- Dockerfile(多阶段构建,Linux 镜像):前端在构建机架构上构建,运行阶段用 python:3.12-slim,依赖装进独立的 venv,
  以非 root 用户(uid 10001)运行,数据在 /data,带健康检查;.dockerignore 排除 .env、data/、各种库文件,镜像里实测没有密钥和数据库
- scripts/package-docker.sh:构建指定平台的镜像(默认按本机架构,可选 linux/amd64),打成
  release/agentlab-<版本>-linux-<架构>.tar,里面有 image.tar、deploy.sh、agentlab.env、README.md、SHA256SUMS
- deploy/deploy.sh:目标机上只依赖 Docker,离线可用。install / start / stop / restart / status / logs / upgrade / uninstall;
  检查架构和配置,自动导入镜像,替换容器时保留旧容器以便回退,数据永远保留,日志轮转
- 修正 bubblewrap 的可用性判断:以前只看有没有装 bwrap,容器里和 Ubuntu 24.04 默认配置下装了却建不了命名空间,
  每次执行代码都失败;现在真起一次试,不能用就退回下一个后端,并写明原因
- README 增加「生产部署」一节,部署细节见 deploy/README.md
2026-09-30 09:48:16 +08:00

97 lines
3.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""生产部署:由服务端直接托管前端构建产物(AGENTLAB_WEB_DIST)。
开发时页面由 Vite 开发服务器提供、/api 由它转发,用不到这里。生产部署只起一个进程,
页面和接口同源,省掉 CORS,也不用另配静态服务器。
规则:
- 构建产物里有这个文件就返回它;
- 其余非 /api 的 GET 回退到 index.html:前端是单页应用,/studio 这类地址由前端路由处理;
- /api 下不存在的路径照旧是 JSON 404。排除 /api 写在路由的路径规则里,不在处理函数里判断——
否则 POST /api/不存在 会因为「路径对上了、方法不对」变成 405;
- 只注册 GET / HEAD 的 HTTP 路由,WebSocket 不经过这里;
- index.html 不缓存,发版后刷新即生效;assets/ 下的文件名带内容哈希,长缓存。
"""
from __future__ import annotations
import mimetypes
from pathlib import Path
from fastapi import FastAPI, HTTPException
from fastapi.responses import FileResponse
from starlette.convertors import Convertor, register_url_convertor
_NO_CACHE = "no-cache"
_IMMUTABLE = "public, max-age=31536000, immutable"
# 精简的 Linux 镜像里没有 /etc/mime.types,mimetypes 的内置表又缺几种(woff2 之类)。
# 构建产物里出现的类型写死在这里,别的再交给 mimetypes 猜
_TYPES = {
".html": "text/html; charset=utf-8",
".js": "text/javascript; charset=utf-8",
".mjs": "text/javascript; charset=utf-8",
".css": "text/css; charset=utf-8",
".json": "application/json",
".map": "application/json",
".svg": "image/svg+xml",
".png": "image/png",
".ico": "image/x-icon",
".woff": "font/woff",
".woff2": "font/woff2",
".wasm": "application/wasm",
".txt": "text/plain; charset=utf-8",
}
class _WebPath(Convertor[str]):
"""任意路径,但不以 api 这一段开头:/api、/api/… 留给接口路由和它们自己的 404。"""
regex = r"(?!api(?:/|$)).*"
def convert(self, value: str) -> str:
return value
def to_string(self, value: str) -> str:
return value
register_url_convertor("agentlab_web", _WebPath())
def _media_type(path: Path) -> str | None:
return _TYPES.get(path.suffix.lower()) or mimetypes.guess_type(path.name)[0]
def mount_web(app: FastAPI, dist: Path) -> None:
"""在 app 上挂页面托管。必须在所有 /api 路由注册之后调用。"""
root = dist.resolve()
index = root / "index.html"
if not index.is_file():
# 配了却没有产物:启动时就报出来,别等打开页面才看到一个 404
raise RuntimeError(
f"AGENTLAB_WEB_DIST 指向的 {root} 下没有 index.html。"
"先构建前端(cd frontend && pnpm build),或者去掉这个设置"
)
def respond(target: Path, *, immutable: bool = False) -> FileResponse:
return FileResponse(
target,
media_type=_media_type(target),
headers={"Cache-Control": _IMMUTABLE if immutable else _NO_CACHE},
)
async def serve(path: str) -> FileResponse:
if path:
target = (root / path).resolve()
# resolve 之后还得在 dist 里面:挡住 %2e%2e 这类穿越和指向外面的软链
if target.is_file() and target.is_relative_to(root):
return respond(target, immutable=path.startswith("assets/"))
if path.startswith("assets/"):
# 发版后旧页面还在请求旧哈希的文件。回退成 index.html 的话浏览器会把它当脚本
# 解析、报一个看不懂的 MIME 错误,所以明确给 404
raise HTTPException(status_code=404)
return respond(index)
app.add_api_route(
"/{path:agentlab_web}", serve, methods=["GET", "HEAD"], include_in_schema=False,
)