Files
nezha_domains/cmd/dashboard/controller/script.go
T

122 lines
3.1 KiB
Go

package controller
import (
"archive/tar"
"archive/zip"
"compress/gzip"
"embed"
"fmt"
"io"
"net/http"
"os"
"path/filepath"
"github.com/gin-gonic/gin"
)
//go:embed scripts/*
var scriptFS embed.FS
func serveScript(c *gin.Context) {
name := c.Param("name")
content, err := scriptFS.ReadFile(filepath.Join("scripts", name))
if err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "Script not found"})
return
}
c.Data(http.StatusOK, "text/plain; charset=utf-8", content)
}
func serveAgentBinary(c *gin.Context) {
osType := c.Param("os")
arch := c.Param("arch")
// Nezha agent releases are usually .zip
// We will proxy the download from GitHub/Gitee and convert it to .tar.gz on the fly
// This allows using 'tar' in the install script and avoids GitHub connectivity issues
repo := "nezhahq/agent"
// For simplicity, we use the latest release
// In a real scenario, you might want to cache this or use a specific version
zipUrl := fmt.Sprintf("https://github.com/%s/releases/latest/download/nezha-agent_%s_%s.zip", repo, osType, arch)
resp, err := http.Get(zipUrl)
if err != nil || resp.StatusCode != http.StatusOK {
// Try Gitee if GitHub fails
zipUrl = fmt.Sprintf("https://gitee.com/naibahq/agent/releases/latest/download/nezha-agent_%s_%s.zip", osType, arch)
resp, err = http.Get(zipUrl)
if err != nil || resp.StatusCode != http.StatusOK {
c.JSON(http.StatusBadGateway, gin.H{"error": "Failed to fetch agent binary from upstream"})
return
}
}
defer resp.Body.Close()
// Create a temporary file to store the zip
tmpZip, err := os.CreateTemp("", "nezha-agent-*.zip")
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create temp file"})
return
}
defer os.Remove(tmpZip.Name())
defer tmpZip.Close()
// Limit the download to 200MB to prevent DoS
limitReader := io.LimitReader(resp.Body, 200*1024*1024)
if _, err := io.Copy(tmpZip, limitReader); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to save zip"})
return
}
// Re-open for reading
zipReader, err := zip.OpenReader(tmpZip.Name())
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to open zip"})
return
}
defer zipReader.Close()
// Set headers for .tar.gz
c.Header("Content-Type", "application/gzip")
c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=nezha-agent_%s_%s.tar.gz", osType, arch))
gw := gzip.NewWriter(c.Writer)
defer gw.Close()
tw := tar.NewWriter(gw)
defer tw.Close()
for _, f := range zipReader.File {
if f.FileInfo().IsDir() {
continue
}
// Security check: Limit uncompressed size to 100MB per file
if f.UncompressedSize64 > 100*1024*1024 {
continue
}
header, err := tar.FileInfoHeader(f.FileInfo(), "")
if err != nil {
continue
}
header.Name = f.Name
if err := tw.WriteHeader(header); err != nil {
continue
}
rc, err := f.Open()
if err != nil {
continue
}
// Use LimitReader as a secondary defense for gosec (G110)
if _, err := io.Copy(tw, io.LimitReader(rc, 100*1024*1024)); err != nil {
rc.Close()
continue
}
rc.Close()
}
}