Compare commits

..

835 Commits

Author SHA1 Message Date
binwiederhier
d3f7aa7008 Self-review 2025-06-01 10:12:06 -04:00
binwiederhier
bbfaf2fc4d Add Forwarded header parsing 2025-06-01 09:57:39 -04:00
binwiederhier
db4ac158e3 Section 2025-05-31 23:09:51 -04:00
binwiederhier
7a33e16945 Cleanup, examples 2025-05-31 23:07:40 -04:00
binwiederhier
eac49feb04 Merge branch 'main' of github.com:binwiederhier/ntfy into client-ip-header 2025-05-31 22:42:40 -04:00
binwiederhier
849884c947 Change to "proxy-forwarded-header" and add "proxy-trusted-addrs" 2025-05-31 22:39:18 -04:00
binwiederhier
2cb4d089ab Merge client ip header 2025-05-31 15:33:21 -04:00
binwiederhier
dc797f8594 Fix release noteFix release notess 2025-05-29 20:35:22 -04:00
binwiederhier
061677a78b Bump version 2025-05-29 20:14:54 -04:00
binwiederhier
b4f15ec9d4 Integrations 2025-05-26 13:41:26 -04:00
binwiederhier
af17661053 Typos, server.yml additions 2025-05-25 20:13:13 -04:00
binwiederhier
635ec88c4f Update release log 2025-05-25 15:28:59 -04:00
Philipp C. Heckel
905f048ab4 Merge pull request #1123 from stendler/json-firebase-cache
feat(server): add Cache and Firebase as keys to JSON publishing
2025-05-25 15:24:51 -04:00
binwiederhier
7f86108379 Update docs 2025-05-25 12:57:02 -04:00
Philipp C. Heckel
425e6d064e Merge pull request #1002 from dandersch/ntfy-client_user_service
Add systemd user service for `ntfy-client.service`
2025-05-25 12:42:51 -04:00
Philipp C. Heckel
ebb61fcccf Merge pull request #1353 from binwiederhier/apns-fix
APNs fix
2025-05-25 12:33:53 -04:00
binwiederhier
9f72eb804d Computers are fast 2025-05-25 12:32:16 -04:00
binwiederhier
42af71e546 Fix test 2025-05-25 12:27:21 -04:00
binwiederhier
df818cfebc Merge branch 'main' of github.com:binwiederhier/ntfy 2025-05-25 12:23:20 -04:00
binwiederhier
0de1990c01 Increase number of access tokens per user to 60 2025-05-25 12:23:02 -04:00
binwiederhier
f40023aa23 APNs fix 2025-05-25 12:09:57 -04:00
Philipp C. Heckel
5765a707fc Merge pull request #1352 from leukosaima/patch-1
Add ntfyrr project to integrations
2025-05-25 06:43:38 -04:00
leukosaima
5eb84f759b Add ntfyrr project 2025-05-25 00:20:55 -04:00
binwiederhier
df7dd9c498 Fix weebpush test 2025-05-24 15:55:02 -04:00
binwiederhier
6fe3913aee Increase Web Push expiration to 55/60 days, update configs 2025-05-24 15:26:25 -04:00
Philipp C. Heckel
0ad9716241 Merge pull request #1212 from KuroSetsuna29/config-webpush-expiry
feat: allow configurable web push expiry duration
2025-05-24 14:57:50 -04:00
binwiederhier
f4c37ccfb9 Bump VIte 2025-05-24 14:22:02 -04:00
Philipp C. Heckel
7182d3a4e5 Merge pull request #1342 from binwiederhier/dependabot/npm_and_yarn/web/multi-4e779676ec
Bump esbuild, vite and vite-plugin-pwa in /web
2025-05-24 14:07:01 -04:00
binwiederhier
eecd3245f0 Release notes 2025-05-24 09:36:16 -04:00
binwiederhier
4dc3b38c95 Allow adding/changing user with password hash via v1/users API 2025-05-24 09:31:57 -04:00
binwiederhier
9edab24d4c Merge branch 'main' of github.com:binwiederhier/ntfy 2025-05-24 09:10:07 -04:00
Philipp C. Heckel
3b627b27b3 Merge pull request #1340 from Tom-Hubrecht/hashed-pwd
user: Allow changing the hashed password directly
2025-05-24 09:10:02 -04:00
binwiederhier
80462f7ee5 Refine user API change 2025-05-24 08:58:44 -04:00
Philipp C. Heckel
65e377ec63 Merge pull request #1267 from wunter8/api-change-user-password
Api change user password
2025-05-24 08:53:28 -04:00
dandersch
45e1707d3b remove systemd user daemon-reload from postinst.sh 2025-05-23 23:00:45 +02:00
dandersch
0581a9e680 remove systemd user service cmds from postinst.sh 2025-05-23 22:52:25 +02:00
Hunter Kehoe
0fb60ae72d test change user password and tier in single request 2025-05-22 20:01:50 -06:00
Hunter Kehoe
e36e4856c9 allow changing password or tier with user PUT 2025-05-22 19:57:57 -06:00
Hunter Kehoe
fa48639517 make POST create user and PUT update user 2025-05-22 19:57:02 -06:00
Hunter Kehoe
2b40ad9a12 make staticcheck happy 2025-05-22 19:57:02 -06:00
Hunter Kehoe
ad7ab18fb7 prevent changing admin passwords 2025-05-22 19:57:02 -06:00
Hunter Kehoe
8f9dafce20 change user password via accounts API 2025-05-22 19:57:00 -06:00
binwiederhier
69cf773834 Fix webpush command 2025-05-22 21:56:28 -04:00
binwiederhier
b2b9891a58 Add Tamil language 2025-05-22 21:43:45 -04:00
Andrea Toska
3bf02d3cd9 Translated using Weblate (Albanian)
Currently translated at 15.0% (61 of 405 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/sq/
2025-05-23 03:23:15 +02:00
Priit Jõerüüt
8777990d2d Translated using Weblate (Estonian)
Currently translated at 24.9% (101 of 405 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/et/
2025-05-23 03:23:15 +02:00
Tyxiel
70f0e7ccc7 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (405 of 405 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/pt_BR/
2025-05-23 03:23:15 +02:00
Max Badran
adfacf820e Translated using Weblate (Ukrainian)
Currently translated at 99.7% (404 of 405 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/uk/
2025-05-23 03:23:15 +02:00
András
35e15cfd9d Translated using Weblate (Hungarian)
Currently translated at 53.5% (217 of 405 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/hu/
2025-05-23 03:23:15 +02:00
Eero Häkkinen
4e2a884da5 Translated using Weblate (Finnish)
Currently translated at 100.0% (405 of 405 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/fi/
2025-05-23 03:23:15 +02:00
Eero Häkkinen
29cf4f16d1 Translated using Weblate (Finnish)
Currently translated at 100.0% (405 of 405 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/fi/
2025-05-23 03:23:15 +02:00
Łukasz Podgórski
609c9fa37d Translated using Weblate (Polish)
Currently translated at 100.0% (405 of 405 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/pl/
2025-05-23 03:23:15 +02:00
Korab Arifi
2eb5eb3e29 Translated using Weblate (Albanian)
Currently translated at 10.1% (41 of 405 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/sq/
2025-05-23 03:23:15 +02:00
Korab Arifi
a92306b181 Added translation using Weblate (Albanian) 2025-05-23 03:23:15 +02:00
OZZY
047cc22dba Translated using Weblate (Arabic)
Currently translated at 88.3% (358 of 405 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/ar/
2025-05-23 03:23:15 +02:00
Priit Jõerüüt
f31d777b69 Translated using Weblate (Estonian)
Currently translated at 10.6% (43 of 405 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/et/
2025-05-23 03:23:15 +02:00
தமிழ்நேரம்
ac983cd9bc Translated using Weblate (Tamil)
Currently translated at 100.0% (405 of 405 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/ta/
2025-05-23 03:23:15 +02:00
AtomicDude
dd45fd90b7 Translated using Weblate (Romanian)
Currently translated at 56.7% (230 of 405 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/ro/
2025-05-23 03:23:15 +02:00
தமிழ்நேரம்
e76e6274a3 Added translation using Weblate (Tamil) 2025-05-23 03:23:15 +02:00
Marius Pop
161ce468fe Translated using Weblate (Romanian)
Currently translated at 46.6% (189 of 405 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/ro/
2025-05-23 03:23:15 +02:00
Danial Behzadi
04df6f1390 Translated using Weblate (Persian)
Currently translated at 13.8% (56 of 405 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/fa/
2025-05-23 03:23:15 +02:00
Faraz Sadri Alamdari
79852fec59 Translated using Weblate (Persian)
Currently translated at 13.8% (56 of 405 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/fa/
2025-05-23 03:23:15 +02:00
Danial Behzadi
92de1b5a88 Translated using Weblate (Persian)
Currently translated at 13.8% (56 of 405 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/fa/
2025-05-23 03:23:15 +02:00
Ihor Kalnytskyi
fc93de9a28 Translated using Weblate (Ukrainian)
Currently translated at 99.7% (404 of 405 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/uk/
2025-05-23 03:23:15 +02:00
qtm
ae9fa85676 Translated using Weblate (Russian)
Currently translated at 100.0% (405 of 405 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/ru/
2025-05-23 03:23:15 +02:00
Christer Solstrand Johannessen
b26666f635 Translated using Weblate (Norwegian Bokmål)
Currently translated at 100.0% (405 of 405 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/nb_NO/
2025-05-23 03:23:15 +02:00
Christer Solstrand Johannessen
70a9301e25 Translated using Weblate (Norwegian Bokmål)
Currently translated at 51.1% (207 of 405 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/nb_NO/
2025-05-23 03:23:15 +02:00
Cairo Braga
86c548ae37 Translated using Weblate (Portuguese)
Currently translated at 76.5% (310 of 405 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/pt/
2025-05-23 03:23:15 +02:00
Ed
1e1b2be464 Translated using Weblate (Portuguese)
Currently translated at 76.5% (310 of 405 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/pt/
2025-05-23 03:23:15 +02:00
Cairo Braga
1b8906f1fd Translated using Weblate (Portuguese (Brazil))
Currently translated at 84.1% (341 of 405 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/pt_BR/
2025-05-23 03:23:15 +02:00
Luis Eduardo Brito
b81f7b21a9 Translated using Weblate (Portuguese (Brazil))
Currently translated at 83.9% (340 of 405 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/pt_BR/
2025-05-23 03:23:15 +02:00
K0ntact
db2dc09189 Translated using Weblate (Vietnamese)
Currently translated at 7.1% (29 of 405 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/vi/
2025-05-23 03:23:14 +02:00
Shoshin Akamine
5f6b7e6f82 Translated using Weblate (Japanese)
Currently translated at 100.0% (405 of 405 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/ja/
2025-05-23 03:23:14 +02:00
109247019824
6daf4141c6 Translated using Weblate (Bulgarian)
Currently translated at 100.0% (405 of 405 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/bg/
2025-05-23 03:23:14 +02:00
Vito0912
41083cfd07 Translated using Weblate (German)
Currently translated at 100.0% (405 of 405 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/de/
2025-05-23 03:23:14 +02:00
Soaibuzzaman
c03f795508 Added translation using Weblate (Bengali) 2025-05-23 03:23:14 +02:00
Ricardo Vieira
58d7cb8ef8 Translated using Weblate (Portuguese)
Currently translated at 76.2% (309 of 405 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/pt/
2025-05-23 03:23:14 +02:00
Linerly
8acf0f4350 Translated using Weblate (Indonesian)
Currently translated at 100.0% (405 of 405 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/id/
2025-05-23 03:23:14 +02:00
Carl Fritze
236b7b7a16 Translated using Weblate (German)
Currently translated at 99.5% (403 of 405 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/de/
2025-05-23 03:23:14 +02:00
Petri Hämäläinen
871883f6e9 Translated using Weblate (Finnish)
Currently translated at 100.0% (405 of 405 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/fi/
2025-05-23 03:23:14 +02:00
Malte Saling
a92c8a9ec9 Translated using Weblate (German)
Currently translated at 95.5% (387 of 405 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/de/
2025-05-23 03:23:14 +02:00
Vito0912
1c6aa49fca Translated using Weblate (German)
Currently translated at 95.3% (386 of 405 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/de/
2025-05-23 03:23:14 +02:00
githubozaurus
49d258706d Translated using Weblate (Romanian)
Currently translated at 31.1% (126 of 405 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/ro/
2025-05-23 03:23:14 +02:00
josé m
bbce1200b4 Translated using Weblate (Galician)
Currently translated at 100.0% (405 of 405 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/gl/
2025-05-23 03:23:14 +02:00
Stefano Maggi
94d0c5a335 Translated using Weblate (Italian)
Currently translated at 100.0% (405 of 405 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/it/
2025-05-23 03:23:14 +02:00
Jakob Malchow
7835fc65c4 Translated using Weblate (Italian)
Currently translated at 87.6% (355 of 405 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/it/
2025-05-23 03:23:14 +02:00
Stefano Maggi
dc6b8ece1e Translated using Weblate (Italian)
Currently translated at 87.6% (355 of 405 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/it/
2025-05-23 03:23:14 +02:00
josé m
f595dff66f Translated using Weblate (Galician)
Currently translated at 100.0% (405 of 405 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/gl/
2025-05-23 03:23:14 +02:00
binwiederhier
0514ea4ac0 Merge branch 'main' of github.com:binwiederhier/ntfy 2025-05-22 20:59:29 -04:00
binwiederhier
1598087e1f Fix tests 2025-05-22 20:58:28 -04:00
Philipp C. Heckel
3709ea689a Merge pull request #1216 from wunter8/since-latest-param
feat: add since=latest subscribe param
2025-05-22 20:57:59 -04:00
Philipp C. Heckel
f4aba12546 Merge branch 'main' into since-latest-param 2025-05-22 20:57:52 -04:00
Philipp C. Heckel
521fe791b0 Merge pull request #1271 from RoboMagus/feat_1174
Add major and minor version tags to docker release flow
2025-05-22 20:51:00 -04:00
binwiederhier
6d15b9face Fix up APNs PR 2025-05-22 20:48:24 -04:00
Philipp C. Heckel
9fbe7804dd Merge pull request #1345 from tr4nt0r/homeassistant_official
Add official Home Assistant integration and async python library
2025-05-22 20:30:18 -04:00
Philipp C. Heckel
faa4dcbcee Merge pull request #1287 from barart/fix-anonymous-read-restriction
Handle anonymous read restrictions by sending a poll_request event
2025-05-22 20:22:58 -04:00
tr4nt0r
ad3e7960ce Add official Home Assistant integration and async python library 2025-05-23 01:31:16 +02:00
Philipp C. Heckel
3234189cd2 Merge pull request #1343 from ptmorris1/patch-2
Add NtfyPwsh integration and blog
2025-05-22 16:51:53 -04:00
Patrick Morris
e64a0bd8c9 Add NtfyPwsh integration and blog 2025-05-22 13:37:54 -05:00
Philipp C. Heckel
97a59f19e0 Merge pull request #1262 from rake5k/markdown-code-scroll
Make markdown code blocks scrollable
2025-05-21 20:57:54 -04:00
binwiederhier
7067d8aa77 Release notes 2025-05-21 20:55:54 -04:00
Philipp C. Heckel
5999653456 Merge pull request #1138 from nogweii/webpush-key-file
Teach ntfy webpush to write the keys to a file
2025-05-21 20:44:44 -04:00
Philipp C. Heckel
9ce6b03450 Merge branch 'main' into webpush-key-file 2025-05-21 20:39:12 -04:00
Philipp C. Heckel
7e916516e0 Merge pull request #1338 from wunter8/websockets-401
Websocket http error codes
2025-05-21 20:26:32 -04:00
Philipp C. Heckel
09c2b4bdca Merge pull request #1239 from thiswillbeyourgithub/integration_toc
docs: add ToC to integrations.md
2025-05-21 20:21:40 -04:00
Philipp C. Heckel
978ee81df3 Merge pull request #1199 from quantum5/ntfy-run
docs: add quantum5/ntfy-run to integrations and examples
2025-05-21 20:19:47 -04:00
Philipp C. Heckel
86f2ab8a55 Merge branch 'main' into ntfy-run 2025-05-21 20:19:39 -04:00
Philipp C. Heckel
e4aff00455 Merge pull request #1234 from ungeskriptet/fix-typo
docs: config.md: fix typo
2025-05-21 20:17:57 -04:00
Philipp C. Heckel
e88f24bae7 Merge pull request #1241 from thiswillbeyourgithub/integration_csv
docs: add integration: Ntfy_CSV_Reminders
2025-05-21 20:16:45 -04:00
Philipp C. Heckel
b4797ef212 Merge branch 'main' into integration_csv 2025-05-21 20:16:35 -04:00
Philipp C. Heckel
2f8c0e4d5d Merge pull request #1224 from dmitrygudkov/patch-1
Update integrations.md: Added EasyMorph
2025-05-21 20:14:07 -04:00
Philipp C. Heckel
56231f9288 Merge branch 'main' into patch-1 2025-05-21 20:13:56 -04:00
Philipp C. Heckel
ef7c7c7b09 Merge pull request #1209 from jim3692/patch-1
Add Clipboard IO to projects
2025-05-21 20:10:51 -04:00
Philipp C. Heckel
88e4b8f0e6 Merge branch 'main' into patch-1 2025-05-21 20:10:43 -04:00
Philipp C. Heckel
090bdd93ba Merge pull request #1187 from 13x1/patch-1
Fix typo
2025-05-21 20:09:18 -04:00
Philipp C. Heckel
790044e899 Merge pull request #1240 from thiswillbeyourgithub/integration_daily_fact
docs: add integration: Daily Fact Ntfy
2025-05-21 20:07:00 -04:00
dependabot[bot]
7aab7d387f Bump esbuild, vite and vite-plugin-pwa in /web
Bumps [esbuild](https://github.com/evanw/esbuild) to 0.25.4 and updates ancestor dependencies [esbuild](https://github.com/evanw/esbuild), [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) and [vite-plugin-pwa](https://github.com/vite-pwa/vite-plugin-pwa). These dependencies need to be updated together.


Updates `esbuild` from 0.18.20 to 0.25.4
- [Release notes](https://github.com/evanw/esbuild/releases)
- [Changelog](https://github.com/evanw/esbuild/blob/main/CHANGELOG-2023.md)
- [Commits](https://github.com/evanw/esbuild/compare/v0.18.20...v0.25.4)

Updates `vite` from 4.5.14 to 6.3.5
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v6.3.5/packages/vite)

Updates `vite-plugin-pwa` from 0.15.2 to 1.0.0
- [Release notes](https://github.com/vite-pwa/vite-plugin-pwa/releases)
- [Commits](https://github.com/vite-pwa/vite-plugin-pwa/compare/v0.15.2...v1.0.0)

---
updated-dependencies:
- dependency-name: esbuild
  dependency-version: 0.25.4
  dependency-type: indirect
- dependency-name: vite
  dependency-version: 6.3.5
  dependency-type: direct:development
- dependency-name: vite-plugin-pwa
  dependency-version: 1.0.0
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-22 00:04:33 +00:00
binwiederhier
a461aafb91 Merge branch 'main' of github.com:binwiederhier/ntfy 2025-05-21 20:02:19 -04:00
binwiederhier
1569c22a65 Fix some broken links in the docs 2025-05-21 20:02:06 -04:00
Philipp C. Heckel
2091ceb4d2 Merge pull request #1248 from pixitha/patch-1
Update quickstart example
2025-05-21 19:50:40 -04:00
Philipp C. Heckel
ec337b5de9 Merge pull request #1296 from sharjeelaziz/add-bashrc-example
Add Terminal Notifications for Long-Running Commands example
2025-05-21 19:33:54 -04:00
Philipp C. Heckel
5a245f889c Merge pull request #1260 from snex/main
add canary in the cage podcast coverage to integrations page
2025-05-21 19:31:27 -04:00
Philipp C. Heckel
ee595067ba Merge pull request #1307 from jlssmt/patch-1
set LABEL org.opencontainers.image.version
2025-05-21 19:24:43 -04:00
Philipp C. Heckel
bd4b5e9e1b Merge pull request #1312 from vkrause/work/vkrause/detect-encrypted-messages-as-unified-push
Consider aes128gcm content encoding as an indicator for UnifiedPush
2025-05-21 19:16:17 -04:00
Philipp C. Heckel
786e588397 Merge pull request #1325 from gitmotion/add-ntfy-me-mcp-integrations-projects
docs: add integration ntfy-me-mcp to integrations.md
2025-05-21 19:15:02 -04:00
Philipp C. Heckel
2dfb53ec53 Merge branch 'main' into add-ntfy-me-mcp-integrations-projects 2025-05-21 19:14:54 -04:00
Philipp C. Heckel
a30c5eb9cf Merge pull request #1310 from biodrone/biodrone/issue1309
docs: correct mountPath for server.yml
2025-05-21 19:13:51 -04:00
Philipp C. Heckel
d96d4b03c7 Merge pull request #1225 from sedlund/docs/fix_typo
docs: publish.md typo
2025-05-21 19:13:23 -04:00
Philipp C. Heckel
3257ce91ef Merge pull request #1264 from brian6932/patch-1
docs: Typo `wep` -> `web`
2025-05-21 19:12:44 -04:00
Philipp C. Heckel
f563b671c8 Merge pull request #1266 from mmatuska/fix/extractIPAddress
server/util.go: fix logic in extractIPAddress()
2025-05-21 19:11:13 -04:00
Philipp C. Heckel
ea1cda5f92 Merge pull request #1299 from cvilsmeier/main
docs: add integration: Monibot
2025-05-21 19:09:49 -04:00
Philipp C. Heckel
36ba27ba09 Merge pull request #1313 from patricksthannon/patch-1
Update integrations.md
2025-05-21 19:09:06 -04:00
Philipp C. Heckel
60eccba2fa Merge pull request #1320 from yassirh/main
docs: Added reference to UptimeObserver integration
2025-05-21 19:07:14 -04:00
Philipp C. Heckel
aec4b97fae Merge pull request #1319 from therobbielee/therobbielee-patch-1
Update integrations.md
2025-05-21 19:06:18 -04:00
Philipp C. Heckel
389ae682a5 Merge pull request #1341 from binwiederhier/security-updates
Security updates
2025-05-21 19:04:26 -04:00
binwiederhier
3f21da7768 Pipelines 2025-05-21 18:55:21 -04:00
binwiederhier
0ad266a495 Derp 2025-05-21 18:53:29 -04:00
binwiederhier
bd192edf1e Release notes 2025-05-21 18:52:45 -04:00
binwiederhier
d1ac8d03e0 Security updates 2025-05-21 18:49:19 -04:00
Tom Hubrecht
44b7c2f198 user: Allow changing the hashed password directly
This adds the detection of `NTFY_PASSWORD_HASH` when creating a user or
changing its passsword so that scripts don't have to manipulate the bare
password.
2025-05-21 16:34:14 +02:00
Hunter Kehoe
cdae5493e2 write http errors to websocket connection instead of always 200 2025-05-14 11:39:18 -06:00
Hunter Kehoe
f110472204 fix typo 2025-05-14 11:20:30 -06:00
gitmotion
3f1342c05b Add ntfy-me-mcp 2025-04-28 19:25:40 -07:00
Yassir Hannoun
8b95b1a213 docs: Added UptimeObserver integration 2025-04-26 21:13:12 +00:00
Robbie Björk
d4dfd3f657 Update integrations.md
Added alertmanager-ntfy-relay to integrations.md
2025-04-26 00:22:54 +02:00
patricksthannon
c1d718ee68 Update integrations.md
Added InvaderInformant integration to integration list. Thanks!
2025-04-09 09:53:17 -07:00
Volker Krause
bd08a120cd Consider aes128gcm content encoding as an indicator for UnifiedPush
Without this a UnifiedPush/Web Push message with encryption would be
turned into an attachment. That in itself isn't pretty but can still
work, but it requires attachments to be enabled in the first place.
2025-04-07 17:19:44 +02:00
Josh J
c9126e7aa9 docs: correct mountPath for server.yml
Fixed #1309
2025-04-07 09:26:52 +01:00
jlssmt
db9b974e47 set LABEL org.opencontainers.image.version 2025-04-06 11:35:42 +02:00
Christoph Vilsmeier
889a6f03f8 docs: add integration: Monibot 2025-03-25 16:56:47 +01:00
Sharjeel Aziz
6af8d03470 Add Terminal Notifications for Long-Running Commands example
Signed-off-by: Sharjeel Aziz <sharjeel.aziz@gmail.com>
2025-03-19 15:35:46 -04:00
barart
6b2cfb1d1d Handle anonymous read restrictions by sending a poll_request event
If a topic does not allow anonymous reads, this change ensures that we send a "poll_request" event instead of relaying the message via Firebase. Additionally, we include generic text in the title and body/message. This way, if the client cannot retrieve the actual message, the user will still receive a notification, prompting them to update the client manually.
2025-03-05 13:04:21 -06:00
RoboMagus
35458230a8 add major and minor version tags to docker release flow 2025-01-30 23:49:22 +01:00
Martin Matuska
bd39cf4b54 server/util.go: fix logic in extractIPAddress() 2025-01-26 00:00:06 +01:00
Brian
f739a3067e docs: Typo wep -> web 2025-01-24 20:28:29 -05:00
Christian Harke
2344eee2c6 Make markdown code blocks scrollable 2025-01-20 22:13:15 +01:00
David Havlicek
5822a2ec41 add canary in the cage podcast coverage to integrations page 2025-01-17 13:26:22 -08:00
Kyle Duren
a49cafbadb more correcting auto-formats 2025-01-06 02:55:03 +00:00
Kyle Duren
0aee6252bb fixing auto-format change 2025-01-06 01:09:40 +00:00
Kyle Duren
0e6a483b2f fixing auto-format change 2025-01-06 01:06:28 +00:00
Kyle Duren
20c014ba8d Adding test and some docs 2025-01-06 00:57:53 +00:00
Kyle Duren
926967b6e7 adding logic to specifcy client-ip header from proxy 2025-01-05 20:29:08 +00:00
Kyle Duren
6345e7f864 Update quickstart example
Just noticed the behind proxy was missing from the example that was supposed to include it.
2025-01-01 22:08:30 -05:00
thiswillbeyourgithub
80bc600ff0 docs: add integration: Ntfy_CSV_Reminders
Signed-off-by: thiswillbeyourgithub
<26625900+thiswillbeyourgithub@users.noreply.github.com>
2024-12-12 17:55:08 +01:00
thiswillbeyourgithub
758828e7aa docs: add integration: Daily Fact Ntfy
Signed-off-by: thiswillbeyourgithub <26625900+thiswillbeyourgithub@users.noreply.github.com>
2024-12-11 12:50:03 +01:00
thiswillbeyourgithub
4c179b7d9d docs: add ToC to integrations.md
Signed-off-by: thiswillbeyourgithub <26625900+thiswillbeyourgithub@users.noreply.github.com>
2024-12-11 12:40:36 +01:00
David Wronek
27398e7d72 docs: config.md: fix typo
Add a missing parenthesis.

Signed-off-by: David Wronek <david@mainlining.org>
2024-12-10 08:42:59 +01:00
Scott Edlund
19f8a35588 docs: publish.md typo 2024-11-23 13:24:24 +07:00
Dmitry Gudkov
8feb0f1a2e Update integrations.md: Added EasyMorph
EasyMorph (https://easymorph.com) is a visual workflow-based data preparation and automation tool. It has 180+ actions, including a dedicated action to send notifications to ntfy as a workflow step.

The proposed link leads to the official help page for the "Send message to ntfy" action.
2024-11-19 21:28:59 -05:00
Hunter Kehoe
9241b0550c feat: add subscribe param 2024-11-04 21:33:35 -07:00
KuroSetsuna29
136b656ccb fix descriptions 2024-11-02 08:50:57 -04:00
KuroSetsuna29
c844c24a16 allow configurable web push expiry duration 2024-11-02 00:46:31 -04:00
jim3692
90f21ba408 Add Clipboard IO to projects 2024-10-30 14:48:32 +02:00
Quantum
b843c69c16 docs: add quantum5/ntfy-run to integrations and examples 2024-10-16 22:00:48 -04:00
binwiederhier
630f2957de Merge branch 'main' of github.com:binwiederhier/ntfy 2024-09-29 13:20:45 -04:00
binwiederhier
d243c22510 Docs, fix lint 2024-09-29 13:20:36 -04:00
Philipp C. Heckel
fbf325a630 Merge pull request #1122 from stendler/docs
update links in integration docs
2024-09-29 13:19:21 -04:00
Philipp C. Heckel
84f421a464 Merge pull request #1175 from Measurity/patch-1
Added note to add ", chain=DOCKER-USER" to the fail2ban jail action if using docker networks
2024-09-29 13:14:26 -04:00
binwiederhier
d38c149263 Docs 2024-09-29 13:12:51 -04:00
Philipp C. Heckel
fc3624cd50 Merge pull request #1141 from pcouy/smtp-sender-date-header
Add Date header to sent e-mails
2024-09-29 12:41:29 -04:00
Philipp C. Heckel
78533e27fe Merge pull request #1133 from Walzen-Group/integrations-wlzntfy
Integrations wlzntfy
2024-09-29 12:40:46 -04:00
Philipp C. Heckel
02e46c1d03 Merge pull request #1137 from ShlomoCode/patch-1
macrodroid supports POST requests
2024-09-29 12:39:25 -04:00
Philipp C. Heckel
81f05b3f15 Merge pull request #1161 from OneWeekNotice/documentation-update
Updating Docs - configuration table, shoutrr, Scrutiny, etc
2024-09-29 12:38:07 -04:00
Philipp C. Heckel
eb700b4b6c Merge pull request #1164 from bishtawi/bishtawi/smtp-auth
Support SMTP Auth Plain for event publishing
2024-09-29 12:30:01 -04:00
Philipp C. Heckel
89c884ab4d Merge pull request #1172 from anirvan/patch-1
Fix typo in cli.md ("subscibe" → "subscribe")
2024-09-29 12:23:26 -04:00
Philipp C. Heckel
0fe0b0c9d7 Merge pull request #1177 from hoho4190/fix-config-md
docs(config.md): remove duplicate comment
2024-09-29 12:22:56 -04:00
binwiederhier
bad3ef43b7 BUmp 2024-09-29 12:22:16 -04:00
lexi
903ef71b6f Fix typo
"Firebase (FCM" -> "Firebase (FCM)"
2024-09-22 11:58:26 +02:00
hoho4190
5726f8e9ba docs(config.md): remove duplicate comment
removed links typed twice
2024-09-03 15:06:46 +09:00
Meas
5b10cd660b Update config.md
Added note to add ", chain=DOCKER-USER" to the fail2ban jail action if using docker networks

By default, the jail action chain is "INPUT", but "FORWARD" is used when using docker networks. "DOCKER-USER", available when using docker, is part of the "FORWARD" chain. Hence the note to use "DOCKER-USER".
2024-08-31 09:06:54 +02:00
Anirvan Chatterjee
333d901661 Fix typo in cli.md ("subscibe" → "subscribe") 2024-08-25 22:19:05 -07:00
Bishtawi
112efaae90 Support SMTP Auth Plain 2024-08-07 17:29:54 -07:00
OneWeekNotice
61bb8a0286 adding Scrutiny project to integrations documentation 2024-07-26 23:16:49 -04:00
OneWeekNotice
be2bebf517 adding logging configurations to Config options table 2024-07-26 23:01:35 -04:00
OneWeekNotice
a4cf40907b removing WATCHTOWER_NOTIFICATIONS as this is Legacy notifications 2024-07-26 22:50:27 -04:00
OneWeekNotice
6562ba6987 adding Watchtower - shoutrrr authentication with auth token utilizing ntfy url 2024-07-26 22:48:14 -04:00
OneWeekNotice
6da554d1e5 adding documentation for caddy WebSockets 2024-07-26 22:43:08 -04:00
Philipp C. Heckel
72f36f8296 Merge pull request #1149 from abhisheksoni27/patch-1
feat: add app store reference
2024-07-19 09:41:55 +02:00
binwiederhier
e8685baf15 Thank you @cdf-eagles for your generous donation 2024-07-19 09:34:14 +02:00
binwiederhier
f8085f8686 Thank you @user8446 for your donation 2024-07-19 09:33:45 +02:00
binwiederhier
3139c13e50 Thank you @chxseh for your donation 2024-07-19 09:33:18 +02:00
binwiederhier
4a2b5676d9 Thank you @wielandp for your donation 2024-07-19 09:32:47 +02:00
binwiederhier
a12195d3c7 Thank you @Proximus888 for your sponsorship 2024-07-19 09:32:03 +02:00
binwiederhier
412e78c4d0 Thank you @suhlig for your sponsorship 2024-07-19 09:31:02 +02:00
binwiederhier
22bdc91630 Thank you @dlt-green for your sponsorship 2024-07-19 09:29:46 +02:00
binwiederhier
e94c2fef52 Thank you @sheetd for your donation 2024-07-19 09:28:36 +02:00
binwiederhier
694363013d Thank you @TheCraiggers for your sponsorship 2024-07-19 09:28:03 +02:00
binwiederhier
fb6a408cca Thank you @avalentic for your donation 2024-07-19 09:26:55 +02:00
binwiederhier
89437019fb Bump 2024-07-19 09:26:44 +02:00
binwiederhier
a095ab56bb Merge branch 'main' of https://hosted.weblate.org/git/ntfy/web 2024-07-19 09:24:49 +02:00
Jiri Grönroos
92905fd860 Translated using Weblate (Finnish)
Currently translated at 98.5% (399 of 405 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/fi/
2024-07-16 13:09:10 +00:00
Priit Jõerüüt
01c216d506 Translated using Weblate (Estonian)
Currently translated at 5.9% (24 of 405 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/et/
2024-07-15 08:09:25 +02:00
Priit Jõerüüt
999678565b Added translation using Weblate (Estonian) 2024-07-13 23:59:26 +02:00
Abhishek Soni
3454a5ca16 feat: add app store reference 2024-07-12 14:44:30 +05:30
wmbirken
63c96b4e80 Translated using Weblate (Swedish)
Currently translated at 100.0% (405 of 405 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/sv/
2024-07-11 15:09:24 +02:00
Pierre Couy
003fec5f83 Add Date header to sent e-mails 2024-06-29 15:31:25 +02:00
Gian Andrea Casavecchia
f0d8f0ad8e Translated using Weblate (Italian)
Currently translated at 78.5% (318 of 405 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/it/
2024-06-26 10:09:27 +00:00
Nogweii
20cca8e888 update go.sum 2024-06-25 22:59:17 -07:00
Nogweii
49a548252c teach ntfy webpush to write the keys to a file 2024-06-25 22:58:36 -07:00
Shlomo
21dbcf65dc macrodroid supports POST requests 2024-06-24 23:50:56 +03:00
Sam
dee213d90c improve wording 2024-06-16 22:51:35 +02:00
Sam
19b99e8285 better wording 2024-06-16 22:50:08 +02:00
Sam
0c68b6a2c7 Update integrations.md 2024-06-16 22:47:41 +02:00
Sam
76b753062d fix url 2024-06-16 22:47:07 +02:00
Sam
ceec0bc71d Update integrations.md 2024-06-16 22:44:56 +02:00
aaron.frost@gmx.de
6ecd96cf6e Translated using Weblate (German)
Currently translated at 94.8% (384 of 405 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/de/
2024-06-14 15:09:15 +00:00
waclaw66
8d38672baf Translated using Weblate (Czech)
Currently translated at 100.0% (405 of 405 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/cs/
2024-06-06 08:09:13 +02:00
Aiman Jalil
36a149dd7a Translated using Weblate (Malay)
Currently translated at 46.4% (188 of 405 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/ms/
2024-06-04 05:58:08 +02:00
Aiman Jalil
1249d9473a Translated using Weblate (Malay)
Currently translated at 24.4% (99 of 405 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/ms/
2024-06-02 22:09:21 +02:00
Ed
5941a8f2a6 Translated using Weblate (Portuguese)
Currently translated at 71.8% (291 of 405 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/pt/
2024-06-02 22:09:20 +02:00
Aiman Jalil
2e8daa962c Added translation using Weblate (Malay) 2024-06-02 12:24:59 +02:00
Ed
3f4d0ef3ea Translated using Weblate (Portuguese)
Currently translated at 59.0% (239 of 405 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/pt/
2024-06-01 21:21:59 +02:00
1024mb
0fba690d02 Translated using Weblate (Spanish)
Currently translated at 100.0% (405 of 405 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/es/
2024-06-01 21:21:58 +02:00
stendler
5211d06f2c feat(server): add Cache and Firebase as keys to JSON publishing
https://github.com/binwiederhier/ntfy/issues/1119
2024-05-29 21:23:06 +02:00
stendler
7121d14bfa docs: add more links 2024-05-29 13:02:11 +02:00
stendler
d5a1e38082 docs: fix description of systemd-ntfy-poweronoff 2024-05-29 12:36:55 +02:00
Guilherme Puida
3ad61c4736 Translated using Weblate (Portuguese (Brazil))
Currently translated at 79.5% (322 of 405 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/pt_BR/
2024-05-28 05:09:33 +02:00
binwiederhier
9d3fc20e58 Thank you @madchr1st for your generous donation! 2024-05-18 12:19:33 -04:00
binwiederhier
0be467f809 Thank you @0x45796164 for your sponsorship 2024-05-13 20:46:37 -04:00
binwiederhier
ec75ce0787 Thank you @herzkerl for your sponsorship 2024-05-13 20:41:56 -04:00
binwiederhier
d11b1007ef Bump 2024-05-13 16:11:29 -04:00
binwiederhier
c542dd8c6f Merge branch 'main' of https://hosted.weblate.org/git/ntfy/web 2024-05-13 16:09:04 -04:00
binwiederhier
37697aed27 Bump 2024-05-13 16:08:46 -04:00
bytequill
4360d157b2 Translated using Weblate (Polish)
Currently translated at 98.7% (400 of 405 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/pl/
2024-05-12 13:29:15 +02:00
Samuel
c3c4d65f99 Translated using Weblate (Portuguese (Brazil))
Currently translated at 78.2% (317 of 405 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/pt_BR/
2024-05-08 16:07:19 +02:00
binwiederhier
ffd7645c0b Releases 2024-05-07 21:24:39 -04:00
Philipp C. Heckel
043738a475 Merge pull request #1098 from wunter8/patch-6
don't cache config.js
2024-05-07 21:21:39 -04:00
binwiederhier
fb52ad6fdb Thank you @arnbrhm for your donation 2024-05-07 21:20:58 -04:00
binwiederhier
29318f9d61 Merge branch 'main' of https://hosted.weblate.org/git/ntfy/web 2024-05-07 21:18:14 -04:00
binwiederhier
030f7266f7 Do not set rate visitor for non-eligible topics 2024-05-07 21:17:51 -04:00
ButterflyOfFire
9692de1469 Translated using Weblate (Arabic)
Currently translated at 88.1% (357 of 405 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/ar/
2024-05-05 21:08:13 +02:00
Mohammad Habib
eab90a0275 Translated using Weblate (Arabic)
Currently translated at 85.9% (348 of 405 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/ar/
2024-05-03 19:07:20 +02:00
Isaac Alves
e6f70f8e41 Translated using Weblate (Portuguese (Brazil))
Currently translated at 76.2% (309 of 405 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/pt_BR/
2024-04-26 21:07:29 +02:00
wunter8
499b0dd839 don't cache config.js 2024-04-25 16:09:27 -06:00
Philipp C. Heckel
31d0c812ce Merge pull request #1082 from wunter8/fix-username-regex
Fix username regex
2024-04-24 16:03:25 -04:00
Philipp C. Heckel
d37f861f6b Merge pull request #1086 from ibacalu/patch-1
docs(integrations): add proxmox-ntfy
2024-04-24 15:58:56 -04:00
binwiederhier
0a49a8d88b Thank you for your donation @Riolku 2024-04-24 14:55:51 -04:00
binwiederhier
b63ef0defb Thank you @rubund for your sponsorship 2024-04-24 14:55:18 -04:00
binwiederhier
f8068ef561 Thank you @IMarkoMC for your sponsorship 2024-04-24 14:54:52 -04:00
binwiederhier
2608687e98 Thank you @spudooli for your donation 2024-04-24 14:54:25 -04:00
binwiederhier
02564a40c7 Thank you @PTR-inc for your sponsorship 2024-04-24 14:53:45 -04:00
binwiederhier
bdd49f4e16 Thank you @afunworm for your sponsorship 2024-04-24 14:52:52 -04:00
binwiederhier
33b603def5 Re-add idx_topic to messages table 2024-04-24 14:38:05 -04:00
binwiederhier
6eff5553b5 Merge branch 'main' of github.com:binwiederhier/ntfy 2024-04-23 20:22:16 -04:00
binwiederhier
7cac03c1ec Bump 2024-04-23 20:22:09 -04:00
Iulian Bacalu
b33918f267 docs(integrations): add proxmox-ntfy
Python script that monitors Proxmox tasks and sends notifications using Ntfy.
More details [here](https://github.com/qtsone/proxmox-ntfy)
2024-04-14 12:24:55 +02:00
Jeroen Bulters
f68ad6acdf Translated using Weblate (Dutch)
Currently translated at 100.0% (405 of 405 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/nl/
2024-04-12 11:02:04 +02:00
Alexey Smirnov
a533bf9efb Translated using Weblate (Russian)
Currently translated at 100.0% (405 of 405 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/ru/
2024-04-12 11:02:04 +02:00
Mohammad Parvin
66ea805cde Translated using Weblate (Persian)
Currently translated at 8.3% (34 of 405 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/fa/
2024-04-10 11:01:49 +02:00
Mohammad Parvin
7c3b6e4521 Added translation using Weblate (Persian) 2024-04-09 10:33:06 +02:00
Alexander Chekalin
9cb3d056fe Translated using Weblate (Russian)
Currently translated at 97.0% (393 of 405 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/ru/
2024-04-08 12:02:14 +02:00
Hunter Kehoe
4111bee0c4 fix linting issue 2024-04-07 21:40:24 -06:00
Hunter Kehoe
e4c2b938d3 clean up invalid username code 2024-04-05 08:43:56 -06:00
Hunter Kehoe
fc7cf5933f fix error message for invalid username/password 2024-04-03 21:58:43 -06:00
Hunter Kehoe
e4d22ebd8b allow + in usernames 2024-04-03 21:58:29 -06:00
Fredrik
69d6e0f890 Translated using Weblate (Swedish)
Currently translated at 96.0% (389 of 405 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/sv/
2024-04-01 03:02:02 +02:00
grinningmosfet
ecab7fbf65 Translated using Weblate (French)
Currently translated at 100.0% (405 of 405 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/fr/
2024-04-01 03:02:01 +02:00
Error504TimeOut
75887e4a62 Translated using Weblate (German)
Currently translated at 94.5% (383 of 405 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/de/
2024-04-01 03:02:00 +02:00
Philipp C. Heckel
130039f5c8 Merge pull request #1071 from truroshan/traccar-example-add-info
Update traccar examples
2024-03-27 12:21:24 -04:00
Roshan Rajak l byteio.ɪn
bec0d4807b Update traccar examples 2024-03-27 18:39:31 +05:30
binwiederhier
5ee62033b5 Bump 2024-03-25 17:21:23 -04:00
binwiederhier
3e02d7b0bb Bump 2024-03-25 17:20:27 -04:00
binwiederhier
290ed1124e Merge branch 'main' of https://hosted.weblate.org/git/ntfy/web 2024-03-25 17:12:04 -04:00
binwiederhier
fc62682334 Thank you @jceloria for your donation 2024-03-25 09:42:09 -04:00
binwiederhier
28404565d2 Thank you @Circenn5130 for your donation 2024-03-25 09:41:37 -04:00
binwiederhier
f8548e9d46 Thank you @tomroth04 for your donation 2024-03-25 09:41:08 -04:00
Philipp C. Heckel
d90b290cd2 Merge pull request #1066 from binwiederhier/templating-disallow
Disallow certain templates
2024-03-25 09:38:33 -04:00
binwiederhier
21c6776269 Fix linter 2024-03-25 09:34:44 -04:00
binwiederhier
7fed392e0c Disallow certain templates 2024-03-24 23:19:16 -04:00
Philipp C. Heckel
913b59b5e3 Merge pull request #1064 from binwiederhier/templating-3
Message templating
2024-03-24 14:47:32 -04:00
binwiederhier
4692ca7b7f REvert parallel tests 2024-03-24 14:36:14 -04:00
binwiederhier
af16542d02 Removed unused vars 2024-03-24 14:28:10 -04:00
binwiederhier
5511812e30 JSON templating 2024-03-24 14:21:28 -04:00
binwiederhier
547b09a7e5 docs 2024-03-23 14:22:56 -04:00
binwiederhier
b9c176ddba Tests 2024-03-22 22:01:41 -04:00
Philipp C. Heckel
f971377cbb Merge pull request #1062 from theparadox1083/patch-1
Update publish.md (PowerShell 7+ Access Token Example)
2024-03-22 21:43:00 -04:00
binwiederhier
a04f2f9c9a Bla 2024-03-22 20:45:16 -04:00
Patrick
763eafd5dd Update publish.md (PowerShell 7+ Access Token Example)
Corrected PowerShell 7+ Parameter from Authorization to Authentication
Converted Token text to SecureString Inline - token must be of type SecureString
Added comment noting the Token parameter must be a SecureString
2024-03-21 09:38:55 -04:00
binwiederhier
9247dac50d Move things, revert naming 2024-03-20 21:39:17 -04:00
binwiederhier
de65d07518 Simplify(?) templating cases 2024-03-20 21:33:54 -04:00
binwiederhier
1966f80855 Merge branch 'main' into templating 2024-03-20 20:27:12 -04:00
binwiederhier
4b2e38320d BUmp 2024-03-20 20:26:18 -04:00
wunter8
83356f565e remove debug print statement 2024-03-20 10:54:41 -06:00
Hunter Kehoe
7fd5f0b29d allow large HTTP body so long as resulting message is small 2024-03-19 21:56:55 -06:00
Hunter Kehoe
03737dbf5c update docs 2024-03-19 20:55:36 -06:00
Hunter Kehoe
867cf28080 refactor gjson parsing code 2024-03-19 20:30:24 -06:00
Hunter Kehoe
b2eb5b94bd use existing message and title fields for templates 2024-03-18 20:04:40 -06:00
Hunter Kehoe
df7d6baec5 add templating for title and message fields 2024-03-17 21:55:50 -06:00
josé m
a4f5c8dee7 Translated using Weblate (Galician)
Currently translated at 100.0% (405 of 405 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/gl/
2024-03-12 13:01:53 +01:00
binwiederhier
4c0ec3f75b Thank you @arthurgleckler for your generous donation 2024-03-10 22:47:19 -04:00
binwiederhier
12bbe9a1ae Thank you @danbartram for your sponsorship 2024-03-10 22:44:43 -04:00
Mazurky
0a589f6242 Translated using Weblate (Slovak)
Currently translated at 100.0% (405 of 405 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/sk/
2024-03-10 17:01:44 +01:00
gallegonovato
ab2dd6136e Translated using Weblate (Spanish)
Currently translated at 100.0% (405 of 405 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/es/
2024-03-10 17:01:44 +01:00
gallegonovato
4d64515e45 Translated using Weblate (Spanish)
Currently translated at 100.0% (405 of 405 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/es/
2024-03-09 16:15:59 +01:00
LiLPandemio
411597ecc2 Translated using Weblate (Spanish)
Currently translated at 100.0% (405 of 405 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/es/
2024-03-09 16:15:59 +01:00
Oğuz Ersen
1a426da913 Translated using Weblate (Turkish)
Currently translated at 100.0% (405 of 405 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/tr/
2024-03-09 16:15:59 +01:00
Philipp C. Heckel
7936c38feb Update README.md 2024-03-08 12:32:37 -05:00
binwiederhier
d0beaa900f Thank you @stannynuytkens for your sponsorship 2024-03-08 11:12:55 -05:00
Linerly
f4bf8fd9bb Translated using Weblate (Indonesian)
Currently translated at 99.7% (404 of 405 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/id/
2024-03-08 07:42:48 +01:00
109247019824
d866cb2fd9 Translated using Weblate (Bulgarian)
Currently translated at 100.0% (405 of 405 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/bg/
2024-03-08 07:42:47 +01:00
binwiederhier
0ab6171962 Blog posts 2024-03-07 23:07:07 -05:00
binwiederhier
b7c2898b9c Thank you @dav23r for your sponsorship 2024-03-07 21:24:33 -05:00
binwiederhier
d155ebb3a4 Thank you @TheTomik1 for your donation 2024-03-07 21:24:07 -05:00
binwiederhier
3d5bb92774 Thank you @Integral-Tech for your sponsorship 2024-03-07 21:23:35 -05:00
binwiederhier
a2f1a45097 Thank you @rwxd for your sponsorship 2024-03-07 21:22:59 -05:00
binwiederhier
b43fff7c7e Thank you @hermannx5 for your donation 2024-03-07 21:22:02 -05:00
binwiederhier
b186c7a324 Thank you for your donation @rxsantos 2024-03-07 21:21:15 -05:00
binwiederhier
e50b6f4075 Thank you @siebej for your donation 2024-03-07 21:19:21 -05:00
binwiederhier
e5342d5eca Thank you @brookmg for your donation 2024-03-07 21:18:55 -05:00
binwiederhier
a25fc1daaa Thank you @dwain-lab for your donation 2024-03-07 21:18:25 -05:00
binwiederhier
6e75108aa9 Thank you @vovayartsev for your sponsorship 2024-03-07 21:17:40 -05:00
binwiederhier
0735a5cb48 Thank you @FrameXX for your donation 2024-03-07 21:17:13 -05:00
binwiederhier
088aede833 Thank you @mursec for your donation 2024-03-07 21:15:58 -05:00
binwiederhier
2119954f67 Thank you @ajay-actuary for your sponsorship 2024-03-07 21:15:00 -05:00
binwiederhier
220b012ae3 Thank you @CataIana for your donation 2024-03-07 21:11:29 -05:00
binwiederhier
e1278a5e92 Thank you @tomershvueli for your donation 2024-03-07 21:10:46 -05:00
binwiederhier
0ae62d36d2 Thank you @zark0s for your sponsorship 2024-03-07 21:10:00 -05:00
binwiederhier
70729edb2b Thank you @Emiliaaah for your donation 2024-03-07 21:09:14 -05:00
binwiederhier
de2f7d3e9b Remove Lemmy 2024-03-07 21:06:46 -05:00
binwiederhier
8c69234e28 Revert Docker user fix 2024-03-07 16:59:42 -05:00
binwiederhier
a9b7c4530b Derp 2024-03-07 16:34:49 -05:00
binwiederhier
76fc015775 Release notes 2024-03-07 16:30:35 -05:00
binwiederhier
ef302d22a9 Avoid panic if user.Current() fails; add logging to "ntfy subscribe" to help figure out what's wrong 2024-03-07 15:56:20 -05:00
Philipp C. Heckel
7ab8ef73a3 Merge pull request #1044 from tripleee/patch-2
Update Danish translations
2024-03-07 15:07:04 -05:00
Philipp C. Heckel
7616b24e30 Merge pull request #1045 from tripleee/patch-3
Update Finnish translations
2024-03-07 15:06:43 -05:00
Philipp C. Heckel
05c1b264f2 Merge pull request #1046 from tripleee/patch-4
Update Norwegian translations
2024-03-07 15:04:43 -05:00
Philipp C. Heckel
35ccfdb8d8 Merge pull request #1043 from tripleee/patch-1
Update Swedish localizations: typo fixes etc
2024-03-07 15:04:01 -05:00
binwiederhier
d744204052 Release notes 2024-03-07 15:01:24 -05:00
binwiederhier
6c3aca4cd6 Merge branch 'main' into cmj2002/main 2024-03-07 14:54:57 -05:00
binwiederhier
d54db74f5a Release notes 2024-03-07 13:08:56 -05:00
binwiederhier
4e1980c2cc Merge branch 'main' into patch-1 2024-03-07 13:05:47 -05:00
Philipp C. Heckel
40f990fffe Merge pull request #1050 from binwiederhier/message-size-limit+delay-limit
Message size limit+delay limit
2024-03-07 13:04:04 -05:00
binwiederhier
8931f25ac5 Ahh 2024-03-07 13:00:39 -05:00
binwiederhier
94f60fb5b8 Lint 2024-03-07 12:53:39 -05:00
binwiederhier
01b397a31a Release notes 2024-03-07 12:50:01 -05:00
binwiederhier
f2cd1edc57 Add some helper for base-url 2024-03-07 12:44:31 -05:00
binwiederhier
243123fd7e Convert duration flags, add docs 2024-03-07 12:22:35 -05:00
binwiederhier
36b33030f3 Add message-{size|delay}-limit 2024-03-07 11:53:12 -05:00
binwiederhier
17709f2fb7 Merge branch 'main' into zhzy0077-patch-1 2024-03-07 10:38:19 -05:00
binwiederhier
a8c17c1856 Release notes 2024-03-07 10:36:56 -05:00
binwiederhier
8ac920d28c Merge branch 'main' of github.com:binwiederhier/ntfy 2024-03-07 10:36:52 -05:00
binwiederhier
239c620707 Merge branch 'main' of https://hosted.weblate.org/git/ntfy/web 2024-03-07 10:36:40 -05:00
Philipp C. Heckel
f2e600d681 Merge pull request #967 from arahja/add_non-root_user_to_containers
Added non-root user to docker images.
2024-03-07 10:33:32 -05:00
Philipp C. Heckel
6486db99fa Merge pull request #1014 from lennart-m/doc/watchtower-example
Docs: Enhance Watchtower example
2024-03-07 10:24:57 -05:00
binwiederhier
766ca05e15 Release notes 2024-03-07 10:24:19 -05:00
binwiederhier
6c41f69db7 Merge branch 'main' into Tom-Hubrecht/main 2024-03-07 10:22:39 -05:00
binwiederhier
cae696c323 Switch to non-deprecated emoji extension in mkdocs 2024-03-07 10:17:43 -05:00
Philipp C. Heckel
42dc8bc3f5 Merge pull request #1049 from binwiederhier/remove-rate-topics
Remove Rate-Topics
2024-03-07 10:17:25 -05:00
binwiederhier
aecf0a5f25 Remove Rate-Topics 2024-03-07 10:07:42 -05:00
Philipp C. Heckel
896a1b007f Merge pull request #1022 from DerRockWolf/patch-1
docs(config.md): fix UnifiedPush links
2024-03-07 08:37:30 -05:00
Philipp C. Heckel
fc5973751a Merge pull request #965 from ksurl/docs-watchtower-token
docs: add shoutrrr auth token url format
2024-03-07 08:36:25 -05:00
Philipp C. Heckel
9ca5133c76 Merge pull request #1001 from UpcraftLP/patch-1
fix apk output path in docs
2024-03-07 08:35:25 -05:00
tripleee
509f59ac3c Update Norwegian translations
* Avoid compound errors
* Typo fix
2024-03-06 19:57:18 +02:00
tripleee
9d8482a119 Update Finnish translations
* Avoid compound errors
* Typo fixes and wording tweaks
2024-03-06 19:48:13 +02:00
tripleee
34343fae02 Update Danish translations
Avoid compound errors
2024-03-06 19:32:24 +02:00
tripleee
1ff6ecf5d8 Update Swedish localizations: typo fixes etc
- Avoid compound errors
- Small wording tweaks
2024-03-06 19:23:00 +02:00
109247019824
f2f7ad8253 Translated using Weblate (Bulgarian)
Currently translated at 100.0% (382 of 382 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/bg/
2024-02-29 13:02:06 +01:00
109247019824
78de5d4866 Translated using Weblate (Bulgarian)
Currently translated at 98.1% (375 of 382 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/bg/
2024-02-27 23:57:45 +01:00
Хусниддин
9449b0b875 Translated using Weblate (Uzbek)
Currently translated at 6.5% (25 of 382 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/uz/
2024-02-25 00:02:05 +01:00
Хусниддин
b86c50c60a Added translation using Weblate (Uzbek) 2024-02-23 23:28:55 +01:00
DerRockWolf
34f90facb2 docs(config.md): fix UnifiedPush links 2024-02-09 21:13:59 +00:00
Tom Hubrecht
99a0c72d49 Remove dependence on mkdocs-simple-hooks
Since mkdocs v1.4, the hooks are a native feature
2024-02-05 13:29:55 +01:00
arahja
c4d9e397ab Merge branch 'binwiederhier:main' into add_non-root_user_to_containers 2024-02-02 10:44:16 -06:00
Lennart
3f49f51847 docs: enhance watchtower example
document usage of environment variable WATCHTOWER_NOTIFICATION_SKIP_TITLE for watchtower container so the provided title will not be overwritten
2024-01-30 17:01:36 +01:00
binwiederhier
1d2b759dc0 Bump 2024-01-21 11:27:10 -05:00
binwiederhier
8e62d2af6f Merge branch 'main' of https://hosted.weblate.org/git/ntfy/web 2024-01-21 11:24:13 -05:00
YMan84
7dfcda9306 Update publish.md with powershell snippet for uploading files 2024-01-15 19:11:55 -05:00
da
b6983e6866 add ntfy-client.service as user service 2024-01-15 22:06:46 +01:00
Up
4cfc64e528 fix docs showing wrong apk path 2024-01-14 10:25:46 +01:00
109247019824
020968961e Translated using Weblate (Bulgarian)
Currently translated at 88.2% (337 of 382 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/bg/
2024-01-05 23:06:59 +00:00
Jonne Saloranta
2cfe18c9c8 Translated using Weblate (Finnish)
Currently translated at 100.0% (382 of 382 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/fi/
2024-01-02 20:09:12 +00:00
Costin Stroie
39dc90e795 Translated using Weblate (Romanian)
Currently translated at 32.4% (124 of 382 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/ro/
2023-12-31 11:09:05 +01:00
Costin Stroie
fe4101d26c Translated using Weblate (Romanian)
Currently translated at 28.2% (108 of 382 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/ro/
2023-12-30 10:09:27 +01:00
tinect
80861d33b4 Translated using Weblate (German)
Currently translated at 100.0% (382 of 382 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/de/
2023-12-30 00:45:43 +01:00
tinect
1383b41062 Translated using Weblate (German)
Currently translated at 100.0% (382 of 382 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/de/
2023-12-29 01:09:12 +01:00
Panda
99c03b0e83 Translated using Weblate (Danish)
Currently translated at 100.0% (382 of 382 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/da/
2023-12-14 00:06:55 +01:00
Philipp C. Heckel
bb4b5d2bc8 Merge pull request #976 from drizzt/patch-1
Fix typo in it.json
2023-12-10 20:19:41 -05:00
M G
4916806298 Added translation using Weblate (Esperanto) 2023-12-10 19:26:48 +01:00
Timothy Redaelli
f350b81da4 Fix typo in it.json 2023-12-09 01:20:48 +01:00
109247019824
1e306e08c9 Translated using Weblate (Bulgarian)
Currently translated at 88.2% (337 of 382 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/bg/
2023-12-07 06:05:18 +01:00
binwiederhier
518505fa9d Thank you @beekeeb for your generous sponsorship 2023-12-04 13:27:33 -05:00
binwiederhier
1f4a0601f8 Thank you @tka85 for your generous donation! 2023-12-04 13:25:50 -05:00
binwiederhier
0347fdc192 Merge branch 'main' of https://hosted.weblate.org/git/ntfy/web 2023-12-04 13:25:07 -05:00
Piotr Icikowski
d207f3ca26 Translated using Weblate (Polish)
Currently translated at 91.8% (351 of 382 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/pl/
2023-12-03 23:08:22 +01:00
Adam Rahja
f2d6f09671 Added non-root user to docker images. This gives you the ability to run ntfy as a non-root user. resolves #966 2023-11-30 08:08:52 -06:00
ksurl
0f44d20da5 clean up 2023-11-30 00:21:11 +00:00
ksurl
aaf53d5d3f add auth token url format 2023-11-30 00:18:24 +00:00
binwiederhier
154e3945c9 Release notes 2023-11-27 21:56:22 -05:00
binwiederhier
0a82729650 Merge branch 'main' of https://hosted.weblate.org/git/ntfy/web 2023-11-27 21:56:06 -05:00
Philipp C. Heckel
5876c923a2 Merge pull request #964 from wunter8/patch-5
Update docker compose example
2023-11-27 09:01:24 -05:00
wunter8
a2b56154a7 Update docker compose example
Noticed that this wasn't consistent with the other paths
2023-11-27 06:33:33 -07:00
Cao Mingjun
4b1468cfd8 Web: support pasting images to PublishDialog 2023-11-25 07:39:20 +00:00
Cao Mingjun
00fe639a95 Remove debug logging 2023-11-25 05:22:02 +00:00
Cao Mingjun
94781c89f3 Preview local file before send 2023-11-25 03:39:26 +00:00
Philipp C. Heckel
6e2f9a5bdd Merge pull request #961 from cmj2002/main
Update docker-dev go version
2023-11-24 15:33:47 -05:00
Cao Mingjun
a3312f69fb Web: support pasting images from clipboard 2023-11-24 14:49:56 +00:00
Cao Mingjun
87bcf59bd4 Update docker-dev go version 2023-11-24 14:00:16 +00:00
Caio Facchinato
4817e33942 Translated using Weblate (Portuguese (Brazil))
Currently translated at 74.3% (284 of 382 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/pt_BR/
2023-11-24 06:02:05 +01:00
binwiederhier
141cbad5ad Thank you @ubipo for your sponsorship 2023-11-22 07:19:51 -05:00
binwiederhier
aaa4976c7d Install page 2023-11-19 16:48:19 -05:00
binwiederhier
2525ef71a9 Release notes 2023-11-19 16:42:04 -05:00
binwiederhier
75a2cb9236 A few links 2023-11-19 09:08:49 -05:00
binwiederhier
6b82cb2b7a cache file 2023-11-19 06:55:52 -05:00
binwiederhier
260e24f68b Added Docker Compose docs 2023-11-19 06:49:24 -05:00
binwiederhier
3c8fabe409 Update deps 2023-11-19 05:49:40 -05:00
binwiederhier
1c3ed3ea40 Do not allow empty passwords when creating users 2023-11-19 05:47:41 -05:00
binwiederhier
497f45e5cd Merge branch 'main' of github.com:binwiederhier/ntfy 2023-11-18 21:54:38 -05:00
binwiederhier
f9a411c307 Release notes 2023-11-18 21:54:26 -05:00
binwiederhier
7d755ce604 Add comments and another test to ACL fix 2023-11-18 21:50:01 -05:00
binwiederhier
f64dbcb6b2 Merge branch 'main' into sandman7920/main 2023-11-18 06:28:48 -05:00
Philipp C. Heckel
0b41356179 Merge pull request #949 from cyqsimon/python-pip-override
Allow overriding `python3` & `pip3` binary in Makefile
2023-11-17 05:35:56 -05:00
cyqsimon
0928d99813 Fix mkdocs call 2023-11-17 10:41:16 +08:00
cyqsimon
e724aace49 Allow overriding python3 & pip3 binary in Makefile 2023-11-17 10:13:06 +08:00
binwiederhier
b65044712b Fix linter 2023-11-16 21:04:49 -05:00
binwiederhier
22f48c5ad3 Change mod path 2023-11-16 20:54:58 -05:00
Philipp C. Heckel
1b4d55fb30 Merge pull request #941 from jhotmann/module-version-2
Append /v2 to module so version 2 can be used as a go package
2023-11-16 20:49:57 -05:00
Philipp C. Heckel
ad8b22482b Merge pull request #939 from artemislena/fix-docs-badges
Fix broken badge image links in docs
2023-11-16 20:48:11 -05:00
binwiederhier
780c149c81 Merge branch 'html-emails' 2023-11-16 09:58:51 -05:00
binwiederhier
859a4e4f79 Added another test 2023-11-16 09:58:32 -05:00
Philipp C. Heckel
d0c2b00fbf Merge pull request #693 from binwiederhier/html-emails
WIP: HTML emails
2023-11-16 09:51:45 -05:00
binwiederhier
37091f25a8 Add PR link 2023-11-16 09:51:23 -05:00
binwiederhier
7f1855ad4d It's better than nothing 2023-11-16 09:49:35 -05:00
binwiederhier
b42958de9f Merge branch 'main' of github.com:binwiederhier/ntfy into html-emails 2023-11-16 06:11:00 -05:00
binwiederhier
73eaf7b8b6 Finnish translation 2023-11-16 05:41:41 -05:00
binwiederhier
52361a1c48 Merge branch 'main' of https://hosted.weblate.org/git/ntfy/web 2023-11-16 05:32:09 -05:00
binwiederhier
9b48d674b4 Merge branch 'main' of github.com:binwiederhier/ntfy 2023-11-16 05:32:05 -05:00
binwiederhier
c0fab933a5 Build docs in venv 2023-11-16 05:31:53 -05:00
binwiederhier
df6d760844 Remove twemoji from docs 2023-11-16 05:22:56 -05:00
Philipp C. Heckel
c6b6c81c83 Merge pull request #947 from wunter8/patch-4
Add JS WebSocket example to docs
2023-11-16 05:18:30 -05:00
wunter8
c2d6e0e316 Include link to websocket example in docs 2023-11-15 09:31:34 -07:00
wunter8
5acc6ad0c4 Add JS websocket example 2023-11-15 09:28:35 -07:00
binwiederhier
a533f352b2 Bump pipelines 2023-11-15 05:55:01 -05:00
binwiederhier
262bb723d9 Thank you @TechMDW for your generous sponsorship 2023-11-15 05:44:02 -05:00
binwiederhier
a97b6de37e Merge branch 'main' of github.com:binwiederhier/ntfy 2023-11-15 05:43:22 -05:00
binwiederhier
9f738e4a85 Thank you @ralhei for your donation 2023-11-15 05:43:11 -05:00
Philipp C. Heckel
8895bd77c1 Merge pull request #944 from wunter8/patch-3
remove broken link in Integrations > Projects+scripts
2023-11-14 11:21:23 -05:00
Philipp C. Heckel
404fd4c720 Merge pull request #945 from vvanouytsel/jetspotter
docs: add jetspotter integration
2023-11-14 11:21:06 -05:00
Vincent Van Ouytsel
058de2d761 docs: add jetspotter integration 2023-11-13 20:22:13 +01:00
wunter8
16d490474d remove broken link 2023-11-13 11:19:19 -07:00
Seppo Lehtimäki
bd2088c480 Translated using Weblate (Finnish)
Currently translated at 95.8% (366 of 382 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/fi/
2023-11-13 12:03:40 +01:00
Samuele Radici
c42f6289f6 Translated using Weblate (Italian)
Currently translated at 80.8% (309 of 382 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/it/
2023-11-10 20:35:55 +01:00
Jordan Hotmann
92cfa4040b Append /v2 to module so version 2 can be used as a go package 2023-11-08 13:06:50 -07:00
artemislena
3f3af275e7 T.: Fixed broken links 2023-11-07 20:00:10 +01:00
Seppo Lehtimäki
28c653043e Translated using Weblate (Finnish)
Currently translated at 93.7% (358 of 382 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/fi/
2023-11-06 12:32:45 +01:00
binwiederhier
abe7275f0c Remove Python version check 2023-11-06 05:55:04 -05:00
binwiederhier
d4af2be7a0 Thank you @pgwiebes for your sponsorship 2023-11-06 05:49:36 -05:00
binwiederhier
8dd4c3e3c0 Bump deps 2023-11-06 05:48:47 -05:00
binwiederhier
af25f164ed Merge branch 'main' of https://hosted.weblate.org/git/ntfy/web 2023-11-06 05:32:10 -05:00
Philipp C. Heckel
64ede0f11c Merge pull request #934 from bear/fix-933_macos_client_location
Fix #933 macos client location
2023-11-06 05:16:52 -05:00
Seppo Lehtimäki
d3565c9b87 Added translation using Weblate (Finnish) 2023-11-04 19:39:29 +01:00
binwiederhier
c332c132fa Fix CI 2023-10-29 20:04:37 -04:00
binwiederhier
b3534aecda Format fixes 2023-10-29 19:57:06 -04:00
binwiederhier
8e04912201 Fixing docs links 2023-10-29 12:32:08 -04:00
Philipp C. Heckel
909f9b3d24 Merge pull request #932 from dgtlmoon/changedetection-io
Adding changedetection.io integration guide
2023-10-29 12:18:13 -04:00
binwiederhier
cad38573d7 Thank you for your donation @bahur142 2023-10-29 12:15:20 -04:00
binwiederhier
a3663e43e4 Thank you for your donation @cminter 2023-10-29 12:14:07 -04:00
binwiederhier
6d451785f0 Thank you @richardleach for your sponsorship 2023-10-29 12:13:26 -04:00
binwiederhier
7791901b2d Thank you @bear for your sponsorship. Awesome user name too! 2023-10-29 12:10:35 -04:00
Mike Taylor
2afe1fbeed Update cli.md
Add macOS client location to the options listed
2023-10-28 13:35:52 -04:00
Mike Taylor
e2097e856e Update install.md
Add macOS client.yml location to the list of locations given
2023-10-28 13:33:39 -04:00
dgtlmoon
03e7a3ea65 lower case 2023-10-28 19:08:14 +02:00
dgtlmoon
27f8cc0e52 Adding changedetection.io integration guide 2023-10-28 19:04:56 +02:00
Nikolay Zlatev
1aa82ff06a FullScenario_Default_DenyAll: add user "john" test case
Add new test for user john

The user should have:

"deny" to mytopic_deny*,
  "ro" to mytopic_ro*,
  "rw" to mytopic*,
  "ro" to the rest
2023-10-24 14:02:52 +03:00
Nikolay Zlatev
0ff1f6520a TestMigrationFrom4: move the longest rule on top 2023-10-24 14:02:04 +03:00
Nikolay Zlatev
ff2a354333 TestMigrationFrom1: move the longest rule on top 2023-10-24 14:00:46 +03:00
Nikolay Zlatev
543709336c TestManager_UserManagement: move the longest rule on top 2023-10-24 13:59:39 +03:00
Nikolay Zlatev
afd6d2e0ee Default_DenyAll: move the longest rule on top 2023-10-24 13:57:46 +03:00
Philipp C. Heckel
32efbd5823 Merge pull request #929 from TheBlusky/patch-1
doc/integrations: link to ntfy-android-builder
2023-10-23 20:21:28 -04:00
Dan Lousqui
6dbdabf9fd doc/integrations: link to ntfy-android-builder 2023-10-23 22:15:01 +02:00
Philipp C. Heckel
75d57b9f04 Merge pull request #926 from 0xFOSSMan/main
Added ntfy.fossman.de to docs/integrations.md/Alternative ntfy Servers
2023-10-23 16:05:20 -04:00
Dan Lousqui
554547b431 doc/integrations: link to ntfy-android-builder 2023-10-23 21:22:21 +02:00
FOSSMan
b811da6b83 Added ntfy.fossman.de to docs/integrations.md/Alternative ntfy Servers 2023-10-20 18:42:44 +00:00
binwiederhier
ca6bc1dcb0 Thank you @surfernv for your donation 2023-10-19 07:38:12 -04:00
binwiederhier
7c3fd42a86 Thank you @thomasskou for your donation 2023-10-19 07:33:40 -04:00
binwiederhier
04f12d1e2f Merge branch 'main' of github.com:binwiederhier/ntfy 2023-10-19 07:32:57 -04:00
binwiederhier
c6b8ea90b7 Thank you @YezGotIt for your donation 2023-10-19 07:32:39 -04:00
Philipp C. Heckel
7f8fb8d571 Merge pull request #911 from noman-land/fix-typo
fix: Remove errant word from Action buttons docs
2023-10-19 07:19:58 -04:00
Philipp C. Heckel
f8cfb084e0 Merge pull request #919 from eworm-de/routeros-scripts
doc/integrations: link RouterOS Scripts
2023-10-19 07:19:22 -04:00
binwiederhier
70b084457a Bump deps 2023-10-19 07:18:03 -04:00
binwiederhier
6c12244587 Merge branch 'main' of github.com:binwiederhier/ntfy 2023-10-19 07:15:49 -04:00
binwiederhier
e7c0365079 Changelog 2023-10-19 07:15:42 -04:00
Philipp C. Heckel
43b11de596 Merge pull request #922 from imkero/bugfix/language-with-underline
fix(i18n): correct usage of language str having underline
2023-10-19 07:13:39 -04:00
imkero
ef45ea5a50 fix(i18n): correct usage of language str having underline 2023-10-19 07:48:06 +00:00
Christian Hesse
483edb70bf doc/integrations: link RouterOS Scripts
... which has a module to send notifications to Ntfy.
2023-10-18 09:52:18 +02:00
Rhodri
7516d25bc6 Translated using Weblate (Welsh)
Currently translated at 12.0% (46 of 382 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/cy/
2023-10-17 06:06:28 +02:00
PW
2f2918bd3b Translated using Weblate (Chinese (Traditional))
Currently translated at 73.0% (279 of 382 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/zh_Hant/
2023-10-17 06:06:27 +02:00
Nikolay Zlatev
7a5572ad7c user.Manager: further improve ACL write/read order
For each user, we should test in order `THE_LONGEST_RULE`->`WRITE_PERMISSION`
2023-10-16 09:41:49 +03:00
Carlos M. Silva
73d2b3363b Translated using Weblate (Portuguese (Brazil))
Currently translated at 57.5% (220 of 382 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/pt_BR/
2023-10-14 21:01:46 +00:00
Nikolay Zlatev
5c9cebf059 user.Manager: fix ACL write, read order
This should fix "read-only access to topic *" being applied before "read-write access to topic _PREFIX_*"
Before this if we have:

ntfy access user "mytopic*" rw
ntfy access user "*" ro

read-only access rule was applied first and user couldn't write to
mytopic*
2023-10-13 15:41:17 +03:00
noman
ba0cc7fbf9 fix: Remove errant word from Action buttons docs 2023-10-08 15:58:29 -04:00
binwiederhier
b7f37138f8 Release notes 2023-10-02 11:49:27 -04:00
Philipp C. Heckel
53a451671c Merge pull request #899 from nihalgonsalves/ng/fix-safari-17-sonoma
fix(pwa): hide install prompt on macOS 14 safari
2023-10-02 11:43:36 -04:00
Philipp C. Heckel
65dff6e8e3 Merge branch 'main' into ng/fix-safari-17-sonoma 2023-10-02 11:42:35 -04:00
Philipp C. Heckel
03a2de961d Merge pull request #900 from nihalgonsalves/ng/remove-firefox-known-issue
docs: remove Firefox-Android known issue
2023-10-02 11:41:56 -04:00
Philipp C. Heckel
b94310a4cc Merge pull request #903 from ohare93/another-ios-shortcut
docs: ios shortcut
2023-10-02 11:41:31 -04:00
Philipp C. Heckel
9c594da847 Merge pull request #902 from Octelly/main
Documentation healthcheck update
2023-10-02 11:41:02 -04:00
Philipp C. Heckel
93e62de3d2 Merge pull request #907 from MaheshBabu11/main
Adding ntfy-java package to libraries built around ntfy
2023-10-02 07:40:56 -04:00
Mahesh Babu
a3efbb3466 Adding ntfy-java package to libraries built around ntfy 2023-10-02 11:27:42 +05:30
Jordan Munch O'Hare
aaf01b98d2 docs: ios shortcut 2023-09-30 16:13:52 +00:00
Octelly
af037b9d70 Update config.md
Field is "healthy", not "health"
2023-09-29 17:50:27 +02:00
Mazurky
5dafd7e4a7 Translated using Weblate (Slovak)
Currently translated at 100.0% (382 of 382 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/sk/
2023-09-29 15:00:28 +02:00
Philipp C. Heckel
e2b5f4a9fb Merge pull request #901 from Jawfish/patch-1
Fix typo "aliase" -> "alias"
2023-09-28 07:14:55 -04:00
James Fitzgerald
2e58f0db10 Fix typo "aliase" -> "alias" 2023-09-28 07:01:28 -04:00
binwiederhier
26b31acbae Thank you @dkramer95 for your donation 2023-09-27 23:19:31 -04:00
binwiederhier
66e96244ef Thank you @alexandzors for your donation 2023-09-27 23:16:24 -04:00
Nihal Gonsalves
4dc0183901 docs: remove firefox-android known issue
Closes #789

Firefox released a bug fix with v116.
2023-09-27 23:44:38 +02:00
Nihal Gonsalves
d33eded060 docs: remove Safari sound warning
iOS 17 does indeed play sounds.
2023-09-27 23:43:50 +02:00
Nihal Gonsalves
5913142389 fix: remove deprecated nodesource script 2023-09-27 23:43:50 +02:00
Nihal Gonsalves
66ef28c2e2 fix(pwa): hide install prompt on macos 14 safari 2023-09-27 23:43:50 +02:00
binwiederhier
19c30fc411 Add Alex's post in the install guide 2023-09-24 20:44:57 -04:00
binwiederhier
50bed826d0 Links links links 2023-09-24 20:36:34 -04:00
binwiederhier
b5851dd6d4 Links and blog posts 2023-09-24 18:32:03 -04:00
binwiederhier
ff1ee7d292 Merge branch 'main' of github.com:binwiederhier/ntfy 2023-09-24 18:31:57 -04:00
binwiederhier
9455428048 Merge branch 'main' of https://hosted.weblate.org/git/ntfy/web 2023-09-24 18:31:52 -04:00
Philipp C. Heckel
0f919f3d49 Merge pull request #895 from binwiederhier/cf-priority
Refined `Priority` header handling
2023-09-24 18:08:45 -04:00
binwiederhier
d556a675e9 Changelog 2023-09-24 18:04:13 -04:00
binwiederhier
bfc1fa5181 Changelog 2023-09-24 18:03:09 -04:00
binwiederhier
d9387dac99 Refine logic 2023-09-24 17:59:23 -04:00
binwiederhier
4818ee57b6 Merge branch 'main' into gusdleon/main 2023-09-24 17:30:04 -04:00
binwiederhier
addb5efebb Release notes 2023-09-24 11:48:26 -04:00
binwiederhier
8adb9ee633 Re-add tzdata to amd64 2023-09-24 11:45:35 -04:00
binwiederhier
418fc98d1a Bump deps 2023-09-24 11:05:21 -04:00
binwiederhier
beffe4a1f2 Thank you @spartan for your sponsorship 2023-09-24 11:02:59 -04:00
Jakob Malchow
ef15b44a1b Translated using Weblate (Italian)
Currently translated at 80.1% (306 of 382 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/it/
2023-09-20 22:17:49 +02:00
Andrea Guarnaccia
bc802bfc77 Translated using Weblate (Italian)
Currently translated at 80.1% (306 of 382 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/it/
2023-09-20 22:17:49 +02:00
Federico Nellen
d10a5df3df Translated using Weblate (Italian)
Currently translated at 77.4% (296 of 382 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/it/
2023-09-19 14:02:05 +00:00
109247019824
b05d27ce45 Translated using Weblate (Bulgarian)
Currently translated at 87.6% (335 of 382 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/bg/
2023-09-19 14:02:04 +00:00
Philipp C. Heckel
e61c9fdde9 Merge pull request #887 from wunter8/patch-2
Add instructions for local-only email publishing
2023-09-17 13:07:46 -04:00
wunter8
d2e2791729 Add instructions for local-only email publishing 2023-09-17 10:39:59 -06:00
Mazurky
68a7756621 Translated using Weblate (Slovak)
Currently translated at 100.0% (382 of 382 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/sk/
2023-09-10 19:15:07 +02:00
Jose Boullosa
42063cbd5c Translated using Weblate (Galician)
Currently translated at 100.0% (382 of 382 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/gl/
2023-09-10 19:15:05 +02:00
josé m
a407a2e0f8 Translated using Weblate (Galician)
Currently translated at 100.0% (382 of 382 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/gl/
2023-09-10 19:15:04 +02:00
jonnysemon
6ec1ccf7a3 Translated using Weblate (Arabic)
Currently translated at 85.8% (328 of 382 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/ar/
2023-09-10 19:15:03 +02:00
Jag_k
044f4182d0 Translated using Weblate (Russian)
Currently translated at 100.0% (382 of 382 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/ru/
2023-09-10 19:15:02 +02:00
SinecKers
bae30d79c9 Translated using Weblate (Turkish)
Currently translated at 100.0% (382 of 382 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/tr/
2023-09-10 19:15:02 +02:00
Christian Meis
25a60969fb Translated using Weblate (German)
Currently translated at 100.0% (382 of 382 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/de/
2023-09-10 19:15:02 +02:00
binwiederhier
528a67722b Thank you @LuckVintage for your sponsorship 2023-09-10 11:23:18 -04:00
binwiederhier
d29dc95962 Merge branch 'main' of github.com:binwiederhier/ntfy 2023-09-10 11:22:00 -04:00
binwiederhier
fc3d4dcf5e Merge branch 'main' of https://hosted.weblate.org/git/ntfy/web 2023-09-10 11:21:56 -04:00
Philipp C. Heckel
3d4218324f Merge pull request #862 from masterujjval/main
readme modified
2023-09-10 15:08:31 +02:00
zhzy0077
6a10bac017 Update template server.yml 2023-09-08 13:21:55 +08:00
Philipp C. Heckel
f6fbb45978 Merge pull request #867 from InvitedToHell/main
Add ios shortcut to the integrations docs
2023-09-06 01:27:40 +02:00
Helly
dee16f543d Add ios shortcut to the integrations docs 2023-09-06 00:52:46 +02:00
jonnysemon
9959d1aa43 Translated using Weblate (Arabic)
Currently translated at 85.6% (327 of 382 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/ar/
2023-09-05 19:56:20 +02:00
Mazurky
76146c4e74 Translated using Weblate (Slovak)
Currently translated at 45.8% (175 of 382 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/sk/
2023-09-04 17:52:06 +02:00
Ron
8a8023fcf8 Translated using Weblate (Chinese (Traditional))
Currently translated at 64.1% (245 of 382 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/zh_Hant/
2023-09-04 17:52:04 +02:00
Mattia
4b0d1e448d Translated using Weblate (Italian)
Currently translated at 70.6% (270 of 382 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/it/
2023-09-04 17:52:03 +02:00
Max Oliver
6748a2f2f3 Translated using Weblate (Portuguese (Brazil))
Currently translated at 57.5% (220 of 382 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/pt_BR/
2023-09-04 17:52:03 +02:00
Bastien S
4c4d772a5f Translated using Weblate (French)
Currently translated at 100.0% (382 of 382 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/fr/
2023-09-04 17:52:03 +02:00
Gustavo de León
85740d810b Fix cloudflarePriorityIgnore
- Now, only if the header being processed is the "priority" header, the cloudflarePriorityIgnore function is called, solving problems with that header injected by CF
- we make the check with regex now.
2023-09-03 18:55:57 -06:00
binwiederhier
2305ebca24 Add known issues 2023-09-03 09:33:54 -04:00
binwiederhier
59bf388534 FAQ 2023-09-03 07:13:04 -04:00
Philipp C. Heckel
3066b95a6d Merge pull request #863 from VardyNg/main
Completed translations for Traditional Chinese and Simplified Chinese
2023-09-03 12:58:41 +02:00
vardy.ng
1bd77a83bd Updated translation for Traditional and Simplified Chinese, simplified translation for "higher" 2023-09-02 17:34:44 -04:00
vardy.ng
d0b7336da7 completed Traditional Chinese Translation, aligned with Simplified Chinese and English translation 2023-09-02 17:31:07 -04:00
vardy.ng
c80f71bd9b update Simplified Chinese Translation, align with English translation by adding missing keys 2023-09-02 17:30:34 -04:00
vardy.ng
15fa3b7d9f break 中文(Chinese) into 繁體中文(Traditional Chinese, zh_Hant) and 简体中文(Simplified Chinese, zh_Hans) in language drop down 2023-09-02 15:54:29 -04:00
masterujjval
e2d7f2cf29 readme modified 2023-09-02 23:09:53 +05:30
Philipp C. Heckel
3031fb910f Merge pull request #861 from eltociear/patch-1
Update releases.md
2023-09-02 15:40:15 +02:00
Ikko Eltociear Ashimine
d999dbe0a0 Update releases.md
suport -> support
2023-09-02 22:29:45 +09:00
binwiederhier
60d5e66e34 Integration links 2023-09-01 15:36:12 -04:00
binwiederhier
c6964502c4 Thank you @teomarcdhio and @MarcMichalsky for your donation/sponsorship 2023-09-01 15:31:52 -04:00
binwiederhier
ca2633ff82 Merge branch 'main' of https://hosted.weblate.org/git/ntfy/web 2023-09-01 15:25:42 -04:00
Mazurky
a1625c7f15 Added translation using Weblate (Slovak) 2023-08-31 22:13:26 +02:00
Gustavo de León
30a913c05c Ignore Cloudflare Priority header
With these changes, If the web request contains the new Priority header (RFC 9218), The server will ignore it and continue searching for other headers or query parameters.
2023-08-28 23:20:04 -06:00
josé m
1d02933481 Translated using Weblate (Galician)
Currently translated at 42.4% (162 of 382 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/gl/
2023-08-29 06:51:00 +02:00
josé m
62c2ec0614 Translated using Weblate (Galician)
Currently translated at 33.2% (127 of 382 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/gl/
2023-08-27 14:54:40 +02:00
binwiederhier
45ca20dec9 Docs 2023-08-26 09:26:54 +02:00
binwiederhier
de362d2322 Merge branch 'main' of https://hosted.weblate.org/git/ntfy/web 2023-08-26 09:19:16 +02:00
Markus
115e6e9cf8 Translated using Weblate (Norwegian Bokmål)
Currently translated at 51.5% (197 of 382 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/nb_NO/
2023-08-25 20:54:02 +02:00
Philipp C. Heckel
f17538b7df Merge pull request #848 from falkheiland/main
Update publish.md
2023-08-22 11:40:38 +02:00
falkheiland
6f68c8cd1f Update publish.md
fixed PowerShell examples
2023-08-22 11:24:35 +02:00
zhzy0077
f565302a0f Fix typo 2023-08-21 11:56:31 +08:00
zhzy0077
6a3f169a47 fix typo. 2023-08-21 11:55:20 +08:00
zhzy0077
6bd8875375 fix typo and update docs 2023-08-21 11:51:48 +08:00
Nguyen Loc
02dd72ba57 Translated using Weblate (Vietnamese)
Currently translated at 4.9% (19 of 382 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/vi/
2023-08-19 06:28:28 +02:00
Philipp C. Heckel
63629efae7 Merge pull request #843 from binwiederhier/acl-underscores
Fix ACL issues with underscores
2023-08-18 22:52:01 +02:00
binwiederhier
9015b27803 Release notes 2023-08-18 22:47:36 +02:00
binwiederhier
a5f0670f7f ACLs and underscores, resolves #840 2023-08-18 22:44:52 +02:00
binwiederhier
d7db395016 Release note details 2023-08-17 23:06:52 +02:00
binwiederhier
99eef493d2 Thank you @spirossi for your sponsorship 2023-08-17 22:23:06 +02:00
binwiederhier
0d395249ff Thank you @eenturk for your donation 2023-08-17 22:21:06 +02:00
binwiederhier
5cf1da974a Thank you @skorokithakis for your donation 2023-08-17 22:20:29 +02:00
binwiederhier
2f0ec88f40 Release bump 2023-08-17 22:17:07 +02:00
binwiederhier
d9d3c4a724 Merge branch 'main' of https://hosted.weblate.org/git/ntfy/web 2023-08-17 22:16:31 +02:00
binwiederhier
bc4d4f424a Pin go-smtp v0.17.0 2023-08-17 22:05:51 +02:00
binwiederhier
67459650d4 Release notes 2023-08-17 21:59:24 +02:00
binwiederhier
c31bce1e2d Merge branch 'main' of github.com:binwiederhier/ntfy 2023-08-17 21:43:05 +02:00
binwiederhier
3e3b556108 Fix excess token deletion bug 2023-08-17 21:42:40 +02:00
Philipp C. Heckel
723daf9497 Merge pull request #819 from nisbet-hubbard/patch-1
Tweak httpd config to use less resources
2023-08-17 21:37:00 +02:00
Erik S
f77958fc35 Translated using Weblate (Russian)
Currently translated at 95.5% (365 of 382 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/ru/
2023-08-16 06:48:49 +02:00
CaptB
ea9f2c6e35 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (382 of 382 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/zh_Hans/
2023-08-11 12:51:15 +02:00
Zhiyuan Zheng
3691e59af1 Expose MessageLimit as a configuration 2023-08-11 13:16:53 +08:00
Philipp C. Heckel
7d20238423 Merge pull request #820 from nihalgonsalves/ng/display-external-images
fix: check extension to display external images
2023-08-08 20:47:43 -04:00
Philipp C. Heckel
31131db756 Merge pull request #834 from wunter8/829-empty-userpass-override
fixes #829
2023-08-08 20:44:32 -04:00
Shjosan
17e634c563 Translated using Weblate (Swedish)
Currently translated at 100.0% (382 of 382 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/sv/
2023-08-08 11:50:23 +02:00
wunter8
a7dc3d84e0 fix typo 2023-08-07 22:59:24 -06:00
Hunter Kehoe
b80aec90d0 fixes #829 2023-08-06 22:44:55 -06:00
Nguyen Loc
8544733048 Added translation using Weblate (Vietnamese) 2023-08-07 03:58:56 +02:00
binwiederhier
140fdcca81 Thank you @bmcgonag for your sponsorship 2023-08-05 21:13:48 -04:00
Christian Meis
2e08c48742 Translated using Weblate (German)
Currently translated at 100.0% (382 of 382 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/de/
2023-08-04 17:07:34 +02:00
binwiederhier
4800bb05d2 Thank you @darkmattercoder for your donation 2023-08-03 10:49:04 -04:00
Nihal Gonsalves
384cabede5 feat: check extension to display external images 2023-07-14 13:10:24 +02:00
nisbet-hubbard
4e9eeb1fa1 Add missing note on log file permissions 2023-07-12 20:24:57 +08:00
nisbet-hubbard
a534cc9eca Add server.yml ex. when using proxy
This would help inexperienced sysadmins who may not realise that since TLS terminates at proxy, ntfy is actually listening on a TCP socket that’s using http rather than https.
2023-07-12 20:00:48 +08:00
nisbet-hubbard
e52132c85b Use mod_alias for redirection
It’s a less resource-intensive alternative to mod_rewrite.
2023-07-12 19:48:51 +08:00
nisbet-hubbard
76667ffcf9 Use mod_proxy_http for websocket upgrade
mod_proxy_wstunnel is deprecated as of httpd 2.4.47. It also uses more resources since it relies on mod_rewrite.

See https://httpd.apache.org/docs/2.4/mod/mod_proxy.html#protoupgrade.
2023-07-12 18:18:48 +08:00
binwiederhier
8ba4b72b37 Changelog 2023-07-11 19:46:10 -04:00
Philipp C. Heckel
81e1417ce5 Merge pull request #817 from nihalgonsalves/ng/fix-web-push-i18n
fix(web-push): re-init i18n on each sw message
2023-07-11 19:43:12 -04:00
binwiederhier
c1576b5b19 Blog posts 2023-07-11 09:34:47 -04:00
Nihal Gonsalves
86cc3b9607 chore(build): bump Dockerfile-build go version 2023-07-10 20:14:29 +02:00
Nihal Gonsalves
c7f85e6283 fix(web-push): re-init i18n on each sw message 2023-07-10 20:10:45 +02:00
binwiederhier
6a93dc9d54 Bump packages 2023-07-09 07:51:33 -04:00
binwiederhier
dfd08b337c Merge branch 'main' of https://hosted.weblate.org/git/ntfy/web 2023-07-09 07:50:34 -04:00
binwiederhier
2d1f2f319f Changelog, CLI fix 2023-07-09 07:50:00 -04:00
binwiederhier
68f82b9182 Fix wording in tests 2023-07-09 07:36:36 -04:00
Nihal Gonsalves
c8f880c701 Web app: add a “publish as markdown” option 2023-07-09 10:28:07 +02:00
binwiederhier
f2d3f0bdf9 Remove underlines 2023-07-08 22:28:41 -04:00
binwiederhier
9f8c63c7d5 Docs etc 2023-07-08 21:54:54 -04:00
binwiederhier
2b5a1a7a1c Documentation 2023-07-08 21:45:03 -04:00
binwiederhier
499b2fb0d6 Docs, tests 2023-07-08 15:48:08 -04:00
binwiederhier
b7679c7826 Remove setting, add persistence 2023-07-08 15:14:35 -04:00
binwiederhier
ce01a66ff3 Merge remote-tracking branch 'nihalgonsalves/ng/markdown' into markdown 2023-07-07 20:53:15 -04:00
binwiederhier
7582be1a39 Merge branch 'main' into markdown 2023-07-07 20:52:31 -04:00
Nihal Gonsalves
f989fd0743 Web app: implement markdown support 2023-07-06 20:25:20 +02:00
Philipp C. Heckel
097e84aeed Merge pull request #811 from bleetube/ansible_role_ntfy
Add new integration ansible-role-ntfy-alertmanager
2023-07-05 20:43:56 -04:00
Brian Lee
faadb5148f Add new integration ansible-role-ntfy-alertmanager 2023-07-05 14:50:01 -07:00
109247019824
8d9fa31f3d Translated using Weblate (Bulgarian)
Currently translated at 83.7% (320 of 382 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/bg/
2023-07-05 22:52:48 +02:00
binwiederhier
56ed4f0515 Blog post 2023-07-05 08:45:26 -04:00
binwiederhier
43981bb675 Merge branch 'main' into markdown 2023-07-04 21:15:08 -04:00
binwiederhier
cd38511ad4 Update deps 2023-07-04 20:52:39 -04:00
binwiederhier
53f13fd811 FAQ 2023-07-04 20:47:19 -04:00
binwiederhier
77cc52e4ac Remove email 2023-07-04 20:11:45 -04:00
binwiederhier
35cb4606f6 FAQ 2023-07-04 20:10:17 -04:00
binwiederhier
d01ed355e0 Changelog 2023-07-04 14:23:44 -04:00
Philipp C. Heckel
495fb24b9a Merge pull request #804 from nimbleghost/rtl
Web app: add RTL support
2023-07-04 14:20:24 -04:00
waclaw66
911fe9e9f8 Translated using Weblate (Czech)
Currently translated at 100.0% (382 of 382 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/cs/
2023-07-04 09:52:38 +02:00
nimbleghost
311ffc3672 Format datetimes using i18n lang 2023-07-03 15:24:26 +02:00
nimbleghost
7a1488fcd3 Web app: add RTL support
Ref:

https://mui.com/material-ui/guides/right-to-left
https://m2.material.io/design/usability/bidirectionality.html
2023-07-03 15:24:26 +02:00
Nicola Rizzo
9f255aee25 Translated using Weblate (Italian)
Currently translated at 70.4% (269 of 382 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/it/
2023-07-02 16:52:40 +02:00
Nicola Rizzo
67603e58bf Translated using Weblate (Italian)
Currently translated at 70.1% (268 of 382 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/it/
2023-07-01 15:53:21 +02:00
binwiederhier
4267c0d9b6 Update docs 2023-06-30 21:54:27 -04:00
binwiederhier
88eb728fe3 Changelog 2023-06-30 21:51:03 -04:00
binwiederhier
26c835cdd1 Install notes, background change for xs dark mode drawer 2023-06-30 09:58:56 -04:00
binwiederhier
7d3d697a20 Fix goreleaser 2023-06-30 09:30:36 -04:00
binwiederhier
798ee3c23c Merge branch 'main' of github.com:binwiederhier/ntfy 2023-06-30 08:45:44 -04:00
binwiederhier
7581058c93 Bump Go version in pipelines 2023-06-30 08:45:28 -04:00
Philipp C. Heckel
4f0ddfc30d Merge pull request #795 from nimbleghost/pwa-improvements
PWA: Fix reload, Firefox mp3 load, reduce mobile padding
2023-06-30 08:43:19 -04:00
nimbleghost
0b918464c1 Move registerSW out 2023-06-30 08:59:31 +02:00
nimbleghost
57bd37ef2f Fix sidebar colour on mobile 2023-06-29 15:22:59 +02:00
nimbleghost
9fa1288dbc Fix update behaviour 2023-06-29 15:07:18 +02:00
nimbleghost
55eed868fa Reduce padding on mobile / narrow screens 2023-06-29 13:15:06 +02:00
nimbleghost
abb1baeecd Don’t include mp3 due to Firefox sw issue 2023-06-29 13:15:06 +02:00
binwiederhier
5784b07f14 Bump 2023-06-28 20:23:10 -04:00
binwiederhier
8e1e0b3740 Overflow auto 2023-06-28 20:17:49 -04:00
binwiederhier
3f42e0e945 Merge branch 'main' into fix-permission-handling 2023-06-28 20:05:26 -04:00
binwiederhier
9146e439d2 Merge branch 'main' of github.com:binwiederhier/ntfy 2023-06-28 20:03:36 -04:00
binwiederhier
7a14a0b81f Merge branch 'main' of https://hosted.weblate.org/git/ntfy/web 2023-06-28 20:03:32 -04:00
Philipp C. Heckel
9247475ab2 Merge pull request #793 from nimbleghost/pwa-action-bar
Make action bar match theme colour when run as PWA
2023-06-28 19:58:41 -04:00
nimbleghost
6b4c04c390 Make action bar match theme colour when run as PWA 2023-06-29 00:22:58 +02:00
nimbleghost
e8216ae9e7 Fix resubscribing when notifications are re-granted
(case: from denied to granted)
2023-06-29 00:02:18 +02:00
nimbleghost
365a0b2832 Fix preferences warnings 2023-06-28 23:38:57 +02:00
Philipp C. Heckel
f78389b6ef Merge pull request #792 from nimbleghost/fix-ntfy-banner
Fix ntfy upgrade banner in dark mode
2023-06-28 15:53:54 -04:00
nimbleghost
0d231d8bd9 Fix snackbars in dark mode 2023-06-28 21:18:04 +02:00
nimbleghost
d838790b8f Fix ntfy upgrade banner in dark mode 2023-06-28 20:43:42 +02:00
nimbleghost
9ce3545901 Fix refreshing things when permission is granted
We refreshed some things but not everything, this makes it more
responsive if you have the settings page open when granting permissions,
for example.
2023-06-28 20:26:54 +02:00
binwiederhier
64ac111d55 Rename UI_MODE to THEME 2023-06-28 13:30:51 -04:00
binwiederhier
e9f170a197 Merge branch 'main' into dark-mode 2023-06-28 13:03:24 -04:00
binwiederhier
e359499e79 Bump install.md 2023-06-28 12:59:30 -04:00
binwiederhier
48a5a55e2f Release notes 2023-06-28 12:54:13 -04:00
nimbleghost
4828e3a691 Add preference 2023-06-28 17:39:44 +02:00
nimbleghost
e607944ad1 Update colors 2023-06-28 17:20:01 +02:00
binwiederhier
d790ad91e2 Bump 2023-06-28 10:58:52 -04:00
nimbleghost
4f39c7c155 Implement dark mode
Resolves #206
2023-06-28 16:52:35 +02:00
怪盗kidou
8db569e8a5 Translated using Weblate (Chinese (Simplified))
Currently translated at 94.5% (361 of 382 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/zh_Hans/
2023-06-28 16:52:23 +02:00
binwiederhier
f3932e4b65 Bump deps 2023-06-28 10:38:11 -04:00
Philipp C. Heckel
d40b776205 Merge pull request #788 from nimbleghost/clarify-web-push-other-server
Clarify that web push is not supported on other servers
2023-06-28 10:07:43 -04:00
nimbleghost
9dbac2cb33 Update wording 2023-06-28 15:53:30 +02:00
nimbleghost
9216dbe28a Add Safari IndexedDB known issue 2023-06-28 10:38:02 +02:00
nimbleghost
95cfe16676 Add background notif text to subscribe dialog
only when web push is enabled
2023-06-28 08:57:57 +02:00
nimbleghost
dabb6a481f Add server name to background notification setting 2023-06-28 08:44:05 +02:00
Philipp C. Heckel
d294a692d2 Merge pull request #786 from nimbleghost/web-notif-compat-docs
Add docs detailing web notification support
2023-06-27 20:44:26 -04:00
nimbleghost
0266c707cc Add docs detailing web notification support 2023-06-27 08:28:33 +02:00
Philipp C. Heckel
0b3e268f2c Merge pull request #784 from nimbleghost/pref-responsive
Web app UI: make preferences responsive
2023-06-26 20:45:07 -04:00
binwiederhier
12df164245 Formatting 2023-06-26 20:38:18 -04:00
nimbleghost
d51ca20992 Use dvh for main height
This takes into account browser UI for the viewport calculation
2023-06-26 23:36:04 +02:00
nimbleghost
4a1adaeab2 Make login and sign up form responsive 2023-06-26 23:34:22 +02:00
nimbleghost
fd5bfd161d Web app UI: make preferences responsive 2023-06-26 23:19:58 +02:00
nimbleghost
0c496ca223 Fix iOS prompt 2023-06-26 21:49:53 +02:00
nimbleghost
175ab5ea76 Fix: refresh web push pref on standalone change 2023-06-26 08:56:07 +02:00
binwiederhier
5627097a6c Remove WebPush.js, move to hooks.js; add docblocks 2023-06-25 21:46:26 -04:00
binwiederhier
94fb23ba17 Style changes 2023-06-25 21:10:25 -04:00
binwiederhier
dbd8ed14bf Merge branch 'main' into refresh-pwa-state 2023-06-25 19:24:51 -04:00
binwiederhier
789078e916 Deps 2023-06-25 19:24:22 -04:00
nimbleghost
833293ad77 Set PWA web push enabled on launch instead 2023-06-25 22:00:45 +02:00
nimbleghost
a8d3297c4e Correctly handle standalone (PWA) mode changes
- Also handle notification permission changes
- Remove web push schedule worker since this complicates
  things and doesn’t do _that_ much. We have the reminder
  notification if the user truly doesn’t reload ntfy in
  more than a week.
2023-06-25 21:25:52 +02:00
binwiederhier
532fd3c560 Merge branch 'main' of github.com:binwiederhier/ntfy 2023-06-25 12:58:30 -04:00
binwiederhier
0c937d02df Bump deps 2023-06-25 12:58:18 -04:00
Philipp C. Heckel
8a800a4cb2 Merge pull request #780 from nimbleghost/docs-fix
Update PWA docs
2023-06-25 12:23:59 -04:00
nimbleghost
8f6f97b8e4 Update PWA docs
Clarify that macOS PWA support is limited to Safari on
macOS 14 (releasing Q4 2023).
2023-06-25 17:51:52 +02:00
binwiederhier
79df1c9040 Words 2023-06-25 09:48:12 -04:00
binwiederhier
9a71c3d8dc Shrink images 2023-06-25 09:47:09 -04:00
binwiederhier
74788893e9 Add Safari images 2023-06-25 09:43:51 -04:00
binwiederhier
5c0ecc0250 Cont'd docs 2023-06-24 22:40:40 -04:00
binwiederhier
c0ac2c95ca Cont'd docs 2023-06-24 22:08:57 -04:00
nimbleghost
be4c80e201 Improve web push docs 2023-06-24 20:35:59 +02:00
Philipp C. Heckel
32a110b601 Merge pull request #778 from nimbleghost/pwa-defaults
Default installed PWA to web push enabled
2023-06-24 14:25:22 -04:00
nimbleghost
48d1f7887d Default web push to enabled on PWAs 2023-06-24 20:22:34 +02:00
nimbleghost
dd02267f9b Disable PWA (manifest) when web push is disabled 2023-06-24 20:11:10 +02:00
binwiederhier
142a297552 typo 2023-06-24 14:10:33 -04:00
binwiederhier
9aeea4d9fa Fix text 2023-06-24 13:45:29 -04:00
binwiederhier
e8ecd6b006 Merge branch 'main' of https://hosted.weblate.org/git/ntfy/web 2023-06-24 13:34:21 -04:00
ayuyydev
71b961d3f3 Translated using Weblate (Chinese (Traditional))
Currently translated at 57.0% (218 of 382 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/zh_Hant/
2023-06-24 16:51:13 +02:00
binwiederhier
271056a4aa The last commit 2023-06-20 21:46:09 -04:00
binwiederhier
141565d9d2 Merge branch 'main' into pwa 2023-06-20 21:23:42 -04:00
binwiederhier
c400c5571f Merge branch 'main' of https://hosted.weblate.org/git/ntfy/web 2023-06-20 21:23:34 -04:00
binwiederhier
d266579be1 Merge branch 'main' of https://hosted.weblate.org/git/ntfy/web into pwa 2023-06-20 21:22:35 -04:00
Oğuz Ersen
f61c67e6be Translated using Weblate (Turkish)
Currently translated at 100.0% (382 of 382 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/tr/
2023-06-20 19:49:30 +02:00
nimbleghost
5f6d753cb7 Remove navigation fallback for all except app root 2023-06-19 21:45:55 +02:00
nimbleghost
8211b4cc24 Fix: add v1 to navigation fallback denylist
This is required for the Stripe redirection flow
2023-06-19 21:28:40 +02:00
nimbleghost
000a3e005c Improve dynamic webmanifest setup 2023-06-19 20:41:41 +02:00
nimbleghost
d7aacb8b24 Fix PWA for non-root web roots 2023-06-19 10:58:15 +02:00
binwiederhier
6615aea5dc Fix grant button in language files 2023-06-18 20:29:08 -04:00
binwiederhier
27a4e58fb1 Merge branch 'main' into pwa 2023-06-18 20:24:47 -04:00
binwiederhier
4c7dc4c1ba Merge branch 'main' of https://hosted.weblate.org/git/ntfy/web 2023-06-18 20:24:20 -04:00
nimbleghost
5ce78660cf Doc fixes (2) 2023-06-18 22:42:19 +02:00
nimbleghost
89f5cc577e Doc fixes 2023-06-18 21:17:49 +02:00
binwiederhier
dc7dd836c6 web-push-startup-queries 2023-06-18 14:20:22 -04:00
binwiederhier
88c6b4adae Rename web-push-subscriptions-file to web-push-file 2023-06-17 21:57:47 -04:00
binwiederhier
020996ea04 Minor changes 2023-06-17 21:51:04 -04:00
nimbleghost
30a8f66db2 Reorder start/stopWorkers 2023-06-17 22:32:24 +02:00
nimbleghost
9ba733d4e0 Add a reload button to error boundary
There are sometimes edge cases on iOS which cause the app to crash,
it’s good to have a reload button as there’s no browser chrome (reload,
back, forward) in an iOS standalone PWA.
2023-06-17 22:15:02 +02:00
nimbleghost
fafe478e5c Sync localStorage to indexedDB on startup 2023-06-17 22:08:25 +02:00
nimbleghost
b7bb4459f9 Check for image mimetype first
URL heuristic is the second check if there is no mime
2023-06-17 21:53:45 +02:00
nimbleghost
3cd61d8278 Add web push delete test 2023-06-17 21:44:21 +02:00
nimbleghost
2d45e397a7 Add disabled web push test 2023-06-17 21:40:08 +02:00
binwiederhier
ff7e894e4c Add more tests, change endpoint 2023-06-17 14:44:55 -04:00
Philipp C. Heckel
7db25d71dd Merge pull request #774 from skittlesvampir/patch-1
Update Docker-Compose Version in install.md
2023-06-17 07:54:57 -04:00
skittlesvampir
2283cc4ce6 Update Docker-Compose Version in install.md
According to https://docs.docker.com/compose/compose-file/compose-file-v2/#healthcheck, 'start_period' is only supported since version 2.3
2023-06-17 12:19:51 +02:00
binwiederhier
341e84f643 Limit number of webpush subscriptions per subscriber IP 2023-06-16 21:59:07 -04:00
binwiederhier
c43a1166e2 Docs, mostly 2023-06-16 16:55:42 -04:00
binwiederhier
6e95d62726 Cosmetic changess 2023-06-15 22:25:05 -04:00
nimbleghost
b197ea3ab6 Use the same notification pipeline everywhere
This means less duplication and `actions` support for all
notifications.
2023-06-15 00:43:18 +02:00
nimbleghost
fa418eef16 Update develop.md sw docs
turns out http://localhost runs service workers just fine on all desktop
browsers 🤷
2023-06-15 00:42:26 +02:00
nimbleghost
83eb4c39e5 Add i18n to service worker 2023-06-14 20:58:58 +02:00
nimbleghost
2dcad150eb Add missing await 2023-06-14 20:58:24 +02:00
nimbleghost
eebe4f8920 Refactor and document sw.js file 2023-06-14 20:47:56 +02:00
binwiederhier
4dc89f6bc5 Tiny fixes 2023-06-14 13:31:34 -04:00
binwiederhier
9403873a7b Re-increate Dexie version number 2023-06-14 13:08:35 -04:00
binwiederhier
ad36f5db46 Merge branch 'main' into pwa 2023-06-14 11:20:49 -04:00
binwiederhier
e96e35b40b Newly created access tokens are now lowercase only 2023-06-14 11:20:29 -04:00
binwiederhier
aeb60735dc Wording 2023-06-14 11:02:54 -04:00
nimbleghost
67948d0767 Remove stray console.log 2023-06-14 14:52:09 +02:00
nimbleghost
e2120bc66d Improve WebPushEnabled conditional display 2023-06-14 14:33:35 +02:00
nimbleghost
67b9d2eaf6 Add missing await 2023-06-14 14:31:34 +02:00
binwiederhier
7083ed9f6b Move websocketSubscriptions to useConnectionListeners 2023-06-14 08:18:55 -04:00
binwiederhier
790fd43369 Tiny changes 2023-06-14 08:04:16 -04:00
binwiederhier
6b38499bdc Revert alert text and button, and warning 2023-06-13 22:03:00 -04:00
binwiederhier
cf050cc289 Merge branch 'pwa' of github.com:nimbleghost/ntfy into pwa 2023-06-13 21:54:23 -04:00
nimbleghost
390d42c607 Format & fix lint 2023-06-13 14:02:54 +02:00
nimbleghost
8ccfa5c3fb Fix session replica behaviour (merge with session)
The harder-to-refactor parts are the places where exists/username/token
are called within a React component. However, `resetAndRedirect` and
`store` are already called from async contexts, so adding an `await`
is simple.

This thus merges the logic, keeping localStorage for the components to
call, but making sure reset/store behaviour works correctly for the
replica.
2023-06-13 14:00:51 +02:00
Laur
8073bb4e24 Translated using Weblate (Romanian)
Currently translated at 26.7% (102 of 382 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/ro/
2023-06-13 11:52:47 +02:00
binwiederhier
9e19183471 Merge branch 'main' into pwa 2023-06-12 21:13:16 -04:00
binwiederhier
ae3e8a0094 Blog post 2023-06-12 21:13:05 -04:00
binwiederhier
2d0c043dfd Derp 2023-06-12 21:03:49 -04:00
binwiederhier
a8def0aed2 Make allowed endpoints a list of patterns 2023-06-12 21:01:43 -04:00
binwiederhier
4e44b034bd Merge branch 'main' of github.com:binwiederhier/ntfy into pwa 2023-06-12 19:38:17 -04:00
binwiederhier
e6c83b6efb Add Lemmy, etc. 2023-06-12 08:48:51 -04:00
binwiederhier
1dbcfe3c6e Thank you @KevinWang15 for your donation 2023-06-10 21:11:48 -04:00
binwiederhier
58992fc795 Make DELETE endpoint, add different UI description 2023-06-10 21:09:01 -04:00
binwiederhier
eb220544a3 Change wording in prefs based on setting 2023-06-10 20:51:24 -04:00
binwiederhier
9d5556c7f5 Rename things, add comments 2023-06-10 20:42:02 -04:00
binwiederhier
1abcc88fce Add subscription_topic table, change updated_at type to INT, split expire function 2023-06-09 23:17:48 -04:00
binwiederhier
2e8292a65f No real changes, just renames 2023-06-09 14:32:34 -04:00
nimbleghost
4704b2a0e4 Set default TTL for web push to the cache duration 2023-06-09 11:32:44 +02:00
nimbleghost
9e4eafe8d5 Format 2023-06-09 10:03:11 +02:00
binwiederhier
966ffe1669 More refactor 2023-06-08 23:09:38 -04:00
binwiederhier
9d38aeb863 Docs in server.yml, schemaVersion table, refactoring 2023-06-08 21:45:52 -04:00
binwiederhier
d3ac976d05 Remove web-push-(enabled|duration*), change endpoint, other cosmetic changes 2023-06-08 14:30:19 -04:00
nimbleghost
4ce6fdcc5a Implement http actions in service worker
These are only supported in Chrome-based browsers via the service worker
and not for regular desktop notifications.
2023-06-08 20:12:41 +02:00
binwiederhier
75a4b5bd88 Small refactor 2023-06-08 12:20:12 -04:00
nimbleghost
2f5acee798 Call pushManager.subscribe only if enabled 2023-06-08 10:55:11 +02:00
nimbleghost
46798ac322 Make web push toggle global 2023-06-08 10:46:51 +02:00
nimbleghost
a8db08c7d4 Use attachment URL for image & add timestamp 2023-06-07 21:57:23 +02:00
nimbleghost
f3db0e083e Add release notes 2023-06-07 21:57:22 +02:00
nimbleghost
18edff9afe Add TODO comment about Safari 17 PWA 2023-06-07 21:09:13 +02:00
nimbleghost
03aa67ed68 Remove webPushDefaultEnabled 2023-06-07 21:09:13 +02:00
nimbleghost
46f34ca1e3 Add push service allowlist and topic limit 2023-06-07 21:09:13 +02:00
nimbleghost
0f0074cbab Implement push subscription expiry 2023-06-07 21:09:13 +02:00
nimbleghost
47ad024ec7 Simplify web push UX and updates
- Use a single endpoint
- Use a declarative web push sync hook. This thus handles all edge cases
  that had to be manually handled before: logout, login, account sync,
  etc.
- Simplify UX: browser notifications are always enabled (unless denied),
  web push toggle only shows up if permissions are already granted.
2023-06-07 20:38:21 +02:00
nimbleghost
4944e3ae4b Remove webPushEndpoint from indexeddb
Rely directly on getting it from the browser
2023-06-07 20:38:21 +02:00
nimbleghost
7aa3d8f59b Hide web push toggles if disabled on server 2023-06-07 20:38:21 +02:00
nimbleghost
0c25425346 Use readJSONWithLimit for web push sub/unsub 2023-06-07 20:38:21 +02:00
nimbleghost
4648f83669 Format emojis in the service worker directly 2023-06-07 20:38:21 +02:00
nimbleghost
44913c1668 Replace if err-nil-Fatal check with require.Nil 2023-06-07 20:38:21 +02:00
binwiederhier
20c7650e51 server.yml update 2023-06-07 20:38:21 +02:00
binwiederhier
e8139ad655 Move web-push-config endpoint to config.js 2023-06-07 20:38:21 +02:00
binwiederhier
9e0687e142 Random tiny changes 2023-06-07 20:38:21 +02:00
binwiederhier
7f3e4b5f47 Move stuff to server_web_push.go 2023-06-07 20:38:21 +02:00
binwiederhier
7b23158e0a Cosmetic changes 2023-06-07 20:38:21 +02:00
nimbleghost
f94bb1aa30 Improve web push docs 2023-06-07 20:38:21 +02:00
nimbleghost
a9fef387fa Add web push tests 2023-06-07 20:38:21 +02:00
nimbleghost
ff5c854192 Add PWA, service worker and Web Push
- Use new notification request/opt-in flow for push
- Implement unsubscribing
- Implement muting
- Implement emojis in title
- Add iOS specific PWA warning
- Don’t use websockets when web push is enabled
- Fix duplicate notifications
- Implement default web push setting
- Implement changing subscription type
- Implement web push subscription refresh
- Implement web push notification click
2023-06-07 20:38:20 +02:00
binwiederhier
733ef4664b Deps 2023-06-07 13:24:41 -04:00
binwiederhier
e89c62174d Merge branch 'main' of github.com:binwiederhier/ntfy 2023-06-07 13:24:26 -04:00
binwiederhier
78e437057c Update deps 2023-06-07 13:24:15 -04:00
binwiederhier
7cdd86c99f Merge branch 'main' of https://hosted.weblate.org/git/ntfy/web 2023-06-07 13:22:19 -04:00
binwiederhier
c045f4d21f Integrations 2023-06-07 13:22:12 -04:00
Philipp C. Heckel
c65b83a6f5 Merge pull request #767 from binwiederhier/dependabot/npm_and_yarn/web/vite-4.3.9
Bump vite from 4.3.8 to 4.3.9 in /web
2023-06-06 09:10:05 -04:00
Shoshin Akamine
2b2753be21 Translated using Weblate (Japanese)
Currently translated at 100.0% (382 of 382 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/ja/
2023-06-06 09:51:44 +02:00
dependabot[bot]
fe3db1375a Bump vite from 4.3.8 to 4.3.9 in /web
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 4.3.8 to 4.3.9.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v4.3.9/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-06 02:43:04 +00:00
JULIANE LEITE
2e9eff69d7 Translated using Weblate (Portuguese (Brazil))
Currently translated at 50.2% (192 of 382 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/pt_BR/
2023-06-04 03:51:46 +02:00
binwiederhier
7d46f1eed9 Merge branch 'main' into markdown 2023-05-26 21:15:38 -04:00
binwiederhier
7812eb9d19 WIP: Markdown 2023-05-24 20:37:27 -04:00
binwiederhier
d88dbbc90f WIP 2023-04-02 13:59:26 -04:00
225 changed files with 18831 additions and 3981 deletions

View File

@@ -1,30 +1,24 @@
name: build
on: [push, pull_request]
on: [ push, pull_request ]
jobs:
build:
runs-on: ubuntu-latest
steps:
-
name: Checkout code
- name: Checkout code
uses: actions/checkout@v3
-
name: Install Go
- name: Install Go
uses: actions/setup-go@v4
with:
go-version: '1.19.x'
-
name: Install node
go-version: '1.24.x'
- name: Install node
uses: actions/setup-node@v3
with:
node-version: '18'
node-version: '20'
cache: 'npm'
cache-dependency-path: './web/package-lock.json'
-
name: Install dependencies
- name: Install dependencies
run: make build-deps-ubuntu
-
name: Build all the things
- name: Build all the things
run: make build
-
name: Print build results and checksums
- name: Print build results and checksums
run: make cli-build-results

View File

@@ -7,35 +7,28 @@ jobs:
release:
runs-on: ubuntu-latest
steps:
-
name: Checkout code
- name: Checkout code
uses: actions/checkout@v3
-
name: Install Go
- name: Install Go
uses: actions/setup-go@v4
with:
go-version: '1.19.x'
-
name: Install node
go-version: '1.24.x'
- name: Install node
uses: actions/setup-node@v3
with:
node-version: '18'
node-version: '20'
cache: 'npm'
cache-dependency-path: './web/package-lock.json'
-
name: Docker login
- name: Docker login
uses: docker/login-action@v2
with:
username: ${{ github.repository_owner }}
password: ${{ secrets.DOCKER_HUB_TOKEN }}
-
name: Install dependencies
- name: Install dependencies
run: make build-deps-ubuntu
-
name: Build and publish
- name: Build and publish
run: make release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-
name: Print build results and checksums
- name: Print build results and checksums
run: make cli-build-results

View File

@@ -1,39 +1,30 @@
name: test
on: [push, pull_request]
on: [ push, pull_request ]
jobs:
test:
runs-on: ubuntu-latest
steps:
-
name: Checkout code
- name: Checkout code
uses: actions/checkout@v3
-
name: Install Go
- name: Install Go
uses: actions/setup-go@v4
with:
go-version: '1.19.x'
-
name: Install node
go-version: '1.24.x'
- name: Install node
uses: actions/setup-node@v3
with:
node-version: '18'
node-version: '20'
cache: 'npm'
cache-dependency-path: './web/package-lock.json'
-
name: Install dependencies
- name: Install dependencies
run: make build-deps-ubuntu
-
name: Build docs (required for tests)
- name: Build docs (required for tests)
run: make docs
-
name: Build web app (required for tests)
- name: Build web app (required for tests)
run: make web
-
name: Run tests, formatting, vetting and linting
- name: Run tests, formatting, vetting and linting
run: make check
-
name: Run coverage
- name: Run coverage
run: make coverage
-
name: Upload coverage to codecov.io
- name: Upload coverage to codecov.io
run: make coverage-upload

3
.gitignore vendored
View File

@@ -13,3 +13,6 @@ secrets/
node_modules/
.DS_Store
__pycache__
web/dev-dist/
venv/
cmd/key-file.yaml

View File

@@ -90,6 +90,8 @@ nfpms:
type: "config|noreplace"
- src: client/ntfy-client.service
dst: /lib/systemd/system/ntfy-client.service
- src: client/user/ntfy-client.service
dst: /lib/systemd/user/ntfy-client.service
- dst: /var/cache/ntfy
type: dir
- dst: /var/cache/ntfy/attachments
@@ -119,8 +121,7 @@ archives:
- server/ntfy.service
- client/client.yml
- client/ntfy-client.service
replacements:
amd64: x86_64
- client/user/ntfy-client.service
-
id: ntfy_windows
builds:
@@ -131,8 +132,6 @@ archives:
- LICENSE
- README.md
- client/client.yml
replacements:
amd64: x86_64
-
id: ntfy_darwin
builds:
@@ -142,8 +141,6 @@ archives:
- LICENSE
- README.md
- client/client.yml
replacements:
darwin: macOS
universal_binaries:
-
id: ntfy_darwin_all
@@ -170,14 +167,14 @@ dockers:
- image_templates:
- &arm64v8_image "binwiederhier/ntfy:{{ .Tag }}-arm64v8"
use: buildx
dockerfile: Dockerfile
dockerfile: Dockerfile-arm
goarch: arm64
build_flag_templates:
- "--platform=linux/arm64/v8"
- image_templates:
- &armv7_image "binwiederhier/ntfy:{{ .Tag }}-armv7"
use: buildx
dockerfile: Dockerfile
dockerfile: Dockerfile-arm
goarch: arm
goarm: 7
build_flag_templates:
@@ -185,7 +182,7 @@ dockers:
- image_templates:
- &armv6_image "binwiederhier/ntfy:{{ .Tag }}-armv6"
use: buildx
dockerfile: Dockerfile
dockerfile: Dockerfile-arm
goarch: arm
goarm: 6
build_flag_templates:
@@ -203,3 +200,15 @@ docker_manifests:
- *arm64v8_image
- *armv7_image
- *armv6_image
- name_template: "binwiederhier/ntfy:v{{ .Major }}"
image_templates:
- *amd64_image
- *arm64v8_image
- *armv7_image
- *armv6_image
- name_template: "binwiederhier/ntfy:v{{ .Major }}.{{ .Minor }}"
image_templates:
- *amd64_image
- *arm64v8_image
- *armv7_image
- *armv6_image

View File

@@ -9,6 +9,7 @@ LABEL org.opencontainers.image.licenses="Apache-2.0, GPL-2.0"
LABEL org.opencontainers.image.title="ntfy"
LABEL org.opencontainers.image.description="Send push notifications to your phone or desktop using PUT/POST"
RUN apk add --no-cache tzdata
COPY ntfy /usr/bin
EXPOSE 80/tcp

18
Dockerfile-arm Normal file
View File

@@ -0,0 +1,18 @@
FROM alpine
LABEL org.opencontainers.image.authors="philipp.heckel@gmail.com"
LABEL org.opencontainers.image.url="https://ntfy.sh/"
LABEL org.opencontainers.image.documentation="https://docs.ntfy.sh/"
LABEL org.opencontainers.image.source="https://github.com/binwiederhier/ntfy"
LABEL org.opencontainers.image.vendor="Philipp C. Heckel"
LABEL org.opencontainers.image.licenses="Apache-2.0, GPL-2.0"
LABEL org.opencontainers.image.title="ntfy"
LABEL org.opencontainers.image.description="Send push notifications to your phone or desktop using PUT/POST"
# Alpine does not support adding "tzdata" on ARM anymore, see
# https://github.com/binwiederhier/ntfy/issues/894
COPY ntfy /usr/bin
EXPOSE 80/tcp
ENTRYPOINT ["ntfy"]

View File

@@ -1,14 +1,20 @@
FROM golang:1.19-bullseye as builder
FROM golang:1.24-bullseye as builder
ARG VERSION=dev
ARG COMMIT=unknown
ARG NODE_MAJOR=18
RUN apt-get update
RUN curl -fsSL https://deb.nodesource.com/setup_18.x | bash
RUN apt-get install -y \
build-essential \
nodejs \
python3-pip
RUN apt-get update && apt-get install -y \
build-essential ca-certificates curl gnupg \
&& mkdir -p /etc/apt/keyrings \
&& curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg \
&& echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_MAJOR.x nodistro main" >> /etc/apt/sources.list.d/nodesource.list \
&& apt-get update \
&& apt-get install -y \
python3-pip \
python3-venv \
nodejs \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
ADD Makefile .
@@ -19,7 +25,7 @@ RUN make docs-deps
ADD ./mkdocs.yml .
ADD ./docs ./docs
RUN make docs-build
# web
ADD ./web/package.json ./web/package-lock.json ./web/
RUN make web-deps
@@ -38,6 +44,8 @@ RUN make VERSION=$VERSION COMMIT=$COMMIT cli-linux-server
FROM alpine
ARG VERSION=dev
LABEL org.opencontainers.image.authors="philipp.heckel@gmail.com"
LABEL org.opencontainers.image.url="https://ntfy.sh/"
LABEL org.opencontainers.image.documentation="https://docs.ntfy.sh/"
@@ -46,6 +54,7 @@ LABEL org.opencontainers.image.vendor="Philipp C. Heckel"
LABEL org.opencontainers.image.licenses="Apache-2.0, GPL-2.0"
LABEL org.opencontainers.image.title="ntfy"
LABEL org.opencontainers.image.description="Send push notifications to your phone or desktop using PUT/POST"
LABEL org.opencontainers.image.version="$VERSION"
COPY --from=builder /app/dist/ntfy_linux_server/ntfy /usr/bin/ntfy

View File

@@ -1,4 +1,6 @@
MAKEFLAGS := --jobs=1
PYTHON := python3
PIP := pip3
VERSION := $(shell git describe --tag)
COMMIT := $(shell git rev-parse --short HEAD)
@@ -39,8 +41,8 @@ help:
@echo " make web-deps - Install web app dependencies (npm install the universe)"
@echo " make web-build - Actually build the web app"
@echo " make web-lint - Run eslint on the web app"
@echo " make web-format - Run prettier on the web app"
@echo " make web-format-check - Run prettier on the web app, but don't change anything"
@echo " make web-fmt - Run prettier on the web app"
@echo " make web-fmt-check - Run prettier on the web app, but don't change anything"
@echo
@echo "Build documentation:"
@echo " make docs - Build the documentation"
@@ -95,6 +97,7 @@ docker-dev:
--build-arg COMMIT=$(COMMIT) \
./
# Ubuntu-specific
build-deps-ubuntu:
@@ -103,32 +106,27 @@ build-deps-ubuntu:
curl \
gcc-aarch64-linux-gnu \
gcc-arm-linux-gnueabi \
python3 \
python3-venv \
jq
which pip3 || sudo apt-get install -y python3-pip
# Documentation
docs: docs-deps docs-build
docs-build: .PHONY
@if ! /bin/echo -e "import sys\nif sys.version_info < (3,8):\n exit(1)" | python3; then \
if which python3.8; then \
echo "python3.8 $(shell which mkdocs) build"; \
python3.8 $(shell which mkdocs) build; \
else \
echo "ERROR: Python version too low. mkdocs-material needs >= 3.8"; \
exit 1; \
fi; \
else \
echo "mkdocs build"; \
mkdocs build; \
fi
docs-venv: .PHONY
$(PYTHON) -m venv ./venv
docs-deps: .PHONY
pip3 install -r requirements.txt
docs-build: docs-venv
(. venv/bin/activate && $(PYTHON) -m mkdocs build)
docs-deps: docs-venv
(. venv/bin/activate && $(PIP) install -r requirements.txt)
docs-deps-update: .PHONY
pip3 install -r requirements.txt --upgrade
(. venv/bin/activate && $(PIP) install -r requirements.txt --upgrade)
# Web app
@@ -151,10 +149,10 @@ web-deps:
web-deps-update:
cd web && npm update
web-format:
web-fmt:
cd web && npm run format
web-format-check:
web-fmt-check:
cd web && npm run format:check
web-lint:
@@ -248,7 +246,7 @@ cli-build-results:
# Test/check targets
check: test web-format-check fmt-check vet web-lint lint staticcheck
check: test web-fmt-check fmt-check vet web-lint lint staticcheck
test: .PHONY
go test $(shell go list ./... | grep -vE 'ntfy/(test|examples|tools)')
@@ -275,7 +273,7 @@ coverage-upload:
# Lint/formatting targets
fmt:
fmt: web-fmt
gofmt -s -w .
fmt-check:

View File

@@ -2,14 +2,13 @@
# ntfy.sh | Send push notifications to your phone or desktop via PUT/POST
[![Release](https://img.shields.io/github/release/binwiederhier/ntfy.svg?color=success&style=flat-square)](https://github.com/binwiederhier/ntfy/releases/latest)
[![Go Reference](https://pkg.go.dev/badge/heckel.io/ntfy.svg)](https://pkg.go.dev/heckel.io/ntfy)
[![Go Reference](https://pkg.go.dev/badge/heckel.io/ntfy.svg)](https://pkg.go.dev/heckel.io/ntfy/v2)
[![Tests](https://github.com/binwiederhier/ntfy/workflows/test/badge.svg)](https://github.com/binwiederhier/ntfy/actions)
[![Go Report Card](https://goreportcard.com/badge/github.com/binwiederhier/ntfy)](https://goreportcard.com/report/github.com/binwiederhier/ntfy)
[![codecov](https://codecov.io/gh/binwiederhier/ntfy/branch/main/graph/badge.svg?token=A597KQ463G)](https://codecov.io/gh/binwiederhier/ntfy)
[![Discord](https://img.shields.io/discord/874398661709295626?label=Discord)](https://discord.gg/cT7ECsZj9w)
[![Matrix](https://img.shields.io/matrix/ntfy:matrix.org?label=Matrix)](https://matrix.to/#/#ntfy:matrix.org)
[![Matrix space](https://img.shields.io/matrix/ntfy-space:matrix.org?label=Matrix+space)](https://matrix.to/#/#ntfy-space:matrix.org)
[![Reddit](https://img.shields.io/reddit/subreddit-subscribers/ntfy?color=%23317f6f&label=-%20r%2Fntfy&style=social)](https://www.reddit.com/r/ntfy/)
[![Healthcheck](https://healthchecks.io/badge/68b65976-b3b0-4102-aec9-980921/kcoEgrLY.svg)](https://ntfy.statuspage.io/)
[![Gitpod](https://img.shields.io/badge/Contribute%20with-Gitpod-908a85?logo=gitpod)](https://gitpod.io/#https://github.com/binwiederhier/ntfy)
@@ -18,7 +17,7 @@ notification service. With ntfy, you can **send notifications to your phone or d
**without having to sign up or pay any fees**. If you'd like to run your own instance of the service, you can easily do
so since ntfy is open source.
You can access the free version of ntfy at **[ntfy.sh](https://ntfy.sh)**. There is also an [open source Android app](https://github.com/binwiederhier/ntfy-android)
You can access the free version of ntfy at **[ntfy.sh](https://ntfy.sh)**. There is also an [open-source Android app](https://github.com/binwiederhier/ntfy-android)
available on [Google Play](https://play.google.com/store/apps/details?id=io.heckel.ntfy) or [F-Droid](https://f-droid.org/en/packages/io.heckel.ntfy/),
as well as an [open source iOS app](https://github.com/binwiederhier/ntfy-ios) available on the [App Store](https://apps.apple.com/us/app/ntfy/id1625396347).
@@ -31,7 +30,10 @@ as well as an [open source iOS app](https://github.com/binwiederhier/ntfy-ios) a
</p>
## [ntfy Pro](https://ntfy.sh/app) 💸 🎉
I now offer paid plans for [ntfy.sh](https://ntfy.sh/) if you don't want to self-host, or you want to support the development of ntfy (→ [Purchase via web app](https://ntfy.sh/app)). You can **buy a plan for as low as $3.33/month** (if you use promo code `MYTOPIC`, limited time only). You can also donate via [GitHub Sponsors](https://github.com/sponsors/binwiederhier), and [Liberapay](https://liberapay.com/ntfy). I would be very humbled by your sponsorship. ❤️
I now offer paid plans for [ntfy.sh](https://ntfy.sh/) if you don't want to self-host, or you want to support the development of
ntfy (→ [Purchase via web app](https://ntfy.sh/app)). You can **buy a plan for as low as $5/month**.
You can also donate via [GitHub Sponsors](https://github.com/sponsors/binwiederhier), and [Liberapay](https://liberapay.com/ntfy).
I would be very humbled by your sponsorship. ❤️
## **[Documentation](https://ntfy.sh/docs/)**
@@ -41,23 +43,21 @@ I now offer paid plans for [ntfy.sh](https://ntfy.sh/) if you don't want to self
[Install / Self-hosting](https://ntfy.sh/docs/install/) |
[Building](https://ntfy.sh/docs/develop/)
## Chat / forum
## Chat/forum
There are a few ways to get in touch with me and/or the rest of the community. Feel free to use any of these methods. Whatever
works best for you:
* [Discord server](https://discord.gg/cT7ECsZj9w) - direct chat with the community
* [Matrix room #ntfy](https://matrix.to/#/#ntfy:matrix.org) (+ [Matrix space](https://matrix.to/#/#ntfy-space:matrix.org)) - same chat, bridged from Discord
* [Reddit r/ntfy](https://www.reddit.com/r/ntfy/) - asynchronous forum (_new as of October 2022_)
* [GitHub issues](https://github.com/binwiederhier/ntfy/issues) - questions, features, bugs
* [Email](https://heckel.io/about) - reach me directly (_I usually prefer the other methods_)
## Announcements / beta testers
## Announcements/beta testers
For announcements of new releases and cutting-edge beta versions, please subscribe to the [ntfy.sh/announcements](https://ntfy.sh/announcements)
topic. If you'd like to test the iOS app, join [TestFlight](https://testflight.apple.com/join/P1fFnAm9). For Android betas,
join Discord/Matrix (I'll eventually make a testing channel in Google Play).
## Contributing
I welcome any and all contributions. Just create a PR or an issue. For larger features/ideas, please reach out
I welcome any contributions. Just create a PR or an issue. For larger features/ideas, please reach out
on Discord/Matrix first to see if I'd accept them. To contribute code, check out the [build instructions](https://ntfy.sh/docs/develop/)
for the server and the Android app. Or, if you'd like to help translate 🇩🇪 🇺🇸 🇧🇬, you can start immediately in
[Hosted Weblate](https://hosted.weblate.org/projects/ntfy/).
@@ -69,7 +69,7 @@ for the server and the Android app. Or, if you'd like to help translate 🇩🇪
## Sponsors
I have just very recently started accepting donations via [GitHub Sponsors](https://github.com/sponsors/binwiederhier),
and [Liberapay](https://liberapay.com/ntfy). I would be humbled if you helped me carry the server and developer
account costs. Even small donations are very much appreciated. A big fat **Thank You** to the folks already sponsoring ntfy:
account costs. Even small donations are very much appreciated. A big fat **Thank You** to the folks who have sponsored ntfy in the past, or are still sponsoring ntfy:
<a href="https://github.com/neutralinsomniac"><img src="https://github.com/neutralinsomniac.png" width="40px" /></a>
<a href="https://github.com/aspyct"><img src="https://github.com/aspyct.png" width="40px" /></a>
@@ -141,8 +141,76 @@ account costs. Even small donations are very much appreciated. A big fat **Thank
<a href="https://github.com/CreativeWarlock"><img src="https://github.com/CreativeWarlock.png" width="40px" /></a>
<a href="https://github.com/darkdragon-001"><img src="https://github.com/darkdragon-001.png" width="40px" /></a>
<a href="https://github.com/jonathan-kosgei"><img src="https://github.com/jonathan-kosgei.png" width="40px" /></a>
<a href="https://github.com/KevinWang15"><img src="https://github.com/KevinWang15.png" width="40px" /></a>
<a href="https://github.com/darkmattercoder"><img src="https://github.com/darkmattercoder.png" width="40px" /></a>
<a href="https://github.com/bmcgonag"><img src="https://github.com/bmcgonag.png" width="40px" /></a>
<a href="https://github.com/skorokithakis"><img src="https://github.com/skorokithakis.png" width="40px" /></a>
<a href="https://github.com/eenturk"><img src="https://github.com/eenturk.png" width="40px" /></a>
<a href="https://github.com/spirossi"><img src="https://github.com/spirossi.png" width="40px" /></a>
<a href="https://github.com/teomarcdhio"><img src="https://github.com/teomarcdhio.png" width="40px" /></a>
<a href="https://github.com/MarcMichalsky"><img src="https://github.com/MarcMichalsky.png" width="40px" /></a>
<a href="https://github.com/LuckVintage"><img src="https://github.com/LuckVintage.png" width="40px" /></a>
<a href="https://github.com/spartan"><img src="https://github.com/spartan.png" width="40px" /></a>
<a href="https://github.com/alexandzors"><img src="https://github.com/alexandzors.png" width="40px" /></a>
<a href="https://github.com/dkramer95"><img src="https://github.com/dkramer95.png" width="40px" /></a>
<a href="https://github.com/YezGotIt"><img src="https://github.com/YezGotIt.png" width="40px" /></a>
<a href="https://github.com/thomasskou"><img src="https://github.com/thomasskou.png" width="40px" /></a>
<a href="https://github.com/surfernv"><img src="https://github.com/surfernv.png" width="40px" /></a>
<a href="https://github.com/richardleach"><img src="https://github.com/richardleach.png" width="40px" /></a>
<a href="https://github.com/bear"><img src="https://github.com/bear.png" width="40px" /></a>
<a href="https://github.com/cminter"><img src="https://github.com/cminter.png" width="40px" /></a>
<a href="https://github.com/bahur142"><img src="https://github.com/bahur142.png" width="40px" /></a>
<a href="https://github.com/pgwiebes"><img src="https://github.com/pgwiebes.png" width="40px" /></a>
<a href="https://github.com/ralhei"><img src="https://github.com/ralhei.png" width="40px" /></a>
<a href="https://github.com/TechMDW"><img src="https://github.com/TechMDW.png" width="40px" /></a>
<a href="https://github.com/ubipo"><img src="https://github.com/ubipo.png" width="40px" /></a>
<a href="https://github.com/tka85"><img src="https://github.com/tka85.png" width="40px" /></a>
<a href="https://github.com/beekeeb"><img src="https://github.com/beekeeb.png" width="40px" /></a>
<a href="https://github.com/Emiliaaah"><img src="https://github.com/Emiliaaah.png" width="40px" /></a>
<a href="https://github.com/zark0s"><img src="https://github.com/zark0s.png" width="40px" /></a>
<a href="https://github.com/tomershvueli"><img src="https://github.com/tomershvueli.png" width="40px" /></a>
<a href="https://github.com/CataIana"><img src="https://github.com/CataIana.png" width="40px" /></a>
<a href="https://github.com/ajay-actuary"><img src="https://github.com/ajay-actuary.png" width="40px" /></a>
<a href="https://github.com/mursec"><img src="https://github.com/mursec.png" width="40px" /></a>
<a href="https://github.com/FrameXX"><img src="https://github.com/FrameXX.png" width="40px" /></a>
<a href="https://github.com/vovayartsev"><img src="https://github.com/vovayartsev.png" width="40px" /></a>
<a href="https://github.com/dwain-lab"><img src="https://github.com/dwain-lab.png" width="40px" /></a>
<a href="https://github.com/brookmg"><img src="https://github.com/brookmg.png" width="40px" /></a>
<a href="https://github.com/siebej"><img src="https://github.com/siebej.png" width="40px" /></a>
<a href="https://github.com/rxsantos"><img src="https://github.com/rxsantos.png" width="40px" /></a>
<a href="https://github.com/hermannx5"><img src="https://github.com/hermannx5.png" width="40px" /></a>
<a href="https://github.com/rwxd"><img src="https://github.com/rwxd.png" width="40px" /></a>
<a href="https://github.com/Integral-Tech"><img src="https://github.com/Integral-Tech.png" width="40px" /></a>
<a href="https://github.com/TheTomik1"><img src="https://github.com/TheTomik1.png" width="40px" /></a>
<a href="https://github.com/dav23r"><img src="https://github.com/dav23r.png" width="40px" /></a>
<a href="https://github.com/stannynuytkens"><img src="https://github.com/stannynuytkens.png" width="40px" /></a>
<a href="https://github.com/danbartram"><img src="https://github.com/danbartram.png" width="40px" /></a>
<a href="https://github.com/arthurgleckler"><img src="https://github.com/arthurgleckler.png" width="40px" /></a>
<a href="https://github.com/tomroth04"><img src="https://github.com/tomroth04.png" width="40px" /></a>
<a href="https://github.com/Circenn5130"><img src="https://github.com/Circenn5130.png" width="40px" /></a>
<a href="https://github.com/jceloria"><img src="https://github.com/jceloria.png" width="40px" /></a>
<a href="https://github.com/afunworm"><img src="https://github.com/afunworm.png" width="40px" /></a>
<a href="https://github.com/PTR-inc"><img src="https://github.com/PTR-inc.png" width="40px" /></a>
<a href="https://github.com/spudooli"><img src="https://github.com/spudooli.png" width="40px" /></a>
<a href="https://github.com/IMarkoMC"><img src="https://github.com/IMarkoMC.png" width="40px" /></a>
<a href="https://github.com/rubund"><img src="https://github.com/rubund.png" width="40px" /></a>
<a href="https://github.com/Riolku"><img src="https://github.com/Riolku.png" width="40px" /></a>
<a href="https://github.com/arnbrhm"><img src="https://github.com/arnbrhm.png" width="40px" /></a>
<a href="https://github.com/herzkerl"><img src="https://github.com/herzkerl.png" width="40px" /></a>
<a href="https://github.com/0x45796164"><img src="https://github.com/0x45796164.png" width="40px" /></a>
<a href="https://github.com/madchr1st"><img src="https://github.com/madchr1st.png" width="40px" /></a>
<a href="https://github.com/avalentic"><img src="https://github.com/avalentic.png" width="40px" /></a>
<a href="https://github.com/TheCraiggers"><img src="https://github.com/TheCraiggers.png" width="40px" /></a>
<a href="https://github.com/sheetd"><img src="https://github.com/sheetd.png" width="40px" /></a>
<a href="https://github.com/dlt-green"><img src="https://github.com/dlt-green.png" width="40px" /></a>
<a href="https://github.com/suhlig"><img src="https://github.com/suhlig.png" width="40px" /></a>
<a href="https://github.com/Proximus888"><img src="https://github.com/Proximus888.png" width="40px" /></a>
<a href="https://github.com/wielandp"><img src="https://github.com/wielandp.png" width="40px" /></a>
<a href="https://github.com/chxseh"><img src="https://github.com/chxseh.png" width="40px" /></a>
<a href="https://github.com/user8446"><img src="https://github.com/user8446.png" width="40px" /></a>
<a href="https://github.com/cdf-eagles"><img src="https://github.com/cdf-eagles.png" width="40px" /></a>
I'd also like to thank JetBrains for providing their awesome [IntelliJ IDEA](https://www.jetbrains.com/idea/) to me for free,
I'd also like to thank JetBrains for their awesome [IntelliJ IDEA](https://www.jetbrains.com/idea/),
and [DigitalOcean](https://m.do.co/c/442b929528db) (*referral link*) for supporting the project:
<a href="https://m.do.co/c/442b929528db"><img src="https://opensource.nyc3.cdn.digitaloceanspaces.com/attribution/assets/SVG/DO_Logo_horizontal_blue.svg" width="201px"></a>
@@ -158,7 +226,7 @@ _Please be sure to read the complete [Code of Conduct](CODE_OF_CONDUCT.md)._
Made with ❤️ by [Philipp C. Heckel](https://heckel.io).
The project is dual licensed under the [Apache License 2.0](LICENSE) and the [GPLv2 License](LICENSE.GPLv2).
Third party libraries and resources:
Third-party libraries and resources:
* [github.com/urfave/cli](https://github.com/urfave/cli) (MIT) is used to drive the CLI
* [Mixkit sounds](https://mixkit.co/free-sound-effects/notification/) (Mixkit Free License) are used as notification sounds
* [Sounds from notificationsounds.com](https://notificationsounds.com) (Creative Commons Attribution) are used as notification sounds
@@ -178,3 +246,4 @@ Third party libraries and resources:
* [Regex for auto-linking](https://github.com/bryanwoods/autolink-js) (MIT) is used to highlight links (the library is not used)
* [Statically linking go-sqlite3](https://www.arp242.net/static-go.html)
* [Linked tabs in mkdocs](https://facelessuser.github.io/pymdown-extensions/extensions/tabbed/#linked-tabs)
* [webpush-go](https://github.com/SherClockHolmes/webpush-go) (MIT) is used to send web push notifications

View File

@@ -7,8 +7,8 @@ import (
"encoding/json"
"errors"
"fmt"
"heckel.io/ntfy/log"
"heckel.io/ntfy/util"
"heckel.io/ntfy/v2/log"
"heckel.io/ntfy/v2/util"
"io"
"net/http"
"regexp"

View File

@@ -7,7 +7,10 @@
# Default credentials will be used with "ntfy publish" and "ntfy subscribe" if no other credentials are provided.
# You can set a default token to use or a default user:password combination, but not both. For an empty password,
# use empty double-quotes ("")
# use empty double-quotes ("").
#
# To override the default user:password combination or default token for a particular subscription (e.g., to send
# no Authorization header), set the user:pass/token for the subscription to empty double-quotes ("").
# default-token:

View File

@@ -3,9 +3,9 @@ package client_test
import (
"fmt"
"github.com/stretchr/testify/require"
"heckel.io/ntfy/client"
"heckel.io/ntfy/log"
"heckel.io/ntfy/test"
"heckel.io/ntfy/v2/client"
"heckel.io/ntfy/v2/log"
"heckel.io/ntfy/v2/test"
"os"
"testing"
"time"

View File

@@ -2,6 +2,7 @@ package client
import (
"gopkg.in/yaml.v2"
"heckel.io/ntfy/v2/log"
"os"
)
@@ -23,9 +24,9 @@ type Config struct {
// Subscribe is the struct for a Subscription within Config
type Subscribe struct {
Topic string `yaml:"topic"`
User string `yaml:"user"`
User *string `yaml:"user"`
Password *string `yaml:"password"`
Token string `yaml:"token"`
Token *string `yaml:"token"`
Command string `yaml:"command"`
If map[string]string `yaml:"if"`
}
@@ -44,6 +45,7 @@ func NewConfig() *Config {
// LoadConfig loads the Client config from a yaml file
func LoadConfig(filename string) (*Config, error) {
log.Debug("Loading client config from %s", filename)
b, err := os.ReadFile(filename)
if err != nil {
return nil, err

View File

@@ -2,7 +2,7 @@ package client_test
import (
"github.com/stretchr/testify/require"
"heckel.io/ntfy/client"
"heckel.io/ntfy/v2/client"
"os"
"path/filepath"
"testing"
@@ -37,7 +37,7 @@ subscribe:
require.Equal(t, 4, len(conf.Subscribe))
require.Equal(t, "no-command-with-auth", conf.Subscribe[0].Topic)
require.Equal(t, "", conf.Subscribe[0].Command)
require.Equal(t, "phil", conf.Subscribe[0].User)
require.Equal(t, "phil", *conf.Subscribe[0].User)
require.Equal(t, "mypass", *conf.Subscribe[0].Password)
require.Equal(t, "echo-this", conf.Subscribe[1].Topic)
require.Equal(t, `echo "Message received: $message"`, conf.Subscribe[1].Command)
@@ -67,7 +67,7 @@ subscribe:
require.Equal(t, 1, len(conf.Subscribe))
require.Equal(t, "no-command-with-auth", conf.Subscribe[0].Topic)
require.Equal(t, "", conf.Subscribe[0].Command)
require.Equal(t, "phil", conf.Subscribe[0].User)
require.Equal(t, "phil", *conf.Subscribe[0].User)
require.Equal(t, "", *conf.Subscribe[0].Password)
}
@@ -91,7 +91,7 @@ subscribe:
require.Equal(t, 1, len(conf.Subscribe))
require.Equal(t, "no-command-with-auth", conf.Subscribe[0].Topic)
require.Equal(t, "", conf.Subscribe[0].Command)
require.Equal(t, "phil", conf.Subscribe[0].User)
require.Equal(t, "phil", *conf.Subscribe[0].User)
require.Nil(t, conf.Subscribe[0].Password)
}
@@ -113,7 +113,7 @@ subscribe:
require.Equal(t, 1, len(conf.Subscribe))
require.Equal(t, "no-command-with-auth", conf.Subscribe[0].Topic)
require.Equal(t, "", conf.Subscribe[0].Command)
require.Equal(t, "phil", conf.Subscribe[0].User)
require.Equal(t, "phil", *conf.Subscribe[0].User)
require.Nil(t, conf.Subscribe[0].Password)
}
@@ -134,7 +134,7 @@ subscribe:
require.Equal(t, "tk_AgQdq7mVBoFD37zQVN29RhuMzNIz2", conf.DefaultToken)
require.Equal(t, 1, len(conf.Subscribe))
require.Equal(t, "mytopic", conf.Subscribe[0].Topic)
require.Equal(t, "", conf.Subscribe[0].User)
require.Nil(t, conf.Subscribe[0].User)
require.Nil(t, conf.Subscribe[0].Password)
require.Equal(t, "", conf.Subscribe[0].Token)
require.Nil(t, conf.Subscribe[0].Token)
}

View File

@@ -2,7 +2,7 @@ package client
import (
"fmt"
"heckel.io/ntfy/util"
"heckel.io/ntfy/v2/util"
"net/http"
"strings"
"time"
@@ -72,6 +72,11 @@ func WithAttach(attach string) PublishOption {
return WithHeader("X-Attach", attach)
}
// WithMarkdown instructs the server to interpret the message body as Markdown
func WithMarkdown() PublishOption {
return WithHeader("X-Markdown", "yes")
}
// WithFilename sets a filename for the attachment, and/or forces the HTTP body to interpreted as an attachment
func WithFilename(filename string) PublishOption {
return WithHeader("X-Filename", filename)
@@ -92,6 +97,11 @@ func WithBearerAuth(token string) PublishOption {
return WithHeader("Authorization", fmt.Sprintf("Bearer %s", token))
}
// WithEmptyAuth clears the Authorization header
func WithEmptyAuth() PublishOption {
return RemoveHeader("Authorization")
}
// WithNoCache instructs the server not to cache the message server-side
func WithNoCache() PublishOption {
return WithHeader("X-Cache", "no")
@@ -182,3 +192,13 @@ func WithQueryParam(param, value string) RequestOption {
return nil
}
}
// RemoveHeader is a generic option to remove a header from a request
func RemoveHeader(header string) RequestOption {
return func(r *http.Request) error {
if header != "" {
delete(r.Header, header)
}
return nil
}
}

View File

@@ -0,0 +1,10 @@
[Unit]
Description=ntfy client
After=network.target
[Service]
ExecStart=/usr/bin/ntfy subscribe --config "%h/.config/ntfy/client.yml" --from-config
Restart=on-failure
[Install]
WantedBy=default.target

View File

@@ -6,8 +6,8 @@ import (
"errors"
"fmt"
"github.com/urfave/cli/v2"
"heckel.io/ntfy/user"
"heckel.io/ntfy/util"
"heckel.io/ntfy/v2/user"
"heckel.io/ntfy/v2/util"
)
func init() {

View File

@@ -4,8 +4,8 @@ import (
"fmt"
"github.com/stretchr/testify/require"
"github.com/urfave/cli/v2"
"heckel.io/ntfy/server"
"heckel.io/ntfy/test"
"heckel.io/ntfy/v2/server"
"heckel.io/ntfy/v2/test"
"testing"
)

View File

@@ -5,7 +5,7 @@ import (
"fmt"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v2/altsrc"
"heckel.io/ntfy/log"
"heckel.io/ntfy/v2/log"
"os"
"regexp"
)

View File

@@ -4,8 +4,8 @@ import (
"bytes"
"encoding/json"
"github.com/urfave/cli/v2"
"heckel.io/ntfy/client"
"heckel.io/ntfy/log"
"heckel.io/ntfy/v2/client"
"heckel.io/ntfy/v2/log"
"os"
"strings"
"testing"

View File

@@ -5,7 +5,7 @@ import (
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v2/altsrc"
"gopkg.in/yaml.v2"
"heckel.io/ntfy/util"
"heckel.io/ntfy/v2/util"
"os"
)

View File

@@ -4,9 +4,9 @@ import (
"errors"
"fmt"
"github.com/urfave/cli/v2"
"heckel.io/ntfy/client"
"heckel.io/ntfy/log"
"heckel.io/ntfy/util"
"heckel.io/ntfy/v2/client"
"heckel.io/ntfy/v2/log"
"heckel.io/ntfy/v2/util"
"io"
"os"
"os/exec"
@@ -31,6 +31,7 @@ var flagsPublish = append(
&cli.StringFlag{Name: "icon", Aliases: []string{"i"}, EnvVars: []string{"NTFY_ICON"}, Usage: "URL to use as notification icon"},
&cli.StringFlag{Name: "actions", Aliases: []string{"A"}, EnvVars: []string{"NTFY_ACTIONS"}, Usage: "actions JSON array or simple definition"},
&cli.StringFlag{Name: "attach", Aliases: []string{"a"}, EnvVars: []string{"NTFY_ATTACH"}, Usage: "URL to send as an external attachment"},
&cli.BoolFlag{Name: "markdown", Aliases: []string{"md"}, EnvVars: []string{"NTFY_MARKDOWN"}, Usage: "Message is formatted as Markdown"},
&cli.StringFlag{Name: "filename", Aliases: []string{"name", "n"}, EnvVars: []string{"NTFY_FILENAME"}, Usage: "filename for the attachment"},
&cli.StringFlag{Name: "file", Aliases: []string{"f"}, EnvVars: []string{"NTFY_FILE"}, Usage: "file to upload as an attachment"},
&cli.StringFlag{Name: "email", Aliases: []string{"mail", "e"}, EnvVars: []string{"NTFY_EMAIL"}, Usage: "also send to e-mail address"},
@@ -95,6 +96,7 @@ func execPublish(c *cli.Context) error {
icon := c.String("icon")
actions := c.String("actions")
attach := c.String("attach")
markdown := c.Bool("markdown")
filename := c.String("filename")
file := c.String("file")
email := c.String("email")
@@ -140,6 +142,9 @@ func execPublish(c *cli.Context) error {
if attach != "" {
options = append(options, client.WithAttach(attach))
}
if markdown {
options = append(options, client.WithMarkdown())
}
if filename != "" {
options = append(options, client.WithFilename(filename))
}

View File

@@ -3,8 +3,8 @@ package cmd
import (
"fmt"
"github.com/stretchr/testify/require"
"heckel.io/ntfy/test"
"heckel.io/ntfy/util"
"heckel.io/ntfy/v2/test"
"heckel.io/ntfy/v2/util"
"net/http"
"net/http/httptest"
"os"

View File

@@ -6,23 +6,22 @@ import (
"errors"
"fmt"
"github.com/stripe/stripe-go/v74"
"heckel.io/ntfy/user"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v2/altsrc"
"heckel.io/ntfy/v2/log"
"heckel.io/ntfy/v2/server"
"heckel.io/ntfy/v2/user"
"heckel.io/ntfy/v2/util"
"io/fs"
"math"
"net"
"net/netip"
"net/url"
"os"
"os/signal"
"strings"
"syscall"
"time"
"heckel.io/ntfy/log"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v2/altsrc"
"heckel.io/ntfy/server"
"heckel.io/ntfy/util"
)
func init() {
@@ -35,7 +34,7 @@ const (
var flagsServe = append(
append([]cli.Flag{}, flagsDefault...),
&cli.StringFlag{Name: "config", Aliases: []string{"c"}, EnvVars: []string{"NTFY_CONFIG_FILE"}, Value: defaultServerConfigFile, DefaultText: defaultServerConfigFile, Usage: "config file"},
&cli.StringFlag{Name: "config", Aliases: []string{"c"}, EnvVars: []string{"NTFY_CONFIG_FILE"}, Value: defaultServerConfigFile, Usage: "config file"},
altsrc.NewStringFlag(&cli.StringFlag{Name: "base-url", Aliases: []string{"base_url", "B"}, EnvVars: []string{"NTFY_BASE_URL"}, Usage: "externally visible base URL for this host (e.g. https://ntfy.sh)"}),
altsrc.NewStringFlag(&cli.StringFlag{Name: "listen-http", Aliases: []string{"listen_http", "l"}, EnvVars: []string{"NTFY_LISTEN_HTTP"}, Value: server.DefaultListenHTTP, Usage: "ip:port used as HTTP listen address"}),
altsrc.NewStringFlag(&cli.StringFlag{Name: "listen-https", Aliases: []string{"listen_https", "L"}, EnvVars: []string{"NTFY_LISTEN_HTTPS"}, Usage: "ip:port used as HTTPS listen address"}),
@@ -45,19 +44,19 @@ var flagsServe = append(
altsrc.NewStringFlag(&cli.StringFlag{Name: "cert-file", Aliases: []string{"cert_file", "E"}, EnvVars: []string{"NTFY_CERT_FILE"}, Usage: "certificate file, if listen-https is set"}),
altsrc.NewStringFlag(&cli.StringFlag{Name: "firebase-key-file", Aliases: []string{"firebase_key_file", "F"}, EnvVars: []string{"NTFY_FIREBASE_KEY_FILE"}, Usage: "Firebase credentials file; if set additionally publish to FCM topic"}),
altsrc.NewStringFlag(&cli.StringFlag{Name: "cache-file", Aliases: []string{"cache_file", "C"}, EnvVars: []string{"NTFY_CACHE_FILE"}, Usage: "cache file used for message caching"}),
altsrc.NewDurationFlag(&cli.DurationFlag{Name: "cache-duration", Aliases: []string{"cache_duration", "b"}, EnvVars: []string{"NTFY_CACHE_DURATION"}, Value: server.DefaultCacheDuration, Usage: "buffer messages for this time to allow `since` requests"}),
altsrc.NewStringFlag(&cli.StringFlag{Name: "cache-duration", Aliases: []string{"cache_duration", "b"}, EnvVars: []string{"NTFY_CACHE_DURATION"}, Value: util.FormatDuration(server.DefaultCacheDuration), Usage: "buffer messages for this time to allow `since` requests"}),
altsrc.NewIntFlag(&cli.IntFlag{Name: "cache-batch-size", Aliases: []string{"cache_batch_size"}, EnvVars: []string{"NTFY_BATCH_SIZE"}, Usage: "max size of messages to batch together when writing to message cache (if zero, writes are synchronous)"}),
altsrc.NewDurationFlag(&cli.DurationFlag{Name: "cache-batch-timeout", Aliases: []string{"cache_batch_timeout"}, EnvVars: []string{"NTFY_CACHE_BATCH_TIMEOUT"}, Usage: "timeout for batched async writes to the message cache (if zero, writes are synchronous)"}),
altsrc.NewStringFlag(&cli.StringFlag{Name: "cache-batch-timeout", Aliases: []string{"cache_batch_timeout"}, EnvVars: []string{"NTFY_CACHE_BATCH_TIMEOUT"}, Value: util.FormatDuration(server.DefaultCacheBatchTimeout), Usage: "timeout for batched async writes to the message cache (if zero, writes are synchronous)"}),
altsrc.NewStringFlag(&cli.StringFlag{Name: "cache-startup-queries", Aliases: []string{"cache_startup_queries"}, EnvVars: []string{"NTFY_CACHE_STARTUP_QUERIES"}, Usage: "queries run when the cache database is initialized"}),
altsrc.NewStringFlag(&cli.StringFlag{Name: "auth-file", Aliases: []string{"auth_file", "H"}, EnvVars: []string{"NTFY_AUTH_FILE"}, Usage: "auth database file used for access control"}),
altsrc.NewStringFlag(&cli.StringFlag{Name: "auth-startup-queries", Aliases: []string{"auth_startup_queries"}, EnvVars: []string{"NTFY_AUTH_STARTUP_QUERIES"}, Usage: "queries run when the auth database is initialized"}),
altsrc.NewStringFlag(&cli.StringFlag{Name: "auth-default-access", Aliases: []string{"auth_default_access", "p"}, EnvVars: []string{"NTFY_AUTH_DEFAULT_ACCESS"}, Value: "read-write", Usage: "default permissions if no matching entries in the auth database are found"}),
altsrc.NewStringFlag(&cli.StringFlag{Name: "attachment-cache-dir", Aliases: []string{"attachment_cache_dir"}, EnvVars: []string{"NTFY_ATTACHMENT_CACHE_DIR"}, Usage: "cache directory for attached files"}),
altsrc.NewStringFlag(&cli.StringFlag{Name: "attachment-total-size-limit", Aliases: []string{"attachment_total_size_limit", "A"}, EnvVars: []string{"NTFY_ATTACHMENT_TOTAL_SIZE_LIMIT"}, DefaultText: "5G", Usage: "limit of the on-disk attachment cache"}),
altsrc.NewStringFlag(&cli.StringFlag{Name: "attachment-file-size-limit", Aliases: []string{"attachment_file_size_limit", "Y"}, EnvVars: []string{"NTFY_ATTACHMENT_FILE_SIZE_LIMIT"}, DefaultText: "15M", Usage: "per-file attachment size limit (e.g. 300k, 2M, 100M)"}),
altsrc.NewDurationFlag(&cli.DurationFlag{Name: "attachment-expiry-duration", Aliases: []string{"attachment_expiry_duration", "X"}, EnvVars: []string{"NTFY_ATTACHMENT_EXPIRY_DURATION"}, Value: server.DefaultAttachmentExpiryDuration, DefaultText: "3h", Usage: "duration after which uploaded attachments will be deleted (e.g. 3h, 20h)"}),
altsrc.NewDurationFlag(&cli.DurationFlag{Name: "keepalive-interval", Aliases: []string{"keepalive_interval", "k"}, EnvVars: []string{"NTFY_KEEPALIVE_INTERVAL"}, Value: server.DefaultKeepaliveInterval, Usage: "interval of keepalive messages"}),
altsrc.NewDurationFlag(&cli.DurationFlag{Name: "manager-interval", Aliases: []string{"manager_interval", "m"}, EnvVars: []string{"NTFY_MANAGER_INTERVAL"}, Value: server.DefaultManagerInterval, Usage: "interval of for message pruning and stats printing"}),
altsrc.NewStringFlag(&cli.StringFlag{Name: "attachment-total-size-limit", Aliases: []string{"attachment_total_size_limit", "A"}, EnvVars: []string{"NTFY_ATTACHMENT_TOTAL_SIZE_LIMIT"}, Value: util.FormatSize(server.DefaultAttachmentTotalSizeLimit), Usage: "limit of the on-disk attachment cache"}),
altsrc.NewStringFlag(&cli.StringFlag{Name: "attachment-file-size-limit", Aliases: []string{"attachment_file_size_limit", "Y"}, EnvVars: []string{"NTFY_ATTACHMENT_FILE_SIZE_LIMIT"}, Value: util.FormatSize(server.DefaultAttachmentFileSizeLimit), Usage: "per-file attachment size limit (e.g. 300k, 2M, 100M)"}),
altsrc.NewStringFlag(&cli.StringFlag{Name: "attachment-expiry-duration", Aliases: []string{"attachment_expiry_duration", "X"}, EnvVars: []string{"NTFY_ATTACHMENT_EXPIRY_DURATION"}, Value: util.FormatDuration(server.DefaultAttachmentExpiryDuration), Usage: "duration after which uploaded attachments will be deleted (e.g. 3h, 20h)"}),
altsrc.NewStringFlag(&cli.StringFlag{Name: "keepalive-interval", Aliases: []string{"keepalive_interval", "k"}, EnvVars: []string{"NTFY_KEEPALIVE_INTERVAL"}, Value: util.FormatDuration(server.DefaultKeepaliveInterval), Usage: "interval of keepalive messages"}),
altsrc.NewStringFlag(&cli.StringFlag{Name: "manager-interval", Aliases: []string{"manager_interval", "m"}, EnvVars: []string{"NTFY_MANAGER_INTERVAL"}, Value: util.FormatDuration(server.DefaultManagerInterval), Usage: "interval of for message pruning and stats printing"}),
altsrc.NewStringSliceFlag(&cli.StringSliceFlag{Name: "disallowed-topics", Aliases: []string{"disallowed_topics"}, EnvVars: []string{"NTFY_DISALLOWED_TOPICS"}, Usage: "topics that are not allowed to be used"}),
altsrc.NewStringFlag(&cli.StringFlag{Name: "web-root", Aliases: []string{"web_root"}, EnvVars: []string{"NTFY_WEB_ROOT"}, Value: "/", Usage: "sets root of the web app (e.g. /, or /app), or disables it (disable)"}),
altsrc.NewBoolFlag(&cli.BoolFlag{Name: "enable-signup", Aliases: []string{"enable_signup"}, EnvVars: []string{"NTFY_ENABLE_SIGNUP"}, Value: false, Usage: "allows users to sign up via the web app, or API"}),
@@ -76,24 +75,35 @@ var flagsServe = append(
altsrc.NewStringFlag(&cli.StringFlag{Name: "twilio-auth-token", Aliases: []string{"twilio_auth_token"}, EnvVars: []string{"NTFY_TWILIO_AUTH_TOKEN"}, Usage: "Twilio auth token"}),
altsrc.NewStringFlag(&cli.StringFlag{Name: "twilio-phone-number", Aliases: []string{"twilio_phone_number"}, EnvVars: []string{"NTFY_TWILIO_PHONE_NUMBER"}, Usage: "Twilio number to use for outgoing calls"}),
altsrc.NewStringFlag(&cli.StringFlag{Name: "twilio-verify-service", Aliases: []string{"twilio_verify_service"}, EnvVars: []string{"NTFY_TWILIO_VERIFY_SERVICE"}, Usage: "Twilio Verify service ID, used for phone number verification"}),
altsrc.NewStringFlag(&cli.StringFlag{Name: "message-size-limit", Aliases: []string{"message_size_limit"}, EnvVars: []string{"NTFY_MESSAGE_SIZE_LIMIT"}, Value: util.FormatSize(server.DefaultMessageSizeLimit), Usage: "size limit for the message (see docs for limitations)"}),
altsrc.NewStringFlag(&cli.StringFlag{Name: "message-delay-limit", Aliases: []string{"message_delay_limit"}, EnvVars: []string{"NTFY_MESSAGE_DELAY_LIMIT"}, Value: util.FormatDuration(server.DefaultMessageDelayMax), Usage: "max duration a message can be scheduled into the future"}),
altsrc.NewIntFlag(&cli.IntFlag{Name: "global-topic-limit", Aliases: []string{"global_topic_limit", "T"}, EnvVars: []string{"NTFY_GLOBAL_TOPIC_LIMIT"}, Value: server.DefaultTotalTopicLimit, Usage: "total number of topics allowed"}),
altsrc.NewIntFlag(&cli.IntFlag{Name: "visitor-subscription-limit", Aliases: []string{"visitor_subscription_limit"}, EnvVars: []string{"NTFY_VISITOR_SUBSCRIPTION_LIMIT"}, Value: server.DefaultVisitorSubscriptionLimit, Usage: "number of subscriptions per visitor"}),
altsrc.NewStringFlag(&cli.StringFlag{Name: "visitor-attachment-total-size-limit", Aliases: []string{"visitor_attachment_total_size_limit"}, EnvVars: []string{"NTFY_VISITOR_ATTACHMENT_TOTAL_SIZE_LIMIT"}, Value: "100M", Usage: "total storage limit used for attachments per visitor"}),
altsrc.NewStringFlag(&cli.StringFlag{Name: "visitor-attachment-total-size-limit", Aliases: []string{"visitor_attachment_total_size_limit"}, EnvVars: []string{"NTFY_VISITOR_ATTACHMENT_TOTAL_SIZE_LIMIT"}, Value: util.FormatSize(server.DefaultVisitorAttachmentTotalSizeLimit), Usage: "total storage limit used for attachments per visitor"}),
altsrc.NewStringFlag(&cli.StringFlag{Name: "visitor-attachment-daily-bandwidth-limit", Aliases: []string{"visitor_attachment_daily_bandwidth_limit"}, EnvVars: []string{"NTFY_VISITOR_ATTACHMENT_DAILY_BANDWIDTH_LIMIT"}, Value: "500M", Usage: "total daily attachment download/upload bandwidth limit per visitor"}),
altsrc.NewIntFlag(&cli.IntFlag{Name: "visitor-request-limit-burst", Aliases: []string{"visitor_request_limit_burst"}, EnvVars: []string{"NTFY_VISITOR_REQUEST_LIMIT_BURST"}, Value: server.DefaultVisitorRequestLimitBurst, Usage: "initial limit of requests per visitor"}),
altsrc.NewDurationFlag(&cli.DurationFlag{Name: "visitor-request-limit-replenish", Aliases: []string{"visitor_request_limit_replenish"}, EnvVars: []string{"NTFY_VISITOR_REQUEST_LIMIT_REPLENISH"}, Value: server.DefaultVisitorRequestLimitReplenish, Usage: "interval at which burst limit is replenished (one per x)"}),
altsrc.NewStringFlag(&cli.StringFlag{Name: "visitor-request-limit-replenish", Aliases: []string{"visitor_request_limit_replenish"}, EnvVars: []string{"NTFY_VISITOR_REQUEST_LIMIT_REPLENISH"}, Value: util.FormatDuration(server.DefaultVisitorRequestLimitReplenish), Usage: "interval at which burst limit is replenished (one per x)"}),
altsrc.NewStringFlag(&cli.StringFlag{Name: "visitor-request-limit-exempt-hosts", Aliases: []string{"visitor_request_limit_exempt_hosts"}, EnvVars: []string{"NTFY_VISITOR_REQUEST_LIMIT_EXEMPT_HOSTS"}, Value: "", Usage: "hostnames and/or IP addresses of hosts that will be exempt from the visitor request limit"}),
altsrc.NewIntFlag(&cli.IntFlag{Name: "visitor-message-daily-limit", Aliases: []string{"visitor_message_daily_limit"}, EnvVars: []string{"NTFY_VISITOR_MESSAGE_DAILY_LIMIT"}, Value: server.DefaultVisitorMessageDailyLimit, Usage: "max messages per visitor per day, derived from request limit if unset"}),
altsrc.NewIntFlag(&cli.IntFlag{Name: "visitor-email-limit-burst", Aliases: []string{"visitor_email_limit_burst"}, EnvVars: []string{"NTFY_VISITOR_EMAIL_LIMIT_BURST"}, Value: server.DefaultVisitorEmailLimitBurst, Usage: "initial limit of e-mails per visitor"}),
altsrc.NewDurationFlag(&cli.DurationFlag{Name: "visitor-email-limit-replenish", Aliases: []string{"visitor_email_limit_replenish"}, EnvVars: []string{"NTFY_VISITOR_EMAIL_LIMIT_REPLENISH"}, Value: server.DefaultVisitorEmailLimitReplenish, Usage: "interval at which burst limit is replenished (one per x)"}),
altsrc.NewStringFlag(&cli.StringFlag{Name: "visitor-email-limit-replenish", Aliases: []string{"visitor_email_limit_replenish"}, EnvVars: []string{"NTFY_VISITOR_EMAIL_LIMIT_REPLENISH"}, Value: util.FormatDuration(server.DefaultVisitorEmailLimitReplenish), Usage: "interval at which burst limit is replenished (one per x)"}),
altsrc.NewBoolFlag(&cli.BoolFlag{Name: "visitor-subscriber-rate-limiting", Aliases: []string{"visitor_subscriber_rate_limiting"}, EnvVars: []string{"NTFY_VISITOR_SUBSCRIBER_RATE_LIMITING"}, Value: false, Usage: "enables subscriber-based rate limiting"}),
altsrc.NewBoolFlag(&cli.BoolFlag{Name: "behind-proxy", Aliases: []string{"behind_proxy", "P"}, EnvVars: []string{"NTFY_BEHIND_PROXY"}, Value: false, Usage: "if set, use X-Forwarded-For header to determine visitor IP address (for rate limiting)"}),
altsrc.NewBoolFlag(&cli.BoolFlag{Name: "behind-proxy", Aliases: []string{"behind_proxy", "P"}, EnvVars: []string{"NTFY_BEHIND_PROXY"}, Value: false, Usage: "if set, use forwarded header (e.g. X-Forwarded-For, X-Client-IP) to determine visitor IP address (for rate limiting)"}),
altsrc.NewStringFlag(&cli.StringFlag{Name: "proxy-forwarded-header", Aliases: []string{"proxy_forwarded_header"}, EnvVars: []string{"NTFY_PROXY_FORWARDED_HEADER"}, Value: "X-Forwarded-For", Usage: "use specified header to determine visitor IP address (for rate limiting)"}),
altsrc.NewStringFlag(&cli.StringFlag{Name: "proxy-trusted-addresses", Aliases: []string{"proxy_trusted_addresses"}, EnvVars: []string{"NTFY_PROXY_TRUSTED_ADDRESSES"}, Value: "", Usage: "comma-separated list of trusted IP addresses to remove from forwarded header"}),
altsrc.NewStringFlag(&cli.StringFlag{Name: "stripe-secret-key", Aliases: []string{"stripe_secret_key"}, EnvVars: []string{"NTFY_STRIPE_SECRET_KEY"}, Value: "", Usage: "key used for the Stripe API communication, this enables payments"}),
altsrc.NewStringFlag(&cli.StringFlag{Name: "stripe-webhook-key", Aliases: []string{"stripe_webhook_key"}, EnvVars: []string{"NTFY_STRIPE_WEBHOOK_KEY"}, Value: "", Usage: "key required to validate the authenticity of incoming webhooks from Stripe"}),
altsrc.NewStringFlag(&cli.StringFlag{Name: "billing-contact", Aliases: []string{"billing_contact"}, EnvVars: []string{"NTFY_BILLING_CONTACT"}, Value: "", Usage: "e-mail or website to display in upgrade dialog (only if payments are enabled)"}),
altsrc.NewBoolFlag(&cli.BoolFlag{Name: "enable-metrics", Aliases: []string{"enable_metrics"}, EnvVars: []string{"NTFY_ENABLE_METRICS"}, Value: false, Usage: "if set, Prometheus metrics are exposed via the /metrics endpoint"}),
altsrc.NewStringFlag(&cli.StringFlag{Name: "metrics-listen-http", Aliases: []string{"metrics_listen_http"}, EnvVars: []string{"NTFY_METRICS_LISTEN_HTTP"}, Usage: "ip:port used to expose the metrics endpoint (implicitly enables metrics)"}),
altsrc.NewStringFlag(&cli.StringFlag{Name: "profile-listen-http", Aliases: []string{"profile_listen_http"}, EnvVars: []string{"NTFY_PROFILE_LISTEN_HTTP"}, Usage: "ip:port used to expose the profiling endpoints (implicitly enables profiling)"}),
altsrc.NewStringFlag(&cli.StringFlag{Name: "web-push-public-key", Aliases: []string{"web_push_public_key"}, EnvVars: []string{"NTFY_WEB_PUSH_PUBLIC_KEY"}, Usage: "public key used for web push notifications"}),
altsrc.NewStringFlag(&cli.StringFlag{Name: "web-push-private-key", Aliases: []string{"web_push_private_key"}, EnvVars: []string{"NTFY_WEB_PUSH_PRIVATE_KEY"}, Usage: "private key used for web push notifications"}),
altsrc.NewStringFlag(&cli.StringFlag{Name: "web-push-file", Aliases: []string{"web_push_file"}, EnvVars: []string{"NTFY_WEB_PUSH_FILE"}, Usage: "file used to store web push subscriptions"}),
altsrc.NewStringFlag(&cli.StringFlag{Name: "web-push-email-address", Aliases: []string{"web_push_email_address"}, EnvVars: []string{"NTFY_WEB_PUSH_EMAIL_ADDRESS"}, Usage: "e-mail address of sender, required to use browser push services"}),
altsrc.NewStringFlag(&cli.StringFlag{Name: "web-push-startup-queries", Aliases: []string{"web_push_startup_queries"}, EnvVars: []string{"NTFY_WEB_PUSH_STARTUP_QUERIES"}, Usage: "queries run when the web push database is initialized"}),
altsrc.NewStringFlag(&cli.StringFlag{Name: "web-push-expiry-duration", Aliases: []string{"web_push_expiry_duration"}, EnvVars: []string{"NTFY_WEB_PUSH_EXPIRY_DURATION"}, Value: util.FormatDuration(server.DefaultWebPushExpiryDuration), Usage: "automatically expire unused subscriptions after this time"}),
altsrc.NewStringFlag(&cli.StringFlag{Name: "web-push-expiry-warning-duration", Aliases: []string{"web_push_expiry_warning_duration"}, EnvVars: []string{"NTFY_WEB_PUSH_EXPIRY_WARNING_DURATION"}, Value: util.FormatDuration(server.DefaultWebPushExpiryWarningDuration), Usage: "send web push warning notification after this time before expiring unused subscriptions"}),
)
var cmdServe = &cli.Command{
@@ -121,7 +131,7 @@ func execServe(c *cli.Context) error {
// Read all the options
config := c.String("config")
baseURL := c.String("base-url")
baseURL := strings.TrimSuffix(c.String("base-url"), "/")
listenHTTP := c.String("listen-http")
listenHTTPS := c.String("listen-https")
listenUnix := c.String("listen-unix")
@@ -129,20 +139,27 @@ func execServe(c *cli.Context) error {
keyFile := c.String("key-file")
certFile := c.String("cert-file")
firebaseKeyFile := c.String("firebase-key-file")
webPushPrivateKey := c.String("web-push-private-key")
webPushPublicKey := c.String("web-push-public-key")
webPushFile := c.String("web-push-file")
webPushEmailAddress := c.String("web-push-email-address")
webPushStartupQueries := c.String("web-push-startup-queries")
webPushExpiryDurationStr := c.String("web-push-expiry-duration")
webPushExpiryWarningDurationStr := c.String("web-push-expiry-warning-duration")
cacheFile := c.String("cache-file")
cacheDuration := c.Duration("cache-duration")
cacheDurationStr := c.String("cache-duration")
cacheStartupQueries := c.String("cache-startup-queries")
cacheBatchSize := c.Int("cache-batch-size")
cacheBatchTimeout := c.Duration("cache-batch-timeout")
cacheBatchTimeoutStr := c.String("cache-batch-timeout")
authFile := c.String("auth-file")
authStartupQueries := c.String("auth-startup-queries")
authDefaultAccess := c.String("auth-default-access")
attachmentCacheDir := c.String("attachment-cache-dir")
attachmentTotalSizeLimitStr := c.String("attachment-total-size-limit")
attachmentFileSizeLimitStr := c.String("attachment-file-size-limit")
attachmentExpiryDuration := c.Duration("attachment-expiry-duration")
keepaliveInterval := c.Duration("keepalive-interval")
managerInterval := c.Duration("manager-interval")
attachmentExpiryDurationStr := c.String("attachment-expiry-duration")
keepaliveIntervalStr := c.String("keepalive-interval")
managerIntervalStr := c.String("manager-interval")
disallowedTopics := c.StringSlice("disallowed-topics")
webRoot := c.String("web-root")
enableSignup := c.Bool("enable-signup")
@@ -161,18 +178,22 @@ func execServe(c *cli.Context) error {
twilioAuthToken := c.String("twilio-auth-token")
twilioPhoneNumber := c.String("twilio-phone-number")
twilioVerifyService := c.String("twilio-verify-service")
messageSizeLimitStr := c.String("message-size-limit")
messageDelayLimitStr := c.String("message-delay-limit")
totalTopicLimit := c.Int("global-topic-limit")
visitorSubscriptionLimit := c.Int("visitor-subscription-limit")
visitorSubscriberRateLimiting := c.Bool("visitor-subscriber-rate-limiting")
visitorAttachmentTotalSizeLimitStr := c.String("visitor-attachment-total-size-limit")
visitorAttachmentDailyBandwidthLimitStr := c.String("visitor-attachment-daily-bandwidth-limit")
visitorRequestLimitBurst := c.Int("visitor-request-limit-burst")
visitorRequestLimitReplenish := c.Duration("visitor-request-limit-replenish")
visitorRequestLimitReplenishStr := c.String("visitor-request-limit-replenish")
visitorRequestLimitExemptHosts := util.SplitNoEmpty(c.String("visitor-request-limit-exempt-hosts"), ",")
visitorMessageDailyLimit := c.Int("visitor-message-daily-limit")
visitorEmailLimitBurst := c.Int("visitor-email-limit-burst")
visitorEmailLimitReplenish := c.Duration("visitor-email-limit-replenish")
visitorEmailLimitReplenishStr := c.String("visitor-email-limit-replenish")
behindProxy := c.Bool("behind-proxy")
proxyForwardedHeader := c.String("proxy-forwarded-header")
proxyTrustedAddresses := util.SplitNoEmpty(c.String("proxy-trusted-addresses"), ",")
stripeSecretKey := c.String("stripe-secret-key")
stripeWebhookKey := c.String("stripe-webhook-key")
billingContact := c.String("billing-contact")
@@ -180,9 +201,77 @@ func execServe(c *cli.Context) error {
enableMetrics := c.Bool("enable-metrics") || metricsListenHTTP != ""
profileListenHTTP := c.String("profile-listen-http")
// Convert durations
cacheDuration, err := util.ParseDuration(cacheDurationStr)
if err != nil {
return fmt.Errorf("invalid cache duration: %s", cacheDurationStr)
}
cacheBatchTimeout, err := util.ParseDuration(cacheBatchTimeoutStr)
if err != nil {
return fmt.Errorf("invalid cache batch timeout: %s", cacheBatchTimeoutStr)
}
attachmentExpiryDuration, err := util.ParseDuration(attachmentExpiryDurationStr)
if err != nil {
return fmt.Errorf("invalid attachment expiry duration: %s", attachmentExpiryDurationStr)
}
keepaliveInterval, err := util.ParseDuration(keepaliveIntervalStr)
if err != nil {
return fmt.Errorf("invalid keepalive interval: %s", keepaliveIntervalStr)
}
managerInterval, err := util.ParseDuration(managerIntervalStr)
if err != nil {
return fmt.Errorf("invalid manager interval: %s", managerIntervalStr)
}
messageDelayLimit, err := util.ParseDuration(messageDelayLimitStr)
if err != nil {
return fmt.Errorf("invalid message delay limit: %s", messageDelayLimitStr)
}
visitorRequestLimitReplenish, err := util.ParseDuration(visitorRequestLimitReplenishStr)
if err != nil {
return fmt.Errorf("invalid visitor request limit replenish: %s", visitorRequestLimitReplenishStr)
}
visitorEmailLimitReplenish, err := util.ParseDuration(visitorEmailLimitReplenishStr)
if err != nil {
return fmt.Errorf("invalid visitor email limit replenish: %s", visitorEmailLimitReplenishStr)
}
webPushExpiryDuration, err := util.ParseDuration(webPushExpiryDurationStr)
if err != nil {
return fmt.Errorf("invalid web push expiry duration: %s", webPushExpiryDurationStr)
}
webPushExpiryWarningDuration, err := util.ParseDuration(webPushExpiryWarningDurationStr)
if err != nil {
return fmt.Errorf("invalid web push expiry warning duration: %s", webPushExpiryWarningDurationStr)
}
// Convert sizes to bytes
messageSizeLimit, err := util.ParseSize(messageSizeLimitStr)
if err != nil {
return fmt.Errorf("invalid message size limit: %s", messageSizeLimitStr)
}
attachmentTotalSizeLimit, err := util.ParseSize(attachmentTotalSizeLimitStr)
if err != nil {
return fmt.Errorf("invalid attachment total size limit: %s", attachmentTotalSizeLimitStr)
}
attachmentFileSizeLimit, err := util.ParseSize(attachmentFileSizeLimitStr)
if err != nil {
return fmt.Errorf("invalid attachment file size limit: %s", attachmentFileSizeLimitStr)
}
visitorAttachmentTotalSizeLimit, err := util.ParseSize(visitorAttachmentTotalSizeLimitStr)
if err != nil {
return fmt.Errorf("invalid visitor attachment total size limit: %s", visitorAttachmentTotalSizeLimitStr)
}
visitorAttachmentDailyBandwidthLimit, err := util.ParseSize(visitorAttachmentDailyBandwidthLimitStr)
if err != nil {
return fmt.Errorf("invalid visitor attachment daily bandwidth limit: %s", visitorAttachmentDailyBandwidthLimitStr)
} else if visitorAttachmentDailyBandwidthLimit > math.MaxInt {
return fmt.Errorf("config option visitor-attachment-daily-bandwidth-limit must be lower than %d", math.MaxInt)
}
// Check values
if firebaseKeyFile != "" && !util.FileExists(firebaseKeyFile) {
return errors.New("if set, FCM key file must exist")
} else if webPushPublicKey != "" && (webPushPrivateKey == "" || webPushFile == "" || webPushEmailAddress == "" || baseURL == "") {
return errors.New("if web push is enabled, web-push-private-key, web-push-public-key, web-push-file, web-push-email-address, and base-url should be set. run 'ntfy webpush keys' to generate keys")
} else if keepaliveInterval < 5*time.Second {
return errors.New("keepalive interval cannot be lower than five seconds")
} else if managerInterval < 5*time.Second {
@@ -201,10 +290,15 @@ func execServe(c *cli.Context) error {
return errors.New("if smtp-server-listen is set, smtp-server-domain must also be set")
} else if attachmentCacheDir != "" && baseURL == "" {
return errors.New("if attachment-cache-dir is set, base-url must also be set")
} else if baseURL != "" && !strings.HasPrefix(baseURL, "http://") && !strings.HasPrefix(baseURL, "https://") {
return errors.New("if set, base-url must start with http:// or https://")
} else if baseURL != "" && strings.HasSuffix(baseURL, "/") {
return errors.New("if set, base-url must not end with a slash (/)")
} else if baseURL != "" {
u, err := url.Parse(baseURL)
if err != nil {
return fmt.Errorf("if set, base-url must be a valid URL, e.g. https://ntfy.mydomain.com: %v", err)
} else if u.Scheme != "http" && u.Scheme != "https" {
return errors.New("if set, base-url must be a valid URL starting with http:// or https://, e.g. https://ntfy.mydomain.com")
} else if u.Path != "" {
return fmt.Errorf("if set, base-url must not have a path (%s), as hosting ntfy on a sub-path is not supported, e.g. https://ntfy.mydomain.com", u.Path)
}
} else if upstreamBaseURL != "" && !strings.HasPrefix(upstreamBaseURL, "http://") && !strings.HasPrefix(upstreamBaseURL, "https://") {
return errors.New("if set, upstream-base-url must start with http:// or https://")
} else if upstreamBaseURL != "" && strings.HasSuffix(upstreamBaseURL, "/") {
@@ -221,6 +315,15 @@ func execServe(c *cli.Context) error {
return errors.New("if stripe-secret-key is set, stripe-webhook-key and base-url must also be set")
} else if twilioAccount != "" && (twilioAuthToken == "" || twilioPhoneNumber == "" || twilioVerifyService == "" || baseURL == "" || authFile == "") {
return errors.New("if twilio-account is set, twilio-auth-token, twilio-phone-number, twilio-verify-service, base-url, and auth-file must also be set")
} else if messageSizeLimit > server.DefaultMessageSizeLimit {
log.Warn("message-size-limit is greater than 4K, this is not recommended and largely untested, and may lead to issues with some clients")
if messageSizeLimit > 5*1024*1024 {
return errors.New("message-size-limit cannot be higher than 5M")
}
} else if webPushExpiryWarningDuration > 0 && webPushExpiryWarningDuration > webPushExpiryDuration {
return errors.New("web push expiry warning duration cannot be higher than web push expiry duration")
} else if behindProxy && proxyForwardedHeader == "" {
return errors.New("if behind-proxy is set, proxy-forwarded-header must also be set")
}
// Backwards compatibility
@@ -245,26 +348,6 @@ func execServe(c *cli.Context) error {
listenHTTP = ""
}
// Convert sizes to bytes
attachmentTotalSizeLimit, err := parseSize(attachmentTotalSizeLimitStr, server.DefaultAttachmentTotalSizeLimit)
if err != nil {
return err
}
attachmentFileSizeLimit, err := parseSize(attachmentFileSizeLimitStr, server.DefaultAttachmentFileSizeLimit)
if err != nil {
return err
}
visitorAttachmentTotalSizeLimit, err := parseSize(visitorAttachmentTotalSizeLimitStr, server.DefaultVisitorAttachmentTotalSizeLimit)
if err != nil {
return err
}
visitorAttachmentDailyBandwidthLimit, err := parseSize(visitorAttachmentDailyBandwidthLimitStr, server.DefaultVisitorAttachmentDailyBandwidthLimit)
if err != nil {
return err
} else if visitorAttachmentDailyBandwidthLimit > math.MaxInt {
return fmt.Errorf("config option visitor-attachment-daily-bandwidth-limit must be lower than %d", math.MaxInt)
}
// Resolve hosts
visitorRequestLimitExemptIPs := make([]netip.Prefix, 0)
for _, host := range visitorRequestLimitExemptHosts {
@@ -325,6 +408,8 @@ func execServe(c *cli.Context) error {
conf.TwilioAuthToken = twilioAuthToken
conf.TwilioPhoneNumber = twilioPhoneNumber
conf.TwilioVerifyService = twilioVerifyService
conf.MessageSizeLimit = int(messageSizeLimit)
conf.MessageDelayMax = messageDelayLimit
conf.TotalTopicLimit = totalTopicLimit
conf.VisitorSubscriptionLimit = visitorSubscriptionLimit
conf.VisitorAttachmentTotalSizeLimit = visitorAttachmentTotalSizeLimit
@@ -337,6 +422,8 @@ func execServe(c *cli.Context) error {
conf.VisitorEmailLimitReplenish = visitorEmailLimitReplenish
conf.VisitorSubscriberRateLimiting = visitorSubscriberRateLimiting
conf.BehindProxy = behindProxy
conf.ProxyForwardedHeader = proxyForwardedHeader
conf.ProxyTrustedAddresses = proxyTrustedAddresses
conf.StripeSecretKey = stripeSecretKey
conf.StripeWebhookKey = stripeWebhookKey
conf.BillingContact = billingContact
@@ -347,6 +434,13 @@ func execServe(c *cli.Context) error {
conf.MetricsListenHTTP = metricsListenHTTP
conf.ProfileListenHTTP = profileListenHTTP
conf.Version = c.App.Version
conf.WebPushPrivateKey = webPushPrivateKey
conf.WebPushPublicKey = webPushPublicKey
conf.WebPushFile = webPushFile
conf.WebPushEmailAddress = webPushEmailAddress
conf.WebPushStartupQueries = webPushStartupQueries
conf.WebPushExpiryDuration = webPushExpiryDuration
conf.WebPushExpiryWarningDuration = webPushExpiryWarningDuration
// Set up hot-reloading of config
go sigHandlerConfigReload(config)
@@ -354,25 +448,14 @@ func execServe(c *cli.Context) error {
// Run server
s, err := server.New(conf)
if err != nil {
log.Fatal(err.Error())
log.Fatal("%s", err.Error())
} else if err := s.Run(); err != nil {
log.Fatal(err.Error())
log.Fatal("%s", err.Error())
}
log.Info("Exiting.")
return nil
}
func parseSize(s string, defaultValue int64) (v int64, err error) {
if s == "" {
return defaultValue, nil
}
v, err = util.ParseSize(s)
if err != nil {
return 0, err
}
return v, nil
}
func sigHandlerConfigReload(config string) {
sigs := make(chan os.Signal, 1)
signal.Notify(sigs, syscall.SIGHUP)

View File

@@ -12,15 +12,11 @@ import (
"github.com/gorilla/websocket"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"heckel.io/ntfy/client"
"heckel.io/ntfy/test"
"heckel.io/ntfy/util"
"heckel.io/ntfy/v2/client"
"heckel.io/ntfy/v2/test"
"heckel.io/ntfy/v2/util"
)
func init() {
rand.Seed(time.Now().UnixMilli())
}
func TestCLI_Serve_Unix_Curl(t *testing.T) {
sockFile := filepath.Join(t.TempDir(), "ntfy.sock")
configFile := newEmptyFile(t) // Avoid issues with existing server.yml file on system

View File

@@ -4,9 +4,9 @@ import (
"errors"
"fmt"
"github.com/urfave/cli/v2"
"heckel.io/ntfy/client"
"heckel.io/ntfy/log"
"heckel.io/ntfy/util"
"heckel.io/ntfy/v2/client"
"heckel.io/ntfy/v2/log"
"heckel.io/ntfy/v2/util"
"os"
"os/exec"
"os/user"
@@ -225,12 +225,17 @@ func doSubscribe(c *cli.Context, cl *client.Client, conf *client.Config, topic,
}
func maybeAddAuthHeader(s client.Subscribe, conf *client.Config) client.SubscribeOption {
// check for subscription token then subscription user:pass
if s.Token != "" {
return client.WithBearerAuth(s.Token)
// if an explicit empty token or empty user:pass is given, exit without auth
if (s.Token != nil && *s.Token == "") || (s.User != nil && *s.User == "" && s.Password != nil && *s.Password == "") {
return client.WithEmptyAuth()
}
if s.User != "" && s.Password != nil {
return client.WithBasicAuth(s.User, *s.Password)
// check for subscription token then subscription user:pass
if s.Token != nil && *s.Token != "" {
return client.WithBearerAuth(*s.Token)
}
if s.User != nil && *s.User != "" && s.Password != nil {
return client.WithBasicAuth(*s.User, *s.Password)
}
// if no subscription token nor subscription user:pass, check for default token then default user:pass
@@ -305,28 +310,43 @@ func loadConfig(c *cli.Context) (*client.Config, error) {
if filename != "" {
return client.LoadConfig(filename)
}
configFile := defaultClientConfigFile()
if s, _ := os.Stat(configFile); s != nil {
return client.LoadConfig(configFile)
configFile, err := defaultClientConfigFile()
if err != nil {
log.Warn("Could not determine default client config file: %s", err.Error())
} else {
if s, _ := os.Stat(configFile); s != nil {
return client.LoadConfig(configFile)
}
log.Debug("Config file %s not found", configFile)
}
log.Debug("Loading default config")
return client.NewConfig(), nil
}
//lint:ignore U1000 Conditionally used in different builds
func defaultClientConfigFileUnix() string {
u, _ := user.Current()
func defaultClientConfigFileUnix() (string, error) {
u, err := user.Current()
if err != nil {
return "", fmt.Errorf("could not determine current user: %w", err)
}
configFile := clientRootConfigFileUnixAbsolute
if u.Uid != "0" {
homeDir, _ := os.UserConfigDir()
return filepath.Join(homeDir, clientUserConfigFileUnixRelative)
homeDir, err := os.UserConfigDir()
if err != nil {
return "", fmt.Errorf("could not determine user config dir: %w", err)
}
return filepath.Join(homeDir, clientUserConfigFileUnixRelative), nil
}
return configFile
return configFile, nil
}
//lint:ignore U1000 Conditionally used in different builds
func defaultClientConfigFileWindows() string {
homeDir, _ := os.UserConfigDir()
return filepath.Join(homeDir, clientUserConfigFileWindowsRelative)
func defaultClientConfigFileWindows() (string, error) {
homeDir, err := os.UserConfigDir()
if err != nil {
return "", fmt.Errorf("could not determine user config dir: %w", err)
}
return filepath.Join(homeDir, clientUserConfigFileWindowsRelative), nil
}
func logMessagePrefix(m *client.Message) string {

View File

@@ -11,6 +11,6 @@ var (
scriptLauncher = []string{"sh", "-c"}
)
func defaultClientConfigFile() string {
func defaultClientConfigFile() (string, error) {
return defaultClientConfigFileUnix()
}

View File

@@ -330,7 +330,7 @@ default-token: tk_AgQdq7mVBoFD37zQVN29RhuMzNIz2
app, _, stdout, _ := newTestApp()
require.Nil(t, app.Run([]string{"ntfy", "subscribe", "--poll", "--config=" + filename, "mytopic"}))
require.Nil(t, app.Run([]string{"ntfy", "subscribe", "--poll", "--from-config", "--config=" + filename, "mytopic"}))
require.Equal(t, message, strings.TrimSpace(stdout.String()))
}
@@ -355,7 +355,63 @@ default-password: mypass
app, _, stdout, _ := newTestApp()
require.Nil(t, app.Run([]string{"ntfy", "subscribe", "--poll", "--config=" + filename, "mytopic"}))
require.Nil(t, app.Run([]string{"ntfy", "subscribe", "--poll", "--from-config", "--config=" + filename, "mytopic"}))
require.Equal(t, message, strings.TrimSpace(stdout.String()))
}
func TestCLI_Subscribe_Override_Default_UserPass_With_Empty_UserPass(t *testing.T) {
message := `{"id":"RXIQBFaieLVr","time":124,"expires":1124,"event":"message","topic":"mytopic","message":"triggered"}`
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
require.Equal(t, "/mytopic/json", r.URL.Path)
require.Equal(t, "", r.Header.Get("Authorization"))
w.WriteHeader(http.StatusOK)
w.Write([]byte(message))
}))
defer server.Close()
filename := filepath.Join(t.TempDir(), "client.yml")
require.Nil(t, os.WriteFile(filename, []byte(fmt.Sprintf(`
default-host: %s
default-user: philipp
default-password: mypass
subscribe:
- topic: mytopic
user: ""
password: ""
`, server.URL)), 0600))
app, _, stdout, _ := newTestApp()
require.Nil(t, app.Run([]string{"ntfy", "subscribe", "--poll", "--from-config", "--config=" + filename}))
require.Equal(t, message, strings.TrimSpace(stdout.String()))
}
func TestCLI_Subscribe_Override_Default_Token_With_Empty_Token(t *testing.T) {
message := `{"id":"RXIQBFaieLVr","time":124,"expires":1124,"event":"message","topic":"mytopic","message":"triggered"}`
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
require.Equal(t, "/mytopic/json", r.URL.Path)
require.Equal(t, "", r.Header.Get("Authorization"))
w.WriteHeader(http.StatusOK)
w.Write([]byte(message))
}))
defer server.Close()
filename := filepath.Join(t.TempDir(), "client.yml")
require.Nil(t, os.WriteFile(filename, []byte(fmt.Sprintf(`
default-host: %s
default-token: tk_AgQdq7mVBoFD37zQVN29RhuMzNIz2
subscribe:
- topic: mytopic
token: ""
`, server.URL)), 0600))
app, _, stdout, _ := newTestApp()
require.Nil(t, app.Run([]string{"ntfy", "subscribe", "--poll", "--from-config", "--config=" + filename}))
require.Equal(t, message, strings.TrimSpace(stdout.String()))
}

View File

@@ -13,6 +13,6 @@ var (
scriptLauncher = []string{"sh", "-c"}
)
func defaultClientConfigFile() string {
func defaultClientConfigFile() (string, error) {
return defaultClientConfigFileUnix()
}

View File

@@ -10,6 +10,6 @@ var (
scriptLauncher = []string{"cmd.exe", "/Q", "/C"}
)
func defaultClientConfigFile() string {
func defaultClientConfigFile() (string, error) {
return defaultClientConfigFileWindows()
}

View File

@@ -6,8 +6,8 @@ import (
"errors"
"fmt"
"github.com/urfave/cli/v2"
"heckel.io/ntfy/user"
"heckel.io/ntfy/util"
"heckel.io/ntfy/v2/user"
"heckel.io/ntfy/v2/util"
)
func init() {
@@ -366,9 +366,9 @@ func printTier(c *cli.Context, tier *user.Tier) {
fmt.Fprintf(c.App.ErrWriter, "- Email limit: %d\n", tier.EmailLimit)
fmt.Fprintf(c.App.ErrWriter, "- Phone call limit: %d\n", tier.CallLimit)
fmt.Fprintf(c.App.ErrWriter, "- Reservation limit: %d\n", tier.ReservationLimit)
fmt.Fprintf(c.App.ErrWriter, "- Attachment file size limit: %s\n", util.FormatSize(tier.AttachmentFileSizeLimit))
fmt.Fprintf(c.App.ErrWriter, "- Attachment total size limit: %s\n", util.FormatSize(tier.AttachmentTotalSizeLimit))
fmt.Fprintf(c.App.ErrWriter, "- Attachment file size limit: %s\n", util.FormatSizeHuman(tier.AttachmentFileSizeLimit))
fmt.Fprintf(c.App.ErrWriter, "- Attachment total size limit: %s\n", util.FormatSizeHuman(tier.AttachmentTotalSizeLimit))
fmt.Fprintf(c.App.ErrWriter, "- Attachment expiry duration: %s (%d seconds)\n", tier.AttachmentExpiryDuration.String(), int64(tier.AttachmentExpiryDuration.Seconds()))
fmt.Fprintf(c.App.ErrWriter, "- Attachment daily bandwidth limit: %s\n", util.FormatSize(tier.AttachmentBandwidthLimit))
fmt.Fprintf(c.App.ErrWriter, "- Attachment daily bandwidth limit: %s\n", util.FormatSizeHuman(tier.AttachmentBandwidthLimit))
fmt.Fprintf(c.App.ErrWriter, "- Stripe prices (monthly/yearly): %s\n", prices)
}

View File

@@ -3,8 +3,8 @@ package cmd
import (
"github.com/stretchr/testify/require"
"github.com/urfave/cli/v2"
"heckel.io/ntfy/server"
"heckel.io/ntfy/test"
"heckel.io/ntfy/v2/server"
"heckel.io/ntfy/v2/test"
"testing"
)

View File

@@ -6,8 +6,8 @@ import (
"errors"
"fmt"
"github.com/urfave/cli/v2"
"heckel.io/ntfy/user"
"heckel.io/ntfy/util"
"heckel.io/ntfy/v2/user"
"heckel.io/ntfy/v2/util"
"net/netip"
"time"
)

View File

@@ -4,8 +4,8 @@ import (
"fmt"
"github.com/stretchr/testify/require"
"github.com/urfave/cli/v2"
"heckel.io/ntfy/server"
"heckel.io/ntfy/test"
"heckel.io/ntfy/v2/server"
"heckel.io/ntfy/v2/test"
"regexp"
"testing"
)

View File

@@ -6,13 +6,13 @@ import (
"crypto/subtle"
"errors"
"fmt"
"heckel.io/ntfy/user"
"heckel.io/ntfy/v2/user"
"os"
"strings"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v2/altsrc"
"heckel.io/ntfy/util"
"heckel.io/ntfy/v2/util"
)
const (
@@ -42,7 +42,7 @@ var cmdUser = &cli.Command{
Name: "add",
Aliases: []string{"a"},
Usage: "Adds a new user",
UsageText: "ntfy user add [--role=admin|user] USERNAME\nNTFY_PASSWORD=... ntfy user add [--role=admin|user] USERNAME",
UsageText: "ntfy user add [--role=admin|user] USERNAME\nNTFY_PASSWORD=... ntfy user add [--role=admin|user] USERNAME\nNTFY_PASSWORD_HASH=... ntfy user add [--role=admin|user] USERNAME",
Action: execUserAdd,
Flags: []cli.Flag{
&cli.StringFlag{Name: "role", Aliases: []string{"r"}, Value: string(user.RoleUser), Usage: "user role"},
@@ -55,12 +55,13 @@ granted otherwise by the auth-default-access setting). An admin user has read an
topics.
Examples:
ntfy user add phil # Add regular user phil
ntfy user add --role=admin phil # Add admin user phil
NTFY_PASSWORD=... ntfy user add phil # Add user, using env variable to set password (for scripts)
ntfy user add phil # Add regular user phil
ntfy user add --role=admin phil # Add admin user phil
NTFY_PASSWORD=... ntfy user add phil # Add user, using env variable to set password (for scripts)
NTFY_PASSWORD_HASH=... ntfy user add phil # Add user, using env variable to set password hash (for scripts)
You may set the NTFY_PASSWORD environment variable to pass the password. This is useful if
you are creating users via scripts.
You may set the NTFY_PASSWORD environment variable to pass the password, or NTFY_PASSWORD_HASH to pass
directly the bcrypt hash. This is useful if you are creating users via scripts.
`,
},
{
@@ -79,7 +80,7 @@ Example:
Name: "change-pass",
Aliases: []string{"chp"},
Usage: "Changes a user's password",
UsageText: "ntfy user change-pass USERNAME\nNTFY_PASSWORD=... ntfy user change-pass USERNAME",
UsageText: "ntfy user change-pass USERNAME\nNTFY_PASSWORD=... ntfy user change-pass USERNAME\nNTFY_PASSWORD_HASH=... ntfy user change-pass USERNAME",
Action: execUserChangePass,
Description: `Change the password for the given user.
@@ -89,9 +90,10 @@ it twice.
Example:
ntfy user change-pass phil
NTFY_PASSWORD=.. ntfy user change-pass phil
NTFY_PASSWORD_HASH=.. ntfy user change-pass phil
You may set the NTFY_PASSWORD environment variable to pass the new password. This is
useful if you are updating users via scripts.
You may set the NTFY_PASSWORD environment variable to pass the new password or NTFY_PASSWORD_HASH to pass
directly the bcrypt hash. This is useful if you are updating users via scripts.
`,
},
@@ -174,7 +176,12 @@ variable to pass the new password. This is useful if you are creating/updating u
func execUserAdd(c *cli.Context) error {
username := c.Args().Get(0)
role := user.Role(c.String("role"))
password := os.Getenv("NTFY_PASSWORD")
password, hashed := os.LookupEnv("NTFY_PASSWORD_HASH")
if !hashed {
password = os.Getenv("NTFY_PASSWORD")
}
if username == "" {
return errors.New("username expected, type 'ntfy user add --help' for help")
} else if username == userEveryone || username == user.Everyone {
@@ -198,10 +205,9 @@ func execUserAdd(c *cli.Context) error {
if err != nil {
return err
}
password = p
}
if err := manager.AddUser(username, password, role); err != nil {
if err := manager.AddUser(username, password, role, hashed); err != nil {
return err
}
fmt.Fprintf(c.App.ErrWriter, "user %s added with role %s\n", username, role)
@@ -231,7 +237,11 @@ func execUserDel(c *cli.Context) error {
func execUserChangePass(c *cli.Context) error {
username := c.Args().Get(0)
password := os.Getenv("NTFY_PASSWORD")
password, hashed := os.LookupEnv("NTFY_PASSWORD_HASH")
if !hashed {
password = os.Getenv("NTFY_PASSWORD")
}
if username == "" {
return errors.New("username expected, type 'ntfy user change-pass --help' for help")
} else if username == userEveryone || username == user.Everyone {
@@ -250,7 +260,7 @@ func execUserChangePass(c *cli.Context) error {
return err
}
}
if err := manager.ChangePassword(username, password); err != nil {
if err := manager.ChangePassword(username, password, hashed); err != nil {
return err
}
fmt.Fprintf(c.App.ErrWriter, "changed password for user %s\n", username)
@@ -343,6 +353,8 @@ func readPasswordAndConfirm(c *cli.Context) (string, error) {
password, err := util.ReadPassword(c.App.Reader)
if err != nil {
return "", err
} else if len(password) == 0 {
return "", errors.New("password cannot be empty")
}
fmt.Fprintf(c.App.ErrWriter, "\r%s\rconfirm: ", strings.Repeat(" ", 25))
confirm, err := util.ReadPassword(c.App.Reader)

View File

@@ -3,9 +3,9 @@ package cmd
import (
"github.com/stretchr/testify/require"
"github.com/urfave/cli/v2"
"heckel.io/ntfy/server"
"heckel.io/ntfy/test"
"heckel.io/ntfy/user"
"heckel.io/ntfy/v2/server"
"heckel.io/ntfy/v2/test"
"heckel.io/ntfy/v2/user"
"os"
"path/filepath"
"testing"

69
cmd/webpush.go Normal file
View File

@@ -0,0 +1,69 @@
//go:build !noserver
package cmd
import (
"fmt"
"os"
"github.com/SherClockHolmes/webpush-go"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v2/altsrc"
)
var flagsWebPush = append(
[]cli.Flag{},
altsrc.NewStringFlag(&cli.StringFlag{Name: "output-file", Aliases: []string{"f"}, Usage: "write VAPID keys to this file"}),
)
func init() {
commands = append(commands, cmdWebPush)
}
var cmdWebPush = &cli.Command{
Name: "webpush",
Usage: "Generate keys, in the future manage web push subscriptions",
UsageText: "ntfy webpush [keys]",
Category: categoryServer,
Subcommands: []*cli.Command{
{
Action: generateWebPushKeys,
Name: "keys",
Usage: "Generate VAPID keys to enable browser background push notifications",
UsageText: "ntfy webpush keys",
Category: categoryServer,
Flags: flagsWebPush,
},
},
}
func generateWebPushKeys(c *cli.Context) error {
privateKey, publicKey, err := webpush.GenerateVAPIDKeys()
if err != nil {
return err
}
if outputFile := c.String("output-file"); outputFile != "" {
contents := fmt.Sprintf(`---
web-push-public-key: %s
web-push-private-key: %s
`, publicKey, privateKey)
err = os.WriteFile(outputFile, []byte(contents), 0660)
if err != nil {
return err
}
_, err = fmt.Fprintf(c.App.ErrWriter, "Web Push keys written to %s.\n", outputFile)
} else {
_, err = fmt.Fprintf(c.App.ErrWriter, `Web Push keys generated. Add the following lines to your config file:
web-push-public-key: %s
web-push-private-key: %s
web-push-file: /var/cache/ntfy/webpush.db # or similar
web-push-email-address: <email address>
See https://ntfy.sh/docs/config/#web-push for details.
`, publicKey, privateKey)
}
return err
}

31
cmd/webpush_test.go Normal file
View File

@@ -0,0 +1,31 @@
package cmd
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/urfave/cli/v2"
"heckel.io/ntfy/v2/server"
)
func TestCLI_WebPush_GenerateKeys(t *testing.T) {
app, _, _, stderr := newTestApp()
require.Nil(t, runWebPushCommand(app, server.NewConfig(), "keys"))
require.Contains(t, stderr.String(), "Web Push keys generated.")
}
func TestCLI_WebPush_WriteKeysToFile(t *testing.T) {
app, _, _, stderr := newTestApp()
require.Nil(t, runWebPushCommand(app, server.NewConfig(), "keys", "--output-file=key-file.yaml"))
require.Contains(t, stderr.String(), "Web Push keys written to key-file.yaml")
require.FileExists(t, "key-file.yaml")
}
func runWebPushCommand(app *cli.App, conf *server.Config, args ...string) error {
webPushArgs := []string{
"ntfy",
"--log-level=ERROR",
"webpush",
}
return app.Run(append(webPushArgs, args...))
}

View File

@@ -24,7 +24,7 @@ get a list of [command line options](#command-line-options).
The most basic settings are `base-url` (the external URL of the ntfy server), the HTTP/HTTPS listen address (`listen-http`
and `listen-https`), and socket path (`listen-unix`). All the other things are additional features.
Here are a few working sample configs:
Here are a few working sample configs using a `/etc/ntfy/server.yml` file:
=== "server.yml (HTTP-only, with cache + attachments)"
``` yaml
@@ -44,6 +44,15 @@ Here are a few working sample configs:
attachment-cache-dir: "/var/cache/ntfy/attachments"
```
=== "server.yml (behind proxy, with cache + attachments)"
``` yaml
base-url: "http://ntfy.example.com"
listen-http: ":2586"
cache-file: "/var/cache/ntfy/cache.db"
attachment-cache-dir: "/var/cache/ntfy/attachments"
behind-proxy: true
```
=== "server.yml (ntfy.sh config)"
``` yaml
# All the things: Behind a proxy, Firebase, cache, attachments,
@@ -65,6 +74,58 @@ Here are a few working sample configs:
keepalive-interval: "45s"
```
Alternatively, you can also use command line arguments or environment variables to configure the server. Here's an example
using Docker Compose (i.e. `docker-compose.yml`):
=== "Docker Compose (w/ auth, cache, attachments)"
``` yaml
version: '3'
services:
ntfy:
image: binwiederhier/ntfy
restart: unless-stopped
environment:
NTFY_BASE_URL: http://ntfy.example.com
NTFY_CACHE_FILE: /var/lib/ntfy/cache.db
NTFY_AUTH_FILE: /var/lib/ntfy/auth.db
NTFY_AUTH_DEFAULT_ACCESS: deny-all
NTFY_BEHIND_PROXY: true
NTFY_ATTACHMENT_CACHE_DIR: /var/lib/ntfy/attachments
NTFY_ENABLE_LOGIN: true
volumes:
- ./:/var/lib/ntfy
ports:
- 80:80
command: serve
```
=== "Docker Compose (w/ auth, cache, web push, iOS)"
``` yaml
version: '3'
services:
ntfy:
image: binwiederhier/ntfy
restart: unless-stopped
environment:
NTFY_BASE_URL: http://ntfy.example.com
NTFY_CACHE_FILE: /var/lib/ntfy/cache.db
NTFY_AUTH_FILE: /var/lib/ntfy/auth.db
NTFY_AUTH_DEFAULT_ACCESS: deny-all
NTFY_BEHIND_PROXY: true
NTFY_ATTACHMENT_CACHE_DIR: /var/lib/ntfy/attachments
NTFY_ENABLE_LOGIN: true
NTFY_UPSTREAM_BASE_URL: https://ntfy.sh
NTFY_WEB_PUSH_PUBLIC_KEY: <public_key>
NTFY_WEB_PUSH_PRIVATE_KEY: <private_key>
NTFY_WEB_PUSH_FILE: /var/lib/ntfy/webpush.db
NTFY_WEB_PUSH_EMAIL_ADDRESS: <email>
volumes:
- ./:/var/lib/ntfy
ports:
- 8093:80
command: serve
```
## Message cache
If desired, ntfy can temporarily keep notifications in an in-memory or an on-disk cache. Caching messages for a short period
of time is important to allow [phones](subscribe/phone.md) and other devices with brittle Internet connections to be able to retrieve
@@ -234,7 +295,7 @@ want to use a dedicated token to publish from your backup host, and one from you
but not yet implemented.
The `ntfy token` command can be used to manage access tokens for users. Tokens can have labels, and they can expire
automatically (or never expire). Each user can have up to 20 tokens (hardcoded).
automatically (or never expire). Each user can have up to 60 tokens (hardcoded).
**Example commands** (type `ntfy token --help` or `ntfy token COMMAND --help` for more details):
```
@@ -344,10 +405,10 @@ with the given username/password. Be sure to use HTTPS to avoid eavesdropping an
```
### Example: UnifiedPush
[UnifiedPush](https://unifiedpush.org) requires that the [application server](https://unifiedpush.org/spec/definitions/#application-server) (e.g. Synapse, Fediverse Server, …)
has anonymous write access to the [topic](https://unifiedpush.org/spec/definitions/#endpoint) used for push messages.
[UnifiedPush](https://unifiedpush.org) requires that the [application server](https://unifiedpush.org/developers/spec/definitions/#application-server) (e.g. Synapse, Fediverse Server, …)
has anonymous write access to the [topic](https://unifiedpush.org/developers/spec/definitions/#endpoint) used for push messages.
The topic names used by UnifiedPush all start with the `up*` prefix. Please refer to the
**[UnifiedPush documentation](https://unifiedpush.org/users/distributors/ntfy/#limit-access-to-some-users)** for more details.
**[UnifiedPush documentation](https://unifiedpush.org/users/distributors/ntfy/#limit-access-to-some-users-acl)** for more details.
To enable support for UnifiedPush for private servers (i.e. `auth-default-access: "deny-all"`), you should either
allow anonymous write access for the entire prefix or explicitly per topic:
@@ -458,6 +519,31 @@ $ dig A mx1.ntfy.sh +short
3.139.215.220
```
### Local-only email
If you want to send emails from an internal service on the same network as your ntfy instance, you do not need to
worry about DNS records at all. Define a port for the SMTP server and pick an SMTP server domain (can be
anything).
=== "/etc/ntfy/server.yml"
``` yaml
smtp-server-listen: ":25"
smtp-server-domain: "example.com"
smtp-server-addr-prefix: "ntfy-" # optional
```
Then, in the email settings of your internal service, set the SMTP server address to the IP address of your
ntfy instance. Set the port to the value you defined in `smtp-server-listen`. Leave any username and password
fields empty. In the "From" address, pick anything (e.g., "alerts@ntfy.sh"); the value doesn't matter.
In the "To" address, put in an email address that follows this pattern: `[topic]@[smtp-server-domain]` (or
`[smtp-server-addr-prefix][topic]@[smtp-server-domain]` if you set `smtp-server-addr-prefix`).
So if you used `example.com` as the SMTP server domain, and you want to send a message to the `email-alerts`
topic, set the "To" address to `email-alerts@example.com`. If the topic has access restrictions, you will need
to include an access token in the "To" address, such as `email-alerts+tk_AbC123dEf456@example.com`.
If the internal service lets you use define an email "Subject", it will become the title of the notification.
The body of the email will become the message of the notification.
## Behind a proxy (TLS, etc.)
!!! warning
If you are running ntfy behind a proxy, you must set the `behind-proxy` flag. Otherwise, all visitors are
@@ -467,17 +553,69 @@ It may be desirable to run ntfy behind a proxy (e.g. nginx, HAproxy or Apache),
using Let's Encrypt using certbot, or simply because you'd like to share the ports (80/443) with other services.
Whatever your reasons may be, there are a few things to consider.
### IP-based rate limiting
If you are running ntfy behind a proxy, you should set the `behind-proxy` flag. This will instruct the
[rate limiting](#rate-limiting) logic to use the `X-Forwarded-For` header as the primary identifier for a visitor,
as opposed to the remote IP address. If the `behind-proxy` flag is not set, all visitors will
be counted as one, because from the perspective of the ntfy server, they all share the proxy's IP address.
[rate limiting](#rate-limiting) logic to use the header configured in `proxy-forwarded-header` (default is `X-Forwarded-For`)
as the primary identifier for a visitor, as opposed to the remote IP address.
=== "/etc/ntfy/server.yml"
If the `behind-proxy` flag is not set, all visitors will be counted as one, because from the perspective of the
ntfy server, they all share the proxy's IP address.
Relevant flags to consider:
* `behind-proxy` makes it so that the real visitor IP address is extracted from the header defined in `proxy-forwarded-header`.
Without this, the remote address of the incoming connection is used (default: `false`).
* `proxy-forwarded-header` is the header to use to identify visitors (default: `X-Forwarded-For`). It may be a single IP address (e.g. `1.2.3.4`),
a comma-separated list of IP addresses (e.g. `1.2.3.4, 5.6.7.8`), or an [RFC 7239](https://datatracker.ietf.org/doc/html/rfc7239)-style
header (e.g. `for=1.2.3.4;by=proxy.example.com, for=5.6.7.8`).
* `proxy-trusted-addresses` is a comma-separated list of IP addresses that are removed from the forwarded header
to determine the real IP address. This is only useful if there are multiple proxies involved that add themselves to
the forwarded header (default: empty).
=== "/etc/ntfy/server.yml (behind a proxy)"
``` yaml
# Tell ntfy to use "X-Forwarded-For" to identify visitors
# Tell ntfy to use "X-Forwarded-For" header to identify visitors for rate limiting
#
# Example: If "X-Forwarded-For: 9.9.9.9, 1.2.3.4" is set,
# the visitor IP will be 1.2.3.4 (right-most address).
#
behind-proxy: true
```
=== "/etc/ntfy/server.yml (X-Client-IP header)"
``` yaml
# Tell ntfy to use "X-Client-IP" header to identify visitors for rate limiting
#
# Example: If "X-Client-IP: 9.9.9.9" is set,
# the visitor IP will be 9.9.9.9.
#
behind-proxy: true
proxy-forwarded-header: "X-Client-IP"
```
=== "/etc/ntfy/server.yml (Forwarded header)"
``` yaml
# Tell ntfy to use "Forwarded" header (RFC 7239) to identify visitors for rate limiting
#
# Example: If "Forwarded: for=1.2.3.4;by=proxy.example.com, for=9.9.9.9" is set,
# the visitor IP will be 9.9.9.9.
#
behind-proxy: true
proxy-forwarded-header: "Forwarded"
```
=== "/etc/ntfy/server.yml (multiple proxies)"
``` yaml
# Tell ntfy to use "X-Forwarded-For" header to identify visitors for rate limiting,
# and to strip the IP addresses of the proxies 1.2.3.4 and 1.2.3.5
#
# Example: If "X-Forwarded-For: 9.9.9.9, 1.2.3.4" is set,
# the visitor IP will be 9.9.9.9 (right-most unknown address).
#
behind-proxy: true
proxy-trusted-addresses: "1.2.3.4, 1.2.3.5"
```
### TLS/SSL
ntfy supports HTTPS/TLS by setting the `listen-https` [config option](#config-options). However, if you
are behind a proxy, it is recommended that TLS/SSL termination is done by the proxy itself (see below).
@@ -546,7 +684,7 @@ or the root domain:
listen 443 ssl http2;
server_name ntfy.sh;
# See https://ssl-config.mozilla.org/#server=nginx&version=1.18.0&config=intermediate&openssl=1.1.1k&hsts=false&ocsp=false&guideline=5.6see https://ssl-config.mozilla.org/#server=nginx&version=1.18.0&config=intermediate&openssl=1.1.1k&hsts=false&ocsp=false&guideline=5.6
# See https://ssl-config.mozilla.org/#server=nginx&version=1.18.0&config=intermediate&openssl=1.1.1k&hsts=false&ocsp=false&guideline=5.6
ssl_session_timeout 1d;
ssl_session_cache shared:MozSSL:10m; # about 40000 sessions
ssl_session_tickets off;
@@ -613,7 +751,7 @@ or the root domain:
listen 443 ssl http2;
server_name ntfy.sh;
# See https://ssl-config.mozilla.org/#server=nginx&version=1.18.0&config=intermediate&openssl=1.1.1k&hsts=false&ocsp=false&guideline=5.6see https://ssl-config.mozilla.org/#server=nginx&version=1.18.0&config=intermediate&openssl=1.1.1k&hsts=false&ocsp=false&guideline=5.6
# See https://ssl-config.mozilla.org/#server=nginx&version=1.18.0&config=intermediate&openssl=1.1.1k&hsts=false&ocsp=false&guideline=5.6
ssl_session_timeout 1d;
ssl_session_cache shared:MozSSL:10m; # about 40000 sessions
ssl_session_tickets off;
@@ -649,8 +787,8 @@ or the root domain:
<VirtualHost *:80>
ServerName ntfy.sh
# Proxy connections to ntfy (requires "a2enmod proxy")
ProxyPass / http://127.0.0.1:2586/
# Proxy connections to ntfy (requires "a2enmod proxy proxy_http")
ProxyPass / http://127.0.0.1:2586/ upgrade=websocket
ProxyPassReverse / http://127.0.0.1:2586/
SetEnv proxy-nokeepalive 1
@@ -658,19 +796,13 @@ or the root domain:
# Higher than the max message size of 4096 bytes
LimitRequestBody 102400
# Enable mod_rewrite (requires "a2enmod rewrite")
RewriteEngine on
# WebSockets support (requires "a2enmod rewrite proxy_wstunnel")
RewriteCond %{HTTP:Upgrade} websocket [NC]
RewriteCond %{HTTP:Connection} upgrade [NC]
RewriteRule ^/?(.*) "ws://127.0.0.1:2586/$1" [P,L]
# Redirect HTTP to HTTPS, but only for GET topic addresses, since we want
# it to work with curl without the annoying https:// prefix
RewriteCond %{REQUEST_METHOD} GET
RewriteRule ^/([-_A-Za-z0-9]{0,64})$ https://%{SERVER_NAME}/$1 [R,L]
# it to work with curl without the annoying https:// prefix (requires "a2enmod alias")
<If "%{REQUEST_METHOD} == 'GET'">
RedirectMatch permanent "^/([-_A-Za-z0-9]{0,64})$" "https://%{SERVER_NAME}/$1"
</If>
</VirtualHost>
<VirtualHost *:443>
@@ -681,8 +813,8 @@ or the root domain:
SSLCertificateKeyFile /etc/letsencrypt/live/ntfy.sh/privkey.pem
Include /etc/letsencrypt/options-ssl-apache.conf
# Proxy connections to ntfy (requires "a2enmod proxy")
ProxyPass / http://127.0.0.1:2586/
# Proxy connections to ntfy (requires "a2enmod proxy proxy_http")
ProxyPass / http://127.0.0.1:2586/ upgrade=websocket
ProxyPassReverse / http://127.0.0.1:2586/
SetEnv proxy-nokeepalive 1
@@ -690,14 +822,7 @@ or the root domain:
# Higher than the max message size of 4096 bytes
LimitRequestBody 102400
# Enable mod_rewrite (requires "a2enmod rewrite")
RewriteEngine on
# WebSockets support (requires "a2enmod rewrite proxy_wstunnel")
RewriteCond %{HTTP:Upgrade} websocket [NC]
RewriteCond %{HTTP:Connection} upgrade [NC]
RewriteRule ^/?(.*) "ws://127.0.0.1:2586/$1" [P,L]
</VirtualHost>
```
@@ -705,6 +830,7 @@ or the root domain:
```
# Note that this config is most certainly incomplete. Please help out and let me know what's missing
# via Discord/Matrix or in a GitHub issue.
# Note: Caddy automatically handles both HTTP and WebSockets with reverse_proxy
ntfy.sh, http://nfty.sh {
reverse_proxy 127.0.0.1:2586
@@ -789,6 +915,59 @@ Note that the self-hosted server literally sends the message `New message` for e
may be `Some other message`. This is so that if iOS cannot talk to the self-hosted server (in time, or at all),
it'll show `New message` as a popup.
## Web Push
[Web Push](https://developer.mozilla.org/en-US/docs/Web/API/Push_API) ([RFC8030](https://datatracker.ietf.org/doc/html/rfc8030))
allows ntfy to receive push notifications, even when the ntfy web app (or even the browser, depending on the platform) is closed.
When enabled, the user can enable **background notifications** for their topics in the web app under Settings. Once enabled by the
user, ntfy will forward published messages to the push endpoint (browser-provided, e.g. fcm.googleapis.com), which will then
forward it to the browser.
To configure Web Push, you need to generate and configure a [VAPID](https://datatracker.ietf.org/doc/html/draft-thomson-webpush-vapid) keypair (via `ntfy webpush keys`),
a database to keep track of the browser's subscriptions, and an admin email address (you):
- `web-push-public-key` is the generated VAPID public key, e.g. AA1234BBCCddvveekaabcdfqwertyuiopasdfghjklzxcvbnm1234567890
- `web-push-private-key` is the generated VAPID private key, e.g. AA2BB1234567890abcdefzxcvbnm1234567890
- `web-push-file` is a database file to keep track of browser subscription endpoints, e.g. `/var/cache/ntfy/webpush.db`
- `web-push-email-address` is the admin email address send to the push provider, e.g. `sysadmin@example.com`
- `web-push-startup-queries` is an optional list of queries to run on startup`
- `web-push-expiry-warning-duration` defines the duration after which unused subscriptions are sent a warning (default is `55d`)
- `web-push-expiry-duration` defines the duration after which unused subscriptions will expire (default is `60d`)
Limitations:
- Like foreground browser notifications, background push notifications require the web app to be served over HTTPS. A _valid_
certificate is required, as service workers will not run on origins with untrusted certificates.
- Web Push is only supported for the same server. You cannot use subscribe to web push on a topic on another server. This
is due to a limitation of the Push API, which doesn't allow multiple push servers for the same origin.
To configure VAPID keys, first generate them:
```sh
$ ntfy webpush keys
Web Push keys generated.
...
```
Then copy the generated values into your `server.yml` or use the corresponding environment variables or command line arguments:
```yaml
web-push-public-key: AA1234BBCCddvveekaabcdfqwertyuiopasdfghjklzxcvbnm1234567890
web-push-private-key: AA2BB1234567890abcdefzxcvbnm1234567890
web-push-file: /var/cache/ntfy/webpush.db
web-push-email-address: sysadmin@example.com
```
The `web-push-file` is used to store the push subscriptions. Unused subscriptions will send out a warning after 55 days,
and will automatically expire after 60 days (default). If the gateway returns an error (e.g. 410 Gone when a user has unsubscribed),
subscriptions are also removed automatically.
The web app refreshes subscriptions on start and regularly on an interval, but this file should be persisted across restarts. If the subscription
file is deleted or lost, any web apps that aren't open will not receive new web push notifications until you open then.
Changing your public/private keypair is **not recommended**. Browsers only allow one server identity (public key) per origin, and
if you change them the clients will not be able to subscribe via web push until the user manually clears the notification permission.
## Tiers
ntfy supports associating users to pre-defined tiers. Tiers can be used to grant users higher limits, such as
daily message limits, attachment size, or make it possible for users to reserve topics. If [payments are enabled](#payments),
@@ -872,6 +1051,15 @@ are the easiest), and then configure the following options:
After you have configured phone calls, create a [tier](#tiers) with a call limit (e.g. `ntfy tier create --call-limit=10 ...`),
and then assign it to a user. Users may then use the `X-Call` header to receive a phone call when publishing a message.
## Message limits
There are a few message limits that you can configure:
* `message-size-limit` defines the max size of a message body. Please note message sizes >4K are **not recommended,
and largely untested**. The Android/iOS and other clients may not work, or work properly. If FCM and/or APNS is used,
the limit should stay 4K, because their limits are around that size. If you increase this size limit regardless,
FCM and APNS will NOT work for large messages.
* `message-delay-limit` defines the max delay of a message when using the "Delay" header and [scheduled delivery](publish.md#scheduled-delivery).
## Rate limiting
!!! info
Be aware that if you are running ntfy behind a proxy, you must set the `behind-proxy` flag.
@@ -955,20 +1143,23 @@ By default, ntfy puts almost all rate limits on the message publisher, e.g. numb
size are all based on the visitor who publishes a message. **Subscriber-based rate limiting is a way to use the rate limits
of a topic's subscriber, instead of the limits of the publisher.**
If enabled, subscribers may opt to have published messages counted against their own rate limits, as opposed
to the publisher's rate limits. This is especially useful to increase the amount of messages that high-volume
publishers (e.g. Matrix/Mastodon servers) are allowed to send.
If subscriber-based rate limiting is enabled, **messages published on UnifiedPush topics** (topics starting with `up`, e.g. `up123456789012`)
will be counted towards the "rate visitor" of the topic. A "rate visitor" is the first subscriber to the topic.
Once enabled, a client may send a `Rate-Topics: <topic1>,<topic2>,...` header when subscribing to topics via
HTTP stream, or websockets, thereby registering itself as the "rate visitor", i.e. the visitor whose rate limits
to use when publishing on this topic. Note that setting the rate visitor requires **read-write permission** on the topic.
Once enabled, a client subscribing to UnifiedPush topics via HTTP stream, or websockets, will be automatically registered as
a "rate visitor", i.e. the visitor whose rate limits will be used when publishing on this topic. Note that setting the rate visitor
requires **read-write permission** on the topic.
UnifiedPush only: If this setting is enabled, publishing to UnifiedPush topics will lead to an `HTTP 507 Insufficient Storage`
If this setting is enabled, publishing to UnifiedPush topics will lead to an `HTTP 507 Insufficient Storage`
response if no "rate visitor" has been previously registered. This is to avoid burning the publisher's
`visitor-message-daily-limit`.
To enable subscriber-based rate limiting, set `visitor-subscriber-rate-limiting: true`.
!!! info
Due to a [denial-of-service issue](https://github.com/binwiederhier/ntfy/issues/1048), support for the `Rate-Topics`
header was removed entirely. This is unfortunate, but subscriber-based rate limiting will still work for `up*` topics.
## Tuning for scale
If you're running ntfy for your home server, you probably don't need to worry about scale at all. In its default config,
if it's not behind a proxy, the ntfy server can keep about **as many connections as the open file limit allows**.
@@ -1107,12 +1298,16 @@ and [here](https://easyengine.io/tutorials/nginx/block-wp-login-php-bruteforce-a
maxretry = 10
```
Note that if you run nginx in a container, append `, chain=DOCKER-USER` to the jail.local action. By default, the jail action chain
is `INPUT`, but `FORWARD` is used when using docker networks. `DOCKER-USER`, available when using docker, is part of the `FORWARD`
chain.
## Health checks
A preliminary health check API endpoint is exposed at `/v1/health`. The endpoint returns a `json` response in the format shown below.
If a non-200 HTTP status code is returned or if the returned `health` field is `false` the ntfy service should be considered as unhealthy.
If a non-200 HTTP status code is returned or if the returned `healthy` field is `false` the ntfy service should be considered as unhealthy.
```json
{"health":true}
{"healthy":true}
```
See [Installation for Docker](install.md#docker) for an example of how this could be used in a `docker-compose` environment.
@@ -1239,15 +1434,17 @@ variable before running the `ntfy` command (e.g. `export NTFY_LISTEN_HTTP=:80`).
| `listen-unix-mode` | `NTFY_LISTEN_UNIX_MODE` | *file mode* | *system default* | File mode of the Unix socket, e.g. 0700 or 0777 |
| `key-file` | `NTFY_KEY_FILE` | *filename* | - | HTTPS/TLS private key file, only used if `listen-https` is set. |
| `cert-file` | `NTFY_CERT_FILE` | *filename* | - | HTTPS/TLS certificate file, only used if `listen-https` is set. |
| `firebase-key-file` | `NTFY_FIREBASE_KEY_FILE` | *filename* | - | If set, also publish messages to a Firebase Cloud Messaging (FCM) topic for your app. This is optional and only required to save battery when using the Android app. See [Firebase (FCM](#firebase-fcm). |
| `firebase-key-file` | `NTFY_FIREBASE_KEY_FILE` | *filename* | - | If set, also publish messages to a Firebase Cloud Messaging (FCM) topic for your app. This is optional and only required to save battery when using the Android app. See [Firebase (FCM)](#firebase-fcm). |
| `cache-file` | `NTFY_CACHE_FILE` | *filename* | - | If set, messages are cached in a local SQLite database instead of only in-memory. This allows for service restarts without losing messages in support of the since= parameter. See [message cache](#message-cache). |
| `cache-duration` | `NTFY_CACHE_DURATION` | *duration* | 12h | Duration for which messages will be buffered before they are deleted. This is required to support the `since=...` and `poll=1` parameter. Set this to `0` to disable the cache entirely. |
| `cache-startup-queries` | `NTFY_CACHE_STARTUP_QUERIES` | *string (SQL queries)* | - | SQL queries to run during database startup; this is useful for tuning and [enabling WAL mode](#wal-for-message-cache) |
| `cache-startup-queries` | `NTFY_CACHE_STARTUP_QUERIES` | *string (SQL queries)* | - | SQL queries to run during database startup; this is useful for tuning and [enabling WAL mode](#message-cache) |
| `cache-batch-size` | `NTFY_CACHE_BATCH_SIZE` | *int* | 0 | Max size of messages to batch together when writing to message cache (if zero, writes are synchronous) |
| `cache-batch-timeout` | `NTFY_CACHE_BATCH_TIMEOUT` | *duration* | 0s | Timeout for batched async writes to the message cache (if zero, writes are synchronous) |
| `auth-file` | `NTFY_AUTH_FILE` | *filename* | - | Auth database file used for access control. If set, enables authentication and access control. See [access control](#access-control). |
| `auth-default-access` | `NTFY_AUTH_DEFAULT_ACCESS` | `read-write`, `read-only`, `write-only`, `deny-all` | `read-write` | Default permissions if no matching entries in the auth database are found. Default is `read-write`. |
| `behind-proxy` | `NTFY_BEHIND_PROXY` | *bool* | false | If set, the X-Forwarded-For header is used to determine the visitor IP address instead of the remote address of the connection. |
| `behind-proxy` | `NTFY_BEHIND_PROXY` | *bool* | false | If set, use forwarded header (e.g. X-Forwarded-For, X-Client-IP) to determine visitor IP address (for rate limiting) |
| `proxy-forwarded-header` | `NTFY_PROXY_FORWARDED_HEADER` | *string* | `X-Forwarded-For` | Use specified header to determine visitor IP address (for rate limiting) |
| `proxy-trusted-addresses` | `NTFY_PROXY_TRUSTED_ADDRESSES` | *comma-separated list of IPs* | - | Comma-separated list of trusted IP addresses to remove from forwarded header |
| `attachment-cache-dir` | `NTFY_ATTACHMENT_CACHE_DIR` | *directory* | - | Cache directory for attached files. To enable attachments, this has to be set. |
| `attachment-total-size-limit` | `NTFY_ATTACHMENT_TOTAL_SIZE_LIMIT` | *size* | 5G | Limit of the on-disk attachment cache directory. If the limits is exceeded, new attachments will be rejected. |
| `attachment-file-size-limit` | `NTFY_ATTACHMENT_FILE_SIZE_LIMIT` | *size* | 15M | Per-file attachment size limit (e.g. 300k, 2M, 100M). Larger attachment will be rejected. |
@@ -1265,6 +1462,8 @@ variable before running the `ntfy` command (e.g. `export NTFY_LISTEN_HTTP=:80`).
| `twilio-verify-service` | `NTFY_TWILIO_VERIFY_SERVICE` | *string* | - | Twilio Verify service SID, e.g. VA12345beefbeef67890beefbeef122586 |
| `keepalive-interval` | `NTFY_KEEPALIVE_INTERVAL` | *duration* | 45s | Interval in which keepalive messages are sent to the client. This is to prevent intermediaries closing the connection for inactivity. Note that the Android app has a hardcoded timeout at 77s, so it should be less than that. |
| `manager-interval` | `NTFY_MANAGER_INTERVAL` | *duration* | 1m | Interval in which the manager prunes old messages, deletes topics and prints the stats. |
| `message-size-limit` | `NTFY_MESSAGE_SIZE_LIMIT` | *size* | 4K | The size limit for the message body. Please note that this is largely untested, and that FCM/APNS have limits around 4KB. If you increase this size limit, FCM and APNS will NOT work for large messages. |
| `message-delay-limit` | `NTFY_MESSAGE_DELAY_LIMIT` | *duration* | 3d | Amount of time a message can be [scheduled](publish.md#scheduled-delivery) into the future when using the `Delay` header |
| `global-topic-limit` | `NTFY_GLOBAL_TOPIC_LIMIT` | *number* | 15,000 | Rate limiting: Total number of topics before the server rejects new topics. |
| `upstream-base-url` | `NTFY_UPSTREAM_BASE_URL` | *URL* | `https://ntfy.sh` | Forward poll request to an upstream server, this is needed for iOS push notifications for self-hosted servers |
| `upstream-access-token` | `NTFY_UPSTREAM_ACCESS_TOKEN` | *string* | `tk_zyYLYj...` | Access token to use for the upstream server; needed only if upstream rate limits are exceeded or upstream server requires auth |
@@ -1285,13 +1484,22 @@ variable before running the `ntfy` command (e.g. `export NTFY_LISTEN_HTTP=:80`).
| `stripe-secret-key` | `NTFY_STRIPE_SECRET_KEY` | *string* | - | Payments: Key used for the Stripe API communication, this enables payments |
| `stripe-webhook-key` | `NTFY_STRIPE_WEBHOOK_KEY` | *string* | - | Payments: Key required to validate the authenticity of incoming webhooks from Stripe |
| `billing-contact` | `NTFY_BILLING_CONTACT` | *email address* or *website* | - | Payments: Email or website displayed in Upgrade dialog as a billing contact |
| `web-push-public-key` | `NTFY_WEB_PUSH_PUBLIC_KEY` | *string* | - | Web Push: Public Key. Run `ntfy webpush keys` to generate |
| `web-push-private-key` | `NTFY_WEB_PUSH_PRIVATE_KEY` | *string* | - | Web Push: Private Key. Run `ntfy webpush keys` to generate |
| `web-push-file` | `NTFY_WEB_PUSH_FILE` | *string* | - | Web Push: Database file that stores subscriptions |
| `web-push-email-address` | `NTFY_WEB_PUSH_EMAIL_ADDRESS` | *string* | - | Web Push: Sender email address |
| `web-push-startup-queries` | `NTFY_WEB_PUSH_STARTUP_QUERIES` | *string* | - | Web Push: SQL queries to run against subscription database at startup |
| `web-push-expiry-duration` | `NTFY_WEB_PUSH_EXPIRY_DURATION` | *duration* | 60d | Web Push: Duration after which a subscription is considered stale and will be deleted. This is to prevent stale subscriptions. |
| `web-push-expiry-warning-duration` | `NTFY_WEB_PUSH_EXPIRY_WARNING_DURATION` | *duration* | 55d | Web Push: Duration after which a warning is sent to subscribers that their subscription will expire soon. This is to prevent stale subscriptions. |
| `log-format` | `NTFY_LOG_FORMAT` | *string* | `text` | Defines the output format, can be text or json |
| `log-file` | `NTFY_LOG_FILE` | *string* | - | Defines the filename to write logs to. If this is not set, ntfy logs to stderr |
| `log-level` | `NTFY_LOG_LEVEL` | *string* | `info` | Defines the default log level, can be one of trace, debug, info, warn or error |
The format for a *duration* is: `<number>(smh)`, e.g. 30s, 20m or 1h.
The format for a *duration* is: `<number>(smhd)`, e.g. 30s, 20m, 1h or 3d.
The format for a *size* is: `<number>(GMK)`, e.g. 1G, 200M or 4000k.
## Command line options
```
$ ntfy serve --help
NAME:
ntfy serve - Run the ntfy server
@@ -1319,35 +1527,36 @@ OPTIONS:
--log-level-overrides value, --log_level_overrides value [ --log-level-overrides value, --log_level_overrides value ] set log level overrides [$NTFY_LOG_LEVEL_OVERRIDES]
--log-format value, --log_format value set log format (default: "text") [$NTFY_LOG_FORMAT]
--log-file value, --log_file value set log file, default is STDOUT [$NTFY_LOG_FILE]
--config value, -c value config file (default: /etc/ntfy/server.yml) [$NTFY_CONFIG_FILE]
--config value, -c value config file (default: "/etc/ntfy/server.yml") [$NTFY_CONFIG_FILE]
--base-url value, --base_url value, -B value externally visible base URL for this host (e.g. https://ntfy.sh) [$NTFY_BASE_URL]
--listen-http value, --listen_http value, -l value ip:port used to as HTTP listen address (default: ":80") [$NTFY_LISTEN_HTTP]
--listen-https value, --listen_https value, -L value ip:port used to as HTTPS listen address [$NTFY_LISTEN_HTTPS]
--listen-http value, --listen_http value, -l value ip:port used as HTTP listen address (default: ":80") [$NTFY_LISTEN_HTTP]
--listen-https value, --listen_https value, -L value ip:port used as HTTPS listen address [$NTFY_LISTEN_HTTPS]
--listen-unix value, --listen_unix value, -U value listen on unix socket path [$NTFY_LISTEN_UNIX]
--listen-unix-mode value, --listen_unix_mode value file permissions of unix socket, e.g. 0700 (default: system default) [$NTFY_LISTEN_UNIX_MODE]
--key-file value, --key_file value, -K value private key file, if listen-https is set [$NTFY_KEY_FILE]
--cert-file value, --cert_file value, -E value certificate file, if listen-https is set [$NTFY_CERT_FILE]
--firebase-key-file value, --firebase_key_file value, -F value Firebase credentials file; if set additionally publish to FCM topic [$NTFY_FIREBASE_KEY_FILE]
--cache-file value, --cache_file value, -C value cache file used for message caching [$NTFY_CACHE_FILE]
--cache-duration since, --cache_duration since, -b since buffer messages for this time to allow since requests (default: 12h0m0s) [$NTFY_CACHE_DURATION]
--cache-duration since, --cache_duration since, -b since buffer messages for this time to allow since requests (default: "12h") [$NTFY_CACHE_DURATION]
--cache-batch-size value, --cache_batch_size value max size of messages to batch together when writing to message cache (if zero, writes are synchronous) (default: 0) [$NTFY_BATCH_SIZE]
--cache-batch-timeout value, --cache_batch_timeout value timeout for batched async writes to the message cache (if zero, writes are synchronous) (default: 0s) [$NTFY_CACHE_BATCH_TIMEOUT]
--cache-batch-timeout value, --cache_batch_timeout value timeout for batched async writes to the message cache (if zero, writes are synchronous) (default: "0s") [$NTFY_CACHE_BATCH_TIMEOUT]
--cache-startup-queries value, --cache_startup_queries value queries run when the cache database is initialized [$NTFY_CACHE_STARTUP_QUERIES]
--auth-file value, --auth_file value, -H value auth database file used for access control [$NTFY_AUTH_FILE]
--auth-startup-queries value, --auth_startup_queries value queries run when the auth database is initialized [$NTFY_AUTH_STARTUP_QUERIES]
--auth-default-access value, --auth_default_access value, -p value default permissions if no matching entries in the auth database are found (default: "read-write") [$NTFY_AUTH_DEFAULT_ACCESS]
--attachment-cache-dir value, --attachment_cache_dir value cache directory for attached files [$NTFY_ATTACHMENT_CACHE_DIR]
--attachment-total-size-limit value, --attachment_total_size_limit value, -A value limit of the on-disk attachment cache (default: 5G) [$NTFY_ATTACHMENT_TOTAL_SIZE_LIMIT]
--attachment-file-size-limit value, --attachment_file_size_limit value, -Y value per-file attachment size limit (e.g. 300k, 2M, 100M) (default: 15M) [$NTFY_ATTACHMENT_FILE_SIZE_LIMIT]
--attachment-expiry-duration value, --attachment_expiry_duration value, -X value duration after which uploaded attachments will be deleted (e.g. 3h, 20h) (default: 3h) [$NTFY_ATTACHMENT_EXPIRY_DURATION]
--keepalive-interval value, --keepalive_interval value, -k value interval of keepalive messages (default: 45s) [$NTFY_KEEPALIVE_INTERVAL]
--manager-interval value, --manager_interval value, -m value interval of for message pruning and stats printing (default: 1m0s) [$NTFY_MANAGER_INTERVAL]
--attachment-total-size-limit value, --attachment_total_size_limit value, -A value limit of the on-disk attachment cache (default: "5G") [$NTFY_ATTACHMENT_TOTAL_SIZE_LIMIT]
--attachment-file-size-limit value, --attachment_file_size_limit value, -Y value per-file attachment size limit (e.g. 300k, 2M, 100M) (default: "15M") [$NTFY_ATTACHMENT_FILE_SIZE_LIMIT]
--attachment-expiry-duration value, --attachment_expiry_duration value, -X value duration after which uploaded attachments will be deleted (e.g. 3h, 20h) (default: "3h") [$NTFY_ATTACHMENT_EXPIRY_DURATION]
--keepalive-interval value, --keepalive_interval value, -k value interval of keepalive messages (default: "45s") [$NTFY_KEEPALIVE_INTERVAL]
--manager-interval value, --manager_interval value, -m value interval of for message pruning and stats printing (default: "1m") [$NTFY_MANAGER_INTERVAL]
--disallowed-topics value, --disallowed_topics value [ --disallowed-topics value, --disallowed_topics value ] topics that are not allowed to be used [$NTFY_DISALLOWED_TOPICS]
--web-root value, --web_root value sets web root to landing page (home), web app (app) or disabled (disable) (default: "app") [$NTFY_WEB_ROOT]
--web-root value, --web_root value sets root of the web app (e.g. /, or /app), or disables it (disable) (default: "/") [$NTFY_WEB_ROOT]
--enable-signup, --enable_signup allows users to sign up via the web app, or API (default: false) [$NTFY_ENABLE_SIGNUP]
--enable-login, --enable_login allows users to log in via the web app, or API (default: false) [$NTFY_ENABLE_LOGIN]
--enable-reservations, --enable_reservations allows users to reserve topics (if their tier allows it) (default: false) [$NTFY_ENABLE_RESERVATIONS]
--upstream-base-url value, --upstream_base_url value forward poll request to an upstream server, this is needed for iOS push notifications for self-hosted servers [$NTFY_UPSTREAM_BASE_URL]
--upstream-access-token value, --upstream_access_token value access token to use for the upstream server; needed only if upstream rate limits are exceeded or upstream server requires auth [$NTFY_UPSTREAM_ACCESS_TOKEN]
--smtp-sender-addr value, --smtp_sender_addr value SMTP server address (host:port) for outgoing emails [$NTFY_SMTP_SENDER_ADDR]
--smtp-sender-user value, --smtp_sender_user value SMTP user (if e-mail sending is enabled) [$NTFY_SMTP_SENDER_USER]
--smtp-sender-pass value, --smtp_sender_pass value SMTP password (if e-mail sending is enabled) [$NTFY_SMTP_SENDER_PASS]
@@ -1355,20 +1564,36 @@ OPTIONS:
--smtp-server-listen value, --smtp_server_listen value SMTP server address (ip:port) for incoming emails, e.g. :25 [$NTFY_SMTP_SERVER_LISTEN]
--smtp-server-domain value, --smtp_server_domain value SMTP domain for incoming e-mail, e.g. ntfy.sh [$NTFY_SMTP_SERVER_DOMAIN]
--smtp-server-addr-prefix value, --smtp_server_addr_prefix value SMTP email address prefix for topics to prevent spam (e.g. 'ntfy-') [$NTFY_SMTP_SERVER_ADDR_PREFIX]
--twilio-account value, --twilio_account value Twilio account SID, used for phone calls, e.g. AC123... [$NTFY_TWILIO_ACCOUNT]
--twilio-auth-token value, --twilio_auth_token value Twilio auth token [$NTFY_TWILIO_AUTH_TOKEN]
--twilio-phone-number value, --twilio_phone_number value Twilio number to use for outgoing calls [$NTFY_TWILIO_PHONE_NUMBER]
--twilio-verify-service value, --twilio_verify_service value Twilio Verify service ID, used for phone number verification [$NTFY_TWILIO_VERIFY_SERVICE]
--message-size-limit value, --message_size_limit value size limit for the message (see docs for limitations) (default: "4K") [$NTFY_MESSAGE_SIZE_LIMIT]
--message-delay-limit value, --message_delay_limit value max duration a message can be scheduled into the future (default: "3d") [$NTFY_MESSAGE_DELAY_LIMIT]
--global-topic-limit value, --global_topic_limit value, -T value total number of topics allowed (default: 15000) [$NTFY_GLOBAL_TOPIC_LIMIT]
--visitor-subscription-limit value, --visitor_subscription_limit value number of subscriptions per visitor (default: 30) [$NTFY_VISITOR_SUBSCRIPTION_LIMIT]
--visitor-attachment-total-size-limit value, --visitor_attachment_total_size_limit value total storage limit used for attachments per visitor (default: "100M") [$NTFY_VISITOR_ATTACHMENT_TOTAL_SIZE_LIMIT]
--visitor-attachment-daily-bandwidth-limit value, --visitor_attachment_daily_bandwidth_limit value total daily attachment download/upload bandwidth limit per visitor (default: "500M") [$NTFY_VISITOR_ATTACHMENT_DAILY_BANDWIDTH_LIMIT]
--visitor-request-limit-burst value, --visitor_request_limit_burst value initial limit of requests per visitor (default: 60) [$NTFY_VISITOR_REQUEST_LIMIT_BURST]
--visitor-request-limit-replenish value, --visitor_request_limit_replenish value interval at which burst limit is replenished (one per x) (default: 5s) [$NTFY_VISITOR_REQUEST_LIMIT_REPLENISH]
--visitor-request-limit-replenish value, --visitor_request_limit_replenish value interval at which burst limit is replenished (one per x) (default: "5s") [$NTFY_VISITOR_REQUEST_LIMIT_REPLENISH]
--visitor-request-limit-exempt-hosts value, --visitor_request_limit_exempt_hosts value hostnames and/or IP addresses of hosts that will be exempt from the visitor request limit [$NTFY_VISITOR_REQUEST_LIMIT_EXEMPT_HOSTS]
--visitor-message-daily-limit value, --visitor_message_daily_limit value max messages per visitor per day, derived from request limit if unset (default: 0) [$NTFY_VISITOR_MESSAGE_DAILY_LIMIT]
--visitor-email-limit-burst value, --visitor_email_limit_burst value initial limit of e-mails per visitor (default: 16) [$NTFY_VISITOR_EMAIL_LIMIT_BURST]
--visitor-email-limit-replenish value, --visitor_email_limit_replenish value interval at which burst limit is replenished (one per x) (default: 1h0m0s) [$NTFY_VISITOR_EMAIL_LIMIT_REPLENISH]
--visitor-email-limit-replenish value, --visitor_email_limit_replenish value interval at which burst limit is replenished (one per x) (default: "1h") [$NTFY_VISITOR_EMAIL_LIMIT_REPLENISH]
--visitor-subscriber-rate-limiting, --visitor_subscriber_rate_limiting enables subscriber-based rate limiting (default: false) [$NTFY_VISITOR_SUBSCRIBER_RATE_LIMITING]
--behind-proxy, --behind_proxy, -P if set, use X-Forwarded-For header to determine visitor IP address (for rate limiting) (default: false) [$NTFY_BEHIND_PROXY]
--stripe-secret-key value, --stripe_secret_key value key used for the Stripe API communication, this enables payments [$NTFY_STRIPE_SECRET_KEY]
--stripe-webhook-key value, --stripe_webhook_key value key required to validate the authenticity of incoming webhooks from Stripe [$NTFY_STRIPE_WEBHOOK_KEY]
--billing-contact value, --billing_contact value e-mail or website to display in upgrade dialog (only if payments are enabled) [$NTFY_BILLING_CONTACT]
--help, -h show help (default: false)
--billing-contact value, --billing_contact value e-mail or website to display in upgrade dialog (only if payments are enabled) [$NTFY_BILLING_CONTACT]
--enable-metrics, --enable_metrics if set, Prometheus metrics are exposed via the /metrics endpoint (default: false) [$NTFY_ENABLE_METRICS]
--metrics-listen-http value, --metrics_listen_http value ip:port used to expose the metrics endpoint (implicitly enables metrics) [$NTFY_METRICS_LISTEN_HTTP]
--profile-listen-http value, --profile_listen_http value ip:port used to expose the profiling endpoints (implicitly enables profiling) [$NTFY_PROFILE_LISTEN_HTTP]
--web-push-public-key value, --web_push_public_key value public key used for web push notifications [$NTFY_WEB_PUSH_PUBLIC_KEY]
--web-push-private-key value, --web_push_private_key value private key used for web push notifications [$NTFY_WEB_PUSH_PRIVATE_KEY]
--web-push-file value, --web_push_file value file used to store web push subscriptions [$NTFY_WEB_PUSH_FILE]
--web-push-email-address value, --web_push_email_address value e-mail address of sender, required to use browser push services [$NTFY_WEB_PUSH_EMAIL_ADDRESS]
--web-push-startup-queries value, --web_push_startup_queries value queries run when the web push database is initialized [$NTFY_WEB_PUSH_STARTUP_QUERIES]
--web-push-expiry-duration value, --web_push_expiry_duration value automatically expire unused subscriptions after this time (default: "60d") [$NTFY_WEB_PUSH_EXPIRY_DURATION]
--web-push-expiry-warning-duration value, --web_push_expiry_warning_duration value send web push warning notification after this time before expiring unused subscriptions (default: "55d") [$NTFY_WEB_PUSH_EXPIRY_WARNING_DURATION]
--help, -h show help
```

View File

@@ -1,4 +1,4 @@
# Deprecation notices
# Deprecations and breaking changes
This page is used to list deprecation notices for ntfy. Deprecated commands and options will be
**removed after 1-3 months** from the time they were deprecated. How long the feature is deprecated
before the behavior is changed depends on the severity of the change, and how prominent the feature is.

View File

@@ -16,7 +16,7 @@ server consists of three components:
* **The documentation** is generated by [MkDocs](https://www.mkdocs.org/) and [Material for MkDocs](https://squidfunk.github.io/mkdocs-material/),
which is written in [Python](https://www.python.org/). You'll need Python and MkDocs (via `pip`) only if you want to
build the docs.
* **The web app** is written in [React](https://reactjs.org/), using [MUI](https://mui.com/). It uses [Create React App](https://create-react-app.dev/)
* **The web app** is written in [React](https://reactjs.org/), using [MUI](https://mui.com/). It uses [Vite](https://vitejs.dev/)
to build the production build. If you want to modify the web app, you need [nodejs](https://nodejs.org/en/) (for `npm`)
and install all the 100,000 dependencies (*sigh*).
@@ -241,6 +241,41 @@ $ cd web
$ npm start
```
### Testing Web Push locally
Reference: <https://stackoverflow.com/questions/34160509/options-for-testing-service-workers-via-http>
#### With the dev servers
1. Get web push keys `go run main.go webpush keys`
2. Run the server with web push enabled
```sh
go run main.go \
--log-level debug \
serve \
--web-push-public-key KEY \
--web-push-private-key KEY \
--web-push-email-address <email> \
--web-push-file=/tmp/webpush.db
```
3. In `web/public/config.js`:
- Set `base_url` to `http://localhost`, This is required as web push can only be used with the server matching the `base_url`.
- Set the `web_push_public_key` correctly.
4. Run `npm run start`
#### With a built package
1. Run `make web-build`
2. Run the server (step 2 above)
3. Open <http://localhost/>
### Build the docs
The sources for the docs live in `docs/`. Similarly to the web app, you can simply run `make docs` to build the
documentation. As long as you have `mkdocs` installed (see above), this should work fine:
@@ -328,7 +363,7 @@ To build your own version with Firebase, you must:
* And change `app_base_url` in [values.xml](https://github.com/binwiederhier/ntfy-android/blob/main/app/src/main/res/values/values.xml)
* Then run:
```
# To build an unsigned .apk (app/build/outputs/apk/play/*.apk)
# To build an unsigned .apk (app/build/outputs/apk/play/release/*.apk)
./gradlew assemblePlayRelease
# To build a bundle .aab (app/play/release/*.aab)
@@ -349,7 +384,7 @@ strictly based off of my development on this app. There may be other versions of
### Apple setup
!!! info
Along with this step, the [PLIST Deployment](#plist-deployment-and-configuration) step is also required
Along with this step, the [PLIST Deployment](#plist-config) step is also required
for these changes to take effect in the iOS app.
1. [Create a new key in Apple Developer Member Center](https://developer.apple.com/account/resources/authkeys/add)
@@ -394,7 +429,7 @@ steps:
### XCode setup
1. Follow step 4 of [https://firebase.google.com/docs/ios/setup](Add Firebase to your Apple project) to install the
1. Follow step 4 of [Add Firebase to your Apple project](https://firebase.google.com/docs/ios/setup) to install the
`firebase-ios-sdk` in XCode, if it's not already present - you can select any packages in addition to Firebase Core / Firebase Messaging
1. Similarly, install the SQLite.swift package dependency in XCode
1. When running the debug build, ensure XCode is pointed to the connected iOS device - registering for push notifications does not work in the iOS simulators

View File

@@ -2,9 +2,9 @@
<!-- This file was generated by scripts/emoji-convert.sh -->
You can [tag messages](../publish/#tags-emojis) with emojis 🥳 🎉 and other relevant strings. Matching tags are automatically
You can [tag messages](publish.md#tags-emojis) with emojis 🥳 🎉 and other relevant strings. Matching tags are automatically
converted to emojis. This is a reference of all supported emojis. To learn more about the feature, please refer to the
[tagging and emojis page](../publish/#tags-emojis).
[tagging and emojis page](publish.md#tags-emojis).
<table class="remove-md-box emoji-table"><tr>

View File

@@ -31,6 +31,12 @@ GitHub have been hopeless. In case it ever becomes available, I want to know imm
*/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
```
You can also use [`ntfy-run`](https://github.com/quantum5/ntfy-run) to send the output of your cronjob in the
notification, so that you know exactly why it failed:
```
0 0 * * * ntfy-run -n https://ntfy.sh/backups --success-priority low --failure-tags warning ~/backup-computer
```
## 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
@@ -135,6 +141,21 @@ You can send a message during a workflow run with curl. Here is an example sendi
${{ secrets.NTFY_URL }}
```
## Changedetection.io
ntfy is an excellent choice for getting notifications when a website has a change sent to your mobile (or desktop),
[changedetection.io](https://changedetection.io) or on GitHub ([dgtlmoon/changedetection.io](https://github.com/dgtlmoon/changedetection.io))
uses [apprise](https://github.com/caronc/apprise) library for notification integrations.
To add any ntfy(s) notification to a website change simply add the [ntfy style URL](https://github.com/caronc/apprise/wiki/Notify_ntfy)
to the notification list.
For example `ntfy://{topic}` or `ntfy://{user}:{password}@{host}:{port}/{topics}`
In your changedetection.io installation, click `Edit` > `Notifications` on a single website watch (or group) then add
the special ntfy Apprise Notification URL to the Notification List.
![ntfy alerts on website change](static/img/cdio-setup.jpg)
## Watchtower (shoutrrr)
You can use [shoutrrr](https://containrrr.dev/shoutrrr/latest/services/ntfy/) to send
[Watchtower](https://github.com/containrrr/watchtower/) notifications to your ntfy topic.
@@ -146,15 +167,30 @@ services:
watchtower:
image: containrrr/watchtower
environment:
- WATCHTOWER_NOTIFICATIONS=shoutrrr
- WATCHTOWER_NOTIFICATION_SKIP_TITLE=True
- WATCHTOWER_NOTIFICATION_URL=ntfy://ntfy.sh/my_watchtower_topic?title=WatchtowerUpdates
```
The environment variable `WATCHTOWER_NOTIFICATION_SKIP_TITLE` is required to prevent Watchtower from [replacing the `title` query parameter](https://containrrr.dev/watchtower/notifications/#settings). If omitted, the provided notification title will not be used.
Or, if you only want to send notifications using shoutrrr:
```
shoutrrr send -u "ntfy://ntfy.sh/my_watchtower_topic?title=WatchtowerUpdates" -m "testMessage"
```
Authentication tokens are also supported:
- (Recommended) Ntfy url format (replace the domain, topic and token with your own):
```
ntfy://:TOKEN@DOMAIN/TOPIC
```
- Generic webhook and authorization header using this url format (replace the domain, topic and token with your own):
```
generic+https://DOMAIN/TOPIC?@authorization=Bearer+TOKEN`
```
## Sonarr, Radarr, Lidarr, Readarr, Prowlarr, SABnzbd
<!-- Sonarr v4 is in beta as of May 2023, should be updated to remove v3 reference when stable -->
@@ -583,6 +619,8 @@ This will only work on selfhosted [traccar](https://www.traccar.org/) ([Github](
The easiest way to integrate traccar with ntfy, is to configure ntfy as the SMS provider for your instance. You then can set your ntfy topic as your account's phone number in traccar. Sending the email notifications to ntfy will not work, as ntfy does not support HTML emails.
**Info:** Add a phone number to your traccar account not in device, as otherwise it will not try to send SMS.
**Caution:** JSON publishing is only possible, when POST-ing to the root URL of the ntfy instance. (see [documentation](publish.md#publish-as-json))
```xml
<entry key='sms.http.url'>https://ntfy.sh</entry>
@@ -602,3 +640,56 @@ or by simply providing traccar with a valid username/password combination.
<entry key='sms.http.user'>phil</entry>
<entry key='sms.http.password'>mypass</entry>
```
## Terminal Notifications for Long-Running Commands
This example provides a simple way to send notifications using [ntfy.sh](https://ntfy.sh) when a terminal command completes. It includes success or failure indicators based on the command's exit status.
Store your ntfy.sh bearer token securely if access control is enabled:
```sh
echo "your_bearer_token_here" > ~/.ntfy_token
chmod 600 ~/.ntfy_token
```
Add the following function and alias to your `.bashrc` or `.bash_profile`:
```sh
# Function for alert notifications using ntfy.sh
notify_via_ntfy() {
local exit_status=$? # Capture the exit status before doing anything else
local token=$(< ~/.ntfy_token) # Securely read the token
local status_icon="$([ $exit_status -eq 0 ] && echo magic_wand || echo warning)"
local last_command=$(history | tail -n1 | sed -e 's/^[[:space:]]*[0-9]\{1,\}[[:space:]]*//' -e 's/[;&|][[:space:]]*alert$//')
curl -s -X POST "https://n.example.dev/alerts" \
-H "Authorization: Bearer $token" \
-H "Title: Terminal" \
-H "X-Priority: 3" \
-H "Tags: $status_icon" \
-d "Command: $last_command (Exit: $exit_status)"
echo "Tags: $status_icon"
echo "$last_command (Exit: $exit_status)"
}
# Add an "alert" alias for long running commands using ntfy.sh
alias alert='notify_via_ntfy'
```
Now you can run any long-running command and append `alert` to notify when it completes:
```sh
sleep 10; alert
```
![ntfy notifications on mobile device](static/img/mobile-screenshot-notification.png)
**Notification Sent** with a success 🪄 (`magic_wand`) or failure ⚠️ (`warning`) tag.
To test failure notifications:
```sh
false; alert # Always fails (exit 1)
ls --invalid; alert # Invalid option
cat nonexistent_file; alert # File not found
```

View File

@@ -76,7 +76,29 @@ However, if you still want to disable it, you can do so with the `web-root: disa
Think of the ntfy web app like an Android/iOS app. It is freely available and accessible to anyone, yet useless without
a proper backend. So as long as you secure your backend with ACLs, exposing the ntfy web app to the Internet is harmless.
## If topic names are public, could I not just brute force them?
If you don't have [ACLs set up](config.md#access-control), the topic name is your password, it says so everywhere. If you
choose a easy-to-guess/dumb topic name, people will be able to guess it. If you choose a randomly generated topic name,
the topic is as good as a good password.
As for brute forcing: It's not possible to brute force a ntfy server for very long, as you'll get quickly rate limited.
In the default configuration, you'll be able to do 60 requests as a burst, and then 1 request per 10 seconds. Assuming you
choose a random 10 digit topic name using only A-Z, a-z, 0-9, _ and -, there are 64^10 possible topic names. Even if you
could do hundreds of requests per seconds (which you cannot), it would take many years to brute force a topic name.
For ntfy.sh, there's even a fail2ban in place which will ban your IP pretty quickly.
## Where can I donate?
I have just very recently started accepting donations via [GitHub Sponsors](https://github.com/sponsors/binwiederhier).
I would be humbled if you helped me carry the server and developer account costs. Even small donations are very much
appreciated.
## Can I email you? Can I DM you on Discord/Matrix?
While I love chatting on [Discord](https://discord.gg/cT7ECsZj9w), [Matrix](https://matrix.to/#/#ntfy-space:matrix.org),
[Lemmy](https://discuss.ntfy.sh/c/ntfy), or [GitHub](https://github.com/binwiederhier/ntfy/issues), I generally
**do not respond to emails about ntfy or direct messages** about ntfy, unless you are paying for a
[ntfy Pro](https://ntfy.sh/#pricing) plan, or you are inquiring about business opportunities.
I am sorry, but answering individual questions about ntfy on a 1-on-1 basis is not scalable. Answering your questions
in the above-mentioned forums benefits others, since I can link to the discussion at a later point in time, or other users
may be able to help out. I hope you understand.

View File

@@ -1,6 +1,7 @@
import os
import shutil
def copy_fonts(config, **kwargs):
site_dir = config['site_dir']
shutil.copytree('docs/static/fonts', os.path.join(site_dir, 'get'))
def on_post_build(config, **kwargs):
site_dir = config["site_dir"]
shutil.copytree("docs/static/fonts", os.path.join(site_dir, "get"))

View File

@@ -3,11 +3,11 @@ ntfy lets you **send push notifications to your phone or desktop via scripts fro
or POST requests. I use it to notify myself when scripts fail, or long-running commands complete.
## Step 1: Get the app
<a href="https://play.google.com/store/apps/details?id=io.heckel.ntfy"><img src="../../static/img/badge-googleplay.png"></a>
<a href="https://f-droid.org/en/packages/io.heckel.ntfy/"><img src="../../static/img/badge-fdroid.png"></a>
<a href="https://apps.apple.com/us/app/ntfy/id1625396347"><img src="../../static/img/badge-appstore.png"></a>
<a href="https://play.google.com/store/apps/details?id=io.heckel.ntfy"><img src="static/img/badge-googleplay.png"></a>
<a href="https://f-droid.org/en/packages/io.heckel.ntfy/"><img src="static/img/badge-fdroid.png"></a>
<a href="https://apps.apple.com/us/app/ntfy/id1625396347"><img src="static/img/badge-appstore.png"></a>
To [receive notifications on your phone](subscribe/phone.md), install the app, either via Google Play or F-Droid.
To [receive notifications on your phone](subscribe/phone.md), install the app, either via Google Play, App Store or F-Droid.
Once installed, open it and subscribe to a topic of your choosing. Topics don't have to explicitly be created, so just
pick a name and use it later when you [publish a message](publish.md). Note that **topic names are public, so it's wise
to choose something that cannot be guessed easily.**

View File

@@ -14,14 +14,15 @@ We support amd64, armv7 and arm64.
1. Install ntfy using one of the methods described below
2. Then (optionally) edit `/etc/ntfy/server.yml` for the server (Linux only, see [configuration](config.md) or [sample server.yml](https://github.com/binwiederhier/ntfy/blob/main/server/server.yml))
3. Or (optionally) create/edit `~/.config/ntfy/client.yml` (for the non-root user) or `/etc/ntfy/client.yml` (for the root user), see [sample client.yml](https://github.com/binwiederhier/ntfy/blob/main/client/client.yml))
3. Or (optionally) create/edit `~/.config/ntfy/client.yml` (for the non-root user), `~/Library/Application Support/ntfy/client.yml` (for the macOS non-root user), or `/etc/ntfy/client.yml` (for the root user), see [sample client.yml](https://github.com/binwiederhier/ntfy/blob/main/client/client.yml))
To run the ntfy server, then just run `ntfy serve` (or `systemctl start ntfy` when using the deb/rpm).
To send messages, use `ntfy publish`. To subscribe to topics, use `ntfy subscribe` (see [subscribing via CLI](subscribe/cli.md)
for details).
If you like video tutorials, check out :simple-youtube: [Kris Occhipinti's ntfy install guide](https://www.youtube.com/watch?v=bZzqrX05mNU).
It's short and to the point. _I am not affiliated with Kris, I just liked the video._
If you like tutorials, check out :simple-youtube: [Kris Occhipinti's ntfy install guide](https://www.youtube.com/watch?v=bZzqrX05mNU) on YouTube, or
[Alex's Docker-based setup guide](https://blog.alexsguardian.net/posts/2023/09/12/selfhosting-ntfy/). Both are great
resources to get started. _I am not affiliated with Kris or Alex, I just liked their video/post._
## Linux binaries
Please check out the [releases page](https://github.com/binwiederhier/ntfy/releases) for binaries and
@@ -29,37 +30,37 @@ deb/rpm packages.
=== "x86_64/amd64"
```bash
wget https://github.com/binwiederhier/ntfy/releases/download/v2.5.0/ntfy_2.5.0_linux_x86_64.tar.gz
tar zxvf ntfy_2.5.0_linux_x86_64.tar.gz
sudo cp -a ntfy_2.5.0_linux_x86_64/ntfy /usr/local/bin/ntfy
sudo mkdir /etc/ntfy && sudo cp ntfy_2.5.0_linux_x86_64/{client,server}/*.yml /etc/ntfy
wget https://github.com/binwiederhier/ntfy/releases/download/v2.12.0/ntfy_2.12.0_linux_amd64.tar.gz
tar zxvf ntfy_2.12.0_linux_amd64.tar.gz
sudo cp -a ntfy_2.12.0_linux_amd64/ntfy /usr/local/bin/ntfy
sudo mkdir /etc/ntfy && sudo cp ntfy_2.12.0_linux_amd64/{client,server}/*.yml /etc/ntfy
sudo ntfy serve
```
=== "armv6"
```bash
wget https://github.com/binwiederhier/ntfy/releases/download/v2.5.0/ntfy_2.5.0_linux_armv6.tar.gz
tar zxvf ntfy_2.5.0_linux_armv6.tar.gz
sudo cp -a ntfy_2.5.0_linux_armv6/ntfy /usr/bin/ntfy
sudo mkdir /etc/ntfy && sudo cp ntfy_2.5.0_linux_armv6/{client,server}/*.yml /etc/ntfy
wget https://github.com/binwiederhier/ntfy/releases/download/v2.12.0/ntfy_2.12.0_linux_armv6.tar.gz
tar zxvf ntfy_2.12.0_linux_armv6.tar.gz
sudo cp -a ntfy_2.12.0_linux_armv6/ntfy /usr/bin/ntfy
sudo mkdir /etc/ntfy && sudo cp ntfy_2.12.0_linux_armv6/{client,server}/*.yml /etc/ntfy
sudo ntfy serve
```
=== "armv7/armhf"
```bash
wget https://github.com/binwiederhier/ntfy/releases/download/v2.5.0/ntfy_2.5.0_linux_armv7.tar.gz
tar zxvf ntfy_2.5.0_linux_armv7.tar.gz
sudo cp -a ntfy_2.5.0_linux_armv7/ntfy /usr/bin/ntfy
sudo mkdir /etc/ntfy && sudo cp ntfy_2.5.0_linux_armv7/{client,server}/*.yml /etc/ntfy
wget https://github.com/binwiederhier/ntfy/releases/download/v2.12.0/ntfy_2.12.0_linux_armv7.tar.gz
tar zxvf ntfy_2.12.0_linux_armv7.tar.gz
sudo cp -a ntfy_2.12.0_linux_armv7/ntfy /usr/bin/ntfy
sudo mkdir /etc/ntfy && sudo cp ntfy_2.12.0_linux_armv7/{client,server}/*.yml /etc/ntfy
sudo ntfy serve
```
=== "arm64"
```bash
wget https://github.com/binwiederhier/ntfy/releases/download/v2.5.0/ntfy_2.5.0_linux_arm64.tar.gz
tar zxvf ntfy_2.5.0_linux_arm64.tar.gz
sudo cp -a ntfy_2.5.0_linux_arm64/ntfy /usr/bin/ntfy
sudo mkdir /etc/ntfy && sudo cp ntfy_2.5.0_linux_arm64/{client,server}/*.yml /etc/ntfy
wget https://github.com/binwiederhier/ntfy/releases/download/v2.12.0/ntfy_2.12.0_linux_arm64.tar.gz
tar zxvf ntfy_2.12.0_linux_arm64.tar.gz
sudo cp -a ntfy_2.12.0_linux_arm64/ntfy /usr/bin/ntfy
sudo mkdir /etc/ntfy && sudo cp ntfy_2.12.0_linux_arm64/{client,server}/*.yml /etc/ntfy
sudo ntfy serve
```
@@ -109,7 +110,7 @@ Manually installing the .deb file:
=== "x86_64/amd64"
```bash
wget https://github.com/binwiederhier/ntfy/releases/download/v2.5.0/ntfy_2.5.0_linux_amd64.deb
wget https://github.com/binwiederhier/ntfy/releases/download/v2.12.0/ntfy_2.12.0_linux_amd64.deb
sudo dpkg -i ntfy_*.deb
sudo systemctl enable ntfy
sudo systemctl start ntfy
@@ -117,7 +118,7 @@ Manually installing the .deb file:
=== "armv6"
```bash
wget https://github.com/binwiederhier/ntfy/releases/download/v2.5.0/ntfy_2.5.0_linux_armv6.deb
wget https://github.com/binwiederhier/ntfy/releases/download/v2.12.0/ntfy_2.12.0_linux_armv6.deb
sudo dpkg -i ntfy_*.deb
sudo systemctl enable ntfy
sudo systemctl start ntfy
@@ -125,7 +126,7 @@ Manually installing the .deb file:
=== "armv7/armhf"
```bash
wget https://github.com/binwiederhier/ntfy/releases/download/v2.5.0/ntfy_2.5.0_linux_armv7.deb
wget https://github.com/binwiederhier/ntfy/releases/download/v2.12.0/ntfy_2.12.0_linux_armv7.deb
sudo dpkg -i ntfy_*.deb
sudo systemctl enable ntfy
sudo systemctl start ntfy
@@ -133,7 +134,7 @@ Manually installing the .deb file:
=== "arm64"
```bash
wget https://github.com/binwiederhier/ntfy/releases/download/v2.5.0/ntfy_2.5.0_linux_arm64.deb
wget https://github.com/binwiederhier/ntfy/releases/download/v2.12.0/ntfy_2.12.0_linux_arm64.deb
sudo dpkg -i ntfy_*.deb
sudo systemctl enable ntfy
sudo systemctl start ntfy
@@ -143,34 +144,36 @@ Manually installing the .deb file:
=== "x86_64/amd64"
```bash
sudo rpm -ivh https://github.com/binwiederhier/ntfy/releases/download/v2.5.0/ntfy_2.5.0_linux_amd64.rpm
sudo rpm -ivh https://github.com/binwiederhier/ntfy/releases/download/v2.12.0/ntfy_2.12.0_linux_amd64.rpm
sudo systemctl enable ntfy
sudo systemctl start ntfy
```
=== "armv6"
```bash
sudo rpm -ivh https://github.com/binwiederhier/ntfy/releases/download/v2.5.0/ntfy_2.5.0_linux_armv6.rpm
sudo rpm -ivh https://github.com/binwiederhier/ntfy/releases/download/v2.12.0/ntfy_2.12.0_linux_armv6.rpm
sudo systemctl enable ntfy
sudo systemctl start ntfy
```
=== "armv7/armhf"
```bash
sudo rpm -ivh https://github.com/binwiederhier/ntfy/releases/download/v2.5.0/ntfy_2.5.0_linux_armv7.rpm
sudo rpm -ivh https://github.com/binwiederhier/ntfy/releases/download/v2.12.0/ntfy_2.12.0_linux_armv7.rpm
sudo systemctl enable ntfy
sudo systemctl start ntfy
```
=== "arm64"
```bash
sudo rpm -ivh https://github.com/binwiederhier/ntfy/releases/download/v2.5.0/ntfy_2.5.0_linux_arm64.rpm
sudo rpm -ivh https://github.com/binwiederhier/ntfy/releases/download/v2.12.0/ntfy_2.12.0_linux_arm64.rpm
sudo systemctl enable ntfy
sudo systemctl start ntfy
```
## Arch Linux
ntfy can be installed using an [AUR package](https://aur.archlinux.org/packages/ntfysh-bin/). You can use an [AUR helper](https://wiki.archlinux.org/title/AUR_helpers) like `paru`, `yay` or others to download, build and install ntfy and keep it up to date.
ntfy can be installed using an [AUR package](https://aur.archlinux.org/packages/ntfysh-bin/).
You can use an [AUR helper](https://wiki.archlinux.org/title/AUR_helpers) like `paru`, `yay` or others to download,
build and install ntfy and keep it up to date.
```
paru -S ntfysh-bin
```
@@ -192,18 +195,18 @@ NixOS also supports [declarative setup of the ntfy server](https://search.nixos.
## macOS
The [ntfy CLI](subscribe/cli.md) (`ntfy publish` and `ntfy subscribe` only) is supported on macOS as well.
To install, please [download the tarball](https://github.com/binwiederhier/ntfy/releases/download/v2.5.0/ntfy_2.5.0_macOS_all.tar.gz),
To install, please [download the tarball](https://github.com/binwiederhier/ntfy/releases/download/v2.12.0/ntfy_2.12.0_darwin_all.tar.gz),
extract it and place it somewhere in your `PATH` (e.g. `/usr/local/bin/ntfy`).
If run as `root`, ntfy will look for its config at `/etc/ntfy/client.yml`. For all other users, it'll look for it at
`~/Library/Application Support/ntfy/client.yml` (sample included in the tarball).
```bash
curl -L https://github.com/binwiederhier/ntfy/releases/download/v2.5.0/ntfy_2.5.0_macOS_all.tar.gz > ntfy_2.5.0_macOS_all.tar.gz
tar zxvf ntfy_2.5.0_macOS_all.tar.gz
sudo cp -a ntfy_2.5.0_macOS_all/ntfy /usr/local/bin/ntfy
curl -L https://github.com/binwiederhier/ntfy/releases/download/v2.12.0/ntfy_2.12.0_darwin_all.tar.gz > ntfy_2.12.0_darwin_all.tar.gz
tar zxvf ntfy_2.12.0_darwin_all.tar.gz
sudo cp -a ntfy_2.12.0_darwin_all/ntfy /usr/local/bin/ntfy
mkdir ~/Library/Application\ Support/ntfy
cp ntfy_2.5.0_macOS_all/client/client.yml ~/Library/Application\ Support/ntfy/client.yml
cp ntfy_2.12.0_darwin_all/client/client.yml ~/Library/Application\ Support/ntfy/client.yml
ntfy --help
```
@@ -221,7 +224,7 @@ brew install ntfy
## Windows
The [ntfy CLI](subscribe/cli.md) (`ntfy publish` and `ntfy subscribe` only) is supported on Windows as well.
To install, please [download the latest ZIP](https://github.com/binwiederhier/ntfy/releases/download/v2.5.0/ntfy_2.5.0_windows_x86_64.zip),
To install, please [download the latest ZIP](https://github.com/binwiederhier/ntfy/releases/download/v2.12.0/ntfy_2.12.0_windows_amd64.zip),
extract it and place the `ntfy.exe` binary somewhere in your `%Path%`.
The default path for the client config file is at `%AppData%\ntfy\client.yml` (not created automatically, sample in the ZIP file).
@@ -277,7 +280,7 @@ docker run \
Using docker-compose with non-root user and healthchecks enabled:
```yaml
version: "2.1"
version: "2.3"
services:
ntfy:
@@ -537,7 +540,7 @@ kubectl apply -k /ntfy
cpu: 150m
memory: 150Mi
volumeMounts:
- mountPath: /etc/ntfy/server.yml
- mountPath: /etc/ntfy
subPath: server.yml
name: config-volume # generated vie configMapGenerator from kustomization file
- mountPath: /var/cache/ntfy

View File

@@ -4,8 +4,21 @@ There are quite a few projects that work with ntfy, integrate ntfy, or have been
I've added a ⭐ to projects or posts that have a significant following, or had a lot of interaction by the community.
## Table of Contents
- [Official integrations](#official-integrations)
- [Integration via HTTP/SMTP/etc.](#integration-via-httpsmtpetc)
- [UnifiedPush integrations](#unifiedpush-integrations)
- [Libraries](#libraries)
- [CLIs + GUIs](#clis-guis)
- [Projects + scripts](#projects-scripts)
- [Blog + forum posts](#blog-forum-posts)
- [Alternative ntfy servers](#alternative-ntfy-servers)
## Official integrations
- [changedetection.io](https://changedetection.io) ⭐ - Website change detection and notification
- [Home Assistant](https://www.home-assistant.io/integrations/ntfy) ⭐ - Home Assistant is an open-source platform for automating and controlling smart home devices.
- [Healthchecks.io](https://healthchecks.io/) ⭐ - Online service for monitoring regularly running tasks such as cron jobs
- [Apprise](https://github.com/caronc/apprise/wiki/Notify_ntfy) ⭐ - Push notifications that work with just about every platform
- [Uptime Kuma](https://uptime.kuma.pet/) ⭐ - A self-hosted monitoring tool
@@ -16,21 +29,30 @@ I've added a ⭐ to projects or posts that have a significant following, or had
- [Gatus](https://gatus.io/) ⭐ - Automated service health dashboard
- [Automatisch](https://automatisch.io/) ⭐ - Open source Zapier alternative / workflow automation tool
- [FlexGet](https://flexget.com/Plugins/Notifiers/ntfysh) ⭐ - Multipurpose automation tool for all of your media
- [Shoutrrr](https://containrrr.dev/shoutrrr/v0.7/services/ntfy/) ⭐ - Notification library for gophers and their furry friends.
- [Shoutrrr](https://containrrr.dev/shoutrrr/v0.8/services/ntfy/) ⭐ - Notification library for gophers and their furry friends.
- [Netdata](https://learn.netdata.cloud/docs/alerts-and-notifications/notifications/agent-alert-notifications/ntfy) ⭐ - Real-time performance monitoring
- [Deployer](https://github.com/deployphp/deployer) ⭐ - PHP deployment tool
- [Scrt.link](https://scrt.link/) - Share a secret
- [Platypush](https://docs.platypush.tech/platypush/plugins/ntfy.html) - Automation platform aimed to run on any device that can run Python
- [diun](https://crazymax.dev/diun/) - Docker Image Update Notifier
- [Cloudron](https://www.cloudron.io/store/sh.ntfy.cloudronapp.html) - Platform that makes it easy to manage web apps on your server
- [Xitoring](https://xitoring.com/docs/notifications/notification-roles/ntfy/) - Server and Uptime monitoring
- [HetrixTools](https://docs.hetrixtools.com/ntfy-sh-notifications/) - Uptime monitoring
- [EasyMorph](https://help.easymorph.com/doku.php?id=transformations:sendntfymessage) - Visual data transformation and automation tool
- [Monibot](https://monibot.io/) - Monibot monitors your websites, servers and applications and notifies you if something goes wrong.
- [Miniflux](https://miniflux.app/docs/ntfy.html) - Minimalist and opinionated feed reader
- [Beszel](https://beszel.dev/guide/notifications/ntfy) - Server monitoring platform
## Integration via HTTP/SMTP/etc.
- [Watchtower](https://containrrr.dev/watchtower/) ⭐ - Automating Docker container base image updates (see [integration example](examples.md#watchtower-shoutrrr))
- [Jellyfin](https://jellyfin.org/) ⭐ - The Free Software Media System (see [integration example](examples.md#))
- [Overseer](https://docs.overseerr.dev/using-overseerr/notifications/webhooks) ⭐ - a request management and media discovery tool for Plex (see [integration example](examples.md#jellyseerroverseerr-webhook))
- [Overseerr](https://docs.overseerr.dev/using-overseerr/notifications/webhooks) ⭐ - a request management and media discovery tool for Plex (see [integration example](examples.md#jellyseerroverseerr-webhook))
- [Tautulli](https://github.com/Tautulli/Tautulli) ⭐ - Monitoring and tracking tool for Plex (integration [via webhook](https://github.com/Tautulli/Tautulli/wiki/Notification-Agents-Guide#webhook))
- [Mailrise](https://github.com/YoRyan/mailrise) - An SMTP gateway (integration via [Apprise](https://github.com/caronc/apprise/wiki/Notify_ntfy))
- [Proxmox-Ntfy](https://github.com/qtsone/proxmox-ntfy) - Python script that monitors Proxmox tasks and sends notifications using the Ntfy service.
- [Scrutiny](https://github.com/AnalogJ/scrutiny) - WebUI for smartd S.M.A.R.T monitoring. Scrutiny includes shoutrrr/ntfy integration ([see integration README](https://github.com/AnalogJ/scrutiny?tab=readme-ov-file#notifications))
- [UptimeObserver](https://uptimeobserver.com) - Uptime Monitoring tool for Websites, APIs, SSL Certificates, DNS, Domain Names and Ports. [Integration Guide](https://support.uptimeobserver.com/integrations/ntfy/)
## [UnifiedPush](https://unifiedpush.org/users/apps/) integrations
@@ -55,16 +77,24 @@ I've added a ⭐ to projects or posts that have a significant following, or had
- [ntfy-for-delphi](https://github.com/hazzelnuts/ntfy-for-delphi) - A friendly library to push instant notifications ntfy (Delphi)
- [ntfy](https://github.com/ffflorian/ntfy) - Send notifications over ntfy (JS)
- [ntfy_dart](https://github.com/jr1221/ntfy_dart) - Dart wrapper around the ntfy API (Dart)
- [gotfy](https://github.com/AnthonyHewins/gotfy) - A Go wrapper for the ntfy API (Go)
- [symfony/ntfy-notifier](https://symfony.com/components/NtfyNotifier) ⭐ - Symfony Notifier integration for ntfy (PHP)
- [ntfy-java](https://github.com/MaheshBabu11/ntfy-java/) - A Java package to interact with a ntfy server (Java)
- [aiontfy](https://github.com/tr4nt0r/aiontfy) - Asynchronous client library for publishing and subscribing to ntfy (Python)
## CLIs + GUIs
- [ntfy.sh.sh](https://github.com/mininmobile/ntfy.sh.sh) - Run scripts on ntfy.sh events
- [ntfy Desktop client](https://codeberg.org/zvava/ntfy-desktop) - Cross-platform desktop application for ntfy
- [ntfy-desktop](https://codeberg.org/zvava/ntfy-desktop) - Cross-platform desktop application for ntfy
- [ntfy-desktop](https://github.com/Aetherinox/ntfy-desktop) - Desktop client for Windows, Linux, and MacOS with push notifications
- [ntfy svelte front-end](https://github.com/novatorem/Ntfy) - Front-end built with svelte
- [wio-ntfy-ticker](https://github.com/nachotp/wio-ntfy-ticker) - Ticker display for a ntfy.sh topic
- [ntfysh-windows](https://github.com/lucas-bortoli/ntfysh-windows) - A ntfy client for Windows Desktop
- [ntfyr](https://github.com/haxwithaxe/ntfyr) - A simple commandline tool to send notifications to ntfy
- [ntfy.py](https://github.com/ioqy/ntfy-client-python) - ntfy.py is a simple nfty.sh client for sending notifications
- [wlzntfy](https://github.com/Walzen-Group/ntfy-toaster) - A minimalistic, receive-only toast notification client for Windows 11
- [Ntfy_CSV_Reminders](https://github.com/thiswillbeyourgithub/Ntfy_CSV_Reminders) - A Python tool that sends random-timing phone notifications for recurring tasks by using daily probability checks based on CSV-defined frequencies.
- [Daily Fact Ntfy](https://github.com/thiswillbeyourgithub/Daily_Fact_Ntfy) - Generate [llm](https://github.com/simonw/llm) generated fact every day about any topic you're interested in.
## Projects + scripts
@@ -73,12 +103,12 @@ I've added a ⭐ to projects or posts that have a significant following, or had
- [Grafana-to-ntfy](https://gitlab.com/Saibe1111/grafana-to-ntfy) - Grafana-to-ntfy alerts channel (Node Js)
- [ntfy-long-zsh-command](https://github.com/robfox92/ntfy-long-zsh-command) - Notifies you once a long-running command completes (zsh)
- [ntfy-shellscripts](https://github.com/nickexyz/ntfy-shellscripts) - A few scripts for the ntfy project (Shell)
- [alertmanager-ntfy-relay](https://github.com/therobbielee/alertmanager-ntfy-relay) - ntfy.sh relay for Alertmanager (Go)
- [QuickStatus](https://github.com/corneliusroot/QuickStatus) - A shell script to alert to any immediate problems upon login (Shell)
- [ntfy.el](https://github.com/shombando/ntfy) - Send notifications from Emacs (Emacs)
- [backup-projects](https://gist.github.com/anthonyaxenov/826ba65abbabd5b00196bc3e6af76002) - Stupidly simple backup script for own projects (Shell)
- [grav-plugin-whistleblower](https://github.com/Himmlisch-Studios/grav-plugin-whistleblower) - Grav CMS plugin to get notifications via ntfy (PHP)
- [ntfy-server-status](https://github.com/filip2cz/ntfy-server-status) - Checking if server is online and reporting through ntfy (C)
- [borg-based backup](https://github.com/davidhi7/backup) - Simple borg-based backup script with notifications based on ntfy.sh or Discord webhooks (Python/Shell)
- [ntfy.sh *arr script](https://github.com/agent-squirrel/nfty-arr-script) - Quick and hacky script to get sonarr/radarr to notify the ntfy.sh service (Shell)
- [website-watcher](https://github.com/muety/website-watcher) - A small tool to watch websites for changes (with XPath support) (Python)
- [siteeagle](https://github.com/tpanum/siteeagle) - A small Python script to monitor websites and notify changes (Python)
@@ -121,9 +151,74 @@ I've added a ⭐ to projects or posts that have a significant following, or had
- [ntfyd](https://github.com/joachimschmidt557/ntfyd) - ntfy desktop daemon (Zig)
- [ntfy-browser](https://github.com/johman10/ntfy-browser) - browser extension to receive notifications without having the page open (TypeScript)
- [ntfy-electron](https://github.com/xdpirate/ntfy-electron) - Electron wrapper for the ntfy web app (JS)
- [systemd-ntfy-poweronoff](https://github.com/stendler/systemd-ntfy-poweronoff) - Systemd services to send notifications on system startup, shutdown and service failure
- [msgdrop](https://github.com/jbrubake/msgdrop) - Send and receive encrypted messages (Bash)
- [vigilant](https://github.com/VerifiedJoseph/vigilant) - Monitor RSS/ATOM and JSON feeds, and send push notifications on new entries (PHP)
- [ansible-role-ntfy-alertmanager](https://github.com/bleetube/ansible-role-ntfy-alertmanager) - Ansible role to install xenrox/ntfy-alertmanager
- [NtfyMe-Blender](https://github.com/NotNanook/NtfyMe-Blender) - Blender addon to send notifications to NtfyMe (Python)
- [ntfy-ios-url-share](https://www.icloud.com/shortcuts/be8a7f49530c45f79733cfe3e41887e6) - An iOS shortcut that lets you share URLs easily and quickly.
- [ntfy-ios-filesharing](https://www.icloud.com/shortcuts/fe948d151b2e4ae08fb2f9d6b27d680b) - An iOS shortcut that lets you share files from your share feed to a topic of your choice.
- [systemd-ntfy](https://hackage.haskell.org/package/systemd-ntfy) - monitor a set of systemd services an send a notification to ntfy.sh whenever their status changes
- [RouterOS Scripts](https://git.eworm.de/cgit/routeros-scripts/about/) - a collection of scripts for MikroTik RouterOS
- [ntfy-android-builder](https://github.com/TheBlusky/ntfy-android-builder) - Script for building ntfy-android with custom Firebase configuration (Docker/Shell)
- [jetspotter](https://github.com/vvanouytsel/jetspotter) - send notifications when planes are spotted near you (Go)
- [monitoring_ntfy](https://www.drupal.org/project/monitoring_ntfy) - Drupal monitoring Ntfy.sh integration (PHP/Drupal)
- [Notify](https://flathub.org/apps/com.ranfdev.Notify) - Native GTK4 client for ntfy (Rust)
- [notify-via-ntfy](https://exchange.checkmk.com/p/notify-via-ntfy) - Checkmk plugin to send notifications via ntfy (Python)
- [ntfy-java](https://github.com/MaheshBabu11/ntfy-java/) - A Java package to interact with a ntfy server (Java)
- [container-update-check](https://github.com/stendler/container-update-check) - Scripts to check and notify if a podman or docker container image can be updated (Podman/Shell)
- [ignition-combustion-template](https://github.com/stendler/ignition-combustion-template) - Templates and scripts to generate a configuration to automatically setup a system on first boot. Including systemd-ntfy-poweronoff (Shell)
- [ntfy-run](https://github.com/quantum5/ntfy-run) - Tool to run a command, capture its output, and send it to ntfy (Rust)
- [Clipboard IO](https://github.com/jim3692/clipboard-io) - End to end encrypted clipboard
- [ntfy-me-mcp](https://github.com/gitmotion/ntfy-me-mcp) - An ntfy MCP server for sending/fetching ntfy notifications to your self-hosted ntfy server from AI Agents (supports secure token auth & more - use with npx or docker!) (Node/Typescript)
- [InvaderInformant](https://github.com/patricksthannon/InvaderInformant) - Script for Mac OS systems that monitors new or dropped connections to your network using ntfy (Shell)
- [NtfyPwsh](https://github.com/ptmorris1/NtfyPwsh) - PowerShell module to help send messages to ntfy (PowerShell)
- [ntfyrr](https://github.com/leukosaima/ntfyrr) - Currently an Overseerr webhook notification to ntfy helper service.
## Blog + forum posts
- [Device notifications via HTTP with ntfy](https://alistairshepherd.uk/writing/ntfy/) - alistairshepherd.uk - 6/2025
- [Notifications about (almost) anything with ntfy.sh](https://hamatti.org/posts/notifications-about-almost-anything-with-ntfy-sh/) - hamatti.org - 6/2025
- [I set up a self-hosted notification service for everything, and I'll never look back](https://www.xda-developers.com/set-up-self-hosted-notification-service/) ⭐ - xda-developers.com - 5/2025
- [How to Set Up Ntfy: Self-Hosted Push Notifications Made Easy](https://www.youtube.com/watch?v=wDJDiAYZ3H0) - youtube.com (sass drew) - 1/2025
- [The NTFY is a game-changer FREE solution for IT people](https://www.youtube.com/watch?v=NtlztHT-sRw) - youtube.com (Valters Tech Turf) - 1/2025
- [Notify: A Powerful Tool for Real-Time Notifications (ntfy.sh)](https://www.youtube.com/watch?v=XXTTeVfGBz0) - youtube.com (LinuxCloudHacks) - 12/2025
- [Push notifications with ntfy and n8n](https://www.youtube.com/watch?v=DKG1R3xYvwQ) - youtube.com (Oskar) - 10/2024
- [Setup ntfy for selfhosted notifications with Cloudflare Tunnel](https://medium.com/@svenvanginkel/setup-ntfy-for-selfhosted-notifications-with-cloudflare-tunnel-e342f470177d) - medium.com (Sven van Ginkel) - 10/2024
- [Self-Host NTFY - How It Works & Easy Setup Guide](https://www.youtube.com/watch?v=79wHc_jfrJE) ⭐ - youtube.com (Techdox)- 9/2024
- [ntfy / Emacs Lisp](https://speechcode.com/blog/ntfy/) - speechcode.com - 3/2024
- [Boost Your Productivity with ntfy.sh: The Ultimate Notification Tool for Command-Line Users](https://dev.to/archetypal/boost-your-productivity-with-ntfysh-the-ultimate-notification-tool-for-command-line-users-iil) - dev.to - 3/2024
- [Nextcloud Talk (F-Droid version) notifications using ntfy (ntfy.sh)](https://www.youtube.com/watch?v=0a6PpfN5PD8) - youtube.com - 2/2024
- [ZFS and SMART Warnings via Ntfy](https://rair.dev/zfs-smart-ntfy/) - rair.dev - 2/2024
- [Automating Security Camera Notifications With Home Assistant and Ntfy](https://runtimeterror.dev/automating-camera-notifications-home-assistant-ntfy/) ⭐ - runtimeterror.dev - 2/2024
- [Ntfy: self-hosted notification service](https://medium.com/@williamdonze/ntfy-self-hosted-notification-service-0f3eada6e657) ⭐ - williamdonze.medium.com - 1/2024
- [Lets Supercharge Snowflake Alerts with Cool ntfy Open-source Notifications!](https://sarathi-data-ml-cloud.medium.com/lets-supercharge-snowflake-alerts-with-cool-ntfy-open-source-notifications-296da442c331) - sarathi-data-ml-cloud.medium.com - 1/2024
- [Setting up NTFY with Ngnix-Proxy-Manager, authentication and Ansible notifications](https://random-it-blog.de/rocky-linux/setting-up-ntfy-with-ngnix-proxy-manager-authentication-and-ansible-notifications/) - random-it-blog.de - 12/2023
- [Introducing the Monitoring Ntfy.sh Integration Module: Real-time Notifications for Drupal Monitoring](https://cyberschorsch.dev/drupal/introducing-monitoring-ntfysh-integration-module-real-time-notifications-drupal-monitoring) - cyberschorsch.dev - 11/2023
- [How to install Ntfy.sh on CasaOS using BigBearCasaOS](https://www.youtube.com/watch?v=wSWhtSNwTd8) - youtube.com - 10/2023
- [Podman Update Notifications via Ntfy](https://rair.dev/podman-update-notifications-ntfy/) - rair.dev - 9/2023
- [Easy Push Notifications With ntfy.sh](https://runtimeterror.dev/easy-push-notifications-with-ntfy/) ⭐ - runtimeterror.dev - 9/2023
- [Ntfy: Your Ultimate Push Notification Powerhouse!](https://kkamalesh117.medium.com/ntfy-your-ultimate-push-notification-powerhouse-1968c070f1d1) - kkamalesh117.medium.com - 9/2023
- [Installing Self Host NTFY On Linux Using Docker Container](https://www.pinoylinux.org/topicsplus/containers/installing-self-host-ntfy-on-linux-using-docker-container/) - pinoylinux.org - 9/2023
- [Homelab Notifications with ntfy](https://blog.alexsguardian.net/posts/2023/09/12/selfhosting-ntfy/) ⭐ - alexsguardian.net - 9/2023
- [Why NTFY is the Ultimate Push Notification Tool for Your Needs](https://osintph.medium.com/why-ntfy-is-the-ultimate-push-notification-tool-for-your-needs-e767421c84c5) - osintph.medium.com - 9/2023
- [Supercharge Your Alerts: Ntfy — The Ultimate Push Notification Solution](https://medium.com/spring-boot/supercharge-your-alerts-ntfy-the-ultimate-push-notification-solution-a3dda79651fe) - spring-boot.medium.com - 9/2023
- [Deploy Ntfy using Docker](https://www.linkedin.com/pulse/deploy-ntfy-mohamed-sharfy/) - linkedin.com - 9/2023
- [Send Notifications With Ntfy for New WordPress Posts](https://www.activepieces.com/blog/ntfy-notifications-for-wordpress-new-posts) - activepieces.com - 9/2023
- [Get Ntfy Notifications About New Zendesk Ticket](https://www.activepieces.com/blog/ntfy-notifications-about-new-zendesk-tickets) - activepieces.com - 9/2023
- [Set reminder for recurring events using ntfy & Cron](https://www.youtube.com/watch?v=J3O4aQ-EcYk) - youtube.com - 9/2023
- [ntfy - Installation and full configuration setup](https://www.youtube.com/watch?v=QMy14rGmpFI) - youtube.com - 9/2023
- [How to install Ntfy.sh on Portainer / Docker Compose](https://www.youtube.com/watch?v=utD9GNbAwyg) - youtube.com - 9/2023
- [ntfy - Push-Benachrichtigungen // Push Notifications](https://www.youtube.com/watch?v=LE3vRPPqZOU) - youtube.com - 9/2023
- [Podman Update Notifications via Ntfy](https://rair.dev/podman-upadte-notifications-ntfy/) - rair.dev - 9/2023
- [How to Send Alerts From Raspberry Pi Pico W to a Phone or Tablet](https://www.tomshardware.com/how-to/send-alerts-raspberry-pi-pico-w-to-mobile-device) - tomshardware.com - 8/2023
- [NetworkChunk - how did I NOT know about this?](https://www.youtube.com/watch?v=poDIT2ruQ9M) ⭐ - youtube.com - 8/2023
- [NTFY - Command-Line Notifications](https://academy.networkchuck.com/blog/ntfy/) - academy.networkchuck.com - 8/2023
- [Open Source Push Notifications! Get notified of any event you can imagine. Triggers abound!](https://www.youtube.com/watch?v=WJgwWXt79pE) ⭐ - youtube.com - 8/2023
- [How to install and self host an Ntfy server on Linux](https://linuxconfig.org/how-to-install-and-self-host-an-ntfy-server-on-linux) - linuxconfig.org - 7/2023
- [Basic website monitoring using cronjobs and ntfy.sh](https://burkhardt.dev/2023/website-monitoring-cron-ntfy/) - burkhardt.dev - 6/2023
- [Pingdom alternative in one line of curl through ntfy.sh](https://piqoni.bearblog.dev/uptime-monitoring-in-one-line-of-curl/) - bearblog.dev - 6/2023
- [#OpenSourceDiscovery 78: ntfy.sh](https://opensourcedisc.substack.com/p/opensourcediscovery-78-ntfysh) - opensourcedisc.substack.com - 6/2023
- [ntfy: des notifications instantanées](https://blogmotion.fr/diy/ntfy-notification-push-domotique-20708) - blogmotion.fr - 5/2023
- [桌面通知ntfy](https://www.cnblogs.com/xueweihan/archive/2023/05/04/17370060.html) - cnblogs.com - 5/2023
- [ntfy.sh - Open source push notifications via PUT/POST](https://lobste.rs/s/5drapz/ntfy_sh_open_source_push_notifications) - lobste.rs - 5/2023
@@ -147,6 +242,7 @@ I've added a ⭐ to projects or posts that have a significant following, or had
- [NTFY - système de notification hyper simple et complet](https://www.youtube.com/watch?v=UieZYWVVgA4) - youtube.com - 12/2022
- [ntfy.sh](https://paramdeo.com/til/ntfy-sh) - paramdeo.com - 11/2022
- [Using ntfy to warn me when my computer is discharging](https://ulysseszh.github.io/programming/2022/11/28/ntfy-warn-discharge.html) - ulysseszh.github.io - 11/2022
- [Enabling SSH Login Notifications using Ntfy](https://paramdeo.com/blog/enabling-ssh-login-notifications-using-ntfy) - paramdeo.com - 11/2022
- [ntfy - Push Notification Service](https://dizzytech.de/posts/ntfy/) - dizzytech.de - 11/2022
- [Console #132](https://console.substack.com/p/console-132) ⭐ - console.substack.com - 11/2022
- [How to make my phone buzz*](https://evbogue.com/howtomakemyphonebuzz) - evbogue.com - 11/2022
@@ -187,7 +283,8 @@ I've added a ⭐ to projects or posts that have a significant following, or had
- [ntfy otro sistema de notificaciones pub-sub simple basado en HTTP](https://ugeek.github.io/blog/post/2021-11-05-ntfy-sh-otro-sistema-de-notificaciones-pub-sub-simple-basado-en-http.html) - ugeek.github.io - 11/2021
- [Show HN: A tool to send push notifications to your phone, written in Go](https://news.ycombinator.com/item?id=29715464) ⭐ - news.ycombinator.com - 12/2021
- [Reddit selfhostable post](https://www.reddit.com/r/selfhosted/comments/qxlsm9/my_open_source_notification_android_app_and/) ⭐ - reddit.com - 11/2021
- [ntfy on The Canary in the Cage Podcast](https://odysee.com/@TheCanaryInTheCage:b/The-Canary-in-the-Cage-Episode-42:1?r=4gitYjTacQqPEjf22874USecDQYJ5y5E&t=3062) - odysee.com - 1/2025
- [NtfyPwsh - A PowerShell Module to Send Ntfy Messages](https://ptmorris1.github.io/posts/NtfyPwsh/) - github.io - 5/2025
## Alternative ntfy servers
@@ -203,6 +300,7 @@ ntfy community. Thanks to everyone running a public server. **You guys rock!**
| [ntfy.envs.net](https://ntfy.envs.net) | 🇩🇪 Germany |
| [ntfy.mzte.de](https://ntfy.mzte.de/) | 🇩🇪 Germany |
| [ntfy.hostux.net](https://ntfy.hostux.net/) | 🇫🇷 France |
| [ntfy.fossman.de](https://ntfy.fossman.de/) | 🇩🇪 Germany |
Please be aware that **server operators can log your messages**. The project also cannot guarantee the reliability
and uptime of third party servers, so use of each server is **at your own discretion**.

View File

@@ -1,5 +1,5 @@
# Known issues
This is an incomplete list of known issues with the ntfy server, Android app, and iOS app. You can find a complete
This is an incomplete list of known issues with the ntfy server, web app, Android app, and iOS app. You can find a complete
list [on GitHub](https://github.com/binwiederhier/ntfy/labels/%F0%9F%AA%B2%20bug), but I thought it may be helpful
to have the prominent ones here to link to.
@@ -26,3 +26,18 @@ Be sure that in your selfhosted server:
* Set `upstream-base-url: "https://ntfy.sh"` (**not your own hostname!**)
* Ensure that the URL you set in `base-url` **matches exactly** what you set the Default Server in iOS to
## iOS app seeing "New message", but not real message content
If you see `New message` notifications on iOS, your iPhone can likely not talk to your self-hosted server. Be sure that
your iOS device and your ntfy server are either on the same network, or that your phone can actually reach the server.
Turn on tracing/debugging on the server (via `log-level: trace` or `log-level: debug`, see [troubleshooting](troubleshooting.md)),
and read docs on [iOS instant notifications](https://docs.ntfy.sh/config/#ios-instant-notifications).
## Safari does not play sounds for web push notifications
Safari does not support playing sounds for web push notifications, and treats them all as silent. This will be fixed with
iOS 17 / Safari 17, which will be released later in 2023.
## PWA on iOS sometimes crashes with an IndexedDB error (see [#787](https://github.com/binwiederhier/ntfy/issues/787))
When resuming the installed PWA from the background, it sometimes crashes with an error from IndexedDB/Dexie, due to a
[WebKit bug]( https://bugs.webkit.org/show_bug.cgi?id=197050). A reload will fix it until a permanent fix is found.

File diff suppressed because one or more lines are too long

View File

@@ -2,6 +2,235 @@
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).
### ntfy server v2.12.0
Released May 29, 2025
This is mainly a maintenance release that updates dependencies, though since it's been over a year, there are a few
new features and bug fixes as well.
Thanks to everyone who contributed to this release, and special thanks to [@wunter8](https://github.com/wunter8) for his continued
user support in Discord/Matrix/GitHub! You rock, man!
**Features:**
* Add username/password auth to email publishing ([#1164](https://github.com/binwiederhier/ntfy/pull/1164), thanks to [@bishtawi](https://github.com/bishtawi))
* Write VAPID keys to file in `ntfy webpush --output-file` ([#1138](https://github.com/binwiederhier/ntfy/pull/1138), thanks to [@nogweii](https://github.com/nogweii))
* Add Docker major/minor version to image tags ([#1271](https://github.com/binwiederhier/ntfy/pull/1271), thanks to [@RoboMagus](https://github.com/RoboMagus))
* Add `latest` subscription param for grabbing just the most recent message ([#1216](https://github.com/binwiederhier/ntfy/pull/1216), thanks to [@wunter8](https://github.com/wunter8))
* Allow using `NTFY_PASSWORD_HASH` in `ntfy user` command instead of raw password ([#1340](https://github.com/binwiederhier/ntfy/pull/1340), thanks to [@Tom-Hubrecht](https://github.com/Tom-Hubrecht) for implementing)
* You can now change passwords via `v1/users` API ([#1267](https://github.com/binwiederhier/ntfy/pull/1267), thanks to [@wunter8](https://github.com/wunter8) for implementing)
* Make WebPush subscription warning/expiry configurable, increase default to 55/60 days ([#1212](https://github.com/binwiederhier/ntfy/pull/1212), thanks to [@KuroSetsuna29](https://github.com/KuroSetsuna29))
* Support [systemd user service](https://docs.ntfy.sh/subscribe/cli/#using-the-systemd-service) `ntfy-client.service` ([#1002](https://github.com/binwiederhier/ntfy/pull/1002), thanks to [@dandersch](https://github.com/dandersch))
**Bug fixes + maintenance:**
* Security updates for dependencies and Docker images ([#1341](https://github.com/binwiederhier/ntfy/pull/1341))
* Upgrade to Vite 6 ([#1342](https://github.com/binwiederhier/ntfy/pull/1342), thanks Dependabot)
* Fix iOS delivery issues for read-protected topics ([#1207](https://github.com/binwiederhier/ntfy/pull/1287), thanks a lot to [@barart](https://github.com/barart)!)
* Add `Date` header to outgoing emails to avoid rejection ([#1141](https://github.com/binwiederhier/ntfy/pull/1141), thanks to [@pcouy](https://github.com/pcouy))
* Fix IP address parsing when behind a proxy ([#1266](https://github.com/binwiederhier/ntfy/pull/1266), thanks to [@mmatuska](https://github.com/mmatuska))
* Make sure UnifiedPush messages are not treated as attachments ([#1312](https://github.com/binwiederhier/ntfy/pull/1312), thanks to [@vkrause](https://github.com/vkrause))
* Add OCI image version to Docker image ([#1307](https://github.com/binwiederhier/ntfy/pull/1307), thanks to [@jlssmt](https://github.com/jlssmt))
* WebSocket returning incorrect HTTP error code ([#1338](https://github.com/binwiederhier/ntfy/pull/1338) / [#1337](https://github.com/binwiederhier/ntfy/pull/1337), thanks to [@wunter8](https://github.com/wunter8) for debugging and implementing)
* Make Markdown in the web app scrollable horizontally ([#1262](https://github.com/binwiederhier/ntfy/pull/1262), thanks to [@rake5k](https://github.com/rake5k) for fixing)
* Make sure WebPush subscription topics are actually deleted (no ticket)
* Increase the number of access tokens per user to 60 ([#1308](https://github.com/binwiederhier/ntfy/issues/1308))
* Allow specifying `cache` and `firebase` via JSON publishing ([#1119](https://github.com/binwiederhier/ntfy/issues/1119)/[#1123](https://github.com/binwiederhier/ntfy/pull/1123), thanks to [@stendler](https://github.com/stendler))
**Documentation:**
* Lots of new integrations and projects. Amazing!
* [ntfy-me-mcp](https://github.com/gitmotion/ntfy-me-mcp)
* [UptimeObserver](https://uptimeobserver.com)
* [alertmanager-ntfy-relay](https://github.com/therobbielee/alertmanager-ntfy-relay)
* [Monibot](https://monibot.io/)
* [Daily_Fact_Ntfy](https://github.com/thiswillbeyourgithub/Daily_Fact_Ntfy)
* [EasyMorph](https://help.easymorph.com/doku.php?id=transformations:sendntfymessage)
* [ntfy-run](https://github.com/quantum5/ntfy-run)
* [Clipboard IO](https://github.com/jim3692/clipboard-io)
* [ntfy-me-mcp](https://github.com/gitmotion/ntfy-me-mcp)
* [InvaderInformant](https://github.com/patricksthannon/InvaderInformant)
* Various docs updates ([#1161](https://github.com/binwiederhier/ntfy/pull/1161), thanks to [@OneWeekNotice](https://github.com/OneWeekNotice))
* Typo in config docs ([#1177](https://github.com/binwiederhier/ntfy/pull/1177), thanks to [@hoho4190](https://github.com/hoho4190))
* Typo in CLI docs ([#1172](https://github.com/binwiederhier/ntfy/pull/1172), thanks to [@anirvan](https://github.com/anirvan))
* Correction about MacroDroid ([#1137](https://github.com/binwiederhier/ntfy/pull/1137), thanks to [@ShlomoCode](https://github.com/ShlomoCode))
* Note about fail2ban in Docker ([#1175](https://github.com/binwiederhier/ntfy/pull/1175)), thanks to [@Measurity](https://github.com/Measurity))
* Lots of other tiny docs updates, thanks to everyone who contributed!
**Languages**
* Update new languages from Weblate. Thanks to all the contributors!
* Added Tamil (தமிழ்) as a new language to the web app
### ntfy server v2.11.0
Released May 13, 2024
This is a tiny release that fixes a database index issue that caused performance issues on ntfy.sh. It also fixes a bug
in the rate visitor logic that caused rate visitors to be assigned to seemingly random topics. Nothing major this time.
❤️ Quick reminder that if you like ntfy, **please consider sponsoring us** via [GitHub Sponsors](https://github.com/sponsors/binwiederhier)
and [Liberapay](https://en.liberapay.com/ntfy/), or buying a [paid plan via the web app](https://ntfy.sh/app). ntfy will always remain open source.
**Bug fixes + maintenance:**
* Re-add database index `idx_topic` to the `messages` table to fix performance issues on ntfy.sh (no ticket, big thanks to [@tcaputi](https://github.com/tcaputi) for finding this issue)
* Do not set rate visitor for non-eligible topics (no ticket)
* Do not cache `config.js` ([#1098](https://github.com/binwiederhier/ntfy/pull/1098), thanks to [@wunter8](https://github.com/wunter8))
### ntfy server v2.10.0
Released Mar 27, 2024
This release adds support for **message templating** in the ntfy server, which allows you to include a message and/or
title template that will be filled with values from a JSON body (e.g. `curl -gd '{"alert":"Disk space low"}' "ntfy.sh/mytopic?tpl=1&m={{.alert}}"`).
This is great for services that let you specify a webhook URL but do not let you change the webhook body (such as GitHub, or Grafana).
**Features:**
* [Message templating](publish.md#message-templating): You can now include a message and/or title template that will be filled with values from a JSON body ([#724](https://github.com/binwiederhier/ntfy/issues/724), thanks to [@wunter8](https://github.com/wunter8) for implementing)
### ntfy server v2.9.0
Released Mar 7, 2024
A small release after a long pause (lots of day job work). This release adds for **larger messages** and **longer
message delays** in scheduled delivery messages. The web app also now supports pasting images from the clipboard. Other
than that, only a few bug fixes and documentation updates, and a teeny tiny breaking change 😬.
!!! info
⚠️ **Breaking change**: The `Rate-Topics` header was removed due to a [DoS issue](https://github.com/binwiederhier/ntfy/issues/1048). This only affects
installations with `visitor-subscriber-rate-limiting: true`, which is not the default and likely very rarely used.
Normally I'd never remove a feature, but this is a security issue, and likely affects almost nobody.
**Features:**
* Support for larger message delays with `message-delay-limit` (see [message limits](config.md#message-limits), [#1050](https://github.com/binwiederhier/ntfy/pull/1050)/[#1019](https://github.com/binwiederhier/ntfy/issues/1019), thanks to [@MrChadMWood](https://github.com/MrChadMWood) for reporting)
* Support for larger message body sizes with `message-size-limit` (use at your own risk, see [message limits](config.md#message-limits), [#836](https://github.com/binwiederhier/ntfy/pull/836)/[#1050](https://github.com/binwiederhier/ntfy/pull/1050), thanks to [@zhzy0077](https://github.com/zhzy0077) for implementing this, and to [@nkjshlsqja7331](https://github.com/nkjshlsqja7331) for reporting)
* Web app: You can now paste images into the message bar or publish dialog ([#963](https://github.com/binwiederhier/ntfy/pull/963)/[#572](https://github.com/binwiederhier/ntfy/issues/572), thanks to [@cmj2002](https://github.com/cmj2002) for implementing, and [@rounakdatta](https://github.com/rounakdatta) for reporting)
**Bug fixes + maintenance:**
* ⚠️ Remove `Rate-Topics` header due to DoS security issue if `visitor-subscriber-rate-limiting: true` ([#1048](https://github.com/binwiederhier/ntfy/issues/1048))
**Documentation:**
* Remove `mkdocs-simple-hooks` ([#1016](https://github.com/binwiederhier/ntfy/pull/1016), thanks to [@Tom-Hubrecht](https://github.com/Tom-Hubrecht))
* Update Watchtower example ([#1014](https://github.com/binwiederhier/ntfy/pull/1014), thanks to [@lennart-m](https://github.com/lennart-m))
* Fix dead links ([#1022](https://github.com/binwiederhier/ntfy/pull/1022), thanks to [@DerRockWolf](https://github.com/DerRockWolf))
* PowerShell file upload example ([#1004](https://github.com/binwiederhier/ntfy/pull/1004), thanks to [@YMan84](https://github.com/YMan84))
## ntfy iOS app v1.3
Released Nov 26, 2023
This release (hopefully) fixes the issues with the iOS UI not updating properly when new notifications arrive, as well
as notifications not being received (anymore) after previously working. Both issues have been annoying and known bugs
for a long time, and I hope that they are finally fixed.
Many thanks to [@tcaputi](https://github.com/tcaputi) for fixing the issues, and to the anonymous donor for sponsoring these fixes.
**Bug fixes:**
* UI not updating properly ([#267](https://github.com/binwiederhier/ntfy/issues/267)/[#402](https://github.com/binwiederhier/ntfy/issues/402), thanks to [@tcaputi](https://github.com/tcaputi))
## ntfy server v2.8.0
Released November 19, 2023
This release brings a handful of random bug fixes: two unrelated access control list fixes, a fix around web app crashes
for languages with underscores in the language code (e.g. `zh_Hant`, `zh_Hans`, `pt_BR`, ...), a workaround for the
`Priority` header (often used in Cloudflare setups), and support among others support for HTML-only emails (finally),
web app crash fixes
**Bug fixes + maintenance:**
* Support for HTML-only emails ([#690](https://github.com/binwiederhier/ntfy/issues/690)/[#693](https://github.com/binwiederhier/ntfy/pull/693), thanks to [@teastrainer](https://github.com/teastrainer) and [@CrazyWolf13](https://github.com/CrazyWolf13) for reporting)
* Fix ACL issue with topic patterns containing underscores ([#840](https://github.com/binwiederhier/ntfy/issues/840), thanks to [@Joe-0237](https://github.com/Joe-0237) for reporting)
* Fix ACL issue with order of read/write rules ([#914](https://github.com/binwiederhier/ntfy/issues/914)/[#917](https://github.com/binwiederhier/ntfy/pull/917), thanks to [@sandman7920](https://github.com/sandman7920))
* Re-add `tzdata` to Docker images for amd64 image ([#894](https://github.com/binwiederhier/ntfy/issues/894), [#307](https://github.com/binwiederhier/ntfy/pull/307))
* Add special logic to ignore `Priority` header if it resembles an RFC 9218 value ([#851](https://github.com/binwiederhier/ntfy/pull/851)/[#895](https://github.com/binwiederhier/ntfy/pull/895), thanks to [@gusdleon](https://github.com/gusdleon), see also [#351](https://github.com/binwiederhier/ntfy/issues/351), [#353](https://github.com/binwiederhier/ntfy/issues/353), [#461](https://github.com/binwiederhier/ntfy/issues/461))
* PWA: hide install prompt on macOS 14 Safari ([#899](https://github.com/binwiederhier/ntfy/pull/899), thanks to [@nihalgonsalves](https://github.com/nihalgonsalves))
* Fix web app crash in Edge for languages with underline in locale ([#922](https://github.com/binwiederhier/ntfy/pull/922)/[#912](https://github.com/binwiederhier/ntfy/issues/912)/[#852](https://github.com/binwiederhier/ntfy/issues/852), thanks to [@imkero](https://github.com/imkero))
**Additional languages:**
* Finnish (thanks to [@Seppo](https://hosted.weblate.org/user/Seppo/))
## ntfy server v2.7.0
Released August 17, 2023
This release ships Markdown support for the web app (not in the Android app yet), and adds support for
right-to-left languages (RTL) in the web app. It also fixes a few issues around date/time formatting,
internationalization support, a CLI auth bug.
Furthermore, it fixes a security issue around access tokens getting erroneously deleted for other users
in a specific scenario. This was a denial-of-service-type security issue, since it **effectively allowed a
single user to deny access to all other users of a ntfy instance**. Please note that while tokens were
erroneously deleted, **nobody but the token owner ever had access to it.** Please refer to [the ticket](https://github.com/binwiederhier/ntfy/issues/838)
for details. **Please upgrade your ntfy instance if you run a multi-user system.**
**Features:**
* Add support for [Markdown formatting](publish.md#markdown-formatting) in web app ([#310](https://github.com/binwiederhier/ntfy/issues/310), thanks to [@nihalgonsalves](https://github.com/nihalgonsalves))
* Add support for right-to-left languages (RTL) in the web app ([#663](https://github.com/binwiederhier/ntfy/issues/663), thanks to [@nimbleghost](https://github.com/nimbleghost))
**Security:** ⚠️
* Fixes issue with access tokens getting deleted ([#838](https://github.com/binwiederhier/ntfy/issues/838))
**Bug fixes + maintenance:**
* Fix issues with date/time with different locales ([#700](https://github.com/binwiederhier/ntfy/issues/700), thanks to [@nimbleghost](https://github.com/nimbleghost))
* Re-init i18n on each service worker message to avoid missing translations ([#817](https://github.com/binwiederhier/ntfy/pull/817), thanks to [@nihalgonsalves](https://github.com/nihalgonsalves))
* You can now unset the default user:pass/token in `client.yml` for an individual subscription to remove the Authorization header ([#829](https://github.com/binwiederhier/ntfy/issues/829), thanks to [@tomeon](https://github.com/tomeon) for reporting and to [@wunter8](https://github.com/wunter8) for fixing)
**Documentation:**
* Update docs for Apache config ([#819](https://github.com/binwiederhier/ntfy/pull/819), thanks to [@nisbet-hubbard](https://github.com/nisbet-hubbard))
## ntfy server v2.6.2
Released June 30, 2023
With this release, the ntfy web app now contains a **[progressive web app](subscribe/pwa.md) (PWA)
with Web Push support**, which means you'll be able to **install the ntfy web app on your desktop or phone** similar
to a native app (__even on iOS!__ 🥳). Installing the PWA gives ntfy web its own launcher, a standalone window,
push notifications, and an app badge with the unread notification count. Note that for self-hosted servers,
[Web Push](config.md#web-push) must be configured.
On top of that, this release also brings **dark mode** 🧛🌙 to the web app.
🙏 A huge thanks for this release goes to [@nimbleghost](https://github.com/nimbleghost), for basically implementing the
Web Push / PWA and dark mode feature by himself. I'm really grateful for your contributions.
❤️ If you like ntfy, **please consider sponsoring us** via [GitHub Sponsors](https://github.com/sponsors/binwiederhier)
and [Liberapay](https://en.liberapay.com/ntfy/), or buying a [paid plan via the web app](https://ntfy.sh/app) (20% off
if you use promo code `MYTOPIC`). ntfy will always remain open source.
**Features:**
* The web app now supports Web Push, and is installable as a [progressive web app (PWA)](https://docs.ntfy.sh/subscribe/pwa/) on Chrome, Edge, Android, and iOS ([#751](https://github.com/binwiederhier/ntfy/pull/751), thanks to [@nimbleghost](https://github.com/nimbleghost))
* Support for dark mode in the web app ([#206](https://github.com/binwiederhier/ntfy/issues/206), thanks to [@nimbleghost](https://github.com/nimbleghost))
**Bug fixes:**
* Support encoding any header as RFC 2047 ([#737](https://github.com/binwiederhier/ntfy/issues/737), thanks to [@cfouche3005](https://github.com/cfouche3005) for reporting)
* Do not forward poll requests for UnifiedPush messages (no ticket, thanks to NoName for reporting)
* Fix `ntfy pub %` segfaulting ([#760](https://github.com/binwiederhier/ntfy/issues/760), thanks to [@clesmian](https://github.com/clesmian) for reporting)
* Newly created access tokens are now lowercase only to fully support `<topic>+<token>@<domain>` email syntax ([#773](https://github.com/binwiederhier/ntfy/issues/773), thanks to gingervitiz for reporting)
* The .1 release fixes a few visual issues with dark mode, and other web app updates ([#791](https://github.com/binwiederhier/ntfy/pull/791), [#793](https://github.com/binwiederhier/ntfy/pull/793), [#792](https://github.com/binwiederhier/ntfy/pull/792), thanks to [@nimbleghost](https://github.com/nimbleghost))
* The .2 release fixes issues with the service worker in Firefox and adds automatic service worker updates ([#795](https://github.com/binwiederhier/ntfy/pull/795), thanks to [@nimbleghost](https://github.com/nimbleghost))
**Maintenance:**
* Improved GitHub Actions flow ([#745](https://github.com/binwiederhier/ntfy/pull/745), thanks to [@nimbleghost](https://github.com/nimbleghost))
* Web: Add JS formatter "prettier" ([#746](https://github.com/binwiederhier/ntfy/pull/746), thanks to [@nimbleghost](https://github.com/nimbleghost))
* Web: Add eslint with eslint-config-airbnb ([#748](https://github.com/binwiederhier/ntfy/pull/748), thanks to [@nimbleghost](https://github.com/nimbleghost))
* Web: Switch to Vite ([#749](https://github.com/binwiederhier/ntfy/pull/749), thanks to [@nimbleghost](https://github.com/nimbleghost))
**Changes in tarball/zip naming:**
Due to a [change in GoReleaser](https://goreleaser.com/deprecations/#archivesreplacements), some of the binary release
archives now have slightly different names. My apologies if this causes issues in the downstream projects that use ntfy:
- `ntfy_v${VERSION}_windows_x86_64.zip` -> `ntfy_v${VERSION}_windows_amd64.zip`
- `ntfy_v${VERSION}_linux_x86_64.tar.gz` -> `ntfy_v${VERSION}_linux_amd64.tar.gz`
- `ntfy_v${VERSION}_macOS_all.tar.gz` -> `ntfy_v${VERSION}_darwin_all.tar.gz`
## ntfy server v2.5.0
Released May 18, 2023
@@ -31,7 +260,7 @@ if you use promo code `MYTOPIC`). ntfy will always remain open source.
## ntfy server v2.4.0
Released Apr 26, 2023
This release adds a tiny `v1/stats` endpoint to expose how many messages have been published, and adds suport to encode the `X-Title`,
This release adds a tiny `v1/stats` endpoint to expose how many messages have been published, and adds support to encode the `X-Title`,
`X-Message` and `X-Tags` header as RFC 2047. It's a pretty small release, and mainly enables the release of the new ntfy.sh website.
❤️ If you like ntfy, **please consider sponsoring me** via [GitHub Sponsors](https://github.com/sponsors/binwiederhier)
@@ -520,7 +749,7 @@ minute or so, due to competing stats gathering (personal installations will like
**Features:**
* Add `cache-startup-queries` option to allow custom [SQLite performance tuning](config.md#wal-for-message-cache) (no ticket)
* Add `cache-startup-queries` option to allow custom [SQLite performance tuning](config.md#message-cache) (no ticket)
* ntfy CLI can now [wait for a command or PID](subscribe/cli.md#wait-for-pidcommand) before publishing ([#263](https://github.com/binwiederhier/ntfy/issues/263), thanks to the [original ntfy](https://github.com/dschep/ntfy) for the idea)
* Trace: Log entire HTTP request to simplify debugging (no ticket)
* Allow setting user password via `NTFY_PASSWORD` env variable ([#327](https://github.com/binwiederhier/ntfy/pull/327), thanks to [@Kenix3](https://github.com/Kenix3))
@@ -1194,7 +1423,7 @@ Released Dec 28, 2021
**Features & bug fixes:**
* [Publish messages via e-mail](ntfy.sh/docs/publish/#e-mail-publishing) #66
* [Publish messages via e-mail](publish.md#e-mail-publishing) #66
* Server-side work to support [unifiedpush.org](https://unifiedpush.org) #64
* Fixing the Santa bug #65
@@ -1204,6 +1433,12 @@ and the [ntfy Android app](https://github.com/binwiederhier/ntfy-android/release
## Not released yet
### ntfy server v2.13.0 (UNRELEASED)
**Features:**
* Support `X-Client-IP`, `X-Real-IP`, `Forwarded` headers for [rate limiting](config.md#ip-based-rate-limiting) via `proxy-forwarded-header` and `proxy-trusted-addresses` ([#1360](https://github.com/binwiederhier/ntfy/pull/1360)/[#1252](https://github.com/binwiederhier/ntfy/pull/1252), thanks to [@pixitha](https://github.com/pixitha))
### ntfy Android app v1.16.1 (UNRELEASED)
**Features:**
@@ -1219,18 +1454,3 @@ and the [ntfy Android app](https://github.com/binwiederhier/ntfy-android/release
**Additional languages:**
* Swedish (thanks to [@hellbown](https://hosted.weblate.org/user/hellbown/))
### ntfy server v2.6.0 (UNRELEASED)
**Bug fixes:**
* Support encoding any header as RFC 2047 ([#737](https://github.com/binwiederhier/ntfy/issues/737), thanks to [@cfouche3005](https://github.com/cfouche3005) for reporting)
* Do not forward poll requests for UnifiedPush messages (no ticket, thanks to NoName for reporting)
* Fix `ntfy pub %` segfaulting ([#760](https://github.com/binwiederhier/ntfy/issues/760), thanks to [@clesmian](https://github.com/clesmian) for reporting)
**Maintenance:**
* Improved GitHub Actions flow ([#745](https://github.com/binwiederhier/ntfy/pull/745), thanks to [@nimbleghost](https://github.com/nimbleghost))
* Web: Add JS formatter "prettier" ([#746](https://github.com/binwiederhier/ntfy/pull/746), thanks to [@nimbleghost](https://github.com/nimbleghost))
* Web: Add eslint with eslint-config-airbnb ([#748](https://github.com/binwiederhier/ntfy/pull/748), thanks to [@nimbleghost](https://github.com/nimbleghost))
* Web: Switch to Vite ([#749](https://github.com/binwiederhier/ntfy/pull/749), thanks to [@nimbleghost](https://github.com/nimbleghost))

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

BIN
docs/static/img/cdio-setup.jpg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 155 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

BIN
docs/static/img/pwa-badge.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 185 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 162 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 KiB

BIN
docs/static/img/pwa-install.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

BIN
docs/static/img/pwa.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

BIN
docs/static/img/web-markdown.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 248 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

After

Width:  |  Height:  |  Size: 96 KiB

View File

@@ -1,99 +1,103 @@
// Link tabs, as per https://facelessuser.github.io/pymdown-extensions/extensions/tabbed/#linked-tabs
const savedCodeTab = localStorage.getItem('savedTab')
const codeTabs = document.querySelectorAll(".tabbed-set > input")
const savedCodeTab = localStorage.getItem("savedTab");
const codeTabs = document.querySelectorAll(".tabbed-set > input");
for (const tab of codeTabs) {
tab.addEventListener("click", () => {
const current = document.querySelector(`label[for=${tab.id}]`)
const pos = current.getBoundingClientRect().top
const labelContent = current.innerHTML
const labels = document.querySelectorAll('.tabbed-set > label, .tabbed-alternate > .tabbed-labels > label')
for (const label of labels) {
if (label.innerHTML === labelContent) {
document.querySelector(`input[id=${label.getAttribute('for')}]`).checked = true
}
}
// Preserve scroll position
const delta = (current.getBoundingClientRect().top) - pos
window.scrollBy(0, delta)
// Save
localStorage.setItem('savedTab', labelContent)
})
// Select saved tab
const current = document.querySelector(`label[for=${tab.id}]`)
const labelContent = current.innerHTML
if (savedCodeTab === labelContent) {
tab.checked = true
tab.addEventListener("click", () => {
const current = document.querySelector(`label[for=${tab.id}]`);
const pos = current.getBoundingClientRect().top;
const labelContent = current.innerHTML;
const labels = document.querySelectorAll(".tabbed-set > label, .tabbed-alternate > .tabbed-labels > label");
for (const label of labels) {
if (label.innerHTML === labelContent) {
document.querySelector(`input[id=${label.getAttribute("for")}]`).checked = true;
}
}
// Preserve scroll position
const delta = (current.getBoundingClientRect().top) - pos;
window.scrollBy(0, delta);
// Save
localStorage.setItem("savedTab", labelContent);
});
// Select saved tab
const current = document.querySelector(`label[for=${tab.id}]`);
const labelContent = current.innerHTML;
if (savedCodeTab === labelContent) {
tab.checked = true;
}
}
// Lightbox for screenshot
const lightbox = document.createElement('div');
lightbox.classList.add('lightbox');
const lightbox = document.createElement("div");
lightbox.classList.add("lightbox");
document.body.appendChild(lightbox);
const showScreenshotOverlay = (e, el, group, index) => {
lightbox.classList.add('show');
document.addEventListener('keydown', nextScreenshotKeyboardListener);
return showScreenshot(e, group, index);
lightbox.classList.add("show");
document.addEventListener("keydown", nextScreenshotKeyboardListener);
return showScreenshot(e, group, index);
};
const showScreenshot = (e, group, index) => {
const actualIndex = resolveScreenshotIndex(group, index);
lightbox.innerHTML = '<div class="close-lightbox"></div>' + screenshots[group][actualIndex].innerHTML;
lightbox.querySelector('img').onclick = (e) => { return showScreenshot(e, group, actualIndex+1); };
currentScreenshotGroup = group;
currentScreenshotIndex = actualIndex;
e.stopPropagation();
return false;
const actualIndex = resolveScreenshotIndex(group, index);
lightbox.innerHTML = "<div class=\"close-lightbox\"></div>" + screenshots[group][actualIndex].innerHTML;
lightbox.querySelector("img").onclick = (e) => {
return showScreenshot(e, group, actualIndex + 1);
};
currentScreenshotGroup = group;
currentScreenshotIndex = actualIndex;
e.stopPropagation();
return false;
};
const nextScreenshot = (e) => {
return showScreenshot(e, currentScreenshotGroup, currentScreenshotIndex+1);
return showScreenshot(e, currentScreenshotGroup, currentScreenshotIndex + 1);
};
const previousScreenshot = (e) => {
return showScreenshot(e, currentScreenshotGroup, currentScreenshotIndex-1);
return showScreenshot(e, currentScreenshotGroup, currentScreenshotIndex - 1);
};
const resolveScreenshotIndex = (group, index) => {
if (index < 0) {
return screenshots[group].length - 1;
} else if (index > screenshots[group].length - 1) {
return 0;
}
return index;
if (index < 0) {
return screenshots[group].length - 1;
} else if (index > screenshots[group].length - 1) {
return 0;
}
return index;
};
const hideScreenshotOverlay = (e) => {
lightbox.classList.remove('show');
document.removeEventListener('keydown', nextScreenshotKeyboardListener);
lightbox.classList.remove("show");
document.removeEventListener("keydown", nextScreenshotKeyboardListener);
};
const nextScreenshotKeyboardListener = (e) => {
switch (e.keyCode) {
case 37:
previousScreenshot(e);
break;
case 39:
nextScreenshot(e);
break;
}
switch (e.keyCode) {
case 37:
previousScreenshot(e);
break;
case 39:
nextScreenshot(e);
break;
}
};
let currentScreenshotGroup = '';
let currentScreenshotGroup = "";
let currentScreenshotIndex = 0;
let screenshots = {};
Array.from(document.getElementsByClassName('screenshots')).forEach((sg) => {
const group = sg.id;
screenshots[group] = [...sg.querySelectorAll('a')];
screenshots[group].forEach((el, index) => {
el.onclick = (e) => { return showScreenshotOverlay(e, el, group, index); };
});
Array.from(document.getElementsByClassName("screenshots")).forEach((sg) => {
const group = sg.id;
screenshots[group] = [...sg.querySelectorAll("a")];
screenshots[group].forEach((el, index) => {
el.onclick = (e) => {
return showScreenshotOverlay(e, el, group, index);
};
});
});
lightbox.onclick = hideScreenshotOverlay;

View File

@@ -132,7 +132,7 @@ easy to use. Here's what it looks like. You may also want to check out the [full
### Subscribe as raw stream
The `/raw` endpoint will output one line per message, and **will only include the message body**. It's useful for extremely
simple scripts, and doesn't include all the data. Additional fields such as [priority](../publish.md#message-priority),
[tags](../publish.md#tags--emojis--) or [message title](../publish.md#message-title) are not included in this output
[tags](../publish.md#tags-emojis) or [message title](../publish.md#message-title) are not included in this output
format. Keepalive messages are sent as empty lines.
=== "Command line (curl)"
@@ -190,9 +190,10 @@ format. Keepalive messages are sent as empty lines.
## WebSockets
You may also subscribe to topics via [WebSockets](https://en.wikipedia.org/wiki/WebSocket), which is also widely
supported in many languages. Most notably, WebSockets are natively supported in JavaScript. On the command line,
I recommend [websocat](https://github.com/vi/websocat), a fantastic tool similar to `socat` or `curl`, but specifically
for WebSockets.
supported in many languages. Most notably, WebSockets are natively supported in JavaScript. You may also want to
check out the [full example on GitHub](https://github.com/binwiederhier/ntfy/tree/main/examples/web-example-websocket).
On the command line, I recommend [websocat](https://github.com/vi/websocat), a fantastic tool similar to `socat`
or `curl`, but specifically for WebSockets.
The WebSockets endpoint is available at `<topic>/ws` and returns messages as JSON objects similar to the
[JSON stream endpoint](#subscribe-as-json-stream).
@@ -256,6 +257,14 @@ curl -s "ntfy.sh/mytopic/json?since=1645970742"
curl -s "ntfy.sh/mytopic/json?since=nFS3knfcQ1xe"
```
### Fetch latest message
If you only want the most recent message sent to a topic and do not have a message ID or timestamp to use with
`since=`, you can use `since=latest` to grab the most recent message from the cache for a particular topic.
```
curl -s "ntfy.sh/mytopic/json?poll=1&since=latest"
```
### Fetch scheduled messages
Messages that are [scheduled to be delivered](../publish.md#scheduled-delivery) at a later date are not typically
returned when subscribing via the API, which makes sense, because after all, the messages have technically not been
@@ -304,7 +313,7 @@ Depending on whether the server is configured to support [access control](../con
may be read/write protected so that only users with the correct credentials can subscribe or publish to them.
To publish/subscribe to protected topics, you can:
* Use [basic auth](../publish.md#basic-auth), e.g. `Authorization: Basic dGVzdHVzZXI6ZmFrZXBhc3N3b3Jk`
* Use [basic auth](../publish.md#authentication), e.g. `Authorization: Basic dGVzdHVzZXI6ZmFrZXBhc3N3b3Jk`
* or use the [`auth` query parameter](../publish.md#query-param), e.g. `?auth=QmFzaWMgZEdWemRIVnpaWEk2Wm1GclpYQmhjM04zYjNKaw`
Please refer to the [publishing documentation](../publish.md#authentication) for additional details.

View File

@@ -10,7 +10,7 @@ to topics via the ntfy CLI. The CLI is included in the same `ntfy` binary that c
## Install + configure
To install the ntfy CLI, simply **follow the steps outlined on the [install page](../install.md)**. The ntfy server and
client are the same binary, so it's all very convenient. After installing, you can (optionally) configure the client
by creating `~/.config/ntfy/client.yml` (for the non-root user), or `/etc/ntfy/client.yml` (for the root user). You
by creating `~/.config/ntfy/client.yml` (for the non-root user), `~/Library/Application Support/ntfy/client.yml` (for the macOS non-root user), or `/etc/ntfy/client.yml` (for the root user). You
can find a [skeleton config](https://github.com/binwiederhier/ntfy/blob/main/client/client.yml) on GitHub.
If you just want to use [ntfy.sh](https://ntfy.sh), you don't have to change anything. If you **self-host your own server**,
@@ -190,6 +190,10 @@ Here's an example config file that subscribes to three different topics, executi
=== "~/.config/ntfy/client.yml (Linux)"
```yaml
default-host: https://ntfy.sh
default-user: phill
default-password: mypass
subscribe:
- topic: echo-this
command: 'echo "Message received: $message"'
@@ -210,9 +214,12 @@ Here's an example config file that subscribes to three different topics, executi
fi
```
=== "~/Library/Application Support/ntfy/client.yml (macOS)"
```yaml
default-host: https://ntfy.sh
default-user: phill
default-password: mypass
subscribe:
- topic: echo-this
command: 'echo "Message received: $message"'
@@ -226,6 +233,10 @@ Here's an example config file that subscribes to three different topics, executi
=== "%AppData%\ntfy\client.yml (Windows)"
```yaml
default-host: https://ntfy.sh
default-user: phill
default-password: mypass
subscribe:
- topic: echo-this
command: 'echo Message received: %message%'
@@ -263,43 +274,31 @@ will be used, otherwise, the subscription settings will override the defaults.
require authentication), be sure that the servers/topics you subscribe to use HTTPS to prevent leaking the username and password.
### Using the systemd service
You can use the `ntfy-client` systemd service (see [ntfy-client.service](https://github.com/binwiederhier/ntfy/blob/main/client/ntfy-client.service))
to subscribe to multiple topics just like in the example above. The service is automatically installed (but not started)
if you install the deb/rpm package. To configure it, simply edit `/etc/ntfy/client.yml` and run `sudo systemctl restart ntfy-client`.
You can use the `ntfy-client` systemd services to subscribe to multiple topics just like in the example above.
!!! info
The `ntfy-client.service` runs as user `ntfy`, meaning that typical Linux permission restrictions apply. See below
for how to fix this.
You have the option of either enabling `ntfy-client` as a **system service** (see [here](https://github.com/binwiederhier/ntfy/blob/main/client/ntfy-client.service))
or **user service** (see [here](https://github.com/binwiederhier/ntfy/blob/main/client/user/ntfy-client.service)). Neither system service nor user service are enabled or started by default, so you have to do that yourself.
If the service runs on your personal desktop machine, you may want to override the service user/group (`User=` and `Group=`), and
adjust the `DISPLAY` and `DBUS_SESSION_BUS_ADDRESS` environment variables. This will allow you to run commands in your X session
as the primary machine user.
You can either manually override these systemd service entries with `sudo systemctl edit ntfy-client`, and add this
(assuming your user is `phil`). Don't forget to run `sudo systemctl daemon-reload` and `sudo systemctl restart ntfy-client`
after editing the service file:
=== "/etc/systemd/system/ntfy-client.service.d/override.conf"
```
[Service]
User=phil
Group=phil
Environment="DISPLAY=:0" "DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus"
```
Or you can run the following script that creates this override config for you:
**System service:** The `ntfy-client` systemd system service runs as the `ntfy` user. When enabled, it is started at system boot. To configure it as a system
service, edit `/etc/ntfy/client.yml` and then enable/start the service (as root), like so:
```
sudo sh -c 'cat > /etc/systemd/system/ntfy-client.service.d/override.conf' <<EOF
[Service]
User=$USER
Group=$USER
Environment="DISPLAY=:0" "DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/$(id -u)/bus"
EOF
sudo systemctl daemon-reload
sudo systemctl enable ntfy-client
sudo systemctl restart ntfy-client
```
The system service runs as user `ntfy`, meaning that typical Linux permission restrictions apply. It also means that the system service cannot run commands in your X session as the primary machine user (unlike the user service).
**User service:** The `ntfy-client` user service is run when the user logs into their desktop environment. To enable/start it, edit `~/.config/ntfy/client.yml` and
run the following commands (without sudo!):
```
systemctl --user enable ntfy-client
systemctl --user restart ntfy-client
```
Unlike the system service, the user service can interact with the user's desktop environment, and run commands like `notify-send` to display desktop notifications.
It can also run commands that require access to the user's home directory, such as `gnome-calculator`.
### Authentication
Depending on whether the server is configured to support [access control](../config.md#access-control), some topics
@@ -317,7 +316,7 @@ You can either add your username and password to the configuration file:
password: mypass
```
Or with the `ntfy subscibe` command:
Or with the `ntfy subscribe` command:
```
ntfy subscribe \
-u phil:mypass \

View File

@@ -12,6 +12,9 @@ You can get the Android app from both [Google Play](https://play.google.com/stor
from [F-Droid](https://f-droid.org/en/packages/io.heckel.ntfy/). Both are largely identical, with the one exception that
the F-Droid flavor does not use Firebase. The iOS app can be downloaded from the [App Store](https://apps.apple.com/us/app/ntfy/id1625396347).
Alternatively, you may also want to consider using the **[progressive web app (PWA)](pwa.md)** instead of the native app.
The PWA is a website that you can add to your home screen, and it will behave just like a native app.
## Overview
A picture is worth a thousand words. Here are a few screenshots showing what the app looks like. It's all pretty
straight forward. You can add topics and as soon as you add them, you can [publish messages](../publish.md) to them.

69
docs/subscribe/pwa.md Normal file
View File

@@ -0,0 +1,69 @@
# Using the progressive web app (PWA)
While ntfy doesn't have a native desktop app, it is built as a [progressive web app](https://developer.mozilla.org/en-US/docs/Web/Progressive_web_apps) (PWA)
and thus can be **installed on both desktop and mobile devices**.
This gives it its own launcher (e.g. shortcut on Windows, app on macOS, launcher shortcut on Linux, home screen icon on iOS, and
launcher icon on Android), a standalone window, push notifications, and an app badge with the unread notification count.
Web app installation is **supported on** (see [compatibility table](https://caniuse.com/web-app-manifest) for details):
- **Chrome:** Android, Windows, Linux, macOS
- **Safari:** iOS 16.4+, macOS 14+
- **Firefox:** Android, as well as on Windows/Linux [via an extension](https://addons.mozilla.org/en-US/firefox/addon/pwas-for-firefox/)
- **Edge:** Windows
Note that for self-hosted servers, [Web Push](../config.md#web-push) must be configured for the PWA to work.
## Installation
### Chrome on Desktop
To install and register the web app via Chrome, click the "install app" icon. After installation, you can find the app in your
app drawer:
<div id="pwa-screenshots-chrome-safari-desktop" class="screenshots">
<a href="../../static/img/pwa-install.png"><img src="../../static/img/pwa-install.png"/></a>
<a href="../../static/img/pwa.png"><img src="../../static/img/pwa.png"/></a>
<a href="../../static/img/pwa-badge.png"><img src="../../static/img/pwa-badge.png"/></a>
</div>
### Safari on macOS
To install and register the web app via Safari, click on the Share menu and click Add to Dock. You need to be on macOS Sonoma (14) or higher.
<div id="pwa-screenshots-safari-desktop" class="screenshots">
<a href="../../static/img/pwa-install-macos-safari-add-to-dock.png"><img src="../../static/img/pwa-install-macos-safari-add-to-dock.png"/></a>
</div>
### Chrome/Firefox on Android
For Chrome on Android, either click the "Add to Home Screen" banner at the bottom of the screen, or select "Install app"
in the menu, and then click "Install" in the popup menu. After installation, you can find the app in your app drawer,
and on your home screen.
<div id="pwa-screenshots-chrome-android" class="screenshots">
<a href="../../static/img/pwa-install-chrome-android.jpg"><img src="../../static/img/pwa-install-chrome-android.jpg"/></a>
<a href="../../static/img/pwa-install-chrome-android-menu.jpg"><img src="../../static/img/pwa-install-chrome-android-menu.jpg"/></a>
<a href="../../static/img/pwa-install-chrome-android-popup.jpg"><img src="../../static/img/pwa-install-chrome-android-popup.jpg"/></a>
</div>
For Firefox, select "Install" in the menu, and then click "Add" to add an icon to your home screen:
<div id="pwa-screenshots-firefox-android" class="screenshots">
<a href="../../static/img/pwa-install-firefox-android-menu.jpg"><img src="../../static/img/pwa-install-firefox-android-menu.jpg"/></a>
<a href="../../static/img/pwa-install-firefox-android-popup.jpg"><img src="../../static/img/pwa-install-firefox-android-popup.jpg"/></a>
</div>
### Safari on iOS
On iOS Safari, tap on the Share menu, then tap "Add to Home Screen":
<div id="pwa-screenshots-safari-ios" class="screenshots">
<a href="../../static/img/pwa-install-safari-ios-button.jpg"><img src="../../static/img/pwa-install-safari-ios-button.jpg"/></a>
<a href="../../static/img/pwa-install-safari-ios-menu.jpg"><img src="../../static/img/pwa-install-safari-ios-menu.jpg"/></a>
<a href="../../static/img/pwa-install-safari-ios-add-icon.jpg"><img src="../../static/img/pwa-install-safari-ios-add-icon.jpg"/></a>
</div>
## Background notifications
Background notifications via web push are enabled by default and cannot be turned off when the app is installed, as notifications would
not be delivered reliably otherwise. You can mute topics you don't want to receive notifications for.
On desktop, you generally need either your browser or the web app open to receive notifications, though the ntfy tab doesn't need to be
open. On mobile, you don't need to have the web app open to receive notifications. Look at the [web docs](./web.md#background-notifications)
for a detailed breakdown.

View File

@@ -1,27 +1,75 @@
# Subscribe from the Web UI
You can use the Web UI to subscribe to topics as well. If you do, and you keep the website open, **notifications will
pop up as desktop notifications**. Simply type in the topic name and click the *Subscribe* button. The browser will
keep a connection open and listen for incoming notifications.
# Subscribe from the web app
The web app lets you subscribe and publish messages to ntfy topics. For ntfy.sh, the web app is available at [ntfy.sh/app](https://ntfy.sh/app).
To subscribe, simply type in the topic name and click the *Subscribe* button. **After subscribing, messages published to the topic
will appear in the web app, and pop up as a notification.**
<div id="subscribe-screenshots" class="screenshots">
<a href="../../static/img/web-subscribe.png"><img src="../../static/img/web-subscribe.png"/></a>
</div>
## Publish messages
To learn how to send messages, check out the [publishing page](../publish.md).
<div id="web-screenshots" class="screenshots">
<a href="../../static/img/web-detail.png"><img src="../../static/img/web-detail.png"/></a>
<a href="../../static/img/web-notification.png"><img src="../../static/img/web-notification.png"/></a>
<a href="../../static/img/web-subscribe.png"><img src="../../static/img/web-subscribe.png"/></a>
</div>
To keep receiving desktop notifications from ntfy, you need to keep the website open. What I do, and what I highly recommend,
is to pin the tab so that it's always open, but sort of out of the way:
<figure markdown>
![pinned](../static/img/web-pin.png){ width=500 }
<figcaption>Pin web app to move it out of the way</figcaption>
</figure>
## Topic reservations
If topic reservations are enabled, you can claim ownership over topics and define access to it:
<div id="reserve-screenshots" class="screenshots">
<a href="../../static/img/web-reserve-topic.png"><img src="../../static/img/web-reserve-topic.png"/></a>
<a href="../../static/img/web-reserve-topic-dialog.png"><img src="../../static/img/web-reserve-topic-dialog.png"/></a>
</div>
## Notification features and browser support
- Emoji tags are supported in all browsers
- [Click](../publish.md#click-action) actions are supported in all browsers
- Only Chrome, Edge, and Opera support displaying view and http [actions](../publish.md#action-buttons) in notifications.
Their presentation is platform specific.
Note that HTTP actions are performed using fetch and thus are limited to the [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS)
rules, which means that any URL you include needs to respond to a [preflight request](https://developer.mozilla.org/en-US/docs/Glossary/Preflight_request)
with headers allowing the origin of the ntfy web app (`Access-Control-Allow-Origin: https://ntfy.sh`) or `*`.
- Only Chrome, Edge, and Opera support displaying [images](../publish.md#attachments) in notifications.
Look at the [Notifications API](https://developer.mozilla.org/en-US/docs/Web/API/Notifications_API#browser_compatibility)
for more info.
## Background notifications
While subscribing, you have the option to enable background notifications on supported browsers (see "Settings" tab).
Note: If you add the web app to your homescreen (as a progressive web app, more info in the [installed web app](pwa.md)
docs), you cannot turn these off, as notifications would not be delivered reliably otherwise. You can mute topics you don't want to receive
notifications for.
**If background notifications are off:** This requires an active ntfy tab to be open to receive notifications.
These are typically instantaneous, and will appear as a system notification. If you don't see these, check that your browser
is allowed to show notifications (for example in System Settings on macOS). If you don't want to enable background notifications,
**pinning the ntfy tab on your browser** is a good solution to leave it running.
**If background notifications are on:** This uses the [Web Push API](https://caniuse.com/push-api). You don't need an active
ntfy tab open, but in some cases you may need to keep your browser open. Background notifications are only supported on the
same server hosting the web app. You cannot use another server, but can instead subscribe on the other server itself.
If the ntfy app is not opened for more than a week, background notifications will be paused. You can resume them
by opening the app again, and will get a warning notification before they are paused.
| Browser | Platform | Browser Running | Browser Not Running | Restrictions |
|---------|----------|-----------------|---------------------|---------------------------------------------------------|
| Chrome | Desktop | ✅ | ❌ | |
| Firefox | Desktop | ✅ | ❌ | |
| Edge | Desktop | ✅ | ❌ | |
| Opera | Desktop | ✅ | ❌ | |
| Safari | Desktop | ✅ | ✅ | requires Safari 16.1, macOS 13 Ventura |
| Chrome | Android | ✅ | ✅ | |
| Firefox | Android | ✅ | ✅ | |
| Safari | iOS | ⚠️ | ⚠️ | requires iOS 16.4, only when app is added to homescreen |
(Browsers below 1% usage not shown, look at the [Push API](https://caniuse.com/push-api) for more info)

View File

@@ -0,0 +1,56 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>ntfy.sh: WebSocket 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: WebSocket 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/WebSocket">WebSocket</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 = `wss://ntfy.sh/example/ws`;
const events = document.getElementById('events');
const websocket = new WebSocket(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
websocket.onopen = () => {
let event = document.createElement('div');
event.innerHTML = `WebSocket connected to ${subscribeURL}`;
events.appendChild(event);
};
websocket.onerror = (e) => {
let event = document.createElement('div');
event.innerHTML = `WebSocket error: Failed to connect to ${subscribeURL}`;
events.appendChild(event);
};
websocket.onmessage = (e) => {
let event = document.createElement('div');
event.innerHTML = e.data;
events.appendChild(event);
};
</script>
</body>
</html>

142
go.mod
View File

@@ -1,74 +1,104 @@
module heckel.io/ntfy
module heckel.io/ntfy/v2
go 1.18
go 1.24
toolchain go1.24.0
require (
cloud.google.com/go/firestore v1.9.0 // indirect
cloud.google.com/go/storage v1.30.1 // indirect
github.com/BurntSushi/toml v1.2.1 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/emersion/go-smtp v0.16.0
github.com/gabriel-vasile/mimetype v1.4.2
github.com/gorilla/websocket v1.5.0
github.com/mattn/go-sqlite3 v1.14.16
github.com/olebedev/when v0.0.0-20221205223600-4d190b02b8d8
github.com/stretchr/testify v1.8.1
github.com/urfave/cli/v2 v2.25.3
golang.org/x/crypto v0.9.0
golang.org/x/oauth2 v0.8.0 // indirect
golang.org/x/sync v0.2.0
golang.org/x/term v0.8.0
golang.org/x/time v0.3.0
google.golang.org/api v0.122.0
cloud.google.com/go/firestore v1.18.0 // indirect
cloud.google.com/go/storage v1.55.0 // indirect
github.com/BurntSushi/toml v1.5.0 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect
github.com/emersion/go-smtp v0.18.0
github.com/gabriel-vasile/mimetype v1.4.9
github.com/gorilla/websocket v1.5.3
github.com/mattn/go-sqlite3 v1.14.28
github.com/olebedev/when v1.1.0
github.com/stretchr/testify v1.10.0
github.com/urfave/cli/v2 v2.27.6
golang.org/x/crypto v0.38.0
golang.org/x/oauth2 v0.30.0 // indirect
golang.org/x/sync v0.14.0
golang.org/x/term v0.32.0
golang.org/x/time v0.11.0
google.golang.org/api v0.235.0
gopkg.in/yaml.v2 v2.4.0
)
replace github.com/emersion/go-smtp => github.com/emersion/go-smtp v0.17.0 // Pin version due to breaking changes, see #839
require github.com/pkg/errors v0.9.1 // indirect
require (
firebase.google.com/go/v4 v4.11.0
github.com/prometheus/client_golang v1.15.1
github.com/stripe/stripe-go/v74 v74.18.0
firebase.google.com/go/v4 v4.15.2
github.com/SherClockHolmes/webpush-go v1.4.0
github.com/microcosm-cc/bluemonday v1.0.27
github.com/prometheus/client_golang v1.22.0
github.com/stripe/stripe-go/v74 v74.30.0
)
require (
cloud.google.com/go v0.110.2 // indirect
cloud.google.com/go/compute v1.19.3 // indirect
cloud.google.com/go/compute/metadata v0.2.3 // indirect
cloud.google.com/go/iam v1.0.1 // indirect
cloud.google.com/go/longrunning v0.4.2 // indirect
cel.dev/expr v0.24.0 // indirect
cloud.google.com/go v0.121.2 // indirect
cloud.google.com/go/auth v0.16.1 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
cloud.google.com/go/compute/metadata v0.7.0 // indirect
cloud.google.com/go/iam v1.5.2 // indirect
cloud.google.com/go/longrunning v0.6.7 // indirect
cloud.google.com/go/monitoring v1.24.2 // indirect
github.com/AlekSi/pointer v1.2.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.28.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.52.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.52.0 // indirect
github.com/MicahParks/keyfunc v1.9.0 // indirect
github.com/aymerick/douceur v0.2.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/emersion/go-sasl v0.0.0-20220912192320-0145f2c60ead // indirect
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/google/s2a-go v0.1.3 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect
github.com/googleapis/gax-go/v2 v2.8.0 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_model v0.4.0 // indirect
github.com/prometheus/common v0.43.0 // indirect
github.com/prometheus/procfs v0.9.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/emersion/go-sasl v0.0.0-20241020182733-b788ff22d5a6 // indirect
github.com/envoyproxy/go-control-plane/envoy v1.32.4 // indirect
github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/go-jose/go-jose/v4 v4.1.0 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/golang-jwt/jwt/v4 v4.5.2 // indirect
github.com/golang-jwt/jwt/v5 v5.2.2 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/google/s2a-go v0.1.9 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect
github.com/googleapis/gax-go/v2 v2.14.2 // indirect
github.com/gorilla/css v1.0.1 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.64.0 // indirect
github.com/prometheus/procfs v0.16.1 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/stretchr/objx v0.5.0 // indirect
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
go.opencensus.io v0.24.0 // indirect
golang.org/x/net v0.10.0 // indirect
golang.org/x/sys v0.8.0 // indirect
golang.org/x/text v0.9.0 // indirect
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/appengine/v2 v2.0.3 // indirect
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect
google.golang.org/grpc v1.55.0 // indirect
google.golang.org/protobuf v1.30.0 // indirect
github.com/spiffe/go-spiffe/v2 v2.5.0 // indirect
github.com/stretchr/objx v0.5.2 // indirect
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
github.com/zeebo/errs v1.4.0 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/contrib/detectors/gcp v1.36.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect
go.opentelemetry.io/otel v1.36.0 // indirect
go.opentelemetry.io/otel/metric v1.36.0 // indirect
go.opentelemetry.io/otel/sdk v1.36.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.36.0 // indirect
go.opentelemetry.io/otel/trace v1.36.0 // indirect
golang.org/x/net v0.40.0 // indirect
golang.org/x/sys v0.33.0 // indirect
golang.org/x/text v0.25.0 // indirect
google.golang.org/appengine/v2 v2.0.6 // indirect
google.golang.org/genproto v0.0.0-20250528174236-200df99c418a // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250528174236-200df99c418a // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a // indirect
google.golang.org/grpc v1.72.2 // indirect
google.golang.org/protobuf v1.36.6 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

436
go.sum
View File

@@ -1,275 +1,285 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.110.2 h1:sdFPBr6xG9/wkBbfhmUz/JmZC7X6LavQgcrVINrKiVA=
cloud.google.com/go v0.110.2/go.mod h1:k04UEeEtb6ZBRTv3dZz4CeJC3jKGxyhl0sAiVVquxiw=
cloud.google.com/go/compute v1.19.3 h1:DcTwsFgGev/wV5+q8o2fzgcHOaac+DKGC91ZlvpsQds=
cloud.google.com/go/compute v1.19.3/go.mod h1:qxvISKp/gYnXkSAD1ppcSOveRAmzxicEv/JlizULFrI=
cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY=
cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA=
cloud.google.com/go/firestore v1.9.0 h1:IBlRyxgGySXu5VuW0RgGFlTtLukSnNkpDiEOMkQkmpA=
cloud.google.com/go/firestore v1.9.0/go.mod h1:HMkjKHNTtRyZNiMzu7YAsLr9K3X2udY2AMwDaMEQiiE=
cloud.google.com/go/iam v1.0.1 h1:lyeCAU6jpnVNrE9zGQkTl3WgNgK/X+uWwaw0kynZJMU=
cloud.google.com/go/iam v1.0.1/go.mod h1:yR3tmSL8BcZB4bxByRv2jkSIahVmCtfKZwLYGBalRE8=
cloud.google.com/go/longrunning v0.4.2 h1:WDKiiNXFTaQ6qz/G8FCOkuY9kJmOJGY67wPUC1M2RbE=
cloud.google.com/go/longrunning v0.4.2/go.mod h1:OHrnaYyLUV6oqwh0xiS7e5sLQhP1m0QU9R+WhGDMgIQ=
cloud.google.com/go/storage v1.30.1 h1:uOdMxAs8HExqBlnLtnQyP0YkvbiDpdGShGKtx6U/oNM=
cloud.google.com/go/storage v1.30.1/go.mod h1:NfxhC0UJE1aXSx7CIIbCf7y9HKT7BiccwkR7+P7gN8E=
firebase.google.com/go/v4 v4.11.0 h1:szjBoiF33A2FavRLIDZjW1mw+OsW/XAtHoYNIqWOjRk=
firebase.google.com/go/v4 v4.11.0/go.mod h1:60c36dWLK4+j05Vw5XMllek3b3PCynU3BfI46OSwsUE=
cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY=
cel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw=
cloud.google.com/go v0.121.2 h1:v2qQpN6Dx9x2NmwrqlesOt3Ys4ol5/lFZ6Mg1B7OJCg=
cloud.google.com/go v0.121.2/go.mod h1:nRFlrHq39MNVWu+zESP2PosMWA0ryJw8KUBZ2iZpxbw=
cloud.google.com/go/auth v0.16.1 h1:XrXauHMd30LhQYVRHLGvJiYeczweKQXZxsTbV9TiguU=
cloud.google.com/go/auth v0.16.1/go.mod h1:1howDHJ5IETh/LwYs3ZxvlkXF48aSqqJUM+5o02dNOI=
cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=
cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=
cloud.google.com/go/compute/metadata v0.7.0 h1:PBWF+iiAerVNe8UCHxdOt6eHLVc3ydFeOCw78U8ytSU=
cloud.google.com/go/compute/metadata v0.7.0/go.mod h1:j5MvL9PprKL39t166CoB1uVHfQMs4tFQZZcKwksXUjo=
cloud.google.com/go/firestore v1.18.0 h1:cuydCaLS7Vl2SatAeivXyhbhDEIR8BDmtn4egDhIn2s=
cloud.google.com/go/firestore v1.18.0/go.mod h1:5ye0v48PhseZBdcl0qbl3uttu7FIEwEYVaWm0UIEOEU=
cloud.google.com/go/iam v1.5.2 h1:qgFRAGEmd8z6dJ/qyEchAuL9jpswyODjA2lS+w234g8=
cloud.google.com/go/iam v1.5.2/go.mod h1:SE1vg0N81zQqLzQEwxL2WI6yhetBdbNQuTvIKCSkUHE=
cloud.google.com/go/logging v1.13.0 h1:7j0HgAp0B94o1YRDqiqm26w4q1rDMH7XNRU34lJXHYc=
cloud.google.com/go/logging v1.13.0/go.mod h1:36CoKh6KA/M0PbhPKMq6/qety2DCAErbhXT62TuXALA=
cloud.google.com/go/longrunning v0.6.7 h1:IGtfDWHhQCgCjwQjV9iiLnUta9LBCo8R9QmAFsS/PrE=
cloud.google.com/go/longrunning v0.6.7/go.mod h1:EAFV3IZAKmM56TyiE6VAP3VoTzhZzySwI/YI1s/nRsY=
cloud.google.com/go/monitoring v1.24.2 h1:5OTsoJ1dXYIiMiuL+sYscLc9BumrL3CarVLL7dd7lHM=
cloud.google.com/go/monitoring v1.24.2/go.mod h1:x7yzPWcgDRnPEv3sI+jJGBkwl5qINf+6qY4eq0I9B4U=
cloud.google.com/go/storage v1.55.0 h1:NESjdAToN9u1tmhVqhXCaCwYBuvEhZLLv0gBr+2znf0=
cloud.google.com/go/storage v1.55.0/go.mod h1:ztSmTTwzsdXe5syLVS0YsbFxXuvEmEyZj7v7zChEmuY=
cloud.google.com/go/trace v1.11.6 h1:2O2zjPzqPYAHrn3OKl029qlqG6W8ZdYaOWRyr8NgMT4=
cloud.google.com/go/trace v1.11.6/go.mod h1:GA855OeDEBiBMzcckLPE2kDunIpC72N+Pq8WFieFjnI=
firebase.google.com/go/v4 v4.15.2 h1:KJtV4rAfO2CVCp40hBfVk+mqUqg7+jQKx7yOgFDnXBg=
firebase.google.com/go/v4 v4.15.2/go.mod h1:qkD/HtSumrPMTLs0ahQrje5gTw2WKFKrzVFoqy4SbKA=
github.com/AlekSi/pointer v1.2.0 h1:glcy/gc4h8HnG2Z3ZECSzZ1IX1x2JxRVuDzaJwQE0+w=
github.com/AlekSi/pointer v1.2.0/go.mod h1:gZGfd3dpW4vEc/UlyfKKi1roIqcCgwOIvb0tSNSBle0=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak=
github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.28.0 h1:VaFXBL0NJpiFBtw4aVJpKHeKULVTcHpD+/G0ibZkcBw=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.28.0/go.mod h1:JXkPazkEc/dZTHzOlzv2vT1DlpWSTbSLmu/1KY6Ly0I=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.52.0 h1:QFgWzcdmJlgEAwJz/zePYVJQxfoJGRtgIqZfIUFg5oQ=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.52.0/go.mod h1:ayYHuYU7iNcNtEs1K9k6D/Bju7u1VEHMQm5qQ1n3GtM=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.52.0 h1:0l8ynskVvq1dvIn5vJbFMf/a/3TqFpRmCMrruFbzlvk=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.52.0/go.mod h1:f/ad5NuHnYz8AOZGuR0cY+l36oSCstdxD73YlIchr6I=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.52.0 h1:wbMd4eG/fOhsCa6+IP8uEDvWF5vl7rNoUWmP5f72Tbs=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.52.0/go.mod h1:gdIm9TxRk5soClCwuB0FtdXsbqtw0aqPwBEurK9tPkw=
github.com/MicahParks/keyfunc v1.9.0 h1:lhKd5xrFHLNOWrDc4Tyb/Q1AJ4LCzQ48GVJyVIID3+o=
github.com/MicahParks/keyfunc v1.9.0/go.mod h1:IdnCilugA0O/99dW+/MkvlyrsX8+L8+x95xuVNtM5jw=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/SherClockHolmes/webpush-go v1.4.0 h1:ocnzNKWN23T9nvHi6IfyrQjkIc0oJWv1B1pULsf9i3s=
github.com/SherClockHolmes/webpush-go v1.4.0/go.mod h1:XSq8pKX11vNV8MJEMwjrlTkxhAj1zKfxmyhdV7Pd6UA=
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443 h1:aQ3y1lwWyqYPiWZThqv1aFbZMiM9vblcSArJRf2Irls=
github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8=
github.com/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3sHPnBo=
github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ=
github.com/emersion/go-sasl v0.0.0-20220912192320-0145f2c60ead h1:fI1Jck0vUrXT8bnphprS1EoVRe2Q5CKCX8iDlpqjQ/Y=
github.com/emersion/go-sasl v0.0.0-20220912192320-0145f2c60ead/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ=
github.com/emersion/go-smtp v0.16.0 h1:eB9CY9527WdEZSs5sWisTmilDX7gG+Q/2IdRcmubpa8=
github.com/emersion/go-smtp v0.16.0/go.mod h1:qm27SGYgoIPRot6ubfQ/GpiPy/g3PaZAVRxiO/sDUgQ=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/emersion/go-sasl v0.0.0-20241020182733-b788ff22d5a6 h1:oP4q0fw+fOSWn3DfFi4EXdT+B+gTtzx8GC9xsc26Znk=
github.com/emersion/go-sasl v0.0.0-20241020182733-b788ff22d5a6/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ=
github.com/emersion/go-smtp v0.17.0 h1:tq90evlrcyqRfE6DSXaWVH54oX6OuZOQECEmhWBMEtI=
github.com/emersion/go-smtp v0.17.0/go.mod h1:qm27SGYgoIPRot6ubfQ/GpiPy/g3PaZAVRxiO/sDUgQ=
github.com/envoyproxy/go-control-plane v0.13.4 h1:zEqyPVyku6IvWCFwux4x9RxkLOMUL+1vC9xUFv5l2/M=
github.com/envoyproxy/go-control-plane v0.13.4/go.mod h1:kDfuBlDVsSj2MjrLEtRWtHlsWIFcGyB2RMO44Dc5GZA=
github.com/envoyproxy/go-control-plane/envoy v1.32.4 h1:jb83lalDRZSpPWW2Z7Mck/8kXZ5CQAFYVjQcdVIr83A=
github.com/envoyproxy/go-control-plane/envoy v1.32.4/go.mod h1:Gzjc5k8JcJswLjAx1Zm+wSYE20UrLtt7JZMWiWQXQEw=
github.com/envoyproxy/go-control-plane/ratelimit v0.1.0 h1:/G9QYbddjL25KvtKTv3an9lx6VBE2cnb8wp1vEGNYGI=
github.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJPzVVHnPgRKdUdwW/KdbRt94AzgRee4=
github.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8=
github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY=
github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok=
github.com/go-jose/go-jose/v4 v4.1.0 h1:cYSYxd3pw5zd2FSXk2vGdn9igQU2PS8MuxrCOCl0FdY=
github.com/go-jose/go-jose/v4 v4.1.0/go.mod h1:GG/vqmYm3Von2nYiB2vGTXzdoNKE5tix5tuc6iAd+sw=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI=
github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/martian/v3 v3.3.2 h1:IqNFLAmvJOgVlpdEBiQbDc2EwKW77amAycfTuWKdfvw=
github.com/google/s2a-go v0.1.3 h1:FAgZmpLl/SXurPEZyCMPBIiiYeTbqfjlbdnCNTAkbGE=
github.com/google/s2a-go v0.1.3/go.mod h1:Ej+mSEMGRnqRzjc7VtF+jdBwYG5fuJfiZ8ELkjEwM0A=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/enterprise-certificate-proxy v0.2.3 h1:yk9/cqRKtT9wXZSsRH9aurXEpJX+U6FLtpYTdC3R06k=
github.com/googleapis/enterprise-certificate-proxy v0.2.3/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k=
github.com/googleapis/gax-go/v2 v2.8.0 h1:UBtEZqx1bjXtOQ5BVTkuYghXrr3N4V123VKJK67vJZc=
github.com/googleapis/gax-go/v2 v2.8.0/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38/qKbhSAKP6QI=
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/martian/v3 v3.3.3 h1:DIhPTQrbPkgs2yJYdXU/eNACCG5DVQjySNRNlflZ9Fc=
github.com/google/martian/v3 v3.3.3/go.mod h1:iEPrYcgCF7jA9OtScMFQyAlZZ4YXTKEtJ1E6RWzmBA0=
github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=
github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/enterprise-certificate-proxy v0.3.6 h1:GW/XbdyBFQ8Qe+YAmFU9uHLo7OnF5tL52HFAgMmyrf4=
github.com/googleapis/enterprise-certificate-proxy v0.3.6/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA=
github.com/googleapis/gax-go/v2 v2.14.2 h1:eBLnkZ9635krYIPD+ag1USrOAI0Nr0QYF3+/3GqO0k0=
github.com/googleapis/gax-go/v2 v2.14.2/go.mod h1:ON64QhlJkhVtSqp4v1uaK92VyZ2gmvDQsweuyLV+8+w=
github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8=
github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0=
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
github.com/olebedev/when v0.0.0-20221205223600-4d190b02b8d8 h1:0uFGkScHef2Xd8g74BMHU1jFcnKEm0PzrPn4CluQ9FI=
github.com/olebedev/when v0.0.0-20221205223600-4d190b02b8d8/go.mod h1:T0THb4kP9D3NNqlvCwIG4GyUioTAzEhB4RNVzig/43E=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/mattn/go-sqlite3 v1.14.28 h1:ThEiQrnbtumT+QMknw63Befp/ce/nUPgBPMlRFEum7A=
github.com/mattn/go-sqlite3 v1.14.28/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk=
github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/olebedev/when v1.1.0 h1:dlpoRa7huImhNtEx4yl0WYfTHVEWmJmIWd7fEkTHayc=
github.com/olebedev/when v1.1.0/go.mod h1:T0THb4kP9D3NNqlvCwIG4GyUioTAzEhB4RNVzig/43E=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo=
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v1.15.1 h1:8tXpTmJbyH5lydzFPoxSIJ0J46jdh3tylbvM1xCv0LI=
github.com/prometheus/client_golang v1.15.1/go.mod h1:e9yaBhRPU2pPNsZwE+JdQl0KEt1N9XgF6zxWmaC0xOk=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY=
github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU=
github.com/prometheus/common v0.43.0 h1:iq+BVjvYLei5f27wiuNiB1DN6DYQkp1c8Bx0Vykh5us=
github.com/prometheus/common v0.43.0/go.mod h1:NCvr5cQIh3Y/gy73/RdVtC9r8xxrxwJnB+2lB3BxrFc=
github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI=
github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q=
github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0=
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
github.com/prometheus/common v0.64.0 h1:pdZeA+g617P7oGv1CzdTzyeShxAGrTBsolKNOLQPGO4=
github.com/prometheus/common v0.64.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8=
github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=
github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/spiffe/go-spiffe/v2 v2.5.0 h1:N2I01KCUkv1FAjZXJMwh95KK1ZIQLYbPfhaxw8WS0hE=
github.com/spiffe/go-spiffe/v2 v2.5.0/go.mod h1:P+NxobPc6wXhVtINNtFjNWGBTreew1GBUCwT2wPmb7g=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stripe/stripe-go/v74 v74.18.0 h1:ImSIoaVkTUozHxa21AhwHYBjwc8fVSJJJB1Q7oaXzIw=
github.com/stripe/stripe-go/v74 v74.18.0/go.mod h1:f9L6LvaXa35ja7eyvP6GQswoaIPaBRvGAimAO+udbBw=
github.com/urfave/cli/v2 v2.25.3 h1:VJkt6wvEBOoSjPFQvOkv6iWIrsJyCrKGtCtxXWwmGeY=
github.com/urfave/cli/v2 v2.25.3/go.mod h1:GHupkWPMM0M/sj1a2b4wUrWBPzazNrIjouW6fmdJLxc=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stripe/stripe-go/v74 v74.30.0 h1:0Kf0KkeFnY7iRhOwvTerX0Ia1BRw+eV1CVJ51mGYAUY=
github.com/stripe/stripe-go/v74 v74.30.0/go.mod h1:f9L6LvaXa35ja7eyvP6GQswoaIPaBRvGAimAO+udbBw=
github.com/urfave/cli/v2 v2.27.6 h1:VdRdS98FNhKZ8/Az8B7MTyGQmpIr36O1EHybx/LaZ4g=
github.com/urfave/cli/v2 v2.27.6/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ=
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4=
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
github.com/zeebo/errs v1.4.0 h1:XNdoD/RRMKP7HD0UhJnIzUy74ISdGGxURlYG8HSWSfM=
github.com/zeebo/errs v1.4.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/contrib/detectors/gcp v1.36.0 h1:F7q2tNlCaHY9nMKHR6XH9/qkp8FktLnIcy6jJNyOCQw=
go.opentelemetry.io/contrib/detectors/gcp v1.36.0/go.mod h1:IbBN8uAIIx734PTonTPxAxnjc2pQTxWNkwfstZ+6H2k=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 h1:q4XOmH/0opmeuJtPsbFNivyl7bCt7yRBbeEm2sC/XtQ=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0/go.mod h1:snMWehoOh2wsEwnvvwtDyFCxVeDAODenXHtn5vzrKjo=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q=
go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg=
go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E=
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0 h1:rixTyDGXFxRy1xzhKrotaHy3/KXdPhlWARrCgK+eqUY=
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0/go.mod h1:dowW6UsM9MKbJq5JTz2AMVp3/5iW5I/TStsk8S+CfHw=
go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE=
go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs=
go.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs=
go.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY=
go.opentelemetry.io/otel/sdk/metric v1.36.0 h1:r0ntwwGosWGaa0CrSt8cuNuTcccMXERFwHX4dThiPis=
go.opentelemetry.io/otel/sdk/metric v1.36.0/go.mod h1:qTNOhFDfKRwX0yXOqJYegL5WRaW376QbB7P4Pb0qva4=
go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w=
go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g=
golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.8.0 h1:6dkIjl3j3LtZ/O3sTgZTMsLKSftL/B8Zgq4huOIIUu8=
golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI=
golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg=
golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk=
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
google.golang.org/api v0.122.0 h1:zDobeejm3E7pEG1mNHvdxvjs5XJoCMzyNH+CmwL94Es=
google.golang.org/api v0.122.0/go.mod h1:gcitW0lvnyWjSp9nKxAbdHKIZ6vF4aajGueeslZOyms=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine/v2 v2.0.3 h1:AyY/mipuqiyCIAqOevfmu5fMDc5/9P/QggWfCQYdkSA=
google.golang.org/appengine/v2 v2.0.3/go.mod h1:2Z0TTdcXxnHdXzmp8drrmOExUDM2WQgyT33c6JDUlJM=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A=
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ=
google.golang.org/grpc v1.55.0 h1:3Oj82/tFSCeUrRTg/5E/7d/W5A1tj6Ky1ABAuZuv5ag=
google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/api v0.235.0 h1:C3MkpQSRxS1Jy6AkzTGKKrpSCOd2WOGrezZ+icKSkKo=
google.golang.org/api v0.235.0/go.mod h1:QpeJkemzkFKe5VCE/PMv7GsUfn9ZF+u+q1Q7w6ckxTg=
google.golang.org/appengine/v2 v2.0.6 h1:LvPZLGuchSBslPBp+LAhihBeGSiRh1myRoYK4NtuBIw=
google.golang.org/appengine/v2 v2.0.6/go.mod h1:WoEXGoXNfa0mLvaH5sV3ZSGXwVmy8yf7Z1JKf3J3wLI=
google.golang.org/genproto v0.0.0-20250528174236-200df99c418a h1:KXuwdBmgjb4T3l4ZzXhP6HxxFKXD9FcK5/8qfJI4WwU=
google.golang.org/genproto v0.0.0-20250528174236-200df99c418a/go.mod h1:Nlk93rrS2X7rV8hiC2gh2A/AJspZhElz9Oh2KGsjLEY=
google.golang.org/genproto/googleapis/api v0.0.0-20250528174236-200df99c418a h1:SGktgSolFCo75dnHJF2yMvnns6jCmHFJ0vE4Vn2JKvQ=
google.golang.org/genproto/googleapis/api v0.0.0-20250528174236-200df99c418a/go.mod h1:a77HrdMjoeKbnd2jmgcWdaS++ZLZAEq3orIOAEIKiVw=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a h1:v2PbRU4K3llS09c7zodFpNePeamkAwG3mPrAery9VeE=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
google.golang.org/grpc v1.72.2 h1:TdbGzwb82ty4OusHWepvFWGLgIbNo1/SUynEN0ssqv8=
google.golang.org/grpc v1.72.2/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

View File

@@ -3,7 +3,7 @@ package log
import (
"encoding/json"
"fmt"
"heckel.io/ntfy/util"
"heckel.io/ntfy/v2/util"
"log"
"os"
"sort"

View File

@@ -198,7 +198,7 @@ func (w *peekLogWriter) Write(p []byte) (n int, err error) {
if len(p) == 0 || p[0] == '{' || CurrentFormat() == TextFormat {
return w.w.Write(p)
}
m := newEvent().Tag(tagStdLog).Render(InfoLevel, strings.TrimSpace(string(p)))
m := newEvent().Tag(tagStdLog).Render(InfoLevel, "%s", strings.TrimSpace(string(p)))
if m == "" {
return 0, nil
}

View File

@@ -3,7 +3,7 @@ package main
import (
"fmt"
"github.com/urfave/cli/v2"
"heckel.io/ntfy/cmd"
"heckel.io/ntfy/v2/cmd"
"os"
"runtime"
)
@@ -23,7 +23,7 @@ If you want to chat, simply join the Discord server (https://discord.gg/cT7ECsZj
the Matrix room (https://matrix.to/#/#ntfy:matrix.org).
ntfy %s (%s), runtime %s, built at %s
Copyright (C) 2022 Philipp C. Heckel, licensed under Apache License 2.0 & GPLv2
Copyright (C) Philipp C. Heckel, licensed under Apache License 2.0 & GPLv2
`, version, commit[:7], runtime.Version(), date)
app := cmd.New()

View File

@@ -64,39 +64,40 @@ markdown_extensions:
- attr_list
- md_in_html
- pymdownx.emoji:
emoji_index: !!python/name:materialx.emoji.twemoji
emoji_generator: !!python/name:materialx.emoji.to_svg
emoji_index: !!python/name:material.extensions.emoji.twemoji
emoji_generator: !!python/name:material.extensions.emoji.to_svg
hooks:
- docs/hooks.py
plugins:
- search
- minify:
minify_html: true
- mkdocs-simple-hooks:
hooks:
on_post_build: "docs.hooks:copy_fonts"
nav:
- "Getting started": index.md
- "Publishing":
- "Sending messages": publish.md
- "Subscribing":
- "From your phone": subscribe/phone.md
- "From the Web app": subscribe/web.md
- "From the CLI": subscribe/cli.md
- "Using the API": subscribe/api.md
- "Self-hosting":
- "Installation": install.md
- "Configuration": config.md
- "Other things":
- "FAQs": faq.md
- "Examples": examples.md
- "Integrations + projects": integrations.md
- "Release notes": releases.md
- "Emojis 🥳 🎉": emojis.md
- "Troubleshooting": troubleshooting.md
- "Known issues": known-issues.md
- "Deprecation notices": deprecations.md
- "Development": develop.md
- "Privacy policy": privacy.md
- "Getting started": index.md
- "Publishing":
- "Sending messages": publish.md
- "Subscribing":
- "From your phone": subscribe/phone.md
- "From the Web app": subscribe/web.md
- "From the Desktop": subscribe/pwa.md
- "From the CLI": subscribe/cli.md
- "Using the API": subscribe/api.md
- "Self-hosting":
- "Installation": install.md
- "Configuration": config.md
- "Other things":
- "FAQs": faq.md
- "Examples": examples.md
- "Integrations + projects": integrations.md
- "Release notes": releases.md
- "Emojis 🥳 🎉": emojis.md
- "Troubleshooting": troubleshooting.md
- "Known issues": known-issues.md
- "Deprecation notices": deprecations.md
- "Development": develop.md
- "Privacy policy": privacy.md

View File

@@ -1,4 +1,3 @@
# The documentation uses 'mkdocs', which is written in Python
mkdocs-material
mkdocs-minify-plugin
mkdocs-simple-hooks

View File

@@ -25,9 +25,9 @@ elif [[ "$1" == *.md ]]; then
<!-- This file was generated by scripts/emoji-convert.sh -->
You can [tag messages](../publish/#tags-emojis) with emojis 🥳 🎉 and other relevant strings. Matching tags are automatically
You can [tag messages](publish.md#tags-emojis) with emojis 🥳 🎉 and other relevant strings. Matching tags are automatically
converted to emojis. This is a reference of all supported emojis. To learn more about the feature, please refer to the
[tagging and emojis page](../publish/#tags-emojis).
[tagging and emojis page](publish.md#tags-emojis).
<table class=\"remove-md-box emoji-table\"><tr>
" > "$1"

View File

@@ -33,7 +33,7 @@ if [ "$1" = "configure" ] || [ "$1" -ge 1 ]; then
fi
fi
if systemctl is-active -q ntfy-client.service; then
echo "Restarting ntfy-client.service ..."
echo "Restarting ntfy-client.service (system) ..."
if [ -x /usr/bin/deb-systemd-invoke ]; then
deb-systemd-invoke try-restart ntfy-client.service >/dev/null || true
else

View File

@@ -4,7 +4,7 @@ import (
"encoding/json"
"errors"
"fmt"
"heckel.io/ntfy/util"
"heckel.io/ntfy/v2/util"
"regexp"
"strings"
"unicode/utf8"

View File

@@ -1,33 +1,41 @@
package server
import (
"heckel.io/ntfy/user"
"io/fs"
"net/netip"
"time"
"heckel.io/ntfy/v2/user"
)
// Defines default config settings (excluding limits, see below)
const (
DefaultListenHTTP = ":80"
DefaultCacheDuration = 12 * time.Hour
DefaultCacheBatchTimeout = time.Duration(0)
DefaultKeepaliveInterval = 45 * time.Second // Not too frequently to save battery (Android read timeout used to be 77s!)
DefaultManagerInterval = time.Minute
DefaultDelayedSenderInterval = 10 * time.Second
DefaultMinDelay = 10 * time.Second
DefaultMaxDelay = 3 * 24 * time.Hour
DefaultMessageDelayMin = 10 * time.Second
DefaultMessageDelayMax = 3 * 24 * time.Hour
DefaultFirebaseKeepaliveInterval = 3 * time.Hour // ~control topic (Android), not too frequently to save battery
DefaultFirebasePollInterval = 20 * time.Minute // ~poll topic (iOS), max. 2-3 times per hour (see docs)
DefaultFirebaseQuotaExceededPenaltyDuration = 10 * time.Minute // Time that over-users are locked out of Firebase if it returns "quota exceeded"
DefaultStripePriceCacheDuration = 3 * time.Hour // Time to keep Stripe prices cached in memory before a refresh is needed
)
// Defines default Web Push settings
const (
DefaultWebPushExpiryWarningDuration = 55 * 24 * time.Hour
DefaultWebPushExpiryDuration = 60 * 24 * time.Hour
)
// Defines all global and per-visitor limits
// - message size limit: the max number of bytes for a message
// - total topic limit: max number of topics overall
// - various attachment limits
const (
DefaultMessageLengthLimit = 4096 // Bytes
DefaultMessageSizeLimit = 4096 // Bytes; note that FCM/APNS have a limit of ~4 KB for the entire message
DefaultTotalTopicLimit = 15000
DefaultAttachmentTotalSizeLimit = int64(5 * 1024 * 1024 * 1024) // 5 GB
DefaultAttachmentFileSizeLimit = int64(15 * 1024 * 1024) // 15 MB
@@ -115,9 +123,9 @@ type Config struct {
MetricsEnable bool
MetricsListenHTTP string
ProfileListenHTTP string
MessageLimit int
MinDelay time.Duration
MaxDelay time.Duration
MessageDelayMin time.Duration
MessageDelayMax time.Duration
MessageSizeLimit int
TotalTopicLimit int
TotalAttachmentSizeLimit int64
VisitorSubscriptionLimit int
@@ -135,7 +143,9 @@ type Config struct {
VisitorAuthFailureLimitReplenish time.Duration
VisitorStatsResetTime time.Time // Time of the day at which to reset visitor stats
VisitorSubscriberRateLimiting bool // Enable subscriber-based rate limiting for UnifiedPush topics
BehindProxy bool
BehindProxy bool // If true, the server will trust the proxy client IP header to determine the client IP address
ProxyForwardedHeader string // The header field to read the real/client IP address from, if BehindProxy is true, defaults to "X-Forwarded-For"
ProxyTrustedAddresses []string // List of trusted proxy addresses that will be stripped from the Forwarded header if BehindProxy is true
StripeSecretKey string
StripeWebhookKey string
StripePriceCacheDuration time.Duration
@@ -146,6 +156,13 @@ type Config struct {
EnableMetrics bool
AccessControlAllowOrigin string // CORS header field to restrict access from web clients
Version string // injected by App
WebPushPrivateKey string
WebPushPublicKey string
WebPushFile string
WebPushEmailAddress string
WebPushStartupQueries string
WebPushExpiryDuration time.Duration
WebPushExpiryWarningDuration time.Duration
}
// NewConfig instantiates a default new server config
@@ -197,9 +214,9 @@ func NewConfig() *Config {
TwilioPhoneNumber: "",
TwilioVerifyBaseURL: "https://verify.twilio.com", // Override for tests
TwilioVerifyService: "",
MessageLimit: DefaultMessageLengthLimit,
MinDelay: DefaultMinDelay,
MaxDelay: DefaultMaxDelay,
MessageSizeLimit: DefaultMessageSizeLimit,
MessageDelayMin: DefaultMessageDelayMin,
MessageDelayMax: DefaultMessageDelayMax,
TotalTopicLimit: DefaultTotalTopicLimit,
TotalAttachmentSizeLimit: 0,
VisitorSubscriptionLimit: DefaultVisitorSubscriptionLimit,
@@ -217,7 +234,8 @@ func NewConfig() *Config {
VisitorAuthFailureLimitReplenish: DefaultVisitorAuthFailureLimitReplenish,
VisitorStatsResetTime: DefaultVisitorStatsResetTime,
VisitorSubscriberRateLimiting: false,
BehindProxy: false,
BehindProxy: false, // If true, the server will trust the proxy client IP header to determine the client IP address
ProxyForwardedHeader: "X-Forwarded-For", // Default header for reverse proxy client IPs
StripeSecretKey: "",
StripeWebhookKey: "",
StripePriceCacheDuration: DefaultStripePriceCacheDuration,
@@ -227,5 +245,11 @@ func NewConfig() *Config {
EnableReservations: false,
AccessControlAllowOrigin: "*",
Version: "",
WebPushPrivateKey: "",
WebPushPublicKey: "",
WebPushFile: "",
WebPushEmailAddress: "",
WebPushExpiryDuration: DefaultWebPushExpiryDuration,
WebPushExpiryWarningDuration: DefaultWebPushExpiryWarningDuration,
}
}

View File

@@ -2,7 +2,7 @@ package server_test
import (
"github.com/stretchr/testify/assert"
"heckel.io/ntfy/server"
"heckel.io/ntfy/v2/server"
"testing"
)

View File

@@ -3,7 +3,7 @@ package server
import (
"encoding/json"
"fmt"
"heckel.io/ntfy/log"
"heckel.io/ntfy/v2/log"
"net/http"
)
@@ -89,7 +89,7 @@ var (
errHTTPBadRequestSinceInvalid = &errHTTP{40008, http.StatusBadRequest, "invalid since parameter", "https://ntfy.sh/docs/subscribe/api/#fetch-cached-messages", nil}
errHTTPBadRequestTopicInvalid = &errHTTP{40009, http.StatusBadRequest, "invalid request: topic invalid", "", nil}
errHTTPBadRequestTopicDisallowed = &errHTTP{40010, http.StatusBadRequest, "invalid request: topic name is not allowed", "", nil}
errHTTPBadRequestMessageNotUTF8 = &errHTTP{40011, http.StatusBadRequest, "invalid message: message must be UTF-8 encoded", "", nil}
errHTTPBadRequestMessageNotUTF8 = &errHTTP{40011, http.StatusBadRequest, "invalid request: message must be UTF-8 encoded", "", nil}
errHTTPBadRequestAttachmentURLInvalid = &errHTTP{40013, http.StatusBadRequest, "invalid request: attachment URL is invalid", "https://ntfy.sh/docs/publish/#attachments", nil}
errHTTPBadRequestAttachmentsDisallowed = &errHTTP{40014, http.StatusBadRequest, "invalid request: attachments not allowed", "https://ntfy.sh/docs/config/#attachments", nil}
errHTTPBadRequestAttachmentsExpiryBeforeDelivery = &errHTTP{40015, http.StatusBadRequest, "invalid request: attachment expiry before delayed delivery date", "https://ntfy.sh/docs/publish/#scheduled-delivery", nil}
@@ -113,7 +113,16 @@ var (
errHTTPBadRequestPhoneNumberNotVerified = &errHTTP{40034, http.StatusBadRequest, "invalid request: phone number not verified, or no matching verified numbers found", "https://ntfy.sh/docs/publish/#phone-calls", nil}
errHTTPBadRequestAnonymousCallsNotAllowed = &errHTTP{40035, http.StatusBadRequest, "invalid request: anonymous phone calls are not allowed", "https://ntfy.sh/docs/publish/#phone-calls", nil}
errHTTPBadRequestPhoneNumberVerifyChannelInvalid = &errHTTP{40036, http.StatusBadRequest, "invalid request: verification channel must be 'sms' or 'call'", "https://ntfy.sh/docs/publish/#phone-calls", nil}
errHTTPBadRequestDelayNoCall = &errHTTP{40037, http.StatusBadRequest, "delayed call notifications are not supported", "", nil}
errHTTPBadRequestDelayNoCall = &errHTTP{40037, http.StatusBadRequest, "invalid request: delayed call notifications are not supported", "", nil}
errHTTPBadRequestWebPushSubscriptionInvalid = &errHTTP{40038, http.StatusBadRequest, "invalid request: web push payload malformed", "", nil}
errHTTPBadRequestWebPushEndpointUnknown = &errHTTP{40039, http.StatusBadRequest, "invalid request: web push endpoint unknown", "", nil}
errHTTPBadRequestWebPushTopicCountTooHigh = &errHTTP{40040, http.StatusBadRequest, "invalid request: too many web push topic subscriptions", "", nil}
errHTTPBadRequestTemplateMessageTooLarge = &errHTTP{40041, http.StatusBadRequest, "invalid request: message or title is too large after replacing template", "https://ntfy.sh/docs/publish/#message-templating", nil}
errHTTPBadRequestTemplateMessageNotJSON = &errHTTP{40042, http.StatusBadRequest, "invalid request: message body must be JSON if templating is enabled", "https://ntfy.sh/docs/publish/#message-templating", nil}
errHTTPBadRequestTemplateInvalid = &errHTTP{40043, http.StatusBadRequest, "invalid request: could not parse template", "https://ntfy.sh/docs/publish/#message-templating", nil}
errHTTPBadRequestTemplateDisallowedFunctionCalls = &errHTTP{40044, http.StatusBadRequest, "invalid request: template contains disallowed function calls, e.g. template, call, or define", "https://ntfy.sh/docs/publish/#message-templating", nil}
errHTTPBadRequestTemplateExecuteFailed = &errHTTP{40045, http.StatusBadRequest, "invalid request: template execution failed", "https://ntfy.sh/docs/publish/#message-templating", nil}
errHTTPBadRequestInvalidUsername = &errHTTP{40046, http.StatusBadRequest, "invalid request: invalid username", "", nil}
errHTTPNotFound = &errHTTP{40401, http.StatusNotFound, "page not found", "", nil}
errHTTPUnauthorized = &errHTTP{40101, http.StatusUnauthorized, "unauthorized", "https://ntfy.sh/docs/publish/#authentication", nil}
errHTTPForbidden = &errHTTP{40301, http.StatusForbidden, "forbidden", "https://ntfy.sh/docs/publish/#authentication", nil}
@@ -138,5 +147,6 @@ var (
errHTTPInternalError = &errHTTP{50001, http.StatusInternalServerError, "internal server error", "", nil}
errHTTPInternalErrorInvalidPath = &errHTTP{50002, http.StatusInternalServerError, "internal server error: invalid path", "", nil}
errHTTPInternalErrorMissingBaseURL = &errHTTP{50003, http.StatusInternalServerError, "internal server error: base-url must be be configured for this feature", "https://ntfy.sh/docs/config/", nil}
errHTTPInternalErrorWebPushUnableToPublish = &errHTTP{50004, http.StatusInternalServerError, "internal server error: unable to publish web push message", "", nil}
errHTTPInsufficientStorageUnifiedPush = &errHTTP{50701, http.StatusInsufficientStorage, "cannot publish to UnifiedPush topic without previously active subscriber", "", nil}
)

View File

@@ -3,8 +3,8 @@ package server
import (
"errors"
"fmt"
"heckel.io/ntfy/log"
"heckel.io/ntfy/util"
"heckel.io/ntfy/v2/log"
"heckel.io/ntfy/v2/util"
"io"
"os"
"path/filepath"

View File

@@ -4,7 +4,7 @@ import (
"bytes"
"fmt"
"github.com/stretchr/testify/require"
"heckel.io/ntfy/util"
"heckel.io/ntfy/v2/util"
"os"
"strings"
"testing"

View File

@@ -4,8 +4,8 @@ import (
"fmt"
"github.com/emersion/go-smtp"
"github.com/gorilla/websocket"
"heckel.io/ntfy/log"
"heckel.io/ntfy/util"
"heckel.io/ntfy/v2/log"
"heckel.io/ntfy/v2/util"
"net/http"
"strings"
"unicode/utf8"
@@ -29,6 +29,7 @@ const (
tagResetter = "resetter"
tagWebsocket = "websocket"
tagMatrix = "matrix"
tagWebPush = "webpush"
)
var (

View File

@@ -10,8 +10,8 @@ import (
"time"
_ "github.com/mattn/go-sqlite3" // SQLite driver
"heckel.io/ntfy/log"
"heckel.io/ntfy/util"
"heckel.io/ntfy/v2/log"
"heckel.io/ntfy/v2/util"
)
var (
@@ -45,6 +45,7 @@ const (
attachment_deleted INT NOT NULL,
sender TEXT NOT NULL,
user TEXT NOT NULL,
content_type TEXT NOT NULL,
encoding TEXT NOT NULL,
published INT NOT NULL
);
@@ -63,43 +64,50 @@ const (
COMMIT;
`
insertMessageQuery = `
INSERT INTO messages (mid, time, expires, topic, message, title, priority, tags, click, icon, actions, attachment_name, attachment_type, attachment_size, attachment_expires, attachment_url, attachment_deleted, sender, user, encoding, published)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
INSERT INTO messages (mid, time, expires, topic, message, title, priority, tags, click, icon, actions, attachment_name, attachment_type, attachment_size, attachment_expires, attachment_url, attachment_deleted, sender, user, content_type, encoding, published)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`
deleteMessageQuery = `DELETE FROM messages WHERE mid = ?`
updateMessagesForTopicExpiryQuery = `UPDATE messages SET expires = ? WHERE topic = ?`
selectRowIDFromMessageID = `SELECT id FROM messages WHERE mid = ?` // Do not include topic, see #336 and TestServer_PollSinceID_MultipleTopics
selectMessagesByIDQuery = `
SELECT mid, time, expires, topic, message, title, priority, tags, click, icon, actions, attachment_name, attachment_type, attachment_size, attachment_expires, attachment_url, sender, user, encoding
SELECT mid, time, expires, topic, message, title, priority, tags, click, icon, actions, attachment_name, attachment_type, attachment_size, attachment_expires, attachment_url, sender, user, content_type, encoding
FROM messages
WHERE mid = ?
`
selectMessagesSinceTimeQuery = `
SELECT mid, time, expires, topic, message, title, priority, tags, click, icon, actions, attachment_name, attachment_type, attachment_size, attachment_expires, attachment_url, sender, user, encoding
SELECT mid, time, expires, topic, message, title, priority, tags, click, icon, actions, attachment_name, attachment_type, attachment_size, attachment_expires, attachment_url, sender, user, content_type, encoding
FROM messages
WHERE topic = ? AND time >= ? AND published = 1
ORDER BY time, id
`
selectMessagesSinceTimeIncludeScheduledQuery = `
SELECT mid, time, expires, topic, message, title, priority, tags, click, icon, actions, attachment_name, attachment_type, attachment_size, attachment_expires, attachment_url, sender, user, encoding
SELECT mid, time, expires, topic, message, title, priority, tags, click, icon, actions, attachment_name, attachment_type, attachment_size, attachment_expires, attachment_url, sender, user, content_type, encoding
FROM messages
WHERE topic = ? AND time >= ?
ORDER BY time, id
`
selectMessagesSinceIDQuery = `
SELECT mid, time, expires, topic, message, title, priority, tags, click, icon, actions, attachment_name, attachment_type, attachment_size, attachment_expires, attachment_url, sender, user, encoding
SELECT mid, time, expires, topic, message, title, priority, tags, click, icon, actions, attachment_name, attachment_type, attachment_size, attachment_expires, attachment_url, sender, user, content_type, encoding
FROM messages
WHERE topic = ? AND id > ? AND published = 1
ORDER BY time, id
`
selectMessagesSinceIDIncludeScheduledQuery = `
SELECT mid, time, expires, topic, message, title, priority, tags, click, icon, actions, attachment_name, attachment_type, attachment_size, attachment_expires, attachment_url, sender, user, encoding
SELECT mid, time, expires, topic, message, title, priority, tags, click, icon, actions, attachment_name, attachment_type, attachment_size, attachment_expires, attachment_url, sender, user, content_type, encoding
FROM messages
WHERE topic = ? AND (id > ? OR published = 0)
ORDER BY time, id
`
selectMessagesLatestQuery = `
SELECT mid, time, expires, topic, message, title, priority, tags, click, icon, actions, attachment_name, attachment_type, attachment_size, attachment_expires, attachment_url, sender, user, content_type, encoding
FROM messages
WHERE topic = ? AND published = 1
ORDER BY time DESC, id DESC
LIMIT 1
`
selectMessagesDueQuery = `
SELECT mid, time, expires, topic, message, title, priority, tags, click, icon, actions, attachment_name, attachment_type, attachment_size, attachment_expires, attachment_url, sender, user, encoding
SELECT mid, time, expires, topic, message, title, priority, tags, click, icon, actions, attachment_name, attachment_type, attachment_size, attachment_expires, attachment_url, sender, user, content_type, encoding
FROM messages
WHERE time <= ? AND published = 0
ORDER BY time, id
@@ -121,7 +129,7 @@ const (
// Schema management queries
const (
currentSchemaVersion = 11
currentSchemaVersion = 13
createSchemaVersionTableQuery = `
CREATE TABLE IF NOT EXISTS schemaVersion (
id INT PRIMARY KEY,
@@ -240,6 +248,16 @@ const (
);
INSERT INTO stats (key, value) VALUES ('messages', 0);
`
// 11 -> 12
migrate11To12AlterMessagesTableQuery = `
ALTER TABLE messages ADD COLUMN content_type TEXT NOT NULL DEFAULT('');
`
// 12 -> 13
migrate12To13AlterMessagesTableQuery = `
CREATE INDEX IF NOT EXISTS idx_topic ON messages (topic);
`
)
var (
@@ -255,6 +273,8 @@ var (
8: migrateFrom8,
9: migrateFrom9,
10: migrateFrom10,
11: migrateFrom11,
12: migrateFrom12,
}
)
@@ -270,7 +290,7 @@ func newSqliteCache(filename, startupQueries string, cacheDuration time.Duration
if err != nil {
return nil, err
}
if err := setupDB(db, startupQueries, cacheDuration); err != nil {
if err := setupMessagesDB(db, startupQueries, cacheDuration); err != nil {
return nil, err
}
var queue *util.BatchingQueue[*message]
@@ -384,6 +404,7 @@ func (c *messageCache) addMessages(ms []*message) error {
attachmentDeleted, // Always zero
sender,
m.User,
m.ContentType,
m.Encoding,
published,
)
@@ -402,6 +423,8 @@ func (c *messageCache) addMessages(ms []*message) error {
func (c *messageCache) Messages(topic string, since sinceMarker, scheduled bool) ([]*message, error) {
if since.IsNone() {
return make([]*message, 0), nil
} else if since.IsLatest() {
return c.messagesLatest(topic)
} else if since.IsID() {
return c.messagesSinceID(topic, since, scheduled)
}
@@ -448,6 +471,14 @@ func (c *messageCache) messagesSinceID(topic string, since sinceMarker, schedule
return readMessages(rows)
}
func (c *messageCache) messagesLatest(topic string) ([]*message, error) {
rows, err := c.db.Query(selectMessagesLatestQuery, topic)
if err != nil {
return nil, err
}
return readMessages(rows)
}
func (c *messageCache) MessagesDue() ([]*message, error) {
rows, err := c.db.Query(selectMessagesDueQuery, time.Now().Unix())
if err != nil {
@@ -656,7 +687,7 @@ func readMessages(rows *sql.Rows) ([]*message, error) {
func readMessage(rows *sql.Rows) (*message, error) {
var timestamp, expires, attachmentSize, attachmentExpires int64
var priority int
var id, topic, msg, title, tagsStr, click, icon, actionsStr, attachmentName, attachmentType, attachmentURL, sender, user, encoding string
var id, topic, msg, title, tagsStr, click, icon, actionsStr, attachmentName, attachmentType, attachmentURL, sender, user, contentType, encoding string
err := rows.Scan(
&id,
&timestamp,
@@ -676,6 +707,7 @@ func readMessage(rows *sql.Rows) (*message, error) {
&attachmentURL,
&sender,
&user,
&contentType,
&encoding,
)
if err != nil {
@@ -706,22 +738,23 @@ func readMessage(rows *sql.Rows) (*message, error) {
}
}
return &message{
ID: id,
Time: timestamp,
Expires: expires,
Event: messageEvent,
Topic: topic,
Message: msg,
Title: title,
Priority: priority,
Tags: tags,
Click: click,
Icon: icon,
Actions: actions,
Attachment: att,
Sender: senderIP, // Must parse assuming database must be correct
User: user,
Encoding: encoding,
ID: id,
Time: timestamp,
Expires: expires,
Event: messageEvent,
Topic: topic,
Message: msg,
Title: title,
Priority: priority,
Tags: tags,
Click: click,
Icon: icon,
Actions: actions,
Attachment: att,
Sender: senderIP, // Must parse assuming database must be correct
User: user,
ContentType: contentType,
Encoding: encoding,
}, nil
}
@@ -749,7 +782,7 @@ func (c *messageCache) Close() error {
return c.db.Close()
}
func setupDB(db *sql.DB, startupQueries string, cacheDuration time.Duration) error {
func setupMessagesDB(db *sql.DB, startupQueries string, cacheDuration time.Duration) error {
// Run startup queries
if startupQueries != "" {
if _, err := db.Exec(startupQueries); err != nil {
@@ -929,7 +962,7 @@ func migrateFrom9(db *sql.DB, cacheDuration time.Duration) error {
return tx.Commit()
}
func migrateFrom10(db *sql.DB, cacheDuration time.Duration) error {
func migrateFrom10(db *sql.DB, _ time.Duration) error {
log.Tag(tagMessageCache).Info("Migrating cache database schema: from 10 to 11")
tx, err := db.Begin()
if err != nil {
@@ -944,3 +977,35 @@ func migrateFrom10(db *sql.DB, cacheDuration time.Duration) error {
}
return tx.Commit()
}
func migrateFrom11(db *sql.DB, _ time.Duration) error {
log.Tag(tagMessageCache).Info("Migrating cache database schema: from 11 to 12")
tx, err := db.Begin()
if err != nil {
return err
}
defer tx.Rollback()
if _, err := tx.Exec(migrate11To12AlterMessagesTableQuery); err != nil {
return err
}
if _, err := tx.Exec(updateSchemaVersion, 12); err != nil {
return err
}
return tx.Commit()
}
func migrateFrom12(db *sql.DB, _ time.Duration) error {
log.Tag(tagMessageCache).Info("Migrating cache database schema: from 12 to 13")
tx, err := db.Begin()
if err != nil {
return err
}
defer tx.Rollback()
if _, err := tx.Exec(migrate12To13AlterMessagesTableQuery); err != nil {
return err
}
if _, err := tx.Exec(updateSchemaVersion, 13); err != nil {
return err
}
return tx.Commit()
}

View File

@@ -8,7 +8,6 @@ import (
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@@ -67,6 +66,11 @@ func testCacheMessages(t *testing.T, c *messageCache) {
require.Equal(t, 1, len(messages))
require.Equal(t, "my other message", messages[0].Message)
// mytopic: latest
messages, _ = c.Messages("mytopic", sinceLatestMessage, false)
require.Equal(t, 1, len(messages))
require.Equal(t, "my other message", messages[0].Message)
// example: count
counts, err = c.MessageCounts()
require.Nil(t, err)
@@ -509,6 +513,14 @@ func TestSqliteCache_Migration_From1(t *testing.T) {
messages, err = c.Messages("mytopic", sinceAllMessages, true)
require.Nil(t, err)
require.Equal(t, 11, len(messages))
// Check that index "idx_topic" exists
rows, err := c.db.Query(`SELECT name FROM sqlite_master WHERE type='index' AND name='idx_topic'`)
require.Nil(t, err)
require.True(t, rows.Next())
var indexName string
require.Nil(t, rows.Scan(&indexName))
require.Equal(t, "idx_topic", indexName)
}
func TestSqliteCache_Migration_From9(t *testing.T) {
@@ -675,15 +687,15 @@ func checkSchemaVersion(t *testing.T, db *sql.DB) {
func TestMemCache_NopCache(t *testing.T) {
c, _ := newNopCache()
assert.Nil(t, c.AddMessage(newDefaultMessage("mytopic", "my message")))
require.Nil(t, c.AddMessage(newDefaultMessage("mytopic", "my message")))
messages, err := c.Messages("mytopic", sinceAllMessages, false)
assert.Nil(t, err)
assert.Empty(t, messages)
require.Nil(t, err)
require.Empty(t, messages)
topics, err := c.Topics()
assert.Nil(t, err)
assert.Empty(t, topics)
require.Nil(t, err)
require.Empty(t, topics)
}
func newSqliteTestCache(t *testing.T) *messageCache {
@@ -700,16 +712,12 @@ func newSqliteTestCacheFile(t *testing.T) string {
func newSqliteTestCacheFromFile(t *testing.T, filename, startupQueries string) *messageCache {
c, err := newSqliteCache(filename, startupQueries, time.Hour, 0, 0, false)
if err != nil {
t.Fatal(err)
}
require.Nil(t, err)
return c
}
func newMemTestCache(t *testing.T) *messageCache {
c, err := newMemCache()
if err != nil {
t.Fatal(err)
}
require.Nil(t, err)
return c
}

View File

@@ -9,13 +9,6 @@ import (
"encoding/json"
"errors"
"fmt"
"github.com/emersion/go-smtp"
"github.com/gorilla/websocket"
"github.com/prometheus/client_golang/prometheus/promhttp"
"golang.org/x/sync/errgroup"
"heckel.io/ntfy/log"
"heckel.io/ntfy/user"
"heckel.io/ntfy/util"
"io"
"net"
"net/http"
@@ -30,8 +23,17 @@ import (
"strconv"
"strings"
"sync"
"text/template"
"time"
"unicode/utf8"
"github.com/emersion/go-smtp"
"github.com/gorilla/websocket"
"github.com/prometheus/client_golang/prometheus/promhttp"
"golang.org/x/sync/errgroup"
"heckel.io/ntfy/v2/log"
"heckel.io/ntfy/v2/user"
"heckel.io/ntfy/v2/util"
)
// Server is the main server, providing the UI and API for ntfy
@@ -52,6 +54,7 @@ type Server struct {
messagesHistory []int64 // Last n values of the messages counter, used to determine rate
userManager *user.Manager // Might be nil!
messageCache *messageCache // Database that stores the messages
webPush *webPushStore // Database that stores web push subscriptions
fileCache *fileCache // File system based cache that stores attachments
stripe stripeAPI // Stripe API, can be replaced with a mock
priceCache *util.LookupCache[map[string]int64] // Stripe price ID -> price as cents (USD implied!)
@@ -76,11 +79,15 @@ var (
publishPathRegex = regexp.MustCompile(`^/[-_A-Za-z0-9]{1,64}/(publish|send|trigger)$`)
webConfigPath = "/config.js"
webManifestPath = "/manifest.webmanifest"
webRootHTMLPath = "/app.html"
webServiceWorkerPath = "/sw.js"
accountPath = "/account"
matrixPushPath = "/_matrix/push/v1/notify"
metricsPath = "/metrics"
apiHealthPath = "/v1/health"
apiStatsPath = "/v1/stats"
apiWebPushPath = "/v1/webpush"
apiTiersPath = "/v1/tiers"
apiUsersPath = "/v1/users"
apiUsersAccessPath = "/v1/users/access"
@@ -117,15 +124,22 @@ var (
const (
firebaseControlTopic = "~control" // See Android if changed
firebasePollTopic = "~poll" // See iOS if changed
firebasePollTopic = "~poll" // See iOS if changed (DISABLED for now)
emptyMessageBody = "triggered" // Used if message body is empty
newMessageBody = "New message" // Used in poll requests as generic message
defaultAttachmentMessage = "You received a file: %s" // Used if message body is empty, and there is an attachment
encodingBase64 = "base64" // Used mainly for binary UnifiedPush messages
jsonBodyBytesLimit = 16384 // Max number of bytes for a JSON request body
jsonBodyBytesLimit = 32768 // Max number of bytes for a request bodys (unless MessageLimit is higher)
unifiedPushTopicPrefix = "up" // Temporarily, we rate limit all "up*" topics based on the subscriber
unifiedPushTopicLength = 14 // Length of UnifiedPush topics, including the "up" part
messagesHistoryMax = 10 // Number of message count values to keep in memory
templateMaxExecutionTime = 100 * time.Millisecond
)
var (
// templateDisallowedRegex tests a template for disallowed expressions. While not really dangerous, they
// are not useful, and seem potentially troublesome.
templateDisallowedRegex = regexp.MustCompile(`(?m)\{\{-?\s*(call|template|define)\b`)
)
// WebSocket constants
@@ -151,6 +165,13 @@ func New(conf *Config) (*Server, error) {
if err != nil {
return nil, err
}
var webPush *webPushStore
if conf.WebPushPublicKey != "" {
webPush, err = newWebPushStore(conf.WebPushFile, conf.WebPushStartupQueries)
if err != nil {
return nil, err
}
}
topics, err := messageCache.Topics()
if err != nil {
return nil, err
@@ -190,6 +211,7 @@ func New(conf *Config) (*Server, error) {
s := &Server{
config: conf,
messageCache: messageCache,
webPush: webPush,
fileCache: fileCache,
firebaseClient: firebaseClient,
smtpSender: mailer,
@@ -342,6 +364,9 @@ func (s *Server) closeDatabases() {
s.userManager.Close()
}
s.messageCache.Close()
if s.webPush != nil {
s.webPush.Close()
}
}
// handle is the main entry point for all HTTP requests
@@ -388,7 +413,8 @@ func (s *Server) handleError(w http.ResponseWriter, r *http.Request, v *visitor,
} else {
ev.Info("WebSocket error: %s", err.Error())
}
return // Do not attempt to write to upgraded connection
w.WriteHeader(httpErr.HTTPCode)
return // Do not attempt to write any body to upgraded connection
}
if isNormalError {
ev.Debug("Connection closed with HTTP %d (ntfy error %d)", httpErr.HTTPCode, httpErr.Code)
@@ -416,10 +442,14 @@ func (s *Server) handleInternal(w http.ResponseWriter, r *http.Request, v *visit
return s.handleHealth(w, r, v)
} else if r.Method == http.MethodGet && r.URL.Path == webConfigPath {
return s.ensureWebEnabled(s.handleWebConfig)(w, r, v)
} else if r.Method == http.MethodGet && r.URL.Path == webManifestPath {
return s.ensureWebPushEnabled(s.handleWebManifest)(w, r, v)
} else if r.Method == http.MethodGet && r.URL.Path == apiUsersPath {
return s.ensureAdmin(s.handleUsersGet)(w, r, v)
} else if r.Method == http.MethodPut && r.URL.Path == apiUsersPath {
} else if r.Method == http.MethodPost && r.URL.Path == apiUsersPath {
return s.ensureAdmin(s.handleUsersAdd)(w, r, v)
} else if r.Method == http.MethodPut && r.URL.Path == apiUsersPath {
return s.ensureAdmin(s.handleUsersUpdate)(w, r, v)
} else if r.Method == http.MethodDelete && r.URL.Path == apiUsersPath {
return s.ensureAdmin(s.handleUsersDelete)(w, r, v)
} else if (r.Method == http.MethodPut || r.Method == http.MethodPost) && r.URL.Path == apiUsersAccessPath {
@@ -470,6 +500,10 @@ func (s *Server) handleInternal(w http.ResponseWriter, r *http.Request, v *visit
return s.ensureUser(s.ensureCallsEnabled(s.withAccountSync(s.handleAccountPhoneNumberAdd)))(w, r, v)
} else if r.Method == http.MethodDelete && r.URL.Path == apiAccountPhonePath {
return s.ensureUser(s.ensureCallsEnabled(s.withAccountSync(s.handleAccountPhoneNumberDelete)))(w, r, v)
} else if r.Method == http.MethodPost && apiWebPushPath == r.URL.Path {
return s.ensureWebPushEnabled(s.limitRequests(s.handleWebPushUpdate))(w, r, v)
} else if r.Method == http.MethodDelete && apiWebPushPath == r.URL.Path {
return s.ensureWebPushEnabled(s.limitRequests(s.handleWebPushDelete))(w, r, v)
} else if r.Method == http.MethodGet && r.URL.Path == apiStatsPath {
return s.handleStats(w, r, v)
} else if r.Method == http.MethodGet && r.URL.Path == apiTiersPath {
@@ -478,7 +512,7 @@ func (s *Server) handleInternal(w http.ResponseWriter, r *http.Request, v *visit
return s.handleMatrixDiscovery(w)
} else if r.Method == http.MethodGet && r.URL.Path == metricsPath && s.metricsHandler != nil {
return s.handleMetrics(w, r, v)
} else if r.Method == http.MethodGet && staticRegex.MatchString(r.URL.Path) {
} else if r.Method == http.MethodGet && (staticRegex.MatchString(r.URL.Path) || r.URL.Path == webServiceWorkerPath || r.URL.Path == webRootHTMLPath) {
return s.ensureWebEnabled(s.handleStatic)(w, r, v)
} else if r.Method == http.MethodGet && docsRegex.MatchString(r.URL.Path) {
return s.ensureWebEnabled(s.handleDocs)(w, r, v)
@@ -552,7 +586,9 @@ func (s *Server) handleWebConfig(w http.ResponseWriter, _ *http.Request, _ *visi
EnableCalls: s.config.TwilioAccount != "",
EnableEmails: s.config.SMTPSenderFrom != "",
EnableReservations: s.config.EnableReservations,
EnableWebPush: s.config.WebPushPublicKey != "",
BillingContact: s.config.BillingContact,
WebPushPublicKey: s.config.WebPushPublicKey,
DisallowedTopics: s.config.DisallowedTopics,
}
b, err := json.MarshalIndent(response, "", " ")
@@ -560,10 +596,30 @@ func (s *Server) handleWebConfig(w http.ResponseWriter, _ *http.Request, _ *visi
return err
}
w.Header().Set("Content-Type", "text/javascript")
w.Header().Set("Cache-Control", "no-cache")
_, err = io.WriteString(w, fmt.Sprintf("// Generated server configuration\nvar config = %s;\n", string(b)))
return err
}
// handleWebManifest serves the web app manifest for the progressive web app (PWA)
func (s *Server) handleWebManifest(w http.ResponseWriter, _ *http.Request, _ *visitor) error {
response := &webManifestResponse{
Name: "ntfy web",
Description: "ntfy lets you send push notifications via scripts from any computer or phone",
ShortName: "ntfy",
Scope: "/",
StartURL: s.config.WebRoot,
Display: "standalone",
BackgroundColor: "#ffffff",
ThemeColor: "#317f6f",
Icons: []*webManifestIcon{
{SRC: "/static/images/pwa-192x192.png", Sizes: "192x192", Type: "image/png"},
{SRC: "/static/images/pwa-512x512.png", Sizes: "512x512", Type: "image/png"},
},
}
return s.writeJSONWithContentType(w, response, "application/manifest+json")
}
// handleMetrics returns Prometheus metrics. This endpoint is only called if enable-metrics is set,
// and listen-metrics-http is not set.
func (s *Server) handleMetrics(w http.ResponseWriter, r *http.Request, _ *visitor) error {
@@ -629,7 +685,7 @@ func (s *Server) handleFile(w http.ResponseWriter, r *http.Request, v *visitor)
// - avoid abuse (e.g. 1 uploader, 1k downloaders)
// - and also uses the higher bandwidth limits of a paying user
m, err := s.messageCache.Message(messageID)
if err == errMessageNotFound {
if errors.Is(err, errMessageNotFound) {
if s.config.CacheBatchTimeout > 0 {
// Strange edge case: If we immediately after upload request the file (the web app does this for images),
// and messages are persisted asynchronously, retry fetching from the database
@@ -689,18 +745,18 @@ func (s *Server) handlePublishInternal(r *http.Request, v *visitor) (*message, e
if err != nil {
return nil, err
}
body, err := util.Peek(r.Body, s.config.MessageLimit)
body, err := util.Peek(r.Body, s.config.MessageSizeLimit)
if err != nil {
return nil, err
}
m := newDefaultMessage(t.ID, "")
cache, firebase, email, call, unifiedpush, e := s.parsePublishParams(r, m)
cache, firebase, email, call, template, unifiedpush, e := s.parsePublishParams(r, m)
if e != nil {
return nil, e.With(t)
}
if unifiedpush && s.config.VisitorSubscriberRateLimiting && t.RateVisitor() == nil {
// UnifiedPush clients must subscribe before publishing to allow proper subscriber-based rate limiting (see
// Rate-Topics header). The 5xx response is because some app servers (in particular Mastodon) will remove
// UnifiedPush clients must subscribe before publishing to allow proper subscriber-based rate limiting.
// The 5xx response is because some app servers (in particular Mastodon) will remove
// the subscription as invalid if any 400-499 code (except 429/408) is returned.
// See https://github.com/mastodon/mastodon/blob/730bb3e211a84a2f30e3e2bbeae3f77149824a68/app/workers/web/push_notification_worker.rb#L35-L46
return nil, errHTTPInsufficientStorageUnifiedPush.With(t)
@@ -725,7 +781,7 @@ func (s *Server) handlePublishInternal(r *http.Request, v *visitor) (*message, e
if cache {
m.Expires = time.Unix(m.Time, 0).Add(v.Limits().MessageExpiryDuration).Unix()
}
if err := s.handlePublishBody(r, v, m, body, unifiedpush); err != nil {
if err := s.handlePublishBody(r, v, m, body, template, unifiedpush); err != nil {
return nil, err
}
if m.Message == "" {
@@ -763,6 +819,9 @@ func (s *Server) handlePublishInternal(r *http.Request, v *visitor) (*message, e
if s.config.UpstreamBaseURL != "" && !unifiedpush { // UP messages are not sent to upstream
go s.forwardPollRequest(v, m)
}
if s.config.WebPushPublicKey != "" {
go s.publishToWebPushEndpoints(v, m)
}
} else {
logvrm(v, r, m).Tag(tagPublish).Debug("Message delayed, will process later")
}
@@ -825,7 +884,7 @@ func (s *Server) sendToFirebase(v *visitor, m *message) {
logvm(v, m).Tag(tagFirebase).Debug("Publishing to Firebase")
if err := s.firebaseClient.Send(v, m); err != nil {
minc(metricFirebasePublishedFailure)
if err == errFirebaseTemporarilyBanned {
if errors.Is(err, errFirebaseTemporarilyBanned) {
logvm(v, m).Tag(tagFirebase).Err(err).Debug("Unable to publish to Firebase: %v", err.Error())
} else {
logvm(v, m).Tag(tagFirebase).Err(err).Warn("Unable to publish to Firebase: %v", err.Error())
@@ -877,7 +936,7 @@ func (s *Server) forwardPollRequest(v *visitor, m *message) {
}
}
func (s *Server) parsePublishParams(r *http.Request, m *message) (cache bool, firebase bool, email, call string, unifiedpush bool, err *errHTTP) {
func (s *Server) parsePublishParams(r *http.Request, m *message) (cache bool, firebase bool, email, call string, template bool, unifiedpush bool, err *errHTTP) {
cache = readBoolParam(r, true, "x-cache", "cache")
firebase = readBoolParam(r, true, "x-firebase", "firebase")
m.Title = readParam(r, "x-title", "title", "t")
@@ -893,7 +952,7 @@ func (s *Server) parsePublishParams(r *http.Request, m *message) (cache bool, fi
}
if attach != "" {
if !urlRegex.MatchString(attach) {
return false, false, "", "", false, errHTTPBadRequestAttachmentURLInvalid
return false, false, "", "", false, false, errHTTPBadRequestAttachmentURLInvalid
}
m.Attachment.URL = attach
if m.Attachment.Name == "" {
@@ -911,19 +970,19 @@ func (s *Server) parsePublishParams(r *http.Request, m *message) (cache bool, fi
}
if icon != "" {
if !urlRegex.MatchString(icon) {
return false, false, "", "", false, errHTTPBadRequestIconURLInvalid
return false, false, "", "", false, false, errHTTPBadRequestIconURLInvalid
}
m.Icon = icon
}
email = readParam(r, "x-email", "x-e-mail", "email", "e-mail", "mail", "e")
if s.smtpSender == nil && email != "" {
return false, false, "", "", false, errHTTPBadRequestEmailDisabled
return false, false, "", "", false, false, errHTTPBadRequestEmailDisabled
}
call = readParam(r, "x-call", "call")
if call != "" && (s.config.TwilioAccount == "" || s.userManager == nil) {
return false, false, "", "", false, errHTTPBadRequestPhoneCallsDisabled
return false, false, "", "", false, false, errHTTPBadRequestPhoneCallsDisabled
} else if call != "" && !isBoolValue(call) && !phoneNumberRegex.MatchString(call) {
return false, false, "", "", false, errHTTPBadRequestPhoneNumberInvalid
return false, false, "", "", false, false, errHTTPBadRequestPhoneNumberInvalid
}
messageStr := strings.ReplaceAll(readParam(r, "x-message", "message", "m"), "\\n", "\n")
if messageStr != "" {
@@ -932,27 +991,27 @@ func (s *Server) parsePublishParams(r *http.Request, m *message) (cache bool, fi
var e error
m.Priority, e = util.ParsePriority(readParam(r, "x-priority", "priority", "prio", "p"))
if e != nil {
return false, false, "", "", false, errHTTPBadRequestPriorityInvalid
return false, false, "", "", false, false, errHTTPBadRequestPriorityInvalid
}
m.Tags = readCommaSeparatedParam(r, "x-tags", "tags", "tag", "ta")
delayStr := readParam(r, "x-delay", "delay", "x-at", "at", "x-in", "in")
if delayStr != "" {
if !cache {
return false, false, "", "", false, errHTTPBadRequestDelayNoCache
return false, false, "", "", false, false, errHTTPBadRequestDelayNoCache
}
if email != "" {
return false, false, "", "", false, errHTTPBadRequestDelayNoEmail // we cannot store the email address (yet)
return false, false, "", "", false, false, errHTTPBadRequestDelayNoEmail // we cannot store the email address (yet)
}
if call != "" {
return false, false, "", "", false, errHTTPBadRequestDelayNoCall // we cannot store the phone number (yet)
return false, false, "", "", false, false, errHTTPBadRequestDelayNoCall // we cannot store the phone number (yet)
}
delay, err := util.ParseFutureTime(delayStr, time.Now())
if err != nil {
return false, false, "", "", false, errHTTPBadRequestDelayCannotParse
} else if delay.Unix() < time.Now().Add(s.config.MinDelay).Unix() {
return false, false, "", "", false, errHTTPBadRequestDelayTooSmall
} else if delay.Unix() > time.Now().Add(s.config.MaxDelay).Unix() {
return false, false, "", "", false, errHTTPBadRequestDelayTooLarge
return false, false, "", "", false, false, errHTTPBadRequestDelayCannotParse
} else if delay.Unix() < time.Now().Add(s.config.MessageDelayMin).Unix() {
return false, false, "", "", false, false, errHTTPBadRequestDelayTooSmall
} else if delay.Unix() > time.Now().Add(s.config.MessageDelayMax).Unix() {
return false, false, "", "", false, false, errHTTPBadRequestDelayTooLarge
}
m.Time = delay.Unix()
}
@@ -960,11 +1019,17 @@ func (s *Server) parsePublishParams(r *http.Request, m *message) (cache bool, fi
if actionsStr != "" {
m.Actions, e = parseActions(actionsStr)
if e != nil {
return false, false, "", "", false, errHTTPBadRequestActionsInvalid.Wrap(e.Error())
return false, false, "", "", false, false, errHTTPBadRequestActionsInvalid.Wrap("%s", e.Error())
}
}
contentType, markdown := readParam(r, "content-type", "content_type"), readBoolParam(r, false, "x-markdown", "markdown", "md")
if markdown || strings.ToLower(contentType) == "text/markdown" {
m.ContentType = "text/markdown"
}
template = readBoolParam(r, false, "x-template", "template", "tpl")
unifiedpush = readBoolParam(r, false, "x-unifiedpush", "unifiedpush", "up") // see GET too!
if unifiedpush {
contentEncoding := readParam(r, "content-encoding")
if unifiedpush || contentEncoding == "aes128gcm" {
firebase = false
unifiedpush = true
}
@@ -974,7 +1039,7 @@ func (s *Server) parsePublishParams(r *http.Request, m *message) (cache bool, fi
cache = false
email = ""
}
return cache, firebase, email, call, unifiedpush, nil
return cache, firebase, email, call, template, unifiedpush, nil
}
// handlePublishBody consumes the PUT/POST body and decides whether the body is an attachment or the message.
@@ -982,16 +1047,18 @@ func (s *Server) parsePublishParams(r *http.Request, m *message) (cache bool, fi
// 1. curl -X POST -H "Poll: 1234" ntfy.sh/...
// If a message is flagged as poll request, the body does not matter and is discarded
// 2. curl -T somebinarydata.bin "ntfy.sh/mytopic?up=1"
// If body is binary, encode as base64, if not do not encode
// If UnifiedPush is enabled, encode as base64 if body is binary, and do not trim
// 3. curl -H "Attach: http://example.com/file.jpg" ntfy.sh/mytopic
// Body must be a message, because we attached an external URL
// 4. curl -T short.txt -H "Filename: short.txt" ntfy.sh/mytopic
// Body must be attachment, because we passed a filename
// 5. curl -T file.txt ntfy.sh/mytopic
// If file.txt is <= 4096 (message limit) and valid UTF-8, treat it as a message
// 5. curl -H "Template: yes" -T file.txt ntfy.sh/mytopic
// If templating is enabled, read up to 32k and treat message body as JSON
// 6. curl -T file.txt ntfy.sh/mytopic
// If file.txt is > message limit, treat it as an attachment
func (s *Server) handlePublishBody(r *http.Request, v *visitor, m *message, body *util.PeekedReadCloser, unifiedpush bool) error {
// If file.txt is <= 4096 (message limit) and valid UTF-8, treat it as a message
// 7. curl -T file.txt ntfy.sh/mytopic
// In all other cases, mostly if file.txt is > message limit, treat it as an attachment
func (s *Server) handlePublishBody(r *http.Request, v *visitor, m *message, body *util.PeekedReadCloser, template, unifiedpush bool) error {
if m.Event == pollRequestEvent { // Case 1
return s.handleBodyDiscard(body)
} else if unifiedpush {
@@ -1000,10 +1067,12 @@ func (s *Server) handlePublishBody(r *http.Request, v *visitor, m *message, body
return s.handleBodyAsTextMessage(m, body) // Case 3
} else if m.Attachment != nil && m.Attachment.Name != "" {
return s.handleBodyAsAttachment(r, v, m, body) // Case 4
} else if template {
return s.handleBodyAsTemplatedTextMessage(m, body) // Case 5
} else if !body.LimitReached && utf8.Valid(body.PeekedBytes) {
return s.handleBodyAsTextMessage(m, body) // Case 5
return s.handleBodyAsTextMessage(m, body) // Case 6
}
return s.handleBodyAsAttachment(r, v, m, body) // Case 6
return s.handleBodyAsAttachment(r, v, m, body) // Case 7
}
func (s *Server) handleBodyDiscard(body *util.PeekedReadCloser) error {
@@ -1035,6 +1104,45 @@ func (s *Server) handleBodyAsTextMessage(m *message, body *util.PeekedReadCloser
return nil
}
func (s *Server) handleBodyAsTemplatedTextMessage(m *message, body *util.PeekedReadCloser) error {
body, err := util.Peek(body, max(s.config.MessageSizeLimit, jsonBodyBytesLimit))
if err != nil {
return err
} else if body.LimitReached {
return errHTTPEntityTooLargeJSONBody
}
peekedBody := strings.TrimSpace(string(body.PeekedBytes))
if m.Message, err = replaceTemplate(m.Message, peekedBody); err != nil {
return err
}
if m.Title, err = replaceTemplate(m.Title, peekedBody); err != nil {
return err
}
if len(m.Message) > s.config.MessageSizeLimit {
return errHTTPBadRequestTemplateMessageTooLarge
}
return nil
}
func replaceTemplate(tpl string, source string) (string, error) {
if templateDisallowedRegex.MatchString(tpl) {
return "", errHTTPBadRequestTemplateDisallowedFunctionCalls
}
var data any
if err := json.Unmarshal([]byte(source), &data); err != nil {
return "", errHTTPBadRequestTemplateMessageNotJSON
}
t, err := template.New("").Parse(tpl)
if err != nil {
return "", errHTTPBadRequestTemplateInvalid
}
var buf bytes.Buffer
if err := t.Execute(util.NewTimeoutWriter(&buf, templateMaxExecutionTime), data); err != nil {
return "", errHTTPBadRequestTemplateExecuteFailed
}
return buf.String(), nil
}
func (s *Server) handleBodyAsAttachment(r *http.Request, v *visitor, m *message, body *util.PeekedReadCloser) error {
if s.fileCache == nil || s.config.BaseURL == "" || s.config.AttachmentCacheDir == "" {
return errHTTPBadRequestAttachmentsDisallowed.With(m)
@@ -1077,7 +1185,7 @@ func (s *Server) handleBodyAsAttachment(r *http.Request, v *visitor, m *message,
util.NewFixedLimiter(vinfo.Stats.AttachmentTotalSizeRemaining),
}
m.Attachment.Size, err = s.fileCache.Write(m.ID, body, limiters...)
if err == util.ErrLimitReached {
if errors.Is(err, util.ErrLimitReached) {
return errHTTPEntityTooLargeAttachment.With(m)
} else if err != nil {
return err
@@ -1131,7 +1239,7 @@ func (s *Server) handleSubscribeHTTP(w http.ResponseWriter, r *http.Request, v *
if err != nil {
return err
}
poll, since, scheduled, filters, rateTopics, err := parseSubscribeParams(r)
poll, since, scheduled, filters, err := parseSubscribeParams(r)
if err != nil {
return err
}
@@ -1161,7 +1269,7 @@ func (s *Server) handleSubscribeHTTP(w http.ResponseWriter, r *http.Request, v *
}
return nil
}
if err := s.maybeSetRateVisitors(r, v, topics, rateTopics); err != nil {
if err := s.maybeSetRateVisitors(r, v, topics); err != nil {
return err
}
w.Header().Set("Access-Control-Allow-Origin", s.config.AccessControlAllowOrigin) // CORS, allow cross-origin requests
@@ -1227,7 +1335,7 @@ func (s *Server) handleSubscribeWS(w http.ResponseWriter, r *http.Request, v *vi
if err != nil {
return err
}
poll, since, scheduled, filters, rateTopics, err := parseSubscribeParams(r)
poll, since, scheduled, filters, err := parseSubscribeParams(r)
if err != nil {
return err
}
@@ -1313,7 +1421,7 @@ func (s *Server) handleSubscribeWS(w http.ResponseWriter, r *http.Request, v *vi
}
return conn.WriteJSON(msg)
}
if err := s.maybeSetRateVisitors(r, v, topics, rateTopics); err != nil {
if err := s.maybeSetRateVisitors(r, v, topics); err != nil {
return err
}
w.Header().Set("Access-Control-Allow-Origin", s.config.AccessControlAllowOrigin) // CORS, allow cross-origin requests
@@ -1346,7 +1454,7 @@ func (s *Server) handleSubscribeWS(w http.ResponseWriter, r *http.Request, v *vi
return err
}
func parseSubscribeParams(r *http.Request) (poll bool, since sinceMarker, scheduled bool, filters *queryFilter, rateTopics []string, err error) {
func parseSubscribeParams(r *http.Request) (poll bool, since sinceMarker, scheduled bool, filters *queryFilter, err error) {
poll = readBoolParam(r, false, "x-poll", "poll", "po")
scheduled = readBoolParam(r, false, "x-scheduled", "scheduled", "sched")
since, err = parseSince(r, poll)
@@ -1357,7 +1465,6 @@ func parseSubscribeParams(r *http.Request) (poll bool, since sinceMarker, schedu
if err != nil {
return
}
rateTopics = readCommaSeparatedParam(r, "x-rate-topics", "rate-topics")
return
}
@@ -1369,9 +1476,8 @@ func parseSubscribeParams(r *http.Request) (poll bool, since sinceMarker, schedu
// - or the topic is reserved, and v.user is the owner
// - or the topic is not reserved, and v.user has write access
//
// Note: This TEMPORARILY also registers all topics starting with "up" (= UnifiedPush). This is to ease the transition
// until the Android app will send the "Rate-Topics" header.
func (s *Server) maybeSetRateVisitors(r *http.Request, v *visitor, topics []*topic, rateTopics []string) error {
// This only applies to UnifiedPush topics ("up...").
func (s *Server) maybeSetRateVisitors(r *http.Request, v *visitor, topics []*topic) error {
// Bail out if not enabled
if !s.config.VisitorSubscriberRateLimiting {
return nil
@@ -1380,7 +1486,7 @@ func (s *Server) maybeSetRateVisitors(r *http.Request, v *visitor, topics []*top
// Make a list of topics that we'll actually set the RateVisitor on
eligibleRateTopics := make([]*topic, 0)
for _, t := range topics {
if (strings.HasPrefix(t.ID, unifiedPushTopicPrefix) && len(t.ID) == unifiedPushTopicLength) || util.Contains(rateTopics, t.ID) {
if strings.HasPrefix(t.ID, unifiedPushTopicPrefix) && len(t.ID) == unifiedPushTopicLength {
eligibleRateTopics = append(eligibleRateTopics, t)
}
}
@@ -1398,6 +1504,9 @@ func (s *Server) maybeSetRateVisitors(r *http.Request, v *visitor, topics []*top
// - topic is not reserved, and v.user has write access
writableRateTopics := make([]*topic, 0)
for _, t := range topics {
if !util.Contains(eligibleRateTopics, t) {
continue
}
ownerUserID, err := s.userManager.ReservationOwner(t.ID)
if err != nil {
return err
@@ -1451,8 +1560,8 @@ func (s *Server) sendOldMessages(topics []*topic, since sinceMarker, scheduled b
// parseSince returns a timestamp identifying the time span from which cached messages should be received.
//
// Values in the "since=..." parameter can be either a unix timestamp or a duration (e.g. 12h), or
// "all" for all messages.
// Values in the "since=..." parameter can be either a unix timestamp or a duration (e.g. 12h),
// "all" for all messages, or "latest" for the most recent message for a topic
func parseSince(r *http.Request, poll bool) (sinceMarker, error) {
since := readParam(r, "x-since", "since", "si")
@@ -1464,6 +1573,8 @@ func parseSince(r *http.Request, poll bool) (sinceMarker, error) {
return sinceNoMessages, nil
} else if since == "all" {
return sinceAllMessages, nil
} else if since == "latest" {
return sinceLatestMessage, nil
} else if since == "none" {
return sinceNoMessages, nil
}
@@ -1692,6 +1803,9 @@ func (s *Server) sendDelayedMessage(v *visitor, m *message) error {
if s.config.UpstreamBaseURL != "" {
go s.forwardPollRequest(v, m)
}
if s.config.WebPushPublicKey != "" {
go s.publishToWebPushEndpoints(v, m)
}
if err := s.messageCache.MarkPublished(m); err != nil {
return err
}
@@ -1702,7 +1816,7 @@ func (s *Server) sendDelayedMessage(v *visitor, m *message) error {
// before passing it on to the next handler. This is meant to be used in combination with handlePublish.
func (s *Server) transformBodyJSON(next handleFunc) handleFunc {
return func(w http.ResponseWriter, r *http.Request, v *visitor) error {
m, err := readJSONWithLimit[publishMessage](r.Body, s.config.MessageLimit*2, false) // 2x to account for JSON format overhead
m, err := readJSONWithLimit[publishMessage](r.Body, s.config.MessageSizeLimit*2, false) // 2x to account for JSON format overhead
if err != nil {
return err
}
@@ -1720,7 +1834,7 @@ func (s *Server) transformBodyJSON(next handleFunc) handleFunc {
if m.Priority != 0 {
r.Header.Set("X-Priority", fmt.Sprintf("%d", m.Priority))
}
if m.Tags != nil && len(m.Tags) > 0 {
if len(m.Tags) > 0 {
r.Header.Set("X-Tags", strings.Join(m.Tags, ","))
}
if m.Attach != "" {
@@ -1735,6 +1849,9 @@ func (s *Server) transformBodyJSON(next handleFunc) handleFunc {
if m.Icon != "" {
r.Header.Set("X-Icon", m.Icon)
}
if m.Markdown {
r.Header.Set("X-Markdown", "yes")
}
if len(m.Actions) > 0 {
actionsStr, err := json.Marshal(m.Actions)
if err != nil {
@@ -1751,13 +1868,19 @@ func (s *Server) transformBodyJSON(next handleFunc) handleFunc {
if m.Call != "" {
r.Header.Set("X-Call", m.Call)
}
if m.Cache != "" {
r.Header.Set("X-Cache", m.Cache)
}
if m.Firebase != "" {
r.Header.Set("X-Firebase", m.Firebase)
}
return next(w, r, v)
}
}
func (s *Server) transformMatrixJSON(next handleFunc) handleFunc {
return func(w http.ResponseWriter, r *http.Request, v *visitor) error {
newRequest, err := newRequestFromMatrixJSON(r, s.config.BaseURL, s.config.MessageLimit)
newRequest, err := newRequestFromMatrixJSON(r, s.config.BaseURL, s.config.MessageSizeLimit)
if err != nil {
logvr(v, r).Tag(tagMatrix).Err(err).Debug("Invalid Matrix request")
if e, ok := err.(*errMatrixPushkeyRejected); ok {
@@ -1774,14 +1897,14 @@ func (s *Server) transformMatrixJSON(next handleFunc) handleFunc {
}
func (s *Server) authorizeTopicWrite(next handleFunc) handleFunc {
return s.autorizeTopic(next, user.PermissionWrite)
return s.authorizeTopic(next, user.PermissionWrite)
}
func (s *Server) authorizeTopicRead(next handleFunc) handleFunc {
return s.autorizeTopic(next, user.PermissionRead)
return s.authorizeTopic(next, user.PermissionRead)
}
func (s *Server) autorizeTopic(next handleFunc, perm user.Permission) handleFunc {
func (s *Server) authorizeTopic(next handleFunc, perm user.Permission) handleFunc {
return func(w http.ResponseWriter, r *http.Request, v *visitor) error {
if s.userManager == nil {
return next(w, r, v)
@@ -1813,8 +1936,8 @@ func (s *Server) autorizeTopic(next handleFunc, perm user.Permission) handleFunc
// This function will ALWAYS return a visitor, even if an error occurs (e.g. unauthorized), so
// that subsequent logging calls still have a visitor context.
func (s *Server) maybeAuthenticate(r *http.Request) (*visitor, error) {
// Read "Authorization" header value, and exit out early if it's not set
ip := extractIPAddress(r, s.config.BehindProxy)
// Read the "Authorization" header value and exit out early if it's not set
ip := extractIPAddress(r, s.config.BehindProxy, s.config.ProxyForwardedHeader, s.config.ProxyTrustedAddresses)
vip := s.visitor(ip, nil)
if s.userManager == nil {
return vip, nil
@@ -1889,7 +2012,7 @@ func (s *Server) authenticateBearerAuth(r *http.Request, token string) (*user.Us
if err != nil {
return nil, err
}
ip := extractIPAddress(r, s.config.BehindProxy)
ip := extractIPAddress(r, s.config.BehindProxy, s.config.ProxyForwardedHeader, s.config.ProxyTrustedAddresses)
go s.userManager.EnqueueTokenUpdate(token, &user.TokenUpdate{
LastAccess: time.Now(),
LastOrigin: ip,
@@ -1912,7 +2035,11 @@ func (s *Server) visitor(ip netip.Addr, user *user.User) *visitor {
}
func (s *Server) writeJSON(w http.ResponseWriter, v any) error {
w.Header().Set("Content-Type", "application/json")
return s.writeJSONWithContentType(w, v, "application/json")
}
func (s *Server) writeJSONWithContentType(w http.ResponseWriter, v any, contentType string) error {
w.Header().Set("Content-Type", contentType)
w.Header().Set("Access-Control-Allow-Origin", s.config.AccessControlAllowOrigin) // CORS, allow cross-origin requests
if err := json.NewEncoder(w).Encode(v); err != nil {
return err

View File

@@ -95,13 +95,23 @@
# auth-default-access: "read-write"
# auth-startup-queries:
# If set, the X-Forwarded-For header is used to determine the visitor IP address
# instead of the remote address of the connection.
# If set, the X-Forwarded-For header (or whatever is configured in proxy-forwarded-header) is used to determine
# the visitor IP address instead of the remote address of the connection.
#
# WARNING: If you are behind a proxy, you must set this, otherwise all visitors are rate limited
# WARNING: If you are behind a proxy, you must set this, otherwise all visitors are rate-limited
# as if they are one.
#
# - behind-proxy makes it so that the real visitor IP address is extracted from the header defined in
# proxy-forwarded-header. Without this, the remote address of the incoming connection is used.
# - proxy-forwarded-header is the header to use to identify visitors. It may be a single IP address (e.g. 1.2.3.4),
# a comma-separated list of IP addresses (e.g. "1.2.3.4, 5.6.7.8"), or an RFC 7239-style header (e.g. "for=1.2.3.4;by=proxy.example.com, for=5.6.7.8").
# - proxy-trusted-addresses is a comma-separated list of IP addresses that are removed from the forwarded header
# to determine the real IP address. This is only useful if there are multiple proxies involved that add themselves to
# the forwarded header.
#
# behind-proxy: false
# proxy-forwarded-header: "X-Forwarded-For"
# proxy-trusted-addresses:
# If enabled, clients can attach files to notifications as attachments. Minimum settings to enable attachments
# are "attachment-cache-dir" and "base-url".
@@ -138,12 +148,37 @@
# - smtp-server-domain is the e-mail domain, e.g. ntfy.sh
# - smtp-server-addr-prefix is an optional prefix for the e-mail addresses to prevent spam. If set to "ntfy-",
# for instance, only e-mails to ntfy-$topic@ntfy.sh will be accepted. If this is not set, all emails to
# $topic@ntfy.sh will be accepted (which may obviously be a spam problem).
# $topic@ntfy.sh will be accepted (which may be a spam problem).
#
# smtp-server-listen:
# smtp-server-domain:
# smtp-server-addr-prefix:
# Web Push support (background notifications for browsers)
#
# If enabled, allows the ntfy web app to receive push notifications, even when the web app is closed. When enabled, users
# can enable background notifications in the web app. Once enabled, ntfy will forward published messages to the push
# endpoint, which will then forward it to the browser.
#
# You must configure web-push-public/private key, web-push-file, and web-push-email-address below to enable Web Push.
# Run "ntfy webpush keys" to generate the keys.
#
# - web-push-public-key is the generated VAPID public key, e.g. AA1234BBCCddvveekaabcdfqwertyuiopasdfghjklzxcvbnm1234567890
# - web-push-private-key is the generated VAPID private key, e.g. AA2BB1234567890abcdefzxcvbnm1234567890
# - web-push-file is a database file to keep track of browser subscription endpoints, e.g. /var/cache/ntfy/webpush.db
# - web-push-email-address is the admin email address send to the push provider, e.g. sysadmin@example.com
# - web-push-startup-queries is an optional list of queries to run on startup`
# - web-push-expiry-warning-duration defines the duration after which unused subscriptions are sent a warning (default is 55d`)
# - web-push-expiry-duration defines the duration after which unused subscriptions will expire (default is 60d)
#
# web-push-public-key:
# web-push-private-key:
# web-push-file:
# web-push-email-address:
# web-push-startup-queries:
# web-push-expiry-warning-duration: "55d"
# web-push-expiry-duration: "60d"
# If enabled, ntfy can perform voice calls via Twilio via the "X-Call" header.
#
# - twilio-account is the Twilio account SID, e.g. AC12345beefbeef67890beefbeef122586
@@ -215,6 +250,16 @@
# upstream-base-url:
# upstream-access-token:
# Configures message-specific limits
#
# - message-size-limit defines the max size of a message body. Please note message sizes >4K are NOT RECOMMENDED,
# and largely untested. If FCM and/or APNS is used, the limit should stay 4K, because their limits are around that size.
# If you increase this size limit regardless, FCM and APNS will NOT work for large messages.
# - message-delay-limit defines the max delay of a message when using the "Delay" header.
#
# message-size-limit: "4k"
# message-delay-limit: "3d"
# Rate limiting: Total number of topics before the server rejects new topics.
#
# global-topic-limit: 15000
@@ -256,15 +301,14 @@
# Rate limiting: Enable subscriber-based rate limiting (mostly used for UnifiedPush)
#
# If enabled, subscribers may opt to have published messages counted against their own rate limits, as opposed
# to the publisher's rate limits. This is especially useful to increase the amount of messages that high-volume
# publishers (e.g. Matrix/Mastodon servers) are allowed to send.
# If subscriber-based rate limiting is enabled, messages published on UnifiedPush topics** (topics starting with "up")
# will be counted towards the "rate visitor" of the topic. A "rate visitor" is the first subscriber to the topic.
#
# Once enabled, a client may send a "Rate-Topics: <topic1>,<topic2>,..." header when subscribing to topics via
# HTTP stream, or websockets, thereby registering itself as the "rate visitor", i.e. the visitor whose rate limits
# to use when publishing on this topic. Note: Setting the rate visitor requires READ-WRITE permission on the topic.
# Once enabled, a client subscribing to UnifiedPush topics via HTTP stream, or websockets, will be automatically registered as
# a "rate visitor", i.e. the visitor whose rate limits will be used when publishing on this topic. Note that setting the rate visitor
# requires **read-write permission** on the topic.
#
# UnifiedPush only: If this setting is enabled, publishing to UnifiedPush topics will lead to a HTTP 507 response if
# If this setting is enabled, publishing to UnifiedPush topics will lead to a HTTP 507 response if
# no "rate visitor" has been previously registered. This is to avoid burning the publisher's "visitor-message-daily-limit".
#
# visitor-subscriber-rate-limiting: false
@@ -321,6 +365,10 @@
# - "field -> level" to match any value, e.g. "time_taken_ms -> debug"
# Warning: Using log-level-overrides has a performance penalty. Only use it for temporary debugging.
#
# Check your permissions:
# If you are running ntfy with systemd, make sure this log file is owned by the
# ntfy user and group by running: chown ntfy.ntfy <filename>.
#
# Example (good for production):
# log-level: info
# log-format: json

View File

@@ -2,9 +2,10 @@ package server
import (
"encoding/json"
"heckel.io/ntfy/log"
"heckel.io/ntfy/user"
"heckel.io/ntfy/util"
"errors"
"heckel.io/ntfy/v2/log"
"heckel.io/ntfy/v2/user"
"heckel.io/ntfy/v2/util"
"net/http"
"net/netip"
"strings"
@@ -36,7 +37,10 @@ func (s *Server) handleAccountCreate(w http.ResponseWriter, r *http.Request, v *
return errHTTPConflictUserExists
}
logvr(v, r).Tag(tagAccount).Field("user_name", newAccount.Username).Info("Creating user %s", newAccount.Username)
if err := s.userManager.AddUser(newAccount.Username, newAccount.Password, user.RoleUser); err != nil {
if err := s.userManager.AddUser(newAccount.Username, newAccount.Password, user.RoleUser, false); err != nil {
if errors.Is(err, user.ErrInvalidArgument) {
return errHTTPBadRequestInvalidUsername
}
return err
}
v.AccountCreated()
@@ -170,6 +174,11 @@ func (s *Server) handleAccountDelete(w http.ResponseWriter, r *http.Request, v *
if _, err := s.userManager.Authenticate(u.Name, req.Password); err != nil {
return errHTTPBadRequestIncorrectPasswordConfirmation
}
if s.webPush != nil && u.ID != "" {
if err := s.webPush.RemoveSubscriptionsByUserID(u.ID); err != nil {
logvr(v, r).Err(err).Warn("Error removing web push subscriptions for %s", u.Name)
}
}
if u.Billing.StripeSubscriptionID != "" {
logvr(v, r).Tag(tagStripe).Info("Canceling billing subscription for user %s", u.Name)
if _, err := s.stripe.CancelSubscription(u.Billing.StripeSubscriptionID); err != nil {
@@ -198,7 +207,7 @@ func (s *Server) handleAccountPasswordChange(w http.ResponseWriter, r *http.Requ
return errHTTPBadRequestIncorrectPasswordConfirmation
}
logvr(v, r).Tag(tagAccount).Debug("Changing password for user %s", u.Name)
if err := s.userManager.ChangePassword(u.Name, req.NewPassword); err != nil {
if err := s.userManager.ChangePassword(u.Name, req.NewPassword, false); err != nil {
return err
}
return s.writeJSON(w, newSuccessResponse())

View File

@@ -3,9 +3,9 @@ package server
import (
"fmt"
"github.com/stretchr/testify/require"
"heckel.io/ntfy/log"
"heckel.io/ntfy/user"
"heckel.io/ntfy/util"
"heckel.io/ntfy/v2/log"
"heckel.io/ntfy/v2/user"
"heckel.io/ntfy/v2/util"
"io"
"net/netip"
"path/filepath"
@@ -87,9 +87,9 @@ func TestAccount_Signup_AsUser(t *testing.T) {
defer s.closeDatabases()
log.Info("1")
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleAdmin))
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleAdmin, false))
log.Info("2")
require.Nil(t, s.userManager.AddUser("ben", "ben", user.RoleUser))
require.Nil(t, s.userManager.AddUser("ben", "ben", user.RoleUser, false))
log.Info("3")
rr := request(t, s, "POST", "/v1/account", `{"username":"emma", "password":"emma"}`, map[string]string{
"Authorization": util.BasicAuth("phil", "phil"),
@@ -174,7 +174,7 @@ func TestAccount_ChangeSettings(t *testing.T) {
s := newTestServer(t, newTestConfigWithAuthFile(t))
defer s.closeDatabases()
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser))
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser, false))
u, _ := s.userManager.User("phil")
token, _ := s.userManager.CreateToken(u.ID, "", time.Unix(0, 0), netip.IPv4Unspecified())
@@ -203,7 +203,7 @@ func TestAccount_Subscription_AddUpdateDelete(t *testing.T) {
s := newTestServer(t, newTestConfigWithAuthFile(t))
defer s.closeDatabases()
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser))
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser, false))
rr := request(t, s, "POST", "/v1/account/subscription", `{"base_url": "http://abc.com", "topic": "def"}`, map[string]string{
"Authorization": util.BasicAuth("phil", "phil"),
@@ -254,7 +254,7 @@ func TestAccount_ChangePassword(t *testing.T) {
s := newTestServer(t, newTestConfigWithAuthFile(t))
defer s.closeDatabases()
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser))
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser, false))
rr := request(t, s, "POST", "/v1/account/password", `{"password": "WRONG", "new_password": ""}`, map[string]string{
"Authorization": util.BasicAuth("phil", "phil"),
@@ -296,7 +296,7 @@ func TestAccount_ExtendToken(t *testing.T) {
s := newTestServer(t, newTestConfigWithAuthFile(t))
defer s.closeDatabases()
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser))
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser, false))
rr := request(t, s, "POST", "/v1/account/token", "", map[string]string{
"Authorization": util.BasicAuth("phil", "phil"),
@@ -332,7 +332,7 @@ func TestAccount_ExtendToken_NoTokenProvided(t *testing.T) {
s := newTestServer(t, newTestConfigWithAuthFile(t))
defer s.closeDatabases()
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser))
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser, false))
rr := request(t, s, "PATCH", "/v1/account/token", "", map[string]string{
"Authorization": util.BasicAuth("phil", "phil"), // Not Bearer!
@@ -345,7 +345,7 @@ func TestAccount_DeleteToken(t *testing.T) {
s := newTestServer(t, newTestConfigWithAuthFile(t))
defer s.closeDatabases()
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser))
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser, false))
rr := request(t, s, "POST", "/v1/account/token", "", map[string]string{
"Authorization": util.BasicAuth("phil", "phil"),
@@ -455,14 +455,14 @@ func TestAccount_Reservation_AddAdminSuccess(t *testing.T) {
Code: "pro",
ReservationLimit: 2,
}))
require.Nil(t, s.userManager.AddUser("noadmin1", "pass", user.RoleUser))
require.Nil(t, s.userManager.AddUser("noadmin1", "pass", user.RoleUser, false))
require.Nil(t, s.userManager.ChangeTier("noadmin1", "pro"))
require.Nil(t, s.userManager.AddReservation("noadmin1", "mytopic", user.PermissionDenyAll))
require.Nil(t, s.userManager.AddUser("noadmin2", "pass", user.RoleUser))
require.Nil(t, s.userManager.AddUser("noadmin2", "pass", user.RoleUser, false))
require.Nil(t, s.userManager.ChangeTier("noadmin2", "pro"))
require.Nil(t, s.userManager.AddUser("phil", "adminpass", user.RoleAdmin))
require.Nil(t, s.userManager.AddUser("phil", "adminpass", user.RoleAdmin, false))
// Admin can reserve topic
rr := request(t, s, "POST", "/v1/account/reservation", `{"topic":"sometopic","everyone":"deny-all"}`, map[string]string{
@@ -624,7 +624,7 @@ func TestAccount_Reservation_Delete_Messages_And_Attachments(t *testing.T) {
s := newTestServer(t, conf)
// Create user with tier
require.Nil(t, s.userManager.AddUser("phil", "mypass", user.RoleUser))
require.Nil(t, s.userManager.AddUser("phil", "mypass", user.RoleUser, false))
require.Nil(t, s.userManager.AddTier(&user.Tier{
Code: "pro",
MessageLimit: 20,
@@ -718,11 +718,11 @@ func TestAccount_Reservation_Delete_Messages_And_Attachments(t *testing.T) {
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser))
require.Nil(t, s.userManager.AddTier(&user.Tier{
Code: "starter",
MessageLimit: 10,
MessageSizeLimit: 10,
}))
require.Nil(t, s.userManager.AddTier(&user.Tier{
Code: "pro",
MessageLimit: 20,
MessageSizeLimit: 20,
}))
require.Nil(t, s.userManager.ChangeTier("phil", "starter"))

View File

@@ -1,7 +1,8 @@
package server
import (
"heckel.io/ntfy/user"
"errors"
"heckel.io/ntfy/v2/user"
"net/http"
)
@@ -38,14 +39,14 @@ func (s *Server) handleUsersGet(w http.ResponseWriter, r *http.Request, v *visit
}
func (s *Server) handleUsersAdd(w http.ResponseWriter, r *http.Request, v *visitor) error {
req, err := readJSONWithLimit[apiUserAddRequest](r.Body, jsonBodyBytesLimit, false)
req, err := readJSONWithLimit[apiUserAddOrUpdateRequest](r.Body, jsonBodyBytesLimit, false)
if err != nil {
return err
} else if !user.AllowedUsername(req.Username) || req.Password == "" {
return errHTTPBadRequest.Wrap("username invalid, or password missing")
} else if !user.AllowedUsername(req.Username) || (req.Password == "" && req.Hash == "") {
return errHTTPBadRequest.Wrap("username invalid, or password/password_hash missing")
}
u, err := s.userManager.User(req.Username)
if err != nil && err != user.ErrUserNotFound {
if err != nil && !errors.Is(err, user.ErrUserNotFound) {
return err
} else if u != nil {
return errHTTPConflictUserExists
@@ -53,13 +54,17 @@ func (s *Server) handleUsersAdd(w http.ResponseWriter, r *http.Request, v *visit
var tier *user.Tier
if req.Tier != "" {
tier, err = s.userManager.Tier(req.Tier)
if err == user.ErrTierNotFound {
if errors.Is(err, user.ErrTierNotFound) {
return errHTTPBadRequestTierInvalid
} else if err != nil {
return err
}
}
if err := s.userManager.AddUser(req.Username, req.Password, user.RoleUser); err != nil {
password, hashed := req.Password, false
if req.Hash != "" {
password, hashed = req.Hash, true
}
if err := s.userManager.AddUser(req.Username, password, user.RoleUser, hashed); err != nil {
return err
}
if tier != nil {
@@ -70,13 +75,60 @@ func (s *Server) handleUsersAdd(w http.ResponseWriter, r *http.Request, v *visit
return s.writeJSON(w, newSuccessResponse())
}
func (s *Server) handleUsersUpdate(w http.ResponseWriter, r *http.Request, v *visitor) error {
req, err := readJSONWithLimit[apiUserAddOrUpdateRequest](r.Body, jsonBodyBytesLimit, false)
if err != nil {
return err
} else if !user.AllowedUsername(req.Username) {
return errHTTPBadRequest.Wrap("username invalid")
} else if req.Password == "" && req.Hash == "" && req.Tier == "" {
return errHTTPBadRequest.Wrap("need to provide at least one of \"password\", \"password_hash\" or \"tier\"")
}
u, err := s.userManager.User(req.Username)
if err != nil && !errors.Is(err, user.ErrUserNotFound) {
return err
} else if u != nil {
if u.IsAdmin() {
return errHTTPForbidden
}
if req.Hash != "" {
if err := s.userManager.ChangePassword(req.Username, req.Hash, true); err != nil {
return err
}
} else if req.Password != "" {
if err := s.userManager.ChangePassword(req.Username, req.Password, false); err != nil {
return err
}
}
} else {
password, hashed := req.Password, false
if req.Hash != "" {
password, hashed = req.Hash, true
}
if err := s.userManager.AddUser(req.Username, password, user.RoleUser, hashed); err != nil {
return err
}
}
if req.Tier != "" {
if _, err = s.userManager.Tier(req.Tier); errors.Is(err, user.ErrTierNotFound) {
return errHTTPBadRequestTierInvalid
} else if err != nil {
return err
}
if err := s.userManager.ChangeTier(req.Username, req.Tier); err != nil {
return err
}
}
return s.writeJSON(w, newSuccessResponse())
}
func (s *Server) handleUsersDelete(w http.ResponseWriter, r *http.Request, v *visitor) error {
req, err := readJSONWithLimit[apiUserDeleteRequest](r.Body, jsonBodyBytesLimit, false)
if err != nil {
return err
}
u, err := s.userManager.User(req.Username)
if err == user.ErrUserNotFound {
if errors.Is(err, user.ErrUserNotFound) {
return errHTTPBadRequestUserNotFound
} else if err != nil {
return err
@@ -98,7 +150,7 @@ func (s *Server) handleAccessAllow(w http.ResponseWriter, r *http.Request, v *vi
return err
}
_, err = s.userManager.User(req.Username)
if err == user.ErrUserNotFound {
if errors.Is(err, user.ErrUserNotFound) {
return errHTTPBadRequestUserNotFound
} else if err != nil {
return err

Some files were not shown because too many files have changed in this diff Show More