fix: Docker container data persistence issue (#249)

- Add GetDataDir() function to respect PULSE_DATA_DIR environment variable
- Update all hardcoded /var/lib/pulse paths to use configurable data directory
- Fix circular import by moving GetDataDir to utils package
- Ensures Docker containers can properly persist configuration and alerts
This commit is contained in:
Pulse Monitor
2025-08-05 07:01:11 +00:00
parent a4bb3c461f
commit 02c5319099
6 changed files with 89 additions and 19 deletions
+5 -3
View File
@@ -10,6 +10,7 @@ import (
"time"
"github.com/rcourtman/pulse-go-rewrite/internal/models"
"github.com/rcourtman/pulse-go-rewrite/internal/utils"
"github.com/rs/zerolog/log"
)
@@ -188,9 +189,10 @@ type Manager struct {
// NewManager creates a new alert manager
func NewManager() *Manager {
alertsDir := filepath.Join(utils.GetDataDir(), "alerts")
m := &Manager{
activeAlerts: make(map[string]*Alert),
historyManager: NewHistoryManager("/var/lib/pulse/alerts"),
historyManager: NewHistoryManager(alertsDir),
escalationStop: make(chan struct{}),
alertRateLimit: make(map[string][]time.Time),
recentAlerts: make(map[string]*Alert),
@@ -1216,7 +1218,7 @@ func (m *Manager) SaveActiveAlerts() error {
defer m.mu.RUnlock()
// Create directory if it doesn't exist
alertsDir := "/var/lib/pulse/alerts"
alertsDir := filepath.Join(utils.GetDataDir(), "alerts")
if err := os.MkdirAll(alertsDir, 0755); err != nil {
return fmt.Errorf("failed to create alerts directory: %w", err)
}
@@ -1253,7 +1255,7 @@ func (m *Manager) LoadActiveAlerts() error {
m.mu.Lock()
defer m.mu.Unlock()
alertsFile := "/var/lib/pulse/alerts/active-alerts.json"
alertsFile := filepath.Join(utils.GetDataDir(), "alerts", "active-alerts.json")
data, err := os.ReadFile(alertsFile)
if err != nil {
if os.IsNotExist(err) {
+2 -1
View File
@@ -8,6 +8,7 @@ import (
"sync"
"time"
"github.com/rcourtman/pulse-go-rewrite/internal/utils"
"github.com/rs/zerolog/log"
)
@@ -41,7 +42,7 @@ type HistoryManager struct {
// NewHistoryManager creates a new history manager
func NewHistoryManager(dataDir string) *HistoryManager {
if dataDir == "" {
dataDir = "/var/lib/pulse"
dataDir = utils.GetDataDir()
}
hm := &HistoryManager{
+44 -8
View File
@@ -302,10 +302,28 @@ func (h *ConfigHandlers) HandleAddNode(w http.ResponseWriter, r *http.Request) {
Msg("Added Proxmox cluster with auto-discovered endpoints")
}
} else {
// Ensure user has realm for PBS
// Parse PBS authentication details
pbsUser := req.User
if req.User != "" && !strings.Contains(req.User, "@") {
pbsUser = req.User + "@pbs" // Default to @pbs realm if not specified
pbsTokenName := req.TokenName
// Handle different token input formats
if req.TokenName != "" && req.TokenValue != "" {
// Check if token name contains the full format (user@realm!tokenname)
if strings.Contains(req.TokenName, "!") {
parts := strings.Split(req.TokenName, "!")
if len(parts) == 2 {
// Extract user from token ID if not already provided
if pbsUser == "" {
pbsUser = parts[0]
}
pbsTokenName = parts[1]
}
}
}
// Ensure user has realm for PBS (if using user/password or token with user)
if pbsUser != "" && !strings.Contains(pbsUser, "@") {
pbsUser = pbsUser + "@pbs" // Default to @pbs realm if not specified
}
pbs := config.PBSInstance{
@@ -313,7 +331,7 @@ func (h *ConfigHandlers) HandleAddNode(w http.ResponseWriter, r *http.Request) {
Host: req.Host,
User: pbsUser,
Password: req.Password,
TokenName: req.TokenName,
TokenName: pbsTokenName,
TokenValue: req.TokenValue,
Fingerprint: req.Fingerprint,
VerifySSL: req.VerifySSL,
@@ -505,17 +523,35 @@ func (h *ConfigHandlers) HandleTestConnection(w http.ResponseWriter, r *http.Req
}
// PBS test connection
// Ensure user has realm for PBS
// Parse PBS authentication details
pbsUser := user
if user != "" && !strings.Contains(user, "@") {
pbsUser = user + "@pbs" // Default to @pbs realm if not specified
pbsTokenName := tokenName
// Handle different token input formats
if req.TokenName != "" && req.TokenValue != "" {
// Check if token name contains the full format (user@realm!tokenname)
if strings.Contains(req.TokenName, "!") && tokenName == req.TokenName {
parts := strings.Split(req.TokenName, "!")
if len(parts) == 2 {
// Extract user from token ID if not already provided
if pbsUser == "" {
pbsUser = parts[0]
}
pbsTokenName = parts[1]
}
}
}
// Ensure user has realm for PBS (if using user/password or token with user)
if pbsUser != "" && !strings.Contains(pbsUser, "@") {
pbsUser = pbsUser + "@pbs" // Default to @pbs realm if not specified
}
clientConfig := pbs.ClientConfig{
Host: host,
User: pbsUser,
Password: req.Password,
TokenName: tokenName,
TokenName: pbsTokenName,
TokenValue: req.TokenValue,
VerifySSL: req.VerifySSL,
Fingerprint: req.Fingerprint,
+1 -1
View File
@@ -19,7 +19,7 @@ type Config struct {
FrontendHost string `envconfig:"FRONTEND_HOST" default:"0.0.0.0"`
FrontendPort int `envconfig:"FRONTEND_PORT" default:"7655"`
ConfigPath string `envconfig:"CONFIG_PATH" default:"/etc/pulse"`
DataPath string `envconfig:"DATA_PATH" default:"/data"`
DataPath string `envconfig:"DATA_PATH" default:"/var/lib/pulse"`
// Proxmox VE connections
PVEInstances []PVEInstance
+13
View File
@@ -0,0 +1,13 @@
package utils
import (
"os"
)
// GetDataDir returns the data directory path from environment or default
func GetDataDir() string {
if dir := os.Getenv("PULSE_DATA_DIR"); dir != "" {
return dir
}
return "/var/lib/pulse"
}
+24 -6
View File
@@ -48,13 +48,31 @@ type auth struct {
// NewClient creates a new PBS API client
func NewClient(cfg ClientConfig) (*Client, error) {
// Parse user and realm
parts := strings.Split(cfg.User, "@")
if len(parts) != 2 {
return nil, fmt.Errorf("invalid user format, expected user@realm")
var user, realm string
// For token auth, user might be empty or in a different format
if cfg.TokenName != "" && cfg.TokenValue != "" {
// Token authentication - user is optional
if cfg.User != "" {
parts := strings.Split(cfg.User, "@")
if len(parts) == 2 {
user = parts[0]
realm = parts[1]
} else {
// If no realm specified, default to pbs
user = cfg.User
realm = "pbs"
}
}
} else {
// Password authentication - user@realm format is required
parts := strings.Split(cfg.User, "@")
if len(parts) != 2 {
return nil, fmt.Errorf("invalid user format, expected user@realm")
}
user = parts[0]
realm = parts[1]
}
user := parts[0]
realm := parts[1]
// Create HTTP client with proper TLS configuration
httpClient := tlsutil.CreateHTTPClient(cfg.VerifySSL, cfg.Fingerprint)