package apache import ( "fmt" "os" "path/filepath" "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 { root := v.vhost.GetDirectiveValue("DocumentRoot") return root != DisablePagePath } func (v *baseVhost) SetEnable(enable bool) error { path := DisablePagePath if enable { // 尝试获取保存的根目录 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) } } } // 设置根目录 if err := v.SetRoot(path); err != nil { return err } // 设置 Include 配置 v.vhost.RemoveDirectives("IncludeOptional") if enable { v.vhost.AddDirective("IncludeOptional", fmt.Sprintf("%s/site/*.conf", v.configDir)) } 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, Args: []string{}} 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 { 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 { 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 { 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 { 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 { 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) 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") && 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 { content := v.Config("010-php.conf", "site") if content == "" { return 0 } // 从配置内容中提取版本号 // 格式: proxy:unix:/tmp/php-cgi-84.sock|fcgi://localhost idx := strings.Index(content, "php-cgi-") if idx == -1 { 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 { if version == 0 { 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) return v.SetConfig("010-php.conf", "site", content) } // ========== 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) }