Self-hosting Sunup for Matrix and UnifiedPush
Background
Mobile notifications—particularly on Android—are challenging to implement. There are 3 methods for app to send notifications in the background:
- Firebase Cloud Messaging-
- UnifiedPush
- proprietary long‑polling connection.
For battery‑life considerations, the proprietary long‑polling approach is undesirable. Consequently, we will concentrate on a push‑based architecture that employs a notification distributor and server. We are eliminating Firebase Cloud Messaging from our sight due to the Google dependency and privacy controversy, and have the crosshair directly on UnifiedPush.
Here’s a flow chart for how notifications work in Matrix via Sunup (bare with my poor calligraphy please!):
You can see that, in the scope of Matrix notifications from clients like Element X, a message is generated passing the following flow:
The homeserver (Synapse, Dendrite, Continuwuity) processes events constantly. For each incoming event the server evaluates the configured push rules, (specifically the
m.mentions
entries, see Intentional Mentions MSC), to determine whether the event satisfies any rule. If a match is found, the server triggers the corresponding push notification workflow.When an event matches a push rule, the homeserver sends a notification to the Matrix Push Gateway, which is common-proxies in our case.
The Matrix Push Gateway (Common Proxies) rewrites received notification data, and sends it to user-specified push endpoint (autopush autoend endpoint).
After receiving the notification data. The push endpoint notifies a push server (autopush autoconnect) about this new notification via Valkey.
- Side note here: If you are using regular UnifiedPush applications, this is the starting point. Matrix does not support UnifiedPush natively so we need a translator.
The push server sends down notification data to your phone, to Sunup.
Sunup broadcasts the pertinent message to the target application, brings the application to the foreground, and then allows it to retrieve the required data and issue the final Android notification.
- We selected Sunup because Ntfy does not function correctly on our Android 16 phones. It fails to wake the target application, preventing reliable notification delivery.
You see the notification.
Self Hosting
Before reading
- Replace every domain name that appears in the article with your own domain.
- Text enclosed in
[]
indicates a placeholder you must fill in yourself - I’m using a package to store configurations. Please use
/etc
rather than/usr
if you’re not. - This article assumes you have basic levels of knowledge of systemd, and is using a FHS-compliant system (i.e., not NixOS). Adjust file locations accordingly if your system uses a different layout.
Required Server Packages (Arch Linux)
Package | Purpose |
---|---|
valkey | In‑memory key‑value store used by the autopush infrastructure. |
autopush‑rs‑p1gp1g‑git | Actual push server, with Redis support |
common‑proxies | Matrix Push Gateway |
pwgen | Generates random passwords for configuration files. |
portable (optional) | Provides isolated shell; install on a workstation rather than the production server. |
Note that p1gp1g’s fork of autopush-rs
is required because upstream hasn’t merged Redis support, and we don’t want to rely on Google BigTable. Valkey is the replacement of Redis.
If you aren’t using Arch Linux, those build scripts are publicly available either on Arch GitLab or AUR
Valkey
Valkey acts like a middleman. It helps autopush autoend and autoconnect to communicate.
Copy an example Valkey configuration file. Note: when you change configurations, make sure there’s no default entry overriding your config.
You may want to disable Valkey’s snapshot, and instead write everything to disk for stability. See Documentation: Persistence for more info.
We are just gonna disable RDB and enable AOF here:
1 | save "" |
Next, generate a password for your autopush-rs server:
1 | pwgen -s 64 1 |
Copy it down, save it in a secure place.
Add autopush user to Valkey:
1 | user autopush on +@all -DEBUG ~* >[your DB password here] |
Final step: you can install the configuration file to /usr/share/serverOS/valkey.conf
. Mind the permission should be 0700 and owned by root.
And now, create the systemd service /usr/lib/systemd/serverOS-valkey.service
for Valkey with the following contents:
1 | [Unit] |
Reload systemd to pick up the new unit file:
1 | systemctl daemon-reload |
Enable the service and start it immediately:
1 | systemctl enable --now serverOS-valkey |
Autopush-rs
Obtain keys
On your desktop system, execute portable-pools build
to create a build sandbox (optional).
Then execute git clone --depth 1 https://github.com/p1gp1g/autopush-rs
to get the Redis fork.
Once finished, run cd autopush-rs && python -m pip install cryptography --break-system-packages
to get dependencies.
Now, you can generate an autopush key with python scripts/fernet_key.py
. Save it somewhere safe.
Use exit
or Control + D to exit out.
Service configuration
Create /usr/share/serverOS/autopush-rs.env
with permission 0700 and owner root, and write the following content:
1 | RUST_BACKTRACE=1 |
This is pretty straightforward, and contains the configuration for both autoend and autoconnect. Basically autoconnect, which is responsible for connecting with your phone will listen on 8080 (this is the default), and you would supply it with Redis database URL, autoend’s port and hostname, and the key for those two components to communicate. Autoend here listens on 127.0.0.5:31451, assumes the URL is https://updates.[your push subdomain here]
. Both of which we’ll use reverse proxy later to map it’s port.
Create 2 systemd services /usr/lib/systemd/system/serverOS-autoconnect.service
and /usr/lib/systemd/system/serverOS-autoendpoint.service
respectively:
1 | [Unit] |
and…
1 | [Unit] |
Reload systemd to recognize any new unit files:
1 | systemctl daemon-reload |
Enable and start the two services immediately:
1 | systemctl enable --now serverOS-autoconnect serverOS-autoendpoint |
Common Proxies
Create configuration file /usr/share/serverOS/common-proxies.toml
with permission 0700 and owner root:
1 | listenAddr = "127.0.0.100:5000" |
Note that you should add your autoend endpoint updates.[your push subdomain here]
and Matrix server to AllowedHosts
.
Create the systemd service for common-proxies: /usr/lib/systemd/system/serverOS-common-proxies.service
1 | [Unit] |
Reload systemd to recognize any new unit files:
1 | systemctl daemon-reload |
Enable and start the service immediately:
1 | systemctl enable --now serverOS-common-proxies |
Reverse Proxy
Here comes the part of reverse proxy. I’m using Nginx as an example, but it should be the same for other software like Caddy.
Create a new server block for your autoend endpoint (This is a simplified example, it will not work!):
1 | server { |
for autoconnect:
1 | server { |
That’s it. You then obtain TLS certificates, restart Nginx and set the push server in Sunup. And notifications will be working, well, if you did the right thing. If you are looking for client-side migration, check my other article for example: Migration of push servers