OwnTracks Recorder is a web application which maps locations over time. Generally, it connects to an MQTT server and subscribes to owntracks/+ topics for any location updates, but it also has a built in function to receive updates over HTTP.
I have been using OwnTracks with MQTT for a while, but found it to be too unreliable on Android (disconnects in the background and doesn’t reconnect nicely). Using HTTP is supposed to be more reliable, so this is how I set it up. The idea is to use OwnTracks on Android to post directly to the OwnTracks recorder over HTTP instead of MQTT and have recorder post the MQTT messages on our behalf using LUA scripts (for Home Assistant).
Friends is an important feature (to let members of the family see where eachother is located) and fortunately it is supported in HTTP mode (but it requires a little bit more configuration).
nginx and base configuration
We will use nginx as a reverse proxy in front of the recorder to provide both TLS and authentication to keep the service private and secure.
sudo dnf install nginx httpd-tools
Configure nginx to proxy to OwnTracks recorder by creating a new config file for the domain you are hosting on. For example, if your domain is owntracks.yourdomain.com then create a file at /etc/nginx/conf.d/owntracks.yourdomain.com. Later certbot will update this to add TLS configuration.
cat << \EOF |sudo tee /etc/nginx/conf.d/owntracks.yourdomain.com.conf server { server_name owntracks.yourdomain.com; root /var/www/html; auth_basic "OwnTracks"; auth_basic_user_file /etc/nginx/owntracks.htpasswd; proxy_set_header X-Limit-U $remote_user; location / { proxy_pass http://127.0.0.1:8083/; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Real-IP $remote_addr; } location /ws { rewrite ^/(.*) /$1 break; proxy_pass http://127.0.0.1:8083; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } location /view/ { proxy_buffering off; proxy_pass http://127.0.0.1:8083/view/; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Real-IP $remote_addr; } location /static/ { proxy_pass http://127.0.0.1:8083/static/; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Real-IP $remote_addr; } location /pub { proxy_pass http://127.0.0.1:8083/pub; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Real-IP $remote_addr; } error_page 404 /404.html; location = /40x.html { } error_page 500 502 503 504 /50x.html; location = /50x.html { } } EOF
Now that we have a web server let’s open the ports to enable traffic on port 80 and 443.
sudo firewall-cmd --zone=FedoraServer --add-service=http sudo firewall-cmd --zone=FedoraServer --add-service=https sudo firewall-cmd --runtime-to-permanent
SELinux will block nginx from acting as a proxy and connecting to our other services, so we need to tell it that it’s OK.
sudo setsebool -P httpd_can_network_connect 1 sudo setsebool -P httpd_can_network_relay 1
Note that we’ve set a password file for nginx to protect recorder in the config, now we need to create that file.
Let’s pretend that we have three users, Alice, Bob and Charlie. Create the nginx password file when you add the password for Alice, then add the password for the other two.
sudo htpasswd -c /etc/nginx/owntracks.htpasswd alice sudo htpasswd /etc/nginx/owntracks.htpasswd bob sudo htpasswd /etc/nginx/owntracks.htpasswd charlie
That’s the core nginx config done, next we will use cerbot to get a certificate and re-configure nginx to use TLS.
Certbot
Install certbot and the nginx plugin, which will let us get signed certificates from Let’s Encrypt. Using the plugin means it will configure nginx to handle the challenge and write the config file automatically. You will need to make sure that port 80 on your nginx server is available over the Internet (and probably also port 443 so that we can connect securely to recorder remotely) as well as a DNS entry pointing to your external IP (I’ll use owntracks.yourdomain.com as an example).
sudo dnf install certbot python3-certbot-nginx
Next, use certbot to get TLS certificates from Let’s Encrypt. Follow the prompts and be sure to enable TLS redirection so that all traffic will be encrypted.
sudo certbot --agree-tos \ --redirect \ --rsa-key-size 4096 \ --nginx \ -d owntracks.yourdomain.com
Now that we have a certificate, let’s enable auto renewals.
sudo systemctl enable --now certbot-renew.timer
OK, nginx should now be configured with TLS and managed by certbot.
Recorder with Docker
Now let’s get the recorder container going! First install and prepare Docker. Note that if you’re running on Fedora 31 or later, you need to revert to cgroup v1 first.
sudo groupadd -r docker sudo gpasswd -a ${USER} docker newgrp docker sudo dnf install -y cockpit-docker docker sudo systemctl start docker sudo systemctl enable docker
Next let’s prepare the configuration and scripts for the container.
sudo mkdir -p /var/lib/owntracks/{config,scripts,logs,last}
Generally we pass variables into containers, but recorder also supports a config file so we’ll use that instead (OTR_LUASCRIPT is not supported as a variable, anyway). Replace the values for your MQTT server below.
NOTE: OTR_PORT must not be a number not a string, else it will be be ignored.
OTR_HOST="mqtt-broker" OTR_PORT=mqtt-port OTR_USER="mqtt-user" OTR_PASS="mqtt-user-password" cat << EOF | sudo tee /var/lib/owntracks/config/recorder.conf OTR_TOPICS = "owntracks/#" OTR_HTTPHOST = "0.0.0.0" OTR_STORAGEDIR = "/store" OTR_HTTPLOGDIR = "/logs" OTR_LUASCRIPT = "/scripts/hook.lua" OTR_HOST = "${OTR_HOST}" OTR_PORT = ${OTR_PORT} OTR_USER = "${OTR_USER}" OTR_PASS = "${OTR_PASS}" OTR_CLIENTID = "owntracks-recorder" EOF
If you’re using TLS on your MQTT server, then copy over the CA (for example, /etc/pki/tls/certs/ca-bundle.crt) and set the OTR_CAFILE config option to point to the file as it will be inside the container. This will automatically enable TLS connection to your MQTT server.
sudo cp /etc/pki/tls/certs/ca-bundle.crt /var/lib/owntracks/config/ca.crt echo 'OTR_CAFILE="/config/ca.crt"' | sudo tee -a /var/lib/owntracks/config/recorder.conf
Next get the Lua scripts ready which will allow recorder to forward HTTP events on to MQTT. We will write a file called hook.lua to run the script, which is referenced in the config above. It has a JSON dependency, which we will download from the Internet.
wget http://regex.info/code/JSON.lua sudo mv JSON.lua /var/lib/owntracks/scripts/JSON.lua cat << EOF | sudo tee /var/lib/owntracks/scripts/hook.lua JSON = (loadfile "/scripts/JSON.lua")() function otr_init() end function otr_exit() end function otr_hook(topic, _type, data) otr.log("DEBUG_PUB:" .. topic .. " " .. JSON:encode(data)) if(data['_http'] == true) then if(data['_repub'] == true) then return end data['_repub'] = true local payload = JSON:encode(data) otr.publish(topic, payload, 1, 1) end end function otr_putrec(u, d, s) j = JSON:decode(s) if (j['_repub'] == true) then return 1 end end EOF
Next we can run the container for recorder. We will map in all of the directories we created earlier and the configuration we created should be read in when the program in the container starts. Note that :Z
option sets the SELinux context on those config files.
docker run -dit --name recorder \ --restart always \ -p 8083:8083 \ -v /var/lib/owntracks/store:/store:Z \ -v /var/lib/owntracks/config:/config:Z \ -v /var/lib/owntracks/scripts:/scripts:Z \ -v /etc/localtime:/etc/localtime:ro \ owntracks/recorder
OwnTracks should now be listening on port 8083, waiting for connections to come in through nginx!
Friends with OwnTracks
To set up friends in HTTP mode we need to get a shell on the container and load friends data into the database.
docker exec -it recorder /bin/sh
Inside the container we load friends data into the database. Let’s use our three friends as an example, Alice with her phone pixel3xl, Bob with his pixel4 and Charlie with her pixel3a, to set up notifications for everyone.
ocat --load=friends << EOF alice-pixel3xl [ "bob/pixel4", "charlie/pixel3a" ] bob-pixel4 [ "alice/pixel3xl, "charlie/pixel3a" ] charlie-pixel3a [ "alice/pixel3xl, "bob/pixel4" ] EOF
We can dump the friends data to see what we’ve loaded, then exit the container.
ocat -S /store --dump=friends exit
Now, whenever Alice, Bob or Charlie update their location, recorder will return JSON data with the location of the other two. OwnTracks will then display that information under the Friends tab. Unfortunately, the one thing thing HTTP mode doesn’t support is Regions notifications to be notified when friends enter or leave defined way points, but I’ve found OwnTracks to be much more reliable with HTTP so I guess that’s a small price to pay…