depot-linux 0.1.0

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.
Files changed (44) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +10 -0
  3. data/README.md +56 -0
  4. data/Rakefile +10 -0
  5. data/bin/depot +8 -0
  6. data/bin/depot-gui +14 -0
  7. data/bin/setup-rubyqt6 +72 -0
  8. data/fixtures/assets/download.png +0 -0
  9. data/fixtures/flatpakrefs/org.qbittorrent.qBittorrent.flatpakref +10 -0
  10. data/fixtures/rpms/Modrinth App-0.13.14-1.x86_64.rpm +0 -0
  11. data/lib/depot/app_customizer.rb +152 -0
  12. data/lib/depot/assets.rb +11 -0
  13. data/lib/depot/backends/app_image.rb +210 -0
  14. data/lib/depot/backends/archive.rb +263 -0
  15. data/lib/depot/backends/deb.rb +265 -0
  16. data/lib/depot/backends/flatpak_ref.rb +123 -0
  17. data/lib/depot/backends/rpm.rb +280 -0
  18. data/lib/depot/backends/support.rb +39 -0
  19. data/lib/depot/cli.rb +344 -0
  20. data/lib/depot/desktop_entry.rb +37 -0
  21. data/lib/depot/doctor.rb +59 -0
  22. data/lib/depot/gui/app.rb +23 -0
  23. data/lib/depot/gui/drop_panel.rb +80 -0
  24. data/lib/depot/gui/main_window.rb +1196 -0
  25. data/lib/depot/inspection.rb +52 -0
  26. data/lib/depot/inspector.rb +387 -0
  27. data/lib/depot/installer.rb +54 -0
  28. data/lib/depot/manifest_store.rb +53 -0
  29. data/lib/depot/packages/archive.rb +188 -0
  30. data/lib/depot/packages/deb.rb +262 -0
  31. data/lib/depot/packages/flatpak_ref.rb +90 -0
  32. data/lib/depot/packages/rpm.rb +301 -0
  33. data/lib/depot/paths.rb +57 -0
  34. data/lib/depot/result.rb +13 -0
  35. data/lib/depot/sandbox.rb +285 -0
  36. data/lib/depot/settings.rb +43 -0
  37. data/lib/depot/source_resolver.rb +36 -0
  38. data/lib/depot/uninstaller.rb +90 -0
  39. data/lib/depot/update_downloader.rb +136 -0
  40. data/lib/depot/updater.rb +230 -0
  41. data/lib/depot/util.rb +33 -0
  42. data/lib/depot/version.rb +5 -0
  43. data/lib/depot.rb +21 -0
  44. metadata +139 -0
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "uri"
4
+
5
+ module Depot
6
+ module SourceResolver
7
+ module_function
8
+
9
+ def resolve(source)
10
+ source = source.to_s
11
+ return source if !source.empty? && File.exist?(source)
12
+
13
+ fixture_match(source)
14
+ end
15
+
16
+ def url?(source)
17
+ uri = URI.parse(source.to_s)
18
+ uri.absolute? && !uri.scheme.to_s.empty?
19
+ rescue URI::InvalidURIError
20
+ false
21
+ end
22
+
23
+ def https_url?(source)
24
+ uri = URI.parse(source.to_s)
25
+ uri.is_a?(URI::HTTPS)
26
+ rescue URI::InvalidURIError
27
+ false
28
+ end
29
+
30
+ def fixture_match(source)
31
+ return nil if source.empty?
32
+
33
+ Dir[File.expand_path("../../fixtures/**/#{File.basename(source)}", __dir__)].first
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fileutils"
4
+ require "open3"
5
+ require_relative "manifest_store"
6
+ require_relative "result"
7
+
8
+ module Depot
9
+ class Uninstaller
10
+ def self.uninstall(app_id)
11
+ new.uninstall(app_id)
12
+ end
13
+
14
+ def initialize(store: ManifestStore.new)
15
+ @store = store
16
+ end
17
+
18
+ def uninstall(app_id)
19
+ manifest = @store.find(app_id)
20
+ return Result.err("No installed app found for #{app_id}") unless manifest
21
+
22
+ flatpak = uninstall_flatpak(manifest)
23
+ return flatpak unless flatpak.ok?
24
+
25
+ deleted = []
26
+ manifest.fetch("created_files", []).each do |path|
27
+ next unless safe_delete_file?(path)
28
+
29
+ FileUtils.rm_f(path)
30
+ deleted << path
31
+ end
32
+
33
+ manifest.fetch("created_dirs", []).reverse_each do |path|
34
+ next unless safe_delete_dir?(path)
35
+
36
+ FileUtils.rm_rf(path) if Dir.exist?(path)
37
+ rescue SystemCallError
38
+ nil
39
+ end
40
+
41
+ @store.delete(app_id)
42
+ Result.ok({ "app_id" => app_id, "deleted_files" => deleted })
43
+ rescue SystemCallError => e
44
+ Result.err("Uninstall failed: #{e.message}")
45
+ end
46
+
47
+ private
48
+
49
+ def uninstall_flatpak(manifest)
50
+ return Result.ok unless manifest["backend"] == "flatpak"
51
+
52
+ flatpak_id = manifest.dig("flatpak", "app_id") || manifest.dig("package", "Name")
53
+ return Result.err("Flatpak manifest is missing the Flatpak app ID.") if flatpak_id.to_s.empty?
54
+ return Result.err("Flatpak is not installed on this system.") unless command_available?("flatpak")
55
+
56
+ stdout, stderr, status = Open3.capture3("flatpak", "uninstall", "--user", "--noninteractive", "-y", flatpak_id)
57
+ return Result.ok if status.success?
58
+
59
+ message = [stderr, stdout].map(&:to_s).find { |text| text.strip != "" } || "flatpak uninstall failed"
60
+ Result.err("Flatpak uninstall failed: #{message.strip}")
61
+ end
62
+
63
+ def safe_delete_file?(path)
64
+ return false unless path.is_a?(String) && !path.empty?
65
+ return false unless File.file?(path) || File.symlink?(path)
66
+
67
+ expanded = File.expand_path(path)
68
+ allowed_roots.any? { |root| expanded.start_with?(root + File::SEPARATOR) }
69
+ end
70
+
71
+ def safe_delete_dir?(path)
72
+ return false unless path.is_a?(String) && !path.empty?
73
+
74
+ expanded = File.expand_path(path)
75
+ expanded.start_with?(File.expand_path(Depot::Paths.apps_dir) + File::SEPARATOR)
76
+ end
77
+
78
+ def allowed_roots
79
+ @allowed_roots ||= [
80
+ Depot::Paths.data_dir,
81
+ Depot::Paths.desktop_entries_dir,
82
+ Depot::Paths.icon_root
83
+ ].map { |path| File.expand_path(path) }
84
+ end
85
+
86
+ def command_available?(command)
87
+ ENV.fetch("PATH", "").split(File::PATH_SEPARATOR).any? { |dir| File.executable?(File.join(dir, command)) }
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,136 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "digest"
4
+ require "fileutils"
5
+ require "net/http"
6
+ require "tmpdir"
7
+ require "uri"
8
+ require_relative "result"
9
+ require_relative "version"
10
+
11
+ module Depot
12
+ module UpdateDownloader
13
+ MAX_DOWNLOAD_BYTES = 2 * 1024 * 1024 * 1024
14
+ MAX_REDIRECTS = 5
15
+ OPEN_TIMEOUT_SECONDS = 15
16
+ READ_TIMEOUT_SECONDS = 60
17
+ KNOWN_SUFFIXES = [
18
+ ".flatpakref",
19
+ ".AppImage",
20
+ ".appimage",
21
+ ".tar.gz",
22
+ ".tgz",
23
+ ".tar.xz",
24
+ ".txz",
25
+ ".tar.zst",
26
+ ".tzst",
27
+ ".deb",
28
+ ".rpm"
29
+ ].freeze
30
+
31
+ module_function
32
+
33
+ def download(url, max_bytes: MAX_DOWNLOAD_BYTES, redirects: MAX_REDIRECTS)
34
+ return Result.err("Update downloader requires a block.") unless block_given?
35
+
36
+ uri = parse_https_url(url)
37
+ return uri unless uri.ok?
38
+
39
+ Dir.mktmpdir("depot-update-") do |dir|
40
+ path = File.join(dir, "package#{suffix_for(uri.value.path)}")
41
+ result = download_to(uri.value, path, max_bytes:, redirects:)
42
+ return result unless result.ok?
43
+
44
+ yield path, result.value
45
+ end
46
+ rescue SystemCallError => e
47
+ Result.err("Could not download update: #{e.message}")
48
+ end
49
+
50
+ def https_url?(url)
51
+ parse_https_url(url).ok?
52
+ end
53
+
54
+ def parse_https_url(url)
55
+ uri = URI.parse(url.to_s)
56
+ return Result.err("Update URL must use https://.") unless uri.is_a?(URI::HTTPS)
57
+ return Result.err("Update URL is missing a host.") if uri.host.to_s.empty?
58
+
59
+ Result.ok(uri)
60
+ rescue URI::InvalidURIError
61
+ Result.err("Update URL must be a valid https:// URL.")
62
+ end
63
+
64
+ def download_to(uri, path, max_bytes:, redirects:)
65
+ return Result.err("Update download redirected too many times.") if redirects.negative?
66
+
67
+ http = Net::HTTP.new(uri.host, uri.port)
68
+ http.use_ssl = true
69
+ http.open_timeout = OPEN_TIMEOUT_SECONDS
70
+ http.read_timeout = READ_TIMEOUT_SECONDS
71
+
72
+ request = Net::HTTP::Get.new(uri)
73
+ request["User-Agent"] = "Depot/#{Depot::VERSION}"
74
+ result = nil
75
+ http.request(request) do |response|
76
+ result = case response
77
+ when Net::HTTPSuccess
78
+ stream_response(uri, response, path, max_bytes)
79
+ when Net::HTTPRedirection
80
+ follow_redirect(uri, response, path, max_bytes:, redirects:)
81
+ else
82
+ Result.err("Update download failed: HTTP #{response.code}")
83
+ end
84
+ end
85
+ result || Result.err("Update download failed before Depot received a response.")
86
+ rescue Timeout::Error, IOError, SystemCallError => e
87
+ Result.err("Update download failed: #{e.message}")
88
+ end
89
+
90
+ def follow_redirect(uri, response, path, max_bytes:, redirects:)
91
+ location = response["location"].to_s
92
+ return Result.err("Update download redirected without a location.") if location.empty?
93
+
94
+ redirected = URI.join(uri, location)
95
+ return Result.err("Update redirects must stay on https://.") unless redirected.is_a?(URI::HTTPS)
96
+
97
+ download_to(redirected, path, max_bytes:, redirects: redirects - 1)
98
+ rescue URI::InvalidURIError
99
+ Result.err("Update download redirected to an invalid URL.")
100
+ end
101
+
102
+ def stream_response(uri, response, path, max_bytes)
103
+ content_length = response["content-length"].to_i
104
+ if content_length.positive? && content_length > max_bytes
105
+ return Result.err("Update is too large to download safely (#{content_length} bytes).")
106
+ end
107
+
108
+ digest = Digest::SHA256.new
109
+ bytes = 0
110
+ File.open(path, "wb") do |file|
111
+ response.read_body do |chunk|
112
+ bytes += chunk.bytesize
113
+ return Result.err("Update exceeded Depot's #{max_bytes} byte safety limit.") if bytes > max_bytes
114
+
115
+ digest.update(chunk)
116
+ file.write(chunk)
117
+ end
118
+ end
119
+
120
+ return Result.err("Update download was empty.") if bytes.zero?
121
+
122
+ Result.ok(
123
+ {
124
+ "url" => uri.to_s,
125
+ "size" => bytes,
126
+ "sha256" => digest.hexdigest
127
+ }
128
+ )
129
+ end
130
+
131
+ def suffix_for(path)
132
+ basename = File.basename(path.to_s)
133
+ KNOWN_SUFFIXES.find { |suffix| basename.end_with?(suffix) } || File.extname(basename)
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,230 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "open3"
4
+ require "time"
5
+ require_relative "installer"
6
+ require_relative "manifest_store"
7
+ require_relative "result"
8
+ require_relative "settings"
9
+ require_relative "source_resolver"
10
+ require_relative "update_downloader"
11
+ require_relative "uninstaller"
12
+
13
+ module Depot
14
+ class Updater
15
+ def self.update(app_id, options = {})
16
+ new.update(app_id, options)
17
+ end
18
+
19
+ def self.update_all(options = {})
20
+ new.update_all(options)
21
+ end
22
+
23
+ def self.set_source(app_id, url)
24
+ new.set_source(app_id, url)
25
+ end
26
+
27
+ def initialize(store: ManifestStore.new, settings: Settings.new)
28
+ @store = store
29
+ @settings = settings
30
+ end
31
+
32
+ def records
33
+ @store.all.map { |manifest| update_record(manifest) }
34
+ end
35
+
36
+ def update(app_id, options = {})
37
+ return Result.err("Updates are disabled in Depot settings.") unless updates_enabled?(options)
38
+
39
+ manifest = @store.find(app_id)
40
+ return Result.err("No installed app found for #{app_id}") unless manifest
41
+
42
+ case manifest.fetch("backend", "")
43
+ when "flatpak"
44
+ update_flatpak(manifest)
45
+ else
46
+ reinstall_from_source(manifest, options)
47
+ end
48
+ end
49
+
50
+ def update_all(options = {})
51
+ return Result.err("Updates are disabled in Depot settings.") unless updates_enabled?(options)
52
+
53
+ results = records.map do |record|
54
+ next { "app_id" => record.fetch("app_id"), "ok" => false, "error" => record.fetch("status") } unless record.fetch("enabled")
55
+
56
+ result = update(record.fetch("app_id"), options)
57
+ { "app_id" => record.fetch("app_id"), "ok" => result.ok?, "error" => result.error, "value" => result.value }
58
+ end
59
+ failed = results.reject { |result| result.fetch("ok") }
60
+ return Result.err("#{failed.length} updates failed.", warnings: failed.map { |result| "#{result.fetch("app_id")}: #{result.fetch("error")}" }) unless failed.empty?
61
+
62
+ Result.ok({ "updated" => results })
63
+ end
64
+
65
+ def set_source(app_id, url)
66
+ manifest = @store.find(app_id)
67
+ return Result.err("No installed app found for #{app_id}") unless manifest
68
+
69
+ return Result.err("Update URL must use https://.") unless UpdateDownloader.https_url?(url)
70
+
71
+ update = manifest.fetch("update", {}).merge(
72
+ "source" => url,
73
+ "mechanism" => "url-download",
74
+ "status" => "ready",
75
+ "last_checked_at" => Time.now.utc.iso8601
76
+ )
77
+ @store.write(manifest.merge("update" => update))
78
+ Result.ok(@store.find(app_id))
79
+ end
80
+
81
+ private
82
+
83
+ def updates_enabled?(options)
84
+ return true if options[:force]
85
+
86
+ @settings.load.fetch("updates_enabled", true)
87
+ end
88
+
89
+ def update_record(manifest)
90
+ source = SourceResolver.resolve(manifest["install_source"])
91
+ update_url = update_url(manifest)
92
+ method = update_method(manifest)
93
+ enabled = method == "flatpak" || !source.nil? || !update_url.nil?
94
+ status = update_status(method, source, update_url, manifest)
95
+ {
96
+ "app_id" => manifest.fetch("app_id"),
97
+ "display_name" => manifest.fetch("display_name"),
98
+ "backend" => manifest.fetch("backend"),
99
+ "method" => method,
100
+ "enabled" => enabled,
101
+ "status" => status,
102
+ "last_updated_at" => manifest.dig("update", "last_updated_at")
103
+ }
104
+ end
105
+
106
+ def update_method(manifest)
107
+ return "flatpak" if manifest.fetch("backend") == "flatpak"
108
+ return "url-download" if update_url(manifest)
109
+
110
+ "reinstall-source"
111
+ end
112
+
113
+ def update_status(method, source, update_url, manifest)
114
+ return "Ready" if method == "flatpak"
115
+ return "Ready from URL" if update_url
116
+ return "Ready" if source
117
+
118
+ raw_source = manifest.dig("update", "source") || manifest["install_source"]
119
+ if SourceResolver.url?(raw_source)
120
+ "Only https update URLs are supported"
121
+ else
122
+ "Original installer is missing"
123
+ end
124
+ end
125
+
126
+ def update_url(manifest)
127
+ source = manifest.dig("update", "source")
128
+ source = manifest["install_source"] if source.to_s.empty?
129
+ return nil unless SourceResolver.https_url?(source)
130
+
131
+ source
132
+ end
133
+
134
+ def update_flatpak(manifest)
135
+ flatpak_id = manifest.dig("flatpak", "app_id") || manifest.dig("package", "Name")
136
+ return Result.err("Flatpak manifest is missing the Flatpak app ID.") if flatpak_id.to_s.empty?
137
+ return Result.err("Flatpak is not installed on this system.") unless command_available?("flatpak")
138
+
139
+ stdout, stderr, status = Open3.capture3("flatpak", "update", "--user", "--noninteractive", "-y", flatpak_id)
140
+ return Result.err("Flatpak update failed: #{command_message(stdout, stderr)}") unless status.success?
141
+
142
+ update_manifest(manifest, "flatpak", "updated")
143
+ Result.ok(@store.find(manifest.fetch("app_id")), warnings: [command_message(stdout, stderr)].reject(&:empty?))
144
+ end
145
+
146
+ def reinstall_from_source(manifest, options)
147
+ url = update_url(manifest)
148
+ return reinstall_from_url(manifest, url, options) if url
149
+
150
+ source = SourceResolver.resolve(manifest["install_source"])
151
+ return Result.err("Original installer is missing: #{manifest["install_source"]}") unless source
152
+
153
+ uninstall = Uninstaller.new(store: @store).uninstall(manifest.fetch("app_id"))
154
+ return uninstall unless uninstall.ok?
155
+
156
+ install = Installer.new(store: @store, settings: @settings).install(source, settings: @settings.load.merge(options.fetch(:settings, {})))
157
+ return install unless install.ok?
158
+
159
+ updated = install.value
160
+ update_manifest(updated, "reinstall-source", "updated")
161
+ Result.ok(@store.find(updated.fetch("app_id")), warnings: install.warnings)
162
+ end
163
+
164
+ def reinstall_from_url(manifest, url, options)
165
+ UpdateDownloader.download(url) do |path, metadata|
166
+ inspection = Inspector.inspect(path)
167
+ return inspection unless inspection.ok?
168
+
169
+ compatible = compatible_update?(manifest, inspection.value)
170
+ return Result.err(compatible) unless compatible == true
171
+
172
+ uninstall = Uninstaller.new(store: @store).uninstall(manifest.fetch("app_id"))
173
+ return uninstall unless uninstall.ok?
174
+
175
+ install = Installer.new(store: @store, settings: @settings).install(path, settings: @settings.load.merge(options.fetch(:settings, {})))
176
+ return install unless install.ok?
177
+
178
+ updated = install.value
179
+ stored = @store.find(updated.fetch("app_id")) || updated
180
+ @store.write(stored.merge("install_source" => url))
181
+ update_manifest(
182
+ @store.find(updated.fetch("app_id")) || stored,
183
+ "url-download",
184
+ "updated",
185
+ "source" => url,
186
+ "last_download_sha256" => metadata.fetch("sha256"),
187
+ "last_download_size" => metadata.fetch("size")
188
+ )
189
+ Result.ok(@store.find(updated.fetch("app_id")), warnings: install.warnings)
190
+ end
191
+ end
192
+
193
+ def compatible_update?(manifest, inspection)
194
+ expected = expected_formats(manifest.fetch("backend", ""))
195
+ return true if expected.include?(inspection.format)
196
+
197
+ "Downloaded update is #{inspection.format}, but #{manifest.fetch("display_name")} was installed as #{manifest.fetch("backend")}. Depot will not switch package families during an update."
198
+ end
199
+
200
+ def expected_formats(backend)
201
+ case backend
202
+ when "appimage" then ["appimage"]
203
+ when "deb-portable" then ["deb"]
204
+ when "rpm-portable" then ["rpm"]
205
+ when "archive-portable" then %w[tar.gz tar.xz tar.zst]
206
+ when "flatpak" then ["flatpakref"]
207
+ else [backend]
208
+ end
209
+ end
210
+
211
+ def update_manifest(manifest, method, status, extra = {})
212
+ stored = @store.find(manifest.fetch("app_id")) || manifest
213
+ update = stored.fetch("update", {}).merge(
214
+ "mechanism" => method,
215
+ "status" => status,
216
+ "last_checked_at" => Time.now.utc.iso8601,
217
+ "last_updated_at" => Time.now.utc.iso8601
218
+ ).merge(extra)
219
+ @store.write(stored.merge("update" => update))
220
+ end
221
+
222
+ def command_message(stdout, stderr)
223
+ [stderr, stdout].map(&:to_s).find { |text| text.strip != "" }.to_s.strip
224
+ end
225
+
226
+ def command_available?(command)
227
+ ENV.fetch("PATH", "").split(File::PATH_SEPARATOR).any? { |dir| File.executable?(File.join(dir, command)) }
228
+ end
229
+ end
230
+ end
data/lib/depot/util.rb ADDED
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "digest"
4
+
5
+ module Depot
6
+ module Util
7
+ module_function
8
+
9
+ def sha256(path)
10
+ Digest::SHA256.file(path).hexdigest
11
+ end
12
+
13
+ def slug(value)
14
+ base = File.basename(value.to_s, ".*").downcase
15
+ base = base.gsub(/[^a-z0-9]+/, "-").gsub(/\A-+|-+\z/, "")
16
+ base.empty? ? "app" : base
17
+ end
18
+
19
+ def unique_id(base, taken)
20
+ candidate = base
21
+ index = 2
22
+ while taken.include?(candidate)
23
+ candidate = "#{base}-#{index}"
24
+ index += 1
25
+ end
26
+ candidate
27
+ end
28
+
29
+ def desktop_exec_quote(path)
30
+ %("#{path.gsub(/(["\\`$])/, '\\\\\\1')}")
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Depot
4
+ VERSION = "0.1.0"
5
+ end
data/lib/depot.rb ADDED
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "depot/version"
4
+ require_relative "depot/paths"
5
+ require_relative "depot/result"
6
+ require_relative "depot/settings"
7
+ require_relative "depot/manifest_store"
8
+ require_relative "depot/assets"
9
+ require_relative "depot/packages/archive"
10
+ require_relative "depot/packages/deb"
11
+ require_relative "depot/packages/rpm"
12
+ require_relative "depot/packages/flatpak_ref"
13
+ require_relative "depot/inspector"
14
+ require_relative "depot/installer"
15
+ require_relative "depot/uninstaller"
16
+ require_relative "depot/app_customizer"
17
+ require_relative "depot/doctor"
18
+ require_relative "depot/source_resolver"
19
+ require_relative "depot/sandbox"
20
+ require_relative "depot/update_downloader"
21
+ require_relative "depot/updater"
metadata ADDED
@@ -0,0 +1,139 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: depot-linux
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - netizensnoopy
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: rubyzip
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '0'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '0'
26
+ - !ruby/object:Gem::Dependency
27
+ name: toml
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: rake
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '13.0'
47
+ type: :development
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '13.0'
54
+ - !ruby/object:Gem::Dependency
55
+ name: rspec
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '3.0'
61
+ type: :development
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '3.0'
68
+ description: Install AppImage, .deb, .rpm, .flatpakref, tar.gz on Linux without root
69
+ email:
70
+ - netizensnoopy@users.noreply.github.com
71
+ executables:
72
+ - depot
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - Gemfile
77
+ - README.md
78
+ - Rakefile
79
+ - bin/depot
80
+ - bin/depot-gui
81
+ - bin/setup-rubyqt6
82
+ - fixtures/assets/download.png
83
+ - fixtures/flatpakrefs/org.qbittorrent.qBittorrent.flatpakref
84
+ - fixtures/rpms/Modrinth App-0.13.14-1.x86_64.rpm
85
+ - lib/depot.rb
86
+ - lib/depot/app_customizer.rb
87
+ - lib/depot/assets.rb
88
+ - lib/depot/backends/app_image.rb
89
+ - lib/depot/backends/archive.rb
90
+ - lib/depot/backends/deb.rb
91
+ - lib/depot/backends/flatpak_ref.rb
92
+ - lib/depot/backends/rpm.rb
93
+ - lib/depot/backends/support.rb
94
+ - lib/depot/cli.rb
95
+ - lib/depot/desktop_entry.rb
96
+ - lib/depot/doctor.rb
97
+ - lib/depot/gui/app.rb
98
+ - lib/depot/gui/drop_panel.rb
99
+ - lib/depot/gui/main_window.rb
100
+ - lib/depot/inspection.rb
101
+ - lib/depot/inspector.rb
102
+ - lib/depot/installer.rb
103
+ - lib/depot/manifest_store.rb
104
+ - lib/depot/packages/archive.rb
105
+ - lib/depot/packages/deb.rb
106
+ - lib/depot/packages/flatpak_ref.rb
107
+ - lib/depot/packages/rpm.rb
108
+ - lib/depot/paths.rb
109
+ - lib/depot/result.rb
110
+ - lib/depot/sandbox.rb
111
+ - lib/depot/settings.rb
112
+ - lib/depot/source_resolver.rb
113
+ - lib/depot/uninstaller.rb
114
+ - lib/depot/update_downloader.rb
115
+ - lib/depot/updater.rb
116
+ - lib/depot/util.rb
117
+ - lib/depot/version.rb
118
+ homepage: https://github.com/netizensnoopy/depot
119
+ licenses:
120
+ - MIT
121
+ metadata: {}
122
+ rdoc_options: []
123
+ require_paths:
124
+ - lib
125
+ required_ruby_version: !ruby/object:Gem::Requirement
126
+ requirements:
127
+ - - ">="
128
+ - !ruby/object:Gem::Version
129
+ version: '3.0'
130
+ required_rubygems_version: !ruby/object:Gem::Requirement
131
+ requirements:
132
+ - - ">="
133
+ - !ruby/object:Gem::Version
134
+ version: '0'
135
+ requirements: []
136
+ rubygems_version: 3.6.9
137
+ specification_version: 4
138
+ summary: Unified Linux app installer
139
+ test_files: []