Really simple rubygem hosting
Geminabox lets you host your own gems, and push new gems to it just like with rubygems.org. The bundler dependencies API is supported out of the box. Authentication is left up to either the web server, or the Rack stack. For basic auth, try Rack::Auth.
gem install geminaboxCreate a config.ru as follows:
require "rubygems"
require "geminabox"
Geminabox.data = "/var/geminabox-data" # ... or wherever
# Use Rack::Protection to prevent XSS and CSRF vulnerability if your geminabox server is open public.
# Rack::Protection requires a session middleware, choose your favorite one such as Rack::Session::Memcache.
# This example uses Rack::Session::Pool for simplicity, but please note that:
# 1) Rack::Session::Pool is not available for multiprocess servers such as unicorn
# 2) Rack::Session::Pool causes memory leak (it does not expire stored `@pool` hash)
use Rack::Session::Pool, expire_after: 1000 # sec
use Rack::Protection
run Geminabox::ServerStart your gem server with 'rackup' to run WEBrick or hook up the config.ru as you normally would (passenger, thin, unicorn, whatever floats your boat).
Geminabox serves only the gems you push to it. To use it together with rubygems.org, declare it as a scoped source in your Gemfile so that every gem is pinned to exactly one source:
source "https://rubygems.org"
source "https://gems.example.com" do
gem "internal-widgets"
gem "internal-tools"
endThis pinning is Bundler's protection against dependency confusion: a gem name that exists on both servers can never silently resolve to the wrong one.
Earlier versions of Geminabox could proxy rubygems.org and serve remote and local gems from one merged namespace. The feature was deprecated in 3.1.0 and removed in 4.0:
/api/v1/dependencies), which was
sunset on 2023-05-24,
so the proxy had not worked for years.When upgrading, remove Geminabox.rubygems_proxy and its related settings
(rubygems_proxy_merge_strategy, allow_remote_failure, ruby_gems_url,
bundler_ruby_gems_url) from your config.ru, and point your Gemfile at
rubygems.org directly with a scoped source block as shown above. Leftover
settings won't break your server: 4.0 warns about each one and ignores it,
and the RUBYGEMS_PROXY and RUBYGEMS_PROXY_MERGE_STRATEGY environment
variables get the same startup warning. These shims go away in 5.0.
If you need a caching or mirroring proxy for rubygems.org (air-gapped networks, bandwidth, protection against upstream yanks), use gemstash, maintained by the RubyGems organization.
Geminabox serves the compact index API
(/versions, /info/GEMNAME, /names). Bundler 1.12+ detects and uses it
automatically; no client configuration is needed. Responses support
If-None-Match and ranged requests, so bundle install only downloads
index data that changed since the last run.
The materialized index lives in data/compact_index/ and is updated
whenever gems are added or removed. Deleting that directory (or hitting
/reindex) is safe; it is rebuilt from the stored gems on the next request.
On installs upgraded from an earlier version with many stored gems, the
first request to /versions builds the index and can take a while as it
checksums every stored gem, so hitting /reindex or /versions right
after upgrading avoids surprising the first bundle install.
A stock nginx or Passenger deployment needs no special configuration for the
compact index. gzip is safe to leave on, even for text/plain: Bundler sends
its ranged requests without Accept-Encoding, and nginx never compresses 206
responses, so 304 revalidation and ranged tail appends keep working.
Two things do interfere:
/versions, /info/*, or /names (nginx
proxy_cache, or a CDN). The files reference each other by checksum, so a
cache serving one fresh and another stale makes Bundler report checksum
mismatches. Bundler already caches and revalidates client-side; leave these
paths uncached.ETag
disables 304 revalidation; removing Repr-Digest/Digest makes Bundler
refuse to append partial responses and re-download the full file after
every change.gem inabox -o (and the allow_replace server option) overwrites a stored
gem in place: same version number, different contents. Geminabox updates the
compact index to match, including the new checksum. Bundler, however, guards
against a version's bytes changing.
Since 2.5, Bundler records each gem's checksum in the CHECKSUMS section of
Gemfile.lock. On a later resolve it compares the checksum the server now
advertises against the locked one, and if they differ for the same name and
version it aborts with Bundler::ChecksumMismatchError, treating the change as
a possible supply-chain swap. Nothing on the server can override this; the
conflict is between the client's lockfile and the replaced contents.
An overwrite is therefore transparent only to consumers who have not locked
that version yet. Anyone whose Gemfile.lock already pins it hits the error on
their next bundle install or bundle update. Their options are to remove
that gem's line from CHECKSUMS, delete the lockfile and re-resolve, or turn
off the check with bundle config set --local disable_checksum_validation true.
The clean fix is to bump the version instead of replacing it; treat -o as a
convenience for a private box whose gems nobody has locked yet.
Geminabox uses the HTTPClient gem to manage its connections to remote resources. The relationship is managed via Geminabox::HttpClientAdapter.
To configure options of HTTPClient, pass your own HTTPClient object in config.ru as:
# Geminabox.http_adapter = Geminabox::HttpClientAdapter.new # default
Geminabox.http_adapter.http_client = HTTPClient.new(ENV['http_proxy']).tap do |http_client|
http_client.transparent_gzip_decompression = true
http_client.keep_alive_timeout = 32 # sec
http_client.ssl_config.verify_mode = OpenSSL::SSL::VERIFY_NONE
http_client.send_timeout = 0
http_client.receive_timeout = 0
endIf you would like to use an alternative HTTP gem, create your own adapter and specify it in config.ru:
Geminabox.http_adapter = YourHttpAdapter.newIt is recommend (but not essential) that your adapter inherits from HttpAdapter. The adapter will need to replace HttpAdapter's methods with those specific to the alternative HTTP gem. It should also be able to handle HTTP proxy settings.
Defining your own adapter also allows you to configure Geminabox to use the local systems SSL certificates.
TemplateFaradayAdapter is provided as an example of an alternative HTTPAdapter.
You can add a hook (anything callable) which will be called when a gem is successfully received.
Geminabox.on_gem_received = Proc.new do |gem|
puts "Gem received: #{gem.spec.name} #{gem.spec.version}"
endTypically you might use this to push a notification to your team chat. Any exceptions which occur within the hook is silently ignored, so please ensure they are handled properly if this is not desirable.
Also, please note that this hook blocks POST /upload and POST /api/v1/gems APIs processing.
Hook authors are responsible to perform any action non-blocking/async to avoid HTTP timeout.
Since version 0.10, Geminabox supports the standard gemcutter push API:
gem push pkg/my-awesome-gem-1.0.gem --host HOSTYou can also use the gem plugin:
gem install geminabox
gem inabox pkg/my-awesome-gem-1.0.gemAnd since version 1.2.0, Geminabox supports the standard gemcutter yank API:
gem yank my-awesome-gem -v 1.0 --host HOSTConfigure Gem in a box (interactive prompt to specify where to upload to):
gem inabox -cChange the host to upload to:
gem inabox -g HOSTSimples!
Usage: gem inabox GEM [options]
Options:
-c, --configure Configure GemInABox
-g, --host HOST Host to upload to.
-o, --overwrite Overwrite Gem.
Common Options:
-h, --help Get help on this command
-V, --[no-]verbose Set the verbose level of output
-q, --quiet Silence commands
--config-file FILE Use this config file instead of default
--backtrace Show stack backtrace on errors
--debug Turn on Ruby debugging
Arguments:
GEM built gem to push up
Summary:
Push a gem up to your GemInABox
Description:
Push a gem up to your GemInABoxUsing Gem in a Box is really simple with the Dockerfile. Move this Dockerfile into a directory that you want to use for your server.
That directory only needs to contain:
config.ru (explained above)
Gemfile
Gemfile.lockYour Gemfile only needs:
source 'https://rubygems.org'
gem 'geminabox'
gem 'rackup'
gem 'webrick' # or other server you preferFrom there
docker build -t geminabox .docker run -d -p 9292:9292 geminabox:latestYour server should now be running!
Running rake will run the complete test suite.
The test suite uses
minitest-reporters
with the default reporter. To get more detailed test output, use rake MINITEST_REPORTER=SpecReporter. With this setting, output of the Geminabox
server that is started for integration tests is sent to stdout as well.
No open issues yet, or sync has not completed.