- 「公网服务端」新增 Nginx 启停控制(Agent 0.15.2,停止带二次确认) - frps 启动失败不再误报成功,并增强启动诊断 - frps vhost 端口禁止 80/443,避免与 VPS 上 Nginx 冲突 - 旧服务端配置的 tcp_mux / login_fail_exit 默认值补齐 - 新增安全审查、页面布局方案与场景操作文档
21 KiB
Agent、Nginx、frps 安装日志与进度方案
1. 背景
当前在界面执行以下操作时,按钮只显示加载圈,用户无法判断任务处于哪个阶段:
- 安装或升级 Agent
- 安装 Nginx
- 安装或升级 frps
这些操作耗时较长,尤其是:
- SSH 上传 Agent 二进制
apt-get update与apt-get install nginx- 下载 frps 安装包
- 校验 SHA-256
- 解压 tar.gz
- 写入 systemd unit
- 启动或重启服务
用户目前只能在操作结束后看到最终输出,失败时也无法判断是网络、SSH、包管理器、下载还是 systemd 阶段的问题。
2. 当前实现分析
2.1 Agent 安装
调用链:
AgentView.vue
-> POST /api/frpmulti/servers/{id}/agent/install
-> internal/api/frpmulti_handler.go
-> Service.agentInstall
-> AgentInstaller.Install
-> SSHInstaller.Install
-> uploadBytes + runSSHSession
特点:
- 安装确实通过 SSH 执行,不依赖 Agent 的 HTTPS 或隧道。
uploadBytes当前一次性写入二进制,没有上传进度。runSSHSession使用session.Stdout和session.Stderr收集输出,但只在结束后返回。- 前端
run()只负责按钮 loading 和最终输出,没有实时事件通道。 - 安装输出里包含
Token: ...,如果直接展示日志,必须先脱敏。
2.2 Nginx 安装
调用链:
ProxyRemoteView.vue
-> POST /api/frpmulti/servers/{id}/agent/nginx/install
-> Service.InstallAgentNginx
-> AgentClient.InstallNginx
-> Agent POST /api/v1/nginx/install
-> Server.installNginx
特点:
- 真正的安装在 VPS Agent 进程内执行。
- 当前使用
exec.CommandContext(...).CombinedOutput(),命令结束后才能拿到输出。 apt-get update、dnf install、yum install、apk add都可能持续数分钟。- Agent 的 HTTP 响应是一次性的,中间过程没有 job、SSE 或状态查询接口。
2.3 frps 安装
调用链:
AgentView.vue
-> POST /api/frpmulti/servers/{id}/agent/frps-install
-> Service.InstallFRPSOnAgent
-> AgentClient.InstallFRPS
-> Agent POST /api/v1/frps/install
-> Server.installFRPS
特点:
downloadToFile当前没有下载进度回调。- SHA-256 校验、解压、写 unit、systemctl 重启都只在最终响应里返回 notes。
- 安装完成后,中央端还会继续下发
frps.toml,这一步也应该纳入任务日志。
2.4 现有可复用能力
项目已有两类进度模式:
-
证书申请任务
internal/acme/job.goPOST /api/certificates/apply返回job_idGET /api/certificates/jobs/{id}/stream使用 SSE- 前端
CertificatesView.vue使用EventSource
-
FRP 客户端下载任务
internal/frp/service.go的DownloadTaskGET /api/frpmulti/download/status- 前端
FrpView.vue每 500ms 轮询 - 支持
downloaded、total、message、status
建议以 ACME 的 SSE 任务模型为主,以 FRP 下载的进度字段为补充,不新造第三套机制。
3. 方案目标
3.1 用户目标
- 点击安装后立即看到任务已经开始。
- 能看到当前阶段,例如“连接 SSH”“上传 Agent”“下载 frps”“校验安装包”“重启服务”。
- 能看到实时日志,而不是只看圈圈。
- 能区分“确定进度”和“不确定进度”。
- 失败时能定位到具体阶段和最后几行日志。
- 刷新页面或切换页面后,任务不丢,回来还能继续看。
3.2 工程目标
- 统一 Agent 安装、Nginx 安装、frps 安装的任务模型。
- 复用现有 SSE、认证、前端 EventSource 经验。
- 保持旧 Agent 兼容,不要求所有 VPS 同时升级。
- 日志必须脱敏,不能把 Agent Token 或 SSH 凭据输出到浏览器。
- 多服务器之间任务完全隔离。
- 同一服务器同一安装类型不允许并发执行。
4. 总体架构
4.1 两类执行域
需要明确区分:
中央 Havline 服务
-> 通过 SSH 执行 Agent 安装
VPS Agent
-> 在 VPS 本机执行 Nginx 安装
-> 在 VPS 本机执行 frps 安装
因此不能只做一套任务管理器:
- Agent 安装任务由中央服务管理。
- Nginx 和 frps 安装任务由 Agent 管理,中央服务负责桥接和转发。
4.2 数据流
sequenceDiagram
participant Browser as 浏览器
participant Central as Havline 中央服务
participant SSH as VPS SSH
participant Agent as VPS Agent
Browser->>Central: POST 创建安装任务
Central-->>Browser: 202 { job_id }
Browser->>Central: GET /jobs/{id}/stream
alt Agent 安装
Central->>SSH: SSH 上传并安装
SSH-->>Central: 实时 stdout / stderr
Central-->>Browser: SSE log / progress / done
else Nginx 或 frps 安装
Central->>Agent: POST 创建 Agent 任务
Agent-->>Central: { job_id }
Central->>Agent: GET /jobs/{id}/stream
Agent-->>Central: 实时日志和进度
Central-->>Browser: 转发为中央 SSE
end
4.3 为什么选择 SSE
推荐使用 SSE,而不是 WebSocket:
- 项目已有 SSE 实现和前端 EventSource 使用经验。
- 安装日志是单向流,不需要双向通信。
- 更容易通过 Nginx、隧道和现有代理。
- 断线后可以重新连接并回放快照。
轮询作为兼容兜底:
- 某些代理环境可能缓冲 SSE。
- 旧 Agent 不支持 SSE 时,中央服务可以轮询 Agent 任务状态。
- 前端 SSE 失败时,可以退化为轮询中央任务状态。
5. 统一任务模型
5.1 任务字段
建议新增共享任务结构:
type OperationJob struct {
ID string
ServerID int64
Kind string
Status string
Phase string
Message string
Current int64
Total int64
Percent float64
Error string
StartedAt time.Time
UpdatedAt time.Time
FinishedAt *time.Time
}
Kind 建议值:
agent-install
nginx-install
frps-install
Status 建议值:
queued
running
completed
failed
cancelled
Phase 建议值:
Agent 安装:
prepare
ssh-connect
upload-agent
upload-script
install-agent
verify-agent
configure-transport
done
Nginx 安装:
detect-package-manager
update-package-index
install-package
enable-service
probe-nginx
done
frps 安装:
fetch-checksum
download-package
verify-checksum
extract-binary
write-systemd-unit
restart-service
push-config
probe-frps
done
5.2 SSE 事件
统一事件格式:
{
"type": "log",
"job_id": "20261006-001",
"kind": "nginx-install",
"status": "running",
"phase": "install-package",
"level": "info",
"message": "正在执行 apt-get install -y nginx",
"current": 0,
"total": 0,
"percent": 0,
"timestamp": "2026-10-06T10:00:00Z"
}
事件类型:
snapshot 连接后先发送当前完整状态
phase 阶段变化
log stdout / stderr 日志
progress 确定进度,例如下载字节数
done 任务完成
error 任务失败
heartbeat 心跳,防止代理断开
进度事件示例:
{
"type": "progress",
"phase": "download-package",
"message": "正在下载 frps 安装包",
"current": 18350080,
"total": 45613056,
"percent": 40.2
}
不确定进度示例:
{
"type": "phase",
"phase": "install-package",
"message": "正在安装 Nginx,包管理器未提供可解析进度"
}
6. 中央服务设计
6.1 新增共享任务包
建议新增:
internal/opjob/job.go
internal/opjob/manager.go
internal/opjob/sse.go
职责:
- 创建任务
- 保存事件快照
- 管理订阅者
- 发送 SSE
- 限制日志缓冲区
- 任务完成和过期清理
internal/acme/job.go 后续可以迁移到该包,避免重复维护。
6.2 中央任务管理器
任务管理器需要支持:
Create(serverID, kind)Get(jobID)Publish(jobID, event)Finish(jobID, result)Stream(ctx, jobID, writer)Latest(serverID, kind)CleanupExpired
内存策略:
- 每个任务最多保留 500 到 1000 条日志事件。
- 超出后丢弃最旧日志,并插入“日志已截断”提示。
- 已完成任务保留 30 分钟。
- 服务重启后任务丢失,前端提示“任务记录已丢失,请重新执行”。
6.3 中央 API
推荐统一操作入口:
POST /api/frpmulti/servers/{id}/operations
Content-Type: application/json
{
"kind": "agent-install",
"params": {
"trust_new_host_key": false
}
}
返回:
{
"job_id": "20261006-abc123",
"status": "queued"
}
其他接口:
GET /api/frpmulti/jobs/{jobID}
GET /api/frpmulti/jobs/{jobID}/stream
GET /api/frpmulti/servers/{id}/jobs/latest?kind=nginx-install
兼容策略:
- 保留现有同步安装接口一个版本周期。
- 新前端优先使用
/operations和 SSE。 - 旧 Agent 不支持任务流时,中央任务使用兼容模式。
6.4 中央任务隔离
必须按 server_id 隔离:
- 任务查询校验
server_id与当前登录会话权限。 - 任务流校验任务属于目标服务器。
- 同一服务器同一
kind只允许一个运行中任务。 - 不同服务器可以并行安装,互不阻塞。
- 前端切换服务器时关闭旧 EventSource,重新连接新服务器任务。
7. Agent 侧任务设计
7.1 Agent 任务管理器
建议新增:
internal/agent/jobs.go
internal/agent/job_handlers.go
Agent 任务接口:
POST /api/v1/jobs/nginx/install
POST /api/v1/jobs/frps/install
GET /api/v1/jobs/{id}
GET /api/v1/jobs/{id}/stream
鉴权:
- 继续使用 Agent Token。
- 浏览器不直接连接 Agent SSE。
- 中央服务使用
Authorization: Bearer <token>读取 Agent SSE。
7.2 Nginx 安装改造
当前:
out, err := install.CombinedOutput()
改造为:
stdout, _ := cmd.StdoutPipe()
stderr, _ := cmd.StderrPipe()
startStreamScanner(stdout, job.Log)
startStreamScanner(stderr, job.Log)
err := cmd.Run()
阶段:
detect-package-managerupdate-package-indexinstall-packageenable-serviceprobe-nginx
进度策略:
- apt、dnf、yum、apk 的安装进度无法统一解析。
- 使用阶段进度,例如 0、15、45、75、90、100。
- UI 使用 indeterminate 进度条,同时显示实时日志。
7.3 frps 安装改造
当前:
downloadToFile(downloadURL, tarball)
改造为:
downloadToFileWithProgress(downloadURL, tarball, func(current, total int64) {
job.Progress("download-package", current, total)
})
阶段和进度:
| 阶段 | 进度 | 说明 |
|---|---|---|
| fetch-checksum | 5% | 获取官方校验文件 |
| download-package | 5% 到 60% | 按字节数计算真实进度 |
| verify-checksum | 65% | 校验 SHA-256 |
| extract-binary | 75% | 解压 frps |
| write-systemd-unit | 85% | 写入或接管 unit |
| restart-service | 95% | systemctl enable/restart |
| push-config | 98% | 中央下发 frps.toml |
| probe-frps | 100% | 探测最终状态 |
7.4 Agent 并发控制
同一 Agent 上:
- Nginx 安装不能并发执行。
- frps 安装不能并发执行。
- 包管理器安装建议全局串行,避免 apt/dpkg 锁冲突。
- 下载任务可以独立,但最终写 unit 和重启服务要串行。
7.5 Agent 任务取消
建议 P1 实现:
POST /api/v1/jobs/{id}/cancel
取消行为:
- 取消 context。
- 对 apt/dnf/yum/apk 使用进程组 kill。
- 保留已产生日志。
- 标记任务为
cancelled。
8. 中央桥接 Agent 任务
8.1 AgentClient 新方法
在 internal/frp/agent_client.go 增加:
StartAgentJob(ctx, kind, payload) (string, error)
AgentJobSnapshot(ctx, jobID) (OperationJob, error)
StreamAgentJob(ctx, jobID, onEvent func(Event) error) error
注意:
- 现有
AgentClient.http有 60 秒总超时,不适合 SSE。 - 新增无总超时的
streamHTTPclient。 - SSE 读取使用
bufio.Scanner,设置最大行长度。 - 连接中断时自动重试快照或重新订阅。
8.2 中央任务执行器
创建中央 job
-> 启动 Agent job
-> 订阅 Agent SSE
-> 将 Agent 事件发布到中央 job
-> Agent 完成
-> 中央执行后续步骤
-> 中央 job 完成
frps 的后续步骤:
agent frps install done
-> 生成 frps.toml
-> 下发 frps.toml
-> 探测 frps 状态
-> 完成
8.3 旧 Agent 兼容
新增最低版本常量,例如:
const agentMinVersionJobs = "0.16.0"
旧 Agent 处理:
- 中央检测到 Agent 不支持
/api/v1/jobs/*。 - 中央仍创建任务,但只发布阶段事件。
- 调用旧同步接口。
- 完成后发布最终日志和结果。
- UI 提示“当前 Agent 版本不支持实时日志,已使用兼容模式”。
9. 前端设计
9.1 新 composable
建议新增:
web/src/composables/useOperationJob.ts
职责:
- 创建任务
- 建立 EventSource
- 维护任务状态
- 维护日志数组
- 自动重连
- 页面隐藏时暂停 UI 更新
- 组件卸载时关闭 EventSource
状态示例:
interface OperationJobState {
jobId: string
kind: string
status: 'idle' | 'queued' | 'running' | 'completed' | 'failed' | 'cancelled'
phase: string
message: string
percent: number
current: number
total: number
logs: OperationLogLine[]
error: string
result?: Record<string, unknown>
}
9.2 新组件
建议新增:
web/src/components/OperationProgressDrawer.vue
布局:
标题:安装 Agent / 安装 Nginx / 安装 frps
状态:运行中 / 已完成 / 失败
阶段:准备 -> SSH -> 上传 -> 安装 -> 校验 -> 完成
进度条:确定进度或 indeterminate
日志框:实时 stdout / stderr
底部:复制日志、下载日志、取消、关闭
交互:
- 安装开始后自动打开抽屉。
- 日志自动滚动,用户手动上滚后暂停自动滚动。
- 失败时显示错误摘要和“查看完整日志”。
- 完成后显示最终结果,例如 Agent 版本、Nginx 路径、frps 版本。
- 关闭抽屉不取消任务。
9.3 页面接入
Agent 页面:
- “安装 Agent / 升级 Agent”改为创建
agent-install任务。 - “安装 / 升级 frps”改为创建
frps-install任务。
公网反代页面:
- “一键安装 Nginx”改为创建
nginx-install任务。
现有 opOutput:
- 保留为历史摘要。
- 实时日志迁移到新抽屉。
- 完成后仍然写入历史摘要,方便回看。
9.4 页面刷新和切换
建议在浏览器保存最近一次任务:
localStorage["havline:last-job:{serverID}:{kind}"] = jobID
页面加载时:
- 读取最近 jobID。
- 请求
GET /api/frpmulti/jobs/{jobID}。 - 如果任务仍在运行,恢复日志面板。
- 如果任务已完成或过期,清理记录。
服务端同时提供:
GET /api/frpmulti/servers/{id}/jobs/latest?kind=agent-install
用于跨浏览器恢复。
10. 日志脱敏
这是本方案的安全硬要求。
10.1 必须脱敏的内容
Token: ...HAVLINE_AGENT_TOKEN=...Authorization: Bearer ...- SSH 密码
- 私钥内容
- 证书私钥
- 可能包含密码的命令行
10.2 脱敏位置
必须在以下位置之前脱敏:
- Agent 任务事件发送前
- 中央任务事件发送前
- SSE 写入前
- 历史日志保存前
不能只在前端隐藏,因为浏览器和日志接口仍可能看到明文。
10.3 示例
原始:
Token: 8f3c2e...
输出:
Token: ********
11. 性能与稳定性
11.1 日志节流
apt、dnf、systemctl 输出可能非常密集。
策略:
- 每 100ms 批量发送一次日志。
- 单条日志超过 4KB 时截断。
- 单任务最多保留 1000 条事件。
- 超出后丢弃旧日志并发送“日志已截断”。
11.2 SSE 心跳
- 每 15 秒发送一次 heartbeat。
- 避免 Nginx、隧道、浏览器代理因空闲断开。
11.3 背压
- 订阅通道使用有缓冲 channel。
- 慢订阅者不能阻塞任务执行。
- 状态、phase、done、error 事件必须可靠送达。
- 普通日志在缓冲满时可以丢弃并标记。
11.4 超时
建议:
| 操作 | 超时 |
|---|---|
| Agent 安装 | 10 分钟 |
| Nginx 安装 | 10 分钟 |
| frps 安装 | 15 分钟 |
| SSE 空闲 | 不超时,使用 heartbeat |
| Agent SSE 重连 | 指数退避,最多 3 次 |
12. 错误处理
12.1 SSH 断开
- 任务标记失败。
- 保留已经收到的日志。
- 提示“SSH 连接中断,Agent 是否安装成功未知,请先点击探测环境”。
12.2 Agent 重启
- 中央连接 Agent SSE 时返回 404。
- 任务标记失败。
- 提示“Agent 已重启,任务状态丢失,请重新执行或探测当前状态”。
12.3 浏览器断开
- 任务继续执行。
- 浏览器重新连接后从 snapshot 开始回放。
12.4 中央服务重启
- 内存任务丢失。
- 前端收到任务不存在。
- 提示“任务记录已丢失,请重新执行或查看实际状态”。
13. 分阶段实施
阶段 1:统一任务基础设施
目标:
- 新增
internal/opjob。 - 实现 Job、Manager、SSE。
- 支持 snapshot、log、phase、progress、done、error、heartbeat。
- 增加单元测试。
不改用户可见行为。
阶段 2:Agent 安装实时日志
目标:
- 新增中央
agent-install任务。 - SSH 上传、安装脚本输出实时推送。
- 前端新增
OperationProgressDrawer。 - Agent 页面接入。
这是收益最高、依赖最少的一步。
阶段 3:Nginx 安装实时日志
目标:
- Agent 增加 job 接口。
installNginx改造为流式执行。- 中央桥接 Agent SSE。
- 公网反代页面接入。
- 旧 Agent 使用兼容模式。
阶段 4:frps 安装进度
目标:
- Agent frps 下载增加字节进度。
- 校验、解压、systemd、配置下发纳入任务阶段。
- Agent 页面接入。
阶段 5:任务恢复和取消
目标:
- 浏览器刷新恢复任务。
- 任务历史查询。
- 取消任务。
- 任务中心或统一历史面板。
阶段 6:持久化任务历史
可选:
- 将已完成任务摘要写入 SQLite。
- 支持按服务器、类型、时间筛选。
- 支持导出诊断包。
14. 文件影响清单
14.1 新增
internal/opjob/job.go
internal/opjob/manager.go
internal/opjob/sse.go
internal/opjob/job_test.go
internal/opjob/sse_test.go
internal/agent/jobs.go
internal/agent/job_handlers.go
web/src/composables/useOperationJob.ts
web/src/composables/useOperationJob.spec.ts
web/src/components/OperationProgressDrawer.vue
14.2 修改
internal/frp/ssh_installer.go
internal/frp/agent_installer.go
internal/frp/agent_client.go
internal/frp/service.go
internal/api/frpmulti_handler.go
internal/api/router.go
internal/agent/server.go
internal/agent/transport.go
web/src/api/client.ts
web/src/api/types.ts
web/src/views/AgentView.vue
web/src/views/ProxyRemoteView.vue
web/src/views/FrpView.vue
web/vite.config.ts
15. 测试计划
15.1 后端单元测试
- Job 快照回放。
- 多订阅者同时接收。
- done 后订阅自动结束。
- 日志缓冲区上限。
- SSE 心跳。
- 脱敏规则。
- 同一服务器同一任务并发拒绝。
- 不同服务器任务隔离。
- 旧 Agent 兼容回退。
15.2 后端集成测试
- 假 SSH 服务器返回多行输出,验证 Agent 安装事件顺序。
- 假 Agent 返回 SSE,验证中央桥接。
- frps 下载模拟慢速 reader,验证进度百分比。
- SSE 中途断开,验证重连和快照恢复。
15.3 前端测试
useOperationJob创建任务并连接 EventSource。- snapshot、log、progress、done、error 状态更新。
- EventSource 重连。
- 组件卸载时关闭连接。
- 日志自动滚动与暂停。
- 安装按钮在任务运行中禁用。
15.4 手工验收
- 慢速 VPS 安装 Agent,能看到上传和安装日志。
- Ubuntu 安装 Nginx,能看到 apt 输出。
- CentOS 安装 Nginx,能看到 yum/dnf 输出。
- frps 下载慢时能看到百分比。
- 安装中途刷新页面,回来后仍能看到任务。
- 切换到另一台服务器,不会串台。
- 日志中没有 Token 和密码。
16. 验收标准
满足以下条件才算完成:
- Agent 安装不再只显示圈圈,至少显示阶段和实时日志。
- Nginx 安装能显示包管理器实时输出。
- frps 安装能显示下载百分比、校验、解压和 systemctl 结果。
- 安装失败时能指出失败阶段和最后错误。
- 浏览器刷新或切换页面后,任务不丢。
- 多服务器任务完全隔离。
- 同一服务器同一安装类型不能重复并发。
- Agent Token、SSH 凭据、私钥不会出现在日志中。
- 旧 Agent 仍然可以安装 Nginx 和 frps,只是没有实时日志。
- 后端、前端测试全部通过。
17. 推荐实施顺序
优先级从高到低:
- 中央
opjob任务基础设施。 - Agent 安装实时日志。
- 前端通用进度抽屉。
- Agent 侧 Nginx job。
- 中央桥接 Agent SSE。
- frps 下载进度。
- 任务恢复。
- 任务取消。
- 任务历史持久化。
先做第 1 到第 3 步,可以最快解决“只有圈圈”的核心体验问题,而且不需要先升级所有 VPS Agent。