tl;dr The code for the complete Unifi setup is available in the niels-s/unifi-terraform-example repo
I’ve been running Unifi AP’s for the last couple of years, but I never took the time to set up a dedicated Unifi Controller to keep track of my AP’s. You are getting some benefits like Automatic Rolling upgrades and new features like Wifi AI, which should configure your AP’s in the most optimal way. Since I wanted to learn some new things, I’ve opted to set up a CoreOS host and utilize systemd to configure all the needed services.
In this first post of a “series”, we get started setting up a primary Droplet on Digital Ocean with SSH configured.
We use Terraform to configure the infrastructure, so we need the Digital Ocean , and the Ignition terraform providers. The Ignition provider generates a provisioning configuration, which the Droplet uses at the first boot. Ignition is a new provisioning utility designed specifically for Container Linux. You can find more information at the CoreOS site.
The basics
First of all let’s configure the Terraform providers we are going to use
provider "digitalocean" {
token = var.digitalocean_token
version = "~> 1.9"
}
provider "ignition" {
version = "~> 1.2"
}
To make the configuration a little easier to configure I’ve abstracted some hardcoded configuration options to variables:
variable "digitalocean_token" {
type = string
description = "Token used to query the DigitalOcean API"
}
variable "ssh_public_key_name" {
type = string
description = "Name of the Public key resource in Digital Ocean"
}
variable "ssh_public_key" {
type = string
description = "Public key used to allow SSH access to the VM"
}
variable "hostname" {
type = string
description = "Fully Qualified host name of the server, this will be used to request certificat with Let's Encrypt"
}
The Digital Ocean Project
Next, we configure the Digital Ocean project. If this is your first Digital
Ocean project, you already created your default project via the Digital Ocean
UI. In that case, you need to import the Digital Ocean project first, so the
Terraform resource is connected to it. Using the following command does the
command terraform import digitalocean_project.unifi your-project-id
. You can
find your project ID from the URL in the browser 😉
resource "digitalocean_project" "unifi" {
name = "unifi"
description = "all resources for Unifi Ubiquiti controller"
purpose = "Unifi Controller"
environment = "Production"
resources = [
"do:droplet:${digitalocean_droplet.unifi_controller.id}"
]
}
resource "digitalocean_ssh_key" "ssh_key" {
name = "Your Name"
public_key = var.ssh_public_key
}
You also provide a public ssh key so we can use a private ssh key for authentication instead of using passwords.
The CoreOS Droplet
Finally we can setup the initial Droplet itself,
resource "digitalocean_droplet" "unifi_controller" {
image = "coreos-stable"
name = var.hostname
region = "ams3"
size = "s-1vcpu-2gb"
ipv6 = true
resize_disk = false
ssh_keys = [digitalocean_ssh_key.ssh_key.fingerprint]
user_data = data.ignition_config.unifi_controller.rendered
lifecycle {
create_before_destroy = true
}
}
data "ignition_config" "unifi_controller" {
files = [
data.ignition_file.profile_variables.rendered,
data.ignition_file.sshd_config.rendered
]
systemd = [
data.ignition_systemd_unit.sshd_port.rendered
]
}
data "ignition_file" "profile_variables" {
filesystem = "root"
path = "/etc/profile.d/variables.sh"
mode = 420 # 644
content {
content = <<-EOT
export TERM=xterm
EOT
}
}
# Configure SSH Service
data "ignition_file" "sshd_config" {
filesystem = "root"
path = "/etc/ssh/sshd_config"
mode = 384 # 600
content {
content = <<-CONFIG
# Use most defaults for sshd configuration.
UsePrivilegeSeparation sandbox
Subsystem sftp internal-sftp
ClientAliveInterval 180
UseDNS no
UsePAM yes
PrintLastLog no # handled by PAM
PrintMotd no # handled by PAM
PermitRootLogin no
AllowUsers core
AuthenticationMethods publickey
CONFIG
}
}
data "ignition_systemd_unit" "sshd_port" {
name = "sshd.socket"
dropin {
name = "10-sshd-port.conf"
content = <<-CONFIG
[Socket]
ListenStream=
ListenStream=2222
CONFIG
}
}
A little explanation with this piece of code. With the digitalocean_droplet resource, we tell DO to create a droplet using these properties. An important property is user_data, this property supplies the generated Ignition configuration, so the CoreOS host knows what it needs to configure (files, mounts, services, …).
When you look at the Ignition resources, you notice we configure
ignition_file and ignition_systemd_unit resource. The first,
ignition_file, creates a file on the host using the content you provide.
I’ve chosen to keep the content inline, but you could also load the content
from external files like file("${path.cwd}/example.conf")
.
The later, ignition_systemd_unit, configures the sshd service to our liking since we already have an sshd service configured we use a drop-in approach here to only adjust the settings we want. But in a later post, you will see how we configure a non-preexisting service.
NOTE: I switched to a droplet with 2GB of memory because the MongoDB data was getting corrupted after some time. I guess some compression is happening periodically, which because of the memory pressure switched to using swap, but that didn’t work out that great. I didn’t have any issues anymore since I upgrade the memory. However, I have a minimal setup (4 sites, 8 AP’s) so you need to take into account your own needs 😉
The code for the complete Unifi setup is available in the niels-s/unifi-terraform-example repo , the changes of this post can be found in this commit
This post is part of a small series, go and read the next post to setup a volume mount