B BullBellDOCS / 目标规范 v3
SELF-HOSTED · END-TO-END ENCRYPTED

一套告警协议,
两端安全抵达。

告警在产生处加密,Linux 服务器只保存与转发密文,Android 和 Windows 使用各自设备密钥在本地解密。

01告警程序产生标题与正文
→
02Sender本地加密与签名
→
03Server只接触密文
→
04设备验证并本地解密
00

先看这里

当前测试版与目标架构

当前原型

已经可以联调

  • 多用户账号、原生设备密钥与指纹批准
  • v1 广播与 v2 定向响铃加密协议
  • 频道角色、细分管理权限与限时邀请
  • all/future 历史密钥策略与成员变更轮换
  • 一次性离线恢复 JSON、HPKE 持有证明与设备恢复
  • Sender 本地加密签名、客户端验签与本地解密
  • 三级图片、文字和脚本内联键组成的 E2EE 频道帖子
  • Android/Windows WebSocket 与 Android FCM 备用通道
  • 服务端与客户端 SQLite 密文模型
  • 持久游标、断网历史读取与增量补齐
下一阶段

仍需实测

  • 多成员/多设备定向响铃与远程停铃
  • all/future 和移除成员的真机密钥边界
  • 红米长时间后台与省电策略
  • Linux HTTPS、备份恢复和轮换演练
Admin 控制面:/admin/ 使用独立身份管理账号、设备信任、会话、运行状态和审计。Admin 可验证当前密码后修改密码,也可向单台已批准设备发送固定文案的诊断响铃:Android 走 FCM,在线 Windows 走已认证 WebSocket。设备信任批准与频道密钥分发相互独立,普通设备批准不会删除任何旧数据。
当前边界:正式版本为 Server 0.15.0、Android 1.0.0+63、Windows 1.1.0+64、Sender 0.9.0 与 Protocol 0.8.0。Admin 无法查看告警正文或频道私钥。当前离线恢复仍是单一服务器级结构,尚未支持每个成员独立恢复。
01

使用教程

从本机测试开始

1

启动测试服务器

cd D:\code\bullbell
npm run build
powershell -ExecutionPolicy Bypass -File .\scripts\start-local-server.ps1

测试数据固定在 data/testing,不会与未来 Linux 生产数据混用。

2

连接客户端

Windows 使用 http://127.0.0.1:8787;Android 使用电脑当前局域网地址,例如 http://192.168.x.x:8787。

3

初始化 Admin 并批准手机

从本机服务日志读取一次性 Admin 初始化代码,打开 http://127.0.0.1:8787/admin/ 创建独立 Admin。客户端提交设备后,在控制台核对指纹并批准。

4

创建 BullBell 账号

手机刷新批准状态后填写账号名、显示名称和密码。此后批准其他设备不会删除旧账号、频道或历史。

5

停止测试服务器

powershell -ExecutionPolicy Bypass -File .\scripts\stop-local-server.ps1
02

安全模型

谁能够看到明文

允许看到明文

  • 产生告警的原始程序
  • 同机 BullBell Sender
  • 已授权 Android/Windows 设备

只能看到密文

  • BullBell Server 与 SQLite
  • 备份、Caddy、FCM、WebSocket
  • 文档站与普通浏览器

服务器仍会看到必要元数据,例如事件时间、频道 ID、密文大小、发送 IP 和设备数量。正文、标题、链接和业务 Payload 必须加密。

03

加密规范

设备、频道与消息三级密钥

DEVICE

设备私钥

Android Keystore / Windows CNG 中生成并持久保存,应用只上传公钥,用于解封频道私钥。

↓
CHANNEL

频道私钥

每频道独立、支持版本轮换,用于解封每条消息的随机密钥。

↓
EVENT

消息密钥

每条事件独立随机 32 字节,使用 AES-256-GCM 加密正文。

内容加密AES-256-GCM
密钥封装HPKE / P-256
请求签名Ed25519
规范化RFC 8785 JCS
登录密码不是加密密钥。修改账号密码不会重新加密历史;数据恢复依靠独立的离线恢复密钥。修改成功后旧访问令牌立即失效,全部旧刷新令牌撤销,其他设备的推送绑定和 Windows 长连接也会失效;当前设备取得新令牌并保持登录。访问令牌约 15 分钟,刷新令牌按设备轮换;客户端会合并同一批并发刷新,避免多个 401 相互撤销登录态。
04

频道

创建、导出与轮换

频道必须由已授权 Android 或 Windows 客户端创建。服务器网页默认不能创建私钥。

创建

客户端生成频道 ID、P-256 密钥对、Webhook Token、Sender 签名密钥和版本号。

导出

一次性导出 .bullbell-channel Sender 配置包,不包含频道私钥。

轮换

泄露后启用新 keyVersion;旧私钥只保留用于读取旧历史。

轮换是完整凭据替换:在频道页点击“轮换密钥”后,客户端会为全部已批准设备和恢复公钥生成新版本包,同时创建新 Token 与新 Sender。旧 Sender 配置立即失效;新配置只显示一次,必须先更新发送端再关闭。
{
  "endpoint": "https://<YOUR_DOMAIN>/hook/<CHANNEL_ID>",
  "channelId": "<CHANNEL_ID>",
  "webhookToken": "<TOKEN>",
  "channelPublicKey": "<BASE64URL_PUBLIC_KEY>",
  "senderPrivateKey": "<ENCRYPTED_SENDER_KEY>",
  "keyVersion": 1
}
04A

多人频道

成员、邀请与历史权限

创建者

可修改/删除频道、授予管理员权限、轮换完整频道凭据,并保留 Sender 配置的控制权。

管理员

只能执行创建者逐项开启的发送告警、创建/撤销邀请、移除成员和选择历史权限。

普通成员

可接收、解密、查看告警和提交加密选择回执,不持有 Webhook Token 或外部 Sender 私钥。

账号资料与头像:@username 全局唯一且可修改,显示名称可以重复。内置头像只同步编号;自定义头像在客户端裁剪缩放后,按每个频道公钥独立加密上传,Server 和备份只能看到头像密文。
应用内发送:有 send_alert 权限的管理员使用本设备独立 Ed25519 密钥签名,私钥留在 Android/Windows 安全存储,不共享 Webhook Token。权限或设备撤销后立即不能再提交新告警。
  1. 创建邀请在频道成员页设置角色、权限、有效期、使用次数和 all/future。邀请链接只显示一次。
  2. 接受邀请新用户可从登录页使用邀请注册;已有用户在应用内粘贴链接。接受后先处于待批准。
  3. 批准并分发密钥all 为新成员设备封装所有历史版本;future 原子轮换新版本,新成员看不到加入前历史。
  4. 移除成员客户端为剩余成员设备生成新版本密钥包,服务端与成员移除原子提交。
为什么多人能解密同一条消息?正文只用随机 DEK 加密一次,频道私钥的同一版本再分别 HPKE 封装给每台成员设备。每台设备都用自己的 Keystore/CNG 私钥解开它的那份频道密钥包。
05

告警端

BullBell Sender

Sender 是独立模块,负责模板、本地加密、签名、幂等、断线重试和密文缓存。普通程序不需要自行实现密码学。

当前 0.9.0:Windows 使用当前用户 DPAPI;Linux 优先使用 Secret Service,无密钥环时使用 scrypt + AES-256-GCM。图片在 Sender 本地生成三级内容并使用独立 AES-256-GCM 文件密钥加密,断线队列仍只保存已签名消息密文。

命令行

bullbell-sender.exe send \
  --config sender.secure \
  --title "BTC 跌破 70000" \
  --body "请立即检查账户" \
  --image monitor.png \
  --inline-keyboard-file buttons.json

按钮是脚本随消息提供的二维 JSON 数组,支持确认并停止、打开 HTTP(S) 链接、复制静态内容和加密选择回执。定向响铃增加 --target-user-ids "<USER_ID>" --non-target normal --ack-policy any-target。

本机中继

POST http://127.0.0.1:17321/v1/alerts
Authorization: Bearer <LOCAL_RELAY_TOKEN>
Content-Type: application/json

{"title":"磁盘空间不足","body":"只剩 10GB"}
严格 E2EE 限制:Server 自 0.5.0 起会拒绝普通明文 Webhook。只支持明文 Webhook 的第三方平台必须先经过用户控制的 Sender/中继,在本机完成加密后再发送。
05A

开发接口

Python / JavaScript / TypeScript SDK

SDK 0.1.0 直接运行在发送脚本内,不需要启动 Sender.exe 或本机中继。库在本机加密、签名后通过 HTTPS 发送,Server 仍看不到标题正文。

登录有权限的 GitHub 账号,从项目私有 Releases 下载 wheel(Python 3.11+)或 tgz(Node.js 22+),校验 SHA256SUMS 后安装。完整安装、配置导入、三种通知级别、定向响铃、内联按钮与重试教程在私有仓库 docs/sdk/README.md。SDK 不发布公共 PyPI/npm,勿安装陌生同名包。

安装与配置

在私有仓库的 sdk-v0.1.0 Release 下载对应安装包和 SHA256SUMS.txt。SDK 下载需要 GitHub 读取权限,日常发送不访问 GitHub。不要把 GitHub Token 写在安装命令或发送脚本中。

# Python:在发送脚本使用的 Python 环境安装
python -m pip install ./bullbell_sdk-0.1.0-py3-none-any.whl

# JavaScript / TypeScript:在业务项目目录安装
npm install ./bullbell-sdk-0.1.0.tgz --save-exact

设置环境变量 BULLBELL_CONFIG 为现有 sender.secure 路径。Windows 必须由导入配置的同一用户运行;Linux 口令配置还需设置 BULLBELL_SENDER_PASSPHRASE。没有安全配置时,Python 使用 protect_config、JavaScript 使用 protectSenderConfig 导入客户端一次性 Sender JSON,无需 Sender 工具。凭据只保存在受信任的发送机器上,不随 SDK 分发。

三种告警级别

normal 为普通通知,important 为重要通知,alarm 为响铃。不传级别时使用 Sender 配置的默认值,同一频道可发送三种级别。

# Python:BULLBELL_CONFIG 指向已有受保护 Sender 配置
import os
from bullbell import BullBell
bell = BullBell(config_file=os.environ["BULLBELL_CONFIG"])
result = bell.send(title="服务异常", body="请检查服务器", urgency="alarm")
// JavaScript / TypeScript(Node.js)
import { BullBell } from "@bullbell/sdk";
const bell = await BullBell.fromFile(process.env.BULLBELL_CONFIG);
const result = await bell.send({title:"服务异常", body:"请检查服务器", urgency:"alarm"});

指定成员响铃

目标使用频道成员 UUID,不是用户名或设备 ID。所有有权成员仍可查看消息;非目标成员按 silent、normal 或 important 处理。

# Python
result = bell.send(
    title="值班告警", body="请值班人员处理", urgency="alarm",
    target_user_ids=["<USER_UUID>"],
    non_target_behavior="normal", acknowledgement_policy="any-target",
)
// JavaScript / TypeScript
const result = await bell.send({
  title: "值班告警", body: "请值班人员处理", urgency: "alarm",
  targetUserIds: ["<USER_UUID>"],
  nonTargetBehavior: "normal", acknowledgementPolicy: "any-target",
});

any-target 表示任一目标确认后停止全部目标;all-targets 要求每个目标用户分别确认。目标列表和级别是服务器可见的路由信息,正文仍为密文。

脚本内联按钮

[
  [{"type":"acknowledge","label":"确认并停止"}],
  [{"type":"url","label":"打开监控","url":"https://monitor.example.invalid/"}],
  [{"type":"copy","label":"复制节点","value":"db-02"}],
  [{"type":"choice","label":"已接单","choiceId":"take","value":"accepted"}]
]

Python 将此数组作为 inline_keyboard=buttons 传入;JavaScript 使用 richContent: {version:1, inlineKeyboard:buttons}。最多 8 行、每行 4 个、总计 20 个按钮。按钮内容随正文加密,选择回执不是服务端代为执行任意 HTTP 回调。

失败与重试

默认最多尝试 3 次,遇到网络错误、408、429 或 5xx 时遵循 Retry-After 并指数退避加抖动;401/403 等永久错误不重试,也不跟随重定向。Python 的 timeout 单位是秒,JavaScript 的 timeoutMs 单位是毫秒,默认每次请求 15 秒。成功结果含 eventId/status/attempts/response,只证明服务端接受,不证明设备已响铃。

# Python:需要业务持久队列时,先保存 prepare 返回的密文
from bullbell import DeliveryError
envelope = bell.prepare(title="服务异常", body="请检查服务器", urgency="alarm")
try:
    result = bell.submit(envelope)
except DeliveryError as error:
    print(error.status, error.retryable)
    # 稍后仅重试 bell.submit(error.envelope),不要重新 send 创建事件

JavaScript 对应 await bell.prepare(input)、await bell.submit(envelope) 和 DeliveryError。SDK 不自动写持久队列;业务只应保存密文信封。服务端有前后 300 秒的时间窗口,过旧或密钥已轮换的事件不能悄悄改时间戳或换 eventId 补发。频道完整轮换后旧配置失效,需更新发送凭据。

范围与重试:首版支持文字、Payload、链接、定向响铃与内联按钮,暂不封装图片上传、回执订阅或持久离线队列。SDK 失败后可用 prepare/submit 重放同一份密文,不能反复新建事件。服务端接受不等于设备已经响铃。SDK 配置、口令和签名私钥不得上传 GitHub 或包含在安装包中。
06

协议

加密信封与防重放

{
  "version": 1,
  "eventId": "<UUID>",
  "channelId": "<CHANNEL_ID>",
  "keyVersion": 1,
  "timestamp": 1786964400,
  "nonce": "<BASE64URL_96_BIT_NONCE>",
  "wrappedKey": "<HPKE_WRAPPED_DEK>",
  "ciphertext": "<AES_GCM_CIPHERTEXT>",
  "senderId": "<SENDER_ID>",
  "signature": "<ED25519_SIGNATURE>"
}

Server 验证 Token、Sender 签名、时间窗口、Nonce、大小和频率。重试必须复用同一 eventId,以保证幂等。任何字段、AAD、密文或签名被篡改都必须失败。

v1 已固化:AAD 是 Header 八个字段的 JCS UTF-8;wrappedKey 为 65 字节 HPKE enc 与 48 字节 DEK 密文拼接;签名覆盖除 signature 外的完整信封。公开互操作向量位于 packages/protocol/test-vectors/v1.json。
v2 定向路由:新增 deliveryMode、targetUserIds、nonTargetBehavior 和 acknowledgementPolicy,全部纳入签名和 AEAD AAD。频道成员仍都可解密消息,只有目标成员响铃。公开向量位于 packages/protocol/test-vectors/v2.json,v1 字节格式保持不变。
富消息正文:richContent v2 为最多 4 张图片保存 E2EE 32px 极小图、最长边 1280px 列表图和最大 10 MB 原图,不改变信封格式。App 先显示极小图,在图片接近视口时下载列表密文,点击后才下载原图;消息流缩略图限制在屏幕高度约 36% 且最高 300 逻辑像素,长图只裁切缩略图,原图查看器仍显示完整内容。后台验证 AES-GCM Tag 与 SHA-256 后按显示尺寸解码。客户端只在 32 MB 内存 LRU 保存压缩明文字节,Flutter 已解码图像/纹理缓存限制为 32 MB 和 64 项,256 MB 磁盘缓存仍只有按服务器和设备隔离的密文;旧 v1 图片继续兼容。频道首次打开直接复用内存中的最近消息并锚定在底部,切换动画完成后再同步,不会先闪过旧消息再跳转;到达最早消息后继续下拉并松手即可加载上一页历史。
07

通知

Android 可选推送通道与本机勿扰

A

Android

“设置 → 常规 → 推送方式”:仅 FCM、仅长连接、FCM 优先/长连接备用、长连接优先/FCM 备用。每台设备单独设置,旧设备默认长连接优先。

W

Windows

托盘客户端通过 WebSocket 收到事件提示,自动下载密文、验证签名并按本用户重新计算通知级别。本轮 Android 勿扰不改变 Windows 通知行为。

频道勿扰:进入频道的“频道资料 → 本机通知与勿扰”,普通成员也能设置,只影响自己的这台手机。支持响铃降为普通、全部静默、仅保存;30 分钟、1 小时、指定截止、长期、每周时段。跨午夜归属开始星期,按手机当地时间判断。全局规则在“设置 → 常规 → 全局勿扰”;默认取更严格规则,频道可显式允许绕过全局。普通通知仍可能有短提示音。
不会自动确认:勿扰在原生接收器中判断,不必先打开应用。已处理的消息按 eventId 去重,勿扰到期不会重新触发它们;尚未到达手机的离线告警仍按原有补响时限与收到时规则处理。“仅保存”不弹通知,历史由原有同步机制下载。可勾选“同时停止当前响铃”,只停本机、不替其他成员确认。作息规则不上传服务器;Admin 测试受全局勿扰约束并上报“本机勿扰已处理”。
发送保护:每个频道和每个 Webhook Token/应用内 Sender 都有独立令牌桶。超限返回 429 与 Retry-After;FCM 的 429、5xx 和临时网络错误会按 Retry-After 或带随机抖动的指数退避最多重试 3 次。
锁屏默认展示BullBell

收到一条新告警,解锁后查看

实时连接与兜底:仅 FCM 停止自建常驻连接;另外三种模式会保留长连接与低重要性常驻通知。双通道不是延迟建连,仍有维持连接的开销。主通道 5 秒没有本机处理回执才尝试备用;已知主通道不可用则直接走另一通道。这里不是等待你按“确认”:静音也算已处理。FCM 回执使用短效一次性 Token,不上传正文。两条通道和历史补响共用本机最近 4096 个事件处理记录。WebSocket 每 2 分钟 Ping,断线指数退避,不永久持有唤醒锁。需 Server 与 APK 配套升级,覆盖安装后打开一次;锁屏和长期待机效果仍须真机验收。
响铃生命周期:Android 的 alarm 使用临时前台服务持续响铃,可选择本地音频,文件不可读时回退内置声音。Windows 使用应用控制的循环系统声音与独立白色窗口;1.1.2 起小窗位于主屏右下角,距工作区边缘 16 像素,不主动抢输入焦点、不打开主界面,也不新增任务栏按钮。“确认并停止”或关闭响铃窗口先停止本机声音,再发送频道确认或 Admin 回执。旧事件的停止指令不会误停新响铃。普通/重要告警仍走系统通知。
Windows 托盘:正式客户端只保留一个运行实例,重复双击 EXE 会恢复已有窗口。左键打开,右键显示“打开 BullBell / 退出”。主窗口右上角关闭仅隐藏到托盘;要彻底停止后台收警,请选择托盘“退出”。退出及应用内更新会先清理图标。强制结束或崩溃遗留的无响应图标可能只是系统残影,鼠标移过后通常会刷新消失,不必重启电脑或 Explorer。
Windows 补响与限制:每次长连接认证成功后同步并检查最近 1000 条缓存,最近十分钟内可解密、未确认、需要本人处理且不是本设备发送的告警可补响;一批只响最新一条,其余候选记为已处理。实时和补响共用最多 4096 项的持久去重记录。窗口不能覆盖 Windows 安全锁屏,静音、音量、耳机输出和电脑睡眠仍会影响唤醒;程序不自动调高音量或阻止睡眠。更新助手支持空格/中文路径,复制后逐文件校验再重启。
Admin 单设备测试:控制台向已批准 Android 设备通过 FCM 发送固定 admin-test-alarm 指令,向当前在线的 Windows 设备通过已认证 WebSocket 发送同类指令。它不创建频道历史、不包含正文或频道密钥。状态区分投递通道接受、设备收到、开始响铃和停止;每设备至少间隔 30 秒,全局每分钟最多 10 次。

定向确认:any-target 中任一目标确认后,所有目标设备收到 alarm-stop;all-targets 中每个目标用户分别确认,同一用户的其他设备会同步停止。客户端仅在 eventId 与当前响铃一致时执行远程停铃。

响铃所需权限:“设置 → 权限管理 → 系统权限”集中显示通知、全屏响铃、HyperOS 锁屏显示、HyperOS 后台弹出界面、HyperOS 自启动、后台与电池优化、安装应用更新七项状态,并在返回应用时实时刷新。HyperOS 三项仅在小米设备显示;其中缺少“后台弹出界面”时,手机可能已经收到 WebSocket/FCM 提示并开始响铃,但系统会拦截响铃页。网络、唤醒锁、振动、前台服务和 FCM 是安装时能力,无需额外开关;BullBell 不需要悬浮窗、通讯录、短信、定位或广泛存储权限。

客户端导航:底部固定为“总览、频道、告警中心、设置”。频道页使用明亮白色列表展示角色、最后告警、未读和待确认状态;频道资料的“频道”区域直接提供成员、邀请、新成员历史权限和管理员权限入口,并集中显示加密状态及仅创建者可见的 Token/Sender 管理。Token 原值只在创建或重置时显示一次,之后仅显示末尾 6 位与轮换时间。

客户端设置:账号资料位于设置页顶部,独立显示头像、显示名称、@username 和修改密码入口,不归入“常规”。“常规”负责外观、固定配色、最近任务卡片、通知声音和响铃音频;“权限管理”的“系统权限”显示全部七项用户可检查权限,随后管理设备批准和离线恢复;“使用指南”按主题打开本页。Android 的“安装应用更新”只影响下载后安装 APK,不影响日常收警。后台隐藏只改变最近任务卡片,不停止 FCM、通知、响铃或后台任务。Android 不允许应用静默覆盖用户已经确认的通道开关,因此悬浮、振动和锁屏显示必须由用户在跳转后的系统页面修改。

08

告警中心

频道历史与跨频道处理

BTC 告警 / 历史记录
19:42

设备本地解密后的标题与正文

18:10

服务器返回的始终是频道密文分页

完整历史移入频道内部。告警中心跨频道提供待处理、指向我、全部、已确认、收藏、频道筛选和本地全文搜索,搜索词不会上传服务器。服务端事件游标在 SQLite 重启后仍然有效。客户端按服务器 URL 与设备 ID 隔离缓存;Android 进入频道后优先读取已经验签解密的明文记录,Windows 读取密文并在内存解密,再在后台同步最新密文。每个频道在本机只保留最近 1000 条,超过本地边界的旧记录按服务端游标联网读取。

Android 本机保存明文:标题、正文、链接和 Payload 在首次成功验签解密后写入应用私有 SQLite;系统备份已关闭,但 root、调试取证或设备失陷仍可能读取。Windows 仍只落密文,BullBell Server、FCM 和 WebSocket 始终不能看到明文。退出登录默认保留本机历史,设置页可显式清除当前范围。
09

密钥生命周期

设备批准、撤销与恢复

  1. 新设备生成密钥私钥留在 Keystore/CNG,公钥注册到服务器并展示 SHA-256 指纹。
  2. Admin 批准设备信任在 /admin/ 核对指纹并批准。此操作不删除旧数据,也不生成或读取频道私钥。
  3. 按频道补齐密钥已有设备在本地解封频道私钥并重新封装给新设备;控制台独立显示齐全或缺失。
  4. 没有现有设备时恢复已由 Admin 批准的设备使用离线恢复 JSON 解开 HPKE 挑战并补齐频道密钥;恢复文件不能自行授予设备信任。
恢复密钥必须离线保存。首次初始化或设置页创建后,先复制并保存到离线加密介质,再确认“我已离线保存”。确认后客户端删除暂存副本,服务器永远无法再次提供私钥。所有设备和恢复密钥同时丢失后,历史将永久无法解密。
恢复文件疑似泄露时:在一台已批准设备的设置页点击“轮换”,立即保存新的恢复 JSON。服务器原子替换全部恢复包并废止旧恢复公钥;提交成功后旧文件不能再恢复设备。
10

部署

2 GB Linux 服务器

CaddyHTTPS / 80 / 443
Node.jsAPI / FCM / WebSocket
SQLite密文 / WAL / 游标
Docker Volume独立生产数据
cd /opt/bullbell/deploy/linux
cp .env.example .env
# 已有 Nginx/Caddy 时只绑定 127.0.0.1:8787
docker compose -f compose.existing-proxy.yaml up -d --build
# 后续正式更新只部署明确标签
./scripts/deploy.sh v0.22.0

8787 不直接开放公网。已有站点时使用独立的现有反向代理 Compose;没有现有代理时使用自带 Caddy 的 compose.yaml。Server 0.15.0 使用 bullbell.sqlite 保存 Admin 摘要与审计、设备注册申请、多用户、设备信任、频道成员/邀请、应用内发送公钥、密钥包、每频道最近 10,000 条密文事件、附件密文和频道头像密文。两套 Compose 均包含健康检查、384 MB 内存限制和 10 MB × 3 日志轮换。客户端正式地址填写 https://<YOUR_DOMAIN>,不要附加 /api。

11

运维

备份不会加密整个 D 盘

加密范围只包括 Linux 上的 BullBell 数据卷和 BullBell 备份文件。本机测试目录是 D:\code\bullbell\data\testing,文档不会启用 BitLocker、修改分区或加密整个 D 盘。迁移验证完成前保留的旧 JSON 也必须作为敏感备份保护。

自动维护:backup.sh 使用 SQLite 在线备份生成 AES-256/PBKDF2 加密快照,无需停止服务;monitor.sh 检查容器、公网 HTTPS、磁盘和内存;systemd timer 默认每 5 分钟监控、每天备份。服务器只保留当前和上一 Server 镜像、APK 与 Windows 包,GitHub Private Releases 可长期归档更多版本。

需要保护

  • SQLite/数据卷备份
  • Firebase 服务账号
  • APK 签名密钥
  • Sender 配置与恢复密钥

绝不写入文档

  • 真实 Token 和密码
  • 设备/频道私钥
  • 恢复密钥
  • 生产内部地址与凭据

Windows 1.2.1 低内存试用版:关闭主窗口先隐藏再释放界面内存;托盘快速重开会等待旧界面退出并合并重复点击。启动复用后台会话,先显示本地缓存,再通过进度条提示联网同步,不再每次打开都刷新令牌或注册设备密钥。登录失效仍要求重新登录,断网仍明确提示。此优化不让整个界面长期驻留后台,完全退出后的冷启动成本仍存在。

Windows 低内存后台(本机试用,未公开发布):实验架构把收警、续期、验签解密、托盘和右下角响铃交给独立原生进程,关闭窗口后退出 Flutter 界面进程,托盘“退出”才结束后台。界面通过当前用户限定、校验进程身份的命名管道通信,不开放本地 HTTP 端口。设备 CNG 私钥不变;后台凭据使用当前用户 DPAPI,历史缓存维持原布局,不让两个进程共写历史库。不会增加管理员权限、Windows 服务或开机自启。1.2.0 本机试用已验证账号迁移、界面关闭后保持连接及实际响铃;仍须补齐反复重开、双进程更新回滚和持续收警验收,当前正式下载包不变。短时低内存测试不代表长期可靠性保证。旧 Refresh Token 可能在迁移后失效,不能未经验证直接降级。
12

发布

版本、签名与更新

# 当前开发代码
Server 0.15.0
Client 1.1.0+64

# 当前网站正式包
Client 1.1.0+64
  • 整套正式发布使用 vX.Y.Z Git 标签,Linux 部署脚本拒绝未标记代码。
  • Android 构建号每次发布递增,并始终使用同一份受保护签名密钥;正式脚本交互读取密码,配置缺失时不会回退到 Debug 签名。
  • Windows 可下载单文件免安装 EXE(内嵌完整运行文件),或解压完整 ZIP。原始 Flutter 目录中的主 EXE 不能单独复制。
  • 发布清单保存版本、构建号、下载 URL、文件大小、APK SHA-256 和发布证书 SHA-256。
Android/Windows 应用内更新:登录后后台检查一次,仅轻提示新版本;也可从“设置 → 常规 → 应用更新”手动检查(Windows 1.1.3 起显示此入口)。Android 校验 APK 大小、SHA-256、应用 ID、构建号和发布签名后打开系统安装页。Windows 校验完整 ZIP 的大小和 SHA-256,安全解压后自动退出、替换并重启;也可直接下载免安装 EXE,双击后自动准备运行文件,无需手工解压。程序文件缓存于当前用户目录;换电脑需重新登录和批准设备,设备私钥不会随 EXE 搬走。
Windows 1.1.4 存储与铃声:“设置 → 常规 → 存储位置”可查看程序、安全存储和缓存目录。选择新缓存位置后,从托盘退出并重新启动,应用会在使用数据库前完成一致性快照与密文附件校验,成功才切换;旧缓存保留,账号和设备密钥不迁移。“响铃声音”可导入不超过 20 MB 的 MP3/WAV、试听最多八秒并调节本应用响铃音量。导入的副本不依赖原文件;真实告警优先于试听,文件不可用时回退默认 WAV。音频引擎失效时最后尝试系统提示音,此时使用系统音量;系统静音、睡眠和音箱关闭仍会影响收警。
13

开发规范

必须按依赖顺序实施

  1. 01 ✓
    协议与测试向量

    Schema、JCS、HPKE、AES-GCM、Ed25519 已完成。

  2. 02 ✓
    设备与恢复

    Keystore、CNG、指纹批准、离线恢复和密钥轮换已完成。

  3. 03 ✓
    频道与 Sender

    安全配置、中继、密文队列与单文件打包已完成。

  4. 04 ✓
    服务端密文模型

    SQLite WAL、防重放、幂等和持久游标已完成。

  5. 05 ✓
    客户端同步与通知

    按频道分页、断网历史和活动告警前台服务已完成。

  6. 06 ◐
    生产演练

    生产 FCM、红米锁屏省电、HTTPS 与备份恢复待实测。

开发前必读:仓库根目录 AGENTS.md 要求所有后续实现先完整阅读 docs/PROJECT_SPEC.md,架构变化必须同步更新本网页。
14

完成标准

不通过这些测试就不算 E2EE