修复: #46 Agent 一键安装脚本在 Debian dash 下执行失败 (#48)

* 修复: #46 Agent 一键安装脚本在 Debian dash 下执行失败

根因(多因素,任何一个都可能导致用户复现的 "sh: 2: Syntax error: newline unexpected"):
- Debian/Ubuntu 默认 /bin/sh → dash;pipe 方式下 shebang 被忽略
- Content-Type: text/x-shellscript 会触发部分 CDN/反向代理的脚本识别与改写
- 如果响应被改写为 HTML,sh 在第 2 行(<html>)即报此语法错误

修复:
1. 前端命令改为 `curl -fsSL URL | sudo bash`(避开 dash)
2. 命令面板增加"先下载再执行"备用命令(代理过滤场景兜底)
3. install handler Content-Type 改为 text/plain;加 nosniff / no-store /
   Content-Disposition 三头,减少中间层改写的概率
4. 脚本模板加 magic marker `BACKUPX_AGENT_INSTALL_V1`,用户可通过
   `head -3` 自查响应完整性;加 bash 自举段,文件执行时优先切到 bash

测试:
- installscript/issue46_test.go 断言 magic + bash-bootstrap 存在于三种模式
- install_flow_test.go 断言新 headers 与 marker
- go test ./... 全绿,前端 build 通过

* 修复: #46 用户截图证实 nginx SPA fallback 返回 index.html

用户反馈截图显示 curl 下载到的是 BackupX 前端 HTML,而非 shell 脚本——
说明 /install/:token 未被反向代理转发到后端,nginx 按 try_files fallback
到 /index.html,sh 读第 2 行 <html> 报语法错误。

真正的根因修复:
1. 后端 install 端点额外暴露 /api/install/:token 别名,让反向代理
   已有的 /api/ 转发规则自动接管
2. 节点创建时返回的 url/composeUrl 统一使用 /api/install/ 前缀
3. 更新 deploy/nginx.conf 模板:
   - 新增 location /install/ 转发(兼容旧版本生成的命令)
   - 新增 /health /ready /metrics 单独转发,避免 SPA fallback

测试:
- install_flow_test.go 新增 TestInstallScriptAliasUnderAPI 断言
  /api/install/:token 路径可用 + 新生成的 url 用 /api/install/ 前缀
This commit is contained in:
Wu Qing
2026-04-20 23:35:39 +08:00
committed by GitHub
parent 1b73f19eb1
commit 1a699da8d6
9 changed files with 191 additions and 6 deletions

View File

@@ -262,14 +262,16 @@ func (h *NodeHandler) CreateInstallToken(c *gin.Context) {
fmt.Sprintf("生成 %s/%s install token TTL=%ds", input.Mode, input.Arch, input.TTLSeconds))
masterURL := resolveMasterURL(c, h.externalURL)
// 使用 /api/install/... 而非 /install/... —— 让反向代理的 /api/ 转发规则
// 自动接管,避免 SPA fallback 把请求当成前端路由返回 index.htmlissue #46
body := gin.H{
"installToken": out.Token,
"expiresAt": out.ExpiresAt,
"url": masterURL + "/install/" + out.Token,
"url": masterURL + "/api/install/" + out.Token,
"composeUrl": "",
}
if input.Mode == "docker" {
body["composeUrl"] = masterURL + "/install/" + out.Token + "/compose.yml"
body["composeUrl"] = masterURL + "/api/install/" + out.Token + "/compose.yml"
}
response.Success(c, body)
}