Jitsi Meet in the Hetzner cloud, with Terraform and cloud-init

With the SARS-CoV-2 pandemic in full swing, “social distancing” is the order of the day. Since we're not allowed to get together with our friends and family, video conferencing is surging, and one popular free tool for that is Jitsi Meet. We've had a bunch of video chats on Jitsi Meet and have always been wondering whether the server(s) provided by Jitsi and shared by presumably thousands of conferences are a bottleneck that impacts the conference user experience. Here's how I've set up a private Jitsi Meet server on a Hetzner Cloud virtual machine.

Basics

Jitsi Meet is open-source software and the manufacturer offers ready-made packages for the current version of Debian GNU/Linux. Hence it should be reasonably simple to install, and in fact there are good explanations available that tell you what to do. So let's build our own Jitsi Meet server on a Hetzner Cloud virtual machine; right now this costs €3/month and the entertainment budget can probably bear that for a few months until the pandemic is over and we get to hang out in RL again.

Just to spice things up a little, we don't want to do a manual installation of Jitsi Meet – instead we're going to use Terraform to provision the VM and install and configure the software. Our goal is to be able to bring up the server with one single

$ terraform apply

command, so even if at some point we decide to save those three Euros per month and have an ice cream cone instead, we're ready for the next pandemic when we may want to do video conferences again.

We could use something like Ansible to configure Jitsi Meet after Terraform has done its thing, but this is a bit of a hassle because Terraform and Ansible don't work together out of the box. It's simpler to let cloud-init do the configuration; this means we will need to do some shell scripting to accomplish what we would otherwise use a few Ansible recipes to do, but it's not a lot of extra work. Cloud-init comes preinstalled on the VMs offered by virtually all commercial IaaS services including Hetzner Cloud, and Terraform supports it natively.

While the public Jitsi Meet instance on meet.jit.si lets anyone start or join a conference, we don't want to allow the whole Internet to avail itself of our private server. Hence we will be enabling a more secure mode of operation where only registered Jitsi Meet users will be allowed to start conferences (they will be prompted for a user name and password). Once a conference is running, anyone who knows the conference URL is free to join it, but the organiser can kick them off again if they are unknown strangers and/or don't behave as they should. Organisers are also free to add a password to a conference and share it with the legitimate participants, who can then use it to get in.

Terraform

The basic idea behind Terraform is to write down the structure of one's datacenter in Terraform's configuration language and let Terraform worry about setting this up on the cloud IaaS provider of one's choice. This works as long as Terraform knows about the cloud IaaS provider of one's choice and can interact with its API to do what is needed. Fortunately for us, being Hetzner Cloud customers, Terraform comes with the necessary interface to talk to Hetzner's API.

Since our “datacenter” consists of one single VM the Terraform configuration is pretty simple. The jitsimeet.tf file looks essentially like

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
variable "hcloud_token" {}
variable "ssh_key_id" {}
variable "meet_fqdn" {}

provider "hcloud" {
  token = var.hcloud_token
}

resource "hcloud_server" "meet" {
  name = "meet"
  server_type = "cx11"
  image = "debian-10"
  location = "nbg1"
  ssh_keys = [ var.ssh_key_id ]

  user_data = file("meet.userdata")
}

resource "hcloud_rdns" "meet" {
  server_id = hcloud_server.meet.id
  ip_address = hcloud_server.meet.ipv4_address
  dns_ptr = var.meet_fqdn
}

The provider declaration in lines 5–7 tells Terraform that we're using Hetzner Cloud as our IaaS provider. The token is an API token which we can obtain from our Hetzner account. For privacy, we don't include the token directly in this configuration; instead it goes into a separate file called hetzner.auto.tfvars, which looks like

1
2
3
hcloud_token = "longRandomSequenceOfLettersAndDigits"
meet_fqdn = "meet.pingucloud.de"
ssh_key_id = "Anselm"

The assignments in this file match the variable declarations at the top of jitsimeet.tf (lines 1–3) – apart from hcloud_token, meet_fqdn is the fully qualified host name of our new VM, and ssh_key_id refers to a public SSH key which Hetzner will install in the VM's root account. You can upload public SSH keys to Hetzner's web site and assign names to them, and that name is what we're looking for here. (You could also let Terraform handle the business of creating SSH keys and making them available to the Hetzner cloud, but since the rest of the PinguCloud infrastructure, including the public SSH key, are already there it seems a little extraneous. Maybe some day.)

Back in jitsimeet.tf, the hcloud_server resource on lines 9–17 describes the new VM which we're going to use for Jitsi Meet. This will be the smallest and cheapest type of VM which Hetzner offers right now (April 2020), a cx11 instance with 2 cores, 2 gigs of RAM, and 20 gigs of mirrored SSD backing store. This is about as low as one should go for Jitsi Meet, and as we're not dealing with huge conferences it should be all right. The VM will run Debian GNU/Linux 10 (“buster”, the current version at this time of writing) and be situated in Hetzner's Nuremberg datacentre (the nbg1).

The user_data declaration on line 16 specifies the meet.userdata file as the input for the cloud-init service on the instance, and we'll be coming back to that soon.

Finally, the hcloud_rdns resource on lines 19–23 controls the reverse mapping of the VM's public IP address, i.e., what happens if someone asks the DNS which name is associated with the machine's IP address. This is very importante for mail servers, and even for private servers like ours it makes the overall impression look neater. Note that we're not hardcoding anything here – all the variable elements come either from the hetzner.auto.tfvars file or else from the dynamic setup of other parts of the configuration. Terraform takes care of the proper sequencing of things; for example, it ensures that the VM is provisioned before the reverse DNS mapping, just so that the VM's public IP address is known to Terraform before it is used to configure the reverse mapping.

We can use the

$ terraform plan

command to preview what Terraform intends to do, and

$ terraform apply

to actually do it (this needs to be confirmed). If we want to get rid of the setup again, the command

$ terraform destroy

removes it (also after confirmation because this step cannot be undone).

Cloud-Init

(To be written.)

Jitsi Meet

(To be written.)