Files
William Gill e96c71cf16
Some checks failed
Release / Build & Vet (push) Successful in 1m29s
Release / Test (push) Successful in 1m35s
Release / Lint (push) Successful in 2m7s
Release / GoReleaser Check (push) Successful in 1m25s
Release / Build & Release (push) Failing after 2m27s
style: Run gofmt on all Go source files
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-04 23:12:41 -05:00

140 lines
4.0 KiB
Go

// Package config defines the application configuration schema and provides
// loading from YAML files with environment variable overrides.
package config
import (
"fmt"
"time"
)
// Config is the root configuration struct. It is loaded once at startup in
// main.go and passed to subsystems via constructors.
type Config struct {
Source SourceConfig `mapstructure:"source"`
Backup BackupConfig `mapstructure:"backup"`
Sync SyncConfig `mapstructure:"sync"`
Server ServerConfig `mapstructure:"server"`
TLS TLSConfig `mapstructure:"tls"`
Log LogConfig `mapstructure:"logging"`
}
// SourceConfig identifies the ScoutFS mount to operate on.
type SourceConfig struct {
MountPath string `mapstructure:"mount_path"`
}
// BackupConfig controls backup behavior.
type BackupConfig struct {
OutputDir string `mapstructure:"output_dir"`
ShardMaxBytes int64 `mapstructure:"shard_max_bytes"`
Parallelism int `mapstructure:"parallelism"`
Compression string `mapstructure:"compression"`
CompressionLevel int `mapstructure:"compression_level"`
Retention RetentionConfig `mapstructure:"retention"`
}
// RetentionConfig controls how many backups to keep.
type RetentionConfig struct {
KeepFull int `mapstructure:"keep_full"`
KeepIncremental int `mapstructure:"keep_incremental"`
}
// SyncConfig controls metadata sync client behavior.
type SyncConfig struct {
Remote RemoteConfig `mapstructure:"remote"`
BatchSize int `mapstructure:"batch_size"`
Interval time.Duration `mapstructure:"interval"`
StateDir string `mapstructure:"state_dir"`
}
// RemoteConfig identifies the remote sync server.
type RemoteConfig struct {
Address string `mapstructure:"address"`
}
// ServerConfig controls the gRPC server for metadata sync.
type ServerConfig struct {
ListenAddress string `mapstructure:"listen_address"`
TargetMount string `mapstructure:"target_mount"`
}
// TLSConfig controls mTLS for gRPC connections.
type TLSConfig struct {
CACert string `mapstructure:"ca_cert"`
Cert string `mapstructure:"cert"`
Key string `mapstructure:"key"`
Mutual bool `mapstructure:"mutual"`
}
// LogConfig controls structured logging output.
type LogConfig struct {
Level string `mapstructure:"level"`
Format string `mapstructure:"format"`
Output string `mapstructure:"output"`
}
// Defaults returns a Config with sensible default values.
func Defaults() *Config {
return &Config{
Backup: BackupConfig{
ShardMaxBytes: 1 << 30, // 1 GiB
Parallelism: 8,
Compression: "zstd",
CompressionLevel: 3,
Retention: RetentionConfig{
KeepFull: 4,
KeepIncremental: 30,
},
},
Sync: SyncConfig{
BatchSize: 500,
Interval: 5 * time.Minute,
},
Server: ServerConfig{
ListenAddress: ":9443",
},
TLS: TLSConfig{
Mutual: true,
},
Log: LogConfig{
Level: "info",
Format: "json",
Output: "stderr",
},
}
}
// Validate checks that the config contains valid values for the fields that
// are populated. It does not require all fields to be set since different
// commands use different subsets.
func (c *Config) Validate() error {
if c.Backup.Parallelism < 1 {
return fmt.Errorf("backup.parallelism must be >= 1, got %d", c.Backup.Parallelism)
}
if c.Backup.ShardMaxBytes < 1024*1024 {
return fmt.Errorf("backup.shard_max_bytes must be >= 1 MiB, got %d", c.Backup.ShardMaxBytes)
}
if c.Sync.BatchSize < 1 {
return fmt.Errorf("sync.batch_size must be >= 1, got %d", c.Sync.BatchSize)
}
switch c.Backup.Compression {
case "zstd", "none", "":
// valid
default:
return fmt.Errorf("backup.compression must be \"zstd\" or \"none\", got %q", c.Backup.Compression)
}
switch c.Log.Level {
case "debug", "info", "warn", "error", "":
// valid
default:
return fmt.Errorf("logging.level must be debug|info|warn|error, got %q", c.Log.Level)
}
switch c.Log.Format {
case "json", "text", "":
// valid
default:
return fmt.Errorf("logging.format must be json|text, got %q", c.Log.Format)
}
return nil
}