litestream 0.12.0-x86_64-linux → 0.14.0-x86_64-linux
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 +64 -30
- data/app/controllers/litestream/application_controller.rb +1 -8
- data/app/controllers/litestream/restorations_controller.rb +1 -1
- 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 +53 -26
- data/lib/litestream/generators/litestream/templates/initializer.rb +29 -17
- data/lib/litestream/upstream.rb +1 -0
- data/lib/litestream/version.rb +1 -1
- data/lib/litestream.rb +82 -51
- data/lib/tasks/litestream_tasks.rake +26 -46
- metadata +6 -38
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: f5472149be2f48a05e36c2e2f1c3feb212355100f4e3085f90ed821559d49200
         | 
| 4 | 
            +
              data.tar.gz: 7d745ffdd094a4148b7660f7ec32c531c125279ecb8d78ceef72bdea149f5394
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: a5ef0cfce7f7fd68d235f873882f8651c9141bb5fabf7b9a4d2b8f5144de4f0658e741f2a3c0e7f09ef381b4016f897673c5b2d80064dad58c6d4778647f801c
         | 
| 7 | 
            +
              data.tar.gz: a959c042fd3c62118cad6976ce554c014ecf6259ca62e1949c398f4cd5697d6ebe0aaa4121f7fd108bcfbdfee8facb9a0cd6391f4024e982116f9a5ed52adaf2
         | 
    
        data/README.md
    CHANGED
    
    | @@ -54,6 +54,7 @@ Supported platforms are: | |
| 54 54 |  | 
| 55 55 | 
             
            - arm64-darwin (macos-arm64)
         | 
| 56 56 | 
             
            - x86_64-darwin (macos-x64)
         | 
| 57 | 
            +
            - aarch64-linux (linux-aarch64)
         | 
| 57 58 | 
             
            - arm64-linux (linux-arm64)
         | 
| 58 59 | 
             
            - x86_64-linux (linux-x64)
         | 
| 59 60 |  | 
| @@ -79,22 +80,27 @@ LITESTREAM_INSTALL_DIR=.bin | |
| 79 80 |  | 
| 80 81 | 
             
            You configure the Litestream executable through the [`config/litestream.yml` file](https://litestream.io/reference/config/), which is a standard Litestream configuration file as if Litestream was running in a traditional installation.
         | 
| 81 82 |  | 
| 82 | 
            -
            The gem streamlines the configuration process by providing a default configuration file for you. This configuration file will backup all SQLite databases defined in your `config/database.yml` file to one replication bucket. In order to ensure that no secrets are stored in plain-text in your repository, this configuration file leverages Litestream's support for environment variables.  | 
| 83 | 
            +
            The gem streamlines the configuration process by providing a default configuration file for you. This configuration file will backup all SQLite databases defined in your `config/database.yml` file to one replication bucket. In order to ensure that no secrets are stored in plain-text in your repository, this configuration file leverages Litestream's support for environment variables. Inspect which environment variables are available by running the `bin/rails litestream:env` command.
         | 
| 84 | 
            +
             | 
| 85 | 
            +
            The default configuration file looks like this if you only have one SQLite database:
         | 
| 83 86 |  | 
| 84 87 | 
             
            ```yaml
         | 
| 85 88 | 
             
            dbs:
         | 
| 86 89 | 
             
              - path: storage/production.sqlite3
         | 
| 87 90 | 
             
                replicas:
         | 
| 88 91 | 
             
                  - type: s3
         | 
| 89 | 
            -
                    bucket: $LITESTREAM_REPLICA_BUCKET
         | 
| 90 92 | 
             
                    path: storage/production.sqlite3
         | 
| 93 | 
            +
                    bucket: $LITESTREAM_REPLICA_BUCKET
         | 
| 91 94 | 
             
                    access-key-id: $LITESTREAM_ACCESS_KEY_ID
         | 
| 92 95 | 
             
                    secret-access-key: $LITESTREAM_SECRET_ACCESS_KEY
         | 
| 93 96 | 
             
            ```
         | 
| 94 97 |  | 
| 95 | 
            -
             | 
| 98 | 
            +
            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).
         | 
| 99 | 
            +
             | 
| 100 | 
            +
            The gem also provides a default initializer file at `config/initializers/litestream.rb` that allows you to configure various variables referenced in the configuration file in Ruby. By providing a Ruby interface to these environment variables, you can use your preferred method of storing secrets. For example, the default generated file uses Rails' encrypted credentials to store your secrets.
         | 
| 96 101 |  | 
| 97 102 | 
             
            ```ruby
         | 
| 103 | 
            +
            # config/initializers/litestream.rb
         | 
| 98 104 | 
             
            Rails.application.configure do
         | 
| 99 105 | 
             
              litestream_credentials = Rails.application.credentials.litestream
         | 
| 100 106 |  | 
| @@ -104,20 +110,33 @@ Rails.application.configure do | |
| 104 110 | 
             
            end
         | 
| 105 111 | 
             
            ```
         | 
| 106 112 |  | 
| 107 | 
            -
             | 
| 113 | 
            +
            Outside of configuring Litestream's replication, you may also configure various other aspects of `litestream-ruby` itself. 
         | 
| 114 | 
            +
             | 
| 115 | 
            +
            ```ruby
         | 
| 116 | 
            +
            # config/initializers/litestream.rb
         | 
| 117 | 
            +
            Rails.application.configure do
         | 
| 118 | 
            +
              # ...
         | 
| 119 | 
            +
             | 
| 120 | 
            +
              # Base controller used for Litestream dashboard
         | 
| 121 | 
            +
              config.litestream.base_controller_class = "MyApplicationController"
         | 
| 122 | 
            +
              # Set the location of the Litestream config
         | 
| 123 | 
            +
              config.litestream.config_path = "config/litestream.yml"
         | 
| 124 | 
            +
            end
         | 
| 125 | 
            +
            ```
         | 
| 126 | 
            +
             | 
| 127 | 
            +
            However, if you need manual control over the Litestream configuration, you can edit the `config/litestream.yml` file. The full range of possible configurations are covered in Litestream's [configuration reference](https://litestream.io/reference/config/).  
         | 
| 108 128 |  | 
| 109 129 | 
             
            ### Replication
         | 
| 110 130 |  | 
| 111 131 | 
             
            In order to stream changes to your configured replicas, you need to start the Litestream replication process.
         | 
| 112 132 |  | 
| 113 | 
            -
            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
         | 
| 133 | 
            +
            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:
         | 
| 114 134 |  | 
| 115 135 | 
             
            ```ruby
         | 
| 116 | 
            -
             | 
| 136 | 
            +
            # Run litestream only in production.
         | 
| 137 | 
            +
            plugin :litestream if ENV.fetch("RAILS_ENV", "production") == "production"
         | 
| 117 138 | 
             
            ```
         | 
| 118 139 |  | 
| 119 | 
            -
            to your `puma.rb` configuration.
         | 
| 120 | 
            -
             | 
| 121 140 | 
             
            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.
         | 
| 122 141 |  | 
| 123 142 | 
             
            The simplest way to spin up a Litestream process separately from your Rails application is to use a `Procfile`:
         | 
| @@ -155,20 +174,32 @@ The Litestream `replicate` command supports the following options, which can be | |
| 155 174 |  | 
| 156 175 | 
             
            ### Restoration
         | 
| 157 176 |  | 
| 158 | 
            -
            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  | 
| 177 | 
            +
            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:
         | 
| 159 178 |  | 
| 160 | 
            -
             | 
| 161 | 
            -
             | 
| 162 | 
            -
             | 
| 163 | 
            -
             | 
| 164 | 
            -
            ```
         | 
| 179 | 
            +
            > [!NOTE]  
         | 
| 180 | 
            +
            > 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.
         | 
| 181 | 
            +
             | 
| 182 | 
            +
            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:
         | 
| 183 | 
            +
               ```shell
         | 
| 184 | 
            +
               # DANGEROUS OPERATION, consider renaming database files instead
         | 
| 185 | 
            +
               bin/rails db:drop DISABLE_DATABASE_ENVIRONMENT_CHECK=1
         | 
| 186 | 
            +
               ```
         | 
| 187 | 
            +
             | 
| 188 | 
            +
            2. Run restore command:
         | 
| 189 | 
            +
               ```shell
         | 
| 190 | 
            +
               bin/rails litestream:restore -- --database=storage/production.sqlite3
         | 
| 191 | 
            +
               # or
         | 
| 192 | 
            +
               bundle exec rake litestream:restore -- --database=storage/production.sqlite3
         | 
| 193 | 
            +
               ```
         | 
| 194 | 
            +
             | 
| 195 | 
            +
            3. Restart your Rails application or Docker container if applicable.
         | 
| 165 196 |  | 
| 166 197 | 
             
            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.
         | 
| 167 198 |  | 
| 168 199 | 
             
            If you need to pass arguments through the rake task to the underlying `litestream` command, that can be done with additional forwarded arguments:
         | 
| 169 200 |  | 
| 170 201 | 
             
            ```shell
         | 
| 171 | 
            -
            bin/rails litestream: | 
| 202 | 
            +
            bin/rails litestream:restore -- --database=storage/production.sqlite3 --if-db-not-exists
         | 
| 172 203 | 
             
            ```
         | 
| 173 204 |  | 
| 174 205 | 
             
            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:
         | 
| @@ -218,9 +249,10 @@ You can verify the integrity of your backed-up databases using the gem's provide | |
| 218 249 |  | 
| 219 250 | 
             
            ```ruby
         | 
| 220 251 | 
             
            Litestream.verify! "storage/production.sqlite3"
         | 
| 252 | 
            +
            Litestream.verify!(replication_sleep: 10) "storage/production.sqlite3"
         | 
| 221 253 | 
             
            ```
         | 
| 222 254 |  | 
| 223 | 
            -
            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  | 
| 255 | 
            +
            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:
         | 
| 224 256 |  | 
| 225 257 | 
             
            1. exists,
         | 
| 226 258 | 
             
            2. can be opened by SQLite, and
         | 
| @@ -228,6 +260,9 @@ In order to verify that the backup for that database is both restorable and fres | |
| 228 260 |  | 
| 229 261 | 
             
            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.
         | 
| 230 262 |  | 
| 263 | 
            +
            > [!NOTE]
         | 
| 264 | 
            +
            > If you configure Litestream's [`sync-interval`](https://litestream.io/reference/config/#replica-settings) to be longer than the default `replication_sleep` value of 10 seconds, you will need to adjust `replication_sleep` to a value larger than `sync-interval`; otherwise, `Litestream.verify!` may appear to fail where it actually simply didn't wait long enough for replication. 
         | 
| 265 | 
            +
             | 
| 231 266 | 
             
            ### Dashboard
         | 
| 232 267 |  | 
| 233 268 | 
             
            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:
         | 
| @@ -256,8 +291,8 @@ Second, you can configure the access credentials via the Rails configuration obj | |
| 256 291 |  | 
| 257 292 | 
             
            ```ruby
         | 
| 258 293 | 
             
            # Set authentication credentials for Litestream
         | 
| 259 | 
            -
            config.litestream.username = Rails.application.credentials.litestream | 
| 260 | 
            -
            config.litestream.password = Rails.application.credentials.litestream | 
| 294 | 
            +
            config.litestream.username = Rails.application.credentials.dig(:litestream, :username)
         | 
| 295 | 
            +
            config.litestream.password = Rails.application.credentials.dig(:litestream, :password)
         | 
| 261 296 | 
             
            ```
         | 
| 262 297 |  | 
| 263 298 | 
             
            Either way, if you have set a username and password, Litestream will use basic HTTP authentication.
         | 
| @@ -269,7 +304,7 @@ If you use Devise for authentication in your app, you can also restrict access t | |
| 269 304 |  | 
| 270 305 | 
             
            ```ruby
         | 
| 271 306 | 
             
            authenticate :user, -> (user) { user.admin? } do
         | 
| 272 | 
            -
              mount  | 
| 307 | 
            +
              mount Litestream::Engine, at: "/litestream"
         | 
| 273 308 | 
             
            end
         | 
| 274 309 | 
             
            ```
         | 
| 275 310 |  | 
| @@ -294,7 +329,7 @@ config.middleware.use ActionDispatch::Flash | |
| 294 329 |  | 
| 295 330 | 
             
            ### Overwriting the views
         | 
| 296 331 |  | 
| 297 | 
            -
            You can find the views in [`app/views`](https://github.com/fractaledmind/litestream/tree/main/app/views).
         | 
| 332 | 
            +
            You can find the views in [`app/views`](https://github.com/fractaledmind/litestream-ruby/tree/main/app/views).
         | 
| 298 333 |  | 
| 299 334 | 
             
            ```bash
         | 
| 300 335 | 
             
            app/views/
         | 
| @@ -362,7 +397,7 @@ You can also list the generations of a specific database: | |
| 362 397 | 
             
            bin/rails litestream:generations -- --database=storage/production.sqlite3
         | 
| 363 398 | 
             
            ```
         | 
| 364 399 |  | 
| 365 | 
            -
            This will list all generations for the specified database, including stats about their lag behind the primary database and the time range they cover | 
| 400 | 
            +
            This will list all generations for the specified database, including stats about their lag behind the primary database and the time range they cover:
         | 
| 366 401 |  | 
| 367 402 | 
             
            ```
         | 
| 368 403 | 
             
            name  generation        lag     start                 end
         | 
| @@ -434,13 +469,6 @@ Litestream::Commands.restore('storage/production.sqlite3') | |
| 434 469 | 
             
            # => "storage/production-20240418090048.sqlite3"
         | 
| 435 470 | 
             
            ```
         | 
| 436 471 |  | 
| 437 | 
            -
            Finally, you can verify the integrity of a restored database using the `Litestream::Commands.verify` method, which returns a hash with the "size" and "tables" keys for the original and restored databases:
         | 
| 438 | 
            -
             | 
| 439 | 
            -
            ```ruby
         | 
| 440 | 
            -
            Litestream::Commands.verify('storage/production.sqlite3')
         | 
| 441 | 
            -
            # => {"size"=>{"original"=>21688320, "restored"=>21688320}, "tables"=>{"original"=>9, "restored"=>9}}
         | 
| 442 | 
            -
            ```
         | 
| 443 | 
            -
             | 
| 444 472 | 
             
            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.
         | 
| 445 473 |  | 
| 446 474 | 
             
            ### Running commands from CLI
         | 
| @@ -461,7 +489,13 @@ litestream wal [arguments] DB_PATH|REPLICA_URL | |
| 461 489 |  | 
| 462 490 | 
             
            ### Using in development
         | 
| 463 491 |  | 
| 464 | 
            -
            By default,  | 
| 492 | 
            +
            By default, if you install the gem and configure via `puma.rb` or `Procfile`, Litestream will not start in development.
         | 
| 493 | 
            +
             | 
| 494 | 
            +
            If you setup via `puma.rb`, then remove the conditional statement.
         | 
| 495 | 
            +
             | 
| 496 | 
            +
            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`.
         | 
| 497 | 
            +
             | 
| 498 | 
            +
            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:
         | 
| 465 499 |  | 
| 466 500 | 
             
            ```sh
         | 
| 467 501 | 
             
            docker run -p 9000:9000 -p 9001:9001 minio/minio server /data --console-address ":9001"
         | 
| @@ -493,7 +527,7 @@ time=YYYY-MM-DDTHH:MM:SS level=INFO msg="replicating to" name=s3 type=s3 sync-in | |
| 493 527 |  | 
| 494 528 | 
             
            After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
         | 
| 495 529 |  | 
| 496 | 
            -
            To install this gem onto your local machine, run `bundle exec rake install`.
         | 
| 530 | 
            +
            To install this gem onto your local machine, run `bundle exec rake install`. To download the Litestream binaries run `bundle exec rake download`.
         | 
| 497 531 |  | 
| 498 532 | 
             
            For maintainers, to release a new version, run `bin/release $VERSION`, which will create a git tag for the version, push git commits and tags, and push all of the platform-specific `.gem` files to [rubygems.org](https://rubygems.org).
         | 
| 499 533 |  | 
| @@ -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
         | 
| @@ -9,7 +9,7 @@ module Litestream | |
| 9 9 | 
             
                  now = Time.now.utc.strftime("%Y%m%d%H%M%S")
         | 
| 10 10 | 
             
                  backup = File.join(dir, "#{base}-#{now}#{ext}")
         | 
| 11 11 |  | 
| 12 | 
            -
                  Litestream::Commands.restore(database,  | 
| 12 | 
            +
                  Litestream::Commands.restore(database, **{"-o" => backup})
         | 
| 13 13 |  | 
| 14 14 | 
             
                  redirect_to root_path, notice: "Restored to <code>#{backup}</code>."
         | 
| 15 15 | 
             
                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
    
    | @@ -1,5 +1,4 @@ | |
| 1 1 | 
             
            require_relative "upstream"
         | 
| 2 | 
            -
            require "logfmt"
         | 
| 3 2 |  | 
| 4 3 | 
             
            module Litestream
         | 
| 5 4 | 
             
              module Commands
         | 
| @@ -21,6 +20,24 @@ module Litestream | |
| 21 20 | 
             
                # raised when a litestream command fails
         | 
| 22 21 | 
             
                CommandFailedException = Class.new(StandardError)
         | 
| 23 22 |  | 
| 23 | 
            +
                module Output
         | 
| 24 | 
            +
                  class << self
         | 
| 25 | 
            +
                    def format(data)
         | 
| 26 | 
            +
                      return "" if data.nil? || data.empty?
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                      headers = data.first.keys.map(&:to_s)
         | 
| 29 | 
            +
                      widths = headers.map.with_index { |h, i|
         | 
| 30 | 
            +
                        [h.length, data.map { |r| r[data.first.keys[i]].to_s.length }.max].max
         | 
| 31 | 
            +
                      }
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                      format_str = widths.map { |w| "%-#{w}s" }.join("  ")
         | 
| 34 | 
            +
                      ([headers] + data.map(&:values)).map { |row|
         | 
| 35 | 
            +
                        sprintf(format_str, *row.map(&:to_s))
         | 
| 36 | 
            +
                      }.join("\n")
         | 
| 37 | 
            +
                    end
         | 
| 38 | 
            +
                  end
         | 
| 39 | 
            +
                end
         | 
| 40 | 
            +
             | 
| 24 41 | 
             
                class << self
         | 
| 25 42 | 
             
                  def platform
         | 
| 26 43 | 
             
                    [:cpu, :os].map { |m| Gem::Platform.local.send(m) }.join("-")
         | 
| @@ -77,44 +94,49 @@ module Litestream | |
| 77 94 | 
             
                    exe_file
         | 
| 78 95 | 
             
                  end
         | 
| 79 96 |  | 
| 97 | 
            +
                  # Replicate can be run either as a fork or in the same process, depending on the context.
         | 
| 98 | 
            +
                  # Puma will start replication as a forked process, while running replication from a rake
         | 
| 99 | 
            +
                  # tasks won't.
         | 
| 80 100 | 
             
                  def replicate(async: false, **argv)
         | 
| 81 | 
            -
                     | 
| 101 | 
            +
                    cmd = prepare("replicate", argv)
         | 
| 102 | 
            +
                    run_replicate(cmd, async: async)
         | 
| 103 | 
            +
                  rescue
         | 
| 104 | 
            +
                    raise CommandFailedException, "Failed to execute `#{cmd.join(" ")}`"
         | 
| 82 105 | 
             
                  end
         | 
| 83 106 |  | 
| 84 | 
            -
                  def restore(database,  | 
| 107 | 
            +
                  def restore(database, **argv)
         | 
| 85 108 | 
             
                    raise DatabaseRequiredException, "database argument is required for restore command, e.g. litestream:restore -- --database=path/to/database.sqlite" if database.nil?
         | 
| 86 | 
            -
                    argv.stringify_keys!
         | 
| 87 109 |  | 
| 88 | 
            -
                    execute("restore", argv, database,  | 
| 110 | 
            +
                    execute("restore", argv, database, tabled_output: false)
         | 
| 89 111 | 
             
                  end
         | 
| 90 112 |  | 
| 91 | 
            -
                  def databases( | 
| 92 | 
            -
                    execute("databases", argv | 
| 113 | 
            +
                  def databases(**argv)
         | 
| 114 | 
            +
                    execute("databases", argv)
         | 
| 93 115 | 
             
                  end
         | 
| 94 116 |  | 
| 95 | 
            -
                  def generations(database,  | 
| 117 | 
            +
                  def generations(database, **argv)
         | 
| 96 118 | 
             
                    raise DatabaseRequiredException, "database argument is required for generations command, e.g. litestream:generations -- --database=path/to/database.sqlite" if database.nil?
         | 
| 97 119 |  | 
| 98 | 
            -
                    execute("generations", argv, database | 
| 120 | 
            +
                    execute("generations", argv, database)
         | 
| 99 121 | 
             
                  end
         | 
| 100 122 |  | 
| 101 | 
            -
                  def snapshots(database,  | 
| 123 | 
            +
                  def snapshots(database, **argv)
         | 
| 102 124 | 
             
                    raise DatabaseRequiredException, "database argument is required for snapshots command, e.g. litestream:snapshots -- --database=path/to/database.sqlite" if database.nil?
         | 
| 103 125 |  | 
| 104 | 
            -
                    execute("snapshots", argv, database | 
| 126 | 
            +
                    execute("snapshots", argv, database)
         | 
| 105 127 | 
             
                  end
         | 
| 106 128 |  | 
| 107 | 
            -
                  def wal(database,  | 
| 129 | 
            +
                  def wal(database, **argv)
         | 
| 108 130 | 
             
                    raise DatabaseRequiredException, "database argument is required for wal command, e.g. litestream:wal -- --database=path/to/database.sqlite" if database.nil?
         | 
| 109 131 |  | 
| 110 | 
            -
                    execute("wal", argv, database | 
| 132 | 
            +
                    execute("wal", argv, database)
         | 
| 111 133 | 
             
                  end
         | 
| 112 134 |  | 
| 113 135 | 
             
                  private
         | 
| 114 136 |  | 
| 115 | 
            -
                  def execute(command, argv = {}, database = nil,  | 
| 137 | 
            +
                  def execute(command, argv = {}, database = nil, tabled_output: true)
         | 
| 116 138 | 
             
                    cmd = prepare(command, argv, database)
         | 
| 117 | 
            -
                    results = run(cmd,  | 
| 139 | 
            +
                    results = run(cmd, tabled_output: tabled_output)
         | 
| 118 140 |  | 
| 119 141 | 
             
                    if Array === results && results.one? && results[0]["level"] == "ERROR"
         | 
| 120 142 | 
             
                      raise CommandFailedException, "Failed to execute `#{cmd.join(" ")}`; Reason: #{results[0]["error"]}"
         | 
| @@ -125,11 +147,13 @@ module Litestream | |
| 125 147 |  | 
| 126 148 | 
             
                  def prepare(command, argv = {}, database = nil)
         | 
| 127 149 | 
             
                    ENV["LITESTREAM_REPLICA_BUCKET"] ||= Litestream.replica_bucket
         | 
| 150 | 
            +
                    ENV["LITESTREAM_REPLICA_REGION"] ||= Litestream.replica_region
         | 
| 151 | 
            +
                    ENV["LITESTREAM_REPLICA_ENDPOINT"] ||= Litestream.replica_endpoint
         | 
| 128 152 | 
             
                    ENV["LITESTREAM_ACCESS_KEY_ID"] ||= Litestream.replica_key_id
         | 
| 129 153 | 
             
                    ENV["LITESTREAM_SECRET_ACCESS_KEY"] ||= Litestream.replica_access_key
         | 
| 130 154 |  | 
| 131 155 | 
             
                    args = {
         | 
| 132 | 
            -
                      "--config" =>  | 
| 156 | 
            +
                      "--config" => Litestream.config_path.to_s
         | 
| 133 157 | 
             
                    }.merge(argv.stringify_keys).to_a.flatten.compact
         | 
| 134 158 | 
             
                    cmd = [executable, command, *args, database].compact
         | 
| 135 159 | 
             
                    puts cmd.inspect if ENV["DEBUG"]
         | 
| @@ -137,21 +161,24 @@ module Litestream | |
| 137 161 | 
             
                    cmd
         | 
| 138 162 | 
             
                  end
         | 
| 139 163 |  | 
| 140 | 
            -
                  def run(cmd,  | 
| 164 | 
            +
                  def run(cmd, tabled_output:)
         | 
| 165 | 
            +
                    stdout = `#{cmd.join(" ")}`.chomp
         | 
| 166 | 
            +
                    return stdout unless tabled_output
         | 
| 167 | 
            +
             | 
| 168 | 
            +
                    keys, *rows = stdout.split("\n").map { _1.split(/\s+/) }
         | 
| 169 | 
            +
                    rows.map { keys.zip(_1).to_h }
         | 
| 170 | 
            +
                  end
         | 
| 171 | 
            +
             | 
| 172 | 
            +
                  def run_replicate(cmd, async:)
         | 
| 141 173 | 
             
                    if async
         | 
| 142 | 
            -
                      # To release the resources of the Ruby process, just fork and exit.
         | 
| 143 | 
            -
                      # The forked process executes litestream and replaces itself.
         | 
| 144 174 | 
             
                      exec(*cmd) if fork.nil?
         | 
| 145 175 | 
             
                    else
         | 
| 146 | 
            -
                       | 
| 147 | 
            -
                       | 
| 176 | 
            +
                      # When running in-process, we capture output continuously and write to stdout.
         | 
| 177 | 
            +
                      IO.popen(cmd, err: [:child, :out]) do |io|
         | 
| 178 | 
            +
                        io.each_line { |line| puts line }
         | 
| 179 | 
            +
                      end
         | 
| 148 180 | 
             
                    end
         | 
| 149 181 | 
             
                  end
         | 
| 150 | 
            -
             | 
| 151 | 
            -
                  def text_table_to_hashes(string)
         | 
| 152 | 
            -
                    keys, *rows = string.split("\n").map { _1.split(/\s+/) }
         | 
| 153 | 
            -
                    rows.map { keys.zip(_1).to_h }
         | 
| 154 | 
            -
                  end
         | 
| 155 182 | 
             
                end
         | 
| 156 183 | 
             
              end
         | 
| 157 184 | 
             
            end
         | 
| @@ -2,32 +2,44 @@ | |
| 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 | 
            +
              # Configure Litestream through environment variables. Use Rails encrypted credentials for secrets.
         | 
| 9 9 | 
             
              # litestream_credentials = Rails.application.credentials.litestream
         | 
| 10 10 |  | 
| 11 | 
            -
              # Replica-specific bucket location.
         | 
| 12 | 
            -
              # This will be your bucket's URL without the `https://` prefix.
         | 
| 11 | 
            +
              # Replica-specific bucket location. This will be your bucket's URL without the `https://` prefix.
         | 
| 13 12 | 
             
              # For example, if you used DigitalOcean Spaces, your bucket URL could look like:
         | 
| 13 | 
            +
              #
         | 
| 14 14 | 
             
              #   https://myapp.fra1.digitaloceanspaces.com
         | 
| 15 | 
            +
              #
         | 
| 15 16 | 
             
              # And so you should set your `replica_bucket` to:
         | 
| 17 | 
            +
              #
         | 
| 16 18 | 
             
              #   myapp.fra1.digitaloceanspaces.com
         | 
| 17 | 
            -
              # | 
| 18 | 
            -
              # Scaleway Object Storage, Google Cloud Storage, Linode Object Storage, and
         | 
| 19 | 
            -
              # any SFTP server.
         | 
| 20 | 
            -
              # In this example, we are using Rails encrypted credentials to store the URL to
         | 
| 21 | 
            -
              # our storage provider bucket.
         | 
| 19 | 
            +
              #
         | 
| 22 20 | 
             
              # config.litestream.replica_bucket = litestream_credentials&.replica_bucket
         | 
| 23 | 
            -
             | 
| 24 | 
            -
              # Replica-specific authentication key.
         | 
| 25 | 
            -
              # Litestream needs authentication credentials to access your storage provider bucket.
         | 
| 26 | 
            -
              # In this example, we are using Rails encrypted credentials to store the access key ID.
         | 
| 21 | 
            +
              #
         | 
| 22 | 
            +
              # Replica-specific authentication key. Litestream needs authentication credentials to access your storage provider bucket.
         | 
| 27 23 | 
             
              # config.litestream.replica_key_id = litestream_credentials&.replica_key_id
         | 
| 28 | 
            -
             | 
| 29 | 
            -
              # Replica-specific secret key.
         | 
| 30 | 
            -
              # Litestream needs authentication credentials to access your storage provider bucket.
         | 
| 31 | 
            -
              # In this example, we are using Rails encrypted credentials to store the secret access key.
         | 
| 24 | 
            +
              #
         | 
| 25 | 
            +
              # Replica-specific secret key. Litestream needs authentication credentials to access your storage provider bucket.
         | 
| 32 26 | 
             
              # config.litestream.replica_access_key = litestream_credentials&.replica_access_key
         | 
| 27 | 
            +
              #
         | 
| 28 | 
            +
              # Replica-specific region. Set the bucket’s region. Only used for AWS S3 & Backblaze B2.
         | 
| 29 | 
            +
              # config.litestream.replica_region = "us-east-1"
         | 
| 30 | 
            +
              #
         | 
| 31 | 
            +
              # Replica-specific endpoint. Set the endpoint URL of the S3-compatible service. Only required for non-AWS services.
         | 
| 32 | 
            +
              # config.litestream.replica_endpoint = "endpoint.your-objectstorage.com"
         | 
| 33 | 
            +
             | 
| 34 | 
            +
              # Configure the default Litestream config path
         | 
| 35 | 
            +
              # config.config_path = Rails.root.join("config", "litestream.yml")
         | 
| 36 | 
            +
             | 
| 37 | 
            +
              # Configure the Litestream dashboard
         | 
| 38 | 
            +
              #
         | 
| 39 | 
            +
              # Set the default base controller class
         | 
| 40 | 
            +
              # config.litestream.base_controller_class = "MyApplicationController"
         | 
| 41 | 
            +
              #
         | 
| 42 | 
            +
              # Set authentication credentials for Litestream dashboard
         | 
| 43 | 
            +
              # config.litestream.username = litestream_credentials&.username
         | 
| 44 | 
            +
              # config.litestream.password = litestream_credentials&.password
         | 
| 33 45 | 
             
            end
         | 
    
        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
    
    | @@ -33,16 +33,17 @@ module Litestream | |
| 33 33 | 
             
                end
         | 
| 34 34 | 
             
              end
         | 
| 35 35 |  | 
| 36 | 
            -
              mattr_writer :username, :password, :queue, :replica_bucket, :replica_key_id, :replica_access_key, :systemctl_command
         | 
| 36 | 
            +
              mattr_writer :username, :password, :queue, :replica_bucket, :replica_region, :replica_endpoint, :replica_key_id, :replica_access_key, :systemctl_command, :config_path
         | 
| 37 | 
            +
              mattr_accessor :base_controller_class, default: "::ApplicationController"
         | 
| 37 38 |  | 
| 38 39 | 
             
              class << self
         | 
| 39 | 
            -
                def verify!(database_path)
         | 
| 40 | 
            +
                def verify!(database_path, replication_sleep: 10)
         | 
| 40 41 | 
             
                  database = SQLite3::Database.new(database_path)
         | 
| 41 42 | 
             
                  database.execute("CREATE TABLE IF NOT EXISTS _litestream_verification (id INTEGER PRIMARY KEY, uuid BLOB)")
         | 
| 42 43 | 
             
                  sentinel = SecureRandom.uuid
         | 
| 43 44 | 
             
                  database.execute("INSERT INTO _litestream_verification (uuid) VALUES (?)", [sentinel])
         | 
| 44 45 | 
             
                  # give the Litestream replication process time to replicate the sentinel value
         | 
| 45 | 
            -
                  sleep  | 
| 46 | 
            +
                  sleep replication_sleep
         | 
| 46 47 |  | 
| 47 48 | 
             
                  backup_path = "tmp/#{Time.now.utc.strftime("%Y%m%d%H%M%S")}_#{sentinel}.sqlite3"
         | 
| 48 49 | 
             
                  Litestream::Commands.restore(database_path, **{"-o" => backup_path})
         | 
| @@ -77,6 +78,14 @@ module Litestream | |
| 77 78 | 
             
                  @@replica_bucket || configuration.replica_bucket
         | 
| 78 79 | 
             
                end
         | 
| 79 80 |  | 
| 81 | 
            +
                def replica_region
         | 
| 82 | 
            +
                  @@replica_region
         | 
| 83 | 
            +
                end
         | 
| 84 | 
            +
             | 
| 85 | 
            +
                def replica_endpoint
         | 
| 86 | 
            +
                  @@replica_endpoint
         | 
| 87 | 
            +
                end
         | 
| 88 | 
            +
             | 
| 80 89 | 
             
                def replica_key_id
         | 
| 81 90 | 
             
                  @@replica_key_id || configuration.replica_key_id
         | 
| 82 91 | 
             
                end
         | 
| @@ -89,55 +98,12 @@ module Litestream | |
| 89 98 | 
             
                  @@systemctl_command || "systemctl status litestream"
         | 
| 90 99 | 
             
                end
         | 
| 91 100 |  | 
| 101 | 
            +
                def config_path
         | 
| 102 | 
            +
                  @@config_path || Rails.root.join("config", "litestream.yml")
         | 
| 103 | 
            +
                end
         | 
| 104 | 
            +
             | 
| 92 105 | 
             
                def replicate_process
         | 
| 93 | 
            -
                   | 
| 94 | 
            -
                  if !`which systemctl`.empty?
         | 
| 95 | 
            -
                    systemctl_status = `#{Litestream.systemctl_command}`.chomp
         | 
| 96 | 
            -
                    # ["● litestream.service - Litestream",
         | 
| 97 | 
            -
                    #  "     Loaded: loaded (/lib/systemd/system/litestream.service; enabled; vendor preset: enabled)",
         | 
| 98 | 
            -
                    #  "     Active: active (running) since Tue 2023-07-25 13:49:43 UTC; 8 months 24 days ago",
         | 
| 99 | 
            -
                    #  "   Main PID: 1179656 (litestream)",
         | 
| 100 | 
            -
                    #  "      Tasks: 9 (limit: 1115)",
         | 
| 101 | 
            -
                    #  "     Memory: 22.9M",
         | 
| 102 | 
            -
                    #  "        CPU: 10h 49.843s",
         | 
| 103 | 
            -
                    #  "     CGroup: /system.slice/litestream.service",
         | 
| 104 | 
            -
                    #  "             └─1179656 /usr/bin/litestream replicate",
         | 
| 105 | 
            -
                    #  "",
         | 
| 106 | 
            -
                    #  "Warning: some journal files were not opened due to insufficient permissions."]
         | 
| 107 | 
            -
                    systemctl_status.split("\n").each do |line|
         | 
| 108 | 
            -
                      line.strip!
         | 
| 109 | 
            -
                      if line.start_with?("Main PID:")
         | 
| 110 | 
            -
                        _key, value = line.split(":")
         | 
| 111 | 
            -
                        pid, _name = value.strip.split(" ")
         | 
| 112 | 
            -
                        info[:pid] = pid
         | 
| 113 | 
            -
                      elsif line.start_with?("Active:")
         | 
| 114 | 
            -
                        value, _ago = line.split(";")
         | 
| 115 | 
            -
                        status, timestamp = value.split(" since ")
         | 
| 116 | 
            -
                        info[:started] = DateTime.strptime(timestamp.strip, "%a %Y-%m-%d %H:%M:%S %Z")
         | 
| 117 | 
            -
                        status_match = status.match(%r{\((?<status>.*)\)})
         | 
| 118 | 
            -
                        info[:status] = status_match ? status_match[:status] : nil
         | 
| 119 | 
            -
                      end
         | 
| 120 | 
            -
                    end
         | 
| 121 | 
            -
                  else
         | 
| 122 | 
            -
                    litestream_replicate_ps = `ps -ax | grep litestream | grep replicate`.chomp
         | 
| 123 | 
            -
                    litestream_replicate_ps.split("\n").each do |line|
         | 
| 124 | 
            -
                      next unless line.include?("litestream replicate")
         | 
| 125 | 
            -
                      pid, * = line.split(" ")
         | 
| 126 | 
            -
                      info[:pid] = pid
         | 
| 127 | 
            -
                      state, _, lstart = `ps -o "state,lstart" #{pid}`.chomp.split("\n").last.partition(/\s+/)
         | 
| 128 | 
            -
             | 
| 129 | 
            -
                      info[:status] = case state[0]
         | 
| 130 | 
            -
                      when "I" then "idle"
         | 
| 131 | 
            -
                      when "R" then "running"
         | 
| 132 | 
            -
                      when "S" then "sleeping"
         | 
| 133 | 
            -
                      when "T" then "stopped"
         | 
| 134 | 
            -
                      when "U" then "uninterruptible"
         | 
| 135 | 
            -
                      when "Z" then "zombie"
         | 
| 136 | 
            -
                      end
         | 
| 137 | 
            -
                      info[:started] = DateTime.strptime(lstart.strip, "%a %b %d %H:%M:%S %Y")
         | 
| 138 | 
            -
                    end
         | 
| 139 | 
            -
                  end
         | 
| 140 | 
            -
                  info
         | 
| 106 | 
            +
                  systemctl_info || process_info || {}
         | 
| 141 107 | 
             
                end
         | 
| 142 108 |  | 
| 143 109 | 
             
                def databases
         | 
| @@ -157,6 +123,71 @@ module Litestream | |
| 157 123 | 
             
                    end
         | 
| 158 124 | 
             
                  end
         | 
| 159 125 | 
             
                end
         | 
| 126 | 
            +
             | 
| 127 | 
            +
                private
         | 
| 128 | 
            +
             | 
| 129 | 
            +
                def systemctl_info
         | 
| 130 | 
            +
                  return if `which systemctl`.empty?
         | 
| 131 | 
            +
             | 
| 132 | 
            +
                  systemctl_output = `#{Litestream.systemctl_command}`
         | 
| 133 | 
            +
                  systemctl_exit_code = $?.exitstatus
         | 
| 134 | 
            +
                  return unless systemctl_exit_code.zero?
         | 
| 135 | 
            +
             | 
| 136 | 
            +
                  # ["● litestream.service - Litestream",
         | 
| 137 | 
            +
                  #  "     Loaded: loaded (/lib/systemd/system/litestream.service; enabled; vendor preset: enabled)",
         | 
| 138 | 
            +
                  #  "     Active: active (running) since Tue 2023-07-25 13:49:43 UTC; 8 months 24 days ago",
         | 
| 139 | 
            +
                  #  "   Main PID: 1179656 (litestream)",
         | 
| 140 | 
            +
                  #  "      Tasks: 9 (limit: 1115)",
         | 
| 141 | 
            +
                  #  "     Memory: 22.9M",
         | 
| 142 | 
            +
                  #  "        CPU: 10h 49.843s",
         | 
| 143 | 
            +
                  #  "     CGroup: /system.slice/litestream.service",
         | 
| 144 | 
            +
                  #  "             └─1179656 /usr/bin/litestream replicate",
         | 
| 145 | 
            +
                  #  "",
         | 
| 146 | 
            +
                  #  "Warning: some journal files were not opened due to insufficient permissions."]
         | 
| 147 | 
            +
             | 
| 148 | 
            +
                  info = {}
         | 
| 149 | 
            +
                  systemctl_output.chomp.split("\n").each do |line|
         | 
| 150 | 
            +
                    line.strip!
         | 
| 151 | 
            +
                    if line.start_with?("Main PID:")
         | 
| 152 | 
            +
                      _key, value = line.split(":")
         | 
| 153 | 
            +
                      pid, _name = value.strip.split(" ")
         | 
| 154 | 
            +
                      info[:pid] = pid
         | 
| 155 | 
            +
                    elsif line.start_with?("Active:")
         | 
| 156 | 
            +
                      value, _ago = line.split(";")
         | 
| 157 | 
            +
                      status, timestamp = value.split(" since ")
         | 
| 158 | 
            +
                      info[:started] = DateTime.strptime(timestamp.strip, "%a %Y-%m-%d %H:%M:%S %Z")
         | 
| 159 | 
            +
                      status_match = status.match(%r{\((?<status>.*)\)})
         | 
| 160 | 
            +
                      info[:status] = status_match ? status_match[:status] : nil
         | 
| 161 | 
            +
                    end
         | 
| 162 | 
            +
                  end
         | 
| 163 | 
            +
                  info
         | 
| 164 | 
            +
                end
         | 
| 165 | 
            +
             | 
| 166 | 
            +
                def process_info
         | 
| 167 | 
            +
                  litestream_replicate_ps = `ps -ax | grep litestream | grep replicate`
         | 
| 168 | 
            +
                  exit_code = $?.exitstatus
         | 
| 169 | 
            +
                  return unless exit_code.zero?
         | 
| 170 | 
            +
             | 
| 171 | 
            +
                  info = {}
         | 
| 172 | 
            +
                  litestream_replicate_ps.chomp.split("\n").each do |line|
         | 
| 173 | 
            +
                    next unless line.include?("litestream replicate")
         | 
| 174 | 
            +
             | 
| 175 | 
            +
                    pid, * = line.split(" ")
         | 
| 176 | 
            +
                    info[:pid] = pid
         | 
| 177 | 
            +
                    state, _, lstart = `ps -o "state,lstart" #{pid}`.chomp.split("\n").last.partition(/\s+/)
         | 
| 178 | 
            +
             | 
| 179 | 
            +
                    info[:status] = case state[0]
         | 
| 180 | 
            +
                    when "I" then "idle"
         | 
| 181 | 
            +
                    when "R" then "running"
         | 
| 182 | 
            +
                    when "S" then "sleeping"
         | 
| 183 | 
            +
                    when "T" then "stopped"
         | 
| 184 | 
            +
                    when "U" then "uninterruptible"
         | 
| 185 | 
            +
                    when "Z" then "zombie"
         | 
| 186 | 
            +
                    end
         | 
| 187 | 
            +
                    info[:started] = DateTime.strptime(lstart.strip, "%a %b %d %H:%M:%S %Y")
         | 
| 188 | 
            +
                  end
         | 
| 189 | 
            +
                  info
         | 
| 190 | 
            +
                end
         | 
| 160 191 | 
             
              end
         | 
| 161 192 | 
             
            end
         | 
| 162 193 |  | 
| @@ -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 |  | 
| @@ -10,83 +12,61 @@ namespace :litestream do | |
| 10 12 |  | 
| 11 13 | 
             
              desc 'Monitor and continuously replicate SQLite databases defined in your config file, for example `rake litestream:replicate -- -exec "foreman start"`'
         | 
| 12 14 | 
             
              task replicate: :environment do
         | 
| 13 | 
            -
                options =  | 
| 14 | 
            -
                if (separator_index = ARGV.index("--"))
         | 
| 15 | 
            -
                  ARGV.slice(separator_index + 1, ARGV.length)
         | 
| 16 | 
            -
                    .map { |pair| pair.split("=") }
         | 
| 17 | 
            -
                    .each { |opt| options[opt[0]] = opt[1] || nil }
         | 
| 18 | 
            -
                end
         | 
| 19 | 
            -
                options.symbolize_keys!
         | 
| 15 | 
            +
                options = parse_argv_options
         | 
| 20 16 |  | 
| 21 | 
            -
                Litestream::Commands.replicate( | 
| 17 | 
            +
                Litestream::Commands.replicate(**options)
         | 
| 22 18 | 
             
              end
         | 
| 23 19 |  | 
| 24 20 | 
             
              desc "Restore a SQLite database from a Litestream replica, for example `rake litestream:restore -- -database=storage/production.sqlite3`"
         | 
| 25 21 | 
             
              task restore: :environment do
         | 
| 26 | 
            -
                options =  | 
| 27 | 
            -
                 | 
| 28 | 
            -
                  ARGV.slice(separator_index + 1, ARGV.length)
         | 
| 29 | 
            -
                    .map { |pair| pair.split("=") }
         | 
| 30 | 
            -
                    .each { |opt| options[opt[0]] = opt[1] || nil }
         | 
| 31 | 
            -
                end
         | 
| 32 | 
            -
                database = options.delete("--database") || options.delete("-database")
         | 
| 33 | 
            -
                options.symbolize_keys!
         | 
| 22 | 
            +
                options = parse_argv_options
         | 
| 23 | 
            +
                database = options.delete(:"--database") || options.delete(:"-database")
         | 
| 34 24 |  | 
| 35 | 
            -
                Litestream::Commands.restore(database,  | 
| 25 | 
            +
                puts Litestream::Commands.restore(database, **options)
         | 
| 36 26 | 
             
              end
         | 
| 37 27 |  | 
| 38 28 | 
             
              desc "List all databases and associated replicas in the config file, for example `rake litestream:databases -- -no-expand-env`"
         | 
| 39 29 | 
             
              task databases: :environment do
         | 
| 40 | 
            -
                options =  | 
| 41 | 
            -
                if (separator_index = ARGV.index("--"))
         | 
| 42 | 
            -
                  ARGV.slice(separator_index + 1, ARGV.length)
         | 
| 43 | 
            -
                    .map { |pair| pair.split("=") }
         | 
| 44 | 
            -
                    .each { |opt| options[opt[0]] = opt[1] || nil }
         | 
| 45 | 
            -
                end
         | 
| 46 | 
            -
                options.symbolize_keys!
         | 
| 30 | 
            +
                options = parse_argv_options
         | 
| 47 31 |  | 
| 48 | 
            -
                Litestream::Commands.databases( | 
| 32 | 
            +
                puts Litestream::Commands::Output.format(Litestream::Commands.databases(**options))
         | 
| 49 33 | 
             
              end
         | 
| 50 34 |  | 
| 51 35 | 
             
              desc "List all generations for a database or replica, for example `rake litestream:generations -- -database=storage/production.sqlite3`"
         | 
| 52 36 | 
             
              task generations: :environment do
         | 
| 53 | 
            -
                options =  | 
| 54 | 
            -
                 | 
| 55 | 
            -
                  ARGV.slice(separator_index + 1, ARGV.length)
         | 
| 56 | 
            -
                    .map { |pair| pair.split("=") }
         | 
| 57 | 
            -
                    .each { |opt| options[opt[0]] = opt[1] || nil }
         | 
| 58 | 
            -
                end
         | 
| 59 | 
            -
                database = options.delete("--database") || options.delete("-database")
         | 
| 60 | 
            -
                options.symbolize_keys!
         | 
| 37 | 
            +
                options = parse_argv_options
         | 
| 38 | 
            +
                database = options.delete(:"--database") || options.delete(:"-database")
         | 
| 61 39 |  | 
| 62 | 
            -
                Litestream::Commands.generations(database,  | 
| 40 | 
            +
                puts Litestream::Commands::Output.format(Litestream::Commands.generations(database, **options))
         | 
| 63 41 | 
             
              end
         | 
| 64 42 |  | 
| 65 43 | 
             
              desc "List all snapshots for a database or replica, for example `rake litestream:snapshots -- -database=storage/production.sqlite3`"
         | 
| 66 44 | 
             
              task snapshots: :environment do
         | 
| 67 | 
            -
                options =  | 
| 68 | 
            -
                 | 
| 69 | 
            -
                  ARGV.slice(separator_index + 1, ARGV.length)
         | 
| 70 | 
            -
                    .map { |pair| pair.split("=") }
         | 
| 71 | 
            -
                    .each { |opt| options[opt[0]] = opt[1] || nil }
         | 
| 72 | 
            -
                end
         | 
| 73 | 
            -
                database = options.delete("--database") || options.delete("-database")
         | 
| 74 | 
            -
                options.symbolize_keys!
         | 
| 45 | 
            +
                options = parse_argv_options
         | 
| 46 | 
            +
                database = options.delete(:"--database") || options.delete(:"-database")
         | 
| 75 47 |  | 
| 76 | 
            -
                Litestream::Commands.snapshots(database,  | 
| 48 | 
            +
                puts Litestream::Commands::Output.format(Litestream::Commands.snapshots(database, **options))
         | 
| 77 49 | 
             
              end
         | 
| 78 50 |  | 
| 79 51 | 
             
              desc "List all wal files for a database or replica, for example `rake litestream:wal -- -database=storage/production.sqlite3`"
         | 
| 80 52 | 
             
              task wal: :environment do
         | 
| 53 | 
            +
                options = parse_argv_options
         | 
| 54 | 
            +
                database = options.delete(:"--database") || options.delete(:"-database")
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                puts Litestream::Commands::Output.format(
         | 
| 57 | 
            +
                  Litestream::Commands.wal(database, **options)
         | 
| 58 | 
            +
                )
         | 
| 59 | 
            +
              end
         | 
| 60 | 
            +
             | 
| 61 | 
            +
              private
         | 
| 62 | 
            +
             | 
| 63 | 
            +
              def parse_argv_options
         | 
| 81 64 | 
             
                options = {}
         | 
| 82 65 | 
             
                if (separator_index = ARGV.index("--"))
         | 
| 83 66 | 
             
                  ARGV.slice(separator_index + 1, ARGV.length)
         | 
| 84 67 | 
             
                    .map { |pair| pair.split("=") }
         | 
| 85 68 | 
             
                    .each { |opt| options[opt[0]] = opt[1] || nil }
         | 
| 86 69 | 
             
                end
         | 
| 87 | 
            -
                database = options.delete("--database") || options.delete("-database")
         | 
| 88 70 | 
             
                options.symbolize_keys!
         | 
| 89 | 
            -
             | 
| 90 | 
            -
                Litestream::Commands.wal(database, async: true, **options)
         | 
| 91 71 | 
             
              end
         | 
| 92 72 | 
             
            end
         | 
    
        metadata
    CHANGED
    
    | @@ -1,29 +1,14 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: litestream
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 0. | 
| 4 | 
            +
              version: 0.14.0
         | 
| 5 5 | 
             
            platform: x86_64-linux
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - Stephen Margheim
         | 
| 8 | 
            -
            autorequire:
         | 
| 9 8 | 
             
            bindir: exe
         | 
| 10 9 | 
             
            cert_chain: []
         | 
| 11 | 
            -
            date:  | 
| 10 | 
            +
            date: 2025-06-13 00:00:00.000000000 Z
         | 
| 12 11 | 
             
            dependencies:
         | 
| 13 | 
            -
            - !ruby/object:Gem::Dependency
         | 
| 14 | 
            -
              name: logfmt
         | 
| 15 | 
            -
              requirement: !ruby/object:Gem::Requirement
         | 
| 16 | 
            -
                requirements:
         | 
| 17 | 
            -
                - - ">="
         | 
| 18 | 
            -
                  - !ruby/object:Gem::Version
         | 
| 19 | 
            -
                    version: 0.0.10
         | 
| 20 | 
            -
              type: :runtime
         | 
| 21 | 
            -
              prerelease: false
         | 
| 22 | 
            -
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 23 | 
            -
                requirements:
         | 
| 24 | 
            -
                - - ">="
         | 
| 25 | 
            -
                  - !ruby/object:Gem::Version
         | 
| 26 | 
            -
                    version: 0.0.10
         | 
| 27 12 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 28 13 | 
             
              name: sqlite3
         | 
| 29 14 | 
             
              requirement: !ruby/object:Gem::Requirement
         | 
| @@ -67,7 +52,7 @@ dependencies: | |
| 67 52 | 
             
                  - !ruby/object:Gem::Version
         | 
| 68 53 | 
             
                    version: '7.0'
         | 
| 69 54 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 70 | 
            -
              name:  | 
| 55 | 
            +
              name: activejob
         | 
| 71 56 | 
             
              requirement: !ruby/object:Gem::Requirement
         | 
| 72 57 | 
             
                requirements:
         | 
| 73 58 | 
             
                - - ">="
         | 
| @@ -81,7 +66,7 @@ dependencies: | |
| 81 66 | 
             
                  - !ruby/object:Gem::Version
         | 
| 82 67 | 
             
                    version: '7.0'
         | 
| 83 68 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 84 | 
            -
              name:  | 
| 69 | 
            +
              name: activesupport
         | 
| 85 70 | 
             
              requirement: !ruby/object:Gem::Requirement
         | 
| 86 71 | 
             
                requirements:
         | 
| 87 72 | 
             
                - - ">="
         | 
| @@ -108,20 +93,6 @@ dependencies: | |
| 108 93 | 
             
                - - ">="
         | 
| 109 94 | 
             
                  - !ruby/object:Gem::Version
         | 
| 110 95 | 
             
                    version: '7.0'
         | 
| 111 | 
            -
            - !ruby/object:Gem::Dependency
         | 
| 112 | 
            -
              name: rubyzip
         | 
| 113 | 
            -
              requirement: !ruby/object:Gem::Requirement
         | 
| 114 | 
            -
                requirements:
         | 
| 115 | 
            -
                - - ">="
         | 
| 116 | 
            -
                  - !ruby/object:Gem::Version
         | 
| 117 | 
            -
                    version: '0'
         | 
| 118 | 
            -
              type: :development
         | 
| 119 | 
            -
              prerelease: false
         | 
| 120 | 
            -
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 121 | 
            -
                requirements:
         | 
| 122 | 
            -
                - - ">="
         | 
| 123 | 
            -
                  - !ruby/object:Gem::Version
         | 
| 124 | 
            -
                    version: '0'
         | 
| 125 96 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 126 97 | 
             
              name: rails
         | 
| 127 98 | 
             
              requirement: !ruby/object:Gem::Requirement
         | 
| @@ -137,7 +108,7 @@ dependencies: | |
| 137 108 | 
             
                  - !ruby/object:Gem::Version
         | 
| 138 109 | 
             
                    version: '0'
         | 
| 139 110 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 140 | 
            -
              name:  | 
| 111 | 
            +
              name: rubyzip
         | 
| 141 112 | 
             
              requirement: !ruby/object:Gem::Requirement
         | 
| 142 113 | 
             
                requirements:
         | 
| 143 114 | 
             
                - - ">="
         | 
| @@ -150,7 +121,6 @@ dependencies: | |
| 150 121 | 
             
                - - ">="
         | 
| 151 122 | 
             
                  - !ruby/object:Gem::Version
         | 
| 152 123 | 
             
                    version: '0'
         | 
| 153 | 
            -
            description:
         | 
| 154 124 | 
             
            email:
         | 
| 155 125 | 
             
            - stephen.margheim@gmail.com
         | 
| 156 126 | 
             
            executables:
         | 
| @@ -190,7 +160,6 @@ metadata: | |
| 190 160 | 
             
              rubygems_mfa_required: 'true'
         | 
| 191 161 | 
             
              source_code_uri: https://github.com/fractaledmind/litestream-ruby
         | 
| 192 162 | 
             
              changelog_uri: https://github.com/fractaledmind/litestream-ruby/CHANGELOG.md
         | 
| 193 | 
            -
            post_install_message:
         | 
| 194 163 | 
             
            rdoc_options: []
         | 
| 195 164 | 
             
            require_paths:
         | 
| 196 165 | 
             
            - lib
         | 
| @@ -205,8 +174,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement | |
| 205 174 | 
             
                - !ruby/object:Gem::Version
         | 
| 206 175 | 
             
                  version: '0'
         | 
| 207 176 | 
             
            requirements: []
         | 
| 208 | 
            -
            rubygems_version: 3. | 
| 209 | 
            -
            signing_key:
         | 
| 177 | 
            +
            rubygems_version: 3.6.3
         | 
| 210 178 | 
             
            specification_version: 4
         | 
| 211 179 | 
             
            summary: Integrate Litestream with the RubyGems infrastructure.
         | 
| 212 180 | 
             
            test_files: []
         |