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

feat: 支持apache

This commit is contained in:
2026-01-13 01:48:07 +08:00
parent 02e0aef265
commit 06f6610d3b
9 changed files with 134 additions and 36 deletions

View File

@@ -107,7 +107,6 @@ func (r *containerRepo) Create(req *request.ContainerCreate) (string, error) {
}
defer func(out client.ImagePullResponse) { _ = out.Close() }(out)
// TODO 实现流式显示拉取进度
if err = out.Wait(ctx); err != nil {
return "", err
}

View File

@@ -106,7 +106,6 @@ func (r *containerImageRepo) Pull(req *request.ContainerImagePull) error {
}
defer func(out client.ImagePullResponse) { _ = out.Close() }(out)
// TODO 实现流式显示拉取进度
return out.Wait(context.Background())
}

View File

@@ -81,21 +81,35 @@ func (r *websiteRepo) GetRewrites() (map[string]string, error) {
}
func (r *websiteRepo) UpdateDefaultConfig(req *request.WebsiteDefaultConfig) error {
if err := io.Write(filepath.Join(app.Root, "server/nginx/html/index.html"), req.Index, 0644); err != nil {
webServer, err := r.setting.Get(biz.SettingKeyWebserver)
if err != nil {
return err
}
if err := io.Write(filepath.Join(app.Root, "server/nginx/html/stop.html"), req.Stop, 0644); err != nil {
var htmlPath string
switch webServer {
case "nginx":
htmlPath = filepath.Join(app.Root, "server/nginx/html")
case "apache":
htmlPath = filepath.Join(app.Root, "server/apache/htdocs")
default:
htmlPath = filepath.Join(app.Root, "server/nginx/html")
}
if err = io.Write(filepath.Join(htmlPath, "index.html"), req.Index, 0644); err != nil {
return err
}
if err = io.Write(filepath.Join(htmlPath, "stop.html"), req.Stop, 0644); err != nil {
return err
}
if req.NotFound != "" {
if err := io.Write(filepath.Join(app.Root, "server/nginx/html/404.html"), req.NotFound, 0644); err != nil {
if err = io.Write(filepath.Join(htmlPath, "404.html"), req.NotFound, 0644); err != nil {
return err
}
}
if err := r.setting.SetSlice(biz.SettingKeyWebsiteTLSVersions, req.TLSVersions); err != nil {
if err = r.setting.SetSlice(biz.SettingKeyWebsiteTLSVersions, req.TLSVersions); err != nil {
return err
}
if err := r.setting.Set(biz.SettingKeyWebsiteCipherSuites, req.CipherSuites); err != nil {
if err = r.setting.Set(biz.SettingKeyWebsiteCipherSuites, req.CipherSuites); err != nil {
return err
}
@@ -245,6 +259,11 @@ func (r *websiteRepo) Create(ctx context.Context, req *request.WebsiteCreate) (*
Remark: req.Remark,
}
webServer, err := r.setting.Get(biz.SettingKeyWebserver)
if err != nil {
return nil, err
}
vhost, err := r.getVhost(w)
if err != nil {
return nil, err
@@ -290,8 +309,14 @@ func (r *websiteRepo) Create(ctx context.Context, req *request.WebsiteCreate) (*
return nil, err
}
// 404 页面
// TODO 需要兼容 Apache
if err = vhost.SetConfig("010-error-404.conf", "site", `error_page 404 /404.html;`); err != nil {
var errorPageConfig string
switch webServer {
case "nginx":
errorPageConfig = `error_page 404 /404.html;`
case "apache":
errorPageConfig = `ErrorDocument 404 /404.html`
}
if err = vhost.SetConfig("010-error-404.conf", "site", errorPageConfig); err != nil {
return nil, err
}
@@ -315,8 +340,10 @@ func (r *websiteRepo) Create(ctx context.Context, req *request.WebsiteCreate) (*
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
var cacheConfig string
switch webServer {
case "nginx":
cacheConfig = `# browser cache
location ~ .*\.(bmp|jpg|jpeg|png|gif|svg|ico|tiff|webp|avif|heif|heic|jxl)$ {
expires 30d;
access_log /dev/null;
@@ -331,7 +358,38 @@ location ~ .*\.(js|css|ttf|otf|woff|woff2|eot)$ {
location ~ ^/(\.user.ini|\.htaccess|\.git|\.svn|\.env) {
return 404;
}
`); err != nil {
`
case "apache":
cacheConfig = `# browser cache
<IfModule mod_expires.c>
ExpiresActive On
ExpiresByType image/bmp "access plus 30 days"
ExpiresByType image/jpeg "access plus 30 days"
ExpiresByType image/png "access plus 30 days"
ExpiresByType image/gif "access plus 30 days"
ExpiresByType image/svg+xml "access plus 30 days"
ExpiresByType image/x-icon "access plus 30 days"
ExpiresByType image/tiff "access plus 30 days"
ExpiresByType image/webp "access plus 30 days"
ExpiresByType image/avif "access plus 30 days"
ExpiresByType image/heif "access plus 30 days"
ExpiresByType image/heic "access plus 30 days"
ExpiresByType image/jxl "access plus 30 days"
ExpiresByType text/css "access plus 6 hours"
ExpiresByType application/javascript "access plus 6 hours"
ExpiresByType font/ttf "access plus 6 hours"
ExpiresByType font/otf "access plus 6 hours"
ExpiresByType font/woff "access plus 6 hours"
ExpiresByType font/woff2 "access plus 6 hours"
ExpiresByType application/vnd.ms-fontobject "access plus 6 hours"
</IfModule>
# deny sensitive files
<FilesMatch "^(\.user\.ini|\.htaccess|\.git|\.svn|\.env)">
Require all denied
</FilesMatch>
`
}
if err = phpVhost.SetConfig("010-cache.conf", "site", cacheConfig); err != nil {
return nil, err
}
}
@@ -358,9 +416,15 @@ location ~ ^/(\.user.ini|\.htaccess|\.git|\.svn|\.env) {
var notFound []byte
// 如果存在自定义 404 页面,则使用自定义的
// TODO 需要兼容 Apache
if io.Exists(filepath.Join(app.Root, "server/nginx/html/404.html")) {
notFound, _ = os.ReadFile(filepath.Join(app.Root, "server/nginx/html/404.html"))
var custom404Path string
switch webServer {
case "nginx":
custom404Path = filepath.Join(app.Root, "server/nginx/html/404.html")
case "apache":
custom404Path = filepath.Join(app.Root, "server/apache/htdocs/404.html")
}
if io.Exists(custom404Path) {
notFound, _ = os.ReadFile(custom404Path)
} else {
switch app.Locale {
case "zh_CN":

View File

@@ -140,7 +140,6 @@ func (s *CliService) Fix(ctx context.Context, cmd *cli.Command) error {
}
func (s *CliService) Info(ctx context.Context, cmd *cli.Command) error {
// TODO 未来加权限设置之后这里需要优化
user := new(biz.User)
if err := s.db.First(user).Error; err != nil {
return errors.New(s.t.Get("Failed to get user info: %v", err))

View File

@@ -35,9 +35,20 @@ func (s *WebsiteService) GetRewrites(w http.ResponseWriter, r *http.Request) {
}
func (s *WebsiteService) GetDefaultConfig(w http.ResponseWriter, r *http.Request) {
index, _ := io.Read(filepath.Join(app.Root, "server/nginx/html/index.html"))
stop, _ := io.Read(filepath.Join(app.Root, "server/nginx/html/stop.html"))
notFound, _ := io.Read(filepath.Join(app.Root, "server/nginx/html/404.html"))
webServer, _ := s.settingRepo.Get(biz.SettingKeyWebserver)
var htmlPath string
switch webServer {
case "nginx":
htmlPath = filepath.Join(app.Root, "server/nginx/html")
case "apache":
htmlPath = filepath.Join(app.Root, "server/apache/htdocs")
default:
htmlPath = filepath.Join(app.Root, "server/nginx/html")
}
index, _ := io.Read(filepath.Join(htmlPath, "index.html"))
stop, _ := io.Read(filepath.Join(htmlPath, "stop.html"))
notFound, _ := io.Read(filepath.Join(htmlPath, "404.html"))
tlsVersions, _ := s.settingRepo.GetSlice(biz.SettingKeyWebsiteTLSVersions)
cipherSuites, _ := s.settingRepo.Get(biz.SettingKeyWebsiteCipherSuites)

View File

@@ -165,8 +165,8 @@ var order = map[string]int{
"Proxy": 1311,
"ProxyMatch": 1312,
"Include": 1400,
"IncludeOptional": 1401,
"Include": 1290,
"IncludeOptional": 1291,
"ErrorLog": 1500,
"CustomLog": 1501,

View File

@@ -205,11 +205,11 @@ func (p *Parser) parseVirtualHost(token Token) (*VirtualHost, error) {
return nil, err
}
// 检查是否是Include类指令
// 检查是否是 Include 类指令
if p.options.ProcessIncludes && (strings.EqualFold(directive.Name, "Include") || strings.EqualFold(directive.Name, "IncludeOptional")) {
includeConfig, err := p.processInclude(directive)
if err != nil {
// 对于IncludeOptional如果文件不存在则忽略错误
// 对于 IncludeOptional如果文件不存在则忽略错误
if strings.EqualFold(directive.Name, "IncludeOptional") && os.IsNotExist(err) {
continue
}
@@ -228,6 +228,21 @@ func (p *Parser) parseVirtualHost(token Token) (*VirtualHost, error) {
} else {
vhost.Directives = append(vhost.Directives, directive)
}
} else if nextToken.Type == BLOCKDIRECTIVE {
// 处理虚拟主机内的块指令(如 Directory, Location 等)
block, err := p.parseBlockDirective(nextToken)
if err != nil {
return nil, err
}
// 将块指令作为带 Block 的 Directive 添加到虚拟主机
directive := &Directive{
Name: block.Type,
Args: block.Args,
Line: block.Line,
Column: block.Column,
Block: block,
}
vhost.Directives = append(vhost.Directives, directive)
} else if nextToken.Type == COMMENT {
// 收集虚拟主机内的注释
comment := &Comment{

View File

@@ -28,7 +28,7 @@ func parseProxyFiles(siteDir string) ([]types.Proxy, error) {
return nil, err
}
var proxies []types.Proxy
proxies := make([]types.Proxy, 0)
for _, entry := range entries {
if entry.IsDir() {
continue
@@ -66,6 +66,7 @@ func parseProxyFile(filePath string) (*types.Proxy, error) {
contentStr := string(content)
proxy := &types.Proxy{
Resolver: []string{},
Replaces: make(map[string]string),
}
@@ -188,6 +189,18 @@ func generateProxyConfig(proxy types.Proxy) string {
if location == "" {
location = "/"
}
// 将 Nginx 风格的 location 转换为 Apache 格式
// ^~ / -> /
// ~ ^/api -> /api (正则匹配需要使用 ProxyPassMatch)
location = strings.TrimPrefix(location, "^~ ")
location = strings.TrimPrefix(location, "~ ")
location = strings.TrimPrefix(location, "= ")
// 如果 location 以 ^ 开头(正则),去掉它
location = strings.TrimPrefix(location, "^")
// 确保 location 以 / 开头
if !strings.HasPrefix(location, "/") {
location = "/" + location
}
sb.WriteString(fmt.Sprintf("# Reverse proxy: %s -> %s\n", location, proxy.Pass))
@@ -261,7 +274,7 @@ func parseBalancerFiles(sharedDir string) ([]types.Upstream, error) {
return nil, err
}
var upstreams []types.Upstream
upstreams := make([]types.Upstream, 0)
for _, entry := range entries {
if entry.IsDir() {
continue
@@ -294,8 +307,9 @@ func parseBalancerFile(filePath string, name string) (*types.Upstream, error) {
contentStr := string(content)
upstream := &types.Upstream{
Name: name,
Servers: make(map[string]string),
Name: name,
Servers: make(map[string]string),
Resolver: []string{},
}
// 解析 <Proxy balancer://name> 块

View File

@@ -641,14 +641,14 @@ func (v *PHPVhost) PHP() uint {
}
// 从配置内容中提取版本号
// 格式: proxy:unix:/tmp/php-cgi-84.sock|fcgi://localhost
idx := strings.Index(content, "php-cgi-")
// 格式: Include /opt/ace/server/apache/conf/extra/enable-php-85.conf
idx := strings.Index(content, "enable-php-")
if idx == -1 {
return 0
}
var result uint
_, err := fmt.Sscanf(content[idx:], "php-cgi-%d.sock", &result)
_, err := fmt.Sscanf(content[idx:], "enable-php-%d.conf", &result)
if err != nil {
return 0
}
@@ -660,12 +660,9 @@ func (v *PHPVhost) SetPHP(version uint) error {
return v.RemoveConfig("010-php.conf", "site")
}
// 生成 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)
// 生成 PHP-FPM 配置,直接 Include Apache 的 PHP-FPM 配置文件
// 配置文件路径: /opt/ace/server/apache/conf/extra/enable-php-85.conf
content := fmt.Sprintf("Include conf/extra/enable-php-%d.conf\n", version)
return v.SetConfig("010-php.conf", "site", content)
}