mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2025-01-31 14:08:06 +08:00
feat: core 功能代码拆分
This commit is contained in:
parent
afcf509ec0
commit
2ef7b8b3b1
186
core/app/api/v1/auth.go
Normal file
186
core/app/api/v1/auth.go
Normal file
@ -0,0 +1,186 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/core/app/api/v1/helper"
|
||||
"github.com/1Panel-dev/1Panel/core/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/core/app/model"
|
||||
"github.com/1Panel-dev/1Panel/core/constant"
|
||||
"github.com/1Panel-dev/1Panel/core/global"
|
||||
|
||||
// "github.com/1Panel-dev/1Panel/core/middleware"
|
||||
"github.com/1Panel-dev/1Panel/core/utils/captcha"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type BaseApi struct{}
|
||||
|
||||
// @Tags Auth
|
||||
// @Summary User login
|
||||
// @Description 用户登录
|
||||
// @Accept json
|
||||
// @Param EntranceCode header string true "安全入口 base64 加密串"
|
||||
// @Param request body dto.Login true "request"
|
||||
// @Success 200 {object} dto.UserLoginInfo
|
||||
// @Router /auth/login [post]
|
||||
func (b *BaseApi) Login(c *gin.Context) {
|
||||
var req dto.Login
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if req.AuthMethod != "jwt" && !req.IgnoreCaptcha {
|
||||
if err := captcha.VerifyCode(req.CaptchaID, req.Captcha); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
entranceItem := c.Request.Header.Get("EntranceCode")
|
||||
var entrance []byte
|
||||
if len(entranceItem) != 0 {
|
||||
entrance, _ = base64.StdEncoding.DecodeString(entranceItem)
|
||||
}
|
||||
|
||||
user, err := authService.Login(c, req, string(entrance))
|
||||
go saveLoginLogs(c, err)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, user)
|
||||
}
|
||||
|
||||
// @Tags Auth
|
||||
// @Summary User login with mfa
|
||||
// @Description 用户 mfa 登录
|
||||
// @Accept json
|
||||
// @Param request body dto.MFALogin true "request"
|
||||
// @Success 200 {object} dto.UserLoginInfo
|
||||
// @Router /auth/mfalogin [post]
|
||||
// @Header 200 {string} EntranceCode "安全入口"
|
||||
func (b *BaseApi) MFALogin(c *gin.Context) {
|
||||
var req dto.MFALogin
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
entranceItem := c.Request.Header.Get("EntranceCode")
|
||||
var entrance []byte
|
||||
if len(entranceItem) != 0 {
|
||||
entrance, _ = base64.StdEncoding.DecodeString(entranceItem)
|
||||
}
|
||||
|
||||
user, err := authService.MFALogin(c, req, string(entrance))
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, user)
|
||||
}
|
||||
|
||||
// @Tags Auth
|
||||
// @Summary User logout
|
||||
// @Description 用户登出
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /auth/logout [post]
|
||||
func (b *BaseApi) LogOut(c *gin.Context) {
|
||||
if err := authService.LogOut(c); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, nil)
|
||||
}
|
||||
|
||||
// @Tags Auth
|
||||
// @Summary Load captcha
|
||||
// @Description 加载验证码
|
||||
// @Success 200 {object} dto.CaptchaResponse
|
||||
// @Router /auth/captcha [get]
|
||||
func (b *BaseApi) Captcha(c *gin.Context) {
|
||||
captcha, err := captcha.CreateCaptcha()
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, captcha)
|
||||
}
|
||||
|
||||
// @Tags Auth
|
||||
// @Summary Load safety status
|
||||
// @Description 获取系统安全登录状态
|
||||
// @Success 200
|
||||
// @Router /auth/issafety [get]
|
||||
func (b *BaseApi) CheckIsSafety(c *gin.Context) {
|
||||
code := c.DefaultQuery("code", "")
|
||||
status, err := authService.CheckIsSafety(code)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
if status == "disable" && len(code) != 0 {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrNotFound, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
if status == "unpass" {
|
||||
// if middleware.Get.LoadErrCode("err-entrance") != 200 {
|
||||
// helper.ErrResponse(c, middleware.LoadErrCode("err-entrance"))
|
||||
// return
|
||||
// }
|
||||
helper.ErrorWithDetail(c, constant.CodeErrEntrance, constant.ErrTypeInternalServer, nil)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithOutData(c)
|
||||
}
|
||||
|
||||
func (b *BaseApi) GetResponsePage(c *gin.Context) {
|
||||
pageCode, err := authService.GetResponsePage()
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, pageCode)
|
||||
}
|
||||
|
||||
// @Tags Auth
|
||||
// @Summary Check System isDemo
|
||||
// @Description 判断是否为demo环境
|
||||
// @Success 200
|
||||
// @Router /auth/demo [get]
|
||||
func (b *BaseApi) CheckIsDemo(c *gin.Context) {
|
||||
helper.SuccessWithData(c, global.CONF.System.IsDemo)
|
||||
}
|
||||
|
||||
// @Tags Auth
|
||||
// @Summary Load System Language
|
||||
// @Description 获取系统语言设置
|
||||
// @Success 200
|
||||
// @Router /auth/language [get]
|
||||
func (b *BaseApi) GetLanguage(c *gin.Context) {
|
||||
settingInfo, err := settingService.GetSettingInfo()
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, settingInfo.Language)
|
||||
}
|
||||
|
||||
func saveLoginLogs(c *gin.Context, err error) {
|
||||
var logs model.LoginLog
|
||||
if err != nil {
|
||||
logs.Status = constant.StatusFailed
|
||||
logs.Message = err.Error()
|
||||
} else {
|
||||
logs.Status = constant.StatusSuccess
|
||||
}
|
||||
logs.IP = c.ClientIP()
|
||||
// qqWry, err := qqwry.NewQQwry()
|
||||
// if err != nil {
|
||||
// global.LOG.Errorf("load qqwry datas failed: %s", err)
|
||||
// }
|
||||
// res := qqWry.Find(logs.IP)
|
||||
logs.Agent = c.GetHeader("User-Agent")
|
||||
// logs.Address = res.Area
|
||||
_ = logService.CreateLoginLog(logs)
|
||||
}
|
16
core/app/api/v1/entry.go
Normal file
16
core/app/api/v1/entry.go
Normal file
@ -0,0 +1,16 @@
|
||||
package v1
|
||||
|
||||
import "github.com/1Panel-dev/1Panel/core/app/service"
|
||||
|
||||
type ApiGroup struct {
|
||||
BaseApi
|
||||
}
|
||||
|
||||
var ApiGroupApp = new(ApiGroup)
|
||||
|
||||
var (
|
||||
authService = service.NewIAuthService()
|
||||
settingService = service.NewISettingService()
|
||||
logService = service.NewILogService()
|
||||
upgradeService = service.NewIUpgradeService()
|
||||
)
|
86
core/app/api/v1/helper/helper.go
Normal file
86
core/app/api/v1/helper/helper.go
Normal file
@ -0,0 +1,86 @@
|
||||
package helper
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/core/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/core/buserr"
|
||||
"github.com/1Panel-dev/1Panel/core/constant"
|
||||
"github.com/1Panel-dev/1Panel/core/global"
|
||||
"github.com/1Panel-dev/1Panel/core/i18n"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func ErrorWithDetail(ctx *gin.Context, code int, msgKey string, err error) {
|
||||
res := dto.Response{
|
||||
Code: code,
|
||||
Message: "",
|
||||
}
|
||||
if msgKey == constant.ErrTypeInternalServer {
|
||||
switch {
|
||||
case errors.Is(err, constant.ErrRecordExist):
|
||||
res.Message = i18n.GetMsgWithMap("ErrRecordExist", nil)
|
||||
case errors.Is(constant.ErrRecordNotFound, err):
|
||||
res.Message = i18n.GetMsgWithMap("ErrRecordNotFound", nil)
|
||||
case errors.Is(constant.ErrInvalidParams, err):
|
||||
res.Message = i18n.GetMsgWithMap("ErrInvalidParams", nil)
|
||||
case errors.Is(constant.ErrStructTransform, err):
|
||||
res.Message = i18n.GetMsgWithMap("ErrStructTransform", map[string]interface{}{"detail": err})
|
||||
case errors.Is(constant.ErrCaptchaCode, err):
|
||||
res.Code = constant.CodeAuth
|
||||
res.Message = "ErrCaptchaCode"
|
||||
case errors.Is(constant.ErrAuth, err):
|
||||
res.Code = constant.CodeAuth
|
||||
res.Message = "ErrAuth"
|
||||
case errors.Is(constant.ErrInitialPassword, err):
|
||||
res.Message = i18n.GetMsgWithMap("ErrInitialPassword", map[string]interface{}{"detail": err})
|
||||
case errors.As(err, &buserr.BusinessError{}):
|
||||
res.Message = err.Error()
|
||||
default:
|
||||
res.Message = i18n.GetMsgWithMap(msgKey, map[string]interface{}{"detail": err})
|
||||
}
|
||||
} else {
|
||||
res.Message = i18n.GetMsgWithMap(msgKey, map[string]interface{}{"detail": err})
|
||||
}
|
||||
ctx.JSON(http.StatusOK, res)
|
||||
ctx.Abort()
|
||||
}
|
||||
|
||||
func SuccessWithData(ctx *gin.Context, data interface{}) {
|
||||
if data == nil {
|
||||
data = gin.H{}
|
||||
}
|
||||
res := dto.Response{
|
||||
Code: constant.CodeSuccess,
|
||||
Data: data,
|
||||
}
|
||||
ctx.JSON(http.StatusOK, res)
|
||||
ctx.Abort()
|
||||
}
|
||||
|
||||
func SuccessWithOutData(ctx *gin.Context) {
|
||||
res := dto.Response{
|
||||
Code: constant.CodeSuccess,
|
||||
Message: "success",
|
||||
}
|
||||
ctx.JSON(http.StatusOK, res)
|
||||
ctx.Abort()
|
||||
}
|
||||
|
||||
func CheckBindAndValidate(req interface{}, c *gin.Context) error {
|
||||
if err := c.ShouldBindJSON(req); err != nil {
|
||||
ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return err
|
||||
}
|
||||
if err := global.VALID.Struct(req); err != nil {
|
||||
ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ErrResponse(ctx *gin.Context, code int) {
|
||||
ctx.JSON(code, nil)
|
||||
ctx.Abort()
|
||||
}
|
83
core/app/api/v1/logs.go
Normal file
83
core/app/api/v1/logs.go
Normal file
@ -0,0 +1,83 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"github.com/1Panel-dev/1Panel/core/app/api/v1/helper"
|
||||
"github.com/1Panel-dev/1Panel/core/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/core/constant"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// @Tags Logs
|
||||
// @Summary Page login logs
|
||||
// @Description 获取系统登录日志列表分页
|
||||
// @Accept json
|
||||
// @Param request body dto.SearchLgLogWithPage true "request"
|
||||
// @Success 200 {object} dto.PageResult
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /logs/login [post]
|
||||
func (b *BaseApi) GetLoginLogs(c *gin.Context) {
|
||||
var req dto.SearchLgLogWithPage
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
total, list, err := logService.PageLoginLog(req)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
|
||||
helper.SuccessWithData(c, dto.PageResult{
|
||||
Items: list,
|
||||
Total: total,
|
||||
})
|
||||
}
|
||||
|
||||
// @Tags Logs
|
||||
// @Summary Page operation logs
|
||||
// @Description 获取系统操作日志列表分页
|
||||
// @Accept json
|
||||
// @Param request body dto.SearchOpLogWithPage true "request"
|
||||
// @Success 200 {object} dto.PageResult
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /logs/operation [post]
|
||||
func (b *BaseApi) GetOperationLogs(c *gin.Context) {
|
||||
var req dto.SearchOpLogWithPage
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
total, list, err := logService.PageOperationLog(req)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
|
||||
helper.SuccessWithData(c, dto.PageResult{
|
||||
Items: list,
|
||||
Total: total,
|
||||
})
|
||||
}
|
||||
|
||||
// @Tags Logs
|
||||
// @Summary Clean operation logs
|
||||
// @Description 清空操作日志
|
||||
// @Accept json
|
||||
// @Param request body dto.CleanLog true "request"
|
||||
// @Success 200 {object} dto.PageResult
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /logs/clean [post]
|
||||
// @x-panel-log {"bodyKeys":["logType"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"清空 [logType] 日志信息","formatEN":"Clean the [logType] log information"}
|
||||
func (b *BaseApi) CleanLogs(c *gin.Context) {
|
||||
var req dto.CleanLog
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err := logService.CleanLogs(req.LogType); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
|
||||
helper.SuccessWithData(c, nil)
|
||||
}
|
344
core/app/api/v1/setting.go
Normal file
344
core/app/api/v1/setting.go
Normal file
@ -0,0 +1,344 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"os"
|
||||
"path"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/core/app/api/v1/helper"
|
||||
"github.com/1Panel-dev/1Panel/core/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/core/constant"
|
||||
"github.com/1Panel-dev/1Panel/core/global"
|
||||
"github.com/1Panel-dev/1Panel/core/utils/mfa"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// @Tags System Setting
|
||||
// @Summary Load system setting info
|
||||
// @Description 加载系统配置信息
|
||||
// @Success 200 {object} dto.SettingInfo
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /settings/search [post]
|
||||
func (b *BaseApi) GetSettingInfo(c *gin.Context) {
|
||||
setting, err := settingService.GetSettingInfo()
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, setting)
|
||||
}
|
||||
|
||||
// @Tags System Setting
|
||||
// @Summary Load system available status
|
||||
// @Description 获取系统可用状态
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /settings/search/available [get]
|
||||
func (b *BaseApi) GetSystemAvailable(c *gin.Context) {
|
||||
helper.SuccessWithData(c, nil)
|
||||
}
|
||||
|
||||
// @Tags System Setting
|
||||
// @Summary Update system setting
|
||||
// @Description 更新系统配置
|
||||
// @Accept json
|
||||
// @Param request body dto.SettingUpdate true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /settings/update [post]
|
||||
// @x-panel-log {"bodyKeys":["key","value"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"修改系统配置 [key] => [value]","formatEN":"update system setting [key] => [value]"}
|
||||
func (b *BaseApi) UpdateSetting(c *gin.Context) {
|
||||
var req dto.SettingUpdate
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err := settingService.Update(req.Key, req.Value); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, nil)
|
||||
}
|
||||
|
||||
// @Tags System Setting
|
||||
// @Summary Update proxy setting
|
||||
// @Description 服务器代理配置
|
||||
// @Accept json
|
||||
// @Param request body dto.ProxyUpdate true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /settings/proxy/update [post]
|
||||
// @x-panel-log {"bodyKeys":["proxyUrl","proxyPort"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"服务器代理配置 [proxyPort]:[proxyPort]","formatEN":"set proxy [proxyPort]:[proxyPort]."}
|
||||
func (b *BaseApi) UpdateProxy(c *gin.Context) {
|
||||
var req dto.ProxyUpdate
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if len(req.ProxyPasswd) != 0 && len(req.ProxyType) != 0 {
|
||||
pass, err := base64.StdEncoding.DecodeString(req.ProxyPasswd)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
req.ProxyPasswd = string(pass)
|
||||
}
|
||||
|
||||
if err := settingService.UpdateProxy(req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, nil)
|
||||
}
|
||||
|
||||
// @Tags System Setting
|
||||
// @Summary Update system setting
|
||||
// @Description 隐藏高级功能菜单
|
||||
// @Accept json
|
||||
// @Param request body dto.SettingUpdate true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /settings/menu/update [post]
|
||||
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"隐藏高级功能菜单","formatEN":"Hide advanced feature menu."}
|
||||
func (b *BaseApi) UpdateMenu(c *gin.Context) {
|
||||
var req dto.SettingUpdate
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err := settingService.Update(req.Key, req.Value); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, nil)
|
||||
}
|
||||
|
||||
// @Tags System Setting
|
||||
// @Summary Update system password
|
||||
// @Description 更新系统登录密码
|
||||
// @Accept json
|
||||
// @Param request body dto.PasswordUpdate true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /settings/password/update [post]
|
||||
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"修改系统密码","formatEN":"update system password"}
|
||||
func (b *BaseApi) UpdatePassword(c *gin.Context) {
|
||||
var req dto.PasswordUpdate
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err := settingService.UpdatePassword(c, req.OldPassword, req.NewPassword); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, nil)
|
||||
}
|
||||
|
||||
// @Tags System Setting
|
||||
// @Summary Update system ssl
|
||||
// @Description 修改系统 ssl 登录
|
||||
// @Accept json
|
||||
// @Param request body dto.SSLUpdate true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /settings/ssl/update [post]
|
||||
// @x-panel-log {"bodyKeys":["ssl"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"修改系统 ssl => [ssl]","formatEN":"update system ssl => [ssl]"}
|
||||
func (b *BaseApi) UpdateSSL(c *gin.Context) {
|
||||
var req dto.SSLUpdate
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err := settingService.UpdateSSL(c, req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, nil)
|
||||
}
|
||||
|
||||
// @Tags System Setting
|
||||
// @Summary Load system cert info
|
||||
// @Description 获取证书信息
|
||||
// @Success 200 {object} dto.SettingInfo
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /settings/ssl/info [get]
|
||||
func (b *BaseApi) LoadFromCert(c *gin.Context) {
|
||||
info, err := settingService.LoadFromCert()
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, info)
|
||||
}
|
||||
|
||||
// @Tags System Setting
|
||||
// @Summary Download system cert
|
||||
// @Description 下载证书
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /settings/ssl/download [post]
|
||||
func (b *BaseApi) DownloadSSL(c *gin.Context) {
|
||||
pathItem := path.Join(global.CONF.System.BaseDir, "1panel/secret/server.crt")
|
||||
if _, err := os.Stat(pathItem); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.File(pathItem)
|
||||
}
|
||||
|
||||
// @Tags System Setting
|
||||
// @Summary Load system address
|
||||
// @Description 获取系统地址信息
|
||||
// @Accept json
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /settings/interface [get]
|
||||
func (b *BaseApi) LoadInterfaceAddr(c *gin.Context) {
|
||||
data, err := settingService.LoadInterfaceAddr()
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, data)
|
||||
}
|
||||
|
||||
// @Tags System Setting
|
||||
// @Summary Update system bind info
|
||||
// @Description 更新系统监听信息
|
||||
// @Accept json
|
||||
// @Param request body dto.BindInfo true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /settings/bind/update [post]
|
||||
// @x-panel-log {"bodyKeys":["ipv6", "bindAddress"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"修改系统监听信息 => ipv6: [ipv6], 监听 IP: [bindAddress]","formatEN":"update system bind info => ipv6: [ipv6], 监听 IP: [bindAddress]"}
|
||||
func (b *BaseApi) UpdateBindInfo(c *gin.Context) {
|
||||
var req dto.BindInfo
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err := settingService.UpdateBindInfo(req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, nil)
|
||||
}
|
||||
|
||||
// @Tags System Setting
|
||||
// @Summary Update system port
|
||||
// @Description 更新系统端口
|
||||
// @Accept json
|
||||
// @Param request body dto.PortUpdate true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /settings/port/update [post]
|
||||
// @x-panel-log {"bodyKeys":["serverPort"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"修改系统端口 => [serverPort]","formatEN":"update system port => [serverPort]"}
|
||||
func (b *BaseApi) UpdatePort(c *gin.Context) {
|
||||
var req dto.PortUpdate
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err := settingService.UpdatePort(req.ServerPort); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, nil)
|
||||
}
|
||||
|
||||
// @Tags System Setting
|
||||
// @Summary Reset system password expired
|
||||
// @Description 重置过期系统登录密码
|
||||
// @Accept json
|
||||
// @Param request body dto.PasswordUpdate true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /settings/expired/handle [post]
|
||||
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"重置过期密码","formatEN":"reset an expired Password"}
|
||||
func (b *BaseApi) HandlePasswordExpired(c *gin.Context) {
|
||||
var req dto.PasswordUpdate
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err := settingService.HandlePasswordExpired(c, req.OldPassword, req.NewPassword); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, nil)
|
||||
}
|
||||
|
||||
// @Tags System Setting
|
||||
// @Summary Load local backup dir
|
||||
// @Description 获取安装根目录
|
||||
// @Success 200 {string} path
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /settings/basedir [get]
|
||||
func (b *BaseApi) LoadBaseDir(c *gin.Context) {
|
||||
helper.SuccessWithData(c, global.CONF.System.DataDir)
|
||||
}
|
||||
|
||||
// @Tags System Setting
|
||||
// @Summary Load mfa info
|
||||
// @Description 获取 mfa 信息
|
||||
// @Accept json
|
||||
// @Param request body dto.MfaCredential true "request"
|
||||
// @Success 200 {object} mfa.Otp
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /settings/mfa [post]
|
||||
func (b *BaseApi) LoadMFA(c *gin.Context) {
|
||||
var req dto.MfaRequest
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
otp, err := mfa.GetOtp("admin", req.Title, req.Interval)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
|
||||
helper.SuccessWithData(c, otp)
|
||||
}
|
||||
|
||||
// @Tags System Setting
|
||||
// @Summary Bind mfa
|
||||
// @Description Mfa 绑定
|
||||
// @Accept json
|
||||
// @Param request body dto.MfaCredential true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /settings/mfa/bind [post]
|
||||
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"mfa 绑定","formatEN":"bind mfa"}
|
||||
func (b *BaseApi) MFABind(c *gin.Context) {
|
||||
var req dto.MfaCredential
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
success := mfa.ValidCode(req.Code, req.Interval, req.Secret)
|
||||
if !success {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, errors.New("code is not valid"))
|
||||
return
|
||||
}
|
||||
|
||||
if err := settingService.Update("MFAInterval", req.Interval); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := settingService.Update("MFAStatus", "enable"); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := settingService.Update("MFASecret", req.Secret); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
|
||||
helper.SuccessWithData(c, nil)
|
||||
}
|
67
core/app/api/v1/upgrade.go
Normal file
67
core/app/api/v1/upgrade.go
Normal file
@ -0,0 +1,67 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"github.com/1Panel-dev/1Panel/core/app/api/v1/helper"
|
||||
"github.com/1Panel-dev/1Panel/core/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/core/constant"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// @Tags System Setting
|
||||
// @Summary Load upgrade info
|
||||
// @Description 系统更新信息
|
||||
// @Success 200 {object} dto.UpgradeInfo
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /settings/upgrade [get]
|
||||
func (b *BaseApi) GetUpgradeInfo(c *gin.Context) {
|
||||
info, err := upgradeService.SearchUpgrade()
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, info)
|
||||
}
|
||||
|
||||
// @Tags System Setting
|
||||
// @Summary Load release notes by version
|
||||
// @Description 获取版本 release notes
|
||||
// @Accept json
|
||||
// @Param request body dto.Upgrade true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /settings/upgrade [get]
|
||||
func (b *BaseApi) GetNotesByVersion(c *gin.Context) {
|
||||
var req dto.Upgrade
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
notes, err := upgradeService.LoadNotes(req)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, notes)
|
||||
}
|
||||
|
||||
// @Tags System Setting
|
||||
// @Summary Upgrade
|
||||
// @Description 系统更新
|
||||
// @Accept json
|
||||
// @Param request body dto.Upgrade true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /settings/upgrade [post]
|
||||
// @x-panel-log {"bodyKeys":["version"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新系统 => [version]","formatEN":"upgrade system => [version]"}
|
||||
func (b *BaseApi) Upgrade(c *gin.Context) {
|
||||
var req dto.Upgrade
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err := upgradeService.Upgrade(req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, nil)
|
||||
}
|
40
core/app/dto/auth.go
Normal file
40
core/app/dto/auth.go
Normal file
@ -0,0 +1,40 @@
|
||||
package dto
|
||||
|
||||
type CaptchaResponse struct {
|
||||
CaptchaID string `json:"captchaID"`
|
||||
ImagePath string `json:"imagePath"`
|
||||
}
|
||||
|
||||
type UserLoginInfo struct {
|
||||
Name string `json:"name"`
|
||||
Token string `json:"token"`
|
||||
MfaStatus string `json:"mfaStatus"`
|
||||
}
|
||||
|
||||
type MfaRequest struct {
|
||||
Title string `json:"title" validate:"required"`
|
||||
Interval int `json:"interval" validate:"required"`
|
||||
}
|
||||
|
||||
type MfaCredential struct {
|
||||
Secret string `json:"secret" validate:"required"`
|
||||
Code string `json:"code" validate:"required"`
|
||||
Interval string `json:"interval" validate:"required"`
|
||||
}
|
||||
|
||||
type Login struct {
|
||||
Name string `json:"name" validate:"required"`
|
||||
Password string `json:"password" validate:"required"`
|
||||
IgnoreCaptcha bool `json:"ignoreCaptcha"`
|
||||
Captcha string `json:"captcha"`
|
||||
CaptchaID string `json:"captchaID"`
|
||||
AuthMethod string `json:"authMethod" validate:"required,oneof=jwt session"`
|
||||
Language string `json:"language" validate:"required,oneof=zh en tw"`
|
||||
}
|
||||
|
||||
type MFALogin struct {
|
||||
Name string `json:"name" validate:"required"`
|
||||
Password string `json:"password" validate:"required"`
|
||||
Code string `json:"code" validate:"required"`
|
||||
AuthMethod string `json:"authMethod"`
|
||||
}
|
54
core/app/dto/common_req.go
Normal file
54
core/app/dto/common_req.go
Normal file
@ -0,0 +1,54 @@
|
||||
package dto
|
||||
|
||||
type SearchWithPage struct {
|
||||
PageInfo
|
||||
Info string `json:"info"`
|
||||
}
|
||||
|
||||
type PageInfo struct {
|
||||
Page int `json:"page" validate:"required,number"`
|
||||
PageSize int `json:"pageSize" validate:"required,number"`
|
||||
}
|
||||
|
||||
type UpdateDescription struct {
|
||||
ID uint `json:"id" validate:"required"`
|
||||
Description string `json:"description" validate:"max=256"`
|
||||
}
|
||||
|
||||
type OperationWithName struct {
|
||||
Name string `json:"name" validate:"required"`
|
||||
}
|
||||
|
||||
type OperateByID struct {
|
||||
ID uint `json:"id" validate:"required"`
|
||||
}
|
||||
|
||||
type Operate struct {
|
||||
Operation string `json:"operation" validate:"required"`
|
||||
}
|
||||
|
||||
type BatchDeleteReq struct {
|
||||
Ids []uint `json:"ids" validate:"required"`
|
||||
}
|
||||
|
||||
type FilePath struct {
|
||||
Path string `json:"path" validate:"required"`
|
||||
}
|
||||
|
||||
type DeleteByName struct {
|
||||
Name string `json:"name" validate:"required"`
|
||||
}
|
||||
|
||||
type UpdateByFile struct {
|
||||
File string `json:"file"`
|
||||
}
|
||||
|
||||
type UpdateByNameAndFile struct {
|
||||
Name string `json:"name"`
|
||||
File string `json:"file"`
|
||||
}
|
||||
|
||||
type OperationWithNameAndType struct {
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type" validate:"required"`
|
||||
}
|
16
core/app/dto/common_res.go
Normal file
16
core/app/dto/common_res.go
Normal file
@ -0,0 +1,16 @@
|
||||
package dto
|
||||
|
||||
type PageResult struct {
|
||||
Total int64 `json:"total"`
|
||||
Items interface{} `json:"items"`
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
Data interface{} `json:"data"`
|
||||
}
|
||||
|
||||
type Options struct {
|
||||
Option string `json:"option"`
|
||||
}
|
50
core/app/dto/logs.go
Normal file
50
core/app/dto/logs.go
Normal file
@ -0,0 +1,50 @@
|
||||
package dto
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type OperationLog struct {
|
||||
ID uint `json:"id"`
|
||||
Source string `json:"source"`
|
||||
|
||||
IP string `json:"ip"`
|
||||
Path string `json:"path"`
|
||||
Method string `json:"method"`
|
||||
UserAgent string `json:"userAgent"`
|
||||
|
||||
Latency time.Duration `json:"latency"`
|
||||
Status string `json:"status"`
|
||||
Message string `json:"message"`
|
||||
|
||||
DetailZH string `json:"detailZH"`
|
||||
DetailEN string `json:"detailEN"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
}
|
||||
|
||||
type SearchOpLogWithPage struct {
|
||||
PageInfo
|
||||
Source string `json:"source"`
|
||||
Status string `json:"status"`
|
||||
Operation string `json:"operation"`
|
||||
}
|
||||
|
||||
type SearchLgLogWithPage struct {
|
||||
PageInfo
|
||||
IP string `json:"ip"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
type LoginLog struct {
|
||||
ID uint `json:"id"`
|
||||
IP string `json:"ip"`
|
||||
Address string `json:"address"`
|
||||
Agent string `json:"agent"`
|
||||
Status string `json:"status"`
|
||||
Message string `json:"message"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
}
|
||||
|
||||
type CleanLog struct {
|
||||
LogType string `json:"logType" validate:"required,oneof=login operation"`
|
||||
}
|
220
core/app/dto/setting.go
Normal file
220
core/app/dto/setting.go
Normal file
@ -0,0 +1,220 @@
|
||||
package dto
|
||||
|
||||
import "time"
|
||||
|
||||
type SettingInfo struct {
|
||||
UserName string `json:"userName"`
|
||||
Email string `json:"email"`
|
||||
SystemIP string `json:"systemIP"`
|
||||
SystemVersion string `json:"systemVersion"`
|
||||
DockerSockPath string `json:"dockerSockPath"`
|
||||
DeveloperMode string `json:"developerMode"`
|
||||
|
||||
SessionTimeout string `json:"sessionTimeout"`
|
||||
LocalTime string `json:"localTime"`
|
||||
TimeZone string `json:"timeZone"`
|
||||
NtpSite string `json:"ntpSite"`
|
||||
|
||||
Port string `json:"port"`
|
||||
Ipv6 string `json:"ipv6"`
|
||||
BindAddress string `json:"bindAddress"`
|
||||
PanelName string `json:"panelName"`
|
||||
Theme string `json:"theme"`
|
||||
MenuTabs string `json:"menuTabs"`
|
||||
Language string `json:"language"`
|
||||
DefaultNetwork string `json:"defaultNetwork"`
|
||||
LastCleanTime string `json:"lastCleanTime"`
|
||||
LastCleanSize string `json:"lastCleanSize"`
|
||||
LastCleanData string `json:"lastCleanData"`
|
||||
|
||||
ServerPort string `json:"serverPort"`
|
||||
SSL string `json:"ssl"`
|
||||
SSLType string `json:"sslType"`
|
||||
BindDomain string `json:"bindDomain"`
|
||||
AllowIPs string `json:"allowIPs"`
|
||||
SecurityEntrance string `json:"securityEntrance"`
|
||||
ExpirationDays string `json:"expirationDays"`
|
||||
ExpirationTime string `json:"expirationTime"`
|
||||
ComplexityVerification string `json:"complexityVerification"`
|
||||
MFAStatus string `json:"mfaStatus"`
|
||||
MFASecret string `json:"mfaSecret"`
|
||||
MFAInterval string `json:"mfaInterval"`
|
||||
|
||||
MonitorStatus string `json:"monitorStatus"`
|
||||
MonitorInterval string `json:"monitorInterval"`
|
||||
MonitorStoreDays string `json:"monitorStoreDays"`
|
||||
|
||||
MessageType string `json:"messageType"`
|
||||
EmailVars string `json:"emailVars"`
|
||||
WeChatVars string `json:"weChatVars"`
|
||||
DingVars string `json:"dingVars"`
|
||||
|
||||
AppStoreVersion string `json:"appStoreVersion"`
|
||||
AppStoreLastModified string `json:"appStoreLastModified"`
|
||||
AppStoreSyncStatus string `json:"appStoreSyncStatus"`
|
||||
|
||||
FileRecycleBin string `json:"fileRecycleBin"`
|
||||
|
||||
SnapshotIgnore string `json:"snapshotIgnore"`
|
||||
XpackHideMenu string `json:"xpackHideMenu"`
|
||||
NoAuthSetting string `json:"noAuthSetting"`
|
||||
|
||||
ProxyUrl string `json:"proxyUrl"`
|
||||
ProxyType string `json:"proxyType"`
|
||||
ProxyPort string `json:"proxyPort"`
|
||||
ProxyUser string `json:"proxyUser"`
|
||||
ProxyPasswd string `json:"proxyPasswd"`
|
||||
ProxyPasswdKeep string `json:"proxyPasswdKeep"`
|
||||
}
|
||||
|
||||
type SettingUpdate struct {
|
||||
Key string `json:"key" validate:"required"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
type SSLUpdate struct {
|
||||
SSLType string `json:"sslType" validate:"required,oneof=self select import import-paste import-local"`
|
||||
Domain string `json:"domain"`
|
||||
SSL string `json:"ssl" validate:"required,oneof=enable disable"`
|
||||
Cert string `json:"cert"`
|
||||
Key string `json:"key"`
|
||||
SSLID uint `json:"sslID"`
|
||||
}
|
||||
type SSLInfo struct {
|
||||
Domain string `json:"domain"`
|
||||
Timeout string `json:"timeout"`
|
||||
RootPath string `json:"rootPath"`
|
||||
Cert string `json:"cert"`
|
||||
Key string `json:"key"`
|
||||
SSLID uint `json:"sslID"`
|
||||
}
|
||||
|
||||
type PasswordUpdate struct {
|
||||
OldPassword string `json:"oldPassword" validate:"required"`
|
||||
NewPassword string `json:"newPassword" validate:"required"`
|
||||
}
|
||||
|
||||
type PortUpdate struct {
|
||||
ServerPort uint `json:"serverPort" validate:"required,number,max=65535,min=1"`
|
||||
}
|
||||
|
||||
type SnapshotStatus struct {
|
||||
Panel string `json:"panel"`
|
||||
PanelInfo string `json:"panelInfo"`
|
||||
DaemonJson string `json:"daemonJson"`
|
||||
AppData string `json:"appData"`
|
||||
PanelData string `json:"panelData"`
|
||||
BackupData string `json:"backupData"`
|
||||
|
||||
Compress string `json:"compress"`
|
||||
Size string `json:"size"`
|
||||
Upload string `json:"upload"`
|
||||
}
|
||||
|
||||
type SnapshotCreate struct {
|
||||
ID uint `json:"id"`
|
||||
From string `json:"from" validate:"required"`
|
||||
DefaultDownload string `json:"defaultDownload" validate:"required"`
|
||||
Description string `json:"description" validate:"max=256"`
|
||||
Secret string `json:"secret"`
|
||||
}
|
||||
type SnapshotRecover struct {
|
||||
IsNew bool `json:"isNew"`
|
||||
ReDownload bool `json:"reDownload"`
|
||||
ID uint `json:"id" validate:"required"`
|
||||
Secret string `json:"secret"`
|
||||
}
|
||||
type SnapshotBatchDelete struct {
|
||||
DeleteWithFile bool `json:"deleteWithFile"`
|
||||
Ids []uint `json:"ids" validate:"required"`
|
||||
}
|
||||
type SnapshotImport struct {
|
||||
From string `json:"from"`
|
||||
Names []string `json:"names"`
|
||||
Description string `json:"description" validate:"max=256"`
|
||||
}
|
||||
type SnapshotInfo struct {
|
||||
ID uint `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description" validate:"max=256"`
|
||||
From string `json:"from"`
|
||||
DefaultDownload string `json:"defaultDownload"`
|
||||
Status string `json:"status"`
|
||||
Message string `json:"message"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
Version string `json:"version"`
|
||||
Size int64 `json:"size"`
|
||||
|
||||
InterruptStep string `json:"interruptStep"`
|
||||
RecoverStatus string `json:"recoverStatus"`
|
||||
RecoverMessage string `json:"recoverMessage"`
|
||||
LastRecoveredAt string `json:"lastRecoveredAt"`
|
||||
RollbackStatus string `json:"rollbackStatus"`
|
||||
RollbackMessage string `json:"rollbackMessage"`
|
||||
LastRollbackedAt string `json:"lastRollbackedAt"`
|
||||
}
|
||||
|
||||
type UpgradeInfo struct {
|
||||
TestVersion string `json:"testVersion"`
|
||||
NewVersion string `json:"newVersion"`
|
||||
LatestVersion string `json:"latestVersion"`
|
||||
ReleaseNote string `json:"releaseNote"`
|
||||
}
|
||||
|
||||
type SyncTime struct {
|
||||
NtpSite string `json:"ntpSite" validate:"required"`
|
||||
}
|
||||
|
||||
type BindInfo struct {
|
||||
Ipv6 string `json:"ipv6" validate:"required,oneof=enable disable"`
|
||||
BindAddress string `json:"bindAddress" validate:"required"`
|
||||
}
|
||||
|
||||
type Upgrade struct {
|
||||
Version string `json:"version" validate:"required"`
|
||||
}
|
||||
|
||||
type ProxyUpdate struct {
|
||||
ProxyUrl string `json:"proxyUrl"`
|
||||
ProxyType string `json:"proxyType"`
|
||||
ProxyPort string `json:"proxyPort"`
|
||||
ProxyUser string `json:"proxyUser"`
|
||||
ProxyPasswd string `json:"proxyPasswd"`
|
||||
ProxyPasswdKeep string `json:"proxyPasswdKeep"`
|
||||
}
|
||||
|
||||
type CleanData struct {
|
||||
SystemClean []CleanTree `json:"systemClean"`
|
||||
UploadClean []CleanTree `json:"uploadClean"`
|
||||
DownloadClean []CleanTree `json:"downloadClean"`
|
||||
SystemLogClean []CleanTree `json:"systemLogClean"`
|
||||
ContainerClean []CleanTree `json:"containerClean"`
|
||||
}
|
||||
|
||||
type CleanTree struct {
|
||||
ID string `json:"id"`
|
||||
Label string `json:"label"`
|
||||
Children []CleanTree `json:"children"`
|
||||
|
||||
Type string `json:"type"`
|
||||
Name string `json:"name"`
|
||||
|
||||
Size uint64 `json:"size"`
|
||||
IsCheck bool `json:"isCheck"`
|
||||
IsRecommend bool `json:"isRecommend"`
|
||||
}
|
||||
|
||||
type Clean struct {
|
||||
TreeType string `json:"treeType"`
|
||||
Name string `json:"name"`
|
||||
Size uint64 `json:"size"`
|
||||
}
|
||||
|
||||
type XpackHideMenu struct {
|
||||
ID string `json:"id"`
|
||||
Label string `json:"label"`
|
||||
IsCheck bool `json:"isCheck"`
|
||||
Title string `json:"title"`
|
||||
Path string `json:"path,omitempty"`
|
||||
Children []XpackHideMenu `json:"children,omitempty"`
|
||||
}
|
9
core/app/model/base.go
Normal file
9
core/app/model/base.go
Normal file
@ -0,0 +1,9 @@
|
||||
package model
|
||||
|
||||
import "time"
|
||||
|
||||
type BaseModel struct {
|
||||
ID uint `gorm:"primarykey;AUTO_INCREMENT" json:"id"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
31
core/app/model/logs.go
Normal file
31
core/app/model/logs.go
Normal file
@ -0,0 +1,31 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type OperationLog struct {
|
||||
BaseModel
|
||||
Source string `gorm:"type:varchar(64)" json:"source"`
|
||||
|
||||
IP string `gorm:"type:varchar(64)" json:"ip"`
|
||||
Path string `gorm:"type:varchar(64)" json:"path"`
|
||||
Method string `gorm:"type:varchar(64)" json:"method"`
|
||||
UserAgent string `gorm:"type:varchar(256)" json:"userAgent"`
|
||||
|
||||
Latency time.Duration `gorm:"type:varchar(64)" json:"latency"`
|
||||
Status string `gorm:"type:varchar(64)" json:"status"`
|
||||
Message string `gorm:"type:varchar(256)" json:"message"`
|
||||
|
||||
DetailZH string `gorm:"type:varchar(256)" json:"detailZH"`
|
||||
DetailEN string `gorm:"type:varchar(256)" json:"detailEN"`
|
||||
}
|
||||
|
||||
type LoginLog struct {
|
||||
BaseModel
|
||||
IP string `gorm:"type:varchar(64)" json:"ip"`
|
||||
Address string `gorm:"type:varchar(64)" json:"address"`
|
||||
Agent string `gorm:"type:varchar(256)" json:"agent"`
|
||||
Status string `gorm:"type:varchar(64)" json:"status"`
|
||||
Message string `gorm:"type:longText" json:"message"`
|
||||
}
|
8
core/app/model/setting.go
Normal file
8
core/app/model/setting.go
Normal file
@ -0,0 +1,8 @@
|
||||
package model
|
||||
|
||||
type Setting struct {
|
||||
BaseModel
|
||||
Key string `json:"key" gorm:"type:varchar(256);not null;"`
|
||||
Value string `json:"value" gorm:"type:varchar(256)"`
|
||||
About string `json:"about" gorm:"type:longText"`
|
||||
}
|
128
core/app/repo/common.go
Normal file
128
core/app/repo/common.go
Normal file
@ -0,0 +1,128 @@
|
||||
package repo
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/core/constant"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type DBOption func(*gorm.DB) *gorm.DB
|
||||
|
||||
type ICommonRepo interface {
|
||||
WithByID(id uint) DBOption
|
||||
WithByName(name string) DBOption
|
||||
WithByType(tp string) DBOption
|
||||
WithOrderBy(orderStr string) DBOption
|
||||
WithOrderRuleBy(orderBy, order string) DBOption
|
||||
WithByGroupID(groupID uint) DBOption
|
||||
WithLikeName(name string) DBOption
|
||||
WithIdsIn(ids []uint) DBOption
|
||||
WithByDate(startTime, endTime time.Time) DBOption
|
||||
WithByStartDate(startTime time.Time) DBOption
|
||||
WithByStatus(status string) DBOption
|
||||
WithByFrom(from string) DBOption
|
||||
}
|
||||
|
||||
type CommonRepo struct{}
|
||||
|
||||
func NewCommonRepo() ICommonRepo {
|
||||
return &CommonRepo{}
|
||||
}
|
||||
|
||||
func (c *CommonRepo) WithByID(id uint) DBOption {
|
||||
return func(g *gorm.DB) *gorm.DB {
|
||||
return g.Where("id = ?", id)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CommonRepo) WithByName(name string) DBOption {
|
||||
return func(g *gorm.DB) *gorm.DB {
|
||||
return g.Where("name = ?", name)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CommonRepo) WithByDate(startTime, endTime time.Time) DBOption {
|
||||
return func(g *gorm.DB) *gorm.DB {
|
||||
return g.Where("start_time > ? AND start_time < ?", startTime, endTime)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CommonRepo) WithByStartDate(startTime time.Time) DBOption {
|
||||
return func(g *gorm.DB) *gorm.DB {
|
||||
return g.Where("start_time < ?", startTime)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CommonRepo) WithByType(tp string) DBOption {
|
||||
return func(g *gorm.DB) *gorm.DB {
|
||||
return g.Where("type = ?", tp)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CommonRepo) WithByGroupID(groupID uint) DBOption {
|
||||
return func(g *gorm.DB) *gorm.DB {
|
||||
if groupID == 0 {
|
||||
return g
|
||||
}
|
||||
return g.Where("group_id = ?", groupID)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CommonRepo) WithByStatus(status string) DBOption {
|
||||
return func(g *gorm.DB) *gorm.DB {
|
||||
if len(status) == 0 {
|
||||
return g
|
||||
}
|
||||
return g.Where("status = ?", status)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CommonRepo) WithByFrom(from string) DBOption {
|
||||
return func(g *gorm.DB) *gorm.DB {
|
||||
return g.Where("`from` = ?", from)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CommonRepo) WithLikeName(name string) DBOption {
|
||||
return func(g *gorm.DB) *gorm.DB {
|
||||
if len(name) == 0 {
|
||||
return g
|
||||
}
|
||||
return g.Where("name like ?", "%"+name+"%")
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CommonRepo) WithOrderBy(orderStr string) DBOption {
|
||||
return func(g *gorm.DB) *gorm.DB {
|
||||
return g.Order(orderStr)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CommonRepo) WithOrderRuleBy(orderBy, order string) DBOption {
|
||||
switch order {
|
||||
case constant.OrderDesc:
|
||||
order = "desc"
|
||||
case constant.OrderAsc:
|
||||
order = "asc"
|
||||
default:
|
||||
orderBy = "created_at"
|
||||
order = "desc"
|
||||
}
|
||||
return func(g *gorm.DB) *gorm.DB {
|
||||
return g.Order(fmt.Sprintf("%s %s", orderBy, order))
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CommonRepo) WithIdsIn(ids []uint) DBOption {
|
||||
return func(g *gorm.DB) *gorm.DB {
|
||||
return g.Where("id in (?)", ids)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CommonRepo) WithIdsNotIn(ids []uint) DBOption {
|
||||
return func(g *gorm.DB) *gorm.DB {
|
||||
return g.Where("id not in (?)", ids)
|
||||
}
|
||||
}
|
101
core/app/repo/logs.go
Normal file
101
core/app/repo/logs.go
Normal file
@ -0,0 +1,101 @@
|
||||
package repo
|
||||
|
||||
import (
|
||||
"github.com/1Panel-dev/1Panel/core/app/model"
|
||||
"github.com/1Panel-dev/1Panel/core/global"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type LogRepo struct{}
|
||||
|
||||
type ILogRepo interface {
|
||||
CleanLogin() error
|
||||
CreateLoginLog(user *model.LoginLog) error
|
||||
PageLoginLog(limit, offset int, opts ...DBOption) (int64, []model.LoginLog, error)
|
||||
|
||||
WithByIP(ip string) DBOption
|
||||
WithByStatus(status string) DBOption
|
||||
WithByGroup(group string) DBOption
|
||||
WithByLikeOperation(operation string) DBOption
|
||||
CleanOperation() error
|
||||
CreateOperationLog(user *model.OperationLog) error
|
||||
PageOperationLog(limit, offset int, opts ...DBOption) (int64, []model.OperationLog, error)
|
||||
}
|
||||
|
||||
func NewILogRepo() ILogRepo {
|
||||
return &LogRepo{}
|
||||
}
|
||||
|
||||
func (u *LogRepo) CleanLogin() error {
|
||||
return global.DB.Exec("delete from login_logs;").Error
|
||||
}
|
||||
|
||||
func (u *LogRepo) CreateLoginLog(log *model.LoginLog) error {
|
||||
return global.DB.Create(log).Error
|
||||
}
|
||||
|
||||
func (u *LogRepo) PageLoginLog(page, size int, opts ...DBOption) (int64, []model.LoginLog, error) {
|
||||
var ops []model.LoginLog
|
||||
db := global.DB.Model(&model.LoginLog{})
|
||||
for _, opt := range opts {
|
||||
db = opt(db)
|
||||
}
|
||||
count := int64(0)
|
||||
db = db.Count(&count)
|
||||
err := db.Limit(size).Offset(size * (page - 1)).Find(&ops).Error
|
||||
return count, ops, err
|
||||
}
|
||||
|
||||
func (u *LogRepo) CleanOperation() error {
|
||||
return global.DB.Exec("delete from operation_logs").Error
|
||||
}
|
||||
|
||||
func (u *LogRepo) CreateOperationLog(log *model.OperationLog) error {
|
||||
return global.DB.Create(log).Error
|
||||
}
|
||||
|
||||
func (u *LogRepo) PageOperationLog(page, size int, opts ...DBOption) (int64, []model.OperationLog, error) {
|
||||
var ops []model.OperationLog
|
||||
db := global.DB.Model(&model.OperationLog{})
|
||||
for _, opt := range opts {
|
||||
db = opt(db)
|
||||
}
|
||||
count := int64(0)
|
||||
db = db.Count(&count)
|
||||
err := db.Limit(size).Offset(size * (page - 1)).Find(&ops).Error
|
||||
return count, ops, err
|
||||
}
|
||||
|
||||
func (c *LogRepo) WithByStatus(status string) DBOption {
|
||||
return func(g *gorm.DB) *gorm.DB {
|
||||
if len(status) == 0 {
|
||||
return g
|
||||
}
|
||||
return g.Where("status = ?", status)
|
||||
}
|
||||
}
|
||||
func (c *LogRepo) WithByGroup(group string) DBOption {
|
||||
return func(g *gorm.DB) *gorm.DB {
|
||||
if len(group) == 0 {
|
||||
return g
|
||||
}
|
||||
return g.Where("source = ?", group)
|
||||
}
|
||||
}
|
||||
func (c *LogRepo) WithByIP(ip string) DBOption {
|
||||
return func(g *gorm.DB) *gorm.DB {
|
||||
if len(ip) == 0 {
|
||||
return g
|
||||
}
|
||||
return g.Where("ip LIKE ?", "%"+ip+"%")
|
||||
}
|
||||
}
|
||||
func (c *LogRepo) WithByLikeOperation(operation string) DBOption {
|
||||
return func(g *gorm.DB) *gorm.DB {
|
||||
if len(operation) == 0 {
|
||||
return g
|
||||
}
|
||||
infoStr := "%" + operation + "%"
|
||||
return g.Where("detail_zh LIKE ? OR detail_en LIKE ?", infoStr, infoStr)
|
||||
}
|
||||
}
|
59
core/app/repo/setting.go
Normal file
59
core/app/repo/setting.go
Normal file
@ -0,0 +1,59 @@
|
||||
package repo
|
||||
|
||||
import (
|
||||
"github.com/1Panel-dev/1Panel/core/app/model"
|
||||
"github.com/1Panel-dev/1Panel/core/global"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type SettingRepo struct{}
|
||||
|
||||
type ISettingRepo interface {
|
||||
GetList(opts ...DBOption) ([]model.Setting, error)
|
||||
Get(opts ...DBOption) (model.Setting, error)
|
||||
Create(key, value string) error
|
||||
Update(key, value string) error
|
||||
WithByKey(key string) DBOption
|
||||
}
|
||||
|
||||
func NewISettingRepo() ISettingRepo {
|
||||
return &SettingRepo{}
|
||||
}
|
||||
|
||||
func (u *SettingRepo) GetList(opts ...DBOption) ([]model.Setting, error) {
|
||||
var settings []model.Setting
|
||||
db := global.DB.Model(&model.Setting{})
|
||||
for _, opt := range opts {
|
||||
db = opt(db)
|
||||
}
|
||||
err := db.Find(&settings).Error
|
||||
return settings, err
|
||||
}
|
||||
|
||||
func (u *SettingRepo) Create(key, value string) error {
|
||||
setting := &model.Setting{
|
||||
Key: key,
|
||||
Value: value,
|
||||
}
|
||||
return global.DB.Create(setting).Error
|
||||
}
|
||||
|
||||
func (u *SettingRepo) Get(opts ...DBOption) (model.Setting, error) {
|
||||
var settings model.Setting
|
||||
db := global.DB.Model(&model.Setting{})
|
||||
for _, opt := range opts {
|
||||
db = opt(db)
|
||||
}
|
||||
err := db.First(&settings).Error
|
||||
return settings, err
|
||||
}
|
||||
|
||||
func (c *SettingRepo) WithByKey(key string) DBOption {
|
||||
return func(g *gorm.DB) *gorm.DB {
|
||||
return g.Where("key = ?", key)
|
||||
}
|
||||
}
|
||||
|
||||
func (u *SettingRepo) Update(key, value string) error {
|
||||
return global.DB.Model(&model.Setting{}).Where("key = ?", key).Updates(map[string]interface{}{"value": value}).Error
|
||||
}
|
196
core/app/service/auth.go
Normal file
196
core/app/service/auth.go
Normal file
@ -0,0 +1,196 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"strconv"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/core/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/core/buserr"
|
||||
"github.com/1Panel-dev/1Panel/core/constant"
|
||||
"github.com/1Panel-dev/1Panel/core/global"
|
||||
"github.com/1Panel-dev/1Panel/core/utils/encrypt"
|
||||
"github.com/1Panel-dev/1Panel/core/utils/jwt"
|
||||
"github.com/1Panel-dev/1Panel/core/utils/mfa"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type AuthService struct{}
|
||||
|
||||
type IAuthService interface {
|
||||
CheckIsSafety(code string) (string, error)
|
||||
GetResponsePage() (string, error)
|
||||
VerifyCode(code string) (bool, error)
|
||||
Login(c *gin.Context, info dto.Login, entrance string) (*dto.UserLoginInfo, error)
|
||||
LogOut(c *gin.Context) error
|
||||
MFALogin(c *gin.Context, info dto.MFALogin, entrance string) (*dto.UserLoginInfo, error)
|
||||
}
|
||||
|
||||
func NewIAuthService() IAuthService {
|
||||
return &AuthService{}
|
||||
}
|
||||
|
||||
func (u *AuthService) Login(c *gin.Context, info dto.Login, entrance string) (*dto.UserLoginInfo, error) {
|
||||
nameSetting, err := settingRepo.Get(settingRepo.WithByKey("UserName"))
|
||||
if err != nil {
|
||||
return nil, errors.WithMessage(constant.ErrRecordNotFound, err.Error())
|
||||
}
|
||||
passwordSetting, err := settingRepo.Get(settingRepo.WithByKey("Password"))
|
||||
if err != nil {
|
||||
return nil, errors.WithMessage(constant.ErrRecordNotFound, err.Error())
|
||||
}
|
||||
pass, err := encrypt.StringDecrypt(passwordSetting.Value)
|
||||
if err != nil {
|
||||
return nil, constant.ErrAuth
|
||||
}
|
||||
if !hmac.Equal([]byte(info.Password), []byte(pass)) || nameSetting.Value != info.Name {
|
||||
return nil, constant.ErrAuth
|
||||
}
|
||||
entranceSetting, err := settingRepo.Get(settingRepo.WithByKey("SecurityEntrance"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(entranceSetting.Value) != 0 && entranceSetting.Value != entrance {
|
||||
return nil, buserr.New(constant.ErrEntrance)
|
||||
}
|
||||
mfa, err := settingRepo.Get(settingRepo.WithByKey("MFAStatus"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = settingRepo.Update("Language", info.Language); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if mfa.Value == "enable" {
|
||||
return &dto.UserLoginInfo{Name: nameSetting.Value, MfaStatus: mfa.Value}, nil
|
||||
}
|
||||
return u.generateSession(c, info.Name, info.AuthMethod)
|
||||
}
|
||||
|
||||
func (u *AuthService) MFALogin(c *gin.Context, info dto.MFALogin, entrance string) (*dto.UserLoginInfo, error) {
|
||||
nameSetting, err := settingRepo.Get(settingRepo.WithByKey("UserName"))
|
||||
if err != nil {
|
||||
return nil, errors.WithMessage(constant.ErrRecordNotFound, err.Error())
|
||||
}
|
||||
passwordSetting, err := settingRepo.Get(settingRepo.WithByKey("Password"))
|
||||
if err != nil {
|
||||
return nil, errors.WithMessage(constant.ErrRecordNotFound, err.Error())
|
||||
}
|
||||
pass, err := encrypt.StringDecrypt(passwordSetting.Value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !hmac.Equal([]byte(info.Password), []byte(pass)) || nameSetting.Value != info.Name {
|
||||
return nil, constant.ErrAuth
|
||||
}
|
||||
entranceSetting, err := settingRepo.Get(settingRepo.WithByKey("SecurityEntrance"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(entranceSetting.Value) != 0 && entranceSetting.Value != entrance {
|
||||
return nil, buserr.New(constant.ErrEntrance)
|
||||
}
|
||||
mfaSecret, err := settingRepo.Get(settingRepo.WithByKey("MFASecret"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mfaInterval, err := settingRepo.Get(settingRepo.WithByKey("MFAInterval"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
success := mfa.ValidCode(info.Code, mfaInterval.Value, mfaSecret.Value)
|
||||
if !success {
|
||||
return nil, constant.ErrAuth
|
||||
}
|
||||
|
||||
return u.generateSession(c, info.Name, info.AuthMethod)
|
||||
}
|
||||
|
||||
func (u *AuthService) generateSession(c *gin.Context, name, authMethod string) (*dto.UserLoginInfo, error) {
|
||||
setting, err := settingRepo.Get(settingRepo.WithByKey("SessionTimeout"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
httpsSetting, err := settingRepo.Get(settingRepo.WithByKey("SSL"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
lifeTime, err := strconv.Atoi(setting.Value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if authMethod == constant.AuthMethodJWT {
|
||||
j := jwt.NewJWT()
|
||||
claims := j.CreateClaims(jwt.BaseClaims{
|
||||
Name: name,
|
||||
})
|
||||
token, err := j.CreateToken(claims)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &dto.UserLoginInfo{Name: name, Token: token}, nil
|
||||
}
|
||||
sID, _ := c.Cookie(constant.SessionName)
|
||||
sessionUser, err := global.SESSION.Get(sID)
|
||||
if err != nil {
|
||||
sID = uuid.New().String()
|
||||
c.SetCookie(constant.SessionName, sID, 0, "", "", httpsSetting.Value == "enable", true)
|
||||
err := global.SESSION.Set(sID, sessionUser, lifeTime)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &dto.UserLoginInfo{Name: name}, nil
|
||||
}
|
||||
if err := global.SESSION.Set(sID, sessionUser, lifeTime); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &dto.UserLoginInfo{Name: name}, nil
|
||||
}
|
||||
|
||||
func (u *AuthService) LogOut(c *gin.Context) error {
|
||||
httpsSetting, err := settingRepo.Get(settingRepo.WithByKey("SSL"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sID, _ := c.Cookie(constant.SessionName)
|
||||
if sID != "" {
|
||||
c.SetCookie(constant.SessionName, sID, -1, "", "", httpsSetting.Value == "enable", true)
|
||||
err := global.SESSION.Delete(sID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *AuthService) VerifyCode(code string) (bool, error) {
|
||||
setting, err := settingRepo.Get(settingRepo.WithByKey("SecurityEntrance"))
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return hmac.Equal([]byte(setting.Value), []byte(code)), nil
|
||||
}
|
||||
|
||||
func (u *AuthService) CheckIsSafety(code string) (string, error) {
|
||||
status, err := settingRepo.Get(settingRepo.WithByKey("SecurityEntrance"))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if len(status.Value) == 0 {
|
||||
return "disable", nil
|
||||
}
|
||||
if status.Value == code {
|
||||
return "pass", nil
|
||||
}
|
||||
return "unpass", nil
|
||||
}
|
||||
|
||||
func (u *AuthService) GetResponsePage() (string, error) {
|
||||
pageCode, err := settingRepo.Get(settingRepo.WithByKey("NoAuthSetting"))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return pageCode.Value, nil
|
||||
}
|
9
core/app/service/entry.go
Normal file
9
core/app/service/entry.go
Normal file
@ -0,0 +1,9 @@
|
||||
package service
|
||||
|
||||
import "github.com/1Panel-dev/1Panel/core/app/repo"
|
||||
|
||||
var (
|
||||
commonRepo = repo.NewCommonRepo()
|
||||
settingRepo = repo.NewISettingRepo()
|
||||
logRepo = repo.NewILogRepo()
|
||||
)
|
128
core/app/service/logs.go
Normal file
128
core/app/service/logs.go
Normal file
@ -0,0 +1,128 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/core/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/core/app/model"
|
||||
"github.com/1Panel-dev/1Panel/core/constant"
|
||||
"github.com/1Panel-dev/1Panel/core/global"
|
||||
"github.com/1Panel-dev/1Panel/core/utils/cmd"
|
||||
"github.com/jinzhu/copier"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type LogService struct{}
|
||||
|
||||
const logs = "https://resource.fit2cloud.com/installation-log.sh"
|
||||
|
||||
type ILogService interface {
|
||||
ListSystemLogFile() ([]string, error)
|
||||
CreateLoginLog(operation model.LoginLog) error
|
||||
PageLoginLog(search dto.SearchLgLogWithPage) (int64, interface{}, error)
|
||||
|
||||
CreateOperationLog(operation model.OperationLog) error
|
||||
PageOperationLog(search dto.SearchOpLogWithPage) (int64, interface{}, error)
|
||||
|
||||
CleanLogs(logtype string) error
|
||||
}
|
||||
|
||||
func NewILogService() ILogService {
|
||||
return &LogService{}
|
||||
}
|
||||
|
||||
func (u *LogService) CreateLoginLog(operation model.LoginLog) error {
|
||||
return logRepo.CreateLoginLog(&operation)
|
||||
}
|
||||
|
||||
func (u *LogService) ListSystemLogFile() ([]string, error) {
|
||||
logDir := path.Join(global.CONF.System.BaseDir, "1panel/log")
|
||||
var files []string
|
||||
if err := filepath.Walk(logDir, func(pathItem string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !info.IsDir() && strings.HasPrefix(info.Name(), "1Panel") {
|
||||
if info.Name() == "1Panel.log" {
|
||||
files = append(files, time.Now().Format("2006-01-02"))
|
||||
return nil
|
||||
}
|
||||
itemFileName := strings.TrimPrefix(info.Name(), "1Panel-")
|
||||
itemFileName = strings.TrimSuffix(itemFileName, ".gz")
|
||||
itemFileName = strings.TrimSuffix(itemFileName, ".log")
|
||||
files = append(files, itemFileName)
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(files) < 2 {
|
||||
return files, nil
|
||||
}
|
||||
sort.Slice(files, func(i, j int) bool {
|
||||
return files[i] > files[j]
|
||||
})
|
||||
|
||||
return files, nil
|
||||
}
|
||||
|
||||
func (u *LogService) PageLoginLog(req dto.SearchLgLogWithPage) (int64, interface{}, error) {
|
||||
total, ops, err := logRepo.PageLoginLog(
|
||||
req.Page,
|
||||
req.PageSize,
|
||||
logRepo.WithByIP(req.IP),
|
||||
logRepo.WithByStatus(req.Status),
|
||||
commonRepo.WithOrderBy("created_at desc"),
|
||||
)
|
||||
var dtoOps []dto.LoginLog
|
||||
for _, op := range ops {
|
||||
var item dto.LoginLog
|
||||
if err := copier.Copy(&item, &op); err != nil {
|
||||
return 0, nil, errors.WithMessage(constant.ErrStructTransform, err.Error())
|
||||
}
|
||||
dtoOps = append(dtoOps, item)
|
||||
}
|
||||
return total, dtoOps, err
|
||||
}
|
||||
|
||||
func (u *LogService) CreateOperationLog(operation model.OperationLog) error {
|
||||
return logRepo.CreateOperationLog(&operation)
|
||||
}
|
||||
|
||||
func (u *LogService) PageOperationLog(req dto.SearchOpLogWithPage) (int64, interface{}, error) {
|
||||
total, ops, err := logRepo.PageOperationLog(
|
||||
req.Page,
|
||||
req.PageSize,
|
||||
logRepo.WithByGroup(req.Source),
|
||||
logRepo.WithByLikeOperation(req.Operation),
|
||||
logRepo.WithByStatus(req.Status),
|
||||
commonRepo.WithOrderBy("created_at desc"),
|
||||
)
|
||||
var dtoOps []dto.OperationLog
|
||||
for _, op := range ops {
|
||||
var item dto.OperationLog
|
||||
if err := copier.Copy(&item, &op); err != nil {
|
||||
return 0, nil, errors.WithMessage(constant.ErrStructTransform, err.Error())
|
||||
}
|
||||
dtoOps = append(dtoOps, item)
|
||||
}
|
||||
return total, dtoOps, err
|
||||
}
|
||||
|
||||
func (u *LogService) CleanLogs(logtype string) error {
|
||||
if logtype == "operation" {
|
||||
return logRepo.CleanOperation()
|
||||
}
|
||||
return logRepo.CleanLogin()
|
||||
}
|
||||
|
||||
func writeLogs(version string) {
|
||||
_, _ = cmd.Execf("curl -sfL %s | sh -s 1p upgrade %s", logs, version)
|
||||
}
|
403
core/app/service/setting.go
Normal file
403
core/app/service/setting.go
Normal file
@ -0,0 +1,403 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/core/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/core/buserr"
|
||||
"github.com/1Panel-dev/1Panel/core/constant"
|
||||
"github.com/1Panel-dev/1Panel/core/global"
|
||||
"github.com/1Panel-dev/1Panel/core/utils/cmd"
|
||||
"github.com/1Panel-dev/1Panel/core/utils/common"
|
||||
"github.com/1Panel-dev/1Panel/core/utils/encrypt"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type SettingService struct{}
|
||||
|
||||
type ISettingService interface {
|
||||
GetSettingInfo() (*dto.SettingInfo, error)
|
||||
LoadInterfaceAddr() ([]string, error)
|
||||
Update(key, value string) error
|
||||
UpdateProxy(req dto.ProxyUpdate) error
|
||||
UpdatePassword(c *gin.Context, old, new string) error
|
||||
UpdatePort(port uint) error
|
||||
UpdateBindInfo(req dto.BindInfo) error
|
||||
UpdateSSL(c *gin.Context, req dto.SSLUpdate) error
|
||||
LoadFromCert() (*dto.SSLInfo, error)
|
||||
HandlePasswordExpired(c *gin.Context, old, new string) error
|
||||
}
|
||||
|
||||
func NewISettingService() ISettingService {
|
||||
return &SettingService{}
|
||||
}
|
||||
|
||||
func (u *SettingService) GetSettingInfo() (*dto.SettingInfo, error) {
|
||||
setting, err := settingRepo.GetList()
|
||||
if err != nil {
|
||||
return nil, constant.ErrRecordNotFound
|
||||
}
|
||||
settingMap := make(map[string]string)
|
||||
for _, set := range setting {
|
||||
settingMap[set.Key] = set.Value
|
||||
}
|
||||
var info dto.SettingInfo
|
||||
arr, err := json.Marshal(settingMap)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := json.Unmarshal(arr, &info); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if info.ProxyPasswdKeep != constant.StatusEnable {
|
||||
info.ProxyPasswd = ""
|
||||
} else {
|
||||
info.ProxyPasswd, _ = encrypt.StringDecrypt(info.ProxyPasswd)
|
||||
}
|
||||
|
||||
info.LocalTime = time.Now().Format("2006-01-02 15:04:05 MST -0700")
|
||||
return &info, err
|
||||
}
|
||||
|
||||
func (u *SettingService) Update(key, value string) error {
|
||||
switch key {
|
||||
case "AppStoreLastModified":
|
||||
exist, _ := settingRepo.Get(settingRepo.WithByKey("AppStoreLastModified"))
|
||||
if exist.ID == 0 {
|
||||
_ = settingRepo.Create("AppStoreLastModified", value)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
if err := settingRepo.Update(key, value); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch key {
|
||||
case "ExpirationDays":
|
||||
timeout, err := strconv.Atoi(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := settingRepo.Update("ExpirationTime", time.Now().AddDate(0, 0, timeout).Format(constant.DateTimeLayout)); err != nil {
|
||||
return err
|
||||
}
|
||||
case "BindDomain":
|
||||
if len(value) != 0 {
|
||||
_ = global.SESSION.Clean()
|
||||
}
|
||||
case "UserName", "Password":
|
||||
_ = global.SESSION.Clean()
|
||||
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *SettingService) LoadInterfaceAddr() ([]string, error) {
|
||||
addrMap := make(map[string]struct{})
|
||||
addrs, err := net.InterfaceAddrs()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, addr := range addrs {
|
||||
ipNet, ok := addr.(*net.IPNet)
|
||||
if ok && ipNet.IP.To16() != nil {
|
||||
addrMap[ipNet.IP.String()] = struct{}{}
|
||||
}
|
||||
}
|
||||
var data []string
|
||||
for key := range addrMap {
|
||||
data = append(data, key)
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (u *SettingService) UpdateBindInfo(req dto.BindInfo) error {
|
||||
if err := settingRepo.Update("Ipv6", req.Ipv6); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := settingRepo.Update("BindAddress", req.BindAddress); err != nil {
|
||||
return err
|
||||
}
|
||||
go func() {
|
||||
time.Sleep(1 * time.Second)
|
||||
_, err := cmd.Exec("systemctl restart 1panel.service")
|
||||
if err != nil {
|
||||
global.LOG.Errorf("restart system with new bind info failed, err: %v", err)
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *SettingService) UpdateProxy(req dto.ProxyUpdate) error {
|
||||
if err := settingRepo.Update("ProxyUrl", req.ProxyUrl); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := settingRepo.Update("ProxyType", req.ProxyType); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := settingRepo.Update("ProxyPort", req.ProxyPort); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := settingRepo.Update("ProxyUser", req.ProxyUser); err != nil {
|
||||
return err
|
||||
}
|
||||
pass, _ := encrypt.StringEncrypt(req.ProxyPasswd)
|
||||
if err := settingRepo.Update("ProxyPasswd", pass); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := settingRepo.Update("ProxyPasswdKeep", req.ProxyPasswdKeep); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *SettingService) UpdatePort(port uint) error {
|
||||
if common.ScanPort(int(port)) {
|
||||
return buserr.WithDetail(constant.ErrPortInUsed, port, nil)
|
||||
}
|
||||
// TODO 修改防火墙端口
|
||||
|
||||
if err := settingRepo.Update("ServerPort", strconv.Itoa(int(port))); err != nil {
|
||||
return err
|
||||
}
|
||||
go func() {
|
||||
time.Sleep(1 * time.Second)
|
||||
_, err := cmd.Exec("systemctl restart 1panel.service")
|
||||
if err != nil {
|
||||
global.LOG.Errorf("restart system port failed, err: %v", err)
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *SettingService) UpdateSSL(c *gin.Context, req dto.SSLUpdate) error {
|
||||
secretDir := path.Join(global.CONF.System.BaseDir, "1panel/secret")
|
||||
if req.SSL == "disable" {
|
||||
if err := settingRepo.Update("SSL", "disable"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := settingRepo.Update("SSLType", "self"); err != nil {
|
||||
return err
|
||||
}
|
||||
_ = os.Remove(path.Join(secretDir, "server.crt"))
|
||||
_ = os.Remove(path.Join(secretDir, "server.key"))
|
||||
sID, _ := c.Cookie(constant.SessionName)
|
||||
c.SetCookie(constant.SessionName, sID, 0, "", "", false, true)
|
||||
|
||||
go func() {
|
||||
_, err := cmd.Exec("systemctl restart 1panel.service")
|
||||
if err != nil {
|
||||
global.LOG.Errorf("restart system failed, err: %v", err)
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
if _, err := os.Stat(secretDir); err != nil && os.IsNotExist(err) {
|
||||
if err = os.MkdirAll(secretDir, os.ModePerm); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := settingRepo.Update("SSLType", req.SSLType); err != nil {
|
||||
return err
|
||||
}
|
||||
var (
|
||||
secret string
|
||||
key string
|
||||
)
|
||||
|
||||
switch req.SSLType {
|
||||
case "import-paste":
|
||||
secret = req.Cert
|
||||
key = req.Key
|
||||
case "import-local":
|
||||
keyFile, err := os.ReadFile(req.Key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
key = string(keyFile)
|
||||
certFile, err := os.ReadFile(req.Cert)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
secret = string(certFile)
|
||||
}
|
||||
|
||||
if err := os.WriteFile(path.Join(secretDir, "server.crt.tmp"), []byte(secret), 0600); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.WriteFile(path.Join(secretDir, "server.key.tmp"), []byte(key), 0600); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := checkCertValid(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.Rename(path.Join(secretDir, "server.crt.tmp"), path.Join(secretDir, "server.crt")); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.Rename(path.Join(secretDir, "server.key.tmp"), path.Join(secretDir, "server.key")); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := settingRepo.Update("SSL", req.SSL); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sID, _ := c.Cookie(constant.SessionName)
|
||||
c.SetCookie(constant.SessionName, sID, 0, "", "", true, true)
|
||||
go func() {
|
||||
time.Sleep(1 * time.Second)
|
||||
_, err := cmd.Exec("systemctl restart 1panel.service")
|
||||
if err != nil {
|
||||
global.LOG.Errorf("restart system failed, err: %v", err)
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *SettingService) LoadFromCert() (*dto.SSLInfo, error) {
|
||||
ssl, err := settingRepo.Get(settingRepo.WithByKey("SSL"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if ssl.Value == "disable" {
|
||||
return &dto.SSLInfo{}, nil
|
||||
}
|
||||
sslType, err := settingRepo.Get(settingRepo.WithByKey("SSLType"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var data dto.SSLInfo
|
||||
switch sslType.Value {
|
||||
case "self":
|
||||
data, err = loadInfoFromCert()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case "import":
|
||||
data, err = loadInfoFromCert()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err := os.Stat(path.Join(global.CONF.System.BaseDir, "1panel/secret/server.crt")); err != nil {
|
||||
return nil, fmt.Errorf("load server.crt file failed, err: %v", err)
|
||||
}
|
||||
certFile, _ := os.ReadFile(path.Join(global.CONF.System.BaseDir, "1panel/secret/server.crt"))
|
||||
data.Cert = string(certFile)
|
||||
|
||||
if _, err := os.Stat(path.Join(global.CONF.System.BaseDir, "1panel/secret/server.key")); err != nil {
|
||||
return nil, fmt.Errorf("load server.key file failed, err: %v", err)
|
||||
}
|
||||
keyFile, _ := os.ReadFile(path.Join(global.CONF.System.BaseDir, "1panel/secret/server.key"))
|
||||
data.Key = string(keyFile)
|
||||
case "select":
|
||||
// TODO select ssl from website
|
||||
}
|
||||
return &data, nil
|
||||
}
|
||||
|
||||
func (u *SettingService) HandlePasswordExpired(c *gin.Context, old, new string) error {
|
||||
setting, err := settingRepo.Get(settingRepo.WithByKey("Password"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
passwordFromDB, err := encrypt.StringDecrypt(setting.Value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if passwordFromDB == old {
|
||||
newPassword, err := encrypt.StringEncrypt(new)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := settingRepo.Update("Password", newPassword); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
expiredSetting, err := settingRepo.Get(settingRepo.WithByKey("ExpirationDays"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
timeout, _ := strconv.Atoi(expiredSetting.Value)
|
||||
if err := settingRepo.Update("ExpirationTime", time.Now().AddDate(0, 0, timeout).Format(constant.DateTimeLayout)); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return constant.ErrInitialPassword
|
||||
}
|
||||
|
||||
func (u *SettingService) UpdatePassword(c *gin.Context, old, new string) error {
|
||||
if err := u.HandlePasswordExpired(c, old, new); err != nil {
|
||||
return err
|
||||
}
|
||||
_ = global.SESSION.Clean()
|
||||
return nil
|
||||
}
|
||||
|
||||
func loadInfoFromCert() (dto.SSLInfo, error) {
|
||||
var info dto.SSLInfo
|
||||
certFile := path.Join(global.CONF.System.BaseDir, "1panel/secret/server.crt")
|
||||
if _, err := os.Stat(certFile); err != nil {
|
||||
return info, err
|
||||
}
|
||||
certData, err := os.ReadFile(certFile)
|
||||
if err != nil {
|
||||
return info, err
|
||||
}
|
||||
certBlock, _ := pem.Decode(certData)
|
||||
if certBlock == nil {
|
||||
return info, err
|
||||
}
|
||||
certObj, err := x509.ParseCertificate(certBlock.Bytes)
|
||||
if err != nil {
|
||||
return info, err
|
||||
}
|
||||
var domains []string
|
||||
if len(certObj.IPAddresses) != 0 {
|
||||
for _, ip := range certObj.IPAddresses {
|
||||
domains = append(domains, ip.String())
|
||||
}
|
||||
}
|
||||
if len(certObj.DNSNames) != 0 {
|
||||
domains = append(domains, certObj.DNSNames...)
|
||||
}
|
||||
return dto.SSLInfo{
|
||||
Domain: strings.Join(domains, ","),
|
||||
Timeout: certObj.NotAfter.Format(constant.DateTimeLayout),
|
||||
RootPath: path.Join(global.CONF.System.BaseDir, "1panel/secret/server.crt"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func checkCertValid() error {
|
||||
certificate, err := os.ReadFile(path.Join(global.CONF.System.BaseDir, "1panel/secret/server.crt.tmp"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
key, err := os.ReadFile(path.Join(global.CONF.System.BaseDir, "1panel/secret/server.key.tmp"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err = tls.X509KeyPair(certificate, key); err != nil {
|
||||
return err
|
||||
}
|
||||
certBlock, _ := pem.Decode(certificate)
|
||||
if certBlock == nil {
|
||||
return err
|
||||
}
|
||||
if _, err := x509.ParseCertificate(certBlock.Bytes); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
352
core/app/service/upgrade.go
Normal file
352
core/app/service/upgrade.go
Normal file
@ -0,0 +1,352 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/core/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/core/constant"
|
||||
"github.com/1Panel-dev/1Panel/core/global"
|
||||
"github.com/1Panel-dev/1Panel/core/utils/cmd"
|
||||
"github.com/1Panel-dev/1Panel/core/utils/common"
|
||||
"github.com/1Panel-dev/1Panel/core/utils/files"
|
||||
httpUtil "github.com/1Panel-dev/1Panel/core/utils/http"
|
||||
)
|
||||
|
||||
type UpgradeService struct{}
|
||||
|
||||
type IUpgradeService interface {
|
||||
Upgrade(req dto.Upgrade) error
|
||||
LoadNotes(req dto.Upgrade) (string, error)
|
||||
SearchUpgrade() (*dto.UpgradeInfo, error)
|
||||
}
|
||||
|
||||
func NewIUpgradeService() IUpgradeService {
|
||||
return &UpgradeService{}
|
||||
}
|
||||
|
||||
func (u *UpgradeService) SearchUpgrade() (*dto.UpgradeInfo, error) {
|
||||
var upgrade dto.UpgradeInfo
|
||||
currentVersion, err := settingRepo.Get(settingRepo.WithByKey("SystemVersion"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
DeveloperMode, err := settingRepo.Get(settingRepo.WithByKey("DeveloperMode"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
upgrade.TestVersion, upgrade.NewVersion, upgrade.LatestVersion = u.loadVersionByMode(DeveloperMode.Value, currentVersion.Value)
|
||||
var itemVersion string
|
||||
if len(upgrade.LatestVersion) != 0 {
|
||||
itemVersion = upgrade.LatestVersion
|
||||
}
|
||||
if len(upgrade.NewVersion) != 0 {
|
||||
itemVersion = upgrade.NewVersion
|
||||
}
|
||||
if (global.CONF.System.Mode == "dev" || DeveloperMode.Value == "enable") && len(upgrade.TestVersion) != 0 {
|
||||
itemVersion = upgrade.TestVersion
|
||||
}
|
||||
if len(itemVersion) == 0 {
|
||||
return &upgrade, nil
|
||||
}
|
||||
mode := global.CONF.System.Mode
|
||||
if strings.Contains(itemVersion, "beta") {
|
||||
mode = "beta"
|
||||
}
|
||||
notes, err := u.loadReleaseNotes(fmt.Sprintf("%s/%s/%s/release/1panel-%s-release-notes", global.CONF.System.RepoUrl, mode, itemVersion, itemVersion))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load releases-notes of version %s failed, err: %v", itemVersion, err)
|
||||
}
|
||||
upgrade.ReleaseNote = notes
|
||||
return &upgrade, nil
|
||||
}
|
||||
|
||||
func (u *UpgradeService) LoadNotes(req dto.Upgrade) (string, error) {
|
||||
mode := global.CONF.System.Mode
|
||||
if strings.Contains(req.Version, "beta") {
|
||||
mode = "beta"
|
||||
}
|
||||
notes, err := u.loadReleaseNotes(fmt.Sprintf("%s/%s/%s/release/1panel-%s-release-notes", global.CONF.System.RepoUrl, mode, req.Version, req.Version))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("load releases-notes of version %s failed, err: %v", req.Version, err)
|
||||
}
|
||||
return notes, nil
|
||||
}
|
||||
|
||||
func (u *UpgradeService) Upgrade(req dto.Upgrade) error {
|
||||
global.LOG.Info("start to upgrade now...")
|
||||
timeStr := time.Now().Format(constant.DateTimeSlimLayout)
|
||||
rootDir := path.Join(global.CONF.System.TmpDir, fmt.Sprintf("upgrade/upgrade_%s/downloads", timeStr))
|
||||
originalDir := path.Join(global.CONF.System.TmpDir, fmt.Sprintf("upgrade/upgrade_%s/original", timeStr))
|
||||
if err := os.MkdirAll(rootDir, os.ModePerm); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.MkdirAll(originalDir, os.ModePerm); err != nil {
|
||||
return err
|
||||
}
|
||||
itemArch, err := loadArch()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
mode := global.CONF.System.Mode
|
||||
if strings.Contains(req.Version, "beta") {
|
||||
mode = "beta"
|
||||
}
|
||||
downloadPath := fmt.Sprintf("%s/%s/%s/release", global.CONF.System.RepoUrl, mode, req.Version)
|
||||
fileName := fmt.Sprintf("1panel-%s-%s-%s.tar.gz", req.Version, "linux", itemArch)
|
||||
_ = settingRepo.Update("SystemStatus", "Upgrading")
|
||||
go func() {
|
||||
if err := files.DownloadFileWithProxy(downloadPath+"/"+fileName, rootDir+"/"+fileName); err != nil {
|
||||
global.LOG.Errorf("download service file failed, err: %v", err)
|
||||
_ = settingRepo.Update("SystemStatus", "Free")
|
||||
return
|
||||
}
|
||||
global.LOG.Info("download all file successful!")
|
||||
defer func() {
|
||||
_ = os.Remove(rootDir)
|
||||
}()
|
||||
if err := files.HandleUnTar(rootDir+"/"+fileName, rootDir, ""); err != nil {
|
||||
global.LOG.Errorf("decompress file failed, err: %v", err)
|
||||
_ = settingRepo.Update("SystemStatus", "Free")
|
||||
return
|
||||
}
|
||||
tmpDir := rootDir + "/" + strings.ReplaceAll(fileName, ".tar.gz", "")
|
||||
|
||||
if err := u.handleBackup(originalDir); err != nil {
|
||||
global.LOG.Errorf("handle backup original file failed, err: %v", err)
|
||||
_ = settingRepo.Update("SystemStatus", "Free")
|
||||
return
|
||||
}
|
||||
global.LOG.Info("backup original data successful, now start to upgrade!")
|
||||
|
||||
if err := files.CopyFile(path.Join(tmpDir, "1panel"), "/usr/local/bin"); err != nil {
|
||||
global.LOG.Errorf("upgrade 1panel failed, err: %v", err)
|
||||
u.handleRollback(originalDir, 1)
|
||||
return
|
||||
}
|
||||
|
||||
if err := files.CopyFile(path.Join(tmpDir, "1pctl"), "/usr/local/bin"); err != nil {
|
||||
global.LOG.Errorf("upgrade 1pctl failed, err: %v", err)
|
||||
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(originalDir, 2)
|
||||
return
|
||||
}
|
||||
|
||||
if err := files.CopyFile(path.Join(tmpDir, "1panel.service"), "/etc/systemd/system"); err != nil {
|
||||
global.LOG.Errorf("upgrade 1panel.service failed, err: %v", err)
|
||||
u.handleRollback(originalDir, 3)
|
||||
return
|
||||
}
|
||||
|
||||
global.LOG.Info("upgrade successful!")
|
||||
go writeLogs(req.Version)
|
||||
_ = settingRepo.Update("SystemVersion", req.Version)
|
||||
_ = settingRepo.Update("SystemStatus", "Free")
|
||||
checkPointOfWal()
|
||||
_, _ = cmd.ExecWithTimeOut("systemctl daemon-reload && systemctl restart 1panel.service", 1*time.Minute)
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *UpgradeService) handleBackup(originalDir string) error {
|
||||
if err := files.CopyFile("/usr/local/bin/1panel", originalDir); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := files.CopyFile("/usr/local/bin/1pctl", originalDir); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := files.CopyFile("/etc/systemd/system/1panel.service", originalDir); err != nil {
|
||||
return err
|
||||
}
|
||||
checkPointOfWal()
|
||||
if err := files.HandleTar(path.Join(global.CONF.System.BaseDir, "1panel/db"), originalDir, "db.tar.gz", "db/1Panel.db-*", ""); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *UpgradeService) handleRollback(originalDir string, errStep int) {
|
||||
_ = settingRepo.Update("SystemStatus", "Free")
|
||||
|
||||
checkPointOfWal()
|
||||
if _, err := os.Stat(path.Join(originalDir, "1Panel.db")); err == nil {
|
||||
if err := files.CopyFile(path.Join(originalDir, "1Panel.db"), global.CONF.System.DbPath); err != nil {
|
||||
global.LOG.Errorf("rollback 1panel db failed, err: %v", err)
|
||||
}
|
||||
}
|
||||
if _, err := os.Stat(path.Join(originalDir, "db.tar.gz")); err == nil {
|
||||
if err := files.HandleUnTar(path.Join(originalDir, "db.tar.gz"), global.CONF.System.DbPath, ""); err != nil {
|
||||
global.LOG.Errorf("rollback 1panel db failed, err: %v", err)
|
||||
}
|
||||
}
|
||||
if err := files.CopyFile(path.Join(originalDir, "1panel"), "/usr/local/bin"); err != nil {
|
||||
global.LOG.Errorf("rollback 1pctl failed, err: %v", err)
|
||||
}
|
||||
if errStep == 1 {
|
||||
return
|
||||
}
|
||||
if err := files.CopyFile(path.Join(originalDir, "1pctl"), "/usr/local/bin"); err != nil {
|
||||
global.LOG.Errorf("rollback 1panel failed, err: %v", err)
|
||||
}
|
||||
if errStep == 2 {
|
||||
return
|
||||
}
|
||||
if err := files.CopyFile(path.Join(originalDir, "1panel.service"), "/etc/systemd/system"); err != nil {
|
||||
global.LOG.Errorf("rollback 1panel failed, err: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (u *UpgradeService) loadVersionByMode(developer, currentVersion string) (string, string, string) {
|
||||
var current, latest string
|
||||
if global.CONF.System.Mode == "dev" {
|
||||
betaVersionLatest := u.loadVersion(true, currentVersion, "beta")
|
||||
devVersionLatest := u.loadVersion(true, currentVersion, "dev")
|
||||
if common.ComparePanelVersion(betaVersionLatest, devVersionLatest) {
|
||||
return betaVersionLatest, "", ""
|
||||
}
|
||||
return devVersionLatest, "", ""
|
||||
}
|
||||
|
||||
betaVersionLatest := ""
|
||||
latest = u.loadVersion(true, currentVersion, "stable")
|
||||
current = u.loadVersion(false, currentVersion, "stable")
|
||||
if developer == "enable" {
|
||||
betaVersionLatest = u.loadVersion(true, currentVersion, "beta")
|
||||
}
|
||||
if current != latest {
|
||||
return betaVersionLatest, current, latest
|
||||
}
|
||||
|
||||
versionPart := strings.Split(current, ".")
|
||||
if len(versionPart) < 3 {
|
||||
return betaVersionLatest, current, latest
|
||||
}
|
||||
num, _ := strconv.Atoi(versionPart[1])
|
||||
if num == 0 {
|
||||
return betaVersionLatest, current, latest
|
||||
}
|
||||
if num >= 10 {
|
||||
if current[:6] == currentVersion[:6] {
|
||||
return betaVersionLatest, current, ""
|
||||
}
|
||||
return betaVersionLatest, "", latest
|
||||
}
|
||||
if current[:5] == currentVersion[:5] {
|
||||
return betaVersionLatest, current, ""
|
||||
}
|
||||
return betaVersionLatest, "", latest
|
||||
}
|
||||
|
||||
func (u *UpgradeService) loadVersion(isLatest bool, currentVersion, mode string) string {
|
||||
path := fmt.Sprintf("%s/%s/latest", global.CONF.System.RepoUrl, mode)
|
||||
if !isLatest {
|
||||
path = fmt.Sprintf("%s/%s/latest.current", global.CONF.System.RepoUrl, mode)
|
||||
}
|
||||
_, latestVersionRes, err := httpUtil.HandleGet(path, http.MethodGet, constant.TimeOut20s)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("load latest version from oss failed, err: %v", err)
|
||||
return ""
|
||||
}
|
||||
version := string(latestVersionRes)
|
||||
if strings.Contains(version, "<") {
|
||||
global.LOG.Errorf("load latest version from oss failed, err: %v", version)
|
||||
return ""
|
||||
}
|
||||
if isLatest {
|
||||
return u.checkVersion(version, currentVersion)
|
||||
}
|
||||
|
||||
versionMap := make(map[string]string)
|
||||
if err := json.Unmarshal(latestVersionRes, &versionMap); err != nil {
|
||||
global.LOG.Errorf("load latest version from oss failed (error unmarshal), err: %v", err)
|
||||
return ""
|
||||
}
|
||||
|
||||
versionPart := strings.Split(currentVersion, ".")
|
||||
if len(versionPart) < 3 {
|
||||
global.LOG.Errorf("current version is error format: %s", currentVersion)
|
||||
return ""
|
||||
}
|
||||
num, _ := strconv.Atoi(versionPart[1])
|
||||
if num == 0 {
|
||||
global.LOG.Errorf("current version is error format: %s", currentVersion)
|
||||
return ""
|
||||
}
|
||||
if num >= 10 {
|
||||
if version, ok := versionMap[currentVersion[0:5]]; ok {
|
||||
return u.checkVersion(version, currentVersion)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
if version, ok := versionMap[currentVersion[0:4]]; ok {
|
||||
return u.checkVersion(version, currentVersion)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (u *UpgradeService) checkVersion(v2, v1 string) string {
|
||||
addSuffix := false
|
||||
if !strings.Contains(v1, "-") {
|
||||
v1 = v1 + "-lts"
|
||||
}
|
||||
if !strings.Contains(v2, "-") {
|
||||
addSuffix = true
|
||||
v2 = v2 + "-lts"
|
||||
}
|
||||
if common.ComparePanelVersion(v2, v1) {
|
||||
if addSuffix {
|
||||
return strings.TrimSuffix(v2, "-lts")
|
||||
}
|
||||
return v2
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (u *UpgradeService) loadReleaseNotes(path string) (string, error) {
|
||||
_, releaseNotes, err := httpUtil.HandleGet(path, http.MethodGet, constant.TimeOut20s)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(releaseNotes), nil
|
||||
}
|
||||
|
||||
func loadArch() (string, error) {
|
||||
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)
|
||||
}
|
||||
|
||||
func checkPointOfWal() {
|
||||
if err := global.DB.Exec("PRAGMA wal_checkpoint(TRUNCATE);").Error; err != nil {
|
||||
global.LOG.Errorf("handle check point failed, err: %v", err)
|
||||
}
|
||||
}
|
55
core/buserr/errors.go
Normal file
55
core/buserr/errors.go
Normal file
@ -0,0 +1,55 @@
|
||||
package buserr
|
||||
|
||||
import (
|
||||
"github.com/1Panel-dev/1Panel/core/i18n"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type BusinessError struct {
|
||||
Msg string
|
||||
Detail interface{}
|
||||
Map map[string]interface{}
|
||||
Err error
|
||||
}
|
||||
|
||||
func (e BusinessError) Error() string {
|
||||
content := ""
|
||||
if e.Detail != nil {
|
||||
content = i18n.GetErrMsg(e.Msg, map[string]interface{}{"detail": e.Detail})
|
||||
} else if e.Map != nil {
|
||||
content = i18n.GetErrMsg(e.Msg, e.Map)
|
||||
} else {
|
||||
content = i18n.GetErrMsg(e.Msg, nil)
|
||||
}
|
||||
if content == "" {
|
||||
if e.Err != nil {
|
||||
return e.Err.Error()
|
||||
}
|
||||
return errors.New(e.Msg).Error()
|
||||
}
|
||||
return content
|
||||
}
|
||||
|
||||
func New(Key string) BusinessError {
|
||||
return BusinessError{
|
||||
Msg: Key,
|
||||
Detail: nil,
|
||||
Err: nil,
|
||||
}
|
||||
}
|
||||
|
||||
func WithDetail(Key string, detail interface{}, err error) BusinessError {
|
||||
return BusinessError{
|
||||
Msg: Key,
|
||||
Detail: detail,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
func WithMap(Key string, maps map[string]interface{}, err error) BusinessError {
|
||||
return BusinessError{
|
||||
Msg: Key,
|
||||
Map: maps,
|
||||
Err: err,
|
||||
}
|
||||
}
|
23
core/buserr/multi_err.go
Normal file
23
core/buserr/multi_err.go
Normal file
@ -0,0 +1,23 @@
|
||||
package buserr
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"sort"
|
||||
)
|
||||
|
||||
type MultiErr map[string]error
|
||||
|
||||
func (e MultiErr) Error() string {
|
||||
var keys []string
|
||||
for key := range e {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
|
||||
buffer := bytes.NewBufferString("")
|
||||
for _, key := range keys {
|
||||
buffer.WriteString(fmt.Sprintf("[%s] %s\n", key, e[key]))
|
||||
}
|
||||
return buffer.String()
|
||||
}
|
60
core/cmd/server/cmd/listen-ip.go
Normal file
60
core/cmd/server/cmd/listen-ip.go
Normal file
@ -0,0 +1,60 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func init() {
|
||||
RootCmd.AddCommand(listenCmd)
|
||||
listenCmd.AddCommand(listenIpv4Cmd)
|
||||
listenCmd.AddCommand(listenIpv6Cmd)
|
||||
}
|
||||
|
||||
var listenCmd = &cobra.Command{
|
||||
Use: "listen-ip",
|
||||
Short: "切换监听 IP",
|
||||
}
|
||||
|
||||
var listenIpv4Cmd = &cobra.Command{
|
||||
Use: "ipv4",
|
||||
Short: "监听 IPv4",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return updateBindInfo("ipv4")
|
||||
},
|
||||
}
|
||||
var listenIpv6Cmd = &cobra.Command{
|
||||
Use: "ipv6",
|
||||
Short: "监听 IPv6",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return updateBindInfo("ipv6")
|
||||
},
|
||||
}
|
||||
|
||||
func updateBindInfo(protocol string) error {
|
||||
if !isRoot() {
|
||||
fmt.Println("请使用 sudo 1pctl listen-ip ipv6 或者切换到 root 用户")
|
||||
return nil
|
||||
}
|
||||
db, err := loadDBConn()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ipv6 := "disable"
|
||||
tcp := "tcp4"
|
||||
address := "0.0.0.0"
|
||||
if protocol == "ipv6" {
|
||||
ipv6 = "enable"
|
||||
tcp = "tcp6"
|
||||
address = "::"
|
||||
}
|
||||
if err := setSettingByKey(db, "Ipv6", ipv6); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := setSettingByKey(db, "BindAddress", address); err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("切换成功!已切换至监听 %s [%s]", tcp, address)
|
||||
return nil
|
||||
}
|
102
core/cmd/server/cmd/reset.go
Normal file
102
core/cmd/server/cmd/reset.go
Normal file
@ -0,0 +1,102 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func init() {
|
||||
RootCmd.AddCommand(resetCmd)
|
||||
resetCmd.AddCommand(resetMFACmd)
|
||||
resetCmd.AddCommand(resetSSLCmd)
|
||||
resetCmd.AddCommand(resetEntranceCmd)
|
||||
resetCmd.AddCommand(resetBindIpsCmd)
|
||||
resetCmd.AddCommand(resetDomainCmd)
|
||||
}
|
||||
|
||||
var resetCmd = &cobra.Command{
|
||||
Use: "reset",
|
||||
Short: "重置系统信息",
|
||||
}
|
||||
|
||||
var resetMFACmd = &cobra.Command{
|
||||
Use: "mfa",
|
||||
Short: "取消 1Panel 两步验证",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if !isRoot() {
|
||||
fmt.Println("请使用 sudo 1pctl reset mfa 或者切换到 root 用户")
|
||||
return nil
|
||||
}
|
||||
db, err := loadDBConn()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return setSettingByKey(db, "MFAStatus", "disable")
|
||||
},
|
||||
}
|
||||
var resetSSLCmd = &cobra.Command{
|
||||
Use: "https",
|
||||
Short: "取消 1Panel https 方式登录",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if !isRoot() {
|
||||
fmt.Println("请使用 sudo 1pctl reset https 或者切换到 root 用户")
|
||||
return nil
|
||||
}
|
||||
db, err := loadDBConn()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return setSettingByKey(db, "SSL", "disable")
|
||||
},
|
||||
}
|
||||
var resetEntranceCmd = &cobra.Command{
|
||||
Use: "entrance",
|
||||
Short: "取消 1Panel 安全入口",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if !isRoot() {
|
||||
fmt.Println("请使用 sudo 1pctl reset entrance 或者切换到 root 用户")
|
||||
return nil
|
||||
}
|
||||
db, err := loadDBConn()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return setSettingByKey(db, "SecurityEntrance", "")
|
||||
},
|
||||
}
|
||||
var resetBindIpsCmd = &cobra.Command{
|
||||
Use: "ips",
|
||||
Short: "取消 1Panel 授权 IP 限制",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if !isRoot() {
|
||||
fmt.Println("请使用 sudo 1pctl reset ips 或者切换到 root 用户")
|
||||
return nil
|
||||
}
|
||||
db, err := loadDBConn()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return setSettingByKey(db, "AllowIPs", "")
|
||||
},
|
||||
}
|
||||
var resetDomainCmd = &cobra.Command{
|
||||
Use: "domain",
|
||||
Short: "取消 1Panel 访问域名绑定",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if !isRoot() {
|
||||
fmt.Println("请使用 sudo 1pctl reset domain 或者切换到 root 用户")
|
||||
return nil
|
||||
}
|
||||
db, err := loadDBConn()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return setSettingByKey(db, "BindDomain", "")
|
||||
},
|
||||
}
|
122
core/cmd/server/cmd/restore.go
Normal file
122
core/cmd/server/cmd/restore.go
Normal file
@ -0,0 +1,122 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
cmdUtils "github.com/1Panel-dev/1Panel/core/utils/cmd"
|
||||
"github.com/1Panel-dev/1Panel/core/utils/files"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func init() {
|
||||
RootCmd.AddCommand(restoreCmd)
|
||||
}
|
||||
|
||||
var restoreCmd = &cobra.Command{
|
||||
Use: "restore",
|
||||
Short: "回滚 1Panel 服务及数据",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if !isRoot() {
|
||||
fmt.Println("请使用 sudo 1pctl restore 或者切换到 root 用户")
|
||||
return nil
|
||||
}
|
||||
stdout, err := cmdUtils.Exec("grep '^BASE_DIR=' /usr/local/bin/1pctl | cut -d'=' -f2")
|
||||
if err != nil {
|
||||
return fmt.Errorf("handle load `BASE_DIR` failed, err: %v", err)
|
||||
}
|
||||
baseDir := strings.ReplaceAll(stdout, "\n", "")
|
||||
upgradeDir := path.Join(baseDir, "1panel", "tmp", "upgrade")
|
||||
|
||||
tmpPath, err := loadRestorePath(upgradeDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if tmpPath == "暂无可回滚文件" {
|
||||
fmt.Println("暂无可回滚文件")
|
||||
return nil
|
||||
}
|
||||
tmpPath = path.Join(upgradeDir, tmpPath, "original")
|
||||
fmt.Printf("(0/4) 开始从 %s 目录回滚 1Panel 服务及数据... \n", tmpPath)
|
||||
|
||||
if err := files.CopyFile(path.Join(tmpPath, "1panel"), "/usr/local/bin"); err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println("(1/4) 1panel 二进制回滚成功")
|
||||
if err := files.CopyFile(path.Join(tmpPath, "1pctl"), "/usr/local/bin"); err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println("(2/4) 1panel 脚本回滚成功")
|
||||
if err := files.CopyFile(path.Join(tmpPath, "1panel.service"), "/etc/systemd/system"); err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println("(3/4) 1panel 服务回滚成功")
|
||||
checkPointOfWal()
|
||||
if _, err := os.Stat(path.Join(tmpPath, "1Panel.db")); err == nil {
|
||||
if err := files.CopyFile(path.Join(tmpPath, "1Panel.db"), path.Join(baseDir, "1panel/db")); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if _, err := os.Stat(path.Join(tmpPath, "db.tar.gz")); err == nil {
|
||||
if err := handleUnTar(path.Join(tmpPath, "db.tar.gz"), path.Join(baseDir, "1panel")); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
fmt.Printf("(4/4) 1panel 数据回滚成功 \n\n")
|
||||
|
||||
fmt.Println("回滚成功!正在重启服务,请稍候...")
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
func checkPointOfWal() {
|
||||
db, err := loadDBConn()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
_ = db.Exec("PRAGMA wal_checkpoint(TRUNCATE);").Error
|
||||
}
|
||||
|
||||
func loadRestorePath(upgradeDir string) (string, error) {
|
||||
if _, err := os.Stat(upgradeDir); err != nil && os.IsNotExist(err) {
|
||||
return "暂无可回滚文件", nil
|
||||
}
|
||||
files, err := os.ReadDir(upgradeDir)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
var folders []string
|
||||
for _, file := range files {
|
||||
if file.IsDir() {
|
||||
folders = append(folders, file.Name())
|
||||
}
|
||||
}
|
||||
if len(folders) == 0 {
|
||||
return "暂无可回滚文件", nil
|
||||
}
|
||||
sort.Slice(folders, func(i, j int) bool {
|
||||
return folders[i] > folders[j]
|
||||
})
|
||||
return folders[0], nil
|
||||
}
|
||||
|
||||
func handleUnTar(sourceFile, targetDir string) error {
|
||||
if _, err := os.Stat(targetDir); err != nil && os.IsNotExist(err) {
|
||||
if err = os.MkdirAll(targetDir, os.ModePerm); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
commands := fmt.Sprintf("tar zxvfC %s %s", sourceFile, targetDir)
|
||||
stdout, err := cmdUtils.ExecWithTimeOut(commands, 20*time.Second)
|
||||
if err != nil {
|
||||
return errors.New(stdout)
|
||||
}
|
||||
return nil
|
||||
}
|
80
core/cmd/server/cmd/root.go
Normal file
80
core/cmd/server/cmd/root.go
Normal file
@ -0,0 +1,80 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os/user"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/core/server"
|
||||
cmdUtils "github.com/1Panel-dev/1Panel/core/utils/cmd"
|
||||
"github.com/glebarez/sqlite"
|
||||
"github.com/spf13/cobra"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func init() {}
|
||||
|
||||
var RootCmd = &cobra.Command{
|
||||
Use: "1panel",
|
||||
Short: "1Panel ,一款现代化的 Linux 面板",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
server.Start()
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
type setting struct {
|
||||
ID uint `gorm:"primarykey;AUTO_INCREMENT" json:"id"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
Key string `json:"key" gorm:"type:varchar(256);not null;"`
|
||||
Value string `json:"value" gorm:"type:varchar(256)"`
|
||||
About string `json:"about" gorm:"type:longText"`
|
||||
}
|
||||
|
||||
func loadDBConn() (*gorm.DB, error) {
|
||||
stdout, err := cmdUtils.Exec("grep '^BASE_DIR=' /usr/local/bin/1pctl | cut -d'=' -f2")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("handle load `BASE_DIR` failed, err: %v", err)
|
||||
}
|
||||
baseDir := strings.ReplaceAll(stdout, "\n", "")
|
||||
if len(baseDir) == 0 {
|
||||
return nil, fmt.Errorf("error `BASE_DIR` find in /usr/local/bin/1pctl \n")
|
||||
}
|
||||
if strings.HasSuffix(baseDir, "/") {
|
||||
baseDir = baseDir[:strings.LastIndex(baseDir, "/")]
|
||||
}
|
||||
|
||||
db, err := gorm.Open(sqlite.Open(baseDir+"/1panel/db/1Panel.db"), &gorm.Config{})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("init my db conn failed, err: %v \n", err)
|
||||
}
|
||||
return db, nil
|
||||
}
|
||||
|
||||
func getSettingByKey(db *gorm.DB, key string) string {
|
||||
var setting setting
|
||||
_ = db.Where("key = ?", key).First(&setting).Error
|
||||
return setting.Value
|
||||
}
|
||||
|
||||
type LoginLog struct{}
|
||||
|
||||
func isDefault(db *gorm.DB) bool {
|
||||
logCount := int64(0)
|
||||
_ = db.Model(&LoginLog{}).Where("status = ?", "Success").Count(&logCount).Error
|
||||
return logCount == 0
|
||||
}
|
||||
|
||||
func setSettingByKey(db *gorm.DB, key, value string) error {
|
||||
return db.Model(&setting{}).Where("key = ?", key).Updates(map[string]interface{}{"value": value}).Error
|
||||
}
|
||||
|
||||
func isRoot() bool {
|
||||
currentUser, err := user.Current()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return currentUser.Uid == "0"
|
||||
}
|
240
core/cmd/server/cmd/update.go
Normal file
240
core/cmd/server/cmd/update.go
Normal file
@ -0,0 +1,240 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/core/global"
|
||||
"github.com/1Panel-dev/1Panel/core/utils/cmd"
|
||||
"github.com/1Panel-dev/1Panel/core/utils/common"
|
||||
"github.com/1Panel-dev/1Panel/core/utils/encrypt"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/term"
|
||||
)
|
||||
|
||||
func init() {
|
||||
RootCmd.AddCommand(updateCmd)
|
||||
updateCmd.AddCommand(updateUserName)
|
||||
updateCmd.AddCommand(updatePassword)
|
||||
updateCmd.AddCommand(updatePort)
|
||||
}
|
||||
|
||||
var updateCmd = &cobra.Command{
|
||||
Use: "update",
|
||||
Short: "修改面板信息",
|
||||
}
|
||||
|
||||
var updateUserName = &cobra.Command{
|
||||
Use: "username",
|
||||
Short: "修改面板用户",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if !isRoot() {
|
||||
fmt.Println("请使用 sudo 1pctl update username 或者切换到 root 用户")
|
||||
return nil
|
||||
}
|
||||
username()
|
||||
return nil
|
||||
},
|
||||
}
|
||||
var updatePassword = &cobra.Command{
|
||||
Use: "password",
|
||||
Short: "修改面板密码",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if !isRoot() {
|
||||
fmt.Println("请使用 sudo 1pctl update password 或者切换到 root 用户")
|
||||
return nil
|
||||
}
|
||||
password()
|
||||
return nil
|
||||
},
|
||||
}
|
||||
var updatePort = &cobra.Command{
|
||||
Use: "port",
|
||||
Short: "修改面板端口",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if !isRoot() {
|
||||
fmt.Println("请使用 sudo 1pctl update port 或者切换到 root 用户")
|
||||
return nil
|
||||
}
|
||||
port()
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
func username() {
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
fmt.Print("修改面板用户: ")
|
||||
newUsername, _ := reader.ReadString('\n')
|
||||
newUsername = strings.Trim(newUsername, "\n")
|
||||
if len(newUsername) == 0 {
|
||||
fmt.Println("错误:输入面板用户为空!")
|
||||
return
|
||||
}
|
||||
if strings.Contains(newUsername, " ") {
|
||||
fmt.Println("错误:输入面板用户中包含空格字符!")
|
||||
return
|
||||
}
|
||||
result, err := regexp.MatchString("^[a-zA-Z0-9_\u4e00-\u9fa5]{3,30}$", newUsername)
|
||||
if !result || err != nil {
|
||||
fmt.Println("错误:输入面板用户错误!仅支持英文、中文、数字和_,长度3-30")
|
||||
return
|
||||
}
|
||||
|
||||
db, err := loadDBConn()
|
||||
if err != nil {
|
||||
fmt.Printf("错误:初始化数据库连接失败,%v\n", err)
|
||||
return
|
||||
}
|
||||
if err := setSettingByKey(db, "UserName", newUsername); err != nil {
|
||||
fmt.Printf("错误:面板用户修改失败,%v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("修改成功!\n\n")
|
||||
fmt.Printf("面板用户:%s\n", newUsername)
|
||||
}
|
||||
|
||||
func password() {
|
||||
fmt.Print("修改面板密码:")
|
||||
bytePassword, err := term.ReadPassword(int(os.Stdin.Fd()))
|
||||
if err != nil {
|
||||
fmt.Printf("\n错误:面板密码信息读取错误,%v\n", err)
|
||||
return
|
||||
}
|
||||
newPassword := string(bytePassword)
|
||||
newPassword = strings.Trim(newPassword, "\n")
|
||||
|
||||
if len(newPassword) == 0 {
|
||||
fmt.Println("\n错误:输入面板密码为空!")
|
||||
return
|
||||
}
|
||||
if strings.Contains(newPassword, " ") {
|
||||
fmt.Println("\n错误:输入面板密码中包含空格字符!")
|
||||
return
|
||||
}
|
||||
db, err := loadDBConn()
|
||||
if err != nil {
|
||||
fmt.Printf("\n错误:初始化数据库连接失败,%v\n", err)
|
||||
return
|
||||
}
|
||||
complexSetting := getSettingByKey(db, "ComplexityVerification")
|
||||
if complexSetting == "enable" {
|
||||
if isValidPassword("newPassword") {
|
||||
fmt.Println("\n错误:面板密码仅支持字母、数字、特殊字符(!@#$%*_,.?),长度 8-30 位!")
|
||||
return
|
||||
}
|
||||
}
|
||||
if len(newPassword) < 6 {
|
||||
fmt.Println("错误:请输入 6 位以上密码!")
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Print("\n确认密码:")
|
||||
byteConfirmPassword, err := term.ReadPassword(int(os.Stdin.Fd()))
|
||||
if err != nil {
|
||||
fmt.Printf("\n错误:面板密码信息读取错误,%v\n", err)
|
||||
return
|
||||
}
|
||||
confirmPassword := string(byteConfirmPassword)
|
||||
confirmPassword = strings.Trim(confirmPassword, "\n")
|
||||
|
||||
if newPassword != confirmPassword {
|
||||
fmt.Printf("\n错误:两次密码不匹配,请检查后重试!,%v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
p := ""
|
||||
encryptSetting := getSettingByKey(db, "EncryptKey")
|
||||
if len(encryptSetting) == 16 {
|
||||
global.CONF.System.EncryptKey = encryptSetting
|
||||
p, _ = encrypt.StringEncrypt(newPassword)
|
||||
} else {
|
||||
p = newPassword
|
||||
}
|
||||
if err := setSettingByKey(db, "Password", p); err != nil {
|
||||
fmt.Printf("\n错误:面板密码修改失败,%v\n", err)
|
||||
return
|
||||
}
|
||||
username := getSettingByKey(db, "UserName")
|
||||
|
||||
fmt.Printf("\n修改成功!\n\n")
|
||||
fmt.Printf("面板用户:%s\n", username)
|
||||
fmt.Printf("面板密码:%s\n", string(newPassword))
|
||||
}
|
||||
|
||||
func port() {
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
fmt.Print("修改面板端口:")
|
||||
|
||||
newPortStr, _ := reader.ReadString('\n')
|
||||
newPortStr = strings.Trim(newPortStr, "\n")
|
||||
newPort, err := strconv.Atoi(strings.TrimSpace(newPortStr))
|
||||
if err != nil || newPort < 1 || newPort > 65535 {
|
||||
fmt.Println("错误:输入的端口号必须在 1 到 65535 之间!")
|
||||
return
|
||||
}
|
||||
if common.ScanPort(newPort) {
|
||||
fmt.Println("错误:该端口号正被占用,请检查后重试!")
|
||||
return
|
||||
}
|
||||
db, err := loadDBConn()
|
||||
if err != nil {
|
||||
fmt.Printf("错误:初始化数据库连接失败,%v\n", err)
|
||||
return
|
||||
}
|
||||
if err := setSettingByKey(db, "ServerPort", newPortStr); err != nil {
|
||||
fmt.Printf("错误:面板端口修改失败,%v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("修改成功!\n\n")
|
||||
fmt.Printf("面板端口:%s\n", newPortStr)
|
||||
|
||||
std, err := cmd.Exec("1pctl restart")
|
||||
if err != nil {
|
||||
fmt.Println(std)
|
||||
}
|
||||
}
|
||||
func isValidPassword(password string) bool {
|
||||
numCount := 0
|
||||
alphaCount := 0
|
||||
specialCount := 0
|
||||
|
||||
for _, char := range password {
|
||||
switch {
|
||||
case unicode.IsDigit(char):
|
||||
numCount++
|
||||
case unicode.IsLetter(char):
|
||||
alphaCount++
|
||||
case isSpecialChar(char):
|
||||
specialCount++
|
||||
}
|
||||
}
|
||||
|
||||
if len(password) < 8 && len(password) > 30 {
|
||||
return false
|
||||
}
|
||||
if (numCount == 0 && alphaCount == 0) || (alphaCount == 0 && specialCount == 0) || (numCount == 0 && specialCount == 0) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func isSpecialChar(char rune) bool {
|
||||
specialChars := "!@#$%*_,.?"
|
||||
return unicode.IsPunct(char) && contains(specialChars, char)
|
||||
}
|
||||
|
||||
func contains(specialChars string, char rune) bool {
|
||||
for _, c := range specialChars {
|
||||
if c == char {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
56
core/cmd/server/cmd/user-info.go
Normal file
56
core/cmd/server/cmd/user-info.go
Normal file
@ -0,0 +1,56 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/core/global"
|
||||
"github.com/1Panel-dev/1Panel/core/utils/encrypt"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func init() {
|
||||
RootCmd.AddCommand(userinfoCmd)
|
||||
}
|
||||
|
||||
var userinfoCmd = &cobra.Command{
|
||||
Use: "user-info",
|
||||
Short: "获取面板信息",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if !isRoot() {
|
||||
fmt.Println("请使用 sudo 1pctl user-info 或者切换到 root 用户")
|
||||
return nil
|
||||
}
|
||||
db, err := loadDBConn()
|
||||
if err != nil {
|
||||
return fmt.Errorf("init my db conn failed, err: %v \n", err)
|
||||
}
|
||||
user := getSettingByKey(db, "UserName")
|
||||
pass := "********"
|
||||
if isDefault(db) {
|
||||
encryptSetting := getSettingByKey(db, "EncryptKey")
|
||||
pass = getSettingByKey(db, "Password")
|
||||
if len(encryptSetting) == 16 {
|
||||
global.CONF.System.EncryptKey = encryptSetting
|
||||
pass, _ = encrypt.StringDecrypt(pass)
|
||||
}
|
||||
}
|
||||
port := getSettingByKey(db, "ServerPort")
|
||||
ssl := getSettingByKey(db, "SSL")
|
||||
entrance := getSettingByKey(db, "SecurityEntrance")
|
||||
address := getSettingByKey(db, "SystemIP")
|
||||
|
||||
protocol := "http"
|
||||
if ssl == "enable" {
|
||||
protocol = "https"
|
||||
}
|
||||
if address == "" {
|
||||
address = "$LOCAL_IP"
|
||||
}
|
||||
|
||||
fmt.Printf("面板地址: %s://%s:%s/%s \n", protocol, address, port, entrance)
|
||||
fmt.Println("面板用户: ", user)
|
||||
fmt.Println("面板密码: ", pass)
|
||||
fmt.Println("提示:修改密码可执行命令:1pctl update password")
|
||||
return nil
|
||||
},
|
||||
}
|
40
core/cmd/server/cmd/version.go
Normal file
40
core/cmd/server/cmd/version.go
Normal file
@ -0,0 +1,40 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/core/cmd/server/conf"
|
||||
"github.com/1Panel-dev/1Panel/core/configs"
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func init() {
|
||||
RootCmd.AddCommand(versionCmd)
|
||||
}
|
||||
|
||||
var versionCmd = &cobra.Command{
|
||||
Use: "version",
|
||||
Short: "获取系统版本信息",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if !isRoot() {
|
||||
fmt.Println("请使用 sudo 1pctl version 或者切换到 root 用户")
|
||||
return nil
|
||||
}
|
||||
db, err := loadDBConn()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
version := getSettingByKey(db, "SystemVersion")
|
||||
|
||||
fmt.Printf("1panel version: %s\n", version)
|
||||
config := configs.ServerConfig{}
|
||||
if err := yaml.Unmarshal(conf.AppYaml, &config); err != nil {
|
||||
return fmt.Errorf("unmarshal conf.App.Yaml failed, errL %v", err)
|
||||
} else {
|
||||
fmt.Printf("mode: %s\n", config.System.Mode)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
17
core/cmd/server/conf/app.yaml
Normal file
17
core/cmd/server/conf/app.yaml
Normal file
@ -0,0 +1,17 @@
|
||||
system:
|
||||
db_file: 1Panel.db
|
||||
base_dir: /opt
|
||||
mode: dev
|
||||
repo_url: https://resource.fit2cloud.com/1panel/package
|
||||
app_repo: https://apps-assets.fit2cloud.com
|
||||
is_demo: false
|
||||
port: 9999
|
||||
username: admin
|
||||
password: admin123
|
||||
|
||||
log:
|
||||
level: debug
|
||||
time_zone: Asia/Shanghai
|
||||
log_name: 1Panel
|
||||
log_suffix: .log
|
||||
max_backup: 10
|
6
core/cmd/server/conf/conf.go
Normal file
6
core/cmd/server/conf/conf.go
Normal file
@ -0,0 +1,6 @@
|
||||
package conf
|
||||
|
||||
import _ "embed"
|
||||
|
||||
//go:embed app.yaml
|
||||
var AppYaml []byte
|
23464
core/cmd/server/docs/docs.go
Normal file
23464
core/cmd/server/docs/docs.go
Normal file
File diff suppressed because it is too large
Load Diff
6
core/cmd/server/docs/swagger.go
Normal file
6
core/cmd/server/docs/swagger.go
Normal file
@ -0,0 +1,6 @@
|
||||
package docs
|
||||
|
||||
import _ "embed"
|
||||
|
||||
//go:embed swagger.json
|
||||
var SwaggerJson []byte
|
23441
core/cmd/server/docs/swagger.json
Normal file
23441
core/cmd/server/docs/swagger.json
Normal file
File diff suppressed because it is too large
Load Diff
15166
core/cmd/server/docs/swagger.yaml
Normal file
15166
core/cmd/server/docs/swagger.yaml
Normal file
File diff suppressed because it is too large
Load Diff
BIN
core/cmd/server/web/favicon.png
Normal file
BIN
core/cmd/server/web/favicon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 9.1 KiB |
15
core/cmd/server/web/web.go
Normal file
15
core/cmd/server/web/web.go
Normal file
@ -0,0 +1,15 @@
|
||||
package web
|
||||
|
||||
import "embed"
|
||||
|
||||
//go:embed index.html
|
||||
var IndexHtml embed.FS
|
||||
|
||||
//go:embed assets/*
|
||||
var Assets embed.FS
|
||||
|
||||
//go:embed index.html
|
||||
var IndexByte []byte
|
||||
|
||||
//go:embed favicon.png
|
||||
var Favicon embed.FS
|
6
core/configs/config.go
Normal file
6
core/configs/config.go
Normal file
@ -0,0 +1,6 @@
|
||||
package configs
|
||||
|
||||
type ServerConfig struct {
|
||||
System System `mapstructure:"system"`
|
||||
LogConfig LogConfig `mapstructure:"log"`
|
||||
}
|
9
core/configs/log.go
Normal file
9
core/configs/log.go
Normal file
@ -0,0 +1,9 @@
|
||||
package configs
|
||||
|
||||
type LogConfig struct {
|
||||
Level string `mapstructure:"level"`
|
||||
TimeZone string `mapstructure:"timeZone"`
|
||||
LogName string `mapstructure:"log_name"`
|
||||
LogSuffix string `mapstructure:"log_suffix"`
|
||||
MaxBackup int `mapstructure:"max_backup"`
|
||||
}
|
25
core/configs/system.go
Normal file
25
core/configs/system.go
Normal file
@ -0,0 +1,25 @@
|
||||
package configs
|
||||
|
||||
type System struct {
|
||||
Port string `mapstructure:"port"`
|
||||
Ipv6 string `mapstructure:"ipv6"`
|
||||
BindAddress string `mapstructure:"bindAddress"`
|
||||
SSL string `mapstructure:"ssl"`
|
||||
DbFile string `mapstructure:"db_file"`
|
||||
DbPath string `mapstructure:"db_path"`
|
||||
LogPath string `mapstructure:"log_path"`
|
||||
DataDir string `mapstructure:"data_dir"`
|
||||
TmpDir string `mapstructure:"tmp_dir"`
|
||||
Cache string `mapstructure:"cache"`
|
||||
Backup string `mapstructure:"backup"`
|
||||
EncryptKey string `mapstructure:"encrypt_key"`
|
||||
BaseDir string `mapstructure:"base_dir"`
|
||||
Mode string `mapstructure:"mode"`
|
||||
RepoUrl string `mapstructure:"repo_url"`
|
||||
Version string `mapstructure:"version"`
|
||||
Username string `mapstructure:"username"`
|
||||
Password string `mapstructure:"password"`
|
||||
Entrance string `mapstructure:"entrance"`
|
||||
IsDemo bool `mapstructure:"is_demo"`
|
||||
ChangeUserInfo string `mapstructure:"change_user_info"`
|
||||
}
|
20
core/constant/common.go
Normal file
20
core/constant/common.go
Normal file
@ -0,0 +1,20 @@
|
||||
package constant
|
||||
|
||||
type DBContext string
|
||||
|
||||
const (
|
||||
TimeOut5s = 5
|
||||
TimeOut20s = 20
|
||||
TimeOut5m = 300
|
||||
|
||||
DateLayout = "2006-01-02" // or use time.DateOnly while go version >= 1.20
|
||||
DefaultDate = "1970-01-01"
|
||||
DateTimeLayout = "2006-01-02 15:04:05" // or use time.DateTime while go version >= 1.20
|
||||
DateTimeSlimLayout = "20060102150405"
|
||||
|
||||
OrderDesc = "descending"
|
||||
OrderAsc = "ascending"
|
||||
|
||||
StatusEnable = "Enable"
|
||||
StatusDisable = "Disable"
|
||||
)
|
20
core/constant/dir.go
Normal file
20
core/constant/dir.go
Normal file
@ -0,0 +1,20 @@
|
||||
package constant
|
||||
|
||||
import (
|
||||
"path"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/core/global"
|
||||
)
|
||||
|
||||
var (
|
||||
DataDir = global.CONF.System.DataDir
|
||||
ResourceDir = path.Join(DataDir, "resource")
|
||||
AppResourceDir = path.Join(ResourceDir, "apps")
|
||||
AppInstallDir = path.Join(DataDir, "apps")
|
||||
LocalAppResourceDir = path.Join(AppResourceDir, "local")
|
||||
LocalAppInstallDir = path.Join(AppInstallDir, "local")
|
||||
RemoteAppResourceDir = path.Join(AppResourceDir, "remote")
|
||||
RuntimeDir = path.Join(DataDir, "runtime")
|
||||
RecycleBinDir = "/.1panel_clash"
|
||||
SSLLogDir = path.Join(global.CONF.System.DataDir, "log", "ssl")
|
||||
)
|
164
core/constant/errs.go
Normal file
164
core/constant/errs.go
Normal file
@ -0,0 +1,164 @@
|
||||
package constant
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
const (
|
||||
CodeSuccess = 200
|
||||
CodeErrBadRequest = 400
|
||||
CodeErrUnauthorized = 401
|
||||
CodeErrNotFound = 404
|
||||
CodeAuth = 406
|
||||
CodeGlobalLoading = 407
|
||||
CodeErrInternalServer = 500
|
||||
|
||||
CodeErrIP = 310
|
||||
CodeErrDomain = 311
|
||||
CodeErrEntrance = 312
|
||||
CodePasswordExpired = 313
|
||||
|
||||
CodeErrXpack = 410
|
||||
)
|
||||
|
||||
// internal
|
||||
var (
|
||||
ErrCaptchaCode = errors.New("ErrCaptchaCode")
|
||||
ErrAuth = errors.New("ErrAuth")
|
||||
ErrRecordExist = errors.New("ErrRecordExist")
|
||||
ErrRecordNotFound = errors.New("ErrRecordNotFound")
|
||||
ErrStructTransform = errors.New("ErrStructTransform")
|
||||
ErrInitialPassword = errors.New("ErrInitialPassword")
|
||||
ErrNotSupportType = errors.New("ErrNotSupportType")
|
||||
ErrInvalidParams = errors.New("ErrInvalidParams")
|
||||
|
||||
ErrTokenParse = errors.New("ErrTokenParse")
|
||||
)
|
||||
|
||||
// api
|
||||
var (
|
||||
ErrTypeInternalServer = "ErrInternalServer"
|
||||
ErrTypeInvalidParams = "ErrInvalidParams"
|
||||
ErrTypeNotLogin = "ErrNotLogin"
|
||||
ErrTypePasswordExpired = "ErrPasswordExpired"
|
||||
ErrNameIsExist = "ErrNameIsExist"
|
||||
ErrDemoEnvironment = "ErrDemoEnvironment"
|
||||
ErrCmdIllegal = "ErrCmdIllegal"
|
||||
ErrXpackNotFound = "ErrXpackNotFound"
|
||||
ErrXpackNotActive = "ErrXpackNotActive"
|
||||
ErrXpackOutOfDate = "ErrXpackOutOfDate"
|
||||
)
|
||||
|
||||
// app
|
||||
var (
|
||||
ErrPortInUsed = "ErrPortInUsed"
|
||||
ErrAppLimit = "ErrAppLimit"
|
||||
ErrFileCanNotRead = "ErrFileCanNotRead"
|
||||
ErrNotInstall = "ErrNotInstall"
|
||||
ErrPortInOtherApp = "ErrPortInOtherApp"
|
||||
ErrDbUserNotValid = "ErrDbUserNotValid"
|
||||
ErrUpdateBuWebsite = "ErrUpdateBuWebsite"
|
||||
Err1PanelNetworkFailed = "Err1PanelNetworkFailed"
|
||||
ErrCmdTimeout = "ErrCmdTimeout"
|
||||
ErrFileParse = "ErrFileParse"
|
||||
ErrInstallDirNotFound = "ErrInstallDirNotFound"
|
||||
ErrContainerName = "ErrContainerName"
|
||||
ErrAppNameExist = "ErrAppNameExist"
|
||||
ErrFileNotFound = "ErrFileNotFound"
|
||||
ErrFileParseApp = "ErrFileParseApp"
|
||||
ErrAppParamKey = "ErrAppParamKey"
|
||||
)
|
||||
|
||||
// website
|
||||
var (
|
||||
ErrDomainIsExist = "ErrDomainIsExist"
|
||||
ErrAliasIsExist = "ErrAliasIsExist"
|
||||
ErrGroupIsUsed = "ErrGroupIsUsed"
|
||||
ErrUsernameIsExist = "ErrUsernameIsExist"
|
||||
ErrUsernameIsNotExist = "ErrUsernameIsNotExist"
|
||||
ErrBackupMatch = "ErrBackupMatch"
|
||||
ErrBackupExist = "ErrBackupExist"
|
||||
ErrDomainIsUsed = "ErrDomainIsUsed"
|
||||
)
|
||||
|
||||
// ssl
|
||||
var (
|
||||
ErrSSLCannotDelete = "ErrSSLCannotDelete"
|
||||
ErrAccountCannotDelete = "ErrAccountCannotDelete"
|
||||
ErrSSLApply = "ErrSSLApply"
|
||||
ErrEmailIsExist = "ErrEmailIsExist"
|
||||
ErrEabKidOrEabHmacKeyCannotBlank = "ErrEabKidOrEabHmacKeyCannotBlank"
|
||||
)
|
||||
|
||||
// file
|
||||
var (
|
||||
ErrPathNotFound = "ErrPathNotFound"
|
||||
ErrMovePathFailed = "ErrMovePathFailed"
|
||||
ErrLinkPathNotFound = "ErrLinkPathNotFound"
|
||||
ErrFileIsExist = "ErrFileIsExist"
|
||||
ErrFileUpload = "ErrFileUpload"
|
||||
ErrFileDownloadDir = "ErrFileDownloadDir"
|
||||
ErrCmdNotFound = "ErrCmdNotFound"
|
||||
ErrFavoriteExist = "ErrFavoriteExist"
|
||||
)
|
||||
|
||||
// mysql
|
||||
var (
|
||||
ErrUserIsExist = "ErrUserIsExist"
|
||||
ErrDatabaseIsExist = "ErrDatabaseIsExist"
|
||||
ErrExecTimeOut = "ErrExecTimeOut"
|
||||
ErrRemoteExist = "ErrRemoteExist"
|
||||
ErrLocalExist = "ErrLocalExist"
|
||||
)
|
||||
|
||||
// redis
|
||||
var (
|
||||
ErrTypeOfRedis = "ErrTypeOfRedis"
|
||||
)
|
||||
|
||||
// container
|
||||
var (
|
||||
ErrInUsed = "ErrInUsed"
|
||||
ErrObjectInUsed = "ErrObjectInUsed"
|
||||
ErrPortRules = "ErrPortRules"
|
||||
ErrPgImagePull = "ErrPgImagePull"
|
||||
)
|
||||
|
||||
// runtime
|
||||
var (
|
||||
ErrDirNotFound = "ErrDirNotFound"
|
||||
ErrFileNotExist = "ErrFileNotExist"
|
||||
ErrImageBuildErr = "ErrImageBuildErr"
|
||||
ErrImageExist = "ErrImageExist"
|
||||
ErrDelWithWebsite = "ErrDelWithWebsite"
|
||||
ErrRuntimeStart = "ErrRuntimeStart"
|
||||
ErrPackageJsonNotFound = "ErrPackageJsonNotFound"
|
||||
ErrScriptsNotFound = "ErrScriptsNotFound"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrBackupInUsed = "ErrBackupInUsed"
|
||||
ErrOSSConn = "ErrOSSConn"
|
||||
ErrEntrance = "ErrEntrance"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrFirewall = "ErrFirewall"
|
||||
)
|
||||
|
||||
// cronjob
|
||||
var (
|
||||
ErrBashExecute = "ErrBashExecute"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrNotExistUser = "ErrNotExistUser"
|
||||
)
|
||||
|
||||
// license
|
||||
var (
|
||||
ErrLicense = "ErrLicense"
|
||||
ErrLicenseCheck = "ErrLicenseCheck"
|
||||
ErrLicenseSave = "ErrLicenseSave"
|
||||
ErrLicenseSync = "ErrLicenseSync"
|
||||
)
|
13
core/constant/session.go
Normal file
13
core/constant/session.go
Normal file
@ -0,0 +1,13 @@
|
||||
package constant
|
||||
|
||||
const (
|
||||
AuthMethodSession = "session"
|
||||
SessionName = "psession"
|
||||
|
||||
AuthMethodJWT = "jwt"
|
||||
JWTHeaderName = "PanelAuthorization"
|
||||
JWTBufferTime = 3600
|
||||
JWTIssuer = "1Panel"
|
||||
|
||||
PasswordExpiredName = "expired"
|
||||
)
|
6
core/constant/status.go
Normal file
6
core/constant/status.go
Normal file
@ -0,0 +1,6 @@
|
||||
package constant
|
||||
|
||||
const (
|
||||
StatusSuccess = "Success"
|
||||
StatusFailed = "Failed"
|
||||
)
|
26
core/global/global.go
Normal file
26
core/global/global.go
Normal file
@ -0,0 +1,26 @@
|
||||
package global
|
||||
|
||||
import (
|
||||
"github.com/1Panel-dev/1Panel/core/configs"
|
||||
"github.com/1Panel-dev/1Panel/core/init/cache/badger_db"
|
||||
"github.com/1Panel-dev/1Panel/core/init/session/psession"
|
||||
"github.com/dgraph-io/badger/v4"
|
||||
"github.com/go-playground/validator/v10"
|
||||
"github.com/nicksnyder/go-i18n/v2/i18n"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/spf13/viper"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
var (
|
||||
DB *gorm.DB
|
||||
LOG *logrus.Logger
|
||||
CONF configs.ServerConfig
|
||||
VALID *validator.Validate
|
||||
SESSION *psession.PSession
|
||||
CACHE *badger_db.Cache
|
||||
CacheDb *badger.DB
|
||||
Viper *viper.Viper
|
||||
|
||||
I18n *i18n.Localizer
|
||||
)
|
105
core/go.mod
Normal file
105
core/go.mod
Normal file
@ -0,0 +1,105 @@
|
||||
module github.com/1Panel-dev/1Panel/core
|
||||
|
||||
go 1.22.4
|
||||
|
||||
require (
|
||||
github.com/dgraph-io/badger/v4 v4.2.0
|
||||
github.com/fsnotify/fsnotify v1.7.0
|
||||
github.com/gin-contrib/gzip v1.0.1
|
||||
github.com/gin-gonic/gin v1.10.0
|
||||
github.com/glebarez/sqlite v1.11.0
|
||||
github.com/go-gormigrate/gormigrate/v2 v2.1.2
|
||||
github.com/go-playground/validator/v10 v10.22.0
|
||||
github.com/golang-jwt/jwt/v4 v4.5.0
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/jinzhu/copier v0.4.0
|
||||
github.com/mojocn/base64Captcha v1.3.6
|
||||
github.com/nicksnyder/go-i18n/v2 v2.4.0
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/robfig/cron/v3 v3.0.1
|
||||
github.com/sirupsen/logrus v1.9.3
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
|
||||
github.com/spf13/cobra v1.8.1
|
||||
github.com/spf13/viper v1.19.0
|
||||
github.com/swaggo/files v1.0.1
|
||||
github.com/swaggo/gin-swagger v1.6.0
|
||||
github.com/swaggo/swag v1.16.3
|
||||
github.com/xlzd/gotp v0.1.0
|
||||
golang.org/x/sys v0.22.0
|
||||
golang.org/x/term v0.22.0
|
||||
golang.org/x/text v0.16.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
gorm.io/gorm v1.25.11
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/KyleBanks/depth v1.2.1 // indirect
|
||||
github.com/PuerkitoBio/purell v1.1.1 // indirect
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
|
||||
github.com/bytedance/sonic v1.11.6 // indirect
|
||||
github.com/bytedance/sonic/loader v0.1.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
github.com/cloudwego/base64x v0.1.4 // indirect
|
||||
github.com/cloudwego/iasm v0.2.0 // indirect
|
||||
github.com/dgraph-io/ristretto v0.1.1 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
|
||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||
github.com/glebarez/go-sqlite v1.21.2 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.19.5 // indirect
|
||||
github.com/go-openapi/jsonreference v0.19.6 // indirect
|
||||
github.com/go-openapi/spec v0.20.4 // indirect
|
||||
github.com/go-openapi/swag v0.19.15 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/goccy/go-json v0.10.2 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
|
||||
github.com/golang/glog v1.0.0 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/golang/snappy v0.0.3 // indirect
|
||||
github.com/google/flatbuffers v1.12.1 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/klauspost/compress v1.17.2 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/magiconair/properties v1.8.7 // indirect
|
||||
github.com/mailru/easyjson v0.7.6 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
github.com/sagikazarmark/locafero v0.4.0 // indirect
|
||||
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
|
||||
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||
github.com/spf13/afero v1.11.0 // indirect
|
||||
github.com/spf13/cast v1.6.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/subosito/gotenv v1.6.0 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.2.12 // indirect
|
||||
go.opencensus.io v0.24.0 // indirect
|
||||
go.uber.org/atomic v1.9.0 // indirect
|
||||
go.uber.org/multierr v1.9.0 // indirect
|
||||
golang.org/x/arch v0.8.0 // indirect
|
||||
golang.org/x/crypto v0.23.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
|
||||
golang.org/x/image v0.13.0 // indirect
|
||||
golang.org/x/net v0.25.0 // indirect
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
|
||||
google.golang.org/protobuf v1.34.1 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
modernc.org/libc v1.22.5 // indirect
|
||||
modernc.org/mathutil v1.5.0 // indirect
|
||||
modernc.org/memory v1.5.0 // indirect
|
||||
modernc.org/sqlite v1.23.1 // indirect
|
||||
)
|
393
core/go.sum
Normal file
393
core/go.sum
Normal file
@ -0,0 +1,393 @@
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
|
||||
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
|
||||
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
|
||||
github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
|
||||
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
||||
github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0=
|
||||
github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
|
||||
github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
|
||||
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
|
||||
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
|
||||
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
|
||||
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dgraph-io/badger/v4 v4.2.0 h1:kJrlajbXXL9DFTNuhhu9yCx7JJa4qpYWxtE8BzuWsEs=
|
||||
github.com/dgraph-io/badger/v4 v4.2.0/go.mod h1:qfCqhPoWDFJRx1gp5QwwyGo8xk1lbHUxvK9nK0OGAak=
|
||||
github.com/dgraph-io/ristretto v0.1.1 h1:6CWw5tJNgpegArSHpNHJKldNeq03FQCwYvfMVWajOK8=
|
||||
github.com/dgraph-io/ristretto v0.1.1/go.mod h1:S1GPSBCYCIhmVNfcth17y2zZtQT6wzkzgwUve0VDWWA=
|
||||
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA=
|
||||
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
|
||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
||||
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
||||
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
|
||||
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
|
||||
github.com/gin-contrib/gzip v1.0.1 h1:HQ8ENHODeLY7a4g1Au/46Z92bdGFl74OhxcZble9WJE=
|
||||
github.com/gin-contrib/gzip v1.0.1/go.mod h1:njt428fdUNRvjuJf16tZMYZ2Yl+WQB53X5wmhDwXvC4=
|
||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
|
||||
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
|
||||
github.com/glebarez/go-sqlite v1.21.2 h1:3a6LFC4sKahUunAmynQKLZceZCOzUthkRkEAl9gAXWo=
|
||||
github.com/glebarez/go-sqlite v1.21.2/go.mod h1:sfxdZyhQjTM2Wry3gVYWaW072Ri1WMdWJi0k6+3382k=
|
||||
github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw=
|
||||
github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ=
|
||||
github.com/go-gormigrate/gormigrate/v2 v2.1.2 h1:F/d1hpHbRAvKezziV2CC5KUE82cVe9zTgHSBoOOZ4CY=
|
||||
github.com/go-gormigrate/gormigrate/v2 v2.1.2/go.mod h1:9nHVX6z3FCMCQPA7PThGcA55t22yKQfK/Dnsf5i7hUo=
|
||||
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||
github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY=
|
||||
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||
github.com/go-openapi/jsonreference v0.19.6 h1:UBIxjkht+AWIgYzCDSv2GN+E/togfwXUJFRTWhl2Jjs=
|
||||
github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns=
|
||||
github.com/go-openapi/spec v0.20.4 h1:O8hJrt0UMnhHcluhIdUgCLRWyM2x7QkBXRvOs7m+O1M=
|
||||
github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7FOEWeq8I=
|
||||
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
|
||||
github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM=
|
||||
github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
|
||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||
github.com/go-playground/validator/v10 v10.22.0 h1:k6HsTZ0sTnROkhS//R0O+55JgM8C4Bx7ia+JlgcnOao=
|
||||
github.com/go-playground/validator/v10 v10.22.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
|
||||
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
|
||||
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/glog v1.0.0 h1:nfP3RFugxnNRyKgeWd4oI1nYvXpxrx8ck8ZrcizshdQ=
|
||||
github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4=
|
||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/golang/snappy v0.0.3 h1:fHPg5GQYlCeLIPB9BZqMVR5nR9A+IM5zcgeTdjMYmLA=
|
||||
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/flatbuffers v1.12.1 h1:MVlul7pQNoDzWRLTw5imwYsl+usrS1TXG2H4jg6ImGw=
|
||||
github.com/google/flatbuffers v1.12.1/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ=
|
||||
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8=
|
||||
github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg=
|
||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v1.17.2 h1:RlWWUY/Dr4fL8qk9YG7DTZ7PDgME2V4csBXA8L/ixi4=
|
||||
github.com/klauspost/compress v1.17.2/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
|
||||
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
|
||||
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA=
|
||||
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/mojocn/base64Captcha v1.3.6 h1:gZEKu1nsKpttuIAQgWHO+4Mhhls8cAKyiV2Ew03H+Tw=
|
||||
github.com/mojocn/base64Captcha v1.3.6/go.mod h1:i5CtHvm+oMbj1UzEPXaA8IH/xHFZ3DGY3Wh3dBpZ28E=
|
||||
github.com/nicksnyder/go-i18n/v2 v2.4.0 h1:3IcvPOAvnCKwNm0TB0dLDTuawWEj+ax/RERNC+diLMM=
|
||||
github.com/nicksnyder/go-i18n/v2 v2.4.0/go.mod h1:nxYSZE9M0bf3Y70gPQjN9ha7XNHX7gMc814+6wVyEI4=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
|
||||
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
|
||||
github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
|
||||
github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
|
||||
github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
|
||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
|
||||
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
|
||||
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
|
||||
github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
|
||||
github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
|
||||
github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
|
||||
github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
||||
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
|
||||
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI=
|
||||
github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
||||
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
||||
github.com/swaggo/files v1.0.1 h1:J1bVJ4XHZNq0I46UU90611i9/YzdrF7x92oX1ig5IdE=
|
||||
github.com/swaggo/files v1.0.1/go.mod h1:0qXmMNH6sXNf+73t65aKeB+ApmgxdnkQzVTAj2uaMUg=
|
||||
github.com/swaggo/gin-swagger v1.6.0 h1:y8sxvQ3E20/RCyrXeFfg60r6H0Z+SwpTjMYsMm+zy8M=
|
||||
github.com/swaggo/gin-swagger v1.6.0/go.mod h1:BG00cCEy294xtVpyIAHG6+e2Qzj/xKlRdOqDkvq0uzo=
|
||||
github.com/swaggo/swag v1.16.3 h1:PnCYjPCah8FK4I26l2F/KQ4yz3sILcVUN3cTlBFA9Pg=
|
||||
github.com/swaggo/swag v1.16.3/go.mod h1:DImHIuOFXKpMFAQjcC7FG4m3Dg4+QuUgUzJmKjI/gRk=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
|
||||
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||
github.com/xlzd/gotp v0.1.0 h1:37blvlKCh38s+fkem+fFh7sMnceltoIEBYTVXyoa5Po=
|
||||
github.com/xlzd/gotp v0.1.0/go.mod h1:ndLJ3JKzi3xLmUProq4LLxCuECL93dG9WASNLpHz8qg=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
|
||||
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
|
||||
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
|
||||
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
|
||||
go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
|
||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||
golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc=
|
||||
golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
|
||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
|
||||
golang.org/x/image v0.13.0 h1:3cge/F/QTkNLauhf2QoE9zp+7sr+ZcL4HnoZmdwg9sg=
|
||||
golang.org/x/image v0.13.0/go.mod h1:6mmbMOeV28HuMTgA6OSRkdXKYw/t5W9Uwn2Yv1r3Yxk=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
|
||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
|
||||
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk=
|
||||
golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
|
||||
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
|
||||
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
||||
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gorm.io/gorm v1.25.11 h1:/Wfyg1B/je1hnDx3sMkX+gAlxrlZpn6X0BXRlwXlvHg=
|
||||
gorm.io/gorm v1.25.11/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
modernc.org/libc v1.22.5 h1:91BNch/e5B0uPbJFgqbxXuOnxBQjlS//icfQEGmvyjE=
|
||||
modernc.org/libc v1.22.5/go.mod h1:jj+Z7dTNX8fBScMVNRAYZ/jF91K8fdT2hYMThc3YjBY=
|
||||
modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ=
|
||||
modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
|
||||
modernc.org/memory v1.5.0 h1:N+/8c5rE6EqugZwHii4IFsaJ7MUhoWX07J5tC/iI5Ds=
|
||||
modernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
|
||||
modernc.org/sqlite v1.23.1 h1:nrSBg4aRQQwq59JpvGEQ15tNxoO5pX/kUjcRNwSAGQM=
|
||||
modernc.org/sqlite v1.23.1/go.mod h1:OrDj17Mggn6MhE+iPbBNf7RGKODDE9NFT0f3EwDzJqk=
|
||||
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
|
||||
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
98
core/i18n/i18n.go
Normal file
98
core/i18n/i18n.go
Normal file
@ -0,0 +1,98 @@
|
||||
package i18n
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"strings"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/core/global"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/nicksnyder/go-i18n/v2/i18n"
|
||||
"golang.org/x/text/language"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func GetMsgWithMap(key string, maps map[string]interface{}) string {
|
||||
var content string
|
||||
if maps == nil {
|
||||
content, _ = global.I18n.Localize(&i18n.LocalizeConfig{
|
||||
MessageID: key,
|
||||
})
|
||||
} else {
|
||||
content, _ = global.I18n.Localize(&i18n.LocalizeConfig{
|
||||
MessageID: key,
|
||||
TemplateData: maps,
|
||||
})
|
||||
}
|
||||
content = strings.ReplaceAll(content, ": <no value>", "")
|
||||
if content == "" {
|
||||
return key
|
||||
} else {
|
||||
return content
|
||||
}
|
||||
}
|
||||
|
||||
func GetMsgWithName(key string, name string, err error) string {
|
||||
var (
|
||||
content string
|
||||
dataMap = make(map[string]interface{})
|
||||
)
|
||||
dataMap["name"] = name
|
||||
if err != nil {
|
||||
dataMap["err"] = err.Error()
|
||||
}
|
||||
content, _ = global.I18n.Localize(&i18n.LocalizeConfig{
|
||||
MessageID: key,
|
||||
TemplateData: dataMap,
|
||||
})
|
||||
content = strings.ReplaceAll(content, "<no value>", "")
|
||||
if content == "" {
|
||||
return key
|
||||
} else {
|
||||
return content
|
||||
}
|
||||
}
|
||||
|
||||
func GetErrMsg(key string, maps map[string]interface{}) string {
|
||||
var content string
|
||||
if maps == nil {
|
||||
content, _ = global.I18n.Localize(&i18n.LocalizeConfig{
|
||||
MessageID: key,
|
||||
})
|
||||
} else {
|
||||
content, _ = global.I18n.Localize(&i18n.LocalizeConfig{
|
||||
MessageID: key,
|
||||
TemplateData: maps,
|
||||
})
|
||||
}
|
||||
return content
|
||||
}
|
||||
|
||||
func GetMsgByKey(key string) string {
|
||||
content, _ := global.I18n.Localize(&i18n.LocalizeConfig{
|
||||
MessageID: key,
|
||||
})
|
||||
return content
|
||||
}
|
||||
|
||||
//go:embed lang/*
|
||||
var fs embed.FS
|
||||
var bundle *i18n.Bundle
|
||||
|
||||
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")
|
||||
}
|
198
core/i18n/lang/en.yaml
Normal file
198
core/i18n/lang/en.yaml
Normal file
@ -0,0 +1,198 @@
|
||||
ErrInvalidParams: "Request parameter error: {{ .detail }}"
|
||||
ErrTokenParse: "Token generation error: {{ .detail }}"
|
||||
ErrInitialPassword: "Initial password error"
|
||||
ErrInternalServer: "Service internal error: {{ .detail }}"
|
||||
ErrRecordExist: "Record already exists"
|
||||
ErrRecordNotFound: "Records not found"
|
||||
ErrStructTransform: "Type conversion failure: {{ .detail }}"
|
||||
ErrNotLogin: "User is not Login: {{ .detail }}"
|
||||
ErrPasswordExpired: "The current password has expired: {{ .detail }}"
|
||||
ErrNotSupportType: "The system does not support the current type: {{ .detail }}"
|
||||
|
||||
#common
|
||||
ErrNameIsExist: "Name is already exist"
|
||||
ErrDemoEnvironment: "Demo server, prohibit this operation!"
|
||||
ErrCmdTimeout: "Command execution timed out!"
|
||||
ErrCmdIllegal: "The command contains illegal characters. Please modify and try again!"
|
||||
ErrPortExist: '{{ .port }} port is already occupied by {{ .type }} [{{ .name }}]'
|
||||
TYPE_APP: "Application"
|
||||
TYPE_RUNTIME: "Runtime environment"
|
||||
TYPE_DOMAIN: "Domain name"
|
||||
ErrTypePort: 'Port {{ .name }} format error'
|
||||
ErrTypePortRange: 'Port range needs to be between 1-65535'
|
||||
Success: "Success"
|
||||
Failed: "Failed"
|
||||
SystemRestart: "System restart causes task interruption"
|
||||
|
||||
#app
|
||||
ErrPortInUsed: "{{ .detail }} port already in use"
|
||||
ErrAppLimit: "App exceeds install limit"
|
||||
ErrAppRequired: "{{ .detail }} app is required"
|
||||
ErrNotInstall: "App not installed"
|
||||
ErrPortInOtherApp: "{{ .port }} port already in use by app {{ .apps }}"
|
||||
ErrDbUserNotValid: "Stock database, username and password do not match!"
|
||||
ErrDockerComposeNotValid: "docker-compose file format error!"
|
||||
ErrUpdateBuWebsite: 'The application was updated successfully, but the modification of the website configuration file failed, please check the configuration!'
|
||||
Err1PanelNetworkFailed: 'Default container network creation failed! {{ .detail }}'
|
||||
ErrFileParse: 'Application docker-compose file parsing failed!'
|
||||
ErrInstallDirNotFound: 'installation directory does not exist'
|
||||
AppStoreIsUpToDate: 'The app store is already up to date!'
|
||||
LocalAppVersionNull: 'The {{.name}} app is not synced to version! Could not add to application list'
|
||||
LocalAppVersionErr: '{{.name}} failed to sync version {{.version}}! {{.err}}'
|
||||
ErrFileNotFound: '{{.name}} file does not exist'
|
||||
ErrFileParseApp: 'Failed to parse {{.name}} file {{.err}}'
|
||||
ErrAppDirNull: 'version folder does not exist'
|
||||
LocalAppErr: "App {{.name}} sync failed! {{.err}}"
|
||||
ErrContainerName: "ContainerName is already exist"
|
||||
ErrAppSystemRestart: "1Panel restart causes the task to terminate"
|
||||
ErrCreateHttpClient: "Failed to create HTTP request {{.err}}"
|
||||
ErrHttpReqTimeOut: "Request timed out {{.err}}"
|
||||
ErrHttpReqFailed: "Request failed {{.err}}"
|
||||
ErrHttpReqNotFound: "The file does not exist"
|
||||
ErrNoSuchHost: "Network connection failed"
|
||||
ErrImagePullTimeOut: 'Image pull timeout'
|
||||
ErrContainerNotFound: '{{ .name }} container does not exist'
|
||||
ErrContainerMsg: '{{ .name }} container is abnormal, please check the log on the container page for details'
|
||||
ErrAppBackup: '{{ .name }} application backup failed err {{.err}}'
|
||||
ErrImagePull: '{{ .name }} image pull failed err {{.err}}'
|
||||
ErrVersionTooLow: 'The current 1Panel version is too low to update the app store, please upgrade the version'
|
||||
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"
|
||||
ErrAppUpgrade: "Failed to upgrade application {{ .name }} {{ .err }}"
|
||||
AppRecover: "App {{ .name }} rolled back "
|
||||
PullImageStart: "Start pulling image {{ .name }}"
|
||||
PullImageSuccess: "Image pulled successfully"
|
||||
UpgradeAppStart: "Start upgrading application {{ .name }}"
|
||||
UpgradeAppSuccess: "App {{ .name }} upgraded successfully"
|
||||
|
||||
#file
|
||||
ErrFileCanNotRead: "File can not read"
|
||||
ErrFileToLarge: "file is too large"
|
||||
ErrPathNotFound: "Path is not found"
|
||||
ErrMovePathFailed: "The target path cannot contain the original path!"
|
||||
ErrLinkPathNotFound: "Target path does not exist!"
|
||||
ErrFileIsExist: "File or directory already exists!"
|
||||
ErrFileUpload: "Failed to upload file {{.name}} {{.detail}}"
|
||||
ErrFileDownloadDir: "Download folder not supported"
|
||||
ErrCmdNotFound: "{{ .name}} command does not exist, please install this command on the host first"
|
||||
ErrSourcePathNotFound: "Source directory does not exist"
|
||||
ErrFavoriteExist: "This path has been collected"
|
||||
ErrInvalidChar: "Illegal characters are prohibited"
|
||||
|
||||
#website
|
||||
ErrDomainIsExist: "Domain is already exist"
|
||||
ErrAliasIsExist: "Alias is already exist"
|
||||
ErrAppDelete: 'Other Website use this App'
|
||||
ErrGroupIsUsed: 'The group is in use and cannot be deleted'
|
||||
ErrBackupMatch: 'the backup file does not match the current partial data of the website: {{ .detail}}'
|
||||
ErrBackupExist: 'the backup file corresponds to a portion of the original data that does not exist: {{ .detail}}'
|
||||
ErrPHPResource: 'The local runtime does not support switching!'
|
||||
ErrPathPermission: 'A folder with non-1000:1000 permissions was detected in the index directory, which may cause an Access denied error when accessing the website. Please click the save button above'
|
||||
ErrDomainIsUsed: "Domain is already used by website {{ .name }}"
|
||||
ErrDomainFormat: "{{ .name }} domain format error"
|
||||
ErrDefaultAlias: "default is a reserved code name, please use another code name"
|
||||
|
||||
#ssl
|
||||
ErrSSLCannotDelete: "The certificate {{ .name }} is being used by the website and cannot be removed"
|
||||
ErrAccountCannotDelete: "The certificate associated with the account cannot be deleted"
|
||||
ErrSSLApply: "The certificate continues to be signed successfully, but openresty reload fails, please check the configuration!"
|
||||
ErrEmailIsExist: 'Email is already exist'
|
||||
ErrSSLKeyNotFound: 'The private key file does not exist'
|
||||
ErrSSLCertificateNotFound: 'The certificate file does not exist'
|
||||
ErrSSLKeyFormat: 'Private key file verification error'
|
||||
ErrSSLCertificateFormat: 'Certificate file format error, please use pem format'
|
||||
ErrEabKidOrEabHmacKeyCannotBlank: 'EabKid or EabHmacKey cannot be empty'
|
||||
ErrOpenrestyNotFound: 'Http mode requires Openresty to be installed first'
|
||||
ApplySSLStart: 'Start applying for certificate, domain name [{{ .domain }}] application method [{{ .type }}] '
|
||||
dnsAccount: "DNS Automatic"
|
||||
dnsManual: "DNS Manual"
|
||||
http: "HTTP"
|
||||
ApplySSLFailed: 'Application for [{{ .domain }}] certificate failed, {{.detail}} '
|
||||
ApplySSLSuccess: 'Application for [{{ .domain }}] certificate successful! ! '
|
||||
DNSAccountName: 'DNS account [{{ .name }}] manufacturer [{{.type}}]'
|
||||
PushDirLog: 'Certificate pushed to directory [{{ .path }}] {{ .status }}'
|
||||
ErrDeleteCAWithSSL: "There is an issued certificate under the current organization and cannot be deleted"
|
||||
ErrDeleteWithPanelSSL: "Panel SSL configuration uses this certificate and cannot be deleted"
|
||||
ErrDefaultCA: "The default organization cannot be deleted"
|
||||
ApplyWebSiteSSLLog: "Start updating {{ .name }} website certificate"
|
||||
ErrUpdateWebsiteSSL: "{{ .name }} website failed to update certificate: {{ .err }}"
|
||||
ApplyWebSiteSSLSuccess: "Update website certificate successfully"
|
||||
|
||||
#mysql
|
||||
ErrUserIsExist: "The current user already exists. Please enter a new user"
|
||||
ErrDatabaseIsExist: "The current database already exists. Please enter a new database"
|
||||
ErrExecTimeOut: "SQL execution timed out, please check the database"
|
||||
ErrRemoteExist: "The remote database already exists with that name, please modify it and try again"
|
||||
ErrLocalExist: "The local database already exists with that name, please modify it and try again"
|
||||
|
||||
#redis
|
||||
ErrTypeOfRedis: "The recovery file type does not match the current persistence mode. Modify the file type and try again"
|
||||
|
||||
#container
|
||||
ErrInUsed: "{{ .detail }} is in use and cannot be deleted"
|
||||
ErrObjectInUsed: "This object is in use and cannot be deleted"
|
||||
ErrPortRules: "The number of ports does not match, please re-enter!"
|
||||
ErrPgImagePull: "Image pull timeout. Please configure image acceleration or manually pull the postgres:16.0-alpine image and try again"
|
||||
|
||||
#runtime
|
||||
ErrDirNotFound: "The build folder does not exist! Please check file integrity!"
|
||||
ErrFileNotExist: "{{ .detail }} file does not exist! Please check source file integrity!"
|
||||
ErrImageBuildErr: "Image build failed"
|
||||
ErrImageExist: "Image is already exist!"
|
||||
ErrDelWithWebsite: "The operating environment has been associated with a website and cannot be deleted"
|
||||
ErrRuntimeStart: "Failed to start"
|
||||
ErrPackageJsonNotFound: "package.json file does not exist"
|
||||
ErrScriptsNotFound: "No scripts configuration item was found in package.json"
|
||||
ErrContainerNameNotFound: "Unable to get container name, please check .env file"
|
||||
ErrNodeModulesNotFound: "The node_modules folder does not exist! Please edit the running environment or wait for the running environment to start successfully"
|
||||
|
||||
#setting
|
||||
ErrBackupInUsed: "The backup account is already being used in a cronjob and cannot be deleted."
|
||||
ErrBackupCheck: "Backup account test connection failed {{ .err}}"
|
||||
ErrOSSConn: "Unable to retrieve the latest version, please check if the server can connect to the external network."
|
||||
ErrEntrance: "Security entrance information error. Please check and try again!"
|
||||
|
||||
#tool
|
||||
ErrConfigNotFound: "Configuration file does not exist"
|
||||
ErrConfigParse: "Configuration file format error"
|
||||
ErrConfigIsNull: "The configuration file is not allowed to be empty"
|
||||
ErrConfigDirNotFound: "The running directory does not exist"
|
||||
ErrConfigAlreadyExist: "A configuration file with the same name already exists"
|
||||
ErrUserFindErr: "Failed to find user {{ .name }} {{ .err }}"
|
||||
|
||||
#ssh
|
||||
ErrFirewall: "No firewalld or ufw service is detected. Please check and try again!"
|
||||
|
||||
#cronjob
|
||||
ErrBashExecute: "Script execution error, please check the specific information in the task output text area."
|
||||
ErrCutWebsiteLog: "{{ .name }} website log cutting failed, error {{ .err }}"
|
||||
CutWebsiteLogSuccess: "{{ .name }} website log cut successfully, backup path {{ .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!"
|
||||
ErrClamdscanNotFound: "The clamdscan command was not detected, please refer to the documentation to install it!"
|
||||
|
||||
#waf
|
||||
ErrScope: "Modification of this configuration is not supported"
|
||||
ErrStateChange: "State modification failed"
|
||||
ErrRuleExist: "Rule is Exist"
|
||||
ErrRuleNotExist: "Rule is not Exist"
|
||||
ErrParseIP: "IP format error"
|
||||
ErrDefaultIP: "default is a reserved name, please change it to another name"
|
||||
ErrGroupInUse: "The IP group is used by the black/white list and cannot be deleted"
|
||||
ErrGroupExist: "IP group name already exists"
|
||||
ErrIPRange: "Wrong IP range"
|
||||
ErrIPExist: "IP is exit"
|
||||
|
||||
#license
|
||||
ErrLicense: "License format error, please check and try again!"
|
||||
ErrLicenseCheck: "License verification failed, please check and try again!"
|
||||
ErrLicenseSave: "Failed to save license information, error {{ .err }}, please try again!"
|
||||
ErrLicenseSync: "Failed to sync license information, no license information detected in the database!"
|
||||
ErrXpackNotFound: "This section is a professional edition feature, please import the license first in Panel Settings-License interface"
|
||||
ErrXpackNotActive: "This section is a professional edition feature, please synchronize the license status first in Panel Settings-License interface"
|
||||
ErrXpackOutOfDate: "The current license has expired, please re-import the license in Panel Settings-License interface"
|
200
core/i18n/lang/zh-Hant.yaml
Normal file
200
core/i18n/lang/zh-Hant.yaml
Normal file
@ -0,0 +1,200 @@
|
||||
ErrInvalidParams: "請求參數錯誤: {{ .detail }}"
|
||||
ErrTokenParse: "Token 產生錯誤: {{ .detail }}"
|
||||
ErrInitialPassword: "原密碼錯誤"
|
||||
ErrInternalServer: "伺服器內部錯誤: {{ .detail }}"
|
||||
ErrRecordExist: "記錄已存在"
|
||||
ErrRecordNotFound: "記錄未找到"
|
||||
ErrStructTransform: "類型轉換失敗: {{ .detail }}"
|
||||
ErrNotLogin: "用戶未登入: {{ .detail }}"
|
||||
ErrPasswordExpired: "當前密碼已過期: {{ .detail }}"
|
||||
ErrNotSupportType: "系統暫不支持當前類型: {{ .detail }}"
|
||||
|
||||
#common
|
||||
ErrNameIsExist: "名稱已存在"
|
||||
ErrDemoEnvironment: "演示伺服器,禁止此操作!"
|
||||
ErrCmdTimeout: "指令執行超時!"
|
||||
ErrCmdIllegal: "執行命令中存在不合法字符,請修改後重試!"
|
||||
ErrPortExist: '{{ .port }} 埠已被 {{ .type }} [{{ .name }}] 佔用'
|
||||
TYPE_APP: "應用"
|
||||
TYPE_RUNTIME: "運作環境"
|
||||
TYPE_DOMAIN: "網域名稱"
|
||||
ErrTypePort: '埠 {{ .name }} 格式錯誤'
|
||||
ErrTypePortRange: '連接埠範圍需要在 1-65535 之間'
|
||||
Success: "成功"
|
||||
Failed: "失敗"
|
||||
SystemRestart: "系統重啟導致任務中斷"
|
||||
ErrInvalidChar: "禁止使用非法字元"
|
||||
|
||||
#app
|
||||
ErrPortInUsed: "{{ .detail }} 端口已被佔用!"
|
||||
ErrAppLimit: "應用超出安裝數量限制"
|
||||
ErrAppRequired: "請先安裝 {{ .detail }} 應用"
|
||||
ErrNotInstall: "應用未安裝"
|
||||
ErrPortInOtherApp: "{{ .port }} 端口已被應用 {{ .apps }} 佔用!"
|
||||
ErrDbUserNotValid: "儲存資料庫,用戶名密碼不匹配!"
|
||||
ErrDockerComposeNotValid: "docker-compose 文件格式錯誤"
|
||||
ErrUpdateBuWebsite: '應用更新成功,但是網站配置文件修改失敗,請檢查配置!'
|
||||
Err1PanelNetworkFailed: '默認容器網絡創建失敗!{{ .detail }}'
|
||||
ErrFileParse: '應用 docker-compose 文件解析失敗!'
|
||||
ErrInstallDirNotFound: '安裝目錄不存在'
|
||||
AppStoreIsUpToDate: '應用商店已經是最新版本'
|
||||
LocalAppVersionNull: '{{.name}} 應用未同步到版本!無法添加到應用列表'
|
||||
LocalAppVersionErr: '{{.name}} 同步版本 {{.version}} 失敗!{{.err}}'
|
||||
ErrFileNotFound: '{{.name}} 文件不存在'
|
||||
ErrFileParseApp: '{{.name}} 文件解析失敗 {{.err}}'
|
||||
ErrAppDirNull: '版本資料夾不存在'
|
||||
LocalAppErr: "應用 {{.name}} 同步失敗!{{.err}}"
|
||||
ErrContainerName: "容器名稱已存在"
|
||||
ErrAppSystemRestart: "1Panel 重啟導致任務中斷"
|
||||
ErrCreateHttpClient: "創建HTTP請求失敗 {{.err}}"
|
||||
ErrHttpReqTimeOut: "請求超時 {{.err}}"
|
||||
ErrHttpReqFailed: "請求失敗 {{.err}}"
|
||||
ErrHttpReqNotFound: "文件不存在"
|
||||
ErrNoSuchHost: "網路連接失敗"
|
||||
ErrImagePullTimeOut: "鏡像拉取超時"
|
||||
ErrContainerNotFound: '{{ .name }} 容器不存在'
|
||||
ErrContainerMsg: '{{ .name }} 容器異常,具體請在容器頁面查看日誌'
|
||||
ErrAppBackup: '{{ .name }} 應用備份失敗 err {{.err}}'
|
||||
ErrImagePull: '{{ .name }} 鏡像拉取失敗 err {{.err}}'
|
||||
ErrVersionTooLow: '當前 1Panel 版本過低,無法更新應用商店,請升級版本之後操作'
|
||||
ErrAppNameExist: '應用名稱已存在'
|
||||
AppStoreIsSyncing: '應用程式商店正在同步中,請稍後再試'
|
||||
ErrGetCompose: "docker-compose.yml 檔案取得失敗!{{ .detail }}"
|
||||
ErrAppWarn: "狀態異常,請查看日誌"
|
||||
ErrAppParamKey: "參數 {{ .name }} 欄位異常"
|
||||
ErrAppUpgrade: "應用程式 {{ .name }} 升級失敗 {{ .err }}"
|
||||
AppRecover: "應用程式 {{ .name }} 回滾 "
|
||||
PullImageStart: "開始拉取鏡像 {{ .name }}"
|
||||
PullImageSuccess: "鏡像拉取成功"
|
||||
UpgradeAppStart: "開始升級應用程式 {{ .name }}"
|
||||
UpgradeAppSuccess: "應用程式 {{ .name }} 升級成功"
|
||||
|
||||
#file
|
||||
ErrFileCanNotRead: "此文件不支持預覽"
|
||||
ErrFileToLarge: "文件超過10M,無法打開"
|
||||
ErrPathNotFound: "目錄不存在"
|
||||
ErrMovePathFailed: "目標路徑不能包含原路徑!"
|
||||
ErrLinkPathNotFound: "目標路徑不存在!"
|
||||
ErrFileIsExist: "文件或文件夾已存在!"
|
||||
ErrFileUpload: "{{ .name }} 上傳文件失敗 {{ .detail}}"
|
||||
ErrFileDownloadDir: "不支持下載文件夾"
|
||||
ErrCmdNotFound: "{{ .name}} 命令不存在,請先在宿主機安裝此命令"
|
||||
ErrSourcePathNotFound: "源目錄不存在"
|
||||
ErrFavoriteExist: "已收藏此路徑"
|
||||
|
||||
#website
|
||||
ErrDomainIsExist: "域名已存在"
|
||||
ErrAliasIsExist: "代號已存在"
|
||||
ErrAppDelete: '其他網站使用此應用,無法刪除'
|
||||
ErrGroupIsUsed: '分組正在使用中,無法刪除'
|
||||
ErrBackupMatch: '該備份文件與當前網站部分數據不匹配: {{ .detail}}'
|
||||
ErrBackupExist: '該備份文件對應部分原數據不存在: {{ .detail}}'
|
||||
ErrPHPResource: '本地運行環境不支持切換!'
|
||||
ErrPathPermission: 'index 目錄下偵測到非 1000:1000 權限資料夾,可能導致網站存取 Access denied 錯誤,請點擊上方儲存按鈕'
|
||||
ErrDomainIsUsed: "域名已被網站【{{ .name }}】使用"
|
||||
ErrDomainFormat: "{{ .name }} 域名格式不正確"
|
||||
ErrDefaultAlias: "default 為保留代號,請使用其他代號"
|
||||
|
||||
#ssl
|
||||
ErrSSLCannotDelete: "{{ .name }} 證書正在被網站使用,無法刪除"
|
||||
ErrAccountCannotDelete: "帳號關聯證書,無法刪除"
|
||||
ErrSSLApply: "證書續簽成功,openresty reload失敗,請檢查配置!"
|
||||
ErrEmailIsExist: '郵箱已存在'
|
||||
ErrSSLKeyNotFound: '私鑰文件不存在'
|
||||
ErrSSLCertificateNotFound: '證書文件不存在'
|
||||
ErrSSLKeyFormat: '私鑰文件校驗錯誤'
|
||||
ErrSSLCertificateFormat: '證書文件格式錯誤,請使用 pem 格式'
|
||||
ErrEabKidOrEabHmacKeyCannotBlank: 'EabKid 或 EabHmacKey 不能為空'
|
||||
ErrOpenrestyNotFound: 'Http 模式需要先安裝 Openresty'
|
||||
ApplySSLStart: '開始申請憑證,網域 [{{ .domain }}] 申請方式 [{{ .type }}] '
|
||||
dnsAccount: "DNS 自動"
|
||||
dnsManual: "DNS 手排"
|
||||
http: "HTTP"
|
||||
ApplySSLFailed: '申請 [{{ .domain }}] 憑證失敗, {{.detail}} '
|
||||
ApplySSLSuccess: '申請 [{{ .domain }}] 憑證成功! ! '
|
||||
DNSAccountName: 'DNS 帳號 [{{ .name }}] 廠商 [{{.type}}]'
|
||||
PushDirLog: '憑證推送到目錄 [{{ .path }}] {{ .status }}'
|
||||
ErrDeleteCAWithSSL: "目前機構下存在已簽發證書,無法刪除"
|
||||
ErrDeleteWithPanelSSL: "面板 SSL 配置使用此證書,無法刪除"
|
||||
ErrDefaultCA: "默認機構不能刪除"
|
||||
ApplyWebSiteSSLLog: "開始更新 {{ .name }} 網站憑證"
|
||||
ErrUpdateWebsiteSSL: "{{ .name }} 網站更新憑證失敗: {{ .err }}"
|
||||
ApplyWebSiteSSLSuccess: "更新網站憑證成功"
|
||||
|
||||
|
||||
#mysql
|
||||
ErrUserIsExist: "當前用戶已存在,請重新輸入"
|
||||
ErrDatabaseIsExist: "當前資料庫已存在,請重新輸入"
|
||||
ErrExecTimeOut: "SQL 執行超時,請檢查數據庫"
|
||||
ErrRemoteExist: "遠程數據庫已存在該名稱,請修改後重試"
|
||||
ErrLocalExist: "本地數據庫已存在該名稱,請修改後重試"
|
||||
|
||||
#redis
|
||||
ErrTypeOfRedis: "恢復文件類型與當前持久化方式不符,請修改後重試"
|
||||
|
||||
#container
|
||||
ErrInUsed: "{{ .detail }} 正被使用,無法刪除"
|
||||
ErrObjectInUsed: "該對象正被使用,無法刪除"
|
||||
ErrPortRules: "端口數目不匹配,請重新輸入!"
|
||||
ErrPgImagePull: "鏡像拉取超時,請配置鏡像加速或手動拉取 postgres:16.0-alpine 鏡像後重試"
|
||||
|
||||
#runtime
|
||||
ErrDirNotFound: "build 文件夾不存在!請檢查文件完整性!"
|
||||
ErrFileNotExist: "{{ .detail }} 文件不存在!請檢查源文件完整性!"
|
||||
ErrImageBuildErr: "鏡像 build 失敗"
|
||||
ErrImageExist: "鏡像已存在!"
|
||||
ErrDelWithWebsite: "運行環境已經關聯網站,無法刪除"
|
||||
ErrRuntimeStart: "啟動失敗"
|
||||
ErrPackageJsonNotFound: "package.json 文件不存在"
|
||||
ErrScriptsNotFound: "沒有在 package.json 中找到 scripts 配置項"
|
||||
ErrContainerNameNotFound: "無法取得容器名稱,請檢查 .env 文件"
|
||||
ErrNodeModulesNotFound: "node_modules 文件夾不存在!請編輯運行環境或者等待運行環境啟動成功"
|
||||
|
||||
#setting
|
||||
ErrBackupInUsed: "該備份帳號已在計劃任務中使用,無法刪除"
|
||||
ErrBackupCheck: "備份帳號測試連接失敗 {{ .err}}"
|
||||
ErrOSSConn: "無法獲取最新版本,請確認伺服器是否能夠連接外部網路。"
|
||||
ErrEntrance: "安全入口信息錯誤,請檢查後重試!"
|
||||
|
||||
#tool
|
||||
ErrConfigNotFound: "配置文件不存在"
|
||||
ErrConfigParse: "配置文件格式有誤"
|
||||
ErrConfigIsNull: "配置文件不允許為空"
|
||||
ErrConfigDirNotFound: "運行目錄不存在"
|
||||
ErrConfigAlreadyExist: "已存在同名配置文件"
|
||||
ErrUserFindErr: "用戶 {{ .name }} 查找失敗 {{ .err }}"
|
||||
|
||||
#ssh
|
||||
ErrFirewall: "當前未檢測到系統 firewalld 或 ufw 服務,請檢查後重試!"
|
||||
|
||||
#cronjob
|
||||
ErrBashExecute: "腳本執行錯誤,請在任務輸出文本域中查看具體信息。"
|
||||
ErrCutWebsiteLog: "{{ .name }} 網站日誌切割失敗,錯誤 {{ .err }}"
|
||||
CutWebsiteLogSuccess: "{{ .name }} 網站日誌切割成功,備份路徑 {{ .path }}"
|
||||
|
||||
#toolbox
|
||||
ErrNotExistUser: "當前使用者不存在,請修改後重試!"
|
||||
ErrBanAction: "設置失敗,當前 {{ .name }} 服務不可用,請檢查後重試!"
|
||||
ErrClamdscanNotFound: "未偵測到 clamdscan 指令,請參考文件安裝!"
|
||||
|
||||
#waf
|
||||
ErrScope: "不支援修改此配置"
|
||||
ErrStateChange: "狀態修改失敗"
|
||||
ErrRuleExist: "規則已存在"
|
||||
ErrRuleNotExist: "規則不存在"
|
||||
ErrParseIP: "IP 格式錯誤"
|
||||
ErrDefaultIP: "default 為保留名稱,請更換其他名稱"
|
||||
ErrGroupInUse: "IP 群組被黑/白名單使用,無法刪除"
|
||||
ErrGroupExist: "IP 群組名稱已存在"
|
||||
ErrIPRange: "IP 範圍錯誤"
|
||||
ErrIPExist: "IP 已存在"
|
||||
|
||||
|
||||
#license
|
||||
ErrLicense: "許可證格式錯誤,請檢查後重試!"
|
||||
ErrLicenseCheck: "許可證校驗失敗,請檢查後重試!"
|
||||
ErrLicenseSave: "許可證信息保存失敗,錯誤 {{ .err }}, 請重試!"
|
||||
ErrLicenseSync: "許可證信息同步失敗,資料庫中未檢測到許可證信息!"
|
||||
ErrXpackNotFound: "該部分為專業版功能,請先在 面板設置-許可證 界面導入許可證"
|
||||
ErrXpackNotActive: "該部分為專業版功能,請先在 面板設置-許可證 界面同步許可證狀態"
|
||||
ErrXpackOutOfDate: "當前許可證已過期,請重新在 面板設置-許可證 界面導入許可證"
|
202
core/i18n/lang/zh.yaml
Normal file
202
core/i18n/lang/zh.yaml
Normal file
@ -0,0 +1,202 @@
|
||||
ErrInvalidParams: "请求参数错误: {{ .detail }}"
|
||||
ErrTokenParse: "Token 生成错误: {{ .detail }}"
|
||||
ErrInitialPassword: "原密码错误"
|
||||
ErrInternalServer: "服务内部错误: {{ .detail }}"
|
||||
ErrRecordExist: "记录已存在"
|
||||
ErrRecordNotFound: "记录未能找到"
|
||||
ErrStructTransform: "类型转换失败: {{ .detail }}"
|
||||
ErrNotLogin: "用户未登录: {{ .detail }}"
|
||||
ErrPasswordExpired: "当前密码已过期: {{ .detail }}"
|
||||
ErrNotSupportType: "系统暂不支持当前类型: {{ .detail }}"
|
||||
|
||||
#common
|
||||
ErrNameIsExist: "名称已存在"
|
||||
ErrDemoEnvironment: "演示服务器,禁止此操作!"
|
||||
ErrCmdTimeout: "命令执行超时!"
|
||||
ErrCmdIllegal: "执行命令中存在不合法字符,请修改后重试!"
|
||||
ErrPortExist: '{{ .port }} 端口已被 {{ .type }} [{{ .name }}] 占用'
|
||||
TYPE_APP: "应用"
|
||||
TYPE_RUNTIME: "运行环境"
|
||||
TYPE_DOMAIN: "域名"
|
||||
ErrTypePort: '端口 {{ .name }} 格式错误'
|
||||
ErrTypePortRange: '端口范围需要在 1-65535 之间'
|
||||
Success: "成功"
|
||||
Failed: "失败"
|
||||
SystemRestart: "系统重启导致任务中断"
|
||||
|
||||
#app
|
||||
ErrPortInUsed: "{{ .detail }} 端口已被占用!"
|
||||
ErrAppLimit: "应用超出安装数量限制"
|
||||
ErrAppRequired: "请先安装 {{ .detail }} 应用"
|
||||
ErrNotInstall: "应用未安装"
|
||||
ErrPortInOtherApp: "{{ .port }} 端口已被应用 {{ .apps }} 占用!"
|
||||
ErrDbUserNotValid: "存量数据库,用户名密码不匹配!"
|
||||
ErrDockerComposeNotValid: "docker-compose 文件格式错误"
|
||||
ErrUpdateBuWebsite: '应用更新成功,但是网站配置文件修改失败,请检查配置!'
|
||||
Err1PanelNetworkFailed: '默认容器网络创建失败!{{ .detail }}'
|
||||
ErrFileParse: '应用 docker-compose 文件解析失败!'
|
||||
ErrInstallDirNotFound: '安装目录不存在'
|
||||
AppStoreIsUpToDate: '应用商店已经是最新版本'
|
||||
LocalAppVersionNull: '{{.name}} 应用未同步到版本!无法添加到应用列表'
|
||||
LocalAppVersionErr: '{{.name}} 同步版本 {{.version}} 失败!{{.err}}'
|
||||
ErrFileNotFound: '{{.name}} 文件不存在'
|
||||
ErrFileParseApp: '{{.name}} 文件解析失败 {{.err}}'
|
||||
ErrAppDirNull: '版本文件夹不存在'
|
||||
LocalAppErr: "应用 {{.name}} 同步失败!{{.err}}"
|
||||
ErrContainerName: "容器名称已存在"
|
||||
ErrAppSystemRestart: "1Panel 重启导致任务终止"
|
||||
ErrCreateHttpClient: "创建HTTP请求失败 {{.err}}"
|
||||
ErrHttpReqTimeOut: "请求超时 {{.err}}"
|
||||
ErrHttpReqFailed: "请求失败 {{.err}}"
|
||||
ErrHttpReqNotFound: "文件不存在"
|
||||
ErrNoSuchHost: "网络连接失败"
|
||||
ErrImagePullTimeOut: '镜像拉取超时'
|
||||
ErrContainerNotFound: '{{ .name }} 容器不存在'
|
||||
ErrContainerMsg: '{{ .name }} 容器异常,具体请在容器页面查看日志'
|
||||
ErrAppBackup: '{{ .name }} 应用备份失败 err {{.err}}'
|
||||
ErrImagePull: '镜像拉取失败 {{.err}}'
|
||||
ErrVersionTooLow: '当前 1Panel 版本过低,无法更新应用商店,请升级版本之后操作'
|
||||
ErrAppNameExist: '应用名称已存在'
|
||||
AppStoreIsSyncing: '应用商店正在同步中,请稍后再试'
|
||||
ErrGetCompose: "docker-compose.yml 文件获取失败!{{ .detail }}"
|
||||
ErrAppWarn: "状态异常,请查看日志"
|
||||
ErrAppParamKey: "参数 {{ .name }} 字段异常"
|
||||
ErrAppUpgrade: "应用 {{ .name }} 升级失败 {{ .err }}"
|
||||
AppRecover: "应用 {{ .name }} 回滚 "
|
||||
PullImageStart: "开始拉取镜像 {{ .name }}"
|
||||
PullImageSuccess: "镜像拉取成功"
|
||||
UpgradeAppStart: "开始升级应用 {{ .name }}"
|
||||
UpgradeAppSuccess: "应用 {{ .name }} 升级成功"
|
||||
|
||||
#file
|
||||
ErrFileCanNotRead: "此文件不支持预览"
|
||||
ErrFileToLarge: "文件超过10M,无法打开"
|
||||
ErrPathNotFound: "目录不存在"
|
||||
ErrMovePathFailed: "目标路径不能包含原路径!"
|
||||
ErrLinkPathNotFound: "目标路径不存在!"
|
||||
ErrFileIsExist: "文件或文件夹已存在!"
|
||||
ErrFileUpload: "{{ .name }} 上传文件失败 {{ .detail}}"
|
||||
ErrFileDownloadDir: "不支持下载文件夹"
|
||||
ErrCmdNotFound: "{{ .name}} 命令不存在,请先在宿主机安装此命令"
|
||||
ErrSourcePathNotFound: "源目录不存在"
|
||||
ErrFavoriteExist: "已收藏此路径"
|
||||
ErrInvalidChar: "禁止使用非法字符"
|
||||
|
||||
#website
|
||||
ErrDomainIsExist: "域名已存在"
|
||||
ErrAliasIsExist: "代号已存在"
|
||||
ErrAppDelete: '其他网站使用此应用,无法删除'
|
||||
ErrGroupIsUsed: '分组正在使用中,无法删除'
|
||||
ErrBackupMatch: '该备份文件与当前网站部分数据不匹配 {{ .detail}}'
|
||||
ErrBackupExist: '该备份文件对应部分源数据不存在 {{ .detail}}'
|
||||
ErrPHPResource: '本地运行环境不支持切换!'
|
||||
ErrPathPermission: 'index 目录下检测到非 1000:1000 权限文件夹,可能导致网站访问 Access denied 错误,请点击上方保存按钮'
|
||||
ErrDomainIsUsed: "域名已被网站【{{ .name }}】使用"
|
||||
ErrDomainFormat: "{{ .name }} 域名格式不正确"
|
||||
ErrDefaultAlias: "default 为保留代号,请使用其他代号"
|
||||
|
||||
#ssl
|
||||
ErrSSLCannotDelete: "{{ .name }} 证书正在被网站使用,无法删除"
|
||||
ErrAccountCannotDelete: "账号关联证书,无法删除"
|
||||
ErrSSLApply: "证书续签成功,openresty reload失败,请检查配置!"
|
||||
ErrEmailIsExist: '邮箱已存在'
|
||||
ErrSSLKeyNotFound: '私钥文件不存在'
|
||||
ErrSSLCertificateNotFound: '证书文件不存在'
|
||||
ErrSSLKeyFormat: '私钥文件校验失败'
|
||||
ErrSSLCertificateFormat: '证书文件格式错误,请使用 pem 格式'
|
||||
ErrEabKidOrEabHmacKeyCannotBlank: 'EabKid 或 EabHmacKey 不能为空'
|
||||
ErrOpenrestyNotFound: 'Http 模式需要首先安装 Openresty'
|
||||
ApplySSLStart: '开始申请证书,域名 [{{ .domain }}] 申请方式 [{{ .type }}] '
|
||||
dnsAccount: "DNS 自动"
|
||||
dnsManual: "DNS 手动"
|
||||
http: "HTTP"
|
||||
ApplySSLFailed: '申请 [{{ .domain }}] 证书失败, {{.detail}} '
|
||||
ApplySSLSuccess: '申请 [{{ .domain }}] 证书成功!!'
|
||||
DNSAccountName: 'DNS 账号 [{{ .name }}] 厂商 [{{.type}}]'
|
||||
PushDirLog: '证书推送到目录 [{{ .path }}] {{ .status }}'
|
||||
ErrDeleteCAWithSSL: "当前机构下存在已签发证书,无法删除"
|
||||
ErrDeleteWithPanelSSL: "面板 SSL 配置使用此证书,无法删除"
|
||||
ErrDefaultCA: "默认机构不能删除"
|
||||
ApplyWebSiteSSLLog: "开始更新 {{ .name }} 网站证书"
|
||||
ErrUpdateWebsiteSSL: "{{ .name }} 网站更新证书失败: {{ .err }}"
|
||||
ApplyWebSiteSSLSuccess: "更新网站证书成功"
|
||||
ErrExecShell: "执行脚本失败 {{ .err }}"
|
||||
ExecShellStart: "开始执行脚本"
|
||||
ExecShellSuccess: "脚本执行成功"
|
||||
|
||||
#mysql
|
||||
ErrUserIsExist: "当前用户已存在,请重新输入"
|
||||
ErrDatabaseIsExist: "当前数据库已存在,请重新输入"
|
||||
ErrExecTimeOut: "SQL 执行超时,请检查数据库"
|
||||
ErrRemoteExist: "远程数据库已存在该名称,请修改后重试"
|
||||
ErrLocalExist: "本地数据库已存在该名称,请修改后重试"
|
||||
|
||||
#redis
|
||||
ErrTypeOfRedis: "恢复文件类型与当前持久化方式不符,请修改后重试"
|
||||
|
||||
#container
|
||||
ErrInUsed: "{{ .detail }} 正被使用,无法删除"
|
||||
ErrObjectInUsed: "该对象正被使用,无法删除"
|
||||
ErrPortRules: "端口数目不匹配,请重新输入!"
|
||||
ErrPgImagePull: "镜像拉取超时,请配置镜像加速或手动拉取 postgres:16.0-alpine 镜像后重试"
|
||||
|
||||
#runtime
|
||||
ErrDirNotFound: "build 文件夹不存在!请检查文件完整性!"
|
||||
ErrFileNotExist: "{{ .detail }} 文件不存在!请检查源文件完整性!"
|
||||
ErrImageBuildErr: "镜像 build 失败"
|
||||
ErrImageExist: "镜像已存在!"
|
||||
ErrDelWithWebsite: "运行环境已经关联网站,无法删除"
|
||||
ErrRuntimeStart: "启动失败"
|
||||
ErrPackageJsonNotFound: "package.json 文件不存在"
|
||||
ErrScriptsNotFound: "没有在 package.json 中找到 scripts 配置项"
|
||||
ErrContainerNameNotFound: "无法获取容器名称,请检查 .env 文件"
|
||||
ErrNodeModulesNotFound: "node_modules 文件夹不存在!请编辑运行环境或者等待运行环境启动成功"
|
||||
|
||||
#setting
|
||||
ErrBackupInUsed: "该备份账号已在计划任务中使用,无法删除"
|
||||
ErrBackupCheck: "备份账号测试连接失败 {{ .err}}"
|
||||
ErrOSSConn: "无法获取最新版本,请确认服务器是否能够连接外部网络。"
|
||||
ErrEntrance: "安全入口信息错误,请检查后重试!"
|
||||
|
||||
#tool
|
||||
ErrConfigNotFound: "配置文件不存在"
|
||||
ErrConfigParse: "配置文件格式有误"
|
||||
ErrConfigIsNull: "配置文件不允许为空"
|
||||
ErrConfigDirNotFound: "运行目录不存在"
|
||||
ErrConfigAlreadyExist: "已存在同名配置文件"
|
||||
ErrUserFindErr: "用户 {{ .name }} 查找失败 {{ .err }}"
|
||||
|
||||
#ssh
|
||||
ErrFirewall: "当前未检测到系统 firewalld 或 ufw 服务,请检查后重试!"
|
||||
|
||||
#cronjob
|
||||
ErrBashExecute: "脚本执行错误,请在任务输出文本域中查看具体信息。"
|
||||
ErrCutWebsiteLog: "{{ .name }} 网站日志切割失败,错误 {{ .err }}"
|
||||
CutWebsiteLogSuccess: "{{ .name }} 网站日志切割成功,备份路径 {{ .path }}"
|
||||
|
||||
#toolbox
|
||||
ErrNotExistUser: "当前用户不存在,请修改后重试!"
|
||||
ErrBanAction: "设置失败,当前 {{ .name }} 服务不可用,请检查后重试!"
|
||||
ErrClamdscanNotFound: "未检测到 clamdscan 命令,请参考文档安装!"
|
||||
|
||||
#waf
|
||||
ErrScope: "不支持修改此配置"
|
||||
ErrStateChange: "状态修改失败"
|
||||
ErrRuleExist: "规则已存在"
|
||||
ErrRuleNotExist: "规则不存在"
|
||||
ErrParseIP: "IP 格式错误"
|
||||
ErrDefaultIP: "default 为保留名称,请更换其他名称"
|
||||
ErrGroupInUse: "IP 组被黑/白名单使用,无法删除"
|
||||
ErrGroupExist: "IP 组名称已存在"
|
||||
ErrIPRange: "IP 范围错误"
|
||||
ErrIPExist: "IP 已存在"
|
||||
|
||||
#license
|
||||
ErrLicense: "许可证格式错误,请检查后重试!"
|
||||
ErrLicenseCheck: "许可证校验失败,请检查后重试!"
|
||||
ErrLicenseSave: "许可证信息保存失败,错误 {{ .err }},请重试!"
|
||||
ErrLicenseSync: "许可证信息同步失败,数据库中未检测到许可证信息!"
|
||||
ErrXpackNotFound: "该部分为专业版功能,请先在 面板设置-许可证 界面导入许可证"
|
||||
ErrXpackNotActive: "该部分为专业版功能,请先在 面板设置-许可证 界面同步许可证状态"
|
||||
ErrXpackOutOfDate: "当前许可证已过期,请重新在 面板设置-许可证 界面导入许可证"
|
||||
|
79
core/init/cache/badger_db/badger_db.go
vendored
Normal file
79
core/init/cache/badger_db/badger_db.go
vendored
Normal file
@ -0,0 +1,79 @@
|
||||
package badger_db
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/dgraph-io/badger/v4"
|
||||
)
|
||||
|
||||
type Cache struct {
|
||||
db *badger.DB
|
||||
}
|
||||
|
||||
func NewCacheDB(db *badger.DB) *Cache {
|
||||
return &Cache{
|
||||
db: db,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Cache) Set(key string, value interface{}) error {
|
||||
err := c.db.Update(func(txn *badger.Txn) error {
|
||||
v := []byte(fmt.Sprintf("%v", value))
|
||||
return txn.Set([]byte(key), v)
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Cache) Del(key string) error {
|
||||
err := c.db.Update(func(txn *badger.Txn) error {
|
||||
return txn.Delete([]byte(key))
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Cache) Clean() error {
|
||||
return c.db.DropAll()
|
||||
}
|
||||
|
||||
func (c *Cache) Get(key string) ([]byte, error) {
|
||||
var result []byte
|
||||
err := c.db.View(func(txn *badger.Txn) error {
|
||||
item, err := txn.Get([]byte(key))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = item.Value(func(val []byte) error {
|
||||
result = append([]byte{}, val...)
|
||||
return nil
|
||||
})
|
||||
return err
|
||||
})
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (c *Cache) SetWithTTL(key string, value interface{}, duration time.Duration) error {
|
||||
err := c.db.Update(func(txn *badger.Txn) error {
|
||||
v := []byte(fmt.Sprintf("%v", value))
|
||||
e := badger.NewEntry([]byte(key), v).WithTTL(duration)
|
||||
return txn.SetEntry(e)
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Cache) PrefixScanKey(prefixStr string) ([]string, error) {
|
||||
var res []string
|
||||
err := c.db.View(func(txn *badger.Txn) error {
|
||||
it := txn.NewIterator(badger.DefaultIteratorOptions)
|
||||
defer it.Close()
|
||||
prefix := []byte(prefixStr)
|
||||
for it.Seek(prefix); it.ValidForPrefix(prefix); it.Next() {
|
||||
item := it.Item()
|
||||
k := item.Key()
|
||||
res = append(res, string(k))
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return res, err
|
||||
}
|
56
core/init/cache/cache.go
vendored
Normal file
56
core/init/cache/cache.go
vendored
Normal file
@ -0,0 +1,56 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/core/global"
|
||||
"github.com/1Panel-dev/1Panel/core/init/cache/badger_db"
|
||||
"github.com/dgraph-io/badger/v4"
|
||||
)
|
||||
|
||||
func Init() {
|
||||
c := global.CONF.System.Cache
|
||||
|
||||
options := badger.Options{
|
||||
Dir: c,
|
||||
ValueDir: c,
|
||||
ValueLogFileSize: 64 << 20,
|
||||
ValueLogMaxEntries: 10 << 20,
|
||||
VLogPercentile: 0.1,
|
||||
|
||||
MemTableSize: 32 << 20,
|
||||
BaseTableSize: 2 << 20,
|
||||
BaseLevelSize: 10 << 20,
|
||||
TableSizeMultiplier: 2,
|
||||
LevelSizeMultiplier: 10,
|
||||
MaxLevels: 7,
|
||||
NumGoroutines: 4,
|
||||
MetricsEnabled: true,
|
||||
NumCompactors: 2,
|
||||
NumLevelZeroTables: 5,
|
||||
NumLevelZeroTablesStall: 15,
|
||||
NumMemtables: 1,
|
||||
BloomFalsePositive: 0.01,
|
||||
BlockSize: 2 * 1024,
|
||||
SyncWrites: false,
|
||||
NumVersionsToKeep: 1,
|
||||
CompactL0OnClose: false,
|
||||
VerifyValueChecksum: false,
|
||||
BlockCacheSize: 32 << 20,
|
||||
IndexCacheSize: 0,
|
||||
ZSTDCompressionLevel: 1,
|
||||
EncryptionKey: []byte{},
|
||||
EncryptionKeyRotationDuration: 10 * 24 * time.Hour, // Default 10 days.
|
||||
DetectConflicts: true,
|
||||
NamespaceOffset: -1,
|
||||
}
|
||||
|
||||
cache, err := badger.Open(options)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
_ = cache.DropAll()
|
||||
global.CacheDb = cache
|
||||
global.CACHE = badger_db.NewCacheDB(cache)
|
||||
global.LOG.Info("init cache successfully")
|
||||
}
|
58
core/init/db/db.go
Normal file
58
core/init/db/db.go
Normal file
@ -0,0 +1,58 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/core/global"
|
||||
"github.com/glebarez/sqlite"
|
||||
"gorm.io/gorm"
|
||||
"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 failed, err: %v", err))
|
||||
}
|
||||
}
|
||||
fullPath := global.CONF.System.DbPath + "/" + global.CONF.System.DbFile
|
||||
if _, err := os.Stat(fullPath); err != nil {
|
||||
f, err := os.Create(fullPath)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("init db file failed, err: %v", err))
|
||||
}
|
||||
_ = f.Close()
|
||||
}
|
||||
|
||||
newLogger := logger.New(
|
||||
log.New(os.Stdout, "\r\n", log.LstdFlags),
|
||||
logger.Config{
|
||||
SlowThreshold: time.Second,
|
||||
LogLevel: logger.Silent,
|
||||
IgnoreRecordNotFoundError: true,
|
||||
Colorful: false,
|
||||
},
|
||||
)
|
||||
|
||||
db, err := gorm.Open(sqlite.Open(fullPath), &gorm.Config{
|
||||
DisableForeignKeyConstraintWhenMigrating: true,
|
||||
Logger: newLogger,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
_ = db.Exec("PRAGMA journal_mode = WAL;")
|
||||
sqlDB, dbError := db.DB()
|
||||
if dbError != nil {
|
||||
panic(dbError)
|
||||
}
|
||||
sqlDB.SetConnMaxIdleTime(10)
|
||||
sqlDB.SetMaxOpenConns(100)
|
||||
sqlDB.SetConnMaxLifetime(time.Hour)
|
||||
|
||||
global.DB = db
|
||||
global.LOG.Info("init db successfully")
|
||||
}
|
82
core/init/hook/hook.go
Normal file
82
core/init/hook/hook.go
Normal file
@ -0,0 +1,82 @@
|
||||
package hook
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/core/app/repo"
|
||||
"github.com/1Panel-dev/1Panel/core/global"
|
||||
"github.com/1Panel-dev/1Panel/core/utils/cmd"
|
||||
"github.com/1Panel-dev/1Panel/core/utils/common"
|
||||
"github.com/1Panel-dev/1Panel/core/utils/encrypt"
|
||||
)
|
||||
|
||||
func Init() {
|
||||
settingRepo := repo.NewISettingRepo()
|
||||
portSetting, err := settingRepo.Get(settingRepo.WithByKey("ServerPort"))
|
||||
if err != nil {
|
||||
global.LOG.Errorf("load service port from setting failed, err: %v", err)
|
||||
}
|
||||
global.CONF.System.Port = portSetting.Value
|
||||
ipv6Setting, err := settingRepo.Get(settingRepo.WithByKey("Ipv6"))
|
||||
if err != nil {
|
||||
global.LOG.Errorf("load ipv6 status from setting failed, err: %v", err)
|
||||
}
|
||||
global.CONF.System.Ipv6 = ipv6Setting.Value
|
||||
bindAddressSetting, err := settingRepo.Get(settingRepo.WithByKey("BindAddress"))
|
||||
if err != nil {
|
||||
global.LOG.Errorf("load bind address from setting failed, err: %v", err)
|
||||
}
|
||||
global.CONF.System.BindAddress = bindAddressSetting.Value
|
||||
sslSetting, err := settingRepo.Get(settingRepo.WithByKey("SSL"))
|
||||
if err != nil {
|
||||
global.LOG.Errorf("load service ssl from setting failed, err: %v", err)
|
||||
}
|
||||
global.CONF.System.SSL = sslSetting.Value
|
||||
|
||||
if _, err := settingRepo.Get(settingRepo.WithByKey("SystemStatus")); err != nil {
|
||||
_ = settingRepo.Create("SystemStatus", "Free")
|
||||
}
|
||||
if err := settingRepo.Update("SystemStatus", "Free"); err != nil {
|
||||
global.LOG.Fatalf("init service before start failed, err: %v", err)
|
||||
}
|
||||
|
||||
handleUserInfo(global.CONF.System.ChangeUserInfo, settingRepo)
|
||||
}
|
||||
|
||||
func handleUserInfo(tags string, settingRepo repo.ISettingRepo) {
|
||||
if len(tags) == 0 {
|
||||
return
|
||||
}
|
||||
if tags == "all" {
|
||||
if err := settingRepo.Update("UserName", common.RandStrAndNum(10)); err != nil {
|
||||
global.LOG.Fatalf("init username before start failed, err: %v", err)
|
||||
}
|
||||
pass, _ := encrypt.StringEncrypt(common.RandStrAndNum(10))
|
||||
if err := settingRepo.Update("Password", pass); err != nil {
|
||||
global.LOG.Fatalf("init password before start failed, err: %v", err)
|
||||
}
|
||||
if err := settingRepo.Update("SecurityEntrance", common.RandStrAndNum(10)); err != nil {
|
||||
global.LOG.Fatalf("init entrance before start failed, err: %v", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
if strings.Contains(global.CONF.System.ChangeUserInfo, "username") {
|
||||
if err := settingRepo.Update("UserName", common.RandStrAndNum(10)); err != nil {
|
||||
global.LOG.Fatalf("init username before start failed, err: %v", err)
|
||||
}
|
||||
}
|
||||
if strings.Contains(global.CONF.System.ChangeUserInfo, "password") {
|
||||
pass, _ := encrypt.StringEncrypt(common.RandStrAndNum(10))
|
||||
if err := settingRepo.Update("Password", pass); err != nil {
|
||||
global.LOG.Fatalf("init password before start failed, err: %v", err)
|
||||
}
|
||||
}
|
||||
if strings.Contains(global.CONF.System.ChangeUserInfo, "entrance") {
|
||||
if err := settingRepo.Update("SecurityEntrance", common.RandStrAndNum(10)); err != nil {
|
||||
global.LOG.Fatalf("init entrance before start failed, err: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
sudo := cmd.SudoHandleCmd()
|
||||
_, _ = cmd.Execf("%s sed -i '/CHANGE_USER_INFO=%v/d' /usr/local/bin/1pctl", sudo, global.CONF.System.ChangeUserInfo)
|
||||
}
|
67
core/init/log/log.go
Normal file
67
core/init/log/log.go
Normal file
@ -0,0 +1,67 @@
|
||||
package log
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/core/configs"
|
||||
"github.com/1Panel-dev/1Panel/core/global"
|
||||
"github.com/1Panel-dev/1Panel/core/log"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const (
|
||||
TimeFormat = "2006-01-02 15:04:05"
|
||||
FileTImeFormat = "2006-01-02"
|
||||
RollingTimePattern = "0 0 * * *"
|
||||
)
|
||||
|
||||
func Init() {
|
||||
l := logrus.New()
|
||||
setOutput(l, global.CONF.LogConfig)
|
||||
global.LOG = l
|
||||
global.LOG.Info("init logger successfully")
|
||||
}
|
||||
|
||||
func setOutput(logger *logrus.Logger, config configs.LogConfig) {
|
||||
writer, err := log.NewWriterFromConfig(&log.Config{
|
||||
LogPath: global.CONF.System.LogPath,
|
||||
FileName: config.LogName,
|
||||
TimeTagFormat: FileTImeFormat,
|
||||
MaxRemain: config.MaxBackup,
|
||||
RollingTimePattern: RollingTimePattern,
|
||||
LogSuffix: config.LogSuffix,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
level, err := logrus.ParseLevel(config.Level)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fileAndStdoutWriter := io.MultiWriter(writer, os.Stdout)
|
||||
|
||||
logger.SetOutput(fileAndStdoutWriter)
|
||||
logger.SetLevel(level)
|
||||
logger.SetFormatter(new(MineFormatter))
|
||||
}
|
||||
|
||||
type MineFormatter struct{}
|
||||
|
||||
func (s *MineFormatter) Format(entry *logrus.Entry) ([]byte, error) {
|
||||
detailInfo := ""
|
||||
if entry.Caller != nil {
|
||||
function := strings.ReplaceAll(entry.Caller.Function, "github.com/1Panel-dev/1Panel/core/", "")
|
||||
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)
|
||||
return []byte(msg), nil
|
||||
}
|
||||
msg := fmt.Sprintf("[%s] [%s] %s %s {%v} \n", time.Now().Format(TimeFormat), strings.ToUpper(entry.Level.String()), entry.Message, detailInfo, entry.Data)
|
||||
return []byte(msg), nil
|
||||
}
|
20
core/init/migration/migrate.go
Normal file
20
core/init/migration/migrate.go
Normal file
@ -0,0 +1,20 @@
|
||||
package migration
|
||||
|
||||
import (
|
||||
"github.com/1Panel-dev/1Panel/core/global"
|
||||
"github.com/1Panel-dev/1Panel/core/init/migration/migrations"
|
||||
|
||||
"github.com/go-gormigrate/gormigrate/v2"
|
||||
)
|
||||
|
||||
func Init() {
|
||||
m := gormigrate.New(global.DB, gormigrate.DefaultOptions, []*gormigrate.Migration{
|
||||
migrations.Init,
|
||||
migrations.InitSetting,
|
||||
})
|
||||
if err := m.Migrate(); err != nil {
|
||||
global.LOG.Error(err)
|
||||
panic(err)
|
||||
}
|
||||
global.LOG.Info("Migration run successfully")
|
||||
}
|
123
core/init/migration/migrations/init.go
Normal file
123
core/init/migration/migrations/init.go
Normal file
@ -0,0 +1,123 @@
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/core/app/model"
|
||||
"github.com/1Panel-dev/1Panel/core/constant"
|
||||
"github.com/1Panel-dev/1Panel/core/global"
|
||||
"github.com/1Panel-dev/1Panel/core/utils/common"
|
||||
"github.com/1Panel-dev/1Panel/core/utils/encrypt"
|
||||
"github.com/go-gormigrate/gormigrate/v2"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
var Init = &gormigrate.Migration{
|
||||
ID: "20200809-add-table-operation-log",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
return tx.AutoMigrate(
|
||||
&model.OperationLog{},
|
||||
&model.LoginLog{},
|
||||
&model.Setting{},
|
||||
)
|
||||
},
|
||||
}
|
||||
|
||||
var InitSetting = &gormigrate.Migration{
|
||||
ID: "20200908-add-table-setting",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
encryptKey := common.RandStr(16)
|
||||
if err := tx.Create(&model.Setting{Key: "UserName", Value: global.CONF.System.Username}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
global.CONF.System.EncryptKey = encryptKey
|
||||
pass, _ := encrypt.StringEncrypt(global.CONF.System.Password)
|
||||
if err := tx.Create(&model.Setting{Key: "Password", Value: pass}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Create(&model.Setting{Key: "Theme", Value: "light"}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Create(&model.Setting{Key: "MenuTabs", Value: "disable"}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Create(&model.Setting{Key: "PanelName", Value: "1Panel"}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Create(&model.Setting{Key: "Language", Value: "zh"}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Create(&model.Setting{Key: "SessionTimeout", Value: "86400"}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Create(&model.Setting{Key: "LocalTime", Value: ""}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := tx.Create(&model.Setting{Key: "DeveloperMode", Value: "disable"}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Create(&model.Setting{Key: "ProxyType", Value: ""}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Create(&model.Setting{Key: "ProxyUrl", Value: ""}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Create(&model.Setting{Key: "ProxyPort", Value: ""}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Create(&model.Setting{Key: "ProxyUser", Value: ""}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Create(&model.Setting{Key: "ProxyPasswd", Value: ""}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Create(&model.Setting{Key: "PrsoxyPasswdKeep", Value: ""}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Create(&model.Setting{Key: "XpackHideMenu", Value: "{\"id\":\"1\",\"label\":\"/xpack\",\"isCheck\":true,\"title\":\"xpack.menu\",\"children\":[{\"id\":\"2\",\"title\":\"xpack.waf.name\",\"path\":\"/xpack/waf/dashboard\",\"label\":\"Dashboard\",\"isCheck\":true},{\"id\":\"3\",\"title\":\"xpack.tamper.tamper\",\"path\":\"/xpack/tamper\",\"label\":\"Tamper\",\"isCheck\":true},{\"id\":\"4\",\"title\":\"xpack.gpu.gpu\",\"path\":\"/xpack/gpu\",\"label\":\"GPU\",\"isCheck\":true},{\"id\":\"5\",\"title\":\"xpack.setting.setting\",\"path\":\"/xpack/setting\",\"label\":\"XSetting\",\"isCheck\":true}]}"}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := tx.Create(&model.Setting{Key: "ServerPort", Value: global.CONF.System.Port}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Create(&model.Setting{Key: "SecurityEntrance", Value: global.CONF.System.Entrance}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Create(&model.Setting{Key: "JWTSigningKey", Value: common.RandStr(16)}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Create(&model.Setting{Key: "EncryptKey", Value: encryptKey}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Create(&model.Setting{Key: "ExpirationTime", Value: time.Now().AddDate(0, 0, 10).Format(constant.DateTimeLayout)}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Create(&model.Setting{Key: "ExpirationDays", Value: "0"}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Create(&model.Setting{Key: "ComplexityVerification", Value: "enable"}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Create(&model.Setting{Key: "MFAStatus", Value: "disable"}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Create(&model.Setting{Key: "MFASecret", Value: ""}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Create(&model.Setting{Key: "SystemVersion", Value: global.CONF.System.Version}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Create(&model.Setting{Key: "BindAddress", Value: "0.0.0.0"}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Create(&model.Setting{Key: "Ipv6", Value: "disable"}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Create(&model.Setting{Key: "NoAuthSetting", Value: "200"}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
76
core/init/router/router.go
Normal file
76
core/init/router/router.go
Normal file
@ -0,0 +1,76 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/core/cmd/server/docs"
|
||||
"github.com/1Panel-dev/1Panel/core/cmd/server/web"
|
||||
"github.com/1Panel-dev/1Panel/core/global"
|
||||
"github.com/1Panel-dev/1Panel/core/i18n"
|
||||
"github.com/1Panel-dev/1Panel/core/middleware"
|
||||
rou "github.com/1Panel-dev/1Panel/core/router"
|
||||
"github.com/gin-contrib/gzip"
|
||||
"github.com/gin-gonic/gin"
|
||||
swaggerfiles "github.com/swaggo/files"
|
||||
ginSwagger "github.com/swaggo/gin-swagger"
|
||||
)
|
||||
|
||||
var (
|
||||
Router *gin.Engine
|
||||
)
|
||||
|
||||
func setWebStatic(rootRouter *gin.RouterGroup) {
|
||||
rootRouter.StaticFS("/public", http.FS(web.Favicon))
|
||||
rootRouter.Static("/api/v1/images", "./uploads")
|
||||
rootRouter.Use(func(c *gin.Context) {
|
||||
c.Next()
|
||||
})
|
||||
rootRouter.GET("/assets/*filepath", func(c *gin.Context) {
|
||||
c.Writer.Header().Set("Cache-Control", fmt.Sprintf("private, max-age=%d", 3600))
|
||||
staticServer := http.FileServer(http.FS(web.Assets))
|
||||
staticServer.ServeHTTP(c.Writer, c.Request)
|
||||
})
|
||||
rootRouter.GET("/", func(c *gin.Context) {
|
||||
staticServer := http.FileServer(http.FS(web.IndexHtml))
|
||||
staticServer.ServeHTTP(c.Writer, c.Request)
|
||||
})
|
||||
}
|
||||
|
||||
func Routers() *gin.Engine {
|
||||
Router = gin.Default()
|
||||
Router.Use(middleware.OperationLog())
|
||||
if global.CONF.System.IsDemo {
|
||||
Router.Use(middleware.DemoHandle())
|
||||
}
|
||||
|
||||
Router.NoRoute(func(c *gin.Context) {
|
||||
c.Writer.WriteHeader(http.StatusOK)
|
||||
_, _ = c.Writer.Write(web.IndexByte)
|
||||
c.Writer.Header().Add("Accept", "text/html")
|
||||
c.Writer.Flush()
|
||||
})
|
||||
|
||||
Router.Use(i18n.UseI18n())
|
||||
|
||||
swaggerRouter := Router.Group("1panel")
|
||||
docs.SwaggerInfo.BasePath = "/api/v1"
|
||||
swaggerRouter.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerfiles.Handler))
|
||||
PublicGroup := Router.Group("")
|
||||
{
|
||||
PublicGroup.GET("/health", func(c *gin.Context) {
|
||||
c.JSON(200, "ok")
|
||||
})
|
||||
PublicGroup.Use(gzip.Gzip(gzip.DefaultCompression))
|
||||
setWebStatic(PublicGroup)
|
||||
}
|
||||
PrivateGroup := Router.Group("/api/v1")
|
||||
PrivateGroup.Use(middleware.WhiteAllow())
|
||||
PrivateGroup.Use(middleware.BindDomain())
|
||||
PrivateGroup.Use(middleware.GlobalLoading())
|
||||
for _, router := range rou.RouterGroupApp {
|
||||
router.InitRouter(PrivateGroup)
|
||||
}
|
||||
|
||||
return Router
|
||||
}
|
47
core/init/session/psession/psession.go
Normal file
47
core/init/session/psession/psession.go
Normal file
@ -0,0 +1,47 @@
|
||||
package psession
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/core/init/cache/badger_db"
|
||||
)
|
||||
|
||||
type SessionUser struct {
|
||||
ID uint `json:"id"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type PSession struct {
|
||||
ExpireTime int64 `json:"expire_time"`
|
||||
store *badger_db.Cache
|
||||
}
|
||||
|
||||
func NewPSession(db *badger_db.Cache) *PSession {
|
||||
return &PSession{
|
||||
store: db,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *PSession) Get(sessionID string) (SessionUser, error) {
|
||||
var result SessionUser
|
||||
item, err := p.store.Get(sessionID)
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
_ = json.Unmarshal(item, &result)
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (p *PSession) Set(sessionID string, user SessionUser, ttlSeconds int) error {
|
||||
p.ExpireTime = time.Now().Unix() + int64(ttlSeconds)
|
||||
return p.store.SetWithTTL(sessionID, user, time.Second*time.Duration(ttlSeconds))
|
||||
}
|
||||
|
||||
func (p *PSession) Delete(sessionID string) error {
|
||||
return p.store.Del(sessionID)
|
||||
}
|
||||
|
||||
func (p *PSession) Clean() error {
|
||||
return p.store.Clean()
|
||||
}
|
11
core/init/session/session.go
Normal file
11
core/init/session/session.go
Normal file
@ -0,0 +1,11 @@
|
||||
package session
|
||||
|
||||
import (
|
||||
"github.com/1Panel-dev/1Panel/core/global"
|
||||
"github.com/1Panel-dev/1Panel/core/init/session/psession"
|
||||
)
|
||||
|
||||
func Init() {
|
||||
global.SESSION = psession.NewPSession(global.CACHE)
|
||||
global.LOG.Info("init session successfully")
|
||||
}
|
65
core/init/validator/validator.go
Normal file
65
core/init/validator/validator.go
Normal file
@ -0,0 +1,65 @@
|
||||
package validator
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"unicode"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/core/global"
|
||||
|
||||
"github.com/go-playground/validator/v10"
|
||||
)
|
||||
|
||||
func Init() {
|
||||
validator := validator.New()
|
||||
if err := validator.RegisterValidation("name", checkNamePattern); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := validator.RegisterValidation("ip", checkIpPattern); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := validator.RegisterValidation("password", checkPasswordPattern); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
global.VALID = validator
|
||||
}
|
||||
|
||||
func checkNamePattern(fl validator.FieldLevel) bool {
|
||||
value := fl.Field().String()
|
||||
result, err := regexp.MatchString("^[a-zA-Z\u4e00-\u9fa5]{1}[a-zA-Z0-9_\u4e00-\u9fa5]{0,30}$", value)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("regexp matchString failed, %v", err)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func checkIpPattern(fl validator.FieldLevel) bool {
|
||||
value := fl.Field().String()
|
||||
result, err := regexp.MatchString(`^((2(5[0-5]|[0-4]\d))|[0-1]?\d{1,2})(\.((2(5[0-5]|[0-4]\d))|[0-1]?\d{1,2})){3}$`, value)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("regexp check ip matchString failed, %v", err)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func checkPasswordPattern(fl validator.FieldLevel) bool {
|
||||
value := fl.Field().String()
|
||||
if len(value) < 8 || len(value) > 30 {
|
||||
return false
|
||||
}
|
||||
|
||||
hasNum := false
|
||||
hasLetter := false
|
||||
for _, r := range value {
|
||||
if unicode.IsLetter(r) && !hasLetter {
|
||||
hasLetter = true
|
||||
}
|
||||
if unicode.IsNumber(r) && !hasNum {
|
||||
hasNum = true
|
||||
}
|
||||
if hasLetter && hasNum {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
122
core/init/viper/viper.go
Normal file
122
core/init/viper/viper.go
Normal file
@ -0,0 +1,122 @@
|
||||
package viper
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/core/cmd/server/conf"
|
||||
"github.com/1Panel-dev/1Panel/core/configs"
|
||||
"github.com/1Panel-dev/1Panel/core/global"
|
||||
"github.com/1Panel-dev/1Panel/core/utils/cmd"
|
||||
"github.com/fsnotify/fsnotify"
|
||||
"github.com/spf13/viper"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func Init() {
|
||||
baseDir := "/opt"
|
||||
port := "9999"
|
||||
mode := ""
|
||||
version := "v1.0.0"
|
||||
username, password, entrance := "", "", ""
|
||||
v := viper.NewWithOptions()
|
||||
v.SetConfigType("yaml")
|
||||
|
||||
config := configs.ServerConfig{}
|
||||
if err := yaml.Unmarshal(conf.AppYaml, &config); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if config.System.Mode != "" {
|
||||
mode = config.System.Mode
|
||||
}
|
||||
_, err := os.Stat("/opt/1panel/conf/app.yaml")
|
||||
if mode == "dev" && err == nil {
|
||||
v.SetConfigName("app")
|
||||
v.AddConfigPath(path.Join("/opt/1panel/conf"))
|
||||
if err := v.ReadInConfig(); err != nil {
|
||||
panic(fmt.Errorf("Fatal error config file: %s \n", err))
|
||||
}
|
||||
} else {
|
||||
baseDir = loadParams("BASE_DIR")
|
||||
port = loadParams("ORIGINAL_PORT")
|
||||
version = loadParams("ORIGINAL_VERSION")
|
||||
username = loadParams("ORIGINAL_USERNAME")
|
||||
password = loadParams("ORIGINAL_PASSWORD")
|
||||
entrance = loadParams("ORIGINAL_ENTRANCE")
|
||||
|
||||
reader := bytes.NewReader(conf.AppYaml)
|
||||
if err := v.ReadConfig(reader); err != nil {
|
||||
panic(fmt.Errorf("Fatal error config file: %s \n", err))
|
||||
}
|
||||
}
|
||||
v.OnConfigChange(func(e fsnotify.Event) {
|
||||
if err := v.Unmarshal(&global.CONF); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
})
|
||||
serverConfig := configs.ServerConfig{}
|
||||
if err := v.Unmarshal(&serverConfig); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
_, err = os.Stat("/opt/1panel/conf/app.yaml")
|
||||
if mode == "dev" && err == nil {
|
||||
if serverConfig.System.BaseDir != "" {
|
||||
baseDir = serverConfig.System.BaseDir
|
||||
}
|
||||
if serverConfig.System.Port != "" {
|
||||
port = serverConfig.System.Port
|
||||
}
|
||||
if serverConfig.System.Version != "" {
|
||||
version = serverConfig.System.Version
|
||||
}
|
||||
if serverConfig.System.Username != "" {
|
||||
username = serverConfig.System.Username
|
||||
}
|
||||
if serverConfig.System.Password != "" {
|
||||
password = serverConfig.System.Password
|
||||
}
|
||||
if serverConfig.System.Entrance != "" {
|
||||
entrance = serverConfig.System.Entrance
|
||||
}
|
||||
}
|
||||
|
||||
global.CONF = serverConfig
|
||||
global.CONF.System.BaseDir = baseDir
|
||||
global.CONF.System.IsDemo = v.GetBool("system.is_demo")
|
||||
global.CONF.System.DataDir = path.Join(global.CONF.System.BaseDir, "1panel")
|
||||
global.CONF.System.Cache = path.Join(global.CONF.System.DataDir, "cache")
|
||||
global.CONF.System.Backup = path.Join(global.CONF.System.DataDir, "backup")
|
||||
global.CONF.System.DbPath = path.Join(global.CONF.System.DataDir, "db")
|
||||
global.CONF.System.LogPath = path.Join(global.CONF.System.DataDir, "log")
|
||||
global.CONF.System.TmpDir = path.Join(global.CONF.System.DataDir, "tmp")
|
||||
global.CONF.System.Port = port
|
||||
global.CONF.System.Version = version
|
||||
global.CONF.System.Username = username
|
||||
global.CONF.System.Password = password
|
||||
global.CONF.System.Entrance = entrance
|
||||
global.CONF.System.ChangeUserInfo = loadChangeInfo()
|
||||
global.Viper = v
|
||||
}
|
||||
|
||||
func loadParams(param string) string {
|
||||
stdout, err := cmd.Execf("grep '^%s=' /usr/local/bin/1pctl | cut -d'=' -f2", param)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
info := strings.ReplaceAll(stdout, "\n", "")
|
||||
if len(info) == 0 || info == `""` {
|
||||
panic(fmt.Sprintf("error `%s` find in /usr/local/bin/1pctl", param))
|
||||
}
|
||||
return info
|
||||
}
|
||||
|
||||
func loadChangeInfo() string {
|
||||
stdout, err := cmd.Exec("grep '^CHANGE_USER_INFO=' /usr/local/bin/1pctl | cut -d'=' -f2")
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return strings.ReplaceAll(stdout, "\n", "")
|
||||
}
|
41
core/log/config.go
Normal file
41
core/log/config.go
Normal file
@ -0,0 +1,41 @@
|
||||
package log
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
)
|
||||
|
||||
var (
|
||||
BufferSize = 0x100000
|
||||
DefaultFileMode = os.FileMode(0644)
|
||||
DefaultFileFlag = os.O_RDWR | os.O_CREATE | os.O_APPEND
|
||||
ErrInvalidArgument = errors.New("error argument invalid")
|
||||
QueueSize = 1024
|
||||
ErrClosed = errors.New("error write on close")
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
TimeTagFormat string
|
||||
LogPath string
|
||||
FileName string
|
||||
LogSuffix string
|
||||
MaxRemain int
|
||||
RollingTimePattern string
|
||||
}
|
||||
|
||||
type Manager interface {
|
||||
Fire() chan string
|
||||
Close()
|
||||
}
|
||||
|
||||
type RollingWriter interface {
|
||||
io.Writer
|
||||
Close() error
|
||||
}
|
||||
|
||||
func FilePath(c *Config) (filepath string) {
|
||||
filepath = path.Join(c.LogPath, c.FileName) + c.LogSuffix
|
||||
return
|
||||
}
|
20
core/log/dup_write_darwin.go
Normal file
20
core/log/dup_write_darwin.go
Normal file
@ -0,0 +1,20 @@
|
||||
package log
|
||||
|
||||
import (
|
||||
"golang.org/x/sys/unix"
|
||||
"os"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
var stdErrFileHandler *os.File
|
||||
|
||||
func dupWrite(file *os.File) error {
|
||||
stdErrFileHandler = file
|
||||
if err := unix.Dup2(int(file.Fd()), int(os.Stderr.Fd())); err != nil {
|
||||
return err
|
||||
}
|
||||
runtime.SetFinalizer(stdErrFileHandler, func(fd *os.File) {
|
||||
fd.Close()
|
||||
})
|
||||
return nil
|
||||
}
|
20
core/log/dup_write_linux.go
Normal file
20
core/log/dup_write_linux.go
Normal file
@ -0,0 +1,20 @@
|
||||
package log
|
||||
|
||||
import (
|
||||
"golang.org/x/sys/unix"
|
||||
"os"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
var stdErrFileHandler *os.File
|
||||
|
||||
func dupWrite(file *os.File) error {
|
||||
stdErrFileHandler = file
|
||||
if err := unix.Dup2(int(file.Fd()), int(os.Stderr.Fd())); err != nil {
|
||||
return err
|
||||
}
|
||||
runtime.SetFinalizer(stdErrFileHandler, func(fd *os.File) {
|
||||
fd.Close()
|
||||
})
|
||||
return nil
|
||||
}
|
9
core/log/dup_write_windows.go
Normal file
9
core/log/dup_write_windows.go
Normal file
@ -0,0 +1,9 @@
|
||||
package log
|
||||
|
||||
import (
|
||||
"os"
|
||||
)
|
||||
|
||||
func dupWrite(file *os.File) error {
|
||||
return nil
|
||||
}
|
53
core/log/manager.go
Normal file
53
core/log/manager.go
Normal file
@ -0,0 +1,53 @@
|
||||
package log
|
||||
|
||||
import (
|
||||
"github.com/robfig/cron/v3"
|
||||
"path"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type manager struct {
|
||||
startAt time.Time
|
||||
fire chan string
|
||||
cr *cron.Cron
|
||||
context chan int
|
||||
wg sync.WaitGroup
|
||||
lock sync.Mutex
|
||||
}
|
||||
|
||||
func (m *manager) Fire() chan string {
|
||||
return m.fire
|
||||
}
|
||||
|
||||
func (m *manager) Close() {
|
||||
close(m.context)
|
||||
m.cr.Stop()
|
||||
}
|
||||
|
||||
func NewManager(c *Config) (Manager, error) {
|
||||
m := &manager{
|
||||
startAt: time.Now(),
|
||||
cr: cron.New(),
|
||||
fire: make(chan string),
|
||||
context: make(chan int),
|
||||
wg: sync.WaitGroup{},
|
||||
}
|
||||
|
||||
if _, err := m.cr.AddFunc(c.RollingTimePattern, func() {
|
||||
m.fire <- m.GenLogFileName(c)
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m.cr.Start()
|
||||
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (m *manager) GenLogFileName(c *Config) (filename string) {
|
||||
m.lock.Lock()
|
||||
filename = path.Join(c.LogPath, c.FileName+"-"+m.startAt.Format(c.TimeTagFormat)) + c.LogSuffix
|
||||
m.startAt = time.Now()
|
||||
m.lock.Unlock()
|
||||
return
|
||||
}
|
250
core/log/writer.go
Normal file
250
core/log/writer.go
Normal file
@ -0,0 +1,250 @@
|
||||
package log
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/core/global"
|
||||
)
|
||||
|
||||
type Writer struct {
|
||||
m Manager
|
||||
file *os.File
|
||||
absPath string
|
||||
fire chan string
|
||||
cf *Config
|
||||
rollingfilech chan string
|
||||
}
|
||||
|
||||
type AsynchronousWriter struct {
|
||||
Writer
|
||||
ctx chan int
|
||||
queue chan []byte
|
||||
errChan chan error
|
||||
closed int32
|
||||
wg sync.WaitGroup
|
||||
}
|
||||
|
||||
func (w *AsynchronousWriter) Close() error {
|
||||
if atomic.CompareAndSwapInt32(&w.closed, 0, 1) {
|
||||
close(w.ctx)
|
||||
w.onClose()
|
||||
|
||||
func() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
global.LOG.Error(r)
|
||||
}
|
||||
}()
|
||||
w.m.Close()
|
||||
}()
|
||||
return w.file.Close()
|
||||
}
|
||||
return ErrClosed
|
||||
}
|
||||
|
||||
func (w *AsynchronousWriter) onClose() {
|
||||
var err error
|
||||
for {
|
||||
select {
|
||||
case b := <-w.queue:
|
||||
if _, err = w.file.Write(b); err != nil {
|
||||
select {
|
||||
case w.errChan <- err:
|
||||
default:
|
||||
_asyncBufferPool.Put(&b)
|
||||
return
|
||||
}
|
||||
}
|
||||
_asyncBufferPool.Put(&b)
|
||||
default:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var _asyncBufferPool = sync.Pool{
|
||||
New: func() interface{} {
|
||||
return make([]byte, BufferSize)
|
||||
},
|
||||
}
|
||||
|
||||
func NewWriterFromConfig(c *Config) (RollingWriter, error) {
|
||||
if c.LogPath == "" || c.FileName == "" {
|
||||
return nil, ErrInvalidArgument
|
||||
}
|
||||
if err := os.MkdirAll(c.LogPath, 0700); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
filepath := FilePath(c)
|
||||
file, err := os.OpenFile(filepath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := dupWrite(file); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mng, err := NewManager(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var rollingWriter RollingWriter
|
||||
writer := Writer{
|
||||
m: mng,
|
||||
file: file,
|
||||
absPath: filepath,
|
||||
fire: mng.Fire(),
|
||||
cf: c,
|
||||
}
|
||||
if c.MaxRemain > 0 {
|
||||
writer.rollingfilech = make(chan string, c.MaxRemain)
|
||||
dir, err := os.ReadDir(c.LogPath)
|
||||
if err != nil {
|
||||
mng.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
files := make([]string, 0, 10)
|
||||
for _, fi := range dir {
|
||||
if fi.IsDir() {
|
||||
continue
|
||||
}
|
||||
|
||||
fileName := c.FileName
|
||||
if strings.Contains(fi.Name(), fileName) && strings.Contains(fi.Name(), c.LogSuffix) {
|
||||
start := strings.Index(fi.Name(), "-")
|
||||
end := strings.Index(fi.Name(), c.LogSuffix)
|
||||
name := fi.Name()
|
||||
if start > 0 && end > 0 {
|
||||
_, err := time.Parse(c.TimeTagFormat, name[start+1:end])
|
||||
if err == nil {
|
||||
files = append(files, fi.Name())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
sort.Slice(files, func(i, j int) bool {
|
||||
t1Start := strings.Index(files[i], "-")
|
||||
t1End := strings.Index(files[i], c.LogSuffix)
|
||||
t2Start := strings.Index(files[i], "-")
|
||||
t2End := strings.Index(files[i], c.LogSuffix)
|
||||
t1, _ := time.Parse(c.TimeTagFormat, files[i][t1Start+1:t1End])
|
||||
t2, _ := time.Parse(c.TimeTagFormat, files[j][t2Start+1:t2End])
|
||||
return t1.Before(t2)
|
||||
})
|
||||
|
||||
for _, file := range files {
|
||||
retry:
|
||||
select {
|
||||
case writer.rollingfilech <- path.Join(c.LogPath, file):
|
||||
default:
|
||||
writer.DoRemove()
|
||||
goto retry
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
wr := &AsynchronousWriter{
|
||||
ctx: make(chan int),
|
||||
queue: make(chan []byte, QueueSize),
|
||||
errChan: make(chan error, QueueSize),
|
||||
wg: sync.WaitGroup{},
|
||||
closed: 0,
|
||||
Writer: writer,
|
||||
}
|
||||
|
||||
wr.wg.Add(1)
|
||||
go wr.writer()
|
||||
wr.wg.Wait()
|
||||
rollingWriter = wr
|
||||
|
||||
return rollingWriter, nil
|
||||
}
|
||||
|
||||
func (w *AsynchronousWriter) writer() {
|
||||
var err error
|
||||
w.wg.Done()
|
||||
for {
|
||||
select {
|
||||
case filename := <-w.fire:
|
||||
if err = w.Reopen(filename); err != nil && len(w.errChan) < cap(w.errChan) {
|
||||
w.errChan <- err
|
||||
}
|
||||
case b := <-w.queue:
|
||||
if _, err = w.file.Write(b); err != nil && len(w.errChan) < cap(w.errChan) {
|
||||
w.errChan <- err
|
||||
}
|
||||
_asyncBufferPool.Put(&b)
|
||||
case <-w.ctx:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (w *Writer) DoRemove() {
|
||||
file := <-w.rollingfilech
|
||||
if err := os.Remove(file); err != nil {
|
||||
log.Println("error in remove log file", file, err)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *Writer) Write(b []byte) (int, error) {
|
||||
var ok = false
|
||||
for !ok {
|
||||
select {
|
||||
case filename := <-w.fire:
|
||||
if err := w.Reopen(filename); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
default:
|
||||
ok = true
|
||||
}
|
||||
}
|
||||
|
||||
fp := atomic.LoadPointer((*unsafe.Pointer)(unsafe.Pointer(&w.file)))
|
||||
file := (*os.File)(fp)
|
||||
return file.Write(b)
|
||||
}
|
||||
|
||||
func (w *Writer) Reopen(file string) error {
|
||||
fileInfo, err := w.file.Stat()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if fileInfo.Size() == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
w.file.Close()
|
||||
if err := os.Rename(w.absPath, file); err != nil {
|
||||
return err
|
||||
}
|
||||
newFile, err := os.OpenFile(w.absPath, DefaultFileFlag, DefaultFileMode)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
w.file = newFile
|
||||
|
||||
go func() {
|
||||
if w.cf.MaxRemain > 0 {
|
||||
retry:
|
||||
select {
|
||||
case w.rollingfilech <- file:
|
||||
default:
|
||||
w.DoRemove()
|
||||
goto retry
|
||||
}
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
}
|
28
core/main.go
Normal file
28
core/main.go
Normal file
@ -0,0 +1,28 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
_ "net/http/pprof"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/core/cmd/server/cmd"
|
||||
_ "github.com/1Panel-dev/1Panel/core/cmd/server/docs"
|
||||
)
|
||||
|
||||
// @title 1Panel
|
||||
// @version 1.0
|
||||
// @description 开源Linux面板
|
||||
// @termsOfService http://swagger.io/terms/
|
||||
// @license.name Apache 2.0
|
||||
// @license.url http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
// @host localhost
|
||||
// @BasePath /api/v1
|
||||
|
||||
//go:generate swag init -o ./docs -g main.go -d ../../backend -g ../cmd/server/main.go
|
||||
func main() {
|
||||
if err := cmd.RootCmd.Execute(); err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
41
core/middleware/bind_domain.go
Normal file
41
core/middleware/bind_domain.go
Normal file
@ -0,0 +1,41 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/core/app/api/v1/helper"
|
||||
"github.com/1Panel-dev/1Panel/core/app/repo"
|
||||
"github.com/1Panel-dev/1Panel/core/constant"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func BindDomain() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
settingRepo := repo.NewISettingRepo()
|
||||
status, err := settingRepo.Get(settingRepo.WithByKey("BindDomain"))
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
if len(status.Value) == 0 {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
domains := c.Request.Host
|
||||
parts := strings.Split(c.Request.Host, ":")
|
||||
if len(parts) > 0 {
|
||||
domains = parts[0]
|
||||
}
|
||||
|
||||
if domains != status.Value {
|
||||
if LoadErrCode("err-domain") != 200 {
|
||||
helper.ErrResponse(c, LoadErrCode("err-domain"))
|
||||
return
|
||||
}
|
||||
helper.ErrorWithDetail(c, constant.CodeErrDomain, constant.ErrTypeInternalServer, errors.New("domain not allowed"))
|
||||
return
|
||||
}
|
||||
c.Next()
|
||||
}
|
||||
}
|
57
core/middleware/demo_handle.go
Normal file
57
core/middleware/demo_handle.go
Normal file
@ -0,0 +1,57 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/core/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/core/buserr"
|
||||
"github.com/1Panel-dev/1Panel/core/constant"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
var whiteUrlList = map[string]struct{}{
|
||||
"/api/v1/auth/login": {},
|
||||
"/api/v1/websites/config": {},
|
||||
"/api/v1/websites/waf/config": {},
|
||||
"/api/v1/files/loadfile": {},
|
||||
"/api/v1/files/size": {},
|
||||
"/api/v1/logs/operation": {},
|
||||
"/api/v1/logs/login": {},
|
||||
"/api/v1/auth/logout": {},
|
||||
|
||||
"/api/v1/apps/installed/loadport": {},
|
||||
"/api/v1/apps/installed/check": {},
|
||||
"/api/v1/apps/installed/conninfo": {},
|
||||
"/api/v1/databases/load/file": {},
|
||||
"/api/v1/databases/variables": {},
|
||||
"/api/v1/databases/status": {},
|
||||
"/api/v1/databases/baseinfo": {},
|
||||
|
||||
"/api/v1/waf/attack/stat": {},
|
||||
"/api/v1/waf/config/website": {},
|
||||
|
||||
"/api/v1/monitor/stat": {},
|
||||
"/api/v1/monitor/visitors": {},
|
||||
"/api/v1/monitor/visitors/loc": {},
|
||||
"/api/v1/monitor/qps": {},
|
||||
}
|
||||
|
||||
func DemoHandle() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
if strings.Contains(c.Request.URL.Path, "search") || c.Request.Method == http.MethodGet {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
if _, ok := whiteUrlList[c.Request.URL.Path]; ok {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusInternalServerError, dto.Response{
|
||||
Code: http.StatusInternalServerError,
|
||||
Message: buserr.New(constant.ErrDemoEnvironment).Error(),
|
||||
})
|
||||
c.Abort()
|
||||
}
|
||||
}
|
32
core/middleware/helper.go
Normal file
32
core/middleware/helper.go
Normal file
@ -0,0 +1,32 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/core/app/repo"
|
||||
)
|
||||
|
||||
func LoadErrCode(errInfo string) int {
|
||||
settingRepo := repo.NewISettingRepo()
|
||||
codeVal, err := settingRepo.Get(settingRepo.WithByKey("NoAuthSetting"))
|
||||
if err != nil {
|
||||
return 500
|
||||
}
|
||||
|
||||
switch codeVal.Value {
|
||||
case "400":
|
||||
return http.StatusBadRequest
|
||||
case "401":
|
||||
return http.StatusUnauthorized
|
||||
case "403":
|
||||
return http.StatusForbidden
|
||||
case "404":
|
||||
return http.StatusNotFound
|
||||
case "408":
|
||||
return http.StatusRequestTimeout
|
||||
case "416":
|
||||
return http.StatusRequestedRangeNotSatisfiable
|
||||
default:
|
||||
return http.StatusOK
|
||||
}
|
||||
}
|
67
core/middleware/ip_limit.go
Normal file
67
core/middleware/ip_limit.go
Normal file
@ -0,0 +1,67 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/core/app/api/v1/helper"
|
||||
"github.com/1Panel-dev/1Panel/core/app/repo"
|
||||
"github.com/1Panel-dev/1Panel/core/constant"
|
||||
"github.com/1Panel-dev/1Panel/core/global"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func WhiteAllow() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
settingRepo := repo.NewISettingRepo()
|
||||
status, err := settingRepo.Get(settingRepo.WithByKey("AllowIPs"))
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
|
||||
if len(status.Value) == 0 {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
clientIP := c.ClientIP()
|
||||
for _, ip := range strings.Split(status.Value, ",") {
|
||||
if len(ip) == 0 {
|
||||
continue
|
||||
}
|
||||
if ip == clientIP || (strings.Contains(ip, "/") && checkIpInCidr(ip, clientIP)) {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
}
|
||||
if LoadErrCode("err-ip") != 200 {
|
||||
helper.ErrResponse(c, LoadErrCode("err-ip"))
|
||||
return
|
||||
}
|
||||
helper.ErrorWithDetail(c, constant.CodeErrIP, constant.ErrTypeInternalServer, errors.New("IP address not allowed"))
|
||||
}
|
||||
}
|
||||
|
||||
func checkIpInCidr(cidr, checkIP string) bool {
|
||||
ip, ipNet, err := net.ParseCIDR(cidr)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("parse CIDR %s failed, err: %v", cidr, err)
|
||||
return false
|
||||
}
|
||||
for ip := ip.Mask(ipNet.Mask); ipNet.Contains(ip); incIP(ip) {
|
||||
if ip.String() == checkIP {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func incIP(ip net.IP) {
|
||||
for j := len(ip) - 1; j >= 0; j-- {
|
||||
ip[j]++
|
||||
if ip[j] > 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
28
core/middleware/jwt.go
Normal file
28
core/middleware/jwt.go
Normal file
@ -0,0 +1,28 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"github.com/1Panel-dev/1Panel/core/app/api/v1/helper"
|
||||
"github.com/1Panel-dev/1Panel/core/constant"
|
||||
jwtUtils "github.com/1Panel-dev/1Panel/core/utils/jwt"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func JwtAuth() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
token := c.Request.Header.Get(constant.JWTHeaderName)
|
||||
if token == "" {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
j := jwtUtils.NewJWT()
|
||||
claims, err := j.ParseToken(token)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrUnauthorized, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
c.Set("claims", claims)
|
||||
c.Set("authMethod", constant.AuthMethodJWT)
|
||||
c.Next()
|
||||
}
|
||||
}
|
24
core/middleware/loading.go
Normal file
24
core/middleware/loading.go
Normal file
@ -0,0 +1,24 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"github.com/1Panel-dev/1Panel/core/app/api/v1/helper"
|
||||
"github.com/1Panel-dev/1Panel/core/app/repo"
|
||||
"github.com/1Panel-dev/1Panel/core/constant"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func GlobalLoading() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
settingRepo := repo.NewISettingRepo()
|
||||
status, err := settingRepo.Get(settingRepo.WithByKey("SystemStatus"))
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
if status.Value != "Free" {
|
||||
helper.ErrorWithDetail(c, constant.CodeGlobalLoading, status.Value, err)
|
||||
return
|
||||
}
|
||||
c.Next()
|
||||
}
|
||||
}
|
216
core/middleware/operation.go
Normal file
216
core/middleware/operation.go
Normal file
@ -0,0 +1,216 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/core/app/model"
|
||||
"github.com/1Panel-dev/1Panel/core/app/service"
|
||||
"github.com/1Panel-dev/1Panel/core/cmd/server/docs"
|
||||
"github.com/1Panel-dev/1Panel/core/constant"
|
||||
"github.com/1Panel-dev/1Panel/core/global"
|
||||
"github.com/1Panel-dev/1Panel/core/utils/copier"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func OperationLog() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
if strings.Contains(c.Request.URL.Path, "search") || c.Request.Method == http.MethodGet {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
|
||||
source := loadLogInfo(c.Request.URL.Path)
|
||||
record := model.OperationLog{
|
||||
Source: source,
|
||||
IP: c.ClientIP(),
|
||||
Method: strings.ToLower(c.Request.Method),
|
||||
Path: strings.ReplaceAll(c.Request.URL.Path, "/api/v1", ""),
|
||||
UserAgent: c.Request.UserAgent(),
|
||||
}
|
||||
var (
|
||||
swagger swaggerJson
|
||||
operationDic operationJson
|
||||
)
|
||||
if err := json.Unmarshal(docs.SwaggerJson, &swagger); err != nil {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
path, hasPath := swagger.Paths[record.Path]
|
||||
if !hasPath {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
methodMap, isMethodMap := path.(map[string]interface{})
|
||||
if !isMethodMap {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
dataMap, hasPost := methodMap["post"]
|
||||
if !hasPost {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
data, isDataMap := dataMap.(map[string]interface{})
|
||||
if !isDataMap {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
xlog, hasXlog := data["x-panel-log"]
|
||||
if !hasXlog {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
if err := copier.Copy(&operationDic, xlog); err != nil {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
if len(operationDic.FormatZH) == 0 {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
|
||||
formatMap := make(map[string]interface{})
|
||||
if len(operationDic.BodyKeys) != 0 {
|
||||
body, err := io.ReadAll(c.Request.Body)
|
||||
if err == nil {
|
||||
c.Request.Body = io.NopCloser(bytes.NewBuffer(body))
|
||||
}
|
||||
bodyMap := make(map[string]interface{})
|
||||
_ = json.Unmarshal(body, &bodyMap)
|
||||
for _, key := range operationDic.BodyKeys {
|
||||
if _, ok := bodyMap[key]; ok {
|
||||
formatMap[key] = bodyMap[key]
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(operationDic.BeforeFunctions) != 0 {
|
||||
for _, funcs := range operationDic.BeforeFunctions {
|
||||
for key, value := range formatMap {
|
||||
if funcs.InputValue == key {
|
||||
var names []string
|
||||
if funcs.IsList {
|
||||
sql := fmt.Sprintf("SELECT %s FROM %s where %s in (?);", funcs.OutputColumn, funcs.DB, funcs.InputColumn)
|
||||
_ = global.DB.Raw(sql, value).Scan(&names)
|
||||
} else {
|
||||
_ = global.DB.Raw(fmt.Sprintf("select %s from %s where %s = ?;", funcs.OutputColumn, funcs.DB, funcs.InputColumn), value).Scan(&names)
|
||||
}
|
||||
formatMap[funcs.OutputValue] = strings.Join(names, ",")
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for key, value := range formatMap {
|
||||
if strings.Contains(operationDic.FormatEN, "["+key+"]") {
|
||||
if arrays, ok := value.([]string); ok {
|
||||
operationDic.FormatZH = strings.ReplaceAll(operationDic.FormatZH, "["+key+"]", fmt.Sprintf("[%v]", strings.Join(arrays, ",")))
|
||||
operationDic.FormatEN = strings.ReplaceAll(operationDic.FormatEN, "["+key+"]", fmt.Sprintf("[%v]", strings.Join(arrays, ",")))
|
||||
} else {
|
||||
operationDic.FormatZH = strings.ReplaceAll(operationDic.FormatZH, "["+key+"]", fmt.Sprintf("[%v]", value))
|
||||
operationDic.FormatEN = strings.ReplaceAll(operationDic.FormatEN, "["+key+"]", fmt.Sprintf("[%v]", value))
|
||||
}
|
||||
}
|
||||
}
|
||||
record.DetailEN = strings.ReplaceAll(operationDic.FormatEN, "[]", "")
|
||||
record.DetailZH = strings.ReplaceAll(operationDic.FormatZH, "[]", "")
|
||||
|
||||
writer := responseBodyWriter{
|
||||
ResponseWriter: c.Writer,
|
||||
body: &bytes.Buffer{},
|
||||
}
|
||||
c.Writer = writer
|
||||
now := time.Now()
|
||||
|
||||
c.Next()
|
||||
|
||||
datas := writer.body.Bytes()
|
||||
if c.Request.Header.Get("Content-Encoding") == "gzip" {
|
||||
buf := bytes.NewReader(writer.body.Bytes())
|
||||
reader, err := gzip.NewReader(buf)
|
||||
if err != nil {
|
||||
record.Status = constant.StatusFailed
|
||||
record.Message = fmt.Sprintf("gzip new reader failed, err: %v", err)
|
||||
latency := time.Since(now)
|
||||
record.Latency = latency
|
||||
|
||||
if err := service.NewILogService().CreateOperationLog(record); err != nil {
|
||||
global.LOG.Errorf("create operation record failed, err: %v", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
defer reader.Close()
|
||||
datas, _ = io.ReadAll(reader)
|
||||
}
|
||||
var res response
|
||||
_ = json.Unmarshal(datas, &res)
|
||||
if res.Code == 200 {
|
||||
record.Status = constant.StatusSuccess
|
||||
} else {
|
||||
record.Status = constant.StatusFailed
|
||||
record.Message = res.Message
|
||||
}
|
||||
|
||||
latency := time.Since(now)
|
||||
record.Latency = latency
|
||||
|
||||
if err := service.NewILogService().CreateOperationLog(record); err != nil {
|
||||
global.LOG.Errorf("create operation record failed, err: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type swaggerJson struct {
|
||||
Paths map[string]interface{} `json:"paths"`
|
||||
}
|
||||
|
||||
type operationJson struct {
|
||||
API string `json:"api"`
|
||||
Method string `json:"method"`
|
||||
BodyKeys []string `json:"bodyKeys"`
|
||||
ParamKeys []string `json:"paramKeys"`
|
||||
BeforeFunctions []functionInfo `json:"beforeFunctions"`
|
||||
FormatZH string `json:"formatZH"`
|
||||
FormatEN string `json:"formatEN"`
|
||||
}
|
||||
type functionInfo struct {
|
||||
InputColumn string `json:"input_column"`
|
||||
InputValue string `json:"input_value"`
|
||||
IsList bool `json:"isList"`
|
||||
DB string `json:"db"`
|
||||
OutputColumn string `json:"output_column"`
|
||||
OutputValue string `json:"output_value"`
|
||||
}
|
||||
|
||||
type response struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
type responseBodyWriter struct {
|
||||
gin.ResponseWriter
|
||||
body *bytes.Buffer
|
||||
}
|
||||
|
||||
func (r responseBodyWriter) Write(b []byte) (int, error) {
|
||||
r.body.Write(b)
|
||||
return r.ResponseWriter.Write(b)
|
||||
}
|
||||
|
||||
func loadLogInfo(path string) string {
|
||||
path = strings.ReplaceAll(path, "/api/v1", "")
|
||||
if !strings.Contains(path, "/") {
|
||||
return ""
|
||||
}
|
||||
pathArrays := strings.Split(path, "/")
|
||||
if len(pathArrays) < 2 {
|
||||
return ""
|
||||
}
|
||||
return pathArrays[1]
|
||||
}
|
45
core/middleware/password_expired.go
Normal file
45
core/middleware/password_expired.go
Normal file
@ -0,0 +1,45 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/core/app/api/v1/helper"
|
||||
"github.com/1Panel-dev/1Panel/core/app/repo"
|
||||
"github.com/1Panel-dev/1Panel/core/constant"
|
||||
"github.com/1Panel-dev/1Panel/core/utils/common"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func PasswordExpired() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
settingRepo := repo.NewISettingRepo()
|
||||
setting, err := settingRepo.Get(settingRepo.WithByKey("ExpirationDays"))
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypePasswordExpired, err)
|
||||
return
|
||||
}
|
||||
expiredDays, _ := strconv.Atoi(setting.Value)
|
||||
if expiredDays == 0 {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
|
||||
extime, err := settingRepo.Get(settingRepo.WithByKey("ExpirationTime"))
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypePasswordExpired, err)
|
||||
return
|
||||
}
|
||||
loc, _ := time.LoadLocation(common.LoadTimeZone())
|
||||
expiredTime, err := time.ParseInLocation(constant.DateTimeLayout, extime.Value, loc)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodePasswordExpired, constant.ErrTypePasswordExpired, err)
|
||||
return
|
||||
}
|
||||
if time.Now().After(expiredTime) {
|
||||
helper.ErrorWithDetail(c, constant.CodePasswordExpired, constant.ErrTypePasswordExpired, err)
|
||||
return
|
||||
}
|
||||
c.Next()
|
||||
}
|
||||
}
|
38
core/middleware/session.go
Normal file
38
core/middleware/session.go
Normal file
@ -0,0 +1,38 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/core/app/api/v1/helper"
|
||||
"github.com/1Panel-dev/1Panel/core/app/repo"
|
||||
"github.com/1Panel-dev/1Panel/core/constant"
|
||||
"github.com/1Panel-dev/1Panel/core/global"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func SessionAuth() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
if method, exist := c.Get("authMethod"); exist && method == constant.AuthMethodJWT {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
sId, err := c.Cookie(constant.SessionName)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrUnauthorized, constant.ErrTypeNotLogin, nil)
|
||||
return
|
||||
}
|
||||
psession, err := global.SESSION.Get(sId)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrUnauthorized, constant.ErrTypeNotLogin, nil)
|
||||
return
|
||||
}
|
||||
settingRepo := repo.NewISettingRepo()
|
||||
setting, err := settingRepo.Get(settingRepo.WithByKey("SessionTimeout"))
|
||||
if err != nil {
|
||||
global.LOG.Errorf("create operation record failed, err: %v", err)
|
||||
}
|
||||
lifeTime, _ := strconv.Atoi(setting.Value)
|
||||
_ = global.SESSION.Set(sId, psession, lifeTime)
|
||||
c.Next()
|
||||
}
|
||||
}
|
9
core/router/common.go
Normal file
9
core/router/common.go
Normal file
@ -0,0 +1,9 @@
|
||||
package router
|
||||
|
||||
func commonGroups() []CommonRouter {
|
||||
return []CommonRouter{
|
||||
&BaseRouter{},
|
||||
&LogRouter{},
|
||||
&SettingRouter{},
|
||||
}
|
||||
}
|
9
core/router/entry.go
Normal file
9
core/router/entry.go
Normal file
@ -0,0 +1,9 @@
|
||||
//go:build !xpack
|
||||
|
||||
package router
|
||||
|
||||
func RouterGroups() []CommonRouter {
|
||||
return commonGroups()
|
||||
}
|
||||
|
||||
var RouterGroupApp = RouterGroups()
|
22
core/router/ro_base.go
Normal file
22
core/router/ro_base.go
Normal file
@ -0,0 +1,22 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
v1 "github.com/1Panel-dev/1Panel/core/app/api/v1"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type BaseRouter struct{}
|
||||
|
||||
func (s *BaseRouter) InitRouter(Router *gin.RouterGroup) {
|
||||
baseRouter := Router.Group("auth")
|
||||
baseApi := v1.ApiGroupApp.BaseApi
|
||||
{
|
||||
baseRouter.GET("/captcha", baseApi.Captcha)
|
||||
baseRouter.POST("/mfalogin", baseApi.MFALogin)
|
||||
baseRouter.POST("/login", baseApi.Login)
|
||||
baseRouter.GET("/issafety", baseApi.CheckIsSafety)
|
||||
baseRouter.POST("/logout", baseApi.LogOut)
|
||||
baseRouter.GET("/demo", baseApi.CheckIsDemo)
|
||||
baseRouter.GET("/language", baseApi.GetLanguage)
|
||||
}
|
||||
}
|
21
core/router/ro_log.go
Normal file
21
core/router/ro_log.go
Normal file
@ -0,0 +1,21 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
v1 "github.com/1Panel-dev/1Panel/core/app/api/v1"
|
||||
"github.com/1Panel-dev/1Panel/core/middleware"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type LogRouter struct{}
|
||||
|
||||
func (s *LogRouter) InitRouter(Router *gin.RouterGroup) {
|
||||
operationRouter := Router.Group("logs")
|
||||
operationRouter.Use(middleware.JwtAuth()).Use(middleware.SessionAuth()).Use(middleware.PasswordExpired())
|
||||
baseApi := v1.ApiGroupApp.BaseApi
|
||||
{
|
||||
operationRouter.POST("/login", baseApi.GetLoginLogs)
|
||||
operationRouter.POST("/operation", baseApi.GetOperationLogs)
|
||||
operationRouter.POST("/clean", baseApi.CleanLogs)
|
||||
}
|
||||
}
|
7
core/router/ro_router.go
Normal file
7
core/router/ro_router.go
Normal file
@ -0,0 +1,7 @@
|
||||
package router
|
||||
|
||||
import "github.com/gin-gonic/gin"
|
||||
|
||||
type CommonRouter interface {
|
||||
InitRouter(Router *gin.RouterGroup)
|
||||
}
|
42
core/router/ro_setting.go
Normal file
42
core/router/ro_setting.go
Normal file
@ -0,0 +1,42 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
v1 "github.com/1Panel-dev/1Panel/core/app/api/v1"
|
||||
"github.com/1Panel-dev/1Panel/core/middleware"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type SettingRouter struct{}
|
||||
|
||||
func (s *SettingRouter) InitRouter(Router *gin.RouterGroup) {
|
||||
router := Router.Group("settings").
|
||||
Use(middleware.JwtAuth()).
|
||||
Use(middleware.SessionAuth())
|
||||
settingRouter := Router.Group("settings").
|
||||
Use(middleware.JwtAuth()).
|
||||
Use(middleware.SessionAuth()).
|
||||
Use(middleware.PasswordExpired())
|
||||
baseApi := v1.ApiGroupApp.BaseApi
|
||||
{
|
||||
router.POST("/search", baseApi.GetSettingInfo)
|
||||
router.POST("/expired/handle", baseApi.HandlePasswordExpired)
|
||||
settingRouter.GET("/search/available", baseApi.GetSystemAvailable)
|
||||
settingRouter.POST("/update", baseApi.UpdateSetting)
|
||||
settingRouter.GET("/interface", baseApi.LoadInterfaceAddr)
|
||||
settingRouter.POST("/menu/update", baseApi.UpdateMenu)
|
||||
settingRouter.POST("/proxy/update", baseApi.UpdateProxy)
|
||||
settingRouter.POST("/bind/update", baseApi.UpdateBindInfo)
|
||||
settingRouter.POST("/port/update", baseApi.UpdatePort)
|
||||
settingRouter.POST("/ssl/update", baseApi.UpdateSSL)
|
||||
settingRouter.GET("/ssl/info", baseApi.LoadFromCert)
|
||||
settingRouter.POST("/ssl/download", baseApi.DownloadSSL)
|
||||
settingRouter.POST("/password/update", baseApi.UpdatePassword)
|
||||
settingRouter.POST("/mfa", baseApi.LoadMFA)
|
||||
settingRouter.POST("/mfa/bind", baseApi.MFABind)
|
||||
|
||||
settingRouter.POST("/upgrade", baseApi.Upgrade)
|
||||
settingRouter.POST("/upgrade/notes", baseApi.GetNotesByVersion)
|
||||
settingRouter.GET("/upgrade", baseApi.GetUpgradeInfo)
|
||||
settingRouter.GET("/basedir", baseApi.LoadBaseDir)
|
||||
}
|
||||
}
|
6
core/server/init.go
Normal file
6
core/server/init.go
Normal file
@ -0,0 +1,6 @@
|
||||
//go:build !xpack
|
||||
|
||||
package server
|
||||
|
||||
func InitOthers() {
|
||||
}
|
88
core/server/server.go
Normal file
88
core/server/server.go
Normal file
@ -0,0 +1,88 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"encoding/gob"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/core/global"
|
||||
"github.com/1Panel-dev/1Panel/core/i18n"
|
||||
"github.com/1Panel-dev/1Panel/core/init/cache"
|
||||
"github.com/1Panel-dev/1Panel/core/init/db"
|
||||
"github.com/1Panel-dev/1Panel/core/init/hook"
|
||||
"github.com/1Panel-dev/1Panel/core/init/log"
|
||||
"github.com/1Panel-dev/1Panel/core/init/migration"
|
||||
"github.com/1Panel-dev/1Panel/core/init/router"
|
||||
"github.com/1Panel-dev/1Panel/core/init/session"
|
||||
"github.com/1Panel-dev/1Panel/core/init/session/psession"
|
||||
"github.com/1Panel-dev/1Panel/core/init/validator"
|
||||
"github.com/1Panel-dev/1Panel/core/init/viper"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func Start() {
|
||||
viper.Init()
|
||||
i18n.Init()
|
||||
log.Init()
|
||||
db.Init()
|
||||
migration.Init()
|
||||
validator.Init()
|
||||
gob.Register(psession.SessionUser{})
|
||||
cache.Init()
|
||||
session.Init()
|
||||
gin.SetMode("debug")
|
||||
hook.Init()
|
||||
|
||||
rootRouter := router.Routers()
|
||||
|
||||
tcpItem := "tcp4"
|
||||
if global.CONF.System.Ipv6 == "enable" {
|
||||
tcpItem = "tcp"
|
||||
global.CONF.System.BindAddress = fmt.Sprintf("[%s]", global.CONF.System.BindAddress)
|
||||
}
|
||||
server := &http.Server{
|
||||
Addr: global.CONF.System.BindAddress + ":" + global.CONF.System.Port,
|
||||
Handler: rootRouter,
|
||||
}
|
||||
ln, err := net.Listen(tcpItem, server.Addr)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
type tcpKeepAliveListener struct {
|
||||
*net.TCPListener
|
||||
}
|
||||
if global.CONF.System.SSL == "enable" {
|
||||
certPath := path.Join(global.CONF.System.BaseDir, "1panel/secret/server.crt")
|
||||
keyPath := path.Join(global.CONF.System.BaseDir, "1panel/secret/server.key")
|
||||
certificate, err := os.ReadFile(certPath)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
key, err := os.ReadFile(keyPath)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
cert, err := tls.X509KeyPair(certificate, key)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
server.TLSConfig = &tls.Config{
|
||||
Certificates: []tls.Certificate{cert},
|
||||
}
|
||||
global.LOG.Infof("listen at https://%s:%s [%s]", global.CONF.System.BindAddress, global.CONF.System.Port, tcpItem)
|
||||
|
||||
if err := server.ServeTLS(tcpKeepAliveListener{ln.(*net.TCPListener)}, certPath, keyPath); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
} else {
|
||||
global.LOG.Infof("listen at http://%s:%s [%s]", global.CONF.System.BindAddress, global.CONF.System.Port, tcpItem)
|
||||
if err := server.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)}); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}
|
45
core/utils/captcha/captcha.go
Normal file
45
core/utils/captcha/captcha.go
Normal file
@ -0,0 +1,45 @@
|
||||
package captcha
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/core/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/core/constant"
|
||||
"github.com/mojocn/base64Captcha"
|
||||
)
|
||||
|
||||
var store = base64Captcha.DefaultMemStore
|
||||
|
||||
func VerifyCode(codeID string, code string) error {
|
||||
if codeID == "" {
|
||||
return constant.ErrCaptchaCode
|
||||
}
|
||||
vv := store.Get(codeID, true)
|
||||
vv = strings.TrimSpace(vv)
|
||||
code = strings.TrimSpace(code)
|
||||
|
||||
if strings.EqualFold(vv, code) {
|
||||
return nil
|
||||
}
|
||||
return constant.ErrCaptchaCode
|
||||
}
|
||||
|
||||
func CreateCaptcha() (*dto.CaptchaResponse, error) {
|
||||
var driverString base64Captcha.DriverString
|
||||
driverString.Source = "1234567890QWERTYUPLKJHGFDSAZXCVBNMqwertyupkjhgfdsazxcvbnm"
|
||||
driverString.Width = 120
|
||||
driverString.Height = 50
|
||||
driverString.NoiseCount = 0
|
||||
driverString.Length = 4
|
||||
driverString.Fonts = []string{"RitaSmith.ttf", "actionj.ttf", "chromohv.ttf"}
|
||||
driver := driverString.ConvertFonts()
|
||||
c := base64Captcha.NewCaptcha(driver, store)
|
||||
id, b64s, _, err := c.Generate()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &dto.CaptchaResponse{
|
||||
CaptchaID: id,
|
||||
ImagePath: b64s,
|
||||
}, nil
|
||||
}
|
76
core/utils/cmd/cmd.go
Normal file
76
core/utils/cmd/cmd.go
Normal file
@ -0,0 +1,76 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/core/buserr"
|
||||
"github.com/1Panel-dev/1Panel/core/constant"
|
||||
)
|
||||
|
||||
func Exec(cmdStr string) (string, error) {
|
||||
return ExecWithTimeOut(cmdStr, 20*time.Second)
|
||||
}
|
||||
|
||||
func SudoHandleCmd() string {
|
||||
cmd := exec.Command("sudo", "-n", "ls")
|
||||
if err := cmd.Run(); err == nil {
|
||||
return "sudo "
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func Execf(cmdStr string, a ...interface{}) (string, error) {
|
||||
cmd := exec.Command("bash", "-c", fmt.Sprintf(cmdStr, a...))
|
||||
var stdout, stderr bytes.Buffer
|
||||
cmd.Stdout = &stdout
|
||||
cmd.Stderr = &stderr
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return handleErr(stdout, stderr, err)
|
||||
}
|
||||
return stdout.String(), nil
|
||||
}
|
||||
|
||||
func ExecWithTimeOut(cmdStr string, timeout time.Duration) (string, error) {
|
||||
cmd := exec.Command("bash", "-c", cmdStr)
|
||||
var stdout, stderr bytes.Buffer
|
||||
cmd.Stdout = &stdout
|
||||
cmd.Stderr = &stderr
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
return stdout.String(), nil
|
||||
}
|
||||
|
||||
func handleErr(stdout, stderr bytes.Buffer, err error) (string, error) {
|
||||
errMsg := ""
|
||||
if len(stderr.String()) != 0 {
|
||||
errMsg = fmt.Sprintf("stderr: %s", stderr.String())
|
||||
}
|
||||
if len(stdout.String()) != 0 {
|
||||
if len(errMsg) != 0 {
|
||||
errMsg = fmt.Sprintf("%s; stdout: %s", errMsg, stdout.String())
|
||||
} else {
|
||||
errMsg = fmt.Sprintf("stdout: %s", stdout.String())
|
||||
}
|
||||
}
|
||||
return errMsg, err
|
||||
}
|
96
core/utils/common/common.go
Normal file
96
core/utils/common/common.go
Normal file
@ -0,0 +1,96 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
mathRand "math/rand"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890")
|
||||
|
||||
func RandStr(n int) string {
|
||||
b := make([]rune, n)
|
||||
for i := range b {
|
||||
b[i] = letters[mathRand.Intn(len(letters))]
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
func RandStrAndNum(n int) string {
|
||||
source := mathRand.NewSource(time.Now().UnixNano())
|
||||
randGen := mathRand.New(source)
|
||||
const charset = "abcdefghijklmnopqrstuvwxyz0123456789"
|
||||
b := make([]byte, n)
|
||||
for i := range b {
|
||||
b[i] = charset[randGen.Intn(len(charset)-1)]
|
||||
}
|
||||
return (string(b))
|
||||
}
|
||||
|
||||
func LoadTimeZone() string {
|
||||
loc := time.Now().Location()
|
||||
if _, err := time.LoadLocation(loc.String()); err != nil {
|
||||
return "Asia/Shanghai"
|
||||
}
|
||||
return loc.String()
|
||||
}
|
||||
|
||||
func ScanPort(port int) bool {
|
||||
ln, err := net.Listen("tcp", ":"+strconv.Itoa(port))
|
||||
if err != nil {
|
||||
return true
|
||||
}
|
||||
defer ln.Close()
|
||||
return false
|
||||
}
|
||||
|
||||
func ComparePanelVersion(version1, version2 string) bool {
|
||||
if version1 == version2 {
|
||||
return false
|
||||
}
|
||||
version1s := SplitStr(version1, ".", "-")
|
||||
version2s := SplitStr(version2, ".", "-")
|
||||
|
||||
if len(version2s) > len(version1s) {
|
||||
for i := 0; i < len(version2s)-len(version1s); i++ {
|
||||
version1s = append(version1s, "0")
|
||||
}
|
||||
}
|
||||
if len(version1s) > len(version2s) {
|
||||
for i := 0; i < len(version1s)-len(version2s); i++ {
|
||||
version2s = append(version2s, "0")
|
||||
}
|
||||
}
|
||||
|
||||
n := min(len(version1s), len(version2s))
|
||||
for i := 0; i < n; i++ {
|
||||
if version1s[i] == version2s[i] {
|
||||
continue
|
||||
} else {
|
||||
v1, err1 := strconv.Atoi(version1s[i])
|
||||
if err1 != nil {
|
||||
return version1s[i] > version2s[i]
|
||||
}
|
||||
v2, err2 := strconv.Atoi(version2s[i])
|
||||
if err2 != nil {
|
||||
return version1s[i] > version2s[i]
|
||||
}
|
||||
return v1 > v2
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func SplitStr(str string, spi ...string) []string {
|
||||
lists := []string{str}
|
||||
var results []string
|
||||
for _, s := range spi {
|
||||
results = []string{}
|
||||
for _, list := range lists {
|
||||
results = append(results, strings.Split(list, s)...)
|
||||
}
|
||||
lists = results
|
||||
}
|
||||
return results
|
||||
}
|
18
core/utils/copier/copier.go
Normal file
18
core/utils/copier/copier.go
Normal file
@ -0,0 +1,18 @@
|
||||
package copier
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func Copy(to, from interface{}) error {
|
||||
b, err := json.Marshal(from)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "marshal from data err")
|
||||
}
|
||||
if err = json.Unmarshal(b, to); err != nil {
|
||||
return errors.Wrap(err, "unmarshal to data err")
|
||||
}
|
||||
return nil
|
||||
}
|
104
core/utils/encrypt/encrypt.go
Normal file
104
core/utils/encrypt/encrypt.go
Normal file
@ -0,0 +1,104 @@
|
||||
package encrypt
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/core/app/model"
|
||||
"github.com/1Panel-dev/1Panel/core/global"
|
||||
)
|
||||
|
||||
func StringEncrypt(text string) (string, error) {
|
||||
if len(text) == 0 {
|
||||
return "", nil
|
||||
}
|
||||
if len(global.CONF.System.EncryptKey) == 0 {
|
||||
var encryptSetting model.Setting
|
||||
if err := global.DB.Where("key = ?", "EncryptKey").First(&encryptSetting).Error; err != nil {
|
||||
return "", err
|
||||
}
|
||||
global.CONF.System.EncryptKey = encryptSetting.Value
|
||||
}
|
||||
key := global.CONF.System.EncryptKey
|
||||
pass := []byte(text)
|
||||
xpass, err := aesEncryptWithSalt([]byte(key), pass)
|
||||
if err == nil {
|
||||
pass64 := base64.StdEncoding.EncodeToString(xpass)
|
||||
return pass64, err
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
|
||||
func StringDecrypt(text string) (string, error) {
|
||||
if len(text) == 0 {
|
||||
return "", nil
|
||||
}
|
||||
if len(global.CONF.System.EncryptKey) == 0 {
|
||||
var encryptSetting model.Setting
|
||||
if err := global.DB.Where("key = ?", "EncryptKey").First(&encryptSetting).Error; err != nil {
|
||||
return "", err
|
||||
}
|
||||
global.CONF.System.EncryptKey = encryptSetting.Value
|
||||
}
|
||||
key := global.CONF.System.EncryptKey
|
||||
bytesPass, err := base64.StdEncoding.DecodeString(text)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
var tpass []byte
|
||||
tpass, err = aesDecryptWithSalt([]byte(key), bytesPass)
|
||||
if err == nil {
|
||||
result := string(tpass[:])
|
||||
return result, err
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
|
||||
func padding(plaintext []byte, blockSize int) []byte {
|
||||
padding := blockSize - len(plaintext)%blockSize
|
||||
padtext := bytes.Repeat([]byte{byte(padding)}, padding)
|
||||
return append(plaintext, padtext...)
|
||||
}
|
||||
|
||||
func unPadding(origData []byte) []byte {
|
||||
length := len(origData)
|
||||
unpadding := int(origData[length-1])
|
||||
return origData[:(length - unpadding)]
|
||||
}
|
||||
|
||||
func aesEncryptWithSalt(key, plaintext []byte) ([]byte, error) {
|
||||
plaintext = padding(plaintext, aes.BlockSize)
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ciphertext := make([]byte, aes.BlockSize+len(plaintext))
|
||||
iv := ciphertext[0:aes.BlockSize]
|
||||
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cbc := cipher.NewCBCEncrypter(block, iv)
|
||||
cbc.CryptBlocks(ciphertext[aes.BlockSize:], plaintext)
|
||||
return ciphertext, nil
|
||||
}
|
||||
func aesDecryptWithSalt(key, ciphertext []byte) ([]byte, error) {
|
||||
var block cipher.Block
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(ciphertext) < aes.BlockSize {
|
||||
return nil, fmt.Errorf("iciphertext too short")
|
||||
}
|
||||
iv := ciphertext[:aes.BlockSize]
|
||||
ciphertext = ciphertext[aes.BlockSize:]
|
||||
cbc := cipher.NewCBCDecrypter(block, iv)
|
||||
cbc.CryptBlocks(ciphertext, ciphertext)
|
||||
ciphertext = unPadding(ciphertext)
|
||||
return ciphertext, nil
|
||||
}
|
139
core/utils/files/files.go
Normal file
139
core/utils/files/files.go
Normal file
@ -0,0 +1,139 @@
|
||||
package files
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/core/constant"
|
||||
"github.com/1Panel-dev/1Panel/core/global"
|
||||
"github.com/1Panel-dev/1Panel/core/utils/cmd"
|
||||
httpUtil "github.com/1Panel-dev/1Panel/core/utils/http"
|
||||
)
|
||||
|
||||
func CopyFile(src, dst string) error {
|
||||
source, err := os.Open(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer source.Close()
|
||||
|
||||
if path.Base(src) != path.Base(dst) {
|
||||
dst = path.Join(dst, path.Base(src))
|
||||
}
|
||||
if _, err := os.Stat(path.Dir(dst)); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
_ = os.MkdirAll(path.Dir(dst), os.ModePerm)
|
||||
}
|
||||
}
|
||||
target, err := os.OpenFile(dst+"_temp", os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0755)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer target.Close()
|
||||
|
||||
if _, err = io.Copy(target, source); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = os.Rename(dst+"_temp", dst); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func HandleTar(sourceDir, targetDir, name, exclusionRules string, secret string) error {
|
||||
if _, err := os.Stat(targetDir); err != nil && os.IsNotExist(err) {
|
||||
if err = os.MkdirAll(targetDir, os.ModePerm); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
excludes := strings.Split(exclusionRules, ",")
|
||||
excludeRules := ""
|
||||
excludes = append(excludes, "*.sock")
|
||||
for _, exclude := range excludes {
|
||||
if len(exclude) == 0 {
|
||||
continue
|
||||
}
|
||||
excludeRules += " --exclude " + exclude
|
||||
}
|
||||
path := ""
|
||||
if strings.Contains(sourceDir, "/") {
|
||||
itemDir := strings.ReplaceAll(sourceDir[strings.LastIndex(sourceDir, "/"):], "/", "")
|
||||
aheadDir := sourceDir[:strings.LastIndex(sourceDir, "/")]
|
||||
if len(aheadDir) == 0 {
|
||||
aheadDir = "/"
|
||||
}
|
||||
path += fmt.Sprintf("-C %s %s", aheadDir, itemDir)
|
||||
} else {
|
||||
path = sourceDir
|
||||
}
|
||||
|
||||
commands := ""
|
||||
|
||||
if len(secret) != 0 {
|
||||
extraCmd := "| openssl enc -aes-256-cbc -salt -k '" + secret + "' -out"
|
||||
commands = fmt.Sprintf("tar --warning=no-file-changed --ignore-failed-read -zcf %s %s %s %s", " -"+excludeRules, path, extraCmd, targetDir+"/"+name)
|
||||
global.LOG.Debug(strings.ReplaceAll(commands, fmt.Sprintf(" %s ", secret), "******"))
|
||||
} else {
|
||||
commands = fmt.Sprintf("tar --warning=no-file-changed --ignore-failed-read -zcf %s %s %s", targetDir+"/"+name, excludeRules, path)
|
||||
global.LOG.Debug(commands)
|
||||
}
|
||||
stdout, err := cmd.ExecWithTimeOut(commands, 24*time.Hour)
|
||||
if err != nil {
|
||||
if len(stdout) != 0 {
|
||||
global.LOG.Errorf("do handle tar failed, stdout: %s, err: %v", stdout, err)
|
||||
return fmt.Errorf("do handle tar failed, stdout: %s, err: %v", stdout, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func HandleUnTar(sourceFile, targetDir string, secret string) error {
|
||||
if _, err := os.Stat(targetDir); err != nil && os.IsNotExist(err) {
|
||||
if err = os.MkdirAll(targetDir, os.ModePerm); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
commands := ""
|
||||
if len(secret) != 0 {
|
||||
extraCmd := "openssl enc -d -aes-256-cbc -k '" + secret + "' -in " + sourceFile + " | "
|
||||
commands = fmt.Sprintf("%s tar -zxvf - -C %s", extraCmd, targetDir+" > /dev/null 2>&1")
|
||||
global.LOG.Debug(strings.ReplaceAll(commands, fmt.Sprintf(" %s ", secret), "******"))
|
||||
} else {
|
||||
commands = fmt.Sprintf("tar zxvfC %s %s", sourceFile, targetDir)
|
||||
global.LOG.Debug(commands)
|
||||
}
|
||||
|
||||
stdout, err := cmd.ExecWithTimeOut(commands, 24*time.Hour)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("do handle untar failed, stdout: %s, err: %v", stdout, err)
|
||||
return errors.New(stdout)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func DownloadFileWithProxy(url, dst string) error {
|
||||
_, resp, err := httpUtil.HandleGet(url, http.MethodGet, constant.TimeOut5m)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
out, err := os.Create(dst)
|
||||
if err != nil {
|
||||
return fmt.Errorf("create download file [%s] error, err %s", dst, err.Error())
|
||||
}
|
||||
defer out.Close()
|
||||
|
||||
reader := bytes.NewReader(resp)
|
||||
if _, err = io.Copy(out, reader); err != nil {
|
||||
return fmt.Errorf("save download file [%s] error, err %s", dst, err.Error())
|
||||
}
|
||||
return nil
|
||||
}
|
97
core/utils/http/http.go
Normal file
97
core/utils/http/http.go
Normal file
@ -0,0 +1,97 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/core/buserr"
|
||||
"github.com/1Panel-dev/1Panel/core/global"
|
||||
)
|
||||
|
||||
func HandleGet(url, method string, timeout int) (int, []byte, error) {
|
||||
transport := &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
DialContext: (&net.Dialer{
|
||||
Timeout: 60 * time.Second,
|
||||
KeepAlive: 60 * time.Second,
|
||||
}).DialContext,
|
||||
TLSHandshakeTimeout: 5 * time.Second,
|
||||
ResponseHeaderTimeout: 10 * time.Second,
|
||||
IdleConnTimeout: 15 * time.Second,
|
||||
}
|
||||
return HandleGetWithTransport(url, method, transport, timeout)
|
||||
}
|
||||
|
||||
func HandleGetWithTransport(url, method string, transport *http.Transport, timeout int) (int, []byte, error) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
global.LOG.Errorf("handle request failed, error message: %v", r)
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
client := http.Client{Timeout: time.Duration(timeout) * time.Second, Transport: transport}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeout)*time.Second)
|
||||
defer cancel()
|
||||
request, err := http.NewRequestWithContext(ctx, method, url, nil)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
request.Header.Set("Content-Type", "application/json")
|
||||
resp, err := client.Do(request)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
return resp.StatusCode, body, nil
|
||||
}
|
||||
|
||||
func GetHttpRes(url string) (*http.Response, error) {
|
||||
client := &http.Client{
|
||||
Timeout: time.Second * 300,
|
||||
}
|
||||
transport := &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
DialContext: (&net.Dialer{
|
||||
Timeout: 60 * time.Second,
|
||||
KeepAlive: 60 * time.Second,
|
||||
}).DialContext,
|
||||
TLSHandshakeTimeout: 5 * time.Second,
|
||||
ResponseHeaderTimeout: 10 * time.Second,
|
||||
IdleConnTimeout: 15 * time.Second,
|
||||
}
|
||||
client.Transport = transport
|
||||
|
||||
req, err := http.NewRequestWithContext(context.Background(), "GET", url, nil)
|
||||
if err != nil {
|
||||
return nil, buserr.WithMap("ErrCreateHttpClient", map[string]interface{}{"err": err.Error()}, err)
|
||||
}
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
if errors.Is(err, context.DeadlineExceeded) {
|
||||
return nil, buserr.WithMap("ErrHttpReqTimeOut", map[string]interface{}{"err": err.Error()}, err)
|
||||
} else {
|
||||
if strings.Contains(err.Error(), "no such host") {
|
||||
return nil, buserr.New("ErrNoSuchHost")
|
||||
}
|
||||
return nil, buserr.WithMap("ErrHttpReqFailed", map[string]interface{}{"err": err.Error()}, err)
|
||||
}
|
||||
}
|
||||
if resp.StatusCode == 404 {
|
||||
return nil, buserr.New("ErrHttpReqNotFound")
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
69
core/utils/jwt/jwt.go
Normal file
69
core/utils/jwt/jwt.go
Normal file
@ -0,0 +1,69 @@
|
||||
package jwt
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/core/app/repo"
|
||||
"github.com/1Panel-dev/1Panel/core/constant"
|
||||
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
)
|
||||
|
||||
type JWT struct {
|
||||
SigningKey []byte
|
||||
}
|
||||
|
||||
type JwtRequest struct {
|
||||
BaseClaims
|
||||
BufferTime int64
|
||||
jwt.RegisteredClaims
|
||||
}
|
||||
|
||||
type CustomClaims struct {
|
||||
BaseClaims
|
||||
BufferTime int64
|
||||
jwt.RegisteredClaims
|
||||
}
|
||||
|
||||
type BaseClaims struct {
|
||||
ID uint
|
||||
Name string
|
||||
}
|
||||
|
||||
func NewJWT() *JWT {
|
||||
settingRepo := repo.NewISettingRepo()
|
||||
jwtSign, _ := settingRepo.Get(settingRepo.WithByKey("JWTSigningKey"))
|
||||
return &JWT{
|
||||
[]byte(jwtSign.Value),
|
||||
}
|
||||
}
|
||||
|
||||
func (j *JWT) CreateClaims(baseClaims BaseClaims) CustomClaims {
|
||||
claims := CustomClaims{
|
||||
BaseClaims: baseClaims,
|
||||
BufferTime: constant.JWTBufferTime,
|
||||
RegisteredClaims: jwt.RegisteredClaims{
|
||||
ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Second * time.Duration(constant.JWTBufferTime))),
|
||||
Issuer: constant.JWTIssuer,
|
||||
},
|
||||
}
|
||||
return claims
|
||||
}
|
||||
|
||||
func (j *JWT) CreateToken(request CustomClaims) (string, error) {
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, &request)
|
||||
return token.SignedString(j.SigningKey)
|
||||
}
|
||||
|
||||
func (j *JWT) ParseToken(tokenStr string) (*JwtRequest, error) {
|
||||
token, err := jwt.ParseWithClaims(tokenStr, &JwtRequest{}, func(token *jwt.Token) (interface{}, error) {
|
||||
return j.SigningKey, nil
|
||||
})
|
||||
if err != nil || token == nil {
|
||||
return nil, constant.ErrTokenParse
|
||||
}
|
||||
if claims, ok := token.Claims.(*JwtRequest); ok && token.Valid {
|
||||
return claims, nil
|
||||
}
|
||||
return nil, constant.ErrTokenParse
|
||||
}
|
46
core/utils/mfa/mfa.go
Normal file
46
core/utils/mfa/mfa.go
Normal file
@ -0,0 +1,46 @@
|
||||
package mfa
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/core/global"
|
||||
"github.com/skip2/go-qrcode"
|
||||
"github.com/xlzd/gotp"
|
||||
)
|
||||
|
||||
const secretLength = 16
|
||||
|
||||
type Otp struct {
|
||||
Secret string `json:"secret"`
|
||||
QrImage string `json:"qrImage"`
|
||||
}
|
||||
|
||||
func GetOtp(username, title string, interval int) (otp Otp, err error) {
|
||||
secret := gotp.RandomSecret(secretLength)
|
||||
otp.Secret = secret
|
||||
totp := gotp.NewTOTP(secret, 6, interval, nil)
|
||||
uri := totp.ProvisioningUri(username, title)
|
||||
subImg, err := qrcode.Encode(uri, qrcode.Medium, 256)
|
||||
dist := make([]byte, 3000)
|
||||
base64.StdEncoding.Encode(dist, subImg)
|
||||
index := bytes.IndexByte(dist, 0)
|
||||
baseImage := dist[0:index]
|
||||
otp.QrImage = "data:image/png;base64," + string(baseImage)
|
||||
return
|
||||
}
|
||||
|
||||
func ValidCode(code, intervalStr, secret string) bool {
|
||||
interval, err := strconv.Atoi(intervalStr)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("type conversion failed, err: %v", err)
|
||||
return false
|
||||
}
|
||||
totp := gotp.NewTOTP(secret, 6, interval, nil)
|
||||
now := time.Now().Unix()
|
||||
strInt64 := strconv.FormatInt(now, 10)
|
||||
id16, _ := strconv.Atoi(strInt64)
|
||||
return totp.Verify(code, int64(id16))
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user