gem_mirror 0.1.0.pre

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 9dc1e0fb7c8d5bee7140b693cb630b95b69f5e9ffb23fe31478759a311748c11
4
+ data.tar.gz: 909b09e6a4dab1184d83dde3004376491c5066ef54d9c8f55b036a90a7148c4c
5
+ SHA512:
6
+ metadata.gz: 359b87727794e927bb4c9332dbae112bfe01e7bd578a99f574d4a0ad58d7a5749897d8975c7f8fff0637b8084806acd3390266191b0392d95c589bb12c4486a2
7
+ data.tar.gz: 376d68180e4fa30da16ae889952c43254be87e7d34ddefca526cad22085c6bdd5d72c8490072f528c23c5a85d8ec439068a0d5a97ea6d43f4ed2e19ad625916b
data/CHANGELOG.md ADDED
@@ -0,0 +1,7 @@
1
+ ## 0.1.0.pre
2
+
3
+ ### Changes
4
+
5
+ - Refactoring and bringing code up to date with modern Ruby
6
+ - Addressed over 250 rubocop recommendations
7
+ - Dropped legacy gem index option
data/LICENSE.txt ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2013, Yorick Peterse
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,85 @@
1
+ # Gem Mirror
2
+
3
+ gem_mirror is a Ruby application that makes it easy to create your own RubyGems
4
+ mirror without having to stitch together ugly Bash scripts or deal with more
5
+ complex tools such as [Geminabox][geminabox]. Unlike tools such as Geminabox
6
+ and others gem_mirror does mirroring only, it has no authentication and you
7
+ can't upload gems to it. I created this project to improve my knowledge of Ruby
8
+ and Ruby infrastructure.
9
+
10
+ It was forked from the now archived github project which has also moved
11
+ to gitlab [gem-mirror].
12
+
13
+ ## Differences with Geminabox
14
+
15
+ Geminabox is basically a tiny DIY RubyGems that focuses more on uploading your
16
+ own gems (e.g. company specific ones) instead of mirroring an external source.
17
+ gem_mirror however purely focuses on generating a mirror that can be used by
18
+ the RubyGems CLI utility.
19
+
20
+ ## Differences with gem-server
21
+
22
+ RubyGems comes with the command `gem-server` which can be used to serve the
23
+ Gems you have installed on that machine and user. Similar to Geminabox it does
24
+ not provide the means to actually mirror an external source. It's also Rack
25
+ based and uses WEBRick, which isn't exactly the best server around.
26
+
27
+ ## How do I use it?
28
+
29
+ The process of setting up a mirror is fairly easy and can be done in a matter
30
+ of minutes depending on the number of gems you are mirroring and your internet
31
+ speed.
32
+
33
+ The first step is to set up a new, empty mirror. This is done by running the
34
+ `gem_mirror init` command similar to how you initialize a new Git repository:
35
+
36
+ $ gem_mirror init /srv/http/mirror.com/
37
+
38
+ Once created, you should edit the main configuration file named `config.rb`.
39
+ This configuration file specifies what sources to mirror (you can mirror
40
+ multiple ones!), which gems, etc.
41
+
42
+ Once configured you can pull all the gems by running the following command:
43
+
44
+ $ gem_mirror update
45
+
46
+ If your configuration file is not located in the current directory you can
47
+ specify the path to it using the `-c` or `--config` option. The process of
48
+ downloading all the Gems takes a while and can use quite a bit of memory so
49
+ make sure you have plenty of time and RAM available.
50
+
51
+ Once all the Gems have been downloaded you'll need to generate an index of all
52
+ the installed files. This can be done as following:
53
+
54
+ $ gem_mirror index
55
+
56
+ Last but not least you may want to generate a list of SHA512 checksums. Although
57
+ RubyGems doesn't use these they might come in handy when verifying Gems (e.g.
58
+ when RubyGems gets hacked again). This can be done using the following command:
59
+
60
+ $ gem_mirror checksum
61
+
62
+ If you want to do all this automatically (which is recommended) you can use a
63
+ simple Cronjob like the following one:
64
+
65
+ @hourly cd /srv/http/mirror.com && gem_mirror update && gem_mirror index
66
+
67
+ ## Requirements
68
+
69
+ * Ruby 2.5.0 or newer
70
+ * Enough space to store Gems
71
+
72
+ ## Installation
73
+
74
+ Assuming RubyGems isn't down you can install the Gem as following:
75
+
76
+ $ gem install gem_mirror
77
+
78
+ ## License
79
+
80
+ All source code in this repository is licensed under the MIT license unless
81
+ specified otherwise. A copy of this license can be found in the file "LICENSE"
82
+ in the root directory of this repository.
83
+
84
+ [geminabox]: https://github.com/geminabox/geminabox
85
+ [gem-mirror]: https://gitlab.com/yorickpeterse/gem-mirror
data/exe/gem_mirror ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # frozen_string_literal: true
4
+
5
+ require File.expand_path("../../lib/gem_mirror", __FILE__)
6
+
7
+ GemMirror::CLI.options.parse
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/gem_mirror/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "gem_mirror"
7
+ s.version = GemMirror::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.required_ruby_version = Gem::Requirement.new(">= 2.5.0")
10
+ s.authors = ["Yorick Peterse", "Patrick Callahan"]
11
+ s.description = <<~DESCRIPTION
12
+ gem-mirror is a tool for creating and managing a private
13
+ mirror of RubyGems. It is also suitable for hosting private
14
+ gems.
15
+ DESCRIPTION
16
+
17
+ s.email = ["pmc@patrickcallahan.com"]
18
+ s.files = Dir["{assets,config,lib,template}/**/*"] + ["gem_mirror.gemspec"]
19
+ s.files += %w[CHANGELOG.md LICENSE.txt README.md]
20
+ s.require_paths = ["lib"]
21
+ s.bindir = "exe"
22
+ s.executables = %w[gem_mirror]
23
+ s.extra_rdoc_files = %w[CHANGELOG.md LICENSE.txt README.md]
24
+ s.homepage = "https://github.com/dirtyharrycallahan/gem_mirror"
25
+ s.licenses = ["MIT"]
26
+ s.summary = %q{A tool for creating and managing a private rubygems mirror.}
27
+
28
+ s.rdoc_options += [
29
+ "--title", "gem_mirror - A Tool for Managing a Private Gem Repository",
30
+ "--main", "README.md",
31
+ "--line-numbers",
32
+ "--inline-source",
33
+ "--quiet"
34
+ ]
35
+ s.metadata["allowed_push_host"] = "https://rubygems.org"
36
+ s.metadata["changelog_uri"] = "https://github.com/dirtyharrycallahan/gem_mirror/blob/master/CHANGELOG.md"
37
+ s.metadata["homepage_uri"] = s.homepage
38
+ s.metadata["source_code_uri"] = s.homepage
39
+ s.metadata["bug_tracker_uri"] = "https://github.com/dirtyharrycallahan/gem_mirror/issues"
40
+
41
+ s.add_runtime_dependency("confstruct", "~> 1.0", "< 2")
42
+ s.add_runtime_dependency("httpclient", "~> 2.8", "< 3")
43
+ s.add_runtime_dependency("slop", "= 3.6")
44
+
45
+ s.add_development_dependency("bundler", ">= 1.17.3", "< 3.0")
46
+ s.add_development_dependency("rake", ">= 13.0.0", "< 14")
47
+ s.add_development_dependency("rake-manifest", "~> 0.2.0")
48
+ s.add_development_dependency("redcarpet", "~> 3.5.0")
49
+ s.add_development_dependency("rspec", "~> 3.2")
50
+ s.add_development_dependency("rubocop", "= 1.16.0")
51
+ s.add_development_dependency("rubocop-performance", "= 1.11.3")
52
+ s.add_development_dependency("rubocop-rake", "= 0.5.1")
53
+ s.add_development_dependency("rubocop-rspec", "= 2.3.0")
54
+ s.add_development_dependency("yard", "~> 0.9.0")
55
+ end
data/lib/gem_mirror.rb ADDED
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rubygems"
4
+ require "rubygems/user_interaction"
5
+ require "rubygems/indexer"
6
+ require "fileutils"
7
+ require "digest/sha2"
8
+ require "confstruct"
9
+ require "slop"
10
+ require "zlib"
11
+ require "httpclient"
12
+ require "logger"
13
+ require "stringio"
14
+
15
+ $LOAD_PATH.unshift(File.expand_path(__dir__)) unless $LOAD_PATH.include?(File.expand_path(__dir__))
16
+
17
+ require_relative "gem_mirror/version"
18
+ require_relative "gem_mirror/configuration"
19
+ require_relative "gem_mirror/gem"
20
+ require_relative "gem_mirror/source"
21
+ require_relative "gem_mirror/mirror_directory"
22
+ require_relative "gem_mirror/mirror_file"
23
+ require_relative "gem_mirror/versions_file"
24
+ require_relative "gem_mirror/versions_fetcher"
25
+ require_relative "gem_mirror/gems_fetcher"
26
+
27
+ require_relative "gem_mirror/cli"
28
+ require_relative "gem_mirror/cli/checksum"
29
+ require_relative "gem_mirror/cli/index"
30
+ require_relative "gem_mirror/cli/init"
31
+ require_relative "gem_mirror/cli/update"
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Namespace for GemMirror
4
+ module GemMirror
5
+ # Namespace for classes and modules that handle the command line interface
6
+ module CLI
7
+ ##
8
+ # Hash containing the default Slop options.
9
+ #
10
+ # @return [Hash]
11
+ #
12
+ SLOP_OPTIONS = {
13
+ strict: true,
14
+ banner: "Usage: gem_mirror [COMMAND] [OPTIONS]"
15
+ }.freeze
16
+
17
+ ##
18
+ # @return [Slop]
19
+ #
20
+ def self.options
21
+ @options ||= default_options
22
+ end
23
+
24
+ ##
25
+ # Loads the specified configuration file or displays an error if it doesn't
26
+ # exist.
27
+ #
28
+ # @param [String] config_file
29
+ # @return [GemMirror::Configuration]
30
+ #
31
+ def self.load_configuration(config_file)
32
+ config_file ||= Configuration.default_configuration_file
33
+ config_file = File.expand_path(config_file, Dir.pwd)
34
+
35
+ abort "The configuration file #{config_file} does not exist" unless File.file?(config_file)
36
+
37
+ require(config_file)
38
+ end
39
+
40
+ ##
41
+ # @return [Slop]
42
+ #
43
+ def self.default_options
44
+ Slop.new(SLOP_OPTIONS.dup) do
45
+ separator "\nOptions:\n"
46
+
47
+ on :h, :help, "Shows this help message" do
48
+ puts self
49
+ exit
50
+ end
51
+
52
+ on :v, :version, "Shows the current version" do
53
+ puts CLI.version_information
54
+ end
55
+ end
56
+ end
57
+
58
+ ##
59
+ # Returns a String containing some platform/version related information.
60
+ #
61
+ # @return [String]
62
+ #
63
+ def self.version_information
64
+ "gem_mirror v#{GemMirror::VERSION} on #{RUBY_DESCRIPTION}"
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ GemMirror::CLI.options.command "checksum" do
4
+ banner "Usage: gem_mirror checksum [OPTIONS]"
5
+ description "Generates SHA512 checksums of all gems"
6
+ separator "\nOptions:\n"
7
+
8
+ on :h, :help, "Shows this help message" do
9
+ puts self
10
+ exit
11
+ end
12
+
13
+ on :c=, :config=, "Path to the configuration file"
14
+
15
+ run do |opts, _args|
16
+ GemMirror::CLI.load_configuration(opts[:c])
17
+
18
+ config = GemMirror.configuration
19
+
20
+ unless File.directory?(config.checksums)
21
+ config.logger("The directory #{config.checksums} does not exist")
22
+ abort
23
+ end
24
+
25
+ unless File.directory?(config.destination)
26
+ config.logger("The directory #{config.destination} does not exist")
27
+ abort
28
+ end
29
+
30
+ Dir[File.join(config.gems_directory, "*.gem")].each do |gem|
31
+ basename = File.basename(gem)
32
+ name = "#{basename}.sha512"
33
+
34
+ config.logger.info("Creating checksum for #{basename}")
35
+
36
+ hash = Digest::SHA512.hexdigest(File.read(gem))
37
+ handle = File.open(File.join(config.checksums, name), "w")
38
+
39
+ handle.write(hash)
40
+ handle.close
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ GemMirror::CLI.options.command "index" do
4
+ banner "Usage: gem_mirror index [OPTIONS]"
5
+ description "Indexes a list of Gems"
6
+ separator "\nOptions:\n"
7
+
8
+ on :h, :help, "Shows this help message" do
9
+ puts self
10
+ exit
11
+ end
12
+
13
+ on :c=, :config=, "Path to the configuration file"
14
+
15
+ run do |opts, _args|
16
+ GemMirror::CLI.load_configuration(opts[:c])
17
+
18
+ config = GemMirror.configuration
19
+
20
+ unless File.directory?(config.destination)
21
+ config.logger.error("The directory #{config.destination} does not exist")
22
+ abort
23
+ end
24
+
25
+ indexer = Gem::Indexer.new(config.destination, build_legacy: opts[:l])
26
+
27
+ config.logger.info("Generating Indexes")
28
+
29
+ indexer.generate_index
30
+ end
31
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ GemMirror::CLI.options.command "init" do
4
+ banner "Usage: gem_mirror init [DIRECTORY] [OPTIONS]"
5
+ description "Sets up a new mirror"
6
+ separator "\nOptions:\n"
7
+
8
+ on :h, :help, "Shows this help message" do
9
+ puts self
10
+ exit
11
+ end
12
+
13
+ run do |_opts, args|
14
+ directory = File.expand_path(args[0] || Dir.pwd)
15
+ template = GemMirror::Configuration.template_directory
16
+
17
+ Dir.mkdir(directory) unless File.directory?(directory)
18
+
19
+ FileUtils.cp_r(File.join(template, "."), directory)
20
+
21
+ puts "Initialized empty mirror in #{directory}"
22
+ end
23
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ GemMirror::CLI.options.command "update" do
4
+ banner "Usage: gem_mirror update [OPTIONS]"
5
+ description "Updates the list of Gems"
6
+ separator "\nOptions:\n"
7
+
8
+ on :h, :help, "Shows this help message" do
9
+ puts self
10
+ exit
11
+ end
12
+
13
+ on :c=, :config=, "Path to the configuration file"
14
+
15
+ run do |opts, _args|
16
+ GemMirror::CLI.load_configuration(opts[:c])
17
+
18
+ GemMirror.configuration.sources.each do |source|
19
+ versions = GemMirror::VersionsFetcher.new(source).fetch
20
+ gems = GemMirror::GemsFetcher.new(source, versions)
21
+
22
+ gems.fetch
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,140 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Namespace for GemMirror
4
+ module GemMirror
5
+ ##
6
+ # @return [GemMirror::Configuration]
7
+ #
8
+ def self.configuration
9
+ @configuration ||= Configuration.new
10
+ end
11
+
12
+ ##
13
+ # Configuration class used for storing data about a mirror such as the
14
+ # destination directory, sources, ignored Gems, etc.
15
+ #
16
+ class Configuration < Confstruct::Configuration
17
+ ##
18
+ # @return [Logger]
19
+ #
20
+ def logger
21
+ @logger = Logger.new($stdout,
22
+ progname: "gem_mirror",
23
+ formatter: proc do |severity, datetime, progname, msg|
24
+ d_format = datetime.strftime("%Y%m%dT%H%M%S%z")
25
+ "[#{d_format}] severity=#{severity} prog=#{progname} pid=#{Process.pid} message=#{msg}\n"
26
+ end)
27
+ end
28
+
29
+ ##
30
+ # @return [String]
31
+ #
32
+ def self.template_directory
33
+ File.expand_path("../../template", __dir__)
34
+ end
35
+
36
+ ##
37
+ # @return [String]
38
+ #
39
+ def self.default_configuration_file
40
+ File.expand_path("config.rb", Dir.pwd)
41
+ end
42
+
43
+ ##
44
+ # Returns the name of the directory that contains the quick
45
+ # specification files.
46
+ #
47
+ # @return [String]
48
+ #
49
+ def self.marshal_identifier
50
+ "Marshal.#{marshal_version}"
51
+ end
52
+
53
+ ##
54
+ # Returns the name of the file that contains an index of all the versions.
55
+ #
56
+ # @return [String]
57
+ #
58
+ def self.versions_file
59
+ "specs.#{marshal_version}.gz"
60
+ end
61
+
62
+ ##
63
+ # Returns a String containing the Marshal version.
64
+ #
65
+ # @return [String]
66
+ #
67
+ def self.marshal_version
68
+ "#{Marshal::MAJOR_VERSION}.#{Marshal::MINOR_VERSION}"
69
+ end
70
+
71
+ ##
72
+ # @return [GemMirror::MirrorDirectory]
73
+ #
74
+ def mirror_directory
75
+ @mirror_directory ||= MirrorDirectory.new(gems_directory)
76
+ end
77
+
78
+ ##
79
+ # @return [String]
80
+ #
81
+ def gems_directory
82
+ File.join(destination, "gems")
83
+ end
84
+
85
+ ##
86
+ # Returns a Hash containing various Gems to ignore and their versions.
87
+ #
88
+ # @return [Hash]
89
+ #
90
+ def ignored_gems
91
+ @ignored_gems ||= Hash.new { |hash, key| hash[key] = [] }
92
+ end
93
+
94
+ ##
95
+ # Adds a Gem to the list of Gems to ignore.
96
+ #
97
+ # @param [String] name
98
+ # @param [String] version
99
+ #
100
+ def ignore_gem(name, version)
101
+ ignored_gems[name] ||= []
102
+ ignored_gems[name] << version
103
+ end
104
+
105
+ ##
106
+ # Checks if a Gem should be ignored.
107
+ #
108
+ # @param [String] name
109
+ # @param [String] version
110
+ # @return [TrueClass|FalseClass]
111
+ #
112
+ def ignore_gem?(name, version)
113
+ ignored_gems[name].include?(version)
114
+ end
115
+
116
+ ##
117
+ # Returns a list of sources to mirror.
118
+ #
119
+ # @return [Array]
120
+ #
121
+ def sources
122
+ @sources ||= []
123
+ end
124
+
125
+ ##
126
+ # Adds a new source to mirror.
127
+ #
128
+ # @param [String] name
129
+ # @param [String] url
130
+ # @param [Proc] block
131
+ # @yieldparam [GemMirror::Source] source
132
+ #
133
+ def source(name, url, &block)
134
+ source = Source.new(name, url)
135
+ source.instance_eval(&block)
136
+
137
+ sources << source
138
+ end
139
+ end
140
+ end