2
0
mirror of https://github.com/acepanel/panel.git synced 2026-02-04 07:57:21 +08:00

feat: 优化网站启停

This commit is contained in:
2025-12-03 22:00:35 +08:00
parent 2ed9d0b6fa
commit 62bd8927e3
12 changed files with 37 additions and 1294 deletions

View File

@@ -1,33 +0,0 @@
package nginx
var order = []string{"listen", "server_name", "index", "root",
"ssl_certificate", "ssl_certificate_key", "ssl_session_timeout", "ssl_session_cache", "ssl_protocols", "ssl_ciphers", "ssl_prefer_server_ciphers", "ssl_early_data", "ssl_stapling", "ssl_stapling_verify", "ssl_trusted_certificate",
"resolver", "error_page", "include", "if", "location", "add_header", "access_log", "error_log"}
const DefaultConf = `server {
listen 80;
server_name localhost;
index index.php index.html;
root /www/wwwroot/default;
# Error page
error_page 404 /404.html;
include enable-php-0.conf;
# Browser cache
location ~ .*\.(bmp|jpg|jpeg|png|gif|svg|ico|tiff|webp|avif|heif|heic|jxl)$ {
expires 30d;
access_log /dev/null;
error_log /dev/null;
}
location ~ .*\.(js|css|ttf|otf|woff|woff2|eot)$ {
expires 6h;
access_log /dev/null;
error_log /dev/null;
}
# Deny sensitive files
location ~ ^/(\.user.ini|\.htaccess|\.git|\.svn|\.env) {
return 404;
}
access_log /www/wwwlogs/default.log;
error_log /www/wwwlogs/default.log;
}
`

View File

@@ -1,223 +0,0 @@
package nginx
import (
"fmt"
"slices"
"strings"
)
func (p *Parser) GetListen() ([][]string, error) {
directives, err := p.Find("server.listen")
if err != nil {
return nil, err
}
var result [][]string
for _, dir := range directives {
result = append(result, p.parameters2Slices(dir.GetParameters()))
}
return result, nil
}
func (p *Parser) GetServerName() ([]string, error) {
directive, err := p.FindOne("server.server_name")
if err != nil {
return nil, err
}
return p.parameters2Slices(directive.GetParameters()), nil
}
func (p *Parser) GetIndex() ([]string, error) {
directive, err := p.FindOne("server.index")
if err != nil {
return nil, err
}
return p.parameters2Slices(directive.GetParameters()), nil
}
func (p *Parser) GetIndexWithComment() ([]string, []string, error) {
directive, err := p.FindOne("server.index")
if err != nil {
return nil, nil, err
}
return p.parameters2Slices(directive.GetParameters()), directive.GetComment(), nil
}
func (p *Parser) GetRoot() (string, error) {
directive, err := p.FindOne("server.root")
if err != nil {
return "", err
}
if len(p.parameters2Slices(directive.GetParameters())) == 0 {
return "", nil
}
return directive.GetParameters()[0].GetValue(), nil
}
func (p *Parser) GetRootWithComment() (string, []string, error) {
directive, err := p.FindOne("server.root")
if err != nil {
return "", nil, err
}
if len(p.parameters2Slices(directive.GetParameters())) == 0 {
return "", directive.GetComment(), nil
}
return directive.GetParameters()[0].GetValue(), directive.GetComment(), nil
}
func (p *Parser) GetIncludes() (includes []string, comments [][]string, err error) {
directives, err := p.Find("server.include")
if err != nil {
return nil, nil, err
}
for _, dir := range directives {
if len(dir.GetParameters()) != 1 {
return nil, nil, fmt.Errorf("invalid include directive, expected 1 parameter but got %d", len(dir.GetParameters()))
}
includes = append(includes, dir.GetParameters()[0].GetValue())
comments = append(comments, dir.GetComment())
}
return includes, comments, nil
}
func (p *Parser) GetPHP() int {
directives, err := p.Find("server.include")
if err != nil {
return 0
}
var result int
for _, dir := range directives {
if slices.ContainsFunc(p.parameters2Slices(dir.GetParameters()), func(s string) bool {
return strings.HasPrefix(s, "enable-php-") && strings.HasSuffix(s, ".conf")
}) {
_, _ = fmt.Sscanf(dir.GetParameters()[0].GetValue(), "enable-php-%d.conf", &result)
}
}
return result
}
func (p *Parser) GetHTTPS() bool {
directive, err := p.FindOne("server.ssl_certificate")
if err != nil {
return false
}
if len(p.parameters2Slices(directive.GetParameters())) == 0 {
return false
}
return true
}
func (p *Parser) GetHTTPSProtocols() []string {
directive, err := p.FindOne("server.ssl_protocols")
if err != nil {
return nil
}
return p.parameters2Slices(directive.GetParameters())
}
func (p *Parser) GetHTTPSCiphers() string {
directive, err := p.FindOne("server.ssl_ciphers")
if err != nil {
return ""
}
if len(p.parameters2Slices(directive.GetParameters())) == 0 {
return ""
}
return directive.GetParameters()[0].GetValue()
}
func (p *Parser) GetOCSP() bool {
directive, err := p.FindOne("server.ssl_stapling")
if err != nil {
return false
}
if len(p.parameters2Slices(directive.GetParameters())) == 0 {
return false
}
return directive.GetParameters()[0].GetValue() == "on"
}
func (p *Parser) GetHSTS() bool {
directives, err := p.Find("server.add_header")
if err != nil {
return false
}
for _, dir := range directives {
if slices.Contains(p.parameters2Slices(dir.GetParameters()), "Strict-Transport-Security") {
return true
}
}
return false
}
func (p *Parser) GetHTTPSRedirect() bool {
directives, err := p.Find("server.if")
if err != nil {
return false
}
for _, dir := range directives {
for _, dir2 := range dir.GetBlock().GetDirectives() {
if dir2.GetName() == "return" && slices.Contains(p.parameters2Slices(dir2.GetParameters()), "https://$host$request_uri") {
return true
}
}
}
return false
}
func (p *Parser) GetAltSvc() string {
directive, err := p.FindOne("server.add_header")
if err != nil {
return ""
}
for i, param := range p.parameters2Slices(directive.GetParameters()) {
if strings.HasPrefix(param, "Alt-Svc") && i+1 < len(p.parameters2Slices(directive.GetParameters())) {
return p.parameters2Slices(directive.GetParameters())[i+1]
}
}
return ""
}
func (p *Parser) GetAccessLog() (string, error) {
directive, err := p.FindOne("server.access_log")
if err != nil {
return "", err
}
if len(p.parameters2Slices(directive.GetParameters())) == 0 {
return "", nil
}
return directive.GetParameters()[0].GetValue(), nil
}
func (p *Parser) GetErrorLog() (string, error) {
directive, err := p.FindOne("server.error_log")
if err != nil {
return "", err
}
if len(p.parameters2Slices(directive.GetParameters())) == 0 {
return "", nil
}
return directive.GetParameters()[0].GetValue(), nil
}

View File

@@ -1,213 +0,0 @@
package nginx
import (
"errors"
"fmt"
"slices"
"strings"
"github.com/tufanbarisyildirim/gonginx/config"
"github.com/tufanbarisyildirim/gonginx/dumper"
"github.com/tufanbarisyildirim/gonginx/parser"
)
// Parser Nginx vhost 配置解析器
type Parser struct {
c *config.Config
orderIndex map[string]int
}
func NewParser(str ...string) (*Parser, error) {
if len(str) == 0 {
str = append(str, DefaultConf)
}
p := parser.NewStringParser(str[0], parser.WithSkipIncludeParsingErr(), parser.WithSkipValidDirectivesErr())
c, err := p.Parse()
if err != nil {
return nil, err
}
orderIndex := make(map[string]int)
for i, name := range order {
orderIndex[name] = i
}
return &Parser{c: c, orderIndex: orderIndex}, nil
}
func (p *Parser) Config() *config.Config {
return p.c
}
// Find 通过表达式查找配置
// e.g. Find("server.listen")
func (p *Parser) Find(key string) ([]config.IDirective, error) {
parts := strings.Split(key, ".")
var block *config.Block
var ok bool
block = p.c.Block
for i := 0; i < len(parts)-1; i++ {
key = parts[i]
directives := block.FindDirectives(key)
if len(directives) == 0 {
return nil, fmt.Errorf("given key %s not found", key)
}
if len(directives) > 1 {
return nil, errors.New("multiple directives found")
}
block, ok = directives[0].GetBlock().(*config.Block)
if !ok {
return nil, errors.New("block is not *config.Block")
}
}
var result []config.IDirective
for _, dir := range block.GetDirectives() {
if dir.GetName() == parts[len(parts)-1] {
result = append(result, dir)
}
}
return result, nil
}
// FindOne 通过表达式查找一个配置
// e.g. FindOne("server.server_name")
func (p *Parser) FindOne(key string) (config.IDirective, error) {
directives, err := p.Find(key)
if err != nil {
return nil, err
}
if len(directives) == 0 {
return nil, fmt.Errorf("given key %s not found", key)
}
return directives[0], nil
}
// Clear 通过表达式移除配置
// e.g. Clear("server.server_name")
func (p *Parser) Clear(key string) error {
parts := strings.Split(key, ".")
last := parts[len(parts)-1]
parts = parts[:len(parts)-1]
var block *config.Block
var ok bool
block = p.c.Block
for i := 0; i < len(parts); i++ {
directives := block.FindDirectives(parts[i])
if len(directives) == 0 {
return fmt.Errorf("given key %s not found", parts[i])
}
if len(directives) > 1 {
return fmt.Errorf("multiple directives found for %s", parts[i])
}
block, ok = directives[0].GetBlock().(*config.Block)
if !ok {
return errors.New("block is not *config.Block")
}
}
var newDirectives []config.IDirective
for _, directive := range block.GetDirectives() {
if directive.GetName() != last {
newDirectives = append(newDirectives, directive)
}
}
block.Directives = newDirectives
return nil
}
// Set 通过表达式设置配置
// e.g. Set("server.server_name", []directive)
func (p *Parser) Set(key string, directives []*config.Directive, after ...string) error {
parts := strings.Split(key, ".")
var block *config.Block
var blockDirective config.IDirective
var ok bool
block = p.c.Block
for i := 0; i < len(parts); i++ {
sub := block.FindDirectives(parts[i])
if len(sub) == 0 {
return fmt.Errorf("given key %s not found", parts[i])
}
if len(sub) > 1 {
return fmt.Errorf("multiple directives found for %s", parts[i])
}
block, ok = sub[0].GetBlock().(*config.Block)
if !ok {
return errors.New("block is not *config.Block")
}
blockDirective = sub[0]
}
iDirectives := make([]config.IDirective, 0, len(directives))
for _, directive := range directives {
directive.SetParent(blockDirective)
iDirectives = append(iDirectives, directive)
}
if len(after) == 0 {
block.Directives = append(block.Directives, iDirectives...)
} else {
insertIndex := -1
for i, d := range block.Directives {
if d.GetName() == after[0] {
insertIndex = i + 1
break
}
}
if insertIndex == -1 {
return fmt.Errorf("after directive %s not found", after[0])
}
block.Directives = append(
block.Directives[:insertIndex],
append(iDirectives, block.Directives[insertIndex:]...)...,
)
}
return nil
}
func (p *Parser) Sort() {
p.sortDirectives(p.c.Directives, p.orderIndex)
}
func (p *Parser) Dump() string {
return dumper.DumpConfig(p.c, dumper.IndentedStyle)
}
func (p *Parser) sortDirectives(directives []config.IDirective, orderIndex map[string]int) {
slices.SortFunc(directives, func(a config.IDirective, b config.IDirective) int {
if orderIndex[a.GetName()] != orderIndex[b.GetName()] {
return orderIndex[a.GetName()] - orderIndex[b.GetName()]
}
return slices.Compare(p.parameters2Slices(a.GetParameters()), p.parameters2Slices(b.GetParameters()))
})
for _, directive := range directives {
if block, ok := directive.GetBlock().(*config.Block); ok {
p.sortDirectives(block.Directives, orderIndex)
}
}
}
func (p *Parser) slices2Parameters(slices []string) []config.Parameter {
var parameters []config.Parameter
for _, slice := range slices {
parameters = append(parameters, config.Parameter{Value: slice})
}
return parameters
}
func (p *Parser) parameters2Slices(parameters []config.Parameter) []string {
var s []string
for _, parameter := range parameters {
s = append(s, parameter.Value)
}
return s
}

View File

@@ -1,234 +0,0 @@
package nginx
import (
"testing"
"github.com/stretchr/testify/suite"
"github.com/acepanel/panel/pkg/io"
)
type NginxTestSuite struct {
suite.Suite
}
func TestNginxTestSuite(t *testing.T) {
suite.Run(t, &NginxTestSuite{})
}
func (s *NginxTestSuite) TestListen() {
parser, err := NewParser()
s.NoError(err)
listen, err := parser.GetListen()
s.NoError(err)
s.Equal([][]string{{"80"}}, listen)
s.NoError(parser.SetListen([][]string{{"80"}, {"443"}}))
listen, err = parser.GetListen()
s.NoError(err)
s.Equal([][]string{{"80"}, {"443"}}, listen)
}
func (s *NginxTestSuite) TestServerName() {
parser, err := NewParser()
s.NoError(err)
serverName, err := parser.GetServerName()
s.NoError(err)
s.Equal([]string{"localhost"}, serverName)
s.NoError(parser.SetServerName([]string{"example.com"}))
serverName, err = parser.GetServerName()
s.NoError(err)
s.Equal([]string{"example.com"}, serverName)
}
func (s *NginxTestSuite) TestIndex() {
parser, err := NewParser()
s.NoError(err)
index, err := parser.GetIndex()
s.NoError(err)
s.Equal([]string{"index.php", "index.html"}, index)
s.NoError(parser.SetIndex([]string{"index.html", "index.php"}))
index, err = parser.GetIndex()
s.NoError(err)
s.Equal([]string{"index.html", "index.php"}, index)
}
func (s *NginxTestSuite) TestIndexWithComment() {
parser, err := NewParser()
s.NoError(err)
index, comment, err := parser.GetIndexWithComment()
s.NoError(err)
s.Equal([]string{"index.php", "index.html"}, index)
s.Equal([]string(nil), comment)
s.NoError(parser.SetIndexWithComment([]string{"index.html", "index.php"}, []string{"# 测试"}))
index, comment, err = parser.GetIndexWithComment()
s.NoError(err)
s.Equal([]string{"index.html", "index.php"}, index)
s.Equal([]string{"# 测试"}, comment)
}
func (s *NginxTestSuite) TestRoot() {
parser, err := NewParser()
s.NoError(err)
root, err := parser.GetRoot()
s.NoError(err)
s.Equal("/www/wwwroot/default", root)
s.NoError(parser.SetRoot("/www/wwwroot/test"))
root, err = parser.GetRoot()
s.NoError(err)
s.Equal("/www/wwwroot/test", root)
}
func (s *NginxTestSuite) TestRootWithComment() {
parser, err := NewParser()
s.NoError(err)
root, comment, err := parser.GetRootWithComment()
s.NoError(err)
s.Equal("/www/wwwroot/default", root)
s.Equal([]string(nil), comment)
s.NoError(parser.SetRootWithComment("/www/wwwroot/test", []string{"# 测试"}))
root, comment, err = parser.GetRootWithComment()
s.NoError(err)
s.Equal("/www/wwwroot/test", root)
s.Equal([]string{"# 测试"}, comment)
}
func (s *NginxTestSuite) TestIncludes() {
parser, err := NewParser()
s.NoError(err)
includes, comments, err := parser.GetIncludes()
s.NoError(err)
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)
s.Equal([]string{"/www/server/vhost/rewrite/default.conf"}, includes)
s.Equal([][]string{[]string(nil)}, comments)
s.NoError(parser.SetIncludes([]string{"/www/server/vhost/rewrite/test.conf"}, [][]string{{"# 伪静态规则测试"}}))
includes, comments, err = parser.GetIncludes()
s.NoError(err)
s.Equal([]string{"/www/server/vhost/rewrite/test.conf"}, includes)
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)
expect, err := io.Read("testdata/http.conf")
s.NoError(err)
s.Equal(expect, parser.Dump())
}
func (s *NginxTestSuite) TestHTTPS() {
parser, err := NewParser()
s.NoError(err)
s.False(parser.GetHTTPS())
s.NoError(parser.SetHTTPS("/www/server/vhost/cert/default.pem", "/www/server/vhost/cert/default.key"))
s.True(parser.GetHTTPS())
expect, err := io.Read("testdata/https.conf")
s.NoError(err)
s.Equal(expect, parser.Dump())
}
func (s *NginxTestSuite) TestHTTPSProtocols() {
parser, err := NewParser()
s.NoError(err)
s.NoError(parser.SetHTTPS("/www/server/vhost/cert/default.pem", "/www/server/vhost/cert/default.key"))
s.Equal([]string{"TLSv1.2", "TLSv1.3"}, parser.GetHTTPSProtocols())
s.NoError(parser.SetHTTPSProtocols([]string{"TLSv1.3"}))
s.Equal([]string{"TLSv1.3"}, parser.GetHTTPSProtocols())
}
func (s *NginxTestSuite) TestHTTPSCiphers() {
parser, err := NewParser()
s.NoError(err)
s.NoError(parser.SetHTTPS("/www/server/vhost/cert/default.pem", "/www/server/vhost/cert/default.key"))
s.Equal("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:DHE-RSA-CHACHA20-POLY1305", parser.GetHTTPSCiphers())
s.NoError(parser.SetHTTPSCiphers("TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384"))
s.Equal("TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384", parser.GetHTTPSCiphers())
}
func (s *NginxTestSuite) TestOCSP() {
parser, err := NewParser()
s.NoError(err)
s.NoError(err)
s.NoError(parser.SetHTTPS("/www/server/vhost/cert/default.pem", "/www/server/vhost/cert/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))
s.False(parser.GetOCSP())
}
func (s *NginxTestSuite) TestHSTS() {
parser, err := NewParser()
s.NoError(err)
s.NoError(parser.SetHTTPS("/www/server/vhost/cert/default.pem", "/www/server/vhost/cert/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))
s.False(parser.GetHSTS())
}
func (s *NginxTestSuite) TestHTTPSRedirect() {
parser, err := NewParser()
s.NoError(err)
s.NoError(parser.SetHTTPS("/www/server/vhost/cert/default.pem", "/www/server/vhost/cert/default.key"))
s.False(parser.GetHTTPSRedirect())
s.NoError(parser.SetHTTPRedirect(false))
s.False(parser.GetHTTPSRedirect())
s.NoError(parser.SetHTTPRedirect(true))
s.True(parser.GetHTTPSRedirect())
s.NoError(parser.SetHTTPRedirect(false))
s.False(parser.GetHTTPSRedirect())
}
func (s *NginxTestSuite) TestAltSvc() {
parser, err := NewParser()
s.NoError(err)
s.NoError(parser.SetHTTPS("/www/server/vhost/cert/default.pem", "/www/server/vhost/cert/default.key"))
s.Equal("", parser.GetAltSvc())
s.NoError(parser.SetAltSvc(`'h3=":$server_port"; ma=2592000'`))
s.Equal(`'h3=":$server_port"; ma=2592000'`, parser.GetAltSvc())
s.NoError(parser.SetAltSvc(""))
s.Equal("", parser.GetAltSvc())
}
func (s *NginxTestSuite) TestAccessLog() {
parser, err := NewParser()
s.NoError(err)
log, err := parser.GetAccessLog()
s.NoError(err)
s.Equal("/www/wwwlogs/default.log", log)
s.NoError(parser.SetAccessLog("/www/wwwlogs/access.log"))
log, err = parser.GetAccessLog()
s.NoError(err)
s.Equal("/www/wwwlogs/access.log", log)
}
func (s *NginxTestSuite) TestErrorLog() {
parser, err := NewParser()
s.NoError(err)
log, err := parser.GetErrorLog()
s.NoError(err)
s.Equal("/www/wwwlogs/default.log", log)
s.NoError(parser.SetErrorLog("/www/wwwlogs/error.log"))
log, err = parser.GetErrorLog()
s.NoError(err)
s.Equal("/www/wwwlogs/error.log", log)
}

View File

@@ -1,492 +0,0 @@
package nginx
import (
"fmt"
"slices"
"strings"
"github.com/tufanbarisyildirim/gonginx/config"
)
func (p *Parser) SetListen(listen [][]string) error {
var directives []*config.Directive
for _, l := range listen {
directives = append(directives, &config.Directive{
Name: "listen",
Parameters: p.slices2Parameters(l),
})
}
if err := p.Clear("server.listen"); err != nil {
return err
}
return p.Set("server", directives)
}
func (p *Parser) SetServerName(serverName []string) error {
if err := p.Clear("server.server_name"); err != nil {
return err
}
return p.Set("server", []*config.Directive{
{
Name: "server_name",
Parameters: p.slices2Parameters(serverName),
},
})
}
func (p *Parser) SetIndex(index []string) error {
if err := p.Clear("server.index"); err != nil {
return err
}
return p.Set("server", []*config.Directive{
{
Name: "index",
Parameters: p.slices2Parameters(index),
},
})
}
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: p.slices2Parameters(index),
Comment: comment,
},
})
}
func (p *Parser) SetRoot(root string) error {
if err := p.Clear("server.root"); err != nil {
return err
}
return p.Set("server", []*config.Directive{
{
Name: "root",
Parameters: []config.Parameter{{Value: root}},
},
})
}
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: []config.Parameter{{Value: root}},
Comment: comment,
},
})
}
func (p *Parser) SetIncludes(includes []string, comments [][]string) error {
if err := p.Clear("server.include"); err != nil {
return err
}
var directives []*config.Directive
for i, item := range includes {
var comment []string
if i < len(comments) {
comment = comments[i]
}
directives = append(directives, &config.Directive{
Name: "include",
Parameters: []config.Parameter{{Value: item}},
Comment: comment,
})
}
return p.Set("server", directives)
}
func (p *Parser) SetPHP(php int) error {
old, err := p.Find("server.include")
if err != nil {
return err
}
if err = p.Clear("server.include"); err != nil {
return err
}
var directives []*config.Directive
var foundFlag bool
for _, item := range old {
// 查找enable-php的配置
if slices.ContainsFunc(p.parameters2Slices(item.GetParameters()), func(s string) bool {
return strings.HasPrefix(s, "enable-php-") && strings.HasSuffix(s, ".conf")
}) {
foundFlag = true
directives = append(directives, &config.Directive{
Name: item.GetName(),
Parameters: []config.Parameter{{Value: fmt.Sprintf("enable-php-%d.conf", php)}},
Comment: item.GetComment(),
})
} else {
// 其余的原样保留
directives = append(directives, &config.Directive{
Name: item.GetName(),
Parameters: item.GetParameters(),
Comment: item.GetComment(),
})
}
}
// 如果没有找到enable-php的配置直接添加一个
if !foundFlag {
directives = append(directives, &config.Directive{
Name: "include",
Parameters: []config.Parameter{{Value: fmt.Sprintf("enable-php-%d.conf", php)}},
})
}
return p.Set("server", directives)
}
func (p *Parser) ClearSetHTTPS() error {
if err := p.Clear("server.ssl_certificate"); err != nil {
return err
}
if err := p.Clear("server.ssl_certificate_key"); err != nil {
return err
}
if err := p.Clear("server.ssl_session_timeout"); err != nil {
return err
}
if err := p.Clear("server.ssl_session_cache"); err != nil {
return err
}
if err := p.Clear("server.ssl_protocols"); err != nil {
return err
}
if err := p.Clear("server.ssl_ciphers"); err != nil {
return err
}
if err := p.Clear("server.ssl_prefer_server_ciphers"); err != nil {
return err
}
if err := p.Clear("server.ssl_early_data"); err != nil {
return err
}
return nil
}
func (p *Parser) SetHTTPS(cert, key string) error {
if err := p.ClearSetHTTPS(); err != nil {
return err
}
return p.Set("server", []*config.Directive{
{
Name: "ssl_certificate",
Parameters: []config.Parameter{{Value: cert}},
},
{
Name: "ssl_certificate_key",
Parameters: []config.Parameter{{Value: key}},
},
{
Name: "ssl_session_timeout",
Parameters: []config.Parameter{{Value: "1d"}},
},
{
Name: "ssl_session_cache",
Parameters: []config.Parameter{{Value: "shared:SSL:10m"}},
},
{
Name: "ssl_protocols",
Parameters: []config.Parameter{{Value: "TLSv1.2"}, {Value: "TLSv1.3"}},
},
{
Name: "ssl_ciphers",
Parameters: []config.Parameter{{Value: "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:DHE-RSA-CHACHA20-POLY1305"}},
},
{
Name: "ssl_prefer_server_ciphers",
Parameters: []config.Parameter{{Value: "off"}},
},
{
Name: "ssl_early_data",
Parameters: []config.Parameter{{Value: "on"}},
},
}, "root")
}
func (p *Parser) SetHTTPSProtocols(protocols []string) error {
if err := p.Clear("server.ssl_protocols"); err != nil {
return err
}
return p.Set("server", []*config.Directive{
{
Name: "ssl_protocols",
Parameters: p.slices2Parameters(protocols),
},
})
}
func (p *Parser) SetHTTPSCiphers(ciphers string) error {
if err := p.Clear("server.ssl_ciphers"); err != nil {
return err
}
return p.Set("server", []*config.Directive{
{
Name: "ssl_ciphers",
Parameters: []config.Parameter{{Value: ciphers}},
},
})
}
func (p *Parser) SetOCSP(ocsp bool) error {
if err := p.Clear("server.ssl_stapling"); err != nil {
return err
}
if err := p.Clear("server.ssl_stapling_verify"); err != nil {
return err
}
if ocsp {
return p.Set("server", []*config.Directive{
{
Name: "ssl_stapling",
Parameters: []config.Parameter{{Value: "on"}},
},
{
Name: "ssl_stapling_verify",
Parameters: []config.Parameter{{Value: "on"}},
},
})
}
return nil
}
func (p *Parser) SetHSTS(hsts bool) error {
old, err := p.Find("server.add_header")
if err != nil {
return err
}
if err = p.Clear("server.add_header"); err != nil {
return err
}
var directives []*config.Directive
var foundFlag bool
for _, dir := range old {
if slices.Contains(p.parameters2Slices(dir.GetParameters()), "Strict-Transport-Security") {
foundFlag = true
if hsts {
directives = append(directives, &config.Directive{
Name: dir.GetName(),
Parameters: []config.Parameter{{Value: "Strict-Transport-Security"}, {Value: "max-age=31536000"}},
Comment: dir.GetComment(),
})
}
} else {
directives = append(directives, &config.Directive{
Name: dir.GetName(),
Parameters: dir.GetParameters(),
Comment: dir.GetComment(),
})
}
}
if !foundFlag && hsts {
directives = append(directives, &config.Directive{
Name: "add_header",
Parameters: []config.Parameter{{Value: "Strict-Transport-Security"}, {Value: "max-age=31536000"}},
})
}
return p.Set("server", directives)
}
func (p *Parser) SetHTTPRedirect(httpRedirect bool) error {
// if 重定向
ifs, err := p.Find("server.if")
if err != nil {
return err
}
if err = p.Clear("server.if"); err != nil {
return err
}
var directives []*config.Directive
var foundFlag bool
for _, dir := range ifs { // 所有 if
if !httpRedirect {
if len(dir.GetParameters()) == 3 && dir.GetParameters()[0].GetValue() == "($scheme" && dir.GetParameters()[1].GetValue() == "=" && dir.GetParameters()[2].GetValue() == "http)" {
continue
}
}
var ifDirectives []config.IDirective
for _, dir2 := range dir.GetBlock().GetDirectives() { // 每个 if 中所有指令
if !httpRedirect {
// 不启用http重定向则判断并移除特定的return指令
if dir2.GetName() != "return" && !slices.Contains(p.parameters2Slices(dir2.GetParameters()), "https://$host$request_uri") {
ifDirectives = append(ifDirectives, dir2)
}
} else {
// 启用http重定向需要检查防止重复添加
if dir2.GetName() == "return" && slices.Contains(p.parameters2Slices(dir2.GetParameters()), "https://$host$request_uri") {
foundFlag = true
}
ifDirectives = append(ifDirectives, dir2)
}
}
// 写回 if 指令
if block, ok := dir.GetBlock().(*config.Block); ok {
block.Directives = ifDirectives
}
directives = append(directives, &config.Directive{
Block: dir.GetBlock(),
Name: dir.GetName(),
Parameters: dir.GetParameters(),
Comment: dir.GetComment(),
})
}
if !foundFlag && httpRedirect {
ifDir := &config.Directive{
Name: "if",
Block: &config.Block{},
Parameters: []config.Parameter{{Value: "($scheme"}, {Value: "="}, {Value: "http)"}},
}
redirectDir := &config.Directive{
Name: "return",
Parameters: []config.Parameter{{Value: "308"}, {Value: "https://$host$request_uri"}},
}
redirectDir.SetParent(ifDir.GetParent())
ifBlock := ifDir.GetBlock().(*config.Block)
ifBlock.Directives = append(ifBlock.Directives, redirectDir)
directives = append(directives, ifDir)
}
if err = p.Set("server", directives); err != nil {
return err
}
// error_page 497 重定向
directives = nil
errorPages, err := p.Find("server.error_page")
if err != nil {
return err
}
if err = p.Clear("server.error_page"); err != nil {
return err
}
var found497 bool
for _, dir := range errorPages {
if !httpRedirect {
// 不启用https重定向则判断并移除特定的return指令
if !slices.Contains(p.parameters2Slices(dir.GetParameters()), "497") && !slices.Contains(p.parameters2Slices(dir.GetParameters()), "https://$host:$server_port$request_uri") {
directives = append(directives, &config.Directive{
Block: dir.GetBlock(),
Name: dir.GetName(),
Parameters: dir.GetParameters(),
Comment: dir.GetComment(),
})
}
} else {
// 启用https重定向需要检查防止重复添加
if slices.Contains(p.parameters2Slices(dir.GetParameters()), "497") && slices.Contains(p.parameters2Slices(dir.GetParameters()), "https://$host:$server_port$request_uri") {
found497 = true
}
directives = append(directives, &config.Directive{
Block: dir.GetBlock(),
Name: dir.GetName(),
Parameters: dir.GetParameters(),
Comment: dir.GetComment(),
})
}
}
if !found497 && httpRedirect {
directives = append(directives, &config.Directive{
Name: "error_page",
Parameters: []config.Parameter{{Value: "497"}, {Value: "=308"}, {Value: "https://$host:$server_port$request_uri"}},
})
}
return p.Set("server", directives)
}
func (p *Parser) SetAltSvc(altSvc string) error {
old, err := p.Find("server.add_header")
if err != nil {
return err
}
if err = p.Clear("server.add_header"); err != nil {
return err
}
var directives []*config.Directive
var foundFlag bool
for _, dir := range old {
if slices.Contains(p.parameters2Slices(dir.GetParameters()), "Alt-Svc") {
foundFlag = true
if altSvc != "" { // 为空表示要删除
directives = append(directives, &config.Directive{
Name: dir.GetName(),
Parameters: []config.Parameter{{Value: "Alt-Svc"}, {Value: altSvc}},
Comment: dir.GetComment(),
})
}
} else {
directives = append(directives, &config.Directive{
Name: dir.GetName(),
Parameters: dir.GetParameters(),
Comment: dir.GetComment(),
})
}
}
if !foundFlag && altSvc != "" {
directives = append(directives, &config.Directive{
Name: "add_header",
Parameters: []config.Parameter{{Value: "Alt-Svc"}, {Value: altSvc}},
})
}
return p.Set("server", directives)
}
func (p *Parser) SetAccessLog(accessLog string) error {
if err := p.Clear("server.access_log"); err != nil {
return err
}
return p.Set("server", []*config.Directive{
{
Name: "access_log",
Parameters: []config.Parameter{{Value: accessLog}},
},
})
}
func (p *Parser) SetErrorLog(errorLog string) error {
if err := p.Clear("server.error_log"); err != nil {
return err
}
return p.Set("server", []*config.Directive{
{
Name: "error_log",
Parameters: []config.Parameter{{Value: errorLog}},
},
})
}

View File

@@ -1,26 +0,0 @@
server {
listen 80;
server_name localhost;
index index.php index.html;
root /www/wwwroot/default;
# Error page
error_page 404 /404.html;
include enable-php-0.conf;
# Browser cache
location ~ .*\.(bmp|jpg|jpeg|png|gif|svg|ico|tiff|webp|avif|heif|heic|jxl)$ {
expires 30d;
access_log /dev/null;
error_log /dev/null;
}
location ~ .*\.(js|css|ttf|otf|woff|woff2|eot)$ {
expires 6h;
access_log /dev/null;
error_log /dev/null;
}
# Deny sensitive files
location ~ ^/(\.user.ini|\.htaccess|\.git|\.svn|\.env) {
return 404;
}
access_log /www/wwwlogs/default.log;
error_log /www/wwwlogs/default.log;
}

View File

@@ -1,34 +0,0 @@
server {
listen 80;
server_name localhost;
index index.php index.html;
root /www/wwwroot/default;
ssl_certificate /www/server/vhost/cert/default.pem;
ssl_certificate_key /www/server/vhost/cert/default.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:DHE-RSA-CHACHA20-POLY1305;
ssl_prefer_server_ciphers off;
ssl_early_data on;
# Error page
error_page 404 /404.html;
include enable-php-0.conf;
# Browser cache
location ~ .*\.(bmp|jpg|jpeg|png|gif|svg|ico|tiff|webp|avif|heif|heic|jxl)$ {
expires 30d;
access_log /dev/null;
error_log /dev/null;
}
location ~ .*\.(js|css|ttf|otf|woff|woff2|eot)$ {
expires 6h;
access_log /dev/null;
error_log /dev/null;
}
# Deny sensitive files
location ~ ^/(\.user.ini|\.htaccess|\.git|\.svn|\.env) {
return 404;
}
access_log /www/wwwlogs/default.log;
error_log /www/wwwlogs/default.log;
}

View File

@@ -108,40 +108,35 @@ func (v *baseVhost) Enable() bool {
return root != DisablePagePath
}
func (v *baseVhost) SetEnable(enable bool, siteConfig ...string) error {
name := ""
func (v *baseVhost) SetEnable(enable bool) error {
path := DisablePagePath
if enable {
if len(siteConfig) != 2 {
return fmt.Errorf("site config is required to enable the vhost")
// 尝试获取保存的根目录
if root, err := os.ReadFile(filepath.Join(v.configDir, "root.saved")); err != nil {
path = filepath.Join(SitesPath, filepath.Dir(v.configDir), "public") // 默认根目录
} else {
path = strings.TrimSpace(string(root))
}
} else {
// 禁用时,保存当前根目录
currentRoot := v.Root()
if currentRoot != "" && currentRoot != DisablePagePath {
if err := os.WriteFile(filepath.Join(v.configDir, "root.saved"), []byte(currentRoot), 0644); err != nil {
return fmt.Errorf("failed to save current root: %w", err)
}
}
name = siteConfig[0]
path = siteConfig[1]
}
// 设置根目录
v.vhost.SetDirective("DocumentRoot", path)
// 更新 Directory 块
dirBlock := v.vhost.GetBlock("Directory")
if dirBlock != nil {
dirBlock.Args = []string{path}
} else {
block := v.vhost.AddBlock("Directory", path)
if block.Block != nil {
block.Block.Directives = append(block.Block.Directives,
&Directive{Name: "Options", Args: []string{"-Indexes", "+FollowSymLinks"}},
&Directive{Name: "AllowOverride", Args: []string{"All"}},
&Directive{Name: "Require", Args: []string{"all", "granted"}},
)
}
if err := v.SetRoot(path); err != nil {
return err
}
// 设置 Include 配置
v.vhost.RemoveDirectives("IncludeOptional")
if enable {
v.vhost.AddDirective("IncludeOptional", fmt.Sprintf("%s/%s/config/site/*.conf", SitesPath, name))
v.vhost.AddDirective("IncludeOptional", fmt.Sprintf("%s/site/*.conf", v.configDir))
}
return nil

View File

@@ -59,7 +59,7 @@ func (s *VhostTestSuite) TestEnable() {
s.False(s.vhost.Enable())
// 重新启用
s.NoError(s.vhost.SetEnable(true, "testsite", "/var/www/test"))
s.NoError(s.vhost.SetEnable(true))
s.True(s.vhost.Enable())
}

View File

@@ -108,26 +108,29 @@ func (v *baseVhost) Enable() bool {
return directive.GetParameters()[0].GetValue() != DisablePagePath
}
func (v *baseVhost) SetEnable(enable bool, siteConfig ...string) error {
name := ""
func (v *baseVhost) SetEnable(enable bool) error {
path := DisablePagePath
if enable {
if len(siteConfig) != 2 {
return fmt.Errorf("site config is required to enable the vhost")
// 尝试获取保存的根目录
if root, err := os.ReadFile(filepath.Join(v.configDir, "root.saved")); err != nil {
path = filepath.Join(SitesPath, filepath.Dir(v.configDir), "public") // 默认根目录
} else {
path = strings.TrimSpace(string(root))
}
} else {
// 禁用时,保存当前根目录
currentRoot := v.Root()
if currentRoot != "" && currentRoot != DisablePagePath {
if err := os.WriteFile(filepath.Join(v.configDir, "root.saved"), []byte(currentRoot), 0644); err != nil {
return fmt.Errorf("failed to save current root: %w", err)
}
}
name = siteConfig[0]
path = siteConfig[1]
}
// 设置根目录
_ = v.parser.Clear("server.root")
if err := v.parser.Set("server", []*config.Directive{
{
Name: "root",
Parameters: v.parser.slices2Parameters([]string{path}),
},
}); err != nil {
if err := v.SetRoot(path); err != nil {
return err
}
@@ -137,7 +140,7 @@ func (v *baseVhost) SetEnable(enable bool, siteConfig ...string) error {
return v.parser.Set("server", []*config.Directive{
{
Name: "include",
Parameters: v.parser.slices2Parameters([]string{fmt.Sprintf("%s/%s/config/site/*.conf", SitesPath, name)}),
Parameters: v.parser.slices2Parameters([]string{fmt.Sprintf("%s/site/*.conf", v.configDir)}),
},
})
}

View File

@@ -58,7 +58,7 @@ func (s *VhostTestSuite) TestEnable() {
s.False(s.vhost.Enable())
// 重新启用
s.NoError(s.vhost.SetEnable(true, "testsite", "/var/www/test"))
s.NoError(s.vhost.SetEnable(true))
s.True(s.vhost.Enable())
}

View File

@@ -6,8 +6,8 @@ type Vhost interface {
// Enable 取启用状态
Enable() bool
// SetEnable 设置启用状态及网站名称和根目录
SetEnable(enable bool, siteConfig ...string) error
// SetEnable 设置启用状态
SetEnable(enable bool) error
// Listen 取监听配置
Listen() []Listen