diff --git a/internal/data/container.go b/internal/data/container.go
index 4c20065c..5d4e7af1 100644
--- a/internal/data/container.go
+++ b/internal/data/container.go
@@ -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
}
diff --git a/internal/data/container_image.go b/internal/data/container_image.go
index d453b030..3a5aa18f 100644
--- a/internal/data/container_image.go
+++ b/internal/data/container_image.go
@@ -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())
}
diff --git a/internal/data/website.go b/internal/data/website.go
index 4edf0db4..34ec951a 100644
--- a/internal/data/website.go
+++ b/internal/data/website.go
@@ -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
+
+ 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"
+
+# deny sensitive files
+
+ Require all denied
+
+`
+ }
+ 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":
diff --git a/internal/service/cli.go b/internal/service/cli.go
index 1393e1ab..9575fb69 100644
--- a/internal/service/cli.go
+++ b/internal/service/cli.go
@@ -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))
diff --git a/internal/service/website.go b/internal/service/website.go
index dc9e877f..3f98fc17 100644
--- a/internal/service/website.go
+++ b/internal/service/website.go
@@ -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)
diff --git a/pkg/webserver/apache/data.go b/pkg/webserver/apache/data.go
index 412ba6f4..b74346a2 100644
--- a/pkg/webserver/apache/data.go
+++ b/pkg/webserver/apache/data.go
@@ -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,
diff --git a/pkg/webserver/apache/parser.go b/pkg/webserver/apache/parser.go
index 7a2d8ec1..438864d8 100644
--- a/pkg/webserver/apache/parser.go
+++ b/pkg/webserver/apache/parser.go
@@ -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{
diff --git a/pkg/webserver/apache/proxy.go b/pkg/webserver/apache/proxy.go
index 5e4d5efa..bdd0d67e 100644
--- a/pkg/webserver/apache/proxy.go
+++ b/pkg/webserver/apache/proxy.go
@@ -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{},
}
// 解析 块
diff --git a/pkg/webserver/apache/vhost.go b/pkg/webserver/apache/vhost.go
index 15e7e9bc..8a03cce3 100644
--- a/pkg/webserver/apache/vhost.go
+++ b/pkg/webserver/apache/vhost.go
@@ -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(`
- SetHandler "proxy:unix:/tmp/php-cgi-%d.sock|fcgi://localhost"
-
-`, 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)
}