Water Ripple "Water Ripple" by Frame Harirak is licensed under Unsplash.

Local Mercurial Hosting

Our site remains ad-free thanks to our affiliates. This post's featured affiliate is Linode. Simplify your infrastructure with Linode's cloud computing and hosting solutions. New users get $100 in free credit.

The post is not sponsored by the above affiliate and the content here is not representative of their company.

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

Additional Information

Below is additional information diving into more specifics on my decisions and details on expanding upon my hosting configuration.

Port Forwarding

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.

Authentication

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.

Backups

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.

DNS Records

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.

Related Posts

Hugo Footnotes and Citations

Add footnotes, citations, and references to your Hugo posts with this simple technique. Give your articles more credibility and improve your posts by making them more informative.

Local Mercurial Hosting

A basic mercurial hosting solution for local development. Learn the ins and outs of creating a mercurial server to hose all your repositories locally.

How to Train SpamAssassin

Learn about the different methods used to train SpamAssassin, along with initial spam data sources to use with SpamAssassin. Update your bayes database easily with existing data.

SpamAssassin SA-Update Tool

Learn what SpamAssassin's sa-update tool does, how it works, and if you should keep it running and modifying the configuration files on your server.