mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2026-11-07 00:05:14 +00:00
Installs that enabled ngx_brotli before v2.3.0 have no http.d include in nginx.conf, because the include is only inserted when a module is toggled or built. The settings page reported this as "cannot write the managed configuration" and skipped the brotli save, so the save path that inserts the include was never reached, even on a stock nginx.conf. The settings response now tells the two cases apart with an in-memory insertion check that writes nothing: - include missing but insertable, or managed file missing: managedPending, the page explains the values take effect after saving and saves normally - no safe insertion point: managedUnavailable, unchanged The save path now inserts the include through ensureNginxHTTPIncludeActive, the same function module activation uses, so both produce identical results. A sentinel error keeps "no insertion point" mapped to ErrBrotliUnsupported while I/O failures are still returned as-is.
184 lines
5.9 KiB
Go
184 lines
5.9 KiB
Go
package service
|
|
|
|
import (
|
|
"errors"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
const plainNginxConf = `user root;
|
|
worker_processes auto;
|
|
|
|
include /usr/local/openresty/nginx/conf/modules-enabled/*.conf;
|
|
|
|
events {
|
|
use epoll;
|
|
}
|
|
|
|
http {
|
|
include mime.types;
|
|
default_type application/octet-stream;
|
|
|
|
gzip on;
|
|
gzip_comp_level 5;
|
|
|
|
limit_conn_zone $binary_remote_addr zone=perip:10m;
|
|
|
|
include /usr/local/openresty/nginx/conf/conf.d/*.conf;
|
|
include /usr/local/openresty/nginx/conf/default/*.conf;
|
|
}
|
|
`
|
|
|
|
func TestInsertNginxHTTPIncludeBeforeConfD(t *testing.T) {
|
|
got, err := insertNginxHTTPInclude(plainNginxConf)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !strings.Contains(got, " "+nginxHTTPIncludeDirective) {
|
|
t.Fatalf("include not inserted with matching indent:\n%s", got)
|
|
}
|
|
// Ordering is the point of the insertion site: panel defaults must be
|
|
// evaluated before per-site configuration.
|
|
httpIdx := strings.Index(got, "conf/http.d/*.conf")
|
|
confDIdx := strings.Index(got, "conf/conf.d/*.conf")
|
|
if httpIdx < 0 || confDIdx < 0 || httpIdx > confDIdx {
|
|
t.Fatalf("http.d must be included before conf.d (http.d=%d conf.d=%d)", httpIdx, confDIdx)
|
|
}
|
|
// The rest of the file must be untouched.
|
|
stripped := strings.Replace(got, " "+nginxHTTPIncludeDirective+"\n", "", 1)
|
|
if stripped != plainNginxConf {
|
|
t.Fatal("insertion altered content outside the inserted line")
|
|
}
|
|
}
|
|
|
|
func TestInsertNginxHTTPIncludeIsIdempotent(t *testing.T) {
|
|
once, err := insertNginxHTTPInclude(plainNginxConf)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
twice, err := insertNginxHTTPInclude(once)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if twice != once {
|
|
t.Fatal("a second insertion must be a no-op")
|
|
}
|
|
}
|
|
|
|
func TestInsertNginxHTTPIncludeFallsBackToHTTPBlock(t *testing.T) {
|
|
content := strings.Replace(plainNginxConf,
|
|
" include /usr/local/openresty/nginx/conf/conf.d/*.conf;\n", "", 1)
|
|
got, err := insertNginxHTTPInclude(content)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
httpIdx := strings.Index(got, "http {")
|
|
incIdx := strings.Index(got, nginxHTTPIncludeDirective)
|
|
if incIdx < 0 || incIdx < httpIdx {
|
|
t.Fatalf("include should land inside the http block:\n%s", got)
|
|
}
|
|
// Indented one level deeper than the http keyword.
|
|
if !strings.Contains(got, " "+nginxHTTPIncludeDirective) {
|
|
t.Errorf("fallback indentation is wrong:\n%s", got)
|
|
}
|
|
}
|
|
|
|
func TestInsertNginxHTTPIncludeRejectsConfigWithoutHTTPBlock(t *testing.T) {
|
|
_, err := insertNginxHTTPInclude("events {}\n")
|
|
if err == nil {
|
|
t.Fatal("a config without an http block must be rejected so callers can degrade")
|
|
}
|
|
// The save path maps this error to an actionable message and must be able
|
|
// to tell it apart from an I/O failure.
|
|
if !errors.Is(err, errNginxHTTPIncludeNoInsertionPoint) {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestInsertNginxHTTPIncludeIgnoresCommentedIncludes(t *testing.T) {
|
|
commented := strings.Replace(plainNginxConf,
|
|
" include /usr/local/openresty/nginx/conf/conf.d/*.conf;",
|
|
" # include /usr/local/openresty/nginx/conf/conf.d/*.conf;", 1)
|
|
got, err := insertNginxHTTPInclude(commented)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// The commented conf.d line is not a valid anchor; the fallback must win.
|
|
if strings.Index(got, nginxHTTPIncludeDirective) < strings.Index(got, "http {") {
|
|
t.Fatal("a commented include must not be used as the anchor")
|
|
}
|
|
}
|
|
|
|
func TestInsertNginxHTTPIncludeHandlesCRLF(t *testing.T) {
|
|
crlf := strings.ReplaceAll(plainNginxConf, "\n", "\r\n")
|
|
got, err := insertNginxHTTPInclude(crlf)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !strings.Contains(got, nginxHTTPIncludeDirective) {
|
|
t.Fatal("include missing on a CRLF file")
|
|
}
|
|
}
|
|
|
|
func TestNginxHTTPIncludeRe(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
content string
|
|
want bool
|
|
}{
|
|
{"present", plainNginxConf + " " + nginxHTTPIncludeDirective + "\n", true},
|
|
{"absent", plainNginxConf, false},
|
|
{"commented out", "# " + nginxHTTPIncludeDirective, false},
|
|
// nginx accepts quoted paths, and a hand-written or legacy installer
|
|
// may use them; a quoted include must count as present.
|
|
{"quoted absolute path", `include "/usr/local/openresty/nginx/conf/http.d/*.conf";`, true},
|
|
{"quoted with extra whitespace", ` include "/usr/local/openresty/nginx/conf/http.d/*.conf" ;`, true},
|
|
{"relative path form", ` include http.d/*.conf;`, true},
|
|
{"a different directory does not count", ` include /usr/local/openresty/nginx/conf/conf.d/*.conf;`, false},
|
|
}
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
if got := nginxHTTPIncludeRe.MatchString(tc.content); got != tc.want {
|
|
t.Fatalf("expected %v, got %v", tc.want, got)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// The anchor for insertion must tolerate the same variants, or a config with
|
|
// a quoted conf.d include would take the http-block fallback for no reason.
|
|
func TestInsertNginxHTTPIncludeWithQuotedConfDAnchor(t *testing.T) {
|
|
quoted := strings.Replace(plainNginxConf,
|
|
" include /usr/local/openresty/nginx/conf/conf.d/*.conf;",
|
|
` include "/usr/local/openresty/nginx/conf/conf.d/*.conf";`, 1)
|
|
got, err := insertNginxHTTPInclude(quoted)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
httpIdx := strings.Index(got, "conf/http.d/*.conf")
|
|
confDIdx := strings.Index(got, "conf/conf.d/*.conf")
|
|
if httpIdx < 0 || confDIdx < 0 || httpIdx > confDIdx {
|
|
t.Fatalf("quoted anchor not used; include misplaced:\n%s", got)
|
|
}
|
|
}
|
|
|
|
// A CRLF file must keep its own line endings after insertion.
|
|
func TestInsertNginxHTTPIncludeKeepsCRLFStyle(t *testing.T) {
|
|
crlf := strings.ReplaceAll(plainNginxConf, "\n", "\r\n")
|
|
got, err := insertNginxHTTPInclude(crlf)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
idx := strings.Index(got, nginxHTTPIncludeDirective)
|
|
if idx < 0 {
|
|
t.Fatal("include missing")
|
|
}
|
|
if got[idx-1] == '\n' || (idx >= 2 && got[idx-2:idx] != "\r\n" && got[idx-1] != ' ') {
|
|
// The inserted line must end with \r\n like the rest of the file.
|
|
}
|
|
end := idx + len(nginxHTTPIncludeDirective)
|
|
if end+2 > len(got) || got[end:end+2] != "\r\n" {
|
|
t.Fatalf("inserted line does not end with CRLF: %q", got[end:end+4])
|
|
}
|
|
}
|