网页控制台与 Web Push
浏览器控制台位于 https://remote.ivjn.us/console/。它支持通过邮件验证注册、账号/密码或操作员令牌登录、工作区范围内的设备、流式命令、xterm PTY、Android 截图/点击/导航/文字输入、指标、审计记录、设备创建/删除,以及主动订阅的通知。只读账号不能执行命令或查看 Android 屏幕。离线设备仍会显示。
设备通告 ubus 能力时显示 ubus 页签,可填写对象、方法和可选的 JSON 对象参数(留空发送 null)。调用使用密封会话,要求设备在线、已启用且具备操作权限。取消只停止等待,无法撤销设备已经执行的方法;切换设备或失去权限会清理待处理调用和结果。
注册会创建独立的工作区。登录前必须使用八位验证码验证邮箱;控制台支持重新发送验证码,并在刷新后返回验证流程。Exchange 必须配置 SMTP(SMTP_HOST、SMTP_PORT、SMTP_FROM,以及 .env.example 中的认证和安全设置)。邮件服务不可用时,注册会报告错误,不会跳过验证。
凭据保存在页面内存中。刷新后需要重新登录。退出登录会关闭会话,尝试移除此浏览器的推送订阅,并撤销通过密码登录建立的会话。主操作员令牌仍保持原有的有效期。点击通知会聚焦已打开的控制台,不会重新加载其凭据。
构建与部署
cd website/console
npm ci
npm test
npm run build
将 dist/ 复制到网站的 console/ 目录。确保现有的 website/assets/ 可通过 /assets/ 访问,以提供共享字体和标志。提供 index.html、sw.js 和清单文件时,应要求重新验证或禁用缓存。带哈希的资源可使用不可变缓存。不要缓存 /api/* 或 /ws/*。
控制台使用同源的 /api/* 和 /ws/browser。将这些路径反向代理到 Exchange,保留 Host 并允许 WebSocket 升级。不要移除 /api 或 /ws 前缀。Vite 开发服务器将 API/WS 代理到 127.0.0.1:18082。Rust Exchange 默认允许同源浏览器访问。通过 REMOTE_CORS_ORIGINS 配置精确来源;只有显式 * 才允许所有来源。Worker 使用 CORS_ORIGINS 及其自身默认值。Bearer 鉴权和一次性 WebSocket 票据仍然必需,不启用跨域 Cookie。
浏览器认证先使用带 Bearer 授权的 POST /api/ws-ticket,随后请求 GET /ws/browser,并发送首个 JSON 帧 {"ticket":"..."}。票据在 30 秒后过期,只能使用一次,使用时会重新检查操作员的启用状态和凭据有效性。长期有效的凭据不会放入 WebSocket URL。原生客户端继续使用 /ws/operator。
所有设备操作都使用原生密封 WebSocket 会话。HTTP 提供身份验证、设备元数据、密钥与授权目录,以及中转会话管理;请参阅 Agent 操作的 HTTP API。
启用 Web Push
在 Exchange 环境中设置以下两个变量(两个都缺失时禁用推送):
WEB_PUSH_VAPID_PRIVATE_KEY=<base64url-unpadded-32-byte-P256-private-key>
WEB_PUSH_VAPID_SUBJECT=https://remote.ivjn.us
使用 python scripts/generate-vapid-key.py 生成私钥。将其保存在 Git 之外、访问受限的环境文件中,重启时保持不变,并安全备份。公钥会自动推导。更换密钥后,浏览器需要重新订阅。
WEB_PUSH_ALLOWED_ORIGINS 默认仅包含以下准确的可信源地址:https://fcm.googleapis.com,https://updates.push.services.mozilla.com,https://web.push.apple.com。只有验证了其他浏览器厂商的 HTTPS 推送源地址后,才能添加该厂商。客户端提供的端点不能扩展此允许列表。系统不会跟随重定向。
需要认证的路由:
| 方法与路径 | 用途 |
|---|---|
GET /api/webpush/config |
启用状态及公开的应用服务器密钥 |
GET/POST /api/webpush/subscriptions |
列出自己的订阅元数据,或订阅/更新插入 |
DELETE /api/webpush/subscriptions/{id} |
移除自己的订阅 |
POST /api/webpush/test |
向调用者的订阅队列加入一条可见的测试通知 |
POST /api/webpush/messages |
发送给自己,或由管理员发送给同一工作区的其他账号 |
POST /api/webpush/devices/{id}/messages |
设备向有管理权限且已订阅的管理员发送消息 |
订阅使用浏览器的 PushSubscription.toJSON()、application_server_key 和 triggers。triggers 最多包含一个 online 对象和一个 offline 对象。每个对象包含 kind、tick_count(默认 3,范围 1–100),以及可选的 device_ids。offline 在连续 tick_count 次未收到响应后通知;online 在连续 tick_count 次成功后通知。device_ids 可以是单个设备 ID(字符串)、设备 ID 列表,或省略/设为 null 以涵盖工作区中的所有设备。空的 triggers 数组会订阅浏览器,但不接收自动上下线通知。不接受 missed_count 和 success_count。消息使用可选的 operator_id 和 notification,后者包含 title、body,以及可选的同源相对 url 和 tag。/webpush/ 是最小参考客户端;生产控制台使用自己的 worker,作用域为 /console/。
迁移 0005_web_push.sql 添加订阅和持久化发件箱。0008_online_offline_triggers.sql 将设备通知迁移到 triggers,并移除 missed_count / success_count。202 表示已入队,不代表已送达或显示。任务在 15 分钟后过期。网络错误、408、429 和 5xx 会重试,总计最多尝试五次;404/410 会移除对应版本的订阅。90 秒租约允许 worker 故障后恢复待处理工作。投递保证至少一次,浏览器是否显示取决于厂商投递、权限和操作系统策略。持久化入队前的设备事件在过载时尽力处理;已入队的事件在重启后仍保留。限制:每账号 20 个订阅,每订阅 32 条排队消息,每账号每分钟 30 次变更操作。
iOS/iPadOS 可能要求先将控制台添加到主屏幕,才能启用通知。浏览器推送服务的可达性与 Exchange 的连接情况是两回事。
验证
运行 cargo test --workspace、cargo clippy --workspace --all-targets -- -D warnings,以及控制台的 npm 测试和构建。进行 PostgreSQL/HTTPS 集成测试时,先安装 psycopg[binary],将 WEB_PUSH_TEST_ADMIN_URL 设置为具有 CREATE DATABASE 权限的测试 PostgreSQL 连接,再运行 python scripts/test-webpush.py。该脚本会创建并删除临时数据库;切勿指向具有非预期权限的数据库角色。
从各端发送
Web Console 的浏览器通知面板可填写消息;移动 Console 设置提供 Web Push 发送、测试和打开 Web Console 入口。桌面 Agent 本机设置、Android Agent 设置和浏览器扩展配置页均可使用设备凭证发送。无界面/OpenWrt Agent 使用以下命令。接收端在 Web Console/PWA 启用通知;此功能不添加原生 FCM/APNs 接收器。
remote-cli webpush status
remote-cli webpush subscriptions
remote-cli webpush test
remote-cli webpush send --title "Backup ready" --body "Completed"
remote-cli webpush send --operator-id ACCOUNT_UUID --title "Notice" --body "Hello"
remote-agent --config /path/to/config.json webpush --title "Device ready" --body "Completed"
{"notification":{"title":"Backup ready","body":"Completed","url":"/console/","device_id":"edge-1"}}
POST /api/webpush/messages 使用操作员 Bearer 凭证。省略 operator_id 表示发送给自己;其他接收者需要管理员权限,并且必须属于同一工作区。上方 JSON 也可通过 remote-cli webpush send --json-file message.json 发送。将设备状态触发器设为空数组可关闭自动上下线提醒,同时保留通用消息接收。
POST /api/webpush/devices/{id}/messages 使用该设备的 Bearer 令牌,直接接收通知对象(title、body、可选 tag)。只有已启用且管理该设备的管理员的有效订阅会接收。服务端固定 device_id、event=device.message、occurred_at 和 /console/,发送者不能指定账号或冒充其他设备。设备发送共用每工作区每分钟 30 次的预算,单次最多 256 个订阅;账号订阅数和队列限制仍生效。没有有效接收者返回 202 {"queued":0};queued 是订阅数,不是送达数。