Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b7498745f9 | |||
| 79f2efaaac | |||
| 19f44749b1 | |||
| c7904c7857 |
3
.gitignore
vendored
3
.gitignore
vendored
@ -37,3 +37,6 @@ CRITICAL_BUGS_FIXED.md
|
||||
LEGAL_DOCUMENTATION.md
|
||||
LEGAL_*.md
|
||||
legal/
|
||||
|
||||
# Release binaries (uploaded via gh release, not git)
|
||||
release/dbbackup_*
|
||||
|
||||
54
CHANGELOG.md
54
CHANGELOG.md
@ -5,6 +5,60 @@ All notable changes to dbbackup will be documented in this file.
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [4.1.3] - 2026-01-27
|
||||
|
||||
### Added
|
||||
- **`--config` / `-c` global flag** - Specify config file path from anywhere
|
||||
- Example: `dbbackup --config /opt/dbbackup/.dbbackup.conf backup single mydb`
|
||||
- No longer need to `cd` to config directory before running commands
|
||||
- Works with all subcommands (backup, restore, verify, etc.)
|
||||
|
||||
## [4.1.2] - 2026-01-27
|
||||
|
||||
### Added
|
||||
- **`--socket` flag for MySQL/MariaDB** - Connect via Unix socket instead of TCP/IP
|
||||
- Usage: `dbbackup backup single mydb --db-type mysql --socket /var/run/mysqld/mysqld.sock`
|
||||
- Works for both backup and restore operations
|
||||
- Supports socket auth (no password required with proper permissions)
|
||||
|
||||
### Fixed
|
||||
- **Socket path as --host now works** - If `--host` starts with `/`, it's auto-detected as a socket path
|
||||
- Example: `--host /var/run/mysqld/mysqld.sock` now works correctly instead of DNS lookup error
|
||||
- Auto-converts to `--socket` internally
|
||||
|
||||
## [4.1.1] - 2026-01-25
|
||||
|
||||
### Added
|
||||
- **`dbbackup_build_info` metric** - Exposes version and git commit as Prometheus labels
|
||||
- Useful for tracking deployed versions across a fleet
|
||||
- Labels: `server`, `version`, `commit`
|
||||
|
||||
### Fixed
|
||||
- **Documentation clarification**: The `pitr_base` value for `backup_type` label is auto-assigned
|
||||
by `dbbackup pitr base` command. CLI `--backup-type` flag only accepts `full` or `incremental`.
|
||||
This was causing confusion in deployments.
|
||||
|
||||
## [4.1.0] - 2026-01-25
|
||||
|
||||
### Added
|
||||
- **Backup Type Tracking**: All backup metrics now include a `backup_type` label
|
||||
(`full`, `incremental`, or `pitr_base` for PITR base backups)
|
||||
- **PITR Metrics**: Complete Point-in-Time Recovery monitoring
|
||||
- `dbbackup_pitr_enabled` - Whether PITR is enabled (1/0)
|
||||
- `dbbackup_pitr_archive_lag_seconds` - Seconds since last WAL/binlog archived
|
||||
- `dbbackup_pitr_chain_valid` - WAL/binlog chain integrity (1=valid)
|
||||
- `dbbackup_pitr_gap_count` - Number of gaps in archive chain
|
||||
- `dbbackup_pitr_archive_count` - Total archived segments
|
||||
- `dbbackup_pitr_archive_size_bytes` - Total archive storage
|
||||
- `dbbackup_pitr_recovery_window_minutes` - Estimated PITR coverage
|
||||
- **PITR Alerting Rules**: 6 new alerts for PITR monitoring
|
||||
- PITRArchiveLag, PITRChainBroken, PITRGapsDetected, PITRArchiveStalled,
|
||||
PITRStorageGrowing, PITRDisabledUnexpectedly
|
||||
- **`dbbackup_backup_by_type` metric** - Count backups by type
|
||||
|
||||
### Changed
|
||||
- `dbbackup_backup_total` type changed from counter to gauge for snapshot-based collection
|
||||
|
||||
## [3.42.110] - 2026-01-24
|
||||
|
||||
### Improved - Code Quality & Testing
|
||||
|
||||
3
QUICK.md
3
QUICK.md
@ -14,6 +14,9 @@ dbbackup backup single myapp
|
||||
# MySQL
|
||||
dbbackup backup single gitea --db-type mysql --host 127.0.0.1 --port 3306
|
||||
|
||||
# MySQL/MariaDB with Unix socket
|
||||
dbbackup backup single myapp --db-type mysql --socket /var/run/mysqld/mysqld.sock
|
||||
|
||||
# With compression level (0-9, default 6)
|
||||
dbbackup backup cluster --compression 9
|
||||
|
||||
|
||||
@ -127,8 +127,8 @@ func runMetricsExport(ctx context.Context) error {
|
||||
}
|
||||
defer cat.Close()
|
||||
|
||||
// Create metrics writer
|
||||
writer := prometheus.NewMetricsWriter(log, cat, server)
|
||||
// Create metrics writer with version info
|
||||
writer := prometheus.NewMetricsWriterWithVersion(log, cat, server, cfg.Version, cfg.GitCommit)
|
||||
|
||||
// Write textfile
|
||||
if err := writer.WriteTextfile(metricsOutput); err != nil {
|
||||
@ -162,8 +162,8 @@ func runMetricsServe(ctx context.Context) error {
|
||||
}
|
||||
defer cat.Close()
|
||||
|
||||
// Create exporter
|
||||
exporter := prometheus.NewExporter(log, cat, server, metricsPort)
|
||||
// Create exporter with version info
|
||||
exporter := prometheus.NewExporterWithVersion(log, cat, server, metricsPort, cfg.Version, cfg.GitCommit)
|
||||
|
||||
// Run server (blocks until context is cancelled)
|
||||
return exporter.Serve(ctx)
|
||||
|
||||
33
cmd/root.go
33
cmd/root.go
@ -3,6 +3,7 @@ package cmd
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"dbbackup/internal/config"
|
||||
"dbbackup/internal/logger"
|
||||
@ -54,9 +55,26 @@ For help with specific commands, use: dbbackup [command] --help`,
|
||||
|
||||
// Load local config if not disabled
|
||||
if !cfg.NoLoadConfig {
|
||||
if localCfg, err := config.LoadLocalConfig(); err != nil {
|
||||
log.Warn("Failed to load local config", "error", err)
|
||||
} else if localCfg != nil {
|
||||
// Use custom config path if specified, otherwise default to current directory
|
||||
var localCfg *config.LocalConfig
|
||||
var err error
|
||||
if cfg.ConfigPath != "" {
|
||||
localCfg, err = config.LoadLocalConfigFromPath(cfg.ConfigPath)
|
||||
if err != nil {
|
||||
log.Warn("Failed to load config from specified path", "path", cfg.ConfigPath, "error", err)
|
||||
} else if localCfg != nil {
|
||||
log.Info("Loaded configuration", "path", cfg.ConfigPath)
|
||||
}
|
||||
} else {
|
||||
localCfg, err = config.LoadLocalConfig()
|
||||
if err != nil {
|
||||
log.Warn("Failed to load local config", "error", err)
|
||||
} else if localCfg != nil {
|
||||
log.Info("Loaded configuration from .dbbackup.conf")
|
||||
}
|
||||
}
|
||||
|
||||
if localCfg != nil {
|
||||
// Save current flag values that were explicitly set
|
||||
savedBackupDir := cfg.BackupDir
|
||||
savedHost := cfg.Host
|
||||
@ -71,7 +89,6 @@ For help with specific commands, use: dbbackup [command] --help`,
|
||||
|
||||
// Apply config from file
|
||||
config.ApplyLocalConfig(cfg, localCfg)
|
||||
log.Info("Loaded configuration from .dbbackup.conf")
|
||||
|
||||
// Restore explicitly set flag values (flags have priority)
|
||||
if flagsSet["backup-dir"] {
|
||||
@ -107,6 +124,12 @@ For help with specific commands, use: dbbackup [command] --help`,
|
||||
}
|
||||
}
|
||||
|
||||
// Auto-detect socket from --host path (if host starts with /)
|
||||
if strings.HasPrefix(cfg.Host, "/") && cfg.Socket == "" {
|
||||
cfg.Socket = cfg.Host
|
||||
cfg.Host = "localhost" // Reset host for socket connections
|
||||
}
|
||||
|
||||
return cfg.SetDatabaseType(cfg.DatabaseType)
|
||||
},
|
||||
}
|
||||
@ -134,8 +157,10 @@ func Execute(ctx context.Context, config *config.Config, logger logger.Logger) e
|
||||
cfg.Version, cfg.BuildTime, cfg.GitCommit)
|
||||
|
||||
// Add persistent flags
|
||||
rootCmd.PersistentFlags().StringVarP(&cfg.ConfigPath, "config", "c", "", "Path to config file (default: .dbbackup.conf in current directory)")
|
||||
rootCmd.PersistentFlags().StringVar(&cfg.Host, "host", cfg.Host, "Database host")
|
||||
rootCmd.PersistentFlags().IntVar(&cfg.Port, "port", cfg.Port, "Database port")
|
||||
rootCmd.PersistentFlags().StringVar(&cfg.Socket, "socket", cfg.Socket, "Unix socket path for MySQL/MariaDB (e.g., /var/run/mysqld/mysqld.sock)")
|
||||
rootCmd.PersistentFlags().StringVar(&cfg.User, "user", cfg.User, "Database user")
|
||||
rootCmd.PersistentFlags().StringVar(&cfg.Database, "database", cfg.Database, "Database name")
|
||||
rootCmd.PersistentFlags().StringVar(&cfg.Password, "password", cfg.Password, "Database password")
|
||||
|
||||
@ -5,13 +5,15 @@ This document provides complete reference for the DBBackup Prometheus exporter,
|
||||
## What's New (January 2026)
|
||||
|
||||
### New Features
|
||||
- **Backup Type Tracking**: All backup metrics now include a `backup_type` label (`full`, `incremental`, `pitr_base`)
|
||||
- **Backup Type Tracking**: All backup metrics now include a `backup_type` label (`full`, `incremental`, or `pitr_base` for PITR base backups)
|
||||
- **Note**: CLI `--backup-type` flag only accepts `full` or `incremental`. The `pitr_base` label is auto-assigned when using `dbbackup pitr base`
|
||||
- **PITR Metrics**: Complete Point-in-Time Recovery monitoring for PostgreSQL WAL and MySQL binlog archiving
|
||||
- **New Alerts**: PITR-specific alerts for archive lag, chain integrity, and gap detection
|
||||
|
||||
### New Metrics Added
|
||||
| Metric | Description |
|
||||
|--------|-------------|
|
||||
| `dbbackup_build_info` | Build info with version and commit labels |
|
||||
| `dbbackup_backup_by_type` | Count backups by type (full/incremental/pitr_base) |
|
||||
| `dbbackup_pitr_enabled` | Whether PITR is enabled (1/0) |
|
||||
| `dbbackup_pitr_archive_lag_seconds` | Seconds since last WAL/binlog archived |
|
||||
|
||||
@ -43,6 +43,13 @@ dbbackup_backup_total{status="success"}
|
||||
**Labels:** `server`, `database`, `backup_type`
|
||||
**Description:** Total count of backups by backup type (`full`, `incremental`, `pitr_base`).
|
||||
|
||||
> **Note:** The `backup_type` label values are:
|
||||
> - `full` - Created with `--backup-type full` (default)
|
||||
> - `incremental` - Created with `--backup-type incremental`
|
||||
> - `pitr_base` - Auto-assigned when using `dbbackup pitr base` command
|
||||
>
|
||||
> The CLI `--backup-type` flag only accepts `full` or `incremental`.
|
||||
|
||||
**Example Query:**
|
||||
```promql
|
||||
# Count of each backup type
|
||||
@ -229,6 +236,32 @@ dbbackup_pitr_chain_valid == 0
|
||||
|
||||
---
|
||||
|
||||
## Build Information Metrics
|
||||
|
||||
### `dbbackup_build_info`
|
||||
**Type:** Gauge
|
||||
**Labels:** `server`, `version`, `commit`
|
||||
**Description:** Build information for the dbbackup exporter. Value is always 1.
|
||||
|
||||
This metric is useful for:
|
||||
- Tracking which version is deployed across your fleet
|
||||
- Alerting when versions drift between servers
|
||||
- Correlating behavior changes with deployments
|
||||
|
||||
**Example Queries:**
|
||||
```promql
|
||||
# Show all deployed versions
|
||||
group by (version) (dbbackup_build_info)
|
||||
|
||||
# Find servers not on latest version
|
||||
dbbackup_build_info{version!="4.1.1"}
|
||||
|
||||
# Alert on version drift
|
||||
count(count by (version) (dbbackup_build_info)) > 1
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Alerting Rules
|
||||
|
||||
See [alerting-rules.yaml](../grafana/alerting-rules.yaml) for pre-configured Prometheus alerting rules.
|
||||
|
||||
@ -17,12 +17,16 @@ type Config struct {
|
||||
BuildTime string
|
||||
GitCommit string
|
||||
|
||||
// Config file path (--config flag)
|
||||
ConfigPath string
|
||||
|
||||
// Database connection
|
||||
Host string
|
||||
Port int
|
||||
User string
|
||||
Database string
|
||||
Password string
|
||||
Socket string // Unix socket path for MySQL/MariaDB
|
||||
DatabaseType string // "postgres" or "mysql"
|
||||
SSLMode string
|
||||
Insecure bool
|
||||
|
||||
@ -42,8 +42,11 @@ type LocalConfig struct {
|
||||
|
||||
// LoadLocalConfig loads configuration from .dbbackup.conf in current directory
|
||||
func LoadLocalConfig() (*LocalConfig, error) {
|
||||
configPath := filepath.Join(".", ConfigFileName)
|
||||
return LoadLocalConfigFromPath(filepath.Join(".", ConfigFileName))
|
||||
}
|
||||
|
||||
// LoadLocalConfigFromPath loads configuration from a specific path
|
||||
func LoadLocalConfigFromPath(configPath string) (*LocalConfig, error) {
|
||||
data, err := os.ReadFile(configPath)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
|
||||
@ -278,8 +278,12 @@ func (m *MySQL) GetTableRowCount(ctx context.Context, database, table string) (i
|
||||
func (m *MySQL) BuildBackupCommand(database, outputFile string, options BackupOptions) []string {
|
||||
cmd := []string{"mysqldump"}
|
||||
|
||||
// Connection parameters - handle localhost vs remote differently
|
||||
if m.cfg.Host == "" || m.cfg.Host == "localhost" {
|
||||
// Connection parameters - socket takes priority, then localhost vs remote
|
||||
if m.cfg.Socket != "" {
|
||||
// Explicit socket path provided
|
||||
cmd = append(cmd, "-S", m.cfg.Socket)
|
||||
cmd = append(cmd, "-u", m.cfg.User)
|
||||
} else if m.cfg.Host == "" || m.cfg.Host == "localhost" {
|
||||
// For localhost, use socket connection (don't specify host/port)
|
||||
cmd = append(cmd, "-u", m.cfg.User)
|
||||
} else {
|
||||
@ -338,8 +342,12 @@ func (m *MySQL) BuildBackupCommand(database, outputFile string, options BackupOp
|
||||
func (m *MySQL) BuildRestoreCommand(database, inputFile string, options RestoreOptions) []string {
|
||||
cmd := []string{"mysql"}
|
||||
|
||||
// Connection parameters - handle localhost vs remote differently
|
||||
if m.cfg.Host == "" || m.cfg.Host == "localhost" {
|
||||
// Connection parameters - socket takes priority, then localhost vs remote
|
||||
if m.cfg.Socket != "" {
|
||||
// Explicit socket path provided
|
||||
cmd = append(cmd, "-S", m.cfg.Socket)
|
||||
cmd = append(cmd, "-u", m.cfg.User)
|
||||
} else if m.cfg.Host == "" || m.cfg.Host == "localhost" {
|
||||
// For localhost, use socket connection (don't specify host/port)
|
||||
cmd = append(cmd, "-u", m.cfg.User)
|
||||
} else {
|
||||
@ -417,8 +425,11 @@ func (m *MySQL) buildDSN() string {
|
||||
|
||||
dsn += "@"
|
||||
|
||||
// Handle localhost with Unix socket vs TCP/IP
|
||||
if m.cfg.Host == "" || m.cfg.Host == "localhost" {
|
||||
// Explicit socket takes priority
|
||||
if m.cfg.Socket != "" {
|
||||
dsn += "unix(" + m.cfg.Socket + ")"
|
||||
} else if m.cfg.Host == "" || m.cfg.Host == "localhost" {
|
||||
// Handle localhost with Unix socket vs TCP/IP
|
||||
// Try common socket paths for localhost connections
|
||||
socketPaths := []string{
|
||||
"/run/mysqld/mysqld.sock",
|
||||
|
||||
@ -345,8 +345,10 @@ func (e *MySQLDumpEngine) Restore(ctx context.Context, opts *RestoreOptions) err
|
||||
// Build mysql command
|
||||
args := []string{}
|
||||
|
||||
// Connection parameters
|
||||
if e.config.Host != "" && e.config.Host != "localhost" {
|
||||
// Connection parameters - socket takes priority over host
|
||||
if e.config.Socket != "" {
|
||||
args = append(args, "-S", e.config.Socket)
|
||||
} else if e.config.Host != "" && e.config.Host != "localhost" {
|
||||
args = append(args, "-h", e.config.Host)
|
||||
args = append(args, "-P", strconv.Itoa(e.config.Port))
|
||||
}
|
||||
@ -494,8 +496,10 @@ func (e *MySQLDumpEngine) BackupToWriter(ctx context.Context, w io.Writer, opts
|
||||
func (e *MySQLDumpEngine) buildArgs(database string) []string {
|
||||
args := []string{}
|
||||
|
||||
// Connection parameters
|
||||
if e.config.Host != "" && e.config.Host != "localhost" {
|
||||
// Connection parameters - socket takes priority over host
|
||||
if e.config.Socket != "" {
|
||||
args = append(args, "-S", e.config.Socket)
|
||||
} else if e.config.Host != "" && e.config.Host != "localhost" {
|
||||
args = append(args, "-h", e.config.Host)
|
||||
args = append(args, "-P", strconv.Itoa(e.config.Port))
|
||||
}
|
||||
|
||||
@ -14,10 +14,12 @@ import (
|
||||
|
||||
// Exporter provides an HTTP endpoint for Prometheus metrics
|
||||
type Exporter struct {
|
||||
log logger.Logger
|
||||
catalog catalog.Catalog
|
||||
instance string
|
||||
port int
|
||||
log logger.Logger
|
||||
catalog catalog.Catalog
|
||||
instance string
|
||||
port int
|
||||
version string
|
||||
gitCommit string
|
||||
|
||||
mu sync.RWMutex
|
||||
cachedData string
|
||||
@ -36,6 +38,19 @@ func NewExporter(log logger.Logger, cat catalog.Catalog, instance string, port i
|
||||
}
|
||||
}
|
||||
|
||||
// NewExporterWithVersion creates a new Prometheus exporter with version info
|
||||
func NewExporterWithVersion(log logger.Logger, cat catalog.Catalog, instance string, port int, version, gitCommit string) *Exporter {
|
||||
return &Exporter{
|
||||
log: log,
|
||||
catalog: cat,
|
||||
instance: instance,
|
||||
port: port,
|
||||
version: version,
|
||||
gitCommit: gitCommit,
|
||||
refreshTTL: 30 * time.Second,
|
||||
}
|
||||
}
|
||||
|
||||
// Serve starts the HTTP server and blocks until context is cancelled
|
||||
func (e *Exporter) Serve(ctx context.Context) error {
|
||||
mux := http.NewServeMux()
|
||||
@ -158,7 +173,7 @@ func (e *Exporter) refreshLoop(ctx context.Context) {
|
||||
|
||||
// refresh updates the cached metrics
|
||||
func (e *Exporter) refresh() error {
|
||||
writer := NewMetricsWriter(e.log, e.catalog, e.instance)
|
||||
writer := NewMetricsWriterWithVersion(e.log, e.catalog, e.instance, e.version, e.gitCommit)
|
||||
data, err := writer.GenerateMetricsString()
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@ -16,17 +16,32 @@ import (
|
||||
|
||||
// MetricsWriter writes metrics in Prometheus text format
|
||||
type MetricsWriter struct {
|
||||
log logger.Logger
|
||||
catalog catalog.Catalog
|
||||
instance string
|
||||
log logger.Logger
|
||||
catalog catalog.Catalog
|
||||
instance string
|
||||
version string
|
||||
gitCommit string
|
||||
}
|
||||
|
||||
// NewMetricsWriter creates a new MetricsWriter
|
||||
func NewMetricsWriter(log logger.Logger, cat catalog.Catalog, instance string) *MetricsWriter {
|
||||
return &MetricsWriter{
|
||||
log: log,
|
||||
catalog: cat,
|
||||
instance: instance,
|
||||
log: log,
|
||||
catalog: cat,
|
||||
instance: instance,
|
||||
version: "unknown",
|
||||
gitCommit: "unknown",
|
||||
}
|
||||
}
|
||||
|
||||
// NewMetricsWriterWithVersion creates a MetricsWriter with version info for build_info metric
|
||||
func NewMetricsWriterWithVersion(log logger.Logger, cat catalog.Catalog, instance, version, gitCommit string) *MetricsWriter {
|
||||
return &MetricsWriter{
|
||||
log: log,
|
||||
catalog: cat,
|
||||
instance: instance,
|
||||
version: version,
|
||||
gitCommit: gitCommit,
|
||||
}
|
||||
}
|
||||
|
||||
@ -193,6 +208,13 @@ func (m *MetricsWriter) formatMetrics(metrics []BackupMetrics) string {
|
||||
b.WriteString(fmt.Sprintf("# Server: %s\n", m.instance))
|
||||
b.WriteString("\n")
|
||||
|
||||
// dbbackup_build_info - version and build information
|
||||
b.WriteString("# HELP dbbackup_build_info Build information for dbbackup exporter\n")
|
||||
b.WriteString("# TYPE dbbackup_build_info gauge\n")
|
||||
b.WriteString(fmt.Sprintf("dbbackup_build_info{server=%q,version=%q,commit=%q} 1\n",
|
||||
m.instance, m.version, m.gitCommit))
|
||||
b.WriteString("\n")
|
||||
|
||||
// dbbackup_last_success_timestamp
|
||||
b.WriteString("# HELP dbbackup_last_success_timestamp Unix timestamp of last successful backup\n")
|
||||
b.WriteString("# TYPE dbbackup_last_success_timestamp gauge\n")
|
||||
|
||||
2
main.go
2
main.go
@ -16,7 +16,7 @@ import (
|
||||
|
||||
// Build information (set by ldflags)
|
||||
var (
|
||||
version = "4.1.0"
|
||||
version = "4.1.3"
|
||||
buildTime = "unknown"
|
||||
gitCommit = "unknown"
|
||||
)
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user