Docs and Matrix tests
This commit is contained in:
		
							parent
							
								
									0ff8e968ca
								
							
						
					
					
						commit
						18bd3c0e55
					
				
					 12 changed files with 172 additions and 140 deletions
				
			
		|  | @ -9,7 +9,9 @@ those out, too. | ||||||
|     [create a pull request](https://github.com/binwiederhier/ntfy/pulls), and I'll happily include it. Also note, that |     [create a pull request](https://github.com/binwiederhier/ntfy/pulls), and I'll happily include it. Also note, that | ||||||
|     I cannot guarantee that all of these examples are functional. Many of them I have not tried myself. |     I cannot guarantee that all of these examples are functional. Many of them I have not tried myself. | ||||||
| 
 | 
 | ||||||
| ## A long process is done: backups, copying data, pipelines, ... | ## Cronjobs | ||||||
|  | ntfy is perfect for any kind of cronjobs or just when long processes are done (backups, pipelines, rsync copy commands, ...). | ||||||
|  | 
 | ||||||
| I started adding notifications pretty much all of my scripts. Typically, I just chain the <tt>curl</tt> call | I started adding notifications pretty much all of my scripts. Typically, I just chain the <tt>curl</tt> call | ||||||
| directly to the command I'm running. The following example will either send <i>Laptop backup succeeded</i> | directly to the command I'm running. The following example will either send <i>Laptop backup succeeded</i> | ||||||
| or ⚠️ <i>Laptop backup failed</i> directly to my phone: | or ⚠️ <i>Laptop backup failed</i> directly to my phone: | ||||||
|  | @ -21,6 +23,15 @@ rsync -a root@laptop /backups/laptop \ | ||||||
|   || curl -H tags:warning -H prio:high -d "Laptop backup failed" ntfy.sh/backups |   || curl -H tags:warning -H prio:high -d "Laptop backup failed" ntfy.sh/backups | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
|  | Here's one for the history books. I desperately want the `github.com/ntfy` organization, but all my tickets with | ||||||
|  | GitHub have been hopeless. In case it ever becomes available, I want to know immediately. | ||||||
|  | 
 | ||||||
|  | ``` cron | ||||||
|  | # Check github/ntfy user | ||||||
|  | */6 * * * * if curl -s https://api.github.com/users/ntfy | grep "Not Found"; then curl -d "github.com/ntfy is available" -H "Tags: tada" -H "Prio: high" ntfy.sh/my-alerts; fi | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| ## Low disk space alerts | ## Low disk space alerts | ||||||
| Here's a simple cronjob that I use to alert me when the disk space on the root disk is running low. It's simple, but  | Here's a simple cronjob that I use to alert me when the disk space on the root disk is running low. It's simple, but  | ||||||
| effective.  | effective.  | ||||||
|  | @ -42,11 +53,7 @@ if [ -n "$avail" ]; then | ||||||
| fi | fi | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| ## Server-sent messages in your web app | ## SSH login alerts | ||||||
| Just as you can [subscribe to topics in the Web UI](subscribe/web.md), you can use ntfy in your own |  | ||||||
| web application. Check out the <a href="/example.html">live example</a>. |  | ||||||
| 
 |  | ||||||
| ## Notify on SSH login |  | ||||||
| Years ago my home server was broken into. That shook me hard, so every time someone logs into any machine that I | Years ago my home server was broken into. That shook me hard, so every time someone logs into any machine that I | ||||||
| own, I now message myself. Here's an example of how to use <a href="https://en.wikipedia.org/wiki/Linux_PAM">PAM</a> | own, I now message myself. Here's an example of how to use <a href="https://en.wikipedia.org/wiki/Linux_PAM">PAM</a> | ||||||
| to notify yourself on SSH login. | to notify yourself on SSH login. | ||||||
|  | @ -102,7 +109,7 @@ One of my co-workers uses the following Ansible task to let him know when things | ||||||
|     body: "{{ inventory_hostname }} reseeding complete" |     body: "{{ inventory_hostname }} reseeding complete" | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| ## Watchtower notifications (shoutrrr) | ## Watchtower (shoutrrr) | ||||||
| You can use [shoutrrr](https://github.com/containrrr/shoutrrr) generic webhook support to send  | You can use [shoutrrr](https://github.com/containrrr/shoutrrr) generic webhook support to send  | ||||||
| [Watchtower](https://github.com/containrrr/watchtower/) notifications to your ntfy topic. | [Watchtower](https://github.com/containrrr/watchtower/) notifications to your ntfy topic. | ||||||
| 
 | 
 | ||||||
|  | @ -121,16 +128,7 @@ Or, if you only want to send notifications using shoutrrr: | ||||||
| shoutrrr send -u "generic+https://ntfy.sh/my_watchtower_topic?title=WatchtowerUpdates" -m "testMessage" | shoutrrr send -u "generic+https://ntfy.sh/my_watchtower_topic?title=WatchtowerUpdates" -m "testMessage" | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| ## Random cronjobs | ## Sonarr, Radarr, Lidarr, Readarr, Prowlarr, SABnzbd | ||||||
| Alright, here's one for the history books. I desperately want the `github.com/ntfy` organization, but all my tickets with |  | ||||||
| GitHub have been hopeless. In case it ever becomes available, I want to know immediately. |  | ||||||
| 
 |  | ||||||
| ``` cron |  | ||||||
| # Check github/ntfy user |  | ||||||
| */6 * * * * if curl -s https://api.github.com/users/ntfy | grep "Not Found"; then curl -d "github.com/ntfy is available" -H "Tags: tada" -H "Prio: high" ntfy.sh/my-alerts; fi |  | ||||||
| ``` |  | ||||||
| 
 |  | ||||||
| ## Download notifications (Sonarr, Radarr, Lidarr, Readarr, Prowlarr, SABnzbd) |  | ||||||
| It's possible to use custom scripts for all the *arr services, plus SABnzbd. Notifications for downloads, warnings, grabs etc. | It's possible to use custom scripts for all the *arr services, plus SABnzbd. Notifications for downloads, warnings, grabs etc. | ||||||
| Some simple bash scripts to achieve this are kindly provided in [nickexyz's repository](https://github.com/nickexyz/ntfy-shellscripts).  | Some simple bash scripts to achieve this are kindly provided in [nickexyz's repository](https://github.com/nickexyz/ntfy-shellscripts).  | ||||||
| 
 | 
 | ||||||
|  | @ -343,7 +341,7 @@ You can use the HTTP request node to send messages with [Node-RED](https://noder | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
| 
 | 
 | ||||||
| ## Gatus service health check | ## Gatus | ||||||
| 
 | 
 | ||||||
| An example for a custom alert with [Gatus](https://github.com/TwiN/gatus): | An example for a custom alert with [Gatus](https://github.com/TwiN/gatus): | ||||||
| ``` yaml | ``` yaml | ||||||
|  | @ -435,11 +433,38 @@ notify: | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| ## Uptime Kuma | ## Uptime Kuma | ||||||
| - Go to your [Uptime Kuma](https://github.com/louislam/uptime-kuma) Settings > Notifications, click on **Setup Notification** | Go to your [Uptime Kuma](https://github.com/louislam/uptime-kuma) Settings > Notifications, click on **Setup Notification**. | ||||||
| -  | Then set your desired **title** (e.g. "Uptime Kuma"), **ntfy topic**, **Server URL** and **priority (1-5)**: | ||||||
| - Set your desired **title** (e.g. "Uptime Kuma"), **ntfy topic**, **Server URL** and **priority (1-5)** | 
 | ||||||
| -  | <div id="uptimekuma-screenshots" class="screenshots"> | ||||||
| - You can now test the notifications and apply them to monitors. |     <a href="../../static/img/uptimekuma-settings.png"><img src="../../static/img/uptimekuma-settings.png"/></a> | ||||||
| -  |     <a href="../../static/img/uptimekuma-setup.png"><img src="../../static/img/uptimekuma-setup.png"/></a> | ||||||
| -  | </div> | ||||||
| -  | 
 | ||||||
|  | 
 | ||||||
|  | You can now test the notifications and apply them to monitors: | ||||||
|  | 
 | ||||||
|  | <div id="uptimekuma-monitor-screenshots" class="screenshots"> | ||||||
|  |     <a href="../../static/img/uptimekuma-ios-test.jpg"><img src="../../static/img/uptimekuma-ios-test.jpg"/></a> | ||||||
|  |     <a href="../../static/img/uptimekuma-ios-down.jpg"><img src="../../static/img/uptimekuma-ios-down.jpg"/></a> | ||||||
|  |     <a href="../../static/img/uptimekuma-ios-up.jpg"><img src="../../static/img/uptimekuma-ios-up.jpg"/></a> | ||||||
|  | </div> | ||||||
|  | 
 | ||||||
|  | ## Apprise | ||||||
|  | ntfy is integrated natively into [Apprise](https://github.com/caronc/apprise) (also check out the  | ||||||
|  | [Apprise/ntfy wiki page](https://github.com/caronc/apprise/wiki/Notify_ntfy)). | ||||||
|  | 
 | ||||||
|  | You can use it like this: | ||||||
|  | 
 | ||||||
|  | ``` | ||||||
|  | apprise -vv -t "Test Message Title" -b "Test Message Body" \ | ||||||
|  |    ntfy://mytopic | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | Or with your own server like this: | ||||||
|  | 
 | ||||||
|  | ``` | ||||||
|  | apprise -vv -t "Test Message Title" -b "Test Message Body" \ | ||||||
|  |    ntfy://ntfy.example.com/mytopic | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  |  | ||||||
|  | @ -2735,6 +2735,22 @@ parameter (or any of its aliases `unifiedpush` or `up`) to `1` to [disable Fireb | ||||||
| option is mostly equivalent to `Firebase: no`, but was introduced to allow future flexibility. The flag additionally  | option is mostly equivalent to `Firebase: no`, but was introduced to allow future flexibility. The flag additionally  | ||||||
| enables auto-detection of the message encoding. If the message is binary, it'll be encoded as base64. | enables auto-detection of the message encoding. If the message is binary, it'll be encoded as base64. | ||||||
| 
 | 
 | ||||||
|  | ### Matrix Gateway | ||||||
|  | The ntfy server implements a [Matrix Push Gateway](https://spec.matrix.org/v1.2/push-gateway-api/) (in combination with | ||||||
|  | [UnifiedPush](https://unifiedpush.org) as the [Provider Push Protocol](https://unifiedpush.org/developers/gateway/)). This makes it easier to integrate | ||||||
|  | with self-hosted [Matrix](https://matrix.org/) servers (such as [synapse](https://github.com/matrix-org/synapse)), since  | ||||||
|  | you don't have to set up a separate push proxy (such as [common-proxies](https://github.com/UnifiedPush/common-proxies)). | ||||||
|  | 
 | ||||||
|  | In short, ntfy accepts Matrix messages on the `/_matrix/push/v1/notify` endpoint (see [Push Gateway API](https://spec.matrix.org/v1.2/push-gateway-api/)),  | ||||||
|  | and forwards them to the ntfy topic defined in the `pushkey` of the message. The message will then be forwarded to the | ||||||
|  | ntfy Android app, and passed on to the Matrix client there. | ||||||
|  | 
 | ||||||
|  | There is a nice diagram in the [Push Gateway docs](https://spec.matrix.org/v1.2/push-gateway-api/). In this diagram, the | ||||||
|  | ntfy server plays the role of the Push Gateway, as well as the Push Provider. UnifiedPush is the Provider Push Protocol. | ||||||
|  | 
 | ||||||
|  | !!! info | ||||||
|  |     This is not a generic Matrix Push Gateway. It only works in combination with UnifiedPush and ntfy. | ||||||
|  | 
 | ||||||
| ## Public topics | ## Public topics | ||||||
| Obviously all topics on ntfy.sh are public, but there are a few designated topics that are used in examples, and topics | Obviously all topics on ntfy.sh are public, but there are a few designated topics that are used in examples, and topics | ||||||
| that you can use to try out what [authentication and access control](#authentication) looks like. | that you can use to try out what [authentication and access control](#authentication) looks like. | ||||||
|  |  | ||||||
|  | @ -2,6 +2,27 @@ | ||||||
| Binaries for all releases can be found on the GitHub releases pages for the [ntfy server](https://github.com/binwiederhier/ntfy/releases) | Binaries for all releases can be found on the GitHub releases pages for the [ntfy server](https://github.com/binwiederhier/ntfy/releases) | ||||||
| and the [ntfy Android app](https://github.com/binwiederhier/ntfy-android/releases). | and the [ntfy Android app](https://github.com/binwiederhier/ntfy-android/releases). | ||||||
| 
 | 
 | ||||||
|  | ## ntfy server v1.26.0 (UNRELEASED) | ||||||
|  | 
 | ||||||
|  | **Features:** | ||||||
|  | 
 | ||||||
|  | * ntfy now is a [Matrix Push Gateway](https://spec.matrix.org/v1.2/push-gateway-api/) (in combination with [UnifiedPush](https://unifiedpush.org) as the [Provider Push Protocol](https://unifiedpush.org/developers/gateway/), [#319](https://github.com/binwiederhier/ntfy/issues/319)/[#326](https://github.com/binwiederhier/ntfy/pull/326), thanks to [@MayeulC](https://github.com/MayeulC) for reporting) | ||||||
|  | * Windows CLI is now available via [Scoop](https://scoop.sh) ([ScoopInstaller#3594](https://github.com/ScoopInstaller/Main/pull/3594), [#311](https://github.com/binwiederhier/ntfy/pull/311), [#269](https://github.com/binwiederhier/ntfy/issues/269), thanks to [@kzshantonu](https://github.com/kzshantonu)) | ||||||
|  | * [Uptime Kuma](https://github.com/louislam/uptime-kuma) now allows publishing to ntfy ([uptime-kuma#1674](https://github.com/louislam/uptime-kuma/pull/1674), thanks to [@philippdormann](https://github.com/philippdormann)) | ||||||
|  | * Display ntfy version in `ntfy serve` command  ([#314](https://github.com/binwiederhier/ntfy/issues/314), thanks to [@poblabs](https://github.com/poblabs)) | ||||||
|  | 
 | ||||||
|  | **Bugs:** | ||||||
|  | 
 | ||||||
|  | * Web app: Show "notifications not supported" alert on HTTP ([#323](https://github.com/binwiederhier/ntfy/issues/323), thanks to [@milksteakjellybeans](https://github.com/milksteakjellybeans) for reporting) | ||||||
|  | 
 | ||||||
|  | **Documentation** | ||||||
|  | 
 | ||||||
|  | * Added [example](examples.md) for [Uptime Kuma](https://github.com/louislam/uptime-kuma) integration ([#315](https://github.com/binwiederhier/ntfy/pull/315), thanks to [@philippdormann](https://github.com/philippdormann)) | ||||||
|  | * Fix Docker install instructions  ([#320](https://github.com/binwiederhier/ntfy/issues/320), thanks to [@milksteakjellybeans](https://github.com/milksteakjellybeans) for reporting) | ||||||
|  | * Add clarifying comments to base-url ([#322](https://github.com/binwiederhier/ntfy/issues/322), thanks to [@milksteakjellybeans](https://github.com/milksteakjellybeans) for reporting) | ||||||
|  | * Update FAQ for iOS app ([#321](https://github.com/binwiederhier/ntfy/issues/321), thanks to [@milksteakjellybeans](https://github.com/milksteakjellybeans) for reporting) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| <!-- | <!-- | ||||||
| 
 | 
 | ||||||
| ## ntfy Android app v1.14.0 (UNRELEASED) | ## ntfy Android app v1.14.0 (UNRELEASED) | ||||||
|  | @ -11,26 +32,6 @@ and the [ntfy Android app](https://github.com/binwiederhier/ntfy-android/release | ||||||
| * Italian (thanks to [@Genio2003](https://hosted.weblate.org/user/Genio2003/)) | * Italian (thanks to [@Genio2003](https://hosted.weblate.org/user/Genio2003/)) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| ## ntfy server v1.26.0 (UNRELEASED) |  | ||||||
| 
 |  | ||||||
| **Bugs:** |  | ||||||
| 
 |  | ||||||
| * Web app: Show "notifications not supported" alert on HTTP ([#323](https://github.com/binwiederhier/ntfy/issues/323), thanks to [@milksteakjellybeans](https://github.com/milksteakjellybeans) for reporting) |  | ||||||
| 
 |  | ||||||
| **Features:** |  | ||||||
| 
 |  | ||||||
| * Windows CLI is now available via [Scoop](https://scoop.sh) ([ScoopInstaller#3594](https://github.com/ScoopInstaller/Main/pull/3594), [#311](https://github.com/binwiederhier/ntfy/pull/311), [#269](https://github.com/binwiederhier/ntfy/issues/269), thanks to [@kzshantonu](https://github.com/kzshantonu)) |  | ||||||
| * [Uptime Kuma](https://github.com/louislam/uptime-kuma) now allows publishing to ntfy ([uptime-kuma#1674](https://github.com/louislam/uptime-kuma/pull/1674), thanks to [@philippdormann](https://github.com/philippdormann)) |  | ||||||
| * Display ntfy version in `ntfy serve` command  ([#314](https://github.com/binwiederhier/ntfy/issues/314), thanks to [@poblabs](https://github.com/poblabs)) |  | ||||||
| 
 |  | ||||||
| **Documentation** |  | ||||||
| 
 |  | ||||||
| * Added [example](examples.md) for [Uptime Kuma](https://github.com/louislam/uptime-kuma) integration ([#315](https://github.com/binwiederhier/ntfy/pull/315), thanks to [@philippdormann](https://github.com/philippdormann)) |  | ||||||
| * Fix Docker install instructions  ([#320](https://github.com/binwiederhier/ntfy/issues/320), thanks to [@milksteakjellybeans](https://github.com/milksteakjellybeans) for reporting) |  | ||||||
| * Add clarifying comments to base-url ([#322](https://github.com/binwiederhier/ntfy/issues/322), thanks to [@milksteakjellybeans](https://github.com/milksteakjellybeans) for reporting) |  | ||||||
| * Update FAQ for iOS app ([#321](https://github.com/binwiederhier/ntfy/issues/321), thanks to [@milksteakjellybeans](https://github.com/milksteakjellybeans) for reporting) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| ## ntfy iOS app v1.2 (UNRELEASED) | ## ntfy iOS app v1.2 (UNRELEASED) | ||||||
| 
 | 
 | ||||||
| This release adds support for authentication/authorization for self-hosted servers. It also allows you to | This release adds support for authentication/authorization for self-hosted servers. It also allows you to | ||||||
|  |  | ||||||
							
								
								
									
										3
									
								
								docs/static/css/extra.css
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								docs/static/css/extra.css
									
										
									
									
										vendored
									
									
								
							|  | @ -60,7 +60,8 @@ figure video { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .screenshots img { | .screenshots img { | ||||||
|     height: 230px; |     max-height: 230px; | ||||||
|  |     max-width: 300px; | ||||||
|     margin: 3px; |     margin: 3px; | ||||||
|     border-radius: 5px; |     border-radius: 5px; | ||||||
|     filter: drop-shadow(2px 2px 2px #ddd); |     filter: drop-shadow(2px 2px 2px #ddd); | ||||||
|  |  | ||||||
|  | @ -87,7 +87,7 @@ recommended way to subscribe to a topic**. The notable exception is JavaScript, | ||||||
| ### Subscribe as SSE stream | ### Subscribe as SSE stream | ||||||
| Using [EventSource](https://developer.mozilla.org/en-US/docs/Web/API/EventSource) in JavaScript, you can consume | Using [EventSource](https://developer.mozilla.org/en-US/docs/Web/API/EventSource) in JavaScript, you can consume | ||||||
| notifications via a [Server-Sent Events (SSE)](https://en.wikipedia.org/wiki/Server-sent_events) stream. It's incredibly  | notifications via a [Server-Sent Events (SSE)](https://en.wikipedia.org/wiki/Server-sent_events) stream. It's incredibly  | ||||||
| easy to use. Here's what it looks like. You may also want to check out the [live example](/example.html). | easy to use. Here's what it looks like. You may also want to check out the [full example on GitHub](https://github.com/binwiederhier/ntfy/tree/main/examples/web-example-eventsource). | ||||||
| 
 | 
 | ||||||
| === "Command line (curl)" | === "Command line (curl)" | ||||||
|     ``` |     ``` | ||||||
|  |  | ||||||
|  | @ -1,56 +0,0 @@ | ||||||
| <!DOCTYPE html> |  | ||||||
| <html lang="en"> |  | ||||||
| <head> |  | ||||||
|     <meta charset="UTF-8"> |  | ||||||
|     <title>ntfy.sh: EventSource Example</title> |  | ||||||
|     <meta name="robots" content="noindex, nofollow" /> |  | ||||||
|     <style> |  | ||||||
|         body { font-size: 1.2em; line-height: 130%; } |  | ||||||
|         #events { font-family: monospace; } |  | ||||||
|     </style> |  | ||||||
| </head> |  | ||||||
| <body> |  | ||||||
| <h1>ntfy.sh: EventSource Example</h1> |  | ||||||
| <p> |  | ||||||
|     This is an example showing how to use <a href="https://ntfy.sh">ntfy.sh</a> with |  | ||||||
|     <a href="https://developer.mozilla.org/en-US/docs/Web/API/EventSource">EventSource</a>.<br/> |  | ||||||
|     This example doesn't need a server. You can just save the HTML page and run it from anywhere. |  | ||||||
| </p> |  | ||||||
| <button id="publishButton">Send test notification</button> |  | ||||||
| <p><b>Log:</b></p> |  | ||||||
| <div id="events"></div> |  | ||||||
| 
 |  | ||||||
| <script type="text/javascript"> |  | ||||||
|     const publishURL = `https://ntfy.sh/example`; |  | ||||||
|     const subscribeURL = `https://ntfy.sh/example/sse`; |  | ||||||
|     const events = document.getElementById('events'); |  | ||||||
|     const eventSource = new EventSource(subscribeURL); |  | ||||||
| 
 |  | ||||||
|     // Publish button |  | ||||||
|     document.getElementById("publishButton").onclick = () => { |  | ||||||
|         fetch(publishURL, { |  | ||||||
|             method: 'POST', // works with PUT as well, though that sends an OPTIONS request too! |  | ||||||
|             body: `It is ${new Date().toString()}. This is a test.` |  | ||||||
|         }) |  | ||||||
|     }; |  | ||||||
| 
 |  | ||||||
|     // Incoming events |  | ||||||
|     eventSource.onopen = () => { |  | ||||||
|         let event = document.createElement('div'); |  | ||||||
|         event.innerHTML = `EventSource connected to ${subscribeURL}`; |  | ||||||
|         events.appendChild(event); |  | ||||||
|     }; |  | ||||||
|     eventSource.onerror = (e) => { |  | ||||||
|         let event = document.createElement('div'); |  | ||||||
|         event.innerHTML = `EventSource error: Failed to connect to ${subscribeURL}`; |  | ||||||
|         events.appendChild(event); |  | ||||||
|     }; |  | ||||||
|     eventSource.onmessage = (e) => { |  | ||||||
|         let event = document.createElement('div'); |  | ||||||
|         event.innerHTML = e.data; |  | ||||||
|         events.appendChild(event); |  | ||||||
|     }; |  | ||||||
| </script> |  | ||||||
| 
 |  | ||||||
| </body> |  | ||||||
| </html> |  | ||||||
|  | @ -75,9 +75,6 @@ var ( | ||||||
| 	disallowedTopics = []string{"docs", "static", "file", "app", "settings"} // If updated, also update in Android app | 	disallowedTopics = []string{"docs", "static", "file", "app", "settings"} // If updated, also update in Android app | ||||||
| 	attachURLRegex   = regexp.MustCompile(`^https?://`) | 	attachURLRegex   = regexp.MustCompile(`^https?://`) | ||||||
| 
 | 
 | ||||||
| 	//go:embed "example.html" |  | ||||||
| 	exampleSource string |  | ||||||
| 
 |  | ||||||
| 	//go:embed site | 	//go:embed site | ||||||
| 	webFs        embed.FS | 	webFs        embed.FS | ||||||
| 	webFsCached  = &util.CachingEmbedFS{ModTime: time.Now(), FS: webFs} | 	webFsCached  = &util.CachingEmbedFS{ModTime: time.Now(), FS: webFs} | ||||||
|  | @ -283,8 +280,6 @@ func (s *Server) handle(w http.ResponseWriter, r *http.Request) { | ||||||
| func (s *Server) handleInternal(w http.ResponseWriter, r *http.Request, v *visitor) error { | func (s *Server) handleInternal(w http.ResponseWriter, r *http.Request, v *visitor) error { | ||||||
| 	if r.Method == http.MethodGet && r.URL.Path == "/" { | 	if r.Method == http.MethodGet && r.URL.Path == "/" { | ||||||
| 		return s.ensureWebEnabled(s.handleHome)(w, r, v) | 		return s.ensureWebEnabled(s.handleHome)(w, r, v) | ||||||
| 	} else if r.Method == http.MethodGet && r.URL.Path == "/example.html" { |  | ||||||
| 		return s.ensureWebEnabled(s.handleExample)(w, r, v) |  | ||||||
| 	} else if r.Method == http.MethodHead && r.URL.Path == "/" { | 	} else if r.Method == http.MethodHead && r.URL.Path == "/" { | ||||||
| 		return s.ensureWebEnabled(s.handleEmpty)(w, r, v) | 		return s.ensureWebEnabled(s.handleEmpty)(w, r, v) | ||||||
| 	} else if r.Method == http.MethodGet && r.URL.Path == webConfigPath { | 	} else if r.Method == http.MethodGet && r.URL.Path == webConfigPath { | ||||||
|  | @ -357,11 +352,6 @@ func (s *Server) handleTopicAuth(w http.ResponseWriter, _ *http.Request, _ *visi | ||||||
| 	return err | 	return err | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (s *Server) handleExample(w http.ResponseWriter, _ *http.Request, _ *visitor) error { |  | ||||||
| 	_, err := io.WriteString(w, exampleSource) |  | ||||||
| 	return err |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (s *Server) handleWebConfig(w http.ResponseWriter, _ *http.Request, _ *visitor) error { | func (s *Server) handleWebConfig(w http.ResponseWriter, _ *http.Request, _ *visitor) error { | ||||||
| 	appRoot := "/" | 	appRoot := "/" | ||||||
| 	if !s.config.WebRootIsApp { | 	if !s.config.WebRootIsApp { | ||||||
|  | @ -435,7 +425,7 @@ func (s *Server) handleFile(w http.ResponseWriter, r *http.Request, v *visitor) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (s *Server) handleMatrixDiscovery(w http.ResponseWriter) error { | func (s *Server) handleMatrixDiscovery(w http.ResponseWriter) error { | ||||||
| 	return handleMatrixDiscovery(w) | 	return writeMatrixDiscoveryResponse(w) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (s *Server) handlePublishWithoutResponse(r *http.Request, v *visitor) (*message, error) { | func (s *Server) handlePublishWithoutResponse(r *http.Request, v *visitor) (*message, error) { | ||||||
|  |  | ||||||
|  | @ -11,9 +11,36 @@ import ( | ||||||
| 	"strings" | 	"strings" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| const ( | // Matrix Push Gateway / UnifiedPush / ntfy integration: | ||||||
| 	matrixPushKeyHeader = "X-Matrix-Pushkey" | // | ||||||
| ) | // ntfy implements a Matrix Push Gateway (as defined in https://spec.matrix.org/v1.2/push-gateway-api/), | ||||||
|  | // in combination with UnifiedPush as the Provider Push Protocol (as defined in https://unifiedpush.org/developers/gateway/). | ||||||
|  | // | ||||||
|  | // In the picture below, ntfy is the Push Gateway (mostly in this file), as well as the Push Provider (ntfy's | ||||||
|  | // main functionality). UnifiedPush is the Provider Push Protocol, as implemented by the ntfy server and the | ||||||
|  | // ntfy Android app. | ||||||
|  | // | ||||||
|  | //                                    +--------------------+  +-------------------+ | ||||||
|  | //                  Matrix HTTP      |                    |  |                   | | ||||||
|  | //             Notification Protocol |   App Developer    |  |   Device Vendor   | | ||||||
|  | //                                   |                    |  |                   | | ||||||
|  | //           +-------------------+   | +----------------+ |  | +---------------+ | | ||||||
|  | //           |                   |   | |                | |  | |               | | | ||||||
|  | //           | Matrix homeserver +----->  Push Gateway  +------> Push Provider | | | ||||||
|  | //           |                   |   | |                | |  | |               | | | ||||||
|  | //           +-^-----------------+   | +----------------+ |  | +----+----------+ | | ||||||
|  | //             |                     |                    |  |      |            | | ||||||
|  | //    Matrix   |                     |                    |  |      |            | | ||||||
|  | // Client/Server API  +              |                    |  |      |            | | ||||||
|  | //             |      |              +--------------------+  +-------------------+ | ||||||
|  | //             |   +--+-+                                           | | ||||||
|  | //             |   |    <-------------------------------------------+ | ||||||
|  | //             +---+    | | ||||||
|  | //                 |    |          Provider Push Protocol | ||||||
|  | //                 +----+ | ||||||
|  | // | ||||||
|  | //         Mobile Device or Client | ||||||
|  | // | ||||||
| 
 | 
 | ||||||
| // matrixRequest represents a Matrix message, as it is sent to a Push Gateway (as per | // matrixRequest represents a Matrix message, as it is sent to a Push Gateway (as per | ||||||
| // this spec: https://spec.matrix.org/v1.2/push-gateway-api/). | // this spec: https://spec.matrix.org/v1.2/push-gateway-api/). | ||||||
|  | @ -30,6 +57,7 @@ const ( | ||||||
| //        ] | //        ] | ||||||
| //      } | //      } | ||||||
| //    } | //    } | ||||||
|  | // | ||||||
| type matrixRequest struct { | type matrixRequest struct { | ||||||
| 	Notification *struct { | 	Notification *struct { | ||||||
| 		Devices []*struct { | 		Devices []*struct { | ||||||
|  | @ -38,10 +66,13 @@ type matrixRequest struct { | ||||||
| 	} `json:"notification"` | 	} `json:"notification"` | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // matrixResponse represents the response to a Matrix push gateway message, as defined | ||||||
|  | // in the spec (https://spec.matrix.org/v1.2/push-gateway-api/). | ||||||
| type matrixResponse struct { | type matrixResponse struct { | ||||||
| 	Rejected []string `json:"rejected"` | 	Rejected []string `json:"rejected"` | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // errMatrix represents an error when handing Matrix gateway messages | ||||||
| type errMatrix struct { | type errMatrix struct { | ||||||
| 	pushKey string | 	pushKey string | ||||||
| 	err     error | 	err     error | ||||||
|  | @ -54,6 +85,12 @@ func (e errMatrix) Error() string { | ||||||
| 	return fmt.Sprintf("message with push key %s rejected", e.pushKey) | 	return fmt.Sprintf("message with push key %s rejected", e.pushKey) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | const ( | ||||||
|  | 	// matrixPushKeyHeader is a header that's used internally to pass the Matrix push key (from the matrixRequest) | ||||||
|  | 	// along with the request. The push key is only used if an error occurs down the line. | ||||||
|  | 	matrixPushKeyHeader = "X-Matrix-Pushkey" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
| // newRequestFromMatrixJSON reads the request body as a Matrix JSON message, parses the "pushkey", and creates a new | // newRequestFromMatrixJSON reads the request body as a Matrix JSON message, parses the "pushkey", and creates a new | ||||||
| // HTTP request that looks like a normal ntfy request from it. | // HTTP request that looks like a normal ntfy request from it. | ||||||
| // | // | ||||||
|  | @ -82,7 +119,7 @@ func newRequestFromMatrixJSON(r *http.Request, baseURL string, messageLimit int) | ||||||
| 	} else if m.Notification == nil || len(m.Notification.Devices) == 0 || m.Notification.Devices[0].PushKey == "" { | 	} else if m.Notification == nil || len(m.Notification.Devices) == 0 || m.Notification.Devices[0].PushKey == "" { | ||||||
| 		return nil, errHTTPBadRequestMatrixMessageInvalid | 		return nil, errHTTPBadRequestMatrixMessageInvalid | ||||||
| 	} | 	} | ||||||
| 	pushKey := m.Notification.Devices[0].PushKey | 	pushKey := m.Notification.Devices[0].PushKey // We ignore other devices for now, see discussion in #316 | ||||||
| 	if !strings.HasPrefix(pushKey, baseURL+"/") { | 	if !strings.HasPrefix(pushKey, baseURL+"/") { | ||||||
| 		return nil, &errMatrix{pushKey: pushKey, err: errHTTPBadRequestMatrixPushkeyBaseURLMismatch} | 		return nil, &errMatrix{pushKey: pushKey, err: errHTTPBadRequestMatrixPushkeyBaseURLMismatch} | ||||||
| 	} | 	} | ||||||
|  | @ -94,21 +131,27 @@ func newRequestFromMatrixJSON(r *http.Request, baseURL string, messageLimit int) | ||||||
| 	return newRequest, nil | 	return newRequest, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func handleMatrixDiscovery(w http.ResponseWriter) error { | // writeMatrixDiscoveryResponse writes the UnifiedPush Matrix Gateway Discovery response to the given http.ResponseWriter, | ||||||
|  | // as per the spec (https://unifiedpush.org/developers/gateway/). | ||||||
|  | func writeMatrixDiscoveryResponse(w http.ResponseWriter) error { | ||||||
| 	w.Header().Set("Content-Type", "application/json") | 	w.Header().Set("Content-Type", "application/json") | ||||||
| 	_, err := io.WriteString(w, `{"unifiedpush":{"gateway":"matrix"}}`+"\n") | 	_, err := io.WriteString(w, `{"unifiedpush":{"gateway":"matrix"}}`+"\n") | ||||||
| 	return err | 	return err | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // writeMatrixError logs and writes the errMatrix to the given http.ResponseWriter as a matrixResponse | ||||||
| func writeMatrixError(w http.ResponseWriter, r *http.Request, v *visitor, err *errMatrix) error { | func writeMatrixError(w http.ResponseWriter, r *http.Request, v *visitor, err *errMatrix) error { | ||||||
| 	log.Debug("%s Matrix gateway error: %s", logHTTPPrefix(v, r), err.Error()) | 	log.Debug("%s Matrix gateway error: %s", logHTTPPrefix(v, r), err.Error()) | ||||||
| 	return writeMatrixResponse(w, err.pushKey) | 	return writeMatrixResponse(w, err.pushKey) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // writeMatrixSuccess writes a successful matrixResponse (no rejected push key) to the given http.ResponseWriter | ||||||
| func writeMatrixSuccess(w http.ResponseWriter) error { | func writeMatrixSuccess(w http.ResponseWriter) error { | ||||||
| 	return writeMatrixResponse(w, "") | 	return writeMatrixResponse(w, "") | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // writeMatrixResponse writes a matrixResponse to the given http.ResponseWriter, as defined in | ||||||
|  | // the spec (https://spec.matrix.org/v1.2/push-gateway-api/) | ||||||
| func writeMatrixResponse(w http.ResponseWriter, rejectedPushKey string) error { | func writeMatrixResponse(w http.ResponseWriter, rejectedPushKey string) error { | ||||||
| 	rejected := make([]string, 0) | 	rejected := make([]string, 0) | ||||||
| 	if rejectedPushKey != "" { | 	if rejectedPushKey != "" { | ||||||
|  |  | ||||||
							
								
								
									
										21
									
								
								server/server_matrix_test.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								server/server_matrix_test.go
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,21 @@ | ||||||
|  | package server | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"github.com/stretchr/testify/require" | ||||||
|  | 	"net/http" | ||||||
|  | 	"strings" | ||||||
|  | 	"testing" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func TestMatrix_NewRequestFromMatrixJSON_Success(t *testing.T) { | ||||||
|  | 	baseURL := "https://ntfy.sh" | ||||||
|  | 	maxLength := 4096 | ||||||
|  | 	body := `{"notification":{"content":{"body":"I'm floating in a most peculiar way.","msgtype":"m.text"},"counts":{"missed_calls":1,"unread":2},"devices":[{"app_id":"org.matrix.matrixConsole.ios","data":{},"pushkey":"https://ntfy.sh/upABCDEFGHI?up=1","pushkey_ts":12345678,"tweaks":{"sound":"bing"}}],"event_id":"$3957tyerfgewrf384","prio":"high","room_alias":"#exampleroom:matrix.org","room_id":"!slw48wfj34rtnrf:example.com","room_name":"Mission Control","sender":"@exampleuser:matrix.org","sender_display_name":"Major Tom","type":"m.room.message"}}` | ||||||
|  | 	r, _ := http.NewRequest("POST", "http://ntfy.example.com/_matrix/push/v1/notify", strings.NewReader(body)) | ||||||
|  | 	newRequest, err := newRequestFromMatrixJSON(r, baseURL, maxLength) | ||||||
|  | 	require.Nil(t, err) | ||||||
|  | 	require.Equal(t, "POST", newRequest.Method) | ||||||
|  | 	require.Equal(t, "https://ntfy.sh/upABCDEFGHI?up=1", newRequest.URL.String()) | ||||||
|  | 	require.Equal(t, "https://ntfy.sh/upABCDEFGHI?up=1", newRequest.Header.Get("X-Matrix-Pushkey")) | ||||||
|  | 	require.Equal(t, body, readAll(t, newRequest.Body)) | ||||||
|  | } | ||||||
|  | @ -6,6 +6,7 @@ import ( | ||||||
| 	"encoding/base64" | 	"encoding/base64" | ||||||
| 	"encoding/json" | 	"encoding/json" | ||||||
| 	"fmt" | 	"fmt" | ||||||
|  | 	"io" | ||||||
| 	"math/rand" | 	"math/rand" | ||||||
| 	"net/http" | 	"net/http" | ||||||
| 	"net/http/httptest" | 	"net/http/httptest" | ||||||
|  | @ -171,10 +172,6 @@ func TestServer_StaticSites(t *testing.T) { | ||||||
| 	require.Equal(t, 301, rr.Code) | 	require.Equal(t, 301, rr.Code) | ||||||
| 
 | 
 | ||||||
| 	// Docs test removed, it was failing annoyingly. | 	// Docs test removed, it was failing annoyingly. | ||||||
| 
 |  | ||||||
| 	rr = request(t, s, "GET", "/example.html", "", nil) |  | ||||||
| 	require.Equal(t, 200, rr.Code) |  | ||||||
| 	require.Contains(t, rr.Body.String(), "</html>") |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestServer_WebEnabled(t *testing.T) { | func TestServer_WebEnabled(t *testing.T) { | ||||||
|  | @ -185,9 +182,6 @@ func TestServer_WebEnabled(t *testing.T) { | ||||||
| 	rr := request(t, s, "GET", "/", "", nil) | 	rr := request(t, s, "GET", "/", "", nil) | ||||||
| 	require.Equal(t, 404, rr.Code) | 	require.Equal(t, 404, rr.Code) | ||||||
| 
 | 
 | ||||||
| 	rr = request(t, s, "GET", "/example.html", "", nil) |  | ||||||
| 	require.Equal(t, 404, rr.Code) |  | ||||||
| 
 |  | ||||||
| 	rr = request(t, s, "GET", "/config.js", "", nil) | 	rr = request(t, s, "GET", "/config.js", "", nil) | ||||||
| 	require.Equal(t, 404, rr.Code) | 	require.Equal(t, 404, rr.Code) | ||||||
| 
 | 
 | ||||||
|  | @ -201,9 +195,6 @@ func TestServer_WebEnabled(t *testing.T) { | ||||||
| 	rr = request(t, s2, "GET", "/", "", nil) | 	rr = request(t, s2, "GET", "/", "", nil) | ||||||
| 	require.Equal(t, 200, rr.Code) | 	require.Equal(t, 200, rr.Code) | ||||||
| 
 | 
 | ||||||
| 	rr = request(t, s2, "GET", "/example.html", "", nil) |  | ||||||
| 	require.Equal(t, 200, rr.Code) |  | ||||||
| 
 |  | ||||||
| 	rr = request(t, s2, "GET", "/config.js", "", nil) | 	rr = request(t, s2, "GET", "/config.js", "", nil) | ||||||
| 	require.Equal(t, 200, rr.Code) | 	require.Equal(t, 200, rr.Code) | ||||||
| 
 | 
 | ||||||
|  | @ -1390,3 +1381,11 @@ func toHTTPError(t *testing.T, s string) *errHTTP { | ||||||
| func basicAuth(s string) string { | func basicAuth(s string) string { | ||||||
| 	return fmt.Sprintf("Basic %s", base64.StdEncoding.EncodeToString([]byte(s))) | 	return fmt.Sprintf("Basic %s", base64.StdEncoding.EncodeToString([]byte(s))) | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | func readAll(t *testing.T, rc io.ReadCloser) string { | ||||||
|  | 	b, err := io.ReadAll(rc) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatal(err) | ||||||
|  | 	} | ||||||
|  | 	return string(b) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -3,7 +3,6 @@ package server | ||||||
| import ( | import ( | ||||||
| 	"github.com/emersion/go-smtp" | 	"github.com/emersion/go-smtp" | ||||||
| 	"github.com/stretchr/testify/require" | 	"github.com/stretchr/testify/require" | ||||||
| 	"io" |  | ||||||
| 	"net" | 	"net" | ||||||
| 	"net/http" | 	"net/http" | ||||||
| 	"strings" | 	"strings" | ||||||
|  | @ -304,14 +303,6 @@ func newTestBackend(t *testing.T, handler func(http.ResponseWriter, *http.Reques | ||||||
| 	return conf, backend | 	return conf, backend | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func readAll(t *testing.T, rc io.ReadCloser) string { |  | ||||||
| 	b, err := io.ReadAll(rc) |  | ||||||
| 	if err != nil { |  | ||||||
| 		t.Fatal(err) |  | ||||||
| 	} |  | ||||||
| 	return string(b) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func fakeConnState(t *testing.T, remoteAddr string) *smtp.ConnectionState { | func fakeConnState(t *testing.T, remoteAddr string) *smtp.ConnectionState { | ||||||
| 	ip, err := net.ResolveIPAddr("ip", remoteAddr) | 	ip, err := net.ResolveIPAddr("ip", remoteAddr) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
|  |  | ||||||
|  | @ -18,7 +18,8 @@ type PeekedReadCloser struct { | ||||||
| 	closed       bool | 	closed       bool | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Peek reads the underlying ReadCloser into memory up until the limit and returns a PeekedReadCloser | // Peek reads the underlying ReadCloser into memory up until the limit and returns a PeekedReadCloser. | ||||||
|  | // It does not return an error if limit is reached. Instead, LimitReached will be set to true. | ||||||
| func Peek(underlying io.ReadCloser, limit int) (*PeekedReadCloser, error) { | func Peek(underlying io.ReadCloser, limit int) (*PeekedReadCloser, error) { | ||||||
| 	if underlying == nil { | 	if underlying == nil { | ||||||
| 		underlying = io.NopCloser(strings.NewReader("")) | 		underlying = io.NopCloser(strings.NewReader("")) | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue