merge: maestro/tsk_V9IrIk2g5Q78 [proto 契约 + buf 工具链] (tsk_FrRarw9YO8Nf)

合并 proto 契约 + buf 工具链任务到 main(已含 apierr + idgen + CONVENTIONS.md):

- server/proto/pangolin/agent/v1/agent.proto: AgentService 6 个 RPC(Enroll/Register/
  Heartbeat/Subscribe/Ack/ReportUsage)及全量消息类型,隐私红线注释保留
- server/buf.yaml: v2 格式,STANDARD lint + FILE breaking 规则
- server/buf.gen.yaml: remote protoc-gen-go v1.36.4 + protoc-gen-go-grpc v1.5.1 插件
- server/internal/pb/: 生成的 agent.pb.go + agent_grpc.pb.go 提交入库
- server/Makefile: 新增 proto / proto-lint / proto-breaking 目标
- server/tools.go: 新增 buf v1.70.0、protoc-gen-go、protoc-gen-go-grpc 工具依赖
- server/go.mod / go.sum: 合并 buf 工具链依赖;github.com/google/uuid 保留为 direct
  依赖(idgen 直接 import);go 版本升至 1.25.10

冲突解决:两侧均修改 go.mod,以 proto 分支(超集)为基础,将 google/uuid 移至
直接依赖 require 块,保留 apierr/idgen 的已有文件不变。

go build ./... ✓  |  go test ./internal/apierr/... ./internal/idgen/... ✓

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
wangjia
2026-06-13 15:11:11 +08:00
parent 30e73b31c2
commit 6ec7cd8146
9 changed files with 2407 additions and 71 deletions
+219
View File
@@ -0,0 +1,219 @@
// agent.proto — Control-plane ↔ Agent 通信契约
//
// 安全红线(禁止违反):
// 1. 所有消息中只使用 dp_uuid 标识数据通道,严禁出现 user_id / email 等用户身份字段。
// 2. ReportUsage 不含目的地址、DNS 查询等任何可还原用户行为的字段(无日志口径)。
// 3. 本文件冻结后,任何 breaking change 须先通过 buf breaking 审查。
syntax = "proto3";
package pangolin.agent.v1;
import "google/protobuf/timestamp.proto";
option go_package = "github.com/wangjia/pangolin/server/internal/pb/pangolin/agent/v1;agentv1";
// ─── Service ──────────────────────────────────────────────────────────────────
// AgentService 是控制面暴露给 Agent 的唯一 gRPC 接口。
// 连接方向:Agent 主动拨号控制面。
// 除 Enroll 外,所有 RPC 均需通过 mTLS 客户端证书鉴权。
service AgentService {
// Enroll 供新节点申请证书。唯一不要求 mTLS 客户端证书的入口。
// Agent 提交 bootstrap_token(云初始化注入)与 CSR,控制面签发短期证书。
rpc Enroll(EnrollRequest) returns (EnrollResponse);
// Register 是 mTLS 建立后的首次调用,返回该节点的全量配置快照。
// ConfigSnapshot 直接作为响应类型(非 RegisterResponse),是有意设计——快照兼用于
// ApplyConfig 下发,避免重复定义。buf:lint:ignore 声明如下。
// buf:lint:ignore RPC_RESPONSE_STANDARD_NAME
rpc Register(RegisterRequest) returns (ConfigSnapshot);
// Heartbeat 由 Agent 每 30 秒发送一次,上报实时负载并与控制面对账配置版本。
// 若 need_full_resync == trueAgent 应主动重新调用 Register。
rpc Heartbeat(HeartbeatRequest) returns (HeartbeatResponse);
// Subscribe 建立控制面 → Agent 的服务端流,用于下发 Command。
// Agent 重连时携带 last_command_id,控制面补发缺失命令(at-least-once 语义)。
// Command 直接作为流元素类型(非 SubscribeResponse),是有意设计——下游消费者
// 直接操作 Command,无需额外包装。buf:lint:ignore 声明如下。
// buf:lint:ignore RPC_RESPONSE_STANDARD_NAME
rpc Subscribe(SubscribeRequest) returns (stream Command);
// Ack 用于 Agent 向控制面回执命令的执行结果。
rpc Ack(AckRequest) returns (AckResponse);
// ReportUsage 上报各数据通道的流量用量,供计费/限速使用。
// 红线:items 中只含 dp_uuid 与字节/分钟计数,不含目的地址或 DNS 字段。
rpc ReportUsage(ReportUsageRequest) returns (ReportUsageResponse);
}
// ─── Enroll ───────────────────────────────────────────────────────────────────
message EnrollRequest {
// bootstrap_token 由云初始化(cloud-init)注入,单次有效,控制面验证后即失效。
string bootstrap_token = 1;
// csr_pem 是 Agent 生成的 X.509 证书签名请求(PEM 编码)。
string csr_pem = 2;
}
message EnrollResponse {
// cert_pem 是控制面签发的节点证书(PEM 编码)。
string cert_pem = 1;
// ca_pem 是控制面 CA 证书链(PEM 编码),供 Agent 验证后续 TLS。
string ca_pem = 2;
// not_after 是证书过期时间,Agent 应在到期前主动轮换。
google.protobuf.Timestamp not_after = 3;
}
// ─── Register ─────────────────────────────────────────────────────────────────
message RegisterRequest {
// agent_version 是 Agent 的语义化版本字符串,例如 "1.2.3"。
string agent_version = 1;
// capabilities 描述 Agent 支持的功能集,例如 ["hysteria2", "reality"]。
repeated string capabilities = 2;
}
// ─── Heartbeat ────────────────────────────────────────────────────────────────
message HeartbeatRequest {
// load 是节点实时负载快照。
NodeLoad load = 1;
// config_version 是 Agent 当前持有的配置版本号,用于对账。
uint64 config_version = 2;
}
message HeartbeatResponse {
// need_full_resync 为 true 时,Agent 应重新调用 Register 获取全量配置。
bool need_full_resync = 1;
}
// NodeLoad 节点实时负载,由 Heartbeat 携带。
message NodeLoad {
// online_count 是当前在线连接数(代理隧道数)。
uint32 online_count = 1;
// bandwidth_up 是上行带宽(bytes/s)。
uint64 bandwidth_up = 2;
// bandwidth_down 是下行带宽(bytes/s)。
uint64 bandwidth_down = 3;
// cpu_percent 是 CPU 使用率(0100)。
float cpu_percent = 4;
}
// ─── Subscribe ────────────────────────────────────────────────────────────────
message SubscribeRequest {
// last_command_id 是 Agent 已成功处理的最后一个命令 ID。
// 首次订阅传 0,控制面将从头补发未 Ack 的命令。
uint64 last_command_id = 1;
}
// ─── Command ──────────────────────────────────────────────────────────────────
// Command 是控制面下发给 Agent 的指令,at-least-once 投递语义。
// command_id 单调递增,Agent 根据 last_command_id 实现幂等去重。
message Command {
// command_id 全局单调递增,用于续传与去重。
uint64 command_id = 1;
oneof payload {
UpsertCredential upsert_credential = 2;
RevokeCredential revoke_credential = 3;
RotateCredential rotate_credential = 4;
ApplyConfig apply_config = 5;
Lifecycle lifecycle = 6;
}
}
// UpsertCredential 新增或更新数据通道凭证。
// 红线:只含 dp_uuid,不含 user_id / email。
message UpsertCredential {
// dp_uuid 是数据通道的唯一标识符(Data-Plane UUID)。
string dp_uuid = 1;
// expires_at 是凭证到期时间,到期后 Agent 应拒绝该 dp_uuid 的连接请求。
google.protobuf.Timestamp expires_at = 2;
}
// RevokeCredential 立即吊销数据通道凭证,Agent 应断开对应连接。
// 红线:只含 dp_uuid,不含 user_id / email。
message RevokeCredential {
string dp_uuid = 1;
}
// RotateCredential 原子替换凭证,grace_seconds 内新旧 dp_uuid 均有效。
// 红线:只含 dp_uuid,不含 user_id / email。
message RotateCredential {
string old_dp_uuid = 1;
string new_dp_uuid = 2;
// grace_seconds 是新旧凭证共存的宽限期(秒),到期后 old_dp_uuid 自动失效。
uint32 grace_seconds = 3;
}
// ApplyConfig 将一份完整的 ConfigSnapshot 应用到 Agent。
message ApplyConfig {
ConfigSnapshot config = 1;
}
// Lifecycle 控制节点生命周期。
message Lifecycle {
enum Action {
ACTION_UNSPECIFIED = 0;
// ACTION_DRAIN 停止接受新连接,等待存量连接自然结束。
ACTION_DRAIN = 1;
// ACTION_SHUTDOWN 立即停止所有服务并退出进程。
ACTION_SHUTDOWN = 2;
}
Action action = 1;
}
// ─── ConfigSnapshot ───────────────────────────────────────────────────────────
// ConfigSnapshot 是节点运行所需的全量配置,由 Register 返回或 ApplyConfig 下发。
message ConfigSnapshot {
// config_version 单调递增,Agent 使用此版本号与控制面对账。
uint64 config_version = 1;
// credentials 是当前有效的数据通道凭证列表。
// 红线:只含 dp_uuid,不含 user_id / email。
repeated CredentialEntry credentials = 2;
// reality_sni 是 REALITY 协议使用的 SNI 域名(例如 "www.example.com")。
string reality_sni = 3;
// hy2_port_start / hy2_port_end 是 Hysteria2 监听的 UDP 端口范围。
uint32 hy2_port_start = 4;
uint32 hy2_port_end = 5;
}
// CredentialEntry 单条数据通道凭证。
// 红线:只含 dp_uuid 与有效期,不含 user_id / email。
message CredentialEntry {
string dp_uuid = 1;
google.protobuf.Timestamp expires_at = 2;
}
// ─── Ack ──────────────────────────────────────────────────────────────────────
message AckRequest {
uint64 command_id = 1;
bool ok = 2;
// error_message 仅在 ok == false 时填写,描述失败原因。
string error_message = 3;
}
message AckResponse {}
// ─── ReportUsage ──────────────────────────────────────────────────────────────
message ReportUsageRequest {
repeated UsageItem items = 1;
}
// UsageItem 单个数据通道的用量记录。
// 红线:只含 dp_uuid 与流量计数,不含目的地址、DNS 查询等可还原用户行为的字段。
message UsageItem {
string dp_uuid = 1;
uint64 bytes_up = 2;
uint64 bytes_down = 3;
uint32 minutes = 4;
}
message ReportUsageResponse {}