运行 hub
hub 是 ANet 网络的服务端。节点向 hub 注册其身份和已签名的 Agent 卡片,通过 hub 为其他节点存放封闭信封,并通过 hub 结算额度付款。hub 中继它无法打开的信封,不保存任何任务内容。相关概念见 Hub 与准入;本页涵盖 anet-hub 与 anet-hub-admin 两个程序的安装、启动和运维。
组成部分
ANetHub 提供两个程序,各有自己的数据目录;运维面读取公开 hub 的数据目录。
程序 |
职责 |
默认监听地址 |
|---|---|---|
|
公开 hub:注册表、中继、联邦、结算、评价以及内嵌的网页界面。运维子命令( |
|
|
运维面:位于 |
|
两个程序均为静态二进制文件(纯 Go,不依赖 C 库)。节点需要 anet 0.3.0 或更高版本才能使用本版本的 hub;anet-hub -version 会输出 hub 的线路协议版本以及所要求的节点版本。
$ anet-hub -version
anet-hub 0.3.0 (wire 3, nodes need anet >= 0.3.0; commit 1a2b3c4, built 2026-10-16T00:00:00Z)
构建
从 ANetHub 仓库的检出目录构建:
$ bash scripts/build.sh # anet-hub and anet-hub-admin, with the commit stamped in
$ SKIP_WEBUI=1 bash scripts/build.sh # Go only; embeds the committed web interface
不使用该脚本时,直接用 Go 构建也能得到同样的程序:
$ CGO_ENABLED=0 go build -o anet-hub ./cmd/anet-hub
$ CGO_ENABLED=0 go build -o anet-hub-admin ./cmd/anet-hub-admin
联邦默认已编译在内。仅在单一组织内部使用、需要完全去除联邦的 hub,使用 no_federation 标签构建:
$ CGO_ENABLED=0 go build -tags no_federation -o anet-hub ./cmd/anet-hub
no_federation 构建不含 /fed/v3 路由,也没有 clear 子命令。/stats 会报告正在运行的 hub 中已编译的模块。
启动 hub
hub 监听回环地址,通过终结 TLS 的反向代理对外提供服务(参见 TLS 与反向代理)。
$ anet-hub --addr 127.0.0.1:8088 \
--data /var/lib/anet-hub \
--public-url https://hub.example.com
首次启动时,会在数据目录中创建 hub 自身的身份(AID 与密钥事件日志)和评审标签密钥,并输出它们的标识:
anet-hub identity: bafyrei...
hub: made the review tag key (tag_key_id 1)
anet-hub review tag key: tag_key_id 1
anet-hub modules: federation
anet-hub 0.3.0 (commit 1a2b3c4) listening on 127.0.0.1:8088 (data: /var/lib/anet-hub)
hub 身份发布在 GET /hub/identity。对等 hub 会固定它,hub 签发的结算收据也据此验证。尚未设置恢复口令的 hub,每次启动都会输出提示;参见 hub 身份与恢复口令。
程序在收到 SIGINT 或 SIGTERM 时停止,并在五秒内关闭监听。
命令行选项
选项可以带一个或两个前导短横线。
选项 |
默认值 |
含义 |
|---|---|---|
|
|
HTTP 监听地址。由反向代理置于 hub 之前时,请使用回环地址。 |
|
|
数据目录(SQLite 存储、hub 身份、密钥)。运行 hub 的账户必须对其具有写权限。 |
|
空 |
hub 的公开基础 URL。它在 |
|
|
输出版本行并退出。 |
|
96 MiB |
|
|
|
单个收件方可排队的未投递信封数。超出后的发送收到 |
|
1 GiB |
单个收件方可排队的未投递信封总字节数。超出后的发送收到 |
|
|
未投递信封在被删除之前的保留时长。最大值为 14 天 22 小时 55 分钟;设置更大的值会使启动中止,因为保留过久的信封可能比唯一能打开它的密钥存活更久。 |
|
48 MiB |
一次 |
|
|
|
|
|
|
发送速率限制、目录查询限制以及其他封闭发送方设置均为固定值,不是选项。需要更大邮箱份额的收件方,可通过 anet-hub quota 获得(参见 单个收件方的配额)。
早期版本中执行某项操作的选项(-grant、-invite-new、-clear 等)已改为子命令。仍使用这些选项的命令行会被拒绝,退出状态为 2,并提示替代方式:
已移除的选项 |
替代方式 |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
无;账本仅保留 14 天,因此不存在需要修复的缺口 |
|
无;这些均为固定值 |
其他版本的数据目录
0.3.0 版不迁移任何数据。0.2 或更早版本写入的数据目录会被拒绝,退出状态为 1,目录保持不变;请将该目录移开后重新启动,以创建新的身份。由更新版本的 anet-hub 写入的数据目录同样会被拒绝;请升级程序,不要删除该目录。同一数据目录上的第二个 anet-hub 进程,在第一个进程运行期间会被拒绝。
TLS 与反向代理
hub 不终结 TLS。应让它在回环地址上运行,并在前面放置反向代理。代理须满足三项要求。
请求体限制。 代理的限制先于 hub 的限制生效。请将
client_max_body_size设置为不小于--relay-max-envelope的值。客户端地址。 hub 按客户端地址对注册限速,并执行按网络划分的配额。它从
X-Real-IP头读取该地址。未设置此头的代理会使所有客户端共用代理自身的桶。不保留访问日志。 请求行中会出现 Agent(
/agents/<aid>/...)。保留访问日志,会在 hub 删除信封很久之后,仍能还原出哪个地址联系过哪个 Agent。请关闭访问日志,并将错误日志级别保持为crit。
下面的 server 块同时满足这三项要求,可作为起点。此处用 ACME 客户端签发证书,其他来源的证书同样可用。
server {
listen 80;
listen [::]:80;
server_name hub.example.com;
access_log off;
error_log /var/log/nginx/hub.error.log crit;
location /.well-known/acme-challenge/ { root /var/www/certbot; }
location / { return 301 https://$host$request_uri; }
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name hub.example.com;
ssl_certificate /etc/letsencrypt/live/hub.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/hub.example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
access_log off;
error_log /var/log/nginx/hub.error.log crit;
add_header X-Content-Type-Options nosniff always;
add_header X-Frame-Options SAMEORIGIN always;
add_header Referrer-Policy strict-origin-when-cross-origin always;
location / {
proxy_pass http://127.0.0.1:8088;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
client_max_body_size 96m;
proxy_read_timeout 300s;
proxy_send_timeout 300s;
}
}
明文 HTTP 不适用于可从互联网访问的 hub。部分网络会篡改未加密的流量,而节点会把注册和邮箱请求发往所获得的地址。
从另一台主机验证安装:
$ curl -s https://hub.example.com/healthz
{"built_at":"2026-10-16T00:00:00Z","commit":"1a2b3c4","status":"ok","version":"0.3.0"}
$ curl -s https://hub.example.com/stats
{"agents":0,"federated_agents":0,"tasks_completed":0,"reviews":0,"avg_rating":0,"modules":["federation"]}
之后节点通过 anet hub-register https://hub.example.com 加入(节点报告的加入状态见 Hub 与准入)。
准入
准入决定哪些身份可以注册。它有两种模式,可通过 anet-hub admission 查看和更改。
closed新 hub 的模式。只有位于准入名单上的身份,以及在开放注册窗口期间到来的身份,才可注册。
open任何身份通过三道关卡后均可注册:22 位的工作量证明、按客户端网络计的每日配额(IPv6 客户端按 /32 计数),以及 72 小时的试用期。注册本身不支付额度;完成试用期的账户获得 20 个试用额度,并受每个网络的上限和整个 hub 每日预算的限制。这些值是固定的,在所有 hub 上相同。位于同一地址之后的组织,应改为按 AID 准入身份。
$ anet-hub --data /var/lib/anet-hub admission
Sign-up: admission list only (anet-hub admit).
$ anet-hub --data /var/lib/anet-hub admission open
Sign-up: open (proof of work, daily quota per network, 72-hour probation).
更改模式只影响新的注册。已注册的节点保持注册。
准入模式可在 hub 首次启动之前设置,例如在容器镜像或部署脚本中:anet-hub --data <目录> admission open --init 在一个已存在的空数据目录中创建 hub 数据并设置模式。不加 --init 时,该命令拒绝没有 hub 数据的目录。
准入身份
尚未获准入的节点会输出运维人员须运行的命令(anet hub-register 报告状态 hub_pending_admission 并在后台重试)。运维人员准入节点所报告的 AID:
$ anet-hub --data /var/lib/anet-hub admit bafyrei... --label "build host 3"
Admitted bafyrei...; it finishes joining on its own once it runs anet hub-register.
其他形式:
$ anet-hub --data /var/lib/anet-hub admit bafyrei... --grant 50
Admitted bafyrei...; the 50-credit grant is paid when it first registers.
$ anet-hub --data /var/lib/anet-hub admit --file aids.txt
Admitted 12 AIDs from aids.txt; 3 of them are already registered.
--file 每行读取一个 AID。出现无效 AID 的行时,导入中止,文件中的任何 AID 都不会被准入。每个 AID 的发放只支付一次;再次准入同一 AID 不会重复支付。该命令可在 hub 运行期间使用。
开放注册窗口
开放注册窗口可在限定的时间和数量内准入新到者,而无需列出其 AID。窗口最长持续 7 天,最多准入 10000 个身份。通过窗口加入的账户仍须经过工作量证明和 72 小时试用期。
$ anet-hub --data /var/lib/anet-hub admit --window 2h --max 50 --label workshop
The sign-up window is open until 10-16 14:00 (at most 50; proof of work required; new accounts are on probation for 72 hours).
$ anet-hub --data /var/lib/anet-hub admit --window off
The sign-up window is closed (31 joined through it).
同一时间只能开启一个窗口。时长写作 Go 时长格式(90m、2h),或以天计(3d、1d12h)。
查看与撤销
$ anet-hub --data /var/lib/anet-hub admissions
Sign-up: admission list only (anet-hub admit).
bafyrei... 50 build host 3
$ anet-hub --data /var/lib/anet-hub admissions --json
$ anet-hub --data /var/lib/anet-hub admissions revoke bafyrei...
Revoked the admission of bafyrei... and deregistered it; its 50 credits stay on the ledger.
列表的每一行包含 AID、发放额度(末尾的 * 表示已支付)和标签,以制表符分隔,因此可以编辑该列表并回传给 admit --file。撤销会移除准入,并在账户已注册时注销它;其余额保留在账本上。在 open 模式的 hub 上,该身份可以再次注册;撤销并不等于封禁。
已注册但连续七天未收取邮件的账户,会被自动注销。
单个收件方的配额
每个收件方的邮箱中,都预留了一部分份额,用于接收来自非联系人发送方的消息。默认值为每分钟 30 次首次联系、500 条非联系人消息和 64 MiB 非联系人数据。运维人员可以为某个 AID 调高这些值,例如为广受欢迎的公开能力调高;低于默认值的取值会被拒绝。
$ anet-hub --data /var/lib/anet-hub quota bafyrei... --non-contact-msgs 2000
--first-contacts-per-min 30 (default)
--non-contact-msgs 2000 (default 500)
--non-contact-mib 64 (default)
不带选项时,该命令显示当前值。hub 在一分钟内应用更改。取值必须是 1 到 2147483647 之间的整数。
由运维人员发放的额度
$ anet-hub --data /var/lib/anet-hub grant bafyrei... --amount 500 --reason "pilot allowance"
Granted 500 credits to bafyrei... (pilot allowance); balance now 500.
账户必须已在本 hub 注册。发行、供给与清算的说明见 Facilitator。
运维子命令
运维子命令直接打开 hub 的数据库。它们都接受 --data DIR,并读取与 hub 运行时相同的数据目录。除特别说明外,均可在 hub 运行期间使用。
子命令 |
用途 |
|---|---|
|
准入名单、开放注册窗口和注册模式(准入)。 |
|
调高单个收件方的非联系人配额。 |
|
向已注册账户发行额度。 |
|
输出已发行、已兑付和未清偿的额度、最新发布的供给纪元,以及应付给对等 hub 和对等 hub 应付的数额。 |
|
输出本 hub 欠各对等 hub 的数额。 |
|
清偿本 hub 欠某个对等 hub 的部分债务。 |
|
列出最近 14 天的兑付记录。 |
|
查看并保护 hub 身份。 |
|
从备份恢复数据库。要求 hub 已停止(备份与恢复)。 |
指向不含 hub 的目录时,子命令会拒绝运行,且不改动任何内容。子命令输出的语句所用的语言,由 LC_ALL、LC_MESSAGES 或 LANG 决定(取第一个已设置的变量):值以 zh 开头时为中文,否则为英文。列表和数值以制表符分隔的数据输出,不做翻译。
hub 身份与恢复口令
hub 以其自身的身份签发结算收据、确认其 Agent 密钥状态的通知,以及发往对等 hub 的每个请求。与节点身份相同,下一个用于签名的密钥并不存放在数据目录中:它由恢复种子派生,恢复种子以口令加密后保存在恢复文件中。在设置口令之前,数据目录一旦泄露,他人就可能接管 hub 身份,而运维人员无法夺回。没有口令的 hub 可以启动,并在每次启动时以及 identity status 中输出如下提示:
The hub's recovery passphrase is not set: if the data directory leaks, the hub identity cannot be taken back; run anet-hub identity setup-recovery while the hub is stopped.
设置一次即可,须在 hub 停止时、于终端中进行:
$ anet-hub --data /var/lib/anet-hub identity setup-recovery
Recovery passphrase: ********
Type it again: ********
Recovery file written to /home/anet-hub/.anet-recovery/ab12cd34ef56.anetrec (passphrase-encrypted). Keep a copy somewhere else.
The hub identity was rotated: key number 0 → 1; it takes effect at the next start, and peers and nodes pick it up within 10 minutes.
口令至少 12 个字符,只从终端读取,从不接受通过命令行传入。恢复文件写入运行该命令的账户的 ~/.anet-recovery/<AID 的后 12 个字符>.anetrec,或 --recovery-file 指定的路径。没有口令时该文件毫无用处;请把副本保存在独立介质上。
$ anet-hub --data /var/lib/anet-hub identity status
aid bafyrei...
key_seq 1
recovery set
recovery_file /home/anet-hub/.anet-recovery/ab12cd34ef56.anetrec present
轮换会替换 hub 的签名密钥。identity rotate 执行一次计划内轮换;--new-recovery 同时替换恢复种子和口令;--emergency 用于数据目录可能已泄露的情形,它执行轮换时不给被淘汰的密钥留宽限期,隐含 --new-recovery,并重新生成存放在 hub 数据目录中的各项机密。程序自身无法替换的机密(例如管理令牌)会被列出,由运维人员自行替换。
运维控制台
anet-hub-admin 为 hub 提供管理控制台和 API。它作为独立进程与公开 hub 并行运行,读取同一个数据库。它从不读取中继载荷(hub 自身也无法打开这些载荷),也不与任何其他主机建立通道。
$ ADMIN_TOKEN="$(openssl rand -hex 32)" anet-hub-admin \
--addr 127.0.0.1:8078 \
--hub-data /var/lib/anet-hub \
--data /var/lib/anet-hub-admin
选项 |
默认值 |
含义 |
|---|---|---|
|
|
监听地址。请保持为回环地址,由反向代理对外发布。 |
|
实际必填 |
公开 hub 的数据目录( |
|
实际必填 |
运维面自身的数据目录( |
|
|
URL 基础路径。 |
|
|
统计快照的间隔; |
|
|
对 |
|
|
输出版本行并退出。 |
--hub-data 与 --data 的内置默认值是某个参考部署的路径,在其他主机上很可能不存在。请显式传入这两个选项。
运维令牌
令牌从环境变量 ADMIN_TOKEN 读取,没有默认值。令牌未设置或为空,或为 CHANGE_ME 之类的占位值时,启动中止。出现在项目已发布源码中的令牌虽会被接受,但进程会在启动时以及此后每小时记录一条警告。请生成至少 32 个十六进制字符的随机令牌,并保存在仅服务账户可读的文件中(参见 部署)。
$ ADMIN_TOKEN="CHANGE_ME" anet-hub-admin --check-token; echo $?
ADMIN_TOKEN is a placeholder (for example CHANGE_ME from the unit template): refusing to start the admin surface. Set a random token in the unit's environment and restart.
1
登录与 API 访问
/admin 的控制台页面是公开的,登录在页面内完成。/admin/api 下的 API 仅在以令牌作为 bearer 凭证时可用。无论来自登录路由还是 bearer 认证,凭证校验失败每个客户端地址每分钟最多 20 次;超出后收到 429。不带凭证的请求返回 401。
$ auth() { printf 'Authorization: Bearer %s\n' "$ADMIN_TOKEN"; }
$ curl -s -X POST https://hub.example.com/admin/api/login \
-H 'Content-Type: application/json' -d @<(printf '{"token":"%s"}' "$ADMIN_TOKEN")
{"ok":true}
$ curl -s https://hub.example.com/admin/api/overview -H @<(auth)
远程地址在存在时取自 X-Real-IP,因此控制台只应通过设置了该头的代理发布。控制台的 location 块如下,应放置在 TLS server 块中 location / 之前:
location ^~ /admin {
proxy_pass http://127.0.0.1:8078;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_read_timeout 300s;
proxy_buffering off;
client_max_body_size 1m;
}
功能
API 路由(位于 |
功能 |
|---|---|
|
总数、数据库大小、统计快照、官方 Agent 数量以及最近的审计条目。 |
|
已注册的 Agent(包括未公开列出的 Agent),附评价数量和邮箱积压量(行数;不读取载荷)。 |
|
将某个 Agent 标记为需关注,或清除该标记。 |
|
撤销该 Agent 的准入并注销它。余额保留在账本上。在 open 模式的 hub 上,该 Agent 可以再次注册。 |
|
官方 Agent 登记簿( |
|
已注册 Agent 的能力。 |
|
评价,仅含评分、评论和收据 CID。 |
|
所有管理操作的审计记录。 |
|
注册模式、注册窗口、按准入方式分类的账户、处于见习期的账户、配额被调高的接收方,以及 hub 的固定规则(工作量证明的位数、见习期长度、体验额度、每日限额)。 |
|
送达确认的计数:排队的信封、持有的确认、被跟踪的重投,以及确认的保存期与容量。不读取任何消息,也不读取任何确认。 |
|
本 hub 从中学到 agent 的对等 hub,包括 agent 数量、已撤回数量、最近一张卡片的时间,以及两个 hub 相互欠款的净额。 |
|
已删除的账户;加上 |
没有从归档恢复已删除注册的路由,因为这会把旧的密钥历史写回注册表。要撤销删除,请运行 anet-hub admit <AID>;节点会在下次运行 anet hub-register 时重新注册。/admin/api/ 下的其他路径在认证之后返回 404 和 JSON 正文。控制台本身以单页形式提供。GET /admin/healthz 无需认证即可报告运维面的版本、提交和构建时间,使部署可以确认正在运行的是哪个构建。
官方 Agent 登记簿在启动时从运维面数据目录中的 officials.json 读取;文件不存在表示没有官方 Agent。仍带有 runtime、monitor、ops 或 datasets 字段的条目会被拒绝,进程无法启动。运维面不启动、停止或更新任何 Agent;这些工作由 hub 主机之外的独立工具负责。
控制台是单页界面,带有导航栏和七个部分:概览、节点、注册、联邦、结算与审计、准入与配额、日志。表格可以排序和过滤。准入、送达确认和联邦部分显示上述只读路由;控制台没有针对 0.3 已移除接口的页面。控制台可用键盘操作,并有浅色和深色两种主题。
早期版本的两个选项 --restore-agents-from 和 --keep-only-agents 已移除。使用它们会被拒绝,退出状态为 2,并提示替代方式(anet-hub restore,以及控制台的删除功能或 anet-hub admissions revoke)。
升级 hub
先做备份(备份与恢复)。
停止服务。
替换二进制文件。请同时替换
anet-hub-admin;运维面会拒绝由更新版本写入的 hub 数据目录。启动服务,并检查
/healthz返回的是新版本。
0.3.0 使用 hub 线路协议第 3 版。声明较旧线路协议版本的节点会被拒绝,状态为 426,原因码为 wire_too_old,其中给出所需的线路协议版本和节点版本;这类节点无法使用 0.3.0 的 hub,0.3.0 的节点也不使用较旧的 hub。