thoughtpile i write things here

Set up UnifiedPush with Matrix support in Gentoo

I set up UnifiedPush because I wanted push notifications in FluffyChat without talking to Google. This was a bit more difficult than I imagined, so I’m writing it down here. I will go into Gentoo specifics but a lot of this article should be useful for other Linux distributions and operating systems as well.

UnifiedPush works like this: You have a UP-server and a notification-application on your phone. The UP-server gets notifications directly from your Matrix server (for example). The notification-application connects to the UP-server and receives push notifications from it. Other applications talk with the notification-application and get the notifications.

Install and configure the server

You can install www-apps/gotify-server-bin from ::tastytea or download the binary or use docker.

Install the Gotify server in Gentoo
sudo eselect repository enable tastytea
sudo emaint sync -r tastytea
echo -e "www-apps/gotify-server-bin\n  acct-user/gotify\n    acct-group/gotify" \
     | sudo tee /etc/portage/package.accept_keywords/gotify
sudo emerge -a www-apps/gotify-server-bin

Put config.yml into /etc/gotify/ and edit it. I will assume that you changed listenaddr to [::1] and port to 7777. An example config.yml is in /usr/share/doc/gotify-server-bin-*/config.example.yml.bz2 and in the upstream repository. Now start the server.

Start the Gotify server in Gentoo and make it automatically start at boot
sudo rc-service gotify-server-bin start
sudo rc-update add gotify-server-bin
Note
If you do not use the OpenRC init script or docker, be aware that the Gotify server creates and uses a directory called data/ in its current path.

Configure nginx

I will not cover TLS certificates here, there are many good guides about that already.

Copy the configuration example, edit server_name, change listen 80; to listen [::]:443 ssl; and listen 443 ssl; and change proxy_pass to http://[::1]:7777. You don’t need the upstream bit.

Your nginx configuration should look similar to this
server {
    listen 443 ssl;
    listen [::]:443 ssl;
    server_name push.example.org;

    # access_log /var/log/nginx/push.example.org_log main;
    error_log /var/log/nginx/push.example.org_log warn;

    ssl_certificate /var/lib/dehydrated/certs/push.example.org/fullchain.pem;
    ssl_certificate_key /var/lib/dehydrated/certs/push.example.org/privkey.pem;

    location / {
        proxy_pass              http://[::1]:7777;
        proxy_http_version      1.1;

        # Ensuring it can use websockets
        proxy_set_header   Upgrade $http_upgrade;
        proxy_set_header   Connection "upgrade";
        proxy_set_header   X-Real-IP $remote_addr;
        proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header   X-Forwarded-Proto http;
        proxy_redirect     http:// $scheme://;

        # The proxy must preserve the host because gotify verifies the host with the origin
        # for WebSocket connections
        proxy_set_header   Host $http_host;

        # These sets the timeout so that the websocket can stay alive
        proxy_connect_timeout   1m;
        proxy_send_timeout      1m;
        proxy_read_timeout      1m;
    }
}

Reload nginx and change the admin password in the web interface.

common-proxies method

This is the recommended method. UnifiedPush Common-Proxies is a rewrite-proxy that converts UnifiedPush messages to a format that Gotify understands. If you’re not on Gentoo, have a look at the installation instructions of the project.

Install UnifiedPush common-proxies in Gentoo
echo -e "www-apps/up-common-proxies" | sudo tee -a /etc/portage/package.accept_keywords/gotify
sudo emerge -a www-apps/up-common-proxies

Change the listenAddr and Gotify address in the config file (/etc/up-common-proxies/config.toml on Gentoo) and add the UnifiedPush locations to your nginx config. I will assume that common-proxies listens on [::1]:7778.

Your config.toml should look similar to this
listenAddr = "[::1]:7778"
verbose = true

[gateway]
    [gateway.matrix]
        enabled = true
[rewrite]
    [rewrite.fcm]
        enabled = false
    [rewrite.gotify]
        enabled = true
        address = "[::1]:7777"
        scheme = "http"
Start common-proxies in Gentoo and make it automatically start at boot
sudo rc-service up-common-proxies start
sudo rc-update add up-common-proxies
Your nginx configuration should look similar to this
server {
    # […]

    location ~ ^/(FCM|UP|_matrix) {
        proxy_pass  http://[::1]:7778;
    }
}

Reload nginx.

Lua method

Warning
The author of the lua-module warns that >=nginx-1.11.11 is still not an officially supported target yet. You are on your own. Expect runtime failures, memory leaks and other problems!

You will need the lua module for nginx and lua-cjson.

Reinstall nginx with lua support and install lua-cjson in Gentoo
echo "www-servers/nginx NGINX_MODULES: http_lua" | sudo tee /etc/portage/package.use/gotify
echo "dev-lua/lua-cjson" | sudo tee -a /etc/portage/package.accept_keywords/gotify
sudo emerge -a1 www-servers/nginx
sudo emerge -a dev-lua/lua-cjson
sudo rc-service nginx restart

You may have to tell nginx its lua module where to find lua-cjson. In my case I had to add lua_package_cpath "/usr/share/lua/5.1/?.so;;"; above the server block. The ;; means that the previous value of lua_package_cpath should be appended.

Copy the configuration example for UnifiedPush into your server block and change proxy_pass to http://[::1]:7777/message.

Copy the configuration example for Matrix into your server block and change relay.example.tld to your server_name.

Your nginx configuration should look similar to this
lua_package_cpath "/usr/share/lua/5.1/?.so;;";

server {
    # […]

    location /UP {
        access_by_lua_block{
            local json=require("cjson")
            ngx.req.read_body()
            local req = ngx.req.get_body_data()
            local newreq = { ["message"] = req }
            local body = json.encode(newreq)
            ngx.req.set_body_data(body)
        }

        proxy_set_header        Content-Type application/json;
        proxy_pass              http://[::1]:7777/message;
        proxy_set_header        Host $host;
    }

    location /_matrix/push/v1/notify {
        set $target '';
        if ($request_method = GET ) {
            return 200 '{"gateway":"matrix","unifiedpush":{"gateway":"matrix"}}';
        }
        access_by_lua_block {
            local cjson = require("cjson")
            ngx.req.read_body()
            local body = ngx.req.get_body_data()
            local parsedBody = cjson.decode(body)
            local accepted = "https://push.example.org/"
            ngx.var.target = parsedBody["notification"]["devices"][1]["pushkey"]
            ngx.req.set_body_data(body)
            if(string.sub(ngx.var.target,1,string.len(accepted))~=accepted) then ngx.var.target="http://0.0.0.0/"
            end
        }
        proxy_set_header Content-Type application/json;
        proxy_set_header Host $host;
        proxy_pass $target;
    }
}

Reload nginx.

Use UnifiedPush

Log into your Gotify server and add a new user. Install the Android application. Make sure you install “Gotify-UP” and not “Gotify”[1]. Open Gotify-UP and register with your server. Applications supporting UnifiedPush should now be registered on your server and show up in the “Apps” tab. You may have to restart the application first. Have a look at the readme of the upstream repository for help with disabling battery optimization and constant foreground notification.

Tip
Want to send push notifications from your programs and shell scripts? Take a look at the examples in the Gotify documentation.

1. Gotify-UP does everything Gotify does, but also allows applications to integrate with it.