@@ -23,8 +23,12 @@ import (
23
23
24
24
// ReplicateCommand represents a command that continuously replicates SQLite databases.
25
25
type ReplicateCommand struct {
26
- cmd * exec.Cmd // subcommand
27
- execCh chan error // subcommand error channel
26
+ runSignal func (os.Signal ) error // run cancel signaler
27
+ runCh chan error // run error channel
28
+
29
+ once bool // replicate once and exit
30
+ forceSnapshot bool // force snapshot to all replicas
31
+ enforceRetention bool // enforce retention of old snapshots
28
32
29
33
Config Config
30
34
@@ -34,14 +38,17 @@ type ReplicateCommand struct {
34
38
35
39
func NewReplicateCommand () * ReplicateCommand {
36
40
return & ReplicateCommand {
37
- execCh : make (chan error ),
41
+ runCh : make (chan error ),
38
42
}
39
43
}
40
44
41
45
// ParseFlags parses the CLI flags and loads the configuration file.
42
46
func (c * ReplicateCommand ) ParseFlags (ctx context.Context , args []string ) (err error ) {
43
47
fs := flag .NewFlagSet ("litestream-replicate" , flag .ContinueOnError )
44
48
execFlag := fs .String ("exec" , "" , "execute subcommand" )
49
+ onceFlag := fs .Bool ("once" , false , "replicate once and exit" )
50
+ forceSnapshotFlag := fs .Bool ("force-snapshot" , false , "force snapshot when replicating once" )
51
+ enforceRetentionFlag := fs .Bool ("enforce-retention" , false , "enforce retention of old snapshots" )
45
52
configPath , noExpandEnv := registerConfigFlag (fs )
46
53
fs .Usage = c .Usage
47
54
if err := fs .Parse (args ); err != nil {
@@ -79,6 +86,22 @@ func (c *ReplicateCommand) ParseFlags(ctx context.Context, args []string) (err e
79
86
c .Config .Exec = * execFlag
80
87
}
81
88
89
+ // Once is mutually exclusive with exec
90
+ c .once = * onceFlag
91
+ if c .once && c .Config .Exec != "" {
92
+ return fmt .Errorf ("cannot specify -once flag with exec" )
93
+ }
94
+
95
+ c .forceSnapshot = * forceSnapshotFlag
96
+ if ! c .once && c .forceSnapshot {
97
+ return fmt .Errorf ("cannot specify -force-snapshot flag without -once" )
98
+ }
99
+
100
+ c .enforceRetention = * enforceRetentionFlag
101
+ if ! c .once && c .enforceRetention {
102
+ return fmt .Errorf ("cannot specify -enforce-retention flag without -once" )
103
+ }
104
+
82
105
return nil
83
106
}
84
107
@@ -98,6 +121,14 @@ func (c *ReplicateCommand) Run() (err error) {
98
121
return err
99
122
}
100
123
124
+ // Disable monitors if we're running once.
125
+ if c .once {
126
+ db .MonitorInterval = 0
127
+ for _ , r := range db .Replicas {
128
+ r .MonitorEnabled = false
129
+ }
130
+ }
131
+
101
132
// Open database & attach to program.
102
133
if err := db .Open (); err != nil {
103
134
return err
@@ -152,14 +183,65 @@ func (c *ReplicateCommand) Run() (err error) {
152
183
return fmt .Errorf ("cannot parse exec command: %w" , err )
153
184
}
154
185
155
- c . cmd = exec .Command (execArgs [0 ], execArgs [1 :]... )
156
- c . cmd .Env = os .Environ ()
157
- c . cmd .Stdout = os .Stdout
158
- c . cmd .Stderr = os .Stderr
159
- if err := c . cmd .Start (); err != nil {
186
+ cmd : = exec .Command (execArgs [0 ], execArgs [1 :]... )
187
+ cmd .Env = os .Environ ()
188
+ cmd .Stdout = os .Stdout
189
+ cmd .Stderr = os .Stderr
190
+ if err := cmd .Start (); err != nil {
160
191
return fmt .Errorf ("cannot start exec command: %w" , err )
161
192
}
162
- go func () { c .execCh <- c .cmd .Wait () }()
193
+ c .runSignal = cmd .Process .Signal
194
+ go func () { c .runCh <- cmd .Wait () }()
195
+ } else if c .once {
196
+ // Run replication once for each replica with cancel.
197
+ ctx , cancel := context .WithCancel (context .Background ())
198
+ c .runSignal = func (s os.Signal ) error {
199
+ cancel ()
200
+ return nil
201
+ }
202
+
203
+ go func () {
204
+ var err error
205
+
206
+ defer func () {
207
+ cancel ()
208
+ c .runCh <- err
209
+ }()
210
+
211
+ for _ , db := range c .DBs {
212
+ if c .forceSnapshot {
213
+ // Force next index with RESTART checkpoint.
214
+ db .MaxCheckpointPageN = 1
215
+ }
216
+
217
+ if err = db .Sync (ctx ); err != nil {
218
+ return
219
+ }
220
+
221
+ // Prevent checkpointing on Close()
222
+ db .MinCheckpointPageN = 0
223
+ db .MaxCheckpointPageN = 0
224
+ db .TruncatePageN = 0
225
+ db .CheckpointInterval = 0
226
+
227
+ for _ , r := range db .Replicas {
228
+ if c .forceSnapshot {
229
+ _ , err = r .Snapshot (ctx )
230
+ } else {
231
+ err = r .Sync (ctx )
232
+ }
233
+ if err != nil {
234
+ return
235
+ }
236
+
237
+ if c .enforceRetention {
238
+ if err = r .EnforceRetention (ctx ); err != nil {
239
+ return
240
+ }
241
+ }
242
+ }
243
+ }
244
+ }()
163
245
}
164
246
165
247
return nil
@@ -202,6 +284,15 @@ Arguments:
202
284
Executes a subcommand. Litestream will exit when the child
203
285
process exits. Useful for simple process management.
204
286
287
+ -once
288
+ Execute replication once and exit.
289
+
290
+ -force-snapshot
291
+ When replicating once, force taking a snapshot to all replicas.
292
+
293
+ -enforce-retention
294
+ When replicating once, enforce rentention of old snapshots.
295
+
205
296
-no-expand-env
206
297
Disables environment variable expansion in configuration file.
207
298
0 commit comments