mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2026-10-12 16:00:33 +00:00
Compare commits
117
Commits
v1.9.6
...
v1.10.2-lts
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3cd0c89c4c | ||
|
|
c27fcf18fc | ||
|
|
b1f20d125e | ||
|
|
cd43bff44d | ||
|
|
8cf8375911 | ||
|
|
ebc3195df4 | ||
|
|
ce69f6a142 | ||
|
|
f6777d51b5 | ||
|
|
d916f2ba6f | ||
|
|
0e159f944a | ||
|
|
329bb94d3c | ||
|
|
7f2f7fa195 | ||
|
|
67c79bcde4 | ||
|
|
d7d7c7fd73 | ||
|
|
94028d97cf | ||
|
|
cbfedb5d5e | ||
|
|
7784ee9575 | ||
|
|
29d5d82fa5 | ||
|
|
46f01032b8 | ||
|
|
341f45b4ef | ||
|
|
a0907592b2 | ||
|
|
825ebd19fb | ||
|
|
8f24432a01 | ||
|
|
6ce35b723b | ||
|
|
a33eabb4f0 | ||
|
|
34a6587ebc | ||
|
|
17020ffe9f | ||
|
|
0f9b0d5d82 | ||
|
|
be36103475 | ||
|
|
a3b5869c2b | ||
|
|
0807da41a5 | ||
|
|
ea6870e3c9 | ||
|
|
70b7daf23a | ||
|
|
1d01fa1e14 | ||
|
|
b643fa2ca3 | ||
|
|
77f4f64f0d | ||
|
|
3b697c7520 | ||
|
|
8ca6b9b6fa | ||
|
|
0b66be7a3a | ||
|
|
f9f68a975c | ||
|
|
ba63907a65 | ||
|
|
2687c91e65 | ||
|
|
eb82c168c5 | ||
|
|
21b1242a94 | ||
|
|
a6784772b9 | ||
|
|
cf4fcaa07f | ||
|
|
62b1bb3191 | ||
|
|
8ce066a9d4 | ||
|
|
7d8da406cf | ||
|
|
b3d6b2b0de | ||
|
|
21f24f2e10 | ||
|
|
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 |
@@ -1,8 +1,8 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: 问题与讨论 / Questions & Discussions
|
||||
url: https://github.com/1Panel-dev/1Panel/discussions
|
||||
about: 提出针对本项目安装部署、使用及其他方面的相关问题 / Raise questions about the installation, deployment, use and other aspects of the project.
|
||||
- name: 对 1Panel 项目有其他问题
|
||||
url: https://1panel.cn
|
||||
about: 如果想要进一步了解 1Panel 项目,欢迎发送邮件到 support@fit2cloud.com 进行提问。
|
||||
- name: 反馈安全问题 (Report Security Bug)
|
||||
url: mailto://support@fit2cloud.com
|
||||
about: 请通过邮箱 support@fit2cloud.com 反馈安全问题 (Please report security vulnerabilities to support@fit2cloud.com)
|
||||
url: https://bbs.fit2cloud.com/c/1p/7
|
||||
about: 如果你对 1Panel 有其他想要提问的,我们欢迎到我们的官方社区进行提问。
|
||||
@@ -1,12 +0,0 @@
|
||||
name: 问题咨询
|
||||
description: 提出针对本项目安装部署、使用及其他方面的相关问题
|
||||
title: "[QUESTION]"
|
||||
labels: question
|
||||
assignees: wanghe-fit2cloud
|
||||
body:
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: "请描述您的问题"
|
||||
validations:
|
||||
required: true
|
||||
@@ -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/
|
||||
|
||||
+2
-2
@@ -5,7 +5,7 @@ PR are always welcome, even if they only contain small fixes like typos or a few
|
||||
|
||||
Please submit a PR broken down into small changes bit by bit. A PR consisting of a lot features and code changes may be hard to review. It is recommended to submit PRs in an incremental fashion.
|
||||
|
||||
This [development guideline](https://kubeoperator.io/docs/dev/dev_manual/) contains information about repository structure, how to setup development environment, how to run it, and more.
|
||||
This [development guideline](https://1panel.cn/docs/dev_manual/dev_manual/) contains information about repository structure, how to setup development environment, how to run it, and more.
|
||||
|
||||
Note: If you split your pull request to small changes, please make sure any of the changes goes to master will not break anything. Otherwise, it can not be merged until this feature complete.
|
||||
|
||||
@@ -22,4 +22,4 @@ When reporting issues, always include:
|
||||
* Snapshots or log files if needed
|
||||
|
||||
Because the issues are open to the public, when submitting files, be sure to remove any sensitive information, e.g. user name, password, IP address, and company name. You can
|
||||
replace those parts with "REDACTED" or other strings like "****".
|
||||
replace those parts with "REDACTED" or other strings like "****".
|
||||
|
||||
@@ -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 展示
|
||||
|
||||
@@ -50,8 +50,7 @@ curl -sSL https://resource.fit2cloud.com/1panel/package/quick_start.sh -o quick_
|
||||
|
||||
如果您在使用过程中发现任何安全问题,请通过以下方式直接联系我们:
|
||||
|
||||
- 邮箱:support@fit2cloud.com
|
||||
- 电话:400-052-0755
|
||||
- 邮箱:wanghe@fit2cloud.com
|
||||
|
||||
## Star History
|
||||
|
||||
|
||||
@@ -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 从服务器获取
|
||||
|
||||
@@ -203,3 +203,19 @@ func (b *BaseApi) OperateNodeModules(c *gin.Context) {
|
||||
}
|
||||
helper.SuccessWithOutData(c)
|
||||
}
|
||||
|
||||
// @Tags Runtime
|
||||
// @Summary Sync runtime status
|
||||
// @Description 同步运行环境状态
|
||||
// @Accept json
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /runtimes/sync [post]
|
||||
func (b *BaseApi) SyncStatus(c *gin.Context) {
|
||||
err := runtimeService.SyncRuntimeStatus()
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithOutData(c)
|
||||
}
|
||||
|
||||
@@ -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"`
|
||||
|
||||
@@ -41,7 +41,7 @@ type MysqlDBInfo struct {
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
Permission string `json:"permission"`
|
||||
BackupCount int `json:"backupCount"`
|
||||
IsDelete bool `json:"isDelete"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ type PostgresqlDBInfo struct {
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
SuperUser bool `json:"superUser"`
|
||||
BackupCount int `json:"backupCount"`
|
||||
IsDelete bool `json:"isDelete"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -52,6 +52,8 @@ type SettingInfo struct {
|
||||
AppStoreSyncStatus string `json:"appStoreSyncStatus"`
|
||||
|
||||
FileRecycleBin string `json:"fileRecycleBin"`
|
||||
|
||||
SnapshotIgnore string `json:"snapshotIgnore"`
|
||||
}
|
||||
|
||||
type SettingUpdate struct {
|
||||
|
||||
@@ -9,5 +9,6 @@ type DatabaseMysql struct {
|
||||
Username string `json:"username" gorm:"type:varchar(256);not null"`
|
||||
Password string `json:"password" gorm:"type:varchar(256);not null"`
|
||||
Permission string `json:"permission" gorm:"type:varchar(256);not null"`
|
||||
IsDelete bool `json:"isDelete" gorm:"type:varchar(64)"`
|
||||
Description string `json:"description" gorm:"type:varchar(256);"`
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ type DatabasePostgresql struct {
|
||||
Format string `json:"format" gorm:"type:varchar(64);not null"`
|
||||
Username string `json:"username" gorm:"type:varchar(256);not null"`
|
||||
Password string `json:"password" gorm:"type:varchar(256);not null"`
|
||||
SuperUser bool `json:"superUser" gorm:"type:varchar(64)" `
|
||||
SuperUser bool `json:"superUser" gorm:"type:varchar(64)"`
|
||||
IsDelete bool `json:"isDelete" gorm:"type:varchar(64)"`
|
||||
Description string `json:"description" gorm:"type:varchar(256);"`
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ type IWebsiteRepo interface {
|
||||
WithDefaultServer() DBOption
|
||||
WithDomainLike(domain string) DBOption
|
||||
WithRuntimeID(runtimeID uint) DBOption
|
||||
WithIDs(ids []uint) DBOption
|
||||
Page(page, size int, opts ...DBOption) (int64, []model.Website, error)
|
||||
List(opts ...DBOption) ([]model.Website, error)
|
||||
GetFirst(opts ...DBOption) (model.Website, error)
|
||||
@@ -36,9 +37,15 @@ func NewIWebsiteRepo() IWebsiteRepo {
|
||||
type WebsiteRepo struct {
|
||||
}
|
||||
|
||||
func (w *WebsiteRepo) WithAppInstallId(appInstallId uint) DBOption {
|
||||
func (w *WebsiteRepo) WithAppInstallId(appInstallID uint) DBOption {
|
||||
return func(db *gorm.DB) *gorm.DB {
|
||||
return db.Where("app_install_id = ?", appInstallId)
|
||||
return db.Where("app_install_id = ?", appInstallID)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *WebsiteRepo) WithIDs(ids []uint) DBOption {
|
||||
return func(db *gorm.DB) *gorm.DB {
|
||||
return db.Where("id in (?)", ids)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ type IWebsiteDomainRepo interface {
|
||||
WithWebsiteId(websiteId uint) DBOption
|
||||
WithPort(port int) DBOption
|
||||
WithDomain(domain string) DBOption
|
||||
WithDomainLike(domain string) DBOption
|
||||
Page(page, size int, opts ...DBOption) (int64, []model.WebsiteDomain, error)
|
||||
GetFirst(opts ...DBOption) (model.WebsiteDomain, error)
|
||||
GetBy(opts ...DBOption) ([]model.WebsiteDomain, error)
|
||||
@@ -44,6 +45,11 @@ func (w WebsiteDomainRepo) WithDomain(domain string) DBOption {
|
||||
return db.Where("domain = ?", domain)
|
||||
}
|
||||
}
|
||||
func (w WebsiteDomainRepo) WithDomainLike(domain string) DBOption {
|
||||
return func(db *gorm.DB) *gorm.DB {
|
||||
return db.Where("domain like ?", "%"+domain+"%")
|
||||
}
|
||||
}
|
||||
func (w WebsiteDomainRepo) Page(page, size int, opts ...DBOption) (int64, []model.WebsiteDomain, error) {
|
||||
var domains []model.WebsiteDomain
|
||||
db := getDb(opts...).Model(&model.WebsiteDomain{})
|
||||
|
||||
@@ -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,69 @@ 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 {
|
||||
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 {
|
||||
switch {
|
||||
case count == 0:
|
||||
if appInstall.Status != constant.Error {
|
||||
appInstall.Status = constant.SyncErr
|
||||
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(),
|
||||
|
||||
@@ -133,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, "", "", httpsSetting.Value == "enable", false)
|
||||
c.SetCookie(constant.SessionName, sID, 0, "", "", httpsSetting.Value == "enable", true)
|
||||
err := global.SESSION.Set(sID, sessionUser, lifeTime)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -154,7 +154,7 @@ func (u *AuthService) LogOut(c *gin.Context) error {
|
||||
}
|
||||
sID, _ := c.Cookie(constant.SessionName)
|
||||
if sID != "" {
|
||||
c.SetCookie(constant.SessionName, sID, -1, "", "", httpsSetting.Value == "enable", false)
|
||||
c.SetCookie(constant.SessionName, sID, -1, "", "", httpsSetting.Value == "enable", true)
|
||||
err := global.SESSION.Delete(sID)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -268,11 +268,13 @@ func (u *BackupService) BatchDeleteRecord(ids []uint) error {
|
||||
for _, record := range records {
|
||||
backupAccount, err := backupRepo.Get(commonRepo.WithByType(record.Source))
|
||||
if err != nil {
|
||||
return err
|
||||
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 {
|
||||
return err
|
||||
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)
|
||||
|
||||
@@ -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")
|
||||
|
||||
|
||||
@@ -190,11 +190,13 @@ func (u *MysqlService) LoadFromRemote(req dto.MysqlLoadDB) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
deleteList := databases
|
||||
for _, data := range datas {
|
||||
hasOld := false
|
||||
for _, oldData := range databases {
|
||||
if strings.EqualFold(oldData.Name, data.Name) && strings.EqualFold(oldData.MysqlName, data.MysqlName) {
|
||||
for i := 0; i < len(databases); i++ {
|
||||
if strings.EqualFold(databases[i].Name, data.Name) && strings.EqualFold(databases[i].MysqlName, data.MysqlName) {
|
||||
hasOld = true
|
||||
deleteList = append(deleteList[:i], deleteList[i+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -208,6 +210,9 @@ func (u *MysqlService) LoadFromRemote(req dto.MysqlLoadDB) error {
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, delItem := range deleteList {
|
||||
_ = mysqlRepo.Update(delItem.ID, map[string]interface{}{"is_delete": true})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -342,7 +347,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 +360,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
|
||||
|
||||
@@ -216,11 +216,13 @@ func (u *PostgresqlService) LoadFromRemote(database string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
deleteList := databases
|
||||
for _, data := range datas {
|
||||
hasOld := false
|
||||
for _, oldData := range databases {
|
||||
if strings.EqualFold(oldData.Name, data.Name) && strings.EqualFold(oldData.PostgresqlName, data.PostgresqlName) {
|
||||
for i := 0; i < len(databases); i++ {
|
||||
if strings.EqualFold(databases[i].Name, data.Name) && strings.EqualFold(databases[i].PostgresqlName, data.PostgresqlName) {
|
||||
hasOld = true
|
||||
deleteList = append(deleteList[:i], deleteList[i+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -234,6 +236,9 @@ func (u *PostgresqlService) LoadFromRemote(database string) error {
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, delItem := range deleteList {
|
||||
_ = postgresqlRepo.Update(delItem.ID, map[string]interface{}{"is_delete": true})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -393,7 +398,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 +411,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{
|
||||
|
||||
@@ -153,22 +153,20 @@ func (r RecycleBinService) Clear() error {
|
||||
}
|
||||
|
||||
func getClashDir(realPath string) (string, error) {
|
||||
trimmedPath := strings.Trim(realPath, "/")
|
||||
parts := strings.Split(trimmedPath, "/")
|
||||
dir := ""
|
||||
if len(parts) > 0 {
|
||||
dir = parts[0]
|
||||
partitions, err := disk.Partitions(false)
|
||||
if err != nil {
|
||||
return "", err
|
||||
partitions, err := disk.Partitions(false)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
for _, p := range partitions {
|
||||
if p.Mountpoint == "/" {
|
||||
continue
|
||||
}
|
||||
for _, p := range partitions {
|
||||
if p.Mountpoint == dir {
|
||||
if err = createClashDir(path.Join(p.Mountpoint, ".1panel_clash")); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return dir, nil
|
||||
if strings.HasPrefix(realPath, p.Mountpoint) {
|
||||
clashDir := path.Join(p.Mountpoint, ".1panel_clash")
|
||||
if err = createClashDir(path.Join(p.Mountpoint, ".1panel_clash")); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return clashDir, nil
|
||||
}
|
||||
}
|
||||
return constant.RecycleBinDir, createClashDir(constant.RecycleBinDir)
|
||||
|
||||
@@ -42,6 +42,7 @@ type IRuntimeService interface {
|
||||
GetNodeModules(req request.NodeModuleReq) ([]response.NodeModule, error)
|
||||
OperateNodeModules(req request.NodeModuleOperateReq) error
|
||||
SyncForRestart() error
|
||||
SyncRuntimeStatus() error
|
||||
}
|
||||
|
||||
func NewRuntimeService() IRuntimeService {
|
||||
@@ -586,3 +587,16 @@ func (r *RuntimeService) SyncForRestart() error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *RuntimeService) SyncRuntimeStatus() error {
|
||||
runtimes, err := runtimeRepo.List()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, runtime := range runtimes {
|
||||
if runtime.Type == constant.RuntimeNode {
|
||||
_ = SyncRuntimeContainerStatus(&runtime)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -181,33 +181,20 @@ func SyncRuntimeContainerStatus(runtime *model.Runtime) error {
|
||||
}
|
||||
container := containers[0]
|
||||
|
||||
interval := 10 * time.Second
|
||||
retries := 60
|
||||
for i := 0; i < retries; i++ {
|
||||
resp, err := cli.InspectContainer(container.ID)
|
||||
if err != nil {
|
||||
time.Sleep(interval)
|
||||
continue
|
||||
switch container.State {
|
||||
case "exited":
|
||||
runtime.Status = constant.RuntimeError
|
||||
case "running":
|
||||
runtime.Status = constant.RuntimeRunning
|
||||
case "paused":
|
||||
runtime.Status = constant.RuntimeStopped
|
||||
default:
|
||||
if runtime.Status != constant.RuntimeBuildIng {
|
||||
runtime.Status = constant.RuntimeStopped
|
||||
}
|
||||
if resp.State.Health != nil {
|
||||
status := strings.ToLower(resp.State.Health.Status)
|
||||
switch status {
|
||||
case "starting":
|
||||
runtime.Status = constant.RuntimeStarting
|
||||
_ = runtimeRepo.Save(runtime)
|
||||
case "healthy":
|
||||
runtime.Status = constant.RuntimeRunning
|
||||
_ = runtimeRepo.Save(runtime)
|
||||
return nil
|
||||
case "unhealthy":
|
||||
runtime.Status = constant.RuntimeUnhealthy
|
||||
_ = runtimeRepo.Save(runtime)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
time.Sleep(interval)
|
||||
}
|
||||
return nil
|
||||
|
||||
return runtimeRepo.Save(runtime)
|
||||
}
|
||||
|
||||
func buildRuntime(runtime *model.Runtime, oldImageID string, rebuild bool) {
|
||||
|
||||
@@ -199,7 +199,7 @@ 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, false)
|
||||
c.SetCookie(constant.SessionName, sID, 0, "", "", false, true)
|
||||
|
||||
go func() {
|
||||
_, err := cmd.Exec("systemctl restart 1panel.service")
|
||||
@@ -280,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 {
|
||||
@@ -294,7 +294,7 @@ func (u *SettingService) UpdateSSL(c *gin.Context, req dto.SSLUpdate) error {
|
||||
}
|
||||
|
||||
sID, _ := c.Cookie(constant.SessionName)
|
||||
c.SetCookie(constant.SessionName, sID, 0, "", "", true, false)
|
||||
c.SetCookie(constant.SessionName, sID, 0, "", "", true, true)
|
||||
go func() {
|
||||
time.Sleep(1 * time.Second)
|
||||
_, err := cmd.Exec("systemctl restart 1panel.service")
|
||||
@@ -418,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
-638
@@ -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,7 +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 {
|
||||
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") {
|
||||
@@ -137,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)
|
||||
@@ -164,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
|
||||
}
|
||||
|
||||
@@ -318,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
|
||||
}
|
||||
|
||||
@@ -475,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)
|
||||
|
||||
@@ -497,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
|
||||
@@ -546,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
|
||||
@@ -597,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()
|
||||
@@ -855,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{}{
|
||||
@@ -903,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 {
|
||||
@@ -1002,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) {
|
||||
@@ -1076,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
|
||||
@@ -136,8 +138,17 @@ func snapPanelData(snap snapHelper, localDir, targetDir string) {
|
||||
if strings.Contains(localDir, dataDir) {
|
||||
exclusionRules += ("." + strings.ReplaceAll(localDir, dataDir, "") + ";")
|
||||
}
|
||||
|
||||
ignoreVal, _ := settingRepo.Get(settingRepo.WithByKey("SnapshotIgnore"))
|
||||
rules := strings.Split(ignoreVal.Value, ",")
|
||||
for _, ignore := range rules {
|
||||
if len(ignore) == 0 || cmd.CheckIllegal(ignore) {
|
||||
continue
|
||||
}
|
||||
exclusionRules += ("." + strings.ReplaceAll(ignore, dataDir, "") + ";")
|
||||
}
|
||||
_ = 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,13 +157,10 @@ 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) {
|
||||
defer func() {
|
||||
global.LOG.Debugf("remove snapshot file %s", rootDir)
|
||||
_ = os.RemoveAll(rootDir)
|
||||
}()
|
||||
_ = snapshotRepo.UpdateStatus(snap.Status.ID, map[string]interface{}{"compress": constant.StatusRunning})
|
||||
tmpDir := path.Join(global.CONF.System.TmpDir, "system")
|
||||
fileName := fmt.Sprintf("%s.tar.gz", path.Base(rootDir))
|
||||
@@ -173,15 +181,13 @@ func snapCompress(snap snapHelper, rootDir string) {
|
||||
snap.Status.Compress = constant.StatusDone
|
||||
snap.Status.Size = size
|
||||
_ = snapshotRepo.UpdateStatus(snap.Status.ID, map[string]interface{}{"compress": constant.StatusDone, "size": size})
|
||||
|
||||
global.LOG.Debugf("remove snapshot file %s", rootDir)
|
||||
_ = os.RemoveAll(rootDir)
|
||||
}
|
||||
|
||||
func snapUpload(snap snapHelper, accounts string, file string) {
|
||||
source := path.Join(global.CONF.System.TmpDir, "system", path.Base(file))
|
||||
defer func() {
|
||||
global.LOG.Debugf("remove snapshot file %s", source)
|
||||
_ = os.Remove(source)
|
||||
}()
|
||||
|
||||
_ = snapshotRepo.UpdateStatus(snap.Status.ID, map[string]interface{}{"upload": constant.StatusUploading})
|
||||
accountMap, err := loadClientMap(accounts)
|
||||
if err != nil {
|
||||
@@ -202,6 +208,9 @@ func snapUpload(snap snapHelper, accounts string, file string) {
|
||||
}
|
||||
snap.Status.Upload = constant.StatusDone
|
||||
_ = snapshotRepo.UpdateStatus(snap.Status.ID, map[string]interface{}{"upload": constant.StatusDone})
|
||||
|
||||
global.LOG.Debugf("remove snapshot file %s", source)
|
||||
_ = os.Remove(source)
|
||||
}
|
||||
|
||||
func handleSnapTar(sourceDir, targetDir, name, exclusionRules string) error {
|
||||
@@ -211,6 +220,7 @@ func handleSnapTar(sourceDir, targetDir, name, exclusionRules string) error {
|
||||
}
|
||||
}
|
||||
|
||||
exMap := make(map[string]struct{})
|
||||
exStr := ""
|
||||
excludes := strings.Split(exclusionRules, ";")
|
||||
excludes = append(excludes, "*.sock")
|
||||
@@ -218,8 +228,12 @@ func handleSnapTar(sourceDir, targetDir, name, exclusionRules string) error {
|
||||
if len(exclude) == 0 {
|
||||
continue
|
||||
}
|
||||
if _, ok := exMap[exclude]; ok {
|
||||
continue
|
||||
}
|
||||
exStr += " --exclude "
|
||||
exStr += exclude
|
||||
exMap[exclude] = struct{}{}
|
||||
}
|
||||
|
||||
commands := fmt.Sprintf("tar --warning=no-file-changed --ignore-failed-read -zcf %s %s -C %s .", targetDir+"/"+name, exStr, sourceDir)
|
||||
|
||||
@@ -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(path.Join(src, "docker/daemon.json"))
|
||||
_, 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!")
|
||||
}
|
||||
@@ -177,7 +177,11 @@ func (u *SSHService) Update(req dto.SSHUpdate) error {
|
||||
},
|
||||
}
|
||||
if err := NewIFirewallService().UpdatePortRule(ruleItem); err != nil {
|
||||
global.LOG.Errorf("reset firewall rules %s -> %s failed, err: %v", req.OldValue, req.OldValue, err)
|
||||
global.LOG.Errorf("reset firewall rules %s -> %s failed, err: %v", req.OldValue, req.NewValue, err)
|
||||
}
|
||||
|
||||
if err = NewIHostService().Update(1, map[string]interface{}{"port": req.NewValue}); err != nil {
|
||||
global.LOG.Errorf("reset host port %s -> %s failed, err: %v", req.OldValue, req.NewValue, err)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -133,24 +132,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 +180,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 {
|
||||
@@ -250,19 +249,24 @@ func (u *UpgradeService) loadReleaseNotes(path string) (string, error) {
|
||||
}
|
||||
|
||||
func loadArch() (string, error) {
|
||||
switch runtime.GOARCH {
|
||||
case "amd64", "ppc64le", "s390x", "arm64":
|
||||
return runtime.GOARCH, nil
|
||||
case "arm":
|
||||
std, err := cmd.Exec("uname -m")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("std: %s, err: %s", std, err.Error())
|
||||
}
|
||||
if std == "armv7l\n" {
|
||||
return "armv7", nil
|
||||
}
|
||||
return "", fmt.Errorf("unsupport such arch: arm-%s", std)
|
||||
default:
|
||||
return "", fmt.Errorf("unsupport such arch: %s", runtime.GOARCH)
|
||||
std, err := cmd.Exec("uname -a")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("std: %s, err: %s", std, err.Error())
|
||||
}
|
||||
if strings.Contains(std, "x86_64") {
|
||||
return "amd64", nil
|
||||
}
|
||||
if strings.Contains(std, "arm64") || strings.Contains(std, "aarch64") {
|
||||
return "arm64", nil
|
||||
}
|
||||
if strings.Contains(std, "armv7l") {
|
||||
return "armv7", nil
|
||||
}
|
||||
if strings.Contains(std, "ppc64le") {
|
||||
return "ppc64le", nil
|
||||
}
|
||||
if strings.Contains(std, "s390x") {
|
||||
return "s390x", nil
|
||||
}
|
||||
return "", fmt.Errorf("unsupported such arch: %s", std)
|
||||
}
|
||||
|
||||
@@ -119,7 +119,16 @@ func (w WebsiteService) PageWebsite(req request.WebsiteSearch) (int64, []respons
|
||||
}
|
||||
opts = append(opts, commonRepo.WithOrderRuleBy(req.OrderBy, req.Order))
|
||||
if req.Name != "" {
|
||||
opts = append(opts, websiteRepo.WithDomainLike(req.Name))
|
||||
domains, _ := websiteDomainRepo.GetBy(websiteDomainRepo.WithDomainLike(req.Name))
|
||||
if len(domains) > 0 {
|
||||
var websiteIds []uint
|
||||
for _, domain := range domains {
|
||||
websiteIds = append(websiteIds, domain.WebsiteID)
|
||||
}
|
||||
opts = append(opts, websiteRepo.WithIDs(websiteIds))
|
||||
} else {
|
||||
opts = append(opts, websiteRepo.WithDomainLike(req.Name))
|
||||
}
|
||||
}
|
||||
if req.WebsiteGroupID != 0 {
|
||||
opts = append(opts, websiteRepo.WithGroupID(req.WebsiteGroupID))
|
||||
@@ -820,30 +829,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 +1517,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 +1545,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 +1623,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 +2073,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 +2217,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 {
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto/request"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto/response"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/model"
|
||||
"github.com/1Panel-dev/1Panel/backend/buserr"
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"gopkg.in/square/go-jose.v2/json"
|
||||
)
|
||||
|
||||
type WebsiteDnsAccountService struct {
|
||||
|
||||
@@ -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,6 +12,7 @@ const (
|
||||
Rebuilding = "Rebuilding"
|
||||
Syncing = "Syncing"
|
||||
SyncSuccess = "SyncSuccess"
|
||||
Paused = "Paused"
|
||||
SyncErr = "SyncErr"
|
||||
|
||||
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"
|
||||
@@ -164,3 +166,9 @@ CutWebsiteLogSuccess: "{{ .name }} website log cut successfully, backup path {{
|
||||
#toolbox
|
||||
ErrNotExistUser: "The current user does not exist. Please modify and retry!"
|
||||
ErrBanAction: "Setting failed, the current {{ .name }} service is unavailable, please check and try again!"
|
||||
|
||||
#waf
|
||||
ErrScope: "Modification of this configuration is not supported"
|
||||
ErrStateChange: "State modification failed"
|
||||
ErrRuleExist: "Rule is Exist"
|
||||
ErrRuleNotExist: "Rule is not Exist"
|
||||
@@ -59,6 +59,8 @@ ErrVersionTooLow: '當前 1Panel 版本過低,無法更新應用商店,請
|
||||
ErrAppNameExist: '應用名稱已存在'
|
||||
AppStoreIsSyncing: '應用程式商店正在同步中,請稍後再試'
|
||||
ErrGetCompose: "docker-compose.yml 檔案取得失敗!{{ .detail }}"
|
||||
ErrAppWarn: "狀態異常,請查看日誌"
|
||||
ErrAppParamKey: "參數 {{ .name }} 欄位異常"
|
||||
|
||||
#file
|
||||
ErrFileCanNotRead: "此文件不支持預覽"
|
||||
@@ -165,3 +167,9 @@ CutWebsiteLogSuccess: "{{ .name }} 網站日誌切割成功,備份路徑 {{ .p
|
||||
#toolbox
|
||||
ErrNotExistUser: "當前使用者不存在,請修改後重試!"
|
||||
ErrBanAction: "設置失敗,當前 {{ .name }} 服務不可用,請檢查後重試!"
|
||||
|
||||
#waf
|
||||
ErrScope: "不支援修改此配置"
|
||||
ErrStateChange: "狀態修改失敗"
|
||||
ErrRuleExist: "規則名稱已存在"
|
||||
ErrRuleNotExist: "規則不存在"
|
||||
|
||||
@@ -59,6 +59,8 @@ ErrVersionTooLow: '当前 1Panel 版本过低,无法更新应用商店,请
|
||||
ErrAppNameExist: '应用名称已存在'
|
||||
AppStoreIsSyncing: '应用商店正在同步中,请稍后再试'
|
||||
ErrGetCompose: "docker-compose.yml 文件获取失败!{{ .detail }}"
|
||||
ErrAppWarn: "状态异常,请查看日志"
|
||||
ErrAppParamKey: "参数 {{ .name }} 字段异常"
|
||||
|
||||
#file
|
||||
ErrFileCanNotRead: "此文件不支持预览"
|
||||
@@ -163,4 +165,10 @@ CutWebsiteLogSuccess: "{{ .name }} 网站日志切割成功,备份路径 {{ .p
|
||||
|
||||
#toolbox
|
||||
ErrNotExistUser: "当前用户不存在,请修改后重试!"
|
||||
ErrBanAction: "设置失败,当前 {{ .name }} 服务不可用,请检查后重试!"
|
||||
ErrBanAction: "设置失败,当前 {{ .name }} 服务不可用,请检查后重试!"
|
||||
|
||||
#waf
|
||||
ErrScope: "不支持修改此配置"
|
||||
ErrStateChange: "状态修改失败"
|
||||
ErrRuleExist: "规则名称已存在"
|
||||
ErrRuleNotExist: "规则不存在"
|
||||
@@ -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))
|
||||
|
||||
@@ -6,24 +6,22 @@ import (
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm/logger"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"github.com/glebarez/sqlite"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"gorm.io/gorm/logger"
|
||||
)
|
||||
|
||||
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)
|
||||
|
||||
@@ -72,6 +72,9 @@ func Init() {
|
||||
migrations.UpdateSnapshotRecords,
|
||||
|
||||
migrations.UpdateWebDavConf,
|
||||
|
||||
migrations.AddSnapshotIgnore,
|
||||
migrations.AddDatabaseIsDelete,
|
||||
})
|
||||
if err := m.Migrate(); err != nil {
|
||||
global.LOG.Error(err)
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"github.com/1Panel-dev/1Panel/backend/app/model"
|
||||
"github.com/go-gormigrate/gormigrate/v2"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
var AddSnapshotIgnore = &gormigrate.Migration{
|
||||
ID: "20240311-add-snapshot-ignore",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
if err := tx.Create(&model.Setting{Key: "SnapshotIgnore", Value: "*.sock"}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var AddDatabaseIsDelete = &gormigrate.Migration{
|
||||
ID: "20240314-add-database-is-delete",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
if err := tx.AutoMigrate(&model.DatabaseMysql{}, &model.DatabasePostgresql{}); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
@@ -124,6 +124,12 @@ var AddTablePHPExtensions = &gormigrate.Migration{
|
||||
if err := tx.Create(&model.PHPExtensions{Name: "默认", Extensions: "bcmath,gd,gettext,intl,pcntl,shmop,soap,sockets,sysvsem,xmlrpc,zip"}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Create(&model.PHPExtensions{Name: "苹果CMS-V10", Extensions: "mysqli,pdo_mysql,zip,gd,redis,memcache,memcached"}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Create(&model.PHPExtensions{Name: "SeaCMS", Extensions: "mysqli,pdo_mysql,gd,curl"}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
@@ -330,7 +336,7 @@ var UpdateCronjobSpec = &gormigrate.Migration{
|
||||
Source: snap.From,
|
||||
BackupType: snap.From,
|
||||
BaseModel: model.BaseModel{
|
||||
CreatedAt: job.CreatedAt,
|
||||
CreatedAt: record.CreatedAt,
|
||||
},
|
||||
}
|
||||
_ = tx.Create(&item).Error
|
||||
@@ -348,7 +354,7 @@ var UpdateCronjobSpec = &gormigrate.Migration{
|
||||
Source: mapAccount[uint(job.TargetDirID)].Type,
|
||||
BackupType: mapAccount[uint(job.TargetDirID)].Type,
|
||||
BaseModel: model.BaseModel{
|
||||
CreatedAt: job.CreatedAt,
|
||||
CreatedAt: record.CreatedAt,
|
||||
},
|
||||
}
|
||||
_ = tx.Create(&item).Error
|
||||
@@ -364,7 +370,7 @@ var UpdateCronjobSpec = &gormigrate.Migration{
|
||||
FileName: path.Base(record.File),
|
||||
BackupType: mapAccount[uint(job.TargetDirID)].Type,
|
||||
BaseModel: model.BaseModel{
|
||||
CreatedAt: job.CreatedAt,
|
||||
CreatedAt: record.CreatedAt,
|
||||
},
|
||||
}
|
||||
if record.FromLocal {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -20,6 +20,7 @@ func (r *RuntimeRouter) InitRouter(Router *gin.RouterGroup) {
|
||||
groupRouter.POST("/del", baseApi.DeleteRuntime)
|
||||
groupRouter.POST("/update", baseApi.UpdateRuntime)
|
||||
groupRouter.GET("/:id", baseApi.GetRuntime)
|
||||
groupRouter.POST("/sync", baseApi.SyncStatus)
|
||||
|
||||
groupRouter.POST("/node/package", baseApi.GetNodePackageRunScript)
|
||||
groupRouter.POST("/operate", baseApi.OperateRuntime)
|
||||
|
||||
@@ -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 ""
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -14,6 +15,7 @@ import (
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/files"
|
||||
odsdk "github.com/goh-chunlin/go-onedrive/onedrive"
|
||||
@@ -116,7 +118,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 +302,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
|
||||
@@ -321,7 +323,6 @@ func (o *oneDriveClient) upSmall(ctx context.Context, srcPath, folderID string,
|
||||
if err := o.client.Do(context.Background(), req, false, &response); err != nil {
|
||||
return false, fmt.Errorf("do request for list failed, err: %v", err)
|
||||
}
|
||||
fmt.Println(response)
|
||||
return true, nil
|
||||
}
|
||||
|
||||
@@ -362,7 +363,13 @@ func (o *oneDriveClient) upBig(ctx context.Context, srcPath, folderID string, fi
|
||||
splitCount += 1
|
||||
}
|
||||
bfReader := bufio.NewReader(file)
|
||||
var fileUploadResp *UploadSessionUploadResponse
|
||||
httpClient := http.Client{
|
||||
Timeout: time.Minute * 10,
|
||||
Transport: &http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
},
|
||||
}
|
||||
for splitNow := int64(0); splitNow < splitCount; splitNow++ {
|
||||
length, err := bfReader.Read(buffer)
|
||||
if err != nil {
|
||||
@@ -376,12 +383,15 @@ func (o *oneDriveClient) upBig(ctx context.Context, srcPath, folderID string, fi
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if err := o.client.Do(ctx, sessionFileUploadReq, false, &fileUploadResp); err != nil {
|
||||
res, err := httpClient.Do(sessionFileUploadReq)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
if fileUploadResp.Id == "" {
|
||||
return false, errors.New("something went wrong. file upload incomplete. consider upload the file in a step-by-step manner")
|
||||
if res.StatusCode != 201 && res.StatusCode != 202 && res.StatusCode != 200 {
|
||||
data, _ := io.ReadAll(res.Body)
|
||||
res.Body.Close()
|
||||
return false, errors.New(string(data))
|
||||
}
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
+53
-40
@@ -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
|
||||
}
|
||||
|
||||
@@ -165,7 +177,8 @@ func CheckIllegal(args ...string) bool {
|
||||
for _, arg := range args {
|
||||
if strings.Contains(arg, "&") || strings.Contains(arg, "|") || strings.Contains(arg, ";") ||
|
||||
strings.Contains(arg, "$") || strings.Contains(arg, "'") || strings.Contains(arg, "`") ||
|
||||
strings.Contains(arg, "(") || strings.Contains(arg, ")") || strings.Contains(arg, "\"") {
|
||||
strings.Contains(arg, "(") || strings.Contains(arg, ")") || strings.Contains(arg, "\"") ||
|
||||
strings.Contains(arg, "\n") || strings.Contains(arg, "\r") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,12 +126,19 @@ 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) SaveFileWithByte(dst string, content []byte, mode fs.FileMode) error {
|
||||
if !f.Stat(path.Dir(dst)) {
|
||||
_ = f.CreateDir(path.Dir(dst), mode.Perm())
|
||||
}
|
||||
file, err := f.Fs.OpenFile(dst, os.O_RDWR|os.O_CREATE|os.O_TRUNC, mode)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
write := bufio.NewWriter(file)
|
||||
_, _ = write.Write(content)
|
||||
write.Flush()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f FileOp) ChownR(dst string, uid string, gid string, sub bool) error {
|
||||
@@ -563,40 +567,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,24 +41,27 @@ 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)
|
||||
},
|
||||
}
|
||||
|
||||
parser.directiveWrappers = map[string]func(*components.Directive) components.IDirective{
|
||||
"server": func(directive *components.Directive) components.IDirective {
|
||||
return parser.parseUpstreamServer(directive)
|
||||
return parser.wrapServer(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.Keyword):
|
||||
context.Directives = append(context.Directives, p.parseStatement())
|
||||
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) || p.curTokenIs(flag.QuotedString):
|
||||
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
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user