I needed a local mercurial hosting option. The following is what I came up with. Hopefully, you will find it useful or the post at least gives you some ideas. My requirements were basic.
- Web browsable repositories
- Minimal and maintenance-free
- Support for pushing new changesets
- Accessible through a URL to easily pull dependencies
- Needed access from a single computer (which let me omit authentication)
I didn’t need bug tracking, code review, or anything fancy. Essentially, all I needed was to run
hg serve all the time, which is what I did. The repositories are hosted on the same computer I develop on, which allowed me to skip adding authentication. The mercurial server is bound to a loopback IP address making it only available to my pc. Adding authentication wouldn’t have added any additional security compared to having the local files on my computer.
In the future, if I have a dedicated server in my office, I will add authentication which is outlined below in the “Authentication” section. At that point, I would probably use SSH in addition to HTTP.
Step by Step Mercurial Hosting
Below are the steps to create the same Mercurial hosting I have been using for the past month. It works well for my purposes. These steps were done on Debian Buster.
Create Repository Directory
First, you’ll want to create a directory that will hold all of your repositories. The idea is that you’ll only interact with these repositories through the mercurial server. I mounted a hard-drive to
/var/repos using fstab. You can use any directory, partition, etc, as long as it’s available when your computer (or server) boots, or more specifically before the systemd service loads.
# mkdir /var/repos
Create the User
Using a dedicated user for daemons is recommended to limit how much access the daemon has. appropriately named the user
hg and set its home directory to the repository directory.
# useradd -M --home /var/repos --system --shell /usr/sbin/nologin hg
The flags used in the above command perform the below actions:
-M: Do not create the user’s home directory.
--home: Sets the user’s home directory.
--system: Creates the user as a system account.
--shell: Set the user’s shell.
Writing the Config
We’re using the
hg serve command as our server. You can modify the configuration file as needed. Below is the base configuration I use which is saved to
/etc/hgweb.conf. For more settings, read the hgweb documentation and the hgrc documentation.
The below configuration also accepts changesets. Don’t make this a publicly accessible server. Finally, the paths section allows for multiple repositories to be served at the same time.
[web] address = 127.100.0.1 port = 8080 allow_push = * push_ssl = false [paths] / = /var/repos/*
Systemd Service File
Below is the systemd file you want to save to
/lib/systemd/system/hgweb.service if you’re using Debian Buster with systemd. For other system managers, you’ll have to look into how to configure services.
[Unit] Description=mercurial web service After=network-online.target [Service] Type=simple ExecStartPre=+iptables -t nat -A OUTPUT -p tcp -d 127.100.0.1 --dport 80 -j DNAT --to 127.100.0.1:8080 ExecStart=/usr/bin/hg serve --web-conf /etc/hgweb.conf Restart=on-failure WorkingDirectory=/var/repos User=hg # Set up a new file system namespace and mounts private /tmp and /var/tmp # directories so this service cannot access the global directories and # other processes cannot access this service's directories. PrivateTmp=true # Mounts the /usr, /boot, and /etc directories read-only for processes # invoked by this unit. ProtectSystem=full # Ensures that the service process and all its children can never gain new # privileges NoNewPrivileges=true # Sets up a new /dev namespace for the executed processes and only adds API # pseudo devices such as /dev/null, /dev/zero or /dev/random (as well as # the pseudo TTY subsystem) to it, but no physical devices such as /dev/sda. PrivateDevices=true # Required for dropping privileges and running as a different user CapabilityBoundingSet=CAP_SETGID CAP_SETUID CAP_SYS_RESOURCE CAP_NET_BIND_SERVICE # Restricts the set of socket address families accessible to the processes # of this unit. Protects against vulnerabilities such as CVE-2016-8655 RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX [Install] WantedBy=multi-user.target
One weird aspect of the service file is the
ExecStartPre directive. This is in place to create an iptables port forward from 80 to 8080. Read the section Port Forwarding below to understand why I decided on this method.
Enable the Service
Systemd services are not enabled by default. Issue the following command to enable the service to have it start automatically when your computer starts. Of course, refrain from running this command if you don’t want this behavior.
# systemctl enable hgweb
You can check the status of the service by issuing the following commands.
# systemctl status hgweb # journalctl -u hgweb
Below is additional information diving into more specifics on my decisions and details on expanding upon my hosting configuration.
Because we’re running the daemon as the user
hg, we can’t open reserved port numbers between 1 and 1024. This becomes a problem when we want to access our repositories through port 80 or 443. The workaround I ended up with was to port forward 80 to 8080. You could also forward from 443 to an alternative port if you are running the server with TLS certificates.
The simple solution would be to run the service as root, but running daemons as root is not recommended due to the possible security risk. Another option is to simply add the port to the request when performing mercurial commands. I’d rather not do that though.
I skipped authentication because the daemon is serving the repositories on the loopback interface. If I were to host the repository on LAN, I would add authentication. The amount of authentication would vary depending on the specifics.
The fastest method for authentication with the above setup is to use a web server that allows reverse proxying. You would then proxy the requests from the web server to the mercurial server. The web server would handle authentication through normal HTTP Authorization. Using this method it’s highly recommended to use TLS to prevent password sniffing and MITM attacks.
An even more secure setup is to have the HTTP repositories as read-only and then use SSH for pushing changesets. There are many different ways this can be implemented, but the specifics are outside the scope of this post.
Backing up your repositories is done the same way you’d backup any other directory. Since all the repositories are stored in
/var/repos or whichever directory you decided to use, you can just archive that directory as root or as the hg user.
To simplify the access to the web interface and when pushing and pulling changesets, I created an A record that pointed to my loopback IP address. My current network setup doesn’t have a DNS server; Instead, I used my domain’s nameservers to edit the A record for
hg.example.com to point to
127.100.0.1. By all means, if you run a local DNS server, configure that instead if you want.
Setting a DNS record solves two problems. First, it makes accessing the repositories a lot easier. Secondly, in the future when you want to change where your repositories are hosted, you can just change the DNS records and not break all the projects relying on the IP address to pull dependencies.