mirror of
https://github.com/acepanel/panel.git
synced 2026-02-04 19:37:18 +08:00
795 lines
24 KiB
Go
795 lines
24 KiB
Go
package data
|
||
|
||
import (
|
||
"errors"
|
||
"fmt"
|
||
"path/filepath"
|
||
"regexp"
|
||
"slices"
|
||
"strconv"
|
||
"strings"
|
||
|
||
"github.com/spf13/cast"
|
||
|
||
"github.com/TheTNB/panel/internal/biz"
|
||
"github.com/TheTNB/panel/internal/embed"
|
||
"github.com/TheTNB/panel/internal/http/request"
|
||
"github.com/TheTNB/panel/internal/panel"
|
||
"github.com/TheTNB/panel/pkg/cert"
|
||
"github.com/TheTNB/panel/pkg/db"
|
||
"github.com/TheTNB/panel/pkg/io"
|
||
"github.com/TheTNB/panel/pkg/shell"
|
||
"github.com/TheTNB/panel/pkg/str"
|
||
"github.com/TheTNB/panel/pkg/systemctl"
|
||
"github.com/TheTNB/panel/pkg/types"
|
||
)
|
||
|
||
type websiteRepo struct {
|
||
settingRepo biz.SettingRepo
|
||
}
|
||
|
||
func NewWebsiteRepo() biz.WebsiteRepo {
|
||
return &websiteRepo{
|
||
settingRepo: NewSettingRepo(),
|
||
}
|
||
}
|
||
|
||
func (r *websiteRepo) UpdateDefaultConfig(req *request.WebsiteDefaultConfig) error {
|
||
if err := io.Write(filepath.Join(panel.Root, "server/openresty/html/index.html"), req.Index, 0644); err != nil {
|
||
return err
|
||
}
|
||
if err := io.Write(filepath.Join(panel.Root, "server/openresty/html/stop.html"), req.Stop, 0644); err != nil {
|
||
return err
|
||
}
|
||
|
||
return systemctl.Reload("openresty")
|
||
}
|
||
|
||
func (r *websiteRepo) Count() (int64, error) {
|
||
var count int64
|
||
if err := panel.Orm.Model(&biz.Website{}).Count(&count).Error; err != nil {
|
||
return 0, err
|
||
}
|
||
|
||
return count, nil
|
||
}
|
||
|
||
func (r *websiteRepo) Get(id uint) (*types.WebsiteSetting, error) {
|
||
website := new(biz.Website)
|
||
if err := panel.Orm.Where("id", id).First(website).Error; err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
config, err := io.Read(filepath.Join(panel.Root, "server/vhost", website.Name+".conf"))
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
setting := new(types.WebsiteSetting)
|
||
setting.Name = website.Name
|
||
setting.Path = website.Path
|
||
setting.SSL = website.SSL
|
||
setting.PHP = strconv.Itoa(website.PHP)
|
||
setting.Raw = config
|
||
|
||
portStr := str.Cut(config, "# port标记位开始", "# port标记位结束")
|
||
matches := regexp.MustCompile(`listen\s+([^;]*);?`).FindAllStringSubmatch(portStr, -1)
|
||
for _, match := range matches {
|
||
if len(match) < 2 {
|
||
continue
|
||
}
|
||
// 跳过 ipv6
|
||
if strings.Contains(match[1], "[::]") {
|
||
continue
|
||
}
|
||
|
||
// 处理 443 ssl 之类的情况
|
||
ports := strings.Fields(match[1])
|
||
if len(ports) == 0 {
|
||
continue
|
||
}
|
||
if !slices.Contains(setting.Ports, ports[0]) {
|
||
setting.Ports = append(setting.Ports, ports[0])
|
||
}
|
||
if len(ports) > 1 && ports[1] == "ssl" {
|
||
setting.SSLPorts = append(setting.SSLPorts, ports[0])
|
||
} else if len(ports) > 1 && ports[1] == "quic" {
|
||
setting.QUICPorts = append(setting.QUICPorts, ports[0])
|
||
}
|
||
}
|
||
serverName := str.Cut(config, "# server_name标记位开始", "# server_name标记位结束")
|
||
match := regexp.MustCompile(`server_name\s+([^;]*);?`).FindStringSubmatch(serverName)
|
||
if len(match) > 1 {
|
||
setting.Domains = strings.Split(match[1], " ")
|
||
}
|
||
root := str.Cut(config, "# root标记位开始", "# root标记位结束")
|
||
match = regexp.MustCompile(`root\s+([^;]*);?`).FindStringSubmatch(root)
|
||
if len(match) > 1 {
|
||
setting.Root = match[1]
|
||
}
|
||
index := str.Cut(config, "# index标记位开始", "# index标记位结束")
|
||
match = regexp.MustCompile(`index\s+([^;]*);?`).FindStringSubmatch(index)
|
||
if len(match) > 1 {
|
||
setting.Index = match[1]
|
||
}
|
||
|
||
if io.Exists(filepath.Join(setting.Root, ".user.ini")) {
|
||
userIni, _ := io.Read(filepath.Join(setting.Root, ".user.ini"))
|
||
if strings.Contains(userIni, "open_basedir") {
|
||
setting.OpenBasedir = true
|
||
}
|
||
}
|
||
|
||
crt, _ := io.Read(filepath.Join(panel.Root, "server/vhost/ssl", website.Name+".pem"))
|
||
setting.SSLCertificate = crt
|
||
key, _ := io.Read(filepath.Join(panel.Root, "server/vhost/ssl", website.Name+".key"))
|
||
setting.SSLCertificateKey = key
|
||
if setting.SSL {
|
||
ssl := str.Cut(config, "# ssl标记位开始", "# ssl标记位结束")
|
||
setting.HTTPRedirect = strings.Contains(ssl, "# http重定向标记位")
|
||
setting.HSTS = strings.Contains(ssl, "# hsts标记位")
|
||
setting.OCSP = strings.Contains(ssl, "# ocsp标记位")
|
||
}
|
||
|
||
// 解析证书信息
|
||
if decode, err := cert.ParseCert(crt); err == nil {
|
||
setting.SSLNotBefore = decode.NotBefore.Format("2006-01-02 15:04:05")
|
||
setting.SSLNotAfter = decode.NotAfter.Format("2006-01-02 15:04:05")
|
||
setting.SSLIssuer = decode.Issuer.CommonName
|
||
setting.SSLOCSPServer = decode.OCSPServer
|
||
setting.SSLDNSNames = decode.DNSNames
|
||
}
|
||
|
||
waf := str.Cut(config, "# waf标记位开始", "# waf标记位结束")
|
||
setting.Waf = strings.Contains(waf, "waf on;")
|
||
match = regexp.MustCompile(`waf_mode\s+([^;]*);?`).FindStringSubmatch(waf)
|
||
if len(match) > 1 {
|
||
setting.WafMode = match[1]
|
||
}
|
||
match = regexp.MustCompile(`waf_cc_deny\s+([^;]*);?`).FindStringSubmatch(waf)
|
||
if len(match) > 1 {
|
||
setting.WafCcDeny = match[1]
|
||
}
|
||
match = regexp.MustCompile(`waf_cache\s+([^;]*);?`).FindStringSubmatch(waf)
|
||
if len(match) > 1 {
|
||
setting.WafCache = match[1]
|
||
}
|
||
|
||
rewrite, _ := io.Read(filepath.Join(panel.Root, "server/vhost/rewrite", website.Name+".conf"))
|
||
setting.Rewrite = rewrite
|
||
log, _ := shell.Execf(`tail -n 100 '%s/wwwlogs/%s.log'`, panel.Root, website.Name)
|
||
setting.Log = log
|
||
|
||
return setting, err
|
||
}
|
||
|
||
func (r *websiteRepo) GetByName(name string) (*types.WebsiteSetting, error) {
|
||
website := new(biz.Website)
|
||
if err := panel.Orm.Where("name", name).First(website).Error; err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
return r.Get(website.ID)
|
||
|
||
}
|
||
|
||
func (r *websiteRepo) List(page, limit uint) ([]*biz.Website, int64, error) {
|
||
var websites []*biz.Website
|
||
var total int64
|
||
|
||
if err := panel.Orm.Model(&biz.Website{}).Count(&total).Error; err != nil {
|
||
return nil, 0, err
|
||
}
|
||
|
||
if err := panel.Orm.Offset(int((page - 1) * limit)).Limit(int(limit)).Find(&websites).Error; err != nil {
|
||
return nil, 0, err
|
||
}
|
||
|
||
return websites, total, nil
|
||
}
|
||
|
||
func (r *websiteRepo) Create(req *request.WebsiteCreate) (*biz.Website, error) {
|
||
w := &biz.Website{
|
||
Name: req.Name,
|
||
Status: true,
|
||
Path: req.Path,
|
||
PHP: cast.ToInt(req.PHP),
|
||
SSL: false,
|
||
}
|
||
if err := panel.Orm.Create(w).Error; err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
if err := io.Mkdir(req.Path, 0755); err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
index, err := embed.WebsiteFS.ReadFile(filepath.Join("website", "index.html"))
|
||
if err != nil {
|
||
return nil, fmt.Errorf("获取index模板文件失败: %w", err)
|
||
}
|
||
if err = io.Write(filepath.Join(req.Path, "index.html"), string(index), 0644); err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
notFound, err := embed.WebsiteFS.ReadFile(filepath.Join("website", "404.html"))
|
||
if err != nil {
|
||
return nil, fmt.Errorf("获取404模板文件失败: %w", err)
|
||
}
|
||
if err = io.Write(req.Path+"/404.html", string(notFound), 0644); err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
portList := ""
|
||
domainList := ""
|
||
portUsed := make(map[uint]bool)
|
||
domainUsed := make(map[string]bool)
|
||
|
||
for i, port := range req.Ports {
|
||
if _, ok := portUsed[port]; !ok {
|
||
if i == len(req.Ports)-1 {
|
||
portList += " listen " + cast.ToString(port) + ";\n"
|
||
portList += " listen [::]:" + cast.ToString(port) + ";"
|
||
} else {
|
||
portList += " listen " + cast.ToString(port) + ";\n"
|
||
portList += " listen [::]:" + cast.ToString(port) + ";\n"
|
||
}
|
||
portUsed[port] = true
|
||
}
|
||
}
|
||
for _, domain := range req.Domains {
|
||
if _, ok := domainUsed[domain]; !ok {
|
||
domainList += " " + domain
|
||
domainUsed[domain] = true
|
||
}
|
||
}
|
||
|
||
nginxConf := fmt.Sprintf(`# 配置文件中的标记位请勿随意修改,改错将导致面板无法识别!
|
||
# 有自定义配置需求的,请将自定义的配置写在各标记位下方。
|
||
server
|
||
{
|
||
# port标记位开始
|
||
%s
|
||
# port标记位结束
|
||
# server_name标记位开始
|
||
server_name%s;
|
||
# server_name标记位结束
|
||
# index标记位开始
|
||
index index.php index.html;
|
||
# index标记位结束
|
||
# root标记位开始
|
||
root %s;
|
||
# root标记位结束
|
||
|
||
# ssl标记位开始
|
||
# ssl标记位结束
|
||
|
||
# php标记位开始
|
||
include enable-php-%s.conf;
|
||
# php标记位结束
|
||
|
||
# waf标记位开始
|
||
waf off;
|
||
waf_rule_path %s/server/openresty/ngx_waf/assets/rules/;
|
||
waf_mode DYNAMIC;
|
||
waf_cc_deny rate=1000r/m duration=60m;
|
||
waf_cache capacity=50;
|
||
# waf标记位结束
|
||
|
||
# 错误页配置,可自行设置
|
||
error_page 404 /404.html;
|
||
#error_page 502 /502.html;
|
||
|
||
# acme证书签发配置,不可修改
|
||
include %s/server/vhost/acme/%s.conf;
|
||
|
||
# 伪静态规则引入,修改后将导致面板设置的伪静态规则失效
|
||
include %s/server/vhost/rewrite/%s.conf;
|
||
|
||
# 面板默认禁止访问部分敏感目录,可自行修改
|
||
location ~ ^/(\.user.ini|\.htaccess|\.git|\.svn)
|
||
{
|
||
return 404;
|
||
}
|
||
# 面板默认不记录静态资源的访问日志并开启1小时浏览器缓存,可自行修改
|
||
location ~ .*\.(js|css)$
|
||
{
|
||
expires 1h;
|
||
error_log /dev/null;
|
||
access_log /dev/null;
|
||
}
|
||
|
||
access_log %s/wwwlogs/%s.log;
|
||
error_log %s/wwwlogs/%s.log;
|
||
}
|
||
`, portList, domainList, req.Path, req.PHP, panel.Root, panel.Root, req.Name, panel.Root, req.Name, panel.Root, req.Name, panel.Root, req.Name)
|
||
|
||
if err = io.Write(filepath.Join(panel.Root, "server/vhost", req.Name+".conf"), nginxConf, 0644); err != nil {
|
||
return nil, err
|
||
}
|
||
if err = io.Write(filepath.Join(panel.Root, "server/vhost/rewrite", req.Name+".conf"), "", 0644); err != nil {
|
||
return nil, err
|
||
}
|
||
if err = io.Write(filepath.Join(panel.Root, "server/vhost/acme", req.Name+".conf"), "", 0644); err != nil {
|
||
return nil, err
|
||
}
|
||
if err = io.Write(filepath.Join(panel.Root, "server/vhost/ssl", req.Name+".pem"), "", 0644); err != nil {
|
||
return nil, err
|
||
}
|
||
if err = io.Write(filepath.Join(panel.Root, "server/vhost/ssl", req.Name+".key"), "", 0644); err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
if err = io.Chmod(req.Path, 0755); err != nil {
|
||
return nil, err
|
||
}
|
||
if err = io.Chown(req.Path, "www", "www"); err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
if err = systemctl.Reload("openresty"); err != nil {
|
||
_, err = shell.Execf("openresty -t")
|
||
return nil, err
|
||
}
|
||
|
||
rootPassword, err := r.settingRepo.Get(biz.SettingKeyPerconaRootPassword)
|
||
if err == nil && req.DB && req.DBType == "mysql" {
|
||
mysql, err := db.NewMySQL("root", rootPassword, "/tmp/mysql.sock", "unix")
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
if err = mysql.DatabaseCreate(req.DBName); err != nil {
|
||
return nil, err
|
||
}
|
||
if err = mysql.UserCreate(req.DBUser, req.DBPassword); err != nil {
|
||
return nil, err
|
||
}
|
||
if err = mysql.PrivilegesGrant(req.DBUser, req.DBName); err != nil {
|
||
return nil, err
|
||
}
|
||
}
|
||
if req.DB && req.DBType == "postgresql" {
|
||
_, _ = shell.Execf(`echo "CREATE DATABASE '%s';" | su - postgres -c "psql"`, req.DBName)
|
||
_, _ = shell.Execf(`echo "CREATE USER '%s' WITH PASSWORD '%s';" | su - postgres -c "psql"`, req.DBUser, req.DBPassword)
|
||
_, _ = shell.Execf(`echo "ALTER DATABASE '%s' OWNER TO '%s';" | su - postgres -c "psql"`, req.DBName, req.DBUser)
|
||
_, _ = shell.Execf(`echo "GRANT ALL PRIVILEGES ON DATABASE '%s' TO '%s';" | su - postgres -c "psql"`, req.DBName, req.DBUser)
|
||
userConfig := "host " + req.DBName + " " + req.DBUser + " 127.0.0.1/32 scram-sha-256"
|
||
_, _ = shell.Execf(`echo "`+userConfig+`" >> %s/server/postgresql/data/pg_hba.conf`, panel.Root)
|
||
_ = systemctl.Reload("postgresql")
|
||
}
|
||
|
||
return w, nil
|
||
}
|
||
|
||
func (r *websiteRepo) Update(req *request.WebsiteUpdate) error {
|
||
website := new(biz.Website)
|
||
if err := panel.Orm.Where("id", req.ID).First(website).Error; err != nil {
|
||
return err
|
||
}
|
||
|
||
if !website.Status {
|
||
return errors.New("网站已停用,请先启用")
|
||
}
|
||
|
||
// 原文
|
||
raw, err := io.Read(filepath.Join(panel.Root, "server/vhost", website.Name+".conf"))
|
||
if err != nil {
|
||
return err
|
||
}
|
||
if strings.TrimSpace(raw) != strings.TrimSpace(req.Raw) {
|
||
if err = io.Write(filepath.Join(panel.Root, "server/vhost", website.Name+".conf"), req.Raw, 0644); err != nil {
|
||
return err
|
||
}
|
||
if err = systemctl.Reload("openresty"); err != nil {
|
||
_, err = shell.Execf("openresty -t")
|
||
return err
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// 目录
|
||
path := req.Path
|
||
if !io.Exists(path) {
|
||
return errors.New("网站目录不存在")
|
||
}
|
||
website.Path = path
|
||
|
||
// 域名
|
||
domain := "server_name"
|
||
domains := req.Domains
|
||
for _, v := range domains {
|
||
if v == "" {
|
||
continue
|
||
}
|
||
domain += " " + v
|
||
}
|
||
domain += ";"
|
||
domainConfigOld := str.Cut(raw, "# server_name标记位开始", "# server_name标记位结束")
|
||
if len(strings.TrimSpace(domainConfigOld)) == 0 {
|
||
return errors.New("配置文件中缺少server_name标记位")
|
||
}
|
||
raw = strings.Replace(raw, domainConfigOld, "\n "+domain+"\n ", -1)
|
||
|
||
// 端口
|
||
var portConf strings.Builder
|
||
ports := req.Ports
|
||
for _, port := range ports {
|
||
https := ""
|
||
quic := false
|
||
if slices.Contains(req.SSLPorts, port) {
|
||
https = " ssl"
|
||
if slices.Contains(req.QUICPorts, port) {
|
||
quic = true
|
||
}
|
||
}
|
||
|
||
portConf.WriteString(fmt.Sprintf(" listen %d%s;\n", port, https))
|
||
portConf.WriteString(fmt.Sprintf(" listen [::]:%d%s;\n", port, https))
|
||
if quic {
|
||
portConf.WriteString(fmt.Sprintf(" listen %d%s;\n", port, " quic"))
|
||
portConf.WriteString(fmt.Sprintf(" listen [::]:%d%s;\n", port, " quic"))
|
||
}
|
||
}
|
||
portConf.WriteString(" ")
|
||
portConfNew := portConf.String()
|
||
portConfOld := str.Cut(raw, "# port标记位开始", "# port标记位结束")
|
||
if len(strings.TrimSpace(portConfOld)) == 0 {
|
||
return errors.New("配置文件中缺少port标记位")
|
||
}
|
||
raw = strings.Replace(raw, portConfOld, "\n"+portConfNew, -1)
|
||
|
||
// 运行目录
|
||
root := str.Cut(raw, "# root标记位开始", "# root标记位结束")
|
||
if len(strings.TrimSpace(root)) == 0 {
|
||
return errors.New("配置文件中缺少root标记位")
|
||
}
|
||
match := regexp.MustCompile(`root\s+(.+);`).FindStringSubmatch(root)
|
||
if len(match) != 2 {
|
||
return errors.New("配置文件中root标记位格式错误")
|
||
}
|
||
rootNew := strings.Replace(root, match[1], req.Root, -1)
|
||
raw = strings.Replace(raw, root, rootNew, -1)
|
||
|
||
// 默认文件
|
||
index := str.Cut(raw, "# index标记位开始", "# index标记位结束")
|
||
if len(strings.TrimSpace(index)) == 0 {
|
||
return errors.New("配置文件中缺少index标记位")
|
||
}
|
||
match = regexp.MustCompile(`index\s+(.+);`).FindStringSubmatch(index)
|
||
if len(match) != 2 {
|
||
return errors.New("配置文件中index标记位格式错误")
|
||
}
|
||
indexNew := strings.Replace(index, match[1], req.Index, -1)
|
||
raw = strings.Replace(raw, index, indexNew, -1)
|
||
|
||
// 防跨站
|
||
if !strings.HasSuffix(req.Root, "/") {
|
||
req.Root += "/"
|
||
}
|
||
if req.OpenBasedir {
|
||
if err = io.Write(req.Root+".user.ini", "open_basedir="+path+":/tmp/", 0644); err != nil {
|
||
return err
|
||
}
|
||
} else {
|
||
if io.Exists(req.Root + ".user.ini") {
|
||
if err = io.Remove(req.Root + ".user.ini"); err != nil {
|
||
return err
|
||
}
|
||
}
|
||
}
|
||
|
||
// WAF
|
||
wafStr := "off"
|
||
if req.Waf {
|
||
wafStr = "on"
|
||
}
|
||
wafConfig := fmt.Sprintf(`# waf标记位开始
|
||
waf %s;
|
||
waf_rule_path %s/server/openresty/ngx_waf/assets/rules/;
|
||
waf_mode %s;
|
||
waf_cc_deny %s;
|
||
waf_cache %s;
|
||
`, wafStr, panel.Root, req.WafMode, req.WafCcDeny, req.WafCache)
|
||
wafConfigOld := str.Cut(raw, "# waf标记位开始", "# waf标记位结束")
|
||
if len(strings.TrimSpace(wafConfigOld)) != 0 {
|
||
raw = strings.Replace(raw, wafConfigOld, "", -1)
|
||
}
|
||
raw = strings.Replace(raw, "# waf标记位开始", wafConfig, -1)
|
||
|
||
// SSL
|
||
if err = io.Write(filepath.Join(panel.Root, "server/vhost/ssl", website.Name+".pem"), req.SSLCertificate, 0644); err != nil {
|
||
return err
|
||
}
|
||
if err = io.Write(filepath.Join(panel.Root, "server/vhost/ssl", website.Name+".key"), req.SSLCertificateKey, 0644); err != nil {
|
||
return err
|
||
}
|
||
website.SSL = req.SSL
|
||
if req.SSL {
|
||
if _, err = cert.ParseCert(req.SSLCertificate); err != nil {
|
||
return errors.New("TLS证书格式错误")
|
||
}
|
||
if _, err = cert.ParseKey(req.SSLCertificateKey); err != nil {
|
||
return errors.New("TLS私钥格式错误")
|
||
}
|
||
sslConfig := fmt.Sprintf(`# ssl标记位开始
|
||
ssl_certificate %s/server/vhost/ssl/%s.pem;
|
||
ssl_certificate_key %s/server/vhost/ssl/%s.key;
|
||
ssl_session_timeout 1d;
|
||
ssl_session_cache shared:SSL:10m;
|
||
ssl_protocols TLSv1.2 TLSv1.3;
|
||
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
|
||
ssl_prefer_server_ciphers off;
|
||
ssl_early_data on;
|
||
`, panel.Root, website.Name, panel.Root, website.Name)
|
||
if req.HTTPRedirect {
|
||
sslConfig += `# http重定向标记位开始
|
||
if ($server_port !~ 443){
|
||
return 301 https://$host$request_uri;
|
||
}
|
||
error_page 497 https://$host$request_uri;
|
||
# http重定向标记位结束
|
||
`
|
||
}
|
||
if req.HSTS {
|
||
sslConfig += `# hsts标记位开始
|
||
add_header Strict-Transport-Security "max-age=63072000" always;
|
||
# hsts标记位结束
|
||
`
|
||
}
|
||
if req.OCSP {
|
||
sslConfig += `# ocsp标记位开始
|
||
ssl_stapling on;
|
||
ssl_stapling_verify on;
|
||
# ocsp标记位结束
|
||
`
|
||
}
|
||
sslConfigOld := str.Cut(raw, "# ssl标记位开始", "# ssl标记位结束")
|
||
if len(strings.TrimSpace(sslConfigOld)) != 0 {
|
||
raw = strings.Replace(raw, sslConfigOld, "", -1)
|
||
}
|
||
raw = strings.Replace(raw, "# ssl标记位开始", sslConfig, -1)
|
||
} else {
|
||
sslConfigOld := str.Cut(raw, "# ssl标记位开始", "# ssl标记位结束")
|
||
if len(strings.TrimSpace(sslConfigOld)) != 0 {
|
||
raw = strings.Replace(raw, sslConfigOld, "\n ", -1)
|
||
}
|
||
}
|
||
|
||
if website.PHP != req.PHP {
|
||
website.PHP = req.PHP
|
||
phpConfigOld := str.Cut(raw, "# php标记位开始", "# php标记位结束")
|
||
phpConfig := `
|
||
include enable-php-` + strconv.Itoa(website.PHP) + `.conf;
|
||
`
|
||
if len(strings.TrimSpace(phpConfigOld)) != 0 {
|
||
raw = strings.Replace(raw, phpConfigOld, phpConfig, -1)
|
||
}
|
||
}
|
||
|
||
if err = panel.Orm.Save(website).Error; err != nil {
|
||
return err
|
||
}
|
||
|
||
if err = io.Write(filepath.Join(panel.Root, "server/vhost", website.Name+".conf"), raw, 0644); err != nil {
|
||
return err
|
||
}
|
||
if err = io.Write(filepath.Join(panel.Root, "server/vhost/rewrite", website.Name+".conf"), req.Rewrite, 0644); err != nil {
|
||
return err
|
||
}
|
||
|
||
err = systemctl.Reload("openresty")
|
||
if err != nil {
|
||
_, err = shell.Execf("openresty -t")
|
||
}
|
||
|
||
return err
|
||
}
|
||
|
||
func (r *websiteRepo) Delete(req *request.WebsiteDelete) error {
|
||
website := new(biz.Website)
|
||
if err := panel.Orm.Preload("Cert").Where("id", req.ID).First(website).Error; err != nil {
|
||
return err
|
||
}
|
||
|
||
if website.Cert != nil {
|
||
return errors.New("网站" + website.Name + "已绑定SSL证书,请先删除证书")
|
||
}
|
||
|
||
if err := panel.Orm.Delete(website).Error; err != nil {
|
||
return err
|
||
}
|
||
|
||
_ = io.Remove(filepath.Join(panel.Root, "server/vhost", website.Name+".conf"))
|
||
_ = io.Remove(filepath.Join(panel.Root, "server/vhost/rewrite", website.Name+".conf"))
|
||
_ = io.Remove(filepath.Join(panel.Root, "server/vhost/acme", website.Name+".conf"))
|
||
_ = io.Remove(filepath.Join(panel.Root, "server/vhost/ssl", website.Name+".pem"))
|
||
_ = io.Remove(filepath.Join(panel.Root, "server/vhost/ssl", website.Name+".key"))
|
||
|
||
if req.Path {
|
||
_ = io.Remove(website.Path)
|
||
}
|
||
if req.DB {
|
||
rootPassword, err := r.settingRepo.Get(biz.SettingKeyPerconaRootPassword)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
mysql, err := db.NewMySQL("root", rootPassword, "/tmp/mysql.sock", "unix")
|
||
if err != nil {
|
||
return err
|
||
}
|
||
_ = mysql.DatabaseDrop(website.Name)
|
||
_ = mysql.UserDrop(website.Name)
|
||
_, _ = shell.Execf(`echo "DROP DATABASE IF EXISTS '%s';" | su - postgres -c "psql"`, website.Name)
|
||
_, _ = shell.Execf(`echo "DROP USER IF EXISTS '%s';" | su - postgres -c "psql"`, website.Name)
|
||
}
|
||
|
||
err := systemctl.Reload("openresty")
|
||
if err != nil {
|
||
_, err = shell.Execf("openresty -t")
|
||
}
|
||
|
||
return err
|
||
}
|
||
|
||
func (r *websiteRepo) ClearLog(id uint) error {
|
||
website := new(biz.Website)
|
||
if err := panel.Orm.Where("id", id).First(website).Error; err != nil {
|
||
return err
|
||
}
|
||
|
||
_, err := shell.Execf(`echo "" > %s/wwwlogs/%s.log`, panel.Root, website.Name)
|
||
return err
|
||
}
|
||
|
||
func (r *websiteRepo) UpdateRemark(id uint, remark string) error {
|
||
website := new(biz.Website)
|
||
if err := panel.Orm.Where("id", id).First(website).Error; err != nil {
|
||
return err
|
||
}
|
||
|
||
website.Remark = remark
|
||
return panel.Orm.Save(website).Error
|
||
}
|
||
|
||
func (r *websiteRepo) ResetConfig(id uint) error {
|
||
website := new(biz.Website)
|
||
if err := panel.Orm.Where("id", id).First(&website).Error; err != nil {
|
||
return err
|
||
}
|
||
|
||
website.Status = true
|
||
website.SSL = false
|
||
if err := panel.Orm.Save(website).Error; err != nil {
|
||
return err
|
||
}
|
||
|
||
raw := fmt.Sprintf(`
|
||
# 配置文件中的标记位请勿随意修改,改错将导致面板无法识别!
|
||
# 有自定义配置需求的,请将自定义的配置写在各标记位下方。
|
||
server
|
||
{
|
||
# port标记位开始
|
||
listen 80;
|
||
# port标记位结束
|
||
# server_name标记位开始
|
||
server_name localhost;
|
||
# server_name标记位结束
|
||
# index标记位开始
|
||
index index.php index.html;
|
||
# index标记位结束
|
||
# root标记位开始
|
||
root %s;
|
||
# root标记位结束
|
||
|
||
# ssl标记位开始
|
||
# ssl标记位结束
|
||
|
||
# php标记位开始
|
||
include enable-php-%d.conf;
|
||
# php标记位结束
|
||
|
||
# waf标记位开始
|
||
waf off;
|
||
waf_rule_path %s/server/openresty/ngx_waf/assets/rules/;
|
||
waf_mode DYNAMIC;
|
||
waf_cc_deny rate=1000r/m duration=60m;
|
||
waf_cache capacity=50;
|
||
# waf标记位结束
|
||
|
||
# 错误页配置,可自行设置
|
||
error_page 404 /404.html;
|
||
#error_page 502 /502.html;
|
||
|
||
# acme证书签发配置,不可修改
|
||
include %s/server/vhost/acme/%s.conf;
|
||
|
||
# 伪静态规则引入,修改后将导致面板设置的伪静态规则失效
|
||
include %s/server/vhost/rewrite/%s.conf;
|
||
|
||
# 面板默认禁止访问部分敏感目录,可自行修改
|
||
location ~ ^/(\.user.ini|\.htaccess|\.git|\.svn)
|
||
{
|
||
return 404;
|
||
}
|
||
# 面板默认不记录静态资源的访问日志并开启1小时浏览器缓存,可自行修改
|
||
location ~ .*\.(js|css)$
|
||
{
|
||
expires 1h;
|
||
error_log /dev/null;
|
||
access_log /dev/null;
|
||
}
|
||
|
||
access_log %s/wwwlogs/%s.log;
|
||
error_log %s/wwwlogs/%s.log;
|
||
}
|
||
|
||
`, website.Path, website.PHP, panel.Root, panel.Root, website.Name, panel.Root, website.Name, panel.Root, website.Name, panel.Root, website.Name)
|
||
if err := io.Write(filepath.Join(panel.Root, "server/vhost", website.Name+".conf"), raw, 0644); err != nil {
|
||
return nil
|
||
}
|
||
if err := io.Write(filepath.Join(panel.Root, "server/vhost/rewrite", website.Name+".conf"), "", 0644); err != nil {
|
||
return nil
|
||
}
|
||
if err := io.Write(filepath.Join(panel.Root, "server/vhost/acme", website.Name+".conf"), "", 0644); err != nil {
|
||
return nil
|
||
}
|
||
if err := systemctl.Reload("openresty"); err != nil {
|
||
_, err = shell.Execf("openresty -t")
|
||
return err
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
func (r *websiteRepo) UpdateStatus(id uint, status bool) error {
|
||
website := new(biz.Website)
|
||
if err := panel.Orm.Where("id", id).First(&website).Error; err != nil {
|
||
return err
|
||
}
|
||
|
||
website.Status = status
|
||
if err := panel.Orm.Save(website).Error; err != nil {
|
||
return err
|
||
}
|
||
|
||
raw, err := io.Read(filepath.Join(panel.Root, "server/vhost", website.Name+".conf"))
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
// 运行目录
|
||
rootConfig := str.Cut(raw, "# root标记位开始\n", "# root标记位结束")
|
||
match := regexp.MustCompile(`root\s+(.+);`).FindStringSubmatch(rootConfig)
|
||
if len(match) == 2 {
|
||
if website.Status {
|
||
root := regexp.MustCompile(`# root\s+(.+);`).FindStringSubmatch(rootConfig)
|
||
raw = strings.ReplaceAll(raw, rootConfig, fmt.Sprintf(" root %s;\n ", root[1]))
|
||
} else {
|
||
raw = strings.ReplaceAll(raw, rootConfig, fmt.Sprintf(" root %s/server/openresty/html;\n # root %s;\n ", panel.Root, match[1]))
|
||
}
|
||
}
|
||
|
||
// 默认文件
|
||
indexConfig := str.Cut(raw, "# index标记位开始\n", "# index标记位结束")
|
||
match = regexp.MustCompile(`index\s+(.+);`).FindStringSubmatch(indexConfig)
|
||
if len(match) == 2 {
|
||
if website.Status {
|
||
index := regexp.MustCompile(`# index\s+(.+);`).FindStringSubmatch(indexConfig)
|
||
raw = strings.ReplaceAll(raw, indexConfig, " index "+index[1]+";\n ")
|
||
} else {
|
||
raw = strings.ReplaceAll(raw, indexConfig, " index stop.html;\n # index "+match[1]+";\n ")
|
||
}
|
||
}
|
||
|
||
if err = io.Write(filepath.Join(panel.Root, "server/vhost", website.Name+".conf"), raw, 0644); err != nil {
|
||
return err
|
||
}
|
||
if err = systemctl.Reload("openresty"); err != nil {
|
||
_, err = shell.Execf("openresty -t")
|
||
return err
|
||
}
|
||
|
||
return nil
|
||
}
|