2
0
mirror of https://github.com/acepanel/panel.git synced 2026-02-04 13:47:15 +08:00
Files
panel/pkg/webserver/nginx/vhost.go
2025-12-01 23:31:59 +08:00

528 lines
12 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package nginx
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"slices"
"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 Nginx 虚拟主机基础实现
type baseVhost struct {
parser *Parser
configDir string // 配置目录
}
// newBaseVhost 创建基础虚拟主机实例
func newBaseVhost(configDir string) (*baseVhost, error) {
if configDir == "" {
return nil, fmt.Errorf("config directory is required")
}
v := &baseVhost{
configDir: configDir,
}
// 加载配置
var parser *Parser
var err error
// 从配置目录加载主配置文件
configFile := filepath.Join(v.configDir, "nginx.conf")
if _, statErr := os.Stat(configFile); statErr == nil {
parser, err = NewParserFromFile(configFile)
if err != nil {
return nil, fmt.Errorf("failed to load nginx config: %w", err)
}
}
// 如果没有配置文件,使用默认配置
if parser == nil {
// 使用空字符串创建默认配置,而不尝试读取文件
parser, err = NewParser("")
if err != nil {
return nil, fmt.Errorf("failed to load default config: %w", err)
}
parser.SetConfigPath(filepath.Join(v.configDir, "nginx.conf"))
}
v.parser = parser
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, "vhost", DisableConfName)
_, err := os.Stat(disableFile)
return os.IsNotExist(err)
}
func (v *baseVhost) SetEnable(enable bool, _ ...string) error {
serverDir := filepath.Join(v.configDir, "vhost")
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 {
directives, err := v.parser.Find("server.listen")
if err != nil {
return nil
}
var result []types.Listen
for _, dir := range directives {
l := v.parser.parameters2Slices(dir.GetParameters())
listen := types.Listen{Address: l[0]}
for i := 1; i < len(l); i++ {
listen.Args = append(listen.Args, l[i])
}
result = append(result, listen)
}
return result
}
func (v *baseVhost) SetListen(listens []types.Listen) error {
// 将通用 Listen 转换为 Nginx 格式
var nginxListens [][]string
for _, l := range listens {
listen := []string{l.Address}
listen = append(listen, l.Args...)
nginxListens = append(nginxListens, listen)
}
return v.parser.SetListen(nginxListens)
}
func (v *baseVhost) ServerName() []string {
directive, err := v.parser.FindOne("server.server_name")
if err != nil {
return nil
}
return v.parser.parameters2Slices(directive.GetParameters())
}
func (v *baseVhost) SetServerName(serverName []string) error {
return v.parser.SetServerName(serverName)
}
func (v *baseVhost) Index() []string {
directive, err := v.parser.FindOne("server.index")
if err != nil {
return nil
}
return v.parser.parameters2Slices(directive.GetParameters())
}
func (v *baseVhost) SetIndex(index []string) error {
return v.parser.SetIndex(index)
}
func (v *baseVhost) Root() string {
root, err := v.parser.GetRoot()
if err != nil {
return ""
}
return root
}
func (v *baseVhost) SetRoot(root string) error {
return v.parser.SetRoot(root)
}
func (v *baseVhost) Includes() []types.IncludeFile {
includes, comments, err := v.parser.GetIncludes()
if err != nil {
return nil
}
var result []types.IncludeFile
for i, inc := range includes {
file := types.IncludeFile{
Path: inc,
}
if i < len(comments) {
file.Comment = comments[i]
}
result = append(result, file)
}
return result
}
func (v *baseVhost) SetIncludes(includes []types.IncludeFile) error {
var paths []string
var comments [][]string
for _, inc := range includes {
paths = append(paths, inc.Path)
comments = append(comments, inc.Comment)
}
return v.parser.SetIncludes(paths, comments)
}
func (v *baseVhost) AccessLog() string {
log, err := v.parser.GetAccessLog()
if err != nil {
return ""
}
return log
}
func (v *baseVhost) SetAccessLog(accessLog string) error {
return v.parser.SetAccessLog(accessLog)
}
func (v *baseVhost) ErrorLog() string {
log, err := v.parser.GetErrorLog()
if err != nil {
return ""
}
return log
}
func (v *baseVhost) SetErrorLog(errorLog string) error {
return v.parser.SetErrorLog(errorLog)
}
func (v *baseVhost) Save() error {
return v.parser.Save()
}
func (v *baseVhost) Reload() error {
parts := strings.Fields("systemctl reload nginx")
if err := exec.Command(parts[0], parts[1:]...).Run(); err != nil {
if testErr := exec.Command("nginx", "-t").Run(); testErr != nil {
return fmt.Errorf("nginx config test failed: %w", testErr)
}
return fmt.Errorf("failed to reload nginx config: %w", err)
}
return nil
}
func (v *baseVhost) Reset() error {
// 重置配置为默认值
parser, err := NewParser("")
if err != nil {
return fmt.Errorf("failed to reset config: %w", err)
}
// 如果有 configDir设置配置文件路径
if v.configDir != "" {
parser.SetConfigPath(filepath.Join(v.configDir, "nginx.conf"))
}
v.parser = parser
return nil
}
func (v *baseVhost) HTTPS() bool {
directive, err := v.parser.FindOne("server.ssl_certificate")
if err != nil {
return false
}
if len(v.parser.parameters2Slices(directive.GetParameters())) == 0 {
return false
}
return true
}
func (v *baseVhost) SSLConfig() *types.SSLConfig {
if !v.HTTPS() {
return nil
}
return &types.SSLConfig{
Protocols: v.parser.GetHTTPSProtocols(),
Ciphers: v.parser.GetHTTPSCiphers(),
HSTS: v.parser.GetHSTS(),
OCSP: v.parser.GetOCSP(),
HTTPRedirect: v.parser.GetHTTPSRedirect(),
AltSvc: v.parser.GetAltSvc(),
}
}
func (v *baseVhost) SetSSLConfig(cfg *types.SSLConfig) error {
if cfg == nil {
return fmt.Errorf("SSL config cannot be nil")
}
// 设置证书和私钥
if err := v.parser.SetHTTPSCert(cfg.Cert, cfg.Key); err != nil {
return err
}
// 设置协议
if len(cfg.Protocols) > 0 {
if err := v.parser.SetHTTPSProtocols(cfg.Protocols); err != nil {
return err
}
}
// 设置加密套件
if cfg.Ciphers != "" {
if err := v.parser.SetHTTPSCiphers(cfg.Ciphers); err != nil {
return err
}
}
// 设置 HSTS
if err := v.parser.SetHSTS(cfg.HSTS); err != nil {
return err
}
// 设置 OCSP
if err := v.parser.SetOCSP(cfg.OCSP); err != nil {
return err
}
// 设置 HTTP 跳转
if err := v.parser.SetHTTPSRedirect(cfg.HTTPRedirect); err != nil {
return err
}
// 设置 Alt-Svc
if cfg.AltSvc != "" {
if err := v.parser.SetAltSvc(cfg.AltSvc); err != nil {
return err
}
}
return nil
}
func (v *baseVhost) ClearHTTPS() error {
return v.parser.ClearHTTPS()
}
func (v *baseVhost) RateLimit() *types.RateLimit {
rate := v.parser.GetLimitRate()
limitConn := v.parser.GetLimitConn()
if rate == "" && len(limitConn) == 0 {
return nil
}
rateLimit := &types.RateLimit{
Rate: rate,
Options: make(map[string]string),
}
// 解析 limit_conn 配置
for _, limit := range limitConn {
if len(limit) >= 2 {
// limit_conn zone connections
// 例如: limit_conn perip 10
rateLimit.Options[limit[0]] = limit[1]
}
}
return rateLimit
}
func (v *baseVhost) SetRateLimit(limit *types.RateLimit) error {
if limit == nil {
// 清除限流配置
if err := v.parser.SetLimitRate(""); err != nil {
return err
}
return v.parser.SetLimitConn(nil)
}
// 设置限速
if err := v.parser.SetLimitRate(limit.Rate); err != nil {
return err
}
// 设置并发连接数限制
var limitConns [][]string
for zone, connections := range limit.Options {
limitConns = append(limitConns, []string{zone, connections})
}
return v.parser.SetLimitConn(limitConns)
}
func (v *baseVhost) BasicAuth() map[string]string {
realm, userFile := v.parser.GetBasicAuth()
if realm == "" || userFile == "" {
return nil
}
// 返回基本认证配置
// 注意:这里只返回配置路径,不解析用户文件内容
return map[string]string{
"realm": realm,
"user_file": userFile,
}
}
func (v *baseVhost) SetBasicAuth(auth map[string]string) error {
if len(auth) == 0 {
// 清除基本认证配置
return v.parser.SetBasicAuth("", "")
}
realm := auth["realm"]
userFile := auth["user_file"]
if realm == "" {
realm = "Restricted"
}
return v.parser.SetBasicAuth(realm, userFile)
}
func (v *baseVhost) Redirects() []types.Redirect {
vhostDir := filepath.Join(v.configDir, "vhost")
redirects, _ := parseRedirectFiles(vhostDir)
return redirects
}
func (v *baseVhost) SetRedirects(redirects []types.Redirect) error {
vhostDir := filepath.Join(v.configDir, "vhost")
return writeRedirectFiles(vhostDir, redirects)
}
// ========== PHPVhost ==========
func (v *PHPVhost) PHP() uint {
directives, err := v.parser.Find("server.include")
if err != nil {
return 0
}
var result uint
for _, dir := range directives {
if slices.ContainsFunc(v.parser.parameters2Slices(dir.GetParameters()), func(s string) bool {
return strings.HasPrefix(s, "enable-php-") && strings.HasSuffix(s, ".conf")
}) {
_, _ = fmt.Sscanf(dir.GetParameters()[0].GetValue(), "enable-php-%d.conf", &result)
}
}
return result
}
func (v *PHPVhost) SetPHP(version uint) error {
// 先移除所有 PHP 相关的 include
includes := v.Includes()
var newIncludes []types.IncludeFile
for _, inc := range includes {
// 过滤掉 enable-php-*.conf
if !strings.HasPrefix(inc.Path, "enable-php-") || !strings.HasSuffix(inc.Path, ".conf") {
newIncludes = append(newIncludes, inc)
}
}
// 如果版本不为 0添加新的 PHP include
if version > 0 {
newIncludes = append(newIncludes, types.IncludeFile{
Path: fmt.Sprintf("enable-php-%d.conf", version),
Comment: []string{fmt.Sprintf("# Enable PHP %d.%d", version/10, version%10)},
})
}
return v.SetIncludes(newIncludes)
}
// ========== ProxyVhost ==========
func (v *ProxyVhost) Proxies() []types.Proxy {
vhostDir := filepath.Join(v.configDir, "vhost")
proxies, _ := parseProxyFiles(vhostDir)
return proxies
}
func (v *ProxyVhost) SetProxies(proxies []types.Proxy) error {
vhostDir := filepath.Join(v.configDir, "vhost")
return writeProxyFiles(vhostDir, proxies)
}
func (v *ProxyVhost) ClearProxies() error {
vhostDir := filepath.Join(v.configDir, "vhost")
return clearProxyFiles(vhostDir)
}
func (v *ProxyVhost) Upstreams() map[string]types.Upstream {
globalDir := filepath.Join(v.configDir, "global")
upstreams, _ := parseUpstreamFiles(globalDir)
return upstreams
}
func (v *ProxyVhost) SetUpstreams(upstreams map[string]types.Upstream) error {
globalDir := filepath.Join(v.configDir, "global")
return writeUpstreamFiles(globalDir, upstreams)
}
func (v *ProxyVhost) ClearUpstreams() error {
globalDir := filepath.Join(v.configDir, "global")
return clearUpstreamFiles(globalDir)
}