feat: v0.3.8–v0.3.14 工作区汇总(依赖升级、整理来源、手动入库、原地入库等)
生产 v0.3.8~v0.3.13 均由 8013196 加工作区修改构建部署,此前未提交;本提交补齐源码,
并含未部署的 v0.3.14 原地入库。中间各版本源码状态未保存,无法逐版拆分,详见
docs/plan/17-full-upgrade.md 与 docs/plan/18-organize-release.md。
- v0.3.8 全面升级:Deno workspace 依赖、Router 8 / Vite 8、camofox 1.18.0 / Node 24、
PostgreSQL 18.6、备份客户端 18.6、演示 qB 5.2.4
- v0.3.9 媒体整理:分类规则、整理来源与目录快照(0059)、文件管理整理入口
- v0.3.10 手动下载与入库:独立手动下载、详情页资源下载、TMDB / Local 元数据兜底
(metadata.resolve_organize)、正常多版本 / 一次性洗版(0060)
- v0.3.11 搜索详情与桌面黑边修复
- v0.3.12 列表分页、搜索历史、首页概况
- v0.3.13 统一整理弹窗与队列
- v0.3.14 原地入库(0061,未部署)
同批审计修复:
- 刮削入队:确认原记录存在后才删重放产生的新记录;写回 run 关联失败时保持 queued,
由处理器补关联(对齐 MP 先登记、只在入队失败时记失败)
- 识别升级链只在结果更确定时采用,不再用 no_match 顶替原有歧义候选
- bluray_dir 资产以原盘目录本身为刮削根(MP 同做法)
- metadata.resolve_organize 登记到入库排障 Skill 与能力地图
- db/src/types.ts 按迁移重新生成;contracts 整理动作枚举顺序与数据库对齐
- 测试:原地入库用例改为直接查记录表;domain 测试模板补 version
- 前端:通知铃铛弹层与用户菜单统一样式;站点卡片共用卡头、底栏贴底、分页间距
依赖升级证据只提交摘要文件,registry-snapshot.json 移至 .local/(SHA-256 记于文档)。
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
8013196e3f
commit
ceb66f8c77
265 files changed
+17751
-2483
No files matched your search
@@ -1,13 +1,13 @@
|
||||
# 浏览器桌面镜像(原站视图桌面模式,docs/design/webtop-remote-browser.md、docs/runbook/browser.md §8):
|
||||
# linuxserver baseimage-selkies(Webtop 4.0 的底座:pixelflux + labwc Wayland 桌面 + Selkies Web 客户端)
|
||||
# + camofox-browser 1.17.0 以有头模式跑在同一个 Wayland 会话里 → 桌面上看到的就是 Agent 驱动的那个 Firefox。
|
||||
# + camofox-browser 1.18.0 以有头模式跑在同一个 Wayland 会话里 → 桌面上看到的就是 Agent 驱动的那个 Firefox。
|
||||
#
|
||||
# camofox 部分不重新下载:直接从旧镜像 camofox-browser:1.17.0(docker/camofox/Dockerfile 构建)复制
|
||||
# node、/app 与 Camoufox 浏览器包,两个镜像的 camofox 完全一致,可随时用 CAMOFOX_IMAGE 切回旧镜像。
|
||||
# camofox 部分不重新下载:直接从基础镜像 camofox-browser:1.18.0(docker/camofox/Dockerfile 构建)复制
|
||||
# node、/app 与 Camoufox 浏览器包,两个镜像的 camofox 完全一致,可随时用 CAMOFOX_IMAGE 切回基础镜像。
|
||||
# 手工构建:
|
||||
# docker build -t camofox-browser:1.17.0 --build-context upstream=… docker/camofox # 先有旧镜像
|
||||
# docker build -t umt-browser-desktop:1.17.0 docker/browser-desktop
|
||||
ARG CAMOFOX_BASE=camofox-browser:1.17.0
|
||||
# docker build -t camofox-browser:1.18.0 --build-context upstream=… docker/camofox # 先有基础镜像
|
||||
# docker build -t umt-browser-desktop:1.18.0 docker/browser-desktop
|
||||
ARG CAMOFOX_BASE=camofox-browser:1.18.0
|
||||
# baseimage-selkies debiantrixie(Selkies 2.x、labwc 0.8.3,已含 fonts-noto-cjk);按 digest 锁定,升级需重新核验
|
||||
ARG SELKIES_BASE=ghcr.io/linuxserver/baseimage-selkies:debiantrixie@sha256:f3ca0b10e923a603941a996ea1c3356b9527cc53163259f4e836f61fd4523e12
|
||||
|
||||
@@ -16,17 +16,19 @@ FROM ${CAMOFOX_BASE} AS camofox
|
||||
FROM ${SELKIES_BASE}
|
||||
|
||||
ARG APT_MIRROR=
|
||||
ARG APT_HTTPS_PROXY=
|
||||
# 1 = 装容器内输入法 fcitx5(拼音 + mozc,缺省装;是否启动由运行时 UMT_DESKTOP_IME=fcitx5 决定)
|
||||
ARG WITH_FCITX5=1
|
||||
|
||||
RUN if [ -n "$APT_MIRROR" ]; then for f in /etc/apt/sources.list.d/debian.sources /etc/apt/sources.list; do [ -f "$f" ] && sed -i "[email protected]@${APT_MIRROR}@g" "$f"; done; fi \
|
||||
&& apt-get update \
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
&& apt-get -o "Acquire::https::Proxy=${APT_HTTPS_PROXY:-DIRECT}" update \
|
||||
&& apt-get -o "Acquire::https::Proxy=${APT_HTTPS_PROXY:-DIRECT}" upgrade -y --no-install-recommends \
|
||||
&& apt-get -o "Acquire::https::Proxy=${APT_HTTPS_PROXY:-DIRECT}" install -y --no-install-recommends \
|
||||
libdbus-glib-1-2 libxt6t64 libasound2t64 libx11-xcb1 libxcomposite1 libxcursor1 libxdamage1 libxfixes3 \
|
||||
libxi6 libxrandr2 libxrender1 libxss1 libxtst6 libegl1 libgl1-mesa-dri libgbm1 \
|
||||
fonts-noto-cjk fonts-noto-cjk-extra fonts-noto-color-emoji fontconfig \
|
||||
&& if [ "$WITH_FCITX5" = "1" ]; then \
|
||||
apt-get install -y --no-install-recommends \
|
||||
apt-get -o "Acquire::https::Proxy=${APT_HTTPS_PROXY:-DIRECT}" install -y --no-install-recommends \
|
||||
fcitx5 fcitx5-chinese-addons fcitx5-mozc fcitx5-frontend-gtk3 fcitx5-config-qt- ; \
|
||||
fi \
|
||||
&& apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/*
|
||||
@@ -40,8 +42,8 @@ COPY root/ /
|
||||
RUN node -e 'const f="/opt/camofox/camofox.config.json";const c=JSON.parse(require("fs").readFileSync(f,"utf8"));c.plugins={...(c.plugins||{}),"umt-cookies":{enabled:true}};require("fs").writeFileSync(f,JSON.stringify(c,null,2))'
|
||||
|
||||
LABEL org.opencontainers.image.title="umt-browser-desktop" \
|
||||
org.opencontainers.image.description="camofox-browser 1.17.0 (headed, Wayland) on linuxserver baseimage-selkies" \
|
||||
org.opencontainers.image.version="1.17.0"
|
||||
org.opencontainers.image.description="camofox-browser 1.18.0 (headed, Wayland) on linuxserver baseimage-selkies" \
|
||||
org.opencontainers.image.version="1.18.0"
|
||||
|
||||
ENV CAMOUFOX_INSTALL_DIR=/opt/camoufox \
|
||||
NODE_ENV=production \
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<labwc_config>
|
||||
<core>
|
||||
<decoration>server</decoration>
|
||||
<gap>0</gap>
|
||||
</core>
|
||||
<keyboard>
|
||||
<default />
|
||||
<keybind key="A-F4"><action name="None" /></keybind>
|
||||
<keybind key="A-Escape"><action name="None" /></keybind>
|
||||
<keybind key="A-space"><action name="None" /></keybind>
|
||||
<keybind key="W-e"><action name="None" /></keybind>
|
||||
</keyboard>
|
||||
<mouse>
|
||||
<default />
|
||||
<context name="Root">
|
||||
<mousebind button="Right" action="Press"><action name="None" /></mousebind>
|
||||
<mousebind button="Middle" action="Press"><action name="None" /></mousebind>
|
||||
</context>
|
||||
</mouse>
|
||||
<windowRules>
|
||||
<windowRule identifier="*" serverDecoration="no">
|
||||
<action name="Maximize" />
|
||||
</windowRule>
|
||||
</windowRules>
|
||||
</labwc_config>
|
||||
@@ -2,7 +2,8 @@
|
||||
# UMT 浏览器桌面:在 linuxserver 的 init-selkies-config 之后、Selkies/桌面启动之前执行(root)。
|
||||
# 1) Selkies 双口令:由 CAMOFOX_ACCESS_KEY 经 HMAC 派生(app 用同一算法),不另设密钥;
|
||||
# 2) labwc autostart 每次覆盖(只起可选 fcitx5,不起终端等其它应用);
|
||||
# 3) 登录状态目录(与旧 camofox 镜像同一个卷 /root/.camofox)交给运行用户 abc。
|
||||
# 3) 写入完整 labwc 配置,保证浏览器最大化;底座按行注释快捷键会破坏 XML。
|
||||
# 4) 登录状态目录(与旧 camofox 镜像同一个卷 /root/.camofox)交给运行用户 abc。
|
||||
set -e
|
||||
ENVDIR=/run/s6/container_environment
|
||||
derive() { printf '%s' "$1" | openssl dgst -sha256 -hmac "$CAMOFOX_ACCESS_KEY" -r | cut -c1-48; }
|
||||
@@ -20,6 +21,9 @@ rm -f "$ENVDIR/PASSWORD" "$ENVDIR/PASSWD"
|
||||
|
||||
LABWC_DIR="$HOME/.config/labwc"
|
||||
mkdir -p "$LABWC_DIR"
|
||||
cp /defaults/umt-labwc.xml "$LABWC_DIR/rc.xml"
|
||||
chown root:abc "$LABWC_DIR/rc.xml"
|
||||
chmod 444 "$LABWC_DIR/rc.xml"
|
||||
cp /defaults/autostart_wayland "$LABWC_DIR/autostart"
|
||||
chown root:abc "$LABWC_DIR/autostart"
|
||||
chmod 550 "$LABWC_DIR/autostart"
|
||||
|
||||
+39
-19
@@ -1,21 +1,18 @@
|
||||
# camofox-browser 镜像(docker-compose.yml 的 camofox 服务)。
|
||||
# 上游 jo-inc/camofox-browser 不发布镜像;源码由命名上下文 upstream 提供(compose 里锁定 git tag v1.17.0,
|
||||
# commit 389c996ae3c7d42e539295a336ee6f975847f066)。本文件照抄上游 Dockerfile 的 camofox-browser 阶段,只改三处:
|
||||
# 1. 上游 v1.17.0 的 Dockerfile 在 `npm ci` 之前没有复制 postinstall.js,构建必然失败(MODULE_NOT_FOUND);
|
||||
# 这里先复制 postinstall.js 与 lib/(Camoufox 已预置,postinstall 见到 version.json 直接跳过下载);
|
||||
# 2. 可选 APT_MIRROR(国内构建换源,如 mirrors.ustc.edu.cn);
|
||||
# 3. CMD 用 exec 启动 node(见文件末尾)。
|
||||
# 手工构建:docker build -t camofox-browser:1.17.0 \
|
||||
# --build-context upstream=https://github.com/jo-inc/camofox-browser.git#v1.17.0 docker/camofox
|
||||
FROM node:22-trixie-slim AS camofox-browser
|
||||
# camofox-browser v1.18.0; upstream commit f94c81babf4bfd44cdd4bd46e1f2e1fb1d91c753.
|
||||
# Build with: docker build --build-context upstream=https://github.com/jo-inc/camofox-browser.git#f94c81babf4bfd44cdd4bd46e1f2e1fb1d91c753 -t camofox-browser:1.18.0 docker/camofox
|
||||
# Runtime manifest/lock below are maintained locally; never run audit fix while building.
|
||||
ARG CAMOUFOX_ASSET_STAGE=downloaded-browser
|
||||
FROM node:24.21.0-trixie-slim@sha256:8ec5d7557396cfe32d21c3f9c13072355ceab22b584578ca4bb28af31120cffe AS browser-base
|
||||
|
||||
ARG CAMOUFOX_VERSION=152.0.4
|
||||
ARG CAMOUFOX_RELEASE=beta.28
|
||||
ARG ARCH=x86_64
|
||||
ARG CAMOUFOX_RELEASE=beta.30
|
||||
ARG TARGETARCH
|
||||
ARG APT_MIRROR=
|
||||
|
||||
RUN if [ -n "$APT_MIRROR" ]; then sed -i "[email protected]@${APT_MIRROR}@g" /etc/apt/sources.list.d/debian.sources; fi \
|
||||
&& apt-get update && apt-get install -y \
|
||||
&& apt-get update \
|
||||
&& apt-get upgrade -y --no-install-recommends \
|
||||
&& apt-get install -y \
|
||||
libgtk-3-0 libdbus-glib-1-2 libxt6 libasound2 libx11-xcb1 libxcomposite1 libxcursor1 libxdamage1 libxfixes3 \
|
||||
libxi6 libxrandr2 libxrender1 libxss1 libxtst6 \
|
||||
libegl1 libgl1-mesa-dri libgbm1 \
|
||||
@@ -23,8 +20,27 @@ RUN if [ -n "$APT_MIRROR" ]; then sed -i "[email protected]@${APT_MIRROR}@g" /etc
|
||||
ca-certificates curl unzip python3-minimal \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN mkdir -p /root/.cache/camoufox \
|
||||
&& curl -fL -o /tmp/camoufox.zip "https://github.com/daijro/camoufox/releases/download/v${CAMOUFOX_VERSION}-${CAMOUFOX_RELEASE}/camoufox-${CAMOUFOX_VERSION}-${CAMOUFOX_RELEASE}-lin.${ARCH}.zip" \
|
||||
# Optional local asset context accelerates builds without changing integrity checks.
|
||||
FROM browser-base AS downloaded-browser
|
||||
RUN case "${TARGETARCH}" in \
|
||||
amd64) ARCH=x86_64; CAMOUFOX_SHA256=5720d45b894ce1770543de024c6f10d514b38be560fa2dc3226b3d8586caf672 ;; \
|
||||
arm64) ARCH=arm64; CAMOUFOX_SHA256=60447260af8bebdb0ec3f2aa72f687b879e5598367303de2e3fdbc7a5be8c124 ;; \
|
||||
*) echo "Unsupported architecture: ${TARGETARCH}" >&2; exit 1 ;; \
|
||||
esac \
|
||||
&& curl -fL -o /tmp/camoufox.zip "https://github.com/daijro/camoufox/releases/download/v${CAMOUFOX_VERSION}-${CAMOUFOX_RELEASE}/camoufox-${CAMOUFOX_VERSION}-${CAMOUFOX_RELEASE}-lin.${ARCH}.zip"
|
||||
|
||||
FROM browser-base AS provided-browser
|
||||
COPY --from=browser-asset /camoufox.zip /tmp/camoufox.zip
|
||||
|
||||
FROM ${CAMOUFOX_ASSET_STAGE} AS camofox-browser
|
||||
ARG TARGETARCH
|
||||
RUN case "${TARGETARCH}" in \
|
||||
amd64) ARCH=x86_64; CAMOUFOX_SHA256=5720d45b894ce1770543de024c6f10d514b38be560fa2dc3226b3d8586caf672 ;; \
|
||||
arm64) ARCH=arm64; CAMOUFOX_SHA256=60447260af8bebdb0ec3f2aa72f687b879e5598367303de2e3fdbc7a5be8c124 ;; \
|
||||
*) echo "Unsupported architecture: ${TARGETARCH}" >&2; exit 1 ;; \
|
||||
esac \
|
||||
&& mkdir -p /root/.cache/camoufox \
|
||||
&& echo "${CAMOUFOX_SHA256} /tmp/camoufox.zip" | sha256sum -c - \
|
||||
&& (unzip -q /tmp/camoufox.zip -d /root/.cache/camoufox || true) \
|
||||
&& rm /tmp/camoufox.zip \
|
||||
&& chmod -R 755 /root/.cache/camoufox \
|
||||
@@ -33,7 +49,8 @@ RUN mkdir -p /root/.cache/camoufox \
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY --from=upstream package.json package-lock.json postinstall.js ./
|
||||
COPY package.json package-lock.json ./
|
||||
COPY --from=upstream postinstall.js ./
|
||||
COPY --from=upstream scripts/ ./scripts/
|
||||
COPY --from=upstream lib/ ./lib/
|
||||
RUN apt-get update \
|
||||
@@ -43,14 +60,17 @@ RUN apt-get update \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
COPY --from=upstream server.js camofox.config.json ./
|
||||
COPY --from=upstream mcp/ ./mcp/
|
||||
COPY --from=upstream mcp/server.mjs ./mcp/server.mjs
|
||||
COPY --from=upstream mcp/lib/ ./mcp/lib/
|
||||
# MCP uses the same installed parent dependency tree; do not ship the unused upstream lock.
|
||||
COPY mcp-package.json ./mcp/package.json
|
||||
COPY --from=upstream plugins/ ./plugins/
|
||||
|
||||
RUN sh scripts/install-plugin-deps.sh
|
||||
|
||||
LABEL org.opencontainers.image.source="https://github.com/jo-inc/camofox-browser" \
|
||||
org.opencontainers.image.version="v1.17.0" \
|
||||
org.opencontainers.image.revision="389c996ae3c7d42e539295a336ee6f975847f066"
|
||||
org.opencontainers.image.version="v1.18.0" \
|
||||
org.opencontainers.image.revision="f94c81babf4bfd44cdd4bd46e1f2e1fb1d91c753"
|
||||
|
||||
ENV NODE_ENV=production
|
||||
ENV CAMOFOX_PORT=9377
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
# 浏览器独立构建
|
||||
|
||||
服务源码固定为 camofox-browser v1.18.0 的提交 `f94c81babf4bfd44cdd4bd46e1f2e1fb1d91c753`。标签对象 `60481dbd…` 不是源码提交。`build.sh` 核对 checkout 的提交与工作区后构建;Dockerfile 使用该源码命名上下文,并使用本目录的生产 manifest 和 lock。
|
||||
|
||||
```sh
|
||||
APT_MIRROR=mirrors.ustc.edu.cn sh docker/camofox/build.sh
|
||||
docker build --network=host --build-arg APT_MIRROR=mirrors.ustc.edu.cn \
|
||||
-t umt-browser-desktop:1.18.0 docker/browser-desktop
|
||||
```
|
||||
|
||||
桌面底座含 Docker HTTPS apt 源;需要时可另传 `--build-arg APT_HTTPS_PROXY=http://127.0.0.1:7890` 使用现有代理。该参数只作用于构建中的 apt HTTPS 请求,默认直连,不写入容器 apt 配置;Debian HTTP 镜像仍直连。
|
||||
|
||||
固定组合:Node 24.21.0(基础镜像固定摘要)、Camoufox 152.0.4-beta.30(amd64/arm64 官方资产 SHA-256)、camoufox-js 0.12.0、playwright-core 1.60.0、better-sqlite3 13.0.3、MCP SDK 1.31.0。桌面底座继续使用已核验的 Selkies 摘要;Cookie 插件、身份、代理、运行用户、umask 和 exec 信号转发保留。
|
||||
|
||||
下载较慢时,可先把对应架构的官方资产存成某目录中的 `camoufox.zip`,设置 `CAMOUFOX_ASSET_DIR=/path/to/asset` 再运行 `build.sh`。可选命名上下文直接提供 ZIP,但仍通过完全相同的 SHA-256 校验、解压与二进制存在检查;默认仍在构建时从官方 URL 下载。
|
||||
|
||||
Playwright 1.60.0 是满足 camoufox-js peer 的最新稳定版。官方 [修复 #312](https://github.com/apify/camoufox-js/commit/3fe80d8448) 说明:1.61 起 `Browser.setDefaultViewport` 的 `viewport.isMobile` 不被 Camoufox 的 Juggler scheme 接受,导致 newContext/newPage 失败。因此没有 override peer,也没有升级到 1.63 后隐藏冲突。
|
||||
|
||||
本地 manifest 从上游生产入口派生,去掉开发测试、OpenClaw 可选 peer 和发布脚本;保留服务、postinstall 和生产可选 MCP。安全 overrides 固定 `adm-zip` 0.6.1、`brace-expansion` 5.0.12、`fast-uri` 3.1.8、`hono` 4.13.12、`ip-address` 10.7.2;fast-uri 保留 AJV 所需的 3.x API。其它生产依赖由独立 lock 固定。这里是显式依赖补丁,没有在构建时运行 `npm audit fix`。
|
||||
|
||||
MCP 和内置插件从服务根目录的同一依赖树解析模块。镜像中的 MCP manifest 同步为 SDK 1.31.0,不复制上游未安装的独立旧 lock。`audit-2026-10-01.json` 记录此生产 lock 的 npm 审计:174 个依赖、0 条漏洞;该结果不代表操作系统包审计。两个镜像均在安装依赖前更新容器系统包,宿主机软件不变。
|
||||
|
||||
需要更新依赖时,先编辑明确版本,再用固定 Node 镜像生成 lock 并审计,审查 lock 差异后构建:
|
||||
|
||||
```sh
|
||||
docker run --rm --network=host -v "$PWD/docker/camofox:/work" -w /work \
|
||||
node:24.21.0-trixie-slim@sha256:8ec5d7557396cfe32d21c3f9c13072355ceab22b584578ca4bb28af31120cffe \
|
||||
npm install --package-lock-only --ignore-scripts --omit=dev
|
||||
docker run --rm --network=host -v "$PWD/docker/camofox:/work:ro" -w /work \
|
||||
node:24.21.0-trixie-slim@sha256:8ec5d7557396cfe32d21c3f9c13072355ceab22b584578ca4bb28af31120cffe \
|
||||
npm audit --package-lock-only --omit=dev --json
|
||||
```
|
||||
|
||||
隔离验收使用独立容器、端口和 profile 卷,不挂生产 `umt-camofox`。`verify-upgrade.mjs` 接受测试地址和访问 key,支持重启前后两次运行以核验 Cookie 落盘和固定身份。此验收覆盖本地行为;真实 PT 挑战、远程桌面客户端兼容和 ARM64 执行仍需各自验收。
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"auditReportVersion": 2,
|
||||
"vulnerabilities": {},
|
||||
"metadata": {
|
||||
"vulnerabilities": {
|
||||
"info": 0,
|
||||
"low": 0,
|
||||
"moderate": 0,
|
||||
"high": 0,
|
||||
"critical": 0,
|
||||
"total": 0
|
||||
},
|
||||
"dependencies": {
|
||||
"prod": 145,
|
||||
"dev": 0,
|
||||
"optional": 29,
|
||||
"peer": 1,
|
||||
"peerOptional": 0,
|
||||
"total": 174
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
#!/bin/sh
|
||||
# Rebuild from a verified immutable upstream checkout and the local runtime lock.
|
||||
set -eu
|
||||
UPSTREAM_REVISION=f94c81babf4bfd44cdd4bd46e1f2e1fb1d91c753
|
||||
BUILD_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
||||
UPSTREAM_DIR=${1:-/tmp/umt-camofox-1.18.0-source}
|
||||
if [ ! -d "$UPSTREAM_DIR/.git" ]; then
|
||||
git clone --depth 1 --branch v1.18.0 https://github.com/jo-inc/camofox-browser.git "$UPSTREAM_DIR"
|
||||
fi
|
||||
test "$(git -C "$UPSTREAM_DIR" rev-parse HEAD)" = "$UPSTREAM_REVISION"
|
||||
test -z "$(git -C "$UPSTREAM_DIR" status --porcelain)"
|
||||
set -- docker build --network=host \
|
||||
--build-arg "APT_MIRROR=${APT_MIRROR:-}" \
|
||||
--build-context "upstream=$UPSTREAM_DIR" \
|
||||
-t "${CAMOFOX_BUILD_TAG:-camofox-browser:1.18.0}"
|
||||
if [ -n "${CAMOUFOX_ASSET_DIR:-}" ]; then
|
||||
test -f "$CAMOUFOX_ASSET_DIR/camoufox.zip"
|
||||
set -- "$@" --build-arg CAMOUFOX_ASSET_STAGE=provided-browser \
|
||||
--build-context "browser-asset=$CAMOUFOX_ASSET_DIR"
|
||||
fi
|
||||
exec "$@" "$BUILD_DIR"
|
||||
@@ -0,0 +1,43 @@
|
||||
{
|
||||
"name": "@askjo/camofox-browser-mcp",
|
||||
"version": "1.18.0",
|
||||
"description": "Standalone stdio MCP server exposing the camofox-browser REST API as MCP tools (Claude Code, Codex, agy, Cursor, opencode). Proxies to an already-running camofox-browser REST server \u2014 does not itself require the browser binary, Playwright, or the core server's dependencies. Initial MCP implementation contributed by @epicsagas.",
|
||||
"type": "module",
|
||||
"main": "server.mjs",
|
||||
"license": "MIT",
|
||||
"author": "Jo Inc <[email protected]>",
|
||||
"homepage": "https://github.com/jo-inc/camofox-browser/tree/master/mcp#readme",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/jo-inc/camofox-browser.git",
|
||||
"directory": "mcp"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/jo-inc/camofox-browser/issues"
|
||||
},
|
||||
"keywords": [
|
||||
"mcp",
|
||||
"model-context-protocol",
|
||||
"camofox",
|
||||
"camoufox",
|
||||
"browser-automation",
|
||||
"claude-code",
|
||||
"codex",
|
||||
"cursor",
|
||||
"opencode"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=22"
|
||||
},
|
||||
"bin": {
|
||||
"camofox-browser-mcp": "server.mjs"
|
||||
},
|
||||
"files": ["server.mjs", "lib/", "README.md"],
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/sdk": "1.31.0"
|
||||
},
|
||||
"overrides": {
|
||||
"fast-uri": "3.1.8",
|
||||
"hono": "4.13.12"
|
||||
}
|
||||
}
|
||||
Generated
+2275
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,148 @@
|
||||
{
|
||||
"name": "@askjo/camofox-browser",
|
||||
"version": "1.18.0",
|
||||
"description": "Headless browser automation server and OpenClaw plugin for AI agents - anti-detection, element refs, and session isolation",
|
||||
"type": "module",
|
||||
"main": "server.js",
|
||||
"license": "MIT",
|
||||
"author": "Jo Inc <[email protected]>",
|
||||
"homepage": "https://github.com/jo-inc/camofox-browser#readme",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/jo-inc/camofox-browser.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/jo-inc/camofox-browser/issues"
|
||||
},
|
||||
"keywords": [
|
||||
"browser",
|
||||
"automation",
|
||||
"headless",
|
||||
"scraping",
|
||||
"camofox",
|
||||
"camoufox",
|
||||
"anti-detection",
|
||||
"ai-agent",
|
||||
"openclaw",
|
||||
"clawdbot",
|
||||
"moltbot",
|
||||
"playwright",
|
||||
"firefox",
|
||||
"youtube",
|
||||
"transcript"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=22"
|
||||
},
|
||||
"files": [
|
||||
"server.js",
|
||||
"plugin.js",
|
||||
"openclaw.plugin.json",
|
||||
"camofox.config.json",
|
||||
"bin/camofox-browser.js",
|
||||
"lib/",
|
||||
"mcp/server.mjs",
|
||||
"mcp/lib/config.mjs",
|
||||
"mcp/lib/cookies.mjs",
|
||||
"mcp/lib/tool-contracts.mjs",
|
||||
"plugins/persistence/index.js",
|
||||
"plugins/vnc/index.js",
|
||||
"plugins/vnc/plugin.json",
|
||||
"plugins/vnc/spawn.js",
|
||||
"plugins/vnc/vnc-launcher.js",
|
||||
"plugins/vnc/vnc-watcher.sh",
|
||||
"plugins/vnc/vnc-watcher-lib.sh",
|
||||
"plugins/youtube/index.js",
|
||||
"plugins/youtube/youtube.js",
|
||||
"postinstall.js"
|
||||
],
|
||||
"openclaw": {
|
||||
"extensions": [
|
||||
"plugin.js"
|
||||
],
|
||||
"runtimeExtensions": [
|
||||
"plugin.js"
|
||||
],
|
||||
"compat": {
|
||||
"pluginApi": ">=2026.9.4"
|
||||
},
|
||||
"build": {
|
||||
"openclawVersion": "2026.9.4"
|
||||
},
|
||||
"tools": [
|
||||
{
|
||||
"name": "camofox_create_tab",
|
||||
"description": "Open a new browser tab at a URL"
|
||||
},
|
||||
{
|
||||
"name": "camofox_snapshot",
|
||||
"description": "Get accessibility snapshot with element refs"
|
||||
},
|
||||
{
|
||||
"name": "camofox_click",
|
||||
"description": "Click an element by ref or CSS selector"
|
||||
},
|
||||
{
|
||||
"name": "camofox_type",
|
||||
"description": "Type text into an element"
|
||||
},
|
||||
{
|
||||
"name": "camofox_navigate",
|
||||
"description": "Navigate to URL or search macro"
|
||||
},
|
||||
{
|
||||
"name": "camofox_scroll",
|
||||
"description": "Scroll page up/down/left/right"
|
||||
},
|
||||
{
|
||||
"name": "camofox_screenshot",
|
||||
"description": "Capture page screenshot"
|
||||
},
|
||||
{
|
||||
"name": "camofox_close_tab",
|
||||
"description": "Close a browser tab"
|
||||
},
|
||||
{
|
||||
"name": "camofox_evaluate",
|
||||
"description": "Execute JavaScript in a Camoufox tab page context"
|
||||
},
|
||||
{
|
||||
"name": "camofox_list_tabs",
|
||||
"description": "List open tabs for a user"
|
||||
},
|
||||
{
|
||||
"name": "camofox_import_cookies",
|
||||
"description": "Import Netscape cookie file (requires CAMOFOX_API_KEY)"
|
||||
}
|
||||
]
|
||||
},
|
||||
"scripts": {
|
||||
"start": "node server.js",
|
||||
"postinstall": "node postinstall.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"better-sqlite3": "13.0.3",
|
||||
"camoufox-js": "0.12.0",
|
||||
"express": "5.2.1",
|
||||
"playwright-core": "1.60.0",
|
||||
"prom-client": "15.1.3",
|
||||
"swagger-jsdoc": "6.3.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@modelcontextprotocol/sdk": "1.31.0"
|
||||
},
|
||||
"overrides": {
|
||||
"swagger-jsdoc": {
|
||||
"glob": "13.0.6"
|
||||
},
|
||||
"adm-zip": "0.6.1",
|
||||
"brace-expansion": "5.0.12",
|
||||
"fast-uri": "3.1.8",
|
||||
"hono": "4.13.12",
|
||||
"ip-address": "10.7.2"
|
||||
},
|
||||
"bin": {
|
||||
"camofox-browser": "./bin/camofox-browser.js",
|
||||
"camofox-browser-mcp": "./mcp/server.mjs"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { readFile, writeFile } from "node:fs/promises";
|
||||
|
||||
// Use only an isolated candidate instance: this creates a synthetic browser profile.
|
||||
const base = process.env.BROWSER_VERIFY_URL ?? "http://127.0.0.1:19377";
|
||||
const key = process.env.BROWSER_VERIFY_KEY ?? "umt-isolated-upgrade-key";
|
||||
const statePath = process.env.BROWSER_VERIFY_STATE ?? "/tmp/umt-browser-upgrade-state.json";
|
||||
const mode = process.argv[2] ?? "before";
|
||||
const userId = "umt-upgrade-isolated";
|
||||
async function call(path, body, auth = true, method = body === undefined ? "GET" : "POST") {
|
||||
const response = await fetch(base + path, {
|
||||
method,
|
||||
headers: {
|
||||
...(auth ? { authorization: `Bearer ${key}` } : {}),
|
||||
...(body === undefined ? {} : { "content-type": "application/json" }),
|
||||
},
|
||||
...(body === undefined ? {} : { body: JSON.stringify(body) }),
|
||||
signal: AbortSignal.timeout(60_000),
|
||||
});
|
||||
const data = await response.json();
|
||||
return { status: response.status, data };
|
||||
}
|
||||
assert.equal((await call("/health", undefined, false)).status, 200);
|
||||
assert.equal((await call("/umt/instance", undefined, false)).status, 401);
|
||||
assert.equal((await call("/tabs", { userId, sessionKey: "upgrade" }, false)).status, 401);
|
||||
const instance = await call("/umt/instance");
|
||||
assert.equal(instance.status, 200);
|
||||
const identity = await call("/umt/identity");
|
||||
assert.equal(identity.status, 200);
|
||||
const cookiesPath = `/umt/sessions/${userId}/cookies`;
|
||||
if (mode === "after") {
|
||||
const before = JSON.parse(await readFile(statePath, "utf8"));
|
||||
assert.notEqual(instance.data.instanceId, before.instanceId);
|
||||
assert.deepEqual(identity.data, before.identity);
|
||||
const persisted = await call(`${cookiesPath}?urls=https%3A%2F%2Fexample.com%2F`);
|
||||
assert.equal(persisted.status, 200);
|
||||
assert.equal(persisted.data.source, "persisted");
|
||||
assert.ok(persisted.data.cookies.some((c) => c.name === "umt_upgrade" && c.value === "candidate-only"));
|
||||
} else {
|
||||
const imported = await call(cookiesPath, {
|
||||
cookies: [
|
||||
{
|
||||
name: "umt_upgrade",
|
||||
value: "candidate-only",
|
||||
domain: "example.com",
|
||||
path: "/",
|
||||
secure: true,
|
||||
httpOnly: true,
|
||||
sameSite: "Lax",
|
||||
expires: Math.floor(Date.now() / 1000) + 3600,
|
||||
},
|
||||
],
|
||||
});
|
||||
assert.equal(imported.status, 200);
|
||||
assert.equal(imported.data.count, 1);
|
||||
await writeFile(statePath, JSON.stringify({ instanceId: instance.data.instanceId, identity: identity.data }), {
|
||||
mode: 0o600,
|
||||
});
|
||||
}
|
||||
const tab = await call("/tabs", { userId, sessionKey: "upgrade" });
|
||||
assert.equal(tab.status, 200, JSON.stringify(tab.data));
|
||||
assert.ok(tab.data.tabId);
|
||||
const tabPath = `/tabs/${tab.data.tabId}`;
|
||||
const evaluated = await call(`${tabPath}/evaluate`, {
|
||||
userId,
|
||||
expression: `(() => { document.body.innerHTML = '<button id="probe">升级验证</button>'; window.probeClicked = false; document.querySelector('#probe').addEventListener('click', () => { window.probeClicked = true; }); return { title: document.title, userAgent: navigator.userAgent }; })()`,
|
||||
});
|
||||
assert.equal(evaluated.status, 200, JSON.stringify(evaluated.data));
|
||||
assert.match(evaluated.data.result.userAgent, /Firefox\/152/);
|
||||
const snapshot = await call(`${tabPath}/snapshot?userId=${userId}`);
|
||||
assert.equal(snapshot.status, 200, JSON.stringify(snapshot.data));
|
||||
assert.match(JSON.stringify(snapshot.data), /升级验证/);
|
||||
const screenshot = await fetch(`${base}${tabPath}/screenshot?userId=${userId}`, {
|
||||
headers: { authorization: `Bearer ${key}` },
|
||||
signal: AbortSignal.timeout(60_000),
|
||||
});
|
||||
assert.equal(screenshot.status, 200);
|
||||
assert.match(screenshot.headers.get("content-type"), /image\/png/);
|
||||
assert.deepEqual(
|
||||
new Uint8Array(await screenshot.arrayBuffer()).slice(0, 8),
|
||||
new Uint8Array([137, 80, 78, 71, 13, 10, 26, 10]),
|
||||
);
|
||||
const clicked = await call(`${tabPath}/click`, { userId, selector: "#probe" });
|
||||
assert.equal(clicked.status, 200, JSON.stringify(clicked.data));
|
||||
const outcome = await call(`${tabPath}/evaluate`, { userId, expression: "window.probeClicked" });
|
||||
assert.equal(outcome.data.result, true);
|
||||
const mouseProbe = await call(`${tabPath}/evaluate`, {
|
||||
userId,
|
||||
expression:
|
||||
"(() => { window.probeTrusted = false; const b = document.querySelector('#probe'); b.addEventListener('click', e => { window.probeTrusted = e.isTrusted; }); const r = b.getBoundingClientRect(); return { x: r.x + r.width / 2, y: r.y + r.height / 2 }; })()",
|
||||
});
|
||||
assert.equal(mouseProbe.status, 200);
|
||||
const mouse = await call(`/umt/tabs/${tab.data.tabId}/mouse-click`, { userId, ...mouseProbe.data.result });
|
||||
assert.equal(mouse.status, 200, JSON.stringify(mouse.data));
|
||||
const trusted = await call(`${tabPath}/evaluate`, { userId, expression: "window.probeTrusted" });
|
||||
assert.equal(trusted.data.result, true);
|
||||
const wrongProfile = await call(`${tabPath}/evaluate`, {
|
||||
userId: "umt-upgrade-other-isolated",
|
||||
expression: "document.title",
|
||||
});
|
||||
assert.notEqual(wrongProfile.status, 200);
|
||||
const cookies = await call(`${cookiesPath}?urls=https%3A%2F%2Fexample.com%2F`);
|
||||
assert.ok(cookies.data.cookies.some((c) => c.name === "umt_upgrade" && c.value === "candidate-only"));
|
||||
await call(`${tabPath}?userId=${userId}`, undefined, true, "DELETE");
|
||||
console.log(
|
||||
JSON.stringify(
|
||||
{
|
||||
mode,
|
||||
ok: true,
|
||||
instanceId: instance.data.instanceId,
|
||||
identity: identity.data,
|
||||
checks: [
|
||||
"health",
|
||||
"authorization",
|
||||
"fixed-identity",
|
||||
"cookies",
|
||||
"new-context",
|
||||
"new-page",
|
||||
"evaluate",
|
||||
"snapshot",
|
||||
"screenshot",
|
||||
"click",
|
||||
"trusted-mouse-click",
|
||||
"profile-isolation",
|
||||
...(mode === "after" ? ["restart-persistence"] : []),
|
||||
],
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
);
|
||||
@@ -26,8 +26,9 @@ BACKUP_KEEP=14
|
||||
# PG_MAINTENANCE_WORK_MEM=256MB
|
||||
# PG_MAX_WAL_SIZE=2GB
|
||||
LOG_LEVEL=info
|
||||
# APP_VERSION=v0.1.0
|
||||
# APP_VERSION=v0.3.8
|
||||
# 国内网络构建换源(可选):APT_MIRROR=mirrors.ustc.edu.cn
|
||||
# 桌面底座 HTTPS 软件源代理(仅构建时生效,地址须能从构建网络访问):APT_HTTPS_PROXY=http://代理地址:端口
|
||||
|
||||
# worker 并发(可选):io 组分文件 / 网络 / 维护三条道,agent 组(Agent 会话)单独一池。
|
||||
# 三条道缺省用 设置 → 自动化 → 高级 里的值(可热改);设了下面的变量就以变量为准。
|
||||
@@ -41,7 +42,7 @@ LOG_LEVEL=info
|
||||
|
||||
# ── 浏览器服务(camofox-browser,原站视图 / Agent 浏览器能力;docs/runbook/browser.md)──
|
||||
# 缺省随 compose 起一个 camofox 容器(只在内网,不映射端口),app 自动登记为 camofox-env。
|
||||
# 缺省镜像是浏览器桌面(原站视图桌面模式,docker/browser-desktop);要先有 camofox-browser:1.17.0(runbook browser.md §1.1)。
|
||||
# 缺省镜像是浏览器桌面(原站视图桌面模式,docker/browser-desktop);要先有 camofox-browser:1.18.0(runbook browser.md §1.1)。
|
||||
# 访问凭据:camofox 与 app 共用;建议设置(openssl rand -hex 24)。桌面模式的控制 / 只读口令也由它派生,不设则桌面模式不可用
|
||||
CAMOFOX_ACCESS_KEY=
|
||||
# 接自有 camofox 实例:改成它的地址与凭据,并把 CAMOFOX_REPLICAS 设 0
|
||||
@@ -62,7 +63,7 @@ CAMOFOX_ACCESS_KEY=
|
||||
|
||||
# ── 原站视图桌面模式(docs/runbook/browser.md §8)──
|
||||
# 只要截图模式:改回纯截图镜像(同一个卷,登录状态通用)
|
||||
# CAMOFOX_IMAGE=camofox-browser:1.17.0
|
||||
# CAMOFOX_IMAGE=camofox-browser:1.18.0
|
||||
# 桌面分辨率与帧率(纯 CPU 编码,持续滚动约占 1 核)
|
||||
# BROWSER_DESKTOP_WIDTH=1280
|
||||
# BROWSER_DESKTOP_HEIGHT=900
|
||||
|
||||
Reference in new issue
Block a user