package apache import ( "fmt" "os" "path/filepath" "strconv" "strings" "github.com/acepanel/panel/pkg/webserver/types" ) // StaticVhost 纯静态虚拟主机 type StaticVhost struct { *baseVhost } // PHPVhost PHP 虚拟主机 type PHPVhost struct { *baseVhost } // ProxyVhost 反向代理虚拟主机 type ProxyVhost struct { *baseVhost } // baseVhost Apache 虚拟主机基础实现 type baseVhost struct { config *Config vhost *VirtualHost configDir string // 配置目录 } // newBaseVhost 创建基础虚拟主机实例 func newBaseVhost(configDir string) (*baseVhost, error) { if configDir == "" { return nil, fmt.Errorf("config directory is required") } v := &baseVhost{ configDir: configDir, } // 加载配置 var config *Config var err error // 从配置目录加载主配置文件 configFile := filepath.Join(v.configDir, "apache.conf") if _, statErr := os.Stat(configFile); statErr == nil { config, err = ParseFile(configFile) if err != nil { return nil, fmt.Errorf("failed to parse apache config: %w", err) } } // 如果没有配置文件,使用默认配置 if config == nil { config, err = ParseString(DefaultVhostConf) if err != nil { return nil, fmt.Errorf("failed to parse default config: %w", err) } } v.config = config // 获取第一个虚拟主机 if len(config.VirtualHosts) > 0 { v.vhost = config.VirtualHosts[0] } else { // 创建默认虚拟主机 v.vhost = config.AddVirtualHost("*:80") } return v, nil } // NewStaticVhost 创建纯静态虚拟主机实例 func NewStaticVhost(configDir string) (*StaticVhost, error) { base, err := newBaseVhost(configDir) if err != nil { return nil, err } return &StaticVhost{baseVhost: base}, nil } // NewPHPVhost 创建 PHP 虚拟主机实例 func NewPHPVhost(configDir string) (*PHPVhost, error) { base, err := newBaseVhost(configDir) if err != nil { return nil, err } return &PHPVhost{baseVhost: base}, nil } // NewProxyVhost 创建反向代理虚拟主机实例 func NewProxyVhost(configDir string) (*ProxyVhost, error) { base, err := newBaseVhost(configDir) if err != nil { return nil, err } return &ProxyVhost{baseVhost: base}, nil } func (v *baseVhost) Enable() bool { // 检查禁用配置文件是否存在 disableFile := filepath.Join(v.configDir, "site", DisableConfName) _, err := os.Stat(disableFile) return os.IsNotExist(err) } func (v *baseVhost) SetEnable(enable bool, _ ...string) error { serverDir := filepath.Join(v.configDir, "site") disableFile := filepath.Join(serverDir, DisableConfName) if enable { // 启用:删除禁用配置文件 if err := os.Remove(disableFile); err != nil && !os.IsNotExist(err) { return fmt.Errorf("failed to remove disable config: %w", err) } return nil } // 禁用:创建禁用配置文件 if err := os.WriteFile(disableFile, []byte(DisableConfContent), 0644); err != nil { return fmt.Errorf("failed to write disable config: %w", err) } return nil } func (v *baseVhost) Listen() []types.Listen { var result []types.Listen // Apache 的监听配置通常在 VirtualHost 的参数中 // 例如: for _, arg := range v.vhost.Args { listen := types.Listen{Address: arg} result = append(result, listen) } return result } func (v *baseVhost) SetListen(listens []types.Listen) error { var args []string for _, l := range listens { args = append(args, l.Address) } v.vhost.Args = args return nil } func (v *baseVhost) ServerName() []string { var names []string // 获取 ServerName serverName := v.vhost.GetDirectiveValue("ServerName") if serverName != "" { names = append(names, serverName) } // 获取 ServerAlias(可能有多个值) aliases := v.vhost.GetDirectives("ServerAlias") for _, alias := range aliases { names = append(names, alias.Args...) } return names } func (v *baseVhost) SetServerName(serverName []string) error { if len(serverName) == 0 { return nil } // 设置主域名 v.vhost.SetDirective("ServerName", serverName[0]) // 删除现有的 ServerAlias v.vhost.RemoveDirectives("ServerAlias") // 设置别名 if len(serverName) > 1 { v.vhost.AddDirective("ServerAlias", serverName[1:]...) } return nil } func (v *baseVhost) Index() []string { values := v.vhost.GetDirectiveValues("DirectoryIndex") if values != nil { return values } return nil } func (v *baseVhost) SetIndex(index []string) error { if len(index) == 0 { v.vhost.RemoveDirective("DirectoryIndex") return nil } v.vhost.SetDirective("DirectoryIndex", index...) return nil } func (v *baseVhost) Root() string { return v.vhost.GetDirectiveValue("DocumentRoot") } func (v *baseVhost) SetRoot(root string) error { v.vhost.SetDirective("DocumentRoot", root) // 同时更新 Directory 块 dirBlock := v.vhost.GetBlock("Directory") if dirBlock != nil { // 更新现有的 Directory 块路径 dirBlock.Args = []string{root} } else { // 添加新的 Directory 块 block := v.vhost.AddBlock("Directory", root) 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"}}, ) } } return nil } func (v *baseVhost) Includes() []types.IncludeFile { var result []types.IncludeFile // 获取所有 Include 和 IncludeOptional 指令 for _, dir := range v.vhost.GetDirectives("Include") { if len(dir.Args) > 0 { result = append(result, types.IncludeFile{ Path: dir.Args[0], }) } } for _, dir := range v.vhost.GetDirectives("IncludeOptional") { if len(dir.Args) > 0 { result = append(result, types.IncludeFile{ Path: dir.Args[0], }) } } return result } func (v *baseVhost) SetIncludes(includes []types.IncludeFile) error { // 删除现有的 Include 指令 v.vhost.RemoveDirectives("Include") v.vhost.RemoveDirectives("IncludeOptional") // 添加新的 Include 指令 for _, inc := range includes { v.vhost.AddDirective("Include", inc.Path) } return nil } func (v *baseVhost) AccessLog() string { return v.vhost.GetDirectiveValue("CustomLog") } func (v *baseVhost) SetAccessLog(accessLog string) error { v.vhost.SetDirective("CustomLog", accessLog, "combined") return nil } func (v *baseVhost) ErrorLog() string { return v.vhost.GetDirectiveValue("ErrorLog") } func (v *baseVhost) SetErrorLog(errorLog string) error { v.vhost.SetDirective("ErrorLog", errorLog) return nil } func (v *baseVhost) Save() error { configFile := filepath.Join(v.configDir, "apache.conf") content := v.config.Export() if err := os.WriteFile(configFile, []byte(content), 0644); err != nil { return fmt.Errorf("failed to save config file: %w", err) } return nil } func (v *baseVhost) Reset() error { // 重置配置为默认值 config, err := ParseString(DefaultVhostConf) if err != nil { return fmt.Errorf("failed to reset config: %w", err) } v.config = config if len(config.VirtualHosts) > 0 { v.vhost = config.VirtualHosts[0] } return nil } func (v *baseVhost) SSL() bool { // 检查是否有 SSL 相关配置 return v.vhost.HasDirective("SSLEngine") && strings.EqualFold(v.vhost.GetDirectiveValue("SSLEngine"), "on") } func (v *baseVhost) SSLConfig() *types.SSLConfig { if !v.SSL() { return nil } config := &types.SSLConfig{ Cert: v.vhost.GetDirectiveValue("SSLCertificateFile"), Key: v.vhost.GetDirectiveValue("SSLCertificateKeyFile"), } // 获取协议 protocols := v.vhost.GetDirectiveValues("SSLProtocol") if protocols != nil { config.Protocols = protocols } // 获取加密套件 config.Ciphers = v.vhost.GetDirectiveValue("SSLCipherSuite") // 检查 HSTS headers := v.vhost.GetDirectives("Header") for _, h := range headers { if len(h.Args) >= 3 && strings.Contains(strings.Join(h.Args, " "), "Strict-Transport-Security") { config.HSTS = true break } } // 检查 OCSP config.OCSP = strings.EqualFold(v.vhost.GetDirectiveValue("SSLUseStapling"), "on") // 检查 HTTP 重定向(通常在 HTTP 虚拟主机中配置) redirects := v.vhost.GetDirectives("RewriteRule") for _, r := range redirects { if len(r.Args) >= 2 && strings.Contains(strings.Join(r.Args, " "), "https://") { config.HTTPRedirect = true break } } return config } func (v *baseVhost) SetSSLConfig(cfg *types.SSLConfig) error { if cfg == nil { return fmt.Errorf("SSL config cannot be nil") } // 启用 SSL v.vhost.SetDirective("SSLEngine", "on") // 设置证书 if cfg.Cert != "" { v.vhost.SetDirective("SSLCertificateFile", cfg.Cert) } if cfg.Key != "" { v.vhost.SetDirective("SSLCertificateKeyFile", cfg.Key) } // 设置协议 if len(cfg.Protocols) > 0 { v.vhost.SetDirective("SSLProtocol", cfg.Protocols...) } else { v.vhost.SetDirective("SSLProtocol", "all", "-SSLv2", "-SSLv3", "-TLSv1", "-TLSv1.1") } // 设置加密套件 if cfg.Ciphers != "" { v.vhost.SetDirective("SSLCipherSuite", cfg.Ciphers) } else { v.vhost.SetDirective("SSLCipherSuite", "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384") } // 设置 HSTS if cfg.HSTS { // 只移除现有的 HSTS Header,保留其他 Header newDirectives := make([]*Directive, 0, len(v.vhost.Directives)) for _, dir := range v.vhost.Directives { if strings.EqualFold(dir.Name, "Header") { if len(dir.Args) >= 3 && strings.Contains(strings.Join(dir.Args, " "), "Strict-Transport-Security") { continue } } newDirectives = append(newDirectives, dir) } v.vhost.Directives = newDirectives v.vhost.AddDirective("Header", "always", "set", "Strict-Transport-Security", `"max-age=31536000"`) } // 设置 OCSP if cfg.OCSP { v.vhost.SetDirective("SSLUseStapling", "on") } // 设置 HTTP 重定向(需要 mod_rewrite) if cfg.HTTPRedirect { v.vhost.SetDirective("RewriteEngine", "on") v.vhost.AddDirective("RewriteCond", "%{HTTPS}", "off") v.vhost.AddDirective("RewriteRule", "^(.*)$", "https://%{HTTP_HOST}%{REQUEST_URI}", "[R=301,L]") } // 更新监听端口为 443 hasSSLPort := false for _, arg := range v.vhost.Args { if strings.Contains(arg, ":443") { hasSSLPort = true break } } if !hasSSLPort { v.vhost.Args = append(v.vhost.Args, "*:443") } return nil } func (v *baseVhost) ClearSSL() error { // 移除 SSL 相关指令 v.vhost.RemoveDirective("SSLEngine") v.vhost.RemoveDirective("SSLCertificateFile") v.vhost.RemoveDirective("SSLCertificateKeyFile") v.vhost.RemoveDirective("SSLProtocol") v.vhost.RemoveDirective("SSLCipherSuite") v.vhost.RemoveDirective("SSLUseStapling") // 只移除 HSTS 相关的 Header 指令 newDirectives := make([]*Directive, 0, len(v.vhost.Directives)) for _, dir := range v.vhost.Directives { if strings.EqualFold(dir.Name, "Header") { // 检查是否是 HSTS Header if len(dir.Args) >= 3 && strings.Contains(strings.Join(dir.Args, " "), "Strict-Transport-Security") { continue // 跳过 HSTS Header } } newDirectives = append(newDirectives, dir) } v.vhost.Directives = newDirectives // 移除重定向规则 v.vhost.RemoveDirective("RewriteEngine") v.vhost.RemoveDirectives("RewriteCond") v.vhost.RemoveDirectives("RewriteRule") // 更新监听端口,移除 443 var newArgs []string for _, arg := range v.vhost.Args { if !strings.Contains(arg, ":443") { newArgs = append(newArgs, arg) } } if len(newArgs) == 0 { newArgs = []string{"*:80"} } v.vhost.Args = newArgs return nil } func (v *baseVhost) RateLimit() *types.RateLimit { // Apache 使用 mod_ratelimit rate := v.vhost.GetDirectiveValue("SetOutputFilter") if rate != "RATE_LIMIT" { return nil } rateLimit := &types.RateLimit{ Zone: make(map[string]string), } // 获取速率限制值 rateValue := v.vhost.GetDirectiveValue("SetEnv") if rateValue != "" { rateLimit.Rate = rateValue } return rateLimit } func (v *baseVhost) SetRateLimit(limit *types.RateLimit) error { // 设置 mod_ratelimit v.vhost.SetDirective("SetOutputFilter", "RATE_LIMIT") if limit.Rate != "" { v.vhost.SetDirective("SetEnv", "rate-limit", limit.Rate) } return nil } func (v *baseVhost) ClearRateLimit() error { v.vhost.RemoveDirective("SetOutputFilter") v.vhost.RemoveDirectives("SetEnv") return nil } func (v *baseVhost) BasicAuth() map[string]string { authType := v.vhost.GetDirectiveValue("AuthType") if authType == "" || !strings.EqualFold(authType, "Basic") { return nil } return map[string]string{ "realm": v.vhost.GetDirectiveValue("AuthName"), "user_file": v.vhost.GetDirectiveValue("AuthUserFile"), } } func (v *baseVhost) SetBasicAuth(auth map[string]string) error { realm := auth["realm"] userFile := auth["user_file"] if realm == "" { realm = "Restricted" } v.vhost.SetDirective("AuthType", "Basic") v.vhost.SetDirective("AuthName", fmt.Sprintf(`"%s"`, realm)) v.vhost.SetDirective("AuthUserFile", userFile) v.vhost.SetDirective("Require", "valid-user") return nil } func (v *baseVhost) ClearBasicAuth() error { v.vhost.RemoveDirective("AuthType") v.vhost.RemoveDirective("AuthName") v.vhost.RemoveDirective("AuthUserFile") v.vhost.RemoveDirective("Require") return nil } func (v *baseVhost) Redirects() []types.Redirect { siteDir := filepath.Join(v.configDir, "site") redirects, _ := parseRedirectFiles(siteDir) return redirects } func (v *baseVhost) SetRedirects(redirects []types.Redirect) error { siteDir := filepath.Join(v.configDir, "site") return writeRedirectFiles(siteDir, redirects) } // ========== 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 } // 检查 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,但版本未知 } } } } } return 0 } 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 } // 添加 PHP-FPM 配置 major := version / 10 minor := version % 10 socketPath := fmt.Sprintf("/run/php/php%d.%d-fpm.sock", major, minor) // 添加 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 } // ========== ProxyVhost ========== func (v *ProxyVhost) Proxies() []types.Proxy { siteDir := filepath.Join(v.configDir, "site") proxies, _ := parseProxyFiles(siteDir) return proxies } func (v *ProxyVhost) SetProxies(proxies []types.Proxy) error { siteDir := filepath.Join(v.configDir, "site") return writeProxyFiles(siteDir, proxies) } func (v *ProxyVhost) ClearProxies() error { siteDir := filepath.Join(v.configDir, "site") return clearProxyFiles(siteDir) } func (v *ProxyVhost) Upstreams() map[string]types.Upstream { sharedDir := filepath.Join(v.configDir, "shared") upstreams, _ := parseBalancerFiles(sharedDir) return upstreams } func (v *ProxyVhost) SetUpstreams(upstreams map[string]types.Upstream) error { sharedDir := filepath.Join(v.configDir, "shared") return writeBalancerFiles(sharedDir, upstreams) } func (v *ProxyVhost) ClearUpstreams() error { sharedDir := filepath.Join(v.configDir, "shared") return clearBalancerFiles(sharedDir) }