litestream 0.11.2-arm64-darwin → 0.13.0-arm64-darwin
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +205 -34
- data/app/controllers/litestream/application_controller.rb +1 -8
- data/app/views/layouts/litestream/_style.html +38 -0
- data/app/views/layouts/litestream/application.html.erb +5 -5
- data/app/views/litestream/processes/show.html.erb +10 -10
- data/lib/litestream/commands.rb +9 -1
- data/lib/litestream/generators/litestream/templates/initializer.rb +1 -1
- data/lib/litestream/upstream.rb +1 -0
- data/lib/litestream/version.rb +1 -1
- data/lib/litestream.rb +88 -52
- data/lib/tasks/litestream_tasks.rake +16 -0
- metadata +3 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3772866d270ae56384f5ec34c1fbb349c0c1f70392c17b21422c1bae710ae60b
|
4
|
+
data.tar.gz: b9e7fd376e348b17f239728fc2ddf85402185c19f5e3b525c6ad1d567dfc5b12
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6d781f0aa3bc09a09dc1dbe7c76866bf6aed35d5e473acf5dcb311f3d6a903d87ff82ace19d8f3b4d20bac39e5bfc8578e155929ed42b9e73afea5d2d8d79d5d
|
7
|
+
data.tar.gz: 63d394cc89396b165f9ce9d56ac59e2c5486f55628117131a6de5b90d3cb12b8547138255d3830ce9d5169506456a7a155d0194950dfe2e03d133a238407cec5
|
data/README.md
CHANGED
@@ -1,5 +1,29 @@
|
|
1
1
|
# litestream-ruby
|
2
2
|
|
3
|
+
<p>
|
4
|
+
<a href="https://rubygems.org/gems/litestream">
|
5
|
+
<img alt="GEM Version" src="https://img.shields.io/gem/v/litestream?color=168AFE&include_prereleases&logo=ruby&logoColor=FE1616">
|
6
|
+
</a>
|
7
|
+
<a href="https://rubygems.org/gems/litestream">
|
8
|
+
<img alt="GEM Downloads" src="https://img.shields.io/gem/dt/litestream?color=168AFE&logo=ruby&logoColor=FE1616">
|
9
|
+
</a>
|
10
|
+
<a href="https://github.com/testdouble/standard">
|
11
|
+
<img alt="Ruby Style" src="https://img.shields.io/badge/style-standard-168AFE?logo=ruby&logoColor=FE1616" />
|
12
|
+
</a>
|
13
|
+
<a href="https://github.com/fractaledmind/litestream-ruby/actions/workflows/main.yml">
|
14
|
+
<img alt="Tests" src="https://github.com/fractaledmind/litestream-ruby/actions/workflows/main.yml/badge.svg" />
|
15
|
+
</a>
|
16
|
+
<a href="https://github.com/sponsors/fractaledmind">
|
17
|
+
<img alt="Sponsors" src="https://img.shields.io/github/sponsors/fractaledmind?color=eb4aaa&logo=GitHub%20Sponsors" />
|
18
|
+
</a>
|
19
|
+
<a href="https://ruby.social/@fractaledmind">
|
20
|
+
<img alt="Ruby.Social Follow" src="https://img.shields.io/mastodon/follow/109291299520066427?domain=https%3A%2F%2Fruby.social&label=%40fractaledmind&style=social">
|
21
|
+
</a>
|
22
|
+
<a href="https://twitter.com/fractaledmind">
|
23
|
+
<img alt="Twitter Follow" src="https://img.shields.io/twitter/url?label=%40fractaledmind&style=social&url=https%3A%2F%2Ftwitter.com%2Ffractaledmind">
|
24
|
+
</a>
|
25
|
+
</p>
|
26
|
+
|
3
27
|
[Litestream](https://litestream.io/) is a standalone streaming replication tool for SQLite. This gem provides a Ruby interface to Litestream.
|
4
28
|
|
5
29
|
## Installation
|
@@ -28,10 +52,11 @@ This gem wraps the standalone executable version of the [Litestream](https://lit
|
|
28
52
|
|
29
53
|
Supported platforms are:
|
30
54
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
55
|
+
- arm64-darwin (macos-arm64)
|
56
|
+
- x86_64-darwin (macos-x64)
|
57
|
+
- aarch64-linux (linux-aarch64)
|
58
|
+
- arm64-linux (linux-arm64)
|
59
|
+
- x86_64-linux (linux-x64)
|
35
60
|
|
36
61
|
### Using a local installation of `litestream`
|
37
62
|
|
@@ -39,13 +64,13 @@ If you are not able to use the vendored standalone executables (for example, if
|
|
39
64
|
|
40
65
|
For example, if you've installed `litestream` so that the executable is found at `/usr/local/bin/litestream`, then you should set your environment variable like so:
|
41
66
|
|
42
|
-
```
|
67
|
+
```sh
|
43
68
|
LITESTREAM_INSTALL_DIR=/usr/local/bin
|
44
69
|
```
|
45
70
|
|
46
71
|
This also works with relative paths. If you've installed into your app's directory at `./.bin/litestream`:
|
47
72
|
|
48
|
-
```
|
73
|
+
```sh
|
49
74
|
LITESTREAM_INSTALL_DIR=.bin
|
50
75
|
```
|
51
76
|
|
@@ -68,31 +93,33 @@ dbs:
|
|
68
93
|
secret-access-key: $LITESTREAM_SECRET_ACCESS_KEY
|
69
94
|
```
|
70
95
|
|
71
|
-
|
96
|
+
This is the default for Amazon S3. The full range of possible replica types (e.g. other S3-compatible object storage servers) are covered in Litestream's [replica guides](https://litestream.io/guides/#replica-guides).
|
97
|
+
|
98
|
+
The gem also provides a default initializer file at `config/initializers/litestream.rb` that allows you to configure these three environment variables referenced in the configuration file in Ruby. By providing a Ruby interface to these environment variables, you can use any method of storing secrets that you prefer. For example, the default generated file uses Rails' encrypted credentials to store your secrets:
|
72
99
|
|
73
100
|
```ruby
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
config.
|
78
|
-
config.
|
101
|
+
Rails.application.configure do
|
102
|
+
litestream_credentials = Rails.application.credentials.litestream
|
103
|
+
|
104
|
+
config.litestream.replica_bucket = litestream_credentials&.replica_bucket
|
105
|
+
config.litestream.replica_key_id = litestream_credentials&.replica_key_id
|
106
|
+
config.litestream.replica_access_key = litestream_credentials&.replica_access_key
|
79
107
|
end
|
80
108
|
```
|
81
109
|
|
82
|
-
However, if you need manual control over the Litestream configuration, you can manually edit the `config/litestream.yml` file. The full range of possible configurations are covered in Litestream's [configuration reference](https://litestream.io/reference/config/).
|
110
|
+
However, if you need manual control over the Litestream configuration, you can manually edit the `config/litestream.yml` file. The full range of possible configurations are covered in Litestream's [configuration reference](https://litestream.io/reference/config/). NB: If you configure a longer `sync-interval`, you may need to adjust `replication_sleep` when calling `Litestream.verify!`.
|
83
111
|
|
84
112
|
### Replication
|
85
113
|
|
86
114
|
In order to stream changes to your configured replicas, you need to start the Litestream replication process.
|
87
115
|
|
88
|
-
The simplest way to run the Litestream replication process is use the Puma plugin provided by the gem. This allows you to run the Litestream replication process together with Puma and have Puma monitor and manage it. You just need to add
|
116
|
+
The simplest way to run the Litestream replication process is use the Puma plugin provided by the gem. This allows you to run the Litestream replication process together with Puma and have Puma monitor and manage it. You just need to add the following to your `puma.rb` configuration:
|
89
117
|
|
90
118
|
```ruby
|
91
|
-
|
119
|
+
# Run litestream only in production.
|
120
|
+
plugin :litestream if ENV.fetch("RAILS_ENV", "production") == "production"
|
92
121
|
```
|
93
122
|
|
94
|
-
to your `puma.rb` configuration.
|
95
|
-
|
96
123
|
If you would prefer to run the Litestream replication process separately from Puma, you can use the provided `litestream:replicate` rake task. This rake task will automatically load the configuration file and set the environment variables before starting the Litestream process.
|
97
124
|
|
98
125
|
The simplest way to spin up a Litestream process separately from your Rails application is to use a `Procfile`:
|
@@ -130,20 +157,32 @@ The Litestream `replicate` command supports the following options, which can be
|
|
130
157
|
|
131
158
|
### Restoration
|
132
159
|
|
133
|
-
You can restore any replicated database at any point using the gem's provided `litestream:restore` rake task. This rake task requires that you specify which specific database you want to restore. As with the `litestream:replicate` task, you pass arguments to the rake task via argument forwarding. For example, to restore the production database, you would
|
160
|
+
You can restore any replicated database at any point using the gem's provided `litestream:restore` rake task. This rake task requires that you specify which specific database you want to restore. As with the `litestream:replicate` task, you pass arguments to the rake task via argument forwarding. For example, to restore the production database, you would do the following:
|
134
161
|
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
```
|
162
|
+
> [!NOTE]
|
163
|
+
> During the restoration process, you need to prevent any interaction with ActiveRecord/SQLite, such as from a running `rails server` or `rails console` instance. If there is any interaction, Rails might regenerate the production database and prevent restoration via litestream. If this happens, you might get a "cannot restore, output path already exists" error.
|
164
|
+
|
165
|
+
1. Rename the production (`production.sqlite3`, `production.sqlite3-shm`, and `production.sqlite3-wal`) databases (**recommended**) or alternatively delete. To delete the production databases locally, you can run the following at your own risk:
|
166
|
+
```shell
|
167
|
+
# DANGEROUS OPERATION, consider renaming database files instead
|
168
|
+
bin/rails db:drop DISABLE_DATABASE_ENVIRONMENT_CHECK=1
|
169
|
+
```
|
170
|
+
|
171
|
+
2. Run restore command:
|
172
|
+
```shell
|
173
|
+
bin/rails litestream:restore -- --database=storage/production.sqlite3
|
174
|
+
# or
|
175
|
+
bundle exec rake litestream:restore -- --database=storage/production.sqlite3
|
176
|
+
```
|
177
|
+
|
178
|
+
3. Restart your Rails application or Docker container if applicable.
|
140
179
|
|
141
180
|
You can restore any of the databases specified in your `config/litestream.yml` file. The `--database` argument should be the path to the database file you want to restore and must match the value for the `path` key of one of your configured databases. The `litestream:restore` rake task will automatically load the configuration file and set the environment variables before calling the Litestream executable.
|
142
181
|
|
143
182
|
If you need to pass arguments through the rake task to the underlying `litestream` command, that can be done with additional forwarded arguments:
|
144
183
|
|
145
184
|
```shell
|
146
|
-
bin/rails litestream:
|
185
|
+
bin/rails litestream:restore -- --database=storage/production.sqlite3 --if-db-not-exists
|
147
186
|
```
|
148
187
|
|
149
188
|
You can forward arguments in whatever order you like, you simply need to ensure that the `--database` argument is present. You can also use either a single-dash `-database` or double-dash `--database` argument format. The Litestream `restore` command supports the following options, which can be passed through the rake task:
|
@@ -195,7 +234,7 @@ You can verify the integrity of your backed-up databases using the gem's provide
|
|
195
234
|
Litestream.verify! "storage/production.sqlite3"
|
196
235
|
```
|
197
236
|
|
198
|
-
In order to verify that the backup for that database is both restorable and fresh, the method will add a new row to that database under the `_litestream_verification` table, which it will create if needed. It will then wait
|
237
|
+
In order to verify that the backup for that database is both restorable and fresh, the method will add a new row to that database under the `_litestream_verification` table, which it will create if needed. It will then wait `replication_sleep` seconds (defaults to 10) to give the Litestream utility time to replicate that change to whatever storage providers you have configured. After that, it will download the latest backup from that storage provider and ensure that this verification row is present in the backup. If the verification row is _not_ present, the method will raise a `Litestream::VerificationFailure` exception. This check ensures that the restored database file:
|
199
238
|
|
200
239
|
1. exists,
|
201
240
|
2. can be opened by SQLite, and
|
@@ -203,6 +242,119 @@ In order to verify that the backup for that database is both restorable and fres
|
|
203
242
|
|
204
243
|
After restoring the backup, the `Litestream.verify!` method will delete the restored database file. If you need the restored database file, use the `litestream:restore` rake task or `Litestream::Commands.restore` method instead.
|
205
244
|
|
245
|
+
### Dashboard
|
246
|
+
|
247
|
+
The gem provides a web dashboard for monitoring the status of your Litestream replication. To mount the dashboard in your Rails application, add the following to your `config/routes.rb` file:
|
248
|
+
|
249
|
+
```ruby
|
250
|
+
authenticate :user, -> (user) { user.admin? } do
|
251
|
+
mount Litestream::Engine, at: "/litestream"
|
252
|
+
end
|
253
|
+
```
|
254
|
+
|
255
|
+
> [!NOTE]
|
256
|
+
> Be sure to [secure the dashboard](#authentication) in production.
|
257
|
+
|
258
|
+
#### Authentication
|
259
|
+
|
260
|
+
Litestream Rails does not restrict access out of the box. You must secure the dashboard yourself. However, it does provide basic HTTP authentication that can be used with basic authentication or Devise. All you need to do is setup a username and password.
|
261
|
+
|
262
|
+
There are two ways to setup a username and password. First, you can use the `LITESTREAM_USERNAME` and `LITESTREAM_PASSWORD` environment variables:
|
263
|
+
|
264
|
+
```ruby
|
265
|
+
ENV["LITESTREAM_USERNAME"] = "frodo"
|
266
|
+
ENV["LITESTREAM_PASSWORD"] = "ikeptmysecrets"
|
267
|
+
```
|
268
|
+
|
269
|
+
Second, you can configure the access credentials via the Rails configuration object, under the `litestream` key, in an initializer:
|
270
|
+
|
271
|
+
```ruby
|
272
|
+
# Set authentication credentials for Litestream
|
273
|
+
config.litestream.username = Rails.application.credentials.dig(:litestream, :username)
|
274
|
+
config.litestream.password = Rails.application.credentials.dig(:litestream, :password)
|
275
|
+
```
|
276
|
+
|
277
|
+
Either way, if you have set a username and password, Litestream will use basic HTTP authentication.
|
278
|
+
|
279
|
+
> [!IMPORTANT]
|
280
|
+
> If you have not set a username and password, Litestream will not require any authentication to view the dashboard.
|
281
|
+
|
282
|
+
If you use Devise for authentication in your app, you can also restrict access to the dashboard by using their `authenticate` constraint in your routes file:
|
283
|
+
|
284
|
+
```ruby
|
285
|
+
authenticate :user, -> (user) { user.admin? } do
|
286
|
+
mount Litestream::Engine, at: "/litestream"
|
287
|
+
end
|
288
|
+
```
|
289
|
+
|
290
|
+
### Examples
|
291
|
+
|
292
|
+
There is only one screen in the dashboard.
|
293
|
+
|
294
|
+
- the show view of the Litestream replication process:
|
295
|
+
|
296
|
+

|
297
|
+
|
298
|
+
### Usage with API-only Applications
|
299
|
+
|
300
|
+
If your Rails application is an API-only application (generated with the `rails new --api` command), you will need to add the following middleware to your `config/application.rb` file in order to use the dashboard UI provided by Litestream:
|
301
|
+
|
302
|
+
```ruby
|
303
|
+
# /config/application.rb
|
304
|
+
config.middleware.use ActionDispatch::Cookies
|
305
|
+
config.middleware.use ActionDispatch::Session::CookieStore
|
306
|
+
config.middleware.use ActionDispatch::Flash
|
307
|
+
```
|
308
|
+
|
309
|
+
### Overwriting the views
|
310
|
+
|
311
|
+
You can find the views in [`app/views`](https://github.com/fractaledmind/litestream-ruby/tree/main/app/views).
|
312
|
+
|
313
|
+
```bash
|
314
|
+
app/views/
|
315
|
+
├── layouts
|
316
|
+
│ └── litestream
|
317
|
+
│ ├── _style.html
|
318
|
+
│ └── application.html.erb
|
319
|
+
└── litestream
|
320
|
+
└── processes
|
321
|
+
└── show.html.erb
|
322
|
+
```
|
323
|
+
|
324
|
+
You can always take control of the views by creating your own views and/or partials at these paths in your application. For example, if you wanted to overwrite the application layout, you could create a file at `app/views/layouts/litestream/application.html.erb`. If you wanted to remove the footer and the automatically disappearing flash messages, as one concrete example, you could define that file as:
|
325
|
+
|
326
|
+
```erb
|
327
|
+
<!DOCTYPE html>
|
328
|
+
<html>
|
329
|
+
<head>
|
330
|
+
<title>Litestream</title>
|
331
|
+
<%= csrf_meta_tags %>
|
332
|
+
<%= csp_meta_tag %>
|
333
|
+
|
334
|
+
<%= render "layouts/litestream/style" %>
|
335
|
+
</head>
|
336
|
+
<body class="h-full flex flex-col">
|
337
|
+
<main class="container mx-auto max-w-4xl mt-4 px-2 grow">
|
338
|
+
<%= content_for?(:content) ? yield(:content) : yield %>
|
339
|
+
</main>
|
340
|
+
|
341
|
+
<div class="fixed top-0 left-0 right-0 text-center py-2">
|
342
|
+
<% if notice.present? %>
|
343
|
+
<p class="py-2 px-3 bg-green-50 text-green-500 font-medium rounded-lg inline-block">
|
344
|
+
<%= notice %>
|
345
|
+
</p>
|
346
|
+
<% end %>
|
347
|
+
|
348
|
+
<% if alert.present? %>
|
349
|
+
<p class="py-2 px-3 bg-red-50 text-red-500 font-medium rounded-lg inline-block">
|
350
|
+
<%= alert %>
|
351
|
+
</p>
|
352
|
+
<% end %>
|
353
|
+
</div>
|
354
|
+
</body>
|
355
|
+
</html>
|
356
|
+
```
|
357
|
+
|
206
358
|
### Introspection
|
207
359
|
|
208
360
|
Litestream offers a handful of commands that allow you to introspect the state of your replication. The gem provides a few rake tasks that wrap these commands for you. For example, you can list the databases that Litestream is configured to replicate:
|
@@ -224,14 +376,14 @@ You can also list the generations of a specific database:
|
|
224
376
|
bin/rails litestream:generations -- --database=storage/production.sqlite3
|
225
377
|
```
|
226
378
|
|
227
|
-
This will list all generations for the specified database, including stats about their lag behind the primary database and the time range they cover
|
379
|
+
This will list all generations for the specified database, including stats about their lag behind the primary database and the time range they cover:
|
228
380
|
|
229
381
|
```
|
230
382
|
name generation lag start end
|
231
383
|
s3 a295b16a796689f3 -156ms 2024-04-17T00:01:19Z 2024-04-17T00:01:19Z
|
232
384
|
```
|
233
385
|
|
234
|
-
|
386
|
+
You can list the snapshots available for a database:
|
235
387
|
|
236
388
|
```shell
|
237
389
|
bin/rails litestream:snapshots -- --database=storage/production.sqlite3
|
@@ -244,6 +396,19 @@ replica generation index size created
|
|
244
396
|
s3 a295b16a796689f3 1 4645465 2024-04-17T00:01:19Z
|
245
397
|
```
|
246
398
|
|
399
|
+
Finally, you can list the wal files available for a database:
|
400
|
+
|
401
|
+
```shell
|
402
|
+
bin/rails litestream:wal -- --database=storage/production.sqlite3
|
403
|
+
```
|
404
|
+
|
405
|
+
This command lists wal files available for that specified database:
|
406
|
+
|
407
|
+
```
|
408
|
+
replica generation index offset size created
|
409
|
+
s3 a295b16a796689f3 1 0 2036 2024-04-17T00:01:19Z
|
410
|
+
```
|
411
|
+
|
247
412
|
### Running commands from Ruby
|
248
413
|
|
249
414
|
In addition to the provided rake tasks, you can also run Litestream commands directly from Ruby. The gem provides a `Litestream::Commands` module that wraps the Litestream CLI commands. This is particularly useful for the introspection commands, as you can use the output in your Ruby code.
|
@@ -269,18 +434,18 @@ Litestream::Commands.snapshots('storage/production.sqlite3')
|
|
269
434
|
# => [{"replica"=>"s3", "generation"=>"5f4341bc3d22d615", "index"=>"0", "size"=>"4645465", "created"=>"2024-04-17T19:48:09Z"}]
|
270
435
|
```
|
271
436
|
|
272
|
-
|
437
|
+
The `Litestream::Commands.wal` method returns an array of hashes with the "replica", "generation", "index", "offset","size", and "created" keys for each wal:
|
273
438
|
|
274
439
|
```ruby
|
275
|
-
Litestream::Commands.
|
276
|
-
# => "
|
440
|
+
Litestream::Commands.wal('storage/production.sqlite3')
|
441
|
+
# => [{"replica"=>"s3", "generation"=>"5f4341bc3d22d615", "index"=>"0", "offset"=>"0", "size"=>"2036", "created"=>"2024-04-17T19:48:09Z"}]
|
277
442
|
```
|
278
443
|
|
279
|
-
|
444
|
+
You can also restore a database programmatically using the `Litestream::Commands.restore` method, which returns the path to the restored database:
|
280
445
|
|
281
446
|
```ruby
|
282
|
-
Litestream::Commands.
|
283
|
-
# =>
|
447
|
+
Litestream::Commands.restore('storage/production.sqlite3')
|
448
|
+
# => "storage/production-20240418090048.sqlite3"
|
284
449
|
```
|
285
450
|
|
286
451
|
You _can_ start the replication process using the `Litestream::Commands.replicate` method, but this is not recommended. The replication process should be managed by Litestream itself, and you should not need to manually start it.
|
@@ -303,7 +468,13 @@ litestream wal [arguments] DB_PATH|REPLICA_URL
|
|
303
468
|
|
304
469
|
### Using in development
|
305
470
|
|
306
|
-
By default,
|
471
|
+
By default, if you install the gem and configure via `puma.rb` or `Procfile`, Litestream will not start in development.
|
472
|
+
|
473
|
+
If you setup via `puma.rb`, then remove the conditional statement.
|
474
|
+
|
475
|
+
If you setup via `Procfile`, you will need to update your `Procfile.dev` file. If you would like to test that your configuration is properly setup, you can manually add the `litestream:replicate` rake task to your `Procfile.dev` file. Just copy the `litestream` definition from the production `Procfile`.
|
476
|
+
|
477
|
+
In order to have a replication bucket for Litestream to point to, you can use a Docker instance of [MinIO](https://min.io/). MinIO is an S3-compatible object storage server that can be run locally. You can run a MinIO server with the following command:
|
307
478
|
|
308
479
|
```sh
|
309
480
|
docker run -p 9000:9000 -p 9001:9001 minio/minio server /data --console-address ":9001"
|
@@ -1,7 +1,6 @@
|
|
1
1
|
module Litestream
|
2
|
-
class ApplicationController <
|
2
|
+
class ApplicationController < Litestream.base_controller_class.constantize
|
3
3
|
protect_from_forgery with: :exception
|
4
|
-
around_action :force_english_locale!
|
5
4
|
|
6
5
|
if Litestream.password
|
7
6
|
http_basic_authenticate_with(
|
@@ -9,11 +8,5 @@ module Litestream
|
|
9
8
|
password: Litestream.password
|
10
9
|
)
|
11
10
|
end
|
12
|
-
|
13
|
-
private
|
14
|
-
|
15
|
-
def force_english_locale!(&action)
|
16
|
-
I18n.with_locale(:en, &action)
|
17
|
-
end
|
18
11
|
end
|
19
12
|
end
|
@@ -801,4 +801,42 @@
|
|
801
801
|
.hover\:underline:hover {
|
802
802
|
text-decoration-line: underline;
|
803
803
|
}
|
804
|
+
@media (prefers-color-scheme: dark) {
|
805
|
+
.dark\:border-gray-800 {
|
806
|
+
--tw-border-opacity: 1;
|
807
|
+
border-color: rgb(31 41 55 / var(--tw-border-opacity));
|
808
|
+
}
|
809
|
+
.dark\:bg-gray-900 {
|
810
|
+
--tw-bg-opacity: 1;
|
811
|
+
background-color: rgb(17 24 39 / var(--tw-bg-opacity));
|
812
|
+
}
|
813
|
+
.dark\:bg-green-900 {
|
814
|
+
--tw-bg-opacity: 1;
|
815
|
+
background-color: rgb(20 83 45 / var(--tw-bg-opacity));
|
816
|
+
}
|
817
|
+
.dark\:bg-red-900 {
|
818
|
+
--tw-bg-opacity: 1;
|
819
|
+
background-color: rgb(127 29 29 / var(--tw-bg-opacity));
|
820
|
+
}
|
821
|
+
.dark\:text-gray-100 {
|
822
|
+
--tw-text-opacity: 1;
|
823
|
+
color: rgb(243 244 246 / var(--tw-text-opacity));
|
824
|
+
}
|
825
|
+
.dark\:text-white {
|
826
|
+
--tw-text-opacity: 1;
|
827
|
+
color: rgb(255 255 255 / var(--tw-text-opacity));
|
828
|
+
}
|
829
|
+
.dark\:even\:bg-gray-800:nth-child(even) {
|
830
|
+
--tw-bg-opacity: 1;
|
831
|
+
background-color: rgb(31 41 55 / var(--tw-bg-opacity));
|
832
|
+
}
|
833
|
+
.dark\:hover\:bg-gray-900:hover {
|
834
|
+
--tw-bg-opacity: 1;
|
835
|
+
background-color: rgb(17 24 39 / var(--tw-bg-opacity));
|
836
|
+
}
|
837
|
+
.dark\:hover\:bg-gray-800:hover {
|
838
|
+
--tw-bg-opacity: 1;
|
839
|
+
background-color: rgb(31 41 55 / var(--tw-bg-opacity));
|
840
|
+
}
|
841
|
+
}
|
804
842
|
</style>
|
@@ -7,22 +7,22 @@
|
|
7
7
|
|
8
8
|
<%= render "layouts/litestream/style" %>
|
9
9
|
</head>
|
10
|
-
<body class="h-full flex flex-col">
|
10
|
+
<body class="h-full flex flex-col dark:bg-gray-900 dark:text-white">
|
11
11
|
<main class="container mx-auto max-w-4xl mt-4 px-2 grow">
|
12
12
|
<%= content_for?(:content) ? yield(:content) : yield %>
|
13
13
|
</main>
|
14
14
|
|
15
|
-
<footer class="container mx-auto mt-24 flex items-center justify-between border-t px-2 py-4 text-base">
|
15
|
+
<footer class="container mx-auto mt-24 flex items-center justify-between border-t dark:border-gray-800 px-2 py-4 text-base">
|
16
16
|
<p>
|
17
17
|
<code><strong>Litestream</strong></code> |
|
18
|
-
Made by <a href="https://twitter.com/fractaledmind" class="text-blue-500 hover:underline decoration-blue-500">@fractaledmind</a> and <a href="https://github.com/fractaledmind/litestream/graphs/contributors" class="text-blue-500 hover:underline decoration-blue-500">friends</a>! Want to help? It's <a href="https://github.com/fractaledmind/litestream" class="text-blue-500 hover:underline decoration-blue-500">open source</a>!
|
18
|
+
Made by <a href="https://twitter.com/fractaledmind" class="text-blue-500 hover:underline decoration-blue-500">@fractaledmind</a> and <a href="https://github.com/fractaledmind/litestream-ruby/graphs/contributors" class="text-blue-500 hover:underline decoration-blue-500">friends</a>! Want to help? It's <a href="https://github.com/fractaledmind/litestream-ruby" class="text-blue-500 hover:underline decoration-blue-500">open source</a>!
|
19
19
|
</p>
|
20
20
|
</footer>
|
21
21
|
|
22
22
|
<div class="fixed top-0 left-0 right-0 text-center py-2">
|
23
23
|
<% if notice.present? %>
|
24
24
|
<p id="notice"
|
25
|
-
class="py-2 px-3 bg-green-50 text-green-500 font-medium rounded-lg inline-block"
|
25
|
+
class="py-2 px-3 bg-green-50 dark:bg-green-900 text-green-500 font-medium rounded-lg inline-block"
|
26
26
|
data-controller="fade">
|
27
27
|
<%= notice.html_safe %>
|
28
28
|
</p>
|
@@ -30,7 +30,7 @@
|
|
30
30
|
|
31
31
|
<% if alert.present? %>
|
32
32
|
<p id="alert"
|
33
|
-
class="py-2 px-3 bg-red-50 text-red-500 font-medium rounded-lg inline-block"
|
33
|
+
class="py-2 px-3 bg-red-50 dark:bg-red-900 text-red-500 font-medium rounded-lg inline-block"
|
34
34
|
data-controller="fade">
|
35
35
|
<%= alert.html_safe %>
|
36
36
|
</p>
|
@@ -40,7 +40,7 @@
|
|
40
40
|
<br>
|
41
41
|
|
42
42
|
<section id="databases" class="">
|
43
|
-
<div class="mb-3 flex items-center justify-between border-b">
|
43
|
+
<div class="mb-3 flex items-center justify-between border-b dark:border-gray-800">
|
44
44
|
<h2 class="text-2xl font-bold">Databases</h2>
|
45
45
|
<p class="text-right">Total: <strong><%= @databases.size %></strong></p>
|
46
46
|
</div>
|
@@ -59,7 +59,7 @@
|
|
59
59
|
<section id="generations" class="ml-6">
|
60
60
|
<% database['generations'].each do |generation| %>
|
61
61
|
<details id="<%= generation['generation'] %>" open="open">
|
62
|
-
<summary class="cursor-pointer rounded p-2 hover:bg-gray-50">
|
62
|
+
<summary class="cursor-pointer rounded p-2 hover:bg-gray-50 dark:hover:bg-gray-800">
|
63
63
|
<code><%= generation['generation'] %></code>
|
64
64
|
(<em><%= generation['lag'] %> lag</em>)
|
65
65
|
</summary>
|
@@ -85,24 +85,24 @@
|
|
85
85
|
<table class="min-w-full divide-y divide-gray-300">
|
86
86
|
<thead>
|
87
87
|
<tr>
|
88
|
-
<th scope="col" class="whitespace-nowrap px-2 py-2 text-left text-sm font-semibold text-gray-900">Created at</th>
|
89
|
-
<th scope="col" class="whitespace-nowrap px-2 py-2 text-right text-sm font-semibold text-gray-900">Index</th>
|
90
|
-
<th scope="col" class="whitespace-nowrap px-2 py-2 text-right text-sm font-semibold text-gray-900">Size</th>
|
88
|
+
<th scope="col" class="whitespace-nowrap px-2 py-2 text-left text-sm font-semibold text-gray-900 dark:text-gray-100">Created at</th>
|
89
|
+
<th scope="col" class="whitespace-nowrap px-2 py-2 text-right text-sm font-semibold text-gray-900 dark:text-gray-100">Index</th>
|
90
|
+
<th scope="col" class="whitespace-nowrap px-2 py-2 text-right text-sm font-semibold text-gray-900 dark:text-gray-100">Size</th>
|
91
91
|
</tr>
|
92
92
|
</thead>
|
93
93
|
|
94
|
-
<tbody class="bg-white">
|
94
|
+
<tbody class="bg-white dark:bg-gray-900">
|
95
95
|
<% generation['snapshots'].each do |snapshot| %>
|
96
|
-
<tr class="align-top even:bg-gray-50">
|
97
|
-
<td scope="col" class="whitespace-nowrap px-2 py-2 text-sm text-gray-900">
|
96
|
+
<tr class="align-top even:bg-gray-50 dark:even:bg-gray-800">
|
97
|
+
<td scope="col" class="whitespace-nowrap px-2 py-2 text-sm text-gray-900 dark:text-gray-100">
|
98
98
|
<abbr title="<%= snapshot['created'] %>" class="underline decoration-dashed decoration-gray-500 cursor-help">
|
99
99
|
<time datetime="<%= snapshot['created'] %>"><%= DateTime.parse(snapshot['created']).to_formatted_s(:db) %></time>
|
100
100
|
</abbr>
|
101
101
|
</td>
|
102
|
-
<td scope="col" class="whitespace-nowrap px-2 py-2 text-sm text-gray-900 text-right">
|
102
|
+
<td scope="col" class="whitespace-nowrap px-2 py-2 text-sm text-gray-900 dark:text-gray-100 text-right">
|
103
103
|
<%= snapshot['index'] %>
|
104
104
|
</td>
|
105
|
-
<td scope="col" class="whitespace-nowrap px-2 py-2 text-sm text-gray-900 text-right">
|
105
|
+
<td scope="col" class="whitespace-nowrap px-2 py-2 text-sm text-gray-900 dark:text-gray-100 text-right">
|
106
106
|
<%= number_to_human_size snapshot['size'] %>
|
107
107
|
</td>
|
108
108
|
</tr>
|
data/lib/litestream/commands.rb
CHANGED
@@ -104,6 +104,12 @@ module Litestream
|
|
104
104
|
execute("snapshots", argv, database, async: async, tabled_output: true)
|
105
105
|
end
|
106
106
|
|
107
|
+
def wal(database, async: false, **argv)
|
108
|
+
raise DatabaseRequiredException, "database argument is required for wal command, e.g. litestream:wal -- --database=path/to/database.sqlite" if database.nil?
|
109
|
+
|
110
|
+
execute("wal", argv, database, async: async, tabled_output: true)
|
111
|
+
end
|
112
|
+
|
107
113
|
private
|
108
114
|
|
109
115
|
def execute(command, argv = {}, database = nil, async: false, tabled_output: false)
|
@@ -119,11 +125,13 @@ module Litestream
|
|
119
125
|
|
120
126
|
def prepare(command, argv = {}, database = nil)
|
121
127
|
ENV["LITESTREAM_REPLICA_BUCKET"] ||= Litestream.replica_bucket
|
128
|
+
ENV["LITESTREAM_REPLICA_REGION"] ||= Litestream.replica_region
|
129
|
+
ENV["LITESTREAM_REPLICA_ENDPOINT"] ||= Litestream.replica_endpoint
|
122
130
|
ENV["LITESTREAM_ACCESS_KEY_ID"] ||= Litestream.replica_key_id
|
123
131
|
ENV["LITESTREAM_SECRET_ACCESS_KEY"] ||= Litestream.replica_access_key
|
124
132
|
|
125
133
|
args = {
|
126
|
-
"--config" =>
|
134
|
+
"--config" => Litestream.config_path.to_s
|
127
135
|
}.merge(argv.stringify_keys).to_a.flatten.compact
|
128
136
|
cmd = [executable, command, *args, database].compact
|
129
137
|
puts cmd.inspect if ENV["DEBUG"]
|
@@ -2,7 +2,7 @@
|
|
2
2
|
# All configuration options will be available as environment variables, e.g.
|
3
3
|
# config.replica_bucket becomes LITESTREAM_REPLICA_BUCKET
|
4
4
|
# This allows you to configure Litestream using Rails encrypted credentials,
|
5
|
-
# or some other mechanism where the values are only
|
5
|
+
# or some other mechanism where the values are only available at runtime.
|
6
6
|
|
7
7
|
Rails.application.configure do
|
8
8
|
# An example of using Rails encrypted credentials to configure Litestream.
|
data/lib/litestream/upstream.rb
CHANGED
@@ -4,6 +4,7 @@ module Litestream
|
|
4
4
|
|
5
5
|
# rubygems platform name => upstream release filename
|
6
6
|
NATIVE_PLATFORMS = {
|
7
|
+
"aarch64-linux" => "litestream-#{VERSION}-linux-arm64.tar.gz",
|
7
8
|
"arm64-darwin" => "litestream-#{VERSION}-darwin-arm64.zip",
|
8
9
|
"arm64-linux" => "litestream-#{VERSION}-linux-arm64.tar.gz",
|
9
10
|
"x86_64-darwin" => "litestream-#{VERSION}-darwin-amd64.zip",
|
data/lib/litestream/version.rb
CHANGED
data/lib/litestream.rb
CHANGED
@@ -19,7 +19,7 @@ module Litestream
|
|
19
19
|
|
20
20
|
def self.configure
|
21
21
|
deprecator.warn(
|
22
|
-
|
22
|
+
"Configuring Litestream via Litestream.configure is deprecated. Use Rails.application.configure { config.litestream.* = ... } instead.",
|
23
23
|
caller
|
24
24
|
)
|
25
25
|
self.configuration ||= Configuration.new
|
@@ -33,16 +33,18 @@ module Litestream
|
|
33
33
|
end
|
34
34
|
end
|
35
35
|
|
36
|
-
mattr_writer :username, :password, :queue, :replica_bucket, :replica_key_id, :replica_access_key
|
36
|
+
mattr_writer :username, :password, :queue, :replica_bucket, :replica_region, :replica_endpoint, :replica_key_id, :replica_access_key, :systemctl_command,
|
37
|
+
:config_path
|
38
|
+
mattr_accessor :base_controller_class, default: "::ApplicationController"
|
37
39
|
|
38
40
|
class << self
|
39
|
-
def verify!(database_path)
|
41
|
+
def verify!(database_path, replication_sleep: 10)
|
40
42
|
database = SQLite3::Database.new(database_path)
|
41
43
|
database.execute("CREATE TABLE IF NOT EXISTS _litestream_verification (id INTEGER PRIMARY KEY, uuid BLOB)")
|
42
44
|
sentinel = SecureRandom.uuid
|
43
45
|
database.execute("INSERT INTO _litestream_verification (uuid) VALUES (?)", [sentinel])
|
44
46
|
# give the Litestream replication process time to replicate the sentinel value
|
45
|
-
sleep
|
47
|
+
sleep replication_sleep
|
46
48
|
|
47
49
|
backup_path = "tmp/#{Time.now.utc.strftime("%Y%m%d%H%M%S")}_#{sentinel}.sqlite3"
|
48
50
|
Litestream::Commands.restore(database_path, **{"-o" => backup_path})
|
@@ -77,6 +79,14 @@ module Litestream
|
|
77
79
|
@@replica_bucket || configuration.replica_bucket
|
78
80
|
end
|
79
81
|
|
82
|
+
def replica_region
|
83
|
+
@@replica_region
|
84
|
+
end
|
85
|
+
|
86
|
+
def replica_endpoint
|
87
|
+
@@replica_endpoint
|
88
|
+
end
|
89
|
+
|
80
90
|
def replica_key_id
|
81
91
|
@@replica_key_id || configuration.replica_key_id
|
82
92
|
end
|
@@ -85,55 +95,16 @@ module Litestream
|
|
85
95
|
@@replica_access_key || configuration.replica_access_key
|
86
96
|
end
|
87
97
|
|
98
|
+
def systemctl_command
|
99
|
+
@@systemctl_command || "systemctl status litestream"
|
100
|
+
end
|
101
|
+
|
102
|
+
def config_path
|
103
|
+
@@config_path || Rails.root.join("config", "litestream.yml")
|
104
|
+
end
|
105
|
+
|
88
106
|
def replicate_process
|
89
|
-
|
90
|
-
if !`which systemctl`.empty?
|
91
|
-
systemctl_status = `systemctl status litestream`.chomp
|
92
|
-
# ["● litestream.service - Litestream",
|
93
|
-
# " Loaded: loaded (/lib/systemd/system/litestream.service; enabled; vendor preset: enabled)",
|
94
|
-
# " Active: active (running) since Tue 2023-07-25 13:49:43 UTC; 8 months 24 days ago",
|
95
|
-
# " Main PID: 1179656 (litestream)",
|
96
|
-
# " Tasks: 9 (limit: 1115)",
|
97
|
-
# " Memory: 22.9M",
|
98
|
-
# " CPU: 10h 49.843s",
|
99
|
-
# " CGroup: /system.slice/litestream.service",
|
100
|
-
# " └─1179656 /usr/bin/litestream replicate",
|
101
|
-
# "",
|
102
|
-
# "Warning: some journal files were not opened due to insufficient permissions."]
|
103
|
-
systemctl_status.split("\n").each do |line|
|
104
|
-
line.strip!
|
105
|
-
if line.start_with?("Main PID:")
|
106
|
-
_key, value = line.split(":")
|
107
|
-
pid, _name = value.strip.split(" ")
|
108
|
-
info[:pid] = pid
|
109
|
-
elsif line.start_with?("Active:")
|
110
|
-
value, _ago = line.split(";")
|
111
|
-
status, timestamp = value.split(" since ")
|
112
|
-
info[:started] = DateTime.strptime(timestamp.strip, "%a %Y-%m-%d %H:%M:%S %Z")
|
113
|
-
status_match = status.match(%r{\((?<status>.*)\)})
|
114
|
-
info[:status] = status_match ? status_match[:status] : nil
|
115
|
-
end
|
116
|
-
end
|
117
|
-
else
|
118
|
-
litestream_replicate_ps = `ps -a | grep litestream | grep replicate`.chomp
|
119
|
-
litestream_replicate_ps.split("\n").each do |line|
|
120
|
-
next unless line.include?("litestream replicate")
|
121
|
-
pid, * = line.split(" ")
|
122
|
-
info[:pid] = pid
|
123
|
-
state, _, lstart = `ps -o "state,lstart" #{pid}`.chomp.split("\n").last.partition(/\s+/)
|
124
|
-
|
125
|
-
info[:status] = case state[0]
|
126
|
-
when "I" then "idle"
|
127
|
-
when "R" then "running"
|
128
|
-
when "S" then "sleeping"
|
129
|
-
when "T" then "stopped"
|
130
|
-
when "U" then "uninterruptible"
|
131
|
-
when "Z" then "zombie"
|
132
|
-
end
|
133
|
-
info[:started] = DateTime.strptime(lstart.strip, "%a %b %d %H:%M:%S %Y")
|
134
|
-
end
|
135
|
-
end
|
136
|
-
info
|
107
|
+
systemctl_info || process_info || {}
|
137
108
|
end
|
138
109
|
|
139
110
|
def databases
|
@@ -153,6 +124,71 @@ module Litestream
|
|
153
124
|
end
|
154
125
|
end
|
155
126
|
end
|
127
|
+
|
128
|
+
private
|
129
|
+
|
130
|
+
def systemctl_info
|
131
|
+
return if `which systemctl`.empty?
|
132
|
+
|
133
|
+
systemctl_output = `#{Litestream.systemctl_command}`
|
134
|
+
systemctl_exit_code = $?.exitstatus
|
135
|
+
return unless systemctl_exit_code.zero?
|
136
|
+
|
137
|
+
# ["● litestream.service - Litestream",
|
138
|
+
# " Loaded: loaded (/lib/systemd/system/litestream.service; enabled; vendor preset: enabled)",
|
139
|
+
# " Active: active (running) since Tue 2023-07-25 13:49:43 UTC; 8 months 24 days ago",
|
140
|
+
# " Main PID: 1179656 (litestream)",
|
141
|
+
# " Tasks: 9 (limit: 1115)",
|
142
|
+
# " Memory: 22.9M",
|
143
|
+
# " CPU: 10h 49.843s",
|
144
|
+
# " CGroup: /system.slice/litestream.service",
|
145
|
+
# " └─1179656 /usr/bin/litestream replicate",
|
146
|
+
# "",
|
147
|
+
# "Warning: some journal files were not opened due to insufficient permissions."]
|
148
|
+
|
149
|
+
info = {}
|
150
|
+
systemctl_output.chomp.split("\n").each do |line|
|
151
|
+
line.strip!
|
152
|
+
if line.start_with?("Main PID:")
|
153
|
+
_key, value = line.split(":")
|
154
|
+
pid, _name = value.strip.split(" ")
|
155
|
+
info[:pid] = pid
|
156
|
+
elsif line.start_with?("Active:")
|
157
|
+
value, _ago = line.split(";")
|
158
|
+
status, timestamp = value.split(" since ")
|
159
|
+
info[:started] = DateTime.strptime(timestamp.strip, "%a %Y-%m-%d %H:%M:%S %Z")
|
160
|
+
status_match = status.match(%r{\((?<status>.*)\)})
|
161
|
+
info[:status] = status_match ? status_match[:status] : nil
|
162
|
+
end
|
163
|
+
end
|
164
|
+
info
|
165
|
+
end
|
166
|
+
|
167
|
+
def process_info
|
168
|
+
litestream_replicate_ps = `ps -ax | grep litestream | grep replicate`
|
169
|
+
exit_code = $?.exitstatus
|
170
|
+
return unless exit_code.zero?
|
171
|
+
|
172
|
+
info = {}
|
173
|
+
litestream_replicate_ps.chomp.split("\n").each do |line|
|
174
|
+
next unless line.include?("litestream replicate")
|
175
|
+
|
176
|
+
pid, * = line.split(" ")
|
177
|
+
info[:pid] = pid
|
178
|
+
state, _, lstart = `ps -o "state,lstart" #{pid}`.chomp.split("\n").last.partition(/\s+/)
|
179
|
+
|
180
|
+
info[:status] = case state[0]
|
181
|
+
when "I" then "idle"
|
182
|
+
when "R" then "running"
|
183
|
+
when "S" then "sleeping"
|
184
|
+
when "T" then "stopped"
|
185
|
+
when "U" then "uninterruptible"
|
186
|
+
when "Z" then "zombie"
|
187
|
+
end
|
188
|
+
info[:started] = DateTime.strptime(lstart.strip, "%a %b %d %H:%M:%S %Y")
|
189
|
+
end
|
190
|
+
info
|
191
|
+
end
|
156
192
|
end
|
157
193
|
end
|
158
194
|
|
@@ -2,6 +2,8 @@ namespace :litestream do
|
|
2
2
|
desc "Print the ENV variables needed for the Litestream config file"
|
3
3
|
task env: :environment do
|
4
4
|
puts "LITESTREAM_REPLICA_BUCKET=#{Litestream.replica_bucket}"
|
5
|
+
puts "LITESTREAM_REPLICA_REGION=#{Litestream.replica_region}"
|
6
|
+
puts "LITESTREAM_REPLICA_ENDPOINT=#{Litestream.replica_endpoint}"
|
5
7
|
puts "LITESTREAM_ACCESS_KEY_ID=#{Litestream.replica_key_id}"
|
6
8
|
puts "LITESTREAM_SECRET_ACCESS_KEY=#{Litestream.replica_access_key}"
|
7
9
|
|
@@ -75,4 +77,18 @@ namespace :litestream do
|
|
75
77
|
|
76
78
|
Litestream::Commands.snapshots(database, async: true, **options)
|
77
79
|
end
|
80
|
+
|
81
|
+
desc "List all wal files for a database or replica, for example `rake litestream:wal -- -database=storage/production.sqlite3`"
|
82
|
+
task wal: :environment do
|
83
|
+
options = {}
|
84
|
+
if (separator_index = ARGV.index("--"))
|
85
|
+
ARGV.slice(separator_index + 1, ARGV.length)
|
86
|
+
.map { |pair| pair.split("=") }
|
87
|
+
.each { |opt| options[opt[0]] = opt[1] || nil }
|
88
|
+
end
|
89
|
+
database = options.delete("--database") || options.delete("-database")
|
90
|
+
options.symbolize_keys!
|
91
|
+
|
92
|
+
Litestream::Commands.wal(database, async: true, **options)
|
93
|
+
end
|
78
94
|
end
|
metadata
CHANGED
@@ -1,14 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: litestream
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.13.0
|
5
5
|
platform: arm64-darwin
|
6
6
|
authors:
|
7
7
|
- Stephen Margheim
|
8
|
-
autorequire:
|
9
8
|
bindir: exe
|
10
9
|
cert_chain: []
|
11
|
-
date:
|
10
|
+
date: 2025-06-03 00:00:00.000000000 Z
|
12
11
|
dependencies:
|
13
12
|
- !ruby/object:Gem::Dependency
|
14
13
|
name: logfmt
|
@@ -150,7 +149,6 @@ dependencies:
|
|
150
149
|
- - ">="
|
151
150
|
- !ruby/object:Gem::Version
|
152
151
|
version: '0'
|
153
|
-
description:
|
154
152
|
email:
|
155
153
|
- stephen.margheim@gmail.com
|
156
154
|
executables:
|
@@ -190,7 +188,6 @@ metadata:
|
|
190
188
|
rubygems_mfa_required: 'true'
|
191
189
|
source_code_uri: https://github.com/fractaledmind/litestream-ruby
|
192
190
|
changelog_uri: https://github.com/fractaledmind/litestream-ruby/CHANGELOG.md
|
193
|
-
post_install_message:
|
194
191
|
rdoc_options: []
|
195
192
|
require_paths:
|
196
193
|
- lib
|
@@ -205,8 +202,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
205
202
|
- !ruby/object:Gem::Version
|
206
203
|
version: '0'
|
207
204
|
requirements: []
|
208
|
-
rubygems_version: 3.
|
209
|
-
signing_key:
|
205
|
+
rubygems_version: 3.6.3
|
210
206
|
specification_version: 4
|
211
207
|
summary: Integrate Litestream with the RubyGems infrastructure.
|
212
208
|
test_files: []
|