How to deploy web applications using Mercurial
Does deploying changes to your site take too long? Are you tired of manually sorting out the update? Here is how to deploy your projects using Mercurial.
Why?
- Ease of updating. Mercurial keeps track of the changes and only sends the necessary changes - you don't need to worry about transferring files.
- Make it possible to roll back changes on the deployed site. You can use hg to roll back from a bad update if necessary.
- Once set up, it's beautiful. "hg deploy". How can you not like that?
- While the differences aren't that big, this setup is better for deployment rather than code distribution via repositories:
- Minimal dependencies. This approach only uses the hg-ssh script from the Mercurial core contrib.
- Manual configuration. You can set different directories for each repository which allows you to work with your existing webroot setup. However, you will need to manually add new repositories, since hg-ssh does not support adding new repositories remotely.
1. Make sure that the .hg directories are never served to the public
To prevent .hg directories from being accessible to other people, you can take a number of (optional, but recommended) precautions:
Move the web root of your project to a different folder
You can probably alter your project repository so that you have an explicit webroot folder in which you only have the files that should be accessible to the public (bootstrap + js/css resources). This makes it less likely that you accidentally serve your .hg directory. You move the files within hg do this using hg move:
mkdir webroot
hg move . webroot
Or you can use hg addremove --similarity=100 after moving the files manually. It will detect identical files as moved.
After this, you may need to update your apache config to serve from the webroot (e.g. adjust the DocumentRoot directive for the site / virtual host).
Set up Apache not to serve .hg directories
You can also prevent Apache from serving directories with .hg in them by adding the following to your httpd.conf:
<DirectoryMatch .hg>
Order allow,deny
Deny from all
</DirectoryMatch>
Restart Apache if you change httpd.conf.
2. Setup the server
Create the user
# make a system user (-r) with a home directory (-m)
useradd -r -m hg
# lock the user account
usermod -L hg
<span style="font-family: Georgia, 'Times New Roman', 'Bitstream Charter', Times, serif; line-height: 19px; white-space: normal; font-size: 13px;">[/codesyntax]</span>
Setup ssh
#install mercurial if not previously installed
yum install mercurial
su hg
cd /home/hg/
wget http://www.selenic.com/repo/hg-stable/raw-file/tip/contrib/hg-ssh
chmod u+x ~hg/hg-ssh
mkdir /home/hg/.ssh/
nano /home/hg/.ssh/authorized_keys
# remember to chmod the ssh config
chmod -R 0700 /home/hg/.ssh/
Copy the public key: ssh-rsa ...(key data)== to /home/hg/.ssh/authorized_keys, one line for each authorized key.
Add the following in front of each authorized key in /home/hg/.ssh/authorized_keys:
command="~hg/hg-ssh /path/to/repository",no-port-forwarding,no-X11-forwarding,no-agent-forwarding ssh-rsa ...==
If necessary, enable public key auth in /etc/ssh/sshd_config and add hg to the AllowUsers directive. Restart sshd if you change sshd_config.
3. Init the repo
New repo on the remote server
Just run hg init in the new repo folder, then hg clone it on your computer from the remote server:
hg clone -v --debug ssh://hg@server:port/path/to/repo
The -v and --debug make the clone show more information about what is going on.
From existing sources
Unfortunately hg-ssh does not support the hg init/hg clone command remotely. If you have an existing repo, you have to copy it first to the repository directory on the server. Since there are many files to move, I recommend gzipping the whole directory before moving it, then unzipping on the server. Do a hg log and hg status to see that everything transferred correctly.
Adding more repos
To add more repositories, simply add another repository path (separated by a space) immediately after the first repo path in .ssh/authorized_keys, and either init a new repo or copy an existing repo.
4. Push updates
After you have the same repository on both the server and locally, you can start pushing stuff to the server:
hg push ssh://hg@server:port/path/relative/to/home/dir
If your repo path is not relative to the home dir, you need an extra slash in front of the push:
hg push ssh://hg@server:port//path/relative/to/base/dir
If you run into problems, try connecting via ssh - see my previous post for some tips on this.
5. Automate hg update
You should automate hg update so that each push causes the repository to be updated automatically to the pushed version. You can automate hg update on the remote repo by adding the following to the remote .hg/hgrc:
[hooks]
changegroup = hg update >&2
Note that output has to be redirected to stderr (or /dev/null), because stdout is used for the data stream -which is why there is the >&2 at the end of the command.
If you get errors in updating automatically, check the permissions. In particular, make sure that .hg/ is owned by hg and that the content is readable by apache.
6. Set up default push or create an alias
If you only push to one location, then you can set up the default locations for pull and push in the local .hg/hgrc:
[paths]
default = ssh://hg@servername/reponame
default-push = ssh//:hg@servername/reponame
This allows you to simply use "hg push" to deploy.
If you need more than one push location, create an alias in the local .hg/hgrc:
[alias]
deploy = push -v --debug ssh://hg@server:port/path/to/repo
This allows you use "hg deploy" as an alias for deploying.
What about configuration files?
One simple solution is to use "hg forget" to forget them once you have deployed the configuration files. This means that hg will not track the configuration files, but will not delete them either.