From aea7afc1fd68e9a577a937f362141f10f6159f63 Mon Sep 17 00:00:00 2001 From: Michael Reber Date: Fri, 31 Jan 2025 18:25:31 +0100 Subject: [PATCH] Implement central logging function and debug mode switching --- cmd/server/main.go | 61 ++++++++++++++++++++++++++++++++----- internal/config/logging.go | 37 ++++++++++++++++++++++ internal/config/settings.go | 31 +++++++++---------- pkg/web/handlers.go | 40 ++++++++++++------------ 4 files changed, 125 insertions(+), 44 deletions(-) create mode 100644 internal/config/logging.go diff --git a/cmd/server/main.go b/cmd/server/main.go index 7d470e4..9c5ec89 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -1,23 +1,70 @@ package main import ( + "fmt" "log" + "time" "github.com/gin-gonic/gin" + "github.com/swissmakers/fail2ban-ui/internal/config" "github.com/swissmakers/fail2ban-ui/pkg/web" ) func main() { - r := gin.Default() + settings := config.GetSettings() - // Load HTML templates from pkg/web/templates - r.LoadHTMLGlob("pkg/web/templates/*") + // Set Gin mode based on settings + if settings.Debug { + gin.SetMode(gin.DebugMode) + } else { + gin.SetMode(gin.ReleaseMode) + } - // Register our routes (IndexHandler, /api/summary, /api/jails/:jail/unban/:ip) - web.RegisterRoutes(r) + router := gin.Default() + router.LoadHTMLGlob("pkg/web/templates/*") // Load HTML templates from pkg/web/templates + web.RegisterRoutes(router) // Register routes (IndexHandler, /api/summary, jail/unban/:ip) etc.. - log.Println("Starting Fail2ban-UI server on :8080.") - if err := r.Run(":8080"); err != nil { + printWelcomeBanner() + log.Println("--- Fail2Ban-UI started in", gin.Mode(), "mode ---") + log.Println("Server listening on port :8080.") + + if err := router.Run(":8080"); err != nil { log.Fatalf("Server crashed: %v", err) } } + +// printWelcomeBanner prints a cool Tux banner with startup info +func printWelcomeBanner() { + greeting := getGreeting() + const tuxBanner = ` + .--. + |o_o | %s + |:_/ | + // \ \ + (| | ) + /'\_ _/'\ + \___)=(___/ + +Fail2Ban UI - A Swissmade Management Interface +---------------------------------------------- +Developers: https://swissmakers.ch +Mode: %s +Listening on: http://0.0.0.0:8080 +---------------------------------------------- + +` + fmt.Printf(tuxBanner, greeting, gin.Mode()) +} + +// getGreeting returns a friendly greeting based on the time of day +func getGreeting() string { + hour := time.Now().Hour() + switch { + case hour < 12: + return "Good morning!" + case hour < 18: + return "Good afternoon!" + default: + return "Good evening!" + } +} diff --git a/internal/config/logging.go b/internal/config/logging.go new file mode 100644 index 0000000..fcf8040 --- /dev/null +++ b/internal/config/logging.go @@ -0,0 +1,37 @@ +// Fail2ban UI - A Swiss made, management interface for Fail2ban. +// +// Copyright (C) 2025 Swissmakers GmbH (https://swissmakers.ch) +// +// Licensed under the GNU General Public License, Version 3 (GPL-3.0) +// You may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.gnu.org/licenses/gpl-3.0.en.html +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package config + +import ( + "log" +) + +// DebugLog prints debug messages only if debug mode is enabled. +func DebugLog(format string, v ...interface{}) { + // Avoid deadlocks by not calling GetSettings() inside DebugLog. + debugEnabled := false + debugEnabled = currentSettings.Debug + if !debugEnabled { + return + } + // Ensure correct usage of fmt.Printf-style formatting + if len(v) > 0 { + log.Printf(format, v...) // Uses format directives + } else { + log.Println(format) // Just prints the message + } +} diff --git a/internal/config/settings.go b/internal/config/settings.go index b8f0f75..10d2154 100644 --- a/internal/config/settings.go +++ b/internal/config/settings.go @@ -20,7 +20,6 @@ import ( "bufio" "encoding/json" "fmt" - "log" "os" "regexp" "strconv" @@ -73,11 +72,12 @@ var ( func init() { // Attempt to load existing file; if it doesn't exist, create with defaults. if err := loadSettings(); err != nil { - fmt.Println("App settings not found, initializing new from jail.local (if exist):", err) + fmt.Println("App settings not found, initializing from jail.local (if exist)") if err := initializeFromJailFile(); err != nil { fmt.Println("Error reading jail.local:", err) } setDefaults() + fmt.Println("Initialized successfully.") // save defaults to file if err := saveSettings(); err != nil { @@ -251,7 +251,7 @@ func ensureJailDConfig() error { // Check if the file already exists if _, err := os.Stat(jailDFile); err == nil { // File already exists, do nothing - fmt.Println("Custom jail.d configuration already exists.") + DebugLog("Custom jail.d configuration already exists.") return nil } @@ -268,7 +268,7 @@ action_mwlg = %(action_)s return fmt.Errorf("failed to write jail.d config: %v", err) } - fmt.Println("Created custom jail.d configuration at:", jailDFile) + DebugLog("Created custom jail.d configuration at: %v", jailDFile) return nil } @@ -317,14 +317,14 @@ logpath = /dev/null return fmt.Errorf("failed to write action file: %w", err) } - fmt.Printf("Action file successfully written to %s\n", actionFile) + DebugLog("Custom-action file successfully written to %s\n", actionFile) return nil } // loadSettings reads fail2ban-ui-settings.json into currentSettings. func loadSettings() error { - fmt.Println("----------------------------") - fmt.Println("loadSettings called (settings.go)") // entry point + DebugLog("----------------------------") + DebugLog("loadSettings called (settings.go)") // entry point data, err := os.ReadFile(settingsFile) if os.IsNotExist(err) { return err // triggers setDefaults + save @@ -346,21 +346,18 @@ func loadSettings() error { // saveSettings writes currentSettings to JSON func saveSettings() error { - fmt.Println("----------------------------") - fmt.Println("saveSettings called (settings.go)") // entry point + DebugLog("----------------------------") + DebugLog("saveSettings called (settings.go)") // entry point b, err := json.MarshalIndent(currentSettings, "", " ") if err != nil { - fmt.Println("Error marshalling settings:", err) // Debug + DebugLog("Error marshalling settings: %v", err) // Debug return err } - fmt.Println("Settings marshaled, writing to file...") // Log marshaling success - //return os.WriteFile(settingsFile, b, 0644) + DebugLog("Settings marshaled, writing to file...") // Log marshaling success err = os.WriteFile(settingsFile, b, 0644) if err != nil { - log.Println("Error writing to file:", err) // Debug - } else { - log.Println("Settings saved successfully!") // Debug + DebugLog("Error writing to file: %v", err) // Debug } // Update the Fail2ban action file return writeFail2banAction() @@ -396,7 +393,7 @@ func UpdateSettings(new AppSettings) (AppSettings, error) { settingsLock.Lock() defer settingsLock.Unlock() - fmt.Println("Locked settings for update") // Log lock acquisition + DebugLog("--- Locked settings for update ---") // Log lock acquisition old := currentSettings @@ -421,7 +418,7 @@ func UpdateSettings(new AppSettings) (AppSettings, error) { } currentSettings = new - fmt.Println("New settings applied:", currentSettings) // Log settings applied + DebugLog("New settings applied: %v", currentSettings) // Log settings applied // persist to file if err := saveSettings(); err != nil { diff --git a/pkg/web/handlers.go b/pkg/web/handlers.go index ebd7844..905c5be 100644 --- a/pkg/web/handlers.go +++ b/pkg/web/handlers.go @@ -81,8 +81,8 @@ func SummaryHandler(c *gin.Context) { // UnbanIPHandler unbans a given IP in a specific jail. func UnbanIPHandler(c *gin.Context) { - fmt.Println("----------------------------") - fmt.Println("UnbanIPHandler called (handlers.go)") // entry point + config.DebugLog("----------------------------") + config.DebugLog("UnbanIPHandler called (handlers.go)") // entry point jail := c.Param("jail") ip := c.Param("ip") @@ -93,7 +93,7 @@ func UnbanIPHandler(c *gin.Context) { }) return } - fmt.Println(ip + " from jail " + jail + " unbanned successfully (handlers.go)") + fmt.Println(ip + " from jail " + jail + " unbanned successfully.") c.JSON(http.StatusOK, gin.H{ "message": "IP unbanned successfully", }) @@ -112,7 +112,7 @@ func BanNotificationHandler(c *gin.Context) { // **DEBUGGING: Log Raw JSON Body** body, _ := io.ReadAll(c.Request.Body) - log.Printf("📩 Incoming Ban Notification: %s\n", string(body)) + config.DebugLog("📩 Incoming Ban Notification: %s\n", string(body)) // Rebind body so Gin can parse it again (important!) c.Request.Body = io.NopCloser(bytes.NewBuffer(body)) @@ -246,8 +246,8 @@ func GetJailFilterConfigHandler(c *gin.Context) { // SetJailFilterConfigHandler overwrites the current filter config with new content func SetJailFilterConfigHandler(c *gin.Context) { - fmt.Println("----------------------------") - fmt.Println("SetJailFilterConfigHandler called (handlers.go)") // entry point + config.DebugLog("----------------------------") + config.DebugLog("SetJailFilterConfigHandler called (handlers.go)") // entry point jail := c.Param("jail") // Parse JSON body (containing the new filter content) @@ -282,16 +282,16 @@ func SetJailFilterConfigHandler(c *gin.Context) { // GetSettingsHandler returns the entire AppSettings struct as JSON func GetSettingsHandler(c *gin.Context) { - fmt.Println("----------------------------") - fmt.Println("GetSettingsHandler called (handlers.go)") // entry point + config.DebugLog("----------------------------") + config.DebugLog("GetSettingsHandler called (handlers.go)") // entry point s := config.GetSettings() c.JSON(http.StatusOK, s) } // UpdateSettingsHandler updates the AppSettings from a JSON body func UpdateSettingsHandler(c *gin.Context) { - fmt.Println("----------------------------") - fmt.Println("UpdateSettingsHandler called (handlers.go)") // entry point + config.DebugLog("----------------------------") + config.DebugLog("UpdateSettingsHandler called (handlers.go)") // entry point var req config.AppSettings if err := c.ShouldBindJSON(&req); err != nil { fmt.Println("JSON binding error:", err) // Debug @@ -301,7 +301,7 @@ func UpdateSettingsHandler(c *gin.Context) { }) return } - fmt.Println("JSON binding successful, updating settings (handlers.go)") + config.DebugLog("JSON binding successful, updating settings (handlers.go)") newSettings, err := config.UpdateSettings(req) if err != nil { @@ -309,7 +309,7 @@ func UpdateSettingsHandler(c *gin.Context) { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } - fmt.Println("Settings updated successfully (handlers.go)") + config.DebugLog("Settings updated successfully (handlers.go)") c.JSON(http.StatusOK, gin.H{ "message": "Settings updated", @@ -320,8 +320,8 @@ func UpdateSettingsHandler(c *gin.Context) { // ListFiltersHandler returns a JSON array of filter names // found as *.conf in /etc/fail2ban/filter.d func ListFiltersHandler(c *gin.Context) { - fmt.Println("----------------------------") - fmt.Println("ListFiltersHandler called (handlers.go)") // entry point + config.DebugLog("----------------------------") + config.DebugLog("ListFiltersHandler called (handlers.go)") // entry point dir := "/etc/fail2ban/filter.d" files, err := os.ReadDir(dir) @@ -344,8 +344,8 @@ func ListFiltersHandler(c *gin.Context) { } func TestFilterHandler(c *gin.Context) { - fmt.Println("----------------------------") - fmt.Println("TestFilterHandler called (handlers.go)") // entry point + config.DebugLog("----------------------------") + config.DebugLog("TestFilterHandler called (handlers.go)") // entry point var req struct { FilterName string `json:"filterName"` LogLines []string `json:"logLines"` @@ -361,8 +361,8 @@ func TestFilterHandler(c *gin.Context) { // ApplyFail2banSettings updates /etc/fail2ban/jail.local [DEFAULT] with our JSON func ApplyFail2banSettings(jailLocalPath string) error { - fmt.Println("----------------------------") - fmt.Println("ApplyFail2banSettings called (handlers.go)") // entry point + config.DebugLog("----------------------------") + config.DebugLog("ApplyFail2banSettings called (handlers.go)") // entry point s := config.GetSettings() // open /etc/fail2ban/jail.local, parse or do a simplistic approach: @@ -387,8 +387,8 @@ func ApplyFail2banSettings(jailLocalPath string) error { // ReloadFail2banHandler reloads the Fail2ban service func ReloadFail2banHandler(c *gin.Context) { - fmt.Println("----------------------------") - fmt.Println("ApplyFail2banSettings called (handlers.go)") // entry point + config.DebugLog("----------------------------") + config.DebugLog("ApplyFail2banSettings called (handlers.go)") // entry point // First we write our new settings to /etc/fail2ban/jail.local // if err := fail2ban.ApplyFail2banSettings("/etc/fail2ban/jail.local"); err != nil {