package installer import ( "context" "errors" "fmt" "io" "os" "os/exec" "path/filepath" "gitea.loveuer.com/yizhisec/pkg3/logger" ) type installer struct { workdir string target string } func (i *installer) buildCommand(ctx context.Context, cmds ...string) *exec.Cmd { if len(cmds) == 0 { return nil } if i.target == "self" { return exec.CommandContext(ctx, cmds[0], cmds[1:]...) } sshArgs := append([]string{i.target}, cmds...) return exec.CommandContext(ctx, "ssh", sshArgs...) } // ExecuteCommand implements syscheck.CommandExecutor interface func (i *installer) ExecuteCommand(ctx context.Context, cmds ...string) (string, error) { cmd := i.buildCommand(ctx, cmds...) if cmd == nil { return "", fmt.Errorf("failed to build command") } output, err := cmd.CombinedOutput() if err != nil { return "", fmt.Errorf("command failed: %w, output: %s", err, string(output)) } return string(output), nil } type CopyFileOption func(*copyFileOptions) type copyFileOptions struct { addExecutable bool } func withCopyFileExecutable() func(*copyFileOptions) { return func(o *copyFileOptions) { o.addExecutable = true } } func (i *installer) copyFile(ctx context.Context, src, dst string, opts ...CopyFileOption) error { logger.Debug("☑️ installer.copyFile: Copying file from %s to %s (target: %s)", src, dst, i.target) var ( err error o = ©FileOptions{} srcFile, dstFile *os.File srcInfo os.FileInfo ) for _, fn := range opts { fn(o) } if i.target == "self" { // Simply copy file locally logger.Debug("Copying file locally: %s -> %s", src, dst) // Open source file if srcFile, err = os.Open(src); err != nil { logger.Error("❌ Failed to open source file %s: %v", src, err) return fmt.Errorf("failed to open source file: %w", err) } defer srcFile.Close() // Create destination directory if needed dstDir := filepath.Dir(dst) if err = os.MkdirAll(dstDir, 0755); err != nil { logger.Error("❌ Failed to create destination directory %s: %v", dstDir, err) return fmt.Errorf("failed to create destination directory: %w", err) } // Create destination file if dstFile, err = os.Create(dst); err != nil { logger.Error("❌ Failed to create destination file %s: %v", dst, err) return fmt.Errorf("failed to create destination file: %w", err) } defer dstFile.Close() // Copy file content if _, err = io.Copy(dstFile, srcFile); err != nil { logger.Error("❌ Failed to copy file content: %v", err) return fmt.Errorf("failed to copy file content: %w", err) } // Get source file permissions if srcInfo, err = os.Stat(src); err == nil { if err = os.Chmod(dst, srcInfo.Mode()); err != nil { logger.Debug("⚠️ Failed to set file permissions: %v", err) } } logger.Info("✅ File copied locally: %s -> %s", src, dst) return nil } // Copy file via scp to remote target logger.Debug("Copying file via scp: %s -> %s:%s", src, i.target, dst) // Format: scp : cmd := exec.CommandContext(ctx, "scp", src, fmt.Sprintf("%s:%s", i.target, dst)) output, err := cmd.CombinedOutput() if err != nil { logger.Error("❌ Failed to copy file via scp: %v, output: %s", err, string(output)) return fmt.Errorf("failed to copy file via scp: %w, output: %s", err, string(output)) } logger.Info("✅ File copied via scp: %s -> %s:%s", src, i.target, dst) return nil } func (i *installer) targetOK(ctx context.Context) error { cmd := i.buildCommand(ctx, "whoami") output, err := cmd.CombinedOutput() if err != nil { logger.Debug("❌ installer.targetOK: check target %s failed, err = %v", i.target, err) return err } if string(output) != "root\n" { logger.Debug("❌ installer.targetOK: check target %s failed, output = %s", i.target, string(output)) return errors.New("target is not root user") } return nil } func NewInstaller(workdir, target string) *installer { if target == "" { logger.Warn("🎯 NewInstaller: target empty, set to default(self)") target = "self" } return &installer{workdir: workdir, target: target} }