diff --git a/backend/embed/migrations/20201013035839_initial_data.sql b/backend/embed/migrations/20201013035839_initial_data.sql index 22a7e63a..55eb59f3 100644 --- a/backend/embed/migrations/20201013035839_initial_data.sql +++ b/backend/embed/migrations/20201013035839_initial_data.sql @@ -150,11 +150,6 @@ INSERT INTO `nginx_template` ( {{/each}} # ------------------------------------------------------------ -{{#if Host.IsDisabled}} -# This Proxy Host is disabled and will not generate functional config -{{/if}} - -{{#unless Host.IsDisabled}} server { set $forward_scheme {{Host.ForwardScheme}} http; # todo set $server ""{{Host.ForwardHost}}""; # todo @@ -270,7 +265,6 @@ server { # Legacy Custom Configuration include /data/nginx/custom/server_proxy[.]conf; } -{{/unless}} " ), ( strftime('%s', 'now'), @@ -303,8 +297,6 @@ server { # Upstream {{Upstream.ID}}: {{Upstream.Name}} # ------------------------------------------------------------ -{{#unless Upstream.IsDeleted~}} - upstream npm_upstream_{{Upstream.ID}} { {{#if Upstream.IPHash~}} @@ -339,8 +331,6 @@ upstream npm_upstream_{{Upstream.ID}} { {{/unless}} {{/each}} } - -{{~/unless~}} " ); diff --git a/backend/internal/api/handler/hosts.go b/backend/internal/api/handler/hosts.go index 3a77f10a..7154f80f 100644 --- a/backend/internal/api/handler/hosts.go +++ b/backend/internal/api/handler/hosts.go @@ -152,6 +152,7 @@ func DeleteHost() func(http.ResponseWriter, *http.Request) { h.ResultErrorJSON(w, r, http.StatusNotFound, "Not found", nil) case nil: h.ResultResponseJSON(w, r, http.StatusOK, item.Delete()) + configureHost(item) default: h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil) } diff --git a/backend/internal/api/handler/upstreams.go b/backend/internal/api/handler/upstreams.go index e505d1ef..e0e9da47 100644 --- a/backend/internal/api/handler/upstreams.go +++ b/backend/internal/api/handler/upstreams.go @@ -110,6 +110,7 @@ func DeleteUpstream() func(http.ResponseWriter, *http.Request) { h.ResultErrorJSON(w, r, http.StatusNotFound, "Not found", nil) case nil: h.ResultResponseJSON(w, r, http.StatusOK, item.Delete()) + configureUpstream(item) default: h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil) } diff --git a/backend/internal/nginx/control.go b/backend/internal/nginx/control.go index 3a319cf8..8fab0e9c 100644 --- a/backend/internal/nginx/control.go +++ b/backend/internal/nginx/control.go @@ -2,6 +2,7 @@ package nginx import ( "fmt" + "os" "npm/internal/config" "npm/internal/entity/certificate" @@ -11,7 +12,15 @@ import ( "npm/internal/status" ) +const ( + DeletedSuffix = ".deleted" + DisabledSuffix = ".disabled" + ErrorSuffix = ".error" +) + // ConfigureHost will attempt to write nginx conf and reload nginx +// When a host is disabled or deleted, it will name the file with a suffix +// that won't be used by nginx. func ConfigureHost(h host.Model) error { // nolint: errcheck, gosec h.Expand([]string{"certificate", "nginxtemplate", "upstream"}) @@ -33,10 +42,16 @@ func ConfigureHost(h host.Model) error { Upstream: h.Upstream, } - filename := fmt.Sprintf("%s/host_%d.conf", data.ConfDir, h.ID) + removeHostFiles(h) + filename := getHostFilename(h, "") + if h.IsDeleted { + filename = getHostFilename(h, DeletedSuffix) + } else if h.IsDisabled { + filename = getHostFilename(h, DisabledSuffix) + } // Write the config to disk - err := writeTemplate(filename, h.NginxTemplate.Template, data) + err := writeTemplate(filename, h.NginxTemplate.Template, data, "") if err != nil { // this configuration failed somehow h.Status = status.StatusError @@ -45,12 +60,23 @@ func ConfigureHost(h host.Model) error { return h.Save(true) } - // nolint: errcheck, gosec + // Reload Nginx and check for errors if output, err := reloadNginx(); err != nil { // reloading nginx failed, likely due to this host having a problem h.Status = status.StatusError h.ErrorMessage = fmt.Sprintf("Nginx configuation error: %s - %s", err.Error(), output) - writeConfigFile(filename, fmt.Sprintf("# %s", h.ErrorMessage)) + + // Write the .error file, if this isn't a deleted or disabled host + // as the reload will only fail because of this host, if it's enabled + if !h.IsDeleted && !h.IsDisabled { + filename = getHostFilename(h, ErrorSuffix) + // Clear existing file(s) again + removeHostFiles(h) + // Write the template again, but with an error message at the end of the file + // nolint: errcheck, gosec + writeTemplate(filename, h.NginxTemplate.Template, data, h.ErrorMessage) + } + logger.Debug(h.ErrorMessage) } else { // All good @@ -75,10 +101,14 @@ func ConfigureUpstream(u upstream.Model) error { Upstream: u, } - filename := fmt.Sprintf("%s/upstream_%d.conf", data.ConfDir, u.ID) + removeUpstreamFiles(u) + filename := getUpstreamFilename(u, "") + if u.IsDeleted { + filename = getUpstreamFilename(u, DeletedSuffix) + } // Write the config to disk - err := writeTemplate(filename, u.NginxTemplate.Template, data) + err := writeTemplate(filename, u.NginxTemplate.Template, data, "") if err != nil { // this configuration failed somehow u.Status = status.StatusError @@ -92,7 +122,18 @@ func ConfigureUpstream(u upstream.Model) error { // reloading nginx failed, likely due to this host having a problem u.Status = status.StatusError u.ErrorMessage = fmt.Sprintf("Nginx configuation error: %s - %s", err.Error(), output) - writeConfigFile(filename, fmt.Sprintf("# %s", u.ErrorMessage)) + + // Write the .error file, if this isn't a deleted upstream + // as the reload will only fail because of this upstream + if !u.IsDeleted { + filename = getUpstreamFilename(u, ErrorSuffix) + // Clear existing file(s) again + removeUpstreamFiles(u) + // Write the template again, but with an error message at the end of the file + // nolint: errcheck, gosec + writeTemplate(filename, u.NginxTemplate.Template, data, u.ErrorMessage) + } + logger.Debug(u.ErrorMessage) } else { // All good @@ -103,3 +144,39 @@ func ConfigureUpstream(u upstream.Model) error { return u.Save(true) } + +func getHostFilename(h host.Model, append string) string { + confDir := fmt.Sprintf("%s/nginx/hosts", config.Configuration.DataFolder) + return fmt.Sprintf("%s/host_%d.conf%s", confDir, h.ID, append) +} + +func getUpstreamFilename(u upstream.Model, append string) string { + confDir := fmt.Sprintf("%s/nginx/upstreams", config.Configuration.DataFolder) + return fmt.Sprintf("%s/upstream_%d.conf%s", confDir, u.ID, append) +} + +func removeHostFiles(h host.Model) { + removeFiles([]string{ + getHostFilename(h, ""), + getHostFilename(h, DeletedSuffix), + getHostFilename(h, DisabledSuffix), + getHostFilename(h, ErrorSuffix), + }) +} + +func removeUpstreamFiles(u upstream.Model) { + removeFiles([]string{ + getUpstreamFilename(u, ""), + getUpstreamFilename(u, DeletedSuffix), + getUpstreamFilename(u, ErrorSuffix), + }) +} + +func removeFiles(files []string) { + for _, file := range files { + if _, err := os.Stat(file); err == nil { + // nolint: errcheck, gosec + os.Remove(file) + } + } +} diff --git a/backend/internal/nginx/templates.go b/backend/internal/nginx/templates.go index 78d432f2..61d4c2fd 100644 --- a/backend/internal/nginx/templates.go +++ b/backend/internal/nginx/templates.go @@ -34,15 +34,22 @@ func renderTemplate(template string, data TemplateData) (string, error) { return raymond.Render(template, data) } -func writeTemplate(filename, template string, data TemplateData) error { +func writeTemplate(filename, template string, data TemplateData, errorInfo string) error { output, err := renderTemplate(template, data) if err != nil { - output = fmt.Sprintf("# Template Error: %s", err.Error()) + errorInfo = err.Error() + } + + output = util.CleanupWhitespace(output) + + // Write some given error information to the end + if errorInfo != "" { + output = fmt.Sprintf("%s\n\n# =========================\n# ERROR:\n# %s\n# ========================\n", output, errorInfo) } // Write it. This will also write an error comment if generation failed // nolint: gosec - writeErr := writeConfigFile(filename, util.CleanupWhitespace(output)) + writeErr := writeConfigFile(filename, output) if err != nil { return err }