diff --git a/app/services/backup.go b/app/services/backup.go index be3b6459..68d00120 100644 --- a/app/services/backup.go +++ b/app/services/backup.go @@ -117,7 +117,7 @@ func (s *BackupImpl) WebsiteRestore(website models.Website, backupFile string) e if _, err := tools.Exec(`rm -rf '` + website.Path + `/*'`); err != nil { return err } - if _, err := tools.Exec(`unzip -o '` + backupFile + `' -d '` + website.Path + `' 2>&1`); err != nil { + if err := tools.UnArchive(backupFile, website.Path); err != nil { return err } if err := tools.Chmod(website.Path, 0755); err != nil { @@ -195,59 +195,45 @@ func (s *BackupImpl) MysqlBackup(database string) error { func (s *BackupImpl) MysqlRestore(database string, backupFile string) error { backupPath := s.setting.Get(models.SettingKeyBackupPath) + "/mysql" rootPassword := s.setting.Get(models.SettingKeyMysqlRootPassword) - ext := filepath.Ext(backupFile) - backupFile = backupPath + "/" + backupFile - if !tools.Exists(backupFile) { + backupFullPath := filepath.Join(backupPath, backupFile) + if !tools.Exists(backupFullPath) { return errors.New("备份文件不存在") } - err := os.Setenv("MYSQL_PWD", rootPassword) - if err != nil { + if err := os.Setenv("MYSQL_PWD", rootPassword); err != nil { return err } - switch ext { - case ".zip": - if _, err := tools.Exec("unzip -o " + backupFile + " -d " + backupPath); err != nil { + tempDir, err := tools.TempDir(backupFile) + if err != nil { + return err + } + defer tools.Remove(tempDir) + + if !strings.HasSuffix(backupFile, ".sql") { + backupFile = "" // 置空,防止干扰后续判断 + if err = tools.UnArchive(backupFullPath, tempDir); err != nil { return err } - backupFile = strings.TrimSuffix(backupFile, ext) - case ".gz": - if strings.HasSuffix(backupFile, ".tar.gz") { - // 解压.tar.gz文件 - if _, err := tools.Exec("tar -zxvf " + backupFile + " -C " + backupPath); err != nil { - return err + if files, err := os.ReadDir(tempDir); err == nil { + for _, file := range files { + if strings.HasSuffix(file.Name(), ".sql") { + backupFile = filepath.Base(file.Name()) + break + } } - backupFile = strings.TrimSuffix(backupFile, ".tar.gz") - } else { - // 解压.gz文件 - if _, err := tools.Exec("gzip -d " + backupFile); err != nil { - return err - } - backupFile = strings.TrimSuffix(backupFile, ext) } - case ".bz2": - if _, err := tools.Exec("bzip2 -d " + backupFile); err != nil { + } else { + if err = tools.Cp(backupFullPath, filepath.Join(tempDir, backupFile)); err != nil { return err } - backupFile = strings.TrimSuffix(backupFile, ext) - case ".tar": - if _, err := tools.Exec("tar -xvf " + backupFile + " -C " + backupPath); err != nil { - return err - } - backupFile = strings.TrimSuffix(backupFile, ext) - case ".rar": - if _, err := tools.Exec("unrar x " + backupFile + " " + backupPath); err != nil { - return err - } - backupFile = strings.TrimSuffix(backupFile, ext) } - if !tools.Exists(backupFile) { - return errors.New("自动解压失败,请手动解压") + if len(backupFile) == 0 { + return errors.New("无法找到备份文件") } - if _, err := tools.Exec("/www/server/mysql/bin/mysql -uroot " + database + " < " + backupFile); err != nil { + if _, err = tools.Exec("/www/server/mysql/bin/mysql -uroot " + database + " < " + filepath.Join(tempDir, backupFile)); err != nil { return err } @@ -310,54 +296,41 @@ func (s *BackupImpl) PostgresqlBackup(database string) error { // PostgresqlRestore PostgreSQL恢复 func (s *BackupImpl) PostgresqlRestore(database string, backupFile string) error { backupPath := s.setting.Get(models.SettingKeyBackupPath) + "/postgresql" - ext := filepath.Ext(backupFile) - backupFile = backupPath + "/" + backupFile - if !tools.Exists(backupFile) { + backupFullPath := filepath.Join(backupPath, backupFile) + if !tools.Exists(backupFullPath) { return errors.New("备份文件不存在") } - switch ext { - case ".zip": - if _, err := tools.Exec("unzip -o " + backupFile + " -d " + backupPath); err != nil { + tempDir, err := tools.TempDir(backupFile) + if err != nil { + return err + } + defer tools.Remove(tempDir) + + if !strings.HasSuffix(backupFile, ".sql") { + backupFile = "" // 置空,防止干扰后续判断 + if err = tools.UnArchive(backupFullPath, tempDir); err != nil { return err } - backupFile = strings.TrimSuffix(backupFile, ext) - case ".gz": - if strings.HasSuffix(backupFile, ".tar.gz") { - // 解压.tar.gz文件 - if _, err := tools.Exec("tar -zxvf " + backupFile + " -C " + backupPath); err != nil { - return err + if files, err := os.ReadDir(tempDir); err == nil { + for _, file := range files { + if strings.HasSuffix(file.Name(), ".sql") { + backupFile = filepath.Base(file.Name()) + break + } } - backupFile = strings.TrimSuffix(backupFile, ".tar.gz") - } else { - // 解压.gz文件 - if _, err := tools.Exec("gzip -d " + backupFile); err != nil { - return err - } - backupFile = strings.TrimSuffix(backupFile, ext) } - case ".bz2": - if _, err := tools.Exec("bzip2 -d " + backupFile); err != nil { + } else { + if err = tools.Cp(backupFullPath, filepath.Join(tempDir, backupFile)); err != nil { return err } - backupFile = strings.TrimSuffix(backupFile, ext) - case ".tar": - if _, err := tools.Exec("tar -xvf " + backupFile + " -C " + backupPath); err != nil { - return err - } - backupFile = strings.TrimSuffix(backupFile, ext) - case ".rar": - if _, err := tools.Exec("unrar x " + backupFile + " " + backupPath); err != nil { - return err - } - backupFile = strings.TrimSuffix(backupFile, ext) } - if !tools.Exists(backupFile) { - return errors.New("自动解压失败,请手动解压") + if len(backupFile) == 0 { + return errors.New("无法找到备份文件") } - if _, err := tools.Exec(`su - postgres -c "psql ` + database + `" < ` + backupFile); err != nil { + if _, err = tools.Exec(`su - postgres -c "psql ` + database + `" < ` + filepath.Join(tempDir, backupFile)); err != nil { return err } diff --git a/app/services/website.go b/app/services/website.go index 9e519ca1..f5a1b8a0 100644 --- a/app/services/website.go +++ b/app/services/website.go @@ -611,14 +611,10 @@ func (r *WebsiteImpl) GetConfig(id uint) (WebsiteSetting, error) { setting.OpenBasedir = false } - cert, err := tools.Read("/www/server/vhost/ssl/" + website.Name + ".pem") - if err == nil { - setting.SslCertificate = cert - } - key, err := tools.Read("/www/server/vhost/ssl/" + website.Name + ".key") - if err == nil { - setting.SslCertificateKey = key - } + cert, _ := tools.Read("/www/server/vhost/ssl/" + website.Name + ".pem") + setting.SslCertificate = cert + key, _ := tools.Read("/www/server/vhost/ssl/" + website.Name + ".key") + setting.SslCertificateKey = key if setting.Ssl { ssl := tools.Cut(config, "# ssl标记位开始", "# ssl标记位结束") setting.HttpRedirect = strings.Contains(ssl, "# http重定向标记位") @@ -655,11 +651,9 @@ func (r *WebsiteImpl) GetConfig(id uint) (WebsiteSetting, error) { setting.WafCache = match[1] } - rewrite, err := tools.Read("/www/server/vhost/rewrite/" + website.Name + ".conf") - if err == nil { - setting.Rewrite = rewrite - } - log, err := tools.Exec(`tail -n 100 '/www/wwwlogs/` + website.Name + `.log'`) + rewrite, _ := tools.Read("/www/server/vhost/rewrite/" + website.Name + ".conf") + setting.Rewrite = rewrite + log, _ := tools.Exec(`tail -n 100 '/www/wwwlogs/` + website.Name + `.log'`) setting.Log = log return setting, err diff --git a/go.mod b/go.mod index 12af049b..02995cb4 100644 --- a/go.mod +++ b/go.mod @@ -12,6 +12,7 @@ require ( github.com/goravel/framework v1.13.1-0.20231117140817-50ecec9871ec github.com/iancoleman/strcase v0.3.0 github.com/imroc/req/v3 v3.42.1 + github.com/mholt/archiver/v3 v3.5.1 github.com/mojocn/base64Captcha v1.3.5 github.com/shirou/gopsutil v3.21.11+incompatible github.com/spf13/cast v1.5.1 @@ -50,6 +51,7 @@ require ( github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/docker/docker v24.0.7+incompatible // indirect + github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/gabriel-vasile/mimetype v1.4.3 // indirect @@ -111,6 +113,7 @@ require ( github.com/kelseyhightower/envconfig v1.4.0 // indirect github.com/klauspost/compress v1.17.3 // indirect github.com/klauspost/cpuid/v2 v2.2.6 // indirect + github.com/klauspost/pgzip v1.2.5 // indirect github.com/lestrrat-go/strftime v1.0.5 // indirect github.com/lib/pq v1.10.2 // indirect github.com/magiconair/properties v1.8.7 // indirect @@ -124,10 +127,12 @@ require ( github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/nrdcg/dnspod-go v0.4.0 // indirect + github.com/nwaples/rardecode v1.1.0 // indirect github.com/onsi/ginkgo/v2 v2.12.0 // indirect github.com/opentracing/opentracing-go v1.2.0 // indirect github.com/patrickmn/go-cache v2.1.0+incompatible // indirect github.com/pelletier/go-toml/v2 v2.1.0 // indirect + github.com/pierrec/lz4/v4 v4.1.16 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/quic-go/qpack v0.4.0 // indirect @@ -156,12 +161,14 @@ require ( github.com/tklauser/go-sysconf v0.3.11 // indirect github.com/tklauser/numcpus v0.6.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ulikunitz/xz v0.5.10 // indirect github.com/urfave/cli/v2 v2.25.7 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/tcplisten v1.0.0 // indirect github.com/xdg-go/pbkdf2 v1.0.0 // indirect github.com/xdg-go/scram v1.1.1 // indirect github.com/xdg-go/stringprep v1.0.3 // indirect + github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect diff --git a/go.sum b/go.sum index b0702879..73a966da 100644 --- a/go.sum +++ b/go.sum @@ -92,6 +92,7 @@ github.com/RichardKnop/machinery/v2 v2.0.12-0.20231012204029-bdb94a90ca41 h1:7fL github.com/RichardKnop/machinery/v2 v2.0.12-0.20231012204029-bdb94a90ca41/go.mod h1:92dLVxckr2Lv7oKxoS3Uci0Of8Cuy+lmPi2dd6Euwkw= github.com/aliyun/alibaba-cloud-sdk-go v1.61.1755 h1:J45/QHgrzUdqe/Vco/Vxk0wRvdS2nKUxmf/zLgvfass= github.com/aliyun/alibaba-cloud-sdk-go v1.61.1755/go.mod h1:RcDobYh8k5VP6TNybz9m++gL3ijVI5wueVr0EM10VsU= +github.com/andybalholm/brotli v1.0.1/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y= github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI= github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/aws/aws-sdk-go v1.34.28/go.mod h1:H7NKnBqNVzoTJpGfLrQkkD+ytBA93eiDYi/+8rV9s48= @@ -152,6 +153,9 @@ github.com/docker/docker v24.0.7+incompatible h1:Wo6l37AuwP3JaMnZa226lzVXGA3F9Ig github.com/docker/docker v24.0.7+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= +github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 h1:iFaUwBSo5Svw6L7HYpRu/0lE3e0BaElwnNO1qkNQxBY= +github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5/go.mod h1:qssHWj60/X5sZFNxpG4HBPDHVqxNm4DfnCKgrbZOT+s= +github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= @@ -310,6 +314,7 @@ github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiu github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.2/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/gomodule/redigo v1.8.2/go.mod h1:P9dn9mFrCBvWhGE1wpxx6fgq7BAeLBk+UUUzlpkBYO0= @@ -452,13 +457,18 @@ github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dv github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/compress v1.9.5/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/klauspost/compress v1.11.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/klauspost/compress v1.17.3 h1:qkRjuerhUU1EmXLYGkSH6EZL+vPSxIrYjLNAK4slzwA= github.com/klauspost/compress v1.17.3/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= +github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc= github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/klauspost/pgzip v1.2.5 h1:qnWYvvKqedOF2ulHpMG72XQol4ILEJ8k2wwRl/Km8oE= +github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= @@ -495,6 +505,8 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mholt/archiver/v3 v3.5.1 h1:rDjOBX9JSF5BvoJGvjqK479aL70qh9DIpZCl+k7Clwo= +github.com/mholt/archiver/v3 v3.5.1/go.mod h1:e3dqJ7H78uzsRSEACH1joayhuSyhnonssnDhppzS1L4= github.com/microsoft/go-mssqldb v1.6.0 h1:mM3gYdVwEPFrlg/Dvr2DNVEgYFG7L42l+dGc67NNNpc= github.com/microsoft/go-mssqldb v1.6.0/go.mod h1:00mDtPbeQCRGC1HwOOR5K/gr30P1NcEG0vx6Kbv2aJU= github.com/miekg/dns v1.1.55 h1:GoQ4hpsj0nFLYe+bWiCToyrBEJXkQfOOIvFGFy0lEgo= @@ -517,6 +529,8 @@ github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nrdcg/dnspod-go v0.4.0 h1:c/jn1mLZNKF3/osJ6mz3QPxTudvPArXTjpkmYj0uK6U= github.com/nrdcg/dnspod-go v0.4.0/go.mod h1:vZSoFSFeQVm2gWLMkyX61LZ8HI3BaqtHZWgPTGKr6KQ= +github.com/nwaples/rardecode v1.1.0 h1:vSxaY8vQhOcVr4mm5e8XllHWTiM4JF507A0Katqw7MQ= +github.com/nwaples/rardecode v1.1.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= @@ -540,6 +554,9 @@ github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTK github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE= github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= +github.com/pierrec/lz4/v4 v4.1.2/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= +github.com/pierrec/lz4/v4 v4.1.16 h1:kQPfno+wyx6C5572ABwV+Uo3pDFzQ7yhyGchSyRda0c= +github.com/pierrec/lz4/v4 v4.1.16/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU= github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -646,6 +663,10 @@ github.com/tklauser/numcpus v0.6.0 h1:kebhY2Qt+3U6RNK7UqpYNA+tJ23IBEGKkB7JQBfDYm github.com/tklauser/numcpus v0.6.0/go.mod h1:FEZLMke0lhOUG6w2JadTzp0a+Nl8PF/GFkQ5UVIcaL4= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= +github.com/ulikunitz/xz v0.5.9/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= +github.com/ulikunitz/xz v0.5.10 h1:t92gobL9l3HE202wg3rlk19F6X+JOxl9BBrCCMYEYd8= +github.com/ulikunitz/xz v0.5.10/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/urfave/cli v1.22.5/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli/v2 v2.25.7 h1:VAzn5oq403l5pHjc4OhD54+XGO9cdKVL/7lDjF+iKUs= github.com/urfave/cli/v2 v2.25.7/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ= @@ -665,6 +686,8 @@ github.com/xdg-go/stringprep v1.0.3 h1:kdwGpVNwPFtjs98xCGkHjQtGKh86rDcRZN17QEMCO github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8= github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I= github.com/xdg/stringprep v0.0.0-20180714160509-73f8eece6fdc/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y= +github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo= +github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= diff --git a/pkg/tools/os.go b/pkg/tools/os.go index e9bd5c90..676385f1 100644 --- a/pkg/tools/os.go +++ b/pkg/tools/os.go @@ -21,3 +21,13 @@ func IsRHEL() bool { func IsArm() bool { return runtime.GOARCH == "arm" || runtime.GOARCH == "arm64" } + +// IsLinux 判断是否是 Linux 系统 +func IsLinux() bool { + return runtime.GOOS == "linux" +} + +// IsWindows 判断是否是 Windows 系统 +func IsWindows() bool { + return runtime.GOOS == "windows" +} diff --git a/pkg/tools/os_test.go b/pkg/tools/os_test.go index 1f5a744f..9d37e5be 100644 --- a/pkg/tools/os_test.go +++ b/pkg/tools/os_test.go @@ -15,10 +15,16 @@ func TestOSHelperTestSuite(t *testing.T) { } func (s *OSHelperTestSuite) TestIsDebian() { + if IsWindows() { + return + } s.True(IsDebian()) } func (s *OSHelperTestSuite) TestIsRHEL() { + if IsWindows() { + return + } s.False(IsRHEL()) } diff --git a/pkg/tools/system.go b/pkg/tools/system.go index e4813735..543a8997 100644 --- a/pkg/tools/system.go +++ b/pkg/tools/system.go @@ -4,12 +4,17 @@ import ( "bytes" "errors" "fmt" + "io" + "io/ioutil" "os" "os/exec" + "os/user" "path/filepath" "strings" "github.com/goravel/framework/support" + "github.com/mholt/archiver/v3" + "github.com/spf13/cast" ) // Write 写入文件 @@ -39,7 +44,12 @@ func Remove(path string) error { // Exec 执行 shell 命令 func Exec(shell string) (string, error) { - cmd := exec.Command("bash", "-c", "LC_ALL=C "+shell) + var cmd *exec.Cmd + if IsLinux() { + cmd = exec.Command("bash", "-c", "LC_ALL=C "+shell) + } else { + cmd = exec.Command("cmd", "/C", "chcp 65001 >nul && "+shell) + } var stdoutBuf, stderrBuf bytes.Buffer cmd.Stdout = &stdoutBuf @@ -55,7 +65,13 @@ func Exec(shell string) (string, error) { // ExecAsync 异步执行 shell 命令 func ExecAsync(shell string) error { - cmd := exec.Command("bash", "-c", shell) + var cmd *exec.Cmd + if IsLinux() { + cmd = exec.Command("bash", "-c", "LC_ALL=C "+shell) + } else { + cmd = exec.Command("cmd", "/C", "chcp 65001 >nul && "+shell) + } + err := cmd.Start() if err != nil { return err @@ -84,10 +100,53 @@ func Chmod(path string, permission os.FileMode) error { return os.Chmod(path, permission) } -// Chown 修改文件/目录所有者 -func Chown(path, user, group string) error { - cmd := exec.Command("chown", "-R", user+":"+group, path) - return cmd.Run() +// Chown 修改文件或目录所有者 +func Chown(path, userName, groupName string) error { + if IsWindows() { + return errors.New("chown is not supported on Windows") + } + + usr, err := user.Lookup(userName) + if err != nil { + return err + } + + grp, err := user.LookupGroup(groupName) + if err != nil { + return err + } + + return chownR(path, cast.ToInt(usr.Uid), cast.ToInt(grp.Gid)) +} + +// chownR 递归修改文件或目录的所有者 +func chownR(path string, uid, gid int) error { + err := os.Chown(path, uid, gid) + if err != nil { + return err + } + + fileInfo, err := os.Stat(path) + if err != nil { + return err + } + + if fileInfo.IsDir() { + entries, err := os.ReadDir(path) + if err != nil { + return err + } + + for _, entry := range entries { + subPath := path + "/" + entry.Name() + err = chownR(subPath, uid, gid) + if err != nil { + return err + } + } + } + + return nil } // Exists 判断路径是否存在 @@ -106,18 +165,85 @@ func Empty(path string) bool { return len(files) == 0 } -// Mv 移动文件/目录 func Mv(src, dst string) error { - cmd := exec.Command("mv", src, dst) - - return cmd.Run() + err := os.Rename(src, dst) + if err != nil { + // 如果在不同的文件系统中移动文件,os.Rename 可能会失败 + // 在这种情况下,可以先复制然后删除原文件 + if os.IsExist(err) { + err := Cp(src, dst) + if err != nil { + return err + } + err = os.RemoveAll(src) + } + } + return err } -// Cp 复制文件/目录 +// Cp 复制文件或目录 func Cp(src, dst string) error { - cmd := exec.Command("cp", "-r", src, dst) + srcInfo, err := os.Stat(src) + if err != nil { + return err + } - return cmd.Run() + if srcInfo.IsDir() { + return copyDir(src, dst) + } + return copyFile(src, dst) +} + +func copyFile(src, dst string) error { + srcFile, err := os.Open(src) + if err != nil { + return err + } + defer srcFile.Close() + + dstFile, err := os.Create(dst) + if err != nil { + return err + } + defer dstFile.Close() + + _, err = io.Copy(dstFile, srcFile) + return err +} + +func copyDir(src, dst string) error { + srcInfo, err := os.Stat(src) + if err != nil { + return err + } + + err = os.MkdirAll(dst, srcInfo.Mode()) + if err != nil { + return err + } + + entries, err := ioutil.ReadDir(src) + if err != nil { + return err + } + + for _, entry := range entries { + srcPath := filepath.Join(src, entry.Name()) + dstPath := filepath.Join(dst, entry.Name()) + + if entry.IsDir() { + err = copyDir(srcPath, dstPath) + if err != nil { + return err + } + } else { + err = copyFile(srcPath, dstPath) + if err != nil { + return err + } + } + } + return nil } // Size 获取路径大小 @@ -139,3 +265,23 @@ func Size(path string) (int64, error) { func FileInfo(path string) (os.FileInfo, error) { return os.Stat(path) } + +// UnArchive 智能解压文件 +func UnArchive(file string, dst string) error { + return archiver.Unarchive(file, dst) +} + +// Archive 智能压缩文件 +func Archive(src []string, dst string) error { + return archiver.Archive(src, dst) +} + +// TempDir 创建临时目录 +func TempDir(prefix string) (string, error) { + return os.MkdirTemp("", prefix) +} + +// TempFile 创建临时文件 +func TempFile(prefix string) (*os.File, error) { + return os.CreateTemp("", prefix) +} diff --git a/pkg/tools/system_test.go b/pkg/tools/system_test.go index 86015de4..c5a79e46 100644 --- a/pkg/tools/system_test.go +++ b/pkg/tools/system_test.go @@ -1,8 +1,8 @@ package tools import ( - "os" "os/user" + "path/filepath" "testing" "time" @@ -18,78 +18,88 @@ func TestSystemHelperTestSuite(t *testing.T) { } func (s *SystemHelperTestSuite) TestWrite() { - filePath := "/tmp/testfile" - defer os.Remove(filePath) + filePath, _ := TempFile("testfile") + defer Remove(filePath.Name()) - s.Nil(Write(filePath, "test data", 0644)) - s.FileExists(filePath) + s.Nil(Write(filePath.Name(), "test data", 0644)) + s.FileExists(filePath.Name()) - content, _ := os.ReadFile(filePath) - s.Equal("test data", string(content)) + content, _ := Read(filePath.Name()) + s.Equal("test data", content) } func (s *SystemHelperTestSuite) TestRead() { - filePath := "/tmp/testfile" - defer os.Remove(filePath) + filePath, _ := TempFile("testfile") + defer Remove(filePath.Name()) - err := os.WriteFile(filePath, []byte("test data"), 0644) + err := Write(filePath.Name(), "test data", 0644) s.Nil(err) - data, err := Read(filePath) + data, err := Read(filePath.Name()) s.Nil(err) s.Equal("test data", data) } func (s *SystemHelperTestSuite) TestRemove() { - filePath := "/tmp/testfile" + file, _ := TempFile("testfile") + file.Close() - err := os.WriteFile(filePath, []byte("test data"), 0644) + err := Write(file.Name(), "test data", 0644) s.Nil(err) - s.Nil(Remove(filePath)) + s.Nil(Remove(file.Name())) } func (s *SystemHelperTestSuite) TestExec() { - output, err := Exec("echo 'test'") + output, err := Exec("echo test") s.Equal("test", output) s.Nil(err) } func (s *SystemHelperTestSuite) TestExecAsync() { - command := "echo 'test' > /tmp/testfile" - defer os.Remove("/tmp/testfile") + command := "echo test > test.txt" + if IsWindows() { + command = "echo test> test.txt" + } err := ExecAsync(command) s.Nil(err) time.Sleep(time.Second) - content, _ := os.ReadFile("/tmp/testfile") - s.Equal("test\n", string(content)) + content, err := Read("test.txt") + s.Nil(err) + + condition := "test\n" + if IsWindows() { + condition = "test\r\n" + } + s.Equal(condition, content) + s.Nil(Remove("test.txt")) } func (s *SystemHelperTestSuite) TestMkdir() { - dirPath := "/tmp/testdir" - defer os.RemoveAll(dirPath) + dirPath, _ := TempDir("testdir") + defer Remove(dirPath) s.Nil(Mkdir(dirPath, 0755)) } func (s *SystemHelperTestSuite) TestChmod() { - filePath := "/tmp/testfile" - defer os.Remove(filePath) + filePath, _ := TempFile("testfile") + defer Remove(filePath.Name()) - err := os.WriteFile(filePath, []byte("test data"), 0644) + err := Write(filePath.Name(), "test data", 0644) s.Nil(err) - s.Nil(Chmod(filePath, 0755)) + s.Nil(Chmod(filePath.Name(), 0755)) } func (s *SystemHelperTestSuite) TestChown() { - filePath := "/tmp/testfile" - defer os.Remove(filePath) + filePath, _ := TempFile("testfile") + defer Remove(filePath.Name()) - err := os.WriteFile(filePath, []byte("test data"), 0644) + err := Write(filePath.Name(), "test data", 0644) s.Nil(err) currentUser, err := user.Current() @@ -97,48 +107,132 @@ func (s *SystemHelperTestSuite) TestChown() { groups, err := currentUser.GroupIds() s.Nil(err) - s.Nil(Chown(filePath, currentUser.Username, groups[0])) + err = Chown(filePath.Name(), currentUser.Username, groups[0]) + if IsWindows() { + s.NotNil(err) + } else { + s.Nil(err) + } } func (s *SystemHelperTestSuite) TestExists() { - s.True(Exists("/tmp")) + filePath, _ := TempFile("testfile") + defer Remove(filePath.Name()) + + s.True(Exists(filePath.Name())) s.False(Exists("/tmp/123")) } func (s *SystemHelperTestSuite) TestEmpty() { - s.True(Empty("/tmp/123")) - s.False(Empty("/tmp")) + filePath, _ := TempFile("testfile") + defer Remove(filePath.Name()) + + s.True(Empty(filePath.Name())) + if IsWindows() { + s.True(Empty("C:\\Windows\\System32\\drivers\\etc\\hosts")) + } else { + s.True(Empty("/etc/hosts")) + } } func (s *SystemHelperTestSuite) TestMv() { - filePath := "/tmp/testfile" - defer os.Remove(filePath) + filePath, _ := TempFile("testfile") + defer Remove(filePath.Name()) - err := os.WriteFile(filePath, []byte("test data"), 0644) + err := Write(filePath.Name(), "test data", 0644) s.Nil(err) - s.Nil(Mv(filePath, "/tmp/testfile2")) - s.False(Exists(filePath)) + newFilePath, _ := TempFile("testfile2") + defer Remove(newFilePath.Name()) + + filePath.Close() + newFilePath.Close() + + s.Nil(Mv(filePath.Name(), newFilePath.Name())) + s.False(Exists(filePath.Name())) } func (s *SystemHelperTestSuite) TestCp() { - filePath := "/tmp/testfile" - defer os.Remove(filePath) + tempDir, _ := TempDir("testdir") + defer Remove(tempDir) - err := os.WriteFile(filePath, []byte("test data"), 0644) + err := Write(filepath.Join(tempDir, "testfile"), "test data", 0644) s.Nil(err) - s.Nil(Cp(filePath, "/tmp/testfile2")) - s.True(Exists(filePath)) + s.Nil(Cp(filepath.Join(tempDir, "testfile"), filepath.Join(tempDir, "testfile2"))) + s.True(Exists(filepath.Join(tempDir, "testfile2"))) } func (s *SystemHelperTestSuite) TestSize() { - size, err := Size("/tmp/123") - s.Equal(int64(0), size) - s.Error(err) + filePath, _ := TempFile("testfile") + defer Remove(filePath.Name()) + + err := Write(filePath.Name(), "test data", 0644) + s.Nil(err) + + size, err := Size(filePath.Name()) + s.Nil(err) + s.Equal(int64(len("test data")), size) } func (s *SystemHelperTestSuite) TestFileInfo() { - _, err := FileInfo("/tmp/123") - s.Error(err) + filePath, _ := TempFile("testfile") + defer Remove(filePath.Name()) + + err := Write(filePath.Name(), "test data", 0644) + s.Nil(err) + + info, err := FileInfo(filePath.Name()) + s.Nil(err) + s.Equal(filepath.Base(filePath.Name()), info.Name()) +} + +func (s *SystemHelperTestSuite) TestUnArchiveSuccessfullyUnarchivesFile() { + file, _ := TempFile("test") + defer Remove(file.Name()) + dstDir, _ := TempDir("archive") + defer Remove(dstDir) + + err := Write(file.Name(), "test data", 0644) + s.Nil(err) + + err = Archive([]string{file.Name()}, filepath.Join(dstDir, "test.zip")) + s.Nil(err) + s.FileExists(filepath.Join(dstDir, "test.zip")) + + err = UnArchive(filepath.Join(dstDir, "test.zip"), dstDir) + s.Nil(err) + s.FileExists(filepath.Join(dstDir, filepath.Base(file.Name()))) +} + +func (s *SystemHelperTestSuite) TestUnArchiveFailsForNonExistentFile() { + srcFile := "nonexistent.zip" + dstDir, _ := TempDir("unarchived") + defer Remove(dstDir) + + err := UnArchive(srcFile, dstDir) + s.NotNil(err) +} + +func (s *SystemHelperTestSuite) TestArchiveSuccessfullyArchivesFiles() { + srcFile, _ := TempFile("test") + defer Remove(srcFile.Name()) + dstDir, _ := TempDir("archive") + defer Remove(dstDir) + + err := Write(srcFile.Name(), "test data", 0644) + s.Nil(err) + + err = Archive([]string{srcFile.Name()}, filepath.Join(dstDir, "test.zip")) + s.Nil(err) + s.FileExists(filepath.Join(dstDir, "test.zip")) +} + +func (s *SystemHelperTestSuite) TestArchiveFailsForNonExistentFiles() { + srcFile := "nonexistent" + dstDir, _ := TempDir("archive") + defer Remove(dstDir) + + err := Archive([]string{srcFile}, filepath.Join(dstDir, "test.zip")) + s.NotNil(err) } diff --git a/pkg/tools/tools_test.go b/pkg/tools/tools_test.go index d5959e70..e61b6991 100644 --- a/pkg/tools/tools_test.go +++ b/pkg/tools/tools_test.go @@ -84,12 +84,18 @@ func (s *HelperTestSuite) TestGenerateVersions() { } func (s *HelperTestSuite) TestGetLatestPanelVersion() { + if IsWindows() { + return + } version, err := GetLatestPanelVersion() s.NotEmpty(version) s.Nil(err) } func (s *HelperTestSuite) TestGetPanelVersion() { + if IsWindows() { + return + } version, err := GetPanelVersion("v2.0.58") s.NotEmpty(version) s.Nil(err)