部署
cr4sync server 有两种部署形态。
形态 A:作为 cloudreve 扩展(同源部署,推荐)
server 与 cloudreve 走同一域名,由 nginx 反代。前端 SPA embed 进二进制,挂在 /extensions/cr4sync。
路由分工
| 路径 | 内容 | 是否鉴权 |
|---|---|---|
/api/v4/cr4sync/* | cr4sync HTTP API | ✓(白名单 /health 除外) |
/extensions/cr4sync /extensions/cr4sync/* | 内嵌前端 SPA 静态资产 | ✗ |
- UI 恒挂
/extensions/cr4sync(前端 Vitebase与 Routerbasename已对齐该前缀)。 - API 恒
/api/v4/cr4sync(前端用相对路径,从 origin 根算)。
nginx 示例
下例把 cr4sync(127.0.0.1:5731)与 cloudreve 挂到同一域名,是实测可行的配置。
cloudreve 上游按你的实际情况填(下例 location / 走 unix socket、= /sw.js 走 TCP 127.0.0.1:5212,
两者都指向同一个 cloudreve;换成你自己的地址即可)。= /sw.js 与 location / 里的 sub_filter
是 Service Worker 三层防御(见下节),纯同源部署可裁掉。
之所以需要这么多配置, 完全是因为 cloudreve 的
service worker导致, 详见下文
# cr4sync API: 保全完整 /api/v4/cr4sync 前缀 (server 路由带全前缀)
# proxy_pass 必须无尾斜杠, 带斜杠会剥掉前缀导致后端 404
location ^~ /api/v4/cr4sync/ {
proxy_pass http://127.0.0.1:5731;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
# SSE: 关缓冲 + 长超时
proxy_buffering off;
proxy_read_timeout 3600s;
}
# cr4sync 前端 SPA (embed-web 内嵌), proxy_pass 同样无尾斜杠
location ^~ /extensions/cr4sync {
proxy_pass http://127.0.0.1:5731;
proxy_set_header Host $host;
}
# Layer 2: 改写 cloudreve 的 sw.js, 往 denylist 注入 /extensions/cr4sync
location = /sw.js {
# cloudreve 上游 原样保全 /sw.js 路径
# cloudreve 上游 (按实际改, 作者此处 unix socket). 注意 socket 末尾 :/ 是 URI 部分,
# 在 location / 里把 / 换成 / 无害; 但勿照搬到精确 location (如 = /sw.js) 否则路径被重写
proxy_pass http://unix:/opt/cloudreve/run/cloudreve.sock:/;
proxy_set_header Host $host;
# 关压缩, 否则 sub_filter 匹配不到
proxy_set_header Accept-Encoding "";
# sw.js 是 JS, 需显式声明类型 (默认只处理 text/html)
sub_filter_types application/javascript text/javascript;
sub_filter_once on;
# 以稳定的正则字面量 /^\/api\/(.+)/ 为锚点, 在其后插入我们的路径
sub_filter '/^\/api\/(.+)/' '/^\/api\/(.+)/,/^\/extensions\/cr4sync/';
}
# 可选: cloudreve 扫码登录插件 qr-login-relay (与 cr4sync 无关, 有就保留)
location = /qr-login-relay {
return 301 /qr-login-relay/;
}
location ^~ /qr-login-relay/ {
proxy_pass http://127.0.0.1:54899/;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 60s;
proxy_send_timeout 60s;
}
# cloudreve 本体 + Layer 1: 往 index.html 注入我们 SW 的注册脚本
# 资产也回落到这里; Accept-Encoding "" 让上游吐未压缩以便 sub_filter 匹配,
# 传输压缩交给 nginx 全局 gzip (需 gzip_types 含 js/css) 重新压
location / {
proxy_set_header HOST $http_host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_redirect off;
# 关压缩, 否则下面的 sub_filter 匹配不到 html
proxy_set_header Accept-Encoding "";
# 不要写 sub_filter_types text/html: 默认已含, 重复声明会报 duplicate MIME type warn
sub_filter_once on;
sub_filter '</head>'
'<script>navigator.serviceWorker&&navigator.serviceWorker.register("/extensions/cr4sync/sw.js",{scope:"/extensions/cr4sync"}).catch(function(){})</script></head>';
# cloudreve 上游 (按实际改, 作者此处 unix socket). 注意 socket 末尾 :/ 是 URI 部分,
# 在 location / 里把 / 换成 / 无害; 但勿照搬到精确 location (如 = /sw.js) 否则路径被重写
proxy_pass http://unix:/opt/cloudreve/run/cloudreve.sock:/;
client_max_body_size 20000m;
}
加入 cloudreve 侧边栏(贴心提示)
配好同源后,可在 cloudreve 管理面板给 cr4sync 加个入口:参数设置 → 外观 → 自定义侧边导航栏, 新增一行:
| Iconify 图标名 | 名称 | 链接 |
|---|---|---|
fluent:extension-24-regular | 数据迁移 | https://你的域名/extensions/cr4sync |
这样用户在 cloudreve 侧边栏点一下就能进 cr4sync 迁移控制台。
同源登录
同源后前端能读 cloudreve 的 localStorage['cloudreve_session'],启动时自动采纳其 access_token /
refresh_token 免手填(见 鉴权 的 Authorization 回退)。cloudreve 轮换
token 后,前端在鉴权失败时会重新采纳。
Service Worker 三层防御(同源必读)
cloudreve v4 前端注册了一个 scope=/ 的 Service Worker(Workbox NavigationRoute,对所有导航
返回 cloudreve 的 index.html,denylist 只放行 /api /f /s /pdfviewer.html)。/extensions/cr4sync
不在 denylist,于是软刷新(Ctrl+R)时该 SW 把我们的导航兜回 cloudreve 的 app shell,只有硬刷新
(Ctrl+F5,绕过 SW)才进得去。
这是 client-side 拦截:SW 命中时直接吐 Cache Storage 里的 index.html,请求根本不到网络,nginx
无法在路由层拦截。故采用三层防御,越靠前越“即时“,越靠后越“持久“:
Layer 1 — 往 cloudreve 的 index.html 注入我们 SW 的注册脚本(nginx sub_filter '</head>' ...)。
用户访问 cloudreve 首页时顺带注册我们的 SW(scope=/extensions/cr4sync),这样等他进 /extensions/cr4sync
时我们的 SW 已就位(scope 更窄者优先)。
- 局限:cloudreve 把自己的 index.html 也 precache 了,存量已装 cloudreve SW 的浏览器访问
/会吃缓存版、拿不到注入脚本。对这些浏览器,在 cloudreve 首页硬刷新一次(Ctrl+F5,绕过 SW 走网络) 即可拿到注入版完成注册;全新/清过缓存的浏览器则首访自动生效。
Layer 2 — 改写 cloudreve 的 /sw.js,把 /extensions/cr4sync 注入它的 denylist(nginx sub_filter)。
注入后 cloudreve 自己的 SW 主动放行我们的导航,不再劫持。
- 覆盖存量用户:SW 脚本在 update check 时从网络重新拉(不吃 precache),故存量浏览器在下次触发更新 检查(导航时,最长约 24h)会自动切到改写版,无需用户操作。
- 局限:绑死 cloudreve 压缩后的
sw.js结构,cloudreve 升级可能使sub_filter失配,需复检。
Layer 3 — cr4sync 前端自带的 sw.js(scope=/extensions/cr4sync,后端发 Service-Worker-Allowed
头放宽到不带尾斜杠)。一旦经 Layer 1/2 或一次硬刷新把我们的 SW 注册上,它就永久控制本前缀,
之后所有软刷新都稳定,不再依赖前两层。
校准提示:部署前先看 cloudreve 实际的
sw.js内容,确认 Layer 2 的匹配串:curl -s https://你的域名/sw.js | grep -o 'denylist[^]]*]'上例按
denylist:[..., /^\/api\/(.+)/, ...]编写(以稳定的正则字面量/^\/api\/(.+)/为锚点插入)。 若混淆后结构不同,按实际调整sub_filter的搜索串。cloudreve 每次大版本升级后建议复检 Layer 1/2。
排障:若浏览器被 cloudreve SW 缓存过旧内容,可在 DevTools → Application → Storage → “Clear site data” 清一次再访问。
形态 B:独立部署
server 独立跑在 :5731,前端与 API 同 host(http://host:5731/extensions/cr4sync 与
http://host:5731/api/v4/cr4sync),相对 API base 天然可用,无需 nginx。自带账号密码 + 2FA + 扫码
登录,server 代理 cloudreve 鉴权,登录后 token 写入浏览器 localStorage。详见 Auth 接口。
构建带前端的二进制(embed-web)
前端 dist 通过 embed-web feature 打进二进制。默认关闭:CI 的 cargo build --features server
无需构建前端。生产出带 UI 的二进制需两步(先前端后后端):
cd web && npm run build # 产出 web/dist
cd ../cr4sync
cargo build --release --features embed-web
./target/release/cr4sync serve # 浏览器开 /extensions/cr4sync
web/dist被 gitignore,故 embed 必须显式走embed-web;未构建前端就带该 feature 编译会因找不到../web/dist失败。仅需 API(不带 UI)时用--features server即可。
构建带文档的二进制(embed-doc)
mdbook 构建出的 doc/book/ 也可打进二进制,挂在 /admin/doc/book 供管理员浏览。默认关闭。
cd cr4sync/doc && mdbook build # 产出 doc/book
cd ..
cargo build --release --features embed-doc
# 浏览器开 /api/v4/cr4sync/admin/doc/book/ (前端 admin 侧栏「API 文档」入口)
可与 embed-web 叠加:--features embed-web,embed-doc。
访问控制
- 首页 / CLI / config 等文档:公开(无需登录)
installation.html/server/api-*.html/server/db-schema.html:仅 当前部署的server_url命中白名单后缀时开放,否则 403- 其余
server/*.html(部署 / 鉴权 / 日志等):公开
doc/book/被 gitignore,故 embed 必须显式走embed-doc;未mdbook build就带该 feature 编译会因找不到doc/book失败。
权限模型
server 模式下,普通用户默认无迁移权限,需通过 API 申请。审批模式由配置文件 [migrate].approve 控制:
manual(缺省):用户提交申请 → 管理员在 admin 面板审批通过 → 用户获得权限。适合多租户环境,由管理员控制哪些用户可以迁移。auto:用户提交申请即生效,无需审批。适合信任环境或个人使用。
管理员可通过 POST /admin/users/{user_id}/ban 封禁用户,封禁后该用户无法创建新的 job 和 storage,但已有 job 会跑完。解封通过 POST /admin/users/{user_id}/unban。
Jobs 并发池
server 通过 [migrate].max_concurrent_jobs(缺省 10,上限 512)控制全局并发 job 上限。超出上限的 job 在后台排队(status = "queued"),等待前面的 job 完成后自动获得 permit 进入运行态。
调大 max_concurrent_jobs 需注意:
- 每个 job 并发上传约 4MB × 并发数 的内存开销, 预取和cr后端local/remote存储策略开启多分片上传时另算; 单个job理论内存所需内存峰值为
chunck_size * 并发数 * 2(预取)如果cr后端存储策略开启了多分片上传,内存模型理论为chunck_size * 并发数 * 3(如果是3分片并发上传) - 各 driver API QPS 限制(每个 job 有自身 rate_limit)
- Cloudreve 端接收带宽