初始化蓝鸟文件快递柜源码与部署文档
This commit is contained in:
commit
5643d22242
70 files changed
+18669
No files matched your search
+56
@@ -0,0 +1,56 @@
|
||||
# HTTP API(v0.1)
|
||||
|
||||
所有路径以 `/api/v1` 开始。JSON 错误为 `{"error":"说明"}`。修改请求带 `Origin`,应匹配 `public_url` 或 `access_urls` 中的完整站点地址。前端请求另携带 `X-Bluebird-Origin: location.origin`,该值也必须匹配入口且与Origin一致,便于反向代理转发后仍正确识别协议。浏览器使用同源 HttpOnly cookie,不开放 CORS。无 cookie 的脚本可使用匿名管理 Bearer 凭据;不要在 URL 中传凭据。
|
||||
|
||||
| 方法/路径 | 用途 |
|
||||
| --- | --- |
|
||||
| GET `/health` | 存活/初始化状态 |
|
||||
| GET `/policy` | 公开有效策略、当前入口及可用入口 URL、初始化 7 天匿名会话 |
|
||||
| POST `/uploads` | 清单声明、完整容量预留、生成上传会话与管理凭据 |
|
||||
| PUT `/uploads/{id}/files/{file}` | 原始文件字节流,需同会话 cookie 或管理 Bearer |
|
||||
| GET `/uploads/{id}` | 查询上传状态,需所有权凭据 |
|
||||
| POST `/uploads/{id}/publish` | 所有文件就绪后发布,仅首次返回取件码 |
|
||||
| GET `/mine` | 当前匿名会话最近100个包裹 |
|
||||
| POST `/parcels/{id}/revoke` | 提前撤销 |
|
||||
| POST `/parcels/{id}/code` | 重生成取件码,旧码/授权失效 |
|
||||
| POST `/pickup/resolve` | `{code,password}`,校验后设置10分钟取件 cookie |
|
||||
| GET `/pickup` | 当前授权包裹的文件清单 |
|
||||
| GET `/pickup/files/{file}/download` | 附件下载;忽略 Range,HEAD 不扣次数 |
|
||||
| POST `/admin/login` | `{username,password}`,设置8小时管理员 cookie |
|
||||
| POST `/admin/logout` | 删除管理员会话 |
|
||||
| GET `/admin/status` | 登录状态 |
|
||||
| GET `/admin/stats` | 实体、逻辑、预留、预算及并发统计 |
|
||||
| GET `/admin/parcels?page=1&page_size=10` | 分页包裹及文件;支持 `q`(文件名/包裹ID片段)、`state` |
|
||||
| POST `/admin/parcels/{id}/delete` | 撤销包裹,后台回收 |
|
||||
| POST `/admin/files/{id}/delete` | 删除单份文件引用,后台回收 |
|
||||
| POST `/admin/parcels/{id}/reissue` | 显式重发恢复待审核包裹,使用当前默认期限/次数 |
|
||||
| GET/PUT `/admin/policy` | 读取/修改完整业务策略 |
|
||||
| GET/PUT `/admin/runtime` | 文件/数据预算、磁盘余量及审计天数/条数/内容预算 |
|
||||
| GET `/admin/audit?page=1&page_size=20` | 分页审计;支持 `action`、`source`、`target`、`from`、`to` |
|
||||
| POST `/admin/cleanup` | 触发清理 |
|
||||
|
||||
上传清单示例:
|
||||
|
||||
```json
|
||||
{"files":[{"name":"example.zip","size":12345}],"days":7,"downloads":5,"password":"optional-password","renew":true}
|
||||
```
|
||||
|
||||
响应包含 `id`、`manage_token` 和带服务生成文件 ID 的 `files`。凭据只保存到受控位置,不转发给收件人。实际文件长度必须与清单相符。会话最长1小时,进程重启会撤销尚未发布的会话。已就绪的文件重发 PUT 幂等返回成功;失败文件可重新完整上传。每次失败重试的传入内容计入日上传流量。
|
||||
|
||||
下载成功是服务器输出完整响应体,无法证明用户已保存。请求开始即扣次数,中断保守计一次;开始前确定没有发送内容则返还。仅成功传输且仍有剩余次数才续期;默认 `到期时间 = min(max(旧到期,完成时刻)+3天,发布时间+30天)`。另一模式为 `min(max(旧到期,完成时刻+3天),总期限上限)`。取件授权到期需重新输入取件码。
|
||||
|
||||
`GET /policy` 另返回 `visitor_expires`(当前浏览器身份到期Unix时间)。管理凭据用于所有权,过期/删除记录不开放重新取件,已清理记录返回404。
|
||||
|
||||
## 管理列表分页与搜索
|
||||
|
||||
包裹和审计列表统一返回 `{items,total,page,page_size,pages}`,其中 `total` 是符合当前筛选条件的总条数,`pages` 是总页数。空结果仍返回 `items:[]`、`page:1`、`pages:1`。页码从1开始;默认包裹每页10个、审计每页20条,`page_size` 支持1至100。超出最后一页时自动回到最后一页;非法页码、条数或筛选值返回400。分页仅加载当前页,数据库保留条数与页面每页条数相互独立。
|
||||
|
||||
包裹 `q` 匹配文件名或包裹ID片段,`state` 为精确状态。审计 `action` 匹配完整操作代码,`source` 和 `target` 匹配字面片段(百分号和下划线无通配作用);`from`、`to` 为Unix秒数,两端均包含,可以仅设置一端,开始时间不能晚于结束时间。多个条件同时满足才匹配。审计按事件ID倒序,包裹按寄出时间、ID倒序,保证同一秒内多条记录的排序明确。
|
||||
|
||||
示例:`/admin/audit?action=admin.delete&source=192.168.1.&from=1791158400&to=1791244800&page=1&page_size=20`。界面时间输入按管理员设备本地时区转换成Unix秒数;搜索和每页条数改变后回到第一页。
|
||||
|
||||
## 多入口与分享链接
|
||||
|
||||
`GET /policy` 的 `public_url` 返回当前访问入口,`access_urls` 返回所有配置入口。发布、重新生成取件码、管理员重新发布返回的 `url` 使用当前入口,`urls` 为可选分享入口列表 `[{label,url}]`;同一取件码在各入口下均有效,下载次数和期限共享。二维码随分享入口切换。
|
||||
|
||||
HTTP与HTTPS并存时,HTTP cookie使用 `_http` 后缀,HTTPS沿用原名称并设置Secure,防止同一域名的两种协议互相覆盖。不同入口的浏览器身份、登录状态及管理凭据存储可能独立;这不会改变文件及取件码。未知Origin、相互不一致的来源头和跨站请求仍被拒绝,不开放任意CORS。
|
||||
@@ -0,0 +1,140 @@
|
||||
# Linux amd64 部署
|
||||
|
||||
前端与SQLite已包含在二进制中,运行无需Go、Node.js、PostgreSQL或Valkey。脚本用于有systemd的Linux amd64/x86_64主机,需root或sudo,以及shadow、util-linux和coreutils(useradd、groupadd、getent、runuser、flock、install等)。
|
||||
|
||||
## 已有安装:添加公网HTTP和独立内网入口
|
||||
|
||||
本节以蓝鸟服务器192.168.1.10、独立反向代理和主入口 `https://files.example.com` 为例。将新版发布包与SHA256SUMS上传至工作目录,解压到一个新的空目录:
|
||||
|
||||
```sh
|
||||
sha256sum -c SHA256SUMS
|
||||
mkdir bluebird-release-new
|
||||
tar -xzf bluebird-linux-amd64.tar.gz -C bluebird-release-new
|
||||
cd bluebird-release-new
|
||||
sudo ./upgrade.sh --lan-url http://192.168.1.10:8080 \
|
||||
--access-url http://files.example.com
|
||||
```
|
||||
|
||||
脚本先校验候选配置,随后自动停服、备份、更新程序和配置、执行完整性检查、启动服务。保留原管理员密码、数据库、文件、密钥、取件码、后台策略和其他配置;不会重新初始化。旧程序、原配置和一致性数据归档保留在 `/opt/bluebird/backups/upgrade-时间-进程号/`。备份含敏感信息,应复制到独立备份位置并按自己的容量策略清理。更新失败会恢复旧程序及配置;当前数据库结构版本仍为1。
|
||||
|
||||
升级后以下入口同时可用:
|
||||
|
||||
- 内网:`http://192.168.1.10:8080`,无需解析公网域名。
|
||||
- 公网HTTP:`http://files.example.com`,由反向代理提供80端口。
|
||||
- 公网HTTPS:`https://files.example.com`,由反向代理提供443端口和有效证书。
|
||||
- 各入口的管理后台:在对应地址后添加 `/#/admin`。
|
||||
|
||||
内网入口直接连接蓝鸟。公网入口仍需域名正确解析到代理、代理转发到 `192.168.1.10:8080`;程序不能绕过公网DNS故障。主机网络/防火墙需允许内网客户端和代理连接8080。HTTP传输不加密;它可以正常上传、取件和管理登录。
|
||||
|
||||
## 首次安装
|
||||
|
||||
已有安装使用上面的upgrade.sh;install.sh不会覆盖已有目录或同名服务。首次部署在发布包解压目录运行:
|
||||
|
||||
```sh
|
||||
sudo ./install.sh --url https://files.example.com \
|
||||
--access-url http://files.example.com \
|
||||
--lan-url http://192.168.1.10:8080 --encrypt --no-start
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl enable --now bluebird.service
|
||||
```
|
||||
|
||||
将域名、IP换成实际值。脚本提示输入两次管理员密码,账号admin,没有默认密码。`--encrypt`启用新上传文件的服务端落盘加密;`--no-start`安装并初始化,但暂不启动。也支持单独的公网HTTP域名:`--url http://files.example.com`,无需在域名后写8080,后端默认监听127.0.0.1:8080。纯内网部署可用 `--url http://192.168.1.10:8080 --lan-http`。
|
||||
|
||||
默认目录:
|
||||
|
||||
```text
|
||||
/opt/bluebird/
|
||||
├── bluebird # 二进制,前端内嵌
|
||||
├── config.toml # root:bluebird / 0640
|
||||
├── bluebird.service # 服务定义副本
|
||||
├── THIRD_PARTY_NOTICES.txt
|
||||
├── backups/ # 升级时生成的备份
|
||||
└── var/ # bluebird:bluebird / 0700,数据库、对象、密钥
|
||||
```
|
||||
|
||||
程序与配置由root管理,服务以bluebird账号运行,仅写var;systemd定义安装在 `/etc/systemd/system/bluebird.service`。不修改代理、DNS、防火墙。自动化初始化可用 `BLUEBIRD_ADMIN_PASSWORD` 环境变量,密码不写入配置或服务定义。
|
||||
|
||||
## 多入口配置
|
||||
|
||||
上述升级命令得到的相关配置为:
|
||||
|
||||
```toml
|
||||
listen = "192.168.1.10:8080"
|
||||
public_url = "https://files.example.com"
|
||||
access_urls = ["http://192.168.1.10:8080", "http://files.example.com"]
|
||||
secure_cookies = true
|
||||
allow_lan_http = true
|
||||
# 将下例替换成反向代理机器的实际IP,保留原有需要信任的代理:
|
||||
# trusted_proxies = ["192.168.1.20/32"]
|
||||
```
|
||||
|
||||
`public_url`是默认入口,`access_urls`增加精确允许的入口,最多16个,地址只包含协议、域名/IP和可选端口,不带路径。`secure_cookies`与默认入口协议一致;其他入口按实际协议自动设置cookie。HTTP与HTTPS同时使用同一域名时,HTTP cookie采用独立名称,避免覆盖Secure cookie。不同入口的浏览器会话和登录可能独立,文件和取件码在所有入口共用,下载额度与期限不会翻倍。
|
||||
|
||||
生成分享时默认采用当前访问入口,可在弹窗中选择其他入口;链接及二维码一起更新,取件码保持相同。内网寄件无需访问公网域名,即使公网DNS不可用也可完成全过程。
|
||||
|
||||
异机代理的真实IP/CIDR需要加入 `trusted_proxies`,使公网访客IP限额与审计准确。脚本不会猜测代理IP,升级保留旧值;不要设置为整个互联网或任意来源。配置修改后重启服务。程序拒绝未知Origin及相互矛盾的来源头,不使用无限制CORS。
|
||||
|
||||
## 代理同时提供HTTP和HTTPS
|
||||
|
||||
在反向代理机器上使用下例(域名与蓝鸟配置一致):
|
||||
|
||||
```caddyfile
|
||||
{
|
||||
auto_https disable_redirects
|
||||
}
|
||||
http://files.example.com {
|
||||
reverse_proxy 192.168.1.10:8080 {
|
||||
flush_interval -1
|
||||
}
|
||||
}
|
||||
https://files.example.com {
|
||||
reverse_proxy 192.168.1.10:8080 {
|
||||
flush_interval -1
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
两个明确的站点块分别提供HTTP与HTTPS;不要仅使用自动将HTTP跳转到HTTPS的配置。Nginx等代理同样需将80和443的请求转发至这个后端,并转发Host、X-Forwarded-Host、X-Forwarded-Proto、X-Forwarded-For及前端的X-Bluebird-Origin。应避免上传磁盘缓冲、下载内容缓存,并配置匹配文件限制的请求大小及超时。不要将var配置成代理静态目录。
|
||||
|
||||
证书与公网DNS在代理侧管理;代理必须能连接192.168.1.10的8080端口。若使用其他软件管理代理,在该软件中为HTTP和HTTPS各开启对应入口。
|
||||
|
||||
## 检查与日常操作
|
||||
|
||||
```sh
|
||||
sudo systemctl status bluebird --no-pager
|
||||
sudo ss -lntp | grep ':8080'
|
||||
sudo journalctl -u bluebird -n 50 --no-pager
|
||||
```
|
||||
|
||||
应看到监听192.168.1.10:8080。没有curl也可直接在浏览器打开 `http://192.168.1.10:8080/api/v1/health`,预期 `{"ready":true}`,然后访问首页测试寄件和取件。检查公网HTTP和HTTPS时分别使用两个协议,不能依赖代理自动跳转;管理页均在 `/#/admin`。
|
||||
|
||||
默认文件预算9GiB、数据预算10GiB、卷最少保留1GiB;单文件100MiB、每批10个、总500MiB。管理员在“服务策略”“容量与保留”修改额度、期限、下载次数和审计规则。初始化后的后台设置持久化于SQLite,编辑TOML的初始值不会覆盖。
|
||||
|
||||
`--prefix`可指定其他已有安装目录;默认 `/opt/bluebird`。`--listen`覆盖监听地址,`--lan-url`未指定listen时自动绑定其IP和端口;独立账号监听1024至65535端口,公网80/443由代理负责。`upgrade.sh --no-start`用于手动停服后的离线升级,完成后暂不启动;必须先确认旧服务已停止,数据锁会拒绝并行维护。首次安装路径不能包含空格或位于/home、/root,systemd保护这些目录。
|
||||
|
||||
## 备份和密码重置
|
||||
|
||||
全部维护CLI需先停服,与服务共用独占数据锁:
|
||||
|
||||
```sh
|
||||
sudo install -d -o bluebird -g bluebird -m 0700 /opt/bluebird/backups
|
||||
sudo systemctl stop bluebird
|
||||
sudo runuser -u bluebird -- /opt/bluebird/bluebird backup \
|
||||
--config /opt/bluebird/config.toml \
|
||||
--output /opt/bluebird/backups/bluebird-backup.tar.gz
|
||||
sudo systemctl start bluebird
|
||||
```
|
||||
|
||||
备份包含数据库、文件、配置和密钥,不能只复制运行中的SQLite主文件或只备份对象。升级保留旧取件码;执行restore恢复历史快照时旧分享及会话失效,管理员需逐项审核重新发布,规则见 [operations.md](operations.md)。
|
||||
|
||||
```sh
|
||||
sudo systemctl stop bluebird
|
||||
sudo runuser -u bluebird -- /opt/bluebird/bluebird reset-password --config /opt/bluebird/config.toml
|
||||
sudo systemctl start bluebird
|
||||
```
|
||||
|
||||
只校验配置、不打开数据库:
|
||||
|
||||
```sh
|
||||
sudo /opt/bluebird/bluebird validate-config --config /opt/bluebird/config.toml
|
||||
```
|
||||
@@ -0,0 +1,35 @@
|
||||
# 运维与容量
|
||||
|
||||
首次安装与systemd部署步骤见 [deployment.md](deployment.md)。
|
||||
|
||||
## 两层限制
|
||||
|
||||
`file_budget` 限制实体对象与尚未写入的预留之和,重复分享共用一个实体;声明清单仍按每个文件完整预留。`data_budget` 另考虑 SQLite/WAL、隔离对象及余量;`min_free_bytes` 防止侵占主机最后的可用空间。配额按文件系统分配块保守估计,不等同于原始文件大小。到期但尚未删除、删除失败或仍有下载引用的对象继续占用额度。
|
||||
|
||||
Btrfs等文件系统可能以inode总数0表示不提供统计,此时不根据free_inodes=0拒绝上传;记录上限及文件系统硬配额仍生效。
|
||||
|
||||
应用控制无法为 SQLite、文件系统目录元数据、系统日志及管理员外部写入提供字节级绝对上限。严格模式需为 `/opt/bluebird/var` 设置独立文件系统项目配额或定容卷;应用预算必须低于硬上限,建议另留至少1GiB,并限制 inode。先用主机实际文件系统支持的管理方式配置配额,再验证 `quota`/`xfs_quota` 等工具显示的限制;软件不自动格式化或修改磁盘。
|
||||
|
||||
如现有主机不支持项目配额,可将数据目录挂载到专用定容卷。数据库、objects和tmp放在同一可靠本机文件系统,tmp与objects必须支持原子rename。不要把SQLite WAL放到NFS/SMB。
|
||||
|
||||
journald 不在 `/opt/bluebird` 下,主机需设置 `SystemMaxUse` 等日志上限。外部反向代理、备份、TLS证书与发布包有各自容量;严格整机预算须一起计入。
|
||||
|
||||
## 清理与故障
|
||||
|
||||
文件传输默认最长1小时、读写空闲30秒,配置可调整。
|
||||
|
||||
默认每分钟回收失效引用,删除最后引用后才删除实体。正在传输的对象延后删除。进程重启撤销尚未发布的上传,避免部分文件可见;未能确认实际输入量的中断尝试按完整预留流量保守计费。日额度按UTC日期计算。孤儿实体移入不可访问的 quarantine,24小时后清理。文件或密钥异常时不通过生成替代对象恢复权限。
|
||||
|
||||
已失效包裹在其文件全部清理、且距记录寄出时间超过30天后删除元数据;审计默认30天/10万条/32MiB估算内容,可在后台“容量与保留”修改;最大文件/访客/授权数受 `max_records` 限制。达到容量或记录上限后拒绝新增,不偷偷删除尚有效的分享。
|
||||
|
||||
停止服务后可执行 `check`(SQLite完整性、全部对象解密/哈希及启动对账)和 `cleanup`。启动恢复会取消未发布会话,故维护命令需安排窗口;不是无副作用的在线巡检。
|
||||
|
||||
## 安全部署
|
||||
|
||||
二进制及配置归 root,服务以专用账号仅写 var。按示例 systemd 收紧权限。支持配置中的HTTP与HTTPS入口;HTTP传输不加密,HTTPS入口使用Secure cookie。只信任实际代理IP/CIDR。上传对象不可通过代理 alias/root 直接获取。
|
||||
|
||||
加密 private identity 和 HMAC 密钥都有恢复意义。密钥轮换目前仅支持开关混存和保留同一既有 identity,不提供后台轮换旧私钥;不要手工覆盖 age.key,否则既有文件无法读取。
|
||||
|
||||
静止备份会短暂中断服务。不要在运行中只复制主数据库文件,不要把不同时间的数据库和对象目录拼接。升级或恢复前保存配置及密钥副本。恢复得到的是历史快照,所以旧分享/会话全部失效,需要管理员逐项审核重新发布。
|
||||
|
||||
后台容量与审计设置保存在SQLite runtime_settings中,TOML的相应值仅首次初始化使用。降低文件预算会停止超出预算的新上传,不删除有效文件;降低审计预算会清理最旧事件。审计计数及内容量由SQLite触发器维护,避免每次日志写入扫描所有记录。
|
||||
@@ -0,0 +1,75 @@
|
||||
# 构建与测试
|
||||
|
||||
源码构建需要go.mod指定的Go版本、Node.js及npm。生产服务器只需要发布包,不需要这些构建工具。脚本先构建Vue前端,再将其嵌入Go二进制;internal/web/dist为生成目录,不提交到源码仓库。
|
||||
|
||||
## 构建
|
||||
|
||||
```sh
|
||||
./scripts/build.sh build
|
||||
# Linux amd64发布包及SHA256SUMS
|
||||
./scripts/build.sh release amd64
|
||||
# 同时生成Linux amd64和arm64发布包
|
||||
./scripts/build.sh release
|
||||
```
|
||||
|
||||
输出位于build,包含前端内嵌的程序、配置模板、安装/升级脚本、systemd单元、运行文档及第三方许可证。node_modules、build和测试结果不纳入Git。
|
||||
|
||||
## 后端检查
|
||||
|
||||
先构建前端,保证Go嵌入的静态资源已生成:
|
||||
|
||||
```sh
|
||||
./scripts/build.sh web
|
||||
go test -race ./...
|
||||
go vet ./...
|
||||
```
|
||||
|
||||
后端用例覆盖匿名所有权、密码取件、加密和去重、空间预留、下载计数与续期、并发传输、分页搜索、审计清理、来源校验、多入口cookie、备份恢复及故障回收。可单独运行较大数据量的并发测试:
|
||||
|
||||
```sh
|
||||
BLUEBIRD_STRESS_MIB=64 go test ./internal/app -run TestConcurrentStreamingTransfers -v -count=1
|
||||
```
|
||||
|
||||
## 浏览器测试
|
||||
|
||||
```sh
|
||||
./scripts/build.sh build
|
||||
cd web
|
||||
npx playwright install chromium
|
||||
npm run test:e2e
|
||||
```
|
||||
|
||||
仅针对隔离测试实例运行,不能指向生产数据。默认实例监听127.0.0.1:18981;多入口用例另使用19081、19082,启动本机HTTPS代理和临时自签名证书,需要openssl。测试覆盖寄件、取件、管理凭据、剪贴板、手机布局、分页筛选,以及同一域名的HTTP/HTTPS和独立IP入口。
|
||||
|
||||
执行前确保这些端口未被占用。浏览器依赖、失败追踪保存在本机缓存和web/test-results,不能提交到仓库。Playwright主实例的临时数据目录由测试配置创建,可在测试结束后按实际路径清理;多入口用例自动回收其进程、证书及数据。手机布局采用Chromium模拟,不等同于Safari或手机实机验证。
|
||||
|
||||
## 原生发布包测试
|
||||
|
||||
Python脚本需要Python 3.12或更新版本:
|
||||
|
||||
```sh
|
||||
./scripts/build.sh release amd64
|
||||
python3 scripts/smoke.py
|
||||
```
|
||||
|
||||
脚本在项目中的隔离临时目录验证发布包的初始化、内嵌前端、加密去重、下载、管理员接口、完整性检查及备份恢复,结束后自动回收进程和数据。
|
||||
|
||||
首次安装测试需要rootless Podman,每次使用一次性Debian容器:
|
||||
|
||||
```sh
|
||||
podman run --rm --security-opt label=disable \
|
||||
-e BLUEBIRD_INSTALL_SMOKE=1 \
|
||||
-v "$PWD/build/linux-amd64:/pkg:ro" \
|
||||
-v "$PWD/scripts/install-smoke.sh:/test.sh:ro" \
|
||||
docker.io/library/debian:bookworm-slim sh /test.sh https
|
||||
```
|
||||
|
||||
将末尾https换成http或lan可检查单独的公网HTTP与内网HTTP配置。label=disable仅用于此一次性容器读取本机测试文件,不修改主机SELinux配置。该脚本禁止直接在普通主机运行,不注册主机systemd服务。
|
||||
|
||||
已有安装升级测试需要保留一份旧版amd64部署包:
|
||||
|
||||
```sh
|
||||
python3 scripts/upgrade-smoke.py /path/to/previous-bluebird-linux-amd64.tar.gz
|
||||
```
|
||||
|
||||
此用例在一次性容器中验证旧密码、加密文件、密钥和取件码保留,新增入口下载、配置权限、一致性备份、非法配置保护和检查失败回滚。容器使用手动停服后的--no-start模式;目标服务器的systemd启停、DNS、代理、证书及防火墙需要在实际部署环境验收。
|
||||
Reference in new issue
Block a user