How to Archive UniFi Protect Footage

Here are some links and notes on archiving a UniFi Protect’s footage.

Apparently, the .ubv files just need to be remuxed to .mp4 so they are easily playable. On the UniFi Protect appliances, they have a ubnt_ubvexport and ubnt_ubvinfo binary that can do the remux. You can copy the binary off and run it with QEMU on x86 hardware.

Helpful Links.

https://github.com/danielfernau/unifi-protect-video-downloader

https://github.com/petergeneric/unifi-protect-remux

https://durdle.com/2022/01/22/extract-unifi-protect-video/

RSYNC

We can setup rsync to copy the raw footage off the Unifi Protect appliance. Once we have it locally, we can use the remux tool to convert the files to .mp4 so we can easily view them.

A cool thing about using rsync, is if our copy gets interrupted, we can just rerun the command and it will pick up where it left off without duplicating anything.

The following command is a mouthful. It searches for all the recorded video files for cameras with the MAC addresses specified. (MAC addresses can be found from the web interface), There are only a couple things to change or tweak for the command to work for you.

MAC1 should be the MAC address of camera 1 while MAC2 is the MAC address of the next camera we want to archive.
Change the dst_directory to the archive directory or drive.
And of course we need to change the IP address (10.0.0.1) to the UniFi Protect IP address.

ssh root@10.0.0.1 'find /srv/unifi-protect/video/ \( -name "MAC1*" -o -name "MAC2*" \) -printf %P\\0\\n' | rsync -a -v --exclude="*timelapse*" --files-from=- root@10.0.0.1:/src/unifi-protect/video/ dst_directory/

Here are the details for the commands.

  • -printf %P\\0\\n : Don’t print the full path, i.e. “/src/unifi-protect/video/”
  • -name “MAC1*” : Search for recording files that start with camera mac1 address.
  • -o -name “MAC2*” : Let’s us search for multiple “cameras” add more -o -name “mac3*” etc
  • rsync
  • -a : archive mode, copies date, permissions etc.
  • -v : verbose output. Not needed, but it is nice to see what it is copying.
  • –exclude=”*timelapse*” : Exclude timelapse files. Remove this if you want to archive them.
  • –files-from=- : Tells rsync to use the standard input for the list of files to download.
  • root@10.10.1.1:/src/unifi-protect/video/ : This is the source directory for where the video files are located
  • /archive/directory : The path where we are archiving the video footage.

Acquire ubnt_ubvinfo from UDM

Before we can use remux, we need to setup a local copy of ubnt_ubvinfo.

You should be able to use the following scp command to copy the ubnt_ubvinfo or ubnt_ubvexport binary from the UniFi Protect appliance.

scp root@10.0.0.1:/usr/share/unifi-protect/app/node_modules/.bin/ubnt_ubvexport ./

To install on Intel or AMD CPU’s, check out the following section on the unifi-protect-remux page.

https://github.com/petergeneric/unifi-protect-remux#quick-start-for-x86-linux

As a side note, it looks like you can download an old x86 version of ubnt_ubvinfo from archive.org. Use at your own discretion.

wget https://archive.org/download/ubnt_ubvinfo/ubnt_ubvinfo

Install unifi-protect-remux

Install ffmpeg

apt install -y ffmpeg

or

dnf install -y ffmpeg

Now we can download and install remux.

wget https://github.com/petergeneric/unifi-protect-remux/releases/download/v3.0.6/remux-x86_64.tar.gz
tar zxf remux-x86_64.tar.gz
sudo mv remux /usr/bin/

Now we can remux the files.

remux --with-audio=true dst_directory/*.ubv

You will need to script a way to recursively loop through the directories, or just do it manually.

UniFi-Video Service failing to Start

Do note that the UniFi-Video software is no longer supported should be migrated to the UniFi Protect systems.

Running “systemctl status unifi-video” gives the following error.

(unifi-video) checking for system.properties and truststore files…
…fail!

It appears that the error is due to an unsupported version of Java. Installing an older version of Java resolves the issue.

You can check which versions of Java are available with

apt-cache show openjdk-8-jre

The versions that showed up for me were

8u282-b08-0ubuntu1
8u252-b09-1ubuntu1 

Installing the last one fixed the issue for now.

sudo apt-get install openjdk-8-jre-headless=8u252-b09-1ubuntu1

https://old.reddit.com/r/Ubiquiti/comments/l30jm5/unifi_video_31013_not_compatible_with_openjdk_180/

Notes on Migrating UniFi-Video NVR Disk

Recently upgraded a UniFi-Video VM by creating a new VM and restoring the backup from the previous one. There was a separate virtual disk used for storing all the videos. Although once I reattached it, I had to change the owner to the unifi-video user. lt was reporting that the owner/group was 127 and 134, looks like that was the user ID from the previous VM.

Example output from ls -hla command

drwxr-xr-x 3 127 134 4.0K Jan 16 2020 176de761-8399-3bed-8ceb-917cbe4d25bd
drwxr-xr-x 2 127 134 4.0K Sep 10 12:28 21d506ef-dded-31f2-baf8-c109a7e653b9
drwxr-xr-x 4 127 134 4.0K Jan 1 2020 1a3afc4c-a030-39a1-b139-857444c8b71c

Ended up changing the owner to the unifi-video owner with the chown command. /path/to/videos is the mount point of the video drive.

chown -R unifi-video:unifi-video /path/to/videos

Configuring UFW Firewall on Ubuntu for UniFi and UniFi-Video

You should be able to copy and paste all the following commands in. You can check the status of the commands after with

sudo ufw status

Allowing access to UniFi ports

sudo ufw allow 3478/UDP
sudo ufw allow 5514/UDP
sudo ufw allow 8080/TCP
sudo ufw allow 8443/TCP
sudo ufw allow 8880/TCP
sudo ufw allow 8843/TCP
sudo ufw allow 6789/TCP
sudo ufw allow 27117/TCP
sudo ufw allow 10001/UDP
sudo ufw allow 1900/UDP

UniFI Video ports

sudo ufw allow 7080/TCP
sudo ufw allow 7443/TCP
sudo ufw allow 7445/TCP
sudo ufw allow 7446/TCP
sudo ufw allow 7447/TCP
sudo ufw allow 10001/UDP
sudo ufw allow 6666/TCP
sudo ufw allow 7004/UDP
sudo ufw allow 7442/TCP

Helpful links

https://help.ui.com/hc/en-us/articles/217875218-UniFi-Video-Ports-Used
https://help.ui.com/hc/en-us/articles/218506997-UniFi-Ports-Used

Change UniFi Camera Password from Command Line

Basic steps are

  • Change password with passwd
  • Copy passwd hash to /tmp/system.cfg
  • Apply Changes

Changing Password with passwd

The passwd command is normally used for changing the password for a Linux user. We’ll use it to change our user password and then copy the hash out of the /etc/passwd file to use in the Ubiquiti config file.

UVC.v4.18.37.67# passwd
Changing password for admin
New password:
Retype password:
passwd: password for admin changed by admin
UVC.v4.18.37.67#

Copy passwd hash to /tmp/system.cfg

Using “cat /etc/passwd”, we can get what the new password hash is and can copy that into the /tmp/system.cfg file.

UVC.v4.18.37.67# cat /etc/passwd
admin:$6$K4VfN1jGcxaX63Eo$yMGBg7jgCCg.HorkmkQf9wnQcEko.1onZcAa6ua421LonYgfYr8FgHprrwkMqaTYzH0KqCtOEPqDlB3AvkvcW.:0:0:Administrator:/etc/persistent:/bin/sh
UVC.v4.18.37.67#

Open up the /tmp/system.cfg config file, find the section below and replace the hash part, i.e. the part that is in bold.

users.1.name=admin
users.1.password=$6$K4VfN1jGcxaX63Eo$yMGBg7jgCCg.HorkmkQf9wnQcEko.1onZcAa6ua421LonYgfYr8FgHprrwkMqaTYzH0KqCtOEPqDlB3AvkvcW.
users.1.status=1

As a side note, you can copy and past all of the “users.1.*” lines and change the 1 to 2 and have a second user.

Apply Changes

You can use the following command to write the changes and then reboot the camera.

cfgmtd -f /tmp/system.cfg -w && reboot

Long Example

[admin@localhost ~]$ ssh ubnt@10.96.1.91
ubnt@10.96.1.91's password:
BusyBox v1.29.2 () built-in shell (ash)
UVC.v4.18.37.67# passwd
Changing password for admin
New password:
Retype password:
passwd: password for admin changed by admin
UVC.v4.18.37.67# cat /etc/passwd
admin:$6$K4VfN1jGcxaX63Eo$yMGBg7jgCCg.HorkmkQf9wnQcEko.1onZcAa6ua421LonYgfYr8FgHprrwkMqaTYzH0KqCtOEPqDlB3AvkvcW.:0:0:Administrator:/etc/persistent:/bin/sh
UVC.v4.18.37.67# vi /tmp/system.cfg <- Edit the text file and replace the hash with the one from above
UVC.v4.18.37.67# cfgmtd -f /tmp/system.cfg -w && reboot

MongoDB – dpkg-deb: error: paste subprocess was killed by signal (Broken pipe)

Ran into an issue when trying to figure out some problems with UniFi and UniFi-Video. Upgrading both to the latest version was causing problems, because they both needed different versions of MongoDB.

Was getting the following error when trying to install MongoDB. Think I was trying to install Mongo following the instructions on their site and then it caused issues with apt.

dpkg: error processing archive /var/cache/apt/archives/mongodb-org-server_4.2.5_amd64.deb (--unpack):
trying to overwrite '/usr/bin/mongod', which is also in package mongodb-server-core 1:3.6.3-0ubuntu1.1
dpkg-deb: error: paste subprocess was killed by signal (Broken pipe)

Removed the following file

rm /etc/apt/sources.list.d/mongodb-org-4.2.list

and was able to use apt again to install MongoDB. Didn’t end up solving my problem because I technically needed two versions, but at least apt was being nice again. Ended up installing the UniFi controller docker container and can run both on the same server that way.

Auto renew SSL Cert with UniFi running in Docker

Setting up the SSL cert for UniFi service when running in docker is fairly easy to do. All you have to do is modify the UniFi SSL renew script to use the UniFi Docker directory and change the start and stop service to start and stop the Docker container. The script below should be ready to go.

Download, chmod +x it, and run, drop it in cron to auto renew.

In the below script, change (unifiDir=”/docker/unifi”) to your UniFi directory.

Note: this triggers calling the teams.sh script that will send an update to Microsoft Teams to let you know that the certs should be renewed. Check here for more info.

#!/usr/bin/env bash
# Added support to do UniFi and UniFi controllers at the same time using the same cert.
# Original script from https://git.sosdg.org/brielle/lets-encrypt-scripts/raw/branch/master/gen-unifi-cert.sh
# More info here https://www.reddit.com/r/Ubiquiti/comments/43v23u/using_letsencrypt_with_the_unifi_controller/ 
# And here https://www.reddit.com/r/Ubiquiti/comments/43v23u/using_letsencrypt_with_the_unifi_controller/
# Modified script from here: https://github.com/FarsetLabs/letsencrypt-helper-scripts/blob/master/letsencrypt-unifi.sh
# Modified by: Brielle Bruns <bruns@2mbit.com>
# Download URL: https://source.sosdg.org/brielle/lets-encrypt-scripts
# Version: 1.7
# Last Changed: 04/10/2020
# 04/10/2020: Changed directories and commands to work with a UniFi Docker install
# 02/02/2016: Fixed some errors with key export/import, removed lame docker requirements
# 02/27/2016: More verbose progress report
# 03/08/2016: Add renew option, reformat code, command line options
# 03/24/2016: More sanity checking, embedding cert
# 10/23/2017: Apparently don't need the ace.jar parts, so disable them
# 02/04/2018: LE disabled tls-sni-01, so switch to just tls-sni, as certbot 0.22 and later automatically fall back to http/80 for auth
# 05/29/2018: Integrate patch from Donald Webster <fryfrog[at]gmail.com> to cleanup and improve tests
# 09/26/2018: Change from TLS to HTTP authenticator

# Location of LetsEncrypt binary we use.  Leave unset if you want to let it find automatically
# LEBINARY="/usr/src/letsencrypt/certbot-auto"

# Change to your UniFi Docker directory
unifiDir="/docker/unifi"

PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"

function usage() {
  echo "Usage: $0 -d <domain> [-e <email>] [-r] [-i]"
  echo "  -d <domain>: The domain name to use."
  echo "  -e <email>: Email address to use for certificate."
  echo "  -r: Renew domain."
  echo "  -i: Insert only, use to force insertion of certificate."
}

while getopts "hird:e:" opt; do
  case $opt in
    i) onlyinsert="yes";;
    r) renew="yes";;
    d) domains+=("$OPTARG");;
    e) email="$OPTARG";;
    h) usage
       exit;;
  esac
done

DEFAULTLEBINARY="/usr/bin/certbot /usr/bin/letsencrypt /usr/sbin/certbot
  /usr/sbin/letsencrypt /usr/local/bin/certbot /usr/local/sbin/certbot
  /usr/local/bin/letsencrypt /usr/local/sbin/letsencrypt
  /usr/src/letsencrypt/certbot-auto /usr/src/letsencrypt/letsencrypt-auto
  /usr/src/certbot/certbot-auto /usr/src/certbot/letsencrypt-auto
  /usr/src/certbot-master/certbot-auto /usr/src/certbot-master/letsencrypt-auto"

if [[ ! -v LEBINARY ]]; then
  for i in ${DEFAULTLEBINARY}; do
    if [[ -x ${i} ]]; then
      LEBINARY=${i}
      echo "Found LetsEncrypt/Certbot binary at ${LEBINARY}"
      break
    fi
  done
fi

# Command line options depending on New or Renew.
NEWCERT="--renew-by-default certonly"
RENEWCERT="-n renew"

# Check for required binaries
if [[ ! -x ${LEBINARY} ]]; then
  echo "Error: LetsEncrypt binary not found in ${LEBINARY} !"
  echo "You'll need to do one of the following:"
  echo "1) Change LEBINARY variable in this script"
  echo "2) Install LE manually or via your package manager and do #1"
  echo "3) Use the included get-letsencrypt.sh script to install it"
  exit 1
fi

if [[ ! -x $( which keytool ) ]]; then
  echo "Error: Java keytool binary not found."
  exit 1
fi

if [[ ! -x $( which openssl ) ]]; then
  echo "Error: OpenSSL binary not found."
  exit 1
fi

if [[ ! -z ${email} ]]; then
  email="--email ${email}"
else
  email=""
fi

shift $((OPTIND -1))
for val in "${domains[@]}"; do
        DOMAINS="${DOMAINS} -d ${val} "
done

MAINDOMAIN=${domains[0]}

if [[ -z ${MAINDOMAIN} ]]; then
  echo "Error: At least one -d argument is required"
  usage
  exit 1
fi

if [[ ${renew} == "yes" ]]; then
  LEOPTIONS="${RENEWCERT}"
else
  LEOPTIONS="${email} ${DOMAINS} ${NEWCERT}"
fi

if [[ ${onlyinsert} != "yes" ]]; then
  echo "Firing up standalone authenticator on TCP port 80 and requesting cert..."
  ${LEBINARY} --server https://acme-v01.api.letsencrypt.org/directory \
              --agree-tos --standalone --preferred-challenges http ${LEOPTIONS}
fi

if [[ ${onlyinsert} != "yes" ]] && md5sum -c "/etc/letsencrypt/live/${MAINDOMAIN}/cert.pem.md5" &>/dev/null; then
  echo "Cert has not changed, not updating controller."
  exit 0
else
  echo "Cert has changed or -i option was used, updating controller..."
  TEMPFILE=$(mktemp)
  CATEMPFILE=$(mktemp)

  # Identrust cross-signed CA cert needed by the java keystore for import.
  # Can get original here: https://www.identrust.com/certificates/trustid/root-download-x3.html
  cat > "${CATEMPFILE}" <<'_EOF'
-----BEGIN CERTIFICATE-----
MIIDSjCCAjKgAwIBAgIQRK+wgNajJ7qJMDmGLvhAazANBgkqhkiG9w0BAQUFADA/
MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT
DkRTVCBSb290IENBIFgzMB4XDTAwMDkzMDIxMTIxOVoXDTIxMDkzMDE0MDExNVow
PzEkMCIGA1UEChMbRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QgQ28uMRcwFQYDVQQD
Ew5EU1QgUm9vdCBDQSBYMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
AN+v6ZdQCINXtMxiZfaQguzH0yxrMMpb7NnDfcdAwRgUi+DoM3ZJKuM/IUmTrE4O
rz5Iy2Xu/NMhD2XSKtkyj4zl93ewEnu1lcCJo6m67XMuegwGMoOifooUMM0RoOEq
OLl5CjH9UL2AZd+3UWODyOKIYepLYYHsUmu5ouJLGiifSKOeDNoJjj4XLh7dIN9b
xiqKqy69cK3FCxolkHRyxXtqqzTWMIn/5WgTe1QLyNau7Fqckh49ZLOMxt+/yUFw
7BZy1SbsOFU5Q9D8/RhcQPGX69Wam40dutolucbY38EVAjqr2m7xPi71XAicPNaD
aeQQmxkqtilX4+U9m5/wAl0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNV
HQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMSnsaR7LHH62+FLkHX/xBVghYkQMA0GCSqG
SIb3DQEBBQUAA4IBAQCjGiybFwBcqR7uKGY3Or+Dxz9LwwmglSBd49lZRNI+DT69
ikugdB/OEIKcdBodfpga3csTS7MgROSR6cz8faXbauX+5v3gTt23ADq1cEmv8uXr
AvHRAosZy5Q6XkjEGB5YGV8eAlrwDPGxrancWYaLbumR9YbK+rlmM6pZW87ipxZz
R8srzJmwN0jP41ZL9c8PDHIyh8bwRLtTcm1D9SZImlJnt1ir/md2cXjbDaJWFBM5
JDGFoqgCWjBH4d1QB7wCCZAA62RjYJsWvIjJEubSfZGL+T0yjWW06XyxV3bqxbYo
Ob8VZRzI9neWagqNdwvYkQsEjgfbKbYK7p2CNTUQ
-----END CERTIFICATE-----
_EOF

  md5sum "/etc/letsencrypt/live/${MAINDOMAIN}/cert.pem" > "/etc/letsencrypt/live/${MAINDOMAIN}/cert.pem.md5"
  echo "Using openssl to prepare certificate..."
  cat "/etc/letsencrypt/live/${MAINDOMAIN}/chain.pem" >> "${CATEMPFILE}"
  openssl pkcs12 -export  -passout pass:aircontrolenterprise \
          -in "/etc/letsencrypt/live/${MAINDOMAIN}/cert.pem" \
          -inkey "/etc/letsencrypt/live/${MAINDOMAIN}/privkey.pem" \
          -out "${TEMPFILE}" -name unifi \
          -CAfile "${CATEMPFILE}" -caname root

  docker container stop ${dockerContainerId}
  sleep 10
  dockerContainerId=$(sudo docker container list | grep unifi-controller | awk '{print $1}')
  echo "Removing existing certificate from Unifi protected keystore..."
  keytool -delete -alias unifi -keystore ${unifiDir}/keystore -deststorepass aircontrolenterprise

  echo "Inserting certificate into Unifi keystore..."
  keytool -trustcacerts -importkeystore \
          -deststorepass aircontrolenterprise \
          -destkeypass aircontrolenterprise \
          -destkeystore ${unifiDir}/keystore \
          -srckeystore "${TEMPFILE}" -srcstoretype PKCS12 \
          -srcstorepass aircontrolenterprise \
          -alias unifi

  sleep 2
  echo "Starting Unifi controllers..."
  docker container start ${dockerContainerId}
  ./teams.sh -b "$(hostname) - UniFi service is restarting, ssl cert should be renewed."

  echo "Done!"
fi

UniFi Cameras – configure static IP or DHCP from command line

The UniFi cameras are different then the AirMax line in how the configuration works. There is a “/tmp/system.cfg” file, but it does not contain most of the configuration option. In the default directory there are some files which appear to handle the device configuration.

The network configuration file is “ubnt_networkd.conf”

Edit the file by opening it up in vi or a different text editor.

Static IP config

{
     "cfgver": 2,
     "dhcp": {
         "fallbackIP": "192.168.1.20",
         "fallbackNetmask": "255.255.255.0",
         "status": 0  <-- 0=Static 1=DHCP
     },
     "dns": {
         "ns1": {
             "ip": "8.8.8.8",
             "status": 1
         },
         "ns2": {
             "ip": "4.2.2.2",
             "status": 1
         }
     },
     "routes": {
         "gateway": "192.168.5.1"
     },
     "statik": {
         "ip": "192.168.5.11",
         "netmask": "255.255.255.0"
     }
}

If you want to set the static address, you should be able to edit the settings in this config and leave DHCP status disabled.

DHCP config

To set the Camera to use DHCP, under dhcp change “”status”: 0″ to “”status”: 1″

{
     "cfgver": 2,
     "dhcp": {
         "fallbackIP": "192.168.1.20",
         "fallbackNetmask": "255.255.255.0",
         "status": 1
     },
     "dns": {
         "ns1": {
             "ip": "8.8.8.8",
             "status": 1
         },
         "ns2": {
             "ip": "4.2.2.2",
             "status": 1
         }
     },
     "routes": {
         "gateway": "192.168.5.1"
     },
     "statik": {
         "ip": "192.168.5.11",
         "netmask": "255.255.255.0"
     }
}

After the settings are applied the camera will use use DHCP regardless of the statik ip settings.

Save changes

Use the following command to write the changes to persistent storage

ubnt_cmd_persist.sh

And reboot the camera.

reboot

Camera should pull a DHCP address when it boots up

Auto renew ssl cert for UniFI and UniFi-Video

The following script was taken from here

Added unifi-video support. Script uses letsencrypt to get the cert and automatically updates the UniFi and UniFi-Video Keystores.

Would be a good idea to check and make sure the the UniFi-Video cameras reconnect and still work after running script.

Installing Let’s Encrypt

Install Let’s Encrypt with the following

sudo apt install letsencrypt

And generate a cert for your domain with

sudo certbot certonly -d unifi.domain.com

Executing Script to Renew Certificate

Copy the script at the bottom of this post and put it in a file called gen-unifi-cert.sh
Run the script to insert the cert into the UniFi and UniFi-Video services.

sudo ./gen-unifi-cert.sh -e email@domain.com -d unifi.domain.com

You can run it with no or the -h argument to show the options and arguments to use.

./gen-unifi-cert.sh -h

Setup Cron Job

You should be able to add the following to a cronjob to auto renew the certificate. Replace path to script and domain name.

30 2 * * * /root/gen-unifi-cert.sh -r -d unifi.domain.com

UniFi SSL Cert Renew Script

#!/usr/bin/env bash
# Added support to do UniFi and UniFi controllers at the same time using the same cert.
# Original script from https://git.sosdg.org/brielle/lets-encrypt-scripts/raw/branch/master/gen-unifi-cert.sh
# More info here https://www.reddit.com/r/Ubiquiti/comments/43v23u/using_letsencrypt_with_the_unifi_controller/ 
# And here https://www.reddit.com/r/Ubiquiti/comments/43v23u/using_letsencrypt_with_the_unifi_controller/
# Modified script from here: https://github.com/FarsetLabs/letsencrypt-helper-scripts/blob/master/letsencrypt-unifi.sh
# Modified by: Brielle Bruns <bruns@2mbit.com>
# Download URL: https://source.sosdg.org/brielle/lets-encrypt-scripts
# Version: 1.7
# Last Changed: 09/26/2018
# 02/02/2016: Fixed some errors with key export/import, removed lame docker requirements
# 02/27/2016: More verbose progress report
# 03/08/2016: Add renew option, reformat code, command line options
# 03/24/2016: More sanity checking, embedding cert
# 10/23/2017: Apparently don't need the ace.jar parts, so disable them
# 02/04/2018: LE disabled tls-sni-01, so switch to just tls-sni, as certbot 0.22 and later automatically fall back to http/80 for auth
# 05/29/2018: Integrate patch from Donald Webster <fryfrog[at]gmail.com> to cleanup and improve tests
# 09/26/2018: Change from TLS to HTTP authenticator

# Location of LetsEncrypt binary we use.  Leave unset if you want to let it find automatically
#LEBINARY="/usr/src/letsencrypt/certbot-auto"

PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"

function usage() {
  echo "Usage: $0 -d <domain> [-e <email>] [-r] [-i]"
  echo "  -d <domain>: The domain name to use."
  echo "  -e <email>: Email address to use for certificate."
  echo "  -r: Renew domain."
  echo "  -i: Insert only, use to force insertion of certificate."
}

while getopts "hird:e:" opt; do
  case $opt in
    i) onlyinsert="yes";;
    r) renew="yes";;
    d) domains+=("$OPTARG");;
    e) email="$OPTARG";;
    h) usage
       exit;;
  esac
done

DEFAULTLEBINARY="/usr/bin/certbot /usr/bin/letsencrypt /usr/sbin/certbot
  /usr/sbin/letsencrypt /usr/local/bin/certbot /usr/local/sbin/certbot
  /usr/local/bin/letsencrypt /usr/local/sbin/letsencrypt
  /usr/src/letsencrypt/certbot-auto /usr/src/letsencrypt/letsencrypt-auto
  /usr/src/certbot/certbot-auto /usr/src/certbot/letsencrypt-auto
  /usr/src/certbot-master/certbot-auto /usr/src/certbot-master/letsencrypt-auto"

if [[ ! -v LEBINARY ]]; then
  for i in ${DEFAULTLEBINARY}; do
    if [[ -x ${i} ]]; then
      LEBINARY=${i}
      echo "Found LetsEncrypt/Certbot binary at ${LEBINARY}"
      break
    fi
  done
fi

# Command line options depending on New or Renew.
NEWCERT="--renew-by-default certonly"
RENEWCERT="-n renew"

# Check for required binaries
if [[ ! -x ${LEBINARY} ]]; then
  echo "Error: LetsEncrypt binary not found in ${LEBINARY} !"
  echo "You'll need to do one of the following:"
  echo "1) Change LEBINARY variable in this script"
  echo "2) Install LE manually or via your package manager and do #1"
  echo "3) Use the included get-letsencrypt.sh script to install it"
  exit 1
fi

if [[ ! -x $( which keytool ) ]]; then
  echo "Error: Java keytool binary not found."
  exit 1
fi

if [[ ! -x $( which openssl ) ]]; then
  echo "Error: OpenSSL binary not found."
  exit 1
fi

if [[ ! -z ${email} ]]; then
  email="--email ${email}"
else
  email=""
fi

shift $((OPTIND -1))
for val in "${domains[@]}"; do
        DOMAINS="${DOMAINS} -d ${val} "
done

MAINDOMAIN=${domains[0]}

if [[ -z ${MAINDOMAIN} ]]; then
  echo "Error: At least one -d argument is required"
  usage
  exit 1
fi

if [[ ${renew} == "yes" ]]; then
  LEOPTIONS="${RENEWCERT}"
else
  LEOPTIONS="${email} ${DOMAINS} ${NEWCERT}"
fi

#if [[ ${onlyinsert} != "yes" ]]; then
if [[ ${onlyinsert} == "yes" ]]; then
  echo "Firing up standalone authenticator on TCP port 80 and requesting cert..."
  ${LEBINARY} --server https://acme-v01.api.letsencrypt.org/directory \
              --agree-tos --standalone --preferred-challenges http ${LEOPTIONS}
fi

#if [[ ${onlyinsert} != "yes" ]] && md5sum -c "/etc/letsencrypt/live/${MAINDOMAIN}/cert.pem.md5" &>/dev/null; then
if [[ ${onlyinsert} == "yes" ]] && md5sum -c "/etc/letsencrypt/live/${MAINDOMAIN}/cert.pem.md5" &>/dev/null; then
  echo "Cert has not changed, not updating controller."
  exit 0
else
  echo "Cert has changed or -i option was used, updating controller..."
  TEMPFILE=$(mktemp)
  CATEMPFILE=$(mktemp)

  # Identrust cross-signed CA cert needed by the java keystore for import.
  # Can get original here: https://www.identrust.com/certificates/trustid/root-download-x3.html
  cat > "${CATEMPFILE}" <<'_EOF'
-----BEGIN CERTIFICATE-----
MIIDSjCCAjKgAwIBAgIQRK+wgNajJ7qJMDmGLvhAazANBgkqhkiG9w0BAQUFADA/
MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT
DkRTVCBSb290IENBIFgzMB4XDTAwMDkzMDIxMTIxOVoXDTIxMDkzMDE0MDExNVow
PzEkMCIGA1UEChMbRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QgQ28uMRcwFQYDVQQD
Ew5EU1QgUm9vdCBDQSBYMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
AN+v6ZdQCINXtMxiZfaQguzH0yxrMMpb7NnDfcdAwRgUi+DoM3ZJKuM/IUmTrE4O
rz5Iy2Xu/NMhD2XSKtkyj4zl93ewEnu1lcCJo6m67XMuegwGMoOifooUMM0RoOEq
OLl5CjH9UL2AZd+3UWODyOKIYepLYYHsUmu5ouJLGiifSKOeDNoJjj4XLh7dIN9b
xiqKqy69cK3FCxolkHRyxXtqqzTWMIn/5WgTe1QLyNau7Fqckh49ZLOMxt+/yUFw
7BZy1SbsOFU5Q9D8/RhcQPGX69Wam40dutolucbY38EVAjqr2m7xPi71XAicPNaD
aeQQmxkqtilX4+U9m5/wAl0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNV
HQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMSnsaR7LHH62+FLkHX/xBVghYkQMA0GCSqG
SIb3DQEBBQUAA4IBAQCjGiybFwBcqR7uKGY3Or+Dxz9LwwmglSBd49lZRNI+DT69
ikugdB/OEIKcdBodfpga3csTS7MgROSR6cz8faXbauX+5v3gTt23ADq1cEmv8uXr
AvHRAosZy5Q6XkjEGB5YGV8eAlrwDPGxrancWYaLbumR9YbK+rlmM6pZW87ipxZz
R8srzJmwN0jP41ZL9c8PDHIyh8bwRLtTcm1D9SZImlJnt1ir/md2cXjbDaJWFBM5
JDGFoqgCWjBH4d1QB7wCCZAA62RjYJsWvIjJEubSfZGL+T0yjWW06XyxV3bqxbYo
Ob8VZRzI9neWagqNdwvYkQsEjgfbKbYK7p2CNTUQ
-----END CERTIFICATE-----
_EOF

  md5sum "/etc/letsencrypt/live/${MAINDOMAIN}/cert.pem" > "/etc/letsencrypt/live/${MAINDOMAIN}/cert.pem.md5"
  echo "Using openssl to prepare certificate..."
  cat "/etc/letsencrypt/live/${MAINDOMAIN}/chain.pem" >> "${CATEMPFILE}"
  openssl pkcs12 -export  -passout pass:aircontrolenterprise \
          -in "/etc/letsencrypt/live/${MAINDOMAIN}/cert.pem" \
          -inkey "/etc/letsencrypt/live/${MAINDOMAIN}/privkey.pem" \
          -out "${TEMPFILE}" -name unifi \
          -CAfile "${CATEMPFILE}" -caname root

  echo "Stopping Unifi and UniFi-Video controllers..."
  systemctl stop unifi unifi-video  

  echo "Removing existing certificate from Unifi protected keystore..."
  keytool -delete -alias unifi -keystore /usr/lib/unifi/data/keystore -deststorepass aircontrolenterprise
  echo "Removing existing certificate from Unifi-Video protected keystore..."
  keytool -delete -alias unifi -keystore /usr/lib/unifi-video/data/keystore -deststorepass ubiquiti
  # following lines are needed for unifi-video
 
  echo "Inserting certificate into Unifi keystore..."
  keytool -trustcacerts -importkeystore \
          -deststorepass aircontrolenterprise \
          -destkeypass aircontrolenterprise \
          -destkeystore /usr/lib/unifi/data/keystore \
          -srckeystore "${TEMPFILE}" -srcstoretype PKCS12 \
          -srcstorepass aircontrolenterprise \
          -alias unifi

  echo "Inserting certificate into Unifi-Video keystore..."
  keytool -trustcacerts -importkeystore \
          -deststorepass ubiquiti \
          -destkeypass ubiquiti \
          -destkeystore /usr/lib/unifi-video/data/keystore \
          -srckeystore "${TEMPFILE}" -srcstoretype PKCS12 \
          -srcstorepass aircontrolenterprise \

          rm -f "${TEMPFILE}" "${CATEMPFILE}"

  mv /usr/lib/unifi-video/data/ufv-truststore{,.old} # Delete old unifi-video keystore
  sleep 5
  echo "Starting Unifi and UniFi-Video controllers..."
  systemctl start unifi unifi-video 

  echo "Done!"
fi

Errors Renewing Lets Encrypt Certificate for UniFi-Video

Had an issue with the Lets Encrypt cert for a UniFi-Video server.  When renewing the cert and reimporting it into the UniFi-Video keystore, the certification was showing out of date.

Issue ended up being something with certbot.

When certbot runs it generates a new cert.pem, chain.pem, fullchain.pem and privkey.pem and puts them in the “/etc/letsencrypt/live/unifi.domain.com/” directory.

The privkey.pem and cert.pem are used to create the keys.p12 file which gets imported into the UniFi-Video keystore.

Apparently the .pem files in “/etc/letsencrypt/live/unifi.domain.com/” are symbolic links to files in “/etc/letsencrypt/archive/unifi.domain.com/”

Upon inspection of the archive directory, multiple cert.pem and privkey.pem files were found with the names cert1.pem, cert2.pem, cert3.pem etc.  Looking at the creation date of the file revealed the symbolic link was referring to an old “cert1.pem” file.

Work around was to stop the unifi-video service and reimport the cert using the latest .pem files in the archive directory.

echo ubiquiti | openssl pkcs12 -export -inkey /etc/letsencrypt/archive/unifi.yourdomain.com/privkey2.pem -in /etc/letsencrypt/archive/unifi.yourdomain.com/cert2.pem -name airvision -out /usr/lib/unifi-video/data/keys.p12 -password stdin
echo y | keytool -importkeystore -srckeystore /etc/letsencrypt/archive/unifi.yourdomain.com/keys.p12 -srcstoretype pkcs12 -destkeystore /usr/lib/unifi-video/data/keystore -storepass ubiquiti -srcstorepass ubiquiti

Remove the old ufv-truststore and start the service.

mv /usr/lib/unifi-video/data/ufv-truststore{,.old}
systemctl start unifi-video

Worked like a charm.