mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2026-10-12 08:00:34 +00:00
Compare commits
79
Commits
v1.9.5
...
v1.10.1-lts
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fe717d993b | ||
|
|
703da8a09a | ||
|
|
a99f19471b | ||
|
|
ca3be03ae7 | ||
|
|
71ca8016e1 | ||
|
|
5633bdd41e | ||
|
|
ab11d7257c | ||
|
|
39b8de7ada | ||
|
|
c49c4b7863 | ||
|
|
d42df7184c | ||
|
|
e7f172520a | ||
|
|
dca29174ea | ||
|
|
94181538a9 | ||
|
|
e9cc7bf1cf | ||
|
|
fa1e945b8d | ||
|
|
94702a79a4 | ||
|
|
24f573f9fd | ||
|
|
7e4606eda5 | ||
|
|
73e118a3e5 | ||
|
|
514d68d395 | ||
|
|
d0bb07796b | ||
|
|
66a451fc1f | ||
|
|
c13e2a36cc | ||
|
|
2affd2bc79 | ||
|
|
c510a028db | ||
|
|
121a1ff5da | ||
|
|
1ecc53dad3 | ||
|
|
279b3942b4 | ||
|
|
a0af04bbf7 | ||
|
|
d5d1125660 | ||
|
|
08801ebfcd | ||
|
|
262cbb9405 | ||
|
|
96233ee9b2 | ||
|
|
49dcb094dc | ||
|
|
f0d5aca657 | ||
|
|
58f58f0fac | ||
|
|
3bd6f46fe2 | ||
|
|
e427ccf70c | ||
|
|
1b47e8dd63 | ||
|
|
b668f55222 | ||
|
|
9a127d9afe | ||
|
|
63484601b5 | ||
|
|
ef5e42cf0b | ||
|
|
50a83e7e11 | ||
|
|
2475c82a63 | ||
|
|
61ff55e157 | ||
|
|
dbc7799e89 | ||
|
|
6275e06806 | ||
|
|
e4f9ec32cd | ||
|
|
319c21fc36 | ||
|
|
e346442d36 | ||
|
|
1781362342 | ||
|
|
3035bb2bf6 | ||
|
|
b896a5be3b | ||
|
|
d51abd529a | ||
|
|
550eed7962 | ||
|
|
51d09b7164 | ||
|
|
046d8f526b | ||
|
|
5bf121b687 | ||
|
|
e6a43d22c8 | ||
|
|
3fd7aa17a8 | ||
|
|
6a903b7fdf | ||
|
|
958f9a1aad | ||
|
|
e48830cd99 | ||
|
|
ece2cb433d | ||
|
|
fb06e29aa3 | ||
|
|
4fc26a3061 | ||
|
|
d8669b90bf | ||
|
|
9e980e8e0c | ||
|
|
140cd564da | ||
|
|
b40087c88c | ||
|
|
8e620172e6 | ||
|
|
9b8b9f505b | ||
|
|
1169648162 | ||
|
|
c169f211a0 | ||
|
|
eef445c1eb | ||
|
|
bedb8ce286 | ||
|
|
3652c46ac9 | ||
|
|
0449868d3b |
@@ -15,7 +15,7 @@ jobs:
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '18.14'
|
||||
node-version: '20.2'
|
||||
- name: Build Web
|
||||
run: |
|
||||
cd frontend && npm install && npm run build:pro
|
||||
@@ -24,7 +24,7 @@ jobs:
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: '1.20.x'
|
||||
go-version: '1.21.x'
|
||||
- name: Build Release
|
||||
uses: goreleaser/goreleaser-action@v4
|
||||
with:
|
||||
|
||||
@@ -39,6 +39,13 @@ frontend/auto-imports.d.ts
|
||||
frontend/components.d.ts
|
||||
frontend/src/xpack
|
||||
backend/xpack
|
||||
backend/router/entry_xpack.go
|
||||
backend/server/init_xpack.go
|
||||
plugins/openresty/waf/db
|
||||
plugins/openresty/waf/data
|
||||
plugins/openresty/waf/sites
|
||||
plugins/openresty/waf/conf/websites.json
|
||||
|
||||
|
||||
.history/
|
||||
dist/
|
||||
|
||||
@@ -11,13 +11,13 @@
|
||||
|
||||
------------------------------
|
||||
|
||||
1Panel 是一个现代化、开源的 Linux 服务器运维管理面板。1Panel 的功能和优势包括:
|
||||
1Panel 是新一代的 Linux 服务器运维管理面板。
|
||||
|
||||
- **快速建站**:深度集成 Wordpress 和 [Halo](https://github.com/halo-dev/halo/),域名绑定、SSL 证书配置等一键搞定;
|
||||
- **高效管理**:通过 Web 端轻松管理 Linux 服务器,包括主机监控、文件管理、数据库管理、容器管理等;
|
||||
- **安全可靠**:基于容器来管理和部署应用,最小漏洞暴露面,提供防火墙和日志审计等功能;
|
||||
- **一键备份**:支持一键备份和恢复,备份数据到各类云端存储,永不丢失;
|
||||
- **应用商店**:精选各类高质量开源工具和应用软件,助力您轻松安装和升级。
|
||||
- **高效管理**:用户可以通过 Web 图形界面轻松管理 Linux 服务器,实现主机监控、文件管理、数据库管理、容器管理等功能;
|
||||
- **快速建站**:深度集成开源建站软件 WordPress 和 [Halo](https://github.com/halo-dev/halo/),域名绑定、SSL 证书配置等操作一键搞定;
|
||||
- **应用商店**:精选上架各类高质量的开源工具和应用软件,协助用户轻松安装并升级;
|
||||
- **安全可靠**:基于容器管理并部署应用,实现最小的漏洞暴露面,同时提供防火墙和日志审计等功能;
|
||||
- **一键备份**:支持一键备份和恢复,用户可以将数据备份到各类云端存储介质,永不丢失。
|
||||
|
||||
## UI 展示
|
||||
|
||||
|
||||
@@ -101,11 +101,7 @@ func (b *BaseApi) LoadRecordLog(c *gin.Context) {
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
content, err := cronjobService.LoadRecordLog(req)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
content := cronjobService.LoadRecordLog(req)
|
||||
helper.SuccessWithData(c, content)
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,22 @@ import (
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// @Tags Dashboard
|
||||
// @Summary Load os info
|
||||
// @Description 获取服务器基础数据
|
||||
// @Accept json
|
||||
// @Success 200 {object} dto.OsInfo
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /dashboard/base/os [get]
|
||||
func (b *BaseApi) LoadDashboardOsInfo(c *gin.Context) {
|
||||
data, err := dashboardService.LoadOsInfo()
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, data)
|
||||
}
|
||||
|
||||
// @Tags Dashboard
|
||||
// @Summary Load dashboard base info
|
||||
// @Description 获取首页基础数据
|
||||
|
||||
@@ -164,24 +164,6 @@ func (b *BaseApi) SearchPostgresql(c *gin.Context) {
|
||||
})
|
||||
}
|
||||
|
||||
// @Tags Database Postgresql
|
||||
// @Summary List postgresql database names
|
||||
// @Description 获取 postgresql 数据库列表
|
||||
// @Accept json
|
||||
// @Param request body dto.PageInfo true "request"
|
||||
// @Success 200 {array} dto.PostgresqlOption
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /databases/pg/options [get]
|
||||
func (b *BaseApi) ListPostgresqlDBName(c *gin.Context) {
|
||||
list, err := postgresqlService.ListDBOption()
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
|
||||
helper.SuccessWithData(c, list)
|
||||
}
|
||||
|
||||
// @Tags Database Postgresql
|
||||
// @Summary Load postgresql database from remote
|
||||
// @Description 从服务器获取
|
||||
|
||||
@@ -38,8 +38,8 @@ type ContainerInfo struct {
|
||||
}
|
||||
|
||||
type ResourceLimit struct {
|
||||
CPU int `json:"cpu"`
|
||||
Memory int `json:"memory"`
|
||||
CPU int `json:"cpu"`
|
||||
Memory uint64 `json:"memory"`
|
||||
}
|
||||
|
||||
type ContainerOperate struct {
|
||||
|
||||
@@ -24,6 +24,14 @@ type DashboardBase struct {
|
||||
CurrentInfo DashboardCurrent `json:"currentInfo"`
|
||||
}
|
||||
|
||||
type OsInfo struct {
|
||||
OS string `json:"os"`
|
||||
Platform string `json:"platform"`
|
||||
PlatformFamily string `json:"platformFamily"`
|
||||
KernelArch string `json:"kernelArch"`
|
||||
KernelVersion string `json:"kernelVersion"`
|
||||
}
|
||||
|
||||
type DashboardCurrent struct {
|
||||
Uptime uint64 `json:"uptime"`
|
||||
TimeSinceUptime string `json:"timeSinceUptime"`
|
||||
|
||||
@@ -64,7 +64,7 @@ type FileDeCompress struct {
|
||||
|
||||
type FileEdit struct {
|
||||
Path string `json:"path" validate:"required"`
|
||||
Content string `json:"content" validate:"required"`
|
||||
Content string `json:"content"`
|
||||
}
|
||||
|
||||
type FileRename struct {
|
||||
|
||||
@@ -397,7 +397,7 @@ func (a AppService) Install(ctx context.Context, req request.AppInstallCreate) (
|
||||
}
|
||||
index++
|
||||
}
|
||||
if app.Limit == 0 && appInstall.Name != serviceName {
|
||||
if app.Limit == 0 && appInstall.Name != serviceName && len(servicesMap) == 1 {
|
||||
servicesMap[appInstall.Name] = servicesMap[serviceName]
|
||||
delete(servicesMap, serviceName)
|
||||
serviceName = appInstall.Name
|
||||
|
||||
@@ -12,8 +12,6 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/i18n"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/files"
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
@@ -152,7 +150,7 @@ func (a *AppInstallService) CheckExist(req request.AppInstalledInfo) (*response.
|
||||
if reflect.DeepEqual(appInstall, model.AppInstall{}) {
|
||||
return res, nil
|
||||
}
|
||||
if err = syncByID(appInstall.ID); err != nil {
|
||||
if err = syncAppInstallStatus(&appInstall); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -242,26 +240,26 @@ func (a *AppInstallService) Operate(req request.AppInstalledOperate) error {
|
||||
if err != nil {
|
||||
return handleErr(install, err, out)
|
||||
}
|
||||
return syncByID(install.ID)
|
||||
return syncAppInstallStatus(&install)
|
||||
case constant.Stop:
|
||||
out, err := compose.Stop(dockerComposePath)
|
||||
if err != nil {
|
||||
return handleErr(install, err, out)
|
||||
}
|
||||
return syncByID(install.ID)
|
||||
return syncAppInstallStatus(&install)
|
||||
case constant.Restart:
|
||||
out, err := compose.Restart(dockerComposePath)
|
||||
if err != nil {
|
||||
return handleErr(install, err, out)
|
||||
}
|
||||
return syncByID(install.ID)
|
||||
return syncAppInstallStatus(&install)
|
||||
case constant.Delete:
|
||||
if err := deleteAppInstall(install, req.DeleteBackup, req.ForceDelete, req.DeleteDB); err != nil && !req.ForceDelete {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
case constant.Sync:
|
||||
return syncByID(install.ID)
|
||||
return syncAppInstallStatus(&install)
|
||||
case constant.Upgrade:
|
||||
return upgradeInstall(install.ID, req.DetailId, req.Backup, req.PullImage)
|
||||
case constant.Reload:
|
||||
@@ -413,7 +411,7 @@ func (a *AppInstallService) SyncAll(systemInit bool) error {
|
||||
return err
|
||||
}
|
||||
for _, i := range allList {
|
||||
if i.Status == constant.Installing || i.Status == constant.Upgrading {
|
||||
if i.Status == constant.Installing || i.Status == constant.Upgrading || i.Status == constant.Rebuilding {
|
||||
if systemInit {
|
||||
i.Status = constant.Error
|
||||
i.Message = "1Panel restart causes the task to terminate"
|
||||
@@ -422,7 +420,7 @@ func (a *AppInstallService) SyncAll(systemInit bool) error {
|
||||
continue
|
||||
}
|
||||
if !systemInit {
|
||||
if err := syncByID(i.ID); err != nil {
|
||||
if err = syncAppInstallStatus(&i); err != nil {
|
||||
global.LOG.Errorf("sync install app[%s] error,mgs: %s", i.Name, err.Error())
|
||||
}
|
||||
}
|
||||
@@ -529,7 +527,7 @@ func (a *AppInstallService) ChangeAppPort(req request.PortUpdate) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := updateInstallInfoInDB(req.Key, req.Name, "port", true, strconv.FormatInt(req.Port, 10)); err != nil {
|
||||
if err := updateInstallInfoInDB(req.Key, req.Name, "port", strconv.FormatInt(req.Port, 10)); err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -641,10 +639,11 @@ func (a *AppInstallService) GetParams(id uint) (*response.AppConfig, error) {
|
||||
for _, form := range appForm.FormFields {
|
||||
if v, ok := envs[form.EnvKey]; ok {
|
||||
appParam := response.AppParam{
|
||||
Edit: false,
|
||||
Key: form.EnvKey,
|
||||
Rule: form.Rule,
|
||||
Type: form.Type,
|
||||
Edit: false,
|
||||
Key: form.EnvKey,
|
||||
Rule: form.Rule,
|
||||
Type: form.Type,
|
||||
Multiple: form.Multiple,
|
||||
}
|
||||
if form.Edit {
|
||||
appParam.Edit = true
|
||||
@@ -656,10 +655,20 @@ func (a *AppInstallService) GetParams(id uint) (*response.AppConfig, error) {
|
||||
appInstall, _ := appInstallRepo.GetFirst(appInstallRepo.WithServiceName(v.(string)))
|
||||
appParam.ShowValue = appInstall.Name
|
||||
} else if form.Type == "select" {
|
||||
for _, fv := range form.Values {
|
||||
if fv.Value == v {
|
||||
appParam.ShowValue = fv.Label
|
||||
break
|
||||
if form.Multiple {
|
||||
if v == "" {
|
||||
appParam.Value = []string{}
|
||||
} else {
|
||||
if str, ok := v.(string); ok {
|
||||
appParam.Value = strings.Split(str, ",")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for _, fv := range form.Values {
|
||||
if fv.Value == v {
|
||||
appParam.ShowValue = fv.Label
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
appParam.Values = form.Values
|
||||
@@ -667,14 +676,15 @@ func (a *AppInstallService) GetParams(id uint) (*response.AppConfig, error) {
|
||||
params = append(params, appParam)
|
||||
} else {
|
||||
params = append(params, response.AppParam{
|
||||
Edit: form.Edit,
|
||||
Key: form.EnvKey,
|
||||
Rule: form.Rule,
|
||||
Type: form.Type,
|
||||
LabelZh: form.LabelZh,
|
||||
LabelEn: form.LabelEn,
|
||||
Value: form.Default,
|
||||
Values: form.Values,
|
||||
Edit: form.Edit,
|
||||
Key: form.EnvKey,
|
||||
Rule: form.Rule,
|
||||
Type: form.Type,
|
||||
LabelZh: form.LabelZh,
|
||||
LabelEn: form.LabelEn,
|
||||
Value: form.Default,
|
||||
Values: form.Values,
|
||||
Multiple: form.Multiple,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -690,15 +700,11 @@ func (a *AppInstallService) GetParams(id uint) (*response.AppConfig, error) {
|
||||
return &res, nil
|
||||
}
|
||||
|
||||
func syncByID(installID uint) error {
|
||||
appInstall, err := appInstallRepo.GetFirst(commonRepo.WithByID(installID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if appInstall.Status == constant.Installing {
|
||||
func syncAppInstallStatus(appInstall *model.AppInstall) error {
|
||||
if appInstall.Status == constant.Installing || appInstall.Status == constant.Rebuilding || appInstall.Status == constant.Upgrading {
|
||||
return nil
|
||||
}
|
||||
containerNames, err := getContainerNames(appInstall)
|
||||
containerNames, err := getContainerNames(*appInstall)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -710,75 +716,67 @@ func syncByID(installID uint) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var (
|
||||
errorContainers []string
|
||||
notFoundContainers []string
|
||||
runningContainers []string
|
||||
exitedContainers []string
|
||||
runningCount int
|
||||
exitedCount int
|
||||
pausedCount int
|
||||
exitedContainerNames []string
|
||||
total = len(containerNames)
|
||||
count = len(containers)
|
||||
)
|
||||
|
||||
for _, n := range containers {
|
||||
switch n.State {
|
||||
foundNames := make(map[string]bool)
|
||||
for _, con := range containers {
|
||||
foundNames[con.Names[0]] = true
|
||||
switch con.State {
|
||||
case "exited":
|
||||
exitedContainers = append(exitedContainers, n.Names[0])
|
||||
exitedContainerNames = append(exitedContainerNames, strings.TrimPrefix(con.Names[0], "/"))
|
||||
exitedCount++
|
||||
case "running":
|
||||
runningContainers = append(runningContainers, n.Names[0])
|
||||
default:
|
||||
errorContainers = append(errorContainers, n.Names[0])
|
||||
runningCount++
|
||||
case "paused":
|
||||
pausedCount++
|
||||
}
|
||||
}
|
||||
|
||||
var notFoundNames []string
|
||||
for _, name := range containerNames {
|
||||
exist := false
|
||||
for _, container := range containers {
|
||||
if common.ExistWithStrArray(name, container.Names) {
|
||||
exist = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !exist {
|
||||
notFoundContainers = append(notFoundContainers, name)
|
||||
if !foundNames["/"+name] {
|
||||
notFoundNames = append(notFoundNames, name)
|
||||
}
|
||||
}
|
||||
|
||||
containerCount := len(containers)
|
||||
errCount := len(errorContainers)
|
||||
notFoundCount := len(notFoundContainers)
|
||||
existedCount := len(exitedContainers)
|
||||
normalCount := len(containerNames)
|
||||
runningCount := len(runningContainers)
|
||||
|
||||
if containerCount == 0 {
|
||||
switch {
|
||||
case count == 0:
|
||||
appInstall.Status = constant.Error
|
||||
appInstall.Message = i18n.GetMsgWithMap("ErrContainerNotFound", map[string]interface{}{"name": strings.Join(containerNames, ",")})
|
||||
return appInstallRepo.Save(context.Background(), &appInstall)
|
||||
}
|
||||
if errCount == 0 && existedCount == 0 && notFoundCount == 0 {
|
||||
appInstall.Status = constant.Running
|
||||
return appInstallRepo.Save(context.Background(), &appInstall)
|
||||
}
|
||||
if existedCount == normalCount {
|
||||
appInstall.Message = buserr.WithName("ErrContainerNotFound", strings.Join(containerNames, ",")).Error()
|
||||
case exitedCount == total:
|
||||
appInstall.Status = constant.Stopped
|
||||
return appInstallRepo.Save(context.Background(), &appInstall)
|
||||
}
|
||||
if errCount == normalCount {
|
||||
appInstall.Status = constant.Error
|
||||
}
|
||||
if runningCount < normalCount {
|
||||
case runningCount == total:
|
||||
appInstall.Status = constant.Running
|
||||
case pausedCount == total:
|
||||
appInstall.Status = constant.Paused
|
||||
default:
|
||||
var msg string
|
||||
if exitedCount > 0 {
|
||||
msg = buserr.WithName("ErrContainerMsg", strings.Join(exitedContainerNames, ",")).Error()
|
||||
}
|
||||
if len(notFoundNames) > 0 {
|
||||
msg += buserr.WithName("ErrContainerNotFound", strings.Join(notFoundNames, ",")).Error()
|
||||
}
|
||||
if msg == "" {
|
||||
msg = buserr.New("ErrAppWarn").Error()
|
||||
}
|
||||
appInstall.Message = msg
|
||||
appInstall.Status = constant.UnHealthy
|
||||
}
|
||||
_ = appInstallRepo.Save(context.Background(), appInstall)
|
||||
|
||||
var errMsg string
|
||||
if errCount > 0 {
|
||||
errMsg += i18n.GetMsgWithMap("ErrContainerMsg", map[string]interface{}{"name": strings.Join(errorContainers, ",")})
|
||||
}
|
||||
if notFoundCount > 0 {
|
||||
errMsg += i18n.GetMsgWithMap("ErrContainerNotFound", map[string]interface{}{"name": strings.Join(notFoundContainers, ",")})
|
||||
}
|
||||
appInstall.Message = errMsg
|
||||
return appInstallRepo.Save(context.Background(), &appInstall)
|
||||
return nil
|
||||
}
|
||||
|
||||
func updateInstallInfoInDB(appKey, appName, param string, isRestart bool, value interface{}) error {
|
||||
func updateInstallInfoInDB(appKey, appName, param string, value interface{}) error {
|
||||
if param != "password" && param != "port" && param != "user-password" {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -354,8 +354,8 @@ func deleteAppInstall(install model.AppInstall, deleteBackup bool, forceDelete b
|
||||
websiteApp, _ := appRepo.GetFirst(commonRepo.WithByID(websiteAppInstall.AppId))
|
||||
if websiteApp.Type == constant.RuntimePHP {
|
||||
go func() {
|
||||
_, _ = compose.Down(install.GetComposePath())
|
||||
_ = op.DeleteDir(install.GetPath())
|
||||
_, _ = compose.Down(websiteAppInstall.GetComposePath())
|
||||
_ = op.DeleteDir(websiteAppInstall.GetPath())
|
||||
}()
|
||||
_ = appInstallRepo.Delete(ctx, websiteAppInstall)
|
||||
}
|
||||
@@ -687,6 +687,12 @@ func handleMap(params map[string]interface{}, envParams map[string]string) {
|
||||
envParams[k] = strconv.Itoa(int(t))
|
||||
case int:
|
||||
envParams[k] = strconv.Itoa(t)
|
||||
case []interface{}:
|
||||
strArray := make([]string, len(t))
|
||||
for i := range t {
|
||||
strArray[i] = strings.ToLower(fmt.Sprintf("%v", t[i]))
|
||||
}
|
||||
envParams[k] = strings.Join(strArray, ",")
|
||||
default:
|
||||
envParams[k] = t.(string)
|
||||
}
|
||||
@@ -843,38 +849,34 @@ func checkContainerNameIsExist(containerName, appDir string) (bool, error) {
|
||||
|
||||
func upApp(appInstall *model.AppInstall, pullImages bool) {
|
||||
upProject := func(appInstall *model.AppInstall) (err error) {
|
||||
if err == nil {
|
||||
var (
|
||||
out string
|
||||
errMsg string
|
||||
)
|
||||
if pullImages && appInstall.App.Type != "php" {
|
||||
out, err = compose.Pull(appInstall.GetComposePath())
|
||||
if err != nil {
|
||||
if out != "" {
|
||||
if strings.Contains(out, "no such host") {
|
||||
errMsg = i18n.GetMsgByKey("ErrNoSuchHost") + ":"
|
||||
}
|
||||
if strings.Contains(out, "timeout") {
|
||||
errMsg = i18n.GetMsgByKey("ErrImagePullTimeOut") + ":"
|
||||
}
|
||||
appInstall.Message = errMsg + out
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
out, err = compose.Up(appInstall.GetComposePath())
|
||||
var (
|
||||
out string
|
||||
errMsg string
|
||||
)
|
||||
if pullImages && appInstall.App.Type != "php" {
|
||||
out, err = compose.Pull(appInstall.GetComposePath())
|
||||
if err != nil {
|
||||
if out != "" {
|
||||
if strings.Contains(out, "no such host") {
|
||||
errMsg = i18n.GetMsgByKey("ErrNoSuchHost") + ":"
|
||||
}
|
||||
if strings.Contains(out, "timeout") {
|
||||
errMsg = i18n.GetMsgByKey("ErrImagePullTimeOut") + ":"
|
||||
}
|
||||
appInstall.Message = errMsg + out
|
||||
}
|
||||
return err
|
||||
}
|
||||
return
|
||||
} else {
|
||||
return
|
||||
}
|
||||
|
||||
out, err = compose.Up(appInstall.GetComposePath())
|
||||
if err != nil {
|
||||
if out != "" {
|
||||
appInstall.Message = errMsg + out
|
||||
}
|
||||
return err
|
||||
}
|
||||
return
|
||||
}
|
||||
if err := upProject(appInstall); err != nil {
|
||||
appInstall.Status = constant.Error
|
||||
@@ -897,11 +899,12 @@ func rebuildApp(appInstall model.AppInstall) error {
|
||||
_ = handleErr(appInstall, err, out)
|
||||
return
|
||||
}
|
||||
out, err = compose.Up(dockerComposePath)
|
||||
out, err = compose.Up(appInstall.GetComposePath())
|
||||
if err != nil {
|
||||
_ = handleErr(appInstall, err, out)
|
||||
return
|
||||
}
|
||||
|
||||
appInstall.Status = constant.Running
|
||||
_ = appInstallRepo.Save(context.Background(), &appInstall)
|
||||
}()
|
||||
@@ -989,6 +992,43 @@ func handleLocalAppDetail(versionDir string, appDetail *model.AppDetail) error {
|
||||
if err != nil {
|
||||
return buserr.WithMap(constant.ErrFileParseApp, map[string]interface{}{"name": "data.yml", "err": err.Error()}, err)
|
||||
}
|
||||
var appParam dto.AppForm
|
||||
if err = json.Unmarshal(dataJson, &appParam); err != nil {
|
||||
return buserr.WithMap(constant.ErrFileParseApp, map[string]interface{}{"name": "data.yml", "err": err.Error()}, err)
|
||||
}
|
||||
for _, formField := range appParam.FormFields {
|
||||
if strings.Contains(formField.EnvKey, " ") {
|
||||
return buserr.WithName(constant.ErrAppParamKey, formField.EnvKey)
|
||||
}
|
||||
}
|
||||
|
||||
var dataMap map[string]interface{}
|
||||
err = yaml.Unmarshal(paramByte, &dataMap)
|
||||
if err != nil {
|
||||
return buserr.WithMap(constant.ErrFileParseApp, map[string]interface{}{"name": "data.yml", "err": err.Error()}, err)
|
||||
}
|
||||
|
||||
additionalProperties, ok := dataMap["additionalProperties"].(map[string]interface{})
|
||||
if !ok {
|
||||
return buserr.WithName(constant.ErrAppParamKey, "additionalProperties")
|
||||
}
|
||||
|
||||
formFieldsInterface, ok := additionalProperties["formFields"]
|
||||
if ok {
|
||||
formFields, ok := formFieldsInterface.([]interface{})
|
||||
if !ok {
|
||||
return buserr.WithName(constant.ErrAppParamKey, "formFields")
|
||||
}
|
||||
for _, item := range formFields {
|
||||
field := item.(map[string]interface{})
|
||||
for key, value := range field {
|
||||
if value == nil {
|
||||
return buserr.WithName(constant.ErrAppParamKey, key)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
appDetail.Params = string(dataJson)
|
||||
return nil
|
||||
}
|
||||
@@ -1040,8 +1080,8 @@ func handleErr(install model.AppInstall, err error, out string) error {
|
||||
if out != "" {
|
||||
install.Message = out
|
||||
reErr = errors.New(out)
|
||||
install.Status = constant.Error
|
||||
}
|
||||
install.Status = constant.Error
|
||||
_ = appInstallRepo.Save(context.Background(), &install)
|
||||
return reErr
|
||||
}
|
||||
@@ -1052,6 +1092,10 @@ func handleInstalled(appInstallList []model.AppInstall, updated bool) ([]respons
|
||||
if updated && (installed.App.Type == "php" || installed.Status == constant.Installing || (installed.App.Key == constant.AppMysql && installed.Version == "5.6.51")) {
|
||||
continue
|
||||
}
|
||||
if err := syncAppInstallStatus(&installed); err != nil {
|
||||
global.LOG.Error("sync app install status error : ", err)
|
||||
}
|
||||
|
||||
installDTO := response.AppInstalledDTO{
|
||||
AppInstall: installed,
|
||||
Path: installed.GetPath(),
|
||||
@@ -1066,7 +1110,7 @@ func handleInstalled(appInstallList []model.AppInstall, updated bool) ([]respons
|
||||
}
|
||||
var versions []string
|
||||
for _, detail := range details {
|
||||
if detail.IgnoreUpgrade {
|
||||
if detail.IgnoreUpgrade || installed.Version == "latest" {
|
||||
continue
|
||||
}
|
||||
if common.IsCrossVersion(installed.Version, detail.Version) && !app.CrossVersionUpdate {
|
||||
|
||||
@@ -109,6 +109,10 @@ func (u *AuthService) generateSession(c *gin.Context, name, authMethod string) (
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
httpsSetting, err := settingRepo.Get(settingRepo.WithByKey("SSL"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
lifeTime, err := strconv.Atoi(setting.Value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -129,7 +133,7 @@ func (u *AuthService) generateSession(c *gin.Context, name, authMethod string) (
|
||||
sessionUser, err := global.SESSION.Get(sID)
|
||||
if err != nil {
|
||||
sID = uuid.New().String()
|
||||
c.SetCookie(constant.SessionName, sID, 0, "", "", false, false)
|
||||
c.SetCookie(constant.SessionName, sID, 0, "", "", httpsSetting.Value == "enable", true)
|
||||
err := global.SESSION.Set(sID, sessionUser, lifeTime)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -144,9 +148,13 @@ func (u *AuthService) generateSession(c *gin.Context, name, authMethod string) (
|
||||
}
|
||||
|
||||
func (u *AuthService) LogOut(c *gin.Context) error {
|
||||
httpsSetting, err := settingRepo.Get(settingRepo.WithByKey("SSL"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sID, _ := c.Cookie(constant.SessionName)
|
||||
if sID != "" {
|
||||
c.SetCookie(constant.SessionName, sID, -1, "", "", false, false)
|
||||
c.SetCookie(constant.SessionName, sID, -1, "", "", httpsSetting.Value == "enable", true)
|
||||
err := global.SESSION.Delete(sID)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -266,22 +266,18 @@ func (u *BackupService) BatchDeleteRecord(ids []uint) error {
|
||||
return err
|
||||
}
|
||||
for _, record := range records {
|
||||
if record.Source == "LOCAL" {
|
||||
if err := os.Remove(record.FileDir + "/" + record.FileName); err != nil {
|
||||
global.LOG.Errorf("remove file %s failed, err: %v", record.FileDir+record.FileName, err)
|
||||
}
|
||||
} else {
|
||||
backupAccount, err := backupRepo.Get(commonRepo.WithByType(record.Source))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
client, err := u.NewClient(&backupAccount)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err = client.Delete(record.FileDir + record.FileName); err != nil {
|
||||
global.LOG.Errorf("remove file %s from %s failed, err: %v", record.FileDir+record.FileName, record.Source, err)
|
||||
}
|
||||
backupAccount, err := backupRepo.Get(commonRepo.WithByType(record.Source))
|
||||
if err != nil {
|
||||
global.LOG.Errorf("load backup account %s info from db failed, err: %v", record.Source, err)
|
||||
continue
|
||||
}
|
||||
client, err := u.NewClient(&backupAccount)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("new client for backup account %s failed, err: %v", record.Source, err)
|
||||
continue
|
||||
}
|
||||
if _, err = client.Delete(path.Join(record.FileDir, record.FileName)); err != nil {
|
||||
global.LOG.Errorf("remove file %s from %s failed, err: %v", path.Join(record.FileDir, record.FileName), record.Source, err)
|
||||
}
|
||||
}
|
||||
return backupRepo.DeleteRecord(context.Background(), commonRepo.WithIdsIn(ids))
|
||||
@@ -481,6 +477,8 @@ func (u *BackupService) loadRecordSize(records []model.BackupRecord) ([]dto.Back
|
||||
datas = append(datas, item)
|
||||
wg.Done()
|
||||
}(i)
|
||||
} else {
|
||||
datas = append(datas, item)
|
||||
}
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
@@ -115,7 +115,7 @@ func handleAppBackup(install *model.AppInstall, backupDir, fileName string) erro
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := handleMysqlBackup(db.MysqlName, db.Name, tmpDir, fmt.Sprintf("%s.sql.gz", install.Name)); err != nil {
|
||||
if err := handleMysqlBackup(db.MysqlName, resource.Key, db.Name, tmpDir, fmt.Sprintf("%s.sql.gz", install.Name)); err != nil {
|
||||
return err
|
||||
}
|
||||
case constant.AppPostgresql:
|
||||
|
||||
@@ -29,7 +29,7 @@ func (u *BackupService) MysqlBackup(req dto.CommonBackup) error {
|
||||
targetDir := path.Join(localDir, itemDir)
|
||||
fileName := fmt.Sprintf("%s_%s.sql.gz", req.DetailName, timeNow+common.RandStrAndNum(5))
|
||||
|
||||
if err := handleMysqlBackup(req.Name, req.DetailName, targetDir, fileName); err != nil {
|
||||
if err := handleMysqlBackup(req.Name, req.Type, req.DetailName, targetDir, fileName); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -100,18 +100,20 @@ func (u *BackupService) MysqlRecoverByUpload(req dto.CommonRecover) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleMysqlBackup(database, dbName, targetDir, fileName string) error {
|
||||
func handleMysqlBackup(database, dbType, dbName, targetDir, fileName string) error {
|
||||
dbInfo, err := mysqlRepo.Get(commonRepo.WithByName(dbName), mysqlRepo.WithByMysqlName(database))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cli, _, err := LoadMysqlClientByFrom(database)
|
||||
cli, version, err := LoadMysqlClientByFrom(database)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
backupInfo := client.BackupInfo{
|
||||
Name: dbName,
|
||||
Type: dbType,
|
||||
Version: version,
|
||||
Format: dbInfo.Format,
|
||||
TargetDir: targetDir,
|
||||
FileName: fileName,
|
||||
@@ -134,7 +136,7 @@ func handleMysqlRecover(req dto.CommonRecover, isRollback bool) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cli, _, err := LoadMysqlClientByFrom(req.Name)
|
||||
cli, version, err := LoadMysqlClientByFrom(req.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -143,6 +145,8 @@ func handleMysqlRecover(req dto.CommonRecover, isRollback bool) error {
|
||||
rollbackFile := path.Join(global.CONF.System.TmpDir, fmt.Sprintf("database/%s/%s_%s.sql.gz", req.Type, req.DetailName, time.Now().Format("20060102150405")))
|
||||
if err := cli.Backup(client.BackupInfo{
|
||||
Name: req.DetailName,
|
||||
Type: req.Type,
|
||||
Version: version,
|
||||
Format: dbInfo.Format,
|
||||
TargetDir: path.Dir(rollbackFile),
|
||||
FileName: path.Base(rollbackFile),
|
||||
@@ -156,6 +160,8 @@ func handleMysqlRecover(req dto.CommonRecover, isRollback bool) error {
|
||||
global.LOG.Info("recover failed, start to rollback now")
|
||||
if err := cli.Recover(client.RecoverInfo{
|
||||
Name: req.DetailName,
|
||||
Type: req.Type,
|
||||
Version: version,
|
||||
Format: dbInfo.Format,
|
||||
SourceFile: rollbackFile,
|
||||
|
||||
@@ -172,6 +178,8 @@ func handleMysqlRecover(req dto.CommonRecover, isRollback bool) error {
|
||||
}
|
||||
if err := cli.Recover(client.RecoverInfo{
|
||||
Name: req.DetailName,
|
||||
Type: req.Type,
|
||||
Version: version,
|
||||
Format: dbInfo.Format,
|
||||
SourceFile: req.File,
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/pkg/errors"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
@@ -18,6 +17,8 @@ import (
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/backend/buserr"
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
@@ -336,7 +337,7 @@ func (u *ContainerService) LoadResourceLimit() (*dto.ResourceLimit, error) {
|
||||
|
||||
data := dto.ResourceLimit{
|
||||
CPU: cpuCounts,
|
||||
Memory: int(memoryInfo.Total),
|
||||
Memory: memoryInfo.Total,
|
||||
}
|
||||
return &data, nil
|
||||
}
|
||||
@@ -434,7 +435,7 @@ func (u *ContainerService) ContainerInfo(req dto.OperationWithName) (*dto.Contai
|
||||
data.AutoRemove = oldContainer.HostConfig.AutoRemove
|
||||
data.Privileged = oldContainer.HostConfig.Privileged
|
||||
data.PublishAllPorts = oldContainer.HostConfig.PublishAllPorts
|
||||
data.RestartPolicy = oldContainer.HostConfig.RestartPolicy.Name
|
||||
data.RestartPolicy = string(oldContainer.HostConfig.RestartPolicy.Name)
|
||||
if oldContainer.HostConfig.NanoCPUs != 0 {
|
||||
data.NanoCPUs = float64(oldContainer.HostConfig.NanoCPUs) / 1000000000
|
||||
}
|
||||
@@ -683,7 +684,6 @@ func (u *ContainerService) ContainerLogs(wsConn *websocket.Conn, containerType,
|
||||
global.LOG.Errorf("read bytes from log failed, err: %v", err)
|
||||
continue
|
||||
}
|
||||
// check if the bytes is valid utf8
|
||||
if !utf8.Valid(buffer[:n]) {
|
||||
continue
|
||||
}
|
||||
@@ -723,7 +723,6 @@ func (u *ContainerService) ContainerStats(id string) (*dto.ContainerStats, error
|
||||
if cache, ok := stats.MemoryStats.Stats["cache"]; ok {
|
||||
data.Cache = float64(cache) / 1024 / 1024
|
||||
}
|
||||
data.Memory = data.Memory - data.Cache
|
||||
data.NetworkRX, data.NetworkTX = calculateNetwork(stats.Networks)
|
||||
data.ShotTime = stats.Read
|
||||
return &data, nil
|
||||
@@ -791,7 +790,7 @@ func calculateCPUPercentUnix(stats *types.StatsJSON) float64 {
|
||||
}
|
||||
func calculateMemPercentUnix(memStats types.MemoryStats) float64 {
|
||||
memPercent := 0.0
|
||||
memUsage := float64(memStats.Usage - memStats.Stats["cache"])
|
||||
memUsage := float64(memStats.Usage)
|
||||
memLimit := float64(memStats.Limit)
|
||||
if memUsage > 0.0 && memLimit > 0.0 {
|
||||
memPercent = (memUsage / memLimit) * 100.0
|
||||
@@ -895,6 +894,7 @@ func loadCpuAndMem(client *client.Client, container string) dto.ContainerListSta
|
||||
data.MemoryCache = stats.MemoryStats.Stats["cache"]
|
||||
data.MemoryUsage = stats.MemoryStats.Usage
|
||||
data.MemoryLimit = stats.MemoryStats.Limit
|
||||
|
||||
data.MemoryPercent = calculateMemPercentUnix(stats.MemoryStats)
|
||||
return data
|
||||
}
|
||||
@@ -985,7 +985,7 @@ func loadConfigInfo(isCreate bool, req dto.ContainerOperate, oldContainer *types
|
||||
hostConf.AutoRemove = req.AutoRemove
|
||||
hostConf.CPUShares = req.CPUShares
|
||||
hostConf.PublishAllPorts = req.PublishAllPorts
|
||||
hostConf.RestartPolicy = container.RestartPolicy{Name: req.RestartPolicy}
|
||||
hostConf.RestartPolicy = container.RestartPolicy{Name: container.RestartPolicyMode(req.RestartPolicy)}
|
||||
if req.RestartPolicy == "on-failure" {
|
||||
hostConf.RestartPolicy.MaximumRetryCount = 5
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@ import (
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/model"
|
||||
"github.com/1Panel-dev/1Panel/backend/buserr"
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"github.com/jinzhu/copier"
|
||||
@@ -33,7 +32,7 @@ type ICronjobService interface {
|
||||
StartJob(cronjob *model.Cronjob) (string, error)
|
||||
CleanRecord(req dto.CronjobClean) error
|
||||
|
||||
LoadRecordLog(req dto.OperateByID) (string, error)
|
||||
LoadRecordLog(req dto.OperateByID) string
|
||||
}
|
||||
|
||||
func NewICronjobService() ICronjobService {
|
||||
@@ -77,19 +76,19 @@ func (u *CronjobService) SearchRecords(search dto.SearchRecord) (int64, interfac
|
||||
return total, dtoCronjobs, err
|
||||
}
|
||||
|
||||
func (u *CronjobService) LoadRecordLog(req dto.OperateByID) (string, error) {
|
||||
func (u *CronjobService) LoadRecordLog(req dto.OperateByID) string {
|
||||
record, err := cronjobRepo.GetRecord(commonRepo.WithByID(req.ID))
|
||||
if err != nil {
|
||||
return "", err
|
||||
return ""
|
||||
}
|
||||
if _, err := os.Stat(record.Records); err != nil {
|
||||
return "", buserr.New("ErrHttpReqNotFound")
|
||||
return ""
|
||||
}
|
||||
content, err := os.ReadFile(record.Records)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return ""
|
||||
}
|
||||
return string(content), nil
|
||||
return string(content)
|
||||
}
|
||||
|
||||
func (u *CronjobService) CleanRecord(req dto.CronjobClean) error {
|
||||
|
||||
@@ -109,7 +109,7 @@ func (u *CronjobService) handleDatabase(cronjob model.Cronjob, startTime time.Ti
|
||||
backupDir := path.Join(global.CONF.System.TmpDir, fmt.Sprintf("database/%s/%s/%s", dbInfo.DBType, record.Name, dbInfo.Name))
|
||||
record.FileName = fmt.Sprintf("db_%s_%s.sql.gz", dbInfo.Name, startTime.Format("20060102150405")+common.RandStrAndNum(5))
|
||||
if cronjob.DBType == "mysql" || cronjob.DBType == "mariadb" {
|
||||
if err := handleMysqlBackup(dbInfo.Database, dbInfo.Name, backupDir, record.FileName); err != nil {
|
||||
if err := handleMysqlBackup(dbInfo.Database, dbInfo.DBType, dbInfo.Name, backupDir, record.FileName); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -82,7 +82,10 @@ func (u *CronjobService) HandleJob(cronjob *model.Cronjob) {
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
cronjobRepo.EndRecords(record, constant.StatusFailed, err.Error(), string(message))
|
||||
if len(message) != 0 {
|
||||
record.Records, _ = mkdirAndWriteFile(cronjob, record.StartTime, message)
|
||||
}
|
||||
cronjobRepo.EndRecords(record, constant.StatusFailed, err.Error(), record.Records)
|
||||
return
|
||||
}
|
||||
if len(message) != 0 {
|
||||
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
type DashboardService struct{}
|
||||
|
||||
type IDashboardService interface {
|
||||
LoadOsInfo() (*dto.OsInfo, error)
|
||||
LoadBaseInfo(ioOption string, netOption string) (*dto.DashboardBase, error)
|
||||
LoadCurrentInfo(ioOption string, netOption string) *dto.DashboardCurrent
|
||||
|
||||
@@ -49,6 +50,27 @@ func (u *DashboardService) Restart(operation string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *DashboardService) LoadOsInfo() (*dto.OsInfo, error) {
|
||||
var baseInfo dto.OsInfo
|
||||
hostInfo, err := host.Info()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
baseInfo.OS = hostInfo.OS
|
||||
baseInfo.Platform = hostInfo.Platform
|
||||
baseInfo.PlatformFamily = hostInfo.PlatformFamily
|
||||
baseInfo.KernelArch = hostInfo.KernelArch
|
||||
baseInfo.KernelVersion = hostInfo.KernelVersion
|
||||
|
||||
if baseInfo.KernelArch == "armv7l" {
|
||||
baseInfo.KernelArch = "armv7"
|
||||
}
|
||||
if baseInfo.KernelArch == "x86_64" {
|
||||
baseInfo.KernelArch = "amd64"
|
||||
}
|
||||
return &baseInfo, nil
|
||||
}
|
||||
|
||||
func (u *DashboardService) LoadBaseInfo(ioOption string, netOption string) (*dto.DashboardBase, error) {
|
||||
var baseInfo dto.DashboardBase
|
||||
hostInfo, err := host.Info()
|
||||
@@ -183,7 +205,10 @@ func loadDiskInfo() []dto.DiskInfo {
|
||||
var datas []dto.DiskInfo
|
||||
stdout, err := cmd.ExecWithTimeOut("df -hT -P|grep '/'|grep -v tmpfs|grep -v 'snap/core'|grep -v udev", 2*time.Second)
|
||||
if err != nil {
|
||||
return datas
|
||||
stdout, err = cmd.ExecWithTimeOut("df -lhT -P|grep '/'|grep -v tmpfs|grep -v 'snap/core'|grep -v udev", 1*time.Second)
|
||||
if err != nil {
|
||||
return datas
|
||||
}
|
||||
}
|
||||
lines := strings.Split(stdout, "\n")
|
||||
|
||||
|
||||
@@ -342,7 +342,7 @@ func (u *MysqlService) ChangePassword(req dto.ChangeDBInfo) error {
|
||||
}
|
||||
|
||||
global.LOG.Infof("start to update mysql password used by app %s-%s", appModel.Key, appInstall.Name)
|
||||
if err := updateInstallInfoInDB(appModel.Key, appInstall.Name, "user-password", true, req.Value); err != nil {
|
||||
if err := updateInstallInfoInDB(appModel.Key, appInstall.Name, "user-password", req.Value); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -355,7 +355,7 @@ func (u *MysqlService) ChangePassword(req dto.ChangeDBInfo) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := updateInstallInfoInDB(req.Type, req.Database, "password", false, req.Value); err != nil {
|
||||
if err := updateInstallInfoInDB(req.Type, req.Database, "password", req.Value); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -393,7 +393,7 @@ func (u *PostgresqlService) ChangePassword(req dto.ChangeDBInfo) error {
|
||||
}
|
||||
|
||||
global.LOG.Infof("start to update postgresql password used by app %s-%s", appModel.Key, appInstall.Name)
|
||||
if err := updateInstallInfoInDB(appModel.Key, appInstall.Name, "user-password", true, req.Value); err != nil {
|
||||
if err := updateInstallInfoInDB(appModel.Key, appInstall.Name, "user-password", req.Value); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -406,7 +406,7 @@ func (u *PostgresqlService) ChangePassword(req dto.ChangeDBInfo) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := updateInstallInfoInDB(req.Type, req.Database, "password", false, req.Value); err != nil {
|
||||
if err := updateInstallInfoInDB(req.Type, req.Database, "password", req.Value); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -55,10 +55,10 @@ func (u *RedisService) UpdateConf(req dto.RedisConfUpdate) error {
|
||||
}
|
||||
|
||||
func (u *RedisService) ChangePassword(req dto.ChangeRedisPass) error {
|
||||
if err := updateInstallInfoInDB("redis", "", "password", true, req.Value); err != nil {
|
||||
if err := updateInstallInfoInDB("redis", "", "password", req.Value); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := updateInstallInfoInDB("redis-commander", "", "password", true, req.Value); err != nil {
|
||||
if err := updateInstallInfoInDB("redis-commander", "", "password", req.Value); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -181,7 +181,7 @@ func (u *DockerService) UpdateConf(req dto.SettingUpdate) error {
|
||||
} else {
|
||||
daemonMap["iptables"] = false
|
||||
}
|
||||
case "Dirver":
|
||||
case "Driver":
|
||||
if opts, ok := daemonMap["exec-opts"]; ok {
|
||||
if optsValue, isArray := opts.([]interface{}); isArray {
|
||||
for i := 0; i < len(optsValue); i++ {
|
||||
|
||||
@@ -37,7 +37,10 @@ func getNginxFull(website *model.Website) (dto.NginxFull, error) {
|
||||
if err != nil {
|
||||
return nginxFull, err
|
||||
}
|
||||
config := parser.NewStringParser(string(content)).Parse()
|
||||
config, err := parser.NewStringParser(string(content)).Parse()
|
||||
if err != nil {
|
||||
return dto.NginxFull{}, err
|
||||
}
|
||||
config.FilePath = nginxConfig.FilePath
|
||||
nginxConfig.OldContent = string(content)
|
||||
nginxConfig.Config = config
|
||||
@@ -54,7 +57,10 @@ func getNginxFull(website *model.Website) (dto.NginxFull, error) {
|
||||
if err != nil {
|
||||
return nginxFull, err
|
||||
}
|
||||
siteConfig := parser.NewStringParser(string(siteNginxContent)).Parse()
|
||||
siteConfig, err := parser.NewStringParser(string(siteNginxContent)).Parse()
|
||||
if err != nil {
|
||||
return dto.NginxFull{}, err
|
||||
}
|
||||
siteConfig.FilePath = siteConfigPath
|
||||
siteNginxConfig.Config = siteConfig
|
||||
siteNginxConfig.OldContent = string(siteNginxContent)
|
||||
@@ -156,15 +162,22 @@ func deleteNginxConfig(scope string, params []dto.NginxParam, website *model.Web
|
||||
}
|
||||
|
||||
func getNginxParamsFromStaticFile(scope dto.NginxKey, newParams []dto.NginxParam) []dto.NginxParam {
|
||||
newConfig := &components.Config{}
|
||||
var (
|
||||
newConfig = &components.Config{}
|
||||
err error
|
||||
)
|
||||
|
||||
updateScope := "in"
|
||||
switch scope {
|
||||
case dto.SSL:
|
||||
newConfig = parser.NewStringParser(string(nginx_conf.SSL)).Parse()
|
||||
newConfig, err = parser.NewStringParser(string(nginx_conf.SSL)).Parse()
|
||||
case dto.CACHE:
|
||||
newConfig = parser.NewStringParser(string(nginx_conf.Cache)).Parse()
|
||||
newConfig, err = parser.NewStringParser(string(nginx_conf.Cache)).Parse()
|
||||
case dto.ProxyCache:
|
||||
newConfig = parser.NewStringParser(string(nginx_conf.ProxyCache)).Parse()
|
||||
newConfig, err = parser.NewStringParser(string(nginx_conf.ProxyCache)).Parse()
|
||||
}
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
for _, dir := range newConfig.GetDirectives() {
|
||||
addParam := dto.NginxParam{
|
||||
|
||||
@@ -198,6 +198,9 @@ func (u *SettingService) UpdateSSL(c *gin.Context, req dto.SSLUpdate) error {
|
||||
}
|
||||
_ = os.Remove(path.Join(secretDir, "server.crt"))
|
||||
_ = os.Remove(path.Join(secretDir, "server.key"))
|
||||
sID, _ := c.Cookie(constant.SessionName)
|
||||
c.SetCookie(constant.SessionName, sID, 0, "", "", false, true)
|
||||
|
||||
go func() {
|
||||
_, err := cmd.Exec("systemctl restart 1panel.service")
|
||||
if err != nil {
|
||||
@@ -277,7 +280,7 @@ func (u *SettingService) UpdateSSL(c *gin.Context, req dto.SSLUpdate) error {
|
||||
if err := fileOp.WriteFile(path.Join(secretDir, "server.key.tmp"), strings.NewReader(key), 0600); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := checkCertValid(req.Domain); err != nil {
|
||||
if err := checkCertValid(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := fileOp.Rename(path.Join(secretDir, "server.crt.tmp"), path.Join(secretDir, "server.crt")); err != nil {
|
||||
@@ -289,6 +292,9 @@ func (u *SettingService) UpdateSSL(c *gin.Context, req dto.SSLUpdate) error {
|
||||
if err := settingRepo.Update("SSL", req.SSL); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sID, _ := c.Cookie(constant.SessionName)
|
||||
c.SetCookie(constant.SessionName, sID, 0, "", "", true, true)
|
||||
go func() {
|
||||
time.Sleep(1 * time.Second)
|
||||
_, err := cmd.Exec("systemctl restart 1panel.service")
|
||||
@@ -412,7 +418,7 @@ func loadInfoFromCert() (*dto.SSLInfo, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
func checkCertValid(domain string) error {
|
||||
func checkCertValid() error {
|
||||
certificate, err := os.ReadFile(path.Join(global.CONF.System.BaseDir, "1panel/secret/server.crt.tmp"))
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
+69
-646
@@ -6,8 +6,6 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -18,10 +16,10 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/compose"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/docker"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/files"
|
||||
"github.com/jinzhu/copier"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/shirou/gopsutil/v3/host"
|
||||
)
|
||||
|
||||
type SnapshotService struct {
|
||||
@@ -74,11 +72,7 @@ func (u *SnapshotService) SnapshotImport(req dto.SnapshotImport) error {
|
||||
for _, snap := range req.Names {
|
||||
shortName := strings.TrimPrefix(snap, "snapshot_")
|
||||
nameItems := strings.Split(shortName, "_")
|
||||
if !strings.HasPrefix(shortName, "1panel_v") || !strings.HasSuffix(shortName, ".tar.gz") || len(nameItems) != 3 {
|
||||
return fmt.Errorf("incorrect snapshot name format of %s", shortName)
|
||||
}
|
||||
formatTime, err := time.Parse("20060102150405", strings.ReplaceAll(nameItems[2], ".tar.gz", ""))
|
||||
if err != nil {
|
||||
if !strings.HasPrefix(shortName, "1panel_v") || !strings.HasSuffix(shortName, ".tar.gz") || len(nameItems) < 3 {
|
||||
return fmt.Errorf("incorrect snapshot name format of %s", shortName)
|
||||
}
|
||||
if strings.HasSuffix(snap, ".tar.gz") {
|
||||
@@ -91,10 +85,6 @@ func (u *SnapshotService) SnapshotImport(req dto.SnapshotImport) error {
|
||||
Version: nameItems[1],
|
||||
Description: req.Description,
|
||||
Status: constant.StatusSuccess,
|
||||
BaseModel: model.BaseModel{
|
||||
CreatedAt: formatTime,
|
||||
UpdatedAt: formatTime,
|
||||
},
|
||||
}
|
||||
if err := snapshotRepo.Create(&itemSnap); err != nil {
|
||||
return err
|
||||
@@ -145,26 +135,13 @@ func (u *SnapshotService) SnapshotRecover(req dto.SnapshotRecover) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if hasOs(snap.Name) && !strings.Contains(snap.Name, loadOs()) {
|
||||
return fmt.Errorf("Restoring snapshots(%s) between different server architectures(%s) is not supported.", snap.Name, loadOs())
|
||||
}
|
||||
if !req.IsNew && len(snap.InterruptStep) != 0 && len(snap.RollbackStatus) != 0 {
|
||||
return fmt.Errorf("the snapshot has been rolled back and cannot be restored again")
|
||||
}
|
||||
isNewSnapshot := isNewSnapVersion(snap.Version)
|
||||
isReTry := false
|
||||
if len(snap.InterruptStep) != 0 && !req.IsNew {
|
||||
isReTry = true
|
||||
}
|
||||
backup, err := backupRepo.Get(commonRepo.WithByType(snap.DefaultDownload))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
client, err := NewIBackupService().NewClient(&backup)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
localDir, err := loadLocalDir()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
baseDir := path.Join(global.CONF.System.TmpDir, fmt.Sprintf("system/%s", snap.Name))
|
||||
if _, err := os.Stat(baseDir); err != nil && os.IsNotExist(err) {
|
||||
_ = os.MkdirAll(baseDir, os.ModePerm)
|
||||
@@ -172,151 +149,7 @@ func (u *SnapshotService) SnapshotRecover(req dto.SnapshotRecover) error {
|
||||
|
||||
_ = snapshotRepo.Update(snap.ID, map[string]interface{}{"recover_status": constant.StatusWaiting})
|
||||
_ = settingRepo.Update("SystemStatus", "Recovering")
|
||||
go func() {
|
||||
_ = global.Cron.Stop()
|
||||
defer func() {
|
||||
global.Cron.Start()
|
||||
}()
|
||||
operation := "recover"
|
||||
if isReTry {
|
||||
operation = "re-recover"
|
||||
}
|
||||
if !isReTry || snap.InterruptStep == "Download" || (isReTry && req.ReDownload) {
|
||||
itemBackupPath := strings.TrimPrefix(backup.BackupPath, "/")
|
||||
itemBackupPath = strings.TrimSuffix(itemBackupPath, "/")
|
||||
ok, err := client.Download(fmt.Sprintf("%s/system_snapshot/%s.tar.gz", itemBackupPath, snap.Name), fmt.Sprintf("%s/%s.tar.gz", baseDir, snap.Name))
|
||||
if err != nil || !ok {
|
||||
if req.ReDownload {
|
||||
updateRecoverStatus(snap.ID, snap.InterruptStep, constant.StatusFailed, fmt.Sprintf("download file %s from %s failed, err: %v", snap.Name, backup.Type, err))
|
||||
return
|
||||
}
|
||||
updateRecoverStatus(snap.ID, "Download", constant.StatusFailed, fmt.Sprintf("download file %s from %s failed, err: %v", snap.Name, backup.Type, err))
|
||||
return
|
||||
}
|
||||
isReTry = false
|
||||
}
|
||||
fileOp := files.NewFileOp()
|
||||
if !isReTry || snap.InterruptStep == "Decompress" || (isReTry && req.ReDownload) {
|
||||
if err := handleUnTar(fmt.Sprintf("%s/%s.tar.gz", baseDir, snap.Name), baseDir); err != nil {
|
||||
if req.ReDownload {
|
||||
updateRecoverStatus(snap.ID, snap.InterruptStep, constant.StatusFailed, fmt.Sprintf("decompress file failed, err: %v", err))
|
||||
return
|
||||
}
|
||||
updateRecoverStatus(snap.ID, "Decompress", constant.StatusFailed, fmt.Sprintf("decompress file failed, err: %v", err))
|
||||
return
|
||||
}
|
||||
isReTry = false
|
||||
}
|
||||
rootDir := fmt.Sprintf("%s/%s", baseDir, snap.Name)
|
||||
|
||||
snapJson, err := u.readFromJson(fmt.Sprintf("%s/snapshot.json", rootDir))
|
||||
if err != nil {
|
||||
updateRecoverStatus(snap.ID, "Readjson", constant.StatusFailed, fmt.Sprintf("decompress file failed, err: %v", err))
|
||||
return
|
||||
}
|
||||
if snap.InterruptStep == "Readjson" {
|
||||
isReTry = false
|
||||
}
|
||||
u.OriginalPath = fmt.Sprintf("%s/1panel_original/original_%s", snapJson.BaseDir, snap.Name)
|
||||
_ = os.MkdirAll(u.OriginalPath, os.ModePerm)
|
||||
|
||||
snapJson.OldBaseDir = global.CONF.System.BaseDir
|
||||
snapJson.OldPanelDataDir = path.Join(global.CONF.System.BaseDir, "1panel")
|
||||
snapJson.OldBackupDataDir = localDir
|
||||
recoverPanelDir := fmt.Sprintf("%s/%s/1panel", baseDir, snap.Name)
|
||||
liveRestore := false
|
||||
|
||||
if !isNewSnapshot {
|
||||
if !isReTry || snap.InterruptStep == "LoadDockerJson" {
|
||||
snapJson.OldDockerDataDir, liveRestore, err = u.loadDockerDataDir()
|
||||
if err != nil {
|
||||
updateRecoverStatus(snap.ID, "LoadDockerJson", constant.StatusFailed, fmt.Sprintf("load docker data dir failed, err: %v", err))
|
||||
return
|
||||
}
|
||||
isReTry = false
|
||||
}
|
||||
if liveRestore {
|
||||
if err := u.updateLiveRestore(false); err != nil {
|
||||
updateRecoverStatus(snap.ID, "UpdateLiveRestore", constant.StatusFailed, fmt.Sprintf("update docker daemon.json live-restore conf failed, err: %v", err))
|
||||
return
|
||||
}
|
||||
isReTry = false
|
||||
}
|
||||
_ = u.saveJson(snapJson, rootDir)
|
||||
|
||||
_, _ = cmd.Exec("systemctl stop docker")
|
||||
if !isReTry || snap.InterruptStep == "DockerDir" {
|
||||
if err := u.handleDockerDatas(fileOp, operation, rootDir, snapJson.DockerDataDir); err != nil {
|
||||
updateRecoverStatus(snap.ID, "DockerDir", constant.StatusFailed, err.Error())
|
||||
return
|
||||
}
|
||||
isReTry = false
|
||||
}
|
||||
} else {
|
||||
if !isReTry || snap.InterruptStep == "DockerDir" {
|
||||
if err := u.handleDockerDatasWithSave(fileOp, operation, rootDir, ""); err != nil {
|
||||
updateRecoverStatus(snap.ID, "DockerDir", constant.StatusFailed, err.Error())
|
||||
return
|
||||
}
|
||||
isReTry = false
|
||||
}
|
||||
}
|
||||
|
||||
if !isReTry || snap.InterruptStep == "DaemonJson" {
|
||||
if err := u.handleDaemonJson(fileOp, operation, rootDir+"/docker/daemon.json", u.OriginalPath); err != nil {
|
||||
updateRecoverStatus(snap.ID, "DaemonJson", constant.StatusFailed, err.Error())
|
||||
return
|
||||
}
|
||||
isReTry = false
|
||||
}
|
||||
_, _ = cmd.Exec("systemctl restart docker")
|
||||
|
||||
if !isReTry || snap.InterruptStep == "1PanelBinary" {
|
||||
if err := u.handlePanelBinary(fileOp, operation, recoverPanelDir+"/1panel", u.OriginalPath+"/1panel"); err != nil {
|
||||
updateRecoverStatus(snap.ID, "1PanelBinary", constant.StatusFailed, err.Error())
|
||||
return
|
||||
}
|
||||
isReTry = false
|
||||
}
|
||||
if !isReTry || snap.InterruptStep == "1PctlBinary" {
|
||||
if err := u.handlePanelctlBinary(fileOp, operation, recoverPanelDir+"/1pctl", u.OriginalPath+"/1pctl"); err != nil {
|
||||
updateRecoverStatus(snap.ID, "1PctlBinary", constant.StatusFailed, err.Error())
|
||||
return
|
||||
}
|
||||
isReTry = false
|
||||
}
|
||||
if !isReTry || snap.InterruptStep == "1PanelService" {
|
||||
if err := u.handlePanelService(fileOp, operation, recoverPanelDir+"/1panel.service", u.OriginalPath+"/1panel.service"); err != nil {
|
||||
updateRecoverStatus(snap.ID, "1PanelService", constant.StatusFailed, err.Error())
|
||||
return
|
||||
}
|
||||
isReTry = false
|
||||
}
|
||||
|
||||
if !isReTry || snap.InterruptStep == "1PanelBackups" {
|
||||
if err := u.handleBackupDatas(fileOp, operation, rootDir, snapJson.BackupDataDir); err != nil {
|
||||
updateRecoverStatus(snap.ID, "1PanelBackups", constant.StatusFailed, err.Error())
|
||||
return
|
||||
}
|
||||
isReTry = false
|
||||
}
|
||||
|
||||
if !isReTry || snap.InterruptStep == "1PanelData" {
|
||||
if err := u.handlePanelDatas(snap.ID, fileOp, operation, rootDir, snapJson.PanelDataDir, localDir, snapJson.OldDockerDataDir); err != nil {
|
||||
updateRecoverStatus(snap.ID, "1PanelData", constant.StatusFailed, err.Error())
|
||||
return
|
||||
}
|
||||
isReTry = false
|
||||
}
|
||||
|
||||
if isNewSnapshot {
|
||||
_ = rebuildAllAppInstall()
|
||||
}
|
||||
|
||||
_ = os.RemoveAll(rootDir)
|
||||
global.LOG.Info("recover successful")
|
||||
_, _ = cmd.Exec("systemctl daemon-reload && systemctl restart 1panel.service")
|
||||
}()
|
||||
go u.HandleSnapshotRecover(snap, true, req)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -326,132 +159,9 @@ func (u *SnapshotService) SnapshotRollback(req dto.SnapshotRecover) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if snap.InterruptStep == "Download" || snap.InterruptStep == "Decompress" || snap.InterruptStep == "Readjson" {
|
||||
return nil
|
||||
}
|
||||
localDir, err := loadLocalDir()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fileOp := files.NewFileOp()
|
||||
isNewSnapshot := isNewSnapVersion(snap.Version)
|
||||
|
||||
rootDir := path.Join(localDir, fmt.Sprintf("system/%s/%s", snap.Name, snap.Name))
|
||||
|
||||
_ = settingRepo.Update("SystemStatus", "Rollbacking")
|
||||
_ = snapshotRepo.Update(snap.ID, map[string]interface{}{"rollback_status": constant.StatusWaiting})
|
||||
go func() {
|
||||
_ = global.Cron.Stop()
|
||||
defer func() {
|
||||
global.Cron.Start()
|
||||
}()
|
||||
snapJson, err := u.readFromJson(fmt.Sprintf("%s/snapshot.json", rootDir))
|
||||
if err != nil {
|
||||
updateRollbackStatus(snap.ID, constant.StatusFailed, fmt.Sprintf("decompress file failed, err: %v", err))
|
||||
return
|
||||
}
|
||||
u.OriginalPath = fmt.Sprintf("%s/1panel_original/original_%s", snapJson.OldBaseDir, snap.Name)
|
||||
if _, err := os.Stat(u.OriginalPath); err != nil && os.IsNotExist(err) {
|
||||
return
|
||||
}
|
||||
|
||||
if !isNewSnapshot {
|
||||
_, _ = cmd.Exec("systemctl stop docker")
|
||||
if err := u.handleDockerDatas(fileOp, "rollback", u.OriginalPath, snapJson.OldDockerDataDir); err != nil {
|
||||
updateRollbackStatus(snap.ID, constant.StatusFailed, err.Error())
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
_, _ = cmd.Exec("systemctl restart docker")
|
||||
}()
|
||||
if snap.InterruptStep == "DockerDir" {
|
||||
return
|
||||
}
|
||||
if snapJson.LiveRestoreEnabled {
|
||||
if err := u.updateLiveRestore(true); err != nil {
|
||||
updateRollbackStatus(snap.ID, constant.StatusFailed, err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
if snap.InterruptStep == "UpdateLiveRestore" {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if err := u.handleDockerDatasWithSave(fileOp, "rollback", u.OriginalPath, ""); err != nil {
|
||||
updateRollbackStatus(snap.ID, constant.StatusFailed, err.Error())
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
_ = rebuildAllAppInstall()
|
||||
}()
|
||||
if snap.InterruptStep == "DockerDir" {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if err := u.handleDaemonJson(fileOp, "rollback", u.OriginalPath+"/daemon.json", ""); err != nil {
|
||||
updateRollbackStatus(snap.ID, constant.StatusFailed, err.Error())
|
||||
return
|
||||
}
|
||||
if snap.InterruptStep == "DaemonJson" {
|
||||
return
|
||||
}
|
||||
|
||||
if err := u.handlePanelBinary(fileOp, "rollback", u.OriginalPath+"/1panel", ""); err != nil {
|
||||
updateRollbackStatus(snap.ID, constant.StatusFailed, err.Error())
|
||||
return
|
||||
}
|
||||
if snap.InterruptStep == "1PanelBinary" {
|
||||
return
|
||||
}
|
||||
|
||||
if err := u.handlePanelctlBinary(fileOp, "rollback", u.OriginalPath+"/1pctl", ""); err != nil {
|
||||
updateRollbackStatus(snap.ID, constant.StatusFailed, err.Error())
|
||||
return
|
||||
}
|
||||
if snap.InterruptStep == "1PctlBinary" {
|
||||
return
|
||||
}
|
||||
|
||||
if err := u.handlePanelService(fileOp, "rollback", u.OriginalPath+"/1panel.service", ""); err != nil {
|
||||
updateRollbackStatus(snap.ID, constant.StatusFailed, err.Error())
|
||||
return
|
||||
}
|
||||
if snap.InterruptStep == "1PanelService" {
|
||||
_, _ = cmd.Exec("systemctl daemon-reload && systemctl restart 1panel.service")
|
||||
return
|
||||
}
|
||||
|
||||
if err := u.handleBackupDatas(fileOp, "rollback", u.OriginalPath, snapJson.OldBackupDataDir); err != nil {
|
||||
updateRollbackStatus(snap.ID, constant.StatusFailed, err.Error())
|
||||
return
|
||||
}
|
||||
if snap.InterruptStep == "1PanelBackups" {
|
||||
_, _ = cmd.Exec("systemctl daemon-reload && systemctl restart 1panel.service")
|
||||
return
|
||||
}
|
||||
|
||||
if err := u.handlePanelDatas(snap.ID, fileOp, "rollback", u.OriginalPath, snapJson.OldPanelDataDir, "", ""); err != nil {
|
||||
updateRollbackStatus(snap.ID, constant.StatusFailed, err.Error())
|
||||
return
|
||||
}
|
||||
if snap.InterruptStep == "1PanelData" {
|
||||
_, _ = cmd.Exec("systemctl daemon-reload && systemctl restart 1panel.service")
|
||||
return
|
||||
}
|
||||
|
||||
_ = os.RemoveAll(rootDir)
|
||||
global.LOG.Info("rollback successful")
|
||||
_, _ = cmd.Exec("systemctl daemon-reload && systemctl restart 1panel.service")
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *SnapshotService) saveJson(snapJson SnapshotJson, path string) error {
|
||||
remarkInfo, _ := json.MarshalIndent(snapJson, "", "\t")
|
||||
if err := os.WriteFile(fmt.Sprintf("%s/snapshot.json", path), remarkInfo, 0640); err != nil {
|
||||
return err
|
||||
}
|
||||
req.IsNew = false
|
||||
snap.InterruptStep = "Readjson"
|
||||
go u.HandleSnapshotRecover(snap, false, req)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -483,9 +193,10 @@ func (u *SnapshotService) HandleSnapshot(isCronjob bool, logPath string, req dto
|
||||
|
||||
if req.ID == 0 {
|
||||
versionItem, _ := settingRepo.Get(settingRepo.WithByKey("SystemVersion"))
|
||||
name := fmt.Sprintf("1panel_%s_%s", versionItem.Value, timeNow)
|
||||
|
||||
name := fmt.Sprintf("1panel_%s_%s_%s", versionItem.Value, loadOs(), timeNow)
|
||||
if isCronjob {
|
||||
name = fmt.Sprintf("snapshot_1panel_%s_%s", versionItem.Value, timeNow)
|
||||
name = fmt.Sprintf("snapshot_1panel_%s_%s_%s", versionItem.Value, loadOs(), timeNow)
|
||||
}
|
||||
rootDir = path.Join(localDir, "system", name)
|
||||
|
||||
@@ -505,6 +216,7 @@ func (u *SnapshotService) HandleSnapshot(isCronjob bool, logPath string, req dto
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
_ = snapshotRepo.Update(snap.ID, map[string]interface{}{"status": constant.StatusWaiting})
|
||||
snapStatus, _ = snapshotRepo.GetStatus(snap.ID)
|
||||
if snapStatus.ID == 0 {
|
||||
snapStatus.SnapID = snap.ID
|
||||
@@ -554,17 +266,23 @@ func (u *SnapshotService) HandleSnapshot(isCronjob bool, logPath string, req dto
|
||||
_ = snapshotRepo.Update(snap.ID, map[string]interface{}{"status": constant.StatusFailed})
|
||||
return
|
||||
}
|
||||
snapPanelData(itemHelper, localDir, backupPanelDir)
|
||||
if snapStatus.PanelData != constant.StatusDone {
|
||||
snapPanelData(itemHelper, localDir, backupPanelDir)
|
||||
}
|
||||
if snapStatus.PanelData != constant.StatusDone {
|
||||
_ = snapshotRepo.Update(snap.ID, map[string]interface{}{"status": constant.StatusFailed})
|
||||
return
|
||||
}
|
||||
snapCompress(itemHelper, rootDir)
|
||||
if snapStatus.Compress != constant.StatusDone {
|
||||
snapCompress(itemHelper, rootDir)
|
||||
}
|
||||
if snapStatus.Compress != constant.StatusDone {
|
||||
_ = snapshotRepo.Update(snap.ID, map[string]interface{}{"status": constant.StatusFailed})
|
||||
return
|
||||
}
|
||||
snapUpload(itemHelper, req.From, fmt.Sprintf("%s.tar.gz", rootDir))
|
||||
if snapStatus.Upload != constant.StatusDone {
|
||||
snapUpload(itemHelper, req.From, fmt.Sprintf("%s.tar.gz", rootDir))
|
||||
}
|
||||
if snapStatus.Upload != constant.StatusDone {
|
||||
_ = snapshotRepo.Update(snap.ID, map[string]interface{}{"status": constant.StatusFailed})
|
||||
return
|
||||
@@ -605,242 +323,6 @@ func (u *SnapshotService) HandleSnapshot(isCronjob bool, logPath string, req dto
|
||||
return snap.Name, nil
|
||||
}
|
||||
|
||||
func (u *SnapshotService) handleDockerDatas(fileOp files.FileOp, operation string, source, target string) error {
|
||||
switch operation {
|
||||
case "snapshot":
|
||||
if err := handleSnapTar(source, target, "docker_data.tar.gz", ""); err != nil {
|
||||
return fmt.Errorf("backup docker data failed, err: %v", err)
|
||||
}
|
||||
case "recover":
|
||||
if err := handleSnapTar(target, u.OriginalPath, "docker_data.tar.gz", ""); err != nil {
|
||||
return fmt.Errorf("backup docker data failed, err: %v", err)
|
||||
}
|
||||
if err := u.handleUnTar(source+"/docker/docker_data.tar.gz", target); err != nil {
|
||||
return fmt.Errorf("recover docker data failed, err: %v", err)
|
||||
}
|
||||
case "re-recover":
|
||||
if err := u.handleUnTar(source+"/docker/docker_data.tar.gz", target); err != nil {
|
||||
return fmt.Errorf("re-recover docker data failed, err: %v", err)
|
||||
}
|
||||
case "rollback":
|
||||
if err := u.handleUnTar(source+"/docker_data.tar.gz", target); err != nil {
|
||||
return fmt.Errorf("rollback docker data failed, err: %v", err)
|
||||
}
|
||||
}
|
||||
global.LOG.Info("handle docker data dir successful!")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *SnapshotService) handleDockerDatasWithSave(fileOp files.FileOp, operation, source, target string) error {
|
||||
switch operation {
|
||||
case "recover":
|
||||
if err := u.handleDockerDatasWithSave(fileOp, "snapshot", "", u.OriginalPath); err != nil {
|
||||
return fmt.Errorf("backup docker data failed, err: %v", err)
|
||||
}
|
||||
std, err := cmd.Execf("docker load < %s", path.Join(source, "docker/docker_image.tar"))
|
||||
if err != nil {
|
||||
return errors.New(std)
|
||||
}
|
||||
case "re-recover":
|
||||
std, err := cmd.Execf("docker load < %s", path.Join(source, "docker/docker_image.tar"))
|
||||
if err != nil {
|
||||
return errors.New(std)
|
||||
}
|
||||
case "rollback":
|
||||
std, err := cmd.Execf("docker load < %s", path.Join(source, "docker_image.tar"))
|
||||
if err != nil {
|
||||
return errors.New(std)
|
||||
}
|
||||
}
|
||||
global.LOG.Info("handle docker data successful!")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *SnapshotService) handleDaemonJson(fileOp files.FileOp, operation string, source, target string) error {
|
||||
daemonJsonPath := "/etc/docker/daemon.json"
|
||||
if operation == "snapshot" || operation == "recover" {
|
||||
_, err := os.Stat(daemonJsonPath)
|
||||
if os.IsNotExist(err) {
|
||||
global.LOG.Info("no daemon.json in snapshot and system now, nothing happened")
|
||||
}
|
||||
if err == nil {
|
||||
if err := fileOp.CopyFile(daemonJsonPath, target); err != nil {
|
||||
return fmt.Errorf("backup docker daemon.json failed, err: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
if operation == "recover" || operation == "rollback" || operation == "re-recover" {
|
||||
_, sourceErr := os.Stat(source)
|
||||
if os.IsNotExist(sourceErr) {
|
||||
_ = os.Remove(daemonJsonPath)
|
||||
}
|
||||
if sourceErr == nil {
|
||||
if err := fileOp.CopyFile(source, "/etc/docker"); err != nil {
|
||||
return fmt.Errorf("recover docker daemon.json failed, err: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
global.LOG.Info("handle docker daemon.json successful!")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *SnapshotService) handlePanelBinary(fileOp files.FileOp, operation string, source, target string) error {
|
||||
panelPath := "/usr/local/bin/1panel"
|
||||
if operation == "snapshot" || operation == "recover" {
|
||||
if _, err := os.Stat(panelPath); err != nil {
|
||||
return fmt.Errorf("1panel binary is not found in %s, err: %v", panelPath, err)
|
||||
} else {
|
||||
if err := cpBinary([]string{panelPath}, target); err != nil {
|
||||
return fmt.Errorf("backup 1panel binary failed, err: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
if operation == "recover" || operation == "rollback" || operation == "re-recover" {
|
||||
if _, err := os.Stat(source); err != nil {
|
||||
return fmt.Errorf("1panel binary is not found in snapshot, err: %v", err)
|
||||
} else {
|
||||
if err := cpBinary([]string{source}, "/usr/local/bin/1panel"); err != nil {
|
||||
return fmt.Errorf("recover 1panel binary failed, err: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
global.LOG.Info("handle binary panel successful!")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *SnapshotService) handlePanelctlBinary(fileOp files.FileOp, operation string, source, target string) error {
|
||||
panelctlPath := "/usr/local/bin/1pctl"
|
||||
if operation == "snapshot" || operation == "recover" {
|
||||
if _, err := os.Stat(panelctlPath); err != nil {
|
||||
return fmt.Errorf("1pctl binary is not found in %s, err: %v", panelctlPath, err)
|
||||
} else {
|
||||
if err := cpBinary([]string{panelctlPath}, target); err != nil {
|
||||
return fmt.Errorf("backup 1pctl binary failed, err: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
if operation == "recover" || operation == "rollback" || operation == "re-recover" {
|
||||
if _, err := os.Stat(source); err != nil {
|
||||
return fmt.Errorf("1pctl binary is not found in snapshot, err: %v", err)
|
||||
} else {
|
||||
if err := cpBinary([]string{source}, "/usr/local/bin/1pctl"); err != nil {
|
||||
return fmt.Errorf("recover 1pctl binary failed, err: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
global.LOG.Info("handle binary 1pactl successful!")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *SnapshotService) handlePanelService(fileOp files.FileOp, operation string, source, target string) error {
|
||||
panelServicePath := "/etc/systemd/system/1panel.service"
|
||||
if operation == "snapshot" || operation == "recover" {
|
||||
if _, err := os.Stat(panelServicePath); err != nil {
|
||||
return fmt.Errorf("1panel service is not found in %s, err: %v", panelServicePath, err)
|
||||
} else {
|
||||
if err := cpBinary([]string{panelServicePath}, target); err != nil {
|
||||
return fmt.Errorf("backup 1panel service failed, err: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
if operation == "recover" || operation == "rollback" || operation == "re-recover" {
|
||||
if _, err := os.Stat(source); err != nil {
|
||||
return fmt.Errorf("1panel service is not found in snapshot, err: %v", err)
|
||||
} else {
|
||||
if err := cpBinary([]string{source}, "/etc/systemd/system/1panel.service"); err != nil {
|
||||
return fmt.Errorf("recover 1panel service failed, err: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
global.LOG.Info("handle panel service successful!")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *SnapshotService) handleBackupDatas(fileOp files.FileOp, operation string, source, target string) error {
|
||||
switch operation {
|
||||
case "recover":
|
||||
if err := handleSnapTar(target, u.OriginalPath, "1panel_backup.tar.gz", "./system;"); err != nil {
|
||||
return fmt.Errorf("restore original local backup dir data failed, err: %v", err)
|
||||
}
|
||||
if err := u.handleUnTar(source+"/1panel/1panel_backup.tar.gz", target); err != nil {
|
||||
return fmt.Errorf("recover local backup dir data failed, err: %v", err)
|
||||
}
|
||||
case "re-recover":
|
||||
if err := u.handleUnTar(source+"/1panel/1panel_backup.tar.gz", target); err != nil {
|
||||
return fmt.Errorf("retry recover local backup dir data failed, err: %v", err)
|
||||
}
|
||||
case "rollback":
|
||||
if err := u.handleUnTar(source+"/1panel_backup.tar.gz", target); err != nil {
|
||||
return fmt.Errorf("rollback local backup dir data failed, err: %v", err)
|
||||
}
|
||||
}
|
||||
global.LOG.Info("handle backup data successful!")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *SnapshotService) handlePanelDatas(snapID uint, fileOp files.FileOp, operation string, source, target, backupDir, dockerDir string) error {
|
||||
switch operation {
|
||||
case "snapshot":
|
||||
exclusionRules := "./tmp;./log;./cache;"
|
||||
if strings.Contains(backupDir, source) {
|
||||
exclusionRules += ("." + strings.ReplaceAll(backupDir, source, "") + ";")
|
||||
}
|
||||
if strings.Contains(dockerDir, source) {
|
||||
exclusionRules += ("." + strings.ReplaceAll(dockerDir, source, "") + ";")
|
||||
}
|
||||
|
||||
if err := handleSnapTar(source, target, "1panel_data.tar.gz", exclusionRules); err != nil {
|
||||
return fmt.Errorf("backup panel data failed, err: %v", err)
|
||||
}
|
||||
case "recover":
|
||||
exclusionRules := "./tmp;./log;./cache;./db/1Panel.db-*;"
|
||||
if strings.Contains(backupDir, target) {
|
||||
exclusionRules += ("." + strings.ReplaceAll(backupDir, target, "") + ";")
|
||||
}
|
||||
if strings.Contains(dockerDir, target) {
|
||||
exclusionRules += ("." + strings.ReplaceAll(dockerDir, target, "") + ";")
|
||||
}
|
||||
|
||||
_ = snapshotRepo.Update(snapID, map[string]interface{}{"recover_status": ""})
|
||||
checkPointOfWal()
|
||||
if err := handleSnapTar(target, u.OriginalPath, "1panel_data.tar.gz", exclusionRules); err != nil {
|
||||
return fmt.Errorf("restore original panel data failed, err: %v", err)
|
||||
}
|
||||
_ = snapshotRepo.Update(snapID, map[string]interface{}{"recover_status": constant.StatusWaiting})
|
||||
|
||||
_ = fileOp.Fs.RemoveAll(path.Join(target, "apps"))
|
||||
checkPointOfWal()
|
||||
if err := u.handleUnTar(source+"/1panel/1panel_data.tar.gz", target); err != nil {
|
||||
return fmt.Errorf("recover panel data failed, err: %v", err)
|
||||
}
|
||||
case "re-recover":
|
||||
_ = fileOp.Fs.RemoveAll(path.Join(target, "apps"))
|
||||
checkPointOfWal()
|
||||
if err := u.handleUnTar(source+"/1panel/1panel_data.tar.gz", target); err != nil {
|
||||
return fmt.Errorf("retry recover panel data failed, err: %v", err)
|
||||
}
|
||||
case "rollback":
|
||||
_ = fileOp.Fs.RemoveAll(path.Join(target, "apps"))
|
||||
checkPointOfWal()
|
||||
if err := u.handleUnTar(source+"/1panel_data.tar.gz", target); err != nil {
|
||||
return fmt.Errorf("rollback panel data failed, err: %v", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *SnapshotService) loadDockerDataDir() (string, bool, error) {
|
||||
client, err := docker.NewDockerClient()
|
||||
if err != nil {
|
||||
return "", false, fmt.Errorf("new docker client failed, err: %v", err)
|
||||
}
|
||||
info, err := client.Info(context.Background())
|
||||
if err != nil {
|
||||
return "", false, fmt.Errorf("load docker info failed, err: %v", err)
|
||||
}
|
||||
return info.DockerRootDir, info.LiveRestoreEnabled, nil
|
||||
}
|
||||
|
||||
func (u *SnapshotService) Delete(req dto.BatchDeleteReq) error {
|
||||
backups, _ := snapshotRepo.GetList(commonRepo.WithIdsIn(req.Ids))
|
||||
localDir, err := loadLocalDir()
|
||||
@@ -863,21 +345,22 @@ func (u *SnapshotService) Delete(req dto.BatchDeleteReq) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func updateRecoverStatus(id uint, interruptStep, status string, message string) {
|
||||
if status != constant.StatusSuccess {
|
||||
global.LOG.Errorf("recover failed, err: %s", message)
|
||||
func updateRecoverStatus(id uint, isRecover bool, interruptStep, status, message string) {
|
||||
if isRecover {
|
||||
if status != constant.StatusSuccess {
|
||||
global.LOG.Errorf("recover failed, err: %s", message)
|
||||
}
|
||||
if err := snapshotRepo.Update(id, map[string]interface{}{
|
||||
"interrupt_step": interruptStep,
|
||||
"recover_status": status,
|
||||
"recover_message": message,
|
||||
"last_recovered_at": time.Now().Format("2006-01-02 15:04:05"),
|
||||
}); err != nil {
|
||||
global.LOG.Errorf("update snap recover status failed, err: %v", err)
|
||||
}
|
||||
_ = settingRepo.Update("SystemStatus", "Free")
|
||||
return
|
||||
}
|
||||
if err := snapshotRepo.Update(id, map[string]interface{}{
|
||||
"interrupt_step": interruptStep,
|
||||
"recover_status": status,
|
||||
"recover_message": message,
|
||||
"last_recovered_at": time.Now().Format("2006-01-02 15:04:05"),
|
||||
}); err != nil {
|
||||
global.LOG.Errorf("update snap recover status failed, err: %v", err)
|
||||
}
|
||||
_ = settingRepo.Update("SystemStatus", "Free")
|
||||
}
|
||||
func updateRollbackStatus(id uint, status string, message string) {
|
||||
_ = settingRepo.Update("SystemStatus", "Free")
|
||||
if status == constant.StatusSuccess {
|
||||
if err := snapshotRepo.Update(id, map[string]interface{}{
|
||||
@@ -911,53 +394,6 @@ func cpBinary(src []string, dst string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *SnapshotService) updateLiveRestore(enabled bool) error {
|
||||
if _, err := os.Stat(constant.DaemonJsonPath); err != nil {
|
||||
return fmt.Errorf("load docker daemon.json conf failed, err: %v", err)
|
||||
}
|
||||
file, err := os.ReadFile(constant.DaemonJsonPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
daemonMap := make(map[string]interface{})
|
||||
_ = json.Unmarshal(file, &daemonMap)
|
||||
|
||||
if !enabled {
|
||||
delete(daemonMap, "live-restore")
|
||||
} else {
|
||||
daemonMap["live-restore"] = enabled
|
||||
}
|
||||
newJson, err := json.MarshalIndent(daemonMap, "", "\t")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.WriteFile(constant.DaemonJsonPath, newJson, 0640); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
stdout, err := cmd.Exec("systemctl restart docker")
|
||||
if err != nil {
|
||||
return errors.New(stdout)
|
||||
}
|
||||
|
||||
ticker := time.NewTicker(3 * time.Second)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
|
||||
defer cancel()
|
||||
for range ticker.C {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
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 live-restore successful!")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *SnapshotService) handleUnTar(sourceDir, targetDir string) error {
|
||||
if _, err := os.Stat(targetDir); err != nil && os.IsNotExist(err) {
|
||||
if err = os.MkdirAll(targetDir, os.ModePerm); err != nil {
|
||||
@@ -1010,64 +446,32 @@ func rebuildAllAppInstall() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func isNewSnapVersion(version string) bool {
|
||||
versionItem := "v1.5.0"
|
||||
if version == versionItem {
|
||||
return true
|
||||
}
|
||||
version1s := strings.Split(version, ".")
|
||||
version2s := strings.Split(versionItem, ".")
|
||||
|
||||
n := min(len(version1s), len(version2s))
|
||||
re := regexp.MustCompile("[0-9]+")
|
||||
for i := 0; i < n; i++ {
|
||||
sVersion1s := re.FindAllString(version1s[i], -1)
|
||||
sVersion2s := re.FindAllString(version2s[i], -1)
|
||||
if len(sVersion1s) == 0 {
|
||||
return false
|
||||
}
|
||||
if len(sVersion2s) == 0 {
|
||||
return false
|
||||
}
|
||||
v1num, _ := strconv.Atoi(sVersion1s[0])
|
||||
v2num, _ := strconv.Atoi(sVersion2s[0])
|
||||
if v1num == v2num {
|
||||
continue
|
||||
} else {
|
||||
return v1num > v2num
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func min(a, b int) int {
|
||||
if a < b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func checkIsAllDone(snapID uint) bool {
|
||||
status, err := snapshotRepo.GetStatus(snapID)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
isOK, _ := checkAllDone(status)
|
||||
return isOK
|
||||
}
|
||||
|
||||
func checkAllDone(status model.SnapshotStatus) (bool, string) {
|
||||
if status.Panel != constant.StatusDone {
|
||||
return false
|
||||
return false, status.Panel
|
||||
}
|
||||
if status.PanelInfo != constant.StatusDone {
|
||||
return false
|
||||
return false, status.PanelInfo
|
||||
}
|
||||
if status.DaemonJson != constant.StatusDone {
|
||||
return false
|
||||
return false, status.DaemonJson
|
||||
}
|
||||
if status.AppData != constant.StatusDone {
|
||||
return false
|
||||
return false, status.AppData
|
||||
}
|
||||
if status.BackupData != constant.StatusDone {
|
||||
return false
|
||||
return false, status.BackupData
|
||||
}
|
||||
return true
|
||||
return true, ""
|
||||
}
|
||||
|
||||
func loadLogByStatus(status model.SnapshotStatus, logPath string) {
|
||||
@@ -1084,9 +488,28 @@ func loadLogByStatus(status model.SnapshotStatus, logPath string) {
|
||||
|
||||
file, err := os.OpenFile(logPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("write snapshot logs failed, err: %v", err)
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
_, _ = file.Write([]byte(logs))
|
||||
}
|
||||
|
||||
func hasOs(name string) bool {
|
||||
return strings.Contains(name, "amd64") ||
|
||||
strings.Contains(name, "arm64") ||
|
||||
strings.Contains(name, "armv7") ||
|
||||
strings.Contains(name, "ppc64le") ||
|
||||
strings.Contains(name, "s390x")
|
||||
}
|
||||
|
||||
func loadOs() string {
|
||||
hostInfo, _ := host.Info()
|
||||
switch hostInfo.KernelArch {
|
||||
case "x86_64":
|
||||
return "amd64"
|
||||
case "armv7l":
|
||||
return "armv7"
|
||||
default:
|
||||
return hostInfo.KernelArch
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,21 +97,23 @@ func snapAppData(snap snapHelper, targetDir string) {
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, rumtime := range runtimes {
|
||||
for _, runtime := range runtimes {
|
||||
for _, existImage := range existImages {
|
||||
if rumtime.Image == existImage && !duplicateMap[rumtime.Image] {
|
||||
imageSaveList = append(imageSaveList, rumtime.Image)
|
||||
duplicateMap[rumtime.Image] = true
|
||||
if runtime.Image == existImage && !duplicateMap[runtime.Image] {
|
||||
imageSaveList = append(imageSaveList, runtime.Image)
|
||||
duplicateMap[runtime.Image] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
global.LOG.Debugf("docker save %s | gzip -c > %s", strings.Join(imageSaveList, " "), path.Join(targetDir, "docker_image.tar"))
|
||||
std, err := cmd.Execf("docker save %s | gzip -c > %s", strings.Join(imageSaveList, " "), path.Join(targetDir, "docker_image.tar"))
|
||||
if err != nil {
|
||||
snap.Status.AppData = err.Error()
|
||||
_ = snapshotRepo.UpdateStatus(snap.Status.ID, map[string]interface{}{"app_data": std})
|
||||
return
|
||||
if len(imageSaveList) != 0 {
|
||||
global.LOG.Debugf("docker save %s | gzip -c > %s", strings.Join(imageSaveList, " "), path.Join(targetDir, "docker_image.tar"))
|
||||
std, err := cmd.Execf("docker save %s | gzip -c > %s", strings.Join(imageSaveList, " "), path.Join(targetDir, "docker_image.tar"))
|
||||
if err != nil {
|
||||
snap.Status.AppData = err.Error()
|
||||
_ = snapshotRepo.UpdateStatus(snap.Status.ID, map[string]interface{}{"app_data": std})
|
||||
return
|
||||
}
|
||||
}
|
||||
snap.Status.AppData = constant.StatusDone
|
||||
_ = snapshotRepo.UpdateStatus(snap.Status.ID, map[string]interface{}{"app_data": constant.StatusDone})
|
||||
@@ -121,7 +123,7 @@ func snapBackup(snap snapHelper, localDir, targetDir string) {
|
||||
defer snap.Wg.Done()
|
||||
_ = snapshotRepo.UpdateStatus(snap.Status.ID, map[string]interface{}{"backup_data": constant.Running})
|
||||
status := constant.StatusDone
|
||||
if err := handleSnapTar(localDir, targetDir, "1panel_backup.tar.gz", "./system;"); err != nil {
|
||||
if err := handleSnapTar(localDir, targetDir, "1panel_backup.tar.gz", "./system;./system_snapshot;"); err != nil {
|
||||
status = err.Error()
|
||||
}
|
||||
snap.Status.BackupData = status
|
||||
@@ -138,6 +140,8 @@ func snapPanelData(snap snapHelper, localDir, targetDir string) {
|
||||
}
|
||||
|
||||
_ = snapshotRepo.Update(snap.SnapID, map[string]interface{}{"status": "OnSaveData"})
|
||||
sysIP, _ := settingRepo.Get(settingRepo.WithByKey("SystemIP"))
|
||||
_ = settingRepo.Update("SystemIP", "")
|
||||
checkPointOfWal()
|
||||
if err := handleSnapTar(dataDir, targetDir, "1panel_data.tar.gz", exclusionRules); err != nil {
|
||||
status = err.Error()
|
||||
@@ -146,6 +150,7 @@ func snapPanelData(snap snapHelper, localDir, targetDir string) {
|
||||
|
||||
snap.Status.PanelData = status
|
||||
_ = snapshotRepo.UpdateStatus(snap.Status.ID, map[string]interface{}{"panel_data": status})
|
||||
_ = settingRepo.Update("SystemIP", sysIP.Value)
|
||||
}
|
||||
|
||||
func snapCompress(snap snapHelper, rootDir string) {
|
||||
|
||||
@@ -0,0 +1,263 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"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/cmd"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/files"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func (u *SnapshotService) HandleSnapshotRecover(snap model.Snapshot, isRecover bool, req dto.SnapshotRecover) {
|
||||
_ = global.Cron.Stop()
|
||||
defer func() {
|
||||
global.Cron.Start()
|
||||
}()
|
||||
|
||||
snapFileDir := ""
|
||||
if isRecover {
|
||||
baseDir := path.Join(global.CONF.System.TmpDir, fmt.Sprintf("system/%s", snap.Name))
|
||||
if _, err := os.Stat(baseDir); err != nil && os.IsNotExist(err) {
|
||||
_ = os.MkdirAll(baseDir, os.ModePerm)
|
||||
}
|
||||
if req.IsNew || snap.InterruptStep == "Backup" {
|
||||
if err := backupBeforeRecover(snap); err != nil {
|
||||
updateRecoverStatus(snap.ID, isRecover, "Backup", constant.StatusFailed, fmt.Sprintf("handle backup before recover failed, err: %v", err))
|
||||
return
|
||||
}
|
||||
global.LOG.Debug("handle backup before recover successful!")
|
||||
req.IsNew = true
|
||||
}
|
||||
if req.IsNew || snap.InterruptStep == "Download" || req.ReDownload {
|
||||
if err := handleDownloadSnapshot(snap, baseDir); err != nil {
|
||||
updateRecoverStatus(snap.ID, isRecover, "Backup", constant.StatusFailed, err.Error())
|
||||
return
|
||||
}
|
||||
global.LOG.Debugf("download snapshot file to %s successful!", baseDir)
|
||||
req.IsNew = true
|
||||
}
|
||||
if req.IsNew || snap.InterruptStep == "Decompress" {
|
||||
if err := handleUnTar(fmt.Sprintf("%s/%s.tar.gz", baseDir, snap.Name), baseDir); err != nil {
|
||||
updateRecoverStatus(snap.ID, isRecover, "Decompress", constant.StatusFailed, fmt.Sprintf("decompress file failed, err: %v", err))
|
||||
return
|
||||
}
|
||||
global.LOG.Debug("decompress snapshot file successful!", baseDir)
|
||||
req.IsNew = true
|
||||
}
|
||||
snapFileDir = fmt.Sprintf("%s/%s", baseDir, snap.Name)
|
||||
} else {
|
||||
snapFileDir = fmt.Sprintf("%s/1panel_original/original_%s", global.CONF.System.BaseDir, snap.Name)
|
||||
if _, err := os.Stat(snapFileDir); err != nil {
|
||||
updateRecoverStatus(snap.ID, isRecover, "", constant.StatusFailed, fmt.Sprintf("cannot find the backup file %s, please try to recover again.", snapFileDir))
|
||||
return
|
||||
}
|
||||
}
|
||||
snapJson, err := u.readFromJson(fmt.Sprintf("%s/snapshot.json", snapFileDir))
|
||||
if err != nil {
|
||||
updateRecoverStatus(snap.ID, isRecover, "Readjson", constant.StatusFailed, fmt.Sprintf("decompress file failed, err: %v", err))
|
||||
return
|
||||
}
|
||||
if snap.InterruptStep == "Readjson" {
|
||||
req.IsNew = true
|
||||
}
|
||||
if req.IsNew || snap.InterruptStep == "AppData" {
|
||||
if err := recoverAppData(snapFileDir); err != nil {
|
||||
updateRecoverStatus(snap.ID, isRecover, "DockerDir", constant.StatusFailed, fmt.Sprintf("handle recover app data failed, err: %v", err))
|
||||
return
|
||||
}
|
||||
global.LOG.Debug("recover app data from snapshot file successful!")
|
||||
req.IsNew = true
|
||||
}
|
||||
if req.IsNew || snap.InterruptStep == "DaemonJson" {
|
||||
fileOp := files.NewFileOp()
|
||||
if err := recoverDaemonJson(snapFileDir, fileOp); err != nil {
|
||||
updateRecoverStatus(snap.ID, isRecover, "DaemonJson", constant.StatusFailed, err.Error())
|
||||
return
|
||||
}
|
||||
global.LOG.Debug("recover daemon.json from snapshot file successful!")
|
||||
req.IsNew = true
|
||||
}
|
||||
|
||||
if req.IsNew || snap.InterruptStep == "1PanelBinary" {
|
||||
if err := recoverPanel(path.Join(snapFileDir, "1panel/1panel"), "/usr/local/bin/1panel"); err != nil {
|
||||
updateRecoverStatus(snap.ID, isRecover, "1PanelBinary", constant.StatusFailed, err.Error())
|
||||
return
|
||||
}
|
||||
global.LOG.Debug("recover 1panel binary from snapshot file successful!")
|
||||
req.IsNew = true
|
||||
}
|
||||
if req.IsNew || snap.InterruptStep == "1PctlBinary" {
|
||||
if err := recoverPanel(path.Join(snapFileDir, "1panel/1pctl"), "/usr/local/bin/1pctl"); err != nil {
|
||||
updateRecoverStatus(snap.ID, isRecover, "1PctlBinary", constant.StatusFailed, err.Error())
|
||||
return
|
||||
}
|
||||
global.LOG.Debug("recover 1pctl from snapshot file successful!")
|
||||
req.IsNew = true
|
||||
}
|
||||
if req.IsNew || snap.InterruptStep == "1PanelService" {
|
||||
if err := recoverPanel(path.Join(snapFileDir, "1panel/1panel.service"), "/etc/systemd/system/1panel.service"); err != nil {
|
||||
updateRecoverStatus(snap.ID, isRecover, "1PanelService", constant.StatusFailed, err.Error())
|
||||
return
|
||||
}
|
||||
global.LOG.Debug("recover 1panel service from snapshot file successful!")
|
||||
req.IsNew = true
|
||||
}
|
||||
|
||||
if req.IsNew || snap.InterruptStep == "1PanelBackups" {
|
||||
if err := u.handleUnTar(path.Join(snapFileDir, "/1panel/1panel_backup.tar.gz"), snapJson.BackupDataDir); err != nil {
|
||||
updateRecoverStatus(snap.ID, isRecover, "1PanelBackups", constant.StatusFailed, err.Error())
|
||||
return
|
||||
}
|
||||
global.LOG.Debug("recover 1panel backups from snapshot file successful!")
|
||||
req.IsNew = true
|
||||
}
|
||||
|
||||
if req.IsNew || snap.InterruptStep == "1PanelData" {
|
||||
checkPointOfWal()
|
||||
if err := u.handleUnTar(path.Join(snapFileDir, "/1panel/1panel_data.tar.gz"), path.Join(snapJson.BaseDir, "1panel")); err != nil {
|
||||
updateRecoverStatus(snap.ID, isRecover, "1PanelData", constant.StatusFailed, err.Error())
|
||||
return
|
||||
}
|
||||
global.LOG.Debug("recover 1panel data from snapshot file successful!")
|
||||
req.IsNew = true
|
||||
}
|
||||
_ = rebuildAllAppInstall()
|
||||
restartCompose(path.Join(snapJson.BaseDir, "1panel/docker/compose"))
|
||||
|
||||
global.LOG.Info("recover successful")
|
||||
if !isRecover {
|
||||
oriPath := fmt.Sprintf("%s/1panel_original/original_%s", global.CONF.System.BaseDir, snap.Name)
|
||||
global.LOG.Debugf("remove the file %s after the operation is successful", oriPath)
|
||||
_ = os.RemoveAll(oriPath)
|
||||
} else {
|
||||
global.LOG.Debugf("remove the file %s after the operation is successful", path.Dir(snapFileDir))
|
||||
_ = os.RemoveAll(path.Dir(snapFileDir))
|
||||
}
|
||||
_, _ = cmd.Exec("systemctl daemon-reload && systemctl restart 1panel.service")
|
||||
}
|
||||
|
||||
func backupBeforeRecover(snap model.Snapshot) error {
|
||||
baseDir := fmt.Sprintf("%s/1panel_original/original_%s", global.CONF.System.BaseDir, snap.Name)
|
||||
var wg sync.WaitGroup
|
||||
var status model.SnapshotStatus
|
||||
itemHelper := snapHelper{SnapID: 0, Status: &status, Wg: &wg, FileOp: files.NewFileOp(), Ctx: context.Background()}
|
||||
|
||||
jsonItem := SnapshotJson{
|
||||
BaseDir: global.CONF.System.BaseDir,
|
||||
BackupDataDir: global.CONF.System.Backup,
|
||||
PanelDataDir: path.Join(global.CONF.System.BaseDir, "1panel"),
|
||||
}
|
||||
_ = os.MkdirAll(path.Join(baseDir, "1panel"), os.ModePerm)
|
||||
_ = os.MkdirAll(path.Join(baseDir, "docker"), os.ModePerm)
|
||||
|
||||
wg.Add(5)
|
||||
itemHelper.Wg = &wg
|
||||
go snapJson(itemHelper, jsonItem, baseDir)
|
||||
go snapPanel(itemHelper, path.Join(baseDir, "1panel"))
|
||||
go snapDaemonJson(itemHelper, path.Join(baseDir, "docker"))
|
||||
go snapAppData(itemHelper, path.Join(baseDir, "docker"))
|
||||
go snapBackup(itemHelper, global.CONF.System.Backup, path.Join(baseDir, "1panel"))
|
||||
wg.Wait()
|
||||
|
||||
allDone, msg := checkAllDone(status)
|
||||
if !allDone {
|
||||
return errors.New(msg)
|
||||
}
|
||||
snapPanelData(itemHelper, global.CONF.System.BaseDir, path.Join(baseDir, "1panel"))
|
||||
if status.PanelData != constant.StatusDone {
|
||||
return errors.New(status.PanelData)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleDownloadSnapshot(snap model.Snapshot, targetDir string) error {
|
||||
backup, err := backupRepo.Get(commonRepo.WithByType(snap.DefaultDownload))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
client, err := NewIBackupService().NewClient(&backup)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pathItem := backup.BackupPath
|
||||
if backup.BackupPath != "/" {
|
||||
pathItem = strings.TrimPrefix(backup.BackupPath, "/")
|
||||
}
|
||||
filePath := fmt.Sprintf("%s/%s.tar.gz", targetDir, snap.Name)
|
||||
_ = os.RemoveAll(filePath)
|
||||
ok, err := client.Download(path.Join(pathItem, fmt.Sprintf("system_snapshot/%s.tar.gz", snap.Name)), filePath)
|
||||
if err != nil || !ok {
|
||||
return fmt.Errorf("download file %s from %s failed, err: %v", snap.Name, backup.Type, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func recoverAppData(src string) error {
|
||||
if _, err := os.Stat(path.Join(src, "docker/docker_image.tar")); err != nil {
|
||||
global.LOG.Debug("no such docker images in snapshot")
|
||||
return nil
|
||||
}
|
||||
std, err := cmd.Execf("docker load < %s", path.Join(src, "docker/docker_image.tar"))
|
||||
if err != nil {
|
||||
return errors.New(std)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func recoverDaemonJson(src string, fileOp files.FileOp) error {
|
||||
daemonJsonPath := "/etc/docker/daemon.json"
|
||||
_, errSrc := os.Stat(src)
|
||||
_, errPath := os.Stat(daemonJsonPath)
|
||||
if os.IsNotExist(errSrc) && os.IsNotExist(errPath) {
|
||||
global.LOG.Debug("the daemon.json file does not exist, nothing happens.")
|
||||
return nil
|
||||
}
|
||||
if errSrc == nil {
|
||||
if err := fileOp.CopyFile(path.Join(src, "docker/daemon.json"), "/etc/docker"); err != nil {
|
||||
return fmt.Errorf("recover docker daemon.json failed, err: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
_, _ = cmd.Exec("systemctl restart docker")
|
||||
return nil
|
||||
}
|
||||
|
||||
func recoverPanel(src string, dst string) error {
|
||||
if _, err := os.Stat(src); err != nil {
|
||||
return fmt.Errorf("file is not found in %s, err: %v", src, err)
|
||||
}
|
||||
global.LOG.Debugf(fmt.Sprintf("\\cp -f %s %s", src, dst))
|
||||
stdout, err := cmd.Exec(fmt.Sprintf("\\cp -f %s %s", src, dst))
|
||||
if err != nil {
|
||||
return fmt.Errorf("cp file failed, stdout: %v, err: %v", stdout, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func restartCompose(composePath string) {
|
||||
composes, err := composeRepo.ListRecord()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for _, compose := range composes {
|
||||
pathItem := path.Join(composePath, compose.Name, "docker-compose.yml")
|
||||
if _, err := os.Stat(pathItem); err != nil {
|
||||
continue
|
||||
}
|
||||
upCmd := fmt.Sprintf("docker-compose -f %s up -d", pathItem)
|
||||
stdout, err := cmd.Exec(upCmd)
|
||||
if err != nil {
|
||||
global.LOG.Debugf("%s failed, err: %v", upCmd, stdout)
|
||||
}
|
||||
}
|
||||
global.LOG.Debug("restart all compose successful!")
|
||||
}
|
||||
@@ -133,24 +133,24 @@ func (u *UpgradeService) Upgrade(req dto.Upgrade) error {
|
||||
|
||||
if err := cpBinary([]string{tmpDir + "/1panel"}, "/usr/local/bin/1panel"); err != nil {
|
||||
global.LOG.Errorf("upgrade 1panel failed, err: %v", err)
|
||||
u.handleRollback(fileOp, originalDir, 1)
|
||||
u.handleRollback(originalDir, 1)
|
||||
return
|
||||
}
|
||||
|
||||
if err := cpBinary([]string{tmpDir + "/1pctl"}, "/usr/local/bin/1pctl"); err != nil {
|
||||
global.LOG.Errorf("upgrade 1pctl failed, err: %v", err)
|
||||
u.handleRollback(fileOp, originalDir, 2)
|
||||
u.handleRollback(originalDir, 2)
|
||||
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("upgrade basedir in 1pctl failed, err: %v", err)
|
||||
u.handleRollback(fileOp, originalDir, 2)
|
||||
u.handleRollback(originalDir, 2)
|
||||
return
|
||||
}
|
||||
|
||||
if err := cpBinary([]string{tmpDir + "/1panel.service"}, "/etc/systemd/system/1panel.service"); err != nil {
|
||||
global.LOG.Errorf("upgrade 1panel.service failed, err: %v", err)
|
||||
u.handleRollback(fileOp, originalDir, 3)
|
||||
u.handleRollback(originalDir, 3)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -181,7 +181,7 @@ func (u *UpgradeService) handleBackup(fileOp files.FileOp, originalDir string) e
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *UpgradeService) handleRollback(fileOp files.FileOp, originalDir string, errStep int) {
|
||||
func (u *UpgradeService) handleRollback(originalDir string, errStep int) {
|
||||
dbPath := global.CONF.System.DbPath + "/1Panel.db"
|
||||
_ = settingRepo.Update("SystemStatus", "Free")
|
||||
if err := cpBinary([]string{originalDir + "/1Panel.db"}, dbPath); err != nil {
|
||||
@@ -261,8 +261,8 @@ func loadArch() (string, error) {
|
||||
if std == "armv7l\n" {
|
||||
return "armv7", nil
|
||||
}
|
||||
return "", fmt.Errorf("unsupport such arch: arm-%s", std)
|
||||
return "", fmt.Errorf("unsupported such arch: arm-%s", std)
|
||||
default:
|
||||
return "", fmt.Errorf("unsupport such arch: %s", runtime.GOARCH)
|
||||
return "", fmt.Errorf("unsupported such arch: %s", runtime.GOARCH)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -820,30 +820,27 @@ func (w WebsiteService) PreInstallCheck(req request.WebsiteInstallCheckReq) ([]r
|
||||
} else {
|
||||
checkIds = append(req.InstallIds, appInstall.ID)
|
||||
}
|
||||
for _, id := range checkIds {
|
||||
if err := syncByID(id); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if len(checkIds) > 0 {
|
||||
installList, _ := appInstallRepo.ListBy(commonRepo.WithIdsIn(checkIds))
|
||||
for _, install := range installList {
|
||||
if err = syncAppInstallStatus(&install); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res = append(res, response.WebsitePreInstallCheck{
|
||||
Name: install.Name,
|
||||
Status: install.Status,
|
||||
Version: install.Version,
|
||||
AppName: install.App.Name,
|
||||
})
|
||||
if install.Status != constant.StatusRunning {
|
||||
if install.Status != constant.Running {
|
||||
showErr = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if showErr {
|
||||
return res, nil
|
||||
} else {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (w WebsiteService) GetWafConfig(req request.WebsiteWafReq) (response.WebsiteWafConfig, error) {
|
||||
@@ -1511,13 +1508,19 @@ func (w WebsiteService) OperateProxy(req request.WebsiteProxyConfig) (err error)
|
||||
|
||||
switch req.Operate {
|
||||
case "create":
|
||||
config = parser.NewStringParser(string(nginx_conf.Proxy)).Parse()
|
||||
config, err = parser.NewStringParser(string(nginx_conf.Proxy)).Parse()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
case "edit":
|
||||
par, err = parser.NewParser(includePath)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
config = par.Parse()
|
||||
config, err = par.Parse()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
oldContent, err = fileOp.GetContent(includePath)
|
||||
if err != nil {
|
||||
return
|
||||
@@ -1533,6 +1536,7 @@ func (w WebsiteService) OperateProxy(req request.WebsiteProxyConfig) (err error)
|
||||
_ = fileOp.Rename(backPath, includePath)
|
||||
return updateNginxConfig(constant.NginxScopeServer, nil, &website)
|
||||
}
|
||||
|
||||
config.FilePath = includePath
|
||||
directives := config.Directives
|
||||
location, ok := directives[0].(*components.Location)
|
||||
@@ -1610,7 +1614,10 @@ func (w WebsiteService) GetProxies(id uint) (res []request.WebsiteProxyConfig, e
|
||||
return
|
||||
}
|
||||
proxyConfig.Content = string(content)
|
||||
config = parser.NewStringParser(string(content)).Parse()
|
||||
config, err = parser.NewStringParser(string(content)).Parse()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
directives := config.GetDirectives()
|
||||
|
||||
location, ok := directives[0].(*components.Location)
|
||||
@@ -2057,7 +2064,10 @@ func (w WebsiteService) OperateRedirect(req request.NginxRedirectReq) (err error
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
config = oldPar.Parse()
|
||||
config, err = oldPar.Parse()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
oldContent, err = fileOp.GetContent(includePath)
|
||||
if err != nil {
|
||||
return
|
||||
@@ -2198,7 +2208,10 @@ func (w WebsiteService) GetRedirect(id uint) (res []response.NginxRedirectConfig
|
||||
return
|
||||
}
|
||||
redirectConfig.Content = string(content)
|
||||
config = parser.NewStringParser(string(content)).Parse()
|
||||
config, err = parser.NewStringParser(string(content)).Parse()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
dirs := config.GetDirectives()
|
||||
if len(dirs) > 0 {
|
||||
|
||||
@@ -2,13 +2,6 @@ package service
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/1Panel-dev/1Panel/backend/buserr"
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"github.com/1Panel-dev/1Panel/backend/i18n"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/common"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/nginx/components"
|
||||
"gopkg.in/yaml.v3"
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
@@ -18,6 +11,14 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/buserr"
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"github.com/1Panel-dev/1Panel/backend/i18n"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/common"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/nginx/components"
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto/request"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||
@@ -114,7 +115,7 @@ func createIndexFile(website *model.Website, runtime *model.Runtime) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func createProxyFile(website *model.Website, runtime *model.Runtime) error {
|
||||
func createProxyFile(website *model.Website) error {
|
||||
nginxInstall, err := getAppInstallByKey(constant.AppOpenresty)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -132,7 +133,10 @@ func createProxyFile(website *model.Website, runtime *model.Runtime) error {
|
||||
return err
|
||||
}
|
||||
}
|
||||
config := parser.NewStringParser(string(nginx_conf.Proxy)).Parse()
|
||||
config, err := parser.NewStringParser(string(nginx_conf.Proxy)).Parse()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
config.FilePath = filePath
|
||||
directives := config.Directives
|
||||
location, ok := directives[0].(*components.Location)
|
||||
@@ -188,7 +192,7 @@ func createWebsiteFolder(nginxInstall model.AppInstall, website *model.Website,
|
||||
}
|
||||
}
|
||||
if website.Type == constant.Proxy {
|
||||
if err := createProxyFile(website, runtime); err != nil {
|
||||
if err := createProxyFile(website); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -208,7 +212,10 @@ func configDefaultNginx(website *model.Website, domains []model.WebsiteDomain, a
|
||||
nginxFileName := website.Alias + ".conf"
|
||||
configPath := path.Join(constant.AppInstallDir, constant.AppOpenresty, nginxInstall.Name, "conf", "conf.d", nginxFileName)
|
||||
nginxContent := string(nginx_conf.WebsiteDefault)
|
||||
config := parser.NewStringParser(nginxContent).Parse()
|
||||
config, err := parser.NewStringParser(nginxContent).Parse()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
servers := config.FindServers()
|
||||
if len(servers) == 0 {
|
||||
return errors.New("nginx config is not valid")
|
||||
@@ -872,7 +879,10 @@ func ChangeHSTSConfig(enable bool, nginxInstall model.AppInstall, website model.
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
config := par.Parse()
|
||||
config, err := par.Parse()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
config.FilePath = path
|
||||
directives := config.Directives
|
||||
location, ok := directives[0].(*components.Location)
|
||||
|
||||
@@ -12,7 +12,7 @@ const (
|
||||
Rebuilding = "Rebuilding"
|
||||
Syncing = "Syncing"
|
||||
SyncSuccess = "SyncSuccess"
|
||||
SyncErr = "SyncErr"
|
||||
Paused = "Paused"
|
||||
|
||||
ContainerPrefix = "1Panel-"
|
||||
|
||||
|
||||
@@ -62,6 +62,7 @@ var (
|
||||
ErrAppNameExist = "ErrAppNameExist"
|
||||
ErrFileNotFound = "ErrFileNotFound"
|
||||
ErrFileParseApp = "ErrFileParseApp"
|
||||
ErrAppParamKey = "ErrAppParamKey"
|
||||
)
|
||||
|
||||
// website
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/backend/init/cache/badger_db"
|
||||
"github.com/1Panel-dev/1Panel/backend/init/session/psession"
|
||||
"github.com/go-playground/validator/v10"
|
||||
"github.com/nicksnyder/go-i18n/v2/i18n"
|
||||
"github.com/robfig/cron/v3"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/spf13/viper"
|
||||
@@ -23,4 +24,6 @@ var (
|
||||
Cron *cron.Cron
|
||||
MonitorCronID cron.EntryID
|
||||
OneDriveCronID cron.EntryID
|
||||
|
||||
I18n *i18n.Localizer
|
||||
)
|
||||
|
||||
+25
-27
@@ -2,9 +2,9 @@ package i18n
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"strings"
|
||||
|
||||
ginI18n "github.com/gin-contrib/i18n"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/nicksnyder/go-i18n/v2/i18n"
|
||||
"golang.org/x/text/language"
|
||||
@@ -12,13 +12,13 @@ import (
|
||||
)
|
||||
|
||||
func GetMsgWithMap(key string, maps map[string]interface{}) string {
|
||||
content := ""
|
||||
var content string
|
||||
if maps == nil {
|
||||
content = ginI18n.MustGetMessage(&i18n.LocalizeConfig{
|
||||
content, _ = global.I18n.Localize(&i18n.LocalizeConfig{
|
||||
MessageID: key,
|
||||
})
|
||||
} else {
|
||||
content = ginI18n.MustGetMessage(&i18n.LocalizeConfig{
|
||||
content, _ = global.I18n.Localize(&i18n.LocalizeConfig{
|
||||
MessageID: key,
|
||||
TemplateData: maps,
|
||||
})
|
||||
@@ -32,13 +32,13 @@ func GetMsgWithMap(key string, maps map[string]interface{}) string {
|
||||
}
|
||||
|
||||
func GetErrMsg(key string, maps map[string]interface{}) string {
|
||||
content := ""
|
||||
var content string
|
||||
if maps == nil {
|
||||
content = ginI18n.MustGetMessage(&i18n.LocalizeConfig{
|
||||
content, _ = global.I18n.Localize(&i18n.LocalizeConfig{
|
||||
MessageID: key,
|
||||
})
|
||||
} else {
|
||||
content = ginI18n.MustGetMessage(&i18n.LocalizeConfig{
|
||||
content, _ = global.I18n.Localize(&i18n.LocalizeConfig{
|
||||
MessageID: key,
|
||||
TemplateData: maps,
|
||||
})
|
||||
@@ -47,7 +47,7 @@ func GetErrMsg(key string, maps map[string]interface{}) string {
|
||||
}
|
||||
|
||||
func GetMsgByKey(key string) string {
|
||||
content := ginI18n.MustGetMessage(&i18n.LocalizeConfig{
|
||||
content, _ := global.I18n.Localize(&i18n.LocalizeConfig{
|
||||
MessageID: key,
|
||||
})
|
||||
return content
|
||||
@@ -55,24 +55,22 @@ func GetMsgByKey(key string) string {
|
||||
|
||||
//go:embed lang/*
|
||||
var fs embed.FS
|
||||
var bundle *i18n.Bundle
|
||||
|
||||
func GinI18nLocalize() gin.HandlerFunc {
|
||||
return ginI18n.Localize(
|
||||
ginI18n.WithBundle(&ginI18n.BundleCfg{
|
||||
RootPath: "./lang",
|
||||
AcceptLanguage: []language.Tag{language.Chinese, language.English, language.TraditionalChinese},
|
||||
DefaultLanguage: language.Chinese,
|
||||
FormatBundleFile: "yaml",
|
||||
UnmarshalFunc: yaml.Unmarshal,
|
||||
Loader: &ginI18n.EmbedLoader{FS: fs},
|
||||
}),
|
||||
ginI18n.WithGetLngHandle(
|
||||
func(context *gin.Context, defaultLng string) string {
|
||||
lng := context.GetHeader("Accept-Language")
|
||||
if lng == "" {
|
||||
return defaultLng
|
||||
}
|
||||
return lng
|
||||
},
|
||||
))
|
||||
func UseI18n() gin.HandlerFunc {
|
||||
return func(context *gin.Context) {
|
||||
lang := context.GetHeader("Accept-Language")
|
||||
if lang == "" {
|
||||
lang = "zh"
|
||||
}
|
||||
global.I18n = i18n.NewLocalizer(bundle, lang)
|
||||
}
|
||||
}
|
||||
|
||||
func Init() {
|
||||
bundle = i18n.NewBundle(language.Chinese)
|
||||
bundle.RegisterUnmarshalFunc("yaml", yaml.Unmarshal)
|
||||
_, _ = bundle.LoadMessageFileFS(fs, "lang/zh.yaml")
|
||||
_, _ = bundle.LoadMessageFileFS(fs, "lang/en.yaml")
|
||||
_, _ = bundle.LoadMessageFileFS(fs, "lang/zh-Hant.yaml")
|
||||
}
|
||||
|
||||
@@ -59,6 +59,8 @@ ErrVersionTooLow: 'The current 1Panel version is too low to update the app store
|
||||
ErrAppNameExist: 'App name is already exist'
|
||||
AppStoreIsSyncing: 'The App Store is syncing, please try again later'
|
||||
ErrGetCompose: "Failed to obtain docker-compose.yml file! {{ .detail }}"
|
||||
ErrAppWarn: "Abnormal status, please check the log"
|
||||
ErrAppParamKey: "Parameter {{ .name }} field exception"
|
||||
|
||||
#file
|
||||
ErrFileCanNotRead: "File can not read"
|
||||
|
||||
@@ -59,6 +59,8 @@ ErrVersionTooLow: '當前 1Panel 版本過低,無法更新應用商店,請
|
||||
ErrAppNameExist: '應用名稱已存在'
|
||||
AppStoreIsSyncing: '應用程式商店正在同步中,請稍後再試'
|
||||
ErrGetCompose: "docker-compose.yml 檔案取得失敗!{{ .detail }}"
|
||||
ErrAppWarn: "狀態異常,請查看日誌"
|
||||
ErrAppParamKey: "參數 {{ .name }} 欄位異常"
|
||||
|
||||
#file
|
||||
ErrFileCanNotRead: "此文件不支持預覽"
|
||||
|
||||
@@ -59,6 +59,8 @@ ErrVersionTooLow: '当前 1Panel 版本过低,无法更新应用商店,请
|
||||
ErrAppNameExist: '应用名称已存在'
|
||||
AppStoreIsSyncing: '应用商店正在同步中,请稍后再试'
|
||||
ErrGetCompose: "docker-compose.yml 文件获取失败!{{ .detail }}"
|
||||
ErrAppWarn: "状态异常,请查看日志"
|
||||
ErrAppParamKey: "参数 {{ .name }} 字段异常"
|
||||
|
||||
#file
|
||||
ErrFileCanNotRead: "此文件不支持预览"
|
||||
|
||||
@@ -23,7 +23,7 @@ func syncApp() {
|
||||
|
||||
func syncInstalledApp() {
|
||||
if err := service.NewIAppInstalledService().SyncAll(true); err != nil {
|
||||
global.LOG.Errorf("sync instaled app error: %s", err.Error())
|
||||
global.LOG.Errorf("sync installed app error: %s", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
-13
@@ -5,7 +5,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/dgraph-io/badger/v4"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type Cache struct {
|
||||
@@ -18,18 +17,6 @@ func NewCacheDB(db *badger.DB) *Cache {
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Cache) SetNX(key string, value interface{}) error {
|
||||
err := c.db.Update(func(txn *badger.Txn) error {
|
||||
_, err := txn.Get([]byte(key))
|
||||
if errors.Is(err, badger.ErrKeyNotFound) {
|
||||
v := []byte(fmt.Sprintf("%v", value))
|
||||
return txn.Set([]byte(key), v)
|
||||
}
|
||||
return err
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Cache) Set(key string, value interface{}) error {
|
||||
err := c.db.Update(func(txn *badger.Txn) error {
|
||||
v := []byte(fmt.Sprintf("%v", value))
|
||||
|
||||
@@ -17,13 +17,13 @@ import (
|
||||
func Init() {
|
||||
if _, err := os.Stat(global.CONF.System.DbPath); err != nil {
|
||||
if err := os.MkdirAll(global.CONF.System.DbPath, os.ModePerm); err != nil {
|
||||
panic(fmt.Errorf("init db dir falied, err: %v", err))
|
||||
panic(fmt.Errorf("init db dir failed, err: %v", err))
|
||||
}
|
||||
}
|
||||
fullPath := global.CONF.System.DbPath + "/" + global.CONF.System.DbFile
|
||||
if _, err := os.Stat(fullPath); err != nil {
|
||||
if _, err := os.Create(fullPath); err != nil {
|
||||
panic(fmt.Errorf("init db file falied, err: %v", err))
|
||||
panic(fmt.Errorf("init db file failed, err: %v", err))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -56,8 +56,8 @@ type MineFormatter struct{}
|
||||
func (s *MineFormatter) Format(entry *logrus.Entry) ([]byte, error) {
|
||||
detailInfo := ""
|
||||
if entry.Caller != nil {
|
||||
funcion := strings.ReplaceAll(entry.Caller.Function, "github.com/1Panel-dev/1Panel/backend/", "")
|
||||
detailInfo = fmt.Sprintf("(%s: %d)", funcion, entry.Caller.Line)
|
||||
function := strings.ReplaceAll(entry.Caller.Function, "github.com/1Panel-dev/1Panel/backend/", "")
|
||||
detailInfo = fmt.Sprintf("(%s: %d)", function, entry.Caller.Line)
|
||||
}
|
||||
if len(entry.Data) == 0 {
|
||||
msg := fmt.Sprintf("[%s] [%s] %s %s \n", time.Now().Format(TimeFormat), strings.ToUpper(entry.Level.String()), entry.Message, detailInfo)
|
||||
|
||||
@@ -70,6 +70,8 @@ func Init() {
|
||||
migrations.UpdateCronjobSpec,
|
||||
migrations.UpdateBackupRecordPath,
|
||||
migrations.UpdateSnapshotRecords,
|
||||
|
||||
migrations.UpdateWebDavConf,
|
||||
})
|
||||
if err := m.Migrate(); err != nil {
|
||||
global.LOG.Error(err)
|
||||
|
||||
@@ -316,7 +316,7 @@ var UpdateCronjobSpec = &gormigrate.Migration{
|
||||
var records []model.JobRecords
|
||||
_ = tx.Where("cronjob_id = ?", job.ID).Find(&records).Error
|
||||
for _, record := range records {
|
||||
if job.Type == "snapshot" {
|
||||
if job.Type == "snapshot" && record.Status == constant.StatusSuccess {
|
||||
var snaps []model.Snapshot
|
||||
_ = tx.Where("name like ?", "snapshot_"+"%").Find(&snaps).Error
|
||||
for _, snap := range snaps {
|
||||
@@ -329,12 +329,15 @@ var UpdateCronjobSpec = &gormigrate.Migration{
|
||||
FileName: snap.Name + ".tar.gz",
|
||||
Source: snap.From,
|
||||
BackupType: snap.From,
|
||||
BaseModel: model.BaseModel{
|
||||
CreatedAt: record.CreatedAt,
|
||||
},
|
||||
}
|
||||
_ = tx.Create(&item).Error
|
||||
}
|
||||
continue
|
||||
}
|
||||
if job.Type == "log" {
|
||||
if job.Type == "log" && record.Status == constant.StatusSuccess {
|
||||
item := model.BackupRecord{
|
||||
From: "cronjob",
|
||||
CronjobID: job.ID,
|
||||
@@ -344,11 +347,14 @@ var UpdateCronjobSpec = &gormigrate.Migration{
|
||||
FileName: path.Base(record.File),
|
||||
Source: mapAccount[uint(job.TargetDirID)].Type,
|
||||
BackupType: mapAccount[uint(job.TargetDirID)].Type,
|
||||
BaseModel: model.BaseModel{
|
||||
CreatedAt: record.CreatedAt,
|
||||
},
|
||||
}
|
||||
_ = tx.Create(&item).Error
|
||||
continue
|
||||
}
|
||||
if job.Type == "directory" {
|
||||
if job.Type == "directory" && record.Status == constant.StatusSuccess {
|
||||
item := model.BackupRecord{
|
||||
From: "cronjob",
|
||||
CronjobID: job.ID,
|
||||
@@ -357,6 +363,9 @@ var UpdateCronjobSpec = &gormigrate.Migration{
|
||||
FileDir: path.Dir(strings.TrimPrefix(record.File, itemPath)),
|
||||
FileName: path.Base(record.File),
|
||||
BackupType: mapAccount[uint(job.TargetDirID)].Type,
|
||||
BaseModel: model.BaseModel{
|
||||
CreatedAt: record.CreatedAt,
|
||||
},
|
||||
}
|
||||
if record.FromLocal {
|
||||
item.Source = constant.Local
|
||||
@@ -442,3 +451,29 @@ var UpdateSnapshotRecords = &gormigrate.Migration{
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var UpdateWebDavConf = &gormigrate.Migration{
|
||||
ID: "20240205-update-webdav-conf",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
var backup model.BackupAccount
|
||||
_ = tx.Where("type = ?", constant.WebDAV).First(&backup).Error
|
||||
if backup.ID == 0 {
|
||||
return nil
|
||||
}
|
||||
varMap := make(map[string]interface{})
|
||||
if err := json.Unmarshal([]byte(backup.Vars), &varMap); err != nil {
|
||||
return err
|
||||
}
|
||||
delete(varMap, "addressItem")
|
||||
if port, ok := varMap["port"]; ok {
|
||||
varMap["address"] = fmt.Sprintf("%s:%v", varMap["address"], port)
|
||||
delete(varMap, "port")
|
||||
}
|
||||
|
||||
vars, _ := json.Marshal(varMap)
|
||||
if err := tx.Model(&model.BackupAccount{}).Where("id = ?", backup.ID).Updates(map[string]interface{}{"vars": string(vars)}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
@@ -9,11 +9,9 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/cmd/server/docs"
|
||||
"github.com/1Panel-dev/1Panel/cmd/server/web"
|
||||
"github.com/gin-contrib/gzip"
|
||||
ginI18n "github.com/gin-contrib/i18n"
|
||||
"github.com/gin-gonic/gin"
|
||||
swaggerfiles "github.com/swaggo/files"
|
||||
ginSwagger "github.com/swaggo/gin-swagger"
|
||||
"html/template"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
@@ -53,10 +51,7 @@ func Routers() *gin.Engine {
|
||||
c.Writer.Flush()
|
||||
})
|
||||
|
||||
Router.Use(i18n.GinI18nLocalize())
|
||||
Router.SetFuncMap(template.FuncMap{
|
||||
"Localize": ginI18n.GetMessage,
|
||||
})
|
||||
Router.Use(i18n.UseI18n())
|
||||
|
||||
swaggerRouter := Router.Group("1panel")
|
||||
docs.SwaggerInfo.BasePath = "/api/v1"
|
||||
|
||||
@@ -16,6 +16,7 @@ func (s *DashboardRouter) InitRouter(Router *gin.RouterGroup) {
|
||||
Use(middleware.PasswordExpired())
|
||||
baseApi := v1.ApiGroupApp.BaseApi
|
||||
{
|
||||
cmdRouter.GET("/base/os", baseApi.LoadDashboardOsInfo)
|
||||
cmdRouter.GET("/base/:ioOption/:netOption", baseApi.LoadDashboardBaseInfo)
|
||||
cmdRouter.GET("/current/:ioOption/:netOption", baseApi.LoadDashboardCurrentInfo)
|
||||
cmdRouter.POST("/system/restart/:operation", baseApi.SystemRestart)
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"crypto/tls"
|
||||
"encoding/gob"
|
||||
"fmt"
|
||||
"github.com/1Panel-dev/1Panel/backend/i18n"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
@@ -31,6 +32,7 @@ import (
|
||||
|
||||
func Start() {
|
||||
viper.Init()
|
||||
i18n.Init()
|
||||
log.Init()
|
||||
db.Init()
|
||||
migration.Init()
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
|
||||
func loadParamFromVars(key string, vars map[string]interface{}) string {
|
||||
if _, ok := vars[key]; !ok {
|
||||
if key != "bucket" {
|
||||
if key != "bucket" && key != "port" {
|
||||
global.LOG.Errorf("load param %s from vars failed, err: not exist!", key)
|
||||
}
|
||||
return ""
|
||||
|
||||
@@ -116,7 +116,7 @@ func (o oneDriveClient) Upload(src, target string) (bool, error) {
|
||||
}
|
||||
var isOk bool
|
||||
if fileInfo.Size() < 4*1024*1024 {
|
||||
isOk, err = o.upSmall(ctx, src, folderID, fileInfo.Size())
|
||||
isOk, err = o.upSmall(src, folderID, fileInfo.Size())
|
||||
} else {
|
||||
isOk, err = o.upBig(ctx, src, folderID, fileInfo.Size())
|
||||
}
|
||||
@@ -300,7 +300,7 @@ func (o *oneDriveClient) NewSessionFileUploadRequest(absoluteUrl string, grandOf
|
||||
return req, err
|
||||
}
|
||||
|
||||
func (o *oneDriveClient) upSmall(ctx context.Context, srcPath, folderID string, fileSize int64) (bool, error) {
|
||||
func (o *oneDriveClient) upSmall(srcPath, folderID string, fileSize int64) (bool, error) {
|
||||
file, err := os.Open(srcPath)
|
||||
if err != nil {
|
||||
return false, err
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"path"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"github.com/pkg/sftp"
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
@@ -21,6 +22,9 @@ type sftpClient struct {
|
||||
func NewSftpClient(vars map[string]interface{}) (*sftpClient, error) {
|
||||
address := loadParamFromVars("address", vars)
|
||||
port := loadParamFromVars("port", vars)
|
||||
if len(port) == 0 {
|
||||
global.LOG.Errorf("load param port from vars failed, err: not exist!")
|
||||
}
|
||||
password := loadParamFromVars("password", vars)
|
||||
username := loadParamFromVars("username", vars)
|
||||
bucket := loadParamFromVars("bucket", vars)
|
||||
@@ -34,6 +38,9 @@ func NewSftpClient(vars map[string]interface{}) (*sftpClient, error) {
|
||||
return nil
|
||||
},
|
||||
}
|
||||
if _, err := ssh.Dial("tcp", fmt.Sprintf("%s:%s", address, port), clientConfig); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &sftpClient{bucket: bucket, connInfo: fmt.Sprintf("%s:%s", address, port), config: clientConfig}, nil
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/studio-b12/gowebdav"
|
||||
@@ -43,7 +44,7 @@ func NewWebDAVClient(vars map[string]interface{}) (*webDAVClient, error) {
|
||||
}
|
||||
|
||||
func (s webDAVClient) Upload(src, target string) (bool, error) {
|
||||
targetFilePath := s.Bucket + "/" + target
|
||||
targetFilePath := path.Join(s.Bucket, target)
|
||||
srcFile, err := os.Open(src)
|
||||
if err != nil {
|
||||
return false, err
|
||||
@@ -62,7 +63,7 @@ func (s webDAVClient) ListBuckets() ([]interface{}, error) {
|
||||
}
|
||||
|
||||
func (s webDAVClient) Download(src, target string) (bool, error) {
|
||||
srcPath := s.Bucket + "/" + src
|
||||
srcPath := path.Join(s.Bucket, src)
|
||||
info, err := s.client.Stat(srcPath)
|
||||
if err != nil {
|
||||
return false, err
|
||||
@@ -85,30 +86,30 @@ func (s webDAVClient) Download(src, target string) (bool, error) {
|
||||
return true, err
|
||||
}
|
||||
|
||||
func (s webDAVClient) Exist(path string) (bool, error) {
|
||||
if _, err := s.client.Stat(s.Bucket + "/" + path); err != nil {
|
||||
func (s webDAVClient) Exist(pathItem string) (bool, error) {
|
||||
if _, err := s.client.Stat(path.Join(s.Bucket, pathItem)); err != nil {
|
||||
return false, err
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (s webDAVClient) Size(path string) (int64, error) {
|
||||
file, err := s.client.Stat(s.Bucket + "/" + path)
|
||||
func (s webDAVClient) Size(pathItem string) (int64, error) {
|
||||
file, err := s.client.Stat(path.Join(s.Bucket, pathItem))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return file.Size(), nil
|
||||
}
|
||||
|
||||
func (s webDAVClient) Delete(filePath string) (bool, error) {
|
||||
if err := s.client.Remove(s.Bucket + "/" + filePath); err != nil {
|
||||
func (s webDAVClient) Delete(pathItem string) (bool, error) {
|
||||
if err := s.client.Remove(path.Join(s.Bucket, pathItem)); err != nil {
|
||||
return false, err
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (s webDAVClient) ListObjects(prefix string) ([]string, error) {
|
||||
files, err := s.client.ReadDir(s.Bucket + "/" + prefix)
|
||||
files, err := s.client.ReadDir(path.Join(s.Bucket, prefix))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
+51
-39
@@ -2,7 +2,6 @@ package cmd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
@@ -14,20 +13,7 @@ import (
|
||||
)
|
||||
|
||||
func Exec(cmdStr string) (string, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
|
||||
defer cancel()
|
||||
cmd := exec.Command("bash", "-c", cmdStr)
|
||||
var stdout, stderr bytes.Buffer
|
||||
cmd.Stdout = &stdout
|
||||
cmd.Stderr = &stderr
|
||||
err := cmd.Run()
|
||||
if ctx.Err() == context.DeadlineExceeded {
|
||||
return "", buserr.New(constant.ErrCmdTimeout)
|
||||
}
|
||||
if err != nil {
|
||||
return handleErr(stdout, stderr, err)
|
||||
}
|
||||
return stdout.String(), nil
|
||||
return ExecWithTimeOut(cmdStr, 20*time.Second)
|
||||
}
|
||||
|
||||
func handleErr(stdout, stderr bytes.Buffer, err error) (string, error) {
|
||||
@@ -46,19 +32,28 @@ func handleErr(stdout, stderr bytes.Buffer, err error) (string, error) {
|
||||
}
|
||||
|
||||
func ExecWithTimeOut(cmdStr string, timeout time.Duration) (string, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
cmd := exec.Command("bash", "-c", cmdStr)
|
||||
var stdout, stderr bytes.Buffer
|
||||
cmd.Stdout = &stdout
|
||||
cmd.Stderr = &stderr
|
||||
err := cmd.Run()
|
||||
if ctx.Err() == context.DeadlineExceeded {
|
||||
if err := cmd.Start(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
done := make(chan error, 1)
|
||||
go func() {
|
||||
done <- cmd.Wait()
|
||||
}()
|
||||
after := time.After(timeout)
|
||||
select {
|
||||
case <-after:
|
||||
_ = cmd.Process.Kill()
|
||||
return "", buserr.New(constant.ErrCmdTimeout)
|
||||
case err := <-done:
|
||||
if err != nil {
|
||||
return handleErr(stdout, stderr, err)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return handleErr(stdout, stderr, err)
|
||||
}
|
||||
|
||||
return stdout.String(), nil
|
||||
}
|
||||
|
||||
@@ -75,9 +70,6 @@ func ExecContainerScript(containerName, cmdStr string, timeout time.Duration) er
|
||||
}
|
||||
|
||||
func ExecCronjobWithTimeOut(cmdStr, workdir, outPath string, timeout time.Duration) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
|
||||
file, err := os.OpenFile(outPath, os.O_WRONLY|os.O_CREATE, 0666)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -88,13 +80,24 @@ func ExecCronjobWithTimeOut(cmdStr, workdir, outPath string, timeout time.Durati
|
||||
cmd.Dir = workdir
|
||||
cmd.Stdout = file
|
||||
cmd.Stderr = file
|
||||
|
||||
err = cmd.Run()
|
||||
if ctx.Err() == context.DeadlineExceeded {
|
||||
return buserr.New(constant.ErrCmdTimeout)
|
||||
if err := cmd.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return err
|
||||
done := make(chan error, 1)
|
||||
go func() {
|
||||
done <- cmd.Wait()
|
||||
}()
|
||||
after := time.After(timeout)
|
||||
select {
|
||||
case <-after:
|
||||
_ = cmd.Process.Kill()
|
||||
return buserr.New(constant.ErrCmdTimeout)
|
||||
case err := <-done:
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func Execf(cmdStr string, a ...interface{}) (string, error) {
|
||||
@@ -122,20 +125,29 @@ func ExecWithCheck(name string, a ...string) (string, error) {
|
||||
}
|
||||
|
||||
func ExecScript(scriptPath, workDir string) (string, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute)
|
||||
defer cancel()
|
||||
cmd := exec.Command("bash", scriptPath)
|
||||
cmd.Dir = workDir
|
||||
var stdout, stderr bytes.Buffer
|
||||
cmd.Dir = workDir
|
||||
cmd.Stdout = &stdout
|
||||
cmd.Stderr = &stderr
|
||||
err := cmd.Run()
|
||||
if ctx.Err() == context.DeadlineExceeded {
|
||||
if err := cmd.Start(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
done := make(chan error, 1)
|
||||
go func() {
|
||||
done <- cmd.Wait()
|
||||
}()
|
||||
after := time.After(10 * time.Minute)
|
||||
select {
|
||||
case <-after:
|
||||
_ = cmd.Process.Kill()
|
||||
return "", buserr.New(constant.ErrCmdTimeout)
|
||||
case err := <-done:
|
||||
if err != nil {
|
||||
return handleErr(stdout, stderr, err)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return handleErr(stdout, stderr, err)
|
||||
}
|
||||
|
||||
return stdout.String(), nil
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ package common
|
||||
import (
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"golang.org/x/net/idna"
|
||||
"io"
|
||||
mathRand "math/rand"
|
||||
"net"
|
||||
@@ -15,6 +14,8 @@ import (
|
||||
"time"
|
||||
"unicode"
|
||||
|
||||
"golang.org/x/net/idna"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
|
||||
)
|
||||
|
||||
@@ -143,15 +144,6 @@ func ScanPortWithProto(port int, proto string) bool {
|
||||
return ScanPort(port)
|
||||
}
|
||||
|
||||
func ExistWithStrArray(str string, arr []string) bool {
|
||||
for _, a := range arr {
|
||||
if strings.Contains(a, str) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func IsNum(s string) bool {
|
||||
_, err := strconv.ParseFloat(s, 64)
|
||||
return err == nil
|
||||
|
||||
@@ -2,14 +2,19 @@ package docker
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/compose-spec/compose-go/loader"
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/joho/godotenv"
|
||||
"path"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/compose-spec/compose-go/v2/loader"
|
||||
"github.com/compose-spec/compose-go/v2/types"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/flags"
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/docker/compose/v2/pkg/compose"
|
||||
"github.com/docker/docker/client"
|
||||
"github.com/joho/godotenv"
|
||||
)
|
||||
|
||||
type ComposeService struct {
|
||||
@@ -17,8 +22,7 @@ type ComposeService struct {
|
||||
project *types.Project
|
||||
}
|
||||
|
||||
func (s *ComposeService) SetProject(project *types.Project) {
|
||||
s.project = project
|
||||
func UpComposeProject(project *types.Project) error {
|
||||
for i, s := range project.Services {
|
||||
s.CustomLabels = map[string]string{
|
||||
api.ProjectLabel: project.Name,
|
||||
@@ -30,47 +34,35 @@ func (s *ComposeService) SetProject(project *types.Project) {
|
||||
}
|
||||
project.Services[i] = s
|
||||
}
|
||||
}
|
||||
|
||||
func (s *ComposeService) ComposeUp() error {
|
||||
return s.Up(context.Background(), s.project, api.UpOptions{
|
||||
apiClient, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var ops []command.CLIOption
|
||||
ops = append(ops, command.WithAPIClient(apiClient), command.WithDefaultContextStoreConfig())
|
||||
cli, err := command.NewDockerCli(ops...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cliOp := flags.NewClientOptions()
|
||||
if err = cli.Initialize(cliOp); err != nil {
|
||||
return err
|
||||
}
|
||||
service := compose.NewComposeService(cli)
|
||||
composeService := ComposeService{Service: service}
|
||||
|
||||
return composeService.Up(context.Background(), project, api.UpOptions{
|
||||
Create: api.CreateOptions{
|
||||
Timeout: getComposeTimeout(),
|
||||
},
|
||||
Start: api.StartOptions{
|
||||
WaitTimeout: *getComposeTimeout(),
|
||||
Wait: true,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (s *ComposeService) ComposeDown() error {
|
||||
return s.Down(context.Background(), s.project.Name, api.DownOptions{})
|
||||
}
|
||||
|
||||
func (s *ComposeService) ComposeStart() error {
|
||||
return s.Start(context.Background(), s.project.Name, api.StartOptions{})
|
||||
}
|
||||
|
||||
func (s *ComposeService) ComposeRestart() error {
|
||||
return s.Restart(context.Background(), s.project.Name, api.RestartOptions{})
|
||||
}
|
||||
|
||||
func (s *ComposeService) ComposeStop() error {
|
||||
return s.Stop(context.Background(), s.project.Name, api.StopOptions{})
|
||||
}
|
||||
|
||||
func (s *ComposeService) ComposeCreate() error {
|
||||
return s.Create(context.Background(), s.project, api.CreateOptions{})
|
||||
}
|
||||
|
||||
func (s *ComposeService) ComposeBuild() error {
|
||||
return s.Build(context.Background(), s.project, api.BuildOptions{})
|
||||
}
|
||||
|
||||
func (s *ComposeService) ComposePull() error {
|
||||
return s.Pull(context.Background(), s.project, api.PullOptions{})
|
||||
}
|
||||
|
||||
func GetComposeProject(projectName, workDir string, yml []byte, env []byte, skipNormalization bool) (*types.Project, error) {
|
||||
var configFiles []types.ConfigFile
|
||||
configFiles = append(configFiles, types.ConfigFile{
|
||||
|
||||
@@ -3,6 +3,8 @@ package docker
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/docker/docker/api/types/container"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/app/model"
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
|
||||
@@ -44,24 +46,17 @@ func NewDockerClient() (*client.Client, error) {
|
||||
return cli, nil
|
||||
}
|
||||
|
||||
func (c Client) ListAllContainers() ([]types.Container, error) {
|
||||
var options types.ContainerListOptions
|
||||
containers, err := c.cli.ContainerList(context.Background(), options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return containers, nil
|
||||
}
|
||||
|
||||
func (c Client) ListContainersByName(names []string) ([]types.Container, error) {
|
||||
var (
|
||||
options types.ContainerListOptions
|
||||
res []types.Container
|
||||
options container.ListOptions
|
||||
namesMap = make(map[string]bool)
|
||||
res []types.Container
|
||||
)
|
||||
options.All = true
|
||||
if len(names) > 0 {
|
||||
var array []filters.KeyValuePair
|
||||
for _, n := range names {
|
||||
namesMap["/"+n] = true
|
||||
array = append(array, filters.Arg("name", n))
|
||||
}
|
||||
options.Filters = filters.NewArgs(array...)
|
||||
@@ -70,9 +65,9 @@ func (c Client) ListContainersByName(names []string) ([]types.Container, error)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, container := range containers {
|
||||
if container.Names[0] == "/"+names[0] {
|
||||
res = append(res, container)
|
||||
for _, con := range containers {
|
||||
if _, ok := namesMap[con.Names[0]]; ok {
|
||||
res = append(res, con)
|
||||
}
|
||||
}
|
||||
return res, nil
|
||||
|
||||
@@ -4,10 +4,8 @@ import (
|
||||
"bytes"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/md5"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
@@ -61,12 +59,6 @@ func StringDecrypt(text string) (string, error) {
|
||||
return "", err
|
||||
}
|
||||
|
||||
func Md5(str string) string {
|
||||
h := md5.New()
|
||||
h.Write([]byte(str))
|
||||
return hex.EncodeToString(h.Sum(nil))
|
||||
}
|
||||
|
||||
func padding(plaintext []byte, blockSize int) []byte {
|
||||
padding := blockSize - len(plaintext)%blockSize
|
||||
padtext := bytes.Repeat([]byte{byte(padding)}, padding)
|
||||
|
||||
@@ -7,11 +7,6 @@ import (
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
|
||||
http2 "github.com/1Panel-dev/1Panel/backend/utils/http"
|
||||
cZip "github.com/klauspost/compress/zip"
|
||||
"golang.org/x/text/encoding/simplifiedchinese"
|
||||
"golang.org/x/text/transform"
|
||||
"io"
|
||||
"io/fs"
|
||||
"net/http"
|
||||
@@ -23,6 +18,12 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
|
||||
http2 "github.com/1Panel-dev/1Panel/backend/utils/http"
|
||||
cZip "github.com/klauspost/compress/zip"
|
||||
"golang.org/x/text/encoding/simplifiedchinese"
|
||||
"golang.org/x/text/transform"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"github.com/mholt/archiver/v4"
|
||||
"github.com/pkg/errors"
|
||||
@@ -85,10 +86,6 @@ func (f FileOp) DeleteFile(dst string) error {
|
||||
return f.Fs.Remove(dst)
|
||||
}
|
||||
|
||||
func (f FileOp) Delete(dst string) error {
|
||||
return os.RemoveAll(dst)
|
||||
}
|
||||
|
||||
func (f FileOp) CleanDir(dst string) error {
|
||||
return cmd.ExecCmd(fmt.Sprintf("rm -rf %s/*", dst))
|
||||
}
|
||||
@@ -129,14 +126,6 @@ func (f FileOp) SaveFile(dst string, content string, mode fs.FileMode) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f FileOp) Chmod(dst string, mode fs.FileMode) error {
|
||||
return f.Fs.Chmod(dst, mode)
|
||||
}
|
||||
|
||||
func (f FileOp) Chown(dst string, uid int, gid int) error {
|
||||
return f.Fs.Chown(dst, uid, gid)
|
||||
}
|
||||
|
||||
func (f FileOp) ChownR(dst string, uid string, gid string, sub bool) error {
|
||||
cmdStr := fmt.Sprintf(`chown %s:%s "%s"`, uid, gid, dst)
|
||||
if sub {
|
||||
@@ -563,40 +552,6 @@ func (f FileOp) Decompress(srcFile string, dst string, cType CompressType) error
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f FileOp) Backup(srcFile string) (string, error) {
|
||||
backupPath := srcFile + "_bak"
|
||||
info, _ := f.Fs.Stat(backupPath)
|
||||
if info != nil {
|
||||
if info.IsDir() {
|
||||
_ = f.DeleteDir(backupPath)
|
||||
} else {
|
||||
_ = f.DeleteFile(backupPath)
|
||||
}
|
||||
}
|
||||
if err := f.Rename(srcFile, backupPath); err != nil {
|
||||
return backupPath, err
|
||||
}
|
||||
|
||||
return backupPath, nil
|
||||
}
|
||||
|
||||
func (f FileOp) CopyAndBackup(src string) (string, error) {
|
||||
backupPath := src + "_bak"
|
||||
info, _ := f.Fs.Stat(backupPath)
|
||||
if info != nil {
|
||||
if info.IsDir() {
|
||||
_ = f.DeleteDir(backupPath)
|
||||
} else {
|
||||
_ = f.DeleteFile(backupPath)
|
||||
}
|
||||
}
|
||||
_ = f.CreateDir(backupPath, 0755)
|
||||
if err := f.Copy(src, backupPath); err != nil {
|
||||
return backupPath, err
|
||||
}
|
||||
return backupPath, nil
|
||||
}
|
||||
|
||||
func ZipFile(files []archiver.File, dst afero.File) error {
|
||||
zw := zip.NewWriter(dst)
|
||||
defer zw.Close()
|
||||
|
||||
@@ -3,6 +3,7 @@ package files
|
||||
import (
|
||||
"bufio"
|
||||
"github.com/spf13/afero"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/user"
|
||||
@@ -87,16 +88,20 @@ func ReadFileByLine(filename string, page, pageSize int) ([]string, bool, error)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
scanner := bufio.NewScanner(file)
|
||||
reader := bufio.NewReaderSize(file, 8192)
|
||||
|
||||
var lines []string
|
||||
currentLine := 0
|
||||
startLine := (page - 1) * pageSize
|
||||
endLine := startLine + pageSize
|
||||
|
||||
for scanner.Scan() {
|
||||
for {
|
||||
line, _, err := reader.ReadLine()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if currentLine >= startLine && currentLine < endLine {
|
||||
lines = append(lines, scanner.Text())
|
||||
lines = append(lines, string(line))
|
||||
}
|
||||
currentLine++
|
||||
if currentLine >= endLine {
|
||||
@@ -104,10 +109,6 @@ func ReadFileByLine(filename string, page, pageSize int) ([]string, bool, error)
|
||||
}
|
||||
}
|
||||
|
||||
if err := scanner.Err(); err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
isEndOfFile := currentLine < endLine
|
||||
|
||||
return lines, isEndOfFile, nil
|
||||
|
||||
@@ -141,7 +141,7 @@ func (f *Ufw) Port(port FireInfo, operation string) error {
|
||||
case "drop":
|
||||
port.Strategy = "deny"
|
||||
default:
|
||||
return fmt.Errorf("unsupport strategy %s", port.Strategy)
|
||||
return fmt.Errorf("unsupported strategy %s", port.Strategy)
|
||||
}
|
||||
if cmd.CheckIllegal(port.Protocol, port.Port) {
|
||||
return buserr.New(constant.ErrCmdIllegal)
|
||||
@@ -168,7 +168,7 @@ func (f *Ufw) RichRules(rule FireInfo, operation string) error {
|
||||
case "drop":
|
||||
rule.Strategy = "deny"
|
||||
default:
|
||||
return fmt.Errorf("unsupport strategy %s", rule.Strategy)
|
||||
return fmt.Errorf("unsupported strategy %s", rule.Strategy)
|
||||
}
|
||||
|
||||
if cmd.CheckIllegal(operation, rule.Protocol, rule.Address, rule.Port) {
|
||||
|
||||
@@ -17,20 +17,3 @@ func GetIniValue(filePath, Group, Key string) (string, error) {
|
||||
}
|
||||
return startKey.Value(), nil
|
||||
}
|
||||
|
||||
func SetIniValue(filePath, Group, Key, value string) error {
|
||||
cfg, err := ini.Load(filePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
service, err := cfg.GetSection(Group)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
targetKey := service.Key(Key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
targetKey.SetValue(value)
|
||||
return cfg.SaveTo(filePath)
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ 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/mysql/client"
|
||||
)
|
||||
|
||||
@@ -50,6 +51,7 @@ func NewMysqlClient(conn client.DBInfo) (MysqlClient, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(conn.Timeout)*time.Second)
|
||||
defer cancel()
|
||||
if err := db.PingContext(ctx); err != nil {
|
||||
global.LOG.Errorf("test mysql conn failed, err: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
if ctx.Err() == context.DeadlineExceeded {
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"github.com/go-sql-driver/mysql"
|
||||
)
|
||||
|
||||
@@ -69,6 +70,8 @@ type AccessChangeInfo struct {
|
||||
|
||||
type BackupInfo struct {
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Version string `json:"version"`
|
||||
Format string `json:"format"`
|
||||
TargetDir string `json:"targetDir"`
|
||||
FileName string `json:"fileName"`
|
||||
@@ -78,6 +81,8 @@ type BackupInfo struct {
|
||||
|
||||
type RecoverInfo struct {
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Version string `json:"version"`
|
||||
Format string `json:"format"`
|
||||
SourceFile string `json:"sourceFile"`
|
||||
|
||||
@@ -101,45 +106,30 @@ var formatMap = map[string]string{
|
||||
"big5": "big5_chinese_ci",
|
||||
}
|
||||
|
||||
func VerifyPeerCertFunc(pool *x509.CertPool) func([][]byte, [][]*x509.Certificate) error {
|
||||
return func(rawCerts [][]byte, _ [][]*x509.Certificate) error {
|
||||
if len(rawCerts) == 0 {
|
||||
return errors.New("no certificates available to verify")
|
||||
}
|
||||
|
||||
cert, err := x509.ParseCertificate(rawCerts[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
opts := x509.VerifyOptions{Roots: pool}
|
||||
if _, err = cert.Verify(opts); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func ConnWithSSL(ssl, skipVerify bool, clientKey, clientCert, rootCert string) (string, error) {
|
||||
if !ssl {
|
||||
return "", nil
|
||||
}
|
||||
pool := x509.NewCertPool()
|
||||
tlsConfig := &tls.Config{
|
||||
InsecureSkipVerify: skipVerify,
|
||||
}
|
||||
if len(rootCert) != 0 {
|
||||
pool := x509.NewCertPool()
|
||||
if ok := pool.AppendCertsFromPEM([]byte(rootCert)); !ok {
|
||||
global.LOG.Error("append certs from pem failed")
|
||||
return "", errors.New("unable to append root cert to pool")
|
||||
}
|
||||
tlsConfig.RootCAs = pool
|
||||
}
|
||||
cert, err := tls.X509KeyPair([]byte(clientCert), []byte(clientKey))
|
||||
if err != nil {
|
||||
return "", err
|
||||
if len(clientCert) != 0 && len(clientKey) != 0 {
|
||||
cert, err := tls.X509KeyPair([]byte(clientCert), []byte(clientKey))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
tlsConfig.Certificates = []tls.Certificate{cert}
|
||||
}
|
||||
if err := mysql.RegisterTLSConfig("cloudsql", &tls.Config{
|
||||
RootCAs: pool,
|
||||
Certificates: []tls.Certificate{cert},
|
||||
InsecureSkipVerify: skipVerify,
|
||||
VerifyPeerCertificate: VerifyPeerCertFunc(pool),
|
||||
}); err != nil {
|
||||
if err := mysql.RegisterTLSConfig("cloudsql", tlsConfig); err != nil {
|
||||
global.LOG.Errorf("register tls config failed, err: %v", err)
|
||||
return "", err
|
||||
}
|
||||
return "&tls=cloudsql", nil
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"compress/gzip"
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -13,7 +16,8 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/files"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/mysql/helper"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/client"
|
||||
)
|
||||
|
||||
type Remote struct {
|
||||
@@ -229,56 +233,63 @@ func (r *Remote) Backup(info BackupInfo) error {
|
||||
return fmt.Errorf("mkdir %s failed, err: %v", info.TargetDir, err)
|
||||
}
|
||||
}
|
||||
fileNameItem := info.TargetDir + "/" + strings.TrimSuffix(info.FileName, ".gz")
|
||||
|
||||
tlsItem, err := ConnWithSSL(r.SSL, r.SkipVerify, r.ClientKey, r.ClientCert, r.RootCert)
|
||||
outfile, _ := os.OpenFile(path.Join(info.TargetDir, info.FileName), os.O_RDWR|os.O_CREATE, 0755)
|
||||
global.LOG.Infof("start to mysqldump | gzip > %s.gzip", info.TargetDir+"/"+info.FileName)
|
||||
image, err := loadImage(info.Type, info.Version)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dns := fmt.Sprintf("%s:%s@tcp(%s:%v)/%s?charset=%s&parseTime=true&loc=Asia%sShanghai%s", r.User, r.Password, r.Address, r.Port, info.Name, info.Format, "%2F", tlsItem)
|
||||
backupCmd := fmt.Sprintf("docker run --rm --net=host -i %s /bin/bash -c 'mysqldump -h %s -P %d -u%s -p%s %s --default-character-set=%s %s'",
|
||||
image, r.Address, r.Port, r.User, r.Password, sslSkip(info.Version), info.Format, info.Name)
|
||||
|
||||
f, _ := os.OpenFile(fileNameItem, os.O_RDWR|os.O_CREATE, 0755)
|
||||
defer f.Close()
|
||||
if err := helper.Dump(dns, helper.WithData(), helper.WithDropTable(), helper.WithWriter(f)); err != nil {
|
||||
return err
|
||||
}
|
||||
global.LOG.Debug(backupCmd)
|
||||
cmd := exec.Command("bash", "-c", backupCmd)
|
||||
|
||||
gzipCmd := exec.Command("gzip", fileNameItem)
|
||||
stdout, err := gzipCmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return fmt.Errorf("gzip file %s failed, stdout: %v, err: %v", strings.TrimSuffix(info.FileName, ".gz"), string(stdout), err)
|
||||
}
|
||||
gzipCmd := exec.Command("gzip", "-cf")
|
||||
gzipCmd.Stdin, _ = cmd.StdoutPipe()
|
||||
gzipCmd.Stdout = outfile
|
||||
_ = gzipCmd.Start()
|
||||
_ = cmd.Run()
|
||||
_ = gzipCmd.Wait()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Remote) Recover(info RecoverInfo) error {
|
||||
fileName := info.SourceFile
|
||||
if strings.HasSuffix(info.SourceFile, ".sql.gz") {
|
||||
fileName = strings.TrimSuffix(info.SourceFile, ".gz")
|
||||
gzipCmd := exec.Command("gunzip", info.SourceFile)
|
||||
stdout, err := gzipCmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return fmt.Errorf("gunzip file %s failed, stdout: %v, err: %v", info.SourceFile, string(stdout), err)
|
||||
}
|
||||
defer func() {
|
||||
gzipCmd := exec.Command("gzip", fileName)
|
||||
_, _ = gzipCmd.CombinedOutput()
|
||||
}()
|
||||
}
|
||||
tlsItem, err := ConnWithSSL(r.SSL, r.SkipVerify, r.ClientKey, r.ClientCert, r.RootCert)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dns := fmt.Sprintf("%s:%s@tcp(%s:%v)/%s?charset=%s&parseTime=true&loc=Asia%sShanghai%s", r.User, r.Password, r.Address, r.Port, info.Name, info.Format, "%2F", tlsItem)
|
||||
fi, _ := os.Open(info.SourceFile)
|
||||
defer fi.Close()
|
||||
|
||||
f, err := os.Open(fileName)
|
||||
image, err := loadImage(info.Type, info.Version)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
if err := helper.Source(dns, f, helper.WithMergeInsert(1000)); err != nil {
|
||||
return err
|
||||
|
||||
recoverCmd := fmt.Sprintf("docker run --rm --net=host -i %s /bin/bash -c 'mysql -h %s -P %d -u%s -p%s %s --default-character-set=%s %s'",
|
||||
image, r.Address, r.Port, r.User, r.Password, sslSkip(info.Version), info.Format, info.Name)
|
||||
|
||||
global.LOG.Debug(recoverCmd)
|
||||
cmd := exec.Command("bash", "-c", recoverCmd)
|
||||
|
||||
if strings.HasSuffix(info.SourceFile, ".gz") {
|
||||
gzipFile, err := os.Open(info.SourceFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer gzipFile.Close()
|
||||
gzipReader, err := gzip.NewReader(gzipFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer gzipReader.Close()
|
||||
cmd.Stdin = gzipReader
|
||||
} else {
|
||||
cmd.Stdin = fi
|
||||
}
|
||||
stdout, err := cmd.CombinedOutput()
|
||||
stdStr := strings.ReplaceAll(string(stdout), "mysql: [Warning] Using a password on the command line interface can be insecure.\n", "")
|
||||
if err != nil || strings.HasPrefix(string(stdStr), "ERROR ") {
|
||||
return errors.New(stdStr)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -389,3 +400,45 @@ func (r *Remote) ExecSQLForHosts(timeout uint) ([]string, error) {
|
||||
}
|
||||
return rows, nil
|
||||
}
|
||||
|
||||
func loadImage(dbType, version string) (string, error) {
|
||||
cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
images, err := cli.ImageList(context.Background(), types.ImageListOptions{})
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
|
||||
for _, image := range images {
|
||||
for _, tag := range image.RepoTags {
|
||||
if !strings.HasPrefix(tag, dbType+":") {
|
||||
continue
|
||||
}
|
||||
if dbType == "mariadb" && strings.HasPrefix(tag, "mariadb:") {
|
||||
return tag, nil
|
||||
}
|
||||
if strings.HasPrefix(version, "5.6") && strings.HasPrefix(tag, "mysql:5.6") {
|
||||
return tag, nil
|
||||
}
|
||||
if strings.HasPrefix(version, "5.7") && strings.HasPrefix(tag, "mysql:5.7") {
|
||||
return tag, nil
|
||||
}
|
||||
if strings.HasPrefix(version, "8.") && strings.HasPrefix(tag, "mysql:8.") {
|
||||
return tag, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
if dbType == "mariadb" || version == "8.x" {
|
||||
return "mysql:8.2.0", nil
|
||||
}
|
||||
return "mysql:" + version, nil
|
||||
}
|
||||
|
||||
func sslSkip(version string) string {
|
||||
if strings.HasPrefix(version, "5.6") || strings.HasPrefix(version, "5.7") {
|
||||
return "--skip-ssl"
|
||||
}
|
||||
return "--ssl-mode=DISABLED"
|
||||
}
|
||||
|
||||
@@ -38,18 +38,6 @@ func WithData() DumpOption {
|
||||
}
|
||||
}
|
||||
|
||||
func WithTables(tables ...string) DumpOption {
|
||||
return func(option *dumpOption) {
|
||||
option.tables = tables
|
||||
}
|
||||
}
|
||||
|
||||
func WithAllTable() DumpOption {
|
||||
return func(option *dumpOption) {
|
||||
option.isAllTable = true
|
||||
}
|
||||
}
|
||||
|
||||
func WithWriter(writer io.Writer) DumpOption {
|
||||
return func(option *dumpOption) {
|
||||
option.writer = writer
|
||||
@@ -152,7 +140,7 @@ func Dump(dns string, opts ...DumpOption) error {
|
||||
var columns []string
|
||||
columns, err = lineRows.Columns()
|
||||
if err != nil {
|
||||
global.LOG.Errorf("get columes falied, err: %v", err)
|
||||
global.LOG.Errorf("get columes failed, err: %v", err)
|
||||
return err
|
||||
}
|
||||
columnTypes, err := lineRows.ColumnTypes()
|
||||
|
||||
@@ -19,24 +19,12 @@ type sourceOption struct {
|
||||
}
|
||||
type SourceOption func(*sourceOption)
|
||||
|
||||
func WithDryRun() SourceOption {
|
||||
return func(o *sourceOption) {
|
||||
o.dryRun = true
|
||||
}
|
||||
}
|
||||
|
||||
func WithMergeInsert(size int) SourceOption {
|
||||
return func(o *sourceOption) {
|
||||
o.mergeInsert = size
|
||||
}
|
||||
}
|
||||
|
||||
func WithDebug() SourceOption {
|
||||
return func(o *sourceOption) {
|
||||
o.debug = true
|
||||
}
|
||||
}
|
||||
|
||||
type dbWrapper struct {
|
||||
DB *sql.DB
|
||||
debug bool
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
package components
|
||||
|
||||
type Block struct {
|
||||
Line int
|
||||
Comment string
|
||||
Directives []IDirective
|
||||
Line int
|
||||
Comment string
|
||||
Directives []IDirective
|
||||
IsLuaBlock bool
|
||||
LiteralCode string
|
||||
}
|
||||
|
||||
func (b *Block) GetDirectives() []IDirective {
|
||||
@@ -18,6 +20,10 @@ func (b *Block) GetLine() int {
|
||||
return b.Line
|
||||
}
|
||||
|
||||
func (b *Block) GetCodeBlock() string {
|
||||
return b.LiteralCode
|
||||
}
|
||||
|
||||
func (b *Block) FindDirectives(directiveName string) []IDirective {
|
||||
directives := make([]IDirective, 0)
|
||||
for _, directive := range b.GetDirectives() {
|
||||
|
||||
@@ -5,19 +5,6 @@ type Config struct {
|
||||
FilePath string
|
||||
}
|
||||
|
||||
func (c *Config) FindDirectives(directiveName string) []IDirective {
|
||||
return c.Block.FindDirectives(directiveName)
|
||||
}
|
||||
|
||||
func (c *Config) FindUpstreams() []*Upstream {
|
||||
var upstreams []*Upstream
|
||||
directives := c.Block.FindDirectives("upstream")
|
||||
for _, directive := range directives {
|
||||
upstreams = append(upstreams, directive.(*Upstream))
|
||||
}
|
||||
return upstreams
|
||||
}
|
||||
|
||||
func (c *Config) FindServers() []*Server {
|
||||
var servers []*Server
|
||||
directives := c.Block.FindDirectives("server")
|
||||
|
||||
@@ -11,6 +11,10 @@ type Http struct {
|
||||
Line int
|
||||
}
|
||||
|
||||
func (h *Http) GetCodeBlock() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (h *Http) GetComment() string {
|
||||
return h.Comment
|
||||
}
|
||||
|
||||
@@ -22,6 +22,10 @@ type Location struct {
|
||||
Replaces map[string]string
|
||||
}
|
||||
|
||||
func (l *Location) GetCodeBlock() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func NewLocation(directive IDirective) *Location {
|
||||
location := &Location{
|
||||
Modifier: "",
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
package components
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type LuaBlock struct {
|
||||
Directives []IDirective
|
||||
Name string
|
||||
Comment string
|
||||
LuaCode string
|
||||
Line int
|
||||
}
|
||||
|
||||
func NewLuaBlock(directive IDirective) (*LuaBlock, error) {
|
||||
if block := directive.GetBlock(); block != nil {
|
||||
lb := &LuaBlock{
|
||||
Directives: []IDirective{},
|
||||
Name: directive.GetName(),
|
||||
LuaCode: block.GetCodeBlock(),
|
||||
}
|
||||
|
||||
lb.Directives = append(lb.Directives, block.GetDirectives()...)
|
||||
return lb, nil
|
||||
}
|
||||
return nil, fmt.Errorf("%s must have a block", directive.GetName())
|
||||
}
|
||||
|
||||
func (lb *LuaBlock) GetName() string {
|
||||
return lb.Name
|
||||
}
|
||||
|
||||
func (lb *LuaBlock) GetParameters() []string {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
func (lb *LuaBlock) GetDirectives() []IDirective {
|
||||
directives := make([]IDirective, 0)
|
||||
directives = append(directives, lb.Directives...)
|
||||
return directives
|
||||
}
|
||||
|
||||
func (lb *LuaBlock) FindDirectives(directiveName string) []IDirective {
|
||||
directives := make([]IDirective, 0)
|
||||
for _, directive := range lb.GetDirectives() {
|
||||
if directive.GetName() == directiveName {
|
||||
directives = append(directives, directive)
|
||||
}
|
||||
if directive.GetBlock() != nil {
|
||||
directives = append(directives, directive.GetBlock().FindDirectives(directiveName)...)
|
||||
}
|
||||
}
|
||||
|
||||
return directives
|
||||
}
|
||||
|
||||
func (lb *LuaBlock) GetCodeBlock() string {
|
||||
return lb.LuaCode
|
||||
}
|
||||
|
||||
func (lb *LuaBlock) GetBlock() IBlock {
|
||||
return lb
|
||||
}
|
||||
|
||||
func (lb *LuaBlock) GetComment() string {
|
||||
return lb.Comment
|
||||
}
|
||||
|
||||
func (lb *LuaBlock) RemoveDirective(key string, params []string) {
|
||||
directives := lb.Directives
|
||||
var newDirectives []IDirective
|
||||
for _, dir := range directives {
|
||||
if dir.GetName() == key {
|
||||
if len(params) > 0 {
|
||||
oldParams := dir.GetParameters()
|
||||
if oldParams[0] == params[0] {
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
}
|
||||
newDirectives = append(newDirectives, dir)
|
||||
}
|
||||
lb.Directives = newDirectives
|
||||
}
|
||||
|
||||
func (lb *LuaBlock) UpdateDirective(key string, params []string) {
|
||||
if key == "" || len(params) == 0 {
|
||||
return
|
||||
}
|
||||
directives := lb.Directives
|
||||
index := -1
|
||||
for i, dir := range directives {
|
||||
if dir.GetName() == key {
|
||||
if IsRepeatKey(key) {
|
||||
oldParams := dir.GetParameters()
|
||||
if !(len(oldParams) > 0 && oldParams[0] == params[0]) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
index = i
|
||||
break
|
||||
}
|
||||
}
|
||||
newDirective := &Directive{
|
||||
Name: key,
|
||||
Parameters: params,
|
||||
}
|
||||
if index > -1 {
|
||||
directives[index] = newDirective
|
||||
} else {
|
||||
directives = append(directives, newDirective)
|
||||
}
|
||||
lb.Directives = directives
|
||||
}
|
||||
|
||||
func (lb *LuaBlock) GetLine() int {
|
||||
return lb.Line
|
||||
}
|
||||
@@ -11,6 +11,10 @@ type Server struct {
|
||||
Line int
|
||||
}
|
||||
|
||||
func (s *Server) GetCodeBlock() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func NewServer(directive IDirective) (*Server, error) {
|
||||
server := &Server{}
|
||||
if block := directive.GetBlock(); block != nil {
|
||||
|
||||
@@ -7,6 +7,7 @@ type IBlock interface {
|
||||
UpdateDirective(name string, params []string)
|
||||
GetComment() string
|
||||
GetLine() int
|
||||
GetCodeBlock() string
|
||||
}
|
||||
|
||||
type IDirective interface {
|
||||
|
||||
@@ -12,6 +12,10 @@ type Upstream struct {
|
||||
Line int
|
||||
}
|
||||
|
||||
func (us *Upstream) GetCodeBlock() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (us *Upstream) GetName() string {
|
||||
return "upstream"
|
||||
}
|
||||
|
||||
@@ -65,6 +65,21 @@ func DumpDirective(d components.IDirective, style *Style) string {
|
||||
|
||||
func DumpBlock(b components.IBlock, style *Style, startLine int) string {
|
||||
var buf bytes.Buffer
|
||||
|
||||
if b.GetCodeBlock() != "" {
|
||||
luaLines := strings.Split(b.GetCodeBlock(), "\n")
|
||||
for i, line := range luaLines {
|
||||
if strings.Replace(line, " ", "", -1) == "" {
|
||||
continue
|
||||
}
|
||||
buf.WriteString(line)
|
||||
if i != len(luaLines)-1 {
|
||||
buf.WriteString("\n")
|
||||
}
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
line := startLine
|
||||
if b.GetLine() > startLine {
|
||||
for i := 0; i < b.GetLine()-startLine; i++ {
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
package nginx
|
||||
|
||||
import (
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/nginx/components"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/nginx/parser"
|
||||
)
|
||||
|
||||
func GetConfig(path string) (*components.Config, error) {
|
||||
p, err := parser.NewParser(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return p.Parse(), nil
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
package nginx
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNginx(t *testing.T) {
|
||||
//config, err := GetConfig("/opt/1Panel/data/apps/nginx/nginx-1/conf/conf.d/word-1.conf")
|
||||
config, err := GetConfig("/opt/1Panel/data/apps/nginx/nginx-new/conf/conf.d/1panel.cloud.conf")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
//server := config.FindServers()[0]
|
||||
//fmt.Println(server)
|
||||
//serverD := config.FindServers()[0]
|
||||
//serverD.AddListen("8989", false)
|
||||
|
||||
fmt.Println(DumpConfig(config, IndentedStyle))
|
||||
}
|
||||
@@ -1,9 +1,5 @@
|
||||
package flag
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type Type int
|
||||
|
||||
const (
|
||||
@@ -18,6 +14,7 @@ const (
|
||||
Comment
|
||||
Illegal
|
||||
Regex
|
||||
LuaCode
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -46,33 +43,13 @@ type Flag struct {
|
||||
Column int
|
||||
}
|
||||
|
||||
func (t Flag) String() string {
|
||||
return fmt.Sprintf("{Type:%s,Literal:\"%s\",Line:%d,Column:%d}", t.Type, t.Literal, t.Line, t.Column)
|
||||
}
|
||||
|
||||
func (t Flag) Lit(literal string) Flag {
|
||||
t.Literal = literal
|
||||
return t
|
||||
}
|
||||
|
||||
func (t Flag) EqualTo(t2 Flag) bool {
|
||||
return t.Type == t2.Type && t.Literal == t2.Literal
|
||||
}
|
||||
|
||||
type Flags []Flag
|
||||
|
||||
func (fs Flags) EqualTo(flags Flags) bool {
|
||||
if len(fs) != len(flags) {
|
||||
return false
|
||||
}
|
||||
for i, t := range fs {
|
||||
if !t.EqualTo(flags[i]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (t Flag) Is(typ Type) bool {
|
||||
return t.Type == typ
|
||||
}
|
||||
|
||||
@@ -3,17 +3,18 @@ package parser
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/nginx/parser/flag"
|
||||
"io"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type lexer struct {
|
||||
reader *bufio.Reader
|
||||
file string
|
||||
line int
|
||||
column int
|
||||
Latest flag.Flag
|
||||
reader *bufio.Reader
|
||||
file string
|
||||
line int
|
||||
column int
|
||||
inLuaBlock bool
|
||||
Latest flag.Flag
|
||||
}
|
||||
|
||||
func lex(content string) *lexer {
|
||||
@@ -45,6 +46,11 @@ func (s *lexer) scan() flag.Flag {
|
||||
//}
|
||||
|
||||
func (s *lexer) getNextFlag() flag.Flag {
|
||||
if s.inLuaBlock {
|
||||
s.inLuaBlock = false
|
||||
flag := s.scanLuaCode()
|
||||
return flag
|
||||
}
|
||||
retoFlag:
|
||||
ch := s.peek()
|
||||
switch {
|
||||
@@ -56,6 +62,9 @@ retoFlag:
|
||||
case ch == ';':
|
||||
return s.NewToken(flag.Semicolon).Lit(string(s.read()))
|
||||
case ch == '{':
|
||||
if isLuaBlock(s.Latest) {
|
||||
s.inLuaBlock = true
|
||||
}
|
||||
return s.NewToken(flag.BlockStart).Lit(string(s.read()))
|
||||
case ch == '}':
|
||||
return s.NewToken(flag.BlockEnd).Lit(string(s.read()))
|
||||
@@ -70,6 +79,35 @@ retoFlag:
|
||||
}
|
||||
}
|
||||
|
||||
func (s *lexer) scanLuaCode() flag.Flag {
|
||||
ret := s.NewToken(flag.LuaCode)
|
||||
stack := make([]rune, 0, 50)
|
||||
code := strings.Builder{}
|
||||
|
||||
for {
|
||||
ch := s.read()
|
||||
if ch == rune(flag.EOF) {
|
||||
panic("unexpected end of file while scanning a string, maybe an unclosed lua code?")
|
||||
}
|
||||
if ch == '#' {
|
||||
code.WriteRune(ch)
|
||||
code.WriteString(s.readUntil(isEndOfLine))
|
||||
continue
|
||||
} else if ch == '}' {
|
||||
if len(stack) == 0 {
|
||||
_ = s.reader.UnreadRune()
|
||||
return ret.Lit(strings.TrimRight(strings.Trim(code.String(), "\n"), "\n "))
|
||||
}
|
||||
if stack[len(stack)-1] == '{' {
|
||||
stack = stack[0 : len(stack)-1]
|
||||
}
|
||||
} else if ch == '{' {
|
||||
stack = append(stack, ch)
|
||||
}
|
||||
code.WriteRune(ch)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *lexer) peek() rune {
|
||||
r, _, _ := s.reader.ReadRune()
|
||||
_ = s.reader.UnreadRune()
|
||||
@@ -128,7 +166,7 @@ func (s *lexer) scanComment() flag.Flag {
|
||||
func (s *lexer) scanQuotedString(delimiter rune) flag.Flag {
|
||||
var buf bytes.Buffer
|
||||
tok := s.NewToken(flag.QuotedString)
|
||||
buf.WriteRune(s.read()) //consume delimiter
|
||||
_, _ = buf.WriteRune(s.read())
|
||||
for {
|
||||
ch := s.read()
|
||||
|
||||
@@ -136,29 +174,13 @@ func (s *lexer) scanQuotedString(delimiter rune) flag.Flag {
|
||||
panic("unexpected end of file while scanning a string, maybe an unclosed quote?")
|
||||
}
|
||||
|
||||
if ch == '\\' {
|
||||
if needsEscape(s.peek(), delimiter) {
|
||||
nextch := s.read()
|
||||
switch nextch {
|
||||
case 'n':
|
||||
fmt.Println("n")
|
||||
buf.WriteRune('\n')
|
||||
case 'r':
|
||||
fmt.Println("r")
|
||||
buf.WriteRune('\r')
|
||||
case 't':
|
||||
fmt.Println("t")
|
||||
buf.WriteRune('\t')
|
||||
case '\\':
|
||||
buf.WriteRune('\\')
|
||||
default:
|
||||
buf.WriteRune('\\')
|
||||
buf.WriteRune(nextch)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if ch == '\\' && (s.peek() == delimiter) {
|
||||
buf.WriteRune(ch)
|
||||
buf.WriteRune(s.read())
|
||||
continue
|
||||
}
|
||||
buf.WriteRune(ch)
|
||||
|
||||
_, _ = buf.WriteRune(ch)
|
||||
if ch == delimiter {
|
||||
break
|
||||
}
|
||||
@@ -168,7 +190,31 @@ func (s *lexer) scanQuotedString(delimiter rune) flag.Flag {
|
||||
}
|
||||
|
||||
func (s *lexer) scanKeyword() flag.Flag {
|
||||
return s.NewToken(flag.Keyword).Lit(s.readUntil(isKeywordTerminator))
|
||||
var buf bytes.Buffer
|
||||
tok := s.NewToken(flag.Keyword)
|
||||
prev := s.read()
|
||||
buf.WriteRune(prev)
|
||||
for {
|
||||
ch := s.peek()
|
||||
|
||||
if isSpace(ch) || isEOF(ch) || ch == ';' {
|
||||
break
|
||||
}
|
||||
|
||||
if ch == '{' {
|
||||
if prev == '$' {
|
||||
buf.WriteString(s.readUntil(func(r rune) bool {
|
||||
return r == '}'
|
||||
}))
|
||||
buf.WriteRune(s.read()) //consume latest '}'
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
buf.WriteRune(s.read())
|
||||
}
|
||||
|
||||
return tok.Lit(buf.String())
|
||||
}
|
||||
|
||||
func (s *lexer) scanVariable() flag.Flag {
|
||||
@@ -198,10 +244,6 @@ func isKeywordTerminator(ch rune) bool {
|
||||
return isSpace(ch) || isEndOfLine(ch) || ch == '{' || ch == ';'
|
||||
}
|
||||
|
||||
func needsEscape(ch, delimiter rune) bool {
|
||||
return ch == delimiter || ch == 'n' || ch == 't' || ch == '\\' || ch == 'r'
|
||||
}
|
||||
|
||||
func isSpace(ch rune) bool {
|
||||
return ch == ' ' || ch == '\t' || isEndOfLine(ch)
|
||||
}
|
||||
@@ -213,3 +255,7 @@ func isEOF(ch rune) bool {
|
||||
func isEndOfLine(ch rune) bool {
|
||||
return ch == '\r' || ch == '\n'
|
||||
}
|
||||
|
||||
func isLuaBlock(t flag.Flag) bool {
|
||||
return t.Type == flag.Keyword && strings.HasSuffix(t.Literal, "_by_lua_block")
|
||||
}
|
||||
|
||||
@@ -2,17 +2,19 @@ package parser
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"fmt"
|
||||
components "github.com/1Panel-dev/1Panel/backend/utils/nginx/components"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/nginx/parser/flag"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Parser struct {
|
||||
lexer *lexer
|
||||
currentToken flag.Flag
|
||||
followingToken flag.Flag
|
||||
blockWrappers map[string]func(*components.Directive) components.IDirective
|
||||
blockWrappers map[string]func(*components.Directive) (components.IDirective, error)
|
||||
directiveWrappers map[string]func(*components.Directive) components.IDirective
|
||||
}
|
||||
|
||||
@@ -39,18 +41,21 @@ func NewParserFromLexer(lexer *lexer) *Parser {
|
||||
parser.nextToken()
|
||||
parser.nextToken()
|
||||
|
||||
parser.blockWrappers = map[string]func(*components.Directive) components.IDirective{
|
||||
"http": func(directive *components.Directive) components.IDirective {
|
||||
return parser.wrapHttp(directive)
|
||||
parser.blockWrappers = map[string]func(*components.Directive) (components.IDirective, error){
|
||||
"http": func(directive *components.Directive) (components.IDirective, error) {
|
||||
return parser.wrapHttp(directive), nil
|
||||
},
|
||||
"server": func(directive *components.Directive) components.IDirective {
|
||||
return parser.wrapServer(directive)
|
||||
"server": func(directive *components.Directive) (components.IDirective, error) {
|
||||
return parser.wrapServer(directive), nil
|
||||
},
|
||||
"location": func(directive *components.Directive) components.IDirective {
|
||||
return parser.wrapLocation(directive)
|
||||
"location": func(directive *components.Directive) (components.IDirective, error) {
|
||||
return parser.wrapLocation(directive), nil
|
||||
},
|
||||
"upstream": func(directive *components.Directive) components.IDirective {
|
||||
return parser.wrapUpstream(directive)
|
||||
"upstream": func(directive *components.Directive) (components.IDirective, error) {
|
||||
return parser.wrapUpstream(directive), nil
|
||||
},
|
||||
"_by_lua_block": func(directive *components.Directive) (components.IDirective, error) {
|
||||
return parser.wrapLuaBlock(directive)
|
||||
},
|
||||
}
|
||||
|
||||
@@ -76,14 +81,19 @@ func (p *Parser) followingTokenIs(t flag.Type) bool {
|
||||
return p.followingToken.Type == t
|
||||
}
|
||||
|
||||
func (p *Parser) Parse() *components.Config {
|
||||
return &components.Config{
|
||||
FilePath: p.lexer.file,
|
||||
Block: p.parseBlock(),
|
||||
func (p *Parser) Parse() (*components.Config, error) {
|
||||
parsedBlock, err := p.parseBlock(false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c := &components.Config{
|
||||
FilePath: p.lexer.file,
|
||||
Block: parsedBlock,
|
||||
}
|
||||
return c, err
|
||||
}
|
||||
|
||||
func (p *Parser) parseBlock() *components.Block {
|
||||
func (p *Parser) parseBlock(inBlock bool) (*components.Block, error) {
|
||||
context := &components.Block{
|
||||
Comment: "",
|
||||
Directives: make([]components.IDirective, 0),
|
||||
@@ -93,10 +103,22 @@ func (p *Parser) parseBlock() *components.Block {
|
||||
parsingloop:
|
||||
for {
|
||||
switch {
|
||||
case p.curTokenIs(flag.EOF) || p.curTokenIs(flag.BlockEnd):
|
||||
case p.curTokenIs(flag.EOF):
|
||||
if inBlock {
|
||||
return nil, errors.New("unexpected eof in block")
|
||||
}
|
||||
break parsingloop
|
||||
case p.curTokenIs(flag.BlockEnd):
|
||||
break parsingloop
|
||||
case p.curTokenIs(flag.LuaCode):
|
||||
context.IsLuaBlock = true
|
||||
context.LiteralCode = p.currentToken.Literal
|
||||
case p.curTokenIs(flag.Keyword):
|
||||
context.Directives = append(context.Directives, p.parseStatement())
|
||||
s, err := p.parseStatement()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
context.Directives = append(context.Directives, s)
|
||||
case p.curTokenIs(flag.Comment):
|
||||
context.Directives = append(context.Directives, &components.Comment{
|
||||
Detail: p.currentToken.Literal,
|
||||
@@ -106,10 +128,10 @@ parsingloop:
|
||||
p.nextToken()
|
||||
}
|
||||
|
||||
return context
|
||||
return context, nil
|
||||
}
|
||||
|
||||
func (p *Parser) parseStatement() components.IDirective {
|
||||
func (p *Parser) parseStatement() (components.IDirective, error) {
|
||||
d := &components.Directive{
|
||||
Name: p.currentToken.Literal,
|
||||
Line: p.currentToken.Line,
|
||||
@@ -121,30 +143,38 @@ func (p *Parser) parseStatement() components.IDirective {
|
||||
|
||||
if p.curTokenIs(flag.Semicolon) {
|
||||
if dw, ok := p.directiveWrappers[d.Name]; ok {
|
||||
return dw(d)
|
||||
return dw(d), nil
|
||||
}
|
||||
if p.followingTokenIs(flag.Comment) && p.currentToken.Line == p.followingToken.Line {
|
||||
d.Comment = p.followingToken.Literal
|
||||
p.nextToken()
|
||||
}
|
||||
return d
|
||||
return d, nil
|
||||
}
|
||||
|
||||
if p.curTokenIs(flag.BlockStart) {
|
||||
|
||||
inLineComment := ""
|
||||
if p.followingTokenIs(flag.Comment) && p.currentToken.Line == p.followingToken.Line {
|
||||
inLineComment = p.followingToken.Literal
|
||||
p.nextToken()
|
||||
p.nextToken()
|
||||
}
|
||||
block := p.parseBlock()
|
||||
block, err := p.parseBlock(false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
block.Comment = inLineComment
|
||||
d.Block = block
|
||||
|
||||
if strings.HasSuffix(d.Name, "_by_lua_block") {
|
||||
return p.blockWrappers["_by_lua_block"](d)
|
||||
}
|
||||
|
||||
if bw, ok := p.blockWrappers[d.Name]; ok {
|
||||
return bw(d)
|
||||
}
|
||||
return d
|
||||
return d, nil
|
||||
}
|
||||
|
||||
panic(fmt.Errorf("unexpected token %s (%s) on line %d, column %d", p.currentToken.Type.String(), p.currentToken.Literal, p.currentToken.Line, p.currentToken.Column))
|
||||
@@ -169,6 +199,10 @@ func (p *Parser) wrapHttp(directive *components.Directive) *components.Http {
|
||||
return h
|
||||
}
|
||||
|
||||
func (p *Parser) wrapLuaBlock(directive *components.Directive) (*components.LuaBlock, error) {
|
||||
return components.NewLuaBlock(directive)
|
||||
}
|
||||
|
||||
func (p *Parser) parseUpstreamServer(directive *components.Directive) *components.UpstreamServer {
|
||||
return components.NewUpstreamServer(directive)
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@ package ssl
|
||||
import (
|
||||
"crypto"
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/app/model"
|
||||
"github.com/go-acme/lego/v4/acme"
|
||||
"github.com/go-acme/lego/v4/acme/api"
|
||||
@@ -21,7 +23,6 @@ import (
|
||||
"github.com/go-acme/lego/v4/providers/http/webroot"
|
||||
"github.com/go-acme/lego/v4/registration"
|
||||
"github.com/pkg/errors"
|
||||
"time"
|
||||
)
|
||||
|
||||
type AcmeUser struct {
|
||||
@@ -204,23 +205,6 @@ func (c *AcmeClient) ObtainSSL(domains []string, privateKey crypto.PrivateKey) (
|
||||
return *certificates, nil
|
||||
}
|
||||
|
||||
func (c *AcmeClient) RenewSSL(certUrl string) (certificate.Resource, error) {
|
||||
certificates, err := c.Client.Certificate.Get(certUrl, true)
|
||||
if err != nil {
|
||||
return certificate.Resource{}, err
|
||||
}
|
||||
certificates, err = c.Client.Certificate.RenewWithOptions(*certificates, &certificate.RenewOptions{
|
||||
Bundle: true,
|
||||
PreferredChain: "",
|
||||
MustStaple: true,
|
||||
})
|
||||
if err != nil {
|
||||
return certificate.Resource{}, err
|
||||
}
|
||||
|
||||
return *certificates, nil
|
||||
}
|
||||
|
||||
type Resolve struct {
|
||||
Key string
|
||||
Value string
|
||||
|
||||
@@ -3,15 +3,16 @@ package websocket
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/files"
|
||||
"github.com/shirou/gopsutil/v3/host"
|
||||
"github.com/shirou/gopsutil/v3/net"
|
||||
"github.com/shirou/gopsutil/v3/process"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type WsInput struct {
|
||||
@@ -86,18 +87,6 @@ type processConnect struct {
|
||||
|
||||
type ProcessConnects []processConnect
|
||||
|
||||
func (p ProcessConnects) Len() int {
|
||||
return len(p)
|
||||
}
|
||||
|
||||
func (p ProcessConnects) Less(i, j int) bool {
|
||||
return p[i].PID < p[j].PID
|
||||
}
|
||||
|
||||
func (p ProcessConnects) Swap(i, j int) {
|
||||
p[i], p[j] = p[j], p[i]
|
||||
}
|
||||
|
||||
type sshSession struct {
|
||||
Username string `json:"username"`
|
||||
PID int32 `json:"PID"`
|
||||
|
||||
+47
-62
@@ -1,5 +1,5 @@
|
||||
// Package docs GENERATED BY SWAG; DO NOT EDIT
|
||||
// This file was generated by swaggo/swag
|
||||
// Code generated by swaggo/swag. DO NOT EDIT.
|
||||
|
||||
package docs
|
||||
|
||||
import "github.com/swaggo/swag"
|
||||
@@ -3830,6 +3830,31 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"/dashboard/base/os": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "获取服务器基础数据",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Dashboard"
|
||||
],
|
||||
"summary": "Load os info",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/dto.OsInfo"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/dashboard/current/:ioOption/:netOption": {
|
||||
"get": {
|
||||
"security": [
|
||||
@@ -4954,45 +4979,6 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"/databases/pg/options": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "获取 postgresql 数据库列表",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Database Postgresql"
|
||||
],
|
||||
"summary": "List postgresql database names",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "request",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/dto.PageInfo"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/dto.PostgresqlOption"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/databases/pg/password": {
|
||||
"post": {
|
||||
"security": [
|
||||
@@ -16867,6 +16853,26 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.OsInfo": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"kernelArch": {
|
||||
"type": "string"
|
||||
},
|
||||
"kernelVersion": {
|
||||
"type": "string"
|
||||
},
|
||||
"os": {
|
||||
"type": "string"
|
||||
},
|
||||
"platform": {
|
||||
"type": "string"
|
||||
},
|
||||
"platformFamily": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.PageContainer": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
@@ -17203,26 +17209,6 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.PostgresqlOption": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"database": {
|
||||
"type": "string"
|
||||
},
|
||||
"from": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.RecordSearch": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
@@ -19026,7 +19012,6 @@ const docTemplate = `{
|
||||
"request.FileEdit": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"content",
|
||||
"path"
|
||||
],
|
||||
"properties": {
|
||||
|
||||
@@ -3823,6 +3823,31 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/dashboard/base/os": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "获取服务器基础数据",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Dashboard"
|
||||
],
|
||||
"summary": "Load os info",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/dto.OsInfo"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/dashboard/current/:ioOption/:netOption": {
|
||||
"get": {
|
||||
"security": [
|
||||
@@ -4947,45 +4972,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/databases/pg/options": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "获取 postgresql 数据库列表",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Database Postgresql"
|
||||
],
|
||||
"summary": "List postgresql database names",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "request",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/dto.PageInfo"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/dto.PostgresqlOption"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/databases/pg/password": {
|
||||
"post": {
|
||||
"security": [
|
||||
@@ -16860,6 +16846,26 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.OsInfo": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"kernelArch": {
|
||||
"type": "string"
|
||||
},
|
||||
"kernelVersion": {
|
||||
"type": "string"
|
||||
},
|
||||
"os": {
|
||||
"type": "string"
|
||||
},
|
||||
"platform": {
|
||||
"type": "string"
|
||||
},
|
||||
"platformFamily": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.PageContainer": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
@@ -17196,26 +17202,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.PostgresqlOption": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"database": {
|
||||
"type": "string"
|
||||
},
|
||||
"from": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.RecordSearch": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
@@ -19019,7 +19005,6 @@
|
||||
"request.FileEdit": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"content",
|
||||
"path"
|
||||
],
|
||||
"properties": {
|
||||
|
||||
@@ -1887,6 +1887,19 @@ definitions:
|
||||
option:
|
||||
type: string
|
||||
type: object
|
||||
dto.OsInfo:
|
||||
properties:
|
||||
kernelArch:
|
||||
type: string
|
||||
kernelVersion:
|
||||
type: string
|
||||
os:
|
||||
type: string
|
||||
platform:
|
||||
type: string
|
||||
platformFamily:
|
||||
type: string
|
||||
type: object
|
||||
dto.PageContainer:
|
||||
properties:
|
||||
excludeAppStore:
|
||||
@@ -2119,19 +2132,6 @@ definitions:
|
||||
- from
|
||||
- type
|
||||
type: object
|
||||
dto.PostgresqlOption:
|
||||
properties:
|
||||
database:
|
||||
type: string
|
||||
from:
|
||||
type: string
|
||||
id:
|
||||
type: integer
|
||||
name:
|
||||
type: string
|
||||
type:
|
||||
type: string
|
||||
type: object
|
||||
dto.RecordSearch:
|
||||
properties:
|
||||
detailName:
|
||||
@@ -3332,7 +3332,6 @@ definitions:
|
||||
path:
|
||||
type: string
|
||||
required:
|
||||
- content
|
||||
- path
|
||||
type: object
|
||||
request.FileMove:
|
||||
@@ -7425,6 +7424,21 @@ paths:
|
||||
summary: Load dashboard base info
|
||||
tags:
|
||||
- Dashboard
|
||||
/dashboard/base/os:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: 获取服务器基础数据
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/dto.OsInfo'
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
summary: Load os info
|
||||
tags:
|
||||
- Dashboard
|
||||
/dashboard/current/:ioOption/:netOption:
|
||||
get:
|
||||
consumes:
|
||||
@@ -8140,30 +8154,6 @@ paths:
|
||||
[description]
|
||||
formatZH: postgresql 数据库 [name] 描述信息修改 [description]
|
||||
paramKeys: []
|
||||
/databases/pg/options:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: 获取 postgresql 数据库列表
|
||||
parameters:
|
||||
- description: request
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/dto.PageInfo'
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
items:
|
||||
$ref: '#/definitions/dto.PostgresqlOption'
|
||||
type: array
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
summary: List postgresql database names
|
||||
tags:
|
||||
- Database Postgresql
|
||||
/databases/pg/password:
|
||||
post:
|
||||
consumes:
|
||||
|
||||
+5
-5
@@ -12,11 +12,11 @@
|
||||
|
||||
1Panel is a modern and Open-Source linux server operation and management panel, the functions and advantages of 1Panel include:
|
||||
|
||||
- **Quick website building**: Deeply integrated with Wordpress and [Halo](https://github.com/halo-dev/halo/), with one-click solutions for domain name binding, SSL certificate configuration, and more;
|
||||
- **Efficient management**: Easily manage Linux servers through the web interface, including application management, host monitoring, file management, database management, container management, and more;
|
||||
- **Secure and reliable**: Minimal vulnerability exposure, with firewall and security audit functions provided;
|
||||
- **One-click backup**: Support for one-click backup and restore, with backup data stored in the cloud and never lost;
|
||||
- **App Store**: Curate a variety of high-quality open-source tools and application software, enabling you to easily install and upgrade them.
|
||||
- **Efficient Management**: Users can easily manage Linux servers through a web graphical interface, achieving functions such as host monitoring, file management, database management, and container management.
|
||||
- **Rapid Website Building**: Deep integration of open-source website building software WordPress and [Halo](https://github.com/halo-dev/halo/), with operations such as domain binding and SSL certificate configuration completed with one click.
|
||||
- **Application Store**: Curates a variety of high-quality open-source tools and application software, assisting users in easy installation and upgrades.
|
||||
- **Security and Reliability**: Based on container management and application deployment, minimizing vulnerability exposure, while providing features such as firewall and log auditing.
|
||||
- **One-Click Backup**: Supports one-click backup and restoration, allowing users to backup data to various cloud storage media, ensuring data is never lost.
|
||||
|
||||
## UI Display
|
||||
|
||||
|
||||
+5
-5
@@ -12,11 +12,11 @@
|
||||
|
||||
1Panel はモダンでオープンソースの Linux サーバー操作と管理パネルです:
|
||||
|
||||
- **素早いウェブサイト構築**: Wordpress および [Halo](https://github.com/halo-dev/halo/) と深く統合されており、ドメイン名のバインディング、SSL 証明書の設定など、ワンクリックで解決できます;
|
||||
- **効率的なマネジメント**: アプリケーション管理、ホスト監視、ファイル管理、データベース管理、コンテナ管理など、web インターフェイスを通じて Linux サーバーを簡単に管理できます;
|
||||
- **安全性と信頼性**: ファイアウォールとセキュリティ監査機能により、脆弱性の露出を最小限に抑えます;
|
||||
- **ワンクリックバックアップ**: ワンクリックでのバックアップと復元をサポートし、バックアップデータはクラウドに保存され、失われることはありません;
|
||||
- **アプリストア**: さまざまな種類の高品質なオープンソースツールとアプリケーションソフトウェアを厳選し、インストールとアップグレードを容易にサポートします。
|
||||
- **効率的管理**:ユーザーは、Web グラフィカルインターフェースを使用して、Linux サーバーを簡単に管理し、ホストの監視、ファイル管理、データベース管理、コンテナ管理などの機能を実現できます。
|
||||
- **迅速なウェブサイト構築**:オープンソースのウェブサイト構築ソフトウェアであるWordPressと[Halo](https://github.com/halo-dev/halo/)を深く統合し、ドメインのバインディング、SSL 証明書の設定などをワンクリックで完了します。
|
||||
- **アプリストア**:さまざまな高品質のオープンソースツールやアプリケーションソフトウェアを厳選して提供し、ユーザーが簡単にインストールおよびアップグレードできるよう支援します。
|
||||
- **安全性と信頼性**:コンテナの管理とアプリケーションのデプロイに基づいて、最小限の脆弱性露出面を実現し、同時にファイアウォールやログ監査などの機能を提供します。
|
||||
- **ワンクリックバックアップ**:ワンクリックでバックアップと復元をサポートし、ユーザーはデータをさまざまなクラウドストレージメディアにバックアップし、永久に失うことはありません。
|
||||
|
||||
## UI ディスプレイ
|
||||
|
||||
|
||||
+5
-5
@@ -13,11 +13,11 @@
|
||||
|
||||
1Panel 是一個現代化、開源的 Linux 服務器運維管理面板。1Panel 的功能和優勢包括:
|
||||
|
||||
- **快速建站**:深度集成 Wordpress 和 [Halo](https://github.com/halo-dev/halo/),域名綁定、SSL 證書配置等一鍵搞定;
|
||||
- **高效管理**:通過 Web 端輕鬆管理 Linux 服務器,包括主機監控、文件管理、數據庫管理、容器管理等;
|
||||
- **安全可靠**:基於容器來管理和部署應用,最小漏洞暴露面,提供防火牆和日誌審計等功能;
|
||||
- **一鍵備份**:支持一鍵備份和恢復,備份數據到各類雲端存儲,永不丟失;
|
||||
- **應用商店**:精選各類高質量開源工具和應用軟件,助力您輕鬆安裝和升級。
|
||||
- **高效管理**:使用者可以透過網頁圖形介面輕鬆管理 Linux 伺服器,實現主機監控、檔案管理、資料庫管理、容器管理等功能;
|
||||
- **快速建站**:深度整合開源建站軟體 WordPress 和 [Halo](https://github.com/halo-dev/halo/),域名綁定、SSL 憑證配置等操作一鍵搞定;
|
||||
- **應用商店**:精選上架各類高品質的開源工具和應用軟體,協助使用者輕鬆安裝並升級;
|
||||
- **安全可靠**:基於容器管理並部署應用,實現最小的漏洞暴露面,同時提供防火牆和日誌審計等功能;
|
||||
- **一鍵備份**:支援一鍵備份和恢復,使用者可以將資料備份到各類雲端儲存媒介,永不遺失。
|
||||
|
||||
## UI 展示
|
||||
|
||||
|
||||
@@ -27,8 +27,8 @@ class RequestHttp {
|
||||
...config.headers,
|
||||
};
|
||||
if (config.url === '/auth/login' || config.url === '/auth/mfalogin') {
|
||||
let entrace = Base64.encode(globalStore.entrance);
|
||||
config.headers.EntranceCode = entrace;
|
||||
let entrance = Base64.encode(globalStore.entrance);
|
||||
config.headers.EntranceCode = entrance;
|
||||
}
|
||||
return {
|
||||
...config,
|
||||
|
||||
@@ -245,7 +245,7 @@ export namespace Container {
|
||||
path: string;
|
||||
template: number;
|
||||
}
|
||||
export interface ComposeOpration {
|
||||
export interface ComposeOperation {
|
||||
name: string;
|
||||
operation: string;
|
||||
path: string;
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
export namespace Dashboard {
|
||||
export interface OsInfo {
|
||||
os: string;
|
||||
platform: string;
|
||||
platformFamily: string;
|
||||
kernelArch: string;
|
||||
kernelVersion: string;
|
||||
}
|
||||
export interface BaseInfo {
|
||||
websiteNumber: number;
|
||||
databaseNumber: number;
|
||||
|
||||
@@ -156,7 +156,7 @@ export const upCompose = (params: Container.ComposeCreate) => {
|
||||
export const testCompose = (params: Container.ComposeCreate) => {
|
||||
return http.post<boolean>(`/containers/compose/test`, params);
|
||||
};
|
||||
export const composeOperator = (params: Container.ComposeOpration) => {
|
||||
export const composeOperator = (params: Container.ComposeOperation) => {
|
||||
return http.post(`/containers/compose/operate`, params);
|
||||
};
|
||||
export const composeUpdate = (params: Container.ComposeUpdate) => {
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import http from '@/api';
|
||||
import { Dashboard } from '../interface/dashboard';
|
||||
|
||||
export const loadOsInfo = () => {
|
||||
return http.get<Dashboard.OsInfo>(`/dashboard/base/os`);
|
||||
};
|
||||
|
||||
export const loadBaseInfo = (ioOption: string, netOption: string) => {
|
||||
return http.get<Dashboard.BaseInfo>(`/dashboard/base/${ioOption}/${netOption}`);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"Anhui": "安徽",
|
||||
"Beijing": "北京",
|
||||
"Fujian": "福建",
|
||||
"Gansu": "甘肃",
|
||||
"Guangdong": "广东",
|
||||
"Guangxi": "广西",
|
||||
"Guizhou": "贵州",
|
||||
"Hainan": "海南",
|
||||
"Hebei": "河北",
|
||||
"Henan": "河南",
|
||||
"Heilongjiang": "黑龙江",
|
||||
"Hubei": "湖北",
|
||||
"Hunan": "湖南",
|
||||
"Jilin": "吉林",
|
||||
"Jiangsu": "江苏",
|
||||
"Jiangxi": "江西",
|
||||
"Liaoning": "辽宁",
|
||||
"Inner Mongolia": "内蒙古",
|
||||
"Ningxia": "宁夏",
|
||||
"Qinghai": "青海",
|
||||
"Shandong": "山东",
|
||||
"Shanxi": "山西",
|
||||
"Shaanxi": "陕西",
|
||||
"Shanghai": "上海",
|
||||
"Sichuan": "四川",
|
||||
"Tianjin": "天津",
|
||||
"Tibet": "西藏",
|
||||
"Xinjiang": "新疆",
|
||||
"Yunnan": "云南",
|
||||
"Zhejiang": "浙江",
|
||||
"Chongqing": "重庆",
|
||||
"HongKong": "香港",
|
||||
"Macao": "澳门",
|
||||
"Taiwan": "台湾"
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,192 @@
|
||||
{
|
||||
"Afghanistan": "阿富汗",
|
||||
"Albania": "阿尔巴尼亚",
|
||||
"Algeria": "阿尔及利亚",
|
||||
"Angola": "安哥拉",
|
||||
"Argentina": "阿根廷",
|
||||
"Armenia": "亚美尼亚",
|
||||
"Australia": "澳大利亚",
|
||||
"Austria": "奥地利",
|
||||
"Azerbaijan": "阿塞拜疆",
|
||||
"Bahamas": "巴哈马",
|
||||
"Bahrain": "巴林",
|
||||
"Bangladesh": "孟加拉国",
|
||||
"Belarus": "白俄罗斯",
|
||||
"Belgium": "比利时",
|
||||
"Belize": "伯利兹",
|
||||
"Benin": "贝宁",
|
||||
"Bhutan": "不丹",
|
||||
"Bolivia": "玻利维亚",
|
||||
"Bosnia and Herz.": "波斯尼亚和黑塞哥维那",
|
||||
"Botswana": "博茨瓦纳",
|
||||
"Brazil": "巴西",
|
||||
"British Virgin Islands": "英属维京群岛",
|
||||
"Brunei": "文莱",
|
||||
"Bulgaria": "保加利亚",
|
||||
"Burkina Faso": "布基纳法索",
|
||||
"Burundi": "布隆迪",
|
||||
"Cambodia": "柬埔寨",
|
||||
"Cameroon": "喀麦隆",
|
||||
"Canada": "加拿大",
|
||||
"Cape Verde": "佛得角",
|
||||
"Cayman Islands": "开曼群岛",
|
||||
"Central African Rep.": "中非共和国",
|
||||
"Chad": "乍得",
|
||||
"Chile": "智利",
|
||||
"China": "中国",
|
||||
"Colombia": "哥伦比亚",
|
||||
"Comoros": "科摩罗",
|
||||
"Congo": "刚果",
|
||||
"Costa Rica": "哥斯达黎加",
|
||||
"Croatia": "克罗地亚",
|
||||
"Cuba": "古巴",
|
||||
"Cyprus": "塞浦路斯",
|
||||
"Czech Rep.": "捷克共和国",
|
||||
"Côte d'Ivoire": "科特迪瓦",
|
||||
"Dem. Rep. Congo": "刚果民主共和国",
|
||||
"Dem. Rep. Korea": "朝鲜",
|
||||
"Denmark": "丹麦",
|
||||
"Djibouti": "吉布提",
|
||||
"Dominican Rep.": "多米尼加共和国",
|
||||
"Ecuador": "厄瓜多尔",
|
||||
"Egypt": "埃及",
|
||||
"El Salvador": "萨尔瓦多",
|
||||
"Equatorial Guinea": "赤道几内亚",
|
||||
"Eritrea": "厄立特里亚",
|
||||
"Estonia": "爱沙尼亚",
|
||||
"Ethiopia": "埃塞俄比亚",
|
||||
"Falkland Is.": "福克兰群岛",
|
||||
"Fiji": "斐济",
|
||||
"Finland": "芬兰",
|
||||
"Fr. S. Antarctic Lands": "所罗门群岛",
|
||||
"France": "法国",
|
||||
"Gabon": "加蓬",
|
||||
"Gambia": "冈比亚",
|
||||
"Georgia": "格鲁吉亚",
|
||||
"Germany": "德国",
|
||||
"Ghana": "加纳",
|
||||
"Greece": "希腊",
|
||||
"Greenland": "格陵兰",
|
||||
"Guatemala": "危地马拉",
|
||||
"Guinea": "几内亚",
|
||||
"Guinea-Bissau": "几内亚比绍",
|
||||
"Guyana": "圭亚那",
|
||||
"Haiti": "海地",
|
||||
"Honduras": "洪都拉斯",
|
||||
"Hungary": "匈牙利",
|
||||
"Iceland": "冰岛",
|
||||
"India": "印度",
|
||||
"Indonesia": "印度尼西亚",
|
||||
"Iran": "伊朗",
|
||||
"Iraq": "伊拉克",
|
||||
"Ireland": "爱尔兰",
|
||||
"Isle of Man": "马恩岛",
|
||||
"Israel": "以色列",
|
||||
"Italy": "意大利",
|
||||
"Jamaica": "牙买加",
|
||||
"Japan": "日本",
|
||||
"Jordan": "约旦",
|
||||
"Kazakhstan": "哈萨克斯坦",
|
||||
"Kenya": "肯尼亚",
|
||||
"Korea": "韩国",
|
||||
"Kuwait": "科威特",
|
||||
"Kyrgyzstan": "吉尔吉斯斯坦",
|
||||
"Lao PDR": "老挝",
|
||||
"Latvia": "拉脱维亚",
|
||||
"Lebanon": "黎巴嫩",
|
||||
"Lesotho": "莱索托",
|
||||
"Liberia": "利比里亚",
|
||||
"Libya": "利比亚",
|
||||
"Lithuania": "立陶宛",
|
||||
"Luxembourg": "卢森堡",
|
||||
"Macedonia": "马其顿",
|
||||
"Madagascar": "马达加斯加",
|
||||
"Malawi": "马拉维",
|
||||
"Malaysia": "马来西亚",
|
||||
"Maldives": "马尔代夫",
|
||||
"Mali": "马里",
|
||||
"Malta": "马耳他",
|
||||
"Mauritania": "毛利塔尼亚",
|
||||
"Mauritius": "毛里求斯",
|
||||
"Mexico": "墨西哥",
|
||||
"Moldova": "摩尔多瓦",
|
||||
"Monaco": "摩纳哥",
|
||||
"Mongolia": "蒙古",
|
||||
"Montenegro": "黑山共和国",
|
||||
"Morocco": "摩洛哥",
|
||||
"Mozambique": "莫桑比克",
|
||||
"Myanmar": "缅甸",
|
||||
"Namibia": "纳米比亚",
|
||||
"Nepal": "尼泊尔",
|
||||
"Netherlands": "荷兰",
|
||||
"New Caledonia": "新喀里多尼亚",
|
||||
"New Zealand": "新西兰",
|
||||
"Nicaragua": "尼加拉瓜",
|
||||
"Niger": "尼日尔",
|
||||
"Nigeria": "尼日利亚",
|
||||
"Norway": "挪威",
|
||||
"Oman": "阿曼",
|
||||
"Pakistan": "巴基斯坦",
|
||||
"Panama": "巴拿马",
|
||||
"Papua New Guinea": "巴布亚新几内亚",
|
||||
"Paraguay": "巴拉圭",
|
||||
"Peru": "秘鲁",
|
||||
"Philippines": "菲律宾",
|
||||
"Poland": "波兰",
|
||||
"Portugal": "葡萄牙",
|
||||
"Puerto Rico": "波多黎各",
|
||||
"Qatar": "卡塔尔",
|
||||
"Reunion": "留尼旺",
|
||||
"Romania": "罗马尼亚",
|
||||
"Russia": "俄罗斯",
|
||||
"Rwanda": "卢旺达",
|
||||
"S. Geo. and S. Sandw. Is.": "南乔治亚和南桑威奇群岛",
|
||||
"S. Sudan": "南苏丹",
|
||||
"San Marino": "圣马力诺",
|
||||
"Saudi Arabia": "沙特阿拉伯",
|
||||
"Senegal": "塞内加尔",
|
||||
"Serbia": "塞尔维亚",
|
||||
"Sierra Leone": "塞拉利昂",
|
||||
"Singapore": "新加坡",
|
||||
"Slovakia": "斯洛伐克",
|
||||
"Slovenia": "斯洛文尼亚",
|
||||
"Solomon Is.": "所罗门群岛",
|
||||
"Somalia": "索马里",
|
||||
"South Africa": "南非",
|
||||
"Spain": "西班牙",
|
||||
"Sri Lanka": "斯里兰卡",
|
||||
"Sudan": "苏丹",
|
||||
"Suriname": "苏里南",
|
||||
"Swaziland": "斯威士兰",
|
||||
"Sweden": "瑞典",
|
||||
"Switzerland": "瑞士",
|
||||
"Syria": "叙利亚",
|
||||
"Taiwan": "中国台湾",
|
||||
"Tajikistan": "塔吉克斯坦",
|
||||
"Tanzania": "坦桑尼亚",
|
||||
"Thailand": "泰国",
|
||||
"Togo": "多哥",
|
||||
"Tonga": "汤加",
|
||||
"Trinidad and Tobago": "特立尼达和多巴哥",
|
||||
"Tunisia": "突尼斯",
|
||||
"Turkey": "土耳其",
|
||||
"Turkmenistan": "土库曼斯坦",
|
||||
"U.S. Virgin Islands": "美属维尔京群岛",
|
||||
"Uganda": "乌干达",
|
||||
"Ukraine": "乌克兰",
|
||||
"United Arab Emirates": "阿拉伯联合酋长国",
|
||||
"United Kingdom": "英国",
|
||||
"United States": "美国",
|
||||
"Uruguay": "乌拉圭",
|
||||
"Uzbekistan": "乌兹别克斯坦",
|
||||
"Vanuatu": "瓦努阿图",
|
||||
"Vatican City": "梵蒂冈城",
|
||||
"Venezuela": "委内瑞拉",
|
||||
"Vietnam": "越南",
|
||||
"W. Sahara": "西撒哈拉",
|
||||
"Yemen": "也门",
|
||||
"Yugoslavia": "南斯拉夫",
|
||||
"Zaire": "扎伊尔",
|
||||
"Zambia": "赞比亚",
|
||||
"Zimbabwe": "津巴布韦"
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user