Files
aiops/backend/pkg/toolinstaller/uninstall.go
T
Logan 7e1c8f583c feat: 工具配置增强、站点拨测与日志中心多项改进
- 工具配置:新增卸载 API/UI、Go 工具链、流水线构建自动安装依赖
- 修复 ListTools VerifyTool 递归栈溢出导致获取工具配置失败
- Node.js Alpine 安装改用 unofficial-builds musl 预编译包与 index.json 解析 LTS
- 站点拨测:详情页路由、告警自动恢复、并发拨测与默认可观测频率
- 日志中心:RBAC、查询构建器、ES/Loki 增强与审计 request/response 展示
- 发布配置:构建节点、流水线远程执行、Deploy RBAC 导航拆分
2026-07-02 07:47:33 +08:00

193 lines
5.7 KiB
Go

package toolinstaller
import (
"bytes"
"fmt"
"log"
"os"
"os/exec"
"path/filepath"
"strings"
"time"
"ezops/model"
)
// UninstallResult 工具卸载结果(含脚本输出,供审计日志)// Logan.Li
type UninstallResult struct {
Output string
Err error
}
// Uninstall 卸载指定工具并重置 DB 状态(允许重新安装)// Logan.Li
func (ti *ToolInstaller) Uninstall(toolName string) UninstallResult {
var tool model.DeployTool
if err := ti.db.Where("name = ?", toolName).First(&tool).Error; err != nil {
return UninstallResult{Err: fmt.Errorf("tool %s not found in DB: %w", toolName, err)}
}
ti.mu.Lock()
delete(ti.onces, toolName)
ti.mu.Unlock()
var buf bytes.Buffer
if err := ti.uninstallTool(&tool, &buf); err != nil {
out := strings.TrimSpace(buf.String())
log.Printf("[ToolInstaller] ERROR uninstalling %s: %v", toolName, err)
ti.resetUninstallStatus(tool.ID, out+": "+err.Error())
return UninstallResult{Output: out, Err: err}
}
ti.resetUninstallStatus(tool.ID, "")
out := strings.TrimSpace(buf.String())
log.Printf("[ToolInstaller] %s uninstalled successfully", toolName)
return UninstallResult{Output: out}
}
func (ti *ToolInstaller) resetUninstallStatus(toolID uint, errMsg string) {
now := time.Now()
updates := map[string]interface{}{
"status": model.ToolNotInstalled,
"installed_ver": "",
"last_check": &now,
}
if errMsg != "" {
updates["error_msg"] = errMsg
} else {
updates["error_msg"] = ""
}
ti.updateStatusMap(toolID, updates)
}
func (ti *ToolInstaller) updateStatusMap(toolID uint, updates map[string]interface{}) {
ti.db.Model(&model.DeployTool{}).Where("id = ?", toolID).Updates(updates)
}
func (ti *ToolInstaller) uninstallTool(tool *model.DeployTool, buf *bytes.Buffer) error {
switch tool.Name {
case "jdk":
return ti.uninstallJDK(buf)
case "maven":
return ti.uninstallMaven(buf)
case "golang":
return ti.uninstallGolang(buf)
case "nvm":
return ti.uninstallNVM(buf)
case "nodejs":
return ti.uninstallNodeJS(buf)
case "git":
return ti.uninstallApkPackage(buf, "git", "git")
case "docker-cli":
return ti.uninstallApkPackage(buf, "docker-cli", "docker")
default:
return ti.uninstallBinary(tool, buf)
}
}
func (ti *ToolInstaller) uninstallJDK(buf *bytes.Buffer) error {
buf.WriteString("[JDK] Removing symlinks and /usr/local/share/jdk\n")
removeIfExists(buf, "/usr/local/bin/java", "/usr/local/bin/javac", "/usr/local/bin/jar")
removeDirIfExists(buf, "/usr/local/share/jdk")
return nil
}
func (ti *ToolInstaller) uninstallMaven(buf *bytes.Buffer) error {
buf.WriteString("[Maven] Removing symlinks and /usr/local/share/maven\n")
removeIfExists(buf, "/usr/local/bin/mvn")
removeDirIfExists(buf, "/usr/local/share/maven")
return nil
}
func (ti *ToolInstaller) uninstallGolang(buf *bytes.Buffer) error {
buf.WriteString("[Go] Removing GOROOT and symlinks\n")
removeIfExists(buf, "/usr/local/bin/go", "/usr/local/bin/gofmt", "/etc/profile.d/golang.sh")
removeDirIfExists(buf, "/usr/local/go")
return nil
}
func (ti *ToolInstaller) uninstallNVM(buf *bytes.Buffer) error {
buf.WriteString("[NVM] Removing NVM and Node versions\n")
removeIfExists(buf, "/usr/local/bin/node", "/usr/local/bin/npm", "/usr/local/bin/npx", "/etc/profile.d/nvm.sh")
removeDirIfExists(buf, "/usr/local/share/nvm")
return nil
}
func (ti *ToolInstaller) uninstallNodeJS(buf *bytes.Buffer) error {
buf.WriteString("[Node.js] Removing symlinks and NVM node versions\n")
removeIfExists(buf, "/usr/local/bin/node", "/usr/local/bin/npm", "/usr/local/bin/npx")
nvmSh := "/usr/local/share/nvm/nvm.sh"
if st, err := os.Stat(nvmSh); err == nil && !st.IsDir() {
script := fmt.Sprintf(`export NVM_DIR="/usr/local/share/nvm"
. "%s"
CUR=$(nvm current 2>/dev/null || true)
if [ -n "$CUR" ] && [ "$CUR" != "none" ] && [ "$CUR" != "system" ]; then
nvm uninstall "$CUR" 2>/dev/null || true
fi
rm -rf "$NVM_DIR/versions/node"
`, nvmSh)
cmd := exec.Command("bash", "-c", script)
out, err := cmd.CombinedOutput()
if len(out) > 0 {
buf.Write(out)
}
if err != nil {
buf.WriteString(fmt.Sprintf("[Node.js] nvm cleanup warning: %v\n", err))
}
}
removeDirIfExists(buf, "/usr/local/share/nvm/versions/node")
return nil
}
func (ti *ToolInstaller) uninstallApkPackage(buf *bytes.Buffer, pkg, binary string) error {
buf.WriteString(fmt.Sprintf("[%s] Trying apk del %s\n", binary, pkg))
if _, err := exec.LookPath("apk"); err != nil {
buf.WriteString(fmt.Sprintf("[%s] apk not found, skip package removal\n", binary))
return nil
}
cmd := exec.Command("apk", "del", "--no-cache", pkg)
out, err := cmd.CombinedOutput()
if len(out) > 0 {
buf.Write(out)
}
if err != nil {
// 可能未通过 apk 安装,仅标记卸载完成 // Logan.Li
buf.WriteString(fmt.Sprintf("[%s] apk del skipped: %v\n", binary, err))
}
return nil
}
func (ti *ToolInstaller) uninstallBinary(tool *model.DeployTool, buf *bytes.Buffer) error {
path := tool.InstallPath
if path == "" && tool.BinaryName != "" {
path = filepath.Join("/usr/local/bin", tool.BinaryName)
}
if path == "" {
return fmt.Errorf("no install path configured for %s", tool.Name)
}
buf.WriteString(fmt.Sprintf("[%s] Removing %s\n", tool.Name, path))
removeIfExists(buf, path)
return nil
}
func removeIfExists(buf *bytes.Buffer, paths ...string) {
for _, p := range paths {
if err := os.Remove(p); err != nil {
if !os.IsNotExist(err) {
buf.WriteString(fmt.Sprintf("remove %s: %v\n", p, err))
}
} else {
buf.WriteString(fmt.Sprintf("removed %s\n", p))
}
}
}
func removeDirIfExists(buf *bytes.Buffer, dir string) {
if err := os.RemoveAll(dir); err != nil {
if !os.IsNotExist(err) {
buf.WriteString(fmt.Sprintf("remove dir %s: %v\n", dir, err))
}
} else {
buf.WriteString(fmt.Sprintf("removed dir %s\n", dir))
}
}