Releases: fonlan/fobe
Release list
fobe 0.1.13
fobe 0.1.13
安全:一轮认证 / 请求 / 出站 / 部署加固(本次发布主体)
运维侧需要留意的变化逐条列出:
- XFF 改为「从右往左取第一个不在
FOBE_TRUSTED_PROXIES内的地址」:旧版取最左,配 nginx 的$proxy_add_x_forwarded_for(追加)等于让每个客户端自选来源 IP,伪造私网地址还能落进 §4.3 的「永不加黑」豁免。README 的 nginx 示例改用$remote_addr(替换式)。服务端的右起取址让追加式写法也不再能被伪造,但替换式才是推荐配置。 /api/login前置一道与来源身份无关的全局闸(突发 30 / 每秒 10 / 并发 4):黑名单按 IP 计数而身份可以无限多,每次校验固定吃 64 MiB,内存上界必须不可伪造。- 失败计数随封禁一起过期:成功登录无条件删行,
RecordLoginFail发现存量行已过期先归零。此前一次手滑之后「每次再错一次就重封 30 分钟」,且残留行在面板上不可见。 - 会话新增 30 天服务端绝对有效期(
security.SessionExpired+ 清理任务);GET /api/sessions只回id_short指纹、不再回显 cookie 值;改密码 / revoke-all 会关闭对应的活动 WS(WS 只在握手时认证一次)。 must_change在服务端强制:除/api/me、/api/password、/api/logout外一律 409。- 请求体上限:默认 8 MiB,
/api/import与 MMDB 上传 64 MiB;http.Server补ReadTimeout/IdleTimeout(刻意不设WriteTimeout)。 /sub/{token}访问日志脱敏;/api/health去掉initialized,登录失败不再区分not_initialized。Sec-Fetch-Site跨站拒绝,只作用于变更类方法与两个有副作用的 GET(/api/export、/api/nodes/{id}/forwards?live=1)。- 安全响应头:CSP + X-Frame-Options + nosniff + Referrer-Policy + Permissions-Policy,https 时加 HSTS;Cookie 的
Secure由「请求自身是 https」或「server.public_url是 https」推导。 - 出站守卫(
security.CheckOutboundHost/GuardClient):AI providerbase_url与通知 webhook 拒绝 link-local(含云元数据 169.254.169.254)/未指定/组播,放行 loopback 与 RFC1918,并逐跳复检重定向;geoip.url补入同一守卫(写侧与下载侧,302 劫持引下载器进内网被拒)。上游响应体不再进入面板可见的错误文本。 - 主密钥解不开的密钥不再静默失效:启动记 error +
secret_unreadable审计,GET /api/settings回unreadable_secrets,测试按钮回notify_secret_unreadable,通知页顶部列出这些键。 cmd_result的 WHERE 补node_id与状态条件(曾可由任一探针终结他节点的排队命令);构建 VERSION 过字符集校验(agentupdate.SafeVersionPart,启动即拒非法值);sing-box 产物下载加解压总量上限;终端 WS 每节点上限 4。- 容器改以
fobe(10001) 提供服务:入口脚本只为接管/data属主短暂以 root 运行后su-exec降权;compose 加no-new-privileges+cap_drop: ALL并只回补入口所需能力。升级提示:首次启动会把/data卷内文件属主改成 uid 10001;用仓库 compose 的请一并git pull拿新的security_opt/cap_drop。
新增:面板登录失败告警
- 新增
security事件组(设置 → 通知里可开关):登录校验失败推login_failed(ip / count / max_fails),跨过 3 次阈值推login_blacklisted(ip / count / block_seconds);受保护网段的失败也推送但不计数(payloadprotected:true),一次失败只产生一条告警。 - 去重是全局 1 小时而非按 IP:
/api/login无需认证,按来源去重等于把消息条数交给换 IP 的攻击者;逐条来源地址仍在audit_logs。 - 新增
TestEveryEventGroupSwitchIsWritable钉住「每个事件组开关都可写」——此前allowedKeys手写,漏加新组会渲染出 PUT 被拒、GET 也不返回的开关。
修复
- 节点设置保存后「已保存」不可见:成功提示被表单的 key 重挂载清掉(key 绑节点数据是有意设计),现在提示上移到父级卡片标题行,跟随基础设置页 3 秒自动清除,并用 ref 存定时器句柄(连续保存不会被旧定时器提前清掉)。
- 通知测试的 i18n 键:页面调
feishu_test_secret_unreadable,字典里加的却是零引用的err_notify_secret_unreadable——tsc全绿,运行时把键名原样显示给用户。已改名对齐(zh/en 同步)。 - Debug 访问日志也脱敏:此前只有 5xx 分支走
logPath(),日志级别开到 Debug 时/sub/<token>仍明文入库。 - 出站守卫补拦一般组播:原条件只挡两个组播子类,实测
239.1.2.3、ff05::1被放行,与函数注释和 design §12.5 不符(只更严不更松)。
重构(行为不变)
internal/agent/singbox.go(1526 行)按 §9 缝线拆为singbox_local/singbox_firewall/singbox_install/singbox_gates/singbox_proc五个文件;删死代码compareVersions/versionPart/atoiSafe/runQuiet/service.InstallAgent;fetchChecksum委托同包fetchExpectedSHA256,同一二进制里不再有两份 sidecar 解析器。httpapi新增writeStoreErr/decodeReqhelper(替换 34 + 30 处样板,语义非统一的 22 处刻意保留);store的 scan 族推广到 ai_models / alerts / commands / templates;validateSettingValue成为PUT /api/settings与 §17 导入共用的单值校验源——导入侧因此变严(坏 theme / webhook URL 现在会被丢弃)。- 前端新增
useAsyncAction,收敛 EditServer / SettingsAI / Settings 三页 29 处 busy/err/msg 样板;新增ErrorState、chartShared.tsx共享件;删 26 个零引用 i18n 键与若干死代码。
无 schema / 无 wire 变更
- 数据库 schema 未动(仍 16),
internal/protocol未动,无新增必填环境变量。 - VERSION = 0.1.13,镜像仅 linux/amd64。
fobe 0.1.9
修复:订阅入口「两个入口用了同一个别名」的误报
选择订阅入口时,若某两条 nftables 规则共用同一个 (proto, src_port, iface)(§21 里靠 handle 才能分开编辑),订阅侧它们其实是同一条入口,但入口选择器会把同一条入口列成两行。前端按入口身份给行做 key,两行同 key 时改一行的名字会同时写进两行,最后输入的那个覆盖两行,保存回来就是 alias_conflict —— 现象正是「我给同一节点的两个入口起了不同的名字,却提示用了同一个别名」。
- 候选按身份去重:一条入口身份在选择器里只占一行(前端再加一道兜底,并在读取时就合并孪生行)。
- 同一目标经两条不同中转本来就是两个不同入口(身份串里带
relay_node_id),不会被合并——这条有专门用例锁住。 - 撞名只在**勾选(会渲染)**的入口之间比较:未勾选(墓碑)行上留着的旧名不再拦人。
- 面板把重名的两个字段就地标红并给出提示,不再只丢一句横幅。
文档
- design §10.2 写明别名判定口径(按字符串比、与节点/中转无关、只比勾选入口、同一份提交里重复出现的同一身份只算一条),并记「实现修订 2026-09-19」。
无 schema / 无 wire 变更
- 数据库 schema 未动(仍 15),
internal/protocol未动;订阅入口的存储与渲染语义除上述去重与校验范围外不变。 - VERSION = 0.1.9,镜像仅 linux/amd64。
fobe 0.1.8
fobe 0.1.8
自 v0.1.7 以来的 8 个提交:通知文案改人读版面并支持语言/时区、审计日志改标准页码分页、面板从 WS 连接源地址观测 NAT 探针的真实公网 IP、详情页新增 sing-box 状态卡与全量入站表格;另有订阅协议列、设置页版面重排与手机终端回看历史修复。
通知文案与语言/时区(design §15)
- 文本消息改人读版面:Telegram 与飞书共用的
notify.MessageText不再是「kind slug + RFC3339 + 原始 JSON」,改成 emoji 严重度 + 人类可读标题(node_offline→🔴 fobe · Probe offline,恢复态带(recovered),测试消息🔔 fobe · Test notification),下面接逐条人读字段:字节转 KB/MB/GB、时间转YYYY-MM-DD HH:MM:SS <时区>、mode/direction转 Inbound/Outbound、集群告警的nodes最多列 8 台后收成+N more。未知 kind 与未知字段仍原样列出(新告警类型不会静默消失)。 - 新增
notify.language(zh-CN/en-US,缺省en-US)与notify.timezone(IANA 名,缺省 UTC),在「设置 → 通知」里切换。两者都按每次投递惰性读取,改完下一条告警即生效、不用重启;写入严格校验(未知语言 →bad_notify_language、解析不了的时区 →bad_timezone),§17 导入同样丢弃这两类值——静默存下一个解不开的时区,看上去就像「已经设置好了」。投递侧读不出来时回落 UTC,绝不因为一个时区值挡住告警。 - 线上契约未动:通用 Webhook 的 JSON 线格式与
alerts.payload的存法一字未改(那是给机器读的),面板告警页仍按kind_*文案显示。
审计日志标准分页(design §4.4/§16)
- 审计页此前只拉最新 200 条,更早的记录在面板上没有任何入口。现在做标准页码分页:默认每页 20 条,底部下拉可切 20/50/100/200(记在 localStorage),并给出总数、「第 x / y 页」、首/中/末页码按钮与「跳至第 N 页」;
GET /api/audit?limit=&page=返回{entries, total, page, limit},越界页码在服务端收敛到最后一页并在响应里回显实际页(否则改小每页条数后手里的页码会指向空表)。 - 代价说清楚:页码与跳页必须有
COUNT(*) + OFFSET,而这张表从不清理——行数越多计数与深翻越慢,且窗口不稳(审计行随时在追加,翻页时可能重复或漏掉一两行)。keyset 游标能消掉后者但给不出页码与跳页,「标准分页」这个交互本身就标着这个价码。
探针真实公网 IP 观测(design §14)
- 阿里云等云厂商的公网 IP 是网关侧 NAT,网卡上只有 VPC 私网地址,agent 只枚举网卡所以永远报不出公网地址。现在改用面板已经看到的那条路:
/ws/agent升级时经 XFF 信任链解析连接源地址,当且仅当探针上报集里没有任何公网地址时,把该地址以scope=public并入node_ips并作为主 IP 指认的候选。 - 三条边界:私网/CGNAT 源地址不并入(不当公网冒充);探针一旦自己报出公网地址即恢复「上报集全归 agent 所有」,观测行随本次全量上报退场;手动主 IP 照旧优先(指认前查
manual_primary,查询失败按「可能有钉」保守处理)。 - 顺带收益:GeoIP 国旗对纯 NAT 探针也能自动命中了;重指认守卫同时放宽一条——存储的主 IP 仍在上报集内但不是当前候选、且无手动钉时也重指,不再保留被旧版写偏的值。无 wire 变更,观测行只活在服务端库里。
详情页(design §16)
- sing-box 运行状态卡片:sing-box 已启用时在基本信息网格里多一张状态卡(未安装/安装中/运行中/异常/已回滚),
last_error作副标题,异常与回滚在详情页一眼可见;版本号从「AnyTLS 端口」卡片移到这张卡。 - 全量入站表格:列出探针 config.json 里全部监听(多个 anytls、vless/socks 混合、one-sing.sh 接管的入站一视同仁),不再是「只显示面板托管的那一个端口」;数据源
node_singbox_inbounds,只含 port/type/tag/status、无凭据,旧 agent 未上报过 config 时整卡不渲染。 - 延迟卡片上移到「近 7 天指标」之前,且只在节点勾选了延迟目标时渲染——没开启测量的节点不再收到一张指向「去新建目标」的空卡片。
- AnyTLS 端口卡片删除:入站表格就位后它没有存在价值(表格已把每个监听一行行列全),
node_singbox.port列本身不动(卸载/重装回圈与订阅兜底仍读它)。入站表格按「参考资料」定位沉到页面最底部。 - 详情页头部新增「编辑」按钮直达
/settings/servers/{id};概览卡片 IP 加一键复制(与设置→服务器列表同款交互)。
订阅(design §10.2)
GET /api/subscriptions/{id}/entries每行新增protocols(该入口渲染成的线上协议 anytls/vless/…),订阅入口列表因此多一列协议。口径取自探针上报的 config.json(正在运行的配置是唯一准绳,不读管理态列):直连行 = 该端口入站的 type,中转行恒为anytls,文件未上报时由管理态端口+证书对兜底;该行标inbound_gone时protocols为空——运行中的配置已经不给答案了,面板不猜。
面板与终端
- 基础设置页轻卡片两两成对改田字布局,黑名单与会话两块合并进同一张安全卡(§16)。
- 手机触摸滚动终端回看历史(§11):xterm 6 起
.xterm-viewport成了空盒子、滚动由 JS 做,手机上没有任何可被浏览器 pan 的滚动盒,手指一拖就整页翻、历史永远回不去。现在由terminalTouchScroll.ts把单指竖直拖动按实测行高换算成scrollLines()(松手带衰减惯性),且只在缓冲区该方向真能滚时才接管手势(两端把拖动还给页面),只认单指——不用touch-action: none一刀切,那会连页面一起锁死。
升级注意
- 无数据库 schema 变更(仍为 15,
schema.sql本次未动);无 wire 变更(internal/protocol未动),旧版 agent 无需重装即可继续工作。 - 镜像只发 linux/amd64;探针按 §5.5 跟随服务端版本,本次会自更新一次(短暂掉线属预期)。
- 通知的时区渲染依赖镜像自带
tzdata(deploy/Dockerfile.server已装);不用容器部署时需保证宿主机有 tzdata,否则解析不到的时区会被拒写。 - 唯一必填环境变量仍为无(compose 默认拉
ghcr.io/fonlan/fobe)。
fobe 0.1.7
fobe 0.1.7
自 v0.1.6 以来的 6 个提交:流量与缴费周期支持按季、探针上/下线与通知投递写入审计、sing-box 安装不再把监听端口搬回旧端口,外加 AI 思考档位与面板细节两处修正。
流量与缴费周期支持按季(design §14/§15)
quota.CycleWindow把「季」当作 3 个月步长,与按月共用月末/闰日钳制;PATCH /api/nodes接受quarter。- 编辑页的流量周期与缴费周期两个下拉都增「按季」,缴费周期长度单位补「周期(季)」,「已续费」按 3 个月滚动。
审计(design §4.4/§7/§15)
- 探针上/下线写入审计:
node_online/node_offline只记状态跃迁 —— 90s 心跳窗口内的重连没有离开过 online,不记账;正文带agent_version与静默时长。 - 通知投递写入审计:
notify_sent/notify_failed按 (告警, 渠道, 结果) 去重(失败的告警每分钟重试,不去重会灌出一整天记录);被事件开关有意拦下的告警不记账。 - 顺带堵住凭据泄漏:投递错误里的完整 URL 会带上 Telegram bot token / 飞书 webhook token,现统一经
notify.SafeError脱敏后才写审计与日志。
sing-box(design §9.2/§9.3 修订 2026-09-18c)
- 修复:安装/更新不再把监听端口搬回旧端口。 端口此前取自记账列
node_singbox.port,而编辑器搬端口不会更新那一列。实测后果有二:面板装过后在编辑器把 22039 改成 22040,再点安装会把监听搬回 22039,而该端口下已无凭据 ⇒ 静默 mint 一份新密码,端口与凭据同时变,所有客户端立刻失效;纯 one-sing.sh 的发现型节点该列恒 0 ⇒ 安装落到随机端口,脚本入站一并消失。 - 现在端口按「显式请求 → 探针上报文件里那条 anytls 的
listen_port→ 记账列 → 随机」解析,安装/更新与启动模板同步共用同一支逻辑;凭据查找同步改为按序多端口(正在重建的端口优先、旧记账端口兜底),两者都没有才生成。 - 新增 4 条回归测试覆盖(编辑器搬移后安装、发现型节点按上报端口、全新节点仍随机、启动同步不搬端口)。
AI 助手(design §12.1/§12.5 修订 2026-09-18g)
- 思考档位下拉统一按所选模型实际支持的档位填充:此前设置页列的是全局词表,能选到该模型根本不支持的档位、选完被服务端静默吃掉;终端页侧栏也不会预选已配置的默认档位。
- 现在档位集合 = 该 (provider, model) 的有效档位;面板预选默认档位(模型不支持则留「默认(不指定)」),设置卡片按所选模型填充,不支持的档位既不显示也不写回。服务端「未指定即补默认档位」的回退一字未改。
面板
- 设置 → 服务器列表的「主 IP」列加复制按钮(§16)。
升级注意
- 无数据库 schema 变更(仍为 15,本次
schema.sql只改了枚举注释);按季周期是新增取值,降级回 v0.1.6 时该栏位不再生效。 - 镜像只发 linux/amd64;探针按 §5.5 跟随服务端版本,本次会自更新一次(短暂掉线属预期)。
- 唯一必填环境变量仍为无(compose 默认拉
ghcr.io/fonlan/fobe)。
fobe 0.1.6
fobe 0.1.6
自 v0.1.5 以来的 14 个提交:AI 助手重做(工具集换成终端三件套)、AI 供应商/模型管理、Web 视觉重铸到品牌色板,外加若干延迟测量与订阅入口的修正。
AI 助手(design §12)
- 工具集换成终端三件套:
run_shell/send_keys/read_terminal,删掉 sing-box 四个工具与tail_logs。助手改为直接操作操作员的终端,观察结果全部取自浏览器缓冲区 (§12.7)。 - 供应商与模型管理(§12.1/§12.5):新增
ai_providers/ 模型表,模型行全局唯一、按 provider 记录可达性;无 slug 的 provider 按 model id 匹配 models.dev 元数据;默认模型扩成三件套并加默认思考档位。 - 终端页:全宽 + 可拖拽分栏,删上下文框与「本轮完成」横幅,Enter 发送,发送行合并模型与档位下拉。
- transcript:一轮一气泡、用户/助手气泡底色分开、助手正文 Markdown 实时渲染;侧栏加号新建会话、对话纵向滚动并自动锁底。
面板
- 视觉重铸到品牌色板(§16):靛蓝/薄荷双声部、玻璃顶栏、登录页脉搏线、状态点心跳。
- 图表图例可点击隐藏/显示指标,y 轴随可见序列重算,全部隐藏不丢图例。
- 延迟目标支持编辑(§13):端点一变,同一事务里清掉该目标的样本并即时下发新定义;只改名字保留历史。
- 订阅直连入口按入站拆行(§9.3/§10.2):一台服务器多条入口分行显示、按端口区分,可分别勾选与命名。
- 改 sing-box 入站端口后新端口立刻出现在面板,不再需要手动刷新。
修复
- AI 工具 schema 的
required不再序列化成null(严格网关会 400 → 每回合 upstream_error)。 - 空助手轮不再落库/上行,修会话被 400 永久钉死。
run_shell清理键吞掉命令首字符——Ctrl+C 后补一个牺牲空格。
升级注意
- 数据库 schema 13 → 15:首次启动自动快照后迁移,仍是纯增量(新增表/列),旧数据不回改。降级回旧版本会有告警。
- 镜像只发 linux/amd64;探针按 §5.5 跟随服务端版本,本次会自更新一次(短暂掉线属预期)。
- 唯一必填环境变量仍为无(compose 默认拉
ghcr.io/fonlan/fobe)。
fobe 0.1.5
fobe 0.1.5 — 修复版本:延迟测量改了目标即时生效(真机上「加了目标却永远不出折线」的两个根因)、btrfs 数据卷不再被误判 inode 耗尽而拒绝装 sing-box,外加四处窄屏与标签细节。
升级方式不变:
docker compose pull && docker compose up -d(或改image:钉ghcr.io/fonlan/fobe:0.1.5)。无 schema 变更(仍为 13);协议仅新增一个可选字段,旧 agent 忽略、向后兼容。
延迟测量:目标变更即时下发到在线探针(§13 修订 2026-09-17k)
根因有两层,修完第一层才看见第二层。
① 下发链路只挂在握手上:测量目标此前只随 hello_ack(WS 握手)下发,面板改完 latency_target_ids 后什么都不推——稳定长连接的探针要等下一次重连(可能永远不发生)才换目标列表。现在编辑保存后立即对该节点下发携带目标列表的 latency_config(hub.PushLatencyTargets,与 hello_ack 同源读 TargetsForNode,两个载体不会不一致);删除全局目标同样即时推给仍选中它的节点(先查 NodeIDsForLatencyTarget 再删,FK 级联会抹掉线索);离线探针由下次 hello_ack 兜底。字段语义:latency_config.targets 为 null(全局频率广播的形态)=目标不变,非 null 空数组=清空、真的停测——所以该字段不能加 omitempty,否则「清空」永远到不了 agent。旧 agent 忽略未知字段不受影响。
② 全丢包的目标在详情页"隐身":下发修好后真机仍然"没有新折线"——目标是防火墙拦 ICMP 的地址,107 个样本全部 icmp_ms=-1, loss=1,而前端对没有有效 RTT 点的目标不画线,于是「在测但全丢」与「根本没在测」在面板上无法区分。现在详情页对画不出线的目标显式标注:有样本标「全部丢包 · 目标无回应」(提示换 tcp 类型),无样本标「暂无样本 · 探针离线或尚未开始」。
新增测试:httpapi 假 agent e2e ×2(编辑推送 + 清空语义、删除推送)、agent 单测 ×1(null 不动 / 替换 / 清空三分支)。
agent:btrfs 数据卷不再被误判 inode 耗尽(§5.4 修订 2026-09-17e)
btrfs 等动态分配 inode 的文件系统 statfs 上报 f_files = f_ffree = 0(df -i 显示 0 0,是「无 inode 预算」而不是「用尽」),原判定把 8 GiB 空闲的 btrfs 数据卷判成 0 inode 可用而拒绝安装,面板报 insufficient disk space: 8375 MiB / 0 inodes free。
现在 statfsFree 一并返回总 inode 数:总数为 0 视为该文件系统不跟踪 inode、跳过 inode 半闸(字节闸不变),错误信息也不再展示无意义的 inode 数字;真实耗尽的 ext4/f2fs(总 inode > 0 且空闲 < 100)照旧拒绝。其余平台落回 fail-open stub。
界面细节
- 概览页到期标签按剩余天数分档配色(93bb5ba):≤7 天(含已逾期)红色提醒,>7 天绿色放行;分档判据就是剩余天数本身。
- sing-box 入站表状态列不再折行(88a1400):状态 chip 是 CJK 短词,auto 表格布局会把它压到单字宽折成两行;
td加nowrap,超出宽度交给.table-wrap横向滚动。390px 竖屏实测 chip 单行 56×22。 - 「表格即卡片」四页补上滚动容器(§16 修订 2026-09-17d):设置→服务器列表、审计日志、告警、延迟测量四页把卡片外观直接放在
<table>上,其overflow-x: auto从未生效(overflow 不适用于表格盒),宽行在 390px 竖屏下把表格顶到 min-content 宽(实测页面scrollWidth587 > 390)导致整页左右平移。现在卡片与滚动由外层.card.table-card接管,页面横向溢出归零、桌面端无滚动条;代价与前同——竖屏在卡片内横向滚动看最后一列。
升级注意
- 数据库 schema 无变化(13),从 0.1.2 / 0.1.3 / 0.1.4 均可直升。
- agent 本版有代码改动(空间闸门),版本号随服务端走:探针会按 §5.5 自更新一次(短暂掉线属预期;一两台探针可用
FOBE_AGENT_UPDATE_STAGGER=1s把错峰压成立即)。
fobe 0.1.4
fobe 0.1.4 — 面板体验小版本:订阅入口选择器表格化、品牌栏显示服务端版本徽章;带上 17j 的订阅命名与 Clash 证书修正。
升级方式不变:
docker compose pull && docker compose up -d(或改image:钉ghcr.io/fonlan/fobe:0.1.4)。无 schema 变更、无协议变更。
订阅入口选择器:表格样式
选择器由「按节点分组的卡片」改为扁平表格:一行一个候选入口,第一列复选框(表头带全选/半选态),节点 / 入口 / 来源(规则备注)/ 订阅内名称 / 状态各占一列。绑定、墓碑、自动落账语义零变化:不可渲染的入口改用整行置灰表示(替代虚线框),仍可勾选,保存时照常全量回传。zh/en 新增列头与全选文案。
其他
- 品牌标题旁小字显示服务端版本号徽章(4f94610)。
- 订阅名去掉「协议:端口」后缀(17j):alias 逐字生效,重名交给渲染器
-2/#2兜底。 - Clash anytls 改
skip-cert-verify: true(17j):mihomo 的 anytls schema 没有ca-str,证书 pinning 在该格式无落地;sing-box JSON 侧保持 pinning 不变。 - compose 本地构建占位版本号去
compose-前缀(裸哈希,f69b2a1)。
升级注意
- 数据库 schema 无变化,从 0.1.2/0.1.3 均可直升。
- agent 自 0.1.3 起无代码改动,但版本号随服务端走:探针会按 §5.5 跟随自更新一次(短暂掉线属预期;一两台探针可用
FOBE_AGENT_UPDATE_STAGGER=1s把错峰压成立即)。
fobe 0.1.3
fobe 0.1.3 — 探针上已有的 sing-box(one-sing.sh 装的)从此「被看见」:状态显示「已识别」,A→B 中转入口回到订阅选择器。
升级方式不变:
docker compose pull && docker compose up -d(或改image:钉ghcr.io/fonlan/fobe:0.1.3)。无 schema 变更。
sing-box:发现即已识别(§9.3 实现修订 2026-09-17i)
0.1.2 的「监听平等」模型之后,发现型(one-sing.sh)节点的 node_singbox.port / cert_pem 恒为空,但订阅入口选择器、中转候选与自动落账判定还在读这两列——结果是:A 的 nftables 转发指向 B 的 anytls 入站时,「B 经 A」这条中转入口在选择器里不出现,而渲染端读的是上报文件,判定与渲染两边打架。本版把判定拉回与渲染同源:
- 中转落点端口 = 上报配置里的 anytls 入站集合(面板装的或脚本装的都算;管理态端口只作"文件还没上报"的兜底)。中转候选、自动落账、入口可用性、直连遮蔽告警全部换到同一来源。
- 中转出站按 nft 规则的目的端口挑入站 pin 证书:监听平等后同一节点多条 anytls 各带各的证书,原来一律 pin 第一条——规则指向第二条时客户端会 pin 到永远握不上手的证书。规则已删时回退旧行为。
- 编辑页状态 Tile:面板从未安装、但探针上发现 sing-box 时,状态显示「已识别(本机实例)」(zh/en),「尚未安装 sing-box」提示不再误导;「未安装」从此只描述面板管理的那套。
测试
新增 TestRelayEntryFromDiscoveredConfig(双 one-sing 节点 A→B:中转入口出现、自动落账、直连入口可用)与 TestRelayPinsTheInboundTheRuleLandsOn(双 anytls 入站按规则目的端口 pin 对应证书);全部既有订阅/入口测试不动而过。
升级注意
- 探针 agent 无协议改动,服务端升级后 agent 不需要(也不会)自更新。
- 生产面板从 0.1.2 直升 0.1.3 无中间步骤;数据库 schema 无变化。
fobe 0.1.2
fobe 0.1.2 — sing-box 入口规则平等 + 每入口独立凭据;凭据丢了也能救回探针。
升级方式不变:
docker compose pull && docker compose up -d(或改image:钉ghcr.io/fonlan/fobe:0.1.2)。数据库 schema 11 → 13,两步纯增量迁移(reg_tokens.node_id列、node_singbox_inbounds表),启动前自动快照。
凭据重发:节点绑定的注册 token(§4.2 实现修订)
探针的 /etc/fobe-agent/config.json 一旦被覆盖、清空或随机器搬迁丢失,服务端只剩旧 secret 的哈希——此前没有任何入口能把凭据发回去,「删掉节点再添加」是唯一出路,还会连带丢掉挂在节点上的端口转发与账单(真机上刚丢过 7 条转发 + 1 条账单)。现在:
- 「重装 / 重发凭据」按钮常显(设置 → 服务器 → 编辑的「Agent 自更新」卡片,二次确认):原先只在 agent 不支持自更新时出现,而凭据丢了的节点恰恰是离线、caps 陈旧的那种,入口正好被藏掉。签发一张绑定该节点的注册 token(TTL 30 分钟、单次有效),安装命令在探针上重跑即可换发新凭据——节点、端口转发、账单、设置原样保留。
- 注册判定按绑定分流:machine_id 相符 → 免旧 secret 换发凭据(审计
node_reissued);machine_id 未注册(连 machine-id 一起丢了 / 换主机)→ 换发凭据并接管新 machine_id;machine_id 已属于别的节点 →409 machine_mismatch;被绑定节点已删除 →404 node_not_found。绑定 token 永不新建节点。 - 代价(有意接受):绑定 token 就是该节点的一次注册授权(bearer)——谁拿到这条命令谁就能顶掉该节点的凭据。因此入口有二次确认,签发(
agent_reinstall_token)与使用(node_reissued)都写审计。 - 通用 token 的两条老路径原样保留(旧 secret 有效 →
node_rebound;无凭据 →409 duplicate_machine)。
sing-box:入口规则平等,「配置唯一真相」收尾
- 「接管」动作删除(2026-09-17d):探针本机已有的监听一上报就自动成为本节点入口(取文件里最后一个 anytls、只认 10000–60000 端口段、认定后永不移动),操作员什么都不用点。面板不再有勾选框与「接管勾选的入站」按钮——要改入站就在规则表读-合并-写,或在探针上改完点「从探针刷新」。纯账面记账:不写配置、不下发期望帧、不重启。
- 全局 anytls 密码取消(2026-09-17e):密码属于入站——每个入口创建时现生成一份 16 位
[A-Za-z0-9](vless 为 v4 UUID),存在节点自己的配置里;重装 / 改端口 / 订阅渲染都读回同一份,绝不新生成。一个凭据泄漏不再要求全场轮换、打断所有客户端。老节点零迁移:它们的密码本来就烤在自己的config.json里。 - 每条入站自己的状态(2026-09-17f/g/h):规则表新增状态列——新增的规则立即可见为「添加中」,只有 agent 本机 TCP 确认对应监听后才变「运行中」;删除改为显示「删除中」,探针上报不再含该端口才消失(回滚时如实保持「删除中」,配合节点级最近错误解释原因)。不再有「本节点入口」特权行:所有可渲染入站统一按
协议:端口命名,订阅用各自端口上报的凭据与证书渲染,缺证书的条目跳过而不借用别入口的。每端口状态持久化在node_singbox_inbounds(schema 13)。 - 闸门③核对配置自身的监听,并引入基线语义:健康判定从记账端口
node_singbox.port换成新配置声明的全部listen_port(去重),且只要求「改前本机可连的监听、改后仍可连」——一条粘死 / 从未可连的端口不再让这台节点的所有编辑 30s 超时回滚死循环。代价(有意接受):基线路径下从未可连的监听不参与健康判定,这类存量问题由「添加中」状态与singbox_down告警负责。
面板
- 到期判定改为直接比较时间戳:过期当天不再显示成「0 天后到期」,非未来一律贴红色「已过期」标签。
- 编辑服务器页费用输入框右侧新增**「已续费」**:按当前缴费周期、以原到期日为基准整周期顺延下次到期日(月末 / 闰日钳制不逐次漂移),只改表单字段、保存才落库(design §15 规格同步修订)。
- 两张规则表的窄屏形态修复:sing-box 入站表控件补面板样式并包进
.table-wrap(390px 竖屏横向溢出 119px → 0),端口转发规则表短字段 nowrap、备注单行省略、行内按钮不再被压成两行。 - 删掉「凭据丢了也能…」与「本机已有的入站自动属于…」两段面板长文案;探针自报不支持自更新时仍保留短提示。
升级注意
- 存量探针会自动跟随到 0.1.2(§5.5:两端注入同一个版本号,探针下载 → sha256 → 旁路自检 → 原地换入)。曾在
Detect()误判期装过的探针(caps 报systemd=false,fallback=true)必须人工重装一次(本版起「重装 / 重发凭据」入口常显)才进入自动跟随,此后的版本都不用手动。 - 镜像仍只发 linux/amd64(agent 产物与 sing-box 缓存按设计就是这个平台)。compose 里
image: ...:latest若本地已有同名旧镜像不会自动拉,换版后先docker compose pull。
镜像
docker pull ghcr.io/fonlan/fobe:0.1.2
三个 tag 0.1.2 / v0.1.2 / latest 指向同一 manifest,仓库公开 ⇒ 匿名即可拉取。
fobe 0.1.1
fobe 0.1.1 — sing-box 配置的所有权交还操作员,附一个 GeoIP 国旗修复。
升级方式不变:
docker compose pull && docker compose up -d(或改image:钉ghcr.io/fonlan/fobe:0.1.1)。数据库 schema 10 → 11,纯增量迁移,启动前自动快照。
sing-box:config.json 成为唯一真相(§9.3 实现修订 2026-09-17b)
上一版让 fobe「接管一次、之后整份重写」,代价落在操作员身上:他继续用 one-sing.sh 的 jq 加入站,下一次收敛(最多 60s)就把它抹掉。本版把这个代价去掉:
- 探针盘上的
/etc/one-sing/config.json是唯一真相:agent 每轮(60s + 启动 + 面板刷新命令)只读扫描,文件变了就上报原文;服务端解析后经Cryptor加密存档,不再按模板整份重写。 - 编辑 = 读-合并-写:
PUT /api/nodes/{id}/singbox/config带reported_hash,对不上回409 config_changed;只改被编辑的字段,未建模的选项(sniff、padding_scheme等)原样保留,缺省凭据继承文件里的值。 - 手工
jq加的入站立刻可见:订阅直接按最近上报的文件渲染;新增POST /api/nodes/{id}/singbox/refresh,不必等下一轮扫描。anytls 证书内容由 agent 按端口上报(文件里只写certificate_path,客户端要的是 PEM)。 - 面板:节点编辑页的入站表格可改端口 / tag / 凭据,可新增与删除;原「接管」退化成一次凭据留空的编辑(服务端写入面板自己的全局密码)。
sing-box:发现并接管 one-sing.sh 装好的实例(§9.3 实现修订 2026-09-17)
纯 one-sing.sh 的探针在面板上一直是「未安装」——State.Singbox 只在声明过期望版本后才上报;而一点安装就整份重写配置,脚本加的 anytls / VLESS / SS / Socks 入站当场消失。
- 识别:探针每轮只读扫描本机布局(二进制 / 配置 / 单元 / 进程),把
config.json原文随 state 帧上报,不受期望状态开关限制;面板显示运行版本、入站与端口,凭据不出库(面板只拿摘要,快照加密落库)。 - 接管
POST /api/nodes/{id}/singbox/adopt:脚本的 anytls 端口成为面板入站端口(用全局密码),其余入站作为extra_inbounds与模板一起下发;订阅为每台被接管节点展开多条出站,各用自己凭据,REALITY 公钥由私钥现场推导(crypto/ecdh);版本优先用探针正在跑的,缓存里没有则拒绝接管且不写任何东西。 - 白名单是硬约束:只有
allowedInboundKeys/allowedTLSKeys/allowedRealityKeys里的键能进config.json—— 闸门①(sing-box check)是 live 探针上唯一的防线,真机上踩过两次服务级事故。
修复
- 国旗真正跟随主 IP(§14 实现修订 2026-09-16b):国别判定原本只存在于「重新指认主 IP」那一个分支,于是 hello 路径从不解析国别(新探针一辈子一个灰点)、手动主 IP 只改地址不重算、「查不到就保留旧值」叠加成内网主 IP 留着上一个地址的旗子。现在 hello 与 state 共用一段逻辑,判定顺序 = 主 IP → 上报集里的公网地址(IPv4 优先);手动主 IP 与「清空国家」立刻重算。
- 编辑页不再把自动识别的国别静默钉成手动:此前保存任何其它字段都会提交
country_code并置country_manual=1,自动识别就此失效。只在操作者真的动过国家输入框时才提交;已被钉住的节点在编辑页点「清空国家」即可恢复自动。
升级注意
- 存量探针会自动跟随到 0.1.1(§5.5:两端注入同一个版本号,探针下载 → sha256 → 旁路自检 → 原地换入)。曾在
Detect()误判期装过的探针(caps 报systemd=false,fallback=true)必须人工重装一次才进入自动跟随,此后的版本都不用手动。 - 镜像仍只发 linux/amd64(agent 产物与 sing-box 缓存按设计就是这个平台)。compose 里
image: ...:latest若本地已有同名旧镜像不会自动拉,换版后先docker compose pull。
镜像
docker pull ghcr.io/fonlan/fobe:0.1.1
三个 tag 0.1.1 / v0.1.1 / latest 指向同一 manifest,仓库公开 ⇒ 匿名即可拉取。
自 v0.1.0 的完整变更
feat(singbox): 配置改为编辑器模型——探针的config.json是唯一真相,与one-sing.sh共存feat(singbox): 发现并接管one-sing.sh装的本机 sing-box(入站 / 端口 / 凭据原样保留)fix(geoip): 国旗真正跟随主 IP——hello 也判国别、内网主 IP 退回公网地址chore(release): web 包版本号对齐 0.1.1
发布前本地预检全绿:gofmt(空)、go vet ./...、go test ./...、cd web && npm run build,以及本地 linux/amd64 镜像构建与容器烟测。