@@ -190,6 +190,9 @@ The commands are:
190190
191191// Config represents a configuration file for the litestream daemon.
192192type Config struct {
193+ // Global replica settings that serve as defaults for all replicas
194+ ReplicaSettings `yaml:",inline"`
195+
193196 // Bind address for serving metrics.
194197 Addr string `yaml:"addr"`
195198
@@ -211,10 +214,6 @@ type Config struct {
211214 // Litestream will shutdown when subcommand exits.
212215 Exec string `yaml:"exec"`
213216
214- // Global S3 settings
215- AccessKeyID string `yaml:"access-key-id"`
216- SecretAccessKey string `yaml:"secret-access-key"`
217-
218217 // Logging
219218 Logging LoggingConfig `yaml:"logging"`
220219
@@ -239,16 +238,15 @@ type LoggingConfig struct {
239238 Stderr bool `yaml:"stderr"`
240239}
241240
242- // propagateGlobalSettings copies global S3 settings to replica configs.
241+ // propagateGlobalSettings copies global replica settings to individual replica configs.
243242func (c * Config ) propagateGlobalSettings () {
244243 for _ , dbc := range c .DBs {
244+ // Handle both old-style 'replicas' and new-style 'replica'
245+ if dbc .Replica != nil {
246+ dbc .Replica .SetDefaults (& c .ReplicaSettings )
247+ }
245248 for _ , rc := range dbc .Replicas {
246- if rc .AccessKeyID == "" {
247- rc .AccessKeyID = c .AccessKeyID
248- }
249- if rc .SecretAccessKey == "" {
250- rc .SecretAccessKey = c .SecretAccessKey
251- }
249+ rc .SetDefaults (& c .ReplicaSettings )
252250 }
253251 }
254252}
@@ -806,12 +804,9 @@ func ParseByteSize(s string) (int64, error) {
806804 return int64 (bytes ), nil
807805}
808806
809- // ReplicaConfig represents the configuration for a single replica in a database.
810- type ReplicaConfig struct {
811- Type string `yaml:"type"` // "file", "s3"
812- Name string `yaml:"name"` // Deprecated
813- Path string `yaml:"path"`
814- URL string `yaml:"url"`
807+ // ReplicaSettings contains settings shared across replica configurations.
808+ // These can be set globally in Config or per-replica in ReplicaConfig.
809+ type ReplicaSettings struct {
815810 SyncInterval * time.Duration `yaml:"sync-interval"`
816811 ValidationInterval * time.Duration `yaml:"validation-interval"`
817812
@@ -865,6 +860,129 @@ type ReplicaConfig struct {
865860 } `yaml:"age"`
866861}
867862
863+ // SetDefaults merges default settings from src into the current ReplicaSettings.
864+ // Individual settings override defaults when already set.
865+ func (rs * ReplicaSettings ) SetDefaults (src * ReplicaSettings ) {
866+ if src == nil {
867+ return
868+ }
869+
870+ // Timing settings
871+ if rs .SyncInterval == nil && src .SyncInterval != nil {
872+ rs .SyncInterval = src .SyncInterval
873+ }
874+ if rs .ValidationInterval == nil && src .ValidationInterval != nil {
875+ rs .ValidationInterval = src .ValidationInterval
876+ }
877+
878+ // S3 settings
879+ if rs .AccessKeyID == "" {
880+ rs .AccessKeyID = src .AccessKeyID
881+ }
882+ if rs .SecretAccessKey == "" {
883+ rs .SecretAccessKey = src .SecretAccessKey
884+ }
885+ if rs .Region == "" {
886+ rs .Region = src .Region
887+ }
888+ if rs .Bucket == "" {
889+ rs .Bucket = src .Bucket
890+ }
891+ if rs .Endpoint == "" {
892+ rs .Endpoint = src .Endpoint
893+ }
894+ if rs .ForcePathStyle == nil {
895+ rs .ForcePathStyle = src .ForcePathStyle
896+ }
897+ if src .SkipVerify {
898+ rs .SkipVerify = true
899+ }
900+
901+ // ABS settings
902+ if rs .AccountName == "" {
903+ rs .AccountName = src .AccountName
904+ }
905+ if rs .AccountKey == "" {
906+ rs .AccountKey = src .AccountKey
907+ }
908+
909+ // SFTP settings
910+ if rs .Host == "" {
911+ rs .Host = src .Host
912+ }
913+ if rs .User == "" {
914+ rs .User = src .User
915+ }
916+ if rs .Password == "" {
917+ rs .Password = src .Password
918+ }
919+ if rs .KeyPath == "" {
920+ rs .KeyPath = src .KeyPath
921+ }
922+ if rs .ConcurrentWrites == nil {
923+ rs .ConcurrentWrites = src .ConcurrentWrites
924+ }
925+
926+ // NATS settings
927+ if rs .JWT == "" {
928+ rs .JWT = src .JWT
929+ }
930+ if rs .Seed == "" {
931+ rs .Seed = src .Seed
932+ }
933+ if rs .Creds == "" {
934+ rs .Creds = src .Creds
935+ }
936+ if rs .NKey == "" {
937+ rs .NKey = src .NKey
938+ }
939+ if rs .Username == "" {
940+ rs .Username = src .Username
941+ }
942+ if rs .Token == "" {
943+ rs .Token = src .Token
944+ }
945+ if ! rs .TLS {
946+ rs .TLS = src .TLS
947+ }
948+ if len (rs .RootCAs ) == 0 {
949+ rs .RootCAs = src .RootCAs
950+ }
951+ if rs .ClientCert == "" {
952+ rs .ClientCert = src .ClientCert
953+ }
954+ if rs .ClientKey == "" {
955+ rs .ClientKey = src .ClientKey
956+ }
957+ if rs .MaxReconnects == nil {
958+ rs .MaxReconnects = src .MaxReconnects
959+ }
960+ if rs .ReconnectWait == nil {
961+ rs .ReconnectWait = src .ReconnectWait
962+ }
963+ if rs .Timeout == nil {
964+ rs .Timeout = src .Timeout
965+ }
966+
967+ // Age encryption settings
968+ if len (rs .Age .Identities ) == 0 {
969+ rs .Age .Identities = src .Age .Identities
970+ }
971+ if len (rs .Age .Recipients ) == 0 {
972+ rs .Age .Recipients = src .Age .Recipients
973+ }
974+ }
975+
976+ // ReplicaConfig represents the configuration for a single replica in a database.
977+ type ReplicaConfig struct {
978+ ReplicaSettings `yaml:",inline"`
979+
980+ Type string `yaml:"type"` // "file", "s3"
981+ Name string `yaml:"name"` // Deprecated
982+ Path string `yaml:"path"`
983+ URL string `yaml:"url"`
984+ }
985+
868986// NewReplicaFromConfig instantiates a replica for a DB based on a config.
869987func NewReplicaFromConfig (c * ReplicaConfig , db * litestream.DB ) (_ * litestream.Replica , err error ) {
870988 // Ensure user did not specify URL in path.
0 commit comments