One of the requirements I had of my web hosting service was the ability to host Mercurial repositories and share them out via SSH. My current host Arvixe does offer SSH and did install hg for me. While hunting for a hosting service I did try GoDaddy for a bit and that was an unpleasant experience for various reasons, one of which was that they did not have hg installed and were unwilling to do so for a shared hosting account. If you have the misfortune of being in this situation, download and compile hg as non-root user (without access to gcc) as detailed here. That works with GoDaddy’s servers.
Once you have hg installed, one way to let collaborators to access repositories on your SSH account is to use the hg-ssh script as detailed here. This works by basically relying on the feature of SSH by which you can restrict SSH for particular public keys by adding a “command=” specification to your authorized_keys file.(If you dont know what the authorized_keys file is, you probably should read some SSH public/private key management tutorial before reading this blog entry.)
Given this general arrangement, I wanted a way to manage “which mercurial users have access to which repositories” on my user account on the shared server and also ensure that I have a view of the repositories that is consistent with those of my collaborators. Hence hg-gateway.
Once hg-getway is setup on your shared remote machine (and I’ll detail the steps below) it lets you do the following. You can add a new user, say user1, to your shared system by typing the following on your local machine:
cat user1.pub | ssh firstname.lastname@example.org hg-gateway adduser user1
Stating the obvious: “user1.pub” is user1′s public key file, “you” is your username and “server.com” is your server. A user added like this doesn’t have access to any repositories on your system yet – you have to explicitly give them access. On the other hand, you as the owner of the account have access to every repo in the system. The owner does not have to be given explicit access to anything. You can give user1 access to some number of repos, say repo1 and repo2, by typing:
ssh email@example.com hg-gateway add user1 repo1 repo2
If you wish to grant someone read-only access to a repo (i.e. disable “push” to the repo) then add the “ro:” prefix to the repo path:
ssh firstname.lastname@example.org hg-gateway add user1 ro:repo3
Once you do this, you can ask your collaborator to clone out repo1 on their own machine where they have their private key setup by typing:
hg clone ssh://email@example.com/repo1
In the above snippet its important to note that the URL they use has your username “you” in it and not their username “user1″. Why? Because they are accessing your user account. The “adduser” subcommand basically adds their public key to your authorized_keys, but in a restricted way. On a shared hosting system you cannot create new user accounts without being root and thats the whole point of using hg-gateway.
They can pull and push and this should work the same way it would with bitbucket.org or some such hg hosting service. They can only access the hg repos that you have given them access to. And finally, they do not have general SSH or SCP access to your account. If a user tries to SSH to firstname.lastname@example.org they will be greeted by a message from hg-gateway and the “hg tip” of each repo that they have been access to. This helps give them a summary of the repos they are part of.
There are also the following commands to help with administration:
Remove access to one or more repos:
ssh email@example.com hg-gateway rm user1 repo1 repo2
ssh firstname.lastname@example.org hg-gateway rm user1 "~/path/to/repo"
List permissions by user name:
ssh email@example.com hg-gateway ls
ssh firstname.lastname@example.org hg-gateway ls user1
List permissions by repo name:
ssh email@example.com hg-gateway lsr
ssh firstname.lastname@example.org hg-gateway lsr repo1
See the “hg tip” repo summary as seen by user1:
ssh email@example.com hg-gateway summary user1
If you ask for the summary without mentioning a particular username, you will see a listing of all repositories (thats are under your $hg_root_dir folder – see below). The listing of all repos is available only to the owner of the SSH account. Normal users of the system will see only the status of repos they have access to.
Create a Bash script
Also, since typing the above remote commands are tedious, I usually like to have a little bash script say “server-hg-gateway” defined to be:
#!/bin/bash ssh firstname.lastname@example.org hg-gateway $*
Installation is straightforward. Copy the hg-gateway Ruby script into some place in your PATH on your remote machine. Edit a few variables defined at the top of the script to suit your environment. The main ones are:
$hg_root_dir = "~/hg"
I like having most of my repos in a directory (say “~/hg”), this variable specifies that directory’s name. Whenever you are dealing with a repo name now, the name is relative to this root dir. Hence repo1 is interpreted as “~/hg/repo1″ in this case.
That’s probably the most important variable. The remaining ones help you setup the name of the file where usernames permission are stored (i.e. information regarding which users can access which repos), the filename where connection attempts will be logged (logging can also be disabled), a banner message displayed to your users and the path to your authorized_keys file (usually this is standard). Many of these variables can be left unchanged, in the common case. Optionally, if you have the misfortune of being hosted on GoDaddy, you can configure $owner_cmd to “source ~/.my_bashrc”, because GoDaddy’s servers do not source .my_bashrc for some reason when you SSH in and execute a specific command i.e. you are left to deal with the annoyance of not having your environment variables (PATH) set. Read about this here and here.
Outside of editing variables in the hg-gateway script, you need to edit your authorized_keys file and add “command=~/bin/hg-gateway owner” next to your own public key (assuming that “~/bin” is where you installed hg-gateway). Once you are done this should look like:
command="~/bin/hg-gateway owner" ssh-dss AAB...XZ= you@machine
The main point of doing this is that your hg urls will also be interpreted relative to $hg_root_dir from now on, not just those of your users. This gives you and your collaborators the same view of the repositories and makes things a bit easier for you.
So here is the the big secret: hg-gateway intercepts incoming SSH connections by being invoked from authorized_keys. When it is placed in the “command=” clause to each SSH key, it determines what what permissions should be enabled for connections on that key. If the key is marked as “owner” as shown above, the key will have full access to the system. If the key is marked as “login <username>” it will have only the privileges assigned to that user.
Getting hg-gateway : the easy way.
hg-gateway is about 300 lines of Ruby, most of which are comments. You can get it from my public repository at: https://bitbucket.org/roshanjames/hg-gateway/get/tip.tar.gz
If you are on Ruby 1.9+, you can use https://bitbucket.org/roshanjames/hg-gateway2/get/tip.tar.gz
Getting hg-gateway: the hardcore way (with benefits)
An alternate means of setting up hg-gateway (for the advanced user), which is strongly recommended, would be to clone the hg-gateway repository and add a symlink to the script to your “~/bin/” directory. Once cloned, make your edits to the script as described above, and then commit your changes locally. For example:
hg clone https://bitbucket.org/roshanjames/hg-gateway/ ~/src/hg-gateway ln -s ~/src/hg-gateway/hg-gateway ~/bin/hg-gateway cd ~/src/hg-gateway emacs hg-gateway hg commit -m "my configuration"
For Ruby 1.9+ use the following repository:
The huge advantage of doing this is that its very easy to update hg-gateway as new versions are released in the future. You just need to type:
ssh you@server hg-gateway update -f
Here the -f is optional. Essentially this does an “hg fetch” and relies on hg to merge in the new updates correctly. As long as you have edited only a few script variables, this should happen seamlessly. You can always achieve the same effect manually by doing:
cd ~/src/hg-gateway hg fetch
That way you have easily upgradeable hg-gateway installation .
Why am I sharing this out? So that it will be useful to some of you and more importantly so that if there is vulnerability in some of this, maybe someone will let me know.