diff --git a/cmd/dashboard/controller/controller.go b/cmd/dashboard/controller/controller.go index 0229b07..6d8cc37 100644 --- a/cmd/dashboard/controller/controller.go +++ b/cmd/dashboard/controller/controller.go @@ -60,6 +60,9 @@ func routers(r *gin.Engine, frontendDist fs.FS) { api.POST("/login", authMiddleware.LoginHandler) api.GET("/oauth2/:provider", commonHandler(oauth2redirect)) + r.GET("/script/:name", serveScript) + r.GET("/script/bin/:os/:arch", serveAgentBinary) + fallbackAuthMw := fallbackAuthMiddleware(authMiddleware) fallbackAuth := api.Group("", fallbackAuthMw) fallbackAuth.GET("/setting", commonHandler(listConfig)) @@ -83,7 +86,6 @@ func routers(r *gin.Engine, frontendDist fs.FS) { auth.GET("/refresh-token", authMiddleware.RefreshHandler) - auth.GET("/file", commonHandler(createFM)) auth.GET("/ws/file/:id", commonHandler(fmStream)) diff --git a/cmd/dashboard/controller/script.go b/cmd/dashboard/controller/script.go new file mode 100644 index 0000000..647ef5b --- /dev/null +++ b/cmd/dashboard/controller/script.go @@ -0,0 +1,121 @@ +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() + } +} diff --git a/cmd/dashboard/controller/scripts/agent.ps1 b/cmd/dashboard/controller/scripts/agent.ps1 new file mode 100644 index 0000000..b239034 --- /dev/null +++ b/cmd/dashboard/controller/scripts/agent.ps1 @@ -0,0 +1,95 @@ +$NZ_BASE_PATH = "C:\nezha" +$NZ_AGENT_PATH = "$NZ_BASE_PATH\agent" + +function err($msg) { + Write-Host $msg -ForegroundColor Red +} + +function success($msg) { + Write-Host $msg -ForegroundColor Green +} + +function info($msg) { + Write-Host $msg -ForegroundColor Yellow +} + +function env_check() { + $arch = $env:PROCESSOR_ARCHITECTURE + switch ($arch) { + "AMD64" { $global:os_arch = "amd64" } + "x86" { $global:os_arch = "386" } + "ARM64" { $global:os_arch = "arm64" } + default { err "Unknown architecture: $arch"; exit 1 } + } + $global:os = "windows" +} + +function install() { + info "Installing nezha-agent..." + + if ($env:NZ_DASHBOARD_URL) { + $NZ_AGENT_URL = "$($env:NZ_DASHBOARD_URL)/script/bin/$global:os/$global:os_arch" + } else { + $NZ_AGENT_URL = "https://github.com/nezhahq/agent/releases/latest/download/nezha-agent_$global:os`_$global:os_arch.zip" + } + + $dest = "$env:TEMP\nezha-agent.zip" + try { + Invoke-WebRequest -Uri $NZ_AGENT_URL -OutFile $dest + } catch { + err "Download nezha-agent failed, check your network connectivity" + exit 1 + } + + if (!(Test-Path $NZ_AGENT_PATH)) { + New-Item -ItemType Directory -Path $NZ_AGENT_PATH + } + + Expand-Archive -Path $dest -DestinationPath $NZ_AGENT_PATH -Force + Remove-Item $dest + + $path = "$NZ_AGENT_PATH\config.yml" + if (Test-Path $path) { + $random = -join ((97..122) + (48..57) | Get-Random -Count 5 | ForEach-Object { [char]$_ }) + $path = "$NZ_AGENT_PATH\config-$random.yml" + } + + if (!($env:NZ_SERVER)) { + err "NZ_SERVER should not be empty" + exit 1 + } + + if (!($env:NZ_CLIENT_SECRET)) { + err "NZ_CLIENT_SECRET should not be empty" + exit 1 + } + + $args = "service -c $path uninstall" + Start-Process -FilePath "$NZ_AGENT_PATH\nezha-agent.exe" -ArgumentList $args -Wait -WindowStyle Hidden -ErrorAction SilentlyContinue + + $env_str = "NZ_UUID=$($env:NZ_UUID) NZ_SERVER=$($env:NZ_SERVER) NZ_CLIENT_SECRET=$($env:NZ_CLIENT_SECRET) NZ_TLS=$($env:NZ_TLS) NZ_DISABLE_AUTO_UPDATE=$($env:NZ_DISABLE_AUTO_UPDATE) NZ_DISABLE_FORCE_UPDATE=$($env:NZ_DISABLE_FORCE_UPDATE) NZ_DISABLE_COMMAND_EXECUTE=$($env:NZ_DISABLE_COMMAND_EXECUTE) NZ_SKIP_CONNECTION_COUNT=$($env:NZ_SKIP_CONNECTION_COUNT)" + + # PowerShell env setting is different, but nezha-agent handles these env vars + $args = "service -c $path install" + # Set env vars for the process + $psi = New-Object System.Diagnostics.ProcessStartInfo + $psi.FileName = "$NZ_AGENT_PATH\nezha-agent.exe" + $psi.Arguments = $args + $psi.EnvironmentVariables["NZ_SERVER"] = $env:NZ_SERVER + $psi.EnvironmentVariables["NZ_TLS"] = $env:NZ_TLS + $psi.EnvironmentVariables["NZ_CLIENT_SECRET"] = $env:NZ_CLIENT_SECRET + if ($env:NZ_UUID) { $psi.EnvironmentVariables["NZ_UUID"] = $env:NZ_UUID } + + $proc = [System.Diagnostics.Process]::Start($psi) + $proc.WaitForExit() + + if ($proc.ExitCode -ne 0) { + err "Install nezha-agent service failed" + exit 1 + } + + success "nezha-agent successfully installed" +} + +env_check +install diff --git a/cmd/dashboard/controller/scripts/agent.sh b/cmd/dashboard/controller/scripts/agent.sh new file mode 100644 index 0000000..024cdc8 --- /dev/null +++ b/cmd/dashboard/controller/scripts/agent.sh @@ -0,0 +1,162 @@ +#!/bin/sh + +NZ_BASE_PATH="/opt/nezha" +NZ_AGENT_PATH="${NZ_BASE_PATH}/agent" + +red='\033[0;31m' +green='\033[0;32m' +yellow='\033[0;33m' +plain='\033[0m' + +err() { + printf "${red}%s${plain}\n" "$*" >&2 +} + +success() { + printf "${green}%s${plain}\n" "$*" +} + +info() { + printf "${yellow}%s${plain}\n" "$*" +} + +sudo() { + myEUID=$(id -ru) + if [ "$myEUID" -ne 0 ]; then + if command -v sudo > /dev/null 2>&1; then + command sudo "$@" + else + err "ERROR: sudo is not installed on the system, the action cannot be proceeded." + exit 1 + fi + else + "$@" + fi +} + +deps_check() { + local deps="curl tar grep" + local _err=0 + local missing="" + + for dep in $deps; do + if ! command -v "$dep" >/dev/null 2>&1; then + _err=1 + missing="${missing} $dep" + fi + done + + if [ "$_err" -ne 0 ]; then + err "Missing dependencies:$missing. Please install them and try again." + exit 1 + fi +} + +env_check() { + mach=$(uname -m) + case "$mach" in + amd64|x86_64) os_arch="amd64" ;; + i386|i686) os_arch="386" ;; + aarch64|arm64) os_arch="arm64" ;; + *arm*) os_arch="arm" ;; + s390x) os_arch="s390x" ;; + riscv64) os_arch="riscv64" ;; + mips) os_arch="mips" ;; + mipsel|mipsle) os_arch="mipsle" ;; + loongarch64) os_arch="loong64" ;; + *) err "Unknown architecture: $mach"; exit 1 ;; + esac + + system=$(uname) + case "$system" in + *Linux*) os="linux" ;; + *Darwin*) os="darwin" ;; + *FreeBSD*) os="freebsd" ;; + *) err "Unknown architecture: $system"; exit 1 ;; + esac +} + +init() { + deps_check + env_check +} + +install() { + info "Installing nezha-agent..." + + if [ -n "$NZ_DASHBOARD_URL" ]; then + NZ_AGENT_URL="${NZ_DASHBOARD_URL}/script/bin/${os}/${os_arch}" + FILE_EXT="tar.gz" + else + # Fallback to GitHub + NZ_AGENT_URL="https://github.com/nezhahq/agent/releases/latest/download/nezha-agent_${os}_${os_arch}.zip" + FILE_EXT="zip" + info "NZ_DASHBOARD_URL not found, falling back to GitHub..." + fi + + _cmd="curl -fsSL \"$NZ_AGENT_URL\" -o /tmp/nezha-agent.${FILE_EXT}" + + if ! eval "$_cmd"; then + err "Download nezha-agent failed from $NZ_AGENT_URL, check your network connectivity" + exit 1 + fi + + sudo mkdir -p $NZ_AGENT_PATH + + if [ "$FILE_EXT" = "tar.gz" ]; then + sudo tar -zxf /tmp/nezha-agent.tar.gz -C $NZ_AGENT_PATH + else + if command -v unzip >/dev/null 2>&1; then + sudo unzip -qo /tmp/nezha-agent.zip -d $NZ_AGENT_PATH + else + err "unzip not found, and could not use tar for zip file. Please install unzip or use a dashboard that supports tar.gz conversion." + exit 1 + fi + fi + + sudo rm -rf /tmp/nezha-agent.${FILE_EXT} + + path="$NZ_AGENT_PATH/config.yml" + if [ -f "$path" ]; then + random=$(LC_ALL=C tr -dc a-z0-9 /dev/null 2>&1 + _cmd="sudo env $env $NZ_AGENT_PATH/nezha-agent service -c $path install" + if ! eval "$_cmd"; then + err "Install nezha-agent service failed" + sudo "${NZ_AGENT_PATH}"/nezha-agent service -c "$path" uninstall >/dev/null 2>&1 + exit 1 + fi + + success "nezha-agent successfully installed" +} + +uninstall() { + find "$NZ_AGENT_PATH" -type f -name "*config*.yml" | while read -r file; do + sudo "$NZ_AGENT_PATH/nezha-agent" service -c "$file" uninstall + sudo rm "$file" + done + info "Uninstallation completed." +} + +if [ "$1" = "uninstall" ]; then + uninstall + exit +fi + +init +install diff --git a/go.mod b/go.mod index ba52717..3ef6f76 100644 --- a/go.mod +++ b/go.mod @@ -21,6 +21,8 @@ require ( github.com/libdns/cloudflare v0.2.2 github.com/libdns/he v1.2.1 github.com/libdns/libdns v1.1.1 + github.com/likexian/whois v1.15.7 + github.com/likexian/whois-parser v1.24.21 github.com/miekg/dns v1.1.72 github.com/nezhahq/libdns-tencentcloud v0.0.0-20250501081622-bd293105845a github.com/ory/graceful v0.2.0 @@ -86,8 +88,6 @@ require ( github.com/klauspost/cpuid/v2 v2.3.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect github.com/likexian/gokit v0.25.16 // indirect - github.com/likexian/whois v1.15.7 // indirect - github.com/likexian/whois-parser v1.24.21 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-sqlite3 v1.14.37 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect diff --git a/script/agent.ps1 b/script/agent.ps1 new file mode 100644 index 0000000..b239034 --- /dev/null +++ b/script/agent.ps1 @@ -0,0 +1,95 @@ +$NZ_BASE_PATH = "C:\nezha" +$NZ_AGENT_PATH = "$NZ_BASE_PATH\agent" + +function err($msg) { + Write-Host $msg -ForegroundColor Red +} + +function success($msg) { + Write-Host $msg -ForegroundColor Green +} + +function info($msg) { + Write-Host $msg -ForegroundColor Yellow +} + +function env_check() { + $arch = $env:PROCESSOR_ARCHITECTURE + switch ($arch) { + "AMD64" { $global:os_arch = "amd64" } + "x86" { $global:os_arch = "386" } + "ARM64" { $global:os_arch = "arm64" } + default { err "Unknown architecture: $arch"; exit 1 } + } + $global:os = "windows" +} + +function install() { + info "Installing nezha-agent..." + + if ($env:NZ_DASHBOARD_URL) { + $NZ_AGENT_URL = "$($env:NZ_DASHBOARD_URL)/script/bin/$global:os/$global:os_arch" + } else { + $NZ_AGENT_URL = "https://github.com/nezhahq/agent/releases/latest/download/nezha-agent_$global:os`_$global:os_arch.zip" + } + + $dest = "$env:TEMP\nezha-agent.zip" + try { + Invoke-WebRequest -Uri $NZ_AGENT_URL -OutFile $dest + } catch { + err "Download nezha-agent failed, check your network connectivity" + exit 1 + } + + if (!(Test-Path $NZ_AGENT_PATH)) { + New-Item -ItemType Directory -Path $NZ_AGENT_PATH + } + + Expand-Archive -Path $dest -DestinationPath $NZ_AGENT_PATH -Force + Remove-Item $dest + + $path = "$NZ_AGENT_PATH\config.yml" + if (Test-Path $path) { + $random = -join ((97..122) + (48..57) | Get-Random -Count 5 | ForEach-Object { [char]$_ }) + $path = "$NZ_AGENT_PATH\config-$random.yml" + } + + if (!($env:NZ_SERVER)) { + err "NZ_SERVER should not be empty" + exit 1 + } + + if (!($env:NZ_CLIENT_SECRET)) { + err "NZ_CLIENT_SECRET should not be empty" + exit 1 + } + + $args = "service -c $path uninstall" + Start-Process -FilePath "$NZ_AGENT_PATH\nezha-agent.exe" -ArgumentList $args -Wait -WindowStyle Hidden -ErrorAction SilentlyContinue + + $env_str = "NZ_UUID=$($env:NZ_UUID) NZ_SERVER=$($env:NZ_SERVER) NZ_CLIENT_SECRET=$($env:NZ_CLIENT_SECRET) NZ_TLS=$($env:NZ_TLS) NZ_DISABLE_AUTO_UPDATE=$($env:NZ_DISABLE_AUTO_UPDATE) NZ_DISABLE_FORCE_UPDATE=$($env:NZ_DISABLE_FORCE_UPDATE) NZ_DISABLE_COMMAND_EXECUTE=$($env:NZ_DISABLE_COMMAND_EXECUTE) NZ_SKIP_CONNECTION_COUNT=$($env:NZ_SKIP_CONNECTION_COUNT)" + + # PowerShell env setting is different, but nezha-agent handles these env vars + $args = "service -c $path install" + # Set env vars for the process + $psi = New-Object System.Diagnostics.ProcessStartInfo + $psi.FileName = "$NZ_AGENT_PATH\nezha-agent.exe" + $psi.Arguments = $args + $psi.EnvironmentVariables["NZ_SERVER"] = $env:NZ_SERVER + $psi.EnvironmentVariables["NZ_TLS"] = $env:NZ_TLS + $psi.EnvironmentVariables["NZ_CLIENT_SECRET"] = $env:NZ_CLIENT_SECRET + if ($env:NZ_UUID) { $psi.EnvironmentVariables["NZ_UUID"] = $env:NZ_UUID } + + $proc = [System.Diagnostics.Process]::Start($psi) + $proc.WaitForExit() + + if ($proc.ExitCode -ne 0) { + err "Install nezha-agent service failed" + exit 1 + } + + success "nezha-agent successfully installed" +} + +env_check +install diff --git a/script/agent.sh b/script/agent.sh new file mode 100644 index 0000000..024cdc8 --- /dev/null +++ b/script/agent.sh @@ -0,0 +1,162 @@ +#!/bin/sh + +NZ_BASE_PATH="/opt/nezha" +NZ_AGENT_PATH="${NZ_BASE_PATH}/agent" + +red='\033[0;31m' +green='\033[0;32m' +yellow='\033[0;33m' +plain='\033[0m' + +err() { + printf "${red}%s${plain}\n" "$*" >&2 +} + +success() { + printf "${green}%s${plain}\n" "$*" +} + +info() { + printf "${yellow}%s${plain}\n" "$*" +} + +sudo() { + myEUID=$(id -ru) + if [ "$myEUID" -ne 0 ]; then + if command -v sudo > /dev/null 2>&1; then + command sudo "$@" + else + err "ERROR: sudo is not installed on the system, the action cannot be proceeded." + exit 1 + fi + else + "$@" + fi +} + +deps_check() { + local deps="curl tar grep" + local _err=0 + local missing="" + + for dep in $deps; do + if ! command -v "$dep" >/dev/null 2>&1; then + _err=1 + missing="${missing} $dep" + fi + done + + if [ "$_err" -ne 0 ]; then + err "Missing dependencies:$missing. Please install them and try again." + exit 1 + fi +} + +env_check() { + mach=$(uname -m) + case "$mach" in + amd64|x86_64) os_arch="amd64" ;; + i386|i686) os_arch="386" ;; + aarch64|arm64) os_arch="arm64" ;; + *arm*) os_arch="arm" ;; + s390x) os_arch="s390x" ;; + riscv64) os_arch="riscv64" ;; + mips) os_arch="mips" ;; + mipsel|mipsle) os_arch="mipsle" ;; + loongarch64) os_arch="loong64" ;; + *) err "Unknown architecture: $mach"; exit 1 ;; + esac + + system=$(uname) + case "$system" in + *Linux*) os="linux" ;; + *Darwin*) os="darwin" ;; + *FreeBSD*) os="freebsd" ;; + *) err "Unknown architecture: $system"; exit 1 ;; + esac +} + +init() { + deps_check + env_check +} + +install() { + info "Installing nezha-agent..." + + if [ -n "$NZ_DASHBOARD_URL" ]; then + NZ_AGENT_URL="${NZ_DASHBOARD_URL}/script/bin/${os}/${os_arch}" + FILE_EXT="tar.gz" + else + # Fallback to GitHub + NZ_AGENT_URL="https://github.com/nezhahq/agent/releases/latest/download/nezha-agent_${os}_${os_arch}.zip" + FILE_EXT="zip" + info "NZ_DASHBOARD_URL not found, falling back to GitHub..." + fi + + _cmd="curl -fsSL \"$NZ_AGENT_URL\" -o /tmp/nezha-agent.${FILE_EXT}" + + if ! eval "$_cmd"; then + err "Download nezha-agent failed from $NZ_AGENT_URL, check your network connectivity" + exit 1 + fi + + sudo mkdir -p $NZ_AGENT_PATH + + if [ "$FILE_EXT" = "tar.gz" ]; then + sudo tar -zxf /tmp/nezha-agent.tar.gz -C $NZ_AGENT_PATH + else + if command -v unzip >/dev/null 2>&1; then + sudo unzip -qo /tmp/nezha-agent.zip -d $NZ_AGENT_PATH + else + err "unzip not found, and could not use tar for zip file. Please install unzip or use a dashboard that supports tar.gz conversion." + exit 1 + fi + fi + + sudo rm -rf /tmp/nezha-agent.${FILE_EXT} + + path="$NZ_AGENT_PATH/config.yml" + if [ -f "$path" ]; then + random=$(LC_ALL=C tr -dc a-z0-9 /dev/null 2>&1 + _cmd="sudo env $env $NZ_AGENT_PATH/nezha-agent service -c $path install" + if ! eval "$_cmd"; then + err "Install nezha-agent service failed" + sudo "${NZ_AGENT_PATH}"/nezha-agent service -c "$path" uninstall >/dev/null 2>&1 + exit 1 + fi + + success "nezha-agent successfully installed" +} + +uninstall() { + find "$NZ_AGENT_PATH" -type f -name "*config*.yml" | while read -r file; do + sudo "$NZ_AGENT_PATH/nezha-agent" service -c "$file" uninstall + sudo rm "$file" + done + info "Uninstallation completed." +} + +if [ "$1" = "uninstall" ]; then + uninstall + exit +fi + +init +install