= Red Hat Mercurial: Setup and Config =
**Summary**: How to configure Mercurial on Red Hat. \\
**Date**: Around 2014 \\
**Refactor**: 29 March 2025: Checked links and formatting. \\
{{tag>redhat linux bash}}
This is the documentation page about the setup from mercurial on Red Hat systems here. Mercurial is a versioning system which uses a master repository server. The usual setup is shown in this picture:
[{{redhatmercurial01.jpg}}] \\
Once you know how everything works, setting up Mercurial is quite easy, and consists of these steps:
* Installation
* Install Mercurial on master repository server
* Install Mercurial on client server
* Repositories
* Create AD Service account for mercurial on master repository server
* Create repository
* Cloning
* Create SSH trust between AD service account on master repository server
* Create clone
* Configure clone
The [[redhat65management]] will function as the master repository server, while all other servers will function as a client, since we want to create a versioning system of all /etc directories.
> Note that the element abbreviation for Mercurial is hg. Using mercurial will be done with the command hg.
= Installation =
The installation can be done through the install of a simple rpm. However, the version that is available in the Red Hat repository is 1.4-3, which is quite old and does not recognize the originating user when editing files using su(do), which is not really useful in our environment since we only use sudo or su. So we got version 2.2.2-1 from http://rpmfind.net//linux/RPM/dag/redhat/el6/x86_64/extras/mercurial-2.2.2-1.el6.rfx.x86_64.html which is also an old version but at least has the ability to recognize the originating user.
We will install the rpm using yum localinstall to keep the yum database intact: {{{sudo yum localinstall mercurial-2.2.2-1.el6.rfx.x86_64.rpm}}}:
[adminsjoerd@rhmgmtsrv repo-getshifting]$ sudo yum localinstall mercurial-2.2.2-1.el6.rfx.x86_64.rpm
Loaded plugins: product-id, refresh-packagekit, subscription-manager
'exceptions.ValueError' object has no attribute 'msg'
Setting up Local Package Process
Examining mercurial-2.2.2-1.el6.rfx.x86_64.rpm: mercurial-2.2.2-1.el6.rfx.x86_64
Marking mercurial-2.2.2-1.el6.rfx.x86_64.rpm to be installed
Resolving Dependencies
--> Running transaction check
---> Package mercurial.x86_64 0:2.2.2-1.el6.rfx will be installed
--> Finished Dependency Resolution
Dependencies Resolved
=================================================================================================================================================================================
Package Arch Version Repository Size
=================================================================================================================================================================================
Installing:
mercurial x86_64 2.2.2-1.el6.rfx /mercurial-2.2.2-1.el6.rfx.x86_64 11 M
Transaction Summary
=================================================================================================================================================================================
Install 1 Package(s)
Total size: 11 M
Installed size: 11 M
Is this ok [y/N]: y
Downloading Packages:
Running rpm_check_debug
Running Transaction Test
Transaction Test Succeeded
Running Transaction
Installing : mercurial-2.2.2-1.el6.rfx.x86_64 1/1
Verifying : mercurial-2.2.2-1.el6.rfx.x86_64 1/1
Installed:
mercurial.x86_64 0:2.2.2-1.el6.rfx
Complete!
[adminsjoerd@rhmgmtsrv repo-getshifting]$ yum list installed | grep mercurial
mercurial.x86_64 2.2.2-1.el6.rfx @/mercurial-2.2.2-1.el6.rfx.x86_64
The installation is the same for the master repository server as for the clients, just get the rpm from the management server:
wget -O /tmp/mercurial.rpm http://rhmgmtsrv/getshiftinginstall/mercurial-2.2.2-1.el6.rfx.x86_64.rpm
sudo yum localinstall mercurial.rpm
= Repositories =
== Create AD Account ==
In our setup all accounts that need to log on remotely have to use an [[rhmgmtsrv_-_unix_management_server|AD account]]. Create one and configure the UNIX properties like this:
[{{redhatmercurial02.jpg}}] \\
Then setup the homedirectory as configured in the AD account:
sudo mkdir /data/hg
sudo chown -R srv-rhmgmtsrv-hg:UNIX-Service-Accounts hg/
Then make sure the UNIX-Service-Accounts group can logon over ssh:
AllowGroups UNIX-Server-Admins UNIX-Service-Accounts
Restart {{{service sshd restart}}} to commit the changes.
== Create Repository ==
We will create the repository for "applscripts" and we will use the following directory structure:
* /data/hg
* repositories
* applscripts
So switch to the created application user and setup the directories:
$> mkdir -p /data/hg/repositories/applscripts
Now switch to the directory, make sure the srv-rhmgmtsrv-hg user is the owner and create the repository using the {{{hg init}}} command:
$> cd /data/hg/repositories/applscripts
$> ll
drwxr-xr-x. 2 srv-rhmgmtsrv-hg UNIX-Service-Accounts 4096 Aug 1 10:36 applscripts
$> hg init
$> ls -la
total 12
drwxr-xr-x. 3 srv-rhmgmtsrv-hg UNIX-Service-Accounts 4096 Aug 1 10:36 .
drwxr-xr-x. 3 srv-rhmgmtsrv-hg UNIX-Service-Accounts 4096 Aug 1 10:36 ..
drwxr-xr-x. 3 srv-rhmgmtsrv-hg UNIX-Service-Accounts 4096 Aug 1 10:36 .hg
= Cloning =
== Create SSH Trust ==
We need to establish a SSH trust between the client and the master server, since we will configure Mercurial to use ssh to send over files.
=== Configure SSH Daemon ===
First we will setup SSH to allow for keys authorization using the {{{vi /etc/ssh/sshd_config}}}, uncomment the following lines:
RSAAuthentication yes
PubkeyAuthentication yes
AuthorizedKeysFile .ssh/authorized_keys
=== Setup Key Exchange ===
As the application user on the client server:
[appluser@rhapplserver ~]$ ssh-keygen
and follow the prompts while keeping the passphrase empty.
Now copy it to the AD user on the master repository:
[appluser@rhapplserver ~]$ ssh-copy-id srv-rhmgmtsrv-hg@rhmgmtsrv
Now we should be able to start a ssh session from appluser to the rhmgmtsrv without a password. However, if you would try you would still be requested for your password. That is because SELinux is blocking the ssh daemon form accessing the ssh authorized_keys file. And that is because the home directory for the srv-rhmgmtsrv-hg is not on the default location. You can check this issue by checking the {{{/var/log/messages}}} file. If you have the setroubleshoot-server package installed you'll see a message like this:
Aug 1 11:06:20 rhmgmtsrv setroubleshoot: SELinux is preventing /usr/sbin/sshd from read access on the file authorized_keys. For complete SELinux messages. run sealert -l af6fe822-1e0a-4ec7-8707-c66c7eb2a88f
You could check the allowed locations for the ssh_home directories like this:
[adminsjoerd@rhmgmtsrv hg]$ sudo semanage fcontext -l | grep ssh_home_t
/root/\.shosts all files system_u:object_r:ssh_home_t:s0
/root/\.ssh(/.*)? all files system_u:object_r:ssh_home_t:s0
/var/lib/amanda/\.ssh(/.*)? all files system_u:object_r:ssh_home_t:s0
/var/lib/gitolite(3)?/\.ssh(/.*)? all files system_u:object_r:ssh_home_t:s0
/var/lib/openshift/[^/]+/\.ssh(/.*)? all files system_u:object_r:ssh_home_t:s0
/var/lib/pgsql/\.ssh(/.*)? all files system_u:object_r:ssh_home_t:s0
/var/lib/stickshift/[^/]+/\.ssh(/.*)? all files system_u:object_r:ssh_home_t:s0
Now add the ssh home directory for the srv-rhmgmtsrv-hg user to this list:
[adminsjoerd@rhmgmtsrv hg]$ sudo semanage fcontext -a -t ssh_home_t '/data/hg/.ssh(/.*)?'
[adminsjoerd@rhmgmtsrv hg]$ sudo semanage fcontext -l | grep ssh_home_t
/data/hg/.ssh(/.*)? all files system_u:object_r:ssh_home_t:s0
/root/\.shosts all files system_u:object_r:ssh_home_t:s0
/root/\.ssh(/.*)? all files system_u:object_r:ssh_home_t:s0
/var/lib/amanda/\.ssh(/.*)? all files system_u:object_r:ssh_home_t:s0
/var/lib/gitolite(3)?/\.ssh(/.*)? all files system_u:object_r:ssh_home_t:s0
/var/lib/openshift/[^/]+/\.ssh(/.*)? all files system_u:object_r:ssh_home_t:s0
/var/lib/pgsql/\.ssh(/.*)? all files system_u:object_r:ssh_home_t:s0
/var/lib/stickshift/[^/]+/\.ssh(/.*)? all files system_u:object_r:ssh_home_t:s0
Now set the SElinux context correct:
[adminsjoerd@rhmgmtsrv hg]$ sudo restorecon -Rv /data/hg
restorecon reset /data/hg/.ssh context unconfined_u:object_r:sshd_key_t:s0->unconfined_u:object_r:ssh_home_t:s0
restorecon reset /data/hg/.ssh/authorized_keys context unconfined_u:object_r:sshd_key_t:s0->unconfined_u:object_r:ssh_home_t:s0
[adminsjoerd@rhmgmtsrv hg]$ sudo ls -lZ .ssh/
-rw-------. srv-rhmgmtsrv-hg UNIX-Service-Accounts unconfined_u:object_r:sshd_key_t:s0 authorized_keys
Now it works!
== Create Clone ==
Now all we have to do to create the clone is to change to the directory that will function as the root of the repository on the client. So for example, if you want to create a repository of all files in the directory {{{//appl/algorithmics/acp/markets}}} (which by coincidence is the homedir of the application user) you do that like this:
[appluser@rhapplserver ~]$ hg clone ssh://srv-rhmgmtsrv-hg@rhmgmtsrv/repositories/markets .
[appluser@rhapplserver ~]$ ll
total 125M
drwx------. 20 appluser appluser 4.0K Aug 1 11:40 .
drwxr-xr-x. 3 appluser appluser 4.0K Jul 9 14:02 ..
......
drwxrwxr-x. 3 appluser appluser 4.0K Aug 1 11:40 .hg
......
As you can see the .hg directory is created meaning the connection for the repository between the master and the client is now established.
== Configure Clone ==
Now we don't want to have all the files in the directory in the repository so we need to configure which files will be included and which will be excluded. For this, create in the same directory where the .hg directory is a file called {{{.hgignore}}}:
^(?!scripts|hg)
(\.|_)(out|log|orig|bak|rwb|scf|tar|gz|zip|csv|trm|bck|rws)$
~$
[0-9]{8}$
hgshelve.pyc$
backup
The topline will only include directories in the hg and scripts directory, while the other lines exclude files that match any of the patterns.
= Basic Mercurial Usage and Commands =
We will use Mercurial for two purposes, first is keeping an remote repository for each server's /etc directory. Second is keeping an adminscripts directory in sync over all servers with scripts that will have to be kept on all servers.
== Creating Remote Repository for ETC Directory ==
=== On The Master Repository Server ===
sudo su - srv-rhmgmtsrv-hg
cd /data/hg/repositories/
mkdir etc-rhapplserver
cd etc-rhapplserver/
hg init
-bash-4.1$ ls -la
total 12
drwxr-xr-x. 3 srv-rhmgmtsrv-hg UNIX-Service-Accounts 4096 Sep 6 20:26 .
drwxr-xr-x. 4 srv-rhmgmtsrv-hg UNIX-Service-Accounts 4096 Sep 6 20:25 ..
drwxr-xr-x. 3 srv-rhmgmtsrv-hg UNIX-Service-Accounts 4096 Sep 6 20:26 .hg
-bash-4.1$ exit
=== On the Client ===
First set up the key exchange as the root server:
[adminsjoerd@rhapplserver ~]$ su - root
[root@rhapplserver ~]# ssh-keygen
[root@rhapplserver ~]# ssh-copy-id srv-rhmgmtsrv-hg@rhmgmtsrv
test the connection now:
[root@rhapplserver ~]# ssh srv-rhmgmtsrv-hg@rhmgmtsrv
Last login: Fri Aug 1 11:36:30 2014 from rhapplserver.prd.domain
-bash-4.1$ exit
Now use these commands to clone the repository, make an exclude file and update the repository with all the files:
[root@rhapplserver ~]# cd /etc
[root@rhapplserver etc]# hg clone ssh://srv-rhmgmtsrv-hg@rhmgmtsrv/repositories/etc-rhapplserver .
abort: destination '.' is not empty
This error is because /etc is not empty. Mercurial expects an empty directory so work around this by letting hg create an empty directory and then move the files:
[root@rhapplserver etc]# hg clone ssh://srv-rhmgmtsrv-hg@rhmgmtsrv/repositories/etc-rhapplserver
[root@rhapplserver etc]# cd etc-rhapplserver
[root@rhapplserver etc-rhapplserver]# mv .hg/ ../
[root@rhapplserver etc-rhapplserver]# cd ..
[root@rhapplserver etc]# rm -rf etc-rhapplserver/
[root@rhapplserver etc]# hg status
This will show you all files in /etc so you first need to create an excludefile:
[root@rhapplserver etc]# vi .hgignore
[root@rhapplserver etc]# cat .hgignore
(\.|_)(out|log|orig|bak|rwb|scf|tar|gz|zip|csv|trm|bck|rws)$
~$
backup
cache
mtab
Now we add and commit the files to the local repository:
[root@rhapplserver etc]# hg add
[root@rhapplserver etc]# hg commit -u "adminsjoerd" -m "initial import in 6 sep 14"
Now push the files to the remote repository:
[root@rhapplserver etc]# hg push
If you ever want some more files not kept in the repository or removed from it you can use the forget command:
hg forget
Do not forget to commit the changes:
hg commit -Am -u "adminsjoerd" "Removed some files from repository"
== Creating An AdminScripts Directory ==
=== On The Master Repository Server ===
sudo su - srv-rhmgmtsrv-hg
cd /data/hg/repositories/
mkdir adminscripts
cd adminscripts/
hg init
-bash-4.1$ ls -la
total 12
drwxr-xr-x. 3 srv-rhmgmtsrv-hg UNIX-Service-Accounts 4096 Sep 6 20:26 .
drwxr-xr-x. 4 srv-rhmgmtsrv-hg UNIX-Service-Accounts 4096 Sep 6 20:25 ..
drwxr-xr-x. 3 srv-rhmgmtsrv-hg UNIX-Service-Accounts 4096 Sep 6 20:26 .hg
-bash-4.1$ exit
=== On the Client ===
[root@rhapplserver etc]# cd /
[root@rhapplserver /]# mkdir adminscripts
[root@rhapplserver /]# cd adminscripts
[root@rhapplserver adminscripts]# hg clone ssh://srv-rhmgmtsrv-hg@rhmgmtsrv/repositories/adminscripts .
no changes found
updating to branch default
0 files updated, 0 files merged, 0 files removed, 0 files unresolved
If you would like the adminscripts dir also on the local server you can also clone the dir locally:
sudo hg clone /data/hg/repositories/adminscripts .
[root@rhapplserver adminscripts]# vi testfile
[root@rhapplserver adminscripts]# ll
total 4
-rw-r--r--. 1 root root 9 Sep 6 21:57 testfile
[root@rhapplserver adminscripts]# hg st
? testfile
[root@rhapplserver adminscripts]# hg add
adding testfile
[root@rhapplserver adminscripts]# hg commit -u "adminsjoerd" -m "Testfile"
[root@rhapplserver adminscripts]# hg push
=== On the Second Client ===
[root@rhapplserver02 adminscripts]# hg pull
[root@rhapplserver02 adminscripts]# hg update
= Restore Files =
== See Files on Master Repository ==
If you just want to look into the current file on the master repository log on the Master Repository Server and browse to the repository directory. Then change to the ".hg -> store -> data" directory and that's where your files will be.
== Overview Off Commits ==
You can use the hg log command to see the different revisions. You can see why it's important to keep your comments nicely documented:
[root@rhapplserver etc]# hg log
changeset: 2:d22a2c80e17e
tag: tip
user: adminsjoerd
date: Wed Sep 10 13:21:38 2014 +0200
summary: removed some non-config files-2
changeset: 1:9daff51252cf
user: adminsjoerd
date: Wed Sep 10 13:20:47 2014 +0200
summary: removed some non-config files
changeset: 0:dafda2b4790f
user: adminsjoerd
date: Sat Sep 06 21:27:00 2014 +0200
summary: initial import in 6 sep 14
== Recover From Accidental Commit ==
If you've committed a change but have not yet pushed it to the master repository use {{{hg rollback}}} to make the changeset go away:
[root@rhapplserver etc]# vi hg-testfile
[root@rhapplserver etc]# hg add
adding hg-testfile
[root@rhapplserver etc]# hg commit
abort: no username supplied (see "hg help config")
[root@rhapplserver etc]# hg commit -u adminsjoerd -m "Added hg-testfile for documentation purposes"
[root@rhapplserver etc]# hg tip
changeset: 3:121369f2f5f2
tag: tip
user: adminsjoerd
date: Wed Sep 10 13:54:39 2014 +0200
summary: Added hg-testfile for documentation purposes
[root@rhapplserver etc]# hg status
[root@rhapplserver etc]# hg rollback
repository tip rolled back to revision 2 (undo commit)
working directory now based on revision 2
[root@rhapplserver etc]# hg status
A hg-testfile
Above you see I've added a file and I want to roll that back. Rolling back the commit makes hg see the file again as added.
> Note that rollback is useless after a push.
== Restore Previous Version From Local Repository ==
In this example I commit and push the changes made to the remote repository. Then I make another change, commit and push these as well. Then I rollback to the previous version:
[root@rhapplserver etc]# hg status
A hg-testfile
[root@rhapplserver etc]# hg commit -u adminsjoerd -m "Added hg-testfile for documentation purposes"
[root@rhapplserver etc]# hg push
pushing to ssh://srv-rhmgmtsrv-hg@rhmgmtsrv/repositories/etc-rhapplserver
searching for changes
remote: adding changesets
remote: adding manifests
remote: adding file changes
remote: added 1 changesets with 1 changes to 1 files
[root@rhapplserver etc]# vi hg-testfile
[root@rhapplserver etc]# hg status
M hg-testfile
[root@rhapplserver etc]# hg commit -u adminsjoerd -m "Added hg-testfile for documentation purposes - version 2"
[root@rhapplserver etc]# hg push
pushing to ssh://srv-rhmgmtsrv-hg@rhmgmtsrv/repositories/etc-rhapplserver
searching for changes
remote: adding changesets
remote: adding manifests
remote: adding file changes
remote: added 1 changesets with 1 changes to 1 files
[root@rhapplserver etc]# cat hg-testfile
version 01
version 02
[root@rhapplserver etc]# hg log
changeset: 4:9649f4d260bb
tag: tip
user: adminsjoerd
date: Wed Sep 10 13:59:00 2014 +0200
summary: Added hg-testfile for documentation purposes - version 2
changeset: 3:8c84648c5677
user: adminsjoerd
date: Wed Sep 10 13:58:21 2014 +0200
summary: Added hg-testfile for documentation purposes
changeset: 2:d22a2c80e17e
user: adminsjoerd
date: Wed Sep 10 13:21:38 2014 +0200
summary: removed some non-config files-2
changeset: 1:9daff51252cf
user: adminsjoerd
date: Wed Sep 10 13:20:47 2014 +0200
summary: removed some non-config files
changeset: 0:dafda2b4790f
user: adminsjoerd
date: Sat Sep 06 21:27:00 2014 +0200
summary: initial import in 6 sep 14
[root@rhapplserver etc]# hg update -r 3
1 files updated, 0 files merged, 0 files removed, 0 files unresolved
[root@rhapplserver etc]# cat hg-testfile
version 01
== Restore File From Master Repository ==
This is an example on how to deal with deleted files. As long as the change is not committed there is no problem. After you delete the file, "hg delete" the file and commit the change that data is gone and you need other needs to restore the files (from tape):
[root@rhapplserver etc]# hg st
[root@rhapplserver etc]# rm hg-testfile
rm: remove regular file `hg-testfile'? y
[root@rhapplserver etc]# hg status
! hg-testfile
[root@rhapplserver etc]# hg revert hg-testfile
[root@rhapplserver etc]# hg status
[root@rhapplserver etc]# cat hg-testfile
version 01
[root@rhapplserver etc]# rm hg-testfile
rm: remove regular file `hg-testfile'? y
[root@rhapplserver etc]# hg st
! hg-testfile
[root@rhapplserver etc]# hg commit
nothing changed (1 missing files, see 'hg status')
[root@rhapplserver etc]# hg st
! hg-testfile
[root@rhapplserver etc]# hg remove hg-testfile
[root@rhapplserver etc]# hg st
R hg-testfile
[root@rhapplserver etc]# hg commit -u adminsjoerd -m "Removed testfile"
created new head
[root@rhapplserver etc]# hg st
[root@rhapplserver etc]# hg revert hg-testfile
hg-testfile: no such file in rev 2421b14f9b72
= Working with Multiple Committers =
Even though I'm not working with multiple committers I found that it is best to follow the guidelines for this as this prevents non trusting errors like:
remote: abort: Permission denied: /.hg/store/data/.
The guidelines for working with multiple editors are found [[http://mercurial.selenic.com/wiki/MultipleCommitters|here]] and come down to going to the mail repository, and make sure all files are owned by the service account and group, and then set the sticky bit so new files that are added to the repository get the same permissions:
cd /data/hg/repositories/adminscripts/
sudo chown srv-rhmgmtsrv-hg:UNIX-Service-Accounts -R .hg/
sudo chmod g+w .hg .hg/* .hg/store/*
sudo chmod g+s .hg .hg/store .hg/store/data
= Script for Automatic Updates =
I also wanted a script so I could create changes to configfiles without having to thing about committing these changes to the repository. Downside of this is of course that if you would have multiple edits on the same file between updates these updated would go missing. Do mitigate this you should run your script as often as possible, or only change files on one server. I created a script that can be scheduled using cron.
== Script ==
# Script to update changes in the adminscripts directory and distribute them among all the servers.
# Author: Sjoerd Hooft
# Date: 27-11-2014
# Schedule time
# This script can be used manually or scheduled. To make sure changes are not pushed at the same time keep this schedule:
# 3:00 rhmgmtsrv01
# 3:05 rhmgmtsrv02
# 4:00 rhdbprd01
# 4:05 rhapprd01
# 5:00 rhdbacp01
# 5:05 rhapacp01
# Script Variables
TODAY=`date +%Y%m%d%H%M`
LOGFILE=/tmp/mercurialdailyupdate.log
HOST=`hostname -s`
REPO="/adminscripts/"
# Create new logfile
echo "Start daily mercurial update on $HOST on $TODAY" > $LOGFILE
# First: Push local changes to the repository
echo "Start push" >> $LOGFILE
/usr/bin/hg add -R $REPO >> $LOGFILE
/usr/bin/hg commit -u "DailyPush" -m "DailyPush" -R $REPO >> $LOGFILE
/usr/bin/hg push -R $REPO >> $LOGFILE
# Second: download changes from other servers
echo "Start pull" >> $LOGFILE
/usr/bin/hg pull -R $REPO >> $LOGFILE
/usr/bin/hg update -R $REPO >> $LOGFILE
# Close
echo "End of log" >> $LOGFILE
exit 0
== Cron ==
Then schedule the script using {{{sudo crontab -e}}} (so the script gets executed as root):
# Run mercurial update every day (time in script)
0 3 * * * /adminscripts/mercurialupdate > /tmp/mercurialupdate.log 2>&1
= Resources =
http://en.wikipedia.org/wiki/Mercurial \\
http://serverfault.com/questions/54148/how-to-setup-etckeeper-with-mercurial-in-ubuntu \\
http://blog.mixu.net/2010/10/04/setting-up-private-ssh-based-mercurial-repo-hosting-on-centos/ \\
http://www.codekoala.com/posts/automatic-config-replication-mercurial/ \\
All sorts of rollback: \\
http://hgbook.red-bean.com/read/finding-and-fixing-mistakes.html \\