2
0
mirror of https://github.com/acepanel/panel.git synced 2026-02-05 04:37:17 +08:00

feat: 调整更多指令到独立配置

This commit is contained in:
2025-12-04 00:26:35 +08:00
parent f70dd51e29
commit 91c3fc3951
9 changed files with 178 additions and 218 deletions

View File

@@ -169,8 +169,7 @@ func (r *websiteRepo) Get(id uint) (*types.WebsiteSetting, error) {
if phpVhost, ok := vhost.(webservertypes.PHPVhost); ok {
setting.PHP = phpVhost.PHP()
// 伪静态
rewrite, _ := io.Read(filepath.Join(app.Root, "sites", website.Name, "config", "site", "010-rewrite.conf"))
setting.Rewrite = rewrite
setting.Rewrite = phpVhost.Config("010-rewrite.conf", "site")
}
// 反向代理网站特有
@@ -277,6 +276,12 @@ func (r *websiteRepo) Create(req *request.WebsiteCreate) (*biz.Website, error) {
return nil, err
}
// 404 页面
// TODO 需要兼容 Apache
if err = vhost.SetConfig("010-error-404.conf", "site", `error_page 404 /404.html;`); err != nil {
return nil, err
}
// 反向代理支持
if proxyVhost, ok := vhost.(webservertypes.ProxyVhost); ok {
if err = proxyVhost.SetProxies([]webservertypes.Proxy{
@@ -294,7 +299,26 @@ func (r *websiteRepo) Create(req *request.WebsiteCreate) (*biz.Website, error) {
if err = phpVhost.SetPHP(req.PHP); err != nil {
return nil, err
}
if err = io.Write(filepath.Join(app.Root, "sites", req.Name, "config", "site", "010-rewrite.conf"), "", 0644); err != nil {
if err = phpVhost.SetConfig("010-rewrite.conf", "site", ""); err != nil {
return nil, err
}
// TODO 需要兼容 Apache
if err = phpVhost.SetConfig("010-cache.conf", "site", `# 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;
}
`); err != nil {
return nil, err
}
}
@@ -335,12 +359,13 @@ func (r *websiteRepo) Create(req *request.WebsiteCreate) (*biz.Website, error) {
}
// 写配置
if err = vhost.SetConfig("001-acme.conf", "site", ""); err != nil {
return nil, err
}
if err = vhost.Save(); err != nil {
return nil, err
}
if err = io.Write(filepath.Join(app.Root, "sites", req.Name, "config", "site", "001-acme.conf"), "", 0644); err != nil {
return nil, err
}
if err = io.Write(filepath.Join(app.Root, "sites", req.Name, "config", "fullchain.pem"), "", 0644); err != nil {
return nil, err
}
@@ -487,7 +512,7 @@ func (r *websiteRepo) Update(req *request.WebsiteUpdate) error {
return err
}
// 伪静态
if err = io.Write(filepath.Join(app.Root, "sites", website.Name, "config", "site", "010-rewrite.conf"), req.Rewrite, 0644); err != nil {
if err = phpVhost.SetConfig("010-rewrite.conf", "site", req.Rewrite); err != nil {
return err
}
// 防跨站
@@ -617,10 +642,10 @@ func (r *websiteRepo) ResetConfig(id uint) error {
return err
}
// 保存配置
if err = vhost.Save(); err != nil {
if err = vhost.SetConfig("001-acme.conf", "site", ""); err != nil {
return err
}
if err = io.Write(filepath.Join(app.Root, "sites", website.Name, "config", "site", "001-acme.conf"), "", 0644); err != nil {
if err = vhost.Save(); err != nil {
return err
}
if err = io.Write(filepath.Join(app.Root, "sites", website.Name, "config", "fullchain.pem"), "", 0644); err != nil {

View File

@@ -19,13 +19,8 @@ const DefaultVhostConf = `<VirtualHost *:80>
ServerName localhost
DocumentRoot /opt/ace/sites/default/public
DirectoryIndex index.php index.html
ErrorLog /opt/ace/sites/default/log/error.log
CustomLog /opt/ace/sites/default/log/access.log combined
# custom configs
IncludeOptional /opt/ace/sites/default/config/site/*.conf
<Directory /opt/ace/sites/default/public>
Options -Indexes +FollowSymLinks
AllowOverride All

View File

@@ -4,7 +4,6 @@ import (
"fmt"
"os"
"path/filepath"
"strconv"
"strings"
"github.com/acepanel/panel/pkg/webserver/types"
@@ -281,21 +280,45 @@ func (v *baseVhost) SetIncludes(includes []types.IncludeFile) error {
}
func (v *baseVhost) AccessLog() string {
return v.vhost.GetDirectiveValue("CustomLog")
content := v.Config("020-access-log.conf", "site")
if content == "" {
return ""
}
var result string
_, err := fmt.Sscanf(content, "CustomLog %s combined", &result)
if err != nil {
return ""
}
return result
}
func (v *baseVhost) SetAccessLog(accessLog string) error {
v.vhost.SetDirective("CustomLog", accessLog, "combined")
return nil
if accessLog == "" {
return v.RemoveConfig("020-access-log.conf", "site")
}
return v.SetConfig("020-access-log.conf", "site", fmt.Sprintf("CustomLog %s combined\n", accessLog))
}
func (v *baseVhost) ErrorLog() string {
return v.vhost.GetDirectiveValue("ErrorLog")
content := v.Config("020-error-log.conf", "site")
if content == "" {
return ""
}
var result string
_, err := fmt.Sscanf(content, "ErrorLog %s", &result)
if err != nil {
return ""
}
return result
}
func (v *baseVhost) SetErrorLog(errorLog string) error {
v.vhost.SetDirective("ErrorLog", errorLog)
return nil
if errorLog == "" {
return v.RemoveConfig("020-error-log.conf", "site")
}
return v.SetConfig("020-error-log.conf", "site", fmt.Sprintf("ErrorLog %s\n", errorLog))
}
func (v *baseVhost) Save() error {
@@ -323,6 +346,31 @@ func (v *baseVhost) Reset() error {
return nil
}
func (v *baseVhost) Config(name string, typ string) string {
conf := filepath.Join(v.configDir, typ, name)
content, err := os.ReadFile(conf)
if err != nil {
return ""
}
return strings.TrimSpace(string(content))
}
func (v *baseVhost) SetConfig(name string, typ string, content string) error {
conf := filepath.Join(v.configDir, typ, name)
if err := os.WriteFile(conf, []byte(content), 0644); err != nil {
return fmt.Errorf("failed to write config file: %w", err)
}
return nil
}
func (v *baseVhost) RemoveConfig(name string, typ string) error {
conf := filepath.Join(v.configDir, typ, name)
if err := os.Remove(conf); err != nil && !os.IsNotExist(err) {
return fmt.Errorf("failed to remove config file: %w", err)
}
return nil
}
func (v *baseVhost) SSL() bool {
// 检查是否有 SSL 相关配置
return v.vhost.HasDirective("SSLEngine") &&
@@ -573,87 +621,39 @@ func (v *baseVhost) SetRedirects(redirects []types.Redirect) error {
// ========== PHPVhost ==========
func (v *PHPVhost) PHP() uint {
// Apache 通常通过 FilesMatch 块配置 PHP
// 或者通过 SetHandler 指令
handler := v.vhost.GetDirectiveValue("SetHandler")
if handler != "" && strings.Contains(handler, "php") {
// 尝试从 handler 中提取版本号
// 例如: proxy:unix:/run/php/php8.4-fpm.sock|fcgi://localhost
if idx := strings.Index(handler, "php"); idx != -1 {
versionStr := ""
for i := idx + 3; i < len(handler); i++ {
c := handler[i]
if (c >= '0' && c <= '9') || c == '.' {
versionStr += string(c)
} else {
break
}
}
if versionStr != "" {
// 转换版本号,如 "8.4" -> 84
parts := strings.Split(versionStr, ".")
if len(parts) >= 2 {
major, _ := strconv.Atoi(parts[0])
minor, _ := strconv.Atoi(parts[1])
return uint(major*10 + minor)
}
}
}
// 如果有 PHP 处理器但无法确定版本,返回默认值
return 1
content := v.Config("010-php.conf", "site")
if content == "" {
return 0
}
// 检查 FilesMatch 块中的 PHP 配置
for _, dir := range v.vhost.Directives {
if dir.Block != nil && strings.EqualFold(dir.Block.Type, "FilesMatch") {
for _, d := range dir.Block.Directives {
if strings.EqualFold(d.Name, "SetHandler") && len(d.Args) > 0 {
if strings.Contains(d.Args[0], "php") {
return 1 // 有 PHP但版本未知
}
}
}
}
// 从配置内容中提取版本号
// 格式: proxy:unix:/tmp/php-cgi-84.sock|fcgi://localhost
idx := strings.Index(content, "php-cgi-")
if idx == -1 {
return 0
}
return 0
var result uint
_, err := fmt.Sscanf(content[idx:], "php-cgi-%d.sock", &result)
if err != nil {
return 0
}
return result
}
func (v *PHPVhost) SetPHP(version uint) error {
// 移除现有的 PHP 配置
v.vhost.RemoveDirective("SetHandler")
// 移除 FilesMatch 块中的 PHP 配置
for i := len(v.vhost.Directives) - 1; i >= 0; i-- {
dir := v.vhost.Directives[i]
if dir.Block != nil && strings.EqualFold(dir.Block.Type, "FilesMatch") {
if len(dir.Block.Args) > 0 && strings.Contains(dir.Block.Args[0], "php") {
v.vhost.Directives = append(v.vhost.Directives[:i], v.vhost.Directives[i+1:]...)
}
}
}
if version == 0 {
return nil // 禁用 PHP
return v.RemoveConfig("010-php.conf", "site")
}
// 添加 PHP-FPM 配置
major := version / 10
minor := version % 10
socketPath := fmt.Sprintf("/run/php/php%d.%d-fpm.sock", major, minor)
// 生成 PHP-FPM 配置
// sock 路径格式: unix:/tmp/php-cgi-84.sock
content := fmt.Sprintf(`<FilesMatch \.php$>
SetHandler "proxy:unix:/tmp/php-cgi-%d.sock|fcgi://localhost"
</FilesMatch>
`, version)
// 添加 FilesMatch 块
block := v.vhost.AddBlock("FilesMatch", `\.php$`)
if block.Block != nil {
block.Block.Directives = append(block.Block.Directives,
&Directive{
Name: "SetHandler",
Args: []string{fmt.Sprintf("proxy:unix:%s|fcgi://localhost", socketPath)},
},
)
}
return nil
return v.SetConfig("010-php.conf", "site", content)
}
// ========== ProxyVhost ==========

View File

@@ -324,10 +324,11 @@ func (s *VhostTestSuite) TestDirectoryBlock() {
func (s *VhostTestSuite) TestPHPFilesMatchBlock() {
s.NoError(s.vhost.SetPHP(84))
content := s.vhost.config.Export()
// PHP 配置现在在独立文件中
content := s.vhost.Config("010-php.conf", "site")
s.Contains(content, "<FilesMatch")
s.Contains(content, "SetHandler")
s.True(strings.Contains(content, "php8.4") || strings.Contains(content, "fcgi"))
s.Contains(content, "php-cgi-84.sock")
}
func (s *VhostTestSuite) TestDefaultVhostConfIncludesServerD() {

View File

@@ -21,27 +21,8 @@ server {
server_name localhost;
root /opt/ace/sites/default/public;
index index.php index.html;
# error page
error_page 404 /404.html;
# 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;
}
# custom configs
include /opt/ace/sites/default/config/site/*.conf;
access_log /opt/ace/sites/default/log/access.log;
error_log /opt/ace/sites/default/log/error.log;
}
`

View File

@@ -1,28 +0,0 @@
include /opt/ace/sites/default/config/shared/*.conf;
server {
listen 80;
server_name localhost;
index index.php index.html;
root /opt/ace/sites/default/public;
# error page
error_page 404 /404.html;
# custom configs
include /opt/ace/sites/default/config/site/*.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 /opt/ace/sites/default/log/access.log;
error_log /opt/ace/sites/default/log/error.log;
}

View File

@@ -1,36 +0,0 @@
include /opt/ace/sites/default/config/shared/*.conf;
server {
listen 80;
server_name localhost;
index index.php index.html;
root /opt/ace/sites/default/public;
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;
# custom configs
include /opt/ace/sites/default/config/site/*.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 /opt/ace/sites/default/log/access.log;
error_log /opt/ace/sites/default/log/error.log;
}

View File

@@ -284,49 +284,45 @@ func (v *baseVhost) SetIncludes(includes []types.IncludeFile) error {
}
func (v *baseVhost) AccessLog() string {
directive, err := v.parser.FindOne("server.access_log")
if err != nil {
return ""
}
if len(v.parser.parameters2Slices(directive.GetParameters())) == 0 {
content := v.Config("020-access-log.conf", "site")
if content == "" {
return ""
}
return directive.GetParameters()[0].GetValue()
var result string
_, err := fmt.Sscanf(content, "access_log %s", &result)
if err != nil {
return ""
}
return strings.TrimSuffix(result, ";")
}
func (v *baseVhost) SetAccessLog(accessLog string) error {
_ = v.parser.Clear("server.access_log")
return v.parser.Set("server", []*config.Directive{
{
Name: "access_log",
Parameters: []config.Parameter{{Value: accessLog}},
},
})
if accessLog == "" {
return v.RemoveConfig("020-access-log.conf", "site")
}
return v.SetConfig("020-access-log.conf", "site", fmt.Sprintf("access_log %s;\n", accessLog))
}
func (v *baseVhost) ErrorLog() string {
directive, err := v.parser.FindOne("server.error_log")
content := v.Config("020-error-log.conf", "site")
if content == "" {
return ""
}
var result string
_, err := fmt.Sscanf(content, "error_log %s", &result)
if err != nil {
return ""
}
if len(v.parser.parameters2Slices(directive.GetParameters())) == 0 {
return ""
}
return directive.GetParameters()[0].GetValue()
return strings.TrimSuffix(result, ";")
}
func (v *baseVhost) SetErrorLog(errorLog string) error {
_ = v.parser.Clear("server.error_log")
return v.parser.Set("server", []*config.Directive{
{
Name: "error_log",
Parameters: []config.Parameter{{Value: errorLog}},
},
})
if errorLog == "" {
return v.RemoveConfig("020-error-log.conf", "site")
}
return v.SetConfig("020-error-log.conf", "site", fmt.Sprintf("error_log %s;\n", errorLog))
}
func (v *baseVhost) Save() error {
@@ -349,6 +345,31 @@ func (v *baseVhost) Reset() error {
return nil
}
func (v *baseVhost) Config(name string, typ string) string {
conf := filepath.Join(v.configDir, typ, name)
content, err := os.ReadFile(conf)
if err != nil {
return ""
}
return strings.TrimSpace(string(content))
}
func (v *baseVhost) SetConfig(name string, typ string, content string) error {
conf := filepath.Join(v.configDir, typ, name)
if err := os.WriteFile(conf, []byte(content), 0644); err != nil {
return fmt.Errorf("failed to write config file: %w", err)
}
return nil
}
func (v *baseVhost) RemoveConfig(name string, typ string) error {
conf := filepath.Join(v.configDir, typ, name)
if err := os.Remove(conf); err != nil && !os.IsNotExist(err) {
return fmt.Errorf("failed to remove config file: %w", err)
}
return nil
}
func (v *baseVhost) SSL() bool {
directive, err := v.parser.FindOne("server.ssl_certificate")
if err != nil {
@@ -662,33 +683,24 @@ func (v *baseVhost) SetRedirects(redirects []types.Redirect) error {
// ========== PHPVhost ==========
func (v *PHPVhost) PHP() uint {
phpConf := filepath.Join(v.configDir, "site", "010-php.conf")
content, err := os.ReadFile(phpConf)
if err != nil {
content := v.Config("010-php.conf", "site")
if content == "" {
return 0
}
var result uint
_, err = fmt.Sscanf(strings.TrimSpace(string(content)), "include enable-php-%d.conf;", &result)
_, err := fmt.Sscanf(content, "include enable-php-%d.conf;", &result)
if err != nil {
return 0
}
return result
}
func (v *PHPVhost) SetPHP(version uint) error {
if version == 0 {
return os.Remove(filepath.Join(v.configDir, "site", "010-php.conf"))
return v.RemoveConfig("010-php.conf", "site")
}
phpConf := filepath.Join(v.configDir, "site", "010-php.conf")
content := fmt.Sprintf("include enable-php-%d.conf;\n", version)
if err := os.WriteFile(phpConf, []byte(content), 0644); err != nil {
return fmt.Errorf("failed to write php config: %w", err)
}
return nil
return v.SetConfig("010-php.conf", "site", fmt.Sprintf("include enable-php-%d.conf;\n", version))
}
// ========== ProxyVhost ==========

View File

@@ -75,6 +75,16 @@ type Vhost interface {
SetBasicAuth(auth map[string]string) error
// ClearBasicAuth 清除基本认证
ClearBasicAuth() error
// Config 取指定名称的配置内容
// type 可选值: "site", "shared"
Config(name string, typ string) string
// SetConfig 设置指定名称的配置内容
// type 可选值: "site", "shared"
SetConfig(name string, typ string, content string) error
// RemoveConfig 清除指定名称的配置内容
// type 可选值: "site", "shared"
RemoveConfig(name string, typ string) error
}
// StaticVhost 纯静态虚拟主机接口