From 5ce850f865cffc15b3aaef2719acaa789a374a14 Mon Sep 17 00:00:00 2001 From: nfebe Date: Fri, 21 Aug 2026 22:41:31 +0100 Subject: [PATCH] fix(nginx): Refresh Lua assets during upgrades Agent upgrades now keep every managed proxy asset on the same version. This prevents responses from failing when new hooks are introduced. --- internal/infra/manager.go | 70 ++++++++++++++++++++++++++++++---- internal/infra/manager_test.go | 49 ++++++++++++++++++++++++ 2 files changed, 111 insertions(+), 8 deletions(-) diff --git a/internal/infra/manager.go b/internal/infra/manager.go index 7eede9e..0e34ef0 100644 --- a/internal/infra/manager.go +++ b/internal/infra/manager.go @@ -712,16 +712,55 @@ func (m *Manager) EnsureBaseNginxConfig() error { if err != nil { return err } - if string(nginxConf) == string(existing) { - return nil + changed, err := writeFileIfChanged(confPath, nginxConf, 0644) + if err != nil { + return err } - if err := os.WriteFile(confPath, nginxConf, 0644); err != nil { - return err + if luaEnabled { + luaDir := filepath.Join(nginxDir, "lua") + if err := os.MkdirAll(luaDir, 0755); err != nil { + return err + } + agentIP := m.GetDockerHostIP() + agentPort := m.GetAgentPort() + securityLua, err := templates.GetNginxSecurityLuaWithConfig(agentIP, agentPort, m.config.Security.InternalAPIToken, m.config.Security.TrustedProxies, m.config.Security.TrustCFHeader) + if err != nil { + return err + } + securityChanged, err := writeFileIfChanged(filepath.Join(luaDir, "security.lua"), securityLua, 0644) + if err != nil { + return err + } + changed = changed || securityChanged + + trafficLua, err := templates.GetNginxTrafficLuaWithConfig(agentIP, agentPort) + if err != nil { + return err + } + trafficChanged, err := writeFileIfChanged(filepath.Join(luaDir, "traffic.lua"), trafficLua, 0644) + if err != nil { + return err + } + changed = changed || trafficChanged + + errorPage, err := templates.GetErrorPage() + if err != nil { + return err + } + errorPagePath := filepath.Join(nginxDir, "html", ".flatrun", "error.html") + if err := os.MkdirAll(filepath.Dir(errorPagePath), 0755); err != nil { + return err + } + errorPageChanged, err := writeFileIfChanged(errorPagePath, errorPage, 0644) + if err != nil { + return err + } + changed = changed || errorPageChanged + } + if changed { + _ = m.reloadNginx() } - // Best-effort reload: the rewritten config also takes effect on the next nginx - // restart, so a reload failure here (e.g. container not yet ready) is not fatal. - _ = m.reloadNginx() return nil } @@ -1456,5 +1495,20 @@ func writeNginxErrorPage(nginxDir string) error { if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil { return err } - return os.WriteFile(path, content, 0644) + _, err = writeFileIfChanged(path, content, 0644) + return err +} + +func writeFileIfChanged(path string, content []byte, mode os.FileMode) (bool, error) { + existing, err := os.ReadFile(path) + if err == nil && bytes.Equal(existing, content) { + return false, nil + } + if err != nil && !os.IsNotExist(err) { + return false, err + } + if err := os.WriteFile(path, content, mode); err != nil { + return false, err + } + return true, nil } diff --git a/internal/infra/manager_test.go b/internal/infra/manager_test.go index 2eaffbc..a55de26 100644 --- a/internal/infra/manager_test.go +++ b/internal/infra/manager_test.go @@ -7,6 +7,7 @@ import ( "testing" "github.com/flatrun/agent/pkg/config" + "github.com/flatrun/agent/templates" ) func TestSetNginxRealtimeCapture(t *testing.T) { @@ -240,6 +241,54 @@ func TestEnsureBaseNginxConfig(t *testing.T) { if !strings.Contains(string(content), "server_names_hash_bucket_size") { t.Errorf("refreshed config should contain server_names_hash_bucket_size, got:\n%s", content) } + securityLua, err := os.ReadFile(filepath.Join(nginxDir, "lua", "security.lua")) + if err != nil { + t.Fatal(err) + } + if !strings.Contains(string(securityLua), "function _M.prepare_error_response()") { + t.Errorf("refreshed security.lua is missing error response support") + } + if _, err := os.Stat(filepath.Join(nginxDir, "lua", "traffic.lua")); err != nil { + t.Fatal(err) + } + if _, err := os.Stat(filepath.Join(nginxDir, "html", ".flatrun", "error.html")); err != nil { + t.Fatal(err) + } + }) + + t.Run("refreshes stale lua beside a current config", func(t *testing.T) { + nginxDir := t.TempDir() + confPath := filepath.Join(nginxDir, "nginx.conf") + current, err := templates.GetNginxConfigWithData(true, templates.NginxConfigData{}) + if err != nil { + t.Fatal(err) + } + if err := os.WriteFile(confPath, current, 0644); err != nil { + t.Fatal(err) + } + luaDir := filepath.Join(nginxDir, "lua") + if err := os.MkdirAll(luaDir, 0755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(luaDir, "security.lua"), []byte("return {}\n"), 0644); err != nil { + t.Fatal(err) + } + + cfg := &config.Config{ + DeploymentsPath: nginxDir, + Nginx: config.NginxConfig{ConfigPath: filepath.Join(nginxDir, "conf.d")}, + } + if err := NewManager(cfg).EnsureBaseNginxConfig(); err != nil { + t.Fatalf("EnsureBaseNginxConfig() = %v", err) + } + + securityLua, err := os.ReadFile(filepath.Join(luaDir, "security.lua")) + if err != nil { + t.Fatal(err) + } + if !strings.Contains(string(securityLua), "function _M.prepare_error_response()") { + t.Errorf("stale security.lua was not refreshed") + } }) t.Run("does not create a config where none exists", func(t *testing.T) {