Compare commits

...

2 Commits

1 changed files with 179 additions and 0 deletions

View File

@ -66,3 +66,182 @@ figuring out the byte pattern which achieves what you want is painful, so i've g
enter... notification servers!
## Notification Servers
when an application wants to sound a notification on iOS or Android when it's not in focus, it's not the application running on the phone which does that, but actually some other server (operated by or for the developer) which tells _Apple's_ server (or Google's) about the notification, and then it reaches your phone. the justification for that is precisely to allow the type of power-saving we're aiming for here, and because that approach is such a privacy nightmare passionate people have created open source alternatives to the official MITM servers.
[UnifiedPush](https://unifiedpush.org/) defines the standards for each confusingly-named portion of this data flow,
and [ntfy](https://ntfy.sh/) provides hosted and self-hosted implementations for all the major components. most mature applications provide a way for you to integrate all this: i'll show how to do this for Matrix-synapse and Prosody (XMPP). both can be configured server-side if you have access to that, or client-side *if your client exposes an option for it*.
but first, let's prove this push notification system in an application-agnostic CLI workflow.
### Minimal ntfy Workflow
ntfy operates a free-to-use server for the MITM component, so install the CLI package for the publish/subscribe portions and we can test a wake-on-notification setup trivially.
on your phone:
```sh
$ ntfy sub TEST_WOWLAN_TOPIC
```
then determine the local port that ntfy process connected from with `lsof -i4 -P`, and create a wake condition for it:
```sh
# clear out previous rules
$ rtl8723cs-wowlan enable-clean
$ rtl8723cs-wowlan tcp --dest-port 1234
#^ replace 1234 with the port from `lsof`
```
then sleep the phone with `rtcwake -m mem`, and from another computer (or the [ntfy web interface](https://ntfy.sh/app)) send a notification:
```sh
$ ntfy pub TEST_WOWLAN_TOPIC "hello phone"
```
if all is well, your phone should awake and the `ntfy sub` command from earlier should have printed "hello phone"!
### Hosting a ntfy instance
this part is **optional**. most applications which speak UnifiedPush provide an option to only send a summary (like "new Matrix message") instead of the actual contents, so you only really need to concern yourself with this if you want the control of self-hosting.
NixOS ships [options](https://search.nixos.org/options?show=services.ntfy-sh) for hosting your own ntfy. use it like:
```nix
services.ntfy-sh.enable = true;
```
ntfy listens on port 2586 by default, so you can subscribe like `ntfy sub MY_INSTANCE_ADDRESS:2586/TEST_WOWLAN_TOPIC` and you can use a wowlan rule that matches the source port instead of checking `lsof` manually: `rtl8723cs-wowlan tcp --source-port 2586`.
however, running it this way allows anyone to use your server. the easy way to overcome this is to treat the topic as a shared secret, and forbid use of any topic except your secret topic. naturally, that only works if you secure the connection so do all this behind a TLS-capable reverse proxy like nginx:
```nix
services.ntfy-sh.enable = true;
services.nginx.virtualHosts."MY.NTFY.HOST" = {
forceSSL = true;
listen = [
{ addr = "0.0.0.0"; port = 2587; ssl = true; }
{ addr = "0.0.0.0"; port = 443; ssl = true; }
];
locations."/" = {
proxyPass = "http://127.0.0.1:2586";
proxyWebsockets = true; #< support websocket upgrades. without that, `ntfy sub` hangs silently
recommendedProxySettings = true; #< adds headers so ntfy logs include the real IP
extraConfig = ''
# absurdly long timeout (86400s=24h) so that we never hang up on clients.
proxy_read_timeout 86400s;
'';
};
};
services.ntfy-sh.settings = {
base-url = "https://MY.NTFY.HOST";
behind-proxy = true;
auth-default-access = "deny-all";
};
systemd.services.ntfy-sh.preStart = ''
# note that this will fail upon first run, i.e. before ntfy has created its db.
# just restart the service.
${pkgs.ntfy-sh}/bin/ntfy access everyone TEST_WOWLAN_TOPIC read-write
'';
```
deploy that and subscribe to the https url this time. note the port change to 2587: you still want a unique port against which you can write a wowlan rule, and it's easier to put nginx on a new port than ntfy's default 2586.
if you're thorough, you might notice some spurious wakeups with this setup. ntfy sends keep-alive packets every 45 seconds, but that's no good for us! best is to disable those keep-alives, and only put the phone to sleep for shortish durations (e.g. 10 minutes): i'll revisit the nuances around this near the end of this article.
```nix
services.ntfy-sh.settings.keepalive-interval = "30m";
```
### Synapse (Matrix) ntfy integration
the Synapse Matrix server exposes an API for controlling its push behavior [here](https://spec.matrix.org/unstable/push-gateway-api/).
some of the larger clients, like Fluffychat, can be seen to host their own push gateways, which they point Synapse to via this API.
presumably one could configure these clients to request a different push gateway (i.e. ntfy.sh, or your own instance from just above),
either in the UI or with an edit to their source code. but if you have CLI admin access to Synapse, it's easier to do this generically from the CLI.
first, grab an auth token for your Matrix account. it looks like `6cC_a03Ty3sTfqvo3FS_x8vEjdvNsxyL5W4mm73` and can be found in Element's "Help & About" settings page.
then, with this token, open a CLI to wherever you host your synapse server and issue this command to see where it's currently sending notifications (if anywhere):
```sh
$ curl --header "Authorization: Bearer ACCESS_TOKEN" \
localhost:8008/_matrix/client/v3/pushers \
| jq .
{
"pushers": [
{
"app_display_name": "Element (iOS)",
"app_id": "im.vector.app.ios.prod",
"data": {
"url": "https://matrix.org/_matrix/push/v1/notify",
"format": "event_id_only",
"default_payload": {
"aps": {
"mutable-content": 1,
"alert": {
"loc-key": "Notification",
"loc-args": []
}
}
}
},
"device_display_name": "iPhone",
"kind": "http",
"lang": "en-US",
"profile_tag": "1Akvq5DDr159ANj9",
"pushkey": "Bap1n7kqGzF9TuPkiDNDy9wW+cuvfDnxy7Cab7AMsIX="
}
]
}
```
now, add a new pusher for your ntfy service. this won't replace the existing settings, instead it'll cause push notifications to be sent to two locations simultaneously:
```sh
$ curl --header "Authorization: Bearer ACCESS_TOKEN" \
--data '{ \
"app_display_name": "ntfy-adapter", \
"app_id": "ntfy.uninsane.org", \
"data": { \
"url": "https://MY.NTFY.HOST/_matrix/push/v1/notify", \
"format": "event_id_only" \
}, \
"device_display_name": "ntfy-adapter", \
"kind": "http", \
"lang": "en-US", \
"profile_tag": "", \
"pushkey": "TEST_WOWLAN_TOPIC" \
}' \
localhost:8008/_matrix/client/v3/pushers/set
```
repeat the first query and you'll see both of these listed. to delete the new pusher, repeat the above `curl` command with `kind` set to `null`.
anyway, put your phone to sleep, have someone send you a message that Synapse would alert you on, and now your phone should awake!
### Prosody (XMPP) ntfy integration
Prosody has [mod_cloud_notify](https://modules.prosody.im/mod_cloud_notify) and [XEP-0357](https://xmpp.org/extensions/xep-0357.html), but at the time of writing client support is wanting. easier is to hack it in server-side.
Prosody has a Lua-based module system, so we can just author our own `mod_ntfy_push`, drop it in the modules directory, and then import it:
```nix
services.prosody.extraPluginsPath = [ ./folder/containing/mod_ntfy_push ];
services.prosody.extraModules = [ "ntfy_push" ];
services.prosody.extraConfig = ''
ntfy_endpoint = "https://MY.NTFY.HOST/TEST_WOWLAN_TOPIC"
'';
TODO: link `mod_ntfy_push.lua` here
```
i've only authored this module to alert me on jingle calls. one could presumably alert on DMs, MUC messages, or anything more particular by finding the right Prosody hooks (mod_cloud_notify may be an ok guide for that).
### Pinephone Wowlan Race Condition