mirror of
https://github.com/acepanel/panel.git
synced 2026-02-04 00:49:22 +08:00
refactor: 网站nginx配置解析生成
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
package fail2ban
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strings"
|
||||
@@ -111,10 +112,12 @@ func (s *Service) Create(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
var ports string
|
||||
for _, port := range website.Ports {
|
||||
fields := strings.Fields(cast.ToString(port))
|
||||
ports += fields[0] + ","
|
||||
for _, listen := range website.Listens {
|
||||
if port, err := cast.ToIntE(listen.Address); err == nil {
|
||||
ports += fmt.Sprintf("%d", port) + ","
|
||||
}
|
||||
}
|
||||
ports = strings.TrimSuffix(ports, ",")
|
||||
|
||||
rule := `
|
||||
# ` + jailWebsiteName + `-` + jailWebsiteMode + `-START
|
||||
|
||||
@@ -12,8 +12,7 @@ type Website struct {
|
||||
Name string `gorm:"not null;unique" json:"name"`
|
||||
Status bool `gorm:"not null;default:true" json:"status"`
|
||||
Path string `gorm:"not null" json:"path"`
|
||||
PHP int `gorm:"not null" json:"php"`
|
||||
SSL bool `gorm:"not null" json:"ssl"`
|
||||
HTTPS bool `gorm:"not null" json:"https"`
|
||||
Remark string `gorm:"not null" json:"remark"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
|
||||
@@ -4,13 +4,9 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cast"
|
||||
|
||||
"github.com/TheTNB/panel/internal/app"
|
||||
"github.com/TheTNB/panel/internal/biz"
|
||||
"github.com/TheTNB/panel/internal/embed"
|
||||
@@ -18,8 +14,9 @@ import (
|
||||
"github.com/TheTNB/panel/pkg/cert"
|
||||
"github.com/TheTNB/panel/pkg/db"
|
||||
"github.com/TheTNB/panel/pkg/io"
|
||||
"github.com/TheTNB/panel/pkg/nginx"
|
||||
"github.com/TheTNB/panel/pkg/punycode"
|
||||
"github.com/TheTNB/panel/pkg/shell"
|
||||
"github.com/TheTNB/panel/pkg/str"
|
||||
"github.com/TheTNB/panel/pkg/systemctl"
|
||||
"github.com/TheTNB/panel/pkg/types"
|
||||
)
|
||||
@@ -59,79 +56,72 @@ func (r *websiteRepo) Get(id uint) (*types.WebsiteSetting, error) {
|
||||
if err := app.Orm.Where("id", id).First(website).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 解析nginx配置
|
||||
config, err := io.Read(filepath.Join(app.Root, "server/vhost", website.Name+".conf"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p, err := nginx.NewParser(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
setting := new(types.WebsiteSetting)
|
||||
setting.ID = website.ID
|
||||
setting.Name = website.Name
|
||||
setting.Path = website.Path
|
||||
setting.SSL = website.SSL
|
||||
setting.PHP = website.PHP
|
||||
setting.HTTPS = website.HTTPS
|
||||
setting.PHP = p.GetPHP()
|
||||
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 {
|
||||
// 监听地址
|
||||
listens, err := p.GetListen()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for listen := range slices.Values(listens) {
|
||||
if len(listen) == 0 {
|
||||
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, cast.ToUint(ports[0])) {
|
||||
setting.Ports = append(setting.Ports, cast.ToUint(ports[0]))
|
||||
}
|
||||
if len(ports) > 1 && ports[1] == "ssl" {
|
||||
setting.SSLPorts = append(setting.SSLPorts, cast.ToUint(ports[0]))
|
||||
} else if len(ports) > 1 && ports[1] == "quic" {
|
||||
setting.QUICPorts = append(setting.QUICPorts, cast.ToUint(ports[0]))
|
||||
}
|
||||
setting.Listens = append(setting.Listens, types.WebsiteListen{
|
||||
Address: listen[0],
|
||||
HTTPS: slices.Contains(listen, "ssl"),
|
||||
QUIC: slices.Contains(listen, "quic"),
|
||||
})
|
||||
}
|
||||
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], " ")
|
||||
// 域名
|
||||
domains, err := p.GetServerName()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
root := str.Cut(config, "# root标记位开始", "# root标记位结束")
|
||||
match = regexp.MustCompile(`root\s+([^;]*);?`).FindStringSubmatch(root)
|
||||
if len(match) > 1 {
|
||||
setting.Root = match[1]
|
||||
domains, err = punycode.DecodeDomains(domains)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
index := str.Cut(config, "# index标记位开始", "# index标记位结束")
|
||||
match = regexp.MustCompile(`index\s+([^;]*);?`).FindStringSubmatch(index)
|
||||
if len(match) > 1 {
|
||||
setting.Index = match[1]
|
||||
}
|
||||
|
||||
setting.Domains = domains
|
||||
// 运行目录
|
||||
root, _ := p.GetRoot()
|
||||
setting.Root = root
|
||||
// 默认文档
|
||||
index, _ := p.GetIndex()
|
||||
setting.Index = index
|
||||
// 防跨站
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
// HTTPS
|
||||
if setting.HTTPS {
|
||||
setting.HTTPRedirect = p.GetHTTPSRedirect()
|
||||
setting.HSTS = p.GetHSTS()
|
||||
setting.OCSP = p.GetOCSP()
|
||||
}
|
||||
// 证书
|
||||
crt, _ := io.Read(filepath.Join(app.Root, "server/vhost/ssl", website.Name+".pem"))
|
||||
setting.SSLCertificate = crt
|
||||
key, _ := io.Read(filepath.Join(app.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")
|
||||
@@ -140,9 +130,10 @@ func (r *websiteRepo) Get(id uint) (*types.WebsiteSetting, error) {
|
||||
setting.SSLOCSPServer = decode.OCSPServer
|
||||
setting.SSLDNSNames = decode.DNSNames
|
||||
}
|
||||
|
||||
// 伪静态
|
||||
rewrite, _ := io.Read(filepath.Join(app.Root, "server/vhost/rewrite", website.Name+".conf"))
|
||||
setting.Rewrite = rewrite
|
||||
// 访问日志
|
||||
log, _ := shell.Execf(`tail -n 100 '%s/wwwlogs/%s.log'`, app.Root, website.Name)
|
||||
setting.Log = log
|
||||
|
||||
@@ -175,21 +166,58 @@ func (r *websiteRepo) List(page, limit uint) ([]*biz.Website, int64, error) {
|
||||
}
|
||||
|
||||
func (r *websiteRepo) Create(req *request.WebsiteCreate) (*biz.Website, error) {
|
||||
w := &biz.Website{
|
||||
Name: req.Name,
|
||||
Status: true,
|
||||
Path: req.Path,
|
||||
PHP: req.PHP,
|
||||
SSL: false,
|
||||
// 初始化nginx配置
|
||||
p, err := nginx.NewParser()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := app.Orm.Create(w).Error; err != nil {
|
||||
// 监听地址
|
||||
var listens [][]string
|
||||
for _, listen := range req.Listens {
|
||||
listens = append(listens, []string{listen}) // ipv4
|
||||
listens = append(listens, []string{"[::]:" + listen}) // ipv6
|
||||
}
|
||||
if err = p.SetListen(listens); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// 域名
|
||||
domains, err := punycode.EncodeDomains(req.Domains)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = p.SetServerName(domains); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// 运行目录
|
||||
if err = p.SetRoot(req.Path); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// PHP
|
||||
if err = p.SetPHP(req.PHP); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// 伪静态
|
||||
includes, comments, err := p.GetIncludes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
includes = append(includes, filepath.Join(app.Root, "server/vhost/rewrite", req.Name+".conf"))
|
||||
comments = append(comments, []string{"# 伪静态规则"})
|
||||
if err = p.SetIncludes(includes, comments); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// 日志
|
||||
if err = p.SetAccessLog(filepath.Join(app.Root, "wwwlogs", req.Name+".log")); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = p.SetErrorLog(filepath.Join(app.Root, "wwwlogs", req.Name+".error.log")); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := io.Mkdir(req.Path, 0755); err != nil {
|
||||
// 初始化网站目录
|
||||
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)
|
||||
@@ -197,100 +225,21 @@ func (r *websiteRepo) Create(req *request.WebsiteCreate) (*biz.Website, error) {
|
||||
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 {
|
||||
if err = io.Write(filepath.Join(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-%d.conf;
|
||||
# php标记位结束
|
||||
|
||||
# 错误页配置,可自行设置
|
||||
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, app.Root, req.Name, app.Root, req.Name, app.Root, req.Name, app.Root, req.Name)
|
||||
|
||||
if err = io.Write(filepath.Join(app.Root, "server/vhost", req.Name+".conf"), nginxConf, 0644); err != nil {
|
||||
// 写nginx配置
|
||||
if err = io.Write(filepath.Join(app.Root, "server/vhost", req.Name+".conf"), p.Dump(), 0644); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = io.Write(filepath.Join(app.Root, "server/vhost/rewrite", req.Name+".conf"), "", 0644); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = io.Write(filepath.Join(app.Root, "server/vhost/acme", req.Name+".conf"), "", 0644); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = io.Write(filepath.Join(app.Root, "server/vhost/ssl", req.Name+".pem"), "", 0644); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -298,6 +247,7 @@ server
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 设置目录权限
|
||||
if err = io.Chmod(req.Path, 0755); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -305,11 +255,23 @@ server
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 创建面板网站
|
||||
w := &biz.Website{
|
||||
Name: req.Name,
|
||||
Status: true,
|
||||
Path: req.Path,
|
||||
HTTPS: false,
|
||||
}
|
||||
if err = app.Orm.Create(w).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = systemctl.Reload("nginx"); err != nil {
|
||||
_, err = shell.Execf("nginx -t")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 创建数据库
|
||||
rootPassword, err := r.settingRepo.Get(biz.SettingKeyMySQLRootPassword)
|
||||
if err == nil && req.DB && req.DBType == "mysql" {
|
||||
mysql, err := db.NewMySQL("root", rootPassword, "/tmp/mysql.sock", "unix")
|
||||
@@ -344,17 +306,17 @@ func (r *websiteRepo) Update(req *request.WebsiteUpdate) error {
|
||||
if err := app.Orm.Where("id", req.ID).First(website).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !website.Status {
|
||||
return errors.New("网站已停用,请先启用")
|
||||
}
|
||||
|
||||
// 原文
|
||||
raw, err := io.Read(filepath.Join(app.Root, "server/vhost", website.Name+".conf"))
|
||||
// 解析nginx配置
|
||||
config, err := io.Read(filepath.Join(app.Root, "server/vhost", website.Name+".conf"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if strings.TrimSpace(raw) != strings.TrimSpace(req.Raw) {
|
||||
// 如果修改了原文,直接写入返回
|
||||
if strings.TrimSpace(config) != strings.TrimSpace(req.Raw) {
|
||||
if err = io.Write(filepath.Join(app.Root, "server/vhost", website.Name+".conf"), req.Raw, 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -362,188 +324,129 @@ func (r *websiteRepo) Update(req *request.WebsiteUpdate) error {
|
||||
_, err = shell.Execf("nginx -t")
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 目录
|
||||
path := req.Path
|
||||
if !io.Exists(path) {
|
||||
// 初始化nginx配置
|
||||
p, err := nginx.NewParser(config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// 监听地址
|
||||
var listens [][]string
|
||||
for _, listen := range req.Listens {
|
||||
listens = append(listens, []string{listen.Address})
|
||||
if listen.HTTPS {
|
||||
listens = append(listens, []string{listen.Address, "ssl"})
|
||||
}
|
||||
if listen.QUIC {
|
||||
listens = append(listens, []string{listen.Address, "quic"})
|
||||
}
|
||||
}
|
||||
if err = p.SetListen(listens); err != nil {
|
||||
return err
|
||||
}
|
||||
// 域名
|
||||
domains, err := punycode.EncodeDomains(req.Domains)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = p.SetServerName(domains); err != nil {
|
||||
return err
|
||||
}
|
||||
// 首页文件
|
||||
if err = p.SetIndex(req.Index); err != nil {
|
||||
return err
|
||||
}
|
||||
// 运行目录
|
||||
if !io.Exists(req.Root) {
|
||||
return errors.New("运行目录不存在")
|
||||
}
|
||||
if err = p.SetRoot(req.Root); err != nil {
|
||||
return err
|
||||
}
|
||||
// 运行目录
|
||||
if !io.Exists(req.Path) {
|
||||
return errors.New("网站目录不存在")
|
||||
}
|
||||
website.Path = path
|
||||
|
||||
// 域名
|
||||
domain := "server_name"
|
||||
domains := req.Domains
|
||||
for _, v := range domains {
|
||||
if v == "" {
|
||||
continue
|
||||
website.Path = req.Path
|
||||
// PHP
|
||||
if err = p.SetPHP(req.PHP); err != nil {
|
||||
return err
|
||||
}
|
||||
// HTTPS
|
||||
certPath := filepath.Join(app.Root, "server/vhost/ssl", website.Name+".pem")
|
||||
keyPath := filepath.Join(app.Root, "server/vhost/ssl", website.Name+".key")
|
||||
if err = io.Write(certPath, req.SSLCertificate, 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = io.Write(keyPath, req.SSLCertificateKey, 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
website.HTTPS = req.HTTPS
|
||||
if req.HTTPS {
|
||||
if err = p.SetHTTPS(certPath, keyPath); err != nil {
|
||||
return err
|
||||
}
|
||||
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
|
||||
}
|
||||
if err = p.SetHTTPRedirect(req.HTTPRedirect); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = p.SetHSTS(req.HSTS); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = p.SetOCSP(req.OCSP); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
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"))
|
||||
} else {
|
||||
if err = p.ClearSetHTTPS(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = p.SetHTTPRedirect(false); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = p.SetHSTS(false); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = p.SetOCSP(false); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
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 += "/"
|
||||
}
|
||||
userIni := filepath.Join(req.Root, ".user.ini")
|
||||
if req.OpenBasedir {
|
||||
if err = io.Write(req.Root+".user.ini", "open_basedir="+path+":/tmp/", 0644); err != nil {
|
||||
if err = io.Write(userIni, fmt.Sprintf("open_basedir=%s:/tmp/", req.Root), 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
_, _ = shell.Execf(`chattr +i '%s'`, userIni)
|
||||
} else {
|
||||
if io.Exists(req.Root + ".user.ini") {
|
||||
if err = io.Remove(req.Root + ".user.ini"); err != nil {
|
||||
if io.Exists(userIni) {
|
||||
_, _ = shell.Execf(`chattr -i '%s'`, userIni)
|
||||
if err = io.Remove(userIni); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SSL
|
||||
if err = io.Write(filepath.Join(app.Root, "server/vhost/ssl", website.Name+".pem"), req.SSLCertificate, 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = io.Write(filepath.Join(app.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;
|
||||
`, app.Root, website.Name, app.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 = app.Orm.Save(website).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = io.Write(filepath.Join(app.Root, "server/vhost", website.Name+".conf"), raw, 0644); err != nil {
|
||||
if err = io.Write(filepath.Join(app.Root, "server/vhost", website.Name+".conf"), p.Dump(), 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = io.Write(filepath.Join(app.Root, "server/vhost/rewrite", website.Name+".conf"), req.Rewrite, 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = systemctl.Reload("nginx")
|
||||
if err != nil {
|
||||
if err = app.Orm.Save(website).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = systemctl.Reload("nginx"); err != nil {
|
||||
_, err = shell.Execf("nginx -t")
|
||||
}
|
||||
|
||||
return err
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *websiteRepo) Delete(req *request.WebsiteDelete) error {
|
||||
@@ -551,15 +454,10 @@ func (r *websiteRepo) Delete(req *request.WebsiteDelete) error {
|
||||
if err := app.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 := app.Orm.Delete(website).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_ = io.Remove(filepath.Join(app.Root, "server/vhost", website.Name+".conf"))
|
||||
_ = io.Remove(filepath.Join(app.Root, "server/vhost/rewrite", website.Name+".conf"))
|
||||
_ = io.Remove(filepath.Join(app.Root, "server/vhost/acme", website.Name+".conf"))
|
||||
@@ -584,12 +482,15 @@ func (r *websiteRepo) Delete(req *request.WebsiteDelete) error {
|
||||
_, _ = shell.Execf(`echo "DROP USER IF EXISTS '%s';" | su - postgres -c "psql"`, website.Name)
|
||||
}
|
||||
|
||||
err := systemctl.Reload("nginx")
|
||||
if err != nil {
|
||||
if err := app.Orm.Delete(website).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := systemctl.Reload("nginx"); err != nil {
|
||||
_, err = shell.Execf("nginx -t")
|
||||
}
|
||||
|
||||
return err
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *websiteRepo) ClearLog(id uint) error {
|
||||
@@ -618,75 +519,47 @@ func (r *websiteRepo) ResetConfig(id uint) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// 初始化nginx配置
|
||||
p, err := nginx.NewParser()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// 运行目录
|
||||
if err = p.SetRoot(website.Path); err != nil {
|
||||
return err
|
||||
}
|
||||
// 伪静态
|
||||
includes, comments, err := p.GetIncludes()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
includes = append(includes, filepath.Join(app.Root, "server/vhost/rewrite", website.Name+".conf"))
|
||||
comments = append(comments, []string{"# 伪静态规则"})
|
||||
if err = p.SetIncludes(includes, comments); err != nil {
|
||||
return err
|
||||
}
|
||||
// 日志
|
||||
if err = p.SetAccessLog(filepath.Join(app.Root, "wwwlogs", website.Name+".log")); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = p.SetErrorLog(filepath.Join(app.Root, "wwwlogs", website.Name+".error.log")); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = io.Write(filepath.Join(app.Root, "server/vhost", website.Name+".conf"), p.Dump(), 0644); err != nil {
|
||||
return nil
|
||||
}
|
||||
if err = io.Write(filepath.Join(app.Root, "server/vhost/rewrite", website.Name+".conf"), "", 0644); err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
website.Status = true
|
||||
website.SSL = false
|
||||
website.HTTPS = false
|
||||
if err := app.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标记位结束
|
||||
|
||||
# 错误页配置,可自行设置
|
||||
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, app.Root, website.Name, app.Root, website.Name, app.Root, website.Name, app.Root, website.Name)
|
||||
if err := io.Write(filepath.Join(app.Root, "server/vhost", website.Name+".conf"), raw, 0644); err != nil {
|
||||
return nil
|
||||
}
|
||||
if err := io.Write(filepath.Join(app.Root, "server/vhost/rewrite", website.Name+".conf"), "", 0644); err != nil {
|
||||
return nil
|
||||
}
|
||||
if err := io.Write(filepath.Join(app.Root, "server/vhost/acme", website.Name+".conf"), "", 0644); err != nil {
|
||||
return nil
|
||||
}
|
||||
if err := systemctl.Reload("nginx"); err != nil {
|
||||
if err = systemctl.Reload("nginx"); err != nil {
|
||||
_, err = shell.Execf("nginx -t")
|
||||
return err
|
||||
}
|
||||
@@ -700,43 +573,61 @@ func (r *websiteRepo) UpdateStatus(id uint, status bool) error {
|
||||
return err
|
||||
}
|
||||
|
||||
website.Status = status
|
||||
if err := app.Orm.Save(website).Error; err != nil {
|
||||
// 解析nginx配置
|
||||
config, err := io.Read(filepath.Join(app.Root, "server/vhost", website.Name+".conf"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
raw, err := io.Read(filepath.Join(app.Root, "server/vhost", website.Name+".conf"))
|
||||
p, err := nginx.NewParser(config)
|
||||
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/nginx/html;\n # root %s;\n ", app.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(app.Root, "server/vhost", website.Name+".conf"), raw, 0644); err != nil {
|
||||
// 取运行目录和默认文档
|
||||
root, rootComment, err := p.GetRootWithComment()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
index, indexComment, err := p.GetIndexWithComment()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
indexStr := strings.Join(index, " ")
|
||||
|
||||
if status {
|
||||
if len(rootComment) == 0 {
|
||||
return fmt.Errorf("未找到运行目录注释")
|
||||
}
|
||||
if !io.Exists(rootComment[0]) {
|
||||
return fmt.Errorf("运行目录不存在")
|
||||
}
|
||||
if err = p.SetRoot(rootComment[0]); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(indexComment) == 0 {
|
||||
return fmt.Errorf("未找到默认文档注释")
|
||||
}
|
||||
if err = p.SetIndex(strings.Fields(indexStr)); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err = p.SetRootWithComment(filepath.Join(app.Root, "server/nginx/html"), []string{root}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = p.SetIndexWithComment([]string{"stop.html"}, []string{indexStr}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err = io.Write(filepath.Join(app.Root, "server/vhost", website.Name+".conf"), p.Dump(), 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
website.Status = status
|
||||
if err = app.Orm.Save(website).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = systemctl.Reload("nginx"); err != nil {
|
||||
_, err = shell.Execf("nginx -t")
|
||||
return err
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package request
|
||||
|
||||
import "github.com/TheTNB/panel/pkg/types"
|
||||
|
||||
type WebsiteDefaultConfig struct {
|
||||
Index string `json:"index" form:"index" validate:"required"`
|
||||
Stop string `json:"stop" form:"stop" validate:"required"`
|
||||
@@ -7,10 +9,10 @@ type WebsiteDefaultConfig struct {
|
||||
|
||||
type WebsiteCreate struct {
|
||||
Name string `form:"name" json:"name" validate:"required"`
|
||||
Listens []string `form:"listens" json:"listens" validate:"required"`
|
||||
Domains []string `form:"domains" json:"domains" validate:"required"`
|
||||
Ports []uint `form:"ports" json:"ports" validate:"required"`
|
||||
Path string `form:"path" json:"path"`
|
||||
PHP int `form:"php" json:"php"`
|
||||
PHP int `form:"php" json:"php" validate:"required,number,gte=0"`
|
||||
DB bool `form:"db" json:"db"`
|
||||
DBType string `form:"db_type" json:"db_type"`
|
||||
DBName string `form:"db_name" json:"db_name"`
|
||||
@@ -25,24 +27,22 @@ type WebsiteDelete struct {
|
||||
}
|
||||
|
||||
type WebsiteUpdate struct {
|
||||
ID uint `form:"id" json:"id" validate:"required"`
|
||||
Domains []string `form:"domains" json:"domains" validate:"required"`
|
||||
Listens []string `form:"listens" json:"listens" validate:"required"`
|
||||
SSLPorts []uint `form:"ssl_ports" json:"ssl_ports" validate:"required"`
|
||||
QUICPorts []uint `form:"quic_ports" json:"quic_ports" validate:"required"`
|
||||
OCSP bool `form:"ocsp" json:"ocsp"`
|
||||
HSTS bool `form:"hsts" json:"hsts"`
|
||||
SSL bool `form:"ssl" json:"ssl"`
|
||||
HTTPRedirect bool `form:"http_redirect" json:"http_redirect"`
|
||||
OpenBasedir bool `form:"open_basedir" json:"open_basedir"`
|
||||
Index string `form:"index" json:"index" validate:"required"`
|
||||
Path string `form:"path" json:"path" validate:"required"`
|
||||
Root string `form:"root" json:"root" validate:"required"`
|
||||
Raw string `form:"raw" json:"raw"`
|
||||
Rewrite string `form:"rewrite" json:"rewrite"`
|
||||
PHP int `form:"php" json:"php"`
|
||||
SSLCertificate string `form:"ssl_certificate" json:"ssl_certificate"`
|
||||
SSLCertificateKey string `form:"ssl_certificate_key" json:"ssl_certificate_key"`
|
||||
ID uint `form:"id" json:"id" validate:"required"`
|
||||
Listens []types.WebsiteListen `form:"listens" json:"listens" validate:"required"`
|
||||
Domains []string `form:"domains" json:"domains" validate:"required"`
|
||||
HTTPS bool `form:"https" json:"https"`
|
||||
OCSP bool `form:"ocsp" json:"ocsp"`
|
||||
HSTS bool `form:"hsts" json:"hsts"`
|
||||
HTTPRedirect bool `form:"http_redirect" json:"http_redirect"`
|
||||
OpenBasedir bool `form:"open_basedir" json:"open_basedir"`
|
||||
Index []string `form:"index" json:"index" validate:"required"`
|
||||
Path string `form:"path" json:"path" validate:"required"` // 网站目录
|
||||
Root string `form:"root" json:"root" validate:"required"` // 运行目录
|
||||
Raw string `form:"raw" json:"raw"`
|
||||
Rewrite string `form:"rewrite" json:"rewrite"`
|
||||
PHP int `form:"php" json:"php"`
|
||||
SSLCertificate string `form:"ssl_certificate" json:"ssl_certificate"`
|
||||
SSLCertificateKey string `form:"ssl_certificate_key" json:"ssl_certificate_key"`
|
||||
}
|
||||
|
||||
type WebsiteUpdateRemark struct {
|
||||
|
||||
@@ -108,15 +108,15 @@ func Cli() []*cli.Command {
|
||||
Required: true,
|
||||
},
|
||||
&cli.StringSliceFlag{
|
||||
Name: "domains",
|
||||
Name: "domain",
|
||||
Usage: "与网站关联的域名列表",
|
||||
Aliases: []string{"d"},
|
||||
Required: true,
|
||||
},
|
||||
&cli.UintSliceFlag{
|
||||
Name: "ports",
|
||||
Usage: "网站使用的端口列表",
|
||||
Aliases: []string{"p"},
|
||||
&cli.StringSliceFlag{
|
||||
Name: "listen",
|
||||
Usage: "与网站关联的监听地址列表",
|
||||
Aliases: []string{"l"},
|
||||
Required: true,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"time"
|
||||
|
||||
"github.com/go-rat/utils/hash"
|
||||
@@ -350,16 +351,17 @@ func (s *CliService) Port(ctx context.Context, cmd *cli.Command) error {
|
||||
|
||||
func (s *CliService) WebsiteCreate(ctx context.Context, cmd *cli.Command) error {
|
||||
var ports []uint
|
||||
for _, port := range cmd.IntSlice("ports") {
|
||||
for port := range slices.Values(cmd.IntSlice("ports")) {
|
||||
if port < 1 || port > 65535 {
|
||||
return fmt.Errorf("端口范围错误")
|
||||
}
|
||||
ports = append(ports, uint(port))
|
||||
}
|
||||
|
||||
req := &request.WebsiteCreate{
|
||||
Name: cmd.String("name"),
|
||||
Domains: cmd.StringSlice("domains"),
|
||||
Ports: ports,
|
||||
Listens: cmd.StringSlice("listen"),
|
||||
Path: cmd.String("path"),
|
||||
PHP: int(cmd.Int("php")),
|
||||
DB: false,
|
||||
|
||||
@@ -11,8 +11,6 @@ const defaultConf = `server {
|
||||
root /www/wwwroot/default;
|
||||
# 错误页配置
|
||||
error_page 404 /404.html;
|
||||
# 伪静态规则
|
||||
include /www/server/vhost/rewrite/default.conf;
|
||||
include enable-php-0.conf;
|
||||
# 不记录静态文件日志
|
||||
location ~ .*\.(bmp|jpg|jpeg|png|gif|svg|ico|tiff|webp|avif|heif|heic|jxl)$ {
|
||||
|
||||
@@ -38,6 +38,15 @@ func (p *Parser) GetIndex() ([]string, error) {
|
||||
return directive.GetParameters(), nil
|
||||
}
|
||||
|
||||
func (p *Parser) GetIndexWithComment() ([]string, []string, error) {
|
||||
directive, err := p.FindOne("server.index")
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return directive.GetParameters(), directive.GetComment(), nil
|
||||
}
|
||||
|
||||
func (p *Parser) GetRoot() (string, error) {
|
||||
directive, err := p.FindOne("server.root")
|
||||
if err != nil {
|
||||
@@ -50,6 +59,18 @@ func (p *Parser) GetRoot() (string, error) {
|
||||
return directive.GetParameters()[0], nil
|
||||
}
|
||||
|
||||
func (p *Parser) GetRootWithComment() (string, []string, error) {
|
||||
directive, err := p.FindOne("server.root")
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
if len(directive.GetParameters()) == 0 {
|
||||
return "", directive.GetComment(), nil
|
||||
}
|
||||
|
||||
return directive.GetParameters()[0], directive.GetComment(), nil
|
||||
}
|
||||
|
||||
func (p *Parser) GetIncludes() (includes []string, comments [][]string, err error) {
|
||||
directives, err := p.Find("server.include")
|
||||
if err != nil {
|
||||
@@ -67,10 +88,10 @@ func (p *Parser) GetIncludes() (includes []string, comments [][]string, err erro
|
||||
return includes, comments, nil
|
||||
}
|
||||
|
||||
func (p *Parser) GetPHP() (int, error) {
|
||||
func (p *Parser) GetPHP() int {
|
||||
directives, err := p.Find("server.include")
|
||||
if err != nil {
|
||||
return 0, err
|
||||
return 0
|
||||
}
|
||||
|
||||
var result int
|
||||
@@ -82,7 +103,7 @@ func (p *Parser) GetPHP() (int, error) {
|
||||
}
|
||||
}
|
||||
|
||||
return result, err
|
||||
return result
|
||||
}
|
||||
|
||||
func (p *Parser) GetHTTPS() bool {
|
||||
@@ -145,21 +166,21 @@ func (p *Parser) GetHSTS() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (p *Parser) GetHTTPSRedirect() (bool, error) {
|
||||
func (p *Parser) GetHTTPSRedirect() bool {
|
||||
directives, err := p.Find("server.if")
|
||||
if err != nil {
|
||||
return false, err
|
||||
return false
|
||||
}
|
||||
|
||||
for _, dir := range directives {
|
||||
for _, dir2 := range dir.GetBlock().GetDirectives() {
|
||||
if dir2.GetName() == "return" && slices.Contains(dir2.GetParameters(), "https://$host$request_uri") {
|
||||
return true, nil
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false, nil
|
||||
return false
|
||||
}
|
||||
|
||||
func (p *Parser) GetAccessLog() (string, error) {
|
||||
|
||||
@@ -68,8 +68,8 @@ func (s *NginxTestSuite) TestIncludes() {
|
||||
s.NoError(err)
|
||||
includes, comments, err := parser.GetIncludes()
|
||||
s.NoError(err)
|
||||
s.Equal([]string{"/www/server/vhost/rewrite/default.conf", "enable-php-0.conf"}, includes)
|
||||
s.Equal([][]string{{"# 伪静态规则"}, []string(nil)}, comments)
|
||||
s.Equal([]string{"enable-php-0.conf"}, includes)
|
||||
s.Equal([][]string{[]string(nil)}, comments)
|
||||
s.NoError(parser.SetIncludes([]string{"/www/server/vhost/rewrite/default.conf"}, nil))
|
||||
includes, comments, err = parser.GetIncludes()
|
||||
s.NoError(err)
|
||||
@@ -82,6 +82,16 @@ func (s *NginxTestSuite) TestIncludes() {
|
||||
s.Equal([][]string{{"# 伪静态规则测试"}}, comments)
|
||||
}
|
||||
|
||||
func (s *NginxTestSuite) TestPHP() {
|
||||
parser, err := NewParser()
|
||||
s.NoError(err)
|
||||
s.Equal(0, parser.GetPHP())
|
||||
s.NoError(parser.SetPHP(80))
|
||||
s.Equal(80, parser.GetPHP())
|
||||
s.NoError(parser.SetPHP(0))
|
||||
s.Equal(0, parser.GetPHP())
|
||||
}
|
||||
|
||||
func (s *NginxTestSuite) TestHTTP() {
|
||||
parser, err := NewParser()
|
||||
s.NoError(err)
|
||||
@@ -126,6 +136,8 @@ func (s *NginxTestSuite) TestOCSP() {
|
||||
s.NoError(err)
|
||||
s.NoError(parser.SetHTTPS("/www/server/vhost/ssl/default.pem", "/www/server/vhost/ssl/default.key"))
|
||||
s.False(parser.GetOCSP())
|
||||
s.NoError(parser.SetOCSP(false))
|
||||
s.False(parser.GetOCSP())
|
||||
s.NoError(parser.SetOCSP(true))
|
||||
s.True(parser.GetOCSP())
|
||||
s.NoError(parser.SetOCSP(false))
|
||||
@@ -137,6 +149,8 @@ func (s *NginxTestSuite) TestHSTS() {
|
||||
s.NoError(err)
|
||||
s.NoError(parser.SetHTTPS("/www/server/vhost/ssl/default.pem", "/www/server/vhost/ssl/default.key"))
|
||||
s.False(parser.GetHSTS())
|
||||
s.NoError(parser.SetHSTS(false))
|
||||
s.False(parser.GetHSTS())
|
||||
s.NoError(parser.SetHSTS(true))
|
||||
s.True(parser.GetHSTS())
|
||||
s.NoError(parser.SetHSTS(false))
|
||||
@@ -148,9 +162,11 @@ func (s *NginxTestSuite) TestHTTPSRedirect() {
|
||||
s.NoError(err)
|
||||
s.NoError(parser.SetHTTPS("/www/server/vhost/ssl/default.pem", "/www/server/vhost/ssl/default.key"))
|
||||
s.False(parser.GetHTTPSRedirect())
|
||||
s.NoError(parser.SetHTTPSRedirect(true))
|
||||
s.NoError(parser.SetHTTPRedirect(false))
|
||||
s.False(parser.GetHTTPSRedirect())
|
||||
s.NoError(parser.SetHTTPRedirect(true))
|
||||
s.True(parser.GetHTTPSRedirect())
|
||||
s.NoError(parser.SetHTTPSRedirect(false))
|
||||
s.NoError(parser.SetHTTPRedirect(false))
|
||||
s.False(parser.GetHTTPSRedirect())
|
||||
}
|
||||
|
||||
|
||||
@@ -50,6 +50,20 @@ func (p *Parser) SetIndex(index []string) error {
|
||||
})
|
||||
}
|
||||
|
||||
func (p *Parser) SetIndexWithComment(index []string, comment []string) error {
|
||||
if err := p.Clear("server.index"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return p.Set("server", []*config.Directive{
|
||||
{
|
||||
Name: "index",
|
||||
Parameters: index,
|
||||
Comment: comment,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (p *Parser) SetRoot(root string) error {
|
||||
if err := p.Clear("server.root"); err != nil {
|
||||
return err
|
||||
@@ -63,6 +77,20 @@ func (p *Parser) SetRoot(root string) error {
|
||||
})
|
||||
}
|
||||
|
||||
func (p *Parser) SetRootWithComment(root string, comment []string) error {
|
||||
if err := p.Clear("server.root"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return p.Set("server", []*config.Directive{
|
||||
{
|
||||
Name: "root",
|
||||
Parameters: []string{root},
|
||||
Comment: comment,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (p *Parser) SetIncludes(includes []string, comments [][]string) error {
|
||||
if err := p.Clear("server.include"); err != nil {
|
||||
return err
|
||||
@@ -127,7 +155,7 @@ func (p *Parser) SetPHP(php int) error {
|
||||
return p.Set("server", directives)
|
||||
}
|
||||
|
||||
func (p *Parser) UnSetHTTPS() error {
|
||||
func (p *Parser) ClearSetHTTPS() error {
|
||||
if err := p.Clear("server.ssl_certificate"); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -157,7 +185,7 @@ func (p *Parser) UnSetHTTPS() error {
|
||||
}
|
||||
|
||||
func (p *Parser) SetHTTPS(cert, key string) error {
|
||||
if err := p.UnSetHTTPS(); err != nil {
|
||||
if err := p.ClearSetHTTPS(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -287,7 +315,7 @@ func (p *Parser) SetHSTS(hsts bool) error {
|
||||
return p.Set("server", directives)
|
||||
}
|
||||
|
||||
func (p *Parser) SetHTTPSRedirect(httpRedirect bool) error {
|
||||
func (p *Parser) SetHTTPRedirect(httpRedirect bool) error {
|
||||
// if 重定向
|
||||
ifs, err := p.Find("server.if")
|
||||
if err != nil {
|
||||
|
||||
2
pkg/nginx/testdata/http.conf
vendored
2
pkg/nginx/testdata/http.conf
vendored
@@ -5,8 +5,6 @@ server {
|
||||
root /www/wwwroot/default;
|
||||
# 错误页配置
|
||||
error_page 404 /404.html;
|
||||
# 伪静态规则
|
||||
include /www/server/vhost/rewrite/default.conf;
|
||||
include enable-php-0.conf;
|
||||
# 不记录静态文件日志
|
||||
location ~ .*\.(bmp|jpg|jpeg|png|gif|svg|ico|tiff|webp|avif|heif|heic|jxl)$ {
|
||||
|
||||
2
pkg/nginx/testdata/https.conf
vendored
2
pkg/nginx/testdata/https.conf
vendored
@@ -13,8 +13,6 @@ server {
|
||||
ssl_early_data on;
|
||||
# 错误页配置
|
||||
error_page 404 /404.html;
|
||||
# 伪静态规则
|
||||
include /www/server/vhost/rewrite/default.conf;
|
||||
include enable-php-0.conf;
|
||||
# 不记录静态文件日志
|
||||
location ~ .*\.(bmp|jpg|jpeg|png|gif|svg|ico|tiff|webp|avif|heif|heic|jxl)$ {
|
||||
|
||||
52
pkg/punycode/punycode.go
Normal file
52
pkg/punycode/punycode.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package punycode
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"slices"
|
||||
|
||||
"golang.org/x/net/idna"
|
||||
)
|
||||
|
||||
// EncodeDomain 将 Unicode 域名编码为 Punycode
|
||||
func EncodeDomain(domain string) (string, error) {
|
||||
ascii, err := idna.ToASCII(domain)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("domain encode failed: %w", err)
|
||||
}
|
||||
return ascii, nil
|
||||
}
|
||||
|
||||
// EncodeDomains 将 Unicode 域名列表编码为 Punycode
|
||||
func EncodeDomains(domain []string) (encoded []string, err error) {
|
||||
var punycode string
|
||||
for item := range slices.Values(domain) {
|
||||
punycode, err = EncodeDomain(item)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
encoded = append(encoded, punycode)
|
||||
}
|
||||
return encoded, nil
|
||||
}
|
||||
|
||||
// DecodeDomain 将 Punycode 域名解码为 Unicode 域名
|
||||
func DecodeDomain(punycodeDomain string) (string, error) {
|
||||
unicode, err := idna.ToUnicode(punycodeDomain)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("domain decode failed: %w", err)
|
||||
}
|
||||
return unicode, nil
|
||||
}
|
||||
|
||||
// DecodeDomains 将 Punycode 域名列表解码为 Unicode 域名
|
||||
func DecodeDomains(punycode []string) (decoded []string, err error) {
|
||||
var unicode string
|
||||
for item := range slices.Values(punycode) {
|
||||
unicode, err = DecodeDomain(item)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
decoded = append(decoded, unicode)
|
||||
}
|
||||
return decoded, nil
|
||||
}
|
||||
@@ -1,30 +1,35 @@
|
||||
package types
|
||||
|
||||
// WebsiteListen 网站监听配置
|
||||
type WebsiteListen struct {
|
||||
Address string `form:"address" json:"address" validate:"required"` // 监听地址 e.g. 80 0.0.0.0:80 [::]:80
|
||||
HTTPS bool `form:"https" json:"https" validate:"required"` // 是否启用HTTPS
|
||||
QUIC bool `form:"quic" json:"quic"` // 是否启用QUIC
|
||||
}
|
||||
|
||||
// WebsiteSetting 网站设置
|
||||
type WebsiteSetting struct {
|
||||
ID uint `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Domains []string `json:"domains"`
|
||||
Ports []uint `json:"ports"`
|
||||
SSLPorts []uint `json:"ssl_ports"`
|
||||
QUICPorts []uint `json:"quic_ports"`
|
||||
Root string `json:"root"`
|
||||
Path string `json:"path"`
|
||||
Index string `json:"index"`
|
||||
PHP int `json:"php"`
|
||||
OpenBasedir bool `json:"open_basedir"`
|
||||
SSL bool `json:"ssl"`
|
||||
SSLCertificate string `json:"ssl_certificate"`
|
||||
SSLCertificateKey string `json:"ssl_certificate_key"`
|
||||
SSLNotBefore string `json:"ssl_not_before"`
|
||||
SSLNotAfter string `json:"ssl_not_after"`
|
||||
SSLDNSNames []string `json:"ssl_dns_names"`
|
||||
SSLIssuer string `json:"ssl_issuer"`
|
||||
SSLOCSPServer []string `json:"ssl_ocsp_server"`
|
||||
HTTPRedirect bool `json:"http_redirect"`
|
||||
HSTS bool `json:"hsts"`
|
||||
OCSP bool `json:"ocsp"`
|
||||
Rewrite string `json:"rewrite"`
|
||||
Raw string `json:"raw"`
|
||||
Log string `json:"log"`
|
||||
ID uint `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Listens []WebsiteListen `form:"listens" json:"listens" validate:"required"`
|
||||
Domains []string `json:"domains"`
|
||||
Path string `json:"path"` // 网站目录
|
||||
Root string `json:"root"` // 运行目录
|
||||
Index []string `json:"index"`
|
||||
PHP int `json:"php"`
|
||||
OpenBasedir bool `json:"open_basedir"`
|
||||
HTTPS bool `json:"https"`
|
||||
SSLCertificate string `json:"ssl_certificate"`
|
||||
SSLCertificateKey string `json:"ssl_certificate_key"`
|
||||
SSLNotBefore string `json:"ssl_not_before"`
|
||||
SSLNotAfter string `json:"ssl_not_after"`
|
||||
SSLDNSNames []string `json:"ssl_dns_names"`
|
||||
SSLIssuer string `json:"ssl_issuer"`
|
||||
SSLOCSPServer []string `json:"ssl_ocsp_server"`
|
||||
HTTPRedirect bool `json:"http_redirect"`
|
||||
HSTS bool `json:"hsts"`
|
||||
OCSP bool `json:"ocsp"`
|
||||
Rewrite string `json:"rewrite"`
|
||||
Raw string `json:"raw"`
|
||||
Log string `json:"log"`
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user