自托管的跨设备剪贴板 —— 文本、图片、文件在手机、电脑、路由器之间实时互传。
Rust 重写自 cloud-clipboard-go,数据完全握在自己手里。
| 特性 | 说明 |
|---|---|
| 🔒 隐私安全 | 部署在自己的机器或服务器上,数据不经过任何第三方 |
| 📦 部署灵活 | 一个静态二进制同时供应 Docker、裸机、OpenWrt 与 Android;另有 Cloudflare Serverless 一路 |
| 🌍 跨平台 | 服务端覆盖 Linux / macOS / Windows / ARM 路由;客户端有桌面端、Android 与快捷指令 |
| ⚡ 实时同步 | WebSocket 双向广播,历史走 HTTP 分页拉取(只推实时、不推全量) |
| 🔐 认证保护 | 全局密码、按房间独立密码、可续期的会话令牌、带密码的分享链接 |
| 🎨 多种界面模式 | 时间流 / 速览 / 便签 / 看板等;?mode= 让同一个浏览器标签页锁定一种模式 |
| 🧩 动作库 | 一条内容换个方式看:Markdown、JSON 美化、编解码、注音、日期计算、哈希…… 还能串成流水线 |
| ⏰ 定时自动化 | 按每天 / 每周 / 仅一次 / 5 字段 cron 把渲染好的文本投进房间,正文支持模板变量 |
| 🔗 分享链接 | 单条内容的短期链接,可限次数、可带密码,分享页自动注入 OG 卡片 |
| 🪶 轻量 | 内存与体积都很小,16 MB flash 的路由器也能跑 |
服务端默认监听 9501。装好之后浏览器打开
http://<服务器地址>:9501就是界面。
docker run -d \
--name clip9 \
--init \
-p 9501:9501 \
-v /path/to/data:/app/server-node/data \
ghcr.io/jonnyan404/clip9:latest
⚠️ --init是必须的:服务端没有装 SIGTERM 处理器,而 PID 1 对没有处理函数的信号是内核直接忽略的。 不加它,docker stop要等满 10 秒超时才被 SIGKILL;加上之后实测 0.22 秒。
⚠️ latest只在正式发布时才有;预发布只推vX.Y.Z,要用 beta 就写全版本号。
docker-compose.yml
仓库根目录自带一份,写法与 Go 版一致:
services:
clip9:
container_name: clip9
restart: always
init: true
ports:
- "9501:9501"
environment:
# 监听
LISTEN_IP: ${LISTEN_IP:-} # 监听地址,默认 0.0.0.0
LISTEN_IP6: ${LISTEN_IP6:-} # IPv6 地址(如 ::),默认不监听
LISTEN_PORT: ${LISTEN_PORT:-} # 监听端口,默认 9501
PREFIX: ${PREFIX:-} # URL 子路径(配合 nginx),如 /clip9
# 访问控制
AUTH_PASSWORD: ${AUTH_PASSWORD:-} # 全局访问密码,留空即无需密码
ROOM_AUTH_JSON: '${ROOM_AUTH_JSON:-{}}' # 房间密码 JSON,如 {"finance":"finance-pass"}
ROOM_LIST: ${ROOM_LIST:-} # 是否公开房间列表,默认 false
# 容量
MESSAGE_NUM: ${MESSAGE_NUM:-} # 历史保留条数,默认 50
TEXT_LIMIT: ${TEXT_LIMIT:-} # 文本长度上限(字节),默认 4096
FILE_EXPIRE: ${FILE_EXPIRE:-} # 文件过期秒数,默认 3600
FILE_LIMIT: ${FILE_LIMIT:-} # 文件大小上限(字节),默认 104857600
# 定时自动化
AUTOMATION_ENABLED: ${AUTOMATION_ENABLED:-} # 总开关,默认 true
DEFAULT_TZ: ${DEFAULT_TZ:-} # 默认时区,默认 Asia/Shanghai
# TLS —— 优先级:手动路径 > mkcert > 不启用
MANUAL_KEY_PATH: ${MANUAL_KEY_PATH:-} # 手动指定私钥路径(优先级最高)
MANUAL_CERT_PATH: ${MANUAL_CERT_PATH:-} # 手动指定证书路径
MKCERT_DOMAIN_OR_IP: ${MKCERT_DOMAIN_OR_IP:-} # 填域名/IP 即自动签自签证书,多个用空格分隔
volumes:
- /path/your/dir/data:/app/server-node/data # 改成你自己的目录
image: ghcr.io/jonnyan404/clip9:latest变量名与 Go 版完全一致,切过来不用改。上面这份就是全部(仓库根的
docker-compose.yml还带一个healthcheck)。⚠️ 直接跑二进制时也可以用环境变量,名字是CLIP9_+ 参数名大写(CLIP9_PORT/CLIP9_CONFIG…)—— 这是本实现额外加的,Go 版没有。
docker compose up -d1️⃣ 独立二进制(Linux / macOS / Windows)
从 Releases 下载对应平台的文件:
./clip9-cli -port 9501 -auth mypassword123常用参数:-host / -port / -auth / -config / -static,全部见 --help。
2️⃣ OpenWrt 路由器
cat /etc/apk/arch # 先看架构
apk add --allow-untrusted ./clip9-<版本>-<架构>.apk # OpenWrt 25.12+
opkg install ./clip9_<版本>_<架构>.ipk # OpenWrt 24.10 及更早装完在 LuCI 里配置。
3️⃣ Android 手机当服务器
从 Releases 装 .apk,打开后设端口/密码、点「启动服务」,
局域网里任何设备访问 http://手机IP:9501 即可。
4️⃣ Cloudflare Workers(Serverless)
基于 Workers + D1 + R2,支持 GitHub Actions 自动部署或本地脚本部署。 详见 Cloudflare 部署指南。
5️⃣ 从源码构建
前置:Node.js ≥ 22、Rust stable。
cd web && npm install && npm run build # 前端产物
node tools/sync-web-assets.mjs # 同步进 rust/crates/server/static/(会被编进二进制)
cd rust && cargo build --release -p clip9-servercargo 只能在 rust/ 里跑 —— 仓库根没有 Cargo.toml。
服务端读一个 JSON 配置文件(默认 config.json,不存在时自动生成一份默认的)。Docker 与 OpenWrt 下由入口脚本生成。
配置字段一览
{
"server": {
"host": ["0.0.0.0"], "port": 9501, "prefix": "",
"history": 50, "dbPath": "clip9.redb", "storageDir": "uploads",
"auth": false, "roomAuth": {}, "cert": "", "key": "",
"roomList": false, "roomCleanup": 3600
},
"text": { "limit": 4096 },
"file": { "expire": 3600, "chunk": 1048576, "limit": 268435456 },
"automation": { "enabled": true, "tickSeconds": 30, "graceSeconds": 600, "defaultTZ": "Asia/Shanghai" }
}ℹ️ 环境变量名与命令行参数名都与 Go 版逐一对齐,换过来通常只需要改 Docker 的
image那一行。
一条子命令把 Go 的 history.json 与 uploads/ 搬进数据库。原始数据一个字节都不动(回退的唯一依据),重复跑不会重复导入。
clip9-cli migrate -from /旧的数据目录 -dry-run # 先试运行:只读、只报告
clip9-cli migrate -from /旧的数据目录 # 确认无误再真跑细节
-
迁:
history.json的receive→ 消息表、file→ 文件登记表、uploads/<uuid>→ 原样拷。 不迁share-log.json(短期)与tasks.json(是配置,不是数据)。 -
幂等:重复跑跳过已存在的 id。
⚠️ 重插会让房间计数 +1(/rooms上直接看得出)。 -
源不动:工具只读 Go 那边的目录 —— 要删请你自己确认之后再删。
-
Docker:数据目录与 Go 版是同一个,卷不用改;但入口脚本不接受参数,要覆盖 entrypoint:
docker stop clip9 docker run --rm --entrypoint /app/server-node/clip9-cli \ -v /你的/data:/app/server-node/data ghcr.io/jonnyan404/clip9:latest \ migrate -from /app/server-node/data -dry-run docker start clip9
-
⚠️ config.json必须是严格 JSON(Go 那份带#注释)—— 解析失败直接退出。这是刻意的: Go 会打一行日志、然后用默认值继续跑,那等于一个拼错的配置让服务不带密码地起来。
- 规格(Redoc,可切中英):jonnyan404.github.io/clip9/spec.html
- 英文原文:
docs/openapi/clip9.openapi.yaml(OpenAPI 3.1,32 条路径) - 中文规格:
docs/openapi/clip9.openapi.zh.yaml—— 生成物, 由zh.yaml译文表 + 英文原文拼出(散文已全部译完);结构由生成保证,两份不会漂。
curl http://localhost:9501/content/latest # 最新一条
curl "http://localhost:9501/content?room=work" # 某个房间的历史展开
| 目录 | 是什么 |
|---|---|
rust/crates/server |
HTTP + WebSocket 服务端(前端产物编在里面) |
rust/crates/client |
连接与同步逻辑(桌面端与 SPA 共用一份契约) |
rust/crates/core |
纯逻辑:配置、动作库、统计、协议类型 |
rust/crates/store |
存储(redb) |
rust/crates/desktop |
Tauri 桌面端 |
web/ |
网页界面(React + Vite) |
android/ |
Android 外壳(Kotlin) |
cloudflare/ |
Workers + D1 + R2 那一路 |
openwrt/ |
路由器打包与 LuCI 插件 |
shortcuts/ |
Apple / Android 快捷指令 |
欢迎 Issue 与 PR。提交前请跑门禁(零警告是硬要求):
cd rust
cargo fmt --all
cargo clippy --workspace --all-targets -- -D warnings
cargo test --workspaceℹ️ 仓库里有一份内部的工程约定与设计稿(
dev-docs/),它刻意不入库 —— 里面混着工作过程记录。 所以代码注释里会看到见 dev-docs/…这类指针,对只拿到仓库的人来说那些指向的是不存在的东西, 这是有意的取舍。
基于 MIT License 开源。
前端与后端最初 fork 自 TransparentLC/cloud-clipboard 与 yurenchen000/cloud-clipboard, 以及它的 Go 前身 Jonnyan404/cloud-clipboard-go。
如果这个项目帮到了你,欢迎 ⭐ Star,或通过以下方式支持我们:
你的支持是我们继续维护和改进项目的动力!
| 方式 | 二维码 |
|---|---|
| 微信 | ![]() |



