Files
xbin/README.zh.md

252 lines
13 KiB
Markdown
Raw Permalink 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.
# Xbin
**_[English](./README.md)_**
[![Codecov](https://codecov.io/github/xixu-me/xbin/graph/badge.svg?token=K95DX723UT)](https://codecov.io/github/xixu-me/xbin)
[![CI](https://github.com/xixu-me/xbin/actions/workflows/ci.yml/badge.svg)](https://github.com/xixu-me/xbin/actions/workflows/ci.yml)
[![CodeQL](https://github.com/xixu-me/xbin/actions/workflows/github-code-scanning/codeql/badge.svg)](https://github.com/xixu-me/xbin/actions/workflows/github-code-scanning/codeql)
[![Deploy](https://github.com/xixu-me/xbin/actions/workflows/deploy.yml/badge.svg)](https://github.com/xixu-me/xbin/actions/workflows/deploy.yml)
Xbin 是一个构建在 Cloudflare Workers 之上的、类似 PrivateBin 的端到端加密 Pastebin。它包含一个用于创建和读取加密内容的浏览器应用、一个现代 JSON API,以及一个兼容旧版 PrivateBin 的接口层,方便迁移和互操作。
所有内容都会先在浏览器中完成加密,再上传到服务端。Worker 仅将加密后的载荷存储到 R2,将生命周期元数据存储到 D1,并借助 Durable Objects 与 Queues 处理阅后即焚声明和后台清理任务。
## 特性亮点
- 浏览器端加密,分享密钥通过 URL fragment 传递
- 支持可选密码、过期时间、阅后即焚链接,以及每条内容独立的删除令牌
- 支持纯文本、代码高亮和 Markdown 渲染
- 支持加密附件上传、下载与预览
- 支持兼容粘贴内容的讨论线程
- 提供位于 `/api/v1/*` 的现代 REST API
- 支持兼容旧版 PrivateBin 的 API,以及文件系统导入工具
- 原生运行在 Cloudflare 平台之上,使用 Workers、D1、R2、Durable Objects、Queues 和定时清理
## 安全模型
1. 浏览器生成随机密钥,并在上传前完成内容加密。
2. Worker 仅在 R2 中保存加密信封,在 D1 中保存元数据。
3. 分享链接通过查询参数携带 paste id,通过 URL fragment 携带解密密钥,例如 `https://paste.example.com/?abcdef1234567890#secretKey`。fragment 不会发送给服务器。
4. 如果设置了可选密码,客户端会在解密前使用 PBKDF2 将密码与 fragment 密钥组合。
5. 任何拿到完整分享 URL 的人都可以解密内容。删除令牌与分享链接分离,且仅在创建或导入 paste 时返回。
## 架构
| 组件 | 职责 |
| ----------------- | ---------------------------------------------------------- |
| Cloudflare Worker | HTTP API、静态资源分发、SEO 元数据重写、配置接口和导入鉴权 |
| Durable Object | 串行化处理阅后即焚的声明与消费操作 |
| D1 | Paste 和评论的元数据、生命周期状态、删除令牌哈希、焚毁声明 |
| R2 | 加密后的 paste 与评论载荷 blob |
| Queue | 异步清理已过期、已删除和已焚毁的内容 |
| Cron trigger | 每分钟释放过期的焚毁声明并扫描已过期的 paste |
| `assets/` SPA | 在浏览器中完成加密、解密、渲染和分享 |
## 快速开始
### 前置条件
- [Bun](https://bun.sh/)
- 一个可使用 Workers、D1、R2、Queues 和 Durable Objects 的 Cloudflare 账号
- 如果你计划在本机部署,需要本地已完成 Wrangler 登录认证
### 安装依赖
```sh
bun install
```
### 创建 Cloudflare 资源
首次部署前,请先创建你自己的资源,然后将 [`wrangler.jsonc`](./wrangler.jsonc) 中的名称和 ID 替换为你账号下的值。
```sh
bunx wrangler d1 create xbin
bunx wrangler r2 bucket create xbin-pastes
bunx wrangler queues create xbin-gc
```
说明:
- 当前存储库中的 `wrangler.jsonc` 已包含具体的 D1 和 R2 标识符。如果你要 fork 或部署自己的实例,请务必替换成你自己账号中的值。
- 你只需要手动创建 D1、R2 和 Queue。Durable Object 绑定以及对应的 SQLite 类已经在 `wrangler.jsonc` 中声明,会在部署和迁移时自动创建。
- 当前存储库设置了 `workers_dev = false`。部署你自己的 fork 前,请先在 `wrangler.jsonc` 中配置自己的路由或自定义域名,或者将其改为 `workers_dev = true`,这样应用才会有可访问的主机名。
- 变更绑定或环境变量后,请重新生成 Worker 类型:
```sh
bun run cf-typegen
```
### 配置本地密钥
将 [`.dev.vars.example`](./.dev.vars.example) 复制为 `.dev.vars`,并填写你要使用的可选密钥:
```sh
Copy-Item .dev.vars.example .dev.vars
```
可用密钥:
- `TURNSTILE_SITE_KEY`
- `TURNSTILE_SECRET_KEY`
- `IMPORT_TOKEN`
大多数非敏感的运行时默认值都位于 [`wrangler.jsonc`](./wrangler.jsonc) 的 `vars` 段中。
### 本地运行
```sh
bun run dev
```
Wrangler 会通过同一个 Worker 入口同时提供 SPA 和 API。
### 通过 GitHub Actions 部署
默认的部署路径是 GitHub Actions。如果你要部署自己的实例,建议从 [xixu-me/xbin](https://github.com/xixu-me/xbin/fork) fork 开始。
发布流程如下:
1. 在你的 fork 中,把 `CLOUDFLARE_ACCOUNT_ID` 和 `CLOUDFLARE_API_TOKEN` 配置为 GitHub Actions secrets。
2. 向你 fork 的 `main` 分支推送代码。
3. [`CI`](./.github/workflows/ci.yml) 会运行格式检查、类型检查、Wrangler 类型校验、带覆盖率的测试,以及 Wrangler 部署 dry run。
4. 如果该次 `main` 推送通过了 CI,[`Deploy`](./.github/workflows/deploy.yml) 会发布通过校验的同一份提交。
Pull Request 同样会运行 CI,但不会自动部署。你也可以在自己的 fork 中通过 `workflow_dispatch` 手动触发部署流程。
部署工作流最终执行的是:
```sh
bunx wrangler deploy --keep-vars --message "GitHub Actions deploy for ${GITHUB_SHA}"
```
`--keep-vars` 表示除非你有意在 Cloudflare 或部署配置中修改,否则远端 Worker 变量会被保留。
### 本地手动部署
如果你需要绕过 GitHub Actions,在本地直接部署:
```sh
bun run deploy
```
## 常用命令
| 命令 | 用途 |
| ----------------------- | ---------------------------------- |
| `bun run dev` | 使用 Wrangler 在本地运行 Worker |
| `bun run start` | 本地 Wrangler 开发的别名 |
| `bun run check` | 对 TypeScript 代码库执行类型检查 |
| `bun run test` | 使用 Vitest 运行 Worker 集成测试 |
| `bun run test:coverage` | 运行测试并输出 Istanbul 覆盖率报告 |
| `bun run format` | 使用 Prettier 格式化整个存储库 |
| `bun run format:check` | 检查格式但不修改文件 |
| `bun run cf-typegen` | 在配置变化后刷新 Worker 绑定类型 |
| `bun run deploy` | 发布 Worker |
## 配置项
| 变量 | 默认值 | 用途 |
| ----------------------------- | ----------------------------------------------------------- | --------------------------------------------------- |
| `XBIN_APP_NAME` | `Xbin` | UI 和元数据中显示的品牌名 |
| `XBIN_APP_VERSION` | `1.0.0` | 在应用配置接口和页脚中暴露的版本号 |
| `XBIN_PROJECT_PAGE_URL` | `https://github.com/xixu-me/xbin` | UI 中显示的项目链接 |
| `XBIN_BASE_PATH` | `/` | 用于构建分享链接、规范 URL 和站点地图链接的挂载路径 |
| `XBIN_MAX_PASTE_BYTES` | `10000000` | 加密 paste 载荷的最大字节数 |
| `XBIN_DEFAULT_EXPIRATION` | `1hour` | UI 和 API 使用的默认过期键 |
| `XBIN_SUPPORTED_EXPIRATIONS` | `5min,10min,30min,1hour,3hour,6hour,12hour,1day,3day,1week` | 应用暴露的过期键列表,逗号分隔 |
| `XBIN_ENABLE_LEGACY_API` | `true` | 是否启用兼容 PrivateBin 的旧版 JSON API |
| `XBIN_REQUIRE_TURNSTILE` | `false` | 是否要求在创建 paste 和评论时传入 `turnstileToken` |
| `XBIN_BURN_CLAIM_TTL_SECONDS` | `120` | 阅后即焚声明在释放前可保留的秒数 |
| `TURNSTILE_SITE_KEY` | 未设置 | 启用 Turnstile 时暴露给客户端的站点密钥 |
| `TURNSTILE_SECRET_KEY` | 未设置 | Worker 用于校验 Turnstile 令牌的密钥 |
| `IMPORT_TOKEN` | 未设置 | 启用并保护 PrivateBin 导入接口 |
配置解析器还支持 `1month`、`1year` 和 `never` 这些过期键,只要你愿意将它们暴露给客户端即可。
## API 概览
写入 API 接收的是经过加密的、PrivateBin 风格的信封对象,而不是明文内容。一个最小创建请求示例如下:
```json
{
"v": 2,
"adata": [["iv", "salt", 100000, 256, 128, "aes", "gcm", "none"], "plaintext", 0, 0],
"ct": "ciphertext",
"meta": { "expire": "1day" }
}
```
核心接口:
| 方法 | 路径 | 用途 |
| -------- | --------------------------------- | --------------------------------------------------- |
| `GET` | `/api/v1/config` | 返回运行时 UI 和功能配置 |
| `POST` | `/api/v1/pastes` | 创建 paste,并返回 `{ id, shareUrl, deleteToken }` |
| `GET` | `/api/v1/pastes/:id` | 返回加密 paste 信封和评论 |
| `DELETE` | `/api/v1/pastes/:id` | 在提供 `{ "deleteToken": "..." }` 时删除 paste |
| `POST` | `/api/v1/pastes/:id/comments` | 为启用讨论的 paste 创建评论 |
| `POST` | `/api/v1/pastes/:id/consume` | 使用 `{ "claimToken": "..." }` 完成一次阅后即焚读取 |
| `POST` | `/api/v1/admin/import/privatebin` | 在授权后导入 PrivateBin 文件系统数据 |
说明:
- 对于阅后即焚的 paste,`GET /api/v1/pastes/:id` 会返回一个 `claimToken`。客户端在成功解密后必须继续调用 `/consume`。
- 启用 Turnstile 后,在创建 paste 和创建评论的请求体中都需要包含 `turnstileToken`。
- 阅后即焚 paste 不支持评论。
## PrivateBin 兼容性与导入
Xbin 提供两种兼容路径:
- 通过 `X-Requested-With: JSONHttpRequest` 识别旧版 JSON API 调用。
- 对于 `/api/v1/pastes?<pasteId>` 这类旧版浏览器分享 URL,返回 SPA 外壳页面,让客户端在本地恢复分享内容。
如果要从 PrivateBin 的文件系统导出中导入数据,先为 Worker 设置 `IMPORT_TOKEN`,然后执行:
```sh
bun run import:privatebin:fs -- --source /path/to/privatebin/data --base-url https://paste.example.com --token your-import-token --report ./import-report.json
```
导入器会:
- 遍历 `*.php` paste 文件及其相邻的 `.discussion/` 目录
- 在可用时保留创建时间和过期时间元数据
- 跳过已经过期的 paste
- 为每条导入后的 paste 返回一个新的 `deleteToken`,因为 Xbin 会在导入时重新生成删除凭证
## 存储库结构
| 路径 | 用途 |
| -------------------------------- | ------------------------------------------------------------- |
| [`src/index.ts`](./src/index.ts) | Worker 入口,负责 HTTP 路由、静态资源分发、定时清理和队列处理 |
| [`src/lib/`](./src/lib) | 配置解析、校验、数据访问、Schema 和共享类型 |
| [`assets/`](./assets) | 浏览器应用、HTML 外壳、CSS 和 vendored 客户端库 |
| [`scripts/`](./scripts) | 一次性工具,例如 PrivateBin 文件系统导入器 |
| [`migrations/`](./migrations) | D1 Schema 迁移 |
| [`test/`](./test) | Worker 集成测试和仓储层测试 |
## 测试与质量
本存储库使用带 Cloudflare Workers 运行池的 Vitest、TypeScript 类型检查、Wrangler 类型校验和 Prettier 格式化。CI 还会在允许生产部署前执行一次 Wrangler 部署 dry run。Codecov 配置了 95% 的项目覆盖率和 90% 的补丁覆盖率目标,因此除文档外,代码改动通常都应附带测试。
与主要 CI 质量门槛一致的一组本地校验命令如下:
```sh
bun run format:check
bun run check
bunx wrangler types --check
bun run test:coverage
```
## 相关文档
- [CONTRIBUTING.md](./CONTRIBUTING.md)
- [SECURITY.md](./SECURITY.md)
- [SUPPORT.md](./SUPPORT.md)
- [CODE_OF_CONDUCT.md](./CODE_OF_CONDUCT.md)
## 许可证
本项目基于 GNU Affero General Public License v3.0 发布。完整条款请参见 [`LICENSE`](./LICENSE)。