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:
2025-12-11 17:53:28 +01:00
parent 6b66ae5429
commit 914307ac8f
89 changed files with 1516 additions and 1618 deletions

View File

@@ -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++
}

View File

@@ -11,6 +11,7 @@ import (
"path/filepath"
"dbbackup/internal/logger"
"golang.org/x/crypto/pbkdf2"
)

View File

@@ -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")))
}

View File

@@ -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