Compare commits

..
13 Commits
Author SHA1 Message Date
王贺 0be45b6a55 Modify the install script 2025-06-10 17:18:51 +08:00
CityFun c922615183 feat: fix issue with build frontend failed (#8994) 2025-06-10 16:57:45 +08:00
CityFun 8b2990fca4 feat: change 1panel v1 docs url (#8993) 2025-06-10 16:22:04 +08:00
longjuan 37b5cd743a feat: add name attrs to login inputs for password managers to recognize (#8953) 2025-06-10 16:21:59 +08:00
玲珑芯ExquisiteCore 3b1324db08 feat: Move current status to first position in MySQL settings and set… (#8882)
* feat: Move current status to first position in MySQL and Redis settings and set as default tab
2025-06-10 16:21:53 +08:00
Alan f56bbb7dc1 ini filetype support (#8628) 2025-06-10 16:21:43 +08:00
CityFun cff649ae95 fix: Fix issue with update website config (#8610) 2025-06-10 16:21:38 +08:00
Wankko Ree cb87ca4361 fix: the docker image which no tag but used be pruned (#8529) 2025-06-10 16:21:32 +08:00
igophper 165d2b1290 fix(cronjob): prevent unnecessary log updates and clean log formatting (#8481)
Refs #8425
2025-06-10 16:21:25 +08:00
igophper 189555e16c fix(editor): reset edit state after not saving content (#8474)
Refs #8461
2025-06-10 16:21:12 +08:00
ChengPlay 2d8250926f Fix issue with errors when reverse proxy source text includes comments. (#8444)
Refs https://github.com/1Panel-dev/1Panel/issues/8442
2025-06-10 16:20:52 +08:00
ChengPlay 47e343712e feat: change commandPlaceHolder translate (#8391) 2025-06-10 16:20:33 +08:00
ssongliu 11ffb4f2be fix: Fix the mysql root account permission switchover exception (#8378) 2025-06-10 16:20:21 +08:00
76 changed files with 840 additions and 2841 deletions
+2 -2
View File
@@ -12,7 +12,7 @@ jobs:
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: '22.19.0'
node-version: '20.2'
- name: Build Web
run: |
cd frontend && npm install && npm run build:pro
@@ -21,7 +21,7 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: '1.24'
go-version: '1.23'
- name: Build Release
uses: goreleaser/goreleaser-action@v6
with:
+2 -2
View File
@@ -12,7 +12,7 @@ jobs:
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: '22.19.0'
node-version: '20.2'
- name: Build Web
run: |
cd frontend && npm install && npm run build:pro
@@ -21,7 +21,7 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: '1.24'
go-version: '1.23'
- name: Build Release
uses: goreleaser/goreleaser-action@v6
with:
+3 -5
View File
@@ -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,7 +31,6 @@ builds:
- arm
- ppc64le
- s390x
- riscv64
archives:
- formats: [ 'tar.gz' ]
@@ -39,11 +38,10 @@ archives:
wrap_in_directory: true
files:
- 1pctl
- install.sh
- 1panel.service
- initscript/*
- lang/*
- install.sh
- GeoIP.mmdb
- lang/*
checksum:
name_template: 'checksums.txt'
+2 -2
View File
@@ -23,7 +23,7 @@ build_frontend:
build_backend_on_linux:
cd $(SERVER_PATH) \
&& CGO_ENABLED=0 GOOS=$(GOOS) GOARCH=$(GOARCH) $(GOBUILD) -trimpath -ldflags '-s -w' -o $(BUILD_PATH)/$(APP_NAME) $(MAIN)
&& GOOS=$(GOOS) GOARCH=$(GOARCH) $(GOBUILD) -trimpath -ldflags '-s -w' -o $(BUILD_PATH)/$(APP_NAME) $(MAIN)
build_backend_on_darwin:
cd $(SERVER_PATH) \
@@ -31,4 +31,4 @@ build_backend_on_darwin:
build_all: build_frontend build_backend_on_linux
build_on_local: clean_assets build_frontend build_backend_on_darwin
build_on_local: clean_assets build_frontend build_backend_on_darwin upx_bin
+3 -12
View File
@@ -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>Simplify Linux Server Management</b></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">
<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,14 +31,13 @@
------------------------------
1Panel provides an intuitive web interface and MCP Server to manage websites, files, containers, databases, and LLMs on a Linux server.
1Panel is an open-source, modern web-based control panel for Linux server management.
- **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
@@ -50,7 +49,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/v2/installation/online_installation/),默认安装 V2 版本,应用数量相较国际版本更丰富。
中国用户请使用这个 [安装脚本](https://1panel.cn/docs/installation/online_installation/),其应用数量比国际版本更丰富。
## Screenshot
@@ -70,10 +69,6 @@ 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
[![Sponsored by GitAds](https://gitads.dev/v1/ad-serve?source=1panel-dev/1panel@github)](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
@@ -81,7 +76,3 @@ 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 -17
View File
@@ -2,14 +2,12 @@ package v1
import (
"encoding/base64"
"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper"
"github.com/1Panel-dev/1Panel/backend/app/dto"
"github.com/1Panel-dev/1Panel/backend/app/model"
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/global"
"github.com/1Panel-dev/1Panel/backend/utils/captcha"
"github.com/1Panel-dev/1Panel/backend/utils/common"
"github.com/gin-gonic/gin"
)
@@ -28,9 +26,7 @@ func (b *BaseApi) Login(c *gin.Context) {
return
}
ip := common.GetRealClientIP(c)
needCaptcha := global.IPTracker.NeedCaptcha(ip)
if needCaptcha {
if req.AuthMethod != "jwt" && !req.IgnoreCaptcha {
if err := captcha.VerifyCode(req.CaptchaID, req.Captcha); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
@@ -52,11 +48,9 @@ func (b *BaseApi) Login(c *gin.Context) {
user, err := authService.Login(c, req, string(entrance))
go saveLoginLogs(c, err)
if err != nil {
global.IPTracker.SetNeedCaptcha(ip)
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
global.IPTracker.Clear(ip)
helper.SuccessWithData(c, user)
}
@@ -140,21 +134,16 @@ func (b *BaseApi) CheckIsIntl(c *gin.Context) {
}
// @Tags Auth
// @Summary Load System Setting for login
// @Success 200 {object} dto.LoginSetting
// @Router /auth/setting [get]
func (b *BaseApi) GetAuthSetting(c *gin.Context) {
// @Summary Load System Language
// @Success 200 {string} language
// @Router /auth/language [get]
func (b *BaseApi) GetLanguage(c *gin.Context) {
settingInfo, err := settingService.GetSettingInfo()
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
ip := common.GetRealClientIP(c)
needCaptcha := global.IPTracker.NeedCaptcha(ip)
helper.SuccessWithData(c, dto.LoginSetting{
NeedCaptcha: needCaptcha,
Language: settingInfo.Language,
})
helper.SuccessWithData(c, settingInfo.Language)
}
func saveLoginLogs(c *gin.Context, err error) {
+5 -5
View File
@@ -76,7 +76,7 @@ func (b *BaseApi) CheckRepoStatus(c *gin.Context) {
// @Tags Container Image-repo
// @Summary Create image repo
// @Accept json
// @Param request body dto.ImageRepoCreate true "request"
// @Param request body dto.ImageRepoDelete true "request"
// @Produce json
// @Success 200
// @Security ApiKeyAuth
@@ -99,20 +99,20 @@ func (b *BaseApi) CreateRepo(c *gin.Context) {
// @Tags Container Image-repo
// @Summary Delete image repo
// @Accept json
// @Param request body dto.OperateByID true "request"
// @Param request body dto.ImageRepoDelete true "request"
// @Produce json
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /containers/repo/del [post]
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"id","isList":false,"db":"image_repos","output_column":"name","output_value":"name"}],"formatZH":"删除镜像仓库 [name]","formatEN":"delete image repo [name]"}
// @x-panel-log {"bodyKeys":["ids"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"ids","isList":true,"db":"image_repos","output_column":"name","output_value":"names"}],"formatZH":"删除镜像仓库 [names]","formatEN":"delete image repo [names]"}
func (b *BaseApi) DeleteRepo(c *gin.Context) {
var req dto.OperateByID
var req dto.ImageRepoDelete
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if err := imageRepoService.Delete(req); err != nil {
if err := imageRepoService.BatchDelete(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
+7 -11
View File
@@ -23,12 +23,13 @@ type MfaCredential struct {
}
type Login struct {
Name string `json:"name" validate:"required"`
Password string `json:"password" validate:"required"`
Captcha string `json:"captcha"`
CaptchaID string `json:"captchaID"`
AuthMethod string `json:"authMethod" validate:"required,oneof=jwt session"`
Language string `json:"language" validate:"required,oneof=zh en tw ja ko ru ms 'pt-BR'"`
Name string `json:"name" validate:"required"`
Password string `json:"password" validate:"required"`
IgnoreCaptcha bool `json:"ignoreCaptcha"`
Captcha string `json:"captcha"`
CaptchaID string `json:"captchaID"`
AuthMethod string `json:"authMethod" validate:"required,oneof=jwt session"`
Language string `json:"language" validate:"required,oneof=zh en tw ja ko ru ms 'pt-BR'"`
}
type MFALogin struct {
@@ -37,8 +38,3 @@ type MFALogin struct {
Code string `json:"code" validate:"required"`
AuthMethod string `json:"authMethod"`
}
type LoginSetting struct {
NeedCaptcha bool `json:"needCaptcha"`
Language string `json:"language"`
}
+4 -4
View File
@@ -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"`
Password string `json:"password"`
Username string `json:"username" validate:"max=256"`
Password string `json:"password" validate:"max=256"`
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"`
Password string `json:"password"`
Username string `json:"username" validate:"max=256"`
Password string `json:"password" validate:"max=256"`
Auth bool `json:"auth"`
}
+5 -6
View File
@@ -92,12 +92,11 @@ 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"`
CoverPaths []string `json:"coverPaths"`
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 FileDownload struct {
-1
View File
@@ -53,5 +53,4 @@ type ExistFileInfo struct {
Path string `json:"path"`
Size float64 `json:"size"`
ModTime time.Time `json:"modTime"`
IsDir bool `json:"isDir"`
}
+6 -31
View File
@@ -442,14 +442,17 @@ func deleteLink(ctx context.Context, install *model.AppInstall, deleteDB bool, f
return appInstallResourceRepo.DeleteBy(ctx, appInstallResourceRepo.WithAppInstallId(install.ID))
}
func handleUpgradeCompose(install model.AppInstall, detail model.AppDetail) (map[string]interface{}, error) {
func getUpgradeCompose(install model.AppInstall, detail model.AppDetail) (string, error) {
if detail.DockerCompose == "" {
return "", nil
}
composeMap := make(map[string]interface{})
if err := yaml.Unmarshal([]byte(detail.DockerCompose), &composeMap); err != nil {
return nil, err
return "", err
}
value, ok := composeMap["services"]
if !ok || value == nil {
return nil, buserr.New("ErrFileParse")
return "", buserr.New(constant.ErrFileParse)
}
servicesMap := value.(map[string]interface{})
if len(servicesMap) == 1 {
@@ -467,34 +470,6 @@ func handleUpgradeCompose(install model.AppInstall, detail model.AppDetail) (map
delete(servicesMap, oldServiceName)
}
}
serviceValue := servicesMap[install.ServiceName].(map[string]interface{})
oldComposeMap := make(map[string]interface{})
if err := yaml.Unmarshal([]byte(install.DockerCompose), &oldComposeMap); err != nil {
return nil, err
}
oldValue, ok := oldComposeMap["services"]
if !ok || oldValue == nil {
return nil, buserr.New("ErrFileParse")
}
oldValueMap := oldValue.(map[string]interface{})
oldServiceValue := oldValueMap[install.ServiceName].(map[string]interface{})
if oldServiceValue["deploy"] != nil {
serviceValue["deploy"] = oldServiceValue["deploy"]
}
servicesMap[install.ServiceName] = serviceValue
composeMap["services"] = servicesMap
return composeMap, nil
}
func getUpgradeCompose(install model.AppInstall, detail model.AppDetail) (string, error) {
if detail.DockerCompose == "" {
return "", nil
}
composeMap, err := handleUpgradeCompose(install, detail)
if err != nil {
return "", err
}
envs := make(map[string]interface{})
if err := json.Unmarshal([]byte(install.Env), &envs); err != nil {
return "", err
+15 -49
View File
@@ -413,9 +413,9 @@ func (u *BackupService) Update(req dto.BackupOperate) error {
if strings.HasSuffix(dirStr, "/") && dirStr != "/" {
dirStr = dirStr[:strings.LastIndex(dirStr, "/")]
}
if err := changeLocalBackup(oldDir, dirStr); err != nil {
if err := copyDir(oldDir, dirStr); err != nil {
_ = backupRepo.Update(req.ID, map[string]interface{}{"vars": oldVars})
return fmt.Errorf("copy dir from %s to %s failed, err: %v", oldDir, dirStr, err)
return err
}
global.CONF.System.Backup = dirStr
}
@@ -518,40 +518,47 @@ func (u *BackupService) loadAccessToken(backup *model.BackupAccount) error {
}
func (u *BackupService) loadRecordSize(records []model.BackupRecord) ([]dto.BackupFile, error) {
data := make([]dto.BackupFile, len(records))
var datas []dto.BackupFile
clientMap := make(map[string]loadSizeHelper)
var wg sync.WaitGroup
for i := 0; i < len(records); i++ {
data[i].ID = records[i].ID
data[i].Name = records[i].FileName
var item dto.BackupFile
item.ID = records[i].ID
item.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
}
data[i].Size, _ = client.Size(path.Join(strings.TrimLeft(backup.BackupPath, "/"), itemPath))
item.Size, _ = client.Size(path.Join(strings.TrimLeft(backup.BackupPath, "/"), itemPath))
datas = append(datas, item)
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) {
data[i].Size, _ = clientMap[records[index].Source].client.Size(path.Join(clientMap[records[index].Source].backupPath, itemPath))
item.Size, _ = clientMap[records[index].Source].client.Size(path.Join(clientMap[records[index].Source].backupPath, itemPath))
datas = append(datas, item)
wg.Done()
}(i)
} else {
datas = append(datas, item)
}
}
wg.Wait()
return data, nil
return datas, nil
}
func loadLocalDir() (string, error) {
@@ -677,44 +684,3 @@ func (u *BackupService) Run() {
}).Error
global.LOG.Info("Successfully refreshed OneDrive token.")
}
func changeLocalBackup(oldPath, newPath string) error {
fileOp := fileUtils.NewFileOp()
if fileOp.Stat(path.Join(oldPath, "app")) {
if err := fileOp.CopyDir(path.Join(oldPath, "app"), newPath); err != nil {
return err
}
}
if fileOp.Stat(path.Join(oldPath, "database")) {
if err := fileOp.CopyDir(path.Join(oldPath, "database"), newPath); err != nil {
return err
}
}
if fileOp.Stat(path.Join(oldPath, "directory")) {
if err := fileOp.CopyDir(path.Join(oldPath, "directory"), newPath); err != nil {
return err
}
}
if fileOp.Stat(path.Join(oldPath, "system_snapshot")) {
if err := fileOp.CopyDir(path.Join(oldPath, "system_snapshot"), newPath); err != nil {
return err
}
}
if fileOp.Stat(path.Join(oldPath, "website")) {
if err := fileOp.CopyDir(path.Join(oldPath, "website"), newPath); err != nil {
return err
}
}
if fileOp.Stat(path.Join(oldPath, "log")) {
if err := fileOp.CopyDir(path.Join(oldPath, "log"), newPath); err != nil {
return err
}
}
_ = fileOp.RmRf(path.Join(oldPath, "app"))
_ = fileOp.RmRf(path.Join(oldPath, "database"))
_ = fileOp.RmRf(path.Join(oldPath, "directory"))
_ = fileOp.RmRf(path.Join(oldPath, "system_snapshot"))
_ = fileOp.RmRf(path.Join(oldPath, "website"))
_ = fileOp.RmRf(path.Join(oldPath, "log"))
return nil
}
+134 -108
View File
@@ -1,6 +1,7 @@
package service
import (
"bufio"
"fmt"
"os"
"os/exec"
@@ -27,14 +28,14 @@ import (
)
const (
clamServiceKey = "clam"
freshClamServiceKey = "freshclam"
resultDir = "clamav"
clamServiceNameCentOs = "clamd@scan.service"
clamServiceNameUbuntu = "clamav-daemon.service"
freshClamService = "clamav-freshclam.service"
resultDir = "clamav"
)
type ClamService struct {
serviceName string
freshClamService string
serviceName string
}
type IClamService interface {
@@ -62,32 +63,23 @@ func (c *ClamService) LoadBaseInfo() (dto.ClamBaseInfo, error) {
var baseInfo dto.ClamBaseInfo
baseInfo.Version = "-"
baseInfo.FreshVersion = "-"
clamSvc, err := systemctl.GetServiceName(clamServiceKey)
if err != nil {
baseInfo.IsExist = false
return baseInfo, nil
exist1, _ := systemctl.IsExist(clamServiceNameCentOs)
if exist1 {
c.serviceName = clamServiceNameCentOs
baseInfo.IsExist = true
baseInfo.IsActive, _ = systemctl.IsActive(clamServiceNameCentOs)
}
c.serviceName = clamSvc
isExist, err := systemctl.IsExist(c.serviceName)
if err != nil {
baseInfo.IsExist = false
exist2, _ := systemctl.IsExist(clamServiceNameUbuntu)
if exist2 {
c.serviceName = clamServiceNameUbuntu
baseInfo.IsExist = true
baseInfo.IsActive, _ = systemctl.IsActive(clamServiceNameUbuntu)
}
baseInfo.IsExist = isExist
baseInfo.IsActive, _ = systemctl.IsActive(clamSvc)
freshSvc, err := systemctl.GetServiceName(freshClamServiceKey)
if err != nil {
baseInfo.FreshIsExist = false
return baseInfo, nil
freshExist, _ := systemctl.IsExist(freshClamService)
if freshExist {
baseInfo.FreshIsExist = true
baseInfo.FreshIsActive, _ = systemctl.IsActive(freshClamService)
}
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
}
@@ -118,27 +110,22 @@ func (c *ClamService) LoadBaseInfo() (dto.ClamBaseInfo, error) {
}
func (c *ClamService) Operate(operate string) error {
var err error
switch operate {
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)
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
default:
return fmt.Errorf("unsupported operation: %s", operate)
return fmt.Errorf("not support such operation: %v", 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) {
@@ -445,80 +432,102 @@ func (c *ClamService) LoadFile(req dto.ClamFileReq) (string, error) {
filePath := ""
switch req.Name {
case "clamd":
filePath = c.getConfigPath("clamd")
if c.serviceName == clamServiceNameUbuntu {
filePath = "/etc/clamav/clamd.conf"
} else {
filePath = "/etc/clamd.d/scan.conf"
}
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":
filePath = c.getConfigPath("freshclam")
if c.serviceName == clamServiceNameUbuntu {
filePath = "/etc/clamav/freshclam.conf"
} else {
filePath = "/etc/freshclam.conf"
}
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("unsupported file type")
return "", fmt.Errorf("not support such type")
}
content, err := systemctl.ViewConfig(filePath, systemctl.ConfigOption{TailLines: req.Tail})
if err != nil {
if _, err := os.Stat(filePath); err != nil {
return "", buserr.New("ErrHttpReqNotFound")
}
return content, nil
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
}
func (c *ClamService) UpdateFile(req dto.UpdateByNameAndFile) error {
var (
filePath string
service string
)
filePath := ""
service := ""
switch req.Name {
case "clamd":
filePath = c.getConfigPath("clamd")
service = c.serviceName
if c.serviceName == clamServiceNameUbuntu {
service = clamServiceNameUbuntu
filePath = "/etc/clamav/clamd.conf"
} else {
service = clamServiceNameCentOs
filePath = "/etc/clamd.d/scan.conf"
}
case "freshclam":
filePath = c.getConfigPath("freshclam")
service = c.freshClamService
if c.serviceName == clamServiceNameUbuntu {
filePath = "/etc/clamav/freshclam.conf"
} else {
filePath = "/etc/freshclam.conf"
}
service = "clamav-freshclam.service"
default:
return fmt.Errorf("unsupported file type")
return fmt.Errorf("not support such 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()
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)
}
_ = systemctl.Restart(service)
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
isexist, _ := systemctl.IsExist(clamServiceKey)
if isexist {
isActive, _ = systemctl.IsActive(clamServiceKey)
exist1, _ := systemctl.IsExist(clamServiceNameCentOs)
if exist1 {
isActive, _ = systemctl.IsActive(clamServiceNameCentOs)
}
exist2, _ := systemctl.IsExist(clamServiceNameUbuntu)
if exist2 {
isActive, _ = systemctl.IsActive(clamServiceNameUbuntu)
}
if isActive {
return false
@@ -581,25 +590,42 @@ func loadResultFromLog(pathItem string) dto.ClamLog {
return data
}
func (c *ClamService) loadLogPath(name string) string {
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 ""
}
for _, line := range strings.Split(string(content), "\n") {
if strings.HasPrefix(line, searchPrefix) {
return strings.TrimSpace(strings.TrimPrefix(line, searchPrefix))
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 ""
}
content, err := os.ReadFile(confPath)
if err != nil {
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 ", ""), " ")
}
}
}
return ""
}
+8 -2
View File
@@ -558,7 +558,10 @@ func (u *ContainerService) ContainerUpgrade(req dto.ContainerUpgrade) error {
hostConf := oldContainer.HostConfig
var networkConf network.NetworkingConfig
if oldContainer.NetworkSettings != nil {
networkConf.EndpointsConfig = oldContainer.NetworkSettings.Networks
for networkKey := range oldContainer.NetworkSettings.Networks {
networkConf.EndpointsConfig = map[string]*network.EndpointSettings{networkKey: {}}
break
}
}
if err := client.ContainerRemove(ctx, req.Name, container.RemoveOptions{Force: true}); err != nil {
return err
@@ -1212,7 +1215,10 @@ func reCreateAfterUpdate(name string, client *client.Client, config *container.C
var oldNetworkConf network.NetworkingConfig
if networkConf != nil {
oldNetworkConf.EndpointsConfig = networkConf.Networks
for networkKey := range networkConf.Networks {
oldNetworkConf.EndpointsConfig = map[string]*network.EndpointSettings{networkKey: {}}
break
}
}
oldContainer, err := client.ContainerCreate(ctx, config, hostConf, &oldNetworkConf, &v1.Platform{}, name)
+1 -2
View File
@@ -18,7 +18,6 @@ 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"
)
@@ -122,7 +121,7 @@ func (u *DeviceService) Update(key, value string) error {
return err
}
go func() {
err := systemctl.Restart("1panel")
_, err := cmd.Exec("systemctl restart 1panel.service")
if err != nil {
global.LOG.Errorf("restart system for new time zone failed, err: %v", err)
}
+2 -2
View File
@@ -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 := systemctl.Restart("1panel")
_, err := cmd.Exec("systemctl restart 1panel.service")
if err != nil {
global.LOG.Errorf("restart system port failed, err: %v", err)
}
+26 -30
View File
@@ -365,18 +365,17 @@ func (u *DockerService) UpdateConfByFile(req dto.DaemonJsonUpdateByFile) error {
func (u *DockerService) OperateDocker(req dto.DockerOperation) error {
service := "docker"
h, err := systemctl.DefaultHandler(service)
sudo := cmd.SudoHandleCmd()
dockerCmd, err := getDockerRestartCommand()
if err != nil {
return err
}
if req.Operation == "stop" {
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)
}
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)
}
}
}
@@ -387,20 +386,13 @@ func (u *DockerService) OperateDocker(req dto.DockerOperation) error {
}
}
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)
stdout, err := cmd.Execf("%s %s %s", dockerCmd, req.Operation, service)
if err != nil {
return errors.New(result.Output)
return errors.New(stdout)
}
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 {
@@ -459,27 +451,31 @@ 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 isDockerSnapInstalled() bool {
func getDockerRestartCommand() (string, error) {
stdout, err := cmd.Exec("which docker")
if err != nil {
return false
return "", fmt.Errorf("failed to find docker: %v", err)
}
stdout = strings.TrimSpace(stdout)
return strings.Contains(stdout, "snap")
dockerPath := stdout
if strings.Contains(dockerPath, "snap") {
return "snap", nil
}
return "systemctl", nil
}
func restartDocker() error {
if isDockerSnapInstalled() {
stdout, err := cmd.Exec("snap restart docker")
if err != nil {
return fmt.Errorf("failed to restart docker: %v", stdout)
}
return nil
restartCmd, err := getDockerRestartCommand()
if err != nil {
return err
}
return systemctl.Restart("docker")
stdout, err := cmd.Execf("%s restart docker", restartCmd)
if err != nil {
return fmt.Errorf("failed to restart Docker: %s", stdout)
}
return nil
}
+1 -10
View File
@@ -366,18 +366,10 @@ 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 {
@@ -526,7 +518,6 @@ func (f *FileService) BatchCheckFiles(req request.FilePathsCheck) []response.Exi
Name: info.Name(),
Path: filePath,
ModTime: info.ModTime(),
IsDir: info.IsDir(),
})
}
}
+82 -112
View File
@@ -3,12 +3,6 @@ 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"
@@ -20,6 +14,11 @@ 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{}
@@ -40,106 +39,91 @@ func NewIHostToolService() IHostToolService {
}
func (h *HostToolService) GetToolStatus(req request.HostToolReq) (*response.HostToolRes, error) {
res := &response.HostToolRes{Type: req.Type}
res := &response.HostToolRes{}
res.Type = req.Type
switch req.Type {
case constant.Supervisord:
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)
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
}
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
}
serviceNameSet, _ := settingRepo.Get(settingRepo.WithByKey(constant.SupervisorServiceName))
if serviceNameSet.ID != 0 || serviceNameSet.Value != "" {
supervisorConfig.ServiceName = serviceNameSet.Value
}
config.Init = true
versionRes, _ := cmd.Exec("supervisord -v")
supervisorConfig.Version = strings.TrimSuffix(versionRes, "\n")
_, ctlRrr := exec.LookPath("supervisorctl")
supervisorConfig.CtlExist = ctlRrr == nil
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
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]
}
}
}
}
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
if supervisorConfig.ConfigPath == "" {
configPath := "/etc/supervisord.conf"
if !fileOp.Stat(configPath) {
configPath = "/etc/supervisor/supervisord.conf"
if fileOp.Stat(configPath) {
supervisorConfig.ConfigPath = configPath
}
}
}
}
}
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]
}
res.Config = supervisorConfig
}
return ""
return res, nil
}
func (h *HostToolService) CreateToolConfig(req request.HostToolCreate) error {
@@ -216,28 +200,14 @@ func (h *HostToolService) CreateToolConfig(req request.HostToolCreate) error {
}
func (h *HostToolService) OperateTool(req request.HostToolReq) error {
var serviceName string
var err error
switch req.Type {
case constant.Supervisord:
serviceName := req.Type
if req.Type == 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
}
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
return systemctl.Operate(req.Operate, serviceName)
}
func (h *HostToolService) OperateToolConfig(req request.HostToolConfig) (*response.HostToolConfig, error) {
+47 -73
View File
@@ -26,7 +26,7 @@ type IImageRepoService interface {
Login(req dto.OperateByID) error
Create(req dto.ImageRepoCreate) error
Update(req dto.ImageRepoUpdate) error
Delete(req dto.OperateByID) error
BatchDelete(req dto.ImageRepoDelete) error
}
func NewIImageRepoService() IImageRepoService {
@@ -89,7 +89,31 @@ func (u *ImageRepoService) Create(req dto.ImageRepoCreate) error {
if err := u.handleRegistries(req.DownloadUrl, "", "create"); err != nil {
return fmt.Errorf("create registry %s failed, err: %v", req.DownloadUrl, err)
}
if err := stopBeforeUpdateRepo(); err != nil {
if err := validateDockerConfig(); err != nil {
return err
}
if err := restartDocker(); err != nil {
return err
}
ticker := time.NewTicker(3 * time.Second)
defer ticker.Stop()
ctx, cancel := context.WithTimeout(context.Background(), time.Second*20)
if err := func() error {
for range ticker.C {
select {
case <-ctx.Done():
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 {
global.LOG.Info("docker restart with new conf successful!")
return nil
}
}
}
return nil
}(); err != nil {
return err
}
}
@@ -107,32 +131,15 @@ func (u *ImageRepoService) Create(req dto.ImageRepoCreate) error {
return imageRepoRepo.Create(&imageRepo)
}
func (u *ImageRepoService) Delete(req dto.OperateByID) error {
if req.ID == 1 {
return errors.New("The default value cannot be edit !")
func (u *ImageRepoService) BatchDelete(req dto.ImageRepoDelete) error {
for _, id := range req.Ids {
if id == 1 {
return errors.New("The default value cannot be edit !")
}
}
itemRepo, _ := imageRepoRepo.Get(commonRepo.WithByID(req.ID))
if itemRepo.ID == 0 {
return buserr.New("ErrRecordNotFound")
}
if itemRepo.Auth {
_, _ = cmd.Execf("docker logout -i %s", itemRepo.DownloadUrl)
}
if itemRepo.Protocol == "https" {
return imageRepoRepo.Delete(commonRepo.WithByID(req.ID))
}
if err := u.handleRegistries("", itemRepo.DownloadUrl, "delete"); err != nil {
return fmt.Errorf("delete registry %s failed, err: %v", itemRepo.DownloadUrl, err)
}
if err := validateDockerConfig(); err != nil {
if err := imageRepoRepo.Delete(commonRepo.WithIdsIn(req.Ids)); err != nil {
return err
}
if err := imageRepoRepo.Delete(commonRepo.WithByID(req.ID)); err != nil {
return err
}
go func() {
_ = restartDocker()
}()
return nil
}
@@ -147,39 +154,37 @@ func (u *ImageRepoService) Update(req dto.ImageRepoUpdate) error {
if err != nil {
return err
}
needRestart := false
if repo.Protocol == "http" && req.Protocol == "https" {
if err := u.handleRegistries("", repo.DownloadUrl, "delete"); err != nil {
return fmt.Errorf("delete registry %s failed, err: %v", repo.DownloadUrl, err)
}
needRestart = true
}
if repo.Protocol == "http" && req.Protocol == "http" {
if repo.DownloadUrl != req.DownloadUrl {
if err := u.handleRegistries(req.DownloadUrl, repo.DownloadUrl, "update"); err != nil {
return fmt.Errorf("update registry %s => %s failed, err: %v", repo.DownloadUrl, req.DownloadUrl, err)
}
needRestart = true
if err := u.handleRegistries(req.DownloadUrl, repo.DownloadUrl, "update"); err != nil {
return fmt.Errorf("update registry %s => %s failed, err: %v", repo.DownloadUrl, req.DownloadUrl, err)
}
}
if repo.Protocol == "https" && req.Protocol == "http" {
if err := u.handleRegistries(req.DownloadUrl, "", "create"); err != nil {
return fmt.Errorf("create registry %s failed, err: %v", req.DownloadUrl, err)
}
needRestart = true
}
if needRestart {
if err := stopBeforeUpdateRepo(); err != nil {
return err
if repo.Auth != req.Auth || repo.DownloadUrl != req.DownloadUrl {
if repo.Auth {
_, _ = cmd.ExecWithCheck("docker", "logout", repo.DownloadUrl)
}
if req.Auth {
if err := u.CheckConn(req.DownloadUrl, req.Username, req.Password); err != nil {
return err
}
}
}
if repo.Auth {
_, _ = cmd.Execf("docker logout -i %s", repo.DownloadUrl)
if err := validateDockerConfig(); err != nil {
return err
}
if req.Auth {
if err := u.CheckConn(req.DownloadUrl, req.Username, req.Password); err != nil {
return err
}
if err := restartDocker(); err != nil {
return err
}
upMap := make(map[string]interface{})
@@ -251,34 +256,3 @@ func (u *ImageRepoService) handleRegistries(newHost, delHost, handle string) err
}
return nil
}
func stopBeforeUpdateRepo() error {
if err := validateDockerConfig(); err != nil {
return err
}
if err := restartDocker(); err != nil {
return err
}
ticker := time.NewTicker(3 * time.Second)
defer ticker.Stop()
ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
if err := func() error {
for range ticker.C {
select {
case <-ctx.Done():
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 {
global.LOG.Info("docker restart with new conf successful!")
return nil
}
}
}
return nil
}(); err != nil {
return err
}
return nil
}
+6 -6
View File
@@ -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 := systemctl.Restart("1panel")
_, err := cmd.Exec("systemctl restart 1panel.service")
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 := systemctl.Restart("1panel")
_, err := cmd.Exec("systemctl restart 1panel.service")
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 := systemctl.Restart("1panel")
_, err := cmd.Exec("systemctl restart 1panel.service")
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 := systemctl.Restart("1panel")
_, err := cmd.Exec("systemctl restart 1panel.service")
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, _ := exportPublicKeyToPEM(&privateKey.PublicKey)
publicKeyPEM, err := exportPublicKeyToPEM(&privateKey.PublicKey)
err = settingRepo.UpdateOrCreate("PASSWORD_PRIVATE_KEY", privateKeyPEM)
if err != nil {
return err
+1 -2
View File
@@ -503,8 +503,7 @@ func hasOs(name string) bool {
strings.Contains(name, "arm64") ||
strings.Contains(name, "armv7") ||
strings.Contains(name, "ppc64le") ||
strings.Contains(name, "s390x") ||
strings.Contains(name, "riscv64")
strings.Contains(name, "s390x")
}
func loadOs() string {
+7 -31
View File
@@ -17,7 +17,6 @@ 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 {
@@ -44,39 +43,16 @@ func snapPanel(snap snapHelper, targetDir string) {
defer snap.Wg.Done()
_ = snapshotRepo.UpdateStatus(snap.Status.ID, map[string]interface{}{"panel": constant.Running})
status := constant.StatusDone
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/1panel", path.Join(targetDir, "1panel")); err != nil {
status = err.Error()
}
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("/usr/local/bin/1pctl", targetDir); err != nil {
status = err.Error()
}
_, _ = cmd.Execf("cp -r /usr/local/bin/lang %s", targetDir)
if err := common.CopyFile(path.Join(binDir, "1panel"), path.Join(targetDir, "1panel")); err != nil {
status = err.Error()
}
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
if err := common.CopyFile("/etc/systemd/system/1panel.service", targetDir); err != nil {
status = err.Error()
}
snap.Status.Panel = status
@@ -284,7 +260,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 '%%P\n' | sed 's|^|./|') -zcf %s %s -C %s .", sourceDir, targetDir+"/"+name, exStr, sourceDir)
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)
global.LOG.Debug(commands)
}
stdout, err := cmd.ExecWithTimeOut(commands, 30*time.Minute)
+5 -34
View File
@@ -15,7 +15,6 @@ 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"
)
@@ -92,15 +91,8 @@ 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" {
binDir := systemctl.BinaryPath
if err := recoverPanel(path.Join(snapFileDir, "1panel/1panel"), binDir); err != nil {
if err := recoverPanel(path.Join(snapFileDir, "1panel/1panel"), "/usr/local/bin"); err != nil {
updateRecoverStatus(snap.ID, isRecover, "1PanelBinary", constant.StatusFailed, err.Error())
return
}
@@ -108,32 +100,16 @@ func (u *SnapshotService) HandleSnapshotRecover(snap model.Snapshot, isRecover b
req.IsNew = true
}
if req.IsNew || snap.InterruptStep == "1PctlBinary" {
binDir := systemctl.BinaryPath
if err := recoverPanel(path.Join(snapFileDir, "1panel/1pctl"), binDir); err != nil {
if err := recoverPanel(path.Join(snapFileDir, "1panel/1pctl"), "/usr/local/bin"); err != nil {
updateRecoverStatus(snap.ID, isRecover, "1PctlBinary", constant.StatusFailed, err.Error())
return
}
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
}
_, _ = cmd.Execf("cp -r %s %s", path.Join(snapFileDir, "1panel/lang"), "/usr/local/bin/")
global.LOG.Debug("recover 1pctl from snapshot file successful!")
req.IsNew = true
}
if req.IsNew || snap.InterruptStep == "1PanelService" {
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 {
if err := recoverPanel(path.Join(snapFileDir, "1panel/1panel.service"), "/etc/systemd/system"); err != nil {
updateRecoverStatus(snap.ID, isRecover, "1PanelService", constant.StatusFailed, err.Error())
return
}
@@ -171,12 +147,7 @@ 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))
}
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)
}
_, _ = cmd.Exec("systemctl daemon-reload && systemctl restart 1panel.service")
}
func backupBeforeRecover(snap model.Snapshot) error {
+164 -216
View File
@@ -2,7 +2,8 @@ package service
import (
"fmt"
"net"
"github.com/1Panel-dev/1Panel/backend/utils/geo"
"github.com/gin-gonic/gin"
"os"
"os/user"
"path"
@@ -11,9 +12,6 @@ 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"
@@ -61,16 +59,26 @@ func (u *SSHService) GetSSHInfo() (*dto.SSHInfo, error) {
if err != nil {
data.Status = constant.StatusDisable
data.Message = err.Error()
}
isEnabled, _ := systemctl.IsEnable(serviceName)
data.AutoStart = isEnabled
isActive, err := systemctl.IsActive(serviceName)
if isActive {
data.Status = constant.StatusEnable
} else {
data.Status = constant.StatusDisable
if err != nil {
data.Message = err.Error()
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
} else {
if out == "alias\n" {
data.AutoStart, _ = systemctl.IsEnable("ssh")
} else {
data.AutoStart = out == "enabled\n"
}
}
@@ -113,26 +121,29 @@ func (u *SSHService) OperateSSH(operation string) error {
if err != nil {
return err
}
switch operation {
case "enable":
return systemctl.Enable(serviceName)
case "disable":
return systemctl.Disable(serviceName)
case "start":
return systemctl.Start(serviceName)
case "stop":
sudo := cmd.SudoHandleCmd()
if operation == "enable" || operation == "disable" {
serviceName += ".service"
}
if operation == "stop" {
isSocketActive, _ := systemctl.IsActive(serviceName + ".socket")
if isSocketActive {
if err := systemctl.Stop(serviceName + ".socket"); err != nil {
global.LOG.Errorf("Failed to stop %s.socket: %v", serviceName, err)
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)
}
}
return systemctl.Stop(serviceName)
case "restart":
return systemctl.Restart(serviceName)
}
if result, err := systemctl.CustomAction(operation, serviceName); err != nil {
return fmt.Errorf("failed to execute custom action: %v", result.Output)
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)
}
return nil
}
@@ -187,10 +198,7 @@ func (u *SSHService) Update(req dto.SSHUpdate) error {
}
}
err = systemctl.Restart(serviceName)
if err != nil {
global.LOG.Errorf("handle restart %s failed, err: %v", serviceName, err)
}
_, _ = cmd.Execf("%s systemctl restart %s", sudo, serviceName)
return nil
}
@@ -208,10 +216,8 @@ func (u *SSHService) UpdateByFile(value string) error {
if _, err = file.WriteString(value); err != nil {
return err
}
err = systemctl.Restart(serviceName)
if err != nil {
global.LOG.Errorf("handle restart %s failed, err: %v", serviceName, err)
}
sudo := cmd.SudoHandleCmd()
_, _ = cmd.Execf("%s systemctl restart %s", sudo, serviceName)
return nil
}
@@ -292,7 +298,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") || strings.HasPrefix(info.Name(), "messages")) {
if !info.IsDir() && (strings.HasPrefix(info.Name(), "secure") || strings.HasPrefix(info.Name(), "auth")) {
if !strings.HasSuffix(info.Name(), ".gz") {
fileList = append(fileList, sshFileItem{Name: pathItem, Year: info.ModTime().Year()})
return nil
@@ -320,8 +326,7 @@ func (u *SSHService) LoadLog(c *gin.Context, req dto.SearchSSHLog) (*dto.SSHLog,
nyc, _ := time.LoadLocation(common.LoadTimeZoneByCmd())
for _, file := range fileList {
commandItem := ""
switch {
case strings.HasPrefix(path.Base(file.Name), "secure"):
if 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)
@@ -330,16 +335,8 @@ 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)
}
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"):
}
if 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)
@@ -435,160 +432,107 @@ 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(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
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++
}
}
}
return datas, successCount, failedCount
}
func parseFailedPasswordLog(parts []string, timeIndex int, dateStr string) dto.SSHHistory {
data := dto.SSHHistory{
DateStr: dateStr,
Status: constant.StatusFailed,
AuthMode: "publickey",
func loadSuccessDatas(line string) dto.SSHHistory {
var data dto.SSHHistory
parts := strings.Fields(line)
index, dataStr := analyzeDateStr(parts)
if dataStr == "" {
return data
}
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
}
// 查找关键字段位置
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"
}
func loadFailedAuthDatas(line string) dto.SSHHistory {
var data dto.SSHHistory
parts := strings.Fields(line)
index, dataStr := analyzeDateStr(parts)
if dataStr == "" {
return data
}
if strings.Contains(strings.Join(parts, " "), "invalid user") {
data.Message = "invalid user attempt"
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]
}
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
@@ -597,36 +541,40 @@ func handleGunzip(path string) error {
}
func loadServiceName() (string, error) {
serviceName, err := systemctl.GetServiceName("ssh")
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)
if err != nil {
if errors.Is(err, systemctl.ErrServiceNotFound) {
return "", fmt.Errorf("SSH service unavailable. Please ensure OpenSSH server is installed and configured")
itemDate, _ = time.ParseInLocation("2006 Jan 2 15:04:05", DateStr, nyc)
}
return itemDate
}
func analyzeDateStr(parts []string) (int, string) {
t, err := time.Parse(time.RFC3339Nano, parts[0])
if err == nil {
if len(parts) < 12 {
return 0, ""
}
return "", fmt.Errorf("failed to load SSH service name: %w", err)
return 0, t.Format("2006 Jan 2 15:04:05")
}
return serviceName, nil
}
func parseLogDate(currentYear int, dateStr string, loc *time.Location) time.Time {
// 处理带年份和不带年份的情况
formats := []string{
"2006 Jan 2 15:04:05",
"Jan 2 15:04:05",
}
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
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")
}
return time.Now().In(loc)
}
func isIPAddress(s string) bool {
return net.ParseIP(s) != nil
if len(parts) < 14 {
return 0, ""
}
return 2, fmt.Sprintf("%s %s %s", parts[0], parts[1], parts[2])
}
+79 -153
View File
@@ -6,7 +6,6 @@ import (
"net/http"
"os"
"path"
"path/filepath"
"strconv"
"strings"
"time"
@@ -18,7 +17,6 @@ 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{}
@@ -105,159 +103,123 @@ 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)
serviceHandle, _ := systemctl.DefaultHandler("1panel")
currentServiceName := serviceHandle.GetServiceName()
if err := settingRepo.Update("SystemStatus", "Upgrading"); err != nil {
return fmt.Errorf("update system status failed: %w", err)
}
_ = settingRepo.Update("SystemStatus", "Upgrading")
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 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)
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")
return
}
defer os.RemoveAll(rootDir)
if err := handleUnTar(path.Join(rootDir, fileName), rootDir, ""); err != nil {
global.LOG.Errorf("Failed to extract package: %v", err)
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")
return
}
tmpDir := path.Join(rootDir, strings.TrimSuffix(fileName, ".tar.gz"))
tmpDir := rootDir + "/" + strings.ReplaceAll(fileName, ".tar.gz", "")
if err := u.handleBackup(fileOp, originalDir); err != nil {
global.LOG.Errorf("Backup failed: %v", err)
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)
return
}
binDir := systemctl.BinaryPath
servicePath, _ := serviceHandle.GetServicePath()
geoPath := path.Join(global.CONF.System.BaseDir, "1panel/geo/GeoIP.mmdb")
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},
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)
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)
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)
u.handleRollback(originalDir, 2)
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)
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
}
global.LOG.Info("upgrade successful!")
go writeLogs(req.Version)
_ = settingRepo.Update("SystemVersion", req.Version)
_ = settingRepo.Update("SystemStatus", "Free")
checkPointOfWal()
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
}
_, _ = cmd.ExecWithTimeOut("systemctl daemon-reload && systemctl restart 1panel.service", 1*time.Minute)
}()
return nil
}
func (u *UpgradeService) handleBackup(fileOp files.FileOp, originalDir string) error {
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/1panel", 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("/usr/local/bin/1pctl", originalDir); 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)
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
}
return nil
}
func (u *UpgradeService) handleRollback(originalDir string, errStep int) {
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")
_ = settingRepo.Update("SystemStatus", "Free")
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)
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)
}
}
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 err := systemctl.Restart("1panel"); err != nil {
global.LOG.Errorf("Service restart during rollback failed: %v", err)
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)
}
}
@@ -395,41 +357,5 @@ 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
}
-3
View File
@@ -2,7 +2,6 @@ package global
import (
"github.com/1Panel-dev/1Panel/backend/configs"
"github.com/1Panel-dev/1Panel/backend/init/auth"
"github.com/1Panel-dev/1Panel/backend/init/cache/badger_db"
"github.com/1Panel-dev/1Panel/backend/init/session/psession"
"github.com/dgraph-io/badger/v4"
@@ -29,8 +28,6 @@ var (
MonitorCronID cron.EntryID
OneDriveCronID cron.EntryID
IPTracker *auth.IPTracker
I18n *i18n.Localizer
I18nForCmd *i18n.Localizer
)
-99
View File
@@ -1,99 +0,0 @@
package auth
import (
"sync"
"time"
)
const (
MaxIPCount = 100
ExpireDuration = 30 * time.Minute
)
type IPRecord struct {
NeedCaptcha bool
LastUpdate time.Time
}
type IPTracker struct {
records map[string]*IPRecord
ipOrder []string
mu sync.RWMutex
}
func NewIPTracker() *IPTracker {
return &IPTracker{
records: make(map[string]*IPRecord),
ipOrder: make([]string, 0),
}
}
func (t *IPTracker) NeedCaptcha(ip string) bool {
t.mu.Lock()
defer t.mu.Unlock()
record, exists := t.records[ip]
if !exists {
return false
}
if time.Since(record.LastUpdate) > ExpireDuration {
t.removeIPUnsafe(ip)
return false
}
return record.NeedCaptcha
}
func (t *IPTracker) SetNeedCaptcha(ip string) {
t.mu.Lock()
defer t.mu.Unlock()
if record, exists := t.records[ip]; exists {
if time.Since(record.LastUpdate) > ExpireDuration {
t.removeIPUnsafe(ip)
} else {
record.NeedCaptcha = true
record.LastUpdate = time.Now()
return
}
}
if len(t.records) >= MaxIPCount {
t.removeOldestUnsafe()
}
t.records[ip] = &IPRecord{
NeedCaptcha: true,
LastUpdate: time.Now(),
}
t.ipOrder = append(t.ipOrder, ip)
}
func (t *IPTracker) Clear(ip string) {
t.mu.Lock()
defer t.mu.Unlock()
t.removeIPUnsafe(ip)
}
func (t *IPTracker) removeIPUnsafe(ip string) {
delete(t.records, ip)
for i, storedIP := range t.ipOrder {
if storedIP == ip {
t.ipOrder = append(t.ipOrder[:i], t.ipOrder[i+1:]...)
break
}
}
}
func (t *IPTracker) removeOldestUnsafe() {
if len(t.ipOrder) == 0 {
return
}
oldestIP := t.ipOrder[0]
delete(t.records, oldestIP)
t.ipOrder = t.ipOrder[1:]
}
-3
View File
@@ -108,9 +108,6 @@ func Init() {
migrations.AddLicenseVerify,
migrations.AddMcpServer,
migrations.AddPbootCMSPHPExtensions,
migrations.DeleteV2Openresty,
migrations.UpdateOnedrive,
})
if err := m.Migrate(); err != nil {
global.LOG.Error(err)
@@ -469,43 +469,3 @@ 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
},
}
var DeleteV2Openresty = &gormigrate.Migration{
ID: "20250701-delete-v2-openresty",
Migrate: func(tx *gorm.DB) error {
return tx.Where("version = '1.27.1.2-0-1-focal'").Delete(&model.AppDetail{}).Error
},
}
var UpdateOnedrive = &gormigrate.Migration{
ID: "20250704-update-onedrive",
Migrate: func(tx *gorm.DB) error {
if err := tx.Model(&model.Setting{}).
Where("key = ?", "OneDriveID").
Update("value", "NTQ0NmNmZTMtNGM3OS00N2EwLWFlMjUtZmM2NDU0NzhlMmQ5").Error; err != nil {
return err
}
if err := tx.Model(&model.Setting{}).
Where("key = ?", "OneDriveSc").
Update("value", "bGRlOFF+WEVrR1M0b25Vb1VsRWpMYzE2MW9rTXZEM25KdnZ1MGN6MA==").Error; err != nil {
return err
}
return nil
},
}
+4 -5
View File
@@ -3,15 +3,14 @@ package router
import (
"encoding/base64"
"fmt"
"github.com/1Panel-dev/1Panel/backend/app/service"
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/cmd/server/res"
"net/http"
"regexp"
"strconv"
"strings"
"github.com/1Panel-dev/1Panel/backend/app/service"
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/cmd/server/res"
"github.com/1Panel-dev/1Panel/backend/global"
"github.com/1Panel-dev/1Panel/backend/i18n"
"github.com/1Panel-dev/1Panel/backend/middleware"
@@ -161,7 +160,7 @@ func setWebStatic(rootRouter *gin.RouterGroup) {
}
func Routers() *gin.Engine {
Router = gin.New()
Router = gin.Default()
Router.Use(middleware.OperationLog())
// Router.Use(middleware.CSRF())
// Router.Use(middleware.LoadCsrfToken())
+1 -6
View File
@@ -8,17 +8,11 @@ import (
"github.com/1Panel-dev/1Panel/backend/app/repo"
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/global"
"github.com/1Panel-dev/1Panel/backend/utils/common"
"github.com/gin-gonic/gin"
)
func WhiteAllow() gin.HandlerFunc {
return func(c *gin.Context) {
clientIP := common.GetRealClientIP(c)
if common.IsPrivateIP(clientIP) {
c.Next()
return
}
settingRepo := repo.NewISettingRepo()
status, err := settingRepo.Get(settingRepo.WithByKey("AllowIPs"))
if err != nil {
@@ -30,6 +24,7 @@ func WhiteAllow() gin.HandlerFunc {
c.Next()
return
}
clientIP := c.ClientIP()
for _, ip := range strings.Split(status.Value, ",") {
if len(ip) == 0 {
continue
+1 -1
View File
@@ -16,7 +16,7 @@ func (s *BaseRouter) InitRouter(Router *gin.RouterGroup) {
baseRouter.POST("/login", baseApi.Login)
baseRouter.POST("/logout", baseApi.LogOut)
baseRouter.GET("/demo", baseApi.CheckIsDemo)
baseRouter.GET("/setting", baseApi.GetAuthSetting)
baseRouter.GET("/language", baseApi.GetLanguage)
baseRouter.GET("/intl", baseApi.CheckIsIntl)
}
}
-2
View File
@@ -13,7 +13,6 @@ import (
"github.com/1Panel-dev/1Panel/backend/i18n"
"github.com/1Panel-dev/1Panel/backend/init/app"
"github.com/1Panel-dev/1Panel/backend/init/auth"
"github.com/1Panel-dev/1Panel/backend/init/business"
"github.com/1Panel-dev/1Panel/backend/init/lang"
@@ -53,7 +52,6 @@ func Start() {
business.Init()
rootRouter := router.Routers()
global.IPTracker = auth.NewIPTracker()
tcpItem := "tcp4"
if global.CONF.System.Ipv6 == "enable" {
+3 -3
View File
@@ -11,13 +11,13 @@ import (
var store = base64Captcha.DefaultMemStore
func VerifyCode(codeID string, code string) error {
if codeID == "" {
return constant.ErrCaptchaCode
}
vv := store.Get(codeID, true)
vv = strings.TrimSpace(vv)
code = strings.TrimSpace(code)
if codeID == "" || code == "" {
return constant.ErrCaptchaCode
}
if strings.EqualFold(vv, code) {
return nil
}
+2 -73
View File
@@ -3,12 +3,13 @@ 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"
@@ -17,9 +18,6 @@ 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"
@@ -126,9 +124,6 @@ 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
@@ -157,57 +152,7 @@ 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, ".")
@@ -426,19 +371,3 @@ func HandleIPList(content string) ([]string, error) {
}
return res, nil
}
func GetRealClientIP(c *gin.Context) string {
addr := c.Request.RemoteAddr
if ip, _, err := net.SplitHostPort(addr); err == nil {
return ip
}
return addr
}
func IsPrivateIP(ipStr string) bool {
ip := net.ParseIP(ipStr)
if ip == nil {
return false
}
return ip.IsPrivate() || ip.IsLoopback()
}
+16 -20
View File
@@ -349,30 +349,26 @@ func (f FileOp) DownloadFileWithProxy(url, dst string) error {
}
func (f FileOp) Cut(oldPaths []string, dst, name string, cover bool) error {
if len(oldPaths) == 0 {
return nil
}
var dstPath string
coverFlag := ""
if name != "" {
dstPath = filepath.Join(dst, name)
if f.Stat(dstPath) {
dstPath = dst
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)
}
coverFlag := ""
if cover {
coverFlag = "-f"
}
} 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
cmdStr := fmt.Sprintf(`mv %s '%s' '%s'`, coverFlag, p, dstPath)
if err := cmd.ExecCmd(cmdStr); err != nil {
return err
}
}
return nil
}
+15 -4
View File
@@ -10,7 +10,6 @@ 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=(.*)$`)
@@ -42,15 +41,27 @@ func (f *Firewall) Version() (string, error) {
}
func (f *Firewall) Start() error {
return systemctl.Start("firewalld")
stdout, err := cmd.Exec("systemctl start firewalld")
if err != nil {
return fmt.Errorf("enable the firewall failed, err: %s", stdout)
}
return nil
}
func (f *Firewall) Stop() error {
return systemctl.Stop("firewalld")
stdout, err := cmd.Exec("systemctl stop firewalld")
if err != nil {
return fmt.Errorf("stop the firewall failed, err: %s", stdout)
}
return nil
}
func (f *Firewall) Restart() error {
return systemctl.Restart("firewalld")
stdout, err := cmd.Exec("systemctl restart firewalld")
if err != nil {
return fmt.Errorf("restart the firewall failed, err: %s", stdout)
}
return nil
}
func (f *Firewall) Reload() error {
+4 -13
View File
@@ -3,10 +3,14 @@ package ssl
import (
"crypto"
"encoding/json"
"github.com/go-acme/lego/v4/providers/dns/westcn"
"os"
"strings"
"time"
"github.com/go-acme/lego/v4/providers/dns/freemyip"
"github.com/go-acme/lego/v4/providers/dns/rainyun"
"github.com/1Panel-dev/1Panel/backend/app/model"
"github.com/go-acme/lego/v4/acme"
"github.com/go-acme/lego/v4/acme/api"
@@ -19,17 +23,13 @@ import (
"github.com/go-acme/lego/v4/providers/dns/cloudflare"
"github.com/go-acme/lego/v4/providers/dns/cloudns"
"github.com/go-acme/lego/v4/providers/dns/dnspod"
"github.com/go-acme/lego/v4/providers/dns/freemyip"
"github.com/go-acme/lego/v4/providers/dns/godaddy"
"github.com/go-acme/lego/v4/providers/dns/huaweicloud"
"github.com/go-acme/lego/v4/providers/dns/namecheap"
"github.com/go-acme/lego/v4/providers/dns/namedotcom"
"github.com/go-acme/lego/v4/providers/dns/namesilo"
"github.com/go-acme/lego/v4/providers/dns/porkbun"
"github.com/go-acme/lego/v4/providers/dns/rainyun"
"github.com/go-acme/lego/v4/providers/dns/tencentcloud"
"github.com/go-acme/lego/v4/providers/dns/volcengine"
"github.com/go-acme/lego/v4/providers/dns/westcn"
"github.com/go-acme/lego/v4/providers/http/webroot"
"github.com/go-acme/lego/v4/registration"
"github.com/pkg/errors"
@@ -87,7 +87,6 @@ const (
HuaweiCloud DnsType = "HuaweiCloud"
RainYun DnsType = "RainYun"
WestCN DnsType = "WestCN"
PorkBun DnsType = "PorkBun"
)
type DNSParam struct {
@@ -245,14 +244,6 @@ func (c *AcmeClient) UseDns(dnsType DnsType, params string, websiteSSL model.Web
westcnConfig.PollingInterval = pollingInterval
westcnConfig.TTL = ttl
p, err = westcn.NewDNSProviderConfig(westcnConfig)
case PorkBun:
porkbunConfig := porkbun.NewDefaultConfig()
porkbunConfig.APIKey = param.APIkey
porkbunConfig.SecretAPIKey = param.SecretKey
porkbunConfig.PropagationTimeout = propagationTimeout
porkbunConfig.PollingInterval = pollingInterval
porkbunConfig.TTL = ttl
p, err = porkbun.NewDNSProviderConfig(porkbunConfig)
}
if err != nil {
return err
-393
View File
@@ -1,393 +0,0 @@
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
}
-283
View File
@@ -1,283 +0,0 @@
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
}
-440
View File
@@ -1,440 +0,0 @@
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)
}
+31 -121
View File
@@ -2,153 +2,63 @@ package systemctl
import (
"fmt"
"os"
"github.com/1Panel-dev/1Panel/backend/global"
"github.com/pkg/errors"
"os/exec"
"strings"
)
func DefaultHandler(serviceName string) (*ServiceHandler, error) {
svcName, err := smartServiceName(serviceName)
func RunSystemCtl(args ...string) (string, error) {
cmd := exec.Command("systemctl", args...)
output, err := cmd.CombinedOutput()
if err != nil {
return nil, ErrServiceNotFound
return string(output), fmt.Errorf("failed to run command: %w", err)
}
return NewServiceHandler(defaultServiceConfig(svcName)), nil
return string(output), nil
}
func GetServiceName(serviceName string) (string, error) {
serviceName, err := smartServiceName(serviceName)
func IsActive(serviceName string) (bool, error) {
out, err := RunSystemCtl("is-active", serviceName)
if err != nil {
return "", ErrServiceNotFound
return false, err
}
return serviceName, nil
return out == "active\n", nil
}
func GetServicePath(serviceName string) (string, error) {
handler, err := DefaultHandler(serviceName)
func IsEnable(serviceName string) (bool, error) {
out, err := RunSystemCtl("is-enabled", serviceName)
if err != nil {
return "", ErrServiceNotFound
return false, err
}
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
return out == "enabled\n", nil
}
func IsExist(serviceName string) (bool, error) {
handler, err := DefaultHandler(serviceName)
out, err := RunSystemCtl("is-enabled", serviceName)
if err != nil {
if strings.Contains(out, "disabled") {
return true, nil
}
return false, nil
}
result, _ := handler.IsExists()
if result.IsExists {
return true, nil
} else {
return false, nil
}
return true, nil
}
func Start(serviceName string) error {
handler, _ := DefaultHandler(serviceName)
result, err := handler.StartService()
func handlerErr(out string, err error) error {
if err != nil {
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)
if out != "" {
return errors.New(out)
}
return err
}
return nil
}
func Restart(serviceName string) error {
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
out, err := RunSystemCtl("restart", 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
func Operate(operate, serviceName string) error {
out, err := RunSystemCtl(operate, serviceName)
return handlerErr(out, err)
}
+27 -115
View File
@@ -3,7 +3,6 @@ package toolbox
import (
"fmt"
"os"
"regexp"
"strings"
"github.com/1Panel-dev/1Panel/backend/global"
@@ -23,15 +22,15 @@ type FirewallClient interface {
}
func NewFail2Ban() (*Fail2ban, error) {
isExist, _ := systemctl.IsExist("fail2ban")
isExist, _ := systemctl.IsExist("fail2ban.service")
if isExist {
if _, err := os.Stat(defaultPath); err != nil {
if err := initLocalFile(); err != nil {
return nil, err
}
err := systemctl.Restart("fail2ban")
stdout, err := cmd.Exec("systemctl restart fail2ban.service")
if err != nil {
global.LOG.Errorf("restart fail2ban failed, err: %s", err)
global.LOG.Errorf("restart fail2ban failed, err: %s", stdout)
return nil, err
}
}
@@ -48,25 +47,20 @@ 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 "-"
}
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 "-"
return strings.ReplaceAll(stdout, "\n", "")
}
func (f *Fail2ban) Operate(operate string) error {
switch operate {
case "start", "restart", "stop", "enable", "disable":
stdout, err := systemctl.CustomAction(operate, "fail2ban")
stdout, err := cmd.Execf("systemctl %s fail2ban.service", operate)
if err != nil {
return fmt.Errorf("%s the fail2ban failed, err: %s", operate, stdout.Output)
return fmt.Errorf("%s the fail2ban.service failed, err: %s", operate, stdout)
}
return nil
case "reload":
@@ -142,32 +136,7 @@ func initLocalFile() error {
return err
}
defer f.Close()
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
initFile := `#DEFAULT-START
[DEFAULT]
bantime = 600
findtime = 300
@@ -177,93 +146,36 @@ action = %(action_mwl)s
#DEFAULT-END
[sshd]
ignoreip = 127.0.0.1/8 ::1
ignoreip = 127.0.0.1/8
enabled = true
filter = sshd
port = $ssh_port
port = 22
maxretry = 5
findtime = 300
bantime = 600
banaction = $banaction
action = %(action_mwl)s
logpath = $logpath
maxmatches = 3
`
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"
}
// 检测防火墙类型
banaction := detectFirewall()
// 检测SSH端口(支持Alpine的sshd_config位置)
sshPort := detectSSHPort()
// 检测日志路径(兼容Alpine的多种日志位置)
logPath := detectAuthLogPath()
// 执行变量替换
initFile = strings.ReplaceAll(initFile, "$banaction", banaction)
initFile = strings.ReplaceAll(initFile, "$logpath", logPath)
initFile = strings.ReplaceAll(initFile, "$ssh_port", sshPort)
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)
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端口
}
+3 -4
View File
@@ -4,6 +4,7 @@ import (
"bufio"
"errors"
"fmt"
"golang.org/x/crypto/bcrypt"
"os"
"os/user"
"path"
@@ -11,8 +12,6 @@ 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"
@@ -95,9 +94,9 @@ func (f *Ftp) Status() (bool, bool) {
func (f *Ftp) Operate(operate string) error {
switch operate {
case "start", "restart", "stop":
stdout, err := systemctl.CustomAction(operate, "pure-ftpd")
stdout, err := cmd.Execf("systemctl %s pure-ftpd.service", operate)
if err != nil {
return fmt.Errorf("%s the pure-ftpd service failed, err: %s", operate, stdout.Output)
return fmt.Errorf("%s the pure-ftpd.service failed, err: %s", operate, stdout)
}
return nil
default:
+13 -20
View File
@@ -6,32 +6,25 @@ command -v wget >/dev/null || {
}
if [ ! -f "1pctl" ]; then
wget https://github.com/1Panel-dev/installer/raw/v1/1pctl
wget https://github.com/1Panel-dev/installer/raw/dev/1pctl
fi
if [ ! -f "1panel.service" ]; then
wget https://github.com/1Panel-dev/installer/raw/dev/1panel.service
fi
if [ ! -f "install.sh" ]; then
wget https://github.com/1Panel-dev/installer/raw/v1/install.sh
fi
if [ ! -d "initscript" ]; then
wget https://github.com/1Panel-dev/installer/raw/v1/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/v1/initscript/$file
done
cd ..
fi
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/v1/lang/$lang.sh
done
cd ..
wget https://github.com/1Panel-dev/installer/raw/dev/install.sh
fi
if [ ! -f "GeoIP.mmdb" ]; then
wget https://resource.fit2cloud.com/1panel/package/geo/GeoIP.mmdb
wget https://resource.1panel.pro/geo/GeoIP.mmdb
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
fi
chmod 755 1pctl install.sh
+2 -16
View File
@@ -58,15 +58,8 @@ 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 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
}
if err := common.CopyFile(path.Join(tmpPath, "1panel.service"), "/etc/systemd/system"); err != nil {
return err
}
fmt.Println(i18n.GetMsgByKeyForCmd("RestoreStep4"))
checkPointOfWal()
@@ -131,10 +124,3 @@ func handleUnTar(sourceFile, targetDir string) error {
}
return nil
}
func isSystemd() bool {
if _, err := os.Stat("/etc/systemd/system"); err == nil {
return true
}
return false
}
-1
View File
@@ -28,4 +28,3 @@ stats.html
*.sln
*.sw?
.vscode
*.code-workspace
+4 -5
View File
@@ -38,9 +38,8 @@
"axios": "^1.7.2",
"codemirror": "^6.0.1",
"crypto-js": "^4.2.0",
"dompurify": "^3.3.1",
"echarts": "^5.5.0",
"element-plus": "2.9.9",
"element-plus": "^2.7.5",
"fit2cloud-ui-plus": "^1.2.0",
"highlight.js": "^11.9.0",
"js-base64": "^3.7.7",
@@ -65,8 +64,8 @@
"@types/node": "^20.15.0",
"@typescript-eslint/eslint-plugin": "^5.22.0",
"@typescript-eslint/parser": "^5.22.0",
"@vitejs/plugin-vue": "^6.0.1",
"@vitejs/plugin-vue-jsx": "^5.1.1",
"@vitejs/plugin-vue": "^5.0.5",
"@vitejs/plugin-vue-jsx": "^4.0.0",
"autoprefixer": "^10.4.7",
"commitizen": "^4.2.4",
"eslint": "^8.43.0",
@@ -85,7 +84,7 @@
"typescript": "^4.5.4",
"unplugin-auto-import": "^0.16.4",
"unplugin-vue-components": "^0.25.0",
"vite": "^7.1.5",
"vite": "^6.0.7",
"vite-plugin-compression": "^0.5.1",
"vite-plugin-eslint": "^1.8.1",
"vite-plugin-html": "^3.2.2",
+1 -4
View File
@@ -2,6 +2,7 @@ export namespace Login {
export interface ReqLoginForm {
name: string;
password: string;
ignoreCaptcha: boolean;
captcha: string;
captchaID: string;
authMethod: string;
@@ -25,8 +26,4 @@ export namespace Login {
export interface ResAuthButtons {
[propName: string]: any;
}
export interface LoginSetting {
language: string;
needCaptcha: boolean;
}
}
+3
View File
@@ -212,6 +212,9 @@ export namespace Container {
password: string;
auth: boolean;
}
export interface RepoDelete {
ids: Array<number>;
}
export interface RepoInfo {
id: number;
createdAt: Date;
+2 -2
View File
@@ -21,8 +21,8 @@ export const checkIsDemo = () => {
return http.get<boolean>('/auth/demo');
};
export const getAuthSetting = () => {
return http.get<Login.LoginSetting>(`/auth/setting`);
export const getLanguage = () => {
return http.get<string>(`/auth/language`);
};
export const checkIsIntl = () => {
+2 -2
View File
@@ -135,8 +135,8 @@ export const createImageRepo = (params: Container.RepoCreate) => {
export const updateImageRepo = (params: Container.RepoUpdate) => {
return http.post(`/containers/repo/update`, params, TimeoutEnum.T_40S);
};
export const deleteImageRepo = (id: Number) => {
return http.post(`/containers/repo/del`, { id: id }, TimeoutEnum.T_40S);
export const deleteImageRepo = (params: Container.RepoDelete) => {
return http.post(`/containers/repo/del`, params, TimeoutEnum.T_40S);
};
// composeTemplate
@@ -1,24 +0,0 @@
<template>
<MdEditor previewOnly v-model="sanitizedReadMe" :theme="isDarkTheme ? 'dark' : 'light'" />
</template>
<script lang="ts" setup>
import MdEditor from 'md-editor-v3';
import 'md-editor-v3/lib/style.css';
import DOMPurify from 'dompurify';
import { GlobalStore } from '@/store';
import { storeToRefs } from 'pinia';
import { computed } from 'vue';
const globalStore = GlobalStore();
const { isDarkTheme } = storeToRefs(globalStore);
const props = defineProps({
content: {
type: String,
default: '',
},
});
const sanitizedReadMe = computed(() => {
return DOMPurify.sanitize(props.content);
});
</script>
@@ -69,7 +69,7 @@
{{ upgradeInfo.testVersion }}
</el-radio>
</el-radio-group>
<MarkDownEditor :content="upgradeInfo.releaseNote" />
<MdEditor v-model="upgradeInfo.releaseNote" previewOnly :theme="isDarkTheme ? 'dark' : 'light'" />
</div>
<template #footer>
<span class="dialog-footer">
@@ -81,10 +81,10 @@
</template>
<script setup lang="ts">
import DrawerHeader from '@/components/drawer-header/index.vue';
import MarkDownEditor from '@/components/mkdown-editor/index.vue';
import { getSettingInfo, loadReleaseNotes, loadUpgradeInfo, upgrade } from '@/api/modules/setting';
import MdEditor from 'md-editor-v3';
import i18n from '@/lang';
import 'md-editor-v3/lib/style.css';
import { MsgSuccess } from '@/utils/message';
import { copyText } from '@/utils/util';
import { onMounted, ref, computed } from 'vue';
@@ -93,7 +93,7 @@ import { ElMessageBox } from 'element-plus';
import { storeToRefs } from 'pinia';
const globalStore = GlobalStore();
const { docsUrl } = storeToRefs(globalStore);
const { isDarkTheme, docsUrl } = storeToRefs(globalStore);
const mobile = computed(() => {
return globalStore.isMobile();
-4
View File
@@ -212,10 +212,6 @@ export const DNSTypes = [
label: i18n.global.t('website.westCN'),
value: 'WestCN',
},
{
label: 'PorkBun',
value: 'PorkBun',
},
{
label: i18n.global.t('website.volcengine'),
value: 'Volcengine',
+1 -4
View File
@@ -333,7 +333,6 @@ const message = {
ssl: 'Certificate | Certificates',
database: 'Database | Databases',
ai_tools: 'AI',
mcp: 'MCP',
container: 'Container | Containers',
cronjob: 'Cron Job | Cron Jobs',
host: 'Host | Hosts',
@@ -834,7 +833,6 @@ const message = {
repo: 'Registries',
createRepo: 'Add',
httpRepoHelper: 'Operating 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:
@@ -1386,8 +1384,7 @@ 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! \n',
coverDirHelper: 'The selected folders to be replaced will be copied to the destination path!',
existFileDirHelper: 'The selected file/folder has a duplicate name. Please proceed with caution!',
noSuchFile: 'The file or directory was not found. Please check and try again.',
},
ssh: {
-3
View File
@@ -333,7 +333,6 @@ const message = {
ssl: '証明書|証明書',
database: 'データベース|データベース',
ai_tools: 'AI',
mcp: 'MCP',
container: 'コンテナ|コンテナ',
cronjob: 'クロンジョブ|クロンの仕事',
host: 'ホスト|ホスト',
@@ -830,7 +829,6 @@ const message = {
repo: 'レジストリ',
createRepo: '追加',
httpRepoHelper: 'HTTPタイプのリポジトリを操作するにはDockerサービスの再起動が必要です。',
httpRepo:
'HTTPプロトコルを選択するには、Dockerサービスを再起動して不安定なレジストリに追加する必要があります。',
delInsecure: 'クレジットの削除',
@@ -1365,7 +1363,6 @@ const message = {
existFileHelper: 'アップロードしたファイルに同じ名前のファイルが含まれています。上書きしますか?',
existFileSize: 'ファイルサイズ(新しい -> 古い)',
existFileDirHelper: '選択したファイル/フォルダーには同じ名前のものが既に存在します。慎重に操作してください!',
coverDirHelper: '上書きするフォルダを選択すると、対象パスにコピーされます!',
noSuchFile: 'ファイルまたはディレクトリが見つかりませんでした。確認して再試行してください。',
},
ssh: {
-3
View File
@@ -334,7 +334,6 @@ const message = {
ssl: '인증서 | 인증서들',
database: '데이터베이스 | 데이터베이스들',
ai_tools: 'AI',
mcp: 'MCP',
container: '컨테이너 | 컨테이너들',
cronjob: '크론 작업 | 크론 작업들',
host: '호스트 | 호스트들',
@@ -820,7 +819,6 @@ const message = {
repo: '레지스트리',
createRepo: '추가',
httpRepoHelper: 'HTTP 타입 저장소 작업 시 Docker 서비스 재시작이 필요합니다.',
httpRepo: 'HTTP 프로토콜을 선택하면 Docker 서비스를 재시작하여 불안정한 레지스트리에 추가해야 합니다.',
delInsecure: '신뢰할 수 없는 항목 삭제',
delInsecureHelper:
@@ -1351,7 +1349,6 @@ const message = {
existFileHelper: '업로드한 파일에 동일한 이름의 파일이 포함되어 있습니다. 덮어쓰시겠습니까?',
existFileSize: '파일 크기 (새로운 -> 오래된)',
existFileDirHelper: '선택한 파일/폴더에 동일한 이름이 이미 존재합니다. 신중하게 작업하세요!',
coverDirHelper: '덮어쓸 폴더를 선택하면 대상 경로로 복사됩니다!',
noSuchFile: '파일 또는 디렉터리를 찾을 수 없습니다. 확인 후 다시 시도하세요.',
},
ssh: {
+1 -4
View File
@@ -340,7 +340,6 @@ const message = {
ssl: 'Certificate | Certificates',
database: 'Database | Databases',
ai_tools: 'AI',
mcp: 'MCP',
container: 'Container | Containers',
cronjob: 'Cron Job | Cron Jobs',
host: 'Host | Hosts',
@@ -846,7 +845,6 @@ const message = {
repo: 'Pendaftaran',
createRepo: 'Tambah',
httpRepoHelper: 'Mengoperasikan 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',
@@ -1407,8 +1405,7 @@ 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!\n',
coverDirHelper: 'Folder yang dipilih untuk ditimpa akan disalin ke laluan destinasi!',
existFileDirHelper: 'Fail/folder yang dipilih mempunyai nama yang sama. Sila berhati-hati!',
noSuchFile: 'Fail atau direktori tidak ditemui. Sila periksa dan cuba lagi.',
},
ssh: {
+1 -4
View File
@@ -338,7 +338,6 @@ const message = {
ssl: 'Certificado | Certificados',
database: 'Banco de Dados | Bancos de Dados',
ai_tools: 'AI',
mcp: 'MCP',
container: 'Container | Containers',
cronjob: 'Tarefa Cron | Tarefas Cron',
host: 'Host | Hosts',
@@ -842,7 +841,6 @@ const message = {
repo: 'Registries',
createRepo: 'Adicionar',
httpRepoHelper: 'Operar 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',
@@ -1394,8 +1392,7 @@ 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!\n',
coverDirHelper: 'As pastas selecionadas para substituição serão copiadas para o caminho de destino!',
existFileDirHelper: 'O arquivo/pasta selecionado tem um nome duplicado. Por favor, prossiga com cautela!',
noSuchFile: 'O arquivo ou diretório não foi encontrado. Por favor, verifique e tente novamente.',
},
ssh: {
+1 -4
View File
@@ -334,7 +334,6 @@ const message = {
ssl: 'Сертификат | Сертификаты',
database: 'База данных | Базы данных',
ai_tools: 'AI',
mcp: 'MCP',
container: 'Контейнер | Контейнеры',
cronjob: 'Cron | Задачи Cron',
host: 'Хост | Хосты',
@@ -844,7 +843,6 @@ const message = {
repo: 'Реестры',
createRepo: 'Добавить',
httpRepoHelper: 'Работа с репозиторием HTTP-типа требует перезапуска службы Docker.',
httpRepo: 'Выбор HTTP протокола требует перезапуска службы Docker для добавления в небезопасные реестры.',
delInsecure: 'Удаление учетных данных',
delInsecureHelper: 'Это перезапустит службу Docker для удаления из небезопасных реестров. Хотите продолжить?',
@@ -1396,8 +1394,7 @@ const message = {
existFileTitle: 'Предупреждение о файле с тем же именем',
existFileHelper: 'Загруженный файл содержит файл с таким же именем. Заменить его?',
existFileSize: 'Размер файла (новый -> старый)',
existFileDirHelper: 'Выбранный файл/папка имеет дублирующееся имя. Пожалуйста, действуйте осторожно!\n',
coverDirHelper: 'При выборе перезаписываемой папки она будет скопирована в целевой путь!',
existFileDirHelper: 'Выбранный файл/папка имеет дублирующееся имя. Пожалуйста, действуйте осторожно!',
noSuchFile: 'Файл или каталог не найдены. Пожалуйста, проверьте и повторите попытку.',
},
ssh: {
-3
View File
@@ -327,7 +327,6 @@ const message = {
ssl: '證書',
database: '資料庫',
ai_tools: 'AI',
mcp: 'MCP',
container: '容器',
cronjob: '計劃任務',
host: '主機',
@@ -805,7 +804,6 @@ const message = {
repo: '倉庫',
createRepo: '新增倉庫',
httpRepoHelper: '操作 HTTP 類型倉庫需要重啟 Docker 服務。',
downloadUrl: '下載網址',
imageRepo: '鏡像倉庫',
repoHelper: '是否包含鏡像倉庫/組織/項目?',
@@ -1321,7 +1319,6 @@ const message = {
existFileHelper: '上傳的檔案存在同名檔案,是否覆蓋?',
existFileSize: '文件大小(新->舊)',
existFileDirHelper: '選擇的檔案/資料夾存在同名,請謹慎操作!',
coverDirHelper: '選取要覆蓋的資料夾,將複製到目標路徑!',
noSuchFile: '找不到該檔案或目錄,請檢查後重試。',
},
ssh: {
-3
View File
@@ -326,7 +326,6 @@ const message = {
ssl: '证书',
database: '数据库',
ai_tools: 'AI',
mcp: 'MCP',
container: '容器',
cronjob: '计划任务',
host: '主机',
@@ -807,7 +806,6 @@ const message = {
repo: '仓库',
createRepo: '添加仓库',
httpRepoHelper: '操作 http 类型仓库需要重启 Docker 服务。',
downloadUrl: '下载地址',
imageRepo: '镜像仓库',
repoHelper: '是否包含镜像仓库/组织/项目?',
@@ -1323,7 +1321,6 @@ const message = {
existFileHelper: '上传的文件存在同名文件,是否覆盖?',
existFileSize: '文件大小 (新 -> 旧)',
existFileDirHelper: '选择的文件/文件夹存在同名,请谨慎操作!',
coverDirHelper: '选中覆盖的文件夹,将复制到目标路径!',
noSuchFile: '未能找到该文件或目录,请检查后重试',
},
ssh: {
+1 -1
View File
@@ -24,7 +24,7 @@ const databaseRouter = {
name: 'MCPServer',
component: () => import('@/views/ai/mcp/server/index.vue'),
meta: {
title: 'menu.mcp',
title: 'MCP',
requiresAuth: true,
},
},
@@ -72,18 +72,21 @@
</div>
</div>
</div>
<MarkDownEditor :content="app.readMe" />
<MdEditor previewOnly v-model="app.readMe" :theme="isDarkTheme ? 'dark' : 'light'" />
</el-drawer>
<Install ref="installRef"></Install>
</template>
<script lang="ts" setup>
import MarkDownEditor from '@/components/mkdown-editor/index.vue';
import { GetApp, GetAppDetail } from '@/api/modules/app';
import MdEditor from 'md-editor-v3';
import { ref } from 'vue';
import Install from './install/index.vue';
import router from '@/routers';
import { GlobalStore } from '@/store';
import { storeToRefs } from 'pinia';
const globalStore = GlobalStore();
const { isDarkTheme } = storeToRefs(globalStore);
const app = ref<any>({});
const appDetail = ref<any>({});
+4 -28
View File
@@ -61,9 +61,8 @@
</template>
</LayoutContent>
<OpDialog ref="opRef" @search="search" @submit="submitDelete" />
<OpDialog ref="opRef" @search="search" />
<OperatorDialog @search="search" ref="dialogRef" />
<ConfirmDialog ref="confirmDialog" @confirm="submitDelete" />
</div>
</template>
@@ -88,8 +87,6 @@ const paginationConfig = reactive({
const searchName = ref();
const opRef = ref();
const confirmDialog = ref();
const currentRepo = ref();
const dockerStatus = ref('Running');
const loadStatus = async () => {
@@ -144,16 +141,7 @@ const onOpenDialog = async (
};
const onDelete = async (row: Container.RepoInfo) => {
currentRepo.value = row.id;
if (row.protocol === 'http') {
let params = {
header: i18n.global.t('container.repo'),
operationInfo: i18n.global.t('container.httpRepoHelper'),
submitInputInfo: i18n.global.t('database.restartNow'),
};
confirmDialog.value!.acceptParams(params);
return;
}
let ids = [row.id];
opRef.value.acceptParams({
title: i18n.global.t('commons.button.delete'),
names: [row.name],
@@ -161,23 +149,11 @@ const onDelete = async (row: Container.RepoInfo) => {
i18n.global.t('container.repo'),
i18n.global.t('commons.button.delete'),
]),
api: null,
params: null,
api: deleteImageRepo,
params: { ids: ids },
});
};
const submitDelete = async () => {
loading.value = true;
await deleteImageRepo(currentRepo.value)
.then(() => {
loading.value = false;
search();
})
.catch(() => {
loading.value = false;
});
};
const onCheckConn = async (row: Container.RepoInfo) => {
loading.value = true;
await checkRepoStatus(row.id)
@@ -81,8 +81,6 @@
</span>
</template>
</el-drawer>
<ConfirmDialog ref="confirmDialog" @confirm="submit" />
</template>
<script lang="ts" setup>
@@ -107,14 +105,8 @@ const drawerVisible = ref(false);
const dialogData = ref<DialogProps>({
title: '',
});
const confirmDialog = ref();
const oldUrl = ref();
const oldProto = ref();
const acceptParams = (params: DialogProps): void => {
dialogData.value = params;
oldUrl.value = params.rowData.downloadUrl;
oldProto.value = params.rowData.protocol;
title.value = i18n.global.t('commons.button.' + dialogData.value.title);
drawerVisible.value = true;
};
@@ -122,7 +114,6 @@ const emit = defineEmits<{ (e: 'search'): void }>();
const handleClose = () => {
drawerVisible.value = false;
emit('search');
};
const rules = reactive({
name: [Rules.requiredInput, Rules.name],
@@ -151,33 +142,21 @@ const onSubmit = async (formEl: FormInstance | undefined) => {
if (!formEl) return;
formEl.validate(async (valid) => {
if (!valid) return;
let newProto = dialogData.value.rowData.protocol;
if (newProto === 'https' && dialogData.value.title === 'add') {
submit();
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;
});
return;
}
if (newProto === oldProto.value) {
if (
(oldProto.value === 'http' && dialogData.value.rowData.downloadUrl === oldUrl.value) ||
oldProto.value === 'https'
) {
submit();
return;
}
}
let params = {
header: i18n.global.t('container.repo'),
operationInfo: i18n.global.t('container.httpRepoHelper'),
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!)
await updateImageRepo(dialogData.value.rowData!)
.then(() => {
loading.value = false;
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
@@ -187,19 +166,9 @@ const submit = async () => {
.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,
});
+2 -3
View File
@@ -452,11 +452,10 @@ 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/legacy-modes/mode/javascript';
import { javascript } from '@codemirror/lang-javascript';
import { oneDark } from '@codemirror/theme-one-dark';
import { StreamLanguage } from '@codemirror/language';
const extensions = [StreamLanguage.define(javascript), oneDark];
const extensions = [javascript(), oneDark];
const router = useRouter();
+5 -15
View File
@@ -289,7 +289,6 @@ const chartOption = ref('network');
let timer: NodeJS.Timer | null = null;
let isInit = ref<boolean>(true);
let isActive = ref(true);
let isCurrentActive = ref(true);
const ioReadBytes = ref<Array<number>>([]);
const ioWriteBytes = ref<Array<number>>([]);
@@ -427,20 +426,12 @@ const onLoadBaseInfo = async (isInit: boolean, range: string) => {
currentInfo.value.uptime = resData.uptime;
loadAppCurrentInfo();
statusRef.value?.acceptParams(currentInfo.value, baseInfo.value);
appRef.value?.acceptParams();
statusRef.value.acceptParams(currentInfo.value, baseInfo.value);
appRef.value.acceptParams();
if (isInit) {
timer = setInterval(async () => {
try {
if (!isCurrentActive.value) {
throw new Error('jump out');
}
if (isActive.value && !globalStore.isOnRestart) {
await loadAppCurrentInfo();
}
} catch {
clearInterval(Number(timer));
timer = null;
if (isActive.value && !globalStore.isOnRestart) {
loadAppCurrentInfo();
}
}, 3000);
}
@@ -448,7 +439,7 @@ const onLoadBaseInfo = async (isInit: boolean, range: string) => {
const loadAppCurrentInfo = async () => {
await Promise.all([onLoadCurrentInfo('gpu'), onLoadCurrentInfo('basic'), onLoadCurrentInfo('ioNet')]);
statusRef.value?.acceptParams(currentInfo.value, baseInfo.value);
statusRef.value.acceptParams(currentInfo.value, baseInfo.value);
};
const onLoadCurrentInfo = async (scope: string) => {
@@ -670,7 +661,6 @@ onMounted(() => {
onBeforeUnmount(() => {
window.removeEventListener('focus', onFocus);
window.removeEventListener('blur', onBlur);
isCurrentActive.value = false;
clearInterval(Number(timer));
timer = null;
});
@@ -4,7 +4,7 @@
:destroy-on-close="true"
:close-on-click-modal="false"
:close-on-press-escape="false"
size="690"
size="675"
>
<template #header>
<DrawerHeader :header="title" :back="handleClose" />
@@ -19,16 +19,6 @@
: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>
@@ -44,12 +34,10 @@
</el-radio-group>
</div>
<div v-if="existFiles.length > 0 && !changeName" class="text-center">
<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 :show-icon="true" type="warning" :closable="false">
<div class="whitespace-break-spaces">
<span>{{ $t('file.existFileDirHelper') }}</span>
</div>
</el-alert>
<el-transfer
v-model="skipFiles"
@@ -86,7 +74,6 @@ 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>;
@@ -103,7 +90,7 @@ const open = ref(false);
const type = ref('cut');
const changeName = ref(false);
const oldName = ref('');
const existFiles = ref<File.ExistFileInfo[]>([]);
const existFiles = ref([]);
const skipFiles = ref([]);
const transferData = ref([]);
@@ -123,7 +110,6 @@ const addForm = reactive({
allNames: [] as string[],
isDir: false,
cover: false,
coverPaths: [] as string[],
});
const rules = reactive<FormRules>({
@@ -150,20 +136,7 @@ const getFileName = (filePath: string) => {
};
const coverFiles: ComputedRef<string[]> = computed(() => {
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);
});
return addForm.oldPaths.filter((item) => !skipFiles.value.includes(getFileName(item))).map((item) => item);
});
const getPath = (path: string) => {
@@ -200,8 +173,7 @@ const submit = async (formEl: FormInstance | undefined) => {
return;
}
loading.value = true;
addForm.coverPaths = coverFiles.value;
addForm.oldPaths = mvFiles.value;
addForm.oldPaths = coverFiles.value;
mvFile();
});
};
@@ -260,7 +232,6 @@ 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);
@@ -290,7 +261,7 @@ defineExpose({ acceptParams });
<style lang="scss" scoped>
:deep(.el-transfer) {
--el-transfer-panel-width: 260px;
--el-transfer-panel-width: 250px;
.el-button {
padding: 4px 7px;
}
@@ -176,7 +176,7 @@
import { ref, reactive, onMounted, computed, nextTick } from 'vue';
import { useRouter } from 'vue-router';
import type { ElForm } from 'element-plus';
import { loginApi, getCaptcha, mfaLoginApi, checkIsDemo, getAuthSetting, checkIsIntl } from '@/api/modules/auth';
import { loginApi, getCaptcha, mfaLoginApi, checkIsDemo, getLanguage, checkIsIntl } from '@/api/modules/auth';
import { GlobalStore, MenuStore, TabsStore } from '@/store';
import { MsgSuccess } from '@/utils/message';
import { useI18n } from 'vue-i18n';
@@ -318,6 +318,7 @@ const login = (formEl: FormInstance | undefined) => {
let requestLoginForm = {
name: loginForm.name,
password: encryptPassword(loginForm.password),
ignoreCaptcha: globalStore.ignoreCaptcha,
captcha: loginForm.captcha,
captchaID: captcha.captchaID,
authMethod: 'session',
@@ -399,12 +400,11 @@ const checkIsSystemDemo = async () => {
isDemo.value = res.data;
};
const loadLoginSetting = async () => {
const loadLanguage = async () => {
try {
const res = await getAuthSetting();
loginForm.language = res.data.language;
globalStore.ignoreCaptcha = !res.data.needCaptcha;
handleCommand(loginForm.language);
const res = await getLanguage();
loginForm.language = res.data;
handleCommand(res.data);
} catch (error) {}
};
@@ -426,7 +426,7 @@ onMounted(() => {
globalStore.isOnRestart = false;
checkIsSystemIntl();
loginVerify();
loadLoginSetting();
loadLanguage();
document.title = globalStore.themeConfig.panelName;
loginForm.agreeLicense = globalStore.agreeLicense;
checkIsSystemDemo();
@@ -138,14 +138,6 @@
<el-input v-model.trim="account.authorization['password']"></el-input>
</el-form-item>
</div>
<div v-if="account.type === 'PorkBun'">
<el-form-item label="API Key" prop="authorization.apiKey">
<el-input v-model.trim="account.authorization['apiKey']"></el-input>
</el-form-item>
<el-form-item label="Secret Key" prop="authorization.secretKey">
<el-input v-model.trim="account.authorization['secretKey']"></el-input>
</el-form-item>
</div>
</el-form>
</el-col>
</el-row>
+12 -12
View File
@@ -1,8 +1,8 @@
module github.com/1Panel-dev/1Panel
go 1.23.0
go 1.23
toolchain go1.24.1
toolchain go1.23.7
require (
github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible
@@ -54,14 +54,13 @@ require (
github.com/swaggo/swag v1.16.3
github.com/tencentyun/cos-go-sdk-v5 v0.7.46
github.com/xlzd/gotp v0.1.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/crypto v0.32.0
golang.org/x/net v0.37.0
golang.org/x/oauth2 v0.24.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
golang.org/x/sys v0.29.0
golang.org/x/term v0.28.0
golang.org/x/text v0.21.0
google.golang.org/genproto v0.0.0-20241021214115-324edc3d5d38
gopkg.in/ini.v1 v1.67.0
gopkg.in/yaml.v3 v3.0.1
gorm.io/gorm v1.25.7
@@ -173,6 +172,7 @@ 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
@@ -200,7 +200,6 @@ require (
github.com/nrdcg/freemyip v0.3.0 // indirect
github.com/nrdcg/mailinabox v0.2.0 // indirect
github.com/nrdcg/namesilo v0.2.1 // indirect
github.com/nrdcg/porkbun v0.4.0 // indirect
github.com/nwaples/rardecode/v2 v2.0.0-beta.2 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b // indirect
@@ -224,7 +223,7 @@ 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.1115 // 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
@@ -256,13 +255,14 @@ 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 v0.0.0-20241021214115-324edc3d5d38 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20241118233622-e639e219e697 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576 // indirect
google.golang.org/grpc v1.67.1 // indirect
+14 -14
View File
@@ -652,6 +652,8 @@ 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=
@@ -762,8 +764,6 @@ github.com/nrdcg/mailinabox v0.2.0 h1:IKq8mfKiVwNW2hQii/ng1dJ4yYMMv3HAP3fMFIq2CF
github.com/nrdcg/mailinabox v0.2.0/go.mod h1:0yxqeYOiGyxAu7Sb94eMxHPIOsPYXAjTeA9ZhePhGnc=
github.com/nrdcg/namesilo v0.2.1 h1:kLjCjsufdW/IlC+iSfAqj0iQGgKjlbUUeDJio5Y6eMg=
github.com/nrdcg/namesilo v0.2.1/go.mod h1:lwMvfQTyYq+BbjJd30ylEG4GPSS6PII0Tia4rRpRiyw=
github.com/nrdcg/porkbun v0.4.0 h1:rWweKlwo1PToQ3H+tEO9gPRW0wzzgmI/Ob3n2Guticw=
github.com/nrdcg/porkbun v0.4.0/go.mod h1:/QMskrHEIM0IhC/wY7iTCUgINsxdT2WcOphktJ9+Q54=
github.com/nwaples/rardecode/v2 v2.0.0-beta.2 h1:e3mzJFJs4k83GXBEiTaQ5HgSc/kOK8q0rDaRO0MPaOk=
github.com/nwaples/rardecode/v2 v2.0.0-beta.2/go.mod h1:yntwv/HfMc/Hbvtq9I19D1n58te3h6KsqCf3GxyfBGY=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
@@ -966,8 +966,8 @@ 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/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.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/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/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.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs=
golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ=
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
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.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
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/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.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
golang.org/x/sys v0.30.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/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.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU=
golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
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/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.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
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/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=