@@ -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
tracePath := fs .String ("trace" , "" , "trace path" )
46
53
configPath , noExpandEnv := registerConfigFlag (fs )
47
54
fs .Usage = c .Usage
@@ -90,6 +97,22 @@ func (c *ReplicateCommand) ParseFlags(ctx context.Context, args []string) (err e
90
97
litestream .Tracef = log .New (f , "" , log .LstdFlags | log .Lmicroseconds | log .LUTC | log .Lshortfile ).Printf
91
98
}
92
99
100
+ // Once is mutually exclusive with exec
101
+ c .once = * onceFlag
102
+ if c .once && c .Config .Exec != "" {
103
+ return fmt .Errorf ("cannot specify -once flag with exec" )
104
+ }
105
+
106
+ c .forceSnapshot = * forceSnapshotFlag
107
+ if ! c .once && c .forceSnapshot {
108
+ return fmt .Errorf ("cannot specify -force-snapshot flag without -once" )
109
+ }
110
+
111
+ c .enforceRetention = * enforceRetentionFlag
112
+ if ! c .once && c .enforceRetention {
113
+ return fmt .Errorf ("cannot specify -enforce-retention flag without -once" )
114
+ }
115
+
93
116
return nil
94
117
}
95
118
@@ -109,6 +132,14 @@ func (c *ReplicateCommand) Run() (err error) {
109
132
return err
110
133
}
111
134
135
+ // Disable monitors if we're running once.
136
+ if c .once {
137
+ db .MonitorInterval = 0
138
+ for _ , r := range db .Replicas {
139
+ r .MonitorEnabled = false
140
+ }
141
+ }
142
+
112
143
// Open database & attach to program.
113
144
if err := db .Open (); err != nil {
114
145
return err
@@ -162,14 +193,64 @@ func (c *ReplicateCommand) Run() (err error) {
162
193
return fmt .Errorf ("cannot parse exec command: %w" , err )
163
194
}
164
195
165
- c . cmd = exec .Command (execArgs [0 ], execArgs [1 :]... )
166
- c . cmd .Env = os .Environ ()
167
- c . cmd .Stdout = os .Stdout
168
- c . cmd .Stderr = os .Stderr
169
- if err := c . cmd .Start (); err != nil {
196
+ cmd : = exec .Command (execArgs [0 ], execArgs [1 :]... )
197
+ cmd .Env = os .Environ ()
198
+ cmd .Stdout = os .Stdout
199
+ cmd .Stderr = os .Stderr
200
+ if err := cmd .Start (); err != nil {
170
201
return fmt .Errorf ("cannot start exec command: %w" , err )
171
202
}
172
- go func () { c .execCh <- c .cmd .Wait () }()
203
+ c .runSignal = cmd .Process .Signal
204
+ go func () { c .runCh <- cmd .Wait () }()
205
+ } else if c .once {
206
+ // Run replication once for each replica with cancel.
207
+ ctx , cancel := context .WithCancel (context .Background ())
208
+ c .runSignal = func (s os.Signal ) error {
209
+ cancel ()
210
+ return nil
211
+ }
212
+
213
+ go func () {
214
+ var err error
215
+
216
+ defer func () {
217
+ cancel ()
218
+ c .runCh <- err
219
+ }()
220
+
221
+ for _ , db := range c .DBs {
222
+ if c .forceSnapshot {
223
+ // Force next index with RESTART checkpoint.
224
+ db .MaxCheckpointPageN = 1
225
+ }
226
+
227
+ if err = db .Sync (ctx ); err != nil {
228
+ return
229
+ }
230
+
231
+ // Prevent checkpointing on Close()
232
+ db .MinCheckpointPageN = 0
233
+ db .MaxCheckpointPageN = 0
234
+ db .CheckpointInterval = 0
235
+
236
+ for _ , r := range db .Replicas {
237
+ if c .forceSnapshot {
238
+ _ , err = r .Snapshot (ctx )
239
+ } else {
240
+ err = r .Sync (ctx )
241
+ }
242
+ if err != nil {
243
+ return
244
+ }
245
+
246
+ if c .enforceRetention {
247
+ if err = r .EnforceRetention (ctx ); err != nil {
248
+ return
249
+ }
250
+ }
251
+ }
252
+ }
253
+ }()
173
254
}
174
255
175
256
return nil
@@ -212,6 +293,15 @@ Arguments:
212
293
Executes a subcommand. Litestream will exit when the child
213
294
process exits. Useful for simple process management.
214
295
296
+ -once
297
+ Execute replication once and exit.
298
+
299
+ -force-snapshot
300
+ When replicating once, force taking a snapshot to all replicas.
301
+
302
+ -enforce-retention
303
+ When replicating once, enforce rentention of old snapshots.
304
+
215
305
-no-expand-env
216
306
Disables environment variable expansion in configuration file.
217
307
0 commit comments