Skip to content

Releases: fonlan/fobe

fobe 0.1.13

Choose a tag to compare

@fonlan fonlan released this 20 Sep 09:23

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.ServerReadTimeout / 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 provider base_url 与通知 webhook 拒绝 link-local(含云元数据 169.254.169.254)/未指定/组播,放行 loopback 与 RFC1918,并逐跳复检重定向;geoip.url 补入同一守卫(写侧与下载侧,302 劫持引下载器进内网被拒)。上游响应体不再进入面板可见的错误文本。
  • 主密钥解不开的密钥不再静默失效:启动记 error + secret_unreadable 审计,GET /api/settingsunreadable_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);受保护网段的失败也推送但不计数(payload protected: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.3ff05::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.InstallAgentfetchChecksum 委托同包 fetchExpectedSHA256,同一二进制里不再有两份 sidecar 解析器。
  • httpapi 新增 writeStoreErr / decodeReq helper(替换 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 样板;新增 ErrorStatechartShared.tsx 共享件;删 26 个零引用 i18n 键与若干死代码。

无 schema / 无 wire 变更

  • 数据库 schema 未动(仍 16),internal/protocol 未动,无新增必填环境变量。
  • VERSION = 0.1.13,镜像仅 linux/amd64。

fobe 0.1.9

Choose a tag to compare

@fonlan fonlan released this 19 Sep 09:43

修复:订阅入口「两个入口用了同一个别名」的误报

选择订阅入口时,若某两条 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

Choose a tag to compare

@fonlan fonlan released this 19 Sep 07:03

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.languagezh-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_goneprotocols 为空——运行中的配置已经不给答案了,面板不猜

面板与终端

  • 基础设置页轻卡片两两成对改田字布局,黑名单与会话两块合并进同一张安全卡(§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 跟随服务端版本,本次会自更新一次(短暂掉线属预期)。
  • 通知的时区渲染依赖镜像自带 tzdatadeploy/Dockerfile.server 已装);不用容器部署时需保证宿主机有 tzdata,否则解析不到的时区会被拒写。
  • 唯一必填环境变量仍为无(compose 默认拉 ghcr.io/fonlan/fobe)。

fobe 0.1.7

Choose a tag to compare

@fonlan fonlan released this 18 Sep 15:41

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

Choose a tag to compare

@fonlan fonlan released this 18 Sep 13:56

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

Choose a tag to compare

@fonlan fonlan released this 17 Sep 16:41

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_confighub.PushLatencyTargets,与 hello_ack 同源读 TargetsForNode,两个载体不会不一致);删除全局目标同样即时推给仍选中它的节点(先查 NodeIDsForLatencyTarget 再删,FK 级联会抹掉线索);离线探针由下次 hello_ack 兜底。字段语义:latency_config.targetsnull(全局频率广播的形态)=目标不变,非 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 = 0df -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 表格布局会把它压到单字宽折成两行;tdnowrap,超出宽度交给 .table-wrap 横向滚动。390px 竖屏实测 chip 单行 56×22。
  • 「表格即卡片」四页补上滚动容器(§16 修订 2026-09-17d):设置→服务器列表、审计日志、告警、延迟测量四页把卡片外观直接放在 <table> 上,其 overflow-x: auto 从未生效(overflow 不适用于表格盒),宽行在 390px 竖屏下把表格顶到 min-content 宽(实测页面 scrollWidth 587 > 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

Choose a tag to compare

@fonlan fonlan released this 17 Sep 12:33

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

Choose a tag to compare

@fonlan fonlan released this 17 Sep 08:43

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

Choose a tag to compare

@fonlan fonlan released this 17 Sep 04:47

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

Choose a tag to compare

@fonlan fonlan released this 16 Sep 17:19

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.shjq 加入站,下一次收敛(最多 60s)就把它抹掉。本版把这个代价去掉:

  • 探针盘上的 /etc/one-sing/config.json 是唯一真相:agent 每轮(60s + 启动 + 面板刷新命令)只读扫描,文件变了就上报原文;服务端解析后经 Cryptor 加密存档,不再按模板整份重写
  • 编辑 = 读-合并-写PUT /api/nodes/{id}/singbox/configreported_hash,对不上回 409 config_changed;只改被编辑的字段,未建模的选项(sniffpadding_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 镜像构建与容器烟测。