litestream 0.13.0-aarch64-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.
data/lib/litestream.rb ADDED
@@ -0,0 +1,198 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "sqlite3"
4
+
5
+ module Litestream
6
+ VerificationFailure = Class.new(StandardError)
7
+
8
+ class << self
9
+ attr_writer :configuration
10
+
11
+ def configuration
12
+ @configuration ||= Configuration.new
13
+ end
14
+
15
+ def deprecator
16
+ @deprecator ||= ActiveSupport::Deprecation.new("0.12.0", "Litestream")
17
+ end
18
+ end
19
+
20
+ def self.configure
21
+ deprecator.warn(
22
+ "Configuring Litestream via Litestream.configure is deprecated. Use Rails.application.configure { config.litestream.* = ... } instead.",
23
+ caller
24
+ )
25
+ self.configuration ||= Configuration.new
26
+ yield(configuration)
27
+ end
28
+
29
+ class Configuration
30
+ attr_accessor :replica_bucket, :replica_key_id, :replica_access_key
31
+
32
+ def initialize
33
+ end
34
+ end
35
+
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"
39
+
40
+ class << self
41
+ def verify!(database_path, replication_sleep: 10)
42
+ database = SQLite3::Database.new(database_path)
43
+ database.execute("CREATE TABLE IF NOT EXISTS _litestream_verification (id INTEGER PRIMARY KEY, uuid BLOB)")
44
+ sentinel = SecureRandom.uuid
45
+ database.execute("INSERT INTO _litestream_verification (uuid) VALUES (?)", [sentinel])
46
+ # give the Litestream replication process time to replicate the sentinel value
47
+ sleep replication_sleep
48
+
49
+ backup_path = "tmp/#{Time.now.utc.strftime("%Y%m%d%H%M%S")}_#{sentinel}.sqlite3"
50
+ Litestream::Commands.restore(database_path, **{"-o" => backup_path})
51
+
52
+ backup = SQLite3::Database.new(backup_path)
53
+ result = backup.execute("SELECT 1 FROM _litestream_verification WHERE uuid = ? LIMIT 1", sentinel) # => [[1]] || []
54
+
55
+ raise VerificationFailure, "Verification failed for `#{database_path}`" if result.empty?
56
+
57
+ true
58
+ ensure
59
+ database.execute("DELETE FROM _litestream_verification WHERE uuid = ?", sentinel)
60
+ database.close
61
+ Dir.glob(backup_path + "*").each { |file| File.delete(file) }
62
+ end
63
+
64
+ # use method instead of attr_accessor to ensure
65
+ # this works if variable set after Litestream is loaded
66
+ def username
67
+ ENV["LITESTREAM_USERNAME"] || @@username || "litestream"
68
+ end
69
+
70
+ def password
71
+ ENV["LITESTREAM_PASSWORD"] || @@password
72
+ end
73
+
74
+ def queue
75
+ ENV["LITESTREAM_QUEUE"] || @@queue || "default"
76
+ end
77
+
78
+ def replica_bucket
79
+ @@replica_bucket || configuration.replica_bucket
80
+ end
81
+
82
+ def replica_region
83
+ @@replica_region
84
+ end
85
+
86
+ def replica_endpoint
87
+ @@replica_endpoint
88
+ end
89
+
90
+ def replica_key_id
91
+ @@replica_key_id || configuration.replica_key_id
92
+ end
93
+
94
+ def replica_access_key
95
+ @@replica_access_key || configuration.replica_access_key
96
+ end
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
+
106
+ def replicate_process
107
+ systemctl_info || process_info || {}
108
+ end
109
+
110
+ def databases
111
+ databases = Commands.databases
112
+
113
+ databases.each do |db|
114
+ generations = Commands.generations(db["path"])
115
+ snapshots = Commands.snapshots(db["path"])
116
+ db["path"] = db["path"].gsub(Rails.root.to_s, "[ROOT]")
117
+
118
+ db["generations"] = generations.map do |generation|
119
+ id = generation["generation"]
120
+ replica = generation["name"]
121
+ generation["snapshots"] = snapshots.select { |snapshot| snapshot["generation"] == id && snapshot["replica"] == replica }
122
+ .map { |s| s.slice("index", "size", "created") }
123
+ generation.slice("generation", "name", "lag", "start", "end", "snapshots")
124
+ end
125
+ end
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
192
+ end
193
+ end
194
+
195
+ require_relative "litestream/version"
196
+ require_relative "litestream/upstream"
197
+ require_relative "litestream/commands"
198
+ require_relative "litestream/engine" if defined?(::Rails::Engine)
@@ -0,0 +1,69 @@
1
+ require "puma/plugin"
2
+
3
+ # Copied from https://github.com/rails/solid_queue/blob/15408647f1780033dad223d3198761ea2e1e983e/lib/puma/plugin/solid_queue.rb
4
+ Puma::Plugin.create do
5
+ attr_reader :puma_pid, :litestream_pid, :log_writer
6
+
7
+ def start(launcher)
8
+ @log_writer = launcher.log_writer
9
+ @puma_pid = $$
10
+
11
+ launcher.events.on_booted do
12
+ @litestream_pid = fork do
13
+ Thread.new { monitor_puma }
14
+ Litestream::Commands.replicate(async: true)
15
+ end
16
+
17
+ in_background do
18
+ monitor_litestream
19
+ end
20
+ end
21
+
22
+ launcher.events.on_stopped { stop_litestream }
23
+ launcher.events.on_restart { stop_litestream }
24
+ end
25
+
26
+ private
27
+
28
+ def stop_litestream
29
+ Process.waitpid(litestream_pid, Process::WNOHANG)
30
+ log_writer.log "Stopping Litestream..."
31
+ Process.kill(:INT, litestream_pid) if litestream_pid
32
+ Process.wait(litestream_pid)
33
+ rescue Errno::ECHILD, Errno::ESRCH
34
+ end
35
+
36
+ def monitor_puma
37
+ monitor(:puma_dead?, "Detected Puma has gone away, stopping Litestream...")
38
+ end
39
+
40
+ def monitor_litestream
41
+ monitor(:litestream_dead?, "Detected Litestream has gone away, stopping Puma...")
42
+ end
43
+
44
+ def monitor(process_dead, message)
45
+ loop do
46
+ if send(process_dead)
47
+ log message
48
+ Process.kill(:INT, $$)
49
+ break
50
+ end
51
+ sleep 2
52
+ end
53
+ end
54
+
55
+ def litestream_dead?
56
+ Process.waitpid(litestream_pid, Process::WNOHANG)
57
+ false
58
+ rescue Errno::ECHILD, Errno::ESRCH
59
+ true
60
+ end
61
+
62
+ def puma_dead?
63
+ Process.ppid != puma_pid
64
+ end
65
+
66
+ def log(...)
67
+ log_writer.log(...)
68
+ end
69
+ end
@@ -0,0 +1,94 @@
1
+ namespace :litestream do
2
+ desc "Print the ENV variables needed for the Litestream config file"
3
+ task env: :environment do
4
+ puts "LITESTREAM_REPLICA_BUCKET=#{Litestream.replica_bucket}"
5
+ puts "LITESTREAM_REPLICA_REGION=#{Litestream.replica_region}"
6
+ puts "LITESTREAM_REPLICA_ENDPOINT=#{Litestream.replica_endpoint}"
7
+ puts "LITESTREAM_ACCESS_KEY_ID=#{Litestream.replica_key_id}"
8
+ puts "LITESTREAM_SECRET_ACCESS_KEY=#{Litestream.replica_access_key}"
9
+
10
+ true
11
+ end
12
+
13
+ desc 'Monitor and continuously replicate SQLite databases defined in your config file, for example `rake litestream:replicate -- -exec "foreman start"`'
14
+ task replicate: :environment do
15
+ options = {}
16
+ if (separator_index = ARGV.index("--"))
17
+ ARGV.slice(separator_index + 1, ARGV.length)
18
+ .map { |pair| pair.split("=") }
19
+ .each { |opt| options[opt[0]] = opt[1] || nil }
20
+ end
21
+ options.symbolize_keys!
22
+
23
+ Litestream::Commands.replicate(async: true, **options)
24
+ end
25
+
26
+ desc "Restore a SQLite database from a Litestream replica, for example `rake litestream:restore -- -database=storage/production.sqlite3`"
27
+ task restore: :environment do
28
+ options = {}
29
+ if (separator_index = ARGV.index("--"))
30
+ ARGV.slice(separator_index + 1, ARGV.length)
31
+ .map { |pair| pair.split("=") }
32
+ .each { |opt| options[opt[0]] = opt[1] || nil }
33
+ end
34
+ database = options.delete("--database") || options.delete("-database")
35
+ options.symbolize_keys!
36
+
37
+ Litestream::Commands.restore(database, async: true, **options)
38
+ end
39
+
40
+ desc "List all databases and associated replicas in the config file, for example `rake litestream:databases -- -no-expand-env`"
41
+ task databases: :environment do
42
+ options = {}
43
+ if (separator_index = ARGV.index("--"))
44
+ ARGV.slice(separator_index + 1, ARGV.length)
45
+ .map { |pair| pair.split("=") }
46
+ .each { |opt| options[opt[0]] = opt[1] || nil }
47
+ end
48
+ options.symbolize_keys!
49
+
50
+ Litestream::Commands.databases(async: true, **options)
51
+ end
52
+
53
+ desc "List all generations for a database or replica, for example `rake litestream:generations -- -database=storage/production.sqlite3`"
54
+ task generations: :environment do
55
+ options = {}
56
+ if (separator_index = ARGV.index("--"))
57
+ ARGV.slice(separator_index + 1, ARGV.length)
58
+ .map { |pair| pair.split("=") }
59
+ .each { |opt| options[opt[0]] = opt[1] || nil }
60
+ end
61
+ database = options.delete("--database") || options.delete("-database")
62
+ options.symbolize_keys!
63
+
64
+ Litestream::Commands.generations(database, async: true, **options)
65
+ end
66
+
67
+ desc "List all snapshots for a database or replica, for example `rake litestream:snapshots -- -database=storage/production.sqlite3`"
68
+ task snapshots: :environment do
69
+ options = {}
70
+ if (separator_index = ARGV.index("--"))
71
+ ARGV.slice(separator_index + 1, ARGV.length)
72
+ .map { |pair| pair.split("=") }
73
+ .each { |opt| options[opt[0]] = opt[1] || nil }
74
+ end
75
+ database = options.delete("--database") || options.delete("-database")
76
+ options.symbolize_keys!
77
+
78
+ Litestream::Commands.snapshots(database, async: true, **options)
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
94
+ end
metadata ADDED
@@ -0,0 +1,208 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: litestream
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.13.0
5
+ platform: aarch64-linux
6
+ authors:
7
+ - Stephen Margheim
8
+ bindir: exe
9
+ cert_chain: []
10
+ date: 2025-06-03 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: logfmt
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: 0.0.10
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: 0.0.10
26
+ - !ruby/object:Gem::Dependency
27
+ name: sqlite3
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
40
+ - !ruby/object:Gem::Dependency
41
+ name: actionpack
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '7.0'
47
+ type: :runtime
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '7.0'
54
+ - !ruby/object:Gem::Dependency
55
+ name: actionview
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '7.0'
61
+ type: :runtime
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '7.0'
68
+ - !ruby/object:Gem::Dependency
69
+ name: activesupport
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: '7.0'
75
+ type: :runtime
76
+ prerelease: false
77
+ version_requirements: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: '7.0'
82
+ - !ruby/object:Gem::Dependency
83
+ name: activejob
84
+ requirement: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: '7.0'
89
+ type: :runtime
90
+ prerelease: false
91
+ version_requirements: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - ">="
94
+ - !ruby/object:Gem::Version
95
+ version: '7.0'
96
+ - !ruby/object:Gem::Dependency
97
+ name: railties
98
+ requirement: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - ">="
101
+ - !ruby/object:Gem::Version
102
+ version: '7.0'
103
+ type: :runtime
104
+ prerelease: false
105
+ version_requirements: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - ">="
108
+ - !ruby/object:Gem::Version
109
+ version: '7.0'
110
+ - !ruby/object:Gem::Dependency
111
+ name: rubyzip
112
+ requirement: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ version: '0'
117
+ type: :development
118
+ prerelease: false
119
+ version_requirements: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - ">="
122
+ - !ruby/object:Gem::Version
123
+ version: '0'
124
+ - !ruby/object:Gem::Dependency
125
+ name: rails
126
+ requirement: !ruby/object:Gem::Requirement
127
+ requirements:
128
+ - - ">="
129
+ - !ruby/object:Gem::Version
130
+ version: '0'
131
+ type: :development
132
+ prerelease: false
133
+ version_requirements: !ruby/object:Gem::Requirement
134
+ requirements:
135
+ - - ">="
136
+ - !ruby/object:Gem::Version
137
+ version: '0'
138
+ - !ruby/object:Gem::Dependency
139
+ name: sqlite3
140
+ requirement: !ruby/object:Gem::Requirement
141
+ requirements:
142
+ - - ">="
143
+ - !ruby/object:Gem::Version
144
+ version: '0'
145
+ type: :development
146
+ prerelease: false
147
+ version_requirements: !ruby/object:Gem::Requirement
148
+ requirements:
149
+ - - ">="
150
+ - !ruby/object:Gem::Version
151
+ version: '0'
152
+ email:
153
+ - stephen.margheim@gmail.com
154
+ executables:
155
+ - litestream
156
+ extensions: []
157
+ extra_rdoc_files: []
158
+ files:
159
+ - LICENSE
160
+ - LICENSE-DEPENDENCIES
161
+ - README.md
162
+ - Rakefile
163
+ - app/controllers/litestream/application_controller.rb
164
+ - app/controllers/litestream/processes_controller.rb
165
+ - app/controllers/litestream/restorations_controller.rb
166
+ - app/jobs/litestream/verification_job.rb
167
+ - app/views/layouts/litestream/_style.html
168
+ - app/views/layouts/litestream/application.html.erb
169
+ - app/views/litestream/processes/show.html.erb
170
+ - config/routes.rb
171
+ - exe/aarch64-linux/litestream
172
+ - exe/litestream
173
+ - lib/litestream.rb
174
+ - lib/litestream/commands.rb
175
+ - lib/litestream/engine.rb
176
+ - lib/litestream/generators/litestream/install_generator.rb
177
+ - lib/litestream/generators/litestream/templates/config.yml.erb
178
+ - lib/litestream/generators/litestream/templates/initializer.rb
179
+ - lib/litestream/upstream.rb
180
+ - lib/litestream/version.rb
181
+ - lib/puma/plugin/litestream.rb
182
+ - lib/tasks/litestream_tasks.rake
183
+ homepage: https://github.com/fractaledmind/litestream-ruby
184
+ licenses:
185
+ - MIT
186
+ metadata:
187
+ homepage_uri: https://github.com/fractaledmind/litestream-ruby
188
+ rubygems_mfa_required: 'true'
189
+ source_code_uri: https://github.com/fractaledmind/litestream-ruby
190
+ changelog_uri: https://github.com/fractaledmind/litestream-ruby/CHANGELOG.md
191
+ rdoc_options: []
192
+ require_paths:
193
+ - lib
194
+ required_ruby_version: !ruby/object:Gem::Requirement
195
+ requirements:
196
+ - - ">="
197
+ - !ruby/object:Gem::Version
198
+ version: 3.0.0
199
+ required_rubygems_version: !ruby/object:Gem::Requirement
200
+ requirements:
201
+ - - ">="
202
+ - !ruby/object:Gem::Version
203
+ version: '0'
204
+ requirements: []
205
+ rubygems_version: 3.6.3
206
+ specification_version: 4
207
+ summary: Integrate Litestream with the RubyGems infrastructure.
208
+ test_files: []