mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2026-10-09 08:00:27 +00:00
Compare commits
44
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6e465ac215 | ||
|
|
21bbadc37b | ||
|
|
3061d640ce | ||
|
|
874717cbcd | ||
|
|
6850acabae | ||
|
|
890ea42749 | ||
|
|
6aaf61cde4 | ||
|
|
73930c5e29 | ||
|
|
8f8d280e90 | ||
|
|
b46192024f | ||
|
|
8f939ad5be | ||
|
|
a6363ce4e5 | ||
|
|
fd490b2642 | ||
|
|
7624670ee0 | ||
|
|
8c3145e1c6 | ||
|
|
bec7b6989f | ||
|
|
326f2654e6 | ||
|
|
708499a335 | ||
|
|
bdeb842939 | ||
|
|
edefdce3ff | ||
|
|
b348c36571 | ||
|
|
6ae150b64e | ||
|
|
3687b9bea2 | ||
|
|
711431da82 | ||
|
|
e93afc33aa | ||
|
|
82dd993751 | ||
|
|
9ba293e154 | ||
|
|
8fa9f128d8 | ||
|
|
ed9f1599e6 | ||
|
|
787a29769d | ||
|
|
6b379389d3 | ||
|
|
5bcbf32823 | ||
|
|
0aa90e1b2d | ||
|
|
a7b87211b4 | ||
|
|
2c666933a5 | ||
|
|
f280b533c6 | ||
|
|
47a16bc9a9 | ||
|
|
713202b6eb | ||
|
|
79020abb1c | ||
|
|
83ef41cf1a | ||
|
|
91675c69e9 | ||
|
|
0e68065b40 | ||
|
|
600b769083 | ||
|
|
e7670f96de |
+5
-3
@@ -10,7 +10,7 @@ before:
|
||||
- ./ci/script.sh
|
||||
- sed -i 's@ORIGINAL_VERSION=.*@ORIGINAL_VERSION=v{{ .Version }}@g' 1pctl
|
||||
- go mod tidy
|
||||
|
||||
|
||||
builds:
|
||||
- main: ./cmd/server/main.go
|
||||
binary: 1panel
|
||||
@@ -31,6 +31,7 @@ builds:
|
||||
- arm
|
||||
- ppc64le
|
||||
- s390x
|
||||
- riscv64
|
||||
|
||||
archives:
|
||||
- formats: [ 'tar.gz' ]
|
||||
@@ -38,10 +39,11 @@ archives:
|
||||
wrap_in_directory: true
|
||||
files:
|
||||
- 1pctl
|
||||
- 1panel.service
|
||||
- install.sh
|
||||
- GeoIP.mmdb
|
||||
- 1panel.service
|
||||
- initscript/*
|
||||
- lang/*
|
||||
- GeoIP.mmdb
|
||||
|
||||
checksum:
|
||||
name_template: 'checksums.txt'
|
||||
|
||||
@@ -23,7 +23,7 @@ build_frontend:
|
||||
|
||||
build_backend_on_linux:
|
||||
cd $(SERVER_PATH) \
|
||||
&& GOOS=$(GOOS) GOARCH=$(GOARCH) $(GOBUILD) -trimpath -ldflags '-s -w' -o $(BUILD_PATH)/$(APP_NAME) $(MAIN)
|
||||
&& CGO_ENABLED=0 GOOS=$(GOOS) GOARCH=$(GOARCH) $(GOBUILD) -trimpath -ldflags '-s -w' -o $(BUILD_PATH)/$(APP_NAME) $(MAIN)
|
||||
|
||||
build_backend_on_darwin:
|
||||
cd $(SERVER_PATH) \
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<p align="center"><a href="https://1panel.pro"><img src="https://resource.1panel.pro/img/1panel-logo.png" alt="1Panel" width="300" /></a></p>
|
||||
<p align="center"><b>Top-Rated Web-based Linux Server Management Tool</b><br>Best VPS control panel<br>新一代的 Linux 服务器运维管理面板</p>
|
||||
<p align="center"><b>Simplify Linux Server Management</b></p>
|
||||
<p align="center">
|
||||
<a href="https://trendshift.io/repositories/2462" target="_blank"><img src="https://trendshift.io/api/badge/repositories/2462" alt="1Panel-dev%2F1Panel | Trendshift" style="width: 240px; height: auto;" /></a>
|
||||
</p>
|
||||
@@ -31,13 +31,14 @@
|
||||
|
||||
------------------------------
|
||||
|
||||
1Panel is an open-source, modern web-based control panel for Linux server management.
|
||||
1Panel provides an intuitive web interface and MCP Server to manage websites, files, containers, databases, and LLMs on a Linux server.
|
||||
|
||||
- **Efficient Management**: Through a user-friendly web graphical interface, 1Panel enables users to effortlessly manage their Linux servers. Key features include host monitoring, file management, database administration, container management, LLMs management.
|
||||
- **Rapid Website Deployment**: With deep integration of the popular open-source website building software WordPress, 1Panel streamlines the process of domain binding and SSL certificate configuration, all achievable with just one click.
|
||||
- **Application Store**: 1Panel curates a wide range of high-quality open-source tools and applications, facilitating easy installation and updates for its users.
|
||||
- **Security and Reliability**: By leveraging containerization and secure application deployment practices, 1Panel minimizes vulnerability exposure. It further enhances security through integrated firewall management and log auditing capabilities.
|
||||
- **One-Click Backup & Restore**: Data protection is made simple with 1Panel's one-click backup and restore functionality, supporting various cloud storage solutions to ensure data integrity and availability.
|
||||
- **MCP Server**: [1Panel MCP Server](https://github.com/1Panel-dev/mcp-1panel) allow user to execute server operations via natural language.
|
||||
|
||||
## Quick Start
|
||||
|
||||
@@ -49,7 +50,7 @@ curl -sSL https://resource.1panel.pro/quick_start.sh -o quick_start.sh && bash q
|
||||
|
||||
Please refer to our [documentation](https://docs.1panel.pro/quick_start/) for more details.
|
||||
|
||||
中国用户请使用这个 [安装脚本](https://1panel.cn/docs/installation/online_installation/),其应用数量比国际版本更丰富。
|
||||
中国用户请使用此 [安装脚本](https://1panel.cn/docs/v2/installation/online_installation/),默认安装 V2 版本,应用数量相较国际版本更丰富。
|
||||
|
||||
## Screenshot
|
||||
|
||||
@@ -69,6 +70,10 @@ Compared to the OSS Edition, 1Panel Pro Edition provides users with a wealth of
|
||||
|
||||
If you discover any security issues, please refer to [SECURITY.md](/SECURITY.md).
|
||||
|
||||
## GitAds Sponsored
|
||||
|
||||
[](https://gitads.dev/v1/ad-track?source=1panel-dev/1panel@github)
|
||||
|
||||
## License
|
||||
|
||||
Licensed under The GNU General Public License version 3 (GPLv3) (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
|
||||
@@ -76,3 +81,7 @@ Licensed under The GNU General Public License version 3 (GPLv3) (the "License")
|
||||
<https://www.gnu.org/licenses/gpl-3.0.html>
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
|
||||
|
||||
<!-- GitAds-Verify: 9M8AMBB11GYJ3RUNXGB5R1MR71KPATU9 -->
|
||||
|
||||
|
||||
|
||||
@@ -6,8 +6,8 @@ type ImageRepoCreate struct {
|
||||
Name string `json:"name" validate:"required"`
|
||||
DownloadUrl string `json:"downloadUrl"`
|
||||
Protocol string `json:"protocol"`
|
||||
Username string `json:"username" validate:"max=256"`
|
||||
Password string `json:"password" validate:"max=256"`
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
Auth bool `json:"auth"`
|
||||
}
|
||||
|
||||
@@ -15,8 +15,8 @@ type ImageRepoUpdate struct {
|
||||
ID uint `json:"id"`
|
||||
DownloadUrl string `json:"downloadUrl"`
|
||||
Protocol string `json:"protocol"`
|
||||
Username string `json:"username" validate:"max=256"`
|
||||
Password string `json:"password" validate:"max=256"`
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
Auth bool `json:"auth"`
|
||||
}
|
||||
|
||||
|
||||
@@ -92,11 +92,12 @@ type FileWget struct {
|
||||
}
|
||||
|
||||
type FileMove struct {
|
||||
Type string `json:"type" validate:"required"`
|
||||
OldPaths []string `json:"oldPaths" validate:"required"`
|
||||
NewPath string `json:"newPath" validate:"required"`
|
||||
Name string `json:"name"`
|
||||
Cover bool `json:"cover"`
|
||||
Type string `json:"type" validate:"required"`
|
||||
OldPaths []string `json:"oldPaths" validate:"required"`
|
||||
NewPath string `json:"newPath" validate:"required"`
|
||||
Name string `json:"name"`
|
||||
Cover bool `json:"cover"`
|
||||
CoverPaths []string `json:"coverPaths"`
|
||||
}
|
||||
|
||||
type FileDownload struct {
|
||||
|
||||
@@ -53,4 +53,5 @@ type ExistFileInfo struct {
|
||||
Path string `json:"path"`
|
||||
Size float64 `json:"size"`
|
||||
ModTime time.Time `json:"modTime"`
|
||||
IsDir bool `json:"isDir"`
|
||||
}
|
||||
|
||||
@@ -518,47 +518,40 @@ func (u *BackupService) loadAccessToken(backup *model.BackupAccount) error {
|
||||
}
|
||||
|
||||
func (u *BackupService) loadRecordSize(records []model.BackupRecord) ([]dto.BackupFile, error) {
|
||||
var datas []dto.BackupFile
|
||||
data := make([]dto.BackupFile, len(records))
|
||||
clientMap := make(map[string]loadSizeHelper)
|
||||
var wg sync.WaitGroup
|
||||
for i := 0; i < len(records); i++ {
|
||||
var item dto.BackupFile
|
||||
item.ID = records[i].ID
|
||||
item.Name = records[i].FileName
|
||||
data[i].ID = records[i].ID
|
||||
data[i].Name = records[i].FileName
|
||||
itemPath := path.Join(records[i].FileDir, records[i].FileName)
|
||||
if _, ok := clientMap[records[i].Source]; !ok {
|
||||
backup, err := backupRepo.Get(commonRepo.WithByType(records[i].Source))
|
||||
if err != nil {
|
||||
global.LOG.Errorf("load backup model %s from db failed, err: %v", records[i].Source, err)
|
||||
clientMap[records[i].Source] = loadSizeHelper{}
|
||||
datas = append(datas, item)
|
||||
continue
|
||||
}
|
||||
client, err := u.NewClient(&backup)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("load backup client %s from db failed, err: %v", records[i].Source, err)
|
||||
clientMap[records[i].Source] = loadSizeHelper{}
|
||||
datas = append(datas, item)
|
||||
continue
|
||||
}
|
||||
item.Size, _ = client.Size(path.Join(strings.TrimLeft(backup.BackupPath, "/"), itemPath))
|
||||
datas = append(datas, item)
|
||||
data[i].Size, _ = client.Size(path.Join(strings.TrimLeft(backup.BackupPath, "/"), itemPath))
|
||||
clientMap[records[i].Source] = loadSizeHelper{backupPath: strings.TrimLeft(backup.BackupPath, "/"), client: client, isOk: true}
|
||||
continue
|
||||
}
|
||||
if clientMap[records[i].Source].isOk {
|
||||
wg.Add(1)
|
||||
go func(index int) {
|
||||
item.Size, _ = clientMap[records[index].Source].client.Size(path.Join(clientMap[records[index].Source].backupPath, itemPath))
|
||||
datas = append(datas, item)
|
||||
data[i].Size, _ = clientMap[records[index].Source].client.Size(path.Join(clientMap[records[index].Source].backupPath, itemPath))
|
||||
wg.Done()
|
||||
}(i)
|
||||
} else {
|
||||
datas = append(datas, item)
|
||||
}
|
||||
}
|
||||
wg.Wait()
|
||||
return datas, nil
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func loadLocalDir() (string, error) {
|
||||
|
||||
+103
-129
@@ -1,7 +1,6 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
@@ -28,14 +27,14 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
clamServiceNameCentOs = "clamd@scan.service"
|
||||
clamServiceNameUbuntu = "clamav-daemon.service"
|
||||
freshClamService = "clamav-freshclam.service"
|
||||
resultDir = "clamav"
|
||||
clamServiceKey = "clam"
|
||||
freshClamServiceKey = "freshclam"
|
||||
resultDir = "clamav"
|
||||
)
|
||||
|
||||
type ClamService struct {
|
||||
serviceName string
|
||||
serviceName string
|
||||
freshClamService string
|
||||
}
|
||||
|
||||
type IClamService interface {
|
||||
@@ -63,23 +62,32 @@ func (c *ClamService) LoadBaseInfo() (dto.ClamBaseInfo, error) {
|
||||
var baseInfo dto.ClamBaseInfo
|
||||
baseInfo.Version = "-"
|
||||
baseInfo.FreshVersion = "-"
|
||||
exist1, _ := systemctl.IsExist(clamServiceNameCentOs)
|
||||
if exist1 {
|
||||
c.serviceName = clamServiceNameCentOs
|
||||
baseInfo.IsExist = true
|
||||
baseInfo.IsActive, _ = systemctl.IsActive(clamServiceNameCentOs)
|
||||
clamSvc, err := systemctl.GetServiceName(clamServiceKey)
|
||||
if err != nil {
|
||||
baseInfo.IsExist = false
|
||||
return baseInfo, nil
|
||||
}
|
||||
exist2, _ := systemctl.IsExist(clamServiceNameUbuntu)
|
||||
if exist2 {
|
||||
c.serviceName = clamServiceNameUbuntu
|
||||
baseInfo.IsExist = true
|
||||
baseInfo.IsActive, _ = systemctl.IsActive(clamServiceNameUbuntu)
|
||||
c.serviceName = clamSvc
|
||||
isExist, err := systemctl.IsExist(c.serviceName)
|
||||
if err != nil {
|
||||
baseInfo.IsExist = false
|
||||
}
|
||||
freshExist, _ := systemctl.IsExist(freshClamService)
|
||||
if freshExist {
|
||||
baseInfo.FreshIsExist = true
|
||||
baseInfo.FreshIsActive, _ = systemctl.IsActive(freshClamService)
|
||||
baseInfo.IsExist = isExist
|
||||
baseInfo.IsActive, _ = systemctl.IsActive(clamSvc)
|
||||
|
||||
freshSvc, err := systemctl.GetServiceName(freshClamServiceKey)
|
||||
if err != nil {
|
||||
baseInfo.FreshIsExist = false
|
||||
return baseInfo, nil
|
||||
}
|
||||
c.freshClamService = freshSvc
|
||||
freshisExist, err := systemctl.IsExist(c.freshClamService)
|
||||
if err != nil {
|
||||
baseInfo.FreshIsExist = false
|
||||
}
|
||||
baseInfo.FreshIsExist = freshisExist
|
||||
baseInfo.FreshIsActive, _ = systemctl.IsActive(freshSvc)
|
||||
|
||||
if !cmd.Which("clamdscan") {
|
||||
baseInfo.IsActive = false
|
||||
}
|
||||
@@ -110,22 +118,27 @@ func (c *ClamService) LoadBaseInfo() (dto.ClamBaseInfo, error) {
|
||||
}
|
||||
|
||||
func (c *ClamService) Operate(operate string) error {
|
||||
var err error
|
||||
switch operate {
|
||||
case "start", "restart", "stop":
|
||||
stdout, err := cmd.Execf("systemctl %s %s", operate, c.serviceName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s the %s failed, err: %s", operate, c.serviceName, stdout)
|
||||
}
|
||||
return nil
|
||||
case "fresh-start", "fresh-restart", "fresh-stop":
|
||||
stdout, err := cmd.Execf("systemctl %s %s", strings.TrimPrefix(operate, "fresh-"), freshClamService)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s the %s failed, err: %s", operate, c.serviceName, stdout)
|
||||
}
|
||||
return nil
|
||||
case "start":
|
||||
err = systemctl.Start(c.serviceName)
|
||||
case "stop":
|
||||
err = systemctl.Stop(c.serviceName)
|
||||
case "restart":
|
||||
err = systemctl.Restart(c.serviceName)
|
||||
case "fresh-start":
|
||||
err = systemctl.Start(c.freshClamService)
|
||||
case "fresh-stop":
|
||||
err = systemctl.Stop(c.freshClamService)
|
||||
case "fresh-restart":
|
||||
err = systemctl.Restart(c.freshClamService)
|
||||
default:
|
||||
return fmt.Errorf("not support such operation: %v", operate)
|
||||
return fmt.Errorf("unsupported operation: %s", operate)
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s %s failed: %v", operate, c.serviceName, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *ClamService) SearchWithPage(req dto.SearchClamWithPage) (int64, interface{}, error) {
|
||||
@@ -432,102 +445,80 @@ func (c *ClamService) LoadFile(req dto.ClamFileReq) (string, error) {
|
||||
filePath := ""
|
||||
switch req.Name {
|
||||
case "clamd":
|
||||
if c.serviceName == clamServiceNameUbuntu {
|
||||
filePath = "/etc/clamav/clamd.conf"
|
||||
} else {
|
||||
filePath = "/etc/clamd.d/scan.conf"
|
||||
}
|
||||
filePath = c.getConfigPath("clamd")
|
||||
case "clamd-log":
|
||||
filePath = c.loadLogPath("clamd-log")
|
||||
if len(filePath) != 0 {
|
||||
break
|
||||
}
|
||||
if c.serviceName == clamServiceNameUbuntu {
|
||||
filePath = "/var/log/clamav/clamav.log"
|
||||
} else {
|
||||
filePath = "/var/log/clamd.scan"
|
||||
}
|
||||
case "freshclam":
|
||||
if c.serviceName == clamServiceNameUbuntu {
|
||||
filePath = "/etc/clamav/freshclam.conf"
|
||||
} else {
|
||||
filePath = "/etc/freshclam.conf"
|
||||
}
|
||||
filePath = c.getConfigPath("freshclam")
|
||||
case "freshclam-log":
|
||||
filePath = c.loadLogPath("freshclam-log")
|
||||
if len(filePath) != 0 {
|
||||
break
|
||||
}
|
||||
if c.serviceName == clamServiceNameUbuntu {
|
||||
filePath = "/var/log/clamav/freshclam.log"
|
||||
} else {
|
||||
filePath = "/var/log/freshclam.log"
|
||||
}
|
||||
default:
|
||||
return "", fmt.Errorf("not support such type")
|
||||
return "", fmt.Errorf("unsupported file type")
|
||||
}
|
||||
if _, err := os.Stat(filePath); err != nil {
|
||||
|
||||
content, err := systemctl.ViewConfig(filePath, systemctl.ConfigOption{TailLines: req.Tail})
|
||||
if err != nil {
|
||||
return "", buserr.New("ErrHttpReqNotFound")
|
||||
}
|
||||
var tail string
|
||||
if req.Tail != "0" {
|
||||
tail = req.Tail
|
||||
} else {
|
||||
tail = "+1"
|
||||
}
|
||||
cmd := exec.Command("tail", "-n", tail, filePath)
|
||||
stdout, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("tail -n %v failed, err: %v", req.Tail, err)
|
||||
}
|
||||
return string(stdout), nil
|
||||
return content, nil
|
||||
}
|
||||
|
||||
func (c *ClamService) UpdateFile(req dto.UpdateByNameAndFile) error {
|
||||
filePath := ""
|
||||
service := ""
|
||||
var (
|
||||
filePath string
|
||||
service string
|
||||
)
|
||||
|
||||
switch req.Name {
|
||||
case "clamd":
|
||||
if c.serviceName == clamServiceNameUbuntu {
|
||||
service = clamServiceNameUbuntu
|
||||
filePath = "/etc/clamav/clamd.conf"
|
||||
} else {
|
||||
service = clamServiceNameCentOs
|
||||
filePath = "/etc/clamd.d/scan.conf"
|
||||
}
|
||||
filePath = c.getConfigPath("clamd")
|
||||
service = c.serviceName
|
||||
case "freshclam":
|
||||
if c.serviceName == clamServiceNameUbuntu {
|
||||
filePath = "/etc/clamav/freshclam.conf"
|
||||
} else {
|
||||
filePath = "/etc/freshclam.conf"
|
||||
}
|
||||
service = "clamav-freshclam.service"
|
||||
filePath = c.getConfigPath("freshclam")
|
||||
service = c.freshClamService
|
||||
default:
|
||||
return fmt.Errorf("not support such type")
|
||||
return fmt.Errorf("unsupported file type")
|
||||
}
|
||||
|
||||
file, err := os.OpenFile(filePath, os.O_WRONLY|os.O_TRUNC, 0640)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
write := bufio.NewWriter(file)
|
||||
_, _ = write.WriteString(req.File)
|
||||
write.Flush()
|
||||
|
||||
_ = systemctl.Restart(service)
|
||||
if _, err := file.WriteString(req.File); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := systemctl.Restart(service); err != nil {
|
||||
return fmt.Errorf("restart %s failed: %v", service, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *ClamService) getConfigPath(confType string) string {
|
||||
switch confType {
|
||||
case "clamd":
|
||||
if _, err := os.Stat("/etc/clamav/clamd.conf"); err == nil {
|
||||
return "/etc/clamav/clamd.conf"
|
||||
}
|
||||
return "/etc/clamd.d/scan.conf"
|
||||
case "freshclam":
|
||||
if _, err := os.Stat("/etc/clamav/freshclam.conf"); err == nil {
|
||||
return "/etc/clamav/freshclam.conf"
|
||||
}
|
||||
return "/etc/freshclam.conf"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func StopAllCronJob(withCheck bool) bool {
|
||||
if withCheck {
|
||||
isActive := false
|
||||
exist1, _ := systemctl.IsExist(clamServiceNameCentOs)
|
||||
if exist1 {
|
||||
isActive, _ = systemctl.IsActive(clamServiceNameCentOs)
|
||||
}
|
||||
exist2, _ := systemctl.IsExist(clamServiceNameUbuntu)
|
||||
if exist2 {
|
||||
isActive, _ = systemctl.IsActive(clamServiceNameUbuntu)
|
||||
isexist, _ := systemctl.IsExist(clamServiceKey)
|
||||
if isexist {
|
||||
isActive, _ = systemctl.IsActive(clamServiceKey)
|
||||
}
|
||||
if isActive {
|
||||
return false
|
||||
@@ -590,42 +581,25 @@ func loadResultFromLog(pathItem string) dto.ClamLog {
|
||||
return data
|
||||
}
|
||||
func (c *ClamService) loadLogPath(name string) string {
|
||||
confPath := ""
|
||||
if name == "clamd-log" {
|
||||
if c.serviceName == clamServiceNameUbuntu {
|
||||
confPath = "/etc/clamav/clamd.conf"
|
||||
} else {
|
||||
confPath = "/etc/clamd.d/scan.conf"
|
||||
}
|
||||
} else {
|
||||
if c.serviceName == clamServiceNameUbuntu {
|
||||
confPath = "/etc/clamav/freshclam.conf"
|
||||
} else {
|
||||
confPath = "/etc/freshclam.conf"
|
||||
}
|
||||
}
|
||||
if _, err := os.Stat(confPath); err != nil {
|
||||
return ""
|
||||
configKey := "clamd"
|
||||
searchPrefix := "LogFile "
|
||||
if name != "clamd-log" {
|
||||
configKey = "freshclam"
|
||||
searchPrefix = "UpdateLogFile "
|
||||
}
|
||||
confPath := c.getConfigPath(configKey)
|
||||
|
||||
content, err := os.ReadFile(confPath)
|
||||
if err != nil {
|
||||
global.LOG.Debugf("Failed to read %s config: %v", configKey, err)
|
||||
return ""
|
||||
}
|
||||
lines := strings.Split(string(content), "\n")
|
||||
if name == "clamd-log" {
|
||||
for _, line := range lines {
|
||||
if strings.HasPrefix(line, "LogFile ") {
|
||||
return strings.Trim(strings.ReplaceAll(line, "LogFile ", ""), " ")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for _, line := range lines {
|
||||
if strings.HasPrefix(line, "UpdateLogFile ") {
|
||||
return strings.Trim(strings.ReplaceAll(line, "UpdateLogFile ", ""), " ")
|
||||
}
|
||||
|
||||
for _, line := range strings.Split(string(content), "\n") {
|
||||
if strings.HasPrefix(line, searchPrefix) {
|
||||
return strings.TrimSpace(strings.TrimPrefix(line, searchPrefix))
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
|
||||
@@ -558,10 +558,7 @@ func (u *ContainerService) ContainerUpgrade(req dto.ContainerUpgrade) error {
|
||||
hostConf := oldContainer.HostConfig
|
||||
var networkConf network.NetworkingConfig
|
||||
if oldContainer.NetworkSettings != nil {
|
||||
for networkKey := range oldContainer.NetworkSettings.Networks {
|
||||
networkConf.EndpointsConfig = map[string]*network.EndpointSettings{networkKey: {}}
|
||||
break
|
||||
}
|
||||
networkConf.EndpointsConfig = oldContainer.NetworkSettings.Networks
|
||||
}
|
||||
if err := client.ContainerRemove(ctx, req.Name, container.RemoveOptions{Force: true}); err != nil {
|
||||
return err
|
||||
@@ -1215,10 +1212,7 @@ func reCreateAfterUpdate(name string, client *client.Client, config *container.C
|
||||
|
||||
var oldNetworkConf network.NetworkingConfig
|
||||
if networkConf != nil {
|
||||
for networkKey := range networkConf.Networks {
|
||||
oldNetworkConf.EndpointsConfig = map[string]*network.EndpointSettings{networkKey: {}}
|
||||
break
|
||||
}
|
||||
oldNetworkConf.EndpointsConfig = networkConf.Networks
|
||||
}
|
||||
|
||||
oldContainer, err := client.ContainerCreate(ctx, config, hostConf, &oldNetworkConf, &v1.Platform{}, name)
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/common"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/ntp"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/systemctl"
|
||||
"github.com/shirou/gopsutil/v3/mem"
|
||||
)
|
||||
|
||||
@@ -121,7 +122,7 @@ func (u *DeviceService) Update(key, value string) error {
|
||||
return err
|
||||
}
|
||||
go func() {
|
||||
_, err := cmd.Exec("systemctl restart 1panel.service")
|
||||
err := systemctl.Restart("1panel")
|
||||
if err != nil {
|
||||
global.LOG.Errorf("restart system for new time zone failed, err: %v", err)
|
||||
}
|
||||
|
||||
@@ -11,12 +11,12 @@ import (
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/docker"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/systemctl"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/common"
|
||||
fileUtils "github.com/1Panel-dev/1Panel/backend/utils/files"
|
||||
"github.com/google/uuid"
|
||||
@@ -285,7 +285,7 @@ func (u *DeviceService) Clean(req []dto.Clean) {
|
||||
|
||||
if restart {
|
||||
go func() {
|
||||
_, err := cmd.Exec("systemctl restart 1panel.service")
|
||||
err := systemctl.Restart("1panel")
|
||||
if err != nil {
|
||||
global.LOG.Errorf("restart system port failed, err: %v", err)
|
||||
}
|
||||
|
||||
@@ -365,17 +365,18 @@ func (u *DockerService) UpdateConfByFile(req dto.DaemonJsonUpdateByFile) error {
|
||||
|
||||
func (u *DockerService) OperateDocker(req dto.DockerOperation) error {
|
||||
service := "docker"
|
||||
sudo := cmd.SudoHandleCmd()
|
||||
dockerCmd, err := getDockerRestartCommand()
|
||||
h, err := systemctl.DefaultHandler(service)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if req.Operation == "stop" {
|
||||
isSocketActive, _ := systemctl.IsActive("docker.socket")
|
||||
if isSocketActive {
|
||||
std, err := cmd.Execf("%s systemctl stop docker.socket", sudo)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("handle systemctl stop docker.socket failed, err: %v", std)
|
||||
socketHandle, err := systemctl.DefaultHandler("docker.socket")
|
||||
if err == nil {
|
||||
status, err := socketHandle.IsActive()
|
||||
if err == nil && status.IsActive {
|
||||
if std, err := socketHandle.ExecuteAction("stop"); err != nil {
|
||||
global.LOG.Errorf("handle stop docker.socket failed, err: %v", std)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -386,13 +387,20 @@ func (u *DockerService) OperateDocker(req dto.DockerOperation) error {
|
||||
}
|
||||
}
|
||||
|
||||
stdout, err := cmd.Execf("%s %s %s", dockerCmd, req.Operation, service)
|
||||
if isDockerSnapInstalled() {
|
||||
command := fmt.Sprintf("snap %s docker", req.Operation)
|
||||
stdout, err := cmd.Exec(command)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to restart docker: %v", stdout)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
result, err := h.ExecuteAction(req.Operation)
|
||||
if err != nil {
|
||||
return errors.New(stdout)
|
||||
return errors.New(result.Output)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func changeLogOption(daemonMap map[string]interface{}, logMaxFile, logMaxSize string) {
|
||||
if opts, ok := daemonMap["log-opts"]; ok {
|
||||
if len(logMaxFile) != 0 || len(logMaxSize) != 0 {
|
||||
@@ -451,31 +459,27 @@ func validateDockerConfig() error {
|
||||
return nil
|
||||
}
|
||||
if err != nil || (stdout != "" && strings.TrimSpace(stdout) != "configuration OK") {
|
||||
return fmt.Errorf("Docker configuration validation failed, err: %v", stdout)
|
||||
return fmt.Errorf("docker configuration validation failed, err: %v", stdout)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getDockerRestartCommand() (string, error) {
|
||||
func isDockerSnapInstalled() bool {
|
||||
stdout, err := cmd.Exec("which docker")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to find docker: %v", err)
|
||||
return false
|
||||
}
|
||||
dockerPath := stdout
|
||||
if strings.Contains(dockerPath, "snap") {
|
||||
return "snap", nil
|
||||
}
|
||||
return "systemctl", nil
|
||||
stdout = strings.TrimSpace(stdout)
|
||||
return strings.Contains(stdout, "snap")
|
||||
}
|
||||
|
||||
func restartDocker() error {
|
||||
restartCmd, err := getDockerRestartCommand()
|
||||
if err != nil {
|
||||
return err
|
||||
if isDockerSnapInstalled() {
|
||||
stdout, err := cmd.Exec("snap restart docker")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to restart docker: %v", stdout)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
stdout, err := cmd.Execf("%s restart docker", restartCmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to restart Docker: %s", stdout)
|
||||
}
|
||||
return nil
|
||||
return systemctl.Restart("docker")
|
||||
}
|
||||
|
||||
@@ -366,10 +366,18 @@ func (f *FileService) MvFile(m request.FileMove) error {
|
||||
return buserr.New(constant.ErrMovePathFailed)
|
||||
}
|
||||
}
|
||||
var errs []error
|
||||
if m.Type == "cut" {
|
||||
if len(m.CoverPaths) > 0 {
|
||||
for _, src := range m.CoverPaths {
|
||||
if err := fo.CopyAndReName(src, m.NewPath, "", true); err != nil {
|
||||
errs = append(errs, err)
|
||||
global.LOG.Errorf("cut copy file [%s] to [%s] failed, err: %s", src, m.NewPath, err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
return fo.Cut(m.OldPaths, m.NewPath, m.Name, m.Cover)
|
||||
}
|
||||
var errs []error
|
||||
if m.Type == "copy" {
|
||||
for _, src := range m.OldPaths {
|
||||
if err := fo.CopyAndReName(src, m.NewPath, m.Name, m.Cover); err != nil {
|
||||
@@ -518,6 +526,7 @@ func (f *FileService) BatchCheckFiles(req request.FilePathsCheck) []response.Exi
|
||||
Name: info.Name(),
|
||||
Path: filePath,
|
||||
ModTime: info.ModTime(),
|
||||
IsDir: info.IsDir(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,12 @@ package service
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"os/user"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto/request"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto/response"
|
||||
"github.com/1Panel-dev/1Panel/backend/buserr"
|
||||
@@ -14,11 +20,6 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/systemctl"
|
||||
"github.com/pkg/errors"
|
||||
"gopkg.in/ini.v1"
|
||||
"os/exec"
|
||||
"os/user"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type HostToolService struct{}
|
||||
@@ -39,93 +40,108 @@ func NewIHostToolService() IHostToolService {
|
||||
}
|
||||
|
||||
func (h *HostToolService) GetToolStatus(req request.HostToolReq) (*response.HostToolRes, error) {
|
||||
res := &response.HostToolRes{}
|
||||
res.Type = req.Type
|
||||
res := &response.HostToolRes{Type: req.Type}
|
||||
switch req.Type {
|
||||
case constant.Supervisord:
|
||||
supervisorConfig := &response.Supervisor{}
|
||||
if !cmd.Which(constant.Supervisord) {
|
||||
supervisorConfig.IsExist = false
|
||||
res.Config = supervisorConfig
|
||||
return res, nil
|
||||
}
|
||||
supervisorConfig.IsExist = true
|
||||
serviceExist, _ := systemctl.IsExist(constant.Supervisord)
|
||||
if !serviceExist {
|
||||
serviceExist, _ = systemctl.IsExist(constant.Supervisor)
|
||||
if !serviceExist {
|
||||
supervisorConfig.IsExist = false
|
||||
res.Config = supervisorConfig
|
||||
return res, nil
|
||||
} else {
|
||||
supervisorConfig.ServiceName = constant.Supervisor
|
||||
}
|
||||
} else {
|
||||
supervisorConfig.ServiceName = constant.Supervisord
|
||||
}
|
||||
|
||||
serviceNameSet, _ := settingRepo.Get(settingRepo.WithByKey(constant.SupervisorServiceName))
|
||||
if serviceNameSet.ID != 0 || serviceNameSet.Value != "" {
|
||||
supervisorConfig.ServiceName = serviceNameSet.Value
|
||||
}
|
||||
|
||||
versionRes, _ := cmd.Exec("supervisord -v")
|
||||
supervisorConfig.Version = strings.TrimSuffix(versionRes, "\n")
|
||||
_, ctlRrr := exec.LookPath("supervisorctl")
|
||||
supervisorConfig.CtlExist = ctlRrr == nil
|
||||
|
||||
active, _ := systemctl.IsActive(supervisorConfig.ServiceName)
|
||||
if active {
|
||||
supervisorConfig.Status = "running"
|
||||
} else {
|
||||
supervisorConfig.Status = "stopped"
|
||||
}
|
||||
|
||||
pathSet, _ := settingRepo.Get(settingRepo.WithByKey(constant.SupervisorConfigPath))
|
||||
if pathSet.ID != 0 || pathSet.Value != "" {
|
||||
supervisorConfig.ConfigPath = pathSet.Value
|
||||
res.Config = supervisorConfig
|
||||
return res, nil
|
||||
} else {
|
||||
supervisorConfig.Init = true
|
||||
}
|
||||
|
||||
servicePath := "/usr/lib/systemd/system/supervisor.service"
|
||||
fileOp := files.NewFileOp()
|
||||
if !fileOp.Stat(servicePath) {
|
||||
servicePath = "/usr/lib/systemd/system/supervisord.service"
|
||||
}
|
||||
if fileOp.Stat(servicePath) {
|
||||
startCmd, _ := ini_conf.GetIniValue(servicePath, "Service", "ExecStart")
|
||||
if startCmd != "" {
|
||||
args := strings.Fields(startCmd)
|
||||
cIndex := -1
|
||||
for i, arg := range args {
|
||||
if arg == "-c" {
|
||||
cIndex = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if cIndex != -1 && cIndex+1 < len(args) {
|
||||
supervisorConfig.ConfigPath = args[cIndex+1]
|
||||
}
|
||||
}
|
||||
}
|
||||
if supervisorConfig.ConfigPath == "" {
|
||||
configPath := "/etc/supervisord.conf"
|
||||
if !fileOp.Stat(configPath) {
|
||||
configPath = "/etc/supervisor/supervisord.conf"
|
||||
if fileOp.Stat(configPath) {
|
||||
supervisorConfig.ConfigPath = configPath
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
res.Config = supervisorConfig
|
||||
return h.getSupervisorStatus(res)
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (h *HostToolService) getSupervisorStatus(res *response.HostToolRes) (*response.HostToolRes, error) {
|
||||
supervisorConfig := &response.Supervisor{}
|
||||
if !cmd.Which(constant.Supervisord) {
|
||||
supervisorConfig.IsExist = false
|
||||
res.Config = supervisorConfig
|
||||
return res, nil
|
||||
}
|
||||
supervisorConfig.IsExist = true
|
||||
|
||||
serviceName, err := h.determineServiceName()
|
||||
if err != nil || serviceName == "" {
|
||||
supervisorConfig.IsExist = false
|
||||
res.Config = supervisorConfig
|
||||
return res, nil
|
||||
}
|
||||
supervisorConfig.ServiceName = serviceName
|
||||
|
||||
if nameSetting, _ := settingRepo.Get(settingRepo.WithByKey(constant.SupervisorServiceName)); nameSetting.Value != "" {
|
||||
supervisorConfig.ServiceName = nameSetting.Value
|
||||
}
|
||||
|
||||
if version, err := cmd.Exec("supervisord -v"); err == nil {
|
||||
supervisorConfig.Version = strings.TrimSpace(version)
|
||||
}
|
||||
|
||||
_, errCtl := exec.LookPath("supervisorctl")
|
||||
supervisorConfig.CtlExist = errCtl == nil
|
||||
|
||||
if active, err := systemctl.IsActive(supervisorConfig.ServiceName); err == nil && active {
|
||||
supervisorConfig.Status = "running"
|
||||
} else {
|
||||
supervisorConfig.Status = "stopped"
|
||||
}
|
||||
|
||||
h.resolveConfigPath(supervisorConfig)
|
||||
|
||||
res.Config = supervisorConfig
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (h *HostToolService) determineServiceName() (string, error) {
|
||||
if setting, _ := settingRepo.Get(settingRepo.WithByKey(constant.SupervisorServiceName)); setting.Value != "" {
|
||||
serviceName, err := systemctl.GetServiceName(setting.Value)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("get service name error: %v", err)
|
||||
}
|
||||
return serviceName, nil
|
||||
}
|
||||
if serviceName, err := systemctl.GetServiceName(constant.Supervisord); err == nil {
|
||||
return serviceName, nil
|
||||
}
|
||||
return systemctl.GetServiceName(constant.Supervisor)
|
||||
}
|
||||
|
||||
func (h *HostToolService) resolveConfigPath(config *response.Supervisor) {
|
||||
if pathSetting, _ := settingRepo.Get(settingRepo.WithByKey(constant.SupervisorConfigPath)); pathSetting.Value != "" {
|
||||
config.ConfigPath = pathSetting.Value
|
||||
return
|
||||
}
|
||||
|
||||
config.Init = true
|
||||
|
||||
if servicePath, err := systemctl.GetServicePath(config.ServiceName); err == nil {
|
||||
if startCmd, _ := ini_conf.GetIniValue(servicePath, "Service", "ExecStart"); startCmd != "" {
|
||||
if path := parseConfigPathFromCommand(startCmd); path != "" {
|
||||
config.ConfigPath = path
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
defaultPaths := []string{
|
||||
"/etc/supervisord.conf",
|
||||
"/etc/supervisor/supervisord.conf",
|
||||
"/usr/local/etc/supervisord.conf",
|
||||
}
|
||||
for _, path := range defaultPaths {
|
||||
if systemctl.FileExist(path) {
|
||||
config.ConfigPath = path
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func parseConfigPathFromCommand(cmd string) string {
|
||||
parts := strings.Fields(cmd)
|
||||
for i, part := range parts {
|
||||
if part == "-c" && i+1 < len(parts) {
|
||||
return parts[i+1]
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (h *HostToolService) CreateToolConfig(req request.HostToolCreate) error {
|
||||
switch req.Type {
|
||||
case constant.Supervisord:
|
||||
@@ -200,14 +216,28 @@ func (h *HostToolService) CreateToolConfig(req request.HostToolCreate) error {
|
||||
}
|
||||
|
||||
func (h *HostToolService) OperateTool(req request.HostToolReq) error {
|
||||
serviceName := req.Type
|
||||
if req.Type == constant.Supervisord {
|
||||
var serviceName string
|
||||
var err error
|
||||
switch req.Type {
|
||||
case constant.Supervisord:
|
||||
serviceNameSet, _ := settingRepo.Get(settingRepo.WithByKey(constant.SupervisorServiceName))
|
||||
if serviceNameSet.ID != 0 || serviceNameSet.Value != "" {
|
||||
serviceName = serviceNameSet.Value
|
||||
} else {
|
||||
serviceName = req.Type
|
||||
}
|
||||
default:
|
||||
serviceName = req.Type
|
||||
}
|
||||
return systemctl.Operate(req.Operate, serviceName)
|
||||
serviceName, err = systemctl.GetServiceName(serviceName)
|
||||
if err != nil {
|
||||
if errors.Is(err, systemctl.ErrServiceNotFound) {
|
||||
return fmt.Errorf("%s service unavailable. Please ensure supervisor server is installed and configured", serviceName)
|
||||
}
|
||||
return err
|
||||
}
|
||||
_, err = systemctl.CustomAction(req.Operate, serviceName)
|
||||
return err
|
||||
}
|
||||
|
||||
func (h *HostToolService) OperateToolConfig(req request.HostToolConfig) (*response.HostToolConfig, error) {
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/common"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/systemctl"
|
||||
"github.com/jinzhu/copier"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
@@ -105,8 +106,7 @@ func (u *ImageRepoService) Create(req dto.ImageRepoCreate) error {
|
||||
cancel()
|
||||
return errors.New("the docker service cannot be restarted")
|
||||
default:
|
||||
stdout, err := cmd.Exec("systemctl is-active docker")
|
||||
if string(stdout) == "active\n" && err == nil {
|
||||
if ok, _ := systemctl.IsActive("docker"); ok {
|
||||
global.LOG.Info("docker restart with new conf successful!")
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -430,7 +430,7 @@ func addProxy(server *model.McpServer) {
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
location.UpdateDirective("proxy_pass", []string{fmt.Sprintf("http://127.0.1:%d%s", server.Port, server.SsePath)})
|
||||
location.UpdateDirective("proxy_pass", []string{fmt.Sprintf("http://127.0.0.1:%d%s", server.Port, server.SsePath)})
|
||||
location.ChangePath("^~", server.SsePath)
|
||||
if err = nginx.WriteConfig(config, nginx.IndentedStyle); err != nil {
|
||||
global.LOG.Errorf("write config failed, err: %v", buserr.WithErr(constant.ErrUpdateBuWebsite, err))
|
||||
@@ -486,7 +486,7 @@ func addMCPProxy(websiteID uint) error {
|
||||
err = errors.New("error")
|
||||
return err
|
||||
}
|
||||
location.UpdateDirective("proxy_pass", []string{fmt.Sprintf("http://127.0.1:%d%s", server.Port, server.SsePath)})
|
||||
location.UpdateDirective("proxy_pass", []string{fmt.Sprintf("http://127.0.0.1:%d%s", server.Port, server.SsePath)})
|
||||
location.ChangePath("^~", server.SsePath)
|
||||
if err = nginx.WriteConfig(config, nginx.IndentedStyle); err != nil {
|
||||
return buserr.WithErr(constant.ErrUpdateBuWebsite, err)
|
||||
|
||||
@@ -21,10 +21,10 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/backend/buserr"
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/common"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/encrypt"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/files"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/systemctl"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/robfig/cron/v3"
|
||||
)
|
||||
@@ -166,7 +166,7 @@ func (u *SettingService) UpdateBindInfo(req dto.BindInfo) error {
|
||||
}
|
||||
go func() {
|
||||
time.Sleep(1 * time.Second)
|
||||
_, err := cmd.Exec("systemctl restart 1panel.service")
|
||||
err := systemctl.Restart("1panel")
|
||||
if err != nil {
|
||||
global.LOG.Errorf("restart system with new bind info failed, err: %v", err)
|
||||
}
|
||||
@@ -214,7 +214,7 @@ func (u *SettingService) UpdatePort(port uint) error {
|
||||
}
|
||||
go func() {
|
||||
time.Sleep(1 * time.Second)
|
||||
_, err := cmd.Exec("systemctl restart 1panel.service")
|
||||
err := systemctl.Restart("1panel")
|
||||
if err != nil {
|
||||
global.LOG.Errorf("restart system port failed, err: %v", err)
|
||||
}
|
||||
@@ -237,7 +237,7 @@ func (u *SettingService) UpdateSSL(c *gin.Context, req dto.SSLUpdate) error {
|
||||
c.SetCookie(constant.SessionName, sID, 0, "", "", false, true)
|
||||
|
||||
go func() {
|
||||
_, err := cmd.Exec("systemctl restart 1panel.service")
|
||||
err := systemctl.Restart("1panel")
|
||||
if err != nil {
|
||||
global.LOG.Errorf("restart system failed, err: %v", err)
|
||||
}
|
||||
@@ -335,7 +335,7 @@ func (u *SettingService) UpdateSSL(c *gin.Context, req dto.SSLUpdate) error {
|
||||
c.SetCookie(constant.SessionName, sID, 0, "", "", true, true)
|
||||
go func() {
|
||||
time.Sleep(1 * time.Second)
|
||||
_, err := cmd.Exec("systemctl restart 1panel.service")
|
||||
err := systemctl.Restart("1panel")
|
||||
if err != nil {
|
||||
global.LOG.Errorf("restart system failed, err: %v", err)
|
||||
}
|
||||
@@ -552,7 +552,7 @@ func (u *SettingService) GenerateRSAKey() error {
|
||||
return err
|
||||
}
|
||||
privateKeyPEM := exportPrivateKeyToPEM(privateKey)
|
||||
publicKeyPEM, err := exportPublicKeyToPEM(&privateKey.PublicKey)
|
||||
publicKeyPEM, _ := exportPublicKeyToPEM(&privateKey.PublicKey)
|
||||
err = settingRepo.UpdateOrCreate("PASSWORD_PRIVATE_KEY", privateKeyPEM)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -503,7 +503,8 @@ func hasOs(name string) bool {
|
||||
strings.Contains(name, "arm64") ||
|
||||
strings.Contains(name, "armv7") ||
|
||||
strings.Contains(name, "ppc64le") ||
|
||||
strings.Contains(name, "s390x")
|
||||
strings.Contains(name, "s390x") ||
|
||||
strings.Contains(name, "riscv64")
|
||||
}
|
||||
|
||||
func loadOs() string {
|
||||
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/common"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/files"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/systemctl"
|
||||
)
|
||||
|
||||
type snapHelper struct {
|
||||
@@ -43,16 +44,39 @@ func snapPanel(snap snapHelper, targetDir string) {
|
||||
defer snap.Wg.Done()
|
||||
_ = snapshotRepo.UpdateStatus(snap.Status.ID, map[string]interface{}{"panel": constant.Running})
|
||||
status := constant.StatusDone
|
||||
if err := common.CopyFile("/usr/local/bin/1panel", path.Join(targetDir, "1panel")); err != nil {
|
||||
status = err.Error()
|
||||
h, err := systemctl.DefaultHandler("1panel")
|
||||
|
||||
if err != nil {
|
||||
status = fmt.Sprintf("initialize service handle failed: %v", err)
|
||||
snap.Status.Panel = status
|
||||
_ = snapshotRepo.UpdateStatus(snap.Status.ID, map[string]interface{}{"panel": status})
|
||||
return
|
||||
}
|
||||
|
||||
if err := common.CopyFile("/usr/local/bin/1pctl", targetDir); err != nil {
|
||||
binDir := systemctl.BinaryPath
|
||||
servicePath, err := h.GetServicePath()
|
||||
if err != nil {
|
||||
status = fmt.Sprintf("get service path failed: %v", err)
|
||||
snap.Status.Panel = status
|
||||
_ = snapshotRepo.UpdateStatus(snap.Status.ID, map[string]interface{}{"panel": status})
|
||||
return
|
||||
}
|
||||
|
||||
if err := common.CopyFile(path.Join(binDir, "1panel"), path.Join(targetDir, "1panel")); err != nil {
|
||||
status = err.Error()
|
||||
}
|
||||
_, _ = cmd.Execf("cp -r /usr/local/bin/lang %s", targetDir)
|
||||
|
||||
if err := common.CopyFile("/etc/systemd/system/1panel.service", targetDir); err != nil {
|
||||
if err := common.CopyFile(path.Join(binDir, "1pctl"), targetDir); err != nil {
|
||||
status = err.Error()
|
||||
}
|
||||
if _, err := cmd.Execf("cp -r %s/lang %s", binDir, targetDir); err != nil {
|
||||
status = err.Error()
|
||||
}
|
||||
initScriptDir := path.Join(constant.DataDir, "initscript")
|
||||
if err := common.CopyFile(servicePath, initScriptDir); err != nil {
|
||||
status = err.Error()
|
||||
}
|
||||
global.LOG.Debugf("from %s copy init script to %s", initScriptDir, path.Join(targetDir, "initscript"))
|
||||
if err := common.CopyDirs(initScriptDir, path.Join(targetDir, "initscript")); err != nil { // copy init script to targetDir
|
||||
status = err.Error()
|
||||
}
|
||||
snap.Status.Panel = status
|
||||
@@ -260,7 +284,7 @@ func handleSnapTar(sourceDir, targetDir, name, exclusionRules string, secret str
|
||||
commands = fmt.Sprintf("tar --warning=no-file-changed --ignore-failed-read --exclude-from=<(find %s -type s -print) -zcf %s %s %s %s", sourceDir, " -"+exStr, path, extraCmd, targetDir+"/"+name)
|
||||
global.LOG.Debug(strings.ReplaceAll(commands, fmt.Sprintf(" %s ", secret), "******"))
|
||||
} else {
|
||||
commands = fmt.Sprintf("tar --warning=no-file-changed --ignore-failed-read --exclude-from=<(find %s -type s -printf '%s' | sed 's|^|./|') -zcf %s %s -C %s .", sourceDir, "%P\n", targetDir+"/"+name, exStr, sourceDir)
|
||||
commands = fmt.Sprintf("tar --warning=no-file-changed --ignore-failed-read --exclude-from=<(find %s -type s -printf '%%P\n' | sed 's|^|./|') -zcf %s %s -C %s .", sourceDir, targetDir+"/"+name, exStr, sourceDir)
|
||||
global.LOG.Debug(commands)
|
||||
}
|
||||
stdout, err := cmd.ExecWithTimeOut(commands, 30*time.Minute)
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/common"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/files"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/systemctl"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
@@ -91,8 +92,15 @@ func (u *SnapshotService) HandleSnapshotRecover(snap model.Snapshot, isRecover b
|
||||
req.IsNew = true
|
||||
}
|
||||
|
||||
h, err := systemctl.DefaultHandler("1panel")
|
||||
if err != nil {
|
||||
updateRecoverStatus(snap.ID, isRecover, "ServiceHandle", constant.StatusFailed, fmt.Sprintf("initialize service handle failed: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
if req.IsNew || snap.InterruptStep == "1PanelBinary" {
|
||||
if err := recoverPanel(path.Join(snapFileDir, "1panel/1panel"), "/usr/local/bin"); err != nil {
|
||||
binDir := systemctl.BinaryPath
|
||||
if err := recoverPanel(path.Join(snapFileDir, "1panel/1panel"), binDir); err != nil {
|
||||
updateRecoverStatus(snap.ID, isRecover, "1PanelBinary", constant.StatusFailed, err.Error())
|
||||
return
|
||||
}
|
||||
@@ -100,16 +108,32 @@ func (u *SnapshotService) HandleSnapshotRecover(snap model.Snapshot, isRecover b
|
||||
req.IsNew = true
|
||||
}
|
||||
if req.IsNew || snap.InterruptStep == "1PctlBinary" {
|
||||
if err := recoverPanel(path.Join(snapFileDir, "1panel/1pctl"), "/usr/local/bin"); err != nil {
|
||||
binDir := systemctl.BinaryPath
|
||||
if err := recoverPanel(path.Join(snapFileDir, "1panel/1pctl"), binDir); err != nil {
|
||||
updateRecoverStatus(snap.ID, isRecover, "1PctlBinary", constant.StatusFailed, err.Error())
|
||||
return
|
||||
}
|
||||
_, _ = cmd.Execf("cp -r %s %s", path.Join(snapFileDir, "1panel/lang"), "/usr/local/bin/")
|
||||
langDir := path.Join(binDir, "lang")
|
||||
if err := os.RemoveAll(langDir); err != nil {
|
||||
updateRecoverStatus(snap.ID, isRecover, "RemoveLang", constant.StatusFailed, fmt.Sprintf("remove lang dir failed: %v", err))
|
||||
return
|
||||
}
|
||||
if err := common.CopyDirs(path.Join(snapFileDir, "1panel/lang"), langDir); err != nil {
|
||||
updateRecoverStatus(snap.ID, isRecover, "CopyLang", constant.StatusFailed, fmt.Sprintf("copy lang files failed: %v", err))
|
||||
return
|
||||
}
|
||||
global.LOG.Debug("recover 1pctl from snapshot file successful!")
|
||||
req.IsNew = true
|
||||
}
|
||||
if req.IsNew || snap.InterruptStep == "1PanelService" {
|
||||
if err := recoverPanel(path.Join(snapFileDir, "1panel/1panel.service"), "/etc/systemd/system"); err != nil {
|
||||
servicePath, err := h.GetServicePath()
|
||||
currentServiceName := h.GetServiceName()
|
||||
if err != nil {
|
||||
updateRecoverStatus(snap.ID, isRecover, "GetServicePath", constant.StatusFailed, fmt.Sprintf("get service path failed: %v", err))
|
||||
return
|
||||
}
|
||||
global.LOG.Debugf("current service path: %s", servicePath)
|
||||
if err := common.CopyFile(selectInitScript(path.Join(snapFileDir, "1panel/initscript"), currentServiceName), servicePath); err != nil {
|
||||
updateRecoverStatus(snap.ID, isRecover, "1PanelService", constant.StatusFailed, err.Error())
|
||||
return
|
||||
}
|
||||
@@ -147,7 +171,12 @@ func (u *SnapshotService) HandleSnapshotRecover(snap model.Snapshot, isRecover b
|
||||
global.LOG.Debugf("remove the file %s after the operation is successful", path.Dir(snapFileDir))
|
||||
_ = os.RemoveAll(path.Dir(snapFileDir))
|
||||
}
|
||||
_, _ = cmd.Exec("systemctl daemon-reload && systemctl restart 1panel.service")
|
||||
if h.ManagerName() == "systemd" {
|
||||
_, _ = cmd.Exec("systemctl daemon-reload")
|
||||
}
|
||||
if err := systemctl.Restart("1panel"); err != nil {
|
||||
global.LOG.Errorf("restart 1panel service failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func backupBeforeRecover(snap model.Snapshot) error {
|
||||
|
||||
+214
-162
@@ -2,8 +2,7 @@ package service
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/geo"
|
||||
"github.com/gin-gonic/gin"
|
||||
"net"
|
||||
"os"
|
||||
"os/user"
|
||||
"path"
|
||||
@@ -12,6 +11,9 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/geo"
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/backend/buserr"
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
@@ -59,26 +61,16 @@ func (u *SSHService) GetSSHInfo() (*dto.SSHInfo, error) {
|
||||
if err != nil {
|
||||
data.Status = constant.StatusDisable
|
||||
data.Message = err.Error()
|
||||
} else {
|
||||
active, err := systemctl.IsActive(serviceName)
|
||||
if !active {
|
||||
data.Status = constant.StatusDisable
|
||||
if err != nil {
|
||||
data.Message = err.Error()
|
||||
}
|
||||
} else {
|
||||
data.Status = constant.StatusEnable
|
||||
}
|
||||
}
|
||||
|
||||
out, err := systemctl.RunSystemCtl("is-enabled", serviceName)
|
||||
if err != nil {
|
||||
data.AutoStart = false
|
||||
isEnabled, _ := systemctl.IsEnable(serviceName)
|
||||
data.AutoStart = isEnabled
|
||||
isActive, err := systemctl.IsActive(serviceName)
|
||||
if isActive {
|
||||
data.Status = constant.StatusEnable
|
||||
} else {
|
||||
if out == "alias\n" {
|
||||
data.AutoStart, _ = systemctl.IsEnable("ssh")
|
||||
} else {
|
||||
data.AutoStart = out == "enabled\n"
|
||||
data.Status = constant.StatusDisable
|
||||
if err != nil {
|
||||
data.Message = err.Error()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,29 +113,26 @@ func (u *SSHService) OperateSSH(operation string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sudo := cmd.SudoHandleCmd()
|
||||
if operation == "enable" || operation == "disable" {
|
||||
serviceName += ".service"
|
||||
}
|
||||
if operation == "stop" {
|
||||
switch operation {
|
||||
case "enable":
|
||||
return systemctl.Enable(serviceName)
|
||||
case "disable":
|
||||
return systemctl.Disable(serviceName)
|
||||
case "start":
|
||||
return systemctl.Start(serviceName)
|
||||
case "stop":
|
||||
isSocketActive, _ := systemctl.IsActive(serviceName + ".socket")
|
||||
if isSocketActive {
|
||||
std, err := cmd.Execf("%s systemctl stop %s", sudo, serviceName+".socket")
|
||||
if err != nil {
|
||||
global.LOG.Errorf("handle systemctl stop %s.socket failed, err: %v", serviceName, std)
|
||||
if err := systemctl.Stop(serviceName + ".socket"); err != nil {
|
||||
global.LOG.Errorf("Failed to stop %s.socket: %v", serviceName, err)
|
||||
}
|
||||
}
|
||||
return systemctl.Stop(serviceName)
|
||||
case "restart":
|
||||
return systemctl.Restart(serviceName)
|
||||
}
|
||||
|
||||
stdout, err := cmd.Execf("%s systemctl %s %s", sudo, operation, serviceName)
|
||||
if err != nil {
|
||||
if strings.Contains(stdout, "alias name or linked unit file") {
|
||||
stdout, err := cmd.Execf("%s systemctl %s ssh", sudo, operation)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s ssh(alias name or linked unit file) failed, stdout: %s, err: %v", operation, stdout, err)
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("%s %s failed, stdout: %s, err: %v", operation, serviceName, stdout, err)
|
||||
if result, err := systemctl.CustomAction(operation, serviceName); err != nil {
|
||||
return fmt.Errorf("failed to execute custom action: %v", result.Output)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -198,7 +187,10 @@ func (u *SSHService) Update(req dto.SSHUpdate) error {
|
||||
}
|
||||
}
|
||||
|
||||
_, _ = cmd.Execf("%s systemctl restart %s", sudo, serviceName)
|
||||
err = systemctl.Restart(serviceName)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("handle restart %s failed, err: %v", serviceName, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -216,8 +208,10 @@ func (u *SSHService) UpdateByFile(value string) error {
|
||||
if _, err = file.WriteString(value); err != nil {
|
||||
return err
|
||||
}
|
||||
sudo := cmd.SudoHandleCmd()
|
||||
_, _ = cmd.Execf("%s systemctl restart %s", sudo, serviceName)
|
||||
err = systemctl.Restart(serviceName)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("handle restart %s failed, err: %v", serviceName, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -298,7 +292,7 @@ func (u *SSHService) LoadLog(c *gin.Context, req dto.SearchSSHLog) (*dto.SSHLog,
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !info.IsDir() && (strings.HasPrefix(info.Name(), "secure") || strings.HasPrefix(info.Name(), "auth")) {
|
||||
if !info.IsDir() && (strings.HasPrefix(info.Name(), "secure") || strings.HasPrefix(info.Name(), "auth") || strings.HasPrefix(info.Name(), "messages")) {
|
||||
if !strings.HasSuffix(info.Name(), ".gz") {
|
||||
fileList = append(fileList, sshFileItem{Name: pathItem, Year: info.ModTime().Year()})
|
||||
return nil
|
||||
@@ -326,7 +320,8 @@ func (u *SSHService) LoadLog(c *gin.Context, req dto.SearchSSHLog) (*dto.SSHLog,
|
||||
nyc, _ := time.LoadLocation(common.LoadTimeZoneByCmd())
|
||||
for _, file := range fileList {
|
||||
commandItem := ""
|
||||
if strings.HasPrefix(path.Base(file.Name), "secure") {
|
||||
switch {
|
||||
case strings.HasPrefix(path.Base(file.Name), "secure"):
|
||||
switch req.Status {
|
||||
case constant.StatusSuccess:
|
||||
commandItem = fmt.Sprintf("cat %s | grep -a Accepted %s", file.Name, command)
|
||||
@@ -335,8 +330,16 @@ func (u *SSHService) LoadLog(c *gin.Context, req dto.SearchSSHLog) (*dto.SSHLog,
|
||||
default:
|
||||
commandItem = fmt.Sprintf("cat %s | grep -aE '(Failed password for|Accepted)' %s", file.Name, command)
|
||||
}
|
||||
}
|
||||
if strings.HasPrefix(path.Base(file.Name), "auth.log") {
|
||||
case strings.HasPrefix(path.Base(file.Name), "messages"):
|
||||
switch req.Status {
|
||||
case constant.StatusSuccess:
|
||||
commandItem = fmt.Sprintf("cat %s | grep -aE 'sshd.*Accepted (password|publickey)' %s", file.Name, command)
|
||||
case constant.StatusFailed:
|
||||
commandItem = fmt.Sprintf("cat %s | grep -aE 'sshd.*(Failed password for|Connection closed by authenticating user)' %s", file.Name, command)
|
||||
default:
|
||||
commandItem = fmt.Sprintf("cat %s | grep -aE 'sshd.*(Accepted|Failed password for|Connection closed)' %s", file.Name, command)
|
||||
}
|
||||
case strings.HasPrefix(path.Base(file.Name), "auth.log"):
|
||||
switch req.Status {
|
||||
case constant.StatusSuccess:
|
||||
commandItem = fmt.Sprintf("cat %s | grep -a Accepted %s", file.Name, command)
|
||||
@@ -432,107 +435,160 @@ func loadSSHData(c *gin.Context, command string, showCountFrom, showCountTo, cur
|
||||
lines := strings.Split(string(stdout2), "\n")
|
||||
for i := len(lines) - 1; i >= 0; i-- {
|
||||
var itemData dto.SSHHistory
|
||||
line := strings.TrimSpace(lines[i])
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
parts := strings.Fields(line)
|
||||
if len(parts) < 12 {
|
||||
continue
|
||||
}
|
||||
|
||||
// 统一时间解析逻辑
|
||||
var dateStr string
|
||||
var timeIndex int
|
||||
if strings.Contains(parts[0], "-") { // 处理RFC3339时间格式
|
||||
t, err := time.Parse(time.RFC3339Nano, parts[0])
|
||||
if err == nil {
|
||||
dateStr = t.Format("2006 Jan 2 15:04:05")
|
||||
timeIndex = 0
|
||||
}
|
||||
} else { // 处理系统日志格式
|
||||
dateParts := parts[:3]
|
||||
if len(dateParts) < 3 {
|
||||
continue
|
||||
}
|
||||
dateStr = strings.Join(dateParts, " ")
|
||||
timeIndex = 3
|
||||
}
|
||||
|
||||
// 根据日志类型解析内容
|
||||
switch {
|
||||
case strings.Contains(lines[i], "Failed password for"):
|
||||
itemData = loadFailedSecureDatas(lines[i])
|
||||
if len(itemData.Address) != 0 {
|
||||
if successCount+failedCount >= showCountFrom && successCount+failedCount < showCountTo {
|
||||
itemData.Area, _ = geo.GetIPLocation(itemData.Address, common.GetLang(c))
|
||||
itemData.Date = loadDate(currentYear, itemData.DateStr, nyc)
|
||||
datas = append(datas, itemData)
|
||||
}
|
||||
failedCount++
|
||||
}
|
||||
case strings.Contains(lines[i], "Connection closed by authenticating user"):
|
||||
itemData = loadFailedAuthDatas(lines[i])
|
||||
if len(itemData.Address) != 0 {
|
||||
if successCount+failedCount >= showCountFrom && successCount+failedCount < showCountTo {
|
||||
itemData.Area, _ = geo.GetIPLocation(itemData.Address, common.GetLang(c))
|
||||
itemData.Date = loadDate(currentYear, itemData.DateStr, nyc)
|
||||
datas = append(datas, itemData)
|
||||
}
|
||||
failedCount++
|
||||
}
|
||||
case strings.Contains(lines[i], "Accepted "):
|
||||
itemData = loadSuccessDatas(lines[i])
|
||||
if len(itemData.Address) != 0 {
|
||||
if successCount+failedCount >= showCountFrom && successCount+failedCount < showCountTo {
|
||||
itemData.Area, _ = geo.GetIPLocation(itemData.Address, common.GetLang(c))
|
||||
itemData.Date = loadDate(currentYear, itemData.DateStr, nyc)
|
||||
datas = append(datas, itemData)
|
||||
}
|
||||
successCount++
|
||||
}
|
||||
case strings.Contains(line, "Failed password for"):
|
||||
itemData = parseFailedPasswordLog(parts, timeIndex, dateStr)
|
||||
case strings.Contains(line, "Connection closed by authenticating user"):
|
||||
itemData = parseConnectionClosedLog(parts, timeIndex, dateStr)
|
||||
case strings.Contains(line, "Accepted"):
|
||||
itemData = parseAcceptedLog(parts, timeIndex, dateStr)
|
||||
default:
|
||||
continue
|
||||
}
|
||||
if itemData.Address == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
total := successCount + failedCount
|
||||
if total >= showCountFrom && total < showCountTo {
|
||||
itemData.Area, _ = geo.GetIPLocation(itemData.Address, common.GetLang(c))
|
||||
itemData.Date = parseLogDate(currentYear, dateStr, nyc)
|
||||
datas = append(datas, itemData)
|
||||
}
|
||||
|
||||
if itemData.Status == constant.StatusSuccess {
|
||||
successCount++
|
||||
} else {
|
||||
failedCount++
|
||||
}
|
||||
|
||||
if total >= showCountTo {
|
||||
break
|
||||
}
|
||||
}
|
||||
return datas, successCount, failedCount
|
||||
}
|
||||
|
||||
func loadSuccessDatas(line string) dto.SSHHistory {
|
||||
var data dto.SSHHistory
|
||||
parts := strings.Fields(line)
|
||||
index, dataStr := analyzeDateStr(parts)
|
||||
if dataStr == "" {
|
||||
return data
|
||||
func parseFailedPasswordLog(parts []string, timeIndex int, dateStr string) dto.SSHHistory {
|
||||
data := dto.SSHHistory{
|
||||
DateStr: dateStr,
|
||||
Status: constant.StatusFailed,
|
||||
AuthMode: "publickey",
|
||||
}
|
||||
data.DateStr = dataStr
|
||||
data.AuthMode = parts[4+index]
|
||||
data.User = parts[6+index]
|
||||
data.Address = parts[8+index]
|
||||
data.Port = parts[10+index]
|
||||
data.Status = constant.StatusSuccess
|
||||
return data
|
||||
}
|
||||
|
||||
func loadFailedAuthDatas(line string) dto.SSHHistory {
|
||||
var data dto.SSHHistory
|
||||
parts := strings.Fields(line)
|
||||
index, dataStr := analyzeDateStr(parts)
|
||||
if dataStr == "" {
|
||||
return data
|
||||
// 查找关键字段位置
|
||||
for i := timeIndex; i < len(parts); i++ {
|
||||
switch parts[i] {
|
||||
case "for":
|
||||
if i+1 < len(parts) {
|
||||
data.User = parts[i+1]
|
||||
}
|
||||
case "from":
|
||||
if i+1 < len(parts) {
|
||||
data.Address = parts[i+1]
|
||||
}
|
||||
case "port":
|
||||
if i+1 < len(parts) {
|
||||
data.Port = parts[i+1]
|
||||
}
|
||||
case "password":
|
||||
data.AuthMode = "password"
|
||||
}
|
||||
}
|
||||
data.DateStr = dataStr
|
||||
switch index {
|
||||
case 1:
|
||||
data.User = parts[9]
|
||||
case 2:
|
||||
data.User = parts[10]
|
||||
default:
|
||||
data.User = parts[7]
|
||||
}
|
||||
data.AuthMode = parts[6+index]
|
||||
data.Address = parts[9+index]
|
||||
data.Port = parts[11+index]
|
||||
data.Status = constant.StatusFailed
|
||||
if strings.Contains(line, ": ") {
|
||||
data.Message = strings.Split(line, ": ")[1]
|
||||
}
|
||||
return data
|
||||
}
|
||||
func loadFailedSecureDatas(line string) dto.SSHHistory {
|
||||
var data dto.SSHHistory
|
||||
parts := strings.Fields(line)
|
||||
index, dataStr := analyzeDateStr(parts)
|
||||
if dataStr == "" {
|
||||
return data
|
||||
}
|
||||
data.DateStr = dataStr
|
||||
if strings.Contains(line, " invalid ") {
|
||||
data.AuthMode = parts[4+index]
|
||||
index += 2
|
||||
} else {
|
||||
data.AuthMode = parts[4+index]
|
||||
}
|
||||
data.User = parts[6+index]
|
||||
data.Address = parts[8+index]
|
||||
data.Port = parts[10+index]
|
||||
data.Status = constant.StatusFailed
|
||||
if strings.Contains(line, ": ") {
|
||||
data.Message = strings.Split(line, ": ")[1]
|
||||
|
||||
if strings.Contains(strings.Join(parts, " "), "invalid user") {
|
||||
data.Message = "invalid user attempt"
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
func parseConnectionClosedLog(parts []string, timeIndex int, dateStr string) dto.SSHHistory {
|
||||
data := dto.SSHHistory{
|
||||
DateStr: dateStr,
|
||||
Status: constant.StatusFailed,
|
||||
}
|
||||
|
||||
// 解析Alpine格式的特殊字段
|
||||
fieldStart := timeIndex + 5 // 跳过时间、主机、进程字段
|
||||
for i := fieldStart; i < len(parts); i++ {
|
||||
switch {
|
||||
case parts[i] == "user":
|
||||
if i+1 < len(parts) {
|
||||
data.User = parts[i+1]
|
||||
}
|
||||
case parts[i] == "port":
|
||||
if i+1 < len(parts) {
|
||||
data.Port = parts[i+1]
|
||||
}
|
||||
case isIPAddress(parts[i]):
|
||||
data.Address = parts[i]
|
||||
}
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
func parseAcceptedLog(parts []string, timeIndex int, dateStr string) dto.SSHHistory {
|
||||
data := dto.SSHHistory{
|
||||
DateStr: dateStr,
|
||||
Status: constant.StatusSuccess,
|
||||
AuthMode: "password", // 默认值
|
||||
}
|
||||
|
||||
// 处理不同日志格式
|
||||
fieldStart := timeIndex + 5 // 基础字段偏移
|
||||
for i := fieldStart; i < len(parts); i++ {
|
||||
switch {
|
||||
case parts[i] == "for":
|
||||
if i+1 < len(parts) {
|
||||
data.User = parts[i+1]
|
||||
}
|
||||
case parts[i] == "from":
|
||||
if i+1 < len(parts) {
|
||||
data.Address = parts[i+1]
|
||||
}
|
||||
case parts[i] == "port":
|
||||
if i+1 < len(parts) {
|
||||
data.Port = parts[i+1]
|
||||
}
|
||||
case strings.Contains(parts[i], "ssh2:"):
|
||||
data.AuthMode = "publickey"
|
||||
case parts[i] == "publickey":
|
||||
data.AuthMode = "publickey"
|
||||
case parts[i] == "password":
|
||||
data.AuthMode = "password"
|
||||
}
|
||||
}
|
||||
return data
|
||||
}
|
||||
func handleGunzip(path string) error {
|
||||
if _, err := cmd.Execf("gunzip %s", path); err != nil {
|
||||
return err
|
||||
@@ -541,40 +597,36 @@ func handleGunzip(path string) error {
|
||||
}
|
||||
|
||||
func loadServiceName() (string, error) {
|
||||
if exist, _ := systemctl.IsExist("sshd"); exist {
|
||||
return "sshd", nil
|
||||
} else if exist, _ := systemctl.IsExist("ssh"); exist {
|
||||
return "ssh", nil
|
||||
}
|
||||
return "", errors.New("The ssh or sshd service is unavailable")
|
||||
}
|
||||
|
||||
func loadDate(currentYear int, DateStr string, nyc *time.Location) time.Time {
|
||||
itemDate, err := time.ParseInLocation("2006 Jan 2 15:04:05", fmt.Sprintf("%d %s", currentYear, DateStr), nyc)
|
||||
serviceName, err := systemctl.GetServiceName("ssh")
|
||||
if err != nil {
|
||||
itemDate, _ = time.ParseInLocation("2006 Jan 2 15:04:05", DateStr, nyc)
|
||||
if errors.Is(err, systemctl.ErrServiceNotFound) {
|
||||
return "", fmt.Errorf("SSH service unavailable. Please ensure OpenSSH server is installed and configured")
|
||||
}
|
||||
return "", fmt.Errorf("failed to load SSH service name: %w", err)
|
||||
}
|
||||
return itemDate
|
||||
return serviceName, nil
|
||||
}
|
||||
|
||||
func analyzeDateStr(parts []string) (int, string) {
|
||||
t, err := time.Parse(time.RFC3339Nano, parts[0])
|
||||
if err == nil {
|
||||
if len(parts) < 12 {
|
||||
return 0, ""
|
||||
}
|
||||
return 0, t.Format("2006 Jan 2 15:04:05")
|
||||
}
|
||||
t, err = time.Parse(constant.DateTimeLayout, fmt.Sprintf("%s %s", parts[0], parts[1]))
|
||||
if err == nil {
|
||||
if len(parts) < 14 {
|
||||
return 0, ""
|
||||
}
|
||||
return 1, t.Format("2006 Jan 2 15:04:05")
|
||||
func parseLogDate(currentYear int, dateStr string, loc *time.Location) time.Time {
|
||||
// 处理带年份和不带年份的情况
|
||||
formats := []string{
|
||||
"2006 Jan 2 15:04:05",
|
||||
"Jan 2 15:04:05",
|
||||
}
|
||||
|
||||
if len(parts) < 14 {
|
||||
return 0, ""
|
||||
for _, format := range formats {
|
||||
t, err := time.ParseInLocation(format, dateStr, loc)
|
||||
if err == nil {
|
||||
if t.Year() == 0 {
|
||||
return time.Date(currentYear, t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second(), 0, loc)
|
||||
}
|
||||
return t
|
||||
}
|
||||
}
|
||||
return 2, fmt.Sprintf("%s %s %s", parts[0], parts[1], parts[2])
|
||||
return time.Now().In(loc)
|
||||
}
|
||||
|
||||
func isIPAddress(s string) bool {
|
||||
return net.ParseIP(s) != nil
|
||||
|
||||
}
|
||||
|
||||
+154
-80
@@ -6,6 +6,7 @@ import (
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -17,6 +18,7 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/common"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/files"
|
||||
httpUtil "github.com/1Panel-dev/1Panel/backend/utils/http"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/systemctl"
|
||||
)
|
||||
|
||||
type UpgradeService struct{}
|
||||
@@ -103,123 +105,159 @@ func (u *UpgradeService) Upgrade(req dto.Upgrade) error {
|
||||
}
|
||||
downloadPath := fmt.Sprintf("%s/%s/%s/release", global.CONF.System.RepoUrl, mode, req.Version)
|
||||
fileName := fmt.Sprintf("1panel-%s-%s-%s.tar.gz", req.Version, "linux", itemArch)
|
||||
_ = settingRepo.Update("SystemStatus", "Upgrading")
|
||||
serviceHandle, _ := systemctl.DefaultHandler("1panel")
|
||||
currentServiceName := serviceHandle.GetServiceName()
|
||||
if err := settingRepo.Update("SystemStatus", "Upgrading"); err != nil {
|
||||
return fmt.Errorf("update system status failed: %w", err)
|
||||
}
|
||||
|
||||
go func() {
|
||||
defer func() {
|
||||
if err := settingRepo.Update("SystemStatus", "Free"); err != nil {
|
||||
global.LOG.Errorf("Reset system status failed: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
_ = global.Cron.Stop()
|
||||
defer func() {
|
||||
global.Cron.Start()
|
||||
}()
|
||||
if err := fileOp.DownloadFileWithProxy(downloadPath+"/"+fileName, rootDir+"/"+fileName); err != nil {
|
||||
global.LOG.Errorf("download service file failed, err: %v", err)
|
||||
_ = settingRepo.Update("SystemStatus", "Free")
|
||||
defer global.Cron.Start()
|
||||
|
||||
if err := fileOp.DownloadFileWithProxy(
|
||||
fmt.Sprintf("%s/%s", downloadPath, fileName),
|
||||
path.Join(rootDir, fileName),
|
||||
); err != nil {
|
||||
global.LOG.Errorf("Failed to download upgrade package: %v", err)
|
||||
return
|
||||
}
|
||||
global.LOG.Info("download all file successful!")
|
||||
defer func() {
|
||||
_ = os.Remove(rootDir)
|
||||
}()
|
||||
if err := handleUnTar(rootDir+"/"+fileName, rootDir, ""); err != nil {
|
||||
global.LOG.Errorf("decompress file failed, err: %v", err)
|
||||
_ = settingRepo.Update("SystemStatus", "Free")
|
||||
defer os.RemoveAll(rootDir)
|
||||
|
||||
if err := handleUnTar(path.Join(rootDir, fileName), rootDir, ""); err != nil {
|
||||
global.LOG.Errorf("Failed to extract package: %v", err)
|
||||
return
|
||||
}
|
||||
tmpDir := rootDir + "/" + strings.ReplaceAll(fileName, ".tar.gz", "")
|
||||
|
||||
tmpDir := path.Join(rootDir, strings.TrimSuffix(fileName, ".tar.gz"))
|
||||
|
||||
if err := u.handleBackup(fileOp, originalDir); err != nil {
|
||||
global.LOG.Errorf("handle backup original file failed, err: %v", err)
|
||||
_ = settingRepo.Update("SystemStatus", "Free")
|
||||
return
|
||||
}
|
||||
global.LOG.Info("backup original data successful, now start to upgrade!")
|
||||
|
||||
if err := common.CopyFile(path.Join(tmpDir, "1panel"), "/usr/local/bin"); err != nil {
|
||||
global.LOG.Errorf("upgrade 1panel failed, err: %v", err)
|
||||
u.handleRollback(originalDir, 1)
|
||||
global.LOG.Errorf("Backup failed: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := common.CopyFile(path.Join(tmpDir, "1pctl"), "/usr/local/bin"); err != nil {
|
||||
global.LOG.Errorf("upgrade 1pctl failed, err: %v", err)
|
||||
u.handleRollback(originalDir, 2)
|
||||
return
|
||||
}
|
||||
_, _ = cmd.Execf("cp -r %s /usr/local/bin", path.Join(tmpDir, "lang"))
|
||||
geoPath := path.Join(global.CONF.System.BaseDir, "1panel/geo")
|
||||
_, _ = cmd.Execf("mkdir %s && cp %s %s/", geoPath, path.Join(tmpDir, "GeoIP.mmdb"), geoPath)
|
||||
binDir := systemctl.BinaryPath
|
||||
servicePath, _ := serviceHandle.GetServicePath()
|
||||
geoPath := path.Join(global.CONF.System.BaseDir, "1panel/geo/GeoIP.mmdb")
|
||||
|
||||
if _, err := cmd.Execf("sed -i -e 's#BASE_DIR=.*#BASE_DIR=%s#g' /usr/local/bin/1pctl", global.CONF.System.BaseDir); err != nil {
|
||||
global.LOG.Errorf("upgrade basedir in 1pctl failed, err: %v", err)
|
||||
criticalUpdates := []struct {
|
||||
src string
|
||||
dest string
|
||||
step int
|
||||
}{
|
||||
{path.Join(tmpDir, "1panel"), path.Join(binDir, "1panel"), 1},
|
||||
{path.Join(tmpDir, "1pctl"), path.Join(binDir, "1pctl"), 2},
|
||||
{selectInitScript(path.Join(tmpDir, "initscript"), currentServiceName), servicePath, 3},
|
||||
}
|
||||
|
||||
for _, update := range criticalUpdates {
|
||||
if err := common.Copy(update.src, update.dest); err != nil {
|
||||
global.LOG.Errorf("Update %s failed: %v", path.Base(update.dest), err)
|
||||
u.handleRollback(originalDir, update.step)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := cmd.Execf("sed -i -e 's#BASE_DIR=.*#BASE_DIR=%s#g' /usr/local/bin/1pctl",
|
||||
global.CONF.System.BaseDir); err != nil {
|
||||
global.LOG.Errorf("Update base directory failed: %v", err)
|
||||
u.handleRollback(originalDir, 2)
|
||||
return
|
||||
}
|
||||
|
||||
if err := common.CopyFile(path.Join(tmpDir, "1panel.service"), "/etc/systemd/system"); err != nil {
|
||||
global.LOG.Errorf("upgrade 1panel.service failed, err: %v", err)
|
||||
u.handleRollback(originalDir, 3)
|
||||
return
|
||||
langDir := path.Join(binDir, "lang")
|
||||
if err := common.Copy(path.Join(tmpDir, "lang"), langDir); err != nil {
|
||||
global.LOG.Errorf("Update language files failed: %v", err)
|
||||
}
|
||||
if err := common.Copy(path.Join(tmpDir, "GeoIP.mmdb"), geoPath); err != nil {
|
||||
global.LOG.Warnf("Update GeoIP database failed: %v", err)
|
||||
}
|
||||
|
||||
global.LOG.Info("upgrade successful!")
|
||||
go writeLogs(req.Version)
|
||||
_ = settingRepo.Update("SystemVersion", req.Version)
|
||||
_ = settingRepo.Update("SystemStatus", "Free")
|
||||
checkPointOfWal()
|
||||
_, _ = cmd.ExecWithTimeOut("systemctl daemon-reload && systemctl restart 1panel.service", 1*time.Minute)
|
||||
if err := settingRepo.Update("SystemVersion", req.Version); err != nil {
|
||||
global.LOG.Errorf("Update system version failed: %v", err)
|
||||
}
|
||||
if serviceHandle.ManagerName() == "systemd" {
|
||||
_, _ = cmd.Exec("systemctl daemon-reload")
|
||||
}
|
||||
if err := systemctl.Restart("1panel"); err != nil {
|
||||
global.LOG.Errorf("Service restart failed: %v", err)
|
||||
return
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *UpgradeService) handleBackup(fileOp files.FileOp, originalDir string) error {
|
||||
if err := fileOp.Copy("/usr/local/bin/1panel", originalDir); err != nil {
|
||||
return err
|
||||
global.LOG.Info("Initiating backup procedure...")
|
||||
h, _ := systemctl.DefaultHandler("1panel")
|
||||
binDir := systemctl.BinaryPath
|
||||
servicePath, _ := h.GetServicePath()
|
||||
geoPath := path.Join(global.CONF.System.BaseDir, "1panel/geo/GeoIP.mmdb")
|
||||
|
||||
backupItems := []struct {
|
||||
src string
|
||||
dest string
|
||||
}{
|
||||
{path.Join(binDir, "1panel"), originalDir},
|
||||
{path.Join(binDir, "1pctl"), originalDir},
|
||||
{servicePath, originalDir},
|
||||
{path.Join(binDir, "lang"), originalDir},
|
||||
{geoPath, originalDir},
|
||||
}
|
||||
if err := fileOp.Copy("/usr/local/bin/1pctl", originalDir); err != nil {
|
||||
return err
|
||||
|
||||
for _, item := range backupItems {
|
||||
if err := fileOp.Copy(item.src, item.dest); err != nil {
|
||||
return fmt.Errorf("backup %s failed: %w", path.Base(item.src), err)
|
||||
}
|
||||
}
|
||||
if err := fileOp.Copy("/etc/systemd/system/1panel.service", originalDir); err != nil {
|
||||
return err
|
||||
}
|
||||
_, _ = cmd.Execf("cp -r /usr/local/bin/lang %s", originalDir)
|
||||
_, _ = cmd.Execf("cp %s %s", path.Join(global.CONF.System.BaseDir, "1panel/geo/GeoIP.mmdb"), originalDir)
|
||||
checkPointOfWal()
|
||||
if err := handleTar(path.Join(global.CONF.System.BaseDir, "1panel/db"), originalDir, "db.tar.gz", "db/1Panel.db-*", ""); err != nil {
|
||||
return err
|
||||
|
||||
if err := handleTar(
|
||||
path.Join(global.CONF.System.BaseDir, "1panel/db"),
|
||||
originalDir,
|
||||
"db.tar.gz",
|
||||
"db/1Panel.db-*",
|
||||
"",
|
||||
); err != nil {
|
||||
return fmt.Errorf("database backup failed: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *UpgradeService) handleRollback(originalDir string, errStep int) {
|
||||
_ = settingRepo.Update("SystemStatus", "Free")
|
||||
global.LOG.Info("Initiating rollback procedure...")
|
||||
h, _ := systemctl.DefaultHandler("1panel")
|
||||
binDir := systemctl.BinaryPath
|
||||
servicePath, _ := h.GetServicePath()
|
||||
geoPath := path.Join(global.CONF.System.BaseDir, "1panel/geo/GeoIP.mmdb")
|
||||
|
||||
checkPointOfWal()
|
||||
if _, err := os.Stat(path.Join(originalDir, "1Panel.db")); err == nil {
|
||||
if err := common.CopyFile(path.Join(originalDir, "1Panel.db"), global.CONF.System.DbPath); err != nil {
|
||||
global.LOG.Errorf("rollback 1panel db failed, err: %v", err)
|
||||
rollbackSteps := []struct {
|
||||
src string
|
||||
dest string
|
||||
}{
|
||||
{path.Join(originalDir, "1panel"), path.Join(binDir, "1panel")},
|
||||
{path.Join(originalDir, "1pctl"), path.Join(binDir, "1pctl")},
|
||||
{path.Join(originalDir, filepath.Base(servicePath)), servicePath},
|
||||
{path.Join(originalDir, "lang"), path.Join(binDir, "lang")},
|
||||
{path.Join(originalDir, "GeoIP.mmdb"), geoPath},
|
||||
}
|
||||
|
||||
for _, step := range rollbackSteps[:errStep] {
|
||||
if err := common.CopyFile(step.src, step.dest); err != nil {
|
||||
global.LOG.Errorf("Rollback %s failed: %v", path.Base(step.src), err)
|
||||
}
|
||||
}
|
||||
if _, err := os.Stat(path.Join(originalDir, "db.tar.gz")); err == nil {
|
||||
if err := handleUnTar(path.Join(originalDir, "db.tar.gz"), global.CONF.System.DbPath, ""); err != nil {
|
||||
global.LOG.Errorf("rollback 1panel db failed, err: %v", err)
|
||||
}
|
||||
}
|
||||
if err := common.CopyFile(path.Join(originalDir, "1panel"), "/usr/local/bin"); err != nil {
|
||||
global.LOG.Errorf("rollback 1pctl failed, err: %v", err)
|
||||
}
|
||||
if errStep == 1 {
|
||||
return
|
||||
}
|
||||
if err := common.CopyFile(path.Join(originalDir, "1pctl"), "/usr/local/bin"); err != nil {
|
||||
global.LOG.Errorf("rollback 1panel failed, err: %v", err)
|
||||
}
|
||||
_, _ = cmd.Execf("cp -r %s /usr/local/bin", path.Join(originalDir, "lang"))
|
||||
geoPath := path.Join(global.CONF.System.BaseDir, "1panel/geo")
|
||||
_, _ = cmd.Execf("mkdir %s && cp %s %s/", geoPath, path.Join(originalDir, "GeoIP.mmdb"), geoPath)
|
||||
|
||||
if errStep == 2 {
|
||||
return
|
||||
}
|
||||
if err := common.CopyFile(path.Join(originalDir, "1panel.service"), "/etc/systemd/system"); err != nil {
|
||||
global.LOG.Errorf("rollback 1panel failed, err: %v", err)
|
||||
if err := systemctl.Restart("1panel"); err != nil {
|
||||
global.LOG.Errorf("Service restart during rollback failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -357,5 +395,41 @@ func loadArch() (string, error) {
|
||||
if strings.Contains(std, "s390x") {
|
||||
return "s390x", nil
|
||||
}
|
||||
if strings.Contains(std, "riscv64") {
|
||||
return "riscv64", nil
|
||||
}
|
||||
return "", fmt.Errorf("unsupported such arch: %s", std)
|
||||
}
|
||||
|
||||
func selectInitScript(path string, serviceName string) string {
|
||||
path = strings.TrimSuffix(path, "/")
|
||||
mgr := systemctl.GetGlobalManager().Name()
|
||||
var serviceFileName string
|
||||
switch mgr {
|
||||
case "systemd":
|
||||
serviceFileName = "1panel.service"
|
||||
case "openrc":
|
||||
serviceFileName = "1paneld.openrc"
|
||||
case "sysvinit":
|
||||
isWrt := systemctl.FileExist("/etc/rc.common")
|
||||
if isWrt {
|
||||
serviceFileName = "1paneld.procd"
|
||||
} else {
|
||||
serviceFileName = "1paneld.init"
|
||||
}
|
||||
default:
|
||||
serviceFileName = serviceName
|
||||
global.LOG.Warnf("[%s]unselect InitScript, used default: %s", mgr, serviceName)
|
||||
}
|
||||
sourcePath := filepath.Join(path, serviceFileName)
|
||||
targetPath := filepath.Join(path, serviceName)
|
||||
|
||||
if serviceFileName != serviceName {
|
||||
if _, err := cmd.Execf("cp %s %s", sourcePath, targetPath); err != nil {
|
||||
global.LOG.Errorf("Failed to copy init script from %s to %s: %v",
|
||||
serviceFileName, serviceName, err)
|
||||
}
|
||||
}
|
||||
|
||||
return targetPath
|
||||
}
|
||||
|
||||
@@ -427,7 +427,9 @@ func (w WebsiteService) UpdateWebsite(req request.WebsiteUpdate) error {
|
||||
}
|
||||
}
|
||||
website.PrimaryDomain = req.PrimaryDomain
|
||||
website.WebsiteGroupID = req.WebsiteGroupID
|
||||
if req.WebsiteGroupID > 0 {
|
||||
website.WebsiteGroupID = req.WebsiteGroupID
|
||||
}
|
||||
website.Remark = req.Remark
|
||||
website.IPV6 = req.IPV6
|
||||
|
||||
@@ -1768,11 +1770,22 @@ func (w WebsiteService) GetProxies(id uint) (res []request.WebsiteProxyConfig, e
|
||||
}
|
||||
directives := config.GetDirectives()
|
||||
|
||||
location, ok := directives[0].(*components.Location)
|
||||
if !ok {
|
||||
err = errors.New("error")
|
||||
return
|
||||
var (
|
||||
location *components.Location
|
||||
ok bool
|
||||
)
|
||||
for _, directive := range directives {
|
||||
if directive.GetName() == "location" {
|
||||
location, ok = directive.(*components.Location)
|
||||
if ok {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if location == nil {
|
||||
return nil, buserr.New("ErrConfigParse")
|
||||
}
|
||||
|
||||
proxyConfig.ProxyPass = location.ProxyPass
|
||||
proxyConfig.Cache = location.Cache
|
||||
if location.CacheTime > 0 {
|
||||
|
||||
@@ -108,6 +108,7 @@ func Init() {
|
||||
migrations.AddLicenseVerify,
|
||||
|
||||
migrations.AddMcpServer,
|
||||
migrations.AddPbootCMSPHPExtensions,
|
||||
})
|
||||
if err := m.Migrate(); err != nil {
|
||||
global.LOG.Error(err)
|
||||
|
||||
@@ -469,3 +469,19 @@ var AddMcpServer = &gormigrate.Migration{
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var AddPbootCMSPHPExtensions = &gormigrate.Migration{
|
||||
ID: "20250625-add-php-extensions",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
item := &model.PHPExtensions{
|
||||
Name: "PbootCMS",
|
||||
}
|
||||
if err := tx.Where("name = ?", "PbootCMS").FirstOrCreate(item, &model.PHPExtensions{
|
||||
Name: "PbootCMS",
|
||||
Extensions: "curl,gd,mbstring,mysqli,openssl,pdo_mysql,pdo_sqlite,sqlite3",
|
||||
}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
@@ -3,13 +3,12 @@ package common
|
||||
import (
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"github.com/1Panel-dev/1Panel/backend/buserr"
|
||||
"github.com/gin-gonic/gin"
|
||||
"io"
|
||||
mathRand "math/rand"
|
||||
"net"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"sort"
|
||||
@@ -18,6 +17,9 @@ import (
|
||||
"time"
|
||||
"unicode"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/buserr"
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"golang.org/x/net/idna"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
|
||||
@@ -124,6 +126,9 @@ func GetSortedVersions(versions []string) []string {
|
||||
}
|
||||
|
||||
func CopyFile(src, dst string) error {
|
||||
if fi, err := os.Stat(src); err == nil && fi.IsDir() {
|
||||
return fmt.Errorf("src is a directory, use CopyDirs instead")
|
||||
}
|
||||
source, err := os.Open(src)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -152,7 +157,57 @@ func CopyFile(src, dst string) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func CopyDirs(srcDir, dstDir string) error {
|
||||
srcInfo, err := os.Stat(srcDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !srcInfo.IsDir() {
|
||||
return fmt.Errorf("%s is not a directory", srcDir)
|
||||
}
|
||||
if err := os.MkdirAll(dstDir, srcInfo.Mode()); err != nil {
|
||||
return err
|
||||
}
|
||||
return filepath.Walk(srcDir, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
relPath, err := filepath.Rel(srcDir, path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dstPath := filepath.Join(dstDir, relPath)
|
||||
if info.IsDir() {
|
||||
return os.MkdirAll(dstPath, info.Mode())
|
||||
}
|
||||
if info.Mode()&os.ModeSymlink != 0 {
|
||||
return copySymlink(path, dstPath)
|
||||
}
|
||||
return CopyFile(path, dstPath)
|
||||
})
|
||||
}
|
||||
|
||||
func copySymlink(src, dst string) error {
|
||||
link, err := os.Readlink(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return os.Symlink(link, dst)
|
||||
}
|
||||
func Copy(src, dst string) error {
|
||||
fi, err := os.Stat(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch mode := fi.Mode(); {
|
||||
case mode.IsDir():
|
||||
return CopyDirs(src, dst)
|
||||
case mode.IsRegular():
|
||||
return CopyFile(src, dst)
|
||||
default:
|
||||
return fmt.Errorf("unsupported file type: %s", mode)
|
||||
}
|
||||
}
|
||||
func IsCrossVersion(version1, version2 string) bool {
|
||||
version1s := strings.Split(version1, ".")
|
||||
version2s := strings.Split(version2, ".")
|
||||
|
||||
@@ -349,26 +349,30 @@ func (f FileOp) DownloadFileWithProxy(url, dst string) error {
|
||||
}
|
||||
|
||||
func (f FileOp) Cut(oldPaths []string, dst, name string, cover bool) error {
|
||||
for _, p := range oldPaths {
|
||||
var dstPath string
|
||||
if name != "" {
|
||||
dstPath = filepath.Join(dst, name)
|
||||
if f.Stat(dstPath) {
|
||||
dstPath = dst
|
||||
}
|
||||
} else {
|
||||
base := filepath.Base(p)
|
||||
dstPath = filepath.Join(dst, base)
|
||||
if len(oldPaths) == 0 {
|
||||
return nil
|
||||
}
|
||||
var dstPath string
|
||||
coverFlag := ""
|
||||
if name != "" {
|
||||
dstPath = filepath.Join(dst, name)
|
||||
if f.Stat(dstPath) {
|
||||
dstPath = dst
|
||||
}
|
||||
coverFlag := ""
|
||||
if cover {
|
||||
coverFlag = "-f"
|
||||
}
|
||||
|
||||
cmdStr := fmt.Sprintf(`mv %s '%s' '%s'`, coverFlag, p, dstPath)
|
||||
if err := cmd.ExecCmd(cmdStr); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
dstPath = dst
|
||||
coverFlag = "-f"
|
||||
}
|
||||
var quotedPaths []string
|
||||
for _, p := range oldPaths {
|
||||
quotedPaths = append(quotedPaths, fmt.Sprintf("'%s'", p))
|
||||
}
|
||||
mvCommand := fmt.Sprintf("mv %s %s '%s'", coverFlag, strings.Join(quotedPaths, " "), dstPath)
|
||||
if err := cmd.ExecCmd(mvCommand); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/systemctl"
|
||||
)
|
||||
|
||||
var ForwardListRegex = regexp.MustCompile(`^port=(\d{1,5}):proto=(.+?):toport=(\d{1,5}):toaddr=(.*)$`)
|
||||
@@ -41,27 +42,15 @@ func (f *Firewall) Version() (string, error) {
|
||||
}
|
||||
|
||||
func (f *Firewall) Start() error {
|
||||
stdout, err := cmd.Exec("systemctl start firewalld")
|
||||
if err != nil {
|
||||
return fmt.Errorf("enable the firewall failed, err: %s", stdout)
|
||||
}
|
||||
return nil
|
||||
return systemctl.Start("firewalld")
|
||||
}
|
||||
|
||||
func (f *Firewall) Stop() error {
|
||||
stdout, err := cmd.Exec("systemctl stop firewalld")
|
||||
if err != nil {
|
||||
return fmt.Errorf("stop the firewall failed, err: %s", stdout)
|
||||
}
|
||||
return nil
|
||||
return systemctl.Stop("firewalld")
|
||||
}
|
||||
|
||||
func (f *Firewall) Restart() error {
|
||||
stdout, err := cmd.Exec("systemctl restart firewalld")
|
||||
if err != nil {
|
||||
return fmt.Errorf("restart the firewall failed, err: %s", stdout)
|
||||
}
|
||||
return nil
|
||||
return systemctl.Restart("firewalld")
|
||||
}
|
||||
|
||||
func (f *Firewall) Reload() error {
|
||||
|
||||
@@ -188,19 +188,18 @@ func (r *Local) ChangeAccess(info AccessChangeInfo) error {
|
||||
info.Name = "*"
|
||||
info.Password = r.Password
|
||||
}
|
||||
if info.Permission == info.OldPermission {
|
||||
return nil
|
||||
}
|
||||
if err := r.Delete(DeleteInfo{
|
||||
Version: info.Version,
|
||||
Username: info.Username,
|
||||
Permission: info.OldPermission,
|
||||
ForceDelete: true,
|
||||
Timeout: 300}); err != nil {
|
||||
return err
|
||||
}
|
||||
if info.Username == "root" {
|
||||
return nil
|
||||
if info.Permission != info.OldPermission {
|
||||
if err := r.Delete(DeleteInfo{
|
||||
Version: info.Version,
|
||||
Username: info.Username,
|
||||
Permission: info.OldPermission,
|
||||
ForceDelete: true,
|
||||
Timeout: 300}); err != nil {
|
||||
return err
|
||||
}
|
||||
if info.Username == "root" {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
if err := r.CreateUser(CreateInfo{
|
||||
Name: info.Name,
|
||||
|
||||
@@ -199,19 +199,18 @@ func (r *Remote) ChangeAccess(info AccessChangeInfo) error {
|
||||
info.Name = "*"
|
||||
info.Password = r.Password
|
||||
}
|
||||
if info.Permission == info.OldPermission {
|
||||
return nil
|
||||
}
|
||||
if err := r.Delete(DeleteInfo{
|
||||
Version: info.Version,
|
||||
Username: info.Username,
|
||||
Permission: info.OldPermission,
|
||||
ForceDelete: true,
|
||||
Timeout: 300}); err != nil {
|
||||
return err
|
||||
}
|
||||
if info.Username == "root" {
|
||||
return nil
|
||||
if info.Permission != info.OldPermission {
|
||||
if err := r.Delete(DeleteInfo{
|
||||
Version: info.Version,
|
||||
Username: info.Username,
|
||||
Permission: info.OldPermission,
|
||||
ForceDelete: true,
|
||||
Timeout: 300}); err != nil {
|
||||
return err
|
||||
}
|
||||
if info.Username == "root" {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
if err := r.CreateUser(CreateInfo{
|
||||
Name: info.Name,
|
||||
|
||||
@@ -0,0 +1,393 @@
|
||||
package systemctl
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"go4.org/syncutil/singleflight"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
var (
|
||||
aliasFile string
|
||||
serviceAliases sync.Map
|
||||
saveTimer *time.Timer
|
||||
saveMutex sync.Mutex
|
||||
afterSaveTime = 20 * time.Second
|
||||
)
|
||||
var (
|
||||
ErrServiceNotFound = errors.New("service not found")
|
||||
ErrDiscoveryTimeout = errors.New("service discovery timeout for: %w")
|
||||
ErrServiceDiscovery = errors.New("service discovery failed for: %w")
|
||||
ErrNoValidService = errors.New("no valid service found for: %w")
|
||||
)
|
||||
|
||||
func loadPredefinedAliases() map[string][]string {
|
||||
return map[string][]string{
|
||||
"clam": {"clamav-daemon.service", "clamd@scan.service", "clamd"},
|
||||
"freshclam": {"clamav-freshclam.service", "freshclam.service"},
|
||||
"fail2ban": {"fail2ban.service", "fail2ban"},
|
||||
"supervisor": {"supervisord.service", "supervisor.service", "supervisord", "supervisor"},
|
||||
"ssh": {"sshd.service", "ssh.service", "sshd", "ssh"},
|
||||
"1panel": {"1panel.service", "1paneld"},
|
||||
"docker": {"docker.service", "dockerd"},
|
||||
}
|
||||
}
|
||||
|
||||
func InitializeServiceDiscovery() {
|
||||
svcName := loadAliasesFromConfig()
|
||||
if len(svcName) > 0 {
|
||||
RegisterServiceAliases(svcName)
|
||||
}
|
||||
}
|
||||
|
||||
func RegisterServiceAliases(aliases map[string][]string) {
|
||||
for key, values := range aliases {
|
||||
existing, loaded := serviceAliases.LoadOrStore(key, values)
|
||||
if loaded {
|
||||
merged := append(existing.([]string), values...)
|
||||
serviceAliases.Store(key, merged)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func loadAliasesFromConfig() map[string][]string {
|
||||
data, err := os.ReadFile(aliasFile)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
var rawAliases map[string][]string
|
||||
json.Unmarshal(data, &rawAliases)
|
||||
|
||||
validAliases := make(map[string][]string)
|
||||
for key, aliases := range rawAliases {
|
||||
valid := []string{}
|
||||
for _, alias := range aliases {
|
||||
confirmed, _ := confirmServiceExists(alias)
|
||||
if confirmed {
|
||||
valid = append(valid, alias)
|
||||
}
|
||||
}
|
||||
if len(valid) > 0 {
|
||||
validAliases[key] = valid
|
||||
}
|
||||
}
|
||||
return validAliases
|
||||
}
|
||||
|
||||
func cleanupKeywordAliases(keyword string) {
|
||||
serviceAliases.Range(func(k, v interface{}) bool {
|
||||
if k.(string) != keyword {
|
||||
return true
|
||||
}
|
||||
aliases := v.([]string)
|
||||
valid := make([]string, 0)
|
||||
for _, alias := range aliases {
|
||||
confirmed, _ := confirmServiceExists(alias)
|
||||
if confirmed {
|
||||
valid = append(valid, alias)
|
||||
}
|
||||
}
|
||||
if len(valid) == 0 {
|
||||
serviceAliases.Delete(k)
|
||||
serviceExistenceCache.Delete(k)
|
||||
} else {
|
||||
serviceAliases.Store(k, valid)
|
||||
}
|
||||
return true
|
||||
})
|
||||
go scheduleSave()
|
||||
}
|
||||
|
||||
func smartServiceName(keyword string) (string, error) {
|
||||
mgr := GetGlobalManager()
|
||||
processedName := handleServiceNaming(mgr, keyword)
|
||||
|
||||
confirmed, _ := confirmServiceExists(processedName)
|
||||
if confirmed {
|
||||
updateAliases(keyword, processedName)
|
||||
return processedName, nil
|
||||
}
|
||||
|
||||
candidates := append([]string{processedName}, getAliases(keyword)...)
|
||||
if name, err := validateCandidatesConcurrently(candidates); err == nil {
|
||||
updateAliases(keyword, name)
|
||||
return name, nil
|
||||
}
|
||||
|
||||
discoveredName, err := discoverAndSelectService(keyword)
|
||||
if err != nil {
|
||||
cleanupKeywordAliases(keyword)
|
||||
return "", ErrServiceNotFound
|
||||
}
|
||||
updateAliases(keyword, discoveredName)
|
||||
return discoveredName, nil
|
||||
}
|
||||
func handleServiceNaming(mgr ServiceManager, keyword string) string {
|
||||
keyword = strings.ToLower(keyword)
|
||||
if strings.HasSuffix(keyword, ".service.socket") {
|
||||
keyword = strings.TrimSuffix(keyword, ".service.socket") + ".socket"
|
||||
}
|
||||
if mgr.Name() != "systemd" {
|
||||
keyword = strings.TrimSuffix(keyword, ".service")
|
||||
return keyword
|
||||
}
|
||||
if !strings.HasSuffix(keyword, ".service") &&
|
||||
!strings.HasSuffix(keyword, ".socket") {
|
||||
keyword += ".service"
|
||||
}
|
||||
return keyword
|
||||
}
|
||||
func validateCandidatesConcurrently(candidates []string) (string, error) {
|
||||
var (
|
||||
g errgroup.Group
|
||||
found = make(chan string, 1)
|
||||
)
|
||||
|
||||
for _, candidate := range candidates {
|
||||
cand := candidate
|
||||
g.Go(func() error {
|
||||
confirmed, _ := confirmServiceExists(cand)
|
||||
if confirmed {
|
||||
select {
|
||||
case found <- cand:
|
||||
default:
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return ErrServiceNotFound
|
||||
})
|
||||
}
|
||||
|
||||
resultErr := make(chan error, 1)
|
||||
go func() {
|
||||
defer close(found)
|
||||
resultErr <- g.Wait()
|
||||
}()
|
||||
|
||||
select {
|
||||
case name := <-found:
|
||||
return name, nil
|
||||
case <-time.After(1000 * time.Millisecond):
|
||||
return "", fmt.Errorf(ErrDiscoveryTimeout.Error(), candidates[0])
|
||||
case err := <-resultErr:
|
||||
if err != nil {
|
||||
return "", ErrServiceNotFound
|
||||
}
|
||||
return "", ErrServiceNotFound
|
||||
}
|
||||
}
|
||||
func discoverAndSelectService(keyword string) (string, error) {
|
||||
discovered, err := discoverServices(keyword)
|
||||
if err != nil {
|
||||
return "", ErrServiceNotFound
|
||||
}
|
||||
|
||||
if len(discovered) == 0 {
|
||||
return "", ErrServiceNotFound
|
||||
}
|
||||
selected, err := selectBestMatch(keyword, discovered)
|
||||
if err != nil {
|
||||
return "", ErrServiceNotFound
|
||||
}
|
||||
|
||||
confirmed, err := confirmServiceExists(selected)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("service existence check failed: %w", err)
|
||||
}
|
||||
if confirmed {
|
||||
return selected, nil
|
||||
}
|
||||
return "", ErrServiceNotFound
|
||||
}
|
||||
func selectBestMatch(keyword string, candidates []string) (string, error) {
|
||||
if len(candidates) == 0 {
|
||||
return "", ErrServiceNotFound
|
||||
}
|
||||
lowerKeyword := strings.ToLower(keyword)
|
||||
var exactMatch string
|
||||
var firstContainMatch string
|
||||
for _, name := range candidates {
|
||||
if strings.EqualFold(name, keyword) {
|
||||
exactMatch = name
|
||||
break
|
||||
}
|
||||
}
|
||||
if exactMatch != "" {
|
||||
return exactMatch, nil
|
||||
}
|
||||
for _, name := range candidates {
|
||||
if strings.Contains(strings.ToLower(name), lowerKeyword) {
|
||||
firstContainMatch = name
|
||||
global.LOG.Debugf("[%s] [keyword: %s] Found first contain match: %s", getManagerName(), keyword, firstContainMatch)
|
||||
break
|
||||
}
|
||||
}
|
||||
if firstContainMatch != "" {
|
||||
return firstContainMatch, nil
|
||||
}
|
||||
return "", fmt.Errorf("%w: %q (no exact or partial match)", ErrNoValidService, keyword)
|
||||
}
|
||||
|
||||
type cacheItem struct {
|
||||
services []string
|
||||
expires time.Time
|
||||
exists bool
|
||||
}
|
||||
|
||||
var (
|
||||
discoveryCache sync.Map
|
||||
discoveryGroup singleflight.Group
|
||||
)
|
||||
|
||||
func discoverServices(keyword string) ([]string, error) {
|
||||
result, err := discoveryGroup.Do(keyword, func() (interface{}, error) {
|
||||
if cached, ok := discoveryCache.Load(keyword); ok {
|
||||
item := cached.(cacheItem)
|
||||
if time.Now().Before(item.expires) {
|
||||
return item.services, nil
|
||||
}
|
||||
discoveryCache.Delete(keyword)
|
||||
}
|
||||
manager := GetGlobalManager()
|
||||
results, err := manager.FindServices(keyword)
|
||||
|
||||
if err != nil {
|
||||
global.LOG.Errorf("Find services failed for %s: %v", keyword, err)
|
||||
return nil, fmt.Errorf("%w: %q (%v)", ErrServiceDiscovery, keyword, err)
|
||||
} else {
|
||||
discoveryCache.Store(keyword, cacheItem{
|
||||
services: results,
|
||||
expires: time.Now().Add(5 * time.Minute),
|
||||
})
|
||||
}
|
||||
return results, err
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return result.([]string), nil
|
||||
}
|
||||
|
||||
func updateAliases(keyword, alias string) {
|
||||
if keyword == alias {
|
||||
return
|
||||
}
|
||||
existing, _ := serviceAliases.LoadOrStore(keyword, []string{})
|
||||
aliases := existing.([]string)
|
||||
if contains(aliases, alias) {
|
||||
return
|
||||
}
|
||||
serviceAliases.Store(keyword, append(aliases, alias))
|
||||
go scheduleSave()
|
||||
}
|
||||
|
||||
func scheduleSave() {
|
||||
saveMutex.Lock()
|
||||
defer saveMutex.Unlock()
|
||||
|
||||
if saveTimer != nil {
|
||||
saveTimer.Stop()
|
||||
}
|
||||
|
||||
dataSnapshot := make(map[string][]string)
|
||||
serviceAliases.Range(func(k, v interface{}) bool {
|
||||
dataSnapshot[k.(string)] = append([]string{}, v.([]string)...)
|
||||
return true
|
||||
})
|
||||
aliasFile = filepath.Join(constant.ResourceDir, "svcaliases.json")
|
||||
saveTimer = time.AfterFunc(afterSaveTime, func() {
|
||||
tmpFile := aliasFile + ".tmp"
|
||||
if err := saveAliasesToFile(dataSnapshot, tmpFile); err == nil {
|
||||
os.Rename(tmpFile, aliasFile)
|
||||
}
|
||||
})
|
||||
}
|
||||
func saveAliasesToFile(data map[string][]string, path string) error {
|
||||
fileData, err := json.MarshalIndent(data, "", " ")
|
||||
if err != nil {
|
||||
return fmt.Errorf("serialization failed: %w", err)
|
||||
}
|
||||
|
||||
if err := os.WriteFile(path, fileData, 0644); err != nil {
|
||||
return fmt.Errorf("file write failed: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func contains(slice []string, item string) bool {
|
||||
for _, s := range slice {
|
||||
if s == item {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
var serviceExistenceCache sync.Map
|
||||
|
||||
func confirmServiceExists(serviceName string) (bool, error) {
|
||||
if val, ok := serviceExistenceCache.Load(serviceName); ok {
|
||||
if item, ok := val.(cacheItem); ok && time.Now().Before(item.expires) {
|
||||
return item.exists, nil
|
||||
}
|
||||
serviceExistenceCache.Delete(serviceName)
|
||||
}
|
||||
handler := NewServiceHandler(defaultServiceConfig(serviceName))
|
||||
isExist, err := handler.IsExists()
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("check service existence failed: %w", err)
|
||||
}
|
||||
serviceExistenceCache.Store(serviceName, cacheItem{
|
||||
exists: isExist.IsExists,
|
||||
expires: time.Now().Add(30 * time.Second),
|
||||
})
|
||||
return isExist.IsExists, nil
|
||||
}
|
||||
|
||||
func getAliases(keyword string) []string {
|
||||
predefined := loadPredefinedAliases()[keyword]
|
||||
runtimeAliases, _ := serviceAliases.LoadOrStore(keyword, []string{})
|
||||
merged := make(map[string]struct{})
|
||||
for _, alias := range predefined {
|
||||
merged[alias] = struct{}{}
|
||||
}
|
||||
for _, alias := range runtimeAliases.([]string) {
|
||||
merged[alias] = struct{}{}
|
||||
}
|
||||
result := make([]string, 0, len(merged))
|
||||
for k := range merged {
|
||||
result = append(result, k)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
type ConfigOption struct {
|
||||
TailLines string
|
||||
}
|
||||
|
||||
func ViewConfig(path string, opt ConfigOption) (string, error) {
|
||||
var cmd []string
|
||||
if opt.TailLines != "" && opt.TailLines != "0" {
|
||||
cmd = []string{"tail", "-n", opt.TailLines, path}
|
||||
} else {
|
||||
cmd = []string{"cat", path}
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
output, err := executeCommand(ctx, cmd[0], cmd[1:]...)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("view config failed: %w", err)
|
||||
}
|
||||
return string(output), nil
|
||||
}
|
||||
@@ -0,0 +1,283 @@
|
||||
package systemctl
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
)
|
||||
|
||||
type ServiceConfig struct {
|
||||
ServiceName map[string]string
|
||||
}
|
||||
|
||||
type ServiceHandler struct {
|
||||
config *ServiceConfig
|
||||
manager ServiceManager
|
||||
}
|
||||
|
||||
func NewServiceHandler(serviceNames map[string]string) *ServiceHandler {
|
||||
mgr := GetGlobalManager()
|
||||
if mgr == nil {
|
||||
global.LOG.Error("failed to get global service manager when creating ServiceHandler")
|
||||
return nil
|
||||
}
|
||||
return &ServiceHandler{
|
||||
config: &ServiceConfig{
|
||||
ServiceName: serviceNames,
|
||||
},
|
||||
manager: mgr,
|
||||
}
|
||||
}
|
||||
|
||||
type ServiceStatus struct {
|
||||
IsActive bool `json:"isActive"`
|
||||
IsEnabled bool `json:"isEnabled"`
|
||||
IsExists bool `json:"isExists"`
|
||||
Output string `json:"output"`
|
||||
}
|
||||
type ServiceIsActive struct {
|
||||
IsActive bool `json:"isActive"`
|
||||
Output string `json:"output"`
|
||||
}
|
||||
|
||||
type ServiceIsEnabled struct {
|
||||
IsEnabled bool `json:"isEnabled"`
|
||||
Output string `json:"output"`
|
||||
}
|
||||
|
||||
type ServiceResult struct {
|
||||
Success bool `json:"success"`
|
||||
Message string `json:"message"`
|
||||
Output string `json:"output"`
|
||||
}
|
||||
|
||||
var (
|
||||
BinaryPath = "/usr/local/bin" // 1panl service default path
|
||||
ErrServiceNotExist = errors.New("service does not exist")
|
||||
)
|
||||
|
||||
func defaultServiceConfig(serviceName string) map[string]string {
|
||||
mgr := getManagerName()
|
||||
if mgr == "" {
|
||||
global.LOG.Error("failed to get manager name for default service config")
|
||||
return nil
|
||||
}
|
||||
return map[string]string{
|
||||
mgr: serviceName,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *ServiceHandler) ManagerName() string { return h.manager.Name() }
|
||||
func getManagerName() string {
|
||||
if mgr := GetGlobalManager(); mgr != nil {
|
||||
return mgr.Name()
|
||||
}
|
||||
global.LOG.Error("failed to get global service manager")
|
||||
return ""
|
||||
}
|
||||
|
||||
func (h *ServiceHandler) GetServiceName() string {
|
||||
manager := h.ManagerName()
|
||||
if manager == "" {
|
||||
global.LOG.Error("manager name is empty when getting service name")
|
||||
return ""
|
||||
}
|
||||
return h.config.ServiceName[manager]
|
||||
}
|
||||
|
||||
func (h *ServiceHandler) GetServicePath() (string, error) {
|
||||
manager := h.ManagerName()
|
||||
serviceName := h.config.ServiceName[manager]
|
||||
|
||||
if serviceName == "" {
|
||||
err := fmt.Errorf("service name not found for %s", manager)
|
||||
global.LOG.Errorf("GetServicePath error: %v", err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
cleanPath := filepath.Clean(serviceName)
|
||||
if strings.Contains(cleanPath, "..") {
|
||||
err := fmt.Errorf("invalid path: %q", cleanPath)
|
||||
global.LOG.Errorf("GetServicePath security check failed: %v", err)
|
||||
return "", err
|
||||
}
|
||||
switch manager {
|
||||
case "systemd":
|
||||
return findSystemdPath(cleanPath)
|
||||
case "openrc", "sysvinit":
|
||||
return checkInitDPath(cleanPath)
|
||||
default:
|
||||
err := fmt.Errorf("unsupported init system: %s", manager)
|
||||
global.LOG.Errorf("GetServicePath error: %v", err)
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
func findSystemdPath(name string) (string, error) {
|
||||
paths := []string{"/etc/systemd/system", "/usr/lib/systemd/system",
|
||||
"/usr/share/systemd/system", "/usr/local/lib/systemd/system"}
|
||||
|
||||
for _, p := range paths {
|
||||
if path := filepath.Join(p, name); FileExist(path) {
|
||||
return path, nil
|
||||
}
|
||||
}
|
||||
err := fmt.Errorf("service path not found for %s", name)
|
||||
global.LOG.Errorf("findSystemdPath error: %v", err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
func checkInitDPath(name string) (string, error) {
|
||||
path := filepath.Join("/etc/init.d", name)
|
||||
if !FileExist(path) {
|
||||
err := fmt.Errorf("service path not found for %s", name)
|
||||
global.LOG.Errorf("checkInitDPath error: %v", err)
|
||||
return "", err
|
||||
}
|
||||
return path, nil
|
||||
}
|
||||
|
||||
func (h *ServiceHandler) ExecuteAction(action string) (ServiceResult, error) {
|
||||
successMsg := fmt.Sprintf("%s : %s completed", action, h.GetServiceName())
|
||||
return h.executeAction(action, successMsg)
|
||||
}
|
||||
|
||||
func (h *ServiceHandler) IsExists() (ServiceStatus, error) {
|
||||
manager := GetGlobalManager()
|
||||
isExist, _ := manager.ServiceExists(h.config)
|
||||
return ServiceStatus{
|
||||
IsExists: isExist,
|
||||
}, nil
|
||||
}
|
||||
func (h *ServiceHandler) IsActive() (ServiceStatus, error) {
|
||||
manager := GetGlobalManager()
|
||||
if manager == nil {
|
||||
global.LOG.Error("service manager not initialized during active check")
|
||||
return ServiceStatus{}, fmt.Errorf("service manager not initialized")
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
activeCmd, err := manager.BuildCommand("status", h.config)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("Build status command failed: %v", err)
|
||||
return ServiceStatus{}, fmt.Errorf("build status command failed: %w", err)
|
||||
}
|
||||
|
||||
output, err := executeCommand(ctx, activeCmd[0], activeCmd[1:]...)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "inactive") {
|
||||
return ServiceStatus{
|
||||
IsExists: false,
|
||||
}, nil
|
||||
}
|
||||
global.LOG.Errorf("Active check execution failed: %v", err)
|
||||
return ServiceStatus{}, fmt.Errorf("status check failed: %w", err)
|
||||
}
|
||||
|
||||
isActive, err := manager.ParseStatus(string(output), h.config, "active")
|
||||
if err != nil {
|
||||
global.LOG.Warnf("Status parse error: %v", err)
|
||||
}
|
||||
return ServiceStatus{
|
||||
IsActive: isActive,
|
||||
Output: string(output),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (h *ServiceHandler) IsEnabled() (ServiceStatus, error) {
|
||||
manager := GetGlobalManager()
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
enabledCmd, err := manager.BuildCommand("is-enabled", h.config)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "not found") {
|
||||
return ServiceStatus{
|
||||
IsEnabled: false,
|
||||
}, nil
|
||||
}
|
||||
global.LOG.Errorf("Build is-enabled command failed: %v", err)
|
||||
return ServiceStatus{}, fmt.Errorf("build enabled check command failed: %w", err)
|
||||
}
|
||||
|
||||
output, err := executeCommand(ctx, enabledCmd[0], enabledCmd[1:]...)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "disabled") {
|
||||
return ServiceStatus{
|
||||
IsEnabled: false,
|
||||
}, nil
|
||||
}
|
||||
// // isEnabled, err := h.ParseStatus(string(output), h.config, "enabled")
|
||||
// global.LOG.Errorf("Enabled check execution failed: %v", err)
|
||||
// return ServiceStatus{}, fmt.Errorf("enabled check failed: %w", err)
|
||||
}
|
||||
|
||||
isEnabled, err := manager.ParseStatus(string(output), h.config, "enabled")
|
||||
if err != nil {
|
||||
global.LOG.Warnf("Enabled status parse error: %v", err)
|
||||
}
|
||||
return ServiceStatus{
|
||||
IsEnabled: isEnabled,
|
||||
Output: string(output),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (h *ServiceHandler) StartService() (ServiceResult, error) {
|
||||
return h.ExecuteAction("start")
|
||||
}
|
||||
|
||||
func (h *ServiceHandler) StopService() (ServiceResult, error) {
|
||||
return h.ExecuteAction("stop")
|
||||
}
|
||||
|
||||
func (h *ServiceHandler) RestartService() (ServiceResult, error) {
|
||||
return h.ExecuteAction("restart")
|
||||
}
|
||||
|
||||
func (h *ServiceHandler) EnableService() (ServiceResult, error) {
|
||||
return h.ExecuteAction("enable")
|
||||
}
|
||||
|
||||
func (h *ServiceHandler) DisableService() (ServiceResult, error) {
|
||||
return h.ExecuteAction("disable")
|
||||
}
|
||||
|
||||
func (h *ServiceHandler) executeAction(action, successMsg string) (ServiceResult, error) {
|
||||
manager := GetGlobalManager()
|
||||
if manager == nil {
|
||||
global.LOG.Error("service manager not initialized during action execution")
|
||||
return ServiceResult{}, fmt.Errorf("service manager not initialized")
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), defaultCommandTimeout)
|
||||
defer cancel()
|
||||
|
||||
cmdArgs, err := manager.BuildCommand(action, h.config)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("Build command failed for action %s: %v", action, err)
|
||||
return ServiceResult{}, fmt.Errorf("build command failed: %w", err)
|
||||
}
|
||||
|
||||
output, err := executeCommand(ctx, cmdArgs[0], cmdArgs[1:]...)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("%s operation failed: %v", action, err)
|
||||
return ServiceResult{
|
||||
Success: false,
|
||||
Message: fmt.Sprintf("%s failed", action),
|
||||
Output: string(output),
|
||||
}, fmt.Errorf("%s operation failed: %w", action, err)
|
||||
}
|
||||
|
||||
global.LOG.Infof("[%s]: %s", manager.Name(), successMsg)
|
||||
return ServiceResult{
|
||||
Success: true,
|
||||
Message: successMsg,
|
||||
Output: string(output),
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,440 @@
|
||||
package systemctl
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
)
|
||||
|
||||
var (
|
||||
managers = make(map[string]ServiceManager)
|
||||
mu sync.RWMutex
|
||||
globalManager ServiceManager
|
||||
managerPriority = []string{"systemd", "openrc", "sysvinit"}
|
||||
)
|
||||
|
||||
const (
|
||||
defaultCommandTimeout = 60 * time.Second
|
||||
serviceCheckTimeout = 5 * time.Second
|
||||
)
|
||||
|
||||
type ServiceManager interface {
|
||||
Name() string
|
||||
IsAvailable() bool
|
||||
ServiceExists(*ServiceConfig) (bool, error)
|
||||
BuildCommand(string, *ServiceConfig) ([]string, error)
|
||||
ParseStatus(string, *ServiceConfig, string) (bool, error)
|
||||
FindServices(string) ([]string, error)
|
||||
}
|
||||
|
||||
type baseManager struct {
|
||||
name string
|
||||
cmdTool string
|
||||
activeRegex *regexp.Regexp
|
||||
enabledRegex *regexp.Regexp
|
||||
}
|
||||
|
||||
func (b *baseManager) Name() string { return b.name }
|
||||
|
||||
func isRootUser() bool {
|
||||
return os.Geteuid() == 0
|
||||
}
|
||||
func (b *baseManager) buildBaseCommand() []string {
|
||||
var cmdArgs []string
|
||||
if !isRootUser() {
|
||||
cmdArgs = append(cmdArgs, "sudo")
|
||||
}
|
||||
cmdArgs = append(cmdArgs, b.cmdTool)
|
||||
return cmdArgs
|
||||
}
|
||||
func (b *baseManager) commonServiceExists(config *ServiceConfig, checkFn func(string) (bool, error)) (bool, error) {
|
||||
if name := config.ServiceName[b.name]; name != "" {
|
||||
exists, checkErr := checkFn(name)
|
||||
if checkErr != nil {
|
||||
return false, nil
|
||||
}
|
||||
return exists, nil
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
func (b *baseManager) ParseStatus(output string, _ *ServiceConfig, statusType string) (bool, error) {
|
||||
if output == "" {
|
||||
return false, nil
|
||||
}
|
||||
switch statusType {
|
||||
case "active":
|
||||
if b.activeRegex == nil {
|
||||
return false, nil
|
||||
}
|
||||
return b.activeRegex.MatchString(output), nil
|
||||
case "enabled":
|
||||
if b.enabledRegex == nil {
|
||||
return false, nil
|
||||
}
|
||||
return b.enabledRegex.MatchString(output), nil
|
||||
default:
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
func registerManager(m ServiceManager) {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
managers[m.Name()] = m
|
||||
}
|
||||
func init() {
|
||||
for _, mgr := range []ServiceManager{
|
||||
newSystemdManager(),
|
||||
newOpenrcManager(),
|
||||
newSysvinitManager(),
|
||||
} {
|
||||
registerManager(mgr)
|
||||
}
|
||||
}
|
||||
func InitializeGlobalManager() (err error) {
|
||||
|
||||
for _, name := range managerPriority {
|
||||
if mgr, ok := managers[name]; ok && mgr.IsAvailable() {
|
||||
if testManager(mgr) {
|
||||
globalManager = mgr
|
||||
global.LOG.Infof("Initialized service manager: %s", name)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
err = fmt.Errorf("no available service manager found (tried: %v)", managerPriority)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func testManager(mgr ServiceManager) bool {
|
||||
_, err := mgr.BuildCommand("status", &ServiceConfig{
|
||||
ServiceName: map[string]string{mgr.Name(): "test-service"},
|
||||
})
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func GetGlobalManager() ServiceManager {
|
||||
if globalManager == nil {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
if globalManager == nil {
|
||||
return initializeWithRetry()
|
||||
}
|
||||
}
|
||||
return globalManager
|
||||
}
|
||||
func initializeWithRetry() ServiceManager {
|
||||
const (
|
||||
maxRetries = 5
|
||||
initialWait = 1 * time.Second
|
||||
)
|
||||
backoff := initialWait
|
||||
for attempt := 1; attempt <= maxRetries; attempt++ {
|
||||
if err := InitializeGlobalManager(); err == nil {
|
||||
return globalManager
|
||||
}
|
||||
|
||||
logMessage := fmt.Sprintf("Manager init attempt %d/%d failed", attempt, maxRetries)
|
||||
if global.LOG != nil {
|
||||
global.LOG.Warn(logMessage)
|
||||
} else {
|
||||
fmt.Printf("[WARN] %s\n", logMessage)
|
||||
}
|
||||
|
||||
if attempt < maxRetries {
|
||||
time.Sleep(backoff)
|
||||
backoff *= 2
|
||||
}
|
||||
}
|
||||
|
||||
if global.LOG != nil {
|
||||
global.LOG.Error("All manager initialization attempts failed")
|
||||
} else {
|
||||
fmt.Println("[FATAL] All manager initialization attempts failed")
|
||||
}
|
||||
panic("unable to initialize service manager")
|
||||
}
|
||||
func executeCommand(ctx context.Context, command string, args ...string) ([]byte, error) {
|
||||
if _, ok := ctx.Deadline(); !ok {
|
||||
var cancel context.CancelFunc
|
||||
ctx, cancel = context.WithTimeout(ctx, defaultCommandTimeout)
|
||||
defer cancel()
|
||||
}
|
||||
|
||||
cmd := exec.CommandContext(ctx, command, args...)
|
||||
var buf bytes.Buffer
|
||||
cmd.Stdout = &buf
|
||||
cmd.Stderr = &buf
|
||||
|
||||
if err := cmd.Run(); err != nil {
|
||||
return buf.Bytes(), &CommandError{
|
||||
Cmd: cmd.String(),
|
||||
Output: buf.String(),
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
type systemdManager struct{ baseManager }
|
||||
|
||||
func newSystemdManager() ServiceManager {
|
||||
return &systemdManager{baseManager{
|
||||
name: "systemd",
|
||||
cmdTool: "systemctl",
|
||||
activeRegex: regexp.MustCompile(`(?i)Active:\s+active\b`),
|
||||
enabledRegex: regexp.MustCompile(`(?i)^\s*enabled\s*$`),
|
||||
}}
|
||||
}
|
||||
|
||||
func (m *systemdManager) IsAvailable() bool {
|
||||
_, err := exec.LookPath(m.cmdTool)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func (m *systemdManager) ServiceExists(config *ServiceConfig) (bool, error) {
|
||||
return m.commonServiceExists(config, func(name string) (bool, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), serviceCheckTimeout)
|
||||
defer cancel()
|
||||
out, cmdErr := executeCommand(ctx, m.cmdTool, "is-enabled", name)
|
||||
if cmdErr == nil || strings.Contains(string(out), "disabled") {
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
})
|
||||
}
|
||||
|
||||
func (m *systemdManager) BuildCommand(action string, config *ServiceConfig) ([]string, error) {
|
||||
cmdArgs := m.buildBaseCommand()
|
||||
service := config.ServiceName[m.name]
|
||||
switch action {
|
||||
case "is-enabled":
|
||||
cmdArgs = append(cmdArgs, "is-enabled", service)
|
||||
default:
|
||||
cmdArgs = append(cmdArgs, action, service)
|
||||
}
|
||||
return cmdArgs, nil
|
||||
}
|
||||
|
||||
func (m *systemdManager) ParseStatus(output string, config *ServiceConfig, statusType string) (bool, error) {
|
||||
if strings.Contains(output, "could not be found") {
|
||||
return false, nil
|
||||
}
|
||||
result, err := m.baseManager.ParseStatus(output, config, statusType)
|
||||
if err != nil {
|
||||
return false, nil
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
func (m *systemdManager) FindServices(keyword string) ([]string, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
out, err := executeCommand(ctx, m.cmdTool, "list-unit-files", "--type=service", "--no-legend")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to list systemd services: %w", err)
|
||||
}
|
||||
|
||||
var services []string
|
||||
lines := strings.Split(string(out), "\n")
|
||||
for _, line := range lines {
|
||||
fields := strings.Fields(line)
|
||||
if len(fields) < 1 {
|
||||
continue
|
||||
}
|
||||
serviceName := fields[0]
|
||||
if strings.Contains(serviceName, keyword) {
|
||||
services = append(services, serviceName)
|
||||
}
|
||||
}
|
||||
return services, nil
|
||||
}
|
||||
|
||||
type sysvinitManager struct{ baseManager }
|
||||
|
||||
func newSysvinitManager() ServiceManager {
|
||||
return &sysvinitManager{baseManager{
|
||||
name: "sysvinit",
|
||||
cmdTool: "service",
|
||||
activeRegex: regexp.MustCompile(`(?i)(?:^|\s)\b(running|active)\b(?:$|\s)`),
|
||||
enabledRegex: regexp.MustCompile(`(?i)(?:^|\s)\b(enabled)\b(?:$|\s)`),
|
||||
}}
|
||||
}
|
||||
|
||||
func (m *sysvinitManager) IsAvailable() bool {
|
||||
_, err := exec.LookPath(m.cmdTool)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func (m *sysvinitManager) ServiceExists(config *ServiceConfig) (bool, error) {
|
||||
return m.commonServiceExists(config, func(name string) (bool, error) {
|
||||
_, err := os.Stat(filepath.Join("/etc/init.d", name))
|
||||
if os.IsNotExist(err) {
|
||||
return false, nil
|
||||
} else if err != nil {
|
||||
return false, fmt.Errorf("stat /etc/init.d/%s failed: %w", name, err)
|
||||
}
|
||||
return true, nil
|
||||
})
|
||||
}
|
||||
|
||||
func (m *sysvinitManager) BuildCommand(action string, config *ServiceConfig) ([]string, error) {
|
||||
service := config.ServiceName[m.name]
|
||||
switch action {
|
||||
case "is-enabled":
|
||||
return []string{
|
||||
"sh",
|
||||
"-c",
|
||||
fmt.Sprintf("if ls /etc/rc*.d/S*%s >/dev/null 2>&1; then echo 'enabled'; else echo 'disabled'; fi", service)}, nil
|
||||
case "is-active", "status":
|
||||
return []string{
|
||||
"sh",
|
||||
"-c",
|
||||
fmt.Sprintf("if service %s status >/dev/null 2>&1; then echo 'active'; else echo 'inactive'; fi", service),
|
||||
}, nil
|
||||
default:
|
||||
cmdArgs := m.buildBaseCommand()
|
||||
cmdArgs = append(cmdArgs, service, action)
|
||||
return cmdArgs, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (m *sysvinitManager) ParseStatus(output string, config *ServiceConfig, statusType string) (bool, error) {
|
||||
serviceName := config.ServiceName[m.name]
|
||||
switch statusType {
|
||||
case "enabled":
|
||||
if strings.Contains(output, "no such file or directory") {
|
||||
return false, nil
|
||||
}
|
||||
return strings.TrimSpace(output) != "", nil
|
||||
case "active":
|
||||
if strings.Contains(output, "not found") {
|
||||
return false, nil
|
||||
}
|
||||
if strings.Contains(output, "running") || strings.Contains(output, "active") {
|
||||
return true, nil
|
||||
}
|
||||
default:
|
||||
result, err := m.baseManager.ParseStatus(output, config, statusType)
|
||||
if err != nil {
|
||||
global.LOG.Debugf("[sysvinit] Status parse failed. [ServiceName:%s] Type: %s, Output: %q", serviceName, statusType, output)
|
||||
}
|
||||
return result, err
|
||||
}
|
||||
return false, fmt.Errorf("unsupported status type: %s", statusType)
|
||||
}
|
||||
|
||||
func (m *sysvinitManager) FindServices(keyword string) ([]string, error) {
|
||||
files, err := os.ReadDir("/etc/init.d/")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read init.d directory: %w", err)
|
||||
}
|
||||
|
||||
var services []string
|
||||
for _, file := range files {
|
||||
if strings.Contains(file.Name(), keyword) {
|
||||
services = append(services, file.Name())
|
||||
}
|
||||
}
|
||||
return services, nil
|
||||
}
|
||||
|
||||
type openrcManager struct{ baseManager }
|
||||
|
||||
func newOpenrcManager() ServiceManager {
|
||||
return &openrcManager{baseManager{
|
||||
name: "openrc",
|
||||
cmdTool: "rc-service",
|
||||
// activeRegex: regexp.MustCompile(`(?i)^\s*status:\s+(started|running|active)\s*$`),
|
||||
// enabledRegex: regexp.MustCompile(`(?i)^[^\|]+\|\s*(default|enabled)\b.*$`),
|
||||
activeRegex: regexp.MustCompile(`(?i)(?:^|\s)\b(running|active)\b(?:$|\s)`),
|
||||
enabledRegex: regexp.MustCompile(`(?i)(?:^|\s)\b(enabled)\b(?:$|\s)`),
|
||||
}}
|
||||
}
|
||||
func (m *openrcManager) IsAvailable() bool {
|
||||
_, err := exec.LookPath(m.cmdTool)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func (m *openrcManager) ServiceExists(config *ServiceConfig) (bool, error) {
|
||||
return m.commonServiceExists(config, func(name string) (bool, error) {
|
||||
_, err := os.Stat(filepath.Join("/etc/init.d", name))
|
||||
if os.IsNotExist(err) {
|
||||
return false, nil
|
||||
} else if err != nil {
|
||||
return false, fmt.Errorf("stat /etc/init.d/%s failed: %w", name, err)
|
||||
}
|
||||
return true, nil
|
||||
})
|
||||
}
|
||||
|
||||
func (m *openrcManager) BuildCommand(action string, config *ServiceConfig) ([]string, error) {
|
||||
cmdArgs := m.buildBaseCommand()
|
||||
service := config.ServiceName[m.name]
|
||||
switch action {
|
||||
case "is-enabled":
|
||||
return []string{
|
||||
"sh",
|
||||
"-c",
|
||||
fmt.Sprintf("if ls /etc/runlevels/default/%s >/dev/null 2>&1; then echo 'enabled'; else echo 'disabled'; fi", service)}, nil
|
||||
case "is-active", "status":
|
||||
return []string{
|
||||
"sh",
|
||||
"-c",
|
||||
fmt.Sprintf("if service %s status >/dev/null 2>&1; then echo 'active'; else echo 'inactive'; fi", service),
|
||||
}, nil
|
||||
case "enable":
|
||||
return []string{"rc-update", "add", service, "default"}, nil
|
||||
case "disable":
|
||||
return []string{"rc-update", "del", service, "default"}, nil
|
||||
default:
|
||||
cmdArgs = append(cmdArgs, service, action)
|
||||
return cmdArgs, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (m *openrcManager) ParseStatus(output string, config *ServiceConfig, statusType string) (bool, error) {
|
||||
if strings.Contains(output, "does not exist") {
|
||||
return false, nil
|
||||
}
|
||||
result, err := m.baseManager.ParseStatus(output, config, statusType)
|
||||
if err != nil {
|
||||
return false, nil
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
func (m *openrcManager) FindServices(keyword string) ([]string, error) {
|
||||
files, err := os.ReadDir("/etc/init.d/")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read init.d directory: %w", err)
|
||||
}
|
||||
keyword = strings.ToLower(keyword)
|
||||
var services []string
|
||||
for _, file := range files {
|
||||
if strings.Contains(file.Name(), keyword) {
|
||||
services = append(services, file.Name())
|
||||
}
|
||||
}
|
||||
return services, nil
|
||||
}
|
||||
|
||||
type CommandError struct {
|
||||
Cmd string
|
||||
Err error
|
||||
Output string
|
||||
}
|
||||
|
||||
func (e CommandError) Error() string {
|
||||
return fmt.Sprintf("command %q failed: %v \nOutput: %s",
|
||||
e.Cmd, e.Err, e.Output)
|
||||
}
|
||||
@@ -2,63 +2,153 @@ package systemctl
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/pkg/errors"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"os"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
)
|
||||
|
||||
func RunSystemCtl(args ...string) (string, error) {
|
||||
cmd := exec.Command("systemctl", args...)
|
||||
output, err := cmd.CombinedOutput()
|
||||
func DefaultHandler(serviceName string) (*ServiceHandler, error) {
|
||||
svcName, err := smartServiceName(serviceName)
|
||||
if err != nil {
|
||||
return string(output), fmt.Errorf("failed to run command: %w", err)
|
||||
return nil, ErrServiceNotFound
|
||||
}
|
||||
return string(output), nil
|
||||
return NewServiceHandler(defaultServiceConfig(svcName)), nil
|
||||
}
|
||||
|
||||
func IsActive(serviceName string) (bool, error) {
|
||||
out, err := RunSystemCtl("is-active", serviceName)
|
||||
func GetServiceName(serviceName string) (string, error) {
|
||||
serviceName, err := smartServiceName(serviceName)
|
||||
if err != nil {
|
||||
return false, err
|
||||
return "", ErrServiceNotFound
|
||||
}
|
||||
return out == "active\n", nil
|
||||
return serviceName, nil
|
||||
}
|
||||
|
||||
func IsEnable(serviceName string) (bool, error) {
|
||||
out, err := RunSystemCtl("is-enabled", serviceName)
|
||||
func GetServicePath(serviceName string) (string, error) {
|
||||
handler, err := DefaultHandler(serviceName)
|
||||
if err != nil {
|
||||
return false, err
|
||||
return "", ErrServiceNotFound
|
||||
}
|
||||
return out == "enabled\n", nil
|
||||
return handler.GetServicePath()
|
||||
}
|
||||
|
||||
func CustomAction(action string, serviceName string) (ServiceResult, error) {
|
||||
handler, err := DefaultHandler(serviceName)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("CustomAction handler init failed: %v", err)
|
||||
return ServiceResult{}, ErrServiceNotFound
|
||||
}
|
||||
result, err := handler.ExecuteAction(action)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("CustomAction %s failed: %v", action, err)
|
||||
return result, fmt.Errorf("%s operation failed: %w | Output: %s", action, err, result.Output)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func IsExist(serviceName string) (bool, error) {
|
||||
out, err := RunSystemCtl("is-enabled", serviceName)
|
||||
handler, err := DefaultHandler(serviceName)
|
||||
if err != nil {
|
||||
if strings.Contains(out, "disabled") {
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
return true, nil
|
||||
result, _ := handler.IsExists()
|
||||
if result.IsExists {
|
||||
return true, nil
|
||||
} else {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
func handlerErr(out string, err error) error {
|
||||
func Start(serviceName string) error {
|
||||
handler, _ := DefaultHandler(serviceName)
|
||||
result, err := handler.StartService()
|
||||
if err != nil {
|
||||
if out != "" {
|
||||
return errors.New(out)
|
||||
}
|
||||
return err
|
||||
global.LOG.Errorf("Service start failed: %v | Output: %s", err, result.Output)
|
||||
return fmt.Errorf("start failed: %v | Output: %s", err, result.Output)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func Stop(serviceName string) error {
|
||||
handler, err := DefaultHandler(serviceName)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("Stop handler init failed: %v", err)
|
||||
return fmt.Errorf("%s is not exist", serviceName)
|
||||
}
|
||||
result, err := handler.StopService()
|
||||
if err != nil {
|
||||
global.LOG.Errorf("Service stop failed: %v", err)
|
||||
return fmt.Errorf("stop failed: %v | Output: %s", err, result.Output)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func Restart(serviceName string) error {
|
||||
out, err := RunSystemCtl("restart", serviceName)
|
||||
return handlerErr(out, err)
|
||||
handler, err := DefaultHandler(serviceName)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("Restart handler init failed: %v", err)
|
||||
return fmt.Errorf("%s is not exist", serviceName)
|
||||
}
|
||||
result, err := handler.RestartService()
|
||||
if err != nil {
|
||||
global.LOG.Errorf("Service restart failed: %v", err)
|
||||
return fmt.Errorf("restart failed: %v | Output: %s", err, result.Output)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func Operate(operate, serviceName string) error {
|
||||
out, err := RunSystemCtl(operate, serviceName)
|
||||
return handlerErr(out, err)
|
||||
func Enable(serviceName string) error {
|
||||
handler, err := DefaultHandler(serviceName)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("Enable handler init failed: %v", err)
|
||||
return fmt.Errorf("%s is not exist", serviceName)
|
||||
}
|
||||
result, err := handler.EnableService()
|
||||
if err != nil {
|
||||
global.LOG.Errorf("Service enable failed: %v | Output: %s", err, result.Output)
|
||||
return fmt.Errorf("%s enable failed: %v ", serviceName, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func Disable(serviceName string) error {
|
||||
handler, _ := DefaultHandler(serviceName)
|
||||
result, err := handler.DisableService()
|
||||
if err != nil {
|
||||
global.LOG.Errorf("Service disable failed: %v", err)
|
||||
return fmt.Errorf("disable failed: %v | Output: %s", err, result.Output)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func IsActive(serviceName string) (bool, error) {
|
||||
handler, err := DefaultHandler(serviceName)
|
||||
if err != nil {
|
||||
return false, nil
|
||||
}
|
||||
status, err := handler.IsActive()
|
||||
if err != nil {
|
||||
return false, nil
|
||||
}
|
||||
return status.IsActive, nil
|
||||
}
|
||||
|
||||
func IsEnable(serviceName string) (bool, error) {
|
||||
handler, err := DefaultHandler(serviceName)
|
||||
if err != nil {
|
||||
return false, nil
|
||||
}
|
||||
status, err := handler.IsEnabled()
|
||||
if err != nil {
|
||||
return false, nil
|
||||
}
|
||||
return status.IsEnabled, nil
|
||||
}
|
||||
|
||||
type LogOption struct {
|
||||
TailLines string
|
||||
}
|
||||
|
||||
func FileExist(path string) bool {
|
||||
_, err := os.Stat(path)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package toolbox
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
@@ -22,15 +23,15 @@ type FirewallClient interface {
|
||||
}
|
||||
|
||||
func NewFail2Ban() (*Fail2ban, error) {
|
||||
isExist, _ := systemctl.IsExist("fail2ban.service")
|
||||
isExist, _ := systemctl.IsExist("fail2ban")
|
||||
if isExist {
|
||||
if _, err := os.Stat(defaultPath); err != nil {
|
||||
if err := initLocalFile(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
stdout, err := cmd.Exec("systemctl restart fail2ban.service")
|
||||
err := systemctl.Restart("fail2ban")
|
||||
if err != nil {
|
||||
global.LOG.Errorf("restart fail2ban failed, err: %s", stdout)
|
||||
global.LOG.Errorf("restart fail2ban failed, err: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
@@ -47,20 +48,25 @@ func (f *Fail2ban) Status() (bool, bool, bool) {
|
||||
}
|
||||
|
||||
func (f *Fail2ban) Version() string {
|
||||
stdout, err := cmd.Exec("fail2ban-client version")
|
||||
stdout, err := cmd.Exec("fail2ban-client --version")
|
||||
if err != nil {
|
||||
global.LOG.Errorf("load the fail2ban version failed, err: %s", stdout)
|
||||
return "-"
|
||||
}
|
||||
return strings.ReplaceAll(stdout, "\n", "")
|
||||
versionRe := regexp.MustCompile(`(?i)fail2ban[:\s-]*v?(\d+\.\d+\.\d+)`)
|
||||
matches := versionRe.FindStringSubmatch(stdout)
|
||||
if len(matches) > 1 {
|
||||
return matches[1]
|
||||
}
|
||||
global.LOG.Errorf("Version regex failed to match output: %s", stdout)
|
||||
return "-"
|
||||
}
|
||||
|
||||
func (f *Fail2ban) Operate(operate string) error {
|
||||
switch operate {
|
||||
case "start", "restart", "stop", "enable", "disable":
|
||||
stdout, err := cmd.Execf("systemctl %s fail2ban.service", operate)
|
||||
stdout, err := systemctl.CustomAction(operate, "fail2ban")
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s the fail2ban.service failed, err: %s", operate, stdout)
|
||||
return fmt.Errorf("%s the fail2ban failed, err: %s", operate, stdout.Output)
|
||||
}
|
||||
return nil
|
||||
case "reload":
|
||||
@@ -136,7 +142,32 @@ func initLocalFile() error {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
initFile := `#DEFAULT-START
|
||||
var initFile string
|
||||
if systemctl.GetGlobalManager().Name() == "openrc" {
|
||||
initFile = `[sshd]
|
||||
enabled = true
|
||||
filter = alpine-sshd
|
||||
port = $ssh_port
|
||||
logpath = $logpath
|
||||
maxretry = 2
|
||||
banaction = $banaction
|
||||
|
||||
[sshd-ddos]
|
||||
enabled = true
|
||||
filter = alpine-sshd-ddos
|
||||
port = $ssh_port
|
||||
logpath = /var/log/messages
|
||||
maxretry = 2
|
||||
|
||||
[sshd-key]
|
||||
enabled = true
|
||||
filter = alpine-sshd-key
|
||||
port = $ssh_port
|
||||
logpath = /var/log/messages
|
||||
maxretry = 2
|
||||
`
|
||||
} else {
|
||||
initFile = `# DEFAULT-START
|
||||
[DEFAULT]
|
||||
bantime = 600
|
||||
findtime = 300
|
||||
@@ -146,36 +177,93 @@ action = %(action_mwl)s
|
||||
#DEFAULT-END
|
||||
|
||||
[sshd]
|
||||
ignoreip = 127.0.0.1/8
|
||||
ignoreip = 127.0.0.1/8 ::1
|
||||
enabled = true
|
||||
filter = sshd
|
||||
port = 22
|
||||
port = $ssh_port
|
||||
maxretry = 5
|
||||
findtime = 300
|
||||
bantime = 600
|
||||
banaction = $banaction
|
||||
action = %(action_mwl)s
|
||||
logpath = $logpath`
|
||||
|
||||
banaction := ""
|
||||
if active, _ := systemctl.IsActive("firewalld"); active {
|
||||
banaction = "firewallcmd-ipset"
|
||||
} else if active, _ := systemctl.IsActive("ufw"); active {
|
||||
banaction = "ufw"
|
||||
} else {
|
||||
banaction = "iptables-allports"
|
||||
logpath = $logpath
|
||||
maxmatches = 3
|
||||
`
|
||||
}
|
||||
// 检测防火墙类型
|
||||
banaction := detectFirewall()
|
||||
|
||||
// 检测SSH端口(支持Alpine的sshd_config位置)
|
||||
sshPort := detectSSHPort()
|
||||
|
||||
// 检测日志路径(兼容Alpine的多种日志位置)
|
||||
logPath := detectAuthLogPath()
|
||||
|
||||
// 执行变量替换
|
||||
initFile = strings.ReplaceAll(initFile, "$banaction", banaction)
|
||||
|
||||
logPath := ""
|
||||
if _, err := os.Stat("/var/log/secure"); err == nil {
|
||||
logPath = "/var/log/secure"
|
||||
} else {
|
||||
logPath = "/var/log/auth.log"
|
||||
}
|
||||
initFile = strings.ReplaceAll(initFile, "$logpath", logPath)
|
||||
initFile = strings.ReplaceAll(initFile, "$ssh_port", sshPort)
|
||||
|
||||
if err := os.WriteFile(defaultPath, []byte(initFile), 0640); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func detectFirewall() string {
|
||||
if active, _ := systemctl.IsActive("firewalld"); active {
|
||||
return "firewallcmd-ipset"
|
||||
}
|
||||
if active, _ := systemctl.IsActive("ufw"); active {
|
||||
return "ufw"
|
||||
}
|
||||
if active, _ := systemctl.IsActive("iptables"); active {
|
||||
return "iptables-allports"
|
||||
}
|
||||
if active, _ := systemctl.IsActive("nftables"); active {
|
||||
return "nftables-allports"
|
||||
}
|
||||
return "iptables-allports"
|
||||
}
|
||||
|
||||
func detectAuthLogPath() string {
|
||||
paths := []string{
|
||||
"/var/log/auth.log", // 常见默认路径
|
||||
"/var/log/messages", // Alpine系统日志
|
||||
"/var/log/secure", // RHEL风格路径
|
||||
"/var/log/syslog", // Debian风格路径
|
||||
}
|
||||
|
||||
for _, path := range paths {
|
||||
if _, err := os.Stat(path); err == nil {
|
||||
return path
|
||||
}
|
||||
}
|
||||
return "/var/log/auth.log"
|
||||
}
|
||||
|
||||
func detectSSHPort() string {
|
||||
// 检查标准sshd_config路径
|
||||
configPaths := []string{
|
||||
"/etc/ssh/sshd_config",
|
||||
"/etc/sshd_config",
|
||||
}
|
||||
|
||||
for _, path := range configPaths {
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
lines := strings.Split(string(data), "\n")
|
||||
for _, line := range lines {
|
||||
if strings.HasPrefix(strings.TrimSpace(line), "Port") {
|
||||
parts := strings.Fields(line)
|
||||
if len(parts) >= 2 {
|
||||
return parts[1]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return "22" // 默认SSH端口
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"fmt"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"os"
|
||||
"os/user"
|
||||
"path"
|
||||
@@ -12,6 +11,8 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
|
||||
@@ -94,9 +95,9 @@ func (f *Ftp) Status() (bool, bool) {
|
||||
func (f *Ftp) Operate(operate string) error {
|
||||
switch operate {
|
||||
case "start", "restart", "stop":
|
||||
stdout, err := cmd.Execf("systemctl %s pure-ftpd.service", operate)
|
||||
stdout, err := systemctl.CustomAction(operate, "pure-ftpd")
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s the pure-ftpd.service failed, err: %s", operate, stdout)
|
||||
return fmt.Errorf("%s the pure-ftpd service failed, err: %s", operate, stdout.Output)
|
||||
}
|
||||
return nil
|
||||
default:
|
||||
|
||||
+17
-10
@@ -9,22 +9,29 @@ if [ ! -f "1pctl" ]; then
|
||||
wget https://github.com/1Panel-dev/installer/raw/main/1pctl
|
||||
fi
|
||||
|
||||
if [ ! -f "1panel.service" ]; then
|
||||
wget https://github.com/1Panel-dev/installer/raw/main/1panel.service
|
||||
fi
|
||||
|
||||
if [ ! -f "install.sh" ]; then
|
||||
wget https://github.com/1Panel-dev/installer/raw/main/install.sh
|
||||
fi
|
||||
|
||||
if [ ! -f "GeoIP.mmdb" ]; then
|
||||
wget https://resource.1panel.pro/geo/GeoIP.mmdb
|
||||
if [ ! -d "initscript" ]; then
|
||||
wget https://github.com/1Panel-dev/installer/raw/main/initscript/1panel.service
|
||||
mkdir -p initscript && cd initscript
|
||||
for file in 1panel.service 1paneld.init 1paneld.openrc 1paneld.procd; do
|
||||
wget -q https://github.com/1Panel-dev/installer/raw/main/initscript/$file
|
||||
done
|
||||
cd ..
|
||||
fi
|
||||
|
||||
if [ ! -f "lang.tar.gz" ]; then
|
||||
wget https://resource.1panel.pro/language/lang.tar.gz
|
||||
tar zxvf lang.tar.gz
|
||||
rm -rf lang.tar.gz
|
||||
if [ ! -d "lang" ]; then
|
||||
mkdir -p lang && cd lang
|
||||
for lang in en fa pt-BR ru zh; do
|
||||
wget -q https://github.com/1Panel-dev/installer/raw/main/lang/$lang.sh
|
||||
done
|
||||
cd ..
|
||||
fi
|
||||
|
||||
if [ ! -f "GeoIP.mmdb" ]; then
|
||||
wget https://resource.fit2cloud.com/1panel/package/geo/GeoIP.mmdb
|
||||
fi
|
||||
|
||||
chmod 755 1pctl install.sh
|
||||
|
||||
@@ -58,8 +58,15 @@ var restoreCmd = &cobra.Command{
|
||||
geoPath := path.Join(global.CONF.System.BaseDir, "1panel/geo")
|
||||
_, _ = cmdUtils.Execf("mkdir %s && cp %s %s/", geoPath, path.Join(tmpPath, "GeoIP.mmdb"), geoPath)
|
||||
fmt.Println(i18n.GetMsgByKeyForCmd("RestoreStep3"))
|
||||
if err := common.CopyFile(path.Join(tmpPath, "1panel.service"), "/etc/systemd/system"); err != nil {
|
||||
return err
|
||||
if isSystemd() {
|
||||
if err := common.CopyFile(path.Join(tmpPath, "1panel.service"), "/etc/systemd/system"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
} else {
|
||||
if err := common.CopyFile(path.Join(tmpPath, "1paneld"), "/etc/init.d"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
fmt.Println(i18n.GetMsgByKeyForCmd("RestoreStep4"))
|
||||
checkPointOfWal()
|
||||
@@ -124,3 +131,10 @@ func handleUnTar(sourceFile, targetDir string) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func isSystemd() bool {
|
||||
if _, err := os.Stat("/etc/systemd/system"); err == nil {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ location ^~ /test {
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection $http_connection;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header X-Forwarded-Port $server_port;
|
||||
proxy_http_version 1.1;
|
||||
add_header X-Cache $upstream_cache_status;
|
||||
add_header Cache-Control no-cache;
|
||||
|
||||
@@ -28,3 +28,4 @@ stats.html
|
||||
*.sln
|
||||
*.sw?
|
||||
.vscode
|
||||
*.code-workspace
|
||||
@@ -95,6 +95,10 @@ export const Languages = [
|
||||
label: 'css',
|
||||
value: ['css'],
|
||||
},
|
||||
{
|
||||
label: 'ini',
|
||||
value: ['ini'],
|
||||
},
|
||||
];
|
||||
|
||||
export const Rewrites = [
|
||||
|
||||
@@ -833,6 +833,7 @@ const message = {
|
||||
|
||||
repo: 'Registries',
|
||||
createRepo: 'Add',
|
||||
createRepoHelper: 'Adding an HTTP-type repository requires restarting the Docker service.',
|
||||
httpRepo: 'Choosing HTTP protocol requires restarting the Docker service to add it into insecure registries.',
|
||||
delInsecure: 'Deletion of credit',
|
||||
delInsecureHelper:
|
||||
@@ -1384,7 +1385,8 @@ const message = {
|
||||
existFileTitle: 'Same name file prompt',
|
||||
existFileHelper: 'The uploaded file contains a file with the same name, do you want to overwrite it?',
|
||||
existFileSize: 'File size (new -> old)',
|
||||
existFileDirHelper: 'The selected file/folder has a duplicate name. Please proceed with caution!',
|
||||
existFileDirHelper: 'The selected file/folder has a duplicate name. Please proceed with caution! \n',
|
||||
coverDirHelper: 'The selected folders to be replaced will be copied to the destination path!',
|
||||
noSuchFile: 'The file or directory was not found. Please check and try again.',
|
||||
},
|
||||
ssh: {
|
||||
@@ -2621,7 +2623,7 @@ const message = {
|
||||
domain: 'Default Access Address',
|
||||
domainHelper: 'For example: 192.168.1.1 or example.com',
|
||||
bindDomain: 'Bind Website',
|
||||
commandPlaceHolder: 'Currently only supports npx startup commands',
|
||||
commandPlaceHolder: 'Currently only supports npx and binary startup commands',
|
||||
importMcpJson: 'Import MCP Server Configuration',
|
||||
importMcpJsonError: 'mcpServers structure is incorrect',
|
||||
bindDomainHelper:
|
||||
|
||||
@@ -829,6 +829,7 @@ const message = {
|
||||
|
||||
repo: 'レジストリ',
|
||||
createRepo: '追加',
|
||||
createRepoHelper: 'HTTPタイプのリポジトリを追加するにはDockerサービスの再起動が必要です。',
|
||||
httpRepo:
|
||||
'HTTPプロトコルを選択するには、Dockerサービスを再起動して不安定なレジストリに追加する必要があります。',
|
||||
delInsecure: 'クレジットの削除',
|
||||
@@ -1363,6 +1364,7 @@ const message = {
|
||||
existFileHelper: 'アップロードしたファイルに同じ名前のファイルが含まれています。上書きしますか?',
|
||||
existFileSize: 'ファイルサイズ(新しい -> 古い)',
|
||||
existFileDirHelper: '選択したファイル/フォルダーには同じ名前のものが既に存在します。慎重に操作してください!',
|
||||
coverDirHelper: '上書きするフォルダを選択すると、対象パスにコピーされます!',
|
||||
noSuchFile: 'ファイルまたはディレクトリが見つかりませんでした。確認して再試行してください。',
|
||||
},
|
||||
ssh: {
|
||||
@@ -2591,7 +2593,7 @@ const message = {
|
||||
domain: 'デフォルトアクセスアドレス',
|
||||
domainHelper: '例: 192.168.1.1 または example.com',
|
||||
bindDomain: 'ウェブサイトをバインド',
|
||||
commandPlaceHolder: '現在、npx スタートアップコマンドのみをサポートしています',
|
||||
commandPlaceHolder: '現在、npx およびバイナリスタートアップコマンドのみをサポートしています',
|
||||
importMcpJson: 'MCP サーバー設定をインポート',
|
||||
importMcpJsonError: 'mcpServers 構造が正しくありません',
|
||||
bindDomainHelper:
|
||||
|
||||
@@ -819,6 +819,7 @@ const message = {
|
||||
|
||||
repo: '레지스트리',
|
||||
createRepo: '추가',
|
||||
createRepoHelper: 'HTTP 타입 저장소 추가 시 Docker 서비스 재시작이 필요합니다.',
|
||||
httpRepo: 'HTTP 프로토콜을 선택하면 Docker 서비스를 재시작하여 불안정한 레지스트리에 추가해야 합니다.',
|
||||
delInsecure: '신뢰할 수 없는 항목 삭제',
|
||||
delInsecureHelper:
|
||||
@@ -1349,6 +1350,7 @@ const message = {
|
||||
existFileHelper: '업로드한 파일에 동일한 이름의 파일이 포함되어 있습니다. 덮어쓰시겠습니까?',
|
||||
existFileSize: '파일 크기 (새로운 -> 오래된)',
|
||||
existFileDirHelper: '선택한 파일/폴더에 동일한 이름이 이미 존재합니다. 신중하게 작업하세요!',
|
||||
coverDirHelper: '덮어쓸 폴더를 선택하면 대상 경로로 복사됩니다!',
|
||||
noSuchFile: '파일 또는 디렉터리를 찾을 수 없습니다. 확인 후 다시 시도하세요.',
|
||||
},
|
||||
ssh: {
|
||||
@@ -2550,7 +2552,7 @@ const message = {
|
||||
domain: 'デフォルトアクセスアドレス',
|
||||
domainHelper: '例: 192.168.1.1 または example.com',
|
||||
bindDomain: 'ウェブサイトをバインド',
|
||||
commandPlaceHolder: '현재 npx 시작 명령만 지원합니다',
|
||||
commandPlaceHolder: '현재 npx 및 바이너리 시작 명령만 지원합니다',
|
||||
importMcpJson: 'MCP サーバー設定をインポート',
|
||||
importMcpJsonError: 'mcpServers 構造が正しくありません',
|
||||
bindDomainHelper:
|
||||
|
||||
@@ -845,6 +845,7 @@ const message = {
|
||||
|
||||
repo: 'Pendaftaran',
|
||||
createRepo: 'Tambah',
|
||||
createRepoHelper: 'Menambah repositori jenis HTTP memerlukan mulakan semula perkhidmatan Docker.',
|
||||
httpRepo:
|
||||
'Memilih protokol HTTP memerlukan memulakan semula perkhidmatan Docker untuk menambahkannya ke pendaftaran tidak selamat.',
|
||||
delInsecure: 'Padamkan pendaftaran tidak selamat',
|
||||
@@ -1405,7 +1406,8 @@ const message = {
|
||||
existFileTitle: 'Amaran fail dengan nama yang sama',
|
||||
existFileHelper: 'Fail yang dimuat naik mengandungi fail dengan nama yang sama. Adakah anda mahu menimpanya?',
|
||||
existFileSize: 'Saiz fail (baru -> lama)',
|
||||
existFileDirHelper: 'Fail/folder yang dipilih mempunyai nama yang sama. Sila berhati-hati!',
|
||||
existFileDirHelper: 'Fail/folder yang dipilih mempunyai nama yang sama. Sila berhati-hati!\n',
|
||||
coverDirHelper: 'Folder yang dipilih untuk ditimpa akan disalin ke laluan destinasi!',
|
||||
noSuchFile: 'Fail atau direktori tidak ditemui. Sila periksa dan cuba lagi.',
|
||||
},
|
||||
ssh: {
|
||||
@@ -2649,7 +2651,7 @@ const message = {
|
||||
domain: 'Alamat Akses Lalai',
|
||||
domainHelper: 'Contoh: 192.168.1.1 atau example.com',
|
||||
bindDomain: 'Sematkan Laman Web',
|
||||
commandPlaceHolder: 'Kini hanya menyokong perintah pelancaran npx',
|
||||
commandPlaceHolder: 'Kini hanya menyokong perintah pelancaran npx dan binari',
|
||||
importMcpJson: 'Import Konfigurasi Pelayan MCP',
|
||||
importMcpJsonError: 'Struktur mcpServers tidak betul',
|
||||
bindDomainHelper:
|
||||
|
||||
@@ -841,6 +841,7 @@ const message = {
|
||||
|
||||
repo: 'Registries',
|
||||
createRepo: 'Adicionar',
|
||||
createRepoHelper: 'Adicionar um repositório do tipo HTTP requer reinicialização do serviço Docker.',
|
||||
httpRepo:
|
||||
'Escolher o protocolo HTTP requer reiniciar o serviço Docker para adicioná-lo a registries inseguros.',
|
||||
delInsecure: 'Remover da lista de segurança',
|
||||
@@ -1392,7 +1393,8 @@ const message = {
|
||||
existFileTitle: 'Aviso de arquivo com o mesmo nome',
|
||||
existFileHelper: 'O arquivo enviado contém um arquivo com o mesmo nome. Deseja substituí-lo?',
|
||||
existFileSize: 'Tamanho do arquivo (novo -> antigo)',
|
||||
existFileDirHelper: 'O arquivo/pasta selecionado tem um nome duplicado. Por favor, prossiga com cautela!',
|
||||
existFileDirHelper: 'O arquivo/pasta selecionado tem um nome duplicado. Por favor, prossiga com cautela!\n',
|
||||
coverDirHelper: 'As pastas selecionadas para substituição serão copiadas para o caminho de destino!',
|
||||
noSuchFile: 'O arquivo ou diretório não foi encontrado. Por favor, verifique e tente novamente.',
|
||||
},
|
||||
ssh: {
|
||||
@@ -2647,7 +2649,7 @@ const message = {
|
||||
domain: 'Endereço de Acesso Padrão',
|
||||
domainHelper: 'Por exemplo: 192.168.1.1 ou example.com',
|
||||
bindDomain: 'Vincular Site',
|
||||
commandPlaceHolder: 'Atualmente, apenas comandos de inicialização npx são suportados',
|
||||
commandPlaceHolder: 'Atualmente, apenas comandos de inicialização npx e binários são suportados',
|
||||
importMcpJson: 'Importar Configuração do Servidor MCP',
|
||||
importMcpJsonError: 'A estrutura mcpServers está incorreta',
|
||||
bindDomainHelper:
|
||||
|
||||
@@ -843,6 +843,7 @@ const message = {
|
||||
|
||||
repo: 'Реестры',
|
||||
createRepo: 'Добавить',
|
||||
createRepoHelper: 'Добавление репозитория HTTP-типа требует перезапуска службы Docker.',
|
||||
httpRepo: 'Выбор HTTP протокола требует перезапуска службы Docker для добавления в небезопасные реестры.',
|
||||
delInsecure: 'Удаление учетных данных',
|
||||
delInsecureHelper: 'Это перезапустит службу Docker для удаления из небезопасных реестров. Хотите продолжить?',
|
||||
@@ -1394,7 +1395,8 @@ const message = {
|
||||
existFileTitle: 'Предупреждение о файле с тем же именем',
|
||||
existFileHelper: 'Загруженный файл содержит файл с таким же именем. Заменить его?',
|
||||
existFileSize: 'Размер файла (новый -> старый)',
|
||||
existFileDirHelper: 'Выбранный файл/папка имеет дублирующееся имя. Пожалуйста, действуйте осторожно!',
|
||||
existFileDirHelper: 'Выбранный файл/папка имеет дублирующееся имя. Пожалуйста, действуйте осторожно!\n',
|
||||
coverDirHelper: 'При выборе перезаписываемой папки она будет скопирована в целевой путь!',
|
||||
noSuchFile: 'Файл или каталог не найдены. Пожалуйста, проверьте и повторите попытку.',
|
||||
},
|
||||
ssh: {
|
||||
@@ -2643,7 +2645,7 @@ const message = {
|
||||
domain: 'Адрес доступа по умолчанию',
|
||||
domainHelper: 'Например: 192.168.1.1 или example.com',
|
||||
bindDomain: 'Привязать сайт',
|
||||
commandPlaceHolder: 'В настоящее время поддерживаются только команды запуска npx',
|
||||
commandPlaceHolder: 'В настоящее время поддерживаются только команды запуска npx и двоичных файлов',
|
||||
importMcpJson: 'Импортировать конфигурацию сервера MCP',
|
||||
importMcpJsonError: 'Структура mcpServers некорректна',
|
||||
bindDomainHelper:
|
||||
|
||||
@@ -804,6 +804,7 @@ const message = {
|
||||
|
||||
repo: '倉庫',
|
||||
createRepo: '新增倉庫',
|
||||
createRepoHelper: '添加 HTTP 類型倉庫需要重啟 Docker 服務。',
|
||||
downloadUrl: '下載網址',
|
||||
imageRepo: '鏡像倉庫',
|
||||
repoHelper: '是否包含鏡像倉庫/組織/項目?',
|
||||
@@ -1319,6 +1320,7 @@ const message = {
|
||||
existFileHelper: '上傳的檔案存在同名檔案,是否覆蓋?',
|
||||
existFileSize: '文件大小(新->舊)',
|
||||
existFileDirHelper: '選擇的檔案/資料夾存在同名,請謹慎操作!',
|
||||
coverDirHelper: '選取要覆蓋的資料夾,將複製到目標路徑!',
|
||||
noSuchFile: '找不到該檔案或目錄,請檢查後重試。',
|
||||
},
|
||||
ssh: {
|
||||
@@ -2456,7 +2458,7 @@ const message = {
|
||||
domain: '默認訪問地址',
|
||||
domainHelper: '例如:192.168.1.1 或者 example.com',
|
||||
bindDomain: '綁定網站',
|
||||
commandPlaceHolder: '當前僅支持 npx 啟動命令',
|
||||
commandPlaceHolder: '當前僅支持 npx 和 二進制啟動的命令',
|
||||
importMcpJson: '導入 MCP Server配置',
|
||||
importMcpJsonError: 'mcpServers 結構不正確',
|
||||
bindDomainHelper: '綁定網站之後會修改所有已安裝 MCP Server 的訪問地址,並關閉端口的外部訪問',
|
||||
|
||||
@@ -806,6 +806,7 @@ const message = {
|
||||
|
||||
repo: '仓库',
|
||||
createRepo: '添加仓库',
|
||||
createRepoHelper: '添加 http 类型仓库需要重启 Docker 服务。',
|
||||
downloadUrl: '下载地址',
|
||||
imageRepo: '镜像仓库',
|
||||
repoHelper: '是否包含镜像仓库/组织/项目?',
|
||||
@@ -1321,6 +1322,7 @@ const message = {
|
||||
existFileHelper: '上传的文件存在同名文件,是否覆盖?',
|
||||
existFileSize: '文件大小 (新 -> 旧)',
|
||||
existFileDirHelper: '选择的文件/文件夹存在同名,请谨慎操作!',
|
||||
coverDirHelper: '选中覆盖的文件夹,将复制到目标路径!',
|
||||
noSuchFile: '未能找到该文件或目录,请检查后重试',
|
||||
},
|
||||
ssh: {
|
||||
@@ -2458,7 +2460,7 @@ const message = {
|
||||
domain: '默认访问地址',
|
||||
domainHelper: '例如:192.168.1.1 或者 example.com',
|
||||
bindDomain: '绑定网站',
|
||||
commandPlaceHolder: '当前仅支持 npx 启动命令',
|
||||
commandPlaceHolder: '当前仅支持 npx 和 二进制启动的命令',
|
||||
importMcpJson: '导入 MCP Server 配置',
|
||||
importMcpJsonError: 'mcpServers 结构不正确',
|
||||
bindDomainHelper: '绑定网站之后会修改所有已安装 MCP Server 的访问地址,并关闭端口的外部访问',
|
||||
|
||||
@@ -49,7 +49,7 @@ const GlobalStore = defineStore({
|
||||
state.themeConfig.theme === 'dark' ||
|
||||
(state.themeConfig.theme === 'auto' && window.matchMedia('(prefers-color-scheme: dark)').matches),
|
||||
isDarkGoldTheme: (state) => state.themeConfig.primary === '#F0BE96' && state.isProductPro,
|
||||
docsUrl: (state) => (state.isIntl ? 'https://docs.1panel.pro' : 'https://1panel.cn/docs'),
|
||||
docsUrl: (state) => (state.isIntl ? 'https://docs.1panel.pro' : 'https://1panel.cn/docs/v1'),
|
||||
},
|
||||
actions: {
|
||||
setOpenMenuTabs(openMenuTabs: boolean) {
|
||||
|
||||
@@ -70,9 +70,10 @@ const acceptParams = async (): Promise<void> => {
|
||||
unUsedList.value = [];
|
||||
for (const item of list) {
|
||||
if (
|
||||
!item.tags ||
|
||||
item.tags.length === 0 ||
|
||||
(item.tags.length === 1 && item.tags[0].indexOf('<none>') !== -1 && !item.isUsed)
|
||||
(!item.tags ||
|
||||
item.tags.length === 0 ||
|
||||
(item.tags.length === 1 && item.tags[0].indexOf('<none>') !== -1)) &&
|
||||
!item.isUsed
|
||||
) {
|
||||
unTagList.value.push(item);
|
||||
}
|
||||
|
||||
@@ -81,6 +81,8 @@
|
||||
</span>
|
||||
</template>
|
||||
</el-drawer>
|
||||
|
||||
<ConfirmDialog ref="confirmDialog" @confirm="submit" />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
@@ -105,6 +107,8 @@ const drawerVisible = ref(false);
|
||||
const dialogData = ref<DialogProps>({
|
||||
title: '',
|
||||
});
|
||||
const confirmDialog = ref();
|
||||
|
||||
const acceptParams = (params: DialogProps): void => {
|
||||
dialogData.value = params;
|
||||
title.value = i18n.global.t('commons.button.' + dialogData.value.title);
|
||||
@@ -114,6 +118,7 @@ const emit = defineEmits<{ (e: 'search'): void }>();
|
||||
|
||||
const handleClose = () => {
|
||||
drawerVisible.value = false;
|
||||
emit('search');
|
||||
};
|
||||
const rules = reactive({
|
||||
name: [Rules.requiredInput, Rules.name],
|
||||
@@ -142,21 +147,23 @@ const onSubmit = async (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return;
|
||||
formEl.validate(async (valid) => {
|
||||
if (!valid) return;
|
||||
loading.value = true;
|
||||
if (dialogData.value.title === 'add') {
|
||||
await createImageRepo(dialogData.value.rowData!)
|
||||
.then(() => {
|
||||
loading.value = false;
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
emit('search');
|
||||
drawerVisible.value = false;
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
if (dialogData.value.rowData.protocol === 'https') {
|
||||
submit();
|
||||
return;
|
||||
}
|
||||
await updateImageRepo(dialogData.value.rowData!)
|
||||
let params = {
|
||||
header: i18n.global.t('container.imageRepo'),
|
||||
operationInfo: i18n.global.t('container.createRepoHelper'),
|
||||
submitInputInfo: i18n.global.t('database.restartNow'),
|
||||
};
|
||||
confirmDialog.value!.acceptParams(params);
|
||||
});
|
||||
};
|
||||
|
||||
const submit = async () => {
|
||||
loading.value = true;
|
||||
if (dialogData.value.title === 'add') {
|
||||
await createImageRepo(dialogData.value.rowData!)
|
||||
.then(() => {
|
||||
loading.value = false;
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
@@ -166,9 +173,19 @@ const onSubmit = async (formEl: FormInstance | undefined) => {
|
||||
.catch(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
});
|
||||
return;
|
||||
}
|
||||
await updateImageRepo(dialogData.value.rowData!)
|
||||
.then(() => {
|
||||
loading.value = false;
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
emit('search');
|
||||
drawerVisible.value = false;
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
acceptParams,
|
||||
});
|
||||
|
||||
@@ -452,10 +452,11 @@ import { storeToRefs } from 'pinia';
|
||||
import { GlobalStore } from '@/store';
|
||||
import LicenseImport from '@/components/license-import/index.vue';
|
||||
import { Codemirror } from 'vue-codemirror';
|
||||
import { javascript } from '@codemirror/lang-javascript';
|
||||
import { javascript } from '@codemirror/legacy-modes/mode/javascript';
|
||||
import { oneDark } from '@codemirror/theme-one-dark';
|
||||
import { StreamLanguage } from '@codemirror/language';
|
||||
|
||||
const extensions = [javascript(), oneDark];
|
||||
const extensions = [StreamLanguage.define(javascript), oneDark];
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
|
||||
@@ -395,6 +395,7 @@ const search = async () => {
|
||||
if (!currentRecord.value) {
|
||||
currentRecord.value = records.value[0];
|
||||
} else {
|
||||
let oldRecordID = currentRecord.value.id;
|
||||
let beDelete = true;
|
||||
for (const item of records.value) {
|
||||
if (item.id === currentRecord.value.id) {
|
||||
@@ -406,6 +407,9 @@ const search = async () => {
|
||||
if (beDelete) {
|
||||
currentRecord.value = records.value[0];
|
||||
}
|
||||
if (oldRecordID === currentRecord.value.id && currentRecord.value.status !== 'Waiting') {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (currentRecord.value?.records) {
|
||||
loadRecord(currentRecord.value);
|
||||
@@ -419,7 +423,7 @@ const forDetail = async (row: Cronjob.Record) => {
|
||||
const loadRecord = async (row: Cronjob.Record) => {
|
||||
if (row.records) {
|
||||
const res = await getRecordLog(row.id);
|
||||
let log = res.data.replace(/\x1B\[[0-?]*[ -/]*[@-~]/g, '');
|
||||
let log = res.data.replace(/\x1B\[[0-?]*[ -/]*[@-~]/g, '').replace(/\r/g, '');
|
||||
if (currentRecordDetail.value === log) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -74,6 +74,8 @@ const changeVisible = ref(false);
|
||||
type FormInstance = InstanceType<typeof ElForm>;
|
||||
const changeFormRef = ref<FormInstance>();
|
||||
const title = ref();
|
||||
const oldPrivilege = ref();
|
||||
const oldPrivilegeIPs = ref();
|
||||
const changeForm = reactive({
|
||||
id: 0,
|
||||
from: '',
|
||||
@@ -124,6 +126,8 @@ const acceptParams = (params: DialogProps): void => {
|
||||
changeForm.privilegeIPs = params.privilegeIPs;
|
||||
changeForm.value = params.value;
|
||||
changeVisible.value = true;
|
||||
oldPrivilege.value = params.privilege;
|
||||
oldPrivilegeIPs.value = params.privilegeIPs;
|
||||
};
|
||||
const emit = defineEmits<{ (e: 'search'): void }>();
|
||||
|
||||
@@ -167,6 +171,10 @@ const submitChangeInfo = async (formEl: FormInstance | undefined) => {
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (changeForm.privilege === oldPrivilege.value && changeForm.privilegeIPs === oldPrivilegeIPs.value) {
|
||||
changeVisible.value = false;
|
||||
return;
|
||||
}
|
||||
if (changeForm.privilege !== 'ip') {
|
||||
param.value = changeForm.privilege;
|
||||
} else {
|
||||
|
||||
@@ -4,9 +4,6 @@
|
||||
<template #title>
|
||||
<back-button name="MySQL" :header="props.database + ' ' + $t('commons.button.set')">
|
||||
<template #buttons>
|
||||
<el-button type="primary" :plain="activeName !== 'conf'" @click="jumpToConf">
|
||||
{{ $t('database.confChange') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
:disabled="mysqlStatus !== 'Running'"
|
||||
@@ -15,6 +12,9 @@
|
||||
>
|
||||
{{ $t('database.currentStatus') }}
|
||||
</el-button>
|
||||
<el-button type="primary" :plain="activeName !== 'conf'" @click="jumpToConf">
|
||||
{{ $t('database.confChange') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
:disabled="mysqlStatus !== 'Running'"
|
||||
@@ -159,7 +159,7 @@ const globalStore = GlobalStore();
|
||||
const loading = ref(false);
|
||||
|
||||
const extensions = [javascript(), oneDark];
|
||||
const activeName = ref('conf');
|
||||
const activeName = ref('status');
|
||||
|
||||
const baseInfo = reactive({
|
||||
name: '',
|
||||
|
||||
@@ -2,9 +2,6 @@
|
||||
<div v-show="settingShow" v-loading="loading">
|
||||
<LayoutContent :title="database + ' ' + $t('commons.button.set')" :reload="true">
|
||||
<template #buttons>
|
||||
<el-button type="primary" :plain="activeName !== 'conf'" @click="changeTab('conf')">
|
||||
{{ $t('database.confChange') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
:disabled="redisStatus !== 'Running'"
|
||||
@@ -13,6 +10,9 @@
|
||||
>
|
||||
{{ $t('database.currentStatus') }}
|
||||
</el-button>
|
||||
<el-button type="primary" :plain="activeName !== 'conf'" @click="changeTab('conf')">
|
||||
{{ $t('database.confChange') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
:disabled="redisStatus !== 'Running'"
|
||||
@@ -170,7 +170,7 @@ const rules = reactive({
|
||||
maxmemory: [Rules.number, checkNumberRange(0, 999999)],
|
||||
});
|
||||
|
||||
const activeName = ref('conf');
|
||||
const activeName = ref('status');
|
||||
const statusRef = ref();
|
||||
const persistenceRef = ref();
|
||||
|
||||
@@ -217,7 +217,7 @@ const acceptParams = (prop: DialogProps): void => {
|
||||
redisStatus.value = prop.status;
|
||||
database.value = prop.database;
|
||||
settingShow.value = true;
|
||||
loadConfFile();
|
||||
changeTab('status');
|
||||
};
|
||||
|
||||
const portRef = ref();
|
||||
|
||||
@@ -328,6 +328,7 @@ const handleClose = () => {
|
||||
saveContent();
|
||||
})
|
||||
.finally(() => {
|
||||
isEdit.value = false;
|
||||
closeEditor();
|
||||
});
|
||||
} else {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
:destroy-on-close="true"
|
||||
:close-on-click-modal="false"
|
||||
:close-on-press-escape="false"
|
||||
size="675"
|
||||
size="690"
|
||||
>
|
||||
<template #header>
|
||||
<DrawerHeader :header="title" :back="handleClose" />
|
||||
@@ -19,6 +19,16 @@
|
||||
:rules="rules"
|
||||
v-loading="loading"
|
||||
>
|
||||
<el-alert
|
||||
v-if="type == 'cut' && existFiles?.length == 0 && addForm.cover && changeName"
|
||||
show-icon
|
||||
type="warning"
|
||||
:closable="false"
|
||||
>
|
||||
<template #title>
|
||||
<span class="whitespace-break-spaces">{{ $t('file.coverDirHelper') }}</span>
|
||||
</template>
|
||||
</el-alert>
|
||||
<el-form-item :label="$t('file.path')" prop="newPath">
|
||||
<el-input v-model="addForm.newPath">
|
||||
<template #prepend><FileList @choose="getPath" :dir="true"></FileList></template>
|
||||
@@ -34,10 +44,12 @@
|
||||
</el-radio-group>
|
||||
</div>
|
||||
<div v-if="existFiles.length > 0 && !changeName" class="text-center">
|
||||
<el-alert :show-icon="true" type="warning" :closable="false">
|
||||
<div class="whitespace-break-spaces">
|
||||
<span>{{ $t('file.existFileDirHelper') }}</span>
|
||||
</div>
|
||||
<el-alert show-icon type="warning" :closable="false">
|
||||
<template #title>
|
||||
<span class="whitespace-break-spaces">
|
||||
{{ $t('file.existFileDirHelper') + $t('file.coverDirHelper') }}
|
||||
</span>
|
||||
</template>
|
||||
</el-alert>
|
||||
<el-transfer
|
||||
v-model="skipFiles"
|
||||
@@ -74,6 +86,7 @@ import FileList from '@/components/file-list/index.vue';
|
||||
import DrawerHeader from '@/components/drawer-header/index.vue';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
import { getDateStr } from '@/utils/util';
|
||||
import { File } from '@/api/interface/file';
|
||||
|
||||
interface MoveProps {
|
||||
oldPaths: Array<string>;
|
||||
@@ -90,7 +103,7 @@ const open = ref(false);
|
||||
const type = ref('cut');
|
||||
const changeName = ref(false);
|
||||
const oldName = ref('');
|
||||
const existFiles = ref([]);
|
||||
const existFiles = ref<File.ExistFileInfo[]>([]);
|
||||
const skipFiles = ref([]);
|
||||
const transferData = ref([]);
|
||||
|
||||
@@ -110,6 +123,7 @@ const addForm = reactive({
|
||||
allNames: [] as string[],
|
||||
isDir: false,
|
||||
cover: false,
|
||||
coverPaths: [] as string[],
|
||||
});
|
||||
|
||||
const rules = reactive<FormRules>({
|
||||
@@ -136,7 +150,20 @@ const getFileName = (filePath: string) => {
|
||||
};
|
||||
|
||||
const coverFiles: ComputedRef<string[]> = computed(() => {
|
||||
return addForm.oldPaths.filter((item) => !skipFiles.value.includes(getFileName(item))).map((item) => item);
|
||||
const existingNames = new Set(
|
||||
existFiles.value.filter((item) => !skipFiles.value.includes(item.name) && item.isDir).map((item) => item.name),
|
||||
);
|
||||
return addForm.oldPaths.filter((path) => existingNames.has(getFileName(path)));
|
||||
});
|
||||
|
||||
const mvFiles: ComputedRef<string[]> = computed(() => {
|
||||
const skipSet = new Set(skipFiles.value);
|
||||
const coverSet = new Set(coverFiles.value.map(getFileName));
|
||||
|
||||
return addForm.oldPaths.filter((path) => {
|
||||
const name = getFileName(path);
|
||||
return !skipSet.has(name) && !coverSet.has(name);
|
||||
});
|
||||
});
|
||||
|
||||
const getPath = (path: string) => {
|
||||
@@ -173,7 +200,8 @@ const submit = async (formEl: FormInstance | undefined) => {
|
||||
return;
|
||||
}
|
||||
loading.value = true;
|
||||
addForm.oldPaths = coverFiles.value;
|
||||
addForm.coverPaths = coverFiles.value;
|
||||
addForm.oldPaths = mvFiles.value;
|
||||
mvFile();
|
||||
});
|
||||
};
|
||||
@@ -232,6 +260,7 @@ const acceptParams = async (props: MoveProps) => {
|
||||
addForm.name = '';
|
||||
addForm.allNames = props.allNames;
|
||||
type.value = props.type;
|
||||
existFiles.value = [];
|
||||
if (props.name && props.name != '') {
|
||||
oldName.value = props.name;
|
||||
const res = await CheckFile(props.path + '/' + props.name, false);
|
||||
@@ -261,7 +290,7 @@ defineExpose({ acceptParams });
|
||||
|
||||
<style lang="scss" scoped>
|
||||
:deep(.el-transfer) {
|
||||
--el-transfer-panel-width: 250px;
|
||||
--el-transfer-panel-width: 260px;
|
||||
.el-button {
|
||||
padding: 4px 7px;
|
||||
}
|
||||
|
||||
@@ -72,6 +72,7 @@
|
||||
</div>
|
||||
<el-form-item prop="name" class="no-border">
|
||||
<el-input
|
||||
name="username"
|
||||
v-model.trim="loginForm.name"
|
||||
:placeholder="$t('commons.login.username')"
|
||||
class="form-input"
|
||||
@@ -85,6 +86,7 @@
|
||||
</el-form-item>
|
||||
<el-form-item prop="password" class="no-border">
|
||||
<el-input
|
||||
name="password"
|
||||
type="password"
|
||||
clearable
|
||||
v-model.trim="loginForm.password"
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
module github.com/1Panel-dev/1Panel
|
||||
|
||||
go 1.23
|
||||
go 1.23.0
|
||||
|
||||
toolchain go1.23.7
|
||||
toolchain go1.24.1
|
||||
|
||||
require (
|
||||
github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible
|
||||
@@ -52,14 +52,18 @@ require (
|
||||
github.com/swaggo/files v1.0.1
|
||||
github.com/swaggo/gin-swagger v1.6.0
|
||||
github.com/swaggo/swag v1.16.3
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cloudapp v1.0.1115
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1115
|
||||
github.com/tencentyun/cos-go-sdk-v5 v0.7.46
|
||||
github.com/xlzd/gotp v0.1.0
|
||||
golang.org/x/crypto v0.32.0
|
||||
golang.org/x/net v0.37.0
|
||||
go4.org v0.0.0-20230225012048-214862532bf5
|
||||
golang.org/x/crypto v0.35.0
|
||||
golang.org/x/net v0.39.0
|
||||
golang.org/x/oauth2 v0.24.0
|
||||
golang.org/x/sys v0.29.0
|
||||
golang.org/x/term v0.28.0
|
||||
golang.org/x/text v0.21.0
|
||||
golang.org/x/sync v0.11.0
|
||||
golang.org/x/sys v0.30.0
|
||||
golang.org/x/term v0.29.0
|
||||
golang.org/x/text v0.22.0
|
||||
google.golang.org/genproto v0.0.0-20241021214115-324edc3d5d38
|
||||
gopkg.in/ini.v1 v1.67.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
@@ -172,7 +176,6 @@ require (
|
||||
github.com/lufia/plan9stats v0.0.0-20231016141302-07b5767bb0ed // indirect
|
||||
github.com/magiconair/properties v1.8.7 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/mark3labs/mcp-go v0.14.1 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-shellwords v1.0.12 // indirect
|
||||
github.com/miekg/dns v1.1.62 // indirect
|
||||
@@ -223,7 +226,6 @@ require (
|
||||
github.com/spf13/cast v1.6.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/stretchr/testify v1.10.0 // indirect
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1105 // indirect
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1065 // indirect
|
||||
github.com/therootcompany/xz v1.0.1 // indirect
|
||||
github.com/theupdateframework/notary v0.7.0 // indirect
|
||||
@@ -255,12 +257,10 @@ require (
|
||||
go.opentelemetry.io/proto/otlp v1.3.1 // indirect
|
||||
go.uber.org/atomic v1.11.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go4.org v0.0.0-20230225012048-214862532bf5 // indirect
|
||||
golang.org/x/arch v0.7.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20241210194714-1829a127f884 // indirect
|
||||
golang.org/x/image v0.18.0 // indirect
|
||||
golang.org/x/mod v0.22.0 // indirect
|
||||
golang.org/x/sync v0.10.0 // indirect
|
||||
golang.org/x/time v0.8.0 // indirect
|
||||
golang.org/x/tools v0.28.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20241118233622-e639e219e697 // indirect
|
||||
|
||||
@@ -652,8 +652,6 @@ github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0V
|
||||
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/mark3labs/mcp-go v0.14.1 h1:NsieyFbuWQaeZSWSHPvJ5TwJdQwu+1jmivAIVljeouY=
|
||||
github.com/mark3labs/mcp-go v0.14.1/go.mod h1:xBB350hekQsJAK7gJAii8bcEoWemboLm2mRm5/+KBaU=
|
||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
@@ -964,10 +962,12 @@ github.com/swaggo/gin-swagger v1.6.0 h1:y8sxvQ3E20/RCyrXeFfg60r6H0Z+SwpTjMYsMm+z
|
||||
github.com/swaggo/gin-swagger v1.6.0/go.mod h1:BG00cCEy294xtVpyIAHG6+e2Qzj/xKlRdOqDkvq0uzo=
|
||||
github.com/swaggo/swag v1.16.3 h1:PnCYjPCah8FK4I26l2F/KQ4yz3sILcVUN3cTlBFA9Pg=
|
||||
github.com/swaggo/swag v1.16.3/go.mod h1:DImHIuOFXKpMFAQjcC7FG4m3Dg4+QuUgUzJmKjI/gRk=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cloudapp v1.0.1115 h1:y0GrxBKOgY697oJbybR+WBrukZb/RB9bU06BtlPSWVA=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cloudapp v1.0.1115/go.mod h1:V1xvKmdqEnfD9cfA3z4v/XKxftR4Gdz7Jyp3Oi+UAtM=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.563/go.mod h1:7sCQWVkxcsR38nffDW057DRGk8mUjK1Ing/EFOK8s8Y=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1065/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1105 h1:lCs0dmezU6/8JcfNwEaam1Pm1RS/5MhXvNhf/X2y65s=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1105/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1115 h1:lYeFC379r76seyzN7PHmSxv1ji9knmsXQJglDB/K0WE=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1115/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1065 h1:aEFtLD1ceyeljQXB1S2BjN0zjTkf0X3XmpuxFIiC29w=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1065/go.mod h1:HWvwy09hFSMXrj9SMvVRWV4U7rZO3l+WuogyNuxiT3M=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/kms v1.0.563/go.mod h1:uom4Nvi9W+Qkom0exYiJ9VWJjXwyxtPYTkKkaLMlfE0=
|
||||
@@ -1111,8 +1111,8 @@ golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw
|
||||
golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
|
||||
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
|
||||
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
|
||||
golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs=
|
||||
golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ=
|
||||
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
@@ -1182,8 +1182,8 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
|
||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
|
||||
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@@ -1257,16 +1257,16 @@ golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
|
||||
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
||||
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o=
|
||||
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
|
||||
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||
golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg=
|
||||
golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
|
||||
golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU=
|
||||
golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
@@ -1282,8 +1282,8 @@ golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
|
||||
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
|
||||
Reference in New Issue
Block a user