GitWit
Dead simple Git hosting for Rails apps.
Quickstart
Create a Rails 3.2 app if you don't already have one. Add git_wit
to your
Gemfile:
# Use github for now - early development:
gem "git_wit", git: "https://github.com/xdissent/git_wit.git"
# Later it might be safe to use a rubygems release:
# gem "git_wit", "~> 0.1.0"
Run bundle install
followed by rails g git_wit:install
and then checkout
config/initializers/git_wit.rb
.
You'll want to first change config.repositories_path
to a folder where you'd
like to store your repositories. Let's use "tmp/repositories" in our app root
for fun:
config.repositories_path = Rails.root.join("tmp", "repositories").to_s
Normally you wouldn't want to allow users to send their authentication credentials over an insecure protocol like HTTP, because they'll be sent in plain text over the wire. And since anonymous write access is always disallowed, that means you can't safely push over 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
Now let's set up some simple (fake) authentication and authorization:
config.authenticate = ->(user, password) do
%w(reader writer).include?(user) && user == password
end
config. = ->(user, repository) do
%w(reader writer).include?(user)
end
config. = ->(user, repository) do
user == "writer"
end
What we've done is effectively create two users: reader
and writer
. Both can
read all repositories, but only writer
may write (and can write to any repo.)
Both users are considered authenticated if the password matches the username.
Now your app is ready to start serving git repos over HTTP. Just create the repositories folder, initialize a repo and start the server:
$ mkdir -p tmp/repositories
$ git init --bare tmp/repositories/example.git
$ rails s
Clone your repo, make some changes, and push:
$ git clone http://localhost:3000/example.git
$ cd example
$ touch README
$ git add README
$ git commit -m "First"
$ git push origin master
Your server will ask you for a username and password when you push - use
writer
for both and it should accept your changes.
Advanced Usage (Devise, Cancan, etc.)
See test/dummy
for an example app that integrates
Devise,
Cancan, 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 autentication. 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 gw-shell
command.
First, create a dedicated SSH user. On Mountain Lion:
$ sudo dscl . -create /Groups/gitwit
$ sudo dscl . -create /Groups/gitwit PrimaryGroupID 333
$ sudo dscl . -create /Groups/gitwit RealName "GitWit Server"
$ sudo dscl . -create /Users/gitwit UniqueID 333
$ sudo dscl . -create /Users/gitwit PrimaryGroupID 333
$ sudo dscl . -create /Users/gitwit NFSHomeDirectory /var/gitwit
$ sudo dscl . -create /Users/gitwit UserShell /bin/bash
$ sudo dscl . -create /Users/gitwit RealName "GitWit Server"
$ sudo mkdir -p ~gitwit
$ sudo chown -R gitwit:gitwit ~gitwit
Enable the ssh_user
config value in config/initializers/git_wit.rb
:
config.ssh_user = "gitwit"
Now your application user needs to be allowed to sudo
as ssh_user
and vice
versa. Edit /etc/sudoers
using sudo visudo
and add the following lines:
rails_user ALL=(gitwit) NOPASSWD:ALL
gitwit ALL=(rails_user) NOPASSWD:ALL
Replace rails_user
with the application under which your Rails app runs, which
will be your personal username if using rails s
or Pow.
Test your sudo
rights and initialize the ssh_user
environment:
$ sudo -u gitwit -i
$ mkdir .ssh
$ chmod 700 .ssh
$ touch .ssh/authorized_keys
$ chmod 600 .ssh/authorized_keys
If you're using RVM or some other wacky environment manipulating tool, you're
going to want to adjust the login environment for ssh_user
by creating a
~ssh_user/.bashrc
file. For example, to load a specific RVM gemset:
source "/Users/xdissent/.rvm/environments/ruby-1.9.3-p385@git_wit"
You may also need to adjust the PATH
to include the location of the gw-shell
executable. If you're using bundle --binstubs
for example:
export PATH="/path/to/app/bin:$PATH"
The gw-shell
command handles the authentication and authorization for the SSH
protocol. It is initially called by ssh_user
upon login (git operation) and it
will attempt to sudo
to the application user and re-run itself with the same
environment. It determines which user is the "application user" by looking at
who owns the rails app root folder. To determine where the app root is actually
located, it looks for the ENV variables RAILS_ROOT
and BUNDLE_GEMFILE
in
order. When in doubt, set RAILS_ROOT
in ~ssh_user/.bashrc
:
export RAILS_ROOT="/path/to/app"
Remember to add export RAILS_ENV="production"
for production deployments!
You can easily sanity check your environment using sudo
as your app user:
$ sudo -u gitwit -i
$ source .bashrc
$ which gw-shell
/Users/xdissent/Code/git_wit/stubs/gw-shell
$ echo $RAILS_ROOT
/Users/xdissent/Code/git_wit/test/dummy
Now all that's left to do is add some authorized_keys
and you're all set.
This can be done from the rails console (rails c
):
GitWit. "writer", "ssh-rsa long-ass-key-string [email protected]"
# => nil
GitWit..keys
# => [command="gw-shell writer",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty ssh-rsa long-ass-key-string [email protected]]
You may now clone/push/pull over SSH - assuming the key you installed for
writer
is known to your ssh agent (ie ~/.ssh/id_rsa
):
$ git clone gitwit@localhost:example.git
See the dummy app in
test/dummy
for
a more advanced example of authorized_keys
management.
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.