主要变更: - RDP 远程桌面:新增 backend/pkg/rdp(RDP 引擎 + credssp/display/rle/trace)、 backend/controller/rdp*(连接/凭证/文件/会话/实时)、frontend RDP 组件与 API - Windows SMB 文件传输:backend/pkg/winsmb - 云账单同步:backend/pkg/billingsync + billing_sync/aliyun_billing,前端账单详情 - Agent CLI Kubernetes 工具:kubernetes_action/get、pod_execute/logs - 服务器导入助手:server_import_helpers、ImportCredentialSettings - 国际化与组件重构(cloud/deploy/monitor/server/system 等多模块) - 清理编译产物二进制:移除 backend/server、backend/agent/bin/aiops-agent-* - .gitignore:新增 backend/agent/ 与 .cache/(避免 334MB go-build 缓存误入库) Co-Authored-By: Claude <noreply@anthropic.com>
171 lines
3.9 KiB
Go
171 lines
3.9 KiB
Go
package winsmb
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"net"
|
|
"os"
|
|
"path"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/hirochachacha/go-smb2"
|
|
)
|
|
|
|
// 虚拟 Download 盘在 Windows 上的相对路径(挂载 C$ 共享)// Logan.Li
|
|
const (
|
|
AdminShare = "C$"
|
|
DownloadRelDir = `Users\Public\AIOpsShare\Download`
|
|
DownloadFullPath = `C:\Users\Public\AIOpsShare\Download`
|
|
)
|
|
|
|
// Auth SMB 管理员凭据 // Logan.Li
|
|
type Auth struct {
|
|
Host string
|
|
Domain string
|
|
Username string
|
|
Password string
|
|
}
|
|
|
|
// FileEntry 目录项 // Logan.Li
|
|
type FileEntry struct {
|
|
Name string
|
|
Size int64
|
|
IsDir bool
|
|
ModTime time.Time
|
|
}
|
|
|
|
func sanitizeFileName(name string) string {
|
|
base := path.Base(strings.ReplaceAll(name, `\`, "/"))
|
|
base = strings.TrimSpace(base)
|
|
if base == "" || base == "." || base == ".." {
|
|
return "upload.bin"
|
|
}
|
|
return base
|
|
}
|
|
|
|
func (a Auth) dial() (*smb2.Share, func(), error) {
|
|
if strings.TrimSpace(a.Host) == "" {
|
|
return nil, nil, fmt.Errorf("SMB 主机为空")
|
|
}
|
|
d := &smb2.Dialer{
|
|
Initiator: &smb2.NTLMInitiator{
|
|
User: a.Username,
|
|
Password: a.Password,
|
|
Domain: a.Domain,
|
|
},
|
|
}
|
|
tcpConn, err := net.DialTimeout("tcp", net.JoinHostPort(a.Host, "445"), 15*time.Second)
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("SMB 连接失败(请确认 445 端口可达且账号有管理员权限): %w", err)
|
|
}
|
|
session, err := d.DialContext(context.Background(), tcpConn)
|
|
if err != nil {
|
|
tcpConn.Close()
|
|
return nil, nil, fmt.Errorf("SMB 认证失败: %w", err)
|
|
}
|
|
share, err := session.Mount(AdminShare)
|
|
if err != nil {
|
|
_ = session.Logoff()
|
|
return nil, nil, fmt.Errorf("挂载 %s 失败: %w", AdminShare, err)
|
|
}
|
|
cleanup := func() {
|
|
_ = share.Umount()
|
|
_ = session.Logoff()
|
|
}
|
|
return share, cleanup, nil
|
|
}
|
|
|
|
// EnsureDownloadDir 创建虚拟 Download 目录 // Logan.Li
|
|
func EnsureDownloadDir(auth Auth) error {
|
|
share, closeFn, err := auth.dial()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer closeFn()
|
|
cur := ""
|
|
for _, part := range strings.Split(DownloadRelDir, `\`) {
|
|
if part == "" {
|
|
continue
|
|
}
|
|
if cur == "" {
|
|
cur = part
|
|
} else {
|
|
cur = cur + `\` + part
|
|
}
|
|
_ = share.Mkdir(cur, 0755)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func remoteFilePath(fileName string) string {
|
|
return DownloadRelDir + `\` + sanitizeFileName(fileName)
|
|
}
|
|
|
|
// Upload 上传文件到虚拟 Download 盘 // Logan.Li
|
|
func Upload(auth Auth, fileName string, data []byte) error {
|
|
if err := EnsureDownloadDir(auth); err != nil {
|
|
return err
|
|
}
|
|
share, closeFn, err := auth.dial()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer closeFn()
|
|
target := remoteFilePath(fileName)
|
|
f, err := share.Create(target)
|
|
if err != nil {
|
|
return fmt.Errorf("创建远端文件失败: %w", err)
|
|
}
|
|
defer f.Close()
|
|
if _, err := f.Write(data); err != nil {
|
|
return fmt.Errorf("写入远端文件失败: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Download 从虚拟 Download 盘读取文件 // Logan.Li
|
|
func Download(auth Auth, fileName string) ([]byte, error) {
|
|
share, closeFn, err := auth.dial()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer closeFn()
|
|
target := remoteFilePath(fileName)
|
|
f, err := share.Open(target)
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
return nil, fmt.Errorf("文件不存在: %s", sanitizeFileName(fileName))
|
|
}
|
|
return nil, fmt.Errorf("打开远端文件失败: %w", err)
|
|
}
|
|
defer f.Close()
|
|
return io.ReadAll(f)
|
|
}
|
|
|
|
// ListDownload 列出虚拟 Download 盘文件 // Logan.Li
|
|
func ListDownload(auth Auth) ([]FileEntry, error) {
|
|
if err := EnsureDownloadDir(auth); err != nil {
|
|
return nil, err
|
|
}
|
|
share, closeFn, err := auth.dial()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer closeFn()
|
|
entries, err := share.ReadDir(DownloadRelDir)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("读取目录失败: %w", err)
|
|
}
|
|
out := make([]FileEntry, 0, len(entries))
|
|
for _, ent := range entries {
|
|
out = append(out, FileEntry{
|
|
Name: ent.Name(),
|
|
Size: ent.Size(),
|
|
IsDir: ent.IsDir(),
|
|
ModTime: ent.ModTime(),
|
|
})
|
|
}
|
|
return out, nil
|
|
} |