svnserve HowTo
How To Install an SVN Server the Easy Way on your Mac
This tutorial loosely follows the instructions given in this blog.
Ever wondered how to get a svn server running on your Mac? Here is how:
On MacOS X 10.5 and later a svn server is already installed. To work with svn you have to prepare a so called source code repository. You can do so as follows:
Running svnserve
- Create a new user subversion and log in as this user (standard user, no admin user).
- Create a Repositories folder in the home of the subversion user.
- Change the permissions of the folder:
chmod o-rwx Repositories
- Start the server by typing
/usr/bin/svnserve --inetd --root=/Users/subversion/Repositories &
in the unix shell and let the server run in the background.
Creating a new repository
- Now as the user subversion use the svnadmin tool to create a repository, for example:
svnadmin create /Users/subversion/Repositories/myrepo
- Once the server is running, you can log in as a different user and manage the versions of your source code with the svn tool. The most common operations are “svn checkout”, “svn add”, “svn update” and “svn commit” commands. For more information how to actually work with svn see the svn handbook.
Testing
- A final simple test to see if it worked is to checkout your repository:
- If you have a Mac that is reachable in your local network as http://mac.local, the address for the new repository above would be svn://mac.local/myrepo. We checkout this repository in a local directory using the unix shell:
svn checkout svn://mac.local/myrepo
- This creates a local copy of the latest version of your repository.
- Of course the local copy is empty initially. See below how to populate your repository with something meaningful.
Auto-launching svnserve
If you did’t get an error message, we have a working svn server, but we are not yet finished. The main part covered by this tutorial, is to get the server running automatically when a svn request comes in and to provide password access for each served repository.
So how do we start the server automatically? We replace the manual startup of the server on the unix shell by preparing a so called plist file for the MacOS X launch daemon:
We edit a new file org.tigris.subversion.svnserve.plist in /Library/LaunchDaemons and put the following text in it:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>Debug</key> <false/> <key>GroupName</key> <string>staff</string> <key>Label</key> <string>org.tigris.subversion.svnserve</string> <key>OnDemand</key> <true/> <key>Program</key> <string>/usr/bin/svnserve</string> <key>ProgramArguments</key> <array> <string>svnserve</string> <string>--inetd</string> <string>--root=/Users/subversion/Repositories</string> </array> <key>ServiceDescription</key> <string>SVN Version Control System</string> <key>Sockets</key> <dict> <key>Listeners</key> <array> <dict> <key>SockFamily</key> <string>IPv4</string> <key>SockServiceName</key> <string>svn</string> <key>SockType</key> <string>stream</string> </dict> <dict> <key>SockFamily</key> <string>IPv6</string> <key>SockServiceName</key> <string>svn</string> <key>SockType</key> <string>stream</string> </dict> </array> </dict> <key>Umask</key> <integer>2</integer> <key>UserName</key> <string>subversion</string> <key>inetdCompatibility</key> <dict> <key>Wait</key> <false/> </dict> </dict> </plist>
This plist file describes that a svn:// request will launch the /usr/bin/svnserve program under the permissions of the user subversion and that repositories are managed in the Repositories folder of this user.
We now start the svn server:
sudo launchctl load /Library/LaunchDaemons/org.tigris.subversion.svnserve.plist
The mapping of a service to a TCP port is described in /etc/services. The lines
svn 3690/udp # Subversion svn 3690/tcp # Subversion
denote that a tcp (or udp) request on port 3690 will be served by the “svn” service, for which svnserve is launched.
Access Control
We’ve installed a server and made sure it launches, however, we still have to do some last set-up work on the server. For security reasons, the server is locked down. Nobody can write to it. So what you have to do is go into your repository folder and edit the conf/svnserve.conf inside it that svnadmin helpfully created for you.
It says the default access permissions are auth-access = write. This tells the server that write access will be granted to everyone who provides a valid user name and password, and anon-access = read, which means that anyone can read the files even without a user name and password. You might want to change that first line to anon-access = none, if you want only people who have a valid user name and password to see your code in the repository.
Next, remove the comment sign “#” and the space at the start of the line password-db = passwd. This will make svnserve look for a file passwd right next to svnserve.conf as its source of user names and passwords. It contains two sample entries that will seem familiar if you’ve read The Subversion Book.
Simply replace those entries with username = password pairs of your own and do not forget to restart the server.
SVN usage example
You can test your permission setup with a “hello svn” message:
- After checking out a repo in a local directory, create the C file hello.c in that local copy:
int main()
{
printf("hello svn\n");
return(0);
}
- Then add this file to version control:
svn add hello.c
- And commit this local change to your repository:
svn commit
- Note: For each commit you need to provide a comment like “initial commit”, the so called commit log.
- The above commit should have failed, because you did not provide a user name and password!
- Next try:
svn --username="username" --password="password" commit
- That should have worked!
- Now modify your file:
- E.g. replace the “hello svn” phrase with “hello svn, you work so sweet!”.
- And show the changes with:
svn diff
- Show the modified files with:
svn stat
- If those changes are ok, then commit them to the repository:
svn commit
- Note: svn stores your password so that you do not need to retype every time you commit.
- Comment: When developing source code, it is mandatory to review your changes before actually committing them. Other developers will just beat the hell out of you, if you broke the build!
- Each change will result in a new version with increasing revision number.
- You are now at revision #2.
- To move to the svn head (the latest development revision) use the svn update command:
svn up
- You can see the log of your code development:
svn log
- Of course you should do something meaningful with your code, compile and execute it:
gcc hello.c -o hello && ./hello
That’s it. You are using (sub)version control!
WebSVN
Finally, we add a web front end to the subversion repositories. There are thousands of alternatives (like ViewCV and indefero), but for this tutorial we use WebSVN, because it is a snap to install:
- Download WebSVN from http://www.websvn.info.
- Unpack the archive to the html directory of your Apache web server.
- Rename the package to websvn.
- Copy the websvn/include/distconfig.php file to websvn/include/config.php and make the following modification:
- Uncomment the line reading config→parentPath to point to your repositories, that is:
$config->parentPath('/Users/subversion/Repositories/');
- Uncomment the line reading config→parentPath to point to your repositories, that is:
- Now open http://mac.local/websvn in your browser and you will see all your repositories in the web front end. Ain’t that sweet and cool.
To be on the safe side, we also should disable the websvn contents from being crawled. We do so by copying the following “robots.txt” into the html directory of the web server:
User-agent: * Disallow: /websvn
Automation
If you need to add repositories frequently, here is a script that automates that task. It creates a new repository for a given user name and sets a random password:
#!/bin/tcsh -f set user = $1 if ($user == "") then echo please specify a svn user exit 1 endif set repo = ~subversion/Repositories echo creating a svn repository for the user $user svnadmin create $repo/$user echo enabling the password-db echo "[general]" > $repo/$user/conf/svnserve.conf echo password-db = passwd >> $repo/$user/conf/svnserve.conf echo restricting access to authorized users echo anon-access = none >> $repo/$user/conf/svnserve.conf set password = `date | /sbin/md5 | head -c8` echo setting the password to $password echo $user = $password >> $repo/$user/conf/passwd echo created a svn repository to be checked out via the following command line: echo svn co --username=$user --password=$password svn://$HOST/$user
It you have a valid email address for the particular user, then you can also email the password automatically:
echo sending the password as email sudo postfix start echo "the password of your svn account $user is $password" | mail -s "Your SVN account@$HOST" user@address
The latter requires postfix to be setup. As a minimum postfix configuration, the relayhost needs to be set in /etc/postfix/main.cf (append “relayhost = …”). How to setup postfix with gmail see here.
Here is a more elaborate script adds a new user to a repository and emails the password to a given email address:
#!/bin/tcsh -f set name = $1 if ($name == "") then echo "please specify a svn repo name" echo "and specify a new user name (or email address) as optional second argument" echo "and specify a repository prefix as optional third argument" exit 1 endif set email = $2 set prefix = $3 set repo = ~subversion/Repositories if ($prefix != "") then set repo = $repo/$prefix if (! -e $repo) mkdir $repo endif set masteruser = "..." set masterpassword = "..." set masteremail = "svn@master.org" set user = $name if ($email != "") set user = $email if ($email:s/@// == $email) set email = "" if ($email != "") then set username = `echo $user | sed "s/@.*//g"` set userhost = `echo $user | sed "s/.*@//g"` set user = $username@`echo $userhost | /sbin/md5 | head -c4` endif set user = $user:s/@/_at_/ set usertime = `date "+%Y%m%d%H%M%S"` set exist = 0 if (-e $repo/$name) set exist = 1 if ($exist != 0 && $user == $name) then echo the repository does already exist exit 1 endif if ($exist == 0) then echo creating the svn repository "$name" svnadmin create $repo/$name echo enabling the password-db echo "[general]" > $repo/$name/conf/svnserve.conf echo password-db = passwd >> $repo/$name/conf/svnserve.conf echo restricting write access to authorized users echo anon-access = read >> $repo/$name/conf/svnserve.conf echo auth-access = write >> $repo/$name/conf/svnserve.conf # echo setting the master password for "$masteruser" to "$masterpassword" echo setting the master password echo $masteruser = $masterpassword >> $repo/$name/conf/passwd endif set password = `date | /sbin/md5 | head -c8` echo setting the password for "$user" to "$password" echo $user = $password >> $repo/$name/conf/passwd set url = svn://$HOST if ("$prefix" != "") set url = $url/$prefix set url = $url/$name echo setup the repository to be checked out via the following command line: echo svn co --username=$user --password=$password $url if ($email != "") then echo sending the password as email to $email set message = /var/tmp/svnmessage$user$usertime.txt echo "A subversion account has been created for you @ $HOST." >> $message echo "The password of your SVN account $user is $password ." >> $message echo "You can check out the repository "$name" via the following unix command:" >> $message echo " svn co --username=$user --password=$password $url" >> $message cat $message | mail -s "Your SVN account @ $HOST" $email cat $message | mail -s "SVN account @ $HOST" $masteremail rm -f $message endif
Example usage:
./svnaddrepo.sh repo user@gmail.com ./svnaddrepo.sh repo anotheruser@gmail.com ...
An example email from the SVN server to the added user:
A subversion account has been created for you @ schorsch.efi.fh-nuernberg.de. The password of your SVN account xxxxxxxxxx@th-nuernberg.de is yyyyyyyy . You can check out the repository zzzzzzzz via the following unix command: svn co --username=xxxxxxxxxx@th-nuernberg.de --password=yyyyyyyy svn://schorsch.efi.fh-nuernberg.de/zzzzzzzz
Cleaning Up
After the svn server has been running for a while, a couple of incidents can happen eventually:
First, the tmp folder may be filling up. To ensure that the disk is not running out of space, we should clean the tmp folder regularly.
To do so, we delete files in /var/tmp that are older than a week, if the modification time is older than 7 days:
sudo find /var/tmp \( -mtime +7 -and -not -type d \) -exec rm {} \;
To let the server do this regularly we add another entry in /etc/crontab
#min hour mday month wday user command 0 2 * * * root /etc/cleanup.sh
and provide a shell script in /etc/cleanup.sh that does the cleaning:
#!/bin/tcsh -f find /var/tmp \( -mtime +7 -and -not -type d \) -exec rm {} \;
Sometimes Apache may be responding very sluggishly. A reason can be that a httpd process is hogging the cpu due to a hung websvn script. To terminate those hung processes, we restart apache gracefully by adding the following line to the above cron job:
/usr/sbin/apachectl -k graceful # httpd.conf: GracefulShutDownTimeout 1000
To ensure that hung processes get terminated eventually we need to set the GracefulShutDownTimeout in /etc/apache2/httpd.conf (the default value is zero, which means that hung processes do not experience a timeout):
GracefulShutDownTimeout 1000
Also, under extremely heavy load by hundreds of clients, hung processes can accumulate so that the system almost grinds to a halt. This can happen especially when people are using aggressive download helpers for downloading large files. To prevent that, we need to limit the number of simultaneous httpd processes in /etc/apache2/httpd.conf (the Mac Mini’s default value of 1024 is far too large):
MinSpareServers 8 MaxSpareServers 32 StartServers 1 MaxKeepAliveRequests 500 KeepAliveTimeout 5 MaxClients 512 ListenBackLog 256
But we also need to ensure that eventually all requests get served by our reduced set of httpd processes in a timely manner. If there are many large files being downloaded simultaneously, we can run out of serving httpd processes, so that another incoming request just sits around until another download finshes. This can take a while. As a consequence, the download of huge files needs to be limited. We are using the mod_bw module of Apache 2.0 for that purpose.
First we need to load the module by adding the following line to /etc/apache2/http.conf of our Mac Mini (for other Unix flavors this may differ):
LoadModule bw_module libexec/apache2/mod_bw.so
Then, we can limit the access of large files by adding the following configuration to the virtual host definition (e.g. /etc/apache2/sites/0000_any_80_.conf):
# Limit band width for large files BandwidthModule On ForceBandWidthModule On BandWidth all 1000000 LargeFileLimit .gz 1000 500000 LargeFileLimit .zip 1000 500000 LargeFileLimit .tif 1000 500000
If we would like to exclude a particular client ip or an entire subnet from a limitation, we can add a “MinBandWidth” directive to the configuration. For example, we can add the following line, which increases download speed for the subnet 141.75.0.0/16 to a minimum of 2MB/s while all others are served with a minimum of 50kb/s only (the order of directives is important, because the first item takes precedence):
MinBandWidth 141.75.0.0/16 2000000 MinBandWidth all 200000
The limitation parameters are dependent on the band width of the actual internet connection of the server and cannot be generalized. More information about the limiting capabilities of the mod_bw module can be found here.
Now our server will not go down under heavily load and should be happily serving SVN, html and large file content for a long long time :)
To monitor that everything is fine, log on your server with “ssh” and have a look at “top”. The httpd processes should show up but neither the cpu load of the entire system nor the load of a single httpd process, that is serving plain http requests or downloads, should be more than a couple of percent.
To see what content is actually being served have a look at the access log:
tail -f /var/log/apache2/access_log
Or have a look at the status page of the server:
You can also use CGI scripting to monitor your server more conveniently, as described in the CGI scripting tutorial. Here is the uptimee of my SVN server:
If anything goes wrong, you can still restart your server via ssh on the command line:
sudo /usr/sbin/apachectl -k restart
Sometimes it is necessary to block a whole subnet due to malicious requests (in my experience mostly from china and russia/ukraine). For example, if we would like to block the entire chinese subnet 211.136.0.0, we add the following rule (number 100) to the firewall configuration:
sudo ipfw add 100 deny all from 211.136.0.0/16 to any
To show the active rule set:
sudo ipfw list
To delete the rule again:
sudo ipfw del 100
To make the rules permanent, we need to add them to /etc/ipfilter/ipfw.conf:
... ... ... add 100 deny all from 211.136.0.0/16 to any
Unfortunately, the permanent rules do only show up after a reboot.