mirror of
https://github.com/0xJacky/nginx-ui.git
synced 2026-05-06 22:12:23 +08:00
* fix: prevent duplicate include directives in nginx.conf The FixNginxConfIncludeSites, FixNginxConfIncludeStreams, and FixNginxConfIncludeConfD functions now check if the include directive already exists before adding a new one. This prevents duplicate include directives that could cause nginx to load configurations twice, leading to errors like 'duplicate upstream' in stream configurations. Fixes the issue where stream and http includes were being added multiple times to nginx.conf: - include /etc/nginx/sites-enabled/*; - include /etc/nginx/streams-enabled/*; - include /etc/nginx/conf.d/*.conf; Added test TestFixNginxConfNoDuplicateIncludes to verify the fix. Co-authored-by: Jacky <me@jackyu.cn> * fix: align Fix function patterns with Check functions and defer backup creation - Change Fix functions to use same glob patterns as Check functions: - 'sites-enabled/*' instead of 'sites-enabled' - 'streams-enabled/*' instead of 'streams-enabled' - 'conf.d/*' instead of 'conf.d' - Move backup file creation after the duplicate check to avoid creating unnecessary backup files when no changes are needed This fixes two issues: 1. Fix functions would incorrectly skip adding includes when a non-glob include existed (e.g., conf.d/default.conf) 2. Backup files were created even when early-returning due to duplicate detection * Fix: Add backup creation for fallback paths in nginx.conf fix functions The backup creation was moved inside the block-found branch, but the fallback paths (when no http/stream block exists) still write to the file without creating a backup first. This fix adds backup creation before each fallback write operation to restore the original behavior where all code paths that modify the file are protected by a backup. --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com>
295 lines
8.5 KiB
Go
295 lines
8.5 KiB
Go
package self_check
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/0xJacky/Nginx-UI/internal/nginx"
|
|
"github.com/tufanbarisyildirim/gonginx/config"
|
|
"github.com/tufanbarisyildirim/gonginx/dumper"
|
|
"github.com/tufanbarisyildirim/gonginx/parser"
|
|
)
|
|
|
|
// CheckNginxConfIncludeSites checks if nginx.conf include sites-enabled
|
|
func CheckNginxConfIncludeSites() error {
|
|
path := nginx.GetConfEntryPath()
|
|
|
|
content, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return ErrFailedToReadNginxConf
|
|
}
|
|
|
|
// parse nginx.conf
|
|
p := parser.NewStringParser(string(content), parser.WithSkipValidDirectivesErr())
|
|
c, err := p.Parse()
|
|
if err != nil {
|
|
return ErrParseNginxConf
|
|
}
|
|
|
|
// find http block
|
|
for _, v := range c.Block.Directives {
|
|
if v.GetName() == "http" {
|
|
// find include sites-enabled
|
|
for _, directive := range v.GetBlock().GetDirectives() {
|
|
if directive.GetName() == "include" && len(directive.GetParameters()) > 0 &&
|
|
strings.Contains(directive.GetParameters()[0].Value, "sites-enabled/*") {
|
|
return nil
|
|
}
|
|
}
|
|
return ErrNginxConfNotIncludeSitesEnabled
|
|
}
|
|
}
|
|
|
|
return ErrNginxConfNoHttpBlock
|
|
}
|
|
|
|
// CheckNginxConfIncludeStreams checks if nginx.conf include streams-enabled
|
|
func CheckNginxConfIncludeStreams() error {
|
|
path := nginx.GetConfEntryPath()
|
|
|
|
content, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return ErrFailedToReadNginxConf
|
|
}
|
|
|
|
// parse nginx.conf
|
|
p := parser.NewStringParser(string(content), parser.WithSkipValidDirectivesErr())
|
|
c, err := p.Parse()
|
|
if err != nil {
|
|
return ErrParseNginxConf
|
|
}
|
|
|
|
// find http block
|
|
for _, v := range c.Block.Directives {
|
|
if v.GetName() == "stream" {
|
|
// find include sites-enabled
|
|
for _, directive := range v.GetBlock().GetDirectives() {
|
|
if directive.GetName() == "include" && len(directive.GetParameters()) > 0 &&
|
|
strings.Contains(directive.GetParameters()[0].Value, "streams-enabled/*") {
|
|
return nil
|
|
}
|
|
}
|
|
return ErrNginxConfNotIncludeStreamEnabled
|
|
}
|
|
}
|
|
|
|
return ErrNginxConfNoStreamBlock
|
|
}
|
|
|
|
// FixNginxConfIncludeSites attempts to fix nginx.conf include sites-enabled
|
|
func FixNginxConfIncludeSites() error {
|
|
path := nginx.GetConfEntryPath()
|
|
|
|
content, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return ErrFailedToReadNginxConf
|
|
}
|
|
|
|
// parse nginx.conf
|
|
p := parser.NewStringParser(string(content), parser.WithSkipValidDirectivesErr())
|
|
c, err := p.Parse()
|
|
if err != nil {
|
|
return ErrParseNginxConf
|
|
}
|
|
|
|
// find http block
|
|
for _, v := range c.Block.Directives {
|
|
if v.GetName() == "http" {
|
|
// check if sites-enabled/* include already exists
|
|
for _, directive := range v.GetBlock().GetDirectives() {
|
|
if directive.GetName() == "include" && len(directive.GetParameters()) > 0 &&
|
|
strings.Contains(directive.GetParameters()[0].Value, "sites-enabled/*") {
|
|
// already exists, nothing to do
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// create a backup file (+.bak.timestamp)
|
|
backupPath := fmt.Sprintf("%s.bak.%d", path, time.Now().Unix())
|
|
err = os.WriteFile(backupPath, content, 0644)
|
|
if err != nil {
|
|
return ErrFailedToCreateBackup
|
|
}
|
|
|
|
// add include sites-enabled/* to http block
|
|
includeDirective := &config.Directive{
|
|
Name: "include",
|
|
Parameters: []config.Parameter{{Value: resolvePath("sites-enabled/*")}},
|
|
}
|
|
|
|
realBlock := v.GetBlock().(*config.HTTP)
|
|
realBlock.Directives = append(realBlock.Directives, includeDirective)
|
|
|
|
// write to file
|
|
return os.WriteFile(path, []byte(dumper.DumpBlock(c.Block, dumper.IndentedStyle)), 0644)
|
|
}
|
|
}
|
|
|
|
// if no http block, append http block with include sites-enabled/*
|
|
// create a backup file (+.bak.timestamp) before modifying
|
|
backupPath := fmt.Sprintf("%s.bak.%d", path, time.Now().Unix())
|
|
err = os.WriteFile(backupPath, content, 0644)
|
|
if err != nil {
|
|
return ErrFailedToCreateBackup
|
|
}
|
|
|
|
content = append(content, fmt.Appendf(nil, "\nhttp {\n\tinclude %s;\n}\n", resolvePath("sites-enabled/*"))...)
|
|
return os.WriteFile(path, content, 0644)
|
|
}
|
|
|
|
// FixNginxConfIncludeStreams attempts to fix nginx.conf include streams-enabled
|
|
func FixNginxConfIncludeStreams() error {
|
|
path := nginx.GetConfEntryPath()
|
|
|
|
content, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return ErrFailedToReadNginxConf
|
|
}
|
|
|
|
// parse nginx.conf
|
|
p := parser.NewStringParser(string(content), parser.WithSkipValidDirectivesErr())
|
|
c, err := p.Parse()
|
|
if err != nil {
|
|
return ErrParseNginxConf
|
|
}
|
|
|
|
// find stream block
|
|
for _, v := range c.Block.Directives {
|
|
if v.GetName() == "stream" {
|
|
// check if streams-enabled/* include already exists
|
|
for _, directive := range v.GetBlock().GetDirectives() {
|
|
if directive.GetName() == "include" && len(directive.GetParameters()) > 0 &&
|
|
strings.Contains(directive.GetParameters()[0].Value, "streams-enabled/*") {
|
|
// already exists, nothing to do
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// create a backup file (+.bak.timestamp)
|
|
backupPath := fmt.Sprintf("%s.bak.%d", path, time.Now().Unix())
|
|
err = os.WriteFile(backupPath, content, 0644)
|
|
if err != nil {
|
|
return ErrFailedToCreateBackup
|
|
}
|
|
|
|
// add include streams-enabled/* to stream block
|
|
includeDirective := &config.Directive{
|
|
Name: "include",
|
|
Parameters: []config.Parameter{{Value: resolvePath("streams-enabled/*")}},
|
|
}
|
|
realBlock := v.GetBlock().(*config.Block)
|
|
realBlock.Directives = append(realBlock.Directives, includeDirective)
|
|
|
|
// write to file
|
|
return os.WriteFile(path, []byte(dumper.DumpBlock(c.Block, dumper.IndentedStyle)), 0644)
|
|
}
|
|
}
|
|
|
|
// if no stream block, append stream block with include streams-enabled/*
|
|
// create a backup file (+.bak.timestamp) before modifying
|
|
backupPath := fmt.Sprintf("%s.bak.%d", path, time.Now().Unix())
|
|
err = os.WriteFile(backupPath, content, 0644)
|
|
if err != nil {
|
|
return ErrFailedToCreateBackup
|
|
}
|
|
|
|
content = append(content, fmt.Appendf(nil, "\nstream {\n\tinclude %s;\n}\n", resolvePath("streams-enabled/*"))...)
|
|
return os.WriteFile(path, content, 0644)
|
|
}
|
|
|
|
// CheckNginxConfIncludeConfD checks if nginx.conf includes conf.d directory
|
|
func CheckNginxConfIncludeConfD() error {
|
|
path := nginx.GetConfEntryPath()
|
|
|
|
content, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return ErrFailedToReadNginxConf
|
|
}
|
|
|
|
// parse nginx.conf
|
|
p := parser.NewStringParser(string(content), parser.WithSkipValidDirectivesErr())
|
|
c, err := p.Parse()
|
|
if err != nil {
|
|
return ErrParseNginxConf
|
|
}
|
|
|
|
// find http block
|
|
for _, v := range c.Block.Directives {
|
|
if v.GetName() == "http" {
|
|
// find include conf.d
|
|
for _, directive := range v.GetBlock().GetDirectives() {
|
|
if directive.GetName() == "include" && len(directive.GetParameters()) > 0 &&
|
|
strings.Contains(directive.GetParameters()[0].Value, "conf.d/*") {
|
|
return nil
|
|
}
|
|
}
|
|
return ErrNginxConfNotIncludeConfD
|
|
}
|
|
}
|
|
|
|
return ErrNginxConfNoHttpBlock
|
|
}
|
|
|
|
// FixNginxConfIncludeConfD attempts to fix nginx.conf to include conf.d directory
|
|
func FixNginxConfIncludeConfD() error {
|
|
path := nginx.GetConfEntryPath()
|
|
|
|
content, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return ErrFailedToReadNginxConf
|
|
}
|
|
|
|
// parse nginx.conf
|
|
p := parser.NewStringParser(string(content), parser.WithSkipValidDirectivesErr())
|
|
c, err := p.Parse()
|
|
if err != nil {
|
|
return ErrParseNginxConf
|
|
}
|
|
|
|
// find http block
|
|
for _, v := range c.Block.Directives {
|
|
if v.GetName() == "http" {
|
|
// check if conf.d/* include already exists
|
|
for _, directive := range v.GetBlock().GetDirectives() {
|
|
if directive.GetName() == "include" && len(directive.GetParameters()) > 0 &&
|
|
strings.Contains(directive.GetParameters()[0].Value, "conf.d/*") {
|
|
// already exists, nothing to do
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// create a backup file (+.bak.timestamp)
|
|
backupPath := fmt.Sprintf("%s.bak.%d", path, time.Now().Unix())
|
|
err = os.WriteFile(backupPath, content, 0644)
|
|
if err != nil {
|
|
return ErrFailedToCreateBackup
|
|
}
|
|
|
|
// add include conf.d/*.conf to http block
|
|
includeDirective := &config.Directive{
|
|
Name: "include",
|
|
Parameters: []config.Parameter{{Value: resolvePath("conf.d/*")}},
|
|
}
|
|
|
|
realBlock := v.GetBlock().(*config.HTTP)
|
|
realBlock.Directives = append(realBlock.Directives, includeDirective)
|
|
|
|
// write to file
|
|
return os.WriteFile(path, []byte(dumper.DumpBlock(c.Block, dumper.IndentedStyle)), 0644)
|
|
}
|
|
}
|
|
|
|
// if no http block, append http block with include conf.d/*.conf
|
|
// create a backup file (+.bak.timestamp) before modifying
|
|
backupPath := fmt.Sprintf("%s.bak.%d", path, time.Now().Unix())
|
|
err = os.WriteFile(backupPath, content, 0644)
|
|
if err != nil {
|
|
return ErrFailedToCreateBackup
|
|
}
|
|
|
|
content = append(content, fmt.Appendf(nil, "\nhttp {\n\tinclude %s;\n}\n", resolvePath("conf.d/*"))...)
|
|
return os.WriteFile(path, content, 0644)
|
|
}
|