GitWit

Build Status Code Climate Gem Version

Dead simple Git hosting for Rails apps.

Crash Course

Start hosting git repositories in seconds. Create a new Rails app and install GitWit:

$ rails new example --skip-bundle; cd example       # New Rails app
$ echo 'gem "git_wit"' >> Gemfile; bundle           # Install git_wit gem
$ rails g git_wit:install insecure_auth insecure_write authenticate authorize_read authorize_write
$ rails s -d  # <- Start Rails server               # ^- Install/config GitWit

That's it - your app is hosting git repositories. Create a repositories folder, init a bare repo, and push to it:

$ git init; git add .; git commit -m "That was easy"
$ mkdir repositories                                # Hosted repos folder
$ git init --bare repositories/example.git          # Example bare repo
$ git remote add origin http://localhost:3000/example.git
$ git push origin master  # Push example app to itself to store in itself!

HTTPS? That works too:

$ sudo echo "pre-loading sudo so we can background tunnels in a moment"
$ rails g git_wit:install authenticate authorize_read authorize_write -f
$ echo 'gem "tunnels"' >> Gemfile; bundle
$ sudo tunnels 443 3000 &       # or `rvmsudo tunnels...` if using RVM
$ git remote add https https://localhost/example.git
$ GIT_SSL_NO_VERIFY=1 git push https master:https-master  # Trust yourself

Still not impressed? Try SSH:

$ rails g git_wit:install authenticate authorize_read authorize_write ssh_user:git_wit -f
$ rails g git_wit:ssh_user      # Creates/configs git_wit SSH user
$ rake git_wit:ssh:add_key      # Grant access for ~/.ssh/id_rsa.pub
$ git remote add ssh git_wit@localhost:example.git
$ git push ssh master:ssh-master

You might want to get rid of that system user you just created:

$ rails d git_wit:ssh_user

Overview

GitWit adds git hosting abilities to any Rails app. It provides configurable authentication and authorization methods that can be integrated with any user/repository access model you'd like. All configuration is handled through a single initializer, config/initializers/git_wit.rb. Run rails g git_wit:install to generate a default configuration for modification. All configuration details are contained within comments inside the initializer, or read on for the highlights.

Authentication

Normally GitWit prevents the user from sending authentication credentials in plaintext (via HTTP without SSL). To disable these protections, something you'd never do in a production environment, change the following config values in the initializer:

config.insecure_auth = true
config.insecure_write = true

Authentication is handled by the config.authenticate attribute. A valid authenticator is any callable that accepts a user model instance and a clear-text password. The authenticator should return a boolean response indicating whether the user is authenticated for the given password. To allow any user as long as the password matches the username:

config.authenticate = ->(user, password) do
  user == password
end

The user model is simply the username as a string by default. Before passing the user to the authenticator, GitWit will call config.user_for_authenication, passing it the username and expecting a new user model instance in return. For example:

config.user_for_authentication = ->(username) do
  User.active. username: username
end

Now the config.authenticate authenticator will recieve the User instance:

config.authenticate = ->(user, password) do
  user.valid_password? password   # user is a User
end

Authorization

Two configuration attributes are responsible for authorization: config.authorize_read and config.authorize_write. They're passed the user instance (already authenticated) and the repository path as a string. The repository path is relative to config.repositories_path (<app root>/repositories by default). The authorizers should return a boolean to grant or deny access accordingly. A simple example:

config.authorize_read = ->(user, repository) do
  %w(reader writer).include?(user)
end

config.authorize_write = ->(user, repository) do
  user == "writer"
end

A quick note about "local requests"

The default Rails development environment has a config value called consider_all_requests_local, which is true. This prevents GitWit from correctly handling authentication responses in some cases. It's not a big deal, you'll just be asked to re-authenticate more often and some responses will be slightly misleading. But the alternative solution, which is to set consider_all_requests_local to false, disables any special Rails error handling - quite a bummer for development. It would be nice to sort this out a little better in the future. Note that the production environment uses false by default and handles errors appropriately.

Advanced Usage (Devise, Cancan, etc.)

See test/dummy for an example app that integrates Devise, Cancan, rolify and twitter-bootstrap-rails. Example controllers for managing repositories and public keys are included.

SSH support - AKA: The hard part

To enable git operations over SSH, you must have a dedicated SSH user. This user will only be used for SSH authentication. Immediately after successfully authenticating, the SSH user will sudo to the application user to continue with the git operation. This eliminates the need for all the bat-shit crazy git pulls/pushes and SSH wrappers and crap that are typical of gitolite/gitosis setups. Your application user owns everything except the authorized_keys file and the ssh_user only needs to know how to call the git_wit git-shell command.

GitWit comes with an initializer to set everything up for you. First, enable the ssh_user config in config/initializers/git_wit.rb:

config.ssh_user = "git_wit"

Now run the initializer:

$ rails g git_wit:ssh_user

To add a public key: rake git_wit:ssh:add_key

Something not working? rake git_wit:ssh:debug

Git hooks and configs and umasks and everything

Dude, your app owns the repos now. Hooks are just files again! Rediscover the grit gem and go nuts with all kinds of fun stuff that used to be a serious pain. Paranoid? Lock down the permissions on your repositories folder so that only your application user can read it. The SSH shell will still be executed as the application user so it's no sweat.

This project rocks and uses MIT-LICENSE.