Build your own private gem server
With Bundler/Gemfile it’s easy to link gems directly to it’s git repository. But some times it’s not ideal. Revisions are not versions, and comparing them is tedious.
Luckly It’s easy to setup a private gem server!
There are services, e.g. GemFury that for a little cash gives you this kind of service.
But there are open source tools out there that you can combine to make the same service, for free, and fully customized.
1 The Gem Server
There is a awesome project called Geminabox that works some how like rubygems. Rubygems demands that every user needs an account for pushing gems, with privileges for it’s own gems. With gembox there is no user account, it takes into account that you trust the people you share the server. Supposing you trust your mates, this is a good option for your company gem server.
Geminabox is a sinatra app. The server configuration is pretty straight forward. Just create a config.ru to hook a rack server, unicorn for example.
gem install gembox
# Gemfile
source 'https://rubygems.org'
gem 'sinatra'
gem 'geminabox'
gem 'unicorn'
# Config.ru
require "bundler/setup"
require "rubygems"
require "geminabox"
Geminabox.data = "/var/gems" # …or wherever
run Geminabox
# unicorn.rb
worker_processes 2
preload_app true
shared_path = "/home/gem/deploy/shared"
current_path= "/home/gem/deploy/current"
# Restart any workers that haven't responded in 30 seconds
timeout 30
# Listen on a Unix data socket
listen "#{shared_path}/sockets/unicorn.sock", :backlog => 2048
user 'gem', 'gem'
stderr_path "#{current_path}/log/unicorn_site.stderr.log"
stdout_path "#{current_path}/log/unicorn_site.stdout.log"
pid File.join(shared_path, "pids/unicorn.pid")
before_fork do |server, worker|
old_pid = File.join(shared_path, 'pids/unicorn.pid.oldbin')
if File.exists?(old_pid) && server.pid != old_pid
begin
Process.kill("QUIT", File.read(old_pid).to_i)
rescue Errno::ENOENT, Errno::ESRCH
end
end
end
This is a basic setup. This is meant to run on the private network card, and exposed using nginx, with some basic authentication.
2 The HTTP Server & Privacy
Geminabox will be running behind nginx. On nginx it’s possible to add an hash in the url (like gemfury does), or use simple HTTP AUTH, or both.
It’s also possible to setup SSL for it, tip: StartSSL, they offer free SSL certificates.
Basic nginx configuration:
# nginx.conf
server {
listen 80;
#listen 443 ssl;
server_name gems.myself.com;
root /home/gem/deploy/current/public;
error_page 404 /404.html;
error_page 500 /500.html;
#ssl on;
#ssl_certificate /home/gem/.server_cert/server.crt;
#ssl_certificate_key /home/gem/.server_cert/server.key;
#ssl_session_timeout 5m;
#ssl_protocols SSLv2 SSLv3 TLSv1;
#ssl_ciphers HIGH:!aNULL:!MD5;
#ssl_prefer_server_ciphers on;
proxy_buffering on;
proxy_buffers 8 64k;
proxy_buffer_size 172k;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_ignore_headers Cache-Control;
proxy_set_header Host $http_host;
proxy_redirect off;
access_log /home/gem/deploy/shared/log/access_log;
client_max_body_size 10000k;
location /random_and_secret_string {
auth_basic "Restricted";
auth_basic_user_file /home/gem/deploy/current/config/http/authentication.htpasswd;
rewrite ^/random_and_secret_string/(.*) /$1 break;
proxy_pass http://unicorn_cluster;
}
location / {
return 404;
}
}
upstream unicorn_cluster {
server unix://home/gem/deploy/shared/sockets/unicorn.sock;
}
This is really standard configuration, you are probably familiar with.
Replace random_and_secret_string
with your own secret hash, nginx shall proxy everything that is behind that hash to the geminabox server.
I’ve also added HTTP Auth to the directory, it will require the user/password to access the gems.
3 Using the Gem Server
With the server up.
3.1 Pushing Gems
For pushing gems, it also requires the geminabox
gem in the developer machine.
It adds the new option inabox
to the gem command.
It will ask about the server url the first time you run this command.
Supposing that the server is running at gems.myself.com, without SSL, and with http auth (user1:password2), and our secret hash is 1234567890
:
gem inabox pkg/my_private_gem-0.0.1.gem
Enter the root url for your personal geminabox instance. (E.g. http://gems/)
Host: http://user1:password2@gems.myself.com/1234567890/
This will push the gems. There’s also a webinterface for administration (same url as above).
3.2 Requesting gems from Gemfile
Adding the new gem server to a Gemfile is easy:
# Gemfile
source 'http://user1:password2@gems.myself.com/1234567890/'
source 'https://rubygems.org'
...
gem 'my_private_gem', '>= 0.0.1'
And it’s done. I’ve been using this approach for almost one year, and it works like perfectly. You can find the source for these files in this Git Repository.