Files
aiops/backend/router/agent_static.go
T
Logan.Li fd60361d93 chore: 统一本地服务器与远程仓库源码
- 合并 main 与 master 历史并以服务器结构为基线
- 保留迁移、数据保留、AWS 账单与部署安全能力
- 建立 SDD 规格和 CI 验证闭环
2026-08-05 20:03:25 +08:00

148 lines
4.1 KiB
Go

package router
import (
"fmt"
"net/http"
"os"
"path/filepath"
"strings"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
)
type agentStaticPaths struct {
binDir string
deployDir string
}
var agentBinaryFiles = map[string]string{
"linux-amd64": "aiops-agent-linux-amd64",
"linux-arm64": "aiops-agent-linux-arm64",
"darwin-arm64": "aiops-agent-darwin-arm64",
"windows-amd64": "aiops-agent-windows-amd64.exe",
}
// registerAgentDownloads 注册 Agent 安装脚本和二进制下载路由,避免维护 backend/agent 重复目录。 // Logan.Li
func registerAgentDownloads(r *gin.Engine, log *zap.Logger) {
paths := resolveAgentStaticPaths(log)
r.GET("/agent/arch", func(c *gin.Context) {
base := requestBaseURL(c)
if base == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "request host is required"})
return
}
downloads := availableAgentDownloads(paths.binDir)
c.JSON(http.StatusOK, gin.H{
"version": "1.0.0",
"downloads": downloads,
"install": fmt.Sprintf("curl -fsSL %s/agent/install.sh | sudo bash -s -- --server %s --token <token>", base, base),
})
})
r.GET("/agent/install.sh", func(c *gin.Context) {
serveAgentStaticFile(c, paths.deployDir, "install.sh")
})
r.GET("/agent/install.ps1", func(c *gin.Context) {
serveAgentStaticFile(c, paths.deployDir, "install.ps1")
})
for _, fileName := range agentBinaryFiles {
name := fileName
r.GET("/agent/"+name, func(c *gin.Context) {
serveAgentStaticFile(c, paths.binDir, name)
})
}
r.GET("/agent/bin/:file", func(c *gin.Context) {
serveAgentStaticFile(c, paths.binDir, c.Param("file"))
})
r.GET("/api/agent/bin/:file", func(c *gin.Context) {
serveAgentStaticFile(c, paths.binDir, c.Param("file"))
})
}
// resolveAgentStaticPaths 按部署形态寻找唯一 Agent 资源目录:容器优先,其次项目根目录。 // Logan.Li
func resolveAgentStaticPaths(log *zap.Logger) agentStaticPaths {
paths := agentStaticPaths{
binDir: firstExistingDir([]string{
"/app/agent/bin",
"./agent/bin",
"../agent/bin",
}),
deployDir: firstExistingDir([]string{
"/app/agent/deploy",
"./agent/deploy",
"../agent/deploy",
}),
}
if log != nil {
log.Info("Agent 静态资源目录已解析",
zap.String("bin_dir", paths.binDir),
zap.String("deploy_dir", paths.deployDir),
)
}
return paths
}
func firstExistingDir(candidates []string) string {
for _, candidate := range candidates {
info, err := os.Stat(candidate)
if err == nil && info.IsDir() {
return candidate
}
}
return ""
}
func availableAgentDownloads(binDir string) gin.H {
downloads := gin.H{}
for platform, fileName := range agentBinaryFiles {
if binDir == "" {
continue
}
fullPath := filepath.Join(binDir, fileName)
if info, err := os.Stat(fullPath); err == nil && !info.IsDir() {
downloads[platform] = "/agent/" + fileName
}
}
return downloads
}
func requestBaseURL(c *gin.Context) string {
scheme := "http"
if c.Request.TLS != nil {
scheme = "https"
}
if proto := c.GetHeader("X-Forwarded-Proto"); proto != "" {
// 多级代理可能传逗号列表,首项是客户端最初使用的协议。 // Logan.Li
scheme = strings.TrimSpace(strings.Split(proto, ",")[0])
}
host := c.Request.Host
if forwardedHost := c.GetHeader("X-Forwarded-Host"); forwardedHost != "" {
// 多级代理可能传逗号列表,首项是客户端最初访问的域名。 // Logan.Li
host = strings.TrimSpace(strings.Split(forwardedHost, ",")[0])
}
if host == "" {
// HTTP/1.1 正常请求必须带 Host,缺失时不再伪造本机地址。 // Logan.Li
return ""
}
return fmt.Sprintf("%s://%s", scheme, host)
}
func serveAgentStaticFile(c *gin.Context, dir string, fileName string) {
if dir == "" {
c.JSON(http.StatusNotFound, gin.H{"error": "agent static directory not found"})
return
}
if strings.Contains(fileName, "/") || strings.Contains(fileName, "\\") || fileName == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid agent file"})
return
}
fullPath := filepath.Join(dir, fileName)
if info, err := os.Stat(fullPath); err != nil || info.IsDir() {
c.JSON(http.StatusNotFound, gin.H{"error": "agent file not found"})
return
}
c.File(fullPath)
}