Compare commits

...

128 Commits

Author SHA1 Message Date
binwiederhier
4e7e6e57fe Bump version 2022-12-23 09:30:24 -05:00
binwiederhier
0b78d3173d Thank you for your sponsorship @voroskoi 2022-12-23 08:39:44 -05:00
binwiederhier
92d7e5c58a Bump version 2022-12-23 08:38:45 -05:00
binwiederhier
6f170b1ad7 Thank you @Terrormixer3000 for your donation 2022-12-21 09:39:13 -05:00
binwiederhier
6dbe25fcc5 Known issues 2022-12-20 21:58:54 -05:00
binwiederhier
74828adcb8 Added blog posts 2022-12-19 21:56:04 -05:00
binwiederhier
3120cd54fe Thank you @CodingTimeDEV for your sponsorship 2022-12-19 10:02:19 -05:00
binwiederhier
b1cafc06eb Merge branch 'main' of github.com:binwiederhier/ntfy 2022-12-19 09:59:47 -05:00
binwiederhier
fd66fb33a8 Merge branch 'main' of https://hosted.weblate.org/git/ntfy/web 2022-12-19 09:59:42 -05:00
Philipp C. Heckel
5af9d0164b Merge pull request #548 from Clortox/integrations-add-drone-ntfy
docs: Integrations add drone ntfy
2022-12-16 21:00:05 -05:00
Tyler Perkins
049a01d58f Fix typo 2022-12-16 20:49:00 -05:00
Tyler Perkins
629af0efc3 Add entry to integrations 2022-12-16 20:44:35 -05:00
109247019824
a1262c2406 Translated using Weblate (Bulgarian)
Currently translated at 100.0% (189 of 189 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/bg/
2022-12-16 08:50:29 +01:00
binwiederhier
97dd879597 Thank you @ksurl for your donation 2022-12-14 05:38:33 -05:00
binwiederhier
f1321d6140 Thanks to @msdeibel for your donation 2022-12-13 15:21:06 -05:00
binwiederhier
0646f48ca6 Code of Conduct 2022-12-12 15:06:04 -05:00
binwiederhier
a50d65393e Thank you @zugaldia and @NathanSweet for your donation 2022-12-12 10:54:53 -05:00
binwiederhier
67221b015d Changelog 2022-12-12 09:55:17 -05:00
Philipp C. Heckel
40aadbad85 Merge pull request #542 from nicois/nicois/use-prepared-statement-for-bulk-writes
Use prepared statement for bulk writes
2022-12-12 09:51:42 -05:00
Philipp Heckel
77ebf306a3 Remove ad-type wording 2022-12-12 09:41:23 -05:00
Philipp C. Heckel
94d3924432 Merge pull request #540 from yardenshoham/gitpod
Add Gitpod configuration for quick setup of development environments
2022-12-12 09:26:12 -05:00
Nick Farrell
1235ea5bb5 Use prepared statement for bulk writes
When executing the same statement multiple times, avoid
the overhead of re-parsing the statement for each insert.
2022-12-12 14:13:40 +11:00
Philipp Heckel
321ed12663 Changelog 2022-12-11 15:50:16 -05:00
Yarden Shoham
265af01f9c Add Gitpod configuration for quick setup of development environments
With this change, any developer can simply open a development environment in Gitpod. The environment has docs, web, and binary being built on every code change.

Also included the vscode extensions for Go and Docker.

Signed-off-by: Yarden Shoham <hrsi88@gmail.com>
2022-12-10 21:56:13 +00:00
Philipp Heckel
a9961df4e2 Merge branch 'main' of github.com:binwiederhier/ntfy 2022-12-10 09:01:55 -05:00
Philipp Heckel
8d3f35f4f7 Thank you @p-samuel for your donation 2022-12-10 09:01:40 -05:00
Philipp C. Heckel
2b8ae406a3 Merge pull request #537 from yardenshoham/alphabet
Add uppercase letters to random topic name generation
2022-12-09 20:11:33 -05:00
Yarden Shoham
d78f1a3ff9 Add uppercase letters to random topic name generation
Signed-off-by: Yarden Shoham <hrsi88@gmail.com>
2022-12-09 20:28:12 +00:00
Philipp Heckel
c500c9c199 Re-word to sound less marketing-y 2022-12-09 10:45:45 -05:00
Philipp C. Heckel
b2363d2783 Merge pull request #536 from farukaydin/patch-1
Add Automatisch to official integrations list
2022-12-09 10:44:51 -05:00
Ömer Faruk Aydın
8aba600fa5 Add Automatisch to official integrations list 2022-12-09 14:03:50 +03:00
Philipp Heckel
18596ecc34 Changelog 2022-12-08 09:16:59 -05:00
Philipp Heckel
420d289d35 Merge branch 'main' of github.com:binwiederhier/ntfy 2022-12-08 09:10:16 -05:00
Philipp C. Heckel
eebd0f113b Merge pull request #533 from yardenshoham/generate-topic-name
Add "Generate topic name" button to "Subscribe to topic" dialog
2022-12-08 09:10:00 -05:00
Philipp Heckel
c4286984ab Merge branch 'main' of https://hosted.weblate.org/git/ntfy/web 2022-12-08 09:08:10 -05:00
Yarden Shoham
e0d6a0b974 Simplify logic
Signed-off-by: Yarden Shoham <hrsi88@gmail.com>
2022-12-08 11:54:37 +00:00
Yarden Shoham
71e46860ac Remove unused layouts
Signed-off-by: Yarden Shoham <hrsi88@gmail.com>
2022-12-08 11:07:16 +00:00
Yarden Shoham
ce942ffe16 Remove nanoid dependency
Signed-off-by: Yarden Shoham <hrsi88@gmail.com>
2022-12-08 10:42:28 +00:00
Yarden Shoham
e083ef0d6d Place "Generate topic name" in the same line as the text field
Signed-off-by: Yarden Shoham <hrsi88@gmail.com>
2022-12-08 10:32:02 +00:00
Yarden Shoham
b91fb3f586 Add "Generate topic name" button to "Subscribe to topic" dialog
Added a new button. When clicked it'll generate a random alphanumeric string and append to the current topic (or replace if empty).

Signed-off-by: Yarden Shoham <hrsi88@gmail.com>
2022-12-08 00:01:32 +00:00
Philipp Heckel
79356baee1 Changelog 2022-12-07 12:03:22 -05:00
Philipp Heckel
cb6c0b6e45 Changelog 2022-12-06 16:18:16 -05:00
Philipp Heckel
543bc24bfd Public server list 2022-12-06 12:23:10 -05:00
Philipp Heckel
789ff72081 Changelog 2022-12-05 20:53:39 -05:00
Ivan Ip
5dc4754181 Translated using Weblate (Chinese (Traditional))
Currently translated at 43.9% (83 of 189 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/zh_Hant/
2022-12-05 05:48:27 +01:00
Philipp Heckel
eaa64b636a Bump Android version number 2022-12-04 23:37:09 -05:00
Philipp Heckel
1c9cd40d34 Changelog 2022-12-04 23:24:07 -05:00
Philipp Heckel
9c54181ff8 Android release notes 2022-12-04 20:38:38 -05:00
Philipp Heckel
d4211441b3 Thanks to @mdlnr for your donation 2022-12-02 19:58:11 -05:00
Philipp Heckel
3307debacc Merge branch 'main' of https://hosted.weblate.org/git/ntfy/web 2022-12-02 19:57:31 -05:00
popinha13
95fd6ecab1 Added translation using Weblate (Portuguese) 2022-11-30 14:58:21 +01:00
Philipp Heckel
84dca41008 Derp 2022-11-28 22:12:20 -05:00
Philipp Heckel
b3d90f04ac Add blog post 2022-11-28 22:11:04 -05:00
Philipp Heckel
c2550dbca9 Release notes + blog post, thanks Timo 2022-11-28 15:15:49 -05:00
Philipp Heckel
fe11ed3ac7 Remove --env-topic flag from "ntfy publish" (as per deprecation) 2022-11-28 11:06:47 -05:00
Philipp Heckel
24b5eb3405 Changelog 2022-11-28 06:44:34 -05:00
Philipp Heckel
bc16c49187 Bump deps 2022-11-27 22:03:00 -05:00
Philipp Heckel
3438e0bfb0 Changelog 2022-11-27 12:42:25 -05:00
Philipp Heckel
7e9abd2350 Changelog 2022-11-26 22:40:01 -05:00
Philipp Heckel
8f6880d809 Changelog 2022-11-26 21:58:51 -05:00
Philipp Heckel
e0024e59f3 Merge branch 'main' of github.com:binwiederhier/ntfy 2022-11-26 13:35:19 -05:00
Philipp Heckel
b9b604c007 Add YunoHost app 2022-11-26 13:34:56 -05:00
Philipp C. Heckel
be6c30fb0d Merge pull request #518 from mcrowder65/patch-1
Typo fix retweetet -> retweeted
2022-11-25 19:08:34 -05:00
Matt Crowder
7001543d28 Typo fix retweetet -> retweeted 2022-11-25 16:32:05 -07:00
Philipp Heckel
bc38c08a5e Thank you DigitalOcean for sponsoring the project 2022-11-25 09:10:40 -05:00
Philipp Heckel
7f49ebb4ec Add healthchecks.io to list of integrations 2022-11-24 11:10:55 -05:00
Philipp Heckel
3746d2935b Changelog 2022-11-23 13:12:25 -05:00
Philipp Heckel
7b6577d543 Merge branch 'main' of https://hosted.weblate.org/git/ntfy/web 2022-11-23 12:45:20 -05:00
Philipp Heckel
f6643ebc12 Update library URL 2022-11-22 21:31:10 -05:00
Micke Nilsson
fd9ab2704c Translated using Weblate (Swedish)
Currently translated at 24.8% (47 of 189 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/sv/
2022-11-21 18:48:14 +01:00
Philipp Heckel
f241003ac6 Add Console post 2022-11-21 09:31:37 -05:00
Philipp Heckel
38f7843861 Release notes 2022-11-19 16:00:37 -05:00
Philipp Heckel
25e95ae1a6 Changelog 2022-11-18 21:45:44 -05:00
Philipp Heckel
4c1c5e56ab Thank you @crosbyh for your donation 2022-11-18 15:38:39 -05:00
Philipp Heckel
ed29b675ee Thank you @tonyakwei for your donation 2022-11-18 10:35:08 -05:00
Philipp Heckel
3d501ceaf9 Integrations 2022-11-17 22:09:40 -05:00
Philipp Heckel
c5b2c8c680 Bump deps 2022-11-17 21:07:17 -05:00
Philipp Heckel
f29fe22d3d Fine tuning 2022-11-17 20:57:01 -05:00
Philipp Heckel
2540a0396d Merge branch 'main' into l-maciej/main 2022-11-17 20:52:21 -05:00
Philipp Heckel
9fec3f35ff Newline 2022-11-17 20:52:16 -05:00
Philipp Heckel
679b075ecc Fix #503, bump version for release 2022-11-17 20:47:27 -05:00
Maciek
b1819d4766 Merge branch 'main' of https://github.com/binwiederhier/ntfy 2022-11-17 19:37:46 +01:00
Maciek
96b7053884 Fix missing line 2022-11-17 19:37:24 +01:00
Philipp Heckel
fcbf71dad7 Thank you @gergepalfi for your sponsorship! 2022-11-17 06:40:59 -05:00
Philipp Heckel
aee791a17d Bump versions 2022-11-16 21:21:41 -05:00
Philipp Heckel
5b2fe66903 Fix test 2022-11-16 21:12:52 -05:00
Philipp C. Heckel
f4daa4508f Merge pull request #502 from binwiederhier/async-message-cache
Batch message INSERTs
2022-11-16 21:04:18 -05:00
Philipp Heckel
755155479a Thank you @skrollme for your sponsorship 2022-11-16 14:26:54 -05:00
Philipp Heckel
978118a400 Release notes 2022-11-16 11:31:29 -05:00
Philipp Heckel
4a91da60dd Docs 2022-11-16 11:27:46 -05:00
Philipp Heckel
db9ca80b69 Fix race condition making it possible for batches to be >batchSize 2022-11-16 11:16:07 -05:00
Philipp Heckel
e147a41f92 Fix race in tests 2022-11-16 10:44:10 -05:00
Philipp Heckel
497f871447 Docs 2022-11-16 10:33:12 -05:00
Philipp Heckel
ad860afb8b Polish async batching 2022-11-16 10:28:20 -05:00
Philipp Heckel
b4933a5645 WIP: Batch message INSERTs 2022-11-15 14:24:56 -05:00
Philipp C. Heckel
46f437126c Merge pull request #501 from QJoly/main
Fix the Kubernetes ConfigMap
2022-11-15 10:43:12 -05:00
Quentin JOLY
90b85f2956 Merge branch 'binwiederhier:main' into main 2022-11-15 15:41:13 +01:00
Quentin JOLY
ebfbf7cc8e Bad indent 2022-11-15 14:10:55 +00:00
Philipp Heckel
499ac76c43 Thank you @finngreig for your sponsorship 2022-11-15 09:09:31 -05:00
Philipp Heckel
fd7f83378d Refine UP docs 2022-11-14 15:21:02 -05:00
bt90
e7b575badc Add UnifiedPush section 2022-11-14 19:38:55 +01:00
Philipp Heckel
a0f2d81337 Release notes 2022-11-14 06:52:41 -05:00
Philipp Heckel
fb6980a81e Merge branch 'main' of github.com:binwiederhier/ntfy 2022-11-13 21:41:21 -05:00
Philipp Heckel
df45459618 Remove test branch 2022-11-13 21:40:39 -05:00
Philipp Heckel
61b2d92595 Update "on:" config 2022-11-13 21:39:36 -05:00
Philipp Heckel
adda27ec57 Rename secret token 2022-11-13 21:33:27 -05:00
Philipp Heckel
b92b5b37fb Testing docs workflow (5) 2022-11-13 21:23:25 -05:00
Philipp Heckel
18d36e1b30 Testing docs workflow (4) 2022-11-13 21:11:51 -05:00
Philipp Heckel
f4cb447f0a Testing docs workflow (3) 2022-11-13 21:08:25 -05:00
Philipp Heckel
069617eba0 Testing docs workflow (2) 2022-11-13 21:05:03 -05:00
Philipp Heckel
aff193a003 Testing docs workflow (1) 2022-11-13 20:59:12 -05:00
Gerge
eb6a86a009 Translated using Weblate (Hungarian)
Currently translated at 100.0% (189 of 189 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/hu/
2022-11-14 00:50:15 +01:00
Philipp C. Heckel
97025fe8ef Merge pull request #494 from jonocarroll/patch-1
add R wrapper to docs
2022-11-13 17:17:09 -05:00
Jonathan Carroll
08bb0103e8 add R wrapper 2022-11-13 14:07:27 -08:00
Philipp Heckel
e02789c70c Merge branch 'main' of github.com:binwiederhier/ntfy 2022-11-13 06:41:41 -05:00
Philipp Heckel
cf7a451198 Release notes 2022-11-13 06:41:26 -05:00
Philipp C. Heckel
f088498f26 Merge pull request #492 from ksurl/actions-curl
add github actions example
2022-11-13 06:39:13 -05:00
Philipp Heckel
bcc20e0aec Release notes 2022-11-13 06:28:10 -05:00
Philipp Heckel
e236214fd5 Add post 2022-11-13 06:24:57 -05:00
ksurl
b103caf9d4 add github actions example 2022-11-12 13:05:19 -08:00
Philipp Heckel
a43a4aea5e Docs 2022-11-12 14:41:28 -05:00
Maciek
9155c49571 Revert "Update branch to fit main"
This reverts commit 0821b8a25f.
2022-11-11 17:09:36 +01:00
Maciek
baa15110ff Merge branch 'main' of https://github.com/binwiederhier/ntfy 2022-11-11 17:04:01 +01:00
Maciek
0234041e1e re-format and cleanup 2022-11-05 15:42:56 +01:00
Maciek
2fb7523d06 Rolled back formatting on existing manual docs. 2022-11-05 14:28:26 +01:00
Maciek
95e087390f Merge branch 'main' of https://github.com/binwiederhier/ntfy 2022-11-05 14:13:14 +01:00
Maciek
0821b8a25f Update branch to fit main 2022-11-05 14:12:57 +01:00
Maciek
2b823556b3 Created documentation for kustomization deployment 2022-11-02 20:27:27 +01:00
36 changed files with 2453 additions and 1240 deletions

36
.github/workflows/docs.yaml vendored Normal file
View File

@@ -0,0 +1,36 @@
name: docs
on:
push:
branches:
- main
jobs:
publish-docs:
runs-on: ubuntu-latest
steps:
-
name: Checkout ntfy code
uses: actions/checkout@v3
-
name: Checkout docs pages code
uses: actions/checkout@v3
with:
repository: binwiederhier/ntfy-docs.github.io
path: build/ntfy-docs.github.io
token: ${{secrets.NTFY_DOCS_PUSH_TOKEN}}
# Expires after 1 year, re-generate via
# User -> Settings -> Developer options -> Personal Access Tokens -> Fine Grained Token
-
name: Build docs
run: make docs
-
name: Copy generated docs
run: rsync -av --exclude CNAME --delete server/docs/ build/ntfy-docs.github.io/docs/
-
name: Publish docs
run: |
cd build/ntfy-docs.github.io
git config user.name "GitHub Actions Bot"
git config user.email "<>"
git add docs/
git commit -m "Updated docs"
git push origin main

28
.gitpod.yml Normal file
View File

@@ -0,0 +1,28 @@
tasks:
- name: docs
before: make docs-deps
command: mkdocs serve
- name: binary
before: |
npm install --global nodemon
make cli-deps-static-sites
command: |
nodemon --watch './**/*.go' --ext go --signal SIGTERM --exec "CGO_ENABLED=1 go run main.go serve --listen-http :2586 --debug --base-url $(gp url 2586)"
openMode: split-right
- name: web
before: make web-deps
command: cd web && npm start
openMode: split-right
vscode:
extensions:
- golang.go
- ms-azuretools.vscode-docker
ports:
- name: docs
port: 8000
- name: binary
port: 2586
- name: web
port: 3000

133
CODE_OF_CONDUCT.md Normal file
View File

@@ -0,0 +1,133 @@
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, caste, color, religion, or sexual
identity and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the overall
community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or advances of
any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email address,
without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement via Discord/Matrix (binwiederhier),
or email (ntfy@heckel.io). All complaints will be reviewed and investigated promptly
and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series of
actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or permanent
ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within the
community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.1, available at
[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
Community Impact Guidelines were inspired by
[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
For answers to common questions about this code of conduct, see the FAQ at
[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
[https://www.contributor-covenant.org/translations][translations].
[homepage]: https://www.contributor-covenant.org
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
[Mozilla CoC]: https://github.com/mozilla/diversity
[FAQ]: https://www.contributor-covenant.org/faq
[translations]: https://www.contributor-covenant.org/translations

View File

@@ -1,13 +1,5 @@
![ntfy](web/public/static/img/ntfy.png)
---
## 👶 Baby break - My baby girl was born!
Hey folks, my daughter was born on 8/30/22, so I'll be taking some time off from working on ntfy. I'll likely return
to working on features and bugs in a few weeks. I hope you understand. I posted some pictures in [#387](https://github.com/binwiederhier/ntfy/issues/387) 🥰
---
# 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)
@@ -19,6 +11,8 @@ to working on features and bugs in a few weeks. I hope you understand. I posted
[![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)
**ntfy** (pronounce: *notify*) is a simple HTTP-based [pub-sub](https://en.wikipedia.org/wiki/Publish%E2%80%93subscribe_pattern) notification service.
It allows you to **send notifications to your phone or desktop via scripts** from any computer, entirely **without signup or cost**.
@@ -58,16 +52,16 @@ topic. If you'd like to test the iOS app, join [TestFlight](https://testflight.a
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. 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
I welcome any and all 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/).
<a href="https://hosted.weblate.org/engage/ntfy/">
<img src="https://hosted.weblate.org/widgets/ntfy/-/multi-blue.svg" alt="Translation status" />
</a>
## Donations
## Sponsors
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. A big fat **Thank You** to the folks already sponsoring ntfy:
@@ -96,6 +90,32 @@ appreciated. A big fat **Thank You** to the folks already sponsoring ntfy:
<a href="https://github.com/nwithan8"><img src="https://github.com/nwithan8.png" width="40px" /></a>
<a href="https://github.com/peterleiser"><img src="https://github.com/peterleiser.png" width="40px" /></a>
<a href="https://github.com/portothree"><img src="https://github.com/portothree.png" width="40px" /></a>
<a href="https://github.com/finngreig"><img src="https://github.com/finngreig.png" width="40px" /></a>
<a href="https://github.com/skrollme"><img src="https://github.com/skrollme.png" width="40px" /></a>
<a href="https://github.com/gergepalfi"><img src="https://github.com/gergepalfi.png" width="40px" /></a>
<a href="https://github.com/tonyakwei"><img src="https://github.com/tonyakwei.png" width="40px" /></a>
<a href="https://github.com/crosbyh"><img src="https://github.com/crosbyh.png" width="40px" /></a>
<a href="https://github.com/mdlnr"><img src="https://github.com/mdlnr.png" width="40px" /></a>
<a href="https://github.com/p-samuel"><img src="https://github.com/p-samuel.png" width="40px" /></a>
<a href="https://github.com/zugaldia"><img src="https://github.com/zugaldia.png" width="40px" /></a>
<a href="https://github.com/NathanSweet"><img src="https://github.com/NathanSweet.png" width="40px" /></a>
<a href="https://github.com/msdeibel"><img src="https://github.com/msdeibel.png" width="40px" /></a>
<a href="https://github.com/ksurl"><img src="https://github.com/ksurl.png" width="40px" /></a>
<a href="https://github.com/CodingTimeDEV"><img src="https://github.com/CodingTimeDEV.png" width="40px" /></a>
<a href="https://github.com/Terrormixer3000"><img src="https://github.com/Terrormixer3000.png" width="40px" /></a>
<a href="https://github.com/voroskoi"><img src="https://github.com/voroskoi.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,
and [DigitalOcean](https://www.digitalocean.com/) for supporting the project with $60/yr:
<a href="https://www.digitalocean.com/"><img src="https://opensource.nyc3.cdn.digitaloceanspaces.com/attribution/assets/SVG/DO_Logo_horizontal_blue.svg" width="201px"></a>
## Code of Conduct
We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, caste, color, religion, or sexual identity and orientation.
**We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community.**
_Please be sure to read the complete [Code of Conduct](CODE_OF_CONDUCT.md)._
## License
Made with ❤️ by [Philipp C. Heckel](https://heckel.io).

View File

@@ -49,7 +49,7 @@ var cmdPublish = &cli.Command{
Usage: "Send message via a ntfy server",
UsageText: `ntfy publish [OPTIONS..] TOPIC [MESSAGE...]
ntfy publish [OPTIONS..] --wait-cmd COMMAND...
NTFY_TOPIC=.. ntfy publish [OPTIONS..] -P [MESSAGE...]`,
NTFY_TOPIC=.. ntfy publish [OPTIONS..] [MESSAGE...]`,
Action: execPublish,
Category: categoryClient,
Flags: flagsPublish,
@@ -72,7 +72,7 @@ Examples:
ntfy pub --wait-pid 1234 mytopic # Wait for process 1234 to exit before publishing
ntfy pub --wait-cmd mytopic rsync -av ./ /tmp/a # Run command and publish after it completes
NTFY_USER=phil:mypass ntfy pub secret Psst # Use env variables to set username/password
NTFY_TOPIC=mytopic ntfy pub -P "some message" # Use NTFY_TOPIC variable as topic
NTFY_TOPIC=mytopic ntfy pub "some message" # Use NTFY_TOPIC variable as topic
cat flower.jpg | ntfy pub --file=- flowers 'Nice!' # Same as above, send image.jpg as attachment
ntfy trigger mywebhook # Sending without message, useful for webhooks
@@ -241,13 +241,9 @@ func parseTopicMessageCommand(c *cli.Context) (topic string, message string, com
}
func parseTopicAndArgs(c *cli.Context) (topic string, args []string, err error) {
envTopic := c.Bool("env-topic")
if envTopic {
fmt.Fprintln(c.App.ErrWriter, "\x1b[1;33mDeprecation notice: The --env-topic/-P flag will be removed in July 2022, see https://ntfy.sh/docs/deprecations/ for details.\x1b[0m")
topic = os.Getenv("NTFY_TOPIC")
if topic == "" {
return "", nil, errors.New("when --env-topic is passed, must define NTFY_TOPIC environment variable")
}
envTopic := os.Getenv("NTFY_TOPIC")
if envTopic != "" {
topic = envTopic
return topic, remainingArgs(c, 0), nil
}
if c.NArg() < 1 {

View File

@@ -17,6 +17,7 @@ func TestCLI_Publish_Subscribe_Poll_Real_Server(t *testing.T) {
app, _, _, _ := newTestApp()
require.Nil(t, app.Run([]string{"ntfy", "publish", "ntfytest", "ntfy unit test " + testMessage}))
time.Sleep(3 * time.Second) // Since #502, ntfy.sh writes messages to the cache asynchronously, after a timeout of ~1.5s
app2, _, stdout, _ := newTestApp()
require.Nil(t, app2.Run([]string{"ntfy", "subscribe", "--poll", "ntfytest"}))

View File

@@ -44,6 +44,8 @@ var flagsServe = append(
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.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-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-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"}),
@@ -110,6 +112,8 @@ func execServe(c *cli.Context) error {
cacheFile := c.String("cache-file")
cacheDuration := c.Duration("cache-duration")
cacheStartupQueries := c.String("cache-startup-queries")
cacheBatchSize := c.Int("cache-batch-size")
cacheBatchTimeout := c.Duration("cache-batch-timeout")
authFile := c.String("auth-file")
authDefaultAccess := c.String("auth-default-access")
attachmentCacheDir := c.String("attachment-cache-dir")
@@ -233,6 +237,8 @@ func execServe(c *cli.Context) error {
conf.CacheFile = cacheFile
conf.CacheDuration = cacheDuration
conf.CacheStartupQueries = cacheStartupQueries
conf.CacheBatchSize = cacheBatchSize
conf.CacheBatchTimeout = cacheBatchTimeout
conf.AuthFile = authFile
conf.AuthDefaultRead = authDefaultRead
conf.AuthDefaultWrite = authDefaultWrite

View File

@@ -309,6 +309,25 @@ 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.
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.
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:
=== "Prefix"
```
$ ntfy access '*' 'up*' write-only
```
=== "Explicitly"
```
$ ntfy access '*' upYzMtZGZiYTY5 write-only
```
## E-mail notifications
To allow forwarding messages via e-mail, you can configure an **SMTP server for outgoing messages**. Once configured,
you can set the `X-Email` header to [send messages via e-mail](publish.md#e-mail-notifications) (e.g.
@@ -806,19 +825,27 @@ out [this discussion on Reddit](https://www.reddit.com/r/golang/comments/r9u4ee/
Depending on *how you run it*, here are a few limits that are relevant:
### WAL for message cache
### Message cache
By default, the [message cache](#message-cache) (defined by `cache-file`) uses the SQLite default settings, which means it
syncs to disk on every write. For personal servers, this is perfectly adequate. For larger installations, such as ntfy.sh,
the [write-ahead log (WAL)](https://sqlite.org/wal.html) should be enabled, and the sync mode should be adjusted.
See [this article](https://phiresky.github.io/blog/2020/sqlite-performance-tuning/) for details.
In addition to that, for very high load servers (such as ntfy.sh), it may be beneficial to write messages to the cache
in batches, and asynchronously. This can be enabled with the `cache-batch-size` and `cache-batch-timeout`. If you start
seeing `database locked` messages in the logs, you should probably enable that.
Here's how ntfy.sh has been tuned in the `server.yml` file:
``` yaml
cache-batch-size: 25
cache-batch-timeout: "1s"
cache-startup-queries: |
pragma journal_mode = WAL;
pragma synchronous = normal;
pragma temp_store = memory;
pragma busy_timeout = 15000;
vacuum;
```
### For systemd services
@@ -880,7 +907,7 @@ and [here](https://easyengine.io/tutorials/nginx/block-wp-login-php-bruteforce-a
```
# Rate limit all IP addresses
http {
limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s;
limit_req_zone $binary_remote_addr zone=one:10m rate=45r/m;
}
# Alternatively, whitelist certain IP addresses
@@ -895,7 +922,7 @@ and [here](https://easyengine.io/tutorials/nginx/block-wp-login-php-bruteforce-a
1 $binary_remote_addr;
0 "";
}
limit_req_zone $limitkey zone=one:10m rate=1r/s;
limit_req_zone $limitkey zone=one:10m rate=45r/m;
}
```
@@ -924,7 +951,7 @@ and [here](https://easyengine.io/tutorials/nginx/block-wp-login-php-bruteforce-a
action = iptables-multiport[name=ReqLimit, port="http,https", protocol=tcp]
logpath = /var/log/nginx/error.log
findtime = 600
bantime = 7200
bantime = 14400
maxretry = 10
```
@@ -971,6 +998,8 @@ variable before running the `ntfy` command (e.g. `export NTFY_LISTEN_HTTP=:80`).
| `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-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. |
@@ -1035,6 +1064,8 @@ OPTIONS:
--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]
--cache-duration since, --cache_duration since, -b since buffer messages for this time to allow since requests (default: 12h0m0s) [$NTFY_CACHE_DURATION]
--cache-file value, --cache_file value, -C value cache file used for message caching [$NTFY_CACHE_FILE]
--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-startup-queries value, --cache_startup_queries value queries run when the cache database is initialized [$NTFY_CACHE_STARTUP_QUERIES]
--cert-file value, --cert_file value, -E value certificate file, if listen-https is set [$NTFY_CERT_FILE]
--config value, -c value config file (default: /etc/ntfy/server.yml) [$NTFY_CONFIG_FILE]

View File

@@ -4,11 +4,14 @@ This page is used to list deprecation notices for ntfy. Deprecated commands and
before the behavior is changed depends on the severity of the change, and how prominent the feature is.
## Active deprecations
_No active deprecations_
## Previous deprecations
### ntfy CLI: `ntfy publish --env-topic` will be removed
> Active since 2022-06-20, behavior will change end of **July 2022**
> Active since 2022-06-20, behavior changed with v1.30.1
The `ntfy publish --env-topic` option will be removed. It'll still be possible to specify a topic via the
The `ntfy publish --env-topic` option will be removed. It'll still be possible to specify a topic via the
`NTFY_TOPIC` environment variable, but it won't be necessary anymore to specify the `--env-topic` flag.
=== "Before"
@@ -21,8 +24,6 @@ The `ntfy publish --env-topic` option will be removed. It'll still be possible t
$ NTFY_TOPIC=mytopic ntfy publish "this is the message"
```
## Previous deprecations
### <del>Android app: WebSockets will become the default connection protocol</del>
> Active since 2022-03-13, behavior will not change (deprecation removed 2022-06-20)

View File

@@ -43,6 +43,13 @@ Build related:
The `web/` and `docs/` folder are the sources for web app and documentation. During the build process,
the generated output is copied to `server/site` (web app and landing page) and `server/docs` (documentation).
### Build/test on Gitpod
To get a quick working development environment you can use [Gitpod](https://gitpod.io), an in-browser IDE
that makes it easy to develop ntfy without having to set up a desktop IDE. For any real development,
I do suggest a proper IDE like [IntelliJ IDEA](https://www.jetbrains.com/idea/).
[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/binwiederhier/ntfy)
### Build requirements
* [Go](https://go.dev/) (required for main server)

View File

@@ -122,6 +122,19 @@ to ntfy at its default URL (`attrs` and other attributes are optional):
priority: 1
```
## GitHub Actions
You can send a message during a workflow run with curl. Here is an example sending info about the repo, commit and job status.
``` yaml
- name: Actions Ntfy
run: |
curl \
-u ${{ secrets.NTFY_CRED }} \
-H "Title: Title here" \
-H "Content-Type: text/plain" \
-d $'Repo: ${{ github.repository }}\nCommit: ${{ github.sha }}\nRef: ${{ github.ref }}\nStatus: ${{ job.status}}' \
${{ secrets.NTFY_URL }}
```
## Watchtower (shoutrrr)
You can use [shoutrrr](https://github.com/containrrr/shoutrrr) generic webhook support to send
[Watchtower](https://github.com/containrrr/watchtower/) notifications to your ntfy topic.

View File

@@ -4,11 +4,20 @@
Who knows. I didn't do a lot of research before making this. It was fun making it.
## Can I use this in my app? Will it stay free?
Yes. As long as you don't abuse it, it'll be available and free of charge. I do not plan on monetizing
the service.
Yes. As long as you don't abuse it, it'll be available and free of charge. While I will always allow usage of the ntfy.sh
server without signup and free of charge, I may also offer paid plans in the future.
## What are the uptime guarantees?
Best effort.
Best effort.
ntfy currently runs on a single DigitalOcean droplet, without any scale out strategy or redundancies. When the time comes,
I'll add scale out features, but for now it is what it is.
In the first year of its life, and to this day (Dec'22), ntfy had **no outages** that I can remember. Other than short
blips and some HTTP 500 spikes, it has been rock solid.
There is a [status page](https://ntfy.statuspage.io/) which is updated based on some automated checks via the amazingly
awesome [healthchecks.io](https://healthchecks.io/) (_no affiliation, just a fan_).
## What happens if there are multiple subscribers to the same topic?
As per usual with pub-sub, all subscribers receive notifications if they are subscribed to a topic.
@@ -43,6 +52,15 @@ decent now.
server and listens for incoming notifications. This consumes additional battery (see above),
but delivers notifications instantly.
## Can you implement feature X?
Yes, maybe. Check out [existing GitHub issues](https://github.com/binwiederhier/ntfy/issues) to see if somebody else had
the same idea before you, or file a new issue. I'll likely get back to you within a few days.
## I'm having issues with iOS, can you help? The iOS app is behind compared to the Android app, can you fix that?
The iOS is very bare bones and quite frankly a little buggy. I wanted to get something out the door to make the iOS users
happy, but halfway through I got frustrated with iOS development and paused development. I will eventually get back to
it, or hopefully, somebody else will come along and help out. Please review the [known issues](known-issues.md) for details.
## Can I disable the web app? Can I protect it with a login screen?
The web app is a static website without a backend (other than the ntfy API). All data is stored locally in the browser
cache and local storage. That means it does not need to be protected with a login screen, and it poses no additional

View File

@@ -26,37 +26,37 @@ deb/rpm packages.
=== "x86_64/amd64"
```bash
wget https://github.com/binwiederhier/ntfy/releases/download/v1.29.0/ntfy_1.29.0_linux_x86_64.tar.gz
tar zxvf ntfy_1.29.0_linux_x86_64.tar.gz
sudo cp -a ntfy_1.29.0_linux_x86_64/ntfy /usr/bin/ntfy
sudo mkdir /etc/ntfy && sudo cp ntfy_1.29.0_linux_x86_64/{client,server}/*.yml /etc/ntfy
wget https://github.com/binwiederhier/ntfy/releases/download/v1.30.1/ntfy_1.30.1_linux_x86_64.tar.gz
tar zxvf ntfy_1.30.1_linux_x86_64.tar.gz
sudo cp -a ntfy_1.30.1_linux_x86_64/ntfy /usr/bin/ntfy
sudo mkdir /etc/ntfy && sudo cp ntfy_1.30.1_linux_x86_64/{client,server}/*.yml /etc/ntfy
sudo ntfy serve
```
=== "armv6"
```bash
wget https://github.com/binwiederhier/ntfy/releases/download/v1.29.0/ntfy_1.29.0_linux_armv6.tar.gz
tar zxvf ntfy_1.29.0_linux_armv6.tar.gz
sudo cp -a ntfy_1.29.0_linux_armv6/ntfy /usr/bin/ntfy
sudo mkdir /etc/ntfy && sudo cp ntfy_1.29.0_linux_armv6/{client,server}/*.yml /etc/ntfy
wget https://github.com/binwiederhier/ntfy/releases/download/v1.30.1/ntfy_1.30.1_linux_armv6.tar.gz
tar zxvf ntfy_1.30.1_linux_armv6.tar.gz
sudo cp -a ntfy_1.30.1_linux_armv6/ntfy /usr/bin/ntfy
sudo mkdir /etc/ntfy && sudo cp ntfy_1.30.1_linux_armv6/{client,server}/*.yml /etc/ntfy
sudo ntfy serve
```
=== "armv7/armhf"
```bash
wget https://github.com/binwiederhier/ntfy/releases/download/v1.29.0/ntfy_1.29.0_linux_armv7.tar.gz
tar zxvf ntfy_1.29.0_linux_armv7.tar.gz
sudo cp -a ntfy_1.29.0_linux_armv7/ntfy /usr/bin/ntfy
sudo mkdir /etc/ntfy && sudo cp ntfy_1.29.0_linux_armv7/{client,server}/*.yml /etc/ntfy
wget https://github.com/binwiederhier/ntfy/releases/download/v1.30.1/ntfy_1.30.1_linux_armv7.tar.gz
tar zxvf ntfy_1.30.1_linux_armv7.tar.gz
sudo cp -a ntfy_1.30.1_linux_armv7/ntfy /usr/bin/ntfy
sudo mkdir /etc/ntfy && sudo cp ntfy_1.30.1_linux_armv7/{client,server}/*.yml /etc/ntfy
sudo ntfy serve
```
=== "arm64"
```bash
wget https://github.com/binwiederhier/ntfy/releases/download/v1.29.0/ntfy_1.29.0_linux_arm64.tar.gz
tar zxvf ntfy_1.29.0_linux_arm64.tar.gz
sudo cp -a ntfy_1.29.0_linux_arm64/ntfy /usr/bin/ntfy
sudo mkdir /etc/ntfy && sudo cp ntfy_1.29.0_linux_arm64/{client,server}/*.yml /etc/ntfy
wget https://github.com/binwiederhier/ntfy/releases/download/v1.30.1/ntfy_1.30.1_linux_arm64.tar.gz
tar zxvf ntfy_1.30.1_linux_arm64.tar.gz
sudo cp -a ntfy_1.30.1_linux_arm64/ntfy /usr/bin/ntfy
sudo mkdir /etc/ntfy && sudo cp ntfy_1.30.1_linux_arm64/{client,server}/*.yml /etc/ntfy
sudo ntfy serve
```
@@ -106,7 +106,7 @@ Manually installing the .deb file:
=== "x86_64/amd64"
```bash
wget https://github.com/binwiederhier/ntfy/releases/download/v1.29.0/ntfy_1.29.0_linux_amd64.deb
wget https://github.com/binwiederhier/ntfy/releases/download/v1.30.1/ntfy_1.30.1_linux_amd64.deb
sudo dpkg -i ntfy_*.deb
sudo systemctl enable ntfy
sudo systemctl start ntfy
@@ -114,7 +114,7 @@ Manually installing the .deb file:
=== "armv6"
```bash
wget https://github.com/binwiederhier/ntfy/releases/download/v1.29.0/ntfy_1.29.0_linux_armv6.deb
wget https://github.com/binwiederhier/ntfy/releases/download/v1.30.1/ntfy_1.30.1_linux_armv6.deb
sudo dpkg -i ntfy_*.deb
sudo systemctl enable ntfy
sudo systemctl start ntfy
@@ -122,7 +122,7 @@ Manually installing the .deb file:
=== "armv7/armhf"
```bash
wget https://github.com/binwiederhier/ntfy/releases/download/v1.29.0/ntfy_1.29.0_linux_armv7.deb
wget https://github.com/binwiederhier/ntfy/releases/download/v1.30.1/ntfy_1.30.1_linux_armv7.deb
sudo dpkg -i ntfy_*.deb
sudo systemctl enable ntfy
sudo systemctl start ntfy
@@ -130,7 +130,7 @@ Manually installing the .deb file:
=== "arm64"
```bash
wget https://github.com/binwiederhier/ntfy/releases/download/v1.29.0/ntfy_1.29.0_linux_arm64.deb
wget https://github.com/binwiederhier/ntfy/releases/download/v1.30.1/ntfy_1.30.1_linux_arm64.deb
sudo dpkg -i ntfy_*.deb
sudo systemctl enable ntfy
sudo systemctl start ntfy
@@ -140,28 +140,28 @@ Manually installing the .deb file:
=== "x86_64/amd64"
```bash
sudo rpm -ivh https://github.com/binwiederhier/ntfy/releases/download/v1.29.0/ntfy_1.29.0_linux_amd64.rpm
sudo rpm -ivh https://github.com/binwiederhier/ntfy/releases/download/v1.30.1/ntfy_1.30.1_linux_amd64.rpm
sudo systemctl enable ntfy
sudo systemctl start ntfy
```
=== "armv6"
```bash
sudo rpm -ivh https://github.com/binwiederhier/ntfy/releases/download/v1.29.0/ntfy_1.29.0_linux_armv6.rpm
sudo rpm -ivh https://github.com/binwiederhier/ntfy/releases/download/v1.30.1/ntfy_1.30.1_linux_armv6.rpm
sudo systemctl enable ntfy
sudo systemctl start ntfy
```
=== "armv7/armhf"
```bash
sudo rpm -ivh https://github.com/binwiederhier/ntfy/releases/download/v1.29.0/ntfy_1.29.0_linux_armv7.rpm
sudo rpm -ivh https://github.com/binwiederhier/ntfy/releases/download/v1.30.1/ntfy_1.30.1_linux_armv7.rpm
sudo systemctl enable ntfy
sudo systemctl start ntfy
```
=== "arm64"
```bash
sudo rpm -ivh https://github.com/binwiederhier/ntfy/releases/download/v1.29.0/ntfy_1.29.0_linux_arm64.rpm
sudo rpm -ivh https://github.com/binwiederhier/ntfy/releases/download/v1.30.1/ntfy_1.30.1_linux_arm64.rpm
sudo systemctl enable ntfy
sudo systemctl start ntfy
```
@@ -189,18 +189,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/v1.29.0/ntfy_1.29.0_macOS_all.tar.gz),
To install, please [download the tarball](https://github.com/binwiederhier/ntfy/releases/download/v1.30.1/ntfy_1.30.1_macOS_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/v1.29.0/ntfy_1.29.0_macOS_all.tar.gz > ntfy_1.29.0_macOS_all.tar.gz
tar zxvf ntfy_1.29.0_macOS_all.tar.gz
sudo cp -a ntfy_1.29.0_macOS_all/ntfy /usr/local/bin/ntfy
curl -L https://github.com/binwiederhier/ntfy/releases/download/v1.30.1/ntfy_1.30.1_macOS_all.tar.gz > ntfy_1.30.1_macOS_all.tar.gz
tar zxvf ntfy_1.30.1_macOS_all.tar.gz
sudo cp -a ntfy_1.30.1_macOS_all/ntfy /usr/local/bin/ntfy
mkdir ~/Library/Application\ Support/ntfy
cp ntfy_1.29.0_macOS_all/client/client.yml ~/Library/Application\ Support/ntfy/client.yml
cp ntfy_1.30.1_macOS_all/client/client.yml ~/Library/Application\ Support/ntfy/client.yml
ntfy --help
```
@@ -212,7 +212,7 @@ ntfy --help
## 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/v1.29.0/ntfy_1.29.0_windows_x86_64.zip),
To install, please [download the latest ZIP](https://github.com/binwiederhier/ntfy/releases/download/v1.30.1/ntfy_1.30.1_windows_x86_64.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).
@@ -300,8 +300,8 @@ This image can be pushed to a container registry and shipped independently. All
## Kubernetes
The setup for Kubernetes is very similar to that for Docker, and requires a fairly minimal deployment or pod definition to function. There
are a few options to mix and match, including a deployment without a cache file, a stateful set with a persistant cache, and a standalone
unmanaged pod.
are a few options to mix and match, including a deployment without a cache file, a stateful set with a persistent cache, and a standalone
unmanned pod.
=== "deployment"
@@ -422,7 +422,7 @@ unmanaged pod.
name: ntfy
```
Configuration is relatively straightforward. As an exmaple, a minimal configuration is provided.
Configuration is relatively straightforward. As an example, a minimal configuration is provided.
=== "resource definition"
```yaml
@@ -431,7 +431,7 @@ Configuration is relatively straightforward. As an exmaple, a minimal configurat
metadata:
name: ntfy
data:
server.yml: |
server.yml: |
# Template: https://github.com/binwiederhier/ntfy/blob/main/server/server.yml
base-url: https://ntfy.sh
```
@@ -440,3 +440,154 @@ Configuration is relatively straightforward. As an exmaple, a minimal configurat
```bash
kubectl create configmap ntfy --from-file=server.yml
```
## Kustomize
ntfy can be deployed in a Kubernetes cluster with [Kustomize](https://github.com/kubernetes-sigs/kustomize), a tool used
to customize Kubernetes objects using a `kustomization.yaml` file.
1. Create new folder - `ntfy`
2. Add all files listed below
1. `kustomization.yaml` - stores all configmaps and resources used in a deployment
2. `ntfy-deployment.yaml` - define deployment type and its parameters
3. `ntfy-pvc.yaml` - describes how [persistent volumes](https://kubernetes.io/docs/concepts/storage/persistent-volumes/) will be created
4. `ntfy-svc.yaml` - expose application to the internal kubernetes network
5. `ntfy-ingress.yaml` - expose service to outside the network using [ingress controller](https://kubernetes.io/docs/concepts/services-networking/ingress-controllers/)
6. `server.yaml` - simple server configuration
3. Replace **TESTNAMESPACE** within `kustomization.yaml` with designated namespace
4. Replace **ntfy.test** within `ntfy-ingress.yaml` with desired DNS name
5. Apply configuration to cluster set in current context:
```bash
kubectl apply -k /ntfy
```
=== "kustomization.yaml"
```yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ntfy-deployment.yaml # deployment definition
- ntfy-svc.yaml # service connecting pods to cluster network
- ntfy-pvc.yaml # pvc used to store cache and attachment
- ntfy-ingress.yaml # ingress definition
configMapGenerator: # will parse config from raw config to configmap,it allows for dynamic reload of application if additional app is deployed ie https://github.com/stakater/Reloader
- name: server-config
files:
- server.yml
namespace: TESTNAMESPACE # select namespace for whole application
```
=== "ntfy-deployment.yaml"
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: ntfy-deployment
labels:
app: ntfy-deployment
spec:
revisionHistoryLimit: 1
replicas: 1
selector:
matchLabels:
app: ntfy-pod
template:
metadata:
labels:
app: ntfy-pod
spec:
containers:
- name: ntfy
image: binwiederhier/ntfy:v1.28.0 # set deployed version
args: ["serve"]
env: #example of adjustments made in environmental variables
- name: TZ # set timezone
value: XXXXXXX
- name: NTFY_DEBUG # enable/disable debug
value: "false"
- name: NTFY_LOG_LEVEL # adjust log level
value: INFO
- name: NTFY_BASE_URL # add base url
value: XXXXXXXXXX
ports:
- containerPort: 80
name: http-ntfy
resources:
limits:
memory: 300Mi
cpu: 200m
requests:
cpu: 150m
memory: 150Mi
volumeMounts:
- mountPath: /etc/ntfy/server.yml
subPath: server.yml
name: config-volume # generated vie configMapGenerator from kustomization file
- mountPath: /var/cache/ntfy
name: cache-volume #cache volume mounted to persistent volume
volumes:
- name: config-volume
configMap: # uses configmap generator to parse server.yml to configmap
name: server-config
- name: cache-volume
persistentVolumeClaim: # stores /cache/ntfy in defined pv
claimName: ntfy-pvc
```
=== "ntfy-pvc.yaml"
```yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: ntfy-pvc
spec:
accessModes:
- ReadWriteOnce
storageClassName: local-path # adjust storage if needed
resources:
requests:
storage: 1Gi
```
=== "ntfy-svc.yaml"
```yaml
apiVersion: v1
kind: Service
metadata:
name: ntfy-svc
spec:
type: ClusterIP
selector:
app: ntfy-pod
ports:
- name: http-ntfy-out
protocol: TCP
port: 80
targetPort: http-ntfy
```
=== "ntfy-ingress.yaml"
```yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ntfy-ingress
spec:
rules:
- host: ntfy.test #select own
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: ntfy-svc
port:
number: 80
```
=== "server.yml"
```yaml
cache-file: "/var/cache/ntfy/cache.db"
attachment-cache-dir: "/var/cache/ntfy/attachments"
```

View File

@@ -6,25 +6,31 @@ I've added a ⭐ to projects or posts that have a significant following, or had
## Public ntfy servers
| URL | Country |
|---------------------------------------------------|:---------:|
| [ntfy.sh](https://ntfy.sh/) (*Official*) | 🇺🇸 |
| [ntfy.tedomum.net](https://ntfy.tedomum.net/) | 🇫🇷 🇪🇺 |
| [ntfy.jae.fi](https://ntfy.jae.fi/) | 🇫🇮 🇪🇺 |
| [ntfy.adminforge.de](https://ntfy.adminforge.de/) | 🇩🇪 🇪🇺 |
Here's a list of public ntfy servers. As of right now, there is only one official server. The others are provided by the
ntfy community. Thanks to everyone running a public server. **You guys rock!**
Thanks to everyone running a public server. **You guys rock!** To the users: Be aware that server operators can log your
messages until I finally finish implementing end-to-end encryption.
| URL | Country |
|---------------------------------------------------|--------------------|
| [ntfy.sh](https://ntfy.sh/) (*Official*) | 🇺🇸 United States |
| [ntfy.tedomum.net](https://ntfy.tedomum.net/) | 🇫🇷 France |
| [ntfy.jae.fi](https://ntfy.jae.fi/) | 🇫🇮 Finland |
| [ntfy.adminforge.de](https://ntfy.adminforge.de/) | 🇩🇪 Germany |
| [ntfy.envs.net](https://ntfy.envs.net) | 🇩🇪 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**.
## Official integrations
- [Apprise](https://github.com/caronc/apprise/wiki/Notify_ntfy) ⭐ - Push Notifications that work with just about every platform
- [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
- [Robusta](https://docs.robusta.dev/master/catalog/sinks/webhook.html) ⭐ - open source platform for Kubernetes troubleshooting
- [borgmatic](https://torsion.org/borgmatic/docs/how-to/monitor-your-backups/#third-party-monitoring-services) ⭐ - configuration-driven backup software for servers and workstations
- [Radarr](https://radarr.video/) ⭐ - Movie collection manager for Usenet and BitTorrent users
- [Sonarr](https://sonarr.tv/) ⭐ - PVR for Usenet and BitTorrent users
- [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
- [Platypush](https://docs.platypush.tech/platypush/plugins/ntfy.html) - Automation platform aimed to run on any device that can run Python
@@ -47,11 +53,12 @@ messages until I finally finish implementing end-to-end encryption.
- [ntfy-middleman](https://github.com/nachotp/ntfy-middleman) - Wraps APIs and send notifications using ntfy.sh on schedule (Python)
- [ntfy-dotnet](https://github.com/nwithan8/ntfy-dotnet) - .NET client library to interact with a ntfy server (C# / .NET)
- [node-ntfy-publish](https://github.com/cityssm/node-ntfy-publish) - A Node package to publish notifications to an ntfy server (Node)
- [ntfy](https://github.com/jonocarroll/ntfy) - Wraps the ntfy API with pipe-friendly tooling (R)
## CLIs + GUIs
- [ntfy.sh.sh](https://github.com/mininmobile/ntfy.sh.sh) - Run scripts on ntfy.sh events
- [ntfy Desktop client](https://github.com/mininmobile/ntfy-desktop) - Cross-platform desktop application for ntfy
- [ntfy Desktop client](https://codeberg.org/zvava/ntfy-desktop) - Cross-platform desktop application for ntfy
- [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
@@ -89,38 +96,51 @@ messages until I finally finish implementing end-to-end encryption.
- [ansible-ntfy](https://github.com/jpmens/ansible-ntfy) - Ansible action plugin to post JSON messages to ntfy (Python)
- [ntfy-notification-channel](https://github.com/wijourdil/ntfy-notification-channel) - Laravel Notification channel for ntfy (PHP)
- [ntfy_on_a_chip](https://github.com/gergepalfi/ntfy_on_a_chip) - ESP8266 and ESP32 client code to communicate with ntfy
- [ntfy-sdk](https://gitlab.com/p2kishimoto/ntfy-sdk) - ntfy client library to send notifications (Rust)
- [ntfy-sdk](https://github.com/yukibtc/ntfy-sdk) - ntfy client library to send notifications (Rust)
- [ntfy_ynh](https://github.com/YunoHost-Apps/ntfy_ynh) - ntfy app for YunoHost
- [drone-ntfy](https://github.com/Clortox/drone-ntfy) - Drone.io plugin for sending ntfy notifications from a pipeline
## Blog + forum posts
- [TLDR Newsletter Daily Update 2022-11-09](https://tldr.tech/tech/newsletter/2022-11-09) - 11/2022
- [Ntfy.sh Send push notifications to your phone via PUT/POST](https://news.ycombinator.com/item?id=33517944) ⭐ - 11/2022
- [Ntfy et Jeedom : un plugin](https://lunarok-domotique.com/2022/11/ntfy-et-jeedom/) - 11/2022
- [Crea tu propio servidor de notificaciones con Ntfy](https://blog.parravidales.es/crea-tu-propio-servidor-de-notificaciones-con-ntfy/) - 11/2022
- [Zero-cost push notifications to your phone or desktop via PUT/POST ](https://lobste.rs/s/41dq13/zero_cost_push_notifications_your_phone) - 10/2022
- [A nifty push notification system: ntfy](https://jpmens.net/2022/10/30/a-nifty-push-notification-system-ntfy/) - 10/2022
- [Alarmanlage der dritten Art (YouTube video)](https://www.youtube.com/watch?v=altb5QLHbaU&feature=youtu.be) - 10/2022
- [Neue Services: Ntfy, TikTok und RustDesk](https://adminforge.de/tools/neue-services-ntfy-tiktok-und-rustdesk/) - 9/2022
- [Ntfy, le service de notifications quil vous faut](https://www.cachem.fr/ntfy-le-service-de-notifications-quil-vous-faut/) - 9/2022
- [NAS Synology et notifications avec ntfy](https://www.cachem.fr/synology-notifications-ntfy/) - 9/2022
- [Self hosted Mobile Push Notifications using NTFY | Thejesh GN](https://thejeshgn.com/2022/08/23/self-hosted-mobile-push-notifications-using-ntfy/) - 8/2022
- [Fedora Magazine | 4 cool new projects to try in Copr](https://fedoramagazine.org/4-cool-new-projects-to-try-in-copr-for-august-2022/) - 8/2022
- [Docker로 오픈소스 푸시알람 프로젝트 ntfy.sh 설치 및 사용하기.(Feat. Uptimekuma)](https://svrforum.com/svr/398979) - 8/2022
- [Easy notifications from R](https://sometimesir.com/posts/easy-notifications-from-r/) - 6/2022
- [ntfy is finally coming to iOS, and Matrix/UnifiedPush gateway support](https://www.reddit.com/r/selfhosted/comments/vdzvxi/ntfy_is_finally_coming_to_ios_with_full/) ⭐ - 6/2022
- [无需注册的通知服务ntfy](https://wbsu2003.4everland.app/2022/05/30/%E6%97%A0%E9%9C%80%E6%B3%A8%E5%86%8C%E7%9A%84%E9%80%9A%E7%9F%A5%E6%9C%8D%E5%8A%A1ntfy/) - 5/2022
- [Install guide (with Docker)](https://chowdera.com/2022/150/202205301257379077.html) - 5/2022
- [无需注册的通知服务ntfy](https://blog.csdn.net/wbsu2004/article/details/125040247) - 5/2022
- [Updated review post (Jan-Lukas Else)](https://jlelse.blog/thoughts/2022/04/ntfy) - 4/2022
- [Reddit feature update post](https://www.reddit.com/r/selfhosted/comments/uetlso/ntfy_is_a_tool_to_send_push_notifications_to_your/) ⭐ - 4/2022
- [無料で簡単に通知の送受信ができつつオープンソースでセルフホストも可能な「ntfy」を使ってみた (Gigazine)](https://gigazine.net/news/20220404-ntfy-push-notification/) - 4/2022
- [Pocketmags ntfy review](https://pocketmags.com/us/linux-format-magazine/march-2022/articles/1104187/ntfy) - 3/2022
- [Reddit web app release post](https://www.reddit.com/r/selfhosted/comments/tc0p0u/say_hello_to_the_brand_new_ntfysh_web_app_push/) ⭐ - 3/2022
- [Lemmy post (Jakob)](https://lemmy.eus/post/15541) - 1/2022
- [Reddit UnifiedPush release post](https://www.reddit.com/r/selfhosted/comments/s5jylf/my_open_source_notification_android_app_and/) ⭐ - 1/2022
- [ntfy: send notifications from your computer to your phone](https://rs1.es/tutorials/2022/01/19/ntfy-send-notifications-phone.html) - 1/2022
- [Short ntfy review (Jan-Lukas Else)](https://jlelse.blog/links/2021/12/ntfy-sh) - 12/2021
- [Free MacroDroid webhook alternative (FrameXX)](https://www.macrodroidforum.com/index.php?threads/ntfy-sh-free-macrodroid-webhook-alternative.1505/) - 12/2021
- [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) - 11/2021
- [Show HN: A tool to send push notifications to your phone, written in Go](https://news.ycombinator.com/item?id=29715464) ⭐ - 12/2021
- [Reddit selfhostable post](https://www.reddit.com/r/selfhosted/comments/qxlsm9/my_open_source_notification_android_app_and/) ⭐ - 11/2021
- [ntfy setup instructions](https://docs.benjamin-altpeter.de/network/vms/1001029-ntfy/) - benjamin-altpeter.de - 12/2022
- [Ntfy Self-Hosted Push Notifications](https://lachlanlife.net/posts/2022-12-ntfy/) - lachlanlife.net - 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
- [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
- [MeshCentral - Ntfy Push Notifications ](https://www.youtube.com/watch?v=wyE4rtUd4Bg) - youtube.com - 11/2022
- [Changelog | Tracking layoffs, tech worker demand still high, ntfy, ...](https://changelog.com/news/tracking-layoffs-tech-worker-demand-still-high-ntfy-devenv-markdoc-mike-bifulco-Y1jW) ⭐ - changelog.com - 11/2022
- [Pointer | Issue #367](https://www.pointer.io/archives/a9495a2a6f/) - pointer.io - 11/2022
- [Envie Push Notifications por POST (de graça e sem cadastro)](https://www.tabnews.com.br/filipedeschamps/envie-push-notifications-por-post-de-graca-e-sem-cadastro) - tabnews.com.br - 11/2022
- [Push Notifications for KDE](https://volkerkrause.eu/2022/11/12/kde-unifiedpush-push-notifications.html) - volkerkrause.eu - 11/2022
- [TLDR Newsletter Daily Update 2022-11-09](https://tldr.tech/tech/newsletter/2022-11-09) ⭐ - tldr.tech - 11/2022
- [Ntfy.sh Send push notifications to your phone via PUT/POST](https://news.ycombinator.com/item?id=33517944) ⭐ - news.ycombinator.com - 11/2022
- [Ntfy et Jeedom : un plugin](https://lunarok-domotique.com/2022/11/ntfy-et-jeedom/) - lunarok-domotique.com - 11/2022
- [Crea tu propio servidor de notificaciones con Ntfy](https://blog.parravidales.es/crea-tu-propio-servidor-de-notificaciones-con-ntfy/) - blog.parravidales.es - 11/2022
- [Zero-cost push notifications to your phone or desktop via PUT/POST ](https://lobste.rs/s/41dq13/zero_cost_push_notifications_your_phone) - lobste.rs - 10/2022
- [A nifty push notification system: ntfy](https://jpmens.net/2022/10/30/a-nifty-push-notification-system-ntfy/) - jpmens.net - 10/2022
- [Alarmanlage der dritten Art (YouTube video)](https://www.youtube.com/watch?v=altb5QLHbaU&feature=youtu.be) - youtube.com - 10/2022
- [Neue Services: Ntfy, TikTok und RustDesk](https://adminforge.de/tools/neue-services-ntfy-tiktok-und-rustdesk/) - adminforge.de - 9/2022
- [Ntfy, le service de notifications quil vous faut](https://www.cachem.fr/ntfy-le-service-de-notifications-quil-vous-faut/) - cachem.fr - 9/2022
- [NAS Synology et notifications avec ntfy](https://www.cachem.fr/synology-notifications-ntfy/) - cachem.fr - 9/2022
- [Self hosted Mobile Push Notifications using NTFY | Thejesh GN](https://thejeshgn.com/2022/08/23/self-hosted-mobile-push-notifications-using-ntfy/) - thejeshgn.com - 8/2022
- [Fedora Magazine | 4 cool new projects to try in Copr](https://fedoramagazine.org/4-cool-new-projects-to-try-in-copr-for-august-2022/) - fedoramagazine.org - 8/2022
- [Docker로 오픈소스 푸시알람 프로젝트 ntfy.sh 설치 및 사용하기.(Feat. Uptimekuma)](https://svrforum.com/svr/398979) - svrforum.com - 8/2022
- [Easy notifications from R](https://sometimesir.com/posts/easy-notifications-from-r/) - sometimesir.com - 6/2022
- [ntfy is finally coming to iOS, and Matrix/UnifiedPush gateway support](https://www.reddit.com/r/selfhosted/comments/vdzvxi/ntfy_is_finally_coming_to_ios_with_full/) ⭐ - reddit.com - 6/2022
- [Install guide (with Docker)](https://chowdera.com/2022/150/202205301257379077.html) - chowdera.com - 5/2022
- [无需注册的通知服务ntfy](https://blog.csdn.net/wbsu2004/article/details/125040247) - blog.csdn.net - 5/2022
- [Updated review post (Jan-Lukas Else)](https://jlelse.blog/thoughts/2022/04/ntfy) - jlelse.blog - 4/2022
- [Using ntfy and Tasker together](https://lachlanlife.net/posts/2022-04-tasker-ntfy/) - lachlanlife.net - 4/2022
- [Reddit feature update post](https://www.reddit.com/r/selfhosted/comments/uetlso/ntfy_is_a_tool_to_send_push_notifications_to_your/) ⭐ - reddit.com - 4/2022
- [無料で簡単に通知の送受信ができつつオープンソースでセルフホストも可能な「ntfy」を使ってみた](https://gigazine.net/news/20220404-ntfy-push-notification/) - gigazine.net - 4/2022
- [Pocketmags ntfy review](https://pocketmags.com/us/linux-format-magazine/march-2022/articles/1104187/ntfy) - pocketmags.com - 3/2022
- [Reddit web app release post](https://www.reddit.com/r/selfhosted/comments/tc0p0u/say_hello_to_the_brand_new_ntfysh_web_app_push/) ⭐ - reddit.com- 3/2022
- [Lemmy post (Jakob)](https://lemmy.eus/post/15541) - lemmy.eus - 1/2022
- [Reddit UnifiedPush release post](https://www.reddit.com/r/selfhosted/comments/s5jylf/my_open_source_notification_android_app_and/) ⭐ - reddit.com - 1/2022
- [ntfy: send notifications from your computer to your phone](https://rs1.es/tutorials/2022/01/19/ntfy-send-notifications-phone.html) - rs1.es - 1/2022
- [Short ntfy review (Jan-Lukas Else)](https://jlelse.blog/links/2021/12/ntfy-sh) - jlelse.blog - 12/2021
- [Free MacroDroid webhook alternative (FrameXX)](https://www.macrodroidforum.com/index.php?threads/ntfy-sh-free-macrodroid-webhook-alternative.1505/) - macrodroidforum.com - 12/2021
- [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

28
docs/known-issues.md Normal file
View File

@@ -0,0 +1,28 @@
# Known issues
This is an incomplete list of known issues with the ntfy server, 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.
## iOS app not refreshing (see [#267](https://github.com/binwiederhier/ntfy/issues/267))
For some (many?) users, the iOS app is not refreshing the view when new notifications come in. Until you manually
swipe down, you do not see the newly arrived messages, even though the popup appeared before.
This is caused by some weirdness between the Notification Service Extension (NSE), SwiftUI and Core Data. I am entirely
clueless on how to fix it, sadly, as it is ephemeral and now clear to me what is causing it.
Please send experienced iOS developers my way to help me figure this out.
## iOS app not receiving notifications (anymore)
If notifications do not show up at all anymore, there are a few causes for it (that I know of):
**Firebase+APNS are being weird and buggy**:
If this is the case, usually it helps to **remove the topic/subscription and re-add it**. That will force Firebase to
re-subscribe to the Firebase topic.
**Self-hosted only: No `upstream-base-url` set, or `base-url` mismatch**:
To make self-hosted servers work with the iOS
app, I had to do some horrible things (see [iOS instant notifications](config.md#ios-instant-notifications) for details).
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

View File

@@ -1316,7 +1316,7 @@ Here's an example using the [`X-Actions` header](#using-a-header):
=== "Command line (curl)"
```
curl \
-d "Somebody retweetet your tweet." \
-d "Somebody retweeted your tweet." \
-H "Actions: view, Open Twitter, https://twitter.com/binwiederhier/status/1467633927951163392" \
ntfy.sh/myhome
```
@@ -1326,7 +1326,7 @@ Here's an example using the [`X-Actions` header](#using-a-header):
ntfy publish \
--actions="view, Open Twitter, https://twitter.com/binwiederhier/status/1467633927951163392" \
myhome \
"Somebody retweetet your tweet."
"Somebody retweeted your tweet."
```
=== "HTTP"
@@ -1335,14 +1335,14 @@ Here's an example using the [`X-Actions` header](#using-a-header):
Host: ntfy.sh
Actions: view, Open Twitter, https://twitter.com/binwiederhier/status/1467633927951163392
Somebody retweetet your tweet.
Somebody retweeted your tweet.
```
=== "JavaScript"
``` javascript
fetch('https://ntfy.sh/myhome', {
method: 'POST',
body: 'Somebody retweetet your tweet.',
body: 'Somebody retweeted your tweet.',
headers: {
'Actions': 'view, Open Twitter, https://twitter.com/binwiederhier/status/1467633927951163392'
}
@@ -1351,7 +1351,7 @@ Here's an example using the [`X-Actions` header](#using-a-header):
=== "Go"
``` go
req, _ := http.NewRequest("POST", "https://ntfy.sh/myhome", strings.NewReader("Somebody retweetet your tweet."))
req, _ := http.NewRequest("POST", "https://ntfy.sh/myhome", strings.NewReader("Somebody retweeted your tweet."))
req.Header.Set("Actions", "view, Open Twitter, https://twitter.com/binwiederhier/status/1467633927951163392")
http.DefaultClient.Do(req)
```
@@ -1360,14 +1360,14 @@ Here's an example using the [`X-Actions` header](#using-a-header):
``` powershell
$uri = "https://ntfy.sh/myhome"
$headers = @{ Actions="view, Open Twitter, https://twitter.com/binwiederhier/status/1467633927951163392" }
$body = "Somebody retweetet your tweet."
$body = "Somebody retweeted your tweet."
Invoke-RestMethod -Method 'Post' -Uri $uri -Headers $headers -Body $body -UseBasicParsing
```
=== "Python"
``` python
requests.post("https://ntfy.sh/myhome",
data="Somebody retweetet your tweet.",
data="Somebody retweeted your tweet.",
headers={ "Actions": "view, Open Twitter, https://twitter.com/binwiederhier/status/1467633927951163392" })
```
@@ -1379,7 +1379,7 @@ Here's an example using the [`X-Actions` header](#using-a-header):
'header' =>
"Content-Type: text/plain\r\n" .
"Actions: view, Open Twitter, https://twitter.com/binwiederhier/status/1467633927951163392",
'content' => 'Somebody retweetet your tweet.'
'content' => 'Somebody retweeted your tweet.'
]
]));
```
@@ -1391,7 +1391,7 @@ And the same example using [JSON publishing](#publish-as-json):
curl ntfy.sh \
-d '{
"topic": "myhome",
"message": "Somebody retweetet your tweet.",
"message": "Somebody retweeted your tweet.",
"actions": [
{
"action": "view",
@@ -1413,7 +1413,7 @@ And the same example using [JSON publishing](#publish-as-json):
}
]' \
myhome \
"Somebody retweetet your tweet."
"Somebody retweeted your tweet."
```
=== "HTTP"
@@ -1423,7 +1423,7 @@ And the same example using [JSON publishing](#publish-as-json):
{
"topic": "myhome",
"message": "Somebody retweetet your tweet.",
"message": "Somebody retweeted your tweet.",
"actions": [
{
"action": "view",
@@ -1440,7 +1440,7 @@ And the same example using [JSON publishing](#publish-as-json):
method: 'POST',
body: JSON.stringify({
topic: "myhome",
message": "Somebody retweetet your tweet.",
message": "Somebody retweeted your tweet.",
actions: [
{
action: "view",
@@ -1459,7 +1459,7 @@ And the same example using [JSON publishing](#publish-as-json):
body := `{
"topic": "myhome",
"message": "Somebody retweetet your tweet.",
"message": "Somebody retweeted your tweet.",
"actions": [
{
"action": "view",
@@ -1477,7 +1477,7 @@ And the same example using [JSON publishing](#publish-as-json):
$uri = "https://ntfy.sh"
$body = @{
topic = "myhome"
message = "Somebody retweetet your tweet."
message = "Somebody retweeted your tweet."
actions = @(
@{
"action"="view"
@@ -1494,7 +1494,7 @@ And the same example using [JSON publishing](#publish-as-json):
requests.post("https://ntfy.sh/",
data=json.dumps({
"topic": "myhome",
"message": "Somebody retweetet your tweet.",
"message": "Somebody retweeted your tweet.",
"actions": [
{
"action": "view",
@@ -1514,7 +1514,7 @@ And the same example using [JSON publishing](#publish-as-json):
'header' => "Content-Type: application/json",
'content' => json_encode([
"topic": "myhome",
"message": "Somebody retweetet your tweet.",
"message": "Somebody retweeted your tweet.",
"actions": [
[
"action": "view",

View File

@@ -2,13 +2,80 @@
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 Android app v1.14.0 (UNRELEASED)
## ntfy server v1.30.1
Released December 23, 2022 🎅
**Features:**
* Web: Generate random topic name button ([#453](https://github.com/binwiederhier/ntfy/issues/453), thanks to [@yardenshoham](https://github.com/yardenshoham))
* Add [Gitpod config](https://github.com/binwiederhier/ntfy/blob/main/.gitpod.yml) ([#540](https://github.com/binwiederhier/ntfy/pull/540), thanks to [@yardenshoham](https://github.com/yardenshoham))
**Bug fixes + maintenance:**
* Remove `--env-topic` option from `ntfy publish` as per [deprecation](deprecations.md) (no ticket)
* Prepared statements for message cache writes ([#542](https://github.com/binwiederhier/ntfy/pull/542), thanks to [@nicois](https://github.com/nicois))
* Do not warn about invalid IP address when behind proxy in unix socket mode (relates to [#552](https://github.com/binwiederhier/ntfy/issues/552))
## ntfy Android app v1.16.0
Released December 11, 2022
This is a feature and platform/dependency upgrade release. You can now have per-subscription notification settings
(including sounds, DND, etc.), and you can make notifications continue ringing until they are dismissed. There's also
support for thematic/adaptive launcher icon for Android 13.
There are a few more Android 13 specific things, as well as many bug fixes: No more crashes from large images, no more
opening the wrong subscription, and we also fixed the icon color issue.
**Features:**
* Custom per-subscription notification settings incl. sounds, DND, etc. ([#6](https://github.com/binwiederhier/ntfy/issues/6), thanks to [@doits](https://github.com/doits))
* Insistent notifications that ring until dismissed ([#417](https://github.com/binwiederhier/ntfy/issues/417), thanks to [@danmed](https://github.com/danmed) for reporting)
* Add thematic/adaptive launcher icon ([#513](https://github.com/binwiederhier/ntfy/issues/513), thanks to [@daedric7](https://github.com/daedric7) for reporting)
**Bug fixes + maintenance:**
* Upgrade Android dependencies and build toolchain to SDK 33 (no ticket)
* Simplify F-Droid build: Disable tasks for Google Services ([#516](https://github.com/binwiederhier/ntfy/issues/516), thanks to [@markosopcic](https://github.com/markosopcic))
* Android 13: Ask for permission to post notifications ([#508](https://github.com/binwiederhier/ntfy/issues/508))
* Android 13: Do not allow swiping away the foreground notification ([#521](https://github.com/binwiederhier/ntfy/issues/521), thanks to [@alexhorner](https://github.com/alexhorner) for reporting)
* Android 5 (SDK 21): Fix crash on unsubscribing ([#528](https://github.com/binwiederhier/ntfy/issues/528), thanks to Roger M.)
* Remove timestamp when copying message text ([#471](https://github.com/binwiederhier/ntfy/issues/471), thanks to [@wunter8](https://github.com/wunter8))
* Fix auto-delete if some icons do not exist anymore ([#506](https://github.com/binwiederhier/ntfy/issues/506))
* Fix notification icon color ([#480](https://github.com/binwiederhier/ntfy/issues/480), thanks to [@s-h-a-r-d](https://github.com/s-h-a-r-d) for reporting)
* Fix topics do not re-subscribe to Firebase after restoring from backup ([#511](https://github.com/binwiederhier/ntfy/issues/511))
* Fix crashes from large images ([#474](https://github.com/binwiederhier/ntfy/issues/474), thanks to [@daedric7](https://github.com/daedric7) for reporting)
* Fix notification click opens wrong subscription ([#261](https://github.com/binwiederhier/ntfy/issues/261), thanks to [@SMAW](https://github.com/SMAW) for reporting)
* Fix Firebase-only "link expired" issue ([#529](https://github.com/binwiederhier/ntfy/issues/529))
* Remove "Install .apk" feature in Google Play variant due to policy change ([#531](https://github.com/binwiederhier/ntfy/issues/531))
* Add donate button (no ticket)
**Additional translations:**
* Korean (thanks to [@YJSofta0f97461d82447ac](https://hosted.weblate.org/user/YJSofta0f97461d82447ac/))
-->
* Portuguese (thanks to [@victormagalhaess](https://hosted.weblate.org/user/victormagalhaess/))
## ntfy server v1.29.1
Released November 17, 2022
This is mostly a bugfix release to address the high load on ntfy.sh. There are now two new options that allow
synchronous batch-writing of messages to the cache. This avoids database locking, and subsequent pileups of waiting
requests.
**Bug fixes:**
* High-load servers: Allow asynchronous batch-writing of messages to cache via `cache-batch-*` options ([#498](https://github.com/binwiederhier/ntfy/issues/498)/[#502](https://github.com/binwiederhier/ntfy/pull/502))
* Sender column in cache.db shows invalid IP ([#503](https://github.com/binwiederhier/ntfy/issues/503))
**Documentation:**
* GitHub Actions example ([#492](https://github.com/binwiederhier/ntfy/pull/492), thanks to [@ksurl](https://github.com/ksurl))
* UnifiedPush ACL clarification ([#497](https://github.com/binwiederhier/ntfy/issues/497), thanks to [@bt90](https://github.com/bt90))
* Install instructions for Kustomize ([#463](https://github.com/binwiederhier/ntfy/pull/463), thanks to [@l-maciej](https://github.com/l-maciej))
**Other things:**
* Put ntfy.sh docs on GitHub pages to reduce AWS outbound traffic cost ([#491](https://github.com/binwiederhier/ntfy/issues/491))
* The ntfy.sh server hardware was upgraded to a bigger box. If you'd like to help out carrying the server cost, **[sponsorships and donations](https://github.com/sponsors/binwiederhier)** 💸 would be very much appreciated
## ntfy server v1.29.0
Released November 12, 2022

42
go.mod
View File

@@ -3,23 +3,23 @@ module heckel.io/ntfy
go 1.18
require (
cloud.google.com/go/firestore v1.8.0 // indirect
cloud.google.com/go/storage v1.28.0 // indirect
cloud.google.com/go/firestore v1.9.0 // indirect
cloud.google.com/go/storage v1.28.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.15.0
github.com/gabriel-vasile/mimetype v1.4.1
github.com/gorilla/websocket v1.5.0
github.com/mattn/go-sqlite3 v1.14.16
github.com/olebedev/when v0.0.0-20211212231525-59bd4edcf9d6
github.com/olebedev/when v0.0.0-20221205223600-4d190b02b8d8
github.com/stretchr/testify v1.8.1
github.com/urfave/cli/v2 v2.23.5
golang.org/x/crypto v0.2.0
golang.org/x/oauth2 v0.2.0 // indirect
github.com/urfave/cli/v2 v2.23.7
golang.org/x/crypto v0.4.0
golang.org/x/oauth2 v0.3.0 // indirect
golang.org/x/sync v0.1.0
golang.org/x/term v0.2.0
golang.org/x/time v0.2.0
google.golang.org/api v0.103.0
golang.org/x/term v0.3.0
golang.org/x/time v0.3.0
google.golang.org/api v0.105.0
gopkg.in/yaml.v2 v2.4.0
)
@@ -28,34 +28,34 @@ require github.com/pkg/errors v0.9.1 // indirect
require firebase.google.com/go/v4 v4.10.0
require (
cloud.google.com/go v0.106.0 // indirect
cloud.google.com/go/compute v1.12.1 // indirect
cloud.google.com/go/compute/metadata v0.2.1 // indirect
cloud.google.com/go/iam v0.7.0 // indirect
cloud.google.com/go v0.107.0 // indirect
cloud.google.com/go/compute v1.14.0 // indirect
cloud.google.com/go/compute/metadata v0.2.3 // indirect
cloud.google.com/go/iam v0.9.0 // indirect
cloud.google.com/go/longrunning v0.3.0 // indirect
github.com/AlekSi/pointer v1.2.0 // indirect
github.com/MicahParks/keyfunc v1.5.3 // indirect
github.com/MicahParks/keyfunc v1.9.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.4.2 // indirect
github.com/golang-jwt/jwt/v4 v4.4.3 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.2.0 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.2.1 // indirect
github.com/googleapis/gax-go/v2 v2.7.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
go.opencensus.io v0.24.0 // indirect
golang.org/x/net v0.2.0 // indirect
golang.org/x/sys v0.2.0 // indirect
golang.org/x/text v0.4.0 // indirect
golang.org/x/net v0.4.0 // indirect
golang.org/x/sys v0.3.0 // indirect
golang.org/x/text v0.5.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.2 // indirect
google.golang.org/genproto v0.0.0-20221111202108-142d8a6fa32e // indirect
google.golang.org/grpc v1.50.1 // indirect
google.golang.org/genproto v0.0.0-20221207170731-23e4bf6bdc37 // indirect
google.golang.org/grpc v1.51.0 // indirect
google.golang.org/protobuf v1.28.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

81
go.sum
View File

@@ -1,18 +1,24 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.106.0 h1:AWaMWuZb2oFeiV91OfNHZbmwUhMVuXEaLPm9sqDAOl8=
cloud.google.com/go v0.106.0/go.mod h1:5NEGxGuIeMQiPaWLwLYZ7kfNWiP6w1+QJK+xqyIT+dw=
cloud.google.com/go/compute v1.12.1 h1:gKVJMEyqV5c/UnpzjjQbo3Rjvvqpr9B1DFSbJC4OXr0=
cloud.google.com/go/compute v1.12.1/go.mod h1:e8yNOBcBONZU1vJKCvCoDw/4JQsA0dpM4x/6PIIOocU=
cloud.google.com/go/compute/metadata v0.2.1 h1:efOwf5ymceDhK6PKMnnrTHP4pppY5L22mle96M1yP48=
cloud.google.com/go/compute/metadata v0.2.1/go.mod h1:jgHgmJd2RKBGzXqF5LR2EZMGxBkeanZ9wwa75XHJgOM=
cloud.google.com/go/firestore v1.8.0 h1:HokMB9Io0hAyYzlGFeFVMgE3iaPXNvaIsDx5JzblGLI=
cloud.google.com/go/firestore v1.8.0/go.mod h1:r3KB8cAdRIe8znzoPWLw8S6gpDVd9treohhn8b09424=
cloud.google.com/go v0.107.0 h1:qkj22L7bgkl6vIeZDlOY2po43Mx/TIa2Wsa7VR+PEww=
cloud.google.com/go v0.107.0/go.mod h1:wpc2eNrD7hXUTy8EKS10jkxpZBjASrORK7goS+3YX2I=
cloud.google.com/go/compute v1.13.0 h1:AYrLkB8NPdDRslNp4Jxmzrhdr03fUAIDbiGFjLWowoU=
cloud.google.com/go/compute v1.13.0/go.mod h1:5aPTS0cUNMIc1CE546K+Th6weJUNQErARyZtRXDJ8GE=
cloud.google.com/go/compute v1.14.0 h1:hfm2+FfxVmnRlh6LpB7cg1ZNU+5edAHmW679JePztk0=
cloud.google.com/go/compute v1.14.0/go.mod h1:YfLtxrj9sU4Yxv+sXzZkyPjEyPBZfXHUvjxega5vAdo=
cloud.google.com/go/compute/metadata v0.2.2 h1:aWKAjYaBaOSrpKl57+jnS/3fJRQnxL7TvR/u1VVbt6k=
cloud.google.com/go/compute/metadata v0.2.2/go.mod h1:jgHgmJd2RKBGzXqF5LR2EZMGxBkeanZ9wwa75XHJgOM=
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 v0.7.0 h1:k4MuwOsS7zGJJ+QfZ5vBK8SgHBAvYN/23BWsiihJ1vs=
cloud.google.com/go/iam v0.7.0/go.mod h1:H5Br8wRaDGNc8XP3keLc4unfUUZeyH3Sfl9XpQEYOeg=
cloud.google.com/go/iam v0.9.0 h1:bK6Or6mxhuL8lnj1i9j0yMo2wE/IeTO2cWlfUrf/TZs=
cloud.google.com/go/iam v0.9.0/go.mod h1:nXAECrMt2qHpF6RZUZseteD6QyanL68reN4OXPw0UWM=
cloud.google.com/go/longrunning v0.3.0 h1:NjljC+FYPV3uh5/OwWT6pVU+doBqMg2x/rZlE+CamDs=
cloud.google.com/go/longrunning v0.3.0/go.mod h1:qth9Y41RRSUE69rDcOn6DdK3HfQfsUI0YSmW3iIlLJc=
cloud.google.com/go/storage v1.28.0 h1:DLrIZ6xkeZX6K70fU/boWx5INJumt6f+nwwWSHXzzGY=
cloud.google.com/go/storage v1.28.0/go.mod h1:qlgZML35PXA3zoEnIkiPLY4/TOkUleufRlu6qmcf7sI=
cloud.google.com/go/storage v1.28.1 h1:F5QDG5ChchaAVQhINh24U99OWHURqrW8OmQcGKXcbgI=
cloud.google.com/go/storage v1.28.1/go.mod h1:Qnisd4CqDdo6BGs2AD5LLnEsmSQ80wQ5ogcBBKhU86Y=
firebase.google.com/go/v4 v4.10.0 h1:dgK/8uwfJbzc5LZK/GyRRfIkZEDObN9q0kgEXsjlXN4=
firebase.google.com/go/v4 v4.10.0/go.mod h1:m0gLwPY9fxKggizzglgCNWOGnFnVPifLpqZzo5u3e/A=
github.com/AlekSi/pointer v1.0.0/go.mod h1:1kjywbfcPFCmncIxtk6fIEub6LKrfMz3gc5QKVOSOA8=
@@ -21,8 +27,10 @@ github.com/AlekSi/pointer v1.2.0/go.mod h1:gZGfd3dpW4vEc/UlyfKKi1roIqcCgwOIvb0tS
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/MicahParks/keyfunc v1.5.3 h1:Y+mv+kX3HtL7/dCXXzK4bIDBHg91eunnGGkdndO0RWk=
github.com/MicahParks/keyfunc v1.5.3/go.mod h1:IdnCilugA0O/99dW+/MkvlyrsX8+L8+x95xuVNtM5jw=
github.com/MicahParks/keyfunc v1.7.0 h1:LBd4tBj6FwGs2S4GXniQbgrG0PXzIldyGDKWch8slhg=
github.com/MicahParks/keyfunc v1.7.0/go.mod h1:IdnCilugA0O/99dW+/MkvlyrsX8+L8+x95xuVNtM5jw=
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/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
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=
@@ -42,8 +50,9 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/gabriel-vasile/mimetype v1.4.1 h1:TRWk7se+TOjCYgRth7+1/OYLNiRNIotknkFtf/dnN7Q=
github.com/gabriel-vasile/mimetype v1.4.1/go.mod h1:05Vi0w3Y9c/lNvJOdmIwvrrAhX3rYhfQQCaf9VJcv7M=
github.com/golang-jwt/jwt/v4 v4.4.2 h1:rcc4lwaZgFMCZ5jxF9ABolDcIHdBytAFgqFPbSJQAYs=
github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang-jwt/jwt/v4 v4.4.3 h1:Hxl6lhQFj4AnOX6MLrsCb/+7tCj7DxP7VA+2rDIq5AU=
github.com/golang-jwt/jwt/v4 v4.4.3/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=
@@ -77,6 +86,8 @@ 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.0 h1:y8Yozv7SZtlU//QXbezB6QkpuE6jMD2/gfzk4AftXjs=
github.com/googleapis/enterprise-certificate-proxy v0.2.0/go.mod h1:8C0jb7/mgJe/9KK8Lm7X9ctZC2t60YyIpYEI16jx0Qg=
github.com/googleapis/enterprise-certificate-proxy v0.2.1 h1:RY7tHKZcRlk788d5WSo/e83gOyyy742E8GSs771ySpg=
github.com/googleapis/enterprise-certificate-proxy v0.2.1/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k=
github.com/googleapis/gax-go/v2 v2.7.0 h1:IcsPKeInNvYi7eqSaDjiZqDDKu5rsmunY0Y1YupQSSQ=
github.com/googleapis/gax-go/v2 v2.7.0/go.mod h1:TEop28CZZQ2y+c0VxMUmu1lV+fQx57QpBWsYpwqHJx8=
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
@@ -85,6 +96,8 @@ github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwp
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/olebedev/when v0.0.0-20211212231525-59bd4edcf9d6 h1:oDSPaYiL2dbjcArLrFS8ANtwgJMyOLzvQCZon+XmFsk=
github.com/olebedev/when v0.0.0-20211212231525-59bd4edcf9d6/go.mod h1:DPucAeQGDPUzYUt+NaWw6qsF5SFapWWToxEiVDh2aV0=
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/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@@ -101,16 +114,20 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
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/urfave/cli/v2 v2.23.5 h1:xbrU7tAYviSpqeR3X4nEFWUdB/uDZ6DE+HxmRU7Xtyw=
github.com/urfave/cli/v2 v2.23.5/go.mod h1:GHupkWPMM0M/sj1a2b4wUrWBPzazNrIjouW6fmdJLxc=
github.com/urfave/cli/v2 v2.23.6 h1:iWmtKD+prGo1nKUtLO0Wg4z9esfBM4rAV4QRLQiEmJ4=
github.com/urfave/cli/v2 v2.23.6/go.mod h1:GHupkWPMM0M/sj1a2b4wUrWBPzazNrIjouW6fmdJLxc=
github.com/urfave/cli/v2 v2.23.7 h1:YHDQ46s3VghFHFf1DdF+Sh7H4RqhcM+t0TmZRJx4oJY=
github.com/urfave/cli/v2 v2.23.7/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=
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
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.2.0 h1:BRXPfhNivWL5Yq0BGQ39a2sW6t44aODpfxkWjYdzewE=
golang.org/x/crypto v0.2.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
golang.org/x/crypto v0.3.0 h1:a06MkbcxBrEFc0w0QIZWXrH/9cCX6KJyWbBOIwAn+7A=
golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
golang.org/x/crypto v0.4.0 h1:UVQgzMY87xqpKNgb+kDsll2Igd33HszWHFLmpaRMq/8=
golang.org/x/crypto v0.4.0/go.mod h1:3quD/ATkf6oY+rnes5c3ExXTbLc8mueNue5/DoinL80=
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=
@@ -126,9 +143,13 @@ golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug
golang.org/x/net v0.0.0-20220708220712-1185a9018129/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.2.0 h1:sZfSu1wtKLGlWI4ZZayP0ck9Y73K1ynO6gqzTdBVdPU=
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
golang.org/x/net v0.4.0 h1:Q5QPcMlvfxFTAPV0+07Xz/MpK9NTXu2VDUuy0FeMfaU=
golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.2.0 h1:GtQkldQ9m7yvzCL1V+LrYow3Khe0eJH0w7RbX/VbaIU=
golang.org/x/oauth2 v0.2.0/go.mod h1:Cwn6afJ8jrQwYMxQDTpISoXmXW9I6qF6vDeuuoX3Ibs=
golang.org/x/oauth2 v0.3.0 h1:6l90koy8/LaBLmLu8jpHeHexzMwEita0zFfYlggy2F8=
golang.org/x/oauth2 v0.3.0/go.mod h1:rQrIauxkUhJ6CuwEXwymO2/eh4xz2ZWF1nBkcxS+tGk=
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-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -140,19 +161,21 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/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.2.0 h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ=
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.2.0 h1:z85xZCsEl7bi/KwbNADeBYoOP0++7W1ipu+aGnpwzRM=
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
golang.org/x/term v0.3.0 h1:qoo4akIqOcDME5bhc/NgxUdovd6BSS2uMsVjB56q1xI=
golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
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.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/time v0.2.0 h1:52I/1L54xyEQAYdtcSuxtiT84KGYTBGXwayxmIpNJhE=
golang.org/x/time v0.2.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM=
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
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/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=
@@ -163,6 +186,8 @@ golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3j
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
google.golang.org/api v0.103.0 h1:9yuVqlu2JCvcLg9p8S3fcFLZij8EPSyvODIY1rkMizQ=
google.golang.org/api v0.103.0/go.mod h1:hGtW6nK1AC+d9si/UBhw8Xli+QMOf6xyNAyJw4qU9w0=
google.golang.org/api v0.105.0 h1:t6P9Jj+6XTn4U9I2wycQai6Q/Kz7iOT+QzjJ3G2V4x8=
google.golang.org/api v0.105.0/go.mod h1:qh7eD5FJks5+BcE+cjBIm6Gz8vioK7EHvnlniqXBnqI=
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=
@@ -172,15 +197,17 @@ google.golang.org/appengine/v2 v2.0.2/go.mod h1:PkgRUWz4o1XOvbqtWTkBtCitEJ5Tp4Ho
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-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20221111202108-142d8a6fa32e h1:azcyH5lGzGy7pkLCbhPe0KkKxsM7c6UA/FZIXImKE7M=
google.golang.org/genproto v0.0.0-20221111202108-142d8a6fa32e/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg=
google.golang.org/genproto v0.0.0-20221202195650-67e5cbc046fd h1:OjndDrsik+Gt+e6fs45z9AxiewiKyLKYpA45W5Kpkks=
google.golang.org/genproto v0.0.0-20221202195650-67e5cbc046fd/go.mod h1:cTsE614GARnxrLsqKREzmNYJACSWWpAWdNMwnD7c2BE=
google.golang.org/genproto v0.0.0-20221207170731-23e4bf6bdc37 h1:jmIfw8+gSvXcZSgaFAGyInDXeWzUhvYH57G/5GKMn70=
google.golang.org/genproto v0.0.0-20221207170731-23e4bf6bdc37/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM=
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.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/grpc v1.50.1 h1:DS/BukOZWp8s6p4Dt/tOaJaTQyPyOoCcrjroHuCeLzY=
google.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI=
google.golang.org/grpc v1.51.0 h1:E1eGv1FTqoLIdnBCZufiSHgKjlqG6fKFf6pPWtMTh8U=
google.golang.org/grpc v1.51.0/go.mod h1:wgNDFcnuBGmxLKI/qn4T+m5BtEBYXJPvibbUPsAIPww=
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=

View File

@@ -87,8 +87,9 @@ nav:
- "Examples": examples.md
- "Integrations + projects": integrations.md
- "Release notes": releases.md
- "Deprecation notices": deprecations.md
- "Emojis 🥳 🎉": emojis.md
- "Known issues": known-issues.md
- "Deprecation notices": deprecations.md
- "Development": develop.md
- "Privacy policy": privacy.md

View File

@@ -61,6 +61,8 @@ type Config struct {
CacheFile string
CacheDuration time.Duration
CacheStartupQueries string
CacheBatchSize int
CacheBatchTimeout time.Duration
AuthFile string
AuthDefaultRead bool
AuthDefaultWrite bool
@@ -114,6 +116,8 @@ func NewConfig() *Config {
FirebaseKeyFile: "",
CacheFile: "",
CacheDuration: DefaultCacheDuration,
CacheBatchSize: 0,
CacheBatchTimeout: 0,
AuthFile: "",
AuthDefaultRead: true,
AuthDefaultWrite: true,

View File

@@ -44,6 +44,7 @@ const (
published INT NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_mid ON messages (mid);
CREATE INDEX IF NOT EXISTS idx_time ON messages (time);
CREATE INDEX IF NOT EXISTS idx_topic ON messages (topic);
COMMIT;
`
@@ -92,7 +93,7 @@ const (
// Schema management queries
const (
currentSchemaVersion = 8
currentSchemaVersion = 9
createSchemaVersionTableQuery = `
CREATE TABLE IF NOT EXISTS schemaVersion (
id INT PRIMARY KEY,
@@ -185,15 +186,21 @@ const (
migrate7To8AlterMessagesTableQuery = `
ALTER TABLE messages ADD COLUMN icon TEXT NOT NULL DEFAULT('');
`
// 8 -> 9
migrate8To9AlterMessagesTableQuery = `
CREATE INDEX IF NOT EXISTS idx_time ON messages (time);
`
)
type messageCache struct {
db *sql.DB
nop bool
db *sql.DB
queue *util.BatchingQueue[*message]
nop bool
}
// newSqliteCache creates a SQLite file-backed cache
func newSqliteCache(filename, startupQueries string, nop bool) (*messageCache, error) {
func newSqliteCache(filename, startupQueries string, batchSize int, batchTimeout time.Duration, nop bool) (*messageCache, error) {
db, err := sql.Open("sqlite3", filename)
if err != nil {
return nil, err
@@ -201,21 +208,28 @@ func newSqliteCache(filename, startupQueries string, nop bool) (*messageCache, e
if err := setupCacheDB(db, startupQueries); err != nil {
return nil, err
}
return &messageCache{
db: db,
nop: nop,
}, nil
var queue *util.BatchingQueue[*message]
if batchSize > 0 || batchTimeout > 0 {
queue = util.NewBatchingQueue[*message](batchSize, batchTimeout)
}
cache := &messageCache{
db: db,
queue: queue,
nop: nop,
}
go cache.processMessageBatches()
return cache, nil
}
// newMemCache creates an in-memory cache
func newMemCache() (*messageCache, error) {
return newSqliteCache(createMemoryFilename(), "", false)
return newSqliteCache(createMemoryFilename(), "", 0, 0, false)
}
// newNopCache creates an in-memory cache that discards all messages;
// it is always empty and can be used if caching is entirely disabled
func newNopCache() (*messageCache, error) {
return newSqliteCache(createMemoryFilename(), "", true)
return newSqliteCache(createMemoryFilename(), "", 0, 0, true)
}
// createMemoryFilename creates a unique memory filename to use for the SQLite backend.
@@ -228,19 +242,36 @@ func createMemoryFilename() string {
return fmt.Sprintf("file:%s?mode=memory&cache=shared", util.RandomString(10))
}
// AddMessage stores a message to the message cache synchronously, or queues it to be stored at a later date asyncronously.
// The message is queued only if "batchSize" or "batchTimeout" are passed to the constructor.
func (c *messageCache) AddMessage(m *message) error {
if c.queue != nil {
c.queue.Enqueue(m)
return nil
}
return c.addMessages([]*message{m})
}
// addMessages synchronously stores a match of messages. If the database is locked, the transaction waits until
// SQLite's busy_timeout is exceeded before erroring out.
func (c *messageCache) addMessages(ms []*message) error {
if c.nop {
return nil
}
if len(ms) == 0 {
return nil
}
start := time.Now()
tx, err := c.db.Begin()
if err != nil {
return err
}
defer tx.Rollback()
stmt, err := tx.Prepare(insertMessageQuery)
if err != nil {
return err
}
defer stmt.Close()
for _, m := range ms {
if m.Event != messageEvent {
return errUnexpectedMessageType
@@ -264,8 +295,11 @@ func (c *messageCache) addMessages(ms []*message) error {
}
actionsStr = string(actionsBytes)
}
_, err := tx.Exec(
insertMessageQuery,
var sender string
if m.Sender.IsValid() {
sender = m.Sender.String()
}
_, err := stmt.Exec(
m.ID,
m.Time,
m.Topic,
@@ -281,7 +315,7 @@ func (c *messageCache) addMessages(ms []*message) error {
attachmentSize,
attachmentExpires,
attachmentURL,
m.Sender.String(),
sender,
m.Encoding,
published,
)
@@ -289,7 +323,12 @@ func (c *messageCache) addMessages(ms []*message) error {
return err
}
}
return tx.Commit()
if err := tx.Commit(); err != nil {
log.Error("Cache: Writing %d message(s) failed (took %v)", len(ms), time.Since(start))
return err
}
log.Debug("Cache: Wrote %d message(s) in %v", len(ms), time.Since(start))
return nil
}
func (c *messageCache) Messages(topic string, since sinceMarker, scheduled bool) ([]*message, error) {
@@ -395,8 +434,12 @@ func (c *messageCache) Topics() (map[string]*topic, error) {
}
func (c *messageCache) Prune(olderThan time.Time) error {
_, err := c.db.Exec(pruneMessagesQuery, olderThan.Unix())
return err
start := time.Now()
if _, err := c.db.Exec(pruneMessagesQuery, olderThan.Unix()); err != nil {
log.Warn("Cache: Pruning failed (after %v): %s", time.Since(start), err.Error())
}
log.Debug("Cache: Pruning successful (took %v)", time.Since(start))
return nil
}
func (c *messageCache) AttachmentBytesUsed(sender string) (int64, error) {
@@ -417,6 +460,17 @@ func (c *messageCache) AttachmentBytesUsed(sender string) (int64, error) {
return size, nil
}
func (c *messageCache) processMessageBatches() {
if c.queue == nil {
return
}
for messages := range c.queue.Dequeue() {
if err := c.addMessages(messages); err != nil {
log.Error("Cache: %s", err.Error())
}
}
}
func readMessages(rows *sql.Rows) ([]*message, error) {
defer rows.Close()
messages := make([]*message, 0)
@@ -458,9 +512,8 @@ func readMessages(rows *sql.Rows) ([]*message, error) {
}
senderIP, err := netip.ParseAddr(sender)
if err != nil {
senderIP = netip.IPv4Unspecified() // if no IP stored in database, 0.0.0.0
senderIP = netip.Addr{} // if no IP stored in database, return invalid address
}
var att *attachment
if attachmentName != "" && attachmentURL != "" {
att = &attachment{
@@ -542,6 +595,8 @@ func setupCacheDB(db *sql.DB, startupQueries string) error {
return migrateFrom6(db)
} else if schemaVersion == 7 {
return migrateFrom7(db)
} else if schemaVersion == 8 {
return migrateFrom8(db)
}
return fmt.Errorf("unexpected schema version found: %d", schemaVersion)
}
@@ -647,5 +702,16 @@ func migrateFrom7(db *sql.DB) error {
if _, err := db.Exec(updateSchemaVersion, 8); err != nil {
return err
}
return migrateFrom8(db)
}
func migrateFrom8(db *sql.DB) error {
log.Info("Migrating cache database schema: from 8 to 9")
if _, err := db.Exec(migrate8To9AlterMessagesTableQuery); err != nil {
return err
}
if _, err := db.Exec(updateSchemaVersion, 9); err != nil {
return err
}
return nil // Update this when a new version is added
}

View File

@@ -450,7 +450,7 @@ func TestSqliteCache_StartupQueries_WAL(t *testing.T) {
startupQueries := `pragma journal_mode = WAL;
pragma synchronous = normal;
pragma temp_store = memory;`
db, err := newSqliteCache(filename, startupQueries, false)
db, err := newSqliteCache(filename, startupQueries, 0, 0, false)
require.Nil(t, err)
require.Nil(t, db.AddMessage(newDefaultMessage("mytopic", "some message")))
require.FileExists(t, filename)
@@ -461,7 +461,7 @@ pragma temp_store = memory;`
func TestSqliteCache_StartupQueries_None(t *testing.T) {
filename := newSqliteTestCacheFile(t)
startupQueries := ""
db, err := newSqliteCache(filename, startupQueries, false)
db, err := newSqliteCache(filename, startupQueries, 0, 0, false)
require.Nil(t, err)
require.Nil(t, db.AddMessage(newDefaultMessage("mytopic", "some message")))
require.FileExists(t, filename)
@@ -472,10 +472,33 @@ func TestSqliteCache_StartupQueries_None(t *testing.T) {
func TestSqliteCache_StartupQueries_Fail(t *testing.T) {
filename := newSqliteTestCacheFile(t)
startupQueries := `xx error`
_, err := newSqliteCache(filename, startupQueries, false)
_, err := newSqliteCache(filename, startupQueries, 0, 0, false)
require.Error(t, err)
}
func TestSqliteCache_Sender(t *testing.T) {
testSender(t, newSqliteTestCache(t))
}
func TestMemCache_Sender(t *testing.T) {
testSender(t, newMemTestCache(t))
}
func testSender(t *testing.T, c *messageCache) {
m1 := newDefaultMessage("mytopic", "mymessage")
m1.Sender = netip.MustParseAddr("1.2.3.4")
require.Nil(t, c.AddMessage(m1))
m2 := newDefaultMessage("mytopic", "mymessage without sender")
require.Nil(t, c.AddMessage(m2))
messages, err := c.Messages("mytopic", sinceAllMessages, false)
require.Nil(t, err)
require.Equal(t, 2, len(messages))
require.Equal(t, messages[0].Sender, netip.MustParseAddr("1.2.3.4"))
require.Equal(t, messages[1].Sender, netip.Addr{})
}
func checkSchemaVersion(t *testing.T, db *sql.DB) {
rows, err := db.Query(`SELECT version FROM schemaVersion`)
require.Nil(t, err)
@@ -501,7 +524,7 @@ func TestMemCache_NopCache(t *testing.T) {
}
func newSqliteTestCache(t *testing.T) *messageCache {
c, err := newSqliteCache(newSqliteTestCacheFile(t), "", false)
c, err := newSqliteCache(newSqliteTestCacheFile(t), "", 0, 0, false)
if err != nil {
t.Fatal(err)
}
@@ -513,7 +536,7 @@ func newSqliteTestCacheFile(t *testing.T) string {
}
func newSqliteTestCacheFromFile(t *testing.T, filename, startupQueries string) *messageCache {
c, err := newSqliteCache(filename, startupQueries, false)
c, err := newSqliteCache(filename, startupQueries, 0, 0, false)
if err != nil {
t.Fatal(err)
}

View File

@@ -159,7 +159,7 @@ func createMessageCache(conf *Config) (*messageCache, error) {
if conf.CacheDuration == 0 {
return newNopCache()
} else if conf.CacheFile != "" {
return newSqliteCache(conf.CacheFile, conf.CacheStartupQueries, false)
return newSqliteCache(conf.CacheFile, conf.CacheStartupQueries, conf.CacheBatchSize, conf.CacheBatchTimeout, false)
}
return newMemCache()
}
@@ -491,6 +491,7 @@ func (s *Server) handlePublishWithoutResponse(r *http.Request, v *visitor) (*mes
log.Debug("%s Message delayed, will process later", logMessagePrefix(v, m))
}
if cache {
log.Debug("%s Adding message to cache", logMessagePrefix(v, m))
if err := s.messageCache.AddMessage(m); err != nil {
return nil, err
}
@@ -1444,7 +1445,9 @@ func (s *Server) visitor(r *http.Request) *visitor {
ip, err = netip.ParseAddr(remoteAddr)
if err != nil {
ip = netip.IPv4Unspecified()
log.Warn("unable to parse IP (%s), new visitor with unspecified IP (0.0.0.0) created %s", remoteAddr, err)
if remoteAddr != "@" || !s.config.BehindProxy { // RemoteAddr is @ when unix socket is used
log.Warn("unable to parse IP (%s), new visitor with unspecified IP (0.0.0.0) created %s", remoteAddr, err)
}
}
}
if s.config.BehindProxy && strings.TrimSpace(r.Header.Get("X-Forwarded-For")) != "" {

View File

@@ -53,6 +53,12 @@
# pragma journal_mode = WAL;
# pragma synchronous = normal;
# pragma temp_store = memory;
# pragma busy_timeout = 15000;
# vacuum;
#
# The "cache-batch-size" and "cache-batch-timeout" parameter allow enabling async batch writing
# of messages. If set, messages will be queued and written to the database in batches of the given
# size, or after the given timeout. This is only required for high volume servers.
#
# Debian/RPM package users:
# Use /var/cache/ntfy/cache.db as cache file to avoid permission issues. The package
@@ -65,6 +71,8 @@
# cache-file: <filename>
# cache-duration: "12h"
# cache-startup-queries:
# cache-batch-size: 0
# cache-batch-timeout: "0ms"
# If set, access to the ntfy server and API can be controlled on a granular level using
# the 'ntfy user' and 'ntfy access' commands. See the --help pages for details, or check the docs.
@@ -173,8 +181,9 @@
# Rate limiting: Allowed GET/PUT/POST requests per second, per visitor:
# - visitor-request-limit-burst is the initial bucket of requests each visitor has
# - visitor-request-limit-replenish is the rate at which the bucket is refilled
# - visitor-request-limit-exempt-hosts is a comma-separated list of hostnames and IPs to be
# exempt from request rate limiting; hostnames are resolved at the time the server is started
# - visitor-request-limit-exempt-hosts is a comma-separated list of hostnames, IPs or CIDRs to be
# exempt from request rate limiting. Hostnames are resolved at the time the server is started.
# Example: "1.2.3.4,ntfy.example.com,8.7.6.0/24"
#
# visitor-request-limit-burst: 60
# visitor-request-limit-replenish: "5s"

86
util/batching_queue.go Normal file
View File

@@ -0,0 +1,86 @@
package util
import (
"sync"
"time"
)
// BatchingQueue is a queue that creates batches of the enqueued elements based on a
// max batch size and a batch timeout.
//
// Example:
//
// q := NewBatchingQueue[int](2, 500 * time.Millisecond)
// go func() {
// for batch := range q.Dequeue() {
// fmt.Println(batch)
// }
// }()
// q.Enqueue(1)
// q.Enqueue(2)
// q.Enqueue(3)
// time.Sleep(time.Second)
//
// This example will emit batch [1, 2] immediately (because the batch size is 2), and
// a batch [3] after 500ms.
type BatchingQueue[T any] struct {
batchSize int
timeout time.Duration
in []T
out chan []T
mu sync.Mutex
}
// NewBatchingQueue creates a new BatchingQueue
func NewBatchingQueue[T any](batchSize int, timeout time.Duration) *BatchingQueue[T] {
q := &BatchingQueue[T]{
batchSize: batchSize,
timeout: timeout,
in: make([]T, 0),
out: make(chan []T),
}
go q.timeoutTicker()
return q
}
// Enqueue enqueues an element to the queue. If the configured batch size is reached,
// the batch will be emitted immediately.
func (q *BatchingQueue[T]) Enqueue(element T) {
q.mu.Lock()
q.in = append(q.in, element)
var elements []T
if len(q.in) == q.batchSize {
elements = q.dequeueAll()
}
q.mu.Unlock()
if len(elements) > 0 {
q.out <- elements
}
}
// Dequeue returns a channel emitting batches of elements
func (q *BatchingQueue[T]) Dequeue() <-chan []T {
return q.out
}
func (q *BatchingQueue[T]) dequeueAll() []T {
elements := make([]T, len(q.in))
copy(elements, q.in)
q.in = q.in[:0]
return elements
}
func (q *BatchingQueue[T]) timeoutTicker() {
if q.timeout == 0 {
return
}
ticker := time.NewTicker(q.timeout)
for range ticker.C {
q.mu.Lock()
elements := q.dequeueAll()
q.mu.Unlock()
if len(elements) > 0 {
q.out <- elements
}
}
}

View File

@@ -0,0 +1,58 @@
package util_test
import (
"github.com/stretchr/testify/require"
"heckel.io/ntfy/util"
"math/rand"
"sync"
"testing"
"time"
)
func TestBatchingQueue_InfTimeout(t *testing.T) {
q := util.NewBatchingQueue[int](25, 1*time.Hour)
batches, total := make([][]int, 0), 0
var mu sync.Mutex
go func() {
for batch := range q.Dequeue() {
mu.Lock()
batches = append(batches, batch)
total += len(batch)
mu.Unlock()
}
}()
for i := 0; i < 101; i++ {
go q.Enqueue(i)
}
time.Sleep(time.Second)
mu.Lock()
require.Equal(t, 100, total) // One is missing, stuck in the last batch!
require.Equal(t, 4, len(batches))
mu.Unlock()
}
func TestBatchingQueue_WithTimeout(t *testing.T) {
q := util.NewBatchingQueue[int](25, 100*time.Millisecond)
batches, total := make([][]int, 0), 0
var mu sync.Mutex
go func() {
for batch := range q.Dequeue() {
mu.Lock()
batches = append(batches, batch)
total += len(batch)
mu.Unlock()
}
}()
for i := 0; i < 101; i++ {
go func(i int) {
time.Sleep(time.Duration(rand.Intn(700)) * time.Millisecond)
q.Enqueue(i)
}(i)
}
time.Sleep(time.Second)
mu.Lock()
require.Equal(t, 101, total)
require.True(t, len(batches) > 4) // 101/25
require.True(t, len(batches) < 21)
mu.Unlock()
}

2279
web/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -30,12 +30,12 @@
"prefs_notifications_title": "Известия",
"prefs_notifications_sound_title": "Звук при получаване",
"prefs_notifications_sound_no_sound": "Без звук",
"prefs_notifications_min_priority_title": "Минимален приоритет",
"prefs_notifications_min_priority_title": "Най-нисък приоритет",
"prefs_notifications_min_priority_any": "Всички",
"prefs_notifications_min_priority_low_and_higher": "Нисък приоритет и по-висок",
"prefs_notifications_min_priority_default_and_higher": "Подразбиран приоритет и по-висок",
"prefs_notifications_min_priority_high_and_higher": "Висок приоритет и по-висок",
"prefs_notifications_min_priority_max_only": "Само максимален приоритет",
"prefs_notifications_min_priority_max_only": "Само най-висок приоритет",
"prefs_notifications_delete_after_never": "Никога",
"prefs_users_add_button": "Добавяне",
"prefs_users_dialog_password_label": "Парола",
@@ -62,11 +62,11 @@
"notifications_click_copy_url_title": "Копиране на препратката в междинната памет",
"notifications_none_for_topic_title": "Липсват известия в темата",
"notifications_none_for_any_title": "Липсват известия",
"notifications_none_for_topic_description": "За да изпратите известия в тази тема, просто направете PUT или POST към адреса ѝ.",
"notifications_none_for_any_description": "За да изпратите известия в тема, просто направете PUT или POST към адреса ѝ. Ето пример с една от вашите теми.",
"notifications_no_subscriptions_description": "Щракнете върху „{{linktext}}“, за да създадете тема или да се абонирате. След това като изпратите съобщения чрез метода PUT или POST ще ги получите тук.",
"notifications_none_for_topic_description": "За да изпратите известия в тази тема направете заявка чрез методите PUT или POST към адреса й.",
"notifications_none_for_any_description": "За да изпратите известия в тема направете заявка чрез методите PUT или POST към адреса ѝ. Ето пример с една от вашите теми.",
"notifications_no_subscriptions_description": "Щракнете върху „{{linktext}}“, за да създадете тема или да се абонирате. След това като направите заявка чрез методите PUT или POST ще ги получите тук.",
"notifications_more_details": "За допълнителна информация посетете <websiteLink>страницата</websiteLink> или <docsLink>документацията</docsLink>.",
"publish_dialog_priority_min": "Мин. приоритет",
"publish_dialog_priority_min": "Най-нисък приоритет",
"publish_dialog_attachment_limits_file_reached": "надвишава ограничението от {{fileSizeLimit}} за размер на файл",
"publish_dialog_base_url_label": "Адрес на услугата",
"publish_dialog_base_url_placeholder": "Адрес на услугата, напр. https://example.com",
@@ -78,7 +78,7 @@
"publish_dialog_title_placeholder": "Заглавие на известието, напр. Предупреждение за диска",
"publish_dialog_tags_label": "Етикети",
"publish_dialog_email_label": "Адрес на електронна поща",
"publish_dialog_priority_max": "Макс. приоритет",
"publish_dialog_priority_max": "Най-висок приоритет",
"publish_dialog_tags_placeholder": "Разделени със запетая етикети, напр. warning, srv1-backup",
"publish_dialog_click_label": "Адрес",
"publish_dialog_topic_label": "Име на темата",
@@ -98,7 +98,7 @@
"publish_dialog_attached_file_title": "Прикачен файл:",
"publish_dialog_attached_file_filename_placeholder": "Име на прикачения файл",
"publish_dialog_drop_file_here": "Пуснете файла тук",
"subscribe_dialog_subscribe_description": "Възможно е темите да не са защитени с парола, затова изберете име, което е трудно за отгатване. След като се абонирате, можете да изпращате известия по PUT или POST.",
"subscribe_dialog_subscribe_description": "Възможно е темите да не са защитени с парола, затова изберете име, което е трудно за отгатване. След като се абонирате, можете да изпращате известия чрез методите PUT или POST.",
"emoji_picker_search_placeholder": "Търсете емоция",
"subscribe_dialog_subscribe_title": "Абониране за тема",
"subscribe_dialog_subscribe_topic_placeholder": "Име на темата, напр. phils_alerts",
@@ -140,10 +140,10 @@
"prefs_notifications_sound_description_some": "При пристигане известията са съпроводени от звука „{{sound}}“",
"prefs_notifications_delete_after_never_description": "Известията никога не се премахват автоматично",
"prefs_notifications_delete_after_three_hours_description": "Известията се премахват автоматично след три часа",
"priority_min": "минимален",
"priority_min": "най-нисък",
"priority_low": "нисък",
"priority_high": "висок",
"priority_max": "максимален",
"priority_max": "най-висок",
"priority_default": "подразбиран",
"prefs_notifications_delete_after_one_week_description": "Известията се премахват автоматично след една седмица",
"prefs_notifications_delete_after_one_day_description": "Известията се премахват автоматично след един ден",
@@ -160,7 +160,7 @@
"nav_button_muted": "Известията са заглушени",
"notifications_list": "Списък с известия",
"notifications_list_item": "Известие",
"notifications_delete": "Изтриване",
"notifications_delete": "Премахване",
"notifications_mark_read": "Отбелязване като прочетено",
"nav_button_connecting": "свързване",
"message_bar_show_dialog": "Показване на диалога за публикуване",
@@ -169,9 +169,9 @@
"notifications_new_indicator": "Ново известие",
"notifications_attachment_image": "Прикачено изображение",
"notifications_attachment_file_image": "файл на изображение",
"notifications_attachment_file_video": "файл на видео",
"notifications_attachment_file_audio": "файл на аудио",
"notifications_attachment_file_app": "Инсталационен файл на приложение за Android",
"notifications_attachment_file_video": "видео",
"notifications_attachment_file_audio": "аудио",
"notifications_attachment_file_app": "инсталационен файл на приложение за Android",
"notifications_attachment_file_document": "друг документ",
"publish_dialog_emoji_picker_show": "Избор на емоция",
"publish_dialog_topic_reset": "Нулиране на тема",
@@ -183,7 +183,7 @@
"subscribe_dialog_subscribe_base_url_label": "Адрес на услугата",
"prefs_notifications_sound_play": "Възпроизвеждане на избрания звук",
"publish_dialog_attach_reset": "Премахване на адреса на файла за прикачане",
"prefs_users_delete_button": "Премахване на потребител",
"prefs_users_delete_button": "Премахване",
"prefs_users_table": "Таблица с потребители",
"prefs_users_edit_button": "Промяна на потребител",
"error_boundary_unsupported_indexeddb_title": "Поверително разглеждане не се поддържа",

View File

@@ -129,6 +129,7 @@
"subscribe_dialog_subscribe_topic_placeholder": "Topic name, e.g. phil_alerts",
"subscribe_dialog_subscribe_use_another_label": "Use another server",
"subscribe_dialog_subscribe_base_url_label": "Service URL",
"subscribe_dialog_subscribe_button_generate_topic_name": "Generate name",
"subscribe_dialog_subscribe_button_cancel": "Cancel",
"subscribe_dialog_subscribe_button_subscribe": "Subscribe",
"subscribe_dialog_login_title": "Login required",

View File

@@ -152,5 +152,40 @@
"error_boundary_stack_trace": "Verem nyomkövetés",
"publish_dialog_title_topic": "A {{topic}} téma értesítése",
"prefs_notifications_sound_description_some": "Az értesítéseket a(z) {{sound}} hang fogja jelezni",
"error_boundary_description": "Ennek nem szabadott volna megtörténnie. Nagyon sajnáljuk.<br/>Ha van egy perced, <githubLink>jelentsd be GitHubon</githubLink>, vagy tudasd velünk <discordLink>Discordon</discordLink>, vagy <matrixLink>Matrixon</matrixLink>."
"error_boundary_description": "Ennek nem szabadott volna megtörténnie. Nagyon sajnáljuk.<br/>Ha van egy perced, <githubLink>jelentsd be GitHubon</githubLink>, vagy tudasd velünk <discordLink>Discordon</discordLink>, vagy <matrixLink>Matrixon</matrixLink>.",
"action_bar_show_menu": "Menü mutatása",
"action_bar_toggle_mute": "Üzenetek némítása/bekapcsolása",
"notifications_list_item": "Értesítés",
"error_boundary_unsupported_indexeddb_description": "A ntfy web alkalmazás működéséhez szükséges az IndexedDB funkció, az ön böngészője nem támogatja az IndexedDB használatát privát böngészés közben.<br/><br/>Miközben privát mód sajnos nem lehetséges, szeretnénk értesíteni hogy magabiztosan használhatja normál módban mert a böngésző minden adatot az ön gépén tárol. Tovább tájékozódhat <githubLink>ezen a Github oldalon</githubLink>, vagy beszéljen velünk <discordLink>Discord-on</discordLink> vagy <matrixLink>Matrix-on</matrixLink>.",
"notifications_priority_x": "Prioritás {{prioritás}}",
"message_bar_show_dialog": "Küldött üzenetek megjelenítése",
"action_bar_logo_alt": "ntfy logó",
"action_bar_toggle_action_menu": "Tevékenységkezelő nyitása/zárása",
"message_bar_publish": "Üzenet küldése",
"nav_button_muted": "Értesítések némítva",
"nav_button_connecting": "csatlakozás",
"notifications_list": "Értesítés lista",
"notifications_mark_read": "Jelölés olvasottként",
"notifications_delete": "Törlés",
"notifications_new_indicator": "Új értesítés",
"notifications_attachment_image": "Csatolt kép",
"notifications_attachment_file_image": "Kép fájl",
"notifications_attachment_file_video": "Videó fájl",
"notifications_attachment_file_audio": "Hang fájl",
"notifications_attachment_file_app": "Android alkalmazás fájl",
"notifications_attachment_file_document": "egyéb dokumentum",
"publish_dialog_emoji_picker_show": "Emoji kiválasztása",
"publish_dialog_topic_reset": "Téma visszaállítása",
"publish_dialog_click_reset": "URL kattintás törlése",
"publish_dialog_email_reset": "Email továbbítás törlése",
"publish_dialog_attach_reset": "Csatolt URL törlése",
"publish_dialog_delay_reset": "Késleltetett kézbesítés törlése",
"publish_dialog_attached_file_remove": "Csatolt fájl törlése",
"emoji_picker_search_clear": "Keresés törlése",
"prefs_notifications_sound_play": "Kijelölt hang lejátszása",
"prefs_users_table": "Felhasználó táblázat",
"prefs_users_edit_button": "Felhasználó szerkesztése",
"prefs_users_delete_button": "Felhasználó törlése",
"error_boundary_unsupported_indexeddb_title": "Privát böngészés nem támogatott",
"subscribe_dialog_subscribe_base_url_label": "Szolgáltató URL"
}

View File

@@ -0,0 +1 @@
{}

View File

@@ -41,5 +41,11 @@
"alert_grant_description": "Ge din webbläsare behörighet att visa skrivbordsnotiser.",
"alert_not_supported_description": "Notiser stöds inte i din webbläsare.",
"notifications_mark_read": "Markera som läst",
"notifications_attachment_file_video": "video fil"
"notifications_attachment_file_video": "video fil",
"notifications_click_copy_url_button": "Kopiera länk",
"notifications_click_open_button": "Öppna länk",
"notifications_actions_open_url_title": "Gå till {{url}}",
"notifications_none_for_any_title": "Du har inte fått några notiser.",
"notifications_example": "Exempel",
"notifications_loading": "Laddar notiser …"
}

View File

@@ -10,15 +10,15 @@
"notifications_list_item": "通知",
"notifications_mark_read": "標示已讀",
"notifications_attachment_image": "附加圖片",
"notifications_attachment_copy_url_title": "複製附件URL到剪貼",
"notifications_attachment_copy_url_button": "複製URL",
"notifications_attachment_copy_url_title": "複製附件 URL 到剪貼簿",
"notifications_attachment_copy_url_button": "複製 URL",
"notifications_attachment_open_title": "前往 {{url}}",
"notifications_attachment_open_button": "開啟附件",
"notifications_attachment_link_expired": "下載連結已過期",
"notifications_attachment_file_video": "影片檔案",
"notifications_attachment_file_app": "Android 應用程式檔案",
"notifications_attachment_file_document": "其他文件",
"notifications_click_copy_url_title": "複製連結URL到剪貼板",
"notifications_click_copy_url_title": "複製連結 URL 到剪貼板",
"notifications_click_copy_url_button": "複製連結",
"notifications_click_open_button": "開啟連結",
"notifications_actions_not_supported": "網頁程式無法支援該動作",
@@ -27,16 +27,16 @@
"notifications_none_for_topic_description": "如要寄送通知到此主題,請使用 PUT 或 POST 到此主題URL。",
"notifications_none_for_any_title": "尚未收到任何通知。",
"action_bar_settings": "設定",
"action_bar_send_test_notification": "送測試通知",
"action_bar_send_test_notification": "送測試通知",
"action_bar_clear_notifications": "清除所有通知",
"action_bar_show_menu": "顯示選單",
"nav_button_documentation": "文件",
"nav_button_publish_message": "發通知",
"nav_button_publish_message": "發通知",
"nav_button_muted": "通知已靜音",
"notifications_copied_to_clipboard": "複製到剪貼",
"message_bar_publish": "發訊息",
"message_bar_show_dialog": "顯示發對話",
"message_bar_error_publishing": "無法發布通知",
"notifications_copied_to_clipboard": "複製到剪貼簿",
"message_bar_publish": "發訊息",
"message_bar_show_dialog": "顯示發對話",
"message_bar_error_publishing": "發佈通知時發生錯誤",
"nav_topics_title": "訂閱主題",
"nav_button_all_notifications": "所有通知",
"nav_button_settings": "設定",
@@ -50,7 +50,36 @@
"notifications_new_indicator": "新通知",
"notifications_attachment_file_audio": "聲音檔案",
"notifications_delete": "刪除",
"notifications_attachment_link_expires": "連結已過期 {{date}}",
"notifications_attachment_link_expires": "連結 {{date}} 過期",
"notifications_attachment_file_image": "圖片檔案",
"notifications_actions_open_url_title": "前往 {{url}}"
"notifications_actions_open_url_title": "前往 {{url}}",
"notifications_no_subscriptions_title": "你尚未有任何訂閱。",
"notifications_example": "範例",
"notifications_more_details": "你可以在 <websiteLink>ntfy 網站</websiteLink>或者<docsLink>技術文件</docsLink>中查看更多資訊。",
"notifications_loading": "載入中…",
"publish_dialog_title_topic": "發佈到 {{topic}}",
"publish_dialog_title_no_topic": "發佈通知",
"publish_dialog_progress_uploading": "上傳中…",
"publish_dialog_priority_label": "優先度",
"publish_dialog_email_label": "電郵地址",
"publish_dialog_filename_label": "檔案名稱",
"publish_dialog_button_cancel": "取消",
"publish_dialog_button_send": "傳送",
"publish_dialog_button_cancel_sending": "取消傳送",
"subscribe_dialog_subscribe_button_cancel": "取消",
"subscribe_dialog_subscribe_button_subscribe": "訂閱",
"emoji_picker_search_clear": "清除",
"subscribe_dialog_login_password_label": "密碼",
"subscribe_dialog_login_button_back": "返回",
"subscribe_dialog_login_button_login": "登入",
"prefs_notifications_delete_after_never": "從不",
"prefs_users_add_button": "新增使用者",
"prefs_users_dialog_password_label": "密碼",
"prefs_users_dialog_title_add": "新增使用者",
"prefs_users_dialog_button_save": "儲存",
"prefs_users_dialog_button_cancel": "取消",
"error_boundary_title": "歐買尬ntfy 壞掉了",
"notifications_none_for_any_description": "要開始發送通知到一個主題,只需要對主題 URL 發送 HTTP PUT 或者 POST例如",
"notifications_no_subscriptions_description": "點選 「{{linktext}}」 連結以建立或訂閱主題。完成後,你就可以使用 HTTP PUT 或者 POST 發送通知到這裡了!",
"error_boundary_description": "很抱歉 ntfy 發生錯誤了。<br/>如果你有時間,煩請到<githubLink> Github </githubLink>回報錯誤,或者到<discordLink> Discord </discordLink>或者<matrixLink> Matrix 聊天室</matrixLink>裡面告訴我們。"
}

View File

@@ -94,7 +94,6 @@ export const unmatchedTags = (tags) => {
else return tags.filter(tag => !(tag in emojis));
}
export const maybeWithBasicAuth = (headers, user) => {
if (user) {
headers['Authorization'] = `Basic ${encodeBase64(`${user.username}:${user.password}`)}`;
@@ -241,3 +240,12 @@ export async function* fetchLinesIterator(fileURL, headers) {
yield chunk.substr(startIndex); // last line didn't end in a newline char
}
}
export const randomAlphanumericString = (len) => {
const alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
let id = "";
for (let i = 0; i < len; i++) {
id += alphabet[(Math.random() * alphabet.length) | 0];
}
return id;
}

View File

@@ -9,7 +9,7 @@ import DialogTitle from '@mui/material/DialogTitle';
import {Autocomplete, Checkbox, FormControlLabel, useMediaQuery} from "@mui/material";
import theme from "./theme";
import api from "../app/Api";
import {topicUrl, validTopic, validUrl} from "../app/utils";
import {randomAlphanumericString, topicUrl, validTopic, validUrl} from "../app/utils";
import userManager from "../app/UserManager";
import subscriptionManager from "../app/SubscriptionManager";
import poller from "../app/Poller";
@@ -104,21 +104,26 @@ const SubscribePage = (props) => {
<DialogContentText>
{t("subscribe_dialog_subscribe_description")}
</DialogContentText>
<TextField
autoFocus
margin="dense"
id="topic"
placeholder={t("subscribe_dialog_subscribe_topic_placeholder")}
value={props.topic}
onChange={ev => props.setTopic(ev.target.value)}
type="text"
fullWidth
variant="standard"
inputProps={{
maxLength: 64,
"aria-label": t("subscribe_dialog_subscribe_topic_placeholder")
}}
/>
<div style={{display: 'flex'}} role="row">
<TextField
autoFocus
margin="dense"
id="topic"
placeholder={t("subscribe_dialog_subscribe_topic_placeholder")}
value={props.topic}
onChange={ev => props.setTopic(ev.target.value)}
type="text"
fullWidth
variant="standard"
inputProps={{
maxLength: 64,
"aria-label": t("subscribe_dialog_subscribe_topic_placeholder")
}}
/>
<Button onClick={() => {props.setTopic(randomAlphanumericString(16))}} style={{flexShrink: "0", marginTop: "0.5em"}}>
{t("subscribe_dialog_subscribe_button_generate_topic_name")}
</Button>
</div>
<FormControlLabel
sx={{pt: 1}}
control={