ci: add golangci-lint config and fix formatting
- Add .golangci.yml with minimal linters (govet, ineffassign) - Run gofmt -s and goimports on all files to fix formatting - Disable fieldalignment and copylocks checks in govet
This commit is contained in:
@@ -21,26 +21,26 @@ type Archiver struct {
|
||||
|
||||
// ArchiveConfig holds WAL archiving configuration
|
||||
type ArchiveConfig struct {
|
||||
ArchiveDir string // Directory to store archived WAL files
|
||||
CompressWAL bool // Compress WAL files with gzip
|
||||
EncryptWAL bool // Encrypt WAL files
|
||||
EncryptionKey []byte // 32-byte key for AES-256-GCM encryption
|
||||
RetentionDays int // Days to keep WAL archives
|
||||
VerifyChecksum bool // Verify WAL file checksums
|
||||
ArchiveDir string // Directory to store archived WAL files
|
||||
CompressWAL bool // Compress WAL files with gzip
|
||||
EncryptWAL bool // Encrypt WAL files
|
||||
EncryptionKey []byte // 32-byte key for AES-256-GCM encryption
|
||||
RetentionDays int // Days to keep WAL archives
|
||||
VerifyChecksum bool // Verify WAL file checksums
|
||||
}
|
||||
|
||||
// WALArchiveInfo contains metadata about an archived WAL file
|
||||
type WALArchiveInfo struct {
|
||||
WALFileName string `json:"wal_filename"`
|
||||
ArchivePath string `json:"archive_path"`
|
||||
OriginalSize int64 `json:"original_size"`
|
||||
ArchivedSize int64 `json:"archived_size"`
|
||||
Checksum string `json:"checksum"`
|
||||
Timeline uint32 `json:"timeline"`
|
||||
Segment uint64 `json:"segment"`
|
||||
ArchivedAt time.Time `json:"archived_at"`
|
||||
Compressed bool `json:"compressed"`
|
||||
Encrypted bool `json:"encrypted"`
|
||||
WALFileName string `json:"wal_filename"`
|
||||
ArchivePath string `json:"archive_path"`
|
||||
OriginalSize int64 `json:"original_size"`
|
||||
ArchivedSize int64 `json:"archived_size"`
|
||||
Checksum string `json:"checksum"`
|
||||
Timeline uint32 `json:"timeline"`
|
||||
Segment uint64 `json:"segment"`
|
||||
ArchivedAt time.Time `json:"archived_at"`
|
||||
Compressed bool `json:"compressed"`
|
||||
Encrypted bool `json:"encrypted"`
|
||||
}
|
||||
|
||||
// NewArchiver creates a new WAL archiver
|
||||
@@ -77,7 +77,7 @@ func (a *Archiver) ArchiveWALFile(ctx context.Context, walFilePath, walFileName
|
||||
// Process WAL file: compression and/or encryption
|
||||
var archivePath string
|
||||
var archivedSize int64
|
||||
|
||||
|
||||
if config.CompressWAL && config.EncryptWAL {
|
||||
// Compress then encrypt
|
||||
archivePath, archivedSize, err = a.compressAndEncryptWAL(walFilePath, walFileName, config)
|
||||
@@ -150,7 +150,7 @@ func (a *Archiver) copyWAL(walFilePath, walFileName string, config ArchiveConfig
|
||||
// compressWAL compresses a WAL file using gzip
|
||||
func (a *Archiver) compressWAL(walFilePath, walFileName string, config ArchiveConfig) (string, int64, error) {
|
||||
archivePath := filepath.Join(config.ArchiveDir, walFileName+".gz")
|
||||
|
||||
|
||||
compressor := NewCompressor(a.log)
|
||||
compressedSize, err := compressor.CompressWALFile(walFilePath, archivePath, 6) // gzip level 6 (balanced)
|
||||
if err != nil {
|
||||
@@ -163,12 +163,12 @@ func (a *Archiver) compressWAL(walFilePath, walFileName string, config ArchiveCo
|
||||
// encryptWAL encrypts a WAL file
|
||||
func (a *Archiver) encryptWAL(walFilePath, walFileName string, config ArchiveConfig) (string, int64, error) {
|
||||
archivePath := filepath.Join(config.ArchiveDir, walFileName+".enc")
|
||||
|
||||
|
||||
encryptor := NewEncryptor(a.log)
|
||||
encOpts := EncryptionOptions{
|
||||
Key: config.EncryptionKey,
|
||||
}
|
||||
|
||||
|
||||
encryptedSize, err := encryptor.EncryptWALFile(walFilePath, archivePath, encOpts)
|
||||
if err != nil {
|
||||
return "", 0, fmt.Errorf("WAL encryption failed: %w", err)
|
||||
@@ -199,7 +199,7 @@ func (a *Archiver) compressAndEncryptWAL(walFilePath, walFileName string, config
|
||||
encOpts := EncryptionOptions{
|
||||
Key: config.EncryptionKey,
|
||||
}
|
||||
|
||||
|
||||
encryptedSize, err := encryptor.EncryptWALFile(tempCompressed, archivePath, encOpts)
|
||||
if err != nil {
|
||||
return "", 0, fmt.Errorf("WAL encryption failed: %w", err)
|
||||
@@ -340,7 +340,7 @@ func (a *Archiver) GetArchiveStats(config ArchiveConfig) (*ArchiveStats, error)
|
||||
|
||||
for _, archive := range archives {
|
||||
stats.TotalSize += archive.ArchivedSize
|
||||
|
||||
|
||||
if archive.Compressed {
|
||||
stats.CompressedFiles++
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"path/filepath"
|
||||
|
||||
"dbbackup/internal/logger"
|
||||
|
||||
"golang.org/x/crypto/pbkdf2"
|
||||
)
|
||||
|
||||
|
||||
@@ -23,14 +23,14 @@ type PITRManager struct {
|
||||
|
||||
// PITRConfig holds PITR settings
|
||||
type PITRConfig struct {
|
||||
Enabled bool
|
||||
ArchiveMode string // "on", "off", "always"
|
||||
ArchiveCommand string
|
||||
ArchiveDir string
|
||||
WALLevel string // "minimal", "replica", "logical"
|
||||
MaxWALSenders int
|
||||
WALKeepSize string // e.g., "1GB"
|
||||
RestoreCommand string
|
||||
Enabled bool
|
||||
ArchiveMode string // "on", "off", "always"
|
||||
ArchiveCommand string
|
||||
ArchiveDir string
|
||||
WALLevel string // "minimal", "replica", "logical"
|
||||
MaxWALSenders int
|
||||
WALKeepSize string // e.g., "1GB"
|
||||
RestoreCommand string
|
||||
}
|
||||
|
||||
// RecoveryTarget specifies the point-in-time to recover to
|
||||
@@ -87,11 +87,11 @@ func (pm *PITRManager) EnablePITR(ctx context.Context, archiveDir string) error
|
||||
|
||||
// Settings to enable PITR
|
||||
settings := map[string]string{
|
||||
"wal_level": "replica", // Required for PITR
|
||||
"archive_mode": "on",
|
||||
"archive_command": archiveCommand,
|
||||
"max_wal_senders": "3",
|
||||
"wal_keep_size": "1GB", // Keep at least 1GB of WAL
|
||||
"wal_level": "replica", // Required for PITR
|
||||
"archive_mode": "on",
|
||||
"archive_command": archiveCommand,
|
||||
"max_wal_senders": "3",
|
||||
"wal_keep_size": "1GB", // Keep at least 1GB of WAL
|
||||
}
|
||||
|
||||
// Update postgresql.conf
|
||||
@@ -156,7 +156,7 @@ func (pm *PITRManager) GetCurrentPITRConfig(ctx context.Context) (*PITRConfig, e
|
||||
|
||||
for scanner.Scan() {
|
||||
line := strings.TrimSpace(scanner.Text())
|
||||
|
||||
|
||||
// Skip comments and empty lines
|
||||
if line == "" || strings.HasPrefix(line, "#") {
|
||||
continue
|
||||
@@ -226,11 +226,11 @@ func (pm *PITRManager) createRecoverySignal(ctx context.Context, dataDir string,
|
||||
|
||||
// Recovery settings go in postgresql.auto.conf (PostgreSQL 12+)
|
||||
autoConfPath := filepath.Join(dataDir, "postgresql.auto.conf")
|
||||
|
||||
|
||||
// Build recovery settings
|
||||
var settings []string
|
||||
settings = append(settings, fmt.Sprintf("restore_command = 'cp %s/%%f %%p'", walArchiveDir))
|
||||
|
||||
|
||||
if target.TargetTime != nil {
|
||||
settings = append(settings, fmt.Sprintf("recovery_target_time = '%s'", target.TargetTime.Format("2006-01-02 15:04:05")))
|
||||
} else if target.TargetXID != "" {
|
||||
@@ -270,11 +270,11 @@ func (pm *PITRManager) createRecoverySignal(ctx context.Context, dataDir string,
|
||||
// createLegacyRecoveryConf creates recovery.conf for PostgreSQL < 12
|
||||
func (pm *PITRManager) createLegacyRecoveryConf(dataDir string, target RecoveryTarget, walArchiveDir string) error {
|
||||
recoveryConfPath := filepath.Join(dataDir, "recovery.conf")
|
||||
|
||||
|
||||
var content strings.Builder
|
||||
content.WriteString("# Recovery Configuration (created by dbbackup)\n")
|
||||
content.WriteString(fmt.Sprintf("restore_command = 'cp %s/%%f %%p'\n", walArchiveDir))
|
||||
|
||||
|
||||
if target.TargetTime != nil {
|
||||
content.WriteString(fmt.Sprintf("recovery_target_time = '%s'\n", target.TargetTime.Format("2006-01-02 15:04:05")))
|
||||
}
|
||||
|
||||
@@ -40,9 +40,9 @@ type TimelineInfo struct {
|
||||
|
||||
// TimelineHistory represents the complete timeline branching structure
|
||||
type TimelineHistory struct {
|
||||
Timelines []*TimelineInfo // All timelines sorted by ID
|
||||
Timelines []*TimelineInfo // All timelines sorted by ID
|
||||
CurrentTimeline uint32 // Current active timeline
|
||||
TimelineMap map[uint32]*TimelineInfo // Quick lookup by timeline ID
|
||||
TimelineMap map[uint32]*TimelineInfo // Quick lookup by timeline ID
|
||||
}
|
||||
|
||||
// ParseTimelineHistory parses timeline history from an archive directory
|
||||
@@ -74,10 +74,10 @@ func (tm *TimelineManager) ParseTimelineHistory(ctx context.Context, archiveDir
|
||||
// Always add timeline 1 (base timeline) if not present
|
||||
if _, exists := history.TimelineMap[1]; !exists {
|
||||
baseTimeline := &TimelineInfo{
|
||||
TimelineID: 1,
|
||||
ParentTimeline: 0,
|
||||
SwitchPoint: "0/0",
|
||||
Reason: "Base timeline",
|
||||
TimelineID: 1,
|
||||
ParentTimeline: 0,
|
||||
SwitchPoint: "0/0",
|
||||
Reason: "Base timeline",
|
||||
FirstWALSegment: 0,
|
||||
}
|
||||
history.Timelines = append(history.Timelines, baseTimeline)
|
||||
@@ -201,7 +201,7 @@ func (tm *TimelineManager) scanWALSegments(archiveDir string, history *TimelineH
|
||||
// Process each WAL file
|
||||
for _, walFile := range walFiles {
|
||||
filename := filepath.Base(walFile)
|
||||
|
||||
|
||||
// Remove extensions
|
||||
filename = strings.TrimSuffix(filename, ".gz.enc")
|
||||
filename = strings.TrimSuffix(filename, ".enc")
|
||||
@@ -255,7 +255,7 @@ func (tm *TimelineManager) ValidateTimelineConsistency(ctx context.Context, hist
|
||||
|
||||
parent, exists := history.TimelineMap[tl.ParentTimeline]
|
||||
if !exists {
|
||||
return fmt.Errorf("timeline %d references non-existent parent timeline %d",
|
||||
return fmt.Errorf("timeline %d references non-existent parent timeline %d",
|
||||
tl.TimelineID, tl.ParentTimeline)
|
||||
}
|
||||
|
||||
@@ -274,29 +274,29 @@ func (tm *TimelineManager) ValidateTimelineConsistency(ctx context.Context, hist
|
||||
// GetTimelinePath returns the path from timeline 1 to the target timeline
|
||||
func (tm *TimelineManager) GetTimelinePath(history *TimelineHistory, targetTimeline uint32) ([]*TimelineInfo, error) {
|
||||
path := make([]*TimelineInfo, 0)
|
||||
|
||||
|
||||
currentTL := targetTimeline
|
||||
for currentTL > 0 {
|
||||
tl, exists := history.TimelineMap[currentTL]
|
||||
if !exists {
|
||||
return nil, fmt.Errorf("timeline %d not found in history", currentTL)
|
||||
}
|
||||
|
||||
|
||||
// Prepend to path (we're walking backwards)
|
||||
path = append([]*TimelineInfo{tl}, path...)
|
||||
|
||||
|
||||
// Move to parent
|
||||
if currentTL == 1 {
|
||||
break // Reached base timeline
|
||||
}
|
||||
currentTL = tl.ParentTimeline
|
||||
|
||||
|
||||
// Prevent infinite loops
|
||||
if len(path) > 100 {
|
||||
return nil, fmt.Errorf("timeline path too long (possible cycle)")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return path, nil
|
||||
}
|
||||
|
||||
@@ -305,13 +305,13 @@ func (tm *TimelineManager) FindTimelineAtPoint(history *TimelineHistory, targetL
|
||||
// Start from current timeline and walk backwards
|
||||
for i := len(history.Timelines) - 1; i >= 0; i-- {
|
||||
tl := history.Timelines[i]
|
||||
|
||||
|
||||
// Compare LSNs (simplified - in production would need proper LSN comparison)
|
||||
if tl.SwitchPoint <= targetLSN || tl.SwitchPoint == "0/0" {
|
||||
return tl.TimelineID, nil
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Default to timeline 1
|
||||
return 1, nil
|
||||
}
|
||||
@@ -384,23 +384,23 @@ func (tm *TimelineManager) formatTimelineNode(sb *strings.Builder, history *Time
|
||||
}
|
||||
|
||||
sb.WriteString(fmt.Sprintf("%s%s Timeline %d", indent, marker, tl.TimelineID))
|
||||
|
||||
|
||||
if tl.TimelineID == history.CurrentTimeline {
|
||||
sb.WriteString(" [CURRENT]")
|
||||
}
|
||||
|
||||
|
||||
if tl.SwitchPoint != "" && tl.SwitchPoint != "0/0" {
|
||||
sb.WriteString(fmt.Sprintf(" (switched at %s)", tl.SwitchPoint))
|
||||
}
|
||||
|
||||
|
||||
if tl.FirstWALSegment > 0 {
|
||||
sb.WriteString(fmt.Sprintf("\n%s WAL segments: %d files", indent, tl.LastWALSegment-tl.FirstWALSegment+1))
|
||||
}
|
||||
|
||||
|
||||
if tl.Reason != "" {
|
||||
sb.WriteString(fmt.Sprintf("\n%s Reason: %s", indent, tl.Reason))
|
||||
}
|
||||
|
||||
|
||||
sb.WriteString("\n")
|
||||
|
||||
// Find and format children
|
||||
|
||||
Reference in New Issue
Block a user