Run prettier on all the things

This commit is contained in:
nimbleghost 2023-05-29 22:25:01 +02:00
parent 4d40de58fc
commit c1ff20f5be
37 changed files with 2831 additions and 2231 deletions

View file

@ -1,26 +1,29 @@
--- ---
name: 🐛 Bug Report name: 🐛 Bug Report
about: Report any errors and problems about: Report any errors and problems
title: '' title: ""
labels: '🪲 bug' labels: "🪲 bug"
assignees: '' assignees: ""
--- ---
:lady_beetle: **Describe the bug** :lady_beetle: **Describe the bug**
<!-- A clear and concise description of the problem. --> <!-- A clear and concise description of the problem. -->
:computer: **Components impacted** :computer: **Components impacted**
<!-- ntfy server, Android app, iOS app, web app --> <!-- ntfy server, Android app, iOS app, web app -->
:bulb: **Screenshots and/or logs** :bulb: **Screenshots and/or logs**
<!--
<!--
If applicable, add screenshots or share logs help explain your problem. If applicable, add screenshots or share logs help explain your problem.
To get logs from the ... To get logs from the ...
- ntfy server: Enable "log-level: trace" in your server.yml file - ntfy server: Enable "log-level: trace" in your server.yml file
- Android app: Go to "Settings" -> "Record logs", then eventually "Copy/upload logs" - Android app: Go to "Settings" -> "Record logs", then eventually "Copy/upload logs"
- web app: Press "F12" and find the "Console" window - web app: Press "F12" and find the "Console" window
--> -->
:crystal_ball: **Additional context** :crystal_ball: **Additional context**
<!-- Add any other context about the problem here. --> <!-- Add any other context about the problem here. -->

View file

@ -1,10 +1,9 @@
--- ---
name: 💡 Feature/Enhancement Request name: 💡 Feature/Enhancement Request
about: Got a great idea? Let us know! about: Got a great idea? Let us know!
title: '' title: ""
labels: 'enhancement' labels: "enhancement"
assignees: '' assignees: ""
--- ---
<!-- <!--
@ -18,9 +17,10 @@ sooner, and there are more people there to help!
--> -->
:bulb: **Idea** :bulb: **Idea**
<!-- Share your thoughts; try to be detailed if you can --> <!-- Share your thoughts; try to be detailed if you can -->
:computer: **Target components** :computer: **Target components**
<!-- Where should this feature/enhancement be added? --> <!-- Where should this feature/enhancement be added? -->
<!-- e.g. ntfy server, Android app, iOS app, web app --> <!-- e.g. ntfy server, Android app, iOS app, web app -->

View file

@ -1,18 +1,16 @@
--- ---
name: 🆘 I need help with ... name: 🆘 I need help with ...
about: Installing ntfy, configuring the app, etc. about: Installing ntfy, configuring the app, etc.
title: '' title: ""
labels: 'tech-support' labels: "tech-support"
assignees: '' assignees: ""
--- ---
<!-- <!--
STOP! STOP!
This is not the right place to ask for help. Consider asking on Discord/Matrix instead. This is not the right place to ask for help. Consider asking on Discord/Matrix instead.
You'll usually get an answer sooner, and there are more people there to help! You'll usually get an answer sooner, and there are more people there to help!
- Discord: https://discord.gg/cT7ECsZj9w - Discord: https://discord.gg/cT7ECsZj9w

View file

@ -1,10 +1,9 @@
--- ---
name: ❓ Question name: ❓ Question
about: Ask a question about ntfy about: Ask a question about ntfy
title: '' title: ""
labels: 'question' labels: "question"
assignees: '' assignees: ""
--- ---
<!-- <!--
@ -18,4 +17,5 @@ sooner, and there are more people there to help!
--> -->
:question: **Question** :question: **Question**
<!-- Go ahead and ask your question here :) --> <!-- Go ahead and ask your question here :) -->

View file

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

View file

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

View file

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

View file

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

View file

@ -25,4 +25,4 @@ ports:
- name: binary - name: binary
port: 2586 port: 2586
- name: web - name: web
port: 3000 port: 3000

View file

@ -3,53 +3,48 @@ before:
- go mod download - go mod download
- go mod tidy - go mod tidy
builds: builds:
- - id: ntfy_linux_amd64
id: ntfy_linux_amd64
binary: ntfy binary: ntfy
env: env:
- CGO_ENABLED=1 # required for go-sqlite3 - CGO_ENABLED=1 # required for go-sqlite3
tags: [sqlite_omit_load_extension,osusergo,netgo] tags: [sqlite_omit_load_extension, osusergo, netgo]
ldflags: ldflags:
- "-linkmode=external -extldflags=-static -s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.Date}}" - "-linkmode=external -extldflags=-static -s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.Date}}"
goos: [linux] goos: [linux]
goarch: [amd64] goarch: [amd64]
- - id: ntfy_linux_armv6
id: ntfy_linux_armv6
binary: ntfy binary: ntfy
env: env:
- CGO_ENABLED=1 # required for go-sqlite3 - CGO_ENABLED=1 # required for go-sqlite3
- CC=arm-linux-gnueabi-gcc # apt install gcc-arm-linux-gnueabi - CC=arm-linux-gnueabi-gcc # apt install gcc-arm-linux-gnueabi
tags: [sqlite_omit_load_extension,osusergo,netgo] tags: [sqlite_omit_load_extension, osusergo, netgo]
ldflags: ldflags:
- "-linkmode=external -extldflags=-static -s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.Date}}" - "-linkmode=external -extldflags=-static -s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.Date}}"
goos: [linux] goos: [linux]
goarch: [arm] goarch: [arm]
goarm: [6] goarm: [6]
- - id: ntfy_linux_armv7
id: ntfy_linux_armv7
binary: ntfy binary: ntfy
env: env:
- CGO_ENABLED=1 # required for go-sqlite3 - CGO_ENABLED=1 # required for go-sqlite3
- CC=arm-linux-gnueabi-gcc # apt install gcc-arm-linux-gnueabi - CC=arm-linux-gnueabi-gcc # apt install gcc-arm-linux-gnueabi
tags: [sqlite_omit_load_extension,osusergo,netgo] tags: [sqlite_omit_load_extension, osusergo, netgo]
ldflags: ldflags:
- "-linkmode=external -extldflags=-static -s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.Date}}" - "-linkmode=external -extldflags=-static -s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.Date}}"
goos: [linux] goos: [linux]
goarch: [arm] goarch: [arm]
goarm: [7] goarm: [7]
- - id: ntfy_linux_arm64
id: ntfy_linux_arm64
binary: ntfy binary: ntfy
env: env:
- CGO_ENABLED=1 # required for go-sqlite3 - CGO_ENABLED=1 # required for go-sqlite3
- CC=aarch64-linux-gnu-gcc # apt install gcc-aarch64-linux-gnu - CC=aarch64-linux-gnu-gcc # apt install gcc-aarch64-linux-gnu
tags: [sqlite_omit_load_extension,osusergo,netgo] tags: [sqlite_omit_load_extension, osusergo, netgo]
ldflags: ldflags:
- "-linkmode=external -extldflags=-static -s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.Date}}" - "-linkmode=external -extldflags=-static -s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.Date}}"
goos: [linux] goos: [linux]
goarch: [arm64] goarch: [arm64]
- - id: ntfy_windows_amd64
id: ntfy_windows_amd64
binary: ntfy binary: ntfy
env: env:
- CGO_ENABLED=0 # explicitly disable, since we don't need go-sqlite3 - CGO_ENABLED=0 # explicitly disable, since we don't need go-sqlite3
@ -58,8 +53,7 @@ builds:
- "-X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.Date}}" - "-X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.Date}}"
goos: [windows] goos: [windows]
goarch: [amd64] goarch: [amd64]
- - id: ntfy_darwin_all
id: ntfy_darwin_all
binary: ntfy binary: ntfy
env: env:
- CGO_ENABLED=0 # explicitly disable, since we don't need go-sqlite3 - CGO_ENABLED=0 # explicitly disable, since we don't need go-sqlite3
@ -69,8 +63,7 @@ builds:
goos: [darwin] goos: [darwin]
goarch: [amd64, arm64] # will be combined to "universal binary" (see below) goarch: [amd64, arm64] # will be combined to "universal binary" (see below)
nfpms: nfpms:
- - package_name: ntfy
package_name: ntfy
homepage: https://heckel.io/ntfy homepage: https://heckel.io/ntfy
maintainer: Philipp C. Heckel <philipp.heckel@gmail.com> maintainer: Philipp C. Heckel <philipp.heckel@gmail.com>
description: Simple pub-sub notification service description: Simple pub-sub notification service
@ -104,8 +97,7 @@ nfpms:
preremove: "scripts/prerm.sh" preremove: "scripts/prerm.sh"
postremove: "scripts/postrm.sh" postremove: "scripts/postrm.sh"
archives: archives:
- - id: ntfy_linux
id: ntfy_linux
builds: builds:
- ntfy_linux_amd64 - ntfy_linux_amd64
- ntfy_linux_armv6 - ntfy_linux_armv6
@ -121,8 +113,7 @@ archives:
- client/ntfy-client.service - client/ntfy-client.service
replacements: replacements:
amd64: x86_64 amd64: x86_64
- - id: ntfy_windows
id: ntfy_windows
builds: builds:
- ntfy_windows_amd64 - ntfy_windows_amd64
format: zip format: zip
@ -133,8 +124,7 @@ archives:
- client/client.yml - client/client.yml
replacements: replacements:
amd64: x86_64 amd64: x86_64
- - id: ntfy_darwin
id: ntfy_darwin
builds: builds:
- ntfy_darwin_all - ntfy_darwin_all
wrap_in_directory: true wrap_in_directory: true
@ -145,20 +135,19 @@ archives:
replacements: replacements:
darwin: macOS darwin: macOS
universal_binaries: universal_binaries:
- - id: ntfy_darwin_all
id: ntfy_darwin_all
replace: true replace: true
name_template: ntfy name_template: ntfy
checksum: checksum:
name_template: 'checksums.txt' name_template: "checksums.txt"
snapshot: snapshot:
name_template: "{{ .Tag }}-next" name_template: "{{ .Tag }}-next"
changelog: changelog:
sort: asc sort: asc
filters: filters:
exclude: exclude:
- '^docs:' - "^docs:"
- '^test:' - "^test:"
dockers: dockers:
- image_templates: - image_templates:
- &amd64_image "binwiederhier/ntfy:{{ .Tag }}-amd64" - &amd64_image "binwiederhier/ntfy:{{ .Tag }}-amd64"

View file

@ -17,23 +17,23 @@ diverse, inclusive, and healthy community.
Examples of behavior that contributes to a positive environment for our Examples of behavior that contributes to a positive environment for our
community include: community include:
* Demonstrating empathy and kindness toward other people - Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences - Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback - Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes, - Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience and learning from the experience
* Focusing on what is best not just for us as individuals, but for the overall - Focusing on what is best not just for us as individuals, but for the overall
community community
Examples of unacceptable behavior include: Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or advances of - The use of sexualized language or imagery, and sexual attention or advances of
any kind any kind
* Trolling, insulting or derogatory comments, and personal or political attacks - Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment - Public or private harassment
* Publishing others' private information, such as a physical or email address, - Publishing others' private information, such as a physical or email address,
without their explicit permission without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a - Other conduct which could reasonably be considered inappropriate in a
professional setting professional setting
## Enforcement Responsibilities ## Enforcement Responsibilities
@ -60,7 +60,7 @@ representative at an online or offline event.
Instances of abusive, harassing, or otherwise unacceptable behavior may be Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement via Discord/Matrix (binwiederhier), 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 or email (ntfy@heckel.io). All complaints will be reviewed and investigated promptly
and fairly. and fairly.
All community leaders are obligated to respect the privacy and security of the All community leaders are obligated to respect the privacy and security of the
@ -130,4 +130,3 @@ For answers to common questions about this code of conduct, see the FAQ at
[Mozilla CoC]: https://github.com/mozilla/diversity [Mozilla CoC]: https://github.com/mozilla/diversity
[FAQ]: https://www.contributor-covenant.org/faq [FAQ]: https://www.contributor-covenant.org/faq
[translations]: https://www.contributor-covenant.org/translations [translations]: https://www.contributor-covenant.org/translations

View file

@ -1,6 +1,7 @@
![ntfy](web/public/static/images/ntfy.png) ![ntfy](web/public/static/images/ntfy.png)
# ntfy.sh | Send push notifications to your phone or desktop via PUT/POST # 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) [![Release](https://img.shields.io/github/release/binwiederhier/ntfy.svg?color=success&style=flat-square)](https://github.com/binwiederhier/ntfy/releases/latest)
[![Go Reference](https://pkg.go.dev/badge/heckel.io/ntfy.svg)](https://pkg.go.dev/heckel.io/ntfy) [![Go Reference](https://pkg.go.dev/badge/heckel.io/ntfy.svg)](https://pkg.go.dev/heckel.io/ntfy)
[![Tests](https://github.com/binwiederhier/ntfy/workflows/test/badge.svg)](https://github.com/binwiederhier/ntfy/actions) [![Tests](https://github.com/binwiederhier/ntfy/workflows/test/badge.svg)](https://github.com/binwiederhier/ntfy/actions)
@ -13,9 +14,9 @@
[![Healthcheck](https://healthchecks.io/badge/68b65976-b3b0-4102-aec9-980921/kcoEgrLY.svg)](https://ntfy.statuspage.io/) [![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) [![Gitpod](https://img.shields.io/badge/Contribute%20with-Gitpod-908a85?logo=gitpod)](https://gitpod.io/#https://github.com/binwiederhier/ntfy)
**ntfy** (pronounced "*notify*") is a simple HTTP-based [pub-sub](https://en.wikipedia.org/wiki/Publish%E2%80%93subscribe_pattern) **ntfy** (pronounced "_notify_") is a simple HTTP-based [pub-sub](https://en.wikipedia.org/wiki/Publish%E2%80%93subscribe_pattern)
notification service. With ntfy, you can **send notifications to your phone or desktop via scripts** from any computer, notification service. With ntfy, you can **send notifications to your phone or desktop via scripts** from any computer,
**without having to sign up or pay any fees**. If you'd like to run your own instance of the service, you can easily do **without having to sign up or pay any fees**. If you'd like to run your own instance of the service, you can easily do
so since ntfy is open source. so since ntfy is open source.
You can access the free version of ntfy at **[ntfy.sh](https://ntfy.sh)**. There is also an [open source Android app](https://github.com/binwiederhier/ntfy-android) You can access the free version of ntfy at **[ntfy.sh](https://ntfy.sh)**. There is also an [open source Android app](https://github.com/binwiederhier/ntfy-android)
@ -31,7 +32,8 @@ as well as an [open source iOS app](https://github.com/binwiederhier/ntfy-ios) a
</p> </p>
## [ntfy Pro](https://ntfy.sh/app) 💸 🎉 ## [ntfy Pro](https://ntfy.sh/app) 💸 🎉
I now offer paid plans for [ntfy.sh](https://ntfy.sh/) if you don't want to self-host, or you want to support the development of ntfy (→ [Purchase via web app](https://ntfy.sh/app)). You can **buy a plan for as low as $3.33/month** (if you use promo code `MYTOPIC`, limited time only). You can also donate via [GitHub Sponsors](https://github.com/sponsors/binwiederhier), and [Liberapay](https://liberapay.com/ntfy). I would be very humbled by your sponsorship. ❤️
I now offer paid plans for [ntfy.sh](https://ntfy.sh/) if you don't want to self-host, or you want to support the development of ntfy (→ [Purchase via web app](https://ntfy.sh/app)). You can **buy a plan for as low as $3.33/month** (if you use promo code `MYTOPIC`, limited time only). You can also donate via [GitHub Sponsors](https://github.com/sponsors/binwiederhier), and [Liberapay](https://liberapay.com/ntfy). I would be very humbled by your sponsorship. ❤️
## **[Documentation](https://ntfy.sh/docs/)** ## **[Documentation](https://ntfy.sh/docs/)**
@ -42,23 +44,26 @@ I now offer paid plans for [ntfy.sh](https://ntfy.sh/) if you don't want to self
[Building](https://ntfy.sh/docs/develop/) [Building](https://ntfy.sh/docs/develop/)
## Chat / forum ## Chat / forum
There are a few ways to get in touch with me and/or the rest of the community. Feel free to use any of these methods. Whatever There are a few ways to get in touch with me and/or the rest of the community. Feel free to use any of these methods. Whatever
works best for you: works best for you:
* [Discord server](https://discord.gg/cT7ECsZj9w) - direct chat with the community - [Discord server](https://discord.gg/cT7ECsZj9w) - direct chat with the community
* [Matrix room #ntfy](https://matrix.to/#/#ntfy:matrix.org) (+ [Matrix space](https://matrix.to/#/#ntfy-space:matrix.org)) - same chat, bridged from Discord - [Matrix room #ntfy](https://matrix.to/#/#ntfy:matrix.org) (+ [Matrix space](https://matrix.to/#/#ntfy-space:matrix.org)) - same chat, bridged from Discord
* [Reddit r/ntfy](https://www.reddit.com/r/ntfy/) - asynchronous forum (_new as of October 2022_) - [Reddit r/ntfy](https://www.reddit.com/r/ntfy/) - asynchronous forum (_new as of October 2022_)
* [GitHub issues](https://github.com/binwiederhier/ntfy/issues) - questions, features, bugs - [GitHub issues](https://github.com/binwiederhier/ntfy/issues) - questions, features, bugs
* [Email](https://heckel.io/about) - reach me directly (_I usually prefer the other methods_) - [Email](https://heckel.io/about) - reach me directly (_I usually prefer the other methods_)
## Announcements / beta testers ## Announcements / beta testers
For announcements of new releases and cutting-edge beta versions, please subscribe to the [ntfy.sh/announcements](https://ntfy.sh/announcements)
For announcements of new releases and cutting-edge beta versions, please subscribe to the [ntfy.sh/announcements](https://ntfy.sh/announcements)
topic. If you'd like to test the iOS app, join [TestFlight](https://testflight.apple.com/join/P1fFnAm9). For Android betas, topic. If you'd like to test the iOS app, join [TestFlight](https://testflight.apple.com/join/P1fFnAm9). For Android betas,
join Discord/Matrix (I'll eventually make a testing channel in Google Play). join Discord/Matrix (I'll eventually make a testing channel in Google Play).
## Contributing ## Contributing
I welcome any and all contributions. Just create a PR or an issue. For larger features/ideas, please reach out 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/) 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 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/). [Hosted Weblate](https://hosted.weblate.org/projects/ntfy/).
@ -67,8 +72,9 @@ for the server and the Android app. Or, if you'd like to help translate 🇩🇪
</a> </a>
## Sponsors ## Sponsors
I have just very recently started accepting donations via [GitHub Sponsors](https://github.com/sponsors/binwiederhier), I have just very recently started accepting donations via [GitHub Sponsors](https://github.com/sponsors/binwiederhier),
and [Liberapay](https://liberapay.com/ntfy). I would be humbled if you helped me carry the server and developer and [Liberapay](https://liberapay.com/ntfy). I would be humbled if you helped me carry the server and developer
account costs. Even small donations are very much appreciated. A big fat **Thank You** to the folks already sponsoring ntfy: account costs. Even small donations are very much appreciated. A big fat **Thank You** to the folks already sponsoring ntfy:
<a href="https://github.com/neutralinsomniac"><img src="https://github.com/neutralinsomniac.png" width="40px" /></a> <a href="https://github.com/neutralinsomniac"><img src="https://github.com/neutralinsomniac.png" width="40px" /></a>
@ -143,38 +149,41 @@ account costs. Even small donations are very much appreciated. A big fat **Thank
<a href="https://github.com/jonathan-kosgei"><img src="https://github.com/jonathan-kosgei.png" width="40px" /></a> <a href="https://github.com/jonathan-kosgei"><img src="https://github.com/jonathan-kosgei.png" width="40px" /></a>
I'd also like to thank JetBrains for providing their awesome [IntelliJ IDEA](https://www.jetbrains.com/idea/) to me for free, I'd also like to thank JetBrains for providing their awesome [IntelliJ IDEA](https://www.jetbrains.com/idea/) to me for free,
and [DigitalOcean](https://m.do.co/c/442b929528db) (*referral link*) for supporting the project: and [DigitalOcean](https://m.do.co/c/442b929528db) (_referral link_) for supporting the project:
<a href="https://m.do.co/c/442b929528db"><img src="https://opensource.nyc3.cdn.digitaloceanspaces.com/attribution/assets/SVG/DO_Logo_horizontal_blue.svg" width="201px"></a> <a href="https://m.do.co/c/442b929528db"><img src="https://opensource.nyc3.cdn.digitaloceanspaces.com/attribution/assets/SVG/DO_Logo_horizontal_blue.svg" width="201px"></a>
## Code of Conduct ## 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 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.** **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)._ _Please be sure to read the complete [Code of Conduct](CODE_OF_CONDUCT.md)._
## License ## License
Made with ❤️ by [Philipp C. Heckel](https://heckel.io).
Made with ❤️ by [Philipp C. Heckel](https://heckel.io).
The project is dual licensed under the [Apache License 2.0](LICENSE) and the [GPLv2 License](LICENSE.GPLv2). The project is dual licensed under the [Apache License 2.0](LICENSE) and the [GPLv2 License](LICENSE.GPLv2).
Third party libraries and resources: Third party libraries and resources:
* [github.com/urfave/cli](https://github.com/urfave/cli) (MIT) is used to drive the CLI
* [Mixkit sounds](https://mixkit.co/free-sound-effects/notification/) (Mixkit Free License) are used as notification sounds - [github.com/urfave/cli](https://github.com/urfave/cli) (MIT) is used to drive the CLI
* [Sounds from notificationsounds.com](https://notificationsounds.com) (Creative Commons Attribution) are used as notification sounds - [Mixkit sounds](https://mixkit.co/free-sound-effects/notification/) (Mixkit Free License) are used as notification sounds
* [Roboto Font](https://fonts.google.com/specimen/Roboto) (Apache 2.0) is used as a font in everything web - [Sounds from notificationsounds.com](https://notificationsounds.com) (Creative Commons Attribution) are used as notification sounds
* [React](https://reactjs.org/) (MIT) is used for the web app - [Roboto Font](https://fonts.google.com/specimen/Roboto) (Apache 2.0) is used as a font in everything web
* [Material UI components](https://mui.com/) (MIT) are used in the web app - [React](https://reactjs.org/) (MIT) is used for the web app
* [MUI dashboard template](https://github.com/mui/material-ui/tree/master/docs/data/material/getting-started/templates/dashboard) (MIT) was used as a basis for the web app - [Material UI components](https://mui.com/) (MIT) are used in the web app
* [Dexie.js](https://github.com/dexie/Dexie.js) (Apache 2.0) is used for web app persistence in IndexedDB - [MUI dashboard template](https://github.com/mui/material-ui/tree/master/docs/data/material/getting-started/templates/dashboard) (MIT) was used as a basis for the web app
* [GoReleaser](https://goreleaser.com/) (MIT) is used to create releases - [Dexie.js](https://github.com/dexie/Dexie.js) (Apache 2.0) is used for web app persistence in IndexedDB
* [go-smtp](https://github.com/emersion/go-smtp) (MIT) is used to receive e-mails - [GoReleaser](https://goreleaser.com/) (MIT) is used to create releases
* [stretchr/testify](https://github.com/stretchr/testify) (MIT) is used for unit and integration tests - [go-smtp](https://github.com/emersion/go-smtp) (MIT) is used to receive e-mails
* [github.com/mattn/go-sqlite3](https://github.com/mattn/go-sqlite3) (MIT) is used to provide the persistent message cache - [stretchr/testify](https://github.com/stretchr/testify) (MIT) is used for unit and integration tests
* [Firebase Admin SDK](https://github.com/firebase/firebase-admin-go) (Apache 2.0) is used to send FCM messages - [github.com/mattn/go-sqlite3](https://github.com/mattn/go-sqlite3) (MIT) is used to provide the persistent message cache
* [github/gemoji](https://github.com/github/gemoji) (MIT) is used for emoji support (specifically the [emoji.json](https://raw.githubusercontent.com/github/gemoji/master/db/emoji.json) file) - [Firebase Admin SDK](https://github.com/firebase/firebase-admin-go) (Apache 2.0) is used to send FCM messages
* [Lightbox with vanilla JS](https://yossiabramov.com/blog/vanilla-js-lightbox) as a lightbox on the landing page - [github/gemoji](https://github.com/github/gemoji) (MIT) is used for emoji support (specifically the [emoji.json](https://raw.githubusercontent.com/github/gemoji/master/db/emoji.json) file)
* [HTTP middleware for gzip compression](https://gist.github.com/CJEnright/bc2d8b8dc0c1389a9feeddb110f822d7) (MIT) is used for serving static files - [Lightbox with vanilla JS](https://yossiabramov.com/blog/vanilla-js-lightbox) as a lightbox on the landing page
* [Regex for auto-linking](https://github.com/bryanwoods/autolink-js) (MIT) is used to highlight links (the library is not used) - [HTTP middleware for gzip compression](https://gist.github.com/CJEnright/bc2d8b8dc0c1389a9feeddb110f822d7) (MIT) is used for serving static files
* [Statically linking go-sqlite3](https://www.arp242.net/static-go.html) - [Regex for auto-linking](https://github.com/bryanwoods/autolink-js) (MIT) is used to highlight links (the library is not used)
* [Linked tabs in mkdocs](https://facelessuser.github.io/pymdown-extensions/extensions/tabbed/#linked-tabs) - [Statically linking go-sqlite3](https://www.arp242.net/static-go.html)
- [Linked tabs in mkdocs](https://facelessuser.github.io/pymdown-extensions/extensions/tabbed/#linked-tabs)

View file

@ -6,7 +6,7 @@ services:
command: command:
- serve - serve
environment: environment:
- TZ=UTC # optional: Change to your desired timezone - TZ=UTC # optional: Change to your desired timezone
user: UID:GID # optional: Set custom user/group or uid/gid user: UID:GID # optional: Set custom user/group or uid/gid
volumes: volumes:
- /var/cache/ntfy:/var/cache/ntfy - /var/cache/ntfy:/var/cache/ntfy
@ -14,4 +14,3 @@ services:
ports: ports:
- 80:80 - 80:80
restart: unless-stopped restart: unless-stopped

View file

@ -1,50 +1,77 @@
{% extends "base.html" %} {% extends "base.html" %} {% block announce %}
{% block announce %}
<style> <style>
div[data-md-component="announce"] { div[data-md-component="announce"] {
z-index: 10; z-index: 10;
} }
div[data-md-component="announce"] a { div[data-md-component="announce"] a {
color: white; color: white;
} }
div[data-md-component="announce"] a:hover, div[data-md-component="announce"] a:focus { div[data-md-component="announce"] a:hover,
transition: ease-in 150ms; div[data-md-component="announce"] a:focus {
color: #ccc; transition: ease-in 150ms;
} color: #ccc;
}
div[data-md-component="announce"] .md-banner__button { div[data-md-component="announce"] .md-banner__button {
color: #ccc; color: #ccc;
} }
div[data-md-component="announce"] .md-banner.hidden { div[data-md-component="announce"] .md-banner.hidden {
display: none; display: none;
} }
div[data-md-component="announce"] .twemoji { div[data-md-component="announce"] .twemoji {
margin-top: 2px; margin-top: 2px;
} }
</style> </style>
<button id="announce-bar-close" class="md-banner__button md-icon" aria-label="Don't show this again"> <button
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> id="announce-bar-close"
<path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41Z"></path> class="md-banner__button md-icon"
</svg> aria-label="Don't show this again"
>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path
d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41Z"
></path>
</svg>
</button> </button>
If you like ntfy, please consider sponsoring me via <a target="_blank" href="https://github.com/sponsors/binwiederhier"><strong>GitHub Sponsors</strong></a> If you like ntfy, please consider sponsoring me via
or <a target="_blank" href="https://en.liberapay.com/ntfy/"><strong>Liberapay</strong></a> <a target="_blank" href="https://github.com/sponsors/binwiederhier"
<svg xmlns="http://www.w3.org/2000/svg" role="img" viewBox="0 0 36 36" class="twemoji md-footer-custom-text"> ><strong>GitHub Sponsors</strong></a
<path fill="#DD2E44" d="M35.885 11.833c0-5.45-4.418-9.868-9.867-9.868-3.308 0-6.227 1.633-8.018 4.129-1.791-2.496-4.71-4.129-8.017-4.129-5.45 0-9.868 4.417-9.868 9.868 0 .772.098 1.52.266 2.241C1.751 22.587 11.216 31.568 18 34.034c6.783-2.466 16.249-11.447 17.617-19.959.17-.721.268-1.469.268-2.242z"/> >
</svg>, or subscribing to <a target="_blank" href="https://ntfy.sh/app"><strong>ntfy Pro</strong></a>. or
<a target="_blank" href="https://en.liberapay.com/ntfy/"
><strong>Liberapay</strong></a
>
<svg
xmlns="http://www.w3.org/2000/svg"
role="img"
viewBox="0 0 36 36"
class="twemoji md-footer-custom-text"
>
<path
fill="#DD2E44"
d="M35.885 11.833c0-5.45-4.418-9.868-9.867-9.868-3.308 0-6.227 1.633-8.018 4.129-1.791-2.496-4.71-4.129-8.017-4.129-5.45 0-9.868 4.417-9.868 9.868 0 .772.098 1.52.266 2.241C1.751 22.587 11.216 31.568 18 34.034c6.783-2.466 16.249-11.447 17.617-19.959.17-.721.268-1.469.268-2.242z"
/></svg
>, or subscribing to
<a target="_blank" href="https://ntfy.sh/app"><strong>ntfy Pro</strong></a
>.
<script> <script>
announceBarKey = 'announce-bar-closed-sponsor'; announceBarKey = "announce-bar-closed-sponsor";
document.getElementById('announce-bar-close').addEventListener('click', (e) => { document
localStorage.setItem(announceBarKey, 'true'); .getElementById("announce-bar-close")
document.querySelector('div[data-md-component="announce"] .md-banner').style.display = 'none'; .addEventListener("click", (e) => {
localStorage.setItem(announceBarKey, "true");
document.querySelector(
'div[data-md-component="announce"] .md-banner'
).style.display = "none";
}); });
if (localStorage.getItem(announceBarKey) === 'true') { if (localStorage.getItem(announceBarKey) === "true") {
document.querySelector('div[data-md-component="announce"] .md-banner').style.display = 'none'; document.querySelector(
} 'div[data-md-component="announce"] .md-banner'
).style.display = "none";
}
</script> </script>
{% endblock %} {% endblock %}

File diff suppressed because it is too large Load diff

View file

@ -1,30 +1,36 @@
# Deprecation notices # Deprecation notices
This page is used to list deprecation notices for ntfy. Deprecated commands and options will be
This page is used to list deprecation notices for ntfy. Deprecated commands and options will be
**removed after 1-3 months** from the time they were deprecated. How long the feature is deprecated **removed after 1-3 months** from the time they were deprecated. How long the feature is deprecated
before the behavior is changed depends on the severity of the change, and how prominent the feature is. before the behavior is changed depends on the severity of the change, and how prominent the feature is.
## Active deprecations ## Active deprecations
_No active deprecations_ _No active deprecations_
## Previous deprecations ## Previous deprecations
### ntfy CLI: `ntfy publish --env-topic` will be removed ### ntfy CLI: `ntfy publish --env-topic` will be removed
> Active since 2022-06-20, behavior changed with v1.30.1 > 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. `NTFY_TOPIC` environment variable, but it won't be necessary anymore to specify the `--env-topic` flag.
=== "Before" === "Before"
```
```sh
$ NTFY_TOPIC=mytopic ntfy publish --env-topic "this is the message" $ NTFY_TOPIC=mytopic ntfy publish --env-topic "this is the message"
``` ```
=== "After" === "After"
```
```sh
$ NTFY_TOPIC=mytopic ntfy publish "this is the message" $ NTFY_TOPIC=mytopic ntfy publish "this is the message"
``` ```
### <del>Android app: WebSockets will become the default connection protocol</del> ### <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) > Active since 2022-03-13, behavior will not change (deprecation removed 2022-06-20)
Instant delivery connections and connections to self-hosted servers in the Android app were going to switch Instant delivery connections and connections to self-hosted servers in the Android app were going to switch
@ -32,14 +38,16 @@ to use the WebSockets protocol by default. It was decided to keep JSON stream as
and add a notice banner in the Android app instead. and add a notice banner in the Android app instead.
### Android app: Using `since=<timestamp>` instead of `since=<id>` ### Android app: Using `since=<timestamp>` instead of `since=<id>`
> Active since 2022-02-27, behavior changed with v1.14.0 > Active since 2022-02-27, behavior changed with v1.14.0
The Android app started using `since=<id>` instead of `since=<timestamp>`, which means as of Android app v1.14.0, The Android app started using `since=<id>` instead of `since=<timestamp>`, which means as of Android app v1.14.0,
it will not work with servers older than v1.16.0 anymore. This is to simplify handling of deduplication in the Android app. it will not work with servers older than v1.16.0 anymore. This is to simplify handling of deduplication in the Android app.
The `since=<timestamp>` endpoint will continue to work. This is merely a notice that the Android app behavior will change. The `since=<timestamp>` endpoint will continue to work. This is merely a notice that the Android app behavior will change.
### Running server via `ntfy` (instead of `ntfy serve`) ### Running server via `ntfy` (instead of `ntfy serve`)
> Deprecated 2021-12-17, behavior changed with v1.10.0 > Deprecated 2021-12-17, behavior changed with v1.10.0
As more commands are added to the `ntfy` CLI tool, using just `ntfy` to run the server is not practical As more commands are added to the `ntfy` CLI tool, using just `ntfy` to run the server is not practical
@ -47,14 +55,15 @@ anymore. Please use `ntfy serve` instead. This also applies to Docker images, as
just the server. just the server.
=== "Before" === "Before"
```
```sh
$ ntfy $ ntfy
2021/12/17 08:16:01 Listening on :80/http 2021/12/17 08:16:01 Listening on :80/http
``` ```
=== "After" === "After"
```
```sh
$ ntfy serve $ ntfy serve
2021/12/17 08:16:01 Listening on :80/http 2021/12/17 08:16:01 Listening on :80/http
``` ```

View file

@ -1,50 +1,53 @@
# Development # Development
Hurray 🥳 🎉, you are interested in writing code for ntfy! **That's awesome.** 😎 Hurray 🥳 🎉, you are interested in writing code for ntfy! **That's awesome.** 😎
I tried my very best to write up detailed instructions, but if at any point in time you run into issues, don't I tried my very best to write up detailed instructions, but if at any point in time you run into issues, don't
hesitate to **contact me on [Discord](https://discord.gg/cT7ECsZj9w) or [Matrix](https://matrix.to/#/#ntfy:matrix.org)**. hesitate to **contact me on [Discord](https://discord.gg/cT7ECsZj9w) or [Matrix](https://matrix.to/#/#ntfy:matrix.org)**.
## ntfy server ## ntfy server
The ntfy server source code is available [on GitHub](https://github.com/binwiederhier/ntfy). The codebase for the The ntfy server source code is available [on GitHub](https://github.com/binwiederhier/ntfy). The codebase for the
server consists of three components: server consists of three components:
* **The main server/client** is written in [Go](https://go.dev/) (so you'll need Go). Its main entrypoint is at - **The main server/client** is written in [Go](https://go.dev/) (so you'll need Go). Its main entrypoint is at
[main.go](https://github.com/binwiederhier/ntfy/blob/main/main.go), and the meat you're likely interested in is [main.go](https://github.com/binwiederhier/ntfy/blob/main/main.go), and the meat you're likely interested in is
in [server.go](https://github.com/binwiederhier/ntfy/blob/main/server/server.go). Notably, the server uses a in [server.go](https://github.com/binwiederhier/ntfy/blob/main/server/server.go). Notably, the server uses a
[SQLite](https://sqlite.org) library called [go-sqlite3](https://github.com/mattn/go-sqlite3), which requires [SQLite](https://sqlite.org) library called [go-sqlite3](https://github.com/mattn/go-sqlite3), which requires
[Cgo](https://go.dev/blog/cgo) and `CGO_ENABLED=1` to be set. Otherwise things will not work (see below). [Cgo](https://go.dev/blog/cgo) and `CGO_ENABLED=1` to be set. Otherwise things will not work (see below).
* **The documentation** is generated by [MkDocs](https://www.mkdocs.org/) and [Material for MkDocs](https://squidfunk.github.io/mkdocs-material/), - **The documentation** is generated by [MkDocs](https://www.mkdocs.org/) and [Material for MkDocs](https://squidfunk.github.io/mkdocs-material/),
which is written in [Python](https://www.python.org/). You'll need Python and MkDocs (via `pip`) only if you want to which is written in [Python](https://www.python.org/). You'll need Python and MkDocs (via `pip`) only if you want to
build the docs. build the docs.
* **The web app** is written in [React](https://reactjs.org/), using [MUI](https://mui.com/). It uses [Create React App](https://create-react-app.dev/) - **The web app** is written in [React](https://reactjs.org/), using [MUI](https://mui.com/). It uses [Create React App](https://create-react-app.dev/)
to build the production build. If you want to modify the web app, you need [nodejs](https://nodejs.org/en/) (for `npm`) to build the production build. If you want to modify the web app, you need [nodejs](https://nodejs.org/en/) (for `npm`)
and install all the 100,000 dependencies (*sigh*). and install all the 100,000 dependencies (_sigh_).
All of these components are built and then **baked into one binary**. All of these components are built and then **baked into one binary**.
### Navigating the code ### Navigating the code
Code: Code:
* [main.go](https://github.com/binwiederhier/ntfy/blob/main/main.go) - Main entrypoint into the CLI, for both server and client - [main.go](https://github.com/binwiederhier/ntfy/blob/main/main.go) - Main entrypoint into the CLI, for both server and client
* [cmd/](https://github.com/binwiederhier/ntfy/tree/main/cmd) - CLI commands, such as `serve` or `publish` - [cmd/](https://github.com/binwiederhier/ntfy/tree/main/cmd) - CLI commands, such as `serve` or `publish`
* [server/](https://github.com/binwiederhier/ntfy/tree/main/server) - The meat of the server logic - [server/](https://github.com/binwiederhier/ntfy/tree/main/server) - The meat of the server logic
* [docs/](https://github.com/binwiederhier/ntfy/tree/main/docs) - The [MkDocs](https://www.mkdocs.org/) documentation, also see `mkdocs.yml` - [docs/](https://github.com/binwiederhier/ntfy/tree/main/docs) - The [MkDocs](https://www.mkdocs.org/) documentation, also see `mkdocs.yml`
* [web/](https://github.com/binwiederhier/ntfy/tree/main/web) - The [React](https://reactjs.org/) application, also see `web/package.json` - [web/](https://github.com/binwiederhier/ntfy/tree/main/web) - The [React](https://reactjs.org/) application, also see `web/package.json`
Build related: Build related:
* [Makefile](https://github.com/binwiederhier/ntfy/blob/main/Makefile) - Main entrypoint for all things related to building - [Makefile](https://github.com/binwiederhier/ntfy/blob/main/Makefile) - Main entrypoint for all things related to building
* [.goreleaser.yml](https://github.com/binwiederhier/ntfy/blob/main/.goreleaser.yml) - Describes all build outputs (for [GoReleaser](https://goreleaser.com/)) - [.goreleaser.yml](https://github.com/binwiederhier/ntfy/blob/main/.goreleaser.yml) - Describes all build outputs (for [GoReleaser](https://goreleaser.com/))
* [go.mod](https://github.com/binwiederhier/ntfy/blob/main/go.mod) - Go modules dependency file - [go.mod](https://github.com/binwiederhier/ntfy/blob/main/go.mod) - Go modules dependency file
* [mkdocs.yml](https://github.com/binwiederhier/ntfy/blob/main/mkdocs.yml) - Config file for the docs (for [MkDocs](https://www.mkdocs.org/)) - [mkdocs.yml](https://github.com/binwiederhier/ntfy/blob/main/mkdocs.yml) - Config file for the docs (for [MkDocs](https://www.mkdocs.org/))
* [web/package.json](https://github.com/binwiederhier/ntfy/blob/main/web/package.json) - Build and dependency file for web app (for npm) - [web/package.json](https://github.com/binwiederhier/ntfy/blob/main/web/package.json) - Build and dependency file for web app (for npm)
The `web/` and `docs/` folder are the sources for web app and documentation. During the build process, 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). the generated output is copied to `server/site` (web app and landing page) and `server/docs` (documentation).
### Build/test on Gitpod ### Build/test on Gitpod
To get a quick working development environment you can use [Gitpod](https://gitpod.io), an in-browser IDE
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, 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/). I do suggest a proper IDE like [IntelliJ IDEA](https://www.jetbrains.com/idea/).
@ -52,19 +55,21 @@ I do suggest a proper IDE like [IntelliJ IDEA](https://www.jetbrains.com/idea/).
### Build requirements ### Build requirements
* [Go](https://go.dev/) (required for main server) - [Go](https://go.dev/) (required for main server)
* [gcc](https://gcc.gnu.org/) (required main server, for SQLite cgo-based bindings) - [gcc](https://gcc.gnu.org/) (required main server, for SQLite cgo-based bindings)
* [Make](https://www.gnu.org/software/make/) (required for convenience) - [Make](https://www.gnu.org/software/make/) (required for convenience)
* [libsqlite3/libsqlite3-dev](https://www.sqlite.org/) (required for main server, for SQLite cgo-based bindings) - [libsqlite3/libsqlite3-dev](https://www.sqlite.org/) (required for main server, for SQLite cgo-based bindings)
* [GoReleaser](https://goreleaser.com/) (required for a proper main server build) - [GoReleaser](https://goreleaser.com/) (required for a proper main server build)
* [Python](https://www.python.org/) (for `pip`, only to build the docs) - [Python](https://www.python.org/) (for `pip`, only to build the docs)
* [nodejs](https://nodejs.org/en/) (for `npm`, only to build the web app) - [nodejs](https://nodejs.org/en/) (for `npm`, only to build the web app)
### Install dependencies ### Install dependencies
These steps **assume Ubuntu**. Steps may vary on different Linux distributions. These steps **assume Ubuntu**. Steps may vary on different Linux distributions.
First, install [Go](https://go.dev/) (see [official instructions](https://go.dev/doc/install)): First, install [Go](https://go.dev/) (see [official instructions](https://go.dev/doc/install)):
``` shell
```shell
wget https://go.dev/dl/go1.19.1.linux-amd64.tar.gz wget https://go.dev/dl/go1.19.1.linux-amd64.tar.gz
sudo rm -rf /usr/local/go && sudo tar -C /usr/local -xzf go1.19.1.linux-amd64.tar.gz sudo rm -rf /usr/local/go && sudo tar -C /usr/local -xzf go1.19.1.linux-amd64.tar.gz
export PATH=$PATH:/usr/local/go/bin:$HOME/go/bin export PATH=$PATH:/usr/local/go/bin:$HOME/go/bin
@ -72,20 +77,23 @@ go version # verifies that it worked
``` ```
Install [GoReleaser](https://goreleaser.com/) (see [official instructions](https://goreleaser.com/install/)): Install [GoReleaser](https://goreleaser.com/) (see [official instructions](https://goreleaser.com/install/)):
``` shell
```shell
go install github.com/goreleaser/goreleaser@latest go install github.com/goreleaser/goreleaser@latest
goreleaser -v # verifies that it worked goreleaser -v # verifies that it worked
``` ```
Install [nodejs](https://nodejs.org/en/) (see [official instructions](https://nodejs.org/en/download/package-manager/)): Install [nodejs](https://nodejs.org/en/) (see [official instructions](https://nodejs.org/en/download/package-manager/)):
``` shell
```shell
curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash - curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash -
sudo apt-get install -y nodejs sudo apt-get install -y nodejs
npm -v # verifies that it worked npm -v # verifies that it worked
``` ```
Then install a few other things required: Then install a few other things required:
``` shell
```shell
sudo apt install \ sudo apt install \
build-essential \ build-essential \
libsqlite3-dev \ libsqlite3-dev \
@ -96,26 +104,30 @@ sudo apt install \
``` ```
### Check out code ### Check out code
Now check out via git from the [GitHub repository](https://github.com/binwiederhier/ntfy): Now check out via git from the [GitHub repository](https://github.com/binwiederhier/ntfy):
=== "via HTTPS" === "via HTTPS"
``` shell
```shell
git clone https://github.com/binwiederhier/ntfy.git git clone https://github.com/binwiederhier/ntfy.git
cd ntfy cd ntfy
``` ```
=== "via SSH" === "via SSH"
``` shell
git clone git@github.com:binwiederhier/ntfy.git ```shell
git clone git@github.com:binwiederhier/ntfy.git
cd ntfy cd ntfy
``` ```
### Build all the things ### Build all the things
Now you can finally build everything. There are tons of `make` targets, so maybe just review what's there first
Now you can finally build everything. There are tons of `make` targets, so maybe just review what's there first
by typing `make`: by typing `make`:
``` shell ```shell
$ make $ make
Typical commands (more see below): Typical commands (more see below):
make build - Build web app, documentation and server/client (sloowwww) make build - Build web app, documentation and server/client (sloowwww)
make cli-linux-amd64 - Build server/client binary (amd64, no web app or docs) make cli-linux-amd64 - Build server/client binary (amd64, no web app or docs)
@ -126,20 +138,20 @@ Typical commands (more see below):
... ...
``` ```
If you want to build the **ntfy binary including web app and docs for all supported architectures** (amd64, armv7, and arm64), If you want to build the **ntfy binary including web app and docs for all supported architectures** (amd64, armv7, and arm64),
you can simply run `make build`: you can simply run `make build`:
``` shell ```shell
$ make build $ make build
... ...
# This builds web app, docs, and the ntfy binary (for amd64, armv7 and arm64). # This builds web app, docs, and the ntfy binary (for amd64, armv7 and arm64).
# This will be SLOW (5+ minutes on my laptop on the first run). Maybe look at the other make targets? # This will be SLOW (5+ minutes on my laptop on the first run). Maybe look at the other make targets?
``` ```
You'll see all the outputs in the `dist/` folder afterwards: You'll see all the outputs in the `dist/` folder afterwards:
``` bash ```bash
$ find dist $ find dist
dist dist
dist/metadata.json dist/metadata.json
dist/ntfy_arm64_linux_arm64 dist/ntfy_arm64_linux_arm64
@ -152,10 +164,10 @@ dist/config.yaml
dist/artifacts.json dist/artifacts.json
``` ```
If you also want to build the **Debian/RPM packages and the Docker images for all supported architectures**, you can If you also want to build the **Debian/RPM packages and the Docker images for all supported architectures**, you can
use the `make release-snapshot` target: use the `make release-snapshot` target:
``` shell ```shell
$ make release-snapshot $ make release-snapshot
... ...
# This will be REALLY SLOW (sometimes 5+ minutes on my laptop) # This will be REALLY SLOW (sometimes 5+ minutes on my laptop)
@ -167,15 +179,16 @@ During development, you may want to be more picky and build only certain things.
This is useful to test the final build with web app, docs, and server without any dependencies locally This is useful to test the final build with web app, docs, and server without any dependencies locally
``` shell ```shell
$ make docker-dev $ make docker-dev
$ docker run --rm -p 80:80 binwiederhier/ntfy:dev serve $ docker run --rm -p 80:80 binwiederhier/ntfy:dev serve
``` ```
### Build the ntfy binary ### Build the ntfy binary
To build only the `ntfy` binary **without the web app or documentation**, use the `make cli-...` targets: To build only the `ntfy` binary **without the web app or documentation**, use the `make cli-...` targets:
``` shell ```shell
$ make $ make
Build server & client (using GoReleaser, not release version): Build server & client (using GoReleaser, not release version):
make cli - Build server & client (all architectures) make cli - Build server & client (all architectures)
@ -191,15 +204,15 @@ So if you're on an amd64/x86_64-based machine, you may just want to run `make cl
system, this shouldn't take longer than 5-10 seconds. I often combine it with `install-linux-amd64` so I can run the binary system, this shouldn't take longer than 5-10 seconds. I often combine it with `install-linux-amd64` so I can run the binary
right away: right away:
``` shell ```shell
$ make cli-linux-amd64 install-linux-amd64 $ make cli-linux-amd64 install-linux-amd64
$ ntfy serve $ ntfy serve
``` ```
**During development of the main app, you can also just use `go run main.go`**, as long as you run **During development of the main app, you can also just use `go run main.go`**, as long as you run
`make cli-deps-static-sites`at least once and `CGO_ENABLED=1`: `make cli-deps-static-sites`at least once and `CGO_ENABLED=1`:
``` shell ```shell
$ export CGO_ENABLED=1 $ export CGO_ENABLED=1
$ make cli-deps-static-sites $ make cli-deps-static-sites
$ go run main.go serve $ go run main.go serve
@ -207,7 +220,8 @@ $ go run main.go serve
... ...
``` ```
If you don't run `cli-deps-static-sites`, you may see an error *`pattern ...: no matching files found`*: If you don't run `cli-deps-static-sites`, you may see an error _`pattern ...: no matching files found`_:
``` ```
$ go run main.go serve $ go run main.go serve
server/server.go:85:13: pattern docs: no matching files found server/server.go:85:13: pattern docs: no matching files found
@ -217,41 +231,43 @@ This is because we use `go:embed` to embed the documentation and web app, so the
present at `server/docs` and `server/site`. If they are not, you'll see the above error. The `cli-deps-static-sites` present at `server/docs` and `server/site`. If they are not, you'll see the above error. The `cli-deps-static-sites`
target creates dummy files that ensure that you'll be able to build. target creates dummy files that ensure that you'll be able to build.
While not officially supported (or released), you can build and run the server **on macOS** as well. Simply run While not officially supported (or released), you can build and run the server **on macOS** as well. Simply run
`make cli-darwin-server` to build a binary, or `go run main.go serve` (see above) to run it. `make cli-darwin-server` to build a binary, or `go run main.go serve` (see above) to run it.
### Build the web app ### Build the web app
The sources for the web app live in `web/`. As long as you have `npm` installed (see above), building the web app
The sources for the web app live in `web/`. As long as you have `npm` installed (see above), building the web app
is really simple. Just type `make web` and you're in business: is really simple. Just type `make web` and you're in business:
``` shell ```shell
$ make web $ make web
... ...
``` ```
This will build the web app using Create React App and then **copy the production build to the `server/site` folder**, so This will build the web app using Create React App and then **copy the production build to the `server/site` folder**, so
that when you `make cli` (or `make cli-linux-amd64`, ...), you will have the web app included in the `ntfy` binary. that when you `make cli` (or `make cli-linux-amd64`, ...), you will have the web app included in the `ntfy` binary.
If you're developing on the web app, it's best to just `cd web` and run `npm start` manually. This will open your browser If you're developing on the web app, it's best to just `cd web` and run `npm start` manually. This will open your browser
at `http://127.0.0.1:3000` with the web app, and as you edit the source files, they will be recompiled and the browser at `http://127.0.0.1:3000` with the web app, and as you edit the source files, they will be recompiled and the browser
will automatically refresh: will automatically refresh:
``` shell ```shell
$ cd web $ cd web
$ npm start $ npm start
``` ```
### Build the docs ### Build the docs
The sources for the docs live in `docs/`. Similarly to the web app, you can simply run `make docs` to build the
The sources for the docs live in `docs/`. Similarly to the web app, you can simply run `make docs` to build the
documentation. As long as you have `mkdocs` installed (see above), this should work fine: documentation. As long as you have `mkdocs` installed (see above), this should work fine:
``` shell ```shell
$ make docs $ make docs
... ...
``` ```
If you are changing the documentation, you should be running `mkdocs serve` directly. This will build the documentation, If you are changing the documentation, you should be running `mkdocs serve` directly. This will build the documentation,
serve the files at `http://127.0.0.1:8000/`, and rebuild every time you save the source files: serve the files at `http://127.0.0.1:8000/`, and rebuild every time you save the source files:
``` ```
$ mkdocs serve $ mkdocs serve
@ -264,33 +280,39 @@ INFO - [16:28:14] Serving on http://127.0.0.1:8000/
Then you can navigate to http://127.0.0.1:8000/ and whenever you change a markdown file in your text editor it'll automatically update. Then you can navigate to http://127.0.0.1:8000/ and whenever you change a markdown file in your text editor it'll automatically update.
## Android app ## Android app
The ntfy Android app source code is available [on GitHub](https://github.com/binwiederhier/ntfy-android). The ntfy Android app source code is available [on GitHub](https://github.com/binwiederhier/ntfy-android).
The Android app has two flavors: The Android app has two flavors:
* **Google Play:** The `play` flavor includes [Firebase (FCM)](https://firebase.google.com/) and requires a Firebase account - **Google Play:** The `play` flavor includes [Firebase (FCM)](https://firebase.google.com/) and requires a Firebase account
* **F-Droid:** The `fdroid` flavor does not include Firebase or Google dependencies - **F-Droid:** The `fdroid` flavor does not include Firebase or Google dependencies
### Navigating the code ### Navigating the code
* [main/](https://github.com/binwiederhier/ntfy-android/tree/main/app/src/main) - Main Android app source code
* [play/](https://github.com/binwiederhier/ntfy-android/tree/main/app/src/play) - Google Play / Firebase specific code - [main/](https://github.com/binwiederhier/ntfy-android/tree/main/app/src/main) - Main Android app source code
* [fdroid/](https://github.com/binwiederhier/ntfy-android/tree/main/app/src/fdroid) - F-Droid Firebase stubs - [play/](https://github.com/binwiederhier/ntfy-android/tree/main/app/src/play) - Google Play / Firebase specific code
* [build.gradle](https://github.com/binwiederhier/ntfy-android/blob/main/app/build.gradle) - Main build file - [fdroid/](https://github.com/binwiederhier/ntfy-android/tree/main/app/src/fdroid) - F-Droid Firebase stubs
- [build.gradle](https://github.com/binwiederhier/ntfy-android/blob/main/app/build.gradle) - Main build file
### IDE/Environment ### IDE/Environment
You should download [Android Studio](https://developer.android.com/studio) (or [IntelliJ IDEA](https://www.jetbrains.com/idea/)
with the relevant Android plugins). Everything else will just be a pain for you. Do yourself a favor. 😀 You should download [Android Studio](https://developer.android.com/studio) (or [IntelliJ IDEA](https://www.jetbrains.com/idea/)
with the relevant Android plugins). Everything else will just be a pain for you. Do yourself a favor. 😀
### Check out the code ### Check out the code
First check out the repository: First check out the repository:
=== "via HTTPS" === "via HTTPS"
``` shell
```shell
git clone https://github.com/binwiederhier/ntfy-android.git git clone https://github.com/binwiederhier/ntfy-android.git
cd ntfy-android cd ntfy-android
``` ```
=== "via SSH" === "via SSH"
``` shell
```shell
git clone git@github.com:binwiederhier/ntfy-android.git git clone git@github.com:binwiederhier/ntfy-android.git
cd ntfy-android cd ntfy-android
``` ```
@ -298,12 +320,14 @@ First check out the repository:
Then either follow the steps for building with or without Firebase. Then either follow the steps for building with or without Firebase.
### Build F-Droid flavor (no FCM) ### Build F-Droid flavor (no FCM)
!!! info !!! info
I do build the ntfy Android app using IntelliJ IDEA (Android Studio), so I don't know if these Gradle commands will I do build the ntfy Android app using IntelliJ IDEA (Android Studio), so I don't know if these Gradle commands will
work without issues. Please give me feedback if it does/doesn't work for you. work without issues. Please give me feedback if it does/doesn't work for you.
Without Firebase, you may want to still change the default `app_base_url` in [values.xml](https://github.com/binwiederhier/ntfy-android/blob/main/app/src/main/res/values/values.xml) Without Firebase, you may want to still change the default `app_base_url` in [values.xml](https://github.com/binwiederhier/ntfy-android/blob/main/app/src/main/res/values/values.xml)
if you're self-hosting the server. Then run: if you're self-hosting the server. Then run:
``` ```
# Remove Google dependencies (FCM) # Remove Google dependencies (FCM)
sed -i -e '/google-services/d' build.gradle sed -i -e '/google-services/d' build.gradle
@ -317,16 +341,18 @@ sed -i -e '/google-services/d' app/build.gradle
``` ```
### Build Play flavor (FCM) ### Build Play flavor (FCM)
!!! info !!! info
I do build the ntfy Android app using IntelliJ IDEA (Android Studio), so I don't know if these Gradle commands will I do build the ntfy Android app using IntelliJ IDEA (Android Studio), so I don't know if these Gradle commands will
work without issues. Please give me feedback if it does/doesn't work for you. work without issues. Please give me feedback if it does/doesn't work for you.
To build your own version with Firebase, you must: To build your own version with Firebase, you must:
* Create a Firebase/FCM account - Create a Firebase/FCM account
* Place your account file at `app/google-services.json` - Place your account file at `app/google-services.json`
* And change `app_base_url` in [values.xml](https://github.com/binwiederhier/ntfy-android/blob/main/app/src/main/res/values/values.xml) - And change `app_base_url` in [values.xml](https://github.com/binwiederhier/ntfy-android/blob/main/app/src/main/res/values/values.xml)
* Then run: - Then run:
``` ```
# To build an unsigned .apk (app/build/outputs/apk/play/*.apk) # To build an unsigned .apk (app/build/outputs/apk/play/*.apk)
./gradlew assemblePlayRelease ./gradlew assemblePlayRelease
@ -336,10 +362,12 @@ To build your own version with Firebase, you must:
``` ```
## iOS app ## iOS app
Building the iOS app is very involved. Please report any inconsistencies or issues with it. The requirements are
Building the iOS app is very involved. Please report any inconsistencies or issues with it. The requirements are
strictly based off of my development on this app. There may be other versions of macOS / XCode that work. strictly based off of my development on this app. There may be other versions of macOS / XCode that work.
### Requirements ### Requirements
1. macOS Monterey or later 1. macOS Monterey or later
1. XCode 13.2+ 1. XCode 13.2+
1. A physical iOS device (for push notifications, Firebase does not work in the XCode simulator) 1. A physical iOS device (for push notifications, Firebase does not work in the XCode simulator)
@ -349,17 +377,17 @@ strictly based off of my development on this app. There may be other versions of
### Apple setup ### Apple setup
!!! info !!! info
Along with this step, the [PLIST Deployment](#plist-deployment-and-configuration) step is also required Along with this step, the [PLIST Deployment](#plist-deployment-and-configuration) step is also required
for these changes to take effect in the iOS app. for these changes to take effect in the iOS app.
1. [Create a new key in Apple Developer Member Center](https://developer.apple.com/account/resources/authkeys/add) 1. [Create a new key in Apple Developer Member Center](https://developer.apple.com/account/resources/authkeys/add)
1. Select "Apple Push Notifications service (APNs)" 1. Select "Apple Push Notifications service (APNs)"
1. Download the newly created key (should have a file name similar to `AuthKey_ZZZZZZ.p8`, where `ZZZZZZ` is the **Key ID**) 1. Download the newly created key (should have a file name similar to `AuthKey_ZZZZZZ.p8`, where `ZZZZZZ` is the **Key ID**)
1. Record your **Team ID** - it can be seen in the top-right corner of the page, or on your Account > Membership page 1. Record your **Team ID** - it can be seen in the top-right corner of the page, or on your Account > Membership page
1. Next, navigate to "Project Settings" in the firebase console for your project, and select the iOS app you created. Then, click "Cloud Messaging" in the left sidebar, and scroll down to the "APNs Authentication Key" section. Click "Upload Key", and upload the key you downloaded from Apple Developer. 1. Next, navigate to "Project Settings" in the firebase console for your project, and select the iOS app you created. Then, click "Cloud Messaging" in the left sidebar, and scroll down to the "APNs Authentication Key" section. Click "Upload Key", and upload the key you downloaded from Apple Developer.
!!! warning !!! warning
If you don't do the above setups for APNS, **notifications will not post instantly or sometimes at all**. This is because of the missing APNS key, which is required for firebase to send notifications to the iOS app. See below for a snip from the firebase docs. If you don't do the above setups for APNS, **notifications will not post instantly or sometimes at all**. This is because of the missing APNS key, which is required for firebase to send notifications to the iOS app. See below for a snip from the firebase docs.
If you don't have an APNs authentication key, you can still send notifications to iOS devices, but they won't be delivered If you don't have an APNs authentication key, you can still send notifications to iOS devices, but they won't be delivered
instantly. Instead, they'll be delivered when the device wakes up to check for new notifications or when your application instantly. Instead, they'll be delivered when the device wakes up to check for new notifications or when your application
@ -372,17 +400,18 @@ recommended.
1. If you haven't already, create a Google / Firebase account 1. If you haven't already, create a Google / Firebase account
1. Visit the [Firebase console](https://console.firebase.google.com) 1. Visit the [Firebase console](https://console.firebase.google.com)
1. Create a new Firebase project: 1. Create a new Firebase project:
1. Enter a project name 1. Enter a project name
1. Disable Google Analytics (currently iOS app does not support analytics) 1. Disable Google Analytics (currently iOS app does not support analytics)
1. On the "Project settings" page, add an iOS app 1. On the "Project settings" page, add an iOS app
1. Apple bundle ID - "com.copephobia.ntfy-ios" (this can be changed to match XCode's ntfy.sh target > "Bundle Identifier" value) 1. Apple bundle ID - "com.copephobia.ntfy-ios" (this can be changed to match XCode's ntfy.sh target > "Bundle Identifier" value)
1. Register the app 1. Register the app
1. Download the config file - GoogleInfo.plist (this will need to be included in the ntfy-ios repository / XCode) 1. Download the config file - GoogleInfo.plist (this will need to be included in the ntfy-ios repository / XCode)
1. Generate a new service account private key for the ntfy server 1. Generate a new service account private key for the ntfy server
1. Go to "Project settings" > "Service accounts" 1. Go to "Project settings" > "Service accounts"
1. Click "Generate new private key" to generate and download a private key to use for sending messages via the ntfy server 1. Click "Generate new private key" to generate and download a private key to use for sending messages via the ntfy server
### ntfy server ### ntfy server
Note that the ntfy server is not officially supported on macOS. It should, however, be able to run on macOS using these Note that the ntfy server is not officially supported on macOS. It should, however, be able to run on macOS using these
steps: steps:
@ -394,18 +423,19 @@ steps:
### XCode setup ### XCode setup
1. Follow step 4 of [https://firebase.google.com/docs/ios/setup](Add Firebase to your Apple project) to install the 1. Follow step 4 of [https://firebase.google.com/docs/ios/setup](Add Firebase to your Apple project) to install the
`firebase-ios-sdk` in XCode, if it's not already present - you can select any packages in addition to Firebase Core / Firebase Messaging `firebase-ios-sdk` in XCode, if it's not already present - you can select any packages in addition to Firebase Core / Firebase Messaging
1. Similarly, install the SQLite.swift package dependency in XCode 1. Similarly, install the SQLite.swift package dependency in XCode
1. When running the debug build, ensure XCode is pointed to the connected iOS device - registering for push notifications does not work in the iOS simulators 1. When running the debug build, ensure XCode is pointed to the connected iOS device - registering for push notifications does not work in the iOS simulators
### PLIST config ### PLIST config
To have instant notifications/better notification delivery when using firebase, you will need to add the
To have instant notifications/better notification delivery when using firebase, you will need to add the
`GoogleService-Info.plist` file to your project. Here's how to do that: `GoogleService-Info.plist` file to your project. Here's how to do that:
1. In XCode, find the NTFY app target. **Not** the NSE app target. 1. In XCode, find the NTFY app target. **Not** the NSE app target.
1. Find the Asset/ folder in the project navigator 1. Find the Asset/ folder in the project navigator
1. Drag the `GoogleService-Info.plist` file into the Asset/ folder that you get from the firebase console. It can be 1. Drag the `GoogleService-Info.plist` file into the Asset/ folder that you get from the firebase console. It can be
found in the "Project settings" > "General" > "Your apps" with a button labled "GoogleService-Info.plist" found in the "Project settings" > "General" > "Your apps" with a button labled "GoogleService-Info.plist"
After that, you should be all set! After that, you should be all set!

View file

@ -5,18 +5,19 @@ There are a million ways to use ntfy, but here are some inspirations. I try to c
those out, too. those out, too.
!!! info !!! info
Many of these examples were contributed by ntfy users. If you have other examples of how you use ntfy, please Many of these examples were contributed by ntfy users. If you have other examples of how you use ntfy, please
[create a pull request](https://github.com/binwiederhier/ntfy/pulls), and I'll happily include it. Also note, that [create a pull request](https://github.com/binwiederhier/ntfy/pulls), and I'll happily include it. Also note, that
I cannot guarantee that all of these examples are functional. Many of them I have not tried myself. I cannot guarantee that all of these examples are functional. Many of them I have not tried myself.
## Cronjobs ## Cronjobs
ntfy is perfect for any kind of cronjobs or just when long processes are done (backups, pipelines, rsync copy commands, ...). ntfy is perfect for any kind of cronjobs or just when long processes are done (backups, pipelines, rsync copy commands, ...).
I started adding notifications pretty much all of my scripts. Typically, I just chain the <tt>curl</tt> call I started adding notifications pretty much all of my scripts. Typically, I just chain the <tt>curl</tt> call
directly to the command I'm running. The following example will either send <i>Laptop backup succeeded</i> directly to the command I'm running. The following example will either send <i>Laptop backup succeeded</i>
or ⚠️ <i>Laptop backup failed</i> directly to my phone: or ⚠️ <i>Laptop backup failed</i> directly to my phone:
``` bash ```bash
rsync -a root@laptop /backups/laptop \ rsync -a root@laptop /backups/laptop \
&& zfs snapshot ... \ && zfs snapshot ... \
&& curl -H prio:low -d "Laptop backup succeeded" ntfy.sh/backups \ && curl -H prio:low -d "Laptop backup succeeded" ntfy.sh/backups \
@ -31,12 +32,12 @@ GitHub have been hopeless. In case it ever becomes available, I want to know imm
*/6 * * * * if curl -s https://api.github.com/users/ntfy | grep "Not Found"; then curl -d "github.com/ntfy is available" -H "Tags: tada" -H "Prio: high" ntfy.sh/my-alerts; fi */6 * * * * if curl -s https://api.github.com/users/ntfy | grep "Not Found"; then curl -d "github.com/ntfy is available" -H "Tags: tada" -H "Prio: high" ntfy.sh/my-alerts; fi
``` ```
## Low disk space alerts ## Low disk space alerts
Here's a simple cronjob that I use to alert me when the disk space on the root disk is running low. It's simple, but
effective.
``` bash Here's a simple cronjob that I use to alert me when the disk space on the root disk is running low. It's simple, but
effective.
```bash
#!/bin/bash #!/bin/bash
mingigs=10 mingigs=10
@ -54,17 +55,20 @@ fi
``` ```
## SSH login alerts ## SSH login alerts
Years ago my home server was broken into. That shook me hard, so every time someone logs into any machine that I Years ago my home server was broken into. That shook me hard, so every time someone logs into any machine that I
own, I now message myself. Here's an example of how to use <a href="https://en.wikipedia.org/wiki/Linux_PAM">PAM</a> own, I now message myself. Here's an example of how to use <a href="https://en.wikipedia.org/wiki/Linux_PAM">PAM</a>
to notify yourself on SSH login. to notify yourself on SSH login.
=== "/etc/pam.d/sshd" === "/etc/pam.d/sshd"
``` ```
# at the end of the file # at the end of the file
session optional pam_exec.so /usr/bin/ntfy-ssh-login.sh session optional pam_exec.so /usr/bin/ntfy-ssh-login.sh
``` ```
=== "/usr/bin/ntfy-ssh-login.sh" === "/usr/bin/ntfy-ssh-login.sh"
```bash ```bash
#!/bin/bash #!/bin/bash
if [ "${PAM_TYPE}" = "open_session" ]; then if [ "${PAM_TYPE}" = "open_session" ]; then
@ -77,31 +81,36 @@ to notify yourself on SSH login.
``` ```
## Collect data from multiple machines ## Collect data from multiple machines
The other day I was running tasks on 20 servers, and I wanted to collect the interim results The other day I was running tasks on 20 servers, and I wanted to collect the interim results
as a CSV in one place. Each of the servers was publishing to a topic as the results completed (`publish-result.sh`), as a CSV in one place. Each of the servers was publishing to a topic as the results completed (`publish-result.sh`),
and I had one central collector to grab the results as they came in (`collect-results.sh`). and I had one central collector to grab the results as they came in (`collect-results.sh`).
It looked something like this: It looked something like this:
=== "collect-results.sh" === "collect-results.sh"
```bash ```bash
while read result; do while read result; do
[ -n "$result" ] && echo "$result" >> results.csv [ -n "$result" ] && echo "$result" >> results.csv
done < <(stdbuf -i0 -o0 curl -s ntfy.sh/results/raw) done < <(stdbuf -i0 -o0 curl -s ntfy.sh/results/raw)
``` ```
=== "publish-result.sh"
=== "publish-result.sh"
```bash ```bash
// This script was run on each of the 20 servers. It was doing heavy processing ... // This script was run on each of the 20 servers. It was doing heavy processing ...
// Publish script results // Publish script results
curl -d "$(hostname),$count,$time" ntfy.sh/results curl -d "$(hostname),$count,$time" ntfy.sh/results
``` ```
## Ansible, Salt and Puppet ## Ansible, Salt and Puppet
You can easily integrate ntfy into Ansible, Salt, or Puppet to notify you when runs are done or are highstated. You can easily integrate ntfy into Ansible, Salt, or Puppet to notify you when runs are done or are highstated.
One of my co-workers uses the following Ansible task to let him know when things are done: One of my co-workers uses the following Ansible task to let him know when things are done:
``` yaml ```yaml
- name: Send ntfy.sh update - name: Send ntfy.sh update
uri: uri:
url: "https://ntfy.sh/{{ ntfy_channel }}" url: "https://ntfy.sh/{{ ntfy_channel }}"
@ -113,18 +122,20 @@ There's also a dedicated Ansible action plugin (one which runs on the Ansible co
[ansible-ntfy](https://github.com/jpmens/ansible-ntfy). The following task posts a message [ansible-ntfy](https://github.com/jpmens/ansible-ntfy). The following task posts a message
to ntfy at its default URL (`attrs` and other attributes are optional): to ntfy at its default URL (`attrs` and other attributes are optional):
``` yaml ```yaml
- name: "Notify ntfy that we're done" - name: "Notify ntfy that we're done"
ntfy: ntfy:
msg: "deployment on {{ inventory_hostname }} is complete. 🐄" msg: "deployment on {{ inventory_hostname }} is complete. 🐄"
attrs: attrs:
tags: [ heavy_check_mark ] tags: [heavy_check_mark]
priority: 1 priority: 1
``` ```
## GitHub Actions ## 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. 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
```yaml
- name: Actions Ntfy - name: Actions Ntfy
run: | run: |
curl \ curl \
@ -136,12 +147,13 @@ You can send a message during a workflow run with curl. Here is an example sendi
``` ```
## Watchtower (shoutrrr) ## Watchtower (shoutrrr)
You can use [shoutrrr](https://containrrr.dev/shoutrrr/latest/services/ntfy/) to send
You can use [shoutrrr](https://containrrr.dev/shoutrrr/latest/services/ntfy/) to send
[Watchtower](https://github.com/containrrr/watchtower/) notifications to your ntfy topic. [Watchtower](https://github.com/containrrr/watchtower/) notifications to your ntfy topic.
Example docker-compose.yml: Example docker-compose.yml:
``` yaml ```yaml
services: services:
watchtower: watchtower:
image: containrrr/watchtower image: containrrr/watchtower
@ -151,6 +163,7 @@ services:
``` ```
Or, if you only want to send notifications using shoutrrr: Or, if you only want to send notifications using shoutrrr:
``` ```
shoutrrr send -u "ntfy://ntfy.sh/my_watchtower_topic?title=WatchtowerUpdates" -m "testMessage" shoutrrr send -u "ntfy://ntfy.sh/my_watchtower_topic?title=WatchtowerUpdates" -m "testMessage"
``` ```
@ -165,90 +178,76 @@ Sonarr v3, Readarr, and SABnzbd support custom scripts for downloads, warnings,
Some simple bash scripts to achieve this are kindly provided in [nickexyz's ntfy-shellscripts repository](https://github.com/nickexyz/ntfy-shellscripts). Some simple bash scripts to achieve this are kindly provided in [nickexyz's ntfy-shellscripts repository](https://github.com/nickexyz/ntfy-shellscripts).
## Node-RED ## Node-RED
You can use the HTTP request node to send messages with [Node-RED](https://nodered.org), some examples: You can use the HTTP request node to send messages with [Node-RED](https://nodered.org), some examples:
<details> <details>
<summary>Example: Send a message (click to expand)</summary> <summary>Example: Send a message (click to expand)</summary>
``` json ```json
[ [
{ {
"id": "c956e688cc74ad8e", "id": "c956e688cc74ad8e",
"type": "http request", "type": "http request",
"z": "fabdd7a3.4045a", "z": "fabdd7a3.4045a",
"name": "ntfy.sh", "name": "ntfy.sh",
"method": "POST", "method": "POST",
"ret": "txt", "ret": "txt",
"paytoqs": "ignore", "paytoqs": "ignore",
"url": "https://ntfy.sh/mytopic", "url": "https://ntfy.sh/mytopic",
"tls": "", "tls": "",
"persist": false, "persist": false,
"proxy": "", "proxy": "",
"authType": "", "authType": "",
"senderr": false, "senderr": false,
"credentials": "credentials": {
{ "user": "",
"user": "", "password": ""
"password": ""
},
"x": 590,
"y": 3160,
"wires":
[
[]
]
}, },
{ "x": 590,
"id": "32ee1eade51fae50", "y": 3160,
"type": "function", "wires": [[]]
"z": "fabdd7a3.4045a", },
"name": "data", {
"func": "msg.payload = \"Something happened\";\nmsg.headers = {};\nmsg.headers['tags'] = 'house';\nmsg.headers['X-Title'] = 'Home Assistant';\n\nreturn msg;", "id": "32ee1eade51fae50",
"outputs": 1, "type": "function",
"noerr": 0, "z": "fabdd7a3.4045a",
"initialize": "", "name": "data",
"finalize": "", "func": "msg.payload = \"Something happened\";\nmsg.headers = {};\nmsg.headers['tags'] = 'house';\nmsg.headers['X-Title'] = 'Home Assistant';\n\nreturn msg;",
"libs": [], "outputs": 1,
"x": 470, "noerr": 0,
"y": 3160, "initialize": "",
"wires": "finalize": "",
[ "libs": [],
[ "x": 470,
"c956e688cc74ad8e" "y": 3160,
] "wires": [["c956e688cc74ad8e"]]
] },
}, {
{ "id": "b287e59cd2311815",
"id": "b287e59cd2311815", "type": "inject",
"type": "inject", "z": "fabdd7a3.4045a",
"z": "fabdd7a3.4045a", "name": "Manual start",
"name": "Manual start", "props": [
"props": {
[ "p": "payload"
{ },
"p": "payload" {
}, "p": "topic",
{ "vt": "str"
"p": "topic", }
"vt": "str" ],
} "repeat": "",
], "crontab": "",
"repeat": "", "once": false,
"crontab": "", "onceDelay": "20",
"once": false, "topic": "",
"onceDelay": "20", "payload": "",
"topic": "", "payloadType": "date",
"payload": "", "x": 330,
"payloadType": "date", "y": 3160,
"x": 330, "wires": [["32ee1eade51fae50"]]
"y": 3160, }
"wires":
[
[
"32ee1eade51fae50"
]
]
}
] ]
``` ```
@ -259,113 +258,92 @@ You can use the HTTP request node to send messages with [Node-RED](https://noder
<details> <details>
<summary>Example: Send a picture (click to expand)</summary> <summary>Example: Send a picture (click to expand)</summary>
``` json ```json
[ [
{ {
"id": "d135a13eadeb9d6d", "id": "d135a13eadeb9d6d",
"type": "http request", "type": "http request",
"z": "fabdd7a3.4045a", "z": "fabdd7a3.4045a",
"name": "Download image", "name": "Download image",
"method": "GET", "method": "GET",
"ret": "bin", "ret": "bin",
"paytoqs": "ignore", "paytoqs": "ignore",
"url": "https://www.google.com/images/branding/googlelogo/1x/googlelogo_color_272x92dp.png", "url": "https://www.google.com/images/branding/googlelogo/1x/googlelogo_color_272x92dp.png",
"tls": "", "tls": "",
"persist": false, "persist": false,
"proxy": "", "proxy": "",
"authType": "", "authType": "",
"senderr": false, "senderr": false,
"credentials": "credentials": {
{ "user": "",
"user": "", "password": ""
"password": ""
},
"x": 490,
"y": 3320,
"wires":
[
[
"6e75bc41d2ec4a03"
]
]
}, },
{ "x": 490,
"id": "6e75bc41d2ec4a03", "y": 3320,
"type": "function", "wires": [["6e75bc41d2ec4a03"]]
"z": "fabdd7a3.4045a", },
"name": "data", {
"func": "msg.payload = msg.payload;\nmsg.headers = {};\nmsg.headers['tags'] = 'house';\nmsg.headers['X-Title'] = 'Home Assistant - Picture';\n\nreturn msg;", "id": "6e75bc41d2ec4a03",
"outputs": 1, "type": "function",
"noerr": 0, "z": "fabdd7a3.4045a",
"initialize": "", "name": "data",
"finalize": "", "func": "msg.payload = msg.payload;\nmsg.headers = {};\nmsg.headers['tags'] = 'house';\nmsg.headers['X-Title'] = 'Home Assistant - Picture';\n\nreturn msg;",
"libs": [], "outputs": 1,
"x": 650, "noerr": 0,
"y": 3320, "initialize": "",
"wires": "finalize": "",
[ "libs": [],
[ "x": 650,
"eb160615b6ceda98" "y": 3320,
] "wires": [["eb160615b6ceda98"]]
] },
{
"id": "eb160615b6ceda98",
"type": "http request",
"z": "fabdd7a3.4045a",
"name": "ntfy.sh",
"method": "PUT",
"ret": "bin",
"paytoqs": "ignore",
"url": "https://ntfy.sh/mytopic",
"tls": "",
"persist": false,
"proxy": "",
"authType": "",
"senderr": false,
"credentials": {
"user": "",
"password": ""
}, },
{ "x": 770,
"id": "eb160615b6ceda98", "y": 3320,
"type": "http request", "wires": [[]]
"z": "fabdd7a3.4045a", },
"name": "ntfy.sh", {
"method": "PUT", "id": "5b8dbf15c8a7a3a5",
"ret": "bin", "type": "inject",
"paytoqs": "ignore", "z": "fabdd7a3.4045a",
"url": "https://ntfy.sh/mytopic", "name": "Manual start",
"tls": "", "props": [
"persist": false, {
"proxy": "", "p": "payload"
"authType": "", },
"senderr": false, {
"credentials": "p": "topic",
{ "vt": "str"
"user": "", }
"password": "" ],
}, "repeat": "",
"x": 770, "crontab": "",
"y": 3320, "once": false,
"wires": "onceDelay": "20",
[ "topic": "",
[] "payload": "",
] "payloadType": "date",
}, "x": 310,
{ "y": 3320,
"id": "5b8dbf15c8a7a3a5", "wires": [["d135a13eadeb9d6d"]]
"type": "inject", }
"z": "fabdd7a3.4045a",
"name": "Manual start",
"props":
[
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": "20",
"topic": "",
"payload": "",
"payloadType": "date",
"x": 310,
"y": 3320,
"wires":
[
[
"d135a13eadeb9d6d"
]
]
}
] ]
``` ```
@ -374,6 +352,7 @@ You can use the HTTP request node to send messages with [Node-RED](https://noder
![Node red picture flow](static/img/nodered-picture.png) ![Node red picture flow](static/img/nodered-picture.png)
## Gatus ## Gatus
To use ntfy with [Gatus](https://github.com/TwiN/gatus), you can use the `ntfy` alerting provider like so: To use ntfy with [Gatus](https://github.com/TwiN/gatus), you can use the `ntfy` alerting provider like so:
```yaml ```yaml
@ -416,24 +395,25 @@ alerting:
</details> </details>
## Jellyseerr/Overseerr webhook ## Jellyseerr/Overseerr webhook
Here is an example for [jellyseerr](https://github.com/Fallenbagel/jellyseerr)/[overseerr](https://overseerr.dev/) webhook Here is an example for [jellyseerr](https://github.com/Fallenbagel/jellyseerr)/[overseerr](https://overseerr.dev/) webhook
JSON payload. Remember to change the `https://request.example.com` to your URL as the value of the JSON key click. JSON payload. Remember to change the `https://request.example.com` to your URL as the value of the JSON key click.
And if you're not using the request `topic`, make sure to change it in the JSON payload to your topic. And if you're not using the request `topic`, make sure to change it in the JSON payload to your topic.
``` json ```json
{ {
"topic": "requests", "topic": "requests",
"title": "{{event}}", "title": "{{event}}",
"message": "{{subject}}\n{{message}}\n\nRequested by: {{requestedBy_username}}\n\nStatus: {{media_status}}\nRequest Id: {{request_id}}", "message": "{{subject}}\n{{message}}\n\nRequested by: {{requestedBy_username}}\n\nStatus: {{media_status}}\nRequest Id: {{request_id}}",
"priority": 4, "priority": 4,
"attach": "{{image}}", "attach": "{{image}}",
"click": "https://requests.example.com/{{media_type}}/{{media_tmdbid}}" "click": "https://requests.example.com/{{media_type}}/{{media_tmdbid}}"
} }
``` ```
## Home Assistant ## Home Assistant
Here is an example for the configuration.yml file to setup a REST notify component. Here is an example for the configuration.yml file to setup a REST notify component.
Since Home Assistant is going to POST JSON, you need to specify the root of your ntfy resource. Since Home Assistant is going to POST JSON, you need to specify the root of your ntfy resource.
@ -482,6 +462,7 @@ notify:
``` ```
## Uptime Kuma ## Uptime Kuma
Go to your [Uptime Kuma](https://github.com/louislam/uptime-kuma) Settings > Notifications, click on **Setup Notification**. Go to your [Uptime Kuma](https://github.com/louislam/uptime-kuma) Settings > Notifications, click on **Setup Notification**.
Then set your desired **title** (e.g. "Uptime Kuma"), **ntfy topic**, **Server URL** and **priority (1-5)**: Then set your desired **title** (e.g. "Uptime Kuma"), **ntfy topic**, **Server URL** and **priority (1-5)**:
@ -499,6 +480,7 @@ You can now test the notifications and apply them to monitors:
</div> </div>
## UptimeRobot ## UptimeRobot
Go to your [UptimeRobot](https://github.com/uptimerobot) My Settings > Alert Contacts > Add Alert Contact Go to your [UptimeRobot](https://github.com/uptimerobot) My Settings > Alert Contacts > Add Alert Contact
Select **Alert Contact Type** = Webhook. Then set your desired **Friendly Name** (e.g. "ntfy-sh-UP"), **URL to Notify**, **POST value** and select checkbox **Send as JSON (application/json)**. Make sure to send the JSON POST request to ntfy.domain.com without the topic name in the url and include the "topic" name in the JSON body. Select **Alert Contact Type** = Webhook. Then set your desired **Friendly Name** (e.g. "ntfy-sh-UP"), **URL to Notify**, **POST value** and select checkbox **Send as JSON (application/json)**. Make sure to send the JSON POST request to ntfy.domain.com without the topic name in the url and include the "topic" name in the JSON body.
@ -506,37 +488,39 @@ Select **Alert Contact Type** = Webhook. Then set your desired **Friendly Name**
<a href="../static/img/uptimerobot-setup.jpg"><img src="../static/img/uptimerobot-setup.jpg"/></a> <a href="../static/img/uptimerobot-setup.jpg"><img src="../static/img/uptimerobot-setup.jpg"/></a>
</div> </div>
``` json ```json
{ {
"topic":"myTopic", "topic":"myTopic",
"title": "*monitorFriendlyName* *alertTypeFriendlyName*", "title": "*monitorFriendlyName* *alertTypeFriendlyName*",
"message": "*alertDetails*", "message": "*alertDetails*",
"tags": ["green_circle"], "tags": ["green_circle"],
"priority": 3, "priority": 3,
"click": https://uptimerobot.com/dashboard#*monitorID* "click": https://uptimerobot.com/dashboard#*monitorID*
} }
``` ```
You can create two Alert Contacts each with a different icon and priority, for example: You can create two Alert Contacts each with a different icon and priority, for example:
``` json ```json
{ {
"topic":"myTopic", "topic":"myTopic",
"title": "*monitorFriendlyName* *alertTypeFriendlyName*", "title": "*monitorFriendlyName* *alertTypeFriendlyName*",
"message": "*alertDetails*", "message": "*alertDetails*",
"tags": ["red_circle"], "tags": ["red_circle"],
"priority": 3, "priority": 3,
"click": https://uptimerobot.com/dashboard#*monitorID* "click": https://uptimerobot.com/dashboard#*monitorID*
} }
``` ```
You can now add the created Alerts Contact(s) to the monitor(s) and test the notifications: You can now add the created Alerts Contact(s) to the monitor(s) and test the notifications:
<div id="uptimerobot-monitor-screenshots" class="screenshots"> <div id="uptimerobot-monitor-screenshots" class="screenshots">
<a href="../static/img/uptimerobot-test.jpg"><img src="../static/img/uptimerobot-test.jpg"/></a> <a href="../static/img/uptimerobot-test.jpg"><img src="../static/img/uptimerobot-test.jpg"/></a>
</div> </div>
## Apprise ## Apprise
ntfy is integrated natively into [Apprise](https://github.com/caronc/apprise) (also check out the
ntfy is integrated natively into [Apprise](https://github.com/caronc/apprise) (also check out the
[Apprise/ntfy wiki page](https://github.com/caronc/apprise/wiki/Notify_ntfy)). [Apprise/ntfy wiki page](https://github.com/caronc/apprise/wiki/Notify_ntfy)).
You can use it like this: You can use it like this:
@ -553,9 +537,9 @@ apprise -vv -t "Test Message Title" -b "Test Message Body" \
ntfy://ntfy.example.com/mytopic ntfy://ntfy.example.com/mytopic
``` ```
## Rundeck ## Rundeck
Rundeck by default sends only HTML email which is not processed by ntfy SMTP server. Append following configurations to
Rundeck by default sends only HTML email which is not processed by ntfy SMTP server. Append following configurations to
[rundeck-config.properties](https://docs.rundeck.com/docs/administration/configuration/config-file-reference.html) : [rundeck-config.properties](https://docs.rundeck.com/docs/administration/configuration/config-file-reference.html) :
``` ```
@ -565,13 +549,14 @@ rundeck.mail.template.log.formatted=false
``` ```
Example `template.html`: Example `template.html`:
```html ```html
<div>Execution ${execution.id} was <b>${execution.status}</b></div> <div>Execution ${execution.id} was <b>${execution.status}</b></div>
<ul> <ul>
<li><a href="${execution.href}">Execution result</a></li> <li><a href="${execution.href}">Execution result</a></li>
<li><a href="${job.href}">Job</a></li> <li><a href="${job.href}">Job</a></li>
<li><a href="${execution.projectHref}">Project: ${execution.project}</a></li> <li><a href="${execution.projectHref}">Project: ${execution.project}</a></li>
<li><a href="${rundeck.href}">Rundeck</a></li> <li><a href="${rundeck.href}">Rundeck</a></li>
</ul> </ul>
``` ```
@ -579,11 +564,13 @@ Add notification on Rundeck (attachment type must be: `Attached as file to email
![Rundeck](static/img/rundeck.png) ![Rundeck](static/img/rundeck.png)
## Traccar ## Traccar
This will only work on selfhosted [traccar](https://www.traccar.org/) ([Github](https://github.com/traccar/traccar)) instances, as you need to be able to set `sms.http.*` keys, which is not possible through the UI attributes This will only work on selfhosted [traccar](https://www.traccar.org/) ([Github](https://github.com/traccar/traccar)) instances, as you need to be able to set `sms.http.*` keys, which is not possible through the UI attributes
The easiest way to integrate traccar with ntfy, is to configure ntfy as the SMS provider for your instance. You then can set your ntfy topic as your account's phone number in traccar. Sending the email notifications to ntfy will not work, as ntfy does not support HTML emails. The easiest way to integrate traccar with ntfy, is to configure ntfy as the SMS provider for your instance. You then can set your ntfy topic as your account's phone number in traccar. Sending the email notifications to ntfy will not work, as ntfy does not support HTML emails.
**Caution:** JSON publishing is only possible, when POST-ing to the root URL of the ntfy instance. (see [documentation](publish.md#publish-as-json)) **Caution:** JSON publishing is only possible, when POST-ing to the root URL of the ntfy instance. (see [documentation](publish.md#publish-as-json))
```xml ```xml
<entry key='sms.http.url'>https://ntfy.sh</entry> <entry key='sms.http.url'>https://ntfy.sh</entry>
<entry key='sms.http.template'> <entry key='sms.http.template'>
@ -593,11 +580,15 @@ The easiest way to integrate traccar with ntfy, is to configure ntfy as the SMS
} }
</entry> </entry>
``` ```
If [access control](config.md#access-control) is enabled, and the target topic does not support anonymous writes, you'll also have to provide an authorization header, for example in form of a privileged token If [access control](config.md#access-control) is enabled, and the target topic does not support anonymous writes, you'll also have to provide an authorization header, for example in form of a privileged token
```xml ```xml
<entry key='sms.http.authorization'>Bearer tk_JhbsnoMrgy2FcfHeofv97Pi5uXaZZ</entry> <entry key='sms.http.authorization'>Bearer tk_JhbsnoMrgy2FcfHeofv97Pi5uXaZZ</entry>
``` ```
or by simply providing traccar with a valid username/password combination. or by simply providing traccar with a valid username/password combination.
```xml ```xml
<entry key='sms.http.user'>phil</entry> <entry key='sms.http.user'>phil</entry>
<entry key='sms.http.password'>mypass</entry> <entry key='sms.http.password'>mypass</entry>

View file

@ -1,82 +1,96 @@
# Frequently asked questions (FAQ) # Frequently asked questions (FAQ)
## Isn't this like ...? ## Isn't this like ...?
Who knows. I didn't do a lot of research before making this. It was fun making it. 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? ## 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. While I will always allow usage of the ntfy.sh 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. server without signup and free of charge, I may also offer paid plans in the future.
## What are the uptime guarantees? ## 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, 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. 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 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. 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 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_). awesome [healthchecks.io](https://healthchecks.io/) (_no affiliation, just a fan_).
## What happens if there are multiple subscribers to the same topic? ## 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. As per usual with pub-sub, all subscribers receive notifications if they are subscribed to a topic.
## Will you know what topics exist, can you spy on me? ## Will you know what topics exist, can you spy on me?
If you don't trust me or your messages are sensitive, run your own server. It's open source. If you don't trust me or your messages are sensitive, run your own server. It's open source.
That said, the logs do contain topic names and IP addresses, but I don't use them for anything other than That said, the logs do contain topic names and IP addresses, but I don't use them for anything other than
troubleshooting and rate limiting. Messages are cached for the duration configured in `server.yml` (12h by default) troubleshooting and rate limiting. Messages are cached for the duration configured in `server.yml` (12h by default)
to facilitate service restarts, message polling and to overcome client network disruptions. to facilitate service restarts, message polling and to overcome client network disruptions.
## Can I self-host it? ## Can I self-host it?
Yes. The server (including this Web UI) can be self-hosted, and the Android/iOS app supports adding topics from Yes. The server (including this Web UI) can be self-hosted, and the Android/iOS app supports adding topics from
your own server as well. Check out the [install instructions](install.md). your own server as well. Check out the [install instructions](install.md).
## Is Firebase used? ## Is Firebase used?
In addition to caching messages locally and delivering them to long-polling subscribers, all messages are also In addition to caching messages locally and delivering them to long-polling subscribers, all messages are also
published to Firebase Cloud Messaging (FCM) (if `FirebaseKeyFile` is set, which it is on ntfy.sh). This published to Firebase Cloud Messaging (FCM) (if `FirebaseKeyFile` is set, which it is on ntfy.sh). This
is to facilitate notifications on Android. is to facilitate notifications on Android.
If you do not care for Firebase, I suggest you install the [F-Droid version](https://f-droid.org/en/packages/io.heckel.ntfy/) If you do not care for Firebase, I suggest you install the [F-Droid version](https://f-droid.org/en/packages/io.heckel.ntfy/)
of the app and [self-host your own ntfy server](install.md). of the app and [self-host your own ntfy server](install.md).
## How much battery does the Android app use? ## How much battery does the Android app use?
If you use the ntfy.sh server, and you don't use the [instant delivery](subscribe/phone.md#instant-delivery) feature,
the Android/iOS app uses no additional battery, since Firebase Cloud Messaging (FCM) is used. If you use your own server, If you use the ntfy.sh server, and you don't use the [instant delivery](subscribe/phone.md#instant-delivery) feature,
or you use *instant delivery* (Android only), or install from F-droid ([which does not support FCM](https://f-droid.org/docs/Inclusion_Policy/)), the Android/iOS app uses no additional battery, since Firebase Cloud Messaging (FCM) is used. If you use your own server,
the app has to maintain a constant connection to the server, which consumes about 0-1% of battery in 17h of use (on my phone). or you use _instant delivery_ (Android only), or install from F-droid ([which does not support FCM](https://f-droid.org/docs/Inclusion_Policy/)),
the app has to maintain a constant connection to the server, which consumes about 0-1% of battery in 17h of use (on my phone).
There has been a ton of testing and improvement around this. I think it's pretty decent now. There has been a ton of testing and improvement around this. I think it's pretty decent now.
## Paid plans? I thought it was open source? ## Paid plans? I thought it was open source?
All of ntfy will remain open source, with a free software license (Apache 2.0 and GPLv2). If you'd like to self-host, you All of ntfy will remain open source, with a free software license (Apache 2.0 and GPLv2). If you'd like to self-host, you
can (and should do that). The paid plans I am offering are for people that do not want to self-host, and/or need higher can (and should do that). The paid plans I am offering are for people that do not want to self-host, and/or need higher
limits. limits.
## What is instant delivery? ## What is instant delivery?
[Instant delivery](subscribe/phone.md#instant-delivery) is a feature in the Android app. If turned on, the app maintains a constant connection to the [Instant delivery](subscribe/phone.md#instant-delivery) is a feature in the Android app. If turned on, the app maintains a constant connection to the
server and listens for incoming notifications. This consumes additional battery (see above), server and listens for incoming notifications. This consumes additional battery (see above),
but delivers notifications instantly. but delivers notifications instantly.
## Can you implement feature X? ## Can you implement feature X?
Yes, maybe. Check out [existing GitHub issues](https://github.com/binwiederhier/ntfy/issues) to see if somebody else had 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. 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? ## 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 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 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. 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? ## 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 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 cache and local storage. That means it does not need to be protected with a login screen, and it poses no additional
security risk. So technically, it does not need to be disabled. security risk. So technically, it does not need to be disabled.
However, if you still want to disable it, you can do so with the `web-root: disable` option in the `server.yml` file. However, if you still want to disable it, you can do so with the `web-root: disable` option in the `server.yml` file.
Think of the ntfy web app like an Android/iOS app. It is freely available and accessible to anyone, yet useless without Think of the ntfy web app like an Android/iOS app. It is freely available and accessible to anyone, yet useless without
a proper backend. So as long as you secure your backend with ACLs, exposing the ntfy web app to the Internet is harmless. a proper backend. So as long as you secure your backend with ACLs, exposing the ntfy web app to the Internet is harmless.
## Where can I donate? ## Where can I donate?
I have just very recently started accepting donations via [GitHub Sponsors](https://github.com/sponsors/binwiederhier). 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 I would be humbled if you helped me carry the server and developer account costs. Even small donations are very much
appreciated. appreciated.

View file

@ -1,16 +1,18 @@
# Getting started # Getting started
ntfy lets you **send push notifications to your phone or desktop via scripts from any computer**, using simple HTTP PUT ntfy lets you **send push notifications to your phone or desktop via scripts from any computer**, using simple HTTP PUT
or POST requests. I use it to notify myself when scripts fail, or long-running commands complete. or POST requests. I use it to notify myself when scripts fail, or long-running commands complete.
## Step 1: Get the app ## Step 1: Get the app
<a href="https://play.google.com/store/apps/details?id=io.heckel.ntfy"><img src="../../static/img/badge-googleplay.png"></a> <a href="https://play.google.com/store/apps/details?id=io.heckel.ntfy"><img src="../../static/img/badge-googleplay.png"></a>
<a href="https://f-droid.org/en/packages/io.heckel.ntfy/"><img src="../../static/img/badge-fdroid.png"></a> <a href="https://f-droid.org/en/packages/io.heckel.ntfy/"><img src="../../static/img/badge-fdroid.png"></a>
<a href="https://apps.apple.com/us/app/ntfy/id1625396347"><img src="../../static/img/badge-appstore.png"></a> <a href="https://apps.apple.com/us/app/ntfy/id1625396347"><img src="../../static/img/badge-appstore.png"></a>
To [receive notifications on your phone](subscribe/phone.md), install the app, either via Google Play or F-Droid. To [receive notifications on your phone](subscribe/phone.md), install the app, either via Google Play or F-Droid.
Once installed, open it and subscribe to a topic of your choosing. Topics don't have to explicitly be created, so just Once installed, open it and subscribe to a topic of your choosing. Topics don't have to explicitly be created, so just
pick a name and use it later when you [publish a message](publish.md). Note that **topic names are public, so it's wise pick a name and use it later when you [publish a message](publish.md). Note that **topic names are public, so it's wise
to choose something that cannot be guessed easily.** to choose something that cannot be guessed easily.**
For this guide, we'll just use `mytopic` as our topic name: For this guide, we'll just use `mytopic` as our topic name:
@ -22,30 +24,35 @@ For this guide, we'll just use `mytopic` as our topic name:
That's it. After you tap "Subscribe", the app is listening for new messages on that topic. That's it. After you tap "Subscribe", the app is listening for new messages on that topic.
## Step 2: Send a message ## Step 2: Send a message
Now let's [send a message](publish.md) to our topic. It's easy in every language, since we're just using HTTP PUT/POST, Now let's [send a message](publish.md) to our topic. It's easy in every language, since we're just using HTTP PUT/POST,
or with the [ntfy CLI](install.md). The message is in the request body. Here's an example showing how to publish a or with the [ntfy CLI](install.md). The message is in the request body. Here's an example showing how to publish a
simple message using a POST request: simple message using a POST request:
=== "Command line (curl)" === "Command line (curl)"
```
```sh
curl -d "Backup successful 😀" ntfy.sh/mytopic curl -d "Backup successful 😀" ntfy.sh/mytopic
``` ```
=== "ntfy CLI" === "ntfy CLI"
```
```sh
ntfy publish mytopic "Backup successful 😀" ntfy publish mytopic "Backup successful 😀"
``` ```
=== "HTTP" === "HTTP"
``` http
```http
POST /mytopic HTTP/1.1 POST /mytopic HTTP/1.1
Host: ntfy.sh Host: ntfy.sh
Backup successful 😀 Backup successful 😀
``` ```
=== "JavaScript" === "JavaScript"
``` javascript
```javascript
fetch('https://ntfy.sh/mytopic', { fetch('https://ntfy.sh/mytopic', {
method: 'POST', // PUT works too method: 'POST', // PUT works too
body: 'Backup successful 😀' body: 'Backup successful 😀'
@ -53,19 +60,22 @@ simple message using a POST request:
``` ```
=== "Go" === "Go"
``` go
```go
http.Post("https://ntfy.sh/mytopic", "text/plain", http.Post("https://ntfy.sh/mytopic", "text/plain",
strings.NewReader("Backup successful 😀")) strings.NewReader("Backup successful 😀"))
``` ```
=== "Python" === "Python"
``` python
```python
requests.post("https://ntfy.sh/mytopic", requests.post("https://ntfy.sh/mytopic",
data="Backup successful 😀".encode(encoding='utf-8')) data="Backup successful 😀".encode(encoding='utf-8'))
``` ```
=== "PHP" === "PHP"
``` php-inline
```php-inline
file_get_contents('https://ntfy.sh/mytopic', false, stream_context_create([ file_get_contents('https://ntfy.sh/mytopic', false, stream_context_create([
'http' => [ 'http' => [
'method' => 'POST', // PUT also works 'method' => 'POST', // PUT also works
@ -91,5 +101,3 @@ Here's another video showing the entire process:
<video controls muted autoplay loop width="650" src="static/img/android-video-overview.mp4"></video> <video controls muted autoplay loop width="650" src="static/img/android-video-overview.mp4"></video>
<figcaption>Sending push notifications to your Android phone</figcaption> <figcaption>Sending push notifications to your Android phone</figcaption>
</figure> </figure>

View file

@ -1,14 +1,16 @@
# Installing ntfy # Installing ntfy
The `ntfy` CLI allows you to [publish messages](publish.md), [subscribe to topics](subscribe/cli.md) as well as to The `ntfy` CLI allows you to [publish messages](publish.md), [subscribe to topics](subscribe/cli.md) as well as to
self-host your own ntfy server. It's all pretty straight forward. Just install the binary, package or Docker image, self-host your own ntfy server. It's all pretty straight forward. Just install the binary, package or Docker image,
configure it and run it. Just like any other software. No fuzz. configure it and run it. Just like any other software. No fuzz.
!!! info !!! info
The following steps are only required if you want to **self-host your own ntfy server or you want to use the ntfy CLI**. The following steps are only required if you want to **self-host your own ntfy server or you want to use the ntfy CLI**.
If you just want to [send messages using ntfy.sh](publish.md), you don't need to install anything. You can just use If you just want to [send messages using ntfy.sh](publish.md), you don't need to install anything. You can just use
`curl`. `curl`.
## General steps ## General steps
The ntfy server comes as a statically linked binary and is shipped as tarball, deb/rpm packages and as a Docker image. The ntfy server comes as a statically linked binary and is shipped as tarball, deb/rpm packages and as a Docker image.
We support amd64, armv7 and arm64. We support amd64, armv7 and arm64.
@ -18,16 +20,18 @@ We support amd64, armv7 and arm64.
To run the ntfy server, then just run `ntfy serve` (or `systemctl start ntfy` when using the deb/rpm). To run the ntfy server, then just run `ntfy serve` (or `systemctl start ntfy` when using the deb/rpm).
To send messages, use `ntfy publish`. To subscribe to topics, use `ntfy subscribe` (see [subscribing via CLI](subscribe/cli.md) To send messages, use `ntfy publish`. To subscribe to topics, use `ntfy subscribe` (see [subscribing via CLI](subscribe/cli.md)
for details). for details).
If you like video tutorials, check out :simple-youtube: [Kris Occhipinti's ntfy install guide](https://www.youtube.com/watch?v=bZzqrX05mNU). If you like video tutorials, check out :simple-youtube: [Kris Occhipinti's ntfy install guide](https://www.youtube.com/watch?v=bZzqrX05mNU).
It's short and to the point. _I am not affiliated with Kris, I just liked the video._ It's short and to the point. _I am not affiliated with Kris, I just liked the video._
## Linux binaries ## Linux binaries
Please check out the [releases page](https://github.com/binwiederhier/ntfy/releases) for binaries and Please check out the [releases page](https://github.com/binwiederhier/ntfy/releases) for binaries and
deb/rpm packages. deb/rpm packages.
=== "x86_64/amd64" === "x86_64/amd64"
```bash ```bash
wget https://github.com/binwiederhier/ntfy/releases/download/v2.5.0/ntfy_2.5.0_linux_x86_64.tar.gz wget https://github.com/binwiederhier/ntfy/releases/download/v2.5.0/ntfy_2.5.0_linux_x86_64.tar.gz
tar zxvf ntfy_2.5.0_linux_x86_64.tar.gz tar zxvf ntfy_2.5.0_linux_x86_64.tar.gz
@ -37,6 +41,7 @@ deb/rpm packages.
``` ```
=== "armv6" === "armv6"
```bash ```bash
wget https://github.com/binwiederhier/ntfy/releases/download/v2.5.0/ntfy_2.5.0_linux_armv6.tar.gz wget https://github.com/binwiederhier/ntfy/releases/download/v2.5.0/ntfy_2.5.0_linux_armv6.tar.gz
tar zxvf ntfy_2.5.0_linux_armv6.tar.gz tar zxvf ntfy_2.5.0_linux_armv6.tar.gz
@ -46,6 +51,7 @@ deb/rpm packages.
``` ```
=== "armv7/armhf" === "armv7/armhf"
```bash ```bash
wget https://github.com/binwiederhier/ntfy/releases/download/v2.5.0/ntfy_2.5.0_linux_armv7.tar.gz wget https://github.com/binwiederhier/ntfy/releases/download/v2.5.0/ntfy_2.5.0_linux_armv7.tar.gz
tar zxvf ntfy_2.5.0_linux_armv7.tar.gz tar zxvf ntfy_2.5.0_linux_armv7.tar.gz
@ -55,6 +61,7 @@ deb/rpm packages.
``` ```
=== "arm64" === "arm64"
```bash ```bash
wget https://github.com/binwiederhier/ntfy/releases/download/v2.5.0/ntfy_2.5.0_linux_arm64.tar.gz wget https://github.com/binwiederhier/ntfy/releases/download/v2.5.0/ntfy_2.5.0_linux_arm64.tar.gz
tar zxvf ntfy_2.5.0_linux_arm64.tar.gz tar zxvf ntfy_2.5.0_linux_arm64.tar.gz
@ -64,15 +71,17 @@ deb/rpm packages.
``` ```
## Debian/Ubuntu repository ## Debian/Ubuntu repository
Installation via Debian repository: Installation via Debian repository:
=== "x86_64/amd64" === "x86_64/amd64"
```bash ```bash
sudo mkdir -p /etc/apt/keyrings sudo mkdir -p /etc/apt/keyrings
curl -fsSL https://archive.heckel.io/apt/pubkey.txt | sudo gpg --dearmor -o /etc/apt/keyrings/archive.heckel.io.gpg curl -fsSL https://archive.heckel.io/apt/pubkey.txt | sudo gpg --dearmor -o /etc/apt/keyrings/archive.heckel.io.gpg
sudo apt install apt-transport-https sudo apt install apt-transport-https
sudo sh -c "echo 'deb [arch=amd64 signed-by=/etc/apt/keyrings/archive.heckel.io.gpg] https://archive.heckel.io/apt debian main' \ sudo sh -c "echo 'deb [arch=amd64 signed-by=/etc/apt/keyrings/archive.heckel.io.gpg] https://archive.heckel.io/apt debian main' \
> /etc/apt/sources.list.d/archive.heckel.io.list" > /etc/apt/sources.list.d/archive.heckel.io.list"
sudo apt update sudo apt update
sudo apt install ntfy sudo apt install ntfy
sudo systemctl enable ntfy sudo systemctl enable ntfy
@ -80,6 +89,7 @@ Installation via Debian repository:
``` ```
=== "armv7/armhf" === "armv7/armhf"
```bash ```bash
sudo mkdir -p /etc/apt/keyrings sudo mkdir -p /etc/apt/keyrings
curl -fsSL https://archive.heckel.io/apt/pubkey.txt | sudo gpg --dearmor -o /etc/apt/keyrings/archive.heckel.io.gpg curl -fsSL https://archive.heckel.io/apt/pubkey.txt | sudo gpg --dearmor -o /etc/apt/keyrings/archive.heckel.io.gpg
@ -93,6 +103,7 @@ Installation via Debian repository:
``` ```
=== "arm64" === "arm64"
```bash ```bash
sudo mkdir -p /etc/apt/keyrings sudo mkdir -p /etc/apt/keyrings
curl -fsSL https://archive.heckel.io/apt/pubkey.txt | sudo gpg --dearmor -o /etc/apt/keyrings/archive.heckel.io.gpg curl -fsSL https://archive.heckel.io/apt/pubkey.txt | sudo gpg --dearmor -o /etc/apt/keyrings/archive.heckel.io.gpg
@ -108,6 +119,7 @@ Installation via Debian repository:
Manually installing the .deb file: Manually installing the .deb file:
=== "x86_64/amd64" === "x86_64/amd64"
```bash ```bash
wget https://github.com/binwiederhier/ntfy/releases/download/v2.5.0/ntfy_2.5.0_linux_amd64.deb wget https://github.com/binwiederhier/ntfy/releases/download/v2.5.0/ntfy_2.5.0_linux_amd64.deb
sudo dpkg -i ntfy_*.deb sudo dpkg -i ntfy_*.deb
@ -116,6 +128,7 @@ Manually installing the .deb file:
``` ```
=== "armv6" === "armv6"
```bash ```bash
wget https://github.com/binwiederhier/ntfy/releases/download/v2.5.0/ntfy_2.5.0_linux_armv6.deb wget https://github.com/binwiederhier/ntfy/releases/download/v2.5.0/ntfy_2.5.0_linux_armv6.deb
sudo dpkg -i ntfy_*.deb sudo dpkg -i ntfy_*.deb
@ -124,6 +137,7 @@ Manually installing the .deb file:
``` ```
=== "armv7/armhf" === "armv7/armhf"
```bash ```bash
wget https://github.com/binwiederhier/ntfy/releases/download/v2.5.0/ntfy_2.5.0_linux_armv7.deb wget https://github.com/binwiederhier/ntfy/releases/download/v2.5.0/ntfy_2.5.0_linux_armv7.deb
sudo dpkg -i ntfy_*.deb sudo dpkg -i ntfy_*.deb
@ -132,6 +146,7 @@ Manually installing the .deb file:
``` ```
=== "arm64" === "arm64"
```bash ```bash
wget https://github.com/binwiederhier/ntfy/releases/download/v2.5.0/ntfy_2.5.0_linux_arm64.deb wget https://github.com/binwiederhier/ntfy/releases/download/v2.5.0/ntfy_2.5.0_linux_arm64.deb
sudo dpkg -i ntfy_*.deb sudo dpkg -i ntfy_*.deb
@ -142,13 +157,15 @@ Manually installing the .deb file:
## Fedora/RHEL/CentOS ## Fedora/RHEL/CentOS
=== "x86_64/amd64" === "x86_64/amd64"
```bash ```bash
sudo rpm -ivh https://github.com/binwiederhier/ntfy/releases/download/v2.5.0/ntfy_2.5.0_linux_amd64.rpm sudo rpm -ivh https://github.com/binwiederhier/ntfy/releases/download/v2.5.0/ntfy_2.5.0_linux_amd64.rpm
sudo systemctl enable ntfy sudo systemctl enable ntfy
sudo systemctl start ntfy sudo systemctl start ntfy
``` ```
=== "armv6" === "armv6"
```bash ```bash
sudo rpm -ivh https://github.com/binwiederhier/ntfy/releases/download/v2.5.0/ntfy_2.5.0_linux_armv6.rpm sudo rpm -ivh https://github.com/binwiederhier/ntfy/releases/download/v2.5.0/ntfy_2.5.0_linux_armv6.rpm
sudo systemctl enable ntfy sudo systemctl enable ntfy
@ -156,26 +173,31 @@ Manually installing the .deb file:
``` ```
=== "armv7/armhf" === "armv7/armhf"
```bash ```bash
sudo rpm -ivh https://github.com/binwiederhier/ntfy/releases/download/v2.5.0/ntfy_2.5.0_linux_armv7.rpm sudo rpm -ivh https://github.com/binwiederhier/ntfy/releases/download/v2.5.0/ntfy_2.5.0_linux_armv7.rpm
sudo systemctl enable ntfy sudo systemctl enable ntfy
sudo systemctl start ntfy sudo systemctl start ntfy
``` ```
=== "arm64" === "arm64"
```bash ```bash
sudo rpm -ivh https://github.com/binwiederhier/ntfy/releases/download/v2.5.0/ntfy_2.5.0_linux_arm64.rpm sudo rpm -ivh https://github.com/binwiederhier/ntfy/releases/download/v2.5.0/ntfy_2.5.0_linux_arm64.rpm
sudo systemctl enable ntfy sudo systemctl enable ntfy
sudo systemctl start ntfy sudo systemctl start ntfy
``` ```
## Arch Linux ## Arch Linux
ntfy can be installed using an [AUR package](https://aur.archlinux.org/packages/ntfysh-bin/). You can use an [AUR helper](https://wiki.archlinux.org/title/AUR_helpers) like `paru`, `yay` or others to download, build and install ntfy and keep it up to date. ntfy can be installed using an [AUR package](https://aur.archlinux.org/packages/ntfysh-bin/). You can use an [AUR helper](https://wiki.archlinux.org/title/AUR_helpers) like `paru`, `yay` or others to download, build and install ntfy and keep it up to date.
``` ```
paru -S ntfysh-bin paru -S ntfysh-bin
``` ```
Alternatively, run the following commands to install ntfy manually: Alternatively, run the following commands to install ntfy manually:
``` ```
curl https://aur.archlinux.org/cgit/aur.git/snapshot/ntfysh-bin.tar.gz | tar xzv curl https://aur.archlinux.org/cgit/aur.git/snapshot/ntfysh-bin.tar.gz | tar xzv
cd ntfysh-bin cd ntfysh-bin
@ -183,46 +205,51 @@ makepkg -si
``` ```
## NixOS / Nix ## NixOS / Nix
ntfy is packaged in nixpkgs as `ntfy-sh`. It can be installed by adding the package name to the configuration file and calling `nixos-rebuild`. Alternatively, the following command can be used to install ntfy in the current user environment: ntfy is packaged in nixpkgs as `ntfy-sh`. It can be installed by adding the package name to the configuration file and calling `nixos-rebuild`. Alternatively, the following command can be used to install ntfy in the current user environment:
``` ```
nix-env -iA ntfy-sh nix-env -iA ntfy-sh
``` ```
NixOS also supports [declarative setup of the ntfy server](https://search.nixos.org/options?channel=unstable&show=services.ntfy-sh.enable&from=0&size=50&sort=relevance&type=packages&query=ntfy). NixOS also supports [declarative setup of the ntfy server](https://search.nixos.org/options?channel=unstable&show=services.ntfy-sh.enable&from=0&size=50&sort=relevance&type=packages&query=ntfy).
## macOS ## macOS
The [ntfy CLI](subscribe/cli.md) (`ntfy publish` and `ntfy subscribe` only) is supported on macOS as well.
To install, please [download the tarball](https://github.com/binwiederhier/ntfy/releases/download/v2.5.0/ntfy_2.5.0_macOS_all.tar.gz),
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 The [ntfy CLI](subscribe/cli.md) (`ntfy publish` and `ntfy subscribe` only) is supported on macOS as well.
To install, please [download the tarball](https://github.com/binwiederhier/ntfy/releases/download/v2.5.0/ntfy_2.5.0_macOS_all.tar.gz),
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). `~/Library/Application Support/ntfy/client.yml` (sample included in the tarball).
```bash ```bash
curl -L https://github.com/binwiederhier/ntfy/releases/download/v2.5.0/ntfy_2.5.0_macOS_all.tar.gz > ntfy_2.5.0_macOS_all.tar.gz curl -L https://github.com/binwiederhier/ntfy/releases/download/v2.5.0/ntfy_2.5.0_macOS_all.tar.gz > ntfy_2.5.0_macOS_all.tar.gz
tar zxvf ntfy_2.5.0_macOS_all.tar.gz tar zxvf ntfy_2.5.0_macOS_all.tar.gz
sudo cp -a ntfy_2.5.0_macOS_all/ntfy /usr/local/bin/ntfy sudo cp -a ntfy_2.5.0_macOS_all/ntfy /usr/local/bin/ntfy
mkdir ~/Library/Application\ Support/ntfy mkdir ~/Library/Application\ Support/ntfy
cp ntfy_2.5.0_macOS_all/client/client.yml ~/Library/Application\ Support/ntfy/client.yml cp ntfy_2.5.0_macOS_all/client/client.yml ~/Library/Application\ Support/ntfy/client.yml
ntfy --help ntfy --help
``` ```
!!! info !!! info
Only the ntfy CLI is supported on macOS. ntfy server is currently not supported, but you can build and run it for Only the ntfy CLI is supported on macOS. ntfy server is currently not supported, but you can build and run it for
development as well. Check out the [build instructions](develop.md) for details. development as well. Check out the [build instructions](develop.md) for details.
## Homebrew ## Homebrew
To install the [ntfy CLI](subscribe/cli.md) (`ntfy publish` and `ntfy subscribe` only) via Homebrew (Linux and macOS), To install the [ntfy CLI](subscribe/cli.md) (`ntfy publish` and `ntfy subscribe` only) via Homebrew (Linux and macOS),
simply run: simply run:
``` ```
brew install ntfy brew install ntfy
``` ```
## Windows ## Windows
The [ntfy CLI](subscribe/cli.md) (`ntfy publish` and `ntfy subscribe` only) is supported on Windows as well. The [ntfy CLI](subscribe/cli.md) (`ntfy publish` and `ntfy subscribe` only) is supported on Windows as well.
To install, please [download the latest ZIP](https://github.com/binwiederhier/ntfy/releases/download/v2.5.0/ntfy_2.5.0_windows_x86_64.zip), To install, please [download the latest ZIP](https://github.com/binwiederhier/ntfy/releases/download/v2.5.0/ntfy_2.5.0_windows_x86_64.zip),
extract it and place the `ntfy.exe` binary somewhere in your `%Path%`. 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). The default path for the client config file is at `%AppData%\ntfy\client.yml` (not created automatically, sample in the ZIP file).
@ -231,28 +258,31 @@ Also available in [Scoop's](https://scoop.sh) Main repository:
`scoop install ntfy` `scoop install ntfy`
!!! info !!! info
There is currently no installer for Windows, and the binary is not signed. If this is desired, please create a There is currently no installer for Windows, and the binary is not signed. If this is desired, please create a
[GitHub issue](https://github.com/binwiederhier/ntfy/issues) to let me know. [GitHub issue](https://github.com/binwiederhier/ntfy/issues) to let me know.
## Docker ## Docker
The [ntfy image](https://hub.docker.com/r/binwiederhier/ntfy) is available for amd64, armv6, armv7 and arm64. It should
The [ntfy image](https://hub.docker.com/r/binwiederhier/ntfy) is available for amd64, armv6, armv7 and arm64. It should
be pretty straight forward to use. be pretty straight forward to use.
The server exposes its web UI and the API on port 80, so you need to expose that in Docker. To use the persistent The server exposes its web UI and the API on port 80, so you need to expose that in Docker. To use the persistent
[message cache](config.md#message-cache), you also need to map a volume to `/var/cache/ntfy`. To change other settings, [message cache](config.md#message-cache), you also need to map a volume to `/var/cache/ntfy`. To change other settings,
you should map `/etc/ntfy`, so you can edit `/etc/ntfy/server.yml`. you should map `/etc/ntfy`, so you can edit `/etc/ntfy/server.yml`.
!!! info !!! info
Note that the Docker image **does not contain a `/etc/ntfy/server.yml` file**. If you'd like to use a config file, Note that the Docker image **does not contain a `/etc/ntfy/server.yml` file**. If you'd like to use a config file,
please manually create one outside the image and map it as a volume, e.g. via `-v /etc/ntfy:/etc/ntfy`. You may please manually create one outside the image and map it as a volume, e.g. via `-v /etc/ntfy:/etc/ntfy`. You may
use the [`server.yml` file on GitHub](https://github.com/binwiederhier/ntfy/blob/main/server/server.yml) as a template. use the [`server.yml` file on GitHub](https://github.com/binwiederhier/ntfy/blob/main/server/server.yml) as a template.
Basic usage (no cache or additional config): Basic usage (no cache or additional config):
``` ```
docker run -p 80:80 -it binwiederhier/ntfy serve docker run -p 80:80 -it binwiederhier/ntfy serve
``` ```
With persistent cache (configured as command line arguments): With persistent cache (configured as command line arguments):
```bash ```bash
docker run \ docker run \
-v /var/cache/ntfy:/var/cache/ntfy \ -v /var/cache/ntfy:/var/cache/ntfy \
@ -264,6 +294,7 @@ docker run \
``` ```
With other config options, timezone, and non-root user (configured via `/etc/ntfy/server.yml`, see [configuration](config.md) for details): With other config options, timezone, and non-root user (configured via `/etc/ntfy/server.yml`, see [configuration](config.md) for details):
```bash ```bash
docker run \ docker run \
-v /etc/ntfy:/etc/ntfy \ -v /etc/ntfy:/etc/ntfy \
@ -276,6 +307,7 @@ docker run \
``` ```
Using docker-compose with non-root user and healthchecks enabled: Using docker-compose with non-root user and healthchecks enabled:
```yaml ```yaml
version: "2.1" version: "2.1"
@ -286,7 +318,7 @@ services:
command: command:
- serve - serve
environment: environment:
- TZ=UTC # optional: set desired timezone - TZ=UTC # optional: set desired timezone
user: UID:GID # optional: replace with your own user/group or uid/gid user: UID:GID # optional: replace with your own user/group or uid/gid
volumes: volumes:
- /var/cache/ntfy:/var/cache/ntfy - /var/cache/ntfy:/var/cache/ntfy
@ -294,22 +326,28 @@ services:
ports: ports:
- 80:80 - 80:80
healthcheck: # optional: remember to adapt the host:port to your environment healthcheck: # optional: remember to adapt the host:port to your environment
test: ["CMD-SHELL", "wget -q --tries=1 http://localhost:80/v1/health -O - | grep -Eo '\"healthy\"\\s*:\\s*true' || exit 1"] test:
interval: 60s [
timeout: 10s "CMD-SHELL",
retries: 3 "wget -q --tries=1 http://localhost:80/v1/health -O - | grep -Eo '\"healthy\"\\s*:\\s*true' || exit 1",
start_period: 40s ]
interval: 60s
timeout: 10s
retries: 3
start_period: 40s
restart: unless-stopped restart: unless-stopped
``` ```
If using a non-root user when running the docker version, be sure to chown the server.yml, user.db, and cache.db files and attachments directory to the same uid/gid. If using a non-root user when running the docker version, be sure to chown the server.yml, user.db, and cache.db files and attachments directory to the same uid/gid.
Alternatively, you may wish to build a customized Docker image that can be run with fewer command-line arguments and without delivering the configuration file separately. Alternatively, you may wish to build a customized Docker image that can be run with fewer command-line arguments and without delivering the configuration file separately.
``` ```
FROM binwiederhier/ntfy FROM binwiederhier/ntfy
COPY server.yml /etc/ntfy/server.yml COPY server.yml /etc/ntfy/server.yml
ENTRYPOINT ["ntfy", "serve"] ENTRYPOINT ["ntfy", "serve"]
``` ```
This image can be pushed to a container registry and shipped independently. All that's needed when running it is mapping ntfy's port to a host port. This image can be pushed to a container registry and shipped independently. All that's needed when running it is mapping ntfy's port to a host port.
## Kubernetes ## Kubernetes
@ -318,8 +356,8 @@ The setup for Kubernetes is very similar to that for Docker, and requires a fair
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 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. unmanned pod.
=== "deployment" === "deployment"
```yaml ```yaml
apiVersion: apps/v1 apiVersion: apps/v1
kind: Deployment kind: Deployment
@ -368,6 +406,7 @@ unmanned pod.
``` ```
=== "stateful set" === "stateful set"
```yaml ```yaml
apiVersion: apps/v1 apiVersion: apps/v1
kind: StatefulSet kind: StatefulSet
@ -411,6 +450,7 @@ unmanned pod.
``` ```
=== "pod" === "pod"
```yaml ```yaml
apiVersion: v1 apiVersion: v1
kind: Pod kind: Pod
@ -442,6 +482,7 @@ unmanned pod.
Configuration is relatively straightforward. As an example, a minimal configuration is provided. Configuration is relatively straightforward. As an example, a minimal configuration is provided.
=== "resource definition" === "resource definition"
```yaml ```yaml
apiVersion: v1 apiVersion: v1
kind: ConfigMap kind: ConfigMap
@ -454,8 +495,9 @@ Configuration is relatively straightforward. As an example, a minimal configurat
``` ```
=== "from-file" === "from-file"
```bash ```bash
kubectl create configmap ntfy --from-file=server.yml kubectl create configmap ntfy --from-file=server.yml
``` ```
## Kustomize ## Kustomize
@ -464,22 +506,23 @@ ntfy can be deployed in a Kubernetes cluster with [Kustomize](https://github.com
to customize Kubernetes objects using a `kustomization.yaml` file. to customize Kubernetes objects using a `kustomization.yaml` file.
1. Create new folder - `ntfy` 1. Create new folder - `ntfy`
2. Add all files listed below 2. Add all files listed below
1. `kustomization.yaml` - stores all configmaps and resources used in a deployment 1. `kustomization.yaml` - stores all configmaps and resources used in a deployment
2. `ntfy-deployment.yaml` - define deployment type and its parameters 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 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 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/) 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 6. `server.yaml` - simple server configuration
3. Replace **TESTNAMESPACE** within `kustomization.yaml` with designated namespace 3. Replace **TESTNAMESPACE** within `kustomization.yaml` with designated namespace
4. Replace **ntfy.test** within `ntfy-ingress.yaml` with desired DNS name 4. Replace **ntfy.test** within `ntfy-ingress.yaml` with desired DNS name
5. Apply configuration to cluster set in current context: 5. Apply configuration to cluster set in current context:
```bash ```bash
kubectl apply -k /ntfy kubectl apply -k /ntfy
``` ```
=== "kustomization.yaml" === "kustomization.yaml"
```yaml ```yaml
apiVersion: kustomize.config.k8s.io/v1beta1 apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization kind: Kustomization
@ -490,11 +533,13 @@ kubectl apply -k /ntfy
- ntfy-ingress.yaml # ingress definition - 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 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 - name: server-config
files: files:
- server.yml - server.yml
namespace: TESTNAMESPACE # select namespace for whole application namespace: TESTNAMESPACE # select namespace for whole application
``` ```
=== "ntfy-deployment.yaml" === "ntfy-deployment.yaml"
```yaml ```yaml
apiVersion: apps/v1 apiVersion: apps/v1
kind: Deployment kind: Deployment
@ -514,7 +559,7 @@ kubectl apply -k /ntfy
app: ntfy-pod app: ntfy-pod
spec: spec:
containers: containers:
- name: ntfy - name: ntfy
image: binwiederhier/ntfy:v1.28.0 # set deployed version image: binwiederhier/ntfy:v1.28.0 # set deployed version
args: ["serve"] args: ["serve"]
env: #example of adjustments made in environmental variables env: #example of adjustments made in environmental variables
@ -525,8 +570,8 @@ kubectl apply -k /ntfy
- name: NTFY_LOG_LEVEL # adjust log level - name: NTFY_LOG_LEVEL # adjust log level
value: INFO value: INFO
- name: NTFY_BASE_URL # add base url - name: NTFY_BASE_URL # add base url
value: XXXXXXXXXX value: XXXXXXXXXX
ports: ports:
- containerPort: 80 - containerPort: 80
name: http-ntfy name: http-ntfy
resources: resources:
@ -550,8 +595,9 @@ kubectl apply -k /ntfy
persistentVolumeClaim: # stores /cache/ntfy in defined pv persistentVolumeClaim: # stores /cache/ntfy in defined pv
claimName: ntfy-pvc claimName: ntfy-pvc
``` ```
=== "ntfy-pvc.yaml" === "ntfy-pvc.yaml"
```yaml ```yaml
apiVersion: v1 apiVersion: v1
kind: PersistentVolumeClaim kind: PersistentVolumeClaim
@ -567,11 +613,12 @@ kubectl apply -k /ntfy
``` ```
=== "ntfy-svc.yaml" === "ntfy-svc.yaml"
```yaml ```yaml
apiVersion: v1 apiVersion: v1
kind: Service kind: Service
metadata: metadata:
name: ntfy-svc name: ntfy-svc
spec: spec:
type: ClusterIP type: ClusterIP
selector: selector:
@ -584,6 +631,7 @@ kubectl apply -k /ntfy
``` ```
=== "ntfy-ingress.yaml" === "ntfy-ingress.yaml"
```yaml ```yaml
apiVersion: networking.k8s.io/v1 apiVersion: networking.k8s.io/v1
kind: Ingress kind: Ingress
@ -604,6 +652,7 @@ kubectl apply -k /ntfy
``` ```
=== "server.yml" === "server.yml"
```yaml ```yaml
cache-file: "/var/cache/ntfy/cache.db" cache-file: "/var/cache/ntfy/cache.db"
attachment-cache-dir: "/var/cache/ntfy/attachments" attachment-cache-dir: "/var/cache/ntfy/attachments"

View file

@ -41,7 +41,7 @@ I've added a ⭐ to projects or posts that have a significant following, or had
- [FindMyDevice](https://gitlab.com/Nulide/findmydevice/) - Find your Device with an SMS or online with the help of FMDServer - [FindMyDevice](https://gitlab.com/Nulide/findmydevice/) - Find your Device with an SMS or online with the help of FMDServer
- [Tox Push Message App](https://github.com/zoff99/tox_push_msg_app) - Tox Push Message App - [Tox Push Message App](https://github.com/zoff99/tox_push_msg_app) - Tox Push Message App
## Libraries ## Libraries
- [ntfy-php-library](https://github.com/VerifiedJoseph/ntfy-php-library) - PHP library for sending messages using a ntfy server (PHP) - [ntfy-php-library](https://github.com/VerifiedJoseph/ntfy-php-library) - PHP library for sending messages using a ntfy server (PHP)
- [ntfy-notifier](https://github.com/DAcodedBEAT/ntfy-notifier) - Symfony Notifier integration for ntfy (PHP) - [ntfy-notifier](https://github.com/DAcodedBEAT/ntfy-notifier) - Symfony Notifier integration for ntfy (PHP)
@ -66,7 +66,7 @@ I've added a ⭐ to projects or posts that have a significant following, or had
- [ntfyr](https://github.com/haxwithaxe/ntfyr) - A simple commandline tool to send notifications to ntfy - [ntfyr](https://github.com/haxwithaxe/ntfyr) - A simple commandline tool to send notifications to ntfy
- [ntfy.py](https://github.com/ioqy/ntfy-client-python) - ntfy.py is a simple nfty.sh client for sending notifications - [ntfy.py](https://github.com/ioqy/ntfy-client-python) - ntfy.py is a simple nfty.sh client for sending notifications
## Projects + scripts ## Projects + scripts
- [Grafana-to-ntfy](https://github.com/kittyandrew/grafana-to-ntfy) - Grafana-to-ntfy alerts channel (Rust) - [Grafana-to-ntfy](https://github.com/kittyandrew/grafana-to-ntfy) - Grafana-to-ntfy alerts channel (Rust)
- [Grafana-ntfy-webhook-integration](https://github.com/academo/grafana-alerting-ntfy-webhook-integration) - Integrates Grafana alerts webhooks (Go) - [Grafana-ntfy-webhook-integration](https://github.com/academo/grafana-alerting-ntfy-webhook-integration) - Integrates Grafana alerts webhooks (Go)
@ -77,9 +77,9 @@ I've added a ⭐ to projects or posts that have a significant following, or had
- [ntfy.el](https://github.com/shombando/ntfy) - Send notifications from Emacs (Emacs) - [ntfy.el](https://github.com/shombando/ntfy) - Send notifications from Emacs (Emacs)
- [backup-projects](https://gist.github.com/anthonyaxenov/826ba65abbabd5b00196bc3e6af76002) - Stupidly simple backup script for own projects (Shell) - [backup-projects](https://gist.github.com/anthonyaxenov/826ba65abbabd5b00196bc3e6af76002) - Stupidly simple backup script for own projects (Shell)
- [grav-plugin-whistleblower](https://github.com/Himmlisch-Studios/grav-plugin-whistleblower) - Grav CMS plugin to get notifications via ntfy (PHP) - [grav-plugin-whistleblower](https://github.com/Himmlisch-Studios/grav-plugin-whistleblower) - Grav CMS plugin to get notifications via ntfy (PHP)
- [ntfy-server-status](https://github.com/filip2cz/ntfy-server-status) - Checking if server is online and reporting through ntfy (C) - [ntfy-server-status](https://github.com/filip2cz/ntfy-server-status) - Checking if server is online and reporting through ntfy (C)
- [borg-based backup](https://github.com/davidhi7/backup) - Simple borg-based backup script with notifications based on ntfy.sh or Discord webhooks (Python/Shell) - [borg-based backup](https://github.com/davidhi7/backup) - Simple borg-based backup script with notifications based on ntfy.sh or Discord webhooks (Python/Shell)
- [ntfy.sh *arr script](https://github.com/agent-squirrel/nfty-arr-script) - Quick and hacky script to get sonarr/radarr to notify the ntfy.sh service (Shell) - [ntfy.sh \*arr script](https://github.com/agent-squirrel/nfty-arr-script) - Quick and hacky script to get sonarr/radarr to notify the ntfy.sh service (Shell)
- [website-watcher](https://github.com/muety/website-watcher) - A small tool to watch websites for changes (with XPath support) (Python) - [website-watcher](https://github.com/muety/website-watcher) - A small tool to watch websites for changes (with XPath support) (Python)
- [siteeagle](https://github.com/tpanum/siteeagle) - A small Python script to monitor websites and notify changes (Python) - [siteeagle](https://github.com/tpanum/siteeagle) - A small Python script to monitor websites and notify changes (Python)
- [send_to_phone](https://github.com/whipped-cream/send_to_phone) - Scripts to upload a file to Transfer.sh and ping ntfy with the download link (Python) - [send_to_phone](https://github.com/whipped-cream/send_to_phone) - Scripts to upload a file to Transfer.sh and ping ntfy with the download link (Python)
@ -103,7 +103,7 @@ I've added a ⭐ to projects or posts that have a significant following, or had
- [ntfy-notification-channel](https://github.com/wijourdil/ntfy-notification-channel) - Laravel Notification channel for ntfy (PHP) - [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_on_a_chip](https://github.com/gergepalfi/ntfy_on_a_chip) - ESP8266 and ESP32 client code to communicate with ntfy
- [ntfy-sdk](https://github.com/yukibtc/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 - [ntfy_ynh](https://github.com/YunoHost-Apps/ntfy_ynh) - ntfy app for YunoHost
- [woodpecker-ntfy](https://codeberg.org/l-x/woodpecker-ntfy)- Woodpecker CI plugin for sending ntfy notfication from a pipeline (Go) - [woodpecker-ntfy](https://codeberg.org/l-x/woodpecker-ntfy)- Woodpecker CI plugin for sending ntfy notfication from a pipeline (Go)
- [drone-ntfy](https://github.com/Clortox/drone-ntfy) - Drone.io plugin for sending ntfy notifications from a pipeline (Shell) - [drone-ntfy](https://github.com/Clortox/drone-ntfy) - Drone.io plugin for sending ntfy notifications from a pipeline (Shell)
- [ignition-ntfy-module](https://github.com/Kyvis-Labs/ignition-ntfy-module) - Adds support for sending notifications via a ntfy server to Ignition (Java) - [ignition-ntfy-module](https://github.com/Kyvis-Labs/ignition-ntfy-module) - Adds support for sending notifications via a ntfy server to Ignition (Java)
@ -125,31 +125,31 @@ I've added a ⭐ to projects or posts that have a significant following, or had
## Blog + forum posts ## Blog + forum posts
- [ntfy: des notifications instantanées](https://blogmotion.fr/diy/ntfy-notification-push-domotique-20708) - blogmotion.fr - 5/2023 - [ntfy: des notifications instantanées](https://blogmotion.fr/diy/ntfy-notification-push-domotique-20708) - blogmotion.fr - 5/2023
- [桌面通知ntfy](https://www.cnblogs.com/xueweihan/archive/2023/05/04/17370060.html) - cnblogs.com - 5/2023 - [桌面通知ntfy](https://www.cnblogs.com/xueweihan/archive/2023/05/04/17370060.html) - cnblogs.com - 5/2023
- [ntfy.sh - Open source push notifications via PUT/POST](https://lobste.rs/s/5drapz/ntfy_sh_open_source_push_notifications) - lobste.rs - 5/2023 - [ntfy.sh - Open source push notifications via PUT/POST](https://lobste.rs/s/5drapz/ntfy_sh_open_source_push_notifications) - lobste.rs - 5/2023
- [Install ntfy Inside Docker Container in Linux](https://lindevs.com/install-ntfy-inside-docker-container-in-linux) - lindevs.com - 4/2023 - [Install ntfy Inside Docker Container in Linux](https://lindevs.com/install-ntfy-inside-docker-container-in-linux) - lindevs.com - 4/2023
- [ntfy.sh](https://neo-sahara.com/wp/2023/03/25/ntfy-sh/) - neo-sahara.com - 3/2023 - [ntfy.sh](https://neo-sahara.com/wp/2023/03/25/ntfy-sh/) - neo-sahara.com - 3/2023
- [Using Ntfy to send and receive push notifications - Samuel Rosa de Oliveria - Delphicon 2023](https://www.youtube.com/watch?v=feu0skpI9QI) - youtube.com - 3/2023 - [Using Ntfy to send and receive push notifications - Samuel Rosa de Oliveria - Delphicon 2023](https://www.youtube.com/watch?v=feu0skpI9QI) - youtube.com - 3/2023
- [ntfy: własny darmowy system powiadomień](https://sprawdzone.it/ntfy-wlasny-darmowy-system-powiadomien/) - sprawdzone.it - 3/2023 - [ntfy: własny darmowy system powiadomień](https://sprawdzone.it/ntfy-wlasny-darmowy-system-powiadomien/) - sprawdzone.it - 3/2023
- [Deploying ntfy on railway](https://www.youtube.com/watch?v=auJICXtxoNA) - youtube.com - 3/2023 - [Deploying ntfy on railway](https://www.youtube.com/watch?v=auJICXtxoNA) - youtube.com - 3/2023
- [Start-Job,Variables, and ntfy.sh](https://klingele.dev/2023/03/01/start-jobvariables-and-ntfy-sh/) - klingele.dev - 3/2023 - [Start-Job,Variables, and ntfy.sh](https://klingele.dev/2023/03/01/start-jobvariables-and-ntfy-sh/) - klingele.dev - 3/2023
- [enviar notificaciones automáticas usando ntfy.sh](https://osiux.com/2023-02-15-send-automatic-notifications-using-ntfy.html) - osiux.com - 2/2023 - [enviar notificaciones automáticas usando ntfy.sh](https://osiux.com/2023-02-15-send-automatic-notifications-using-ntfy.html) - osiux.com - 2/2023
- [Carnet IP动态解析以及通过ntfy推送IP信息](https://blog.wslll.cn/index.php/archives/201/) - blog.wslll.cn - 2/2023 - [Carnet IP 动态解析以及通过 ntfy 推送 IP 信息](https://blog.wslll.cn/index.php/archives/201/) - blog.wslll.cn - 2/2023
- [Open-Source-Brieftaube: ntfy verschickt Push-Meldungen auf Smartphone und PC](https://www.heise.de/news/Open-Source-Brieftaube-ntfy-verschickt-Push-Meldungen-auf-Smartphone-und-PC-7521583.html) ⭐ - heise.de - 2/2023 - [Open-Source-Brieftaube: ntfy verschickt Push-Meldungen auf Smartphone und PC](https://www.heise.de/news/Open-Source-Brieftaube-ntfy-verschickt-Push-Meldungen-auf-Smartphone-und-PC-7521583.html) ⭐ - heise.de - 2/2023
- [Video: Simple Push Notifications ntfy](https://www.youtube.com/watch?v=u9EcWrsjE20) ⭐ - youtube.com - 2/2023 - [Video: Simple Push Notifications ntfy](https://www.youtube.com/watch?v=u9EcWrsjE20) ⭐ - youtube.com - 2/2023
- [Use ntfy.sh with Home Assistant](https://diecknet.de/en/2023/02/12/ntfy-sh-with-homeassistant/) - diecknet.de - 2/2023 - [Use ntfy.sh with Home Assistant](https://diecknet.de/en/2023/02/12/ntfy-sh-with-homeassistant/) - diecknet.de - 2/2023
- [On installe Ntfy sur Synology Docker](https://www.maison-et-domotique.com/140356-serveur-notification-jeedom-ntfy-synology-docker/) - maison-et-domotique.co - 1/2023 - [On installe Ntfy sur Synology Docker](https://www.maison-et-domotique.com/140356-serveur-notification-jeedom-ntfy-synology-docker/) - maison-et-domotique.co - 1/2023
- [January 2023 Developer Update](https://community.nodebb.org/topic/16908/january-2023-developer-update) - nodebb.org - 1/2023 - [January 2023 Developer Update](https://community.nodebb.org/topic/16908/january-2023-developer-update) - nodebb.org - 1/2023
- [Comment envoyer des notifications push sur votre téléphone facilement et gratuitement?](https://korben.info/notifications-push-telephone.html) - 1/2023 - [Comment envoyer des notifications push sur votre téléphone facilement et gratuitement?](https://korben.info/notifications-push-telephone.html) - 1/2023
- [UnifiedPush: a decentralized, open-source push notification protocol](https://f-droid.org/en/2022/12/18/unifiedpush.html) ⭐ - 12/2022 - [UnifiedPush: a decentralized, open-source push notification protocol](https://f-droid.org/en/2022/12/18/unifiedpush.html) ⭐ - 12/2022
- [ntfy setup instructions](https://docs.benjamin-altpeter.de/network/vms/1001029-ntfy/) - benjamin-altpeter.de - 12/2022 - [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 Self-Hosted Push Notifications](https://lachlanlife.net/posts/2022-12-ntfy/) - lachlanlife.net - 12/2022
- [NTFY - système de notification hyper simple et complet](https://www.youtube.com/watch?v=UieZYWVVgA4) - youtube.com - 12/2022 - [NTFY - système de notification hyper simple et complet](https://www.youtube.com/watch?v=UieZYWVVgA4) - youtube.com - 12/2022
- [ntfy.sh](https://paramdeo.com/til/ntfy-sh) - paramdeo.com - 11/2022 - [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 - [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 - [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 - [Console #132](https://console.substack.com/p/console-132) ⭐ - console.substack.com - 11/2022
- [How to make my phone buzz*](https://evbogue.com/howtomakemyphonebuzz) - evbogue.com - 11/2022 - [How to make my phone buzz\*](https://evbogue.com/howtomakemyphonebuzz) - evbogue.com - 11/2022
- [MeshCentral - Ntfy Push Notifications ](https://www.youtube.com/watch?v=wyE4rtUd4Bg) - youtube.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 - [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 - [Pointer | Issue #367](https://www.pointer.io/archives/a9495a2a6f/) - pointer.io - 11/2022
@ -161,20 +161,20 @@ I've added a ⭐ to projects or posts that have a significant following, or had
- [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 - [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
- [unRAID Notifications with ntfy.sh](https://lder.dev/posts/ntfy-Notifications-With-unRAID/) - lder.dev - 10/2022 - [unRAID Notifications with ntfy.sh](https://lder.dev/posts/ntfy-Notifications-With-unRAID/) - lder.dev - 10/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 - [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 - [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 - [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 - [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 - [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 - [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 - [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 - [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 - [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 - [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 - [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 - [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 - [无需注册的通知服务 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 - [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 - [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 - [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 - [無料で簡単に通知の送受信ができつつオープンソースでセルフホストも可能な「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 - [Pocketmags ntfy review](https://pocketmags.com/us/linux-format-magazine/march-2022/articles/1104187/ntfy) - pocketmags.com - 3/2022
@ -188,15 +188,14 @@ I've added a ⭐ to projects or posts that have a significant following, or had
- [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 - [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 - [Reddit selfhostable post](https://www.reddit.com/r/selfhosted/comments/qxlsm9/my_open_source_notification_android_app_and/) ⭐ - reddit.com - 11/2021
## Alternative ntfy servers ## Alternative ntfy servers
Here's a list of public ntfy servers. As of right now, there is only one official server. The others are provided by the 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!** ntfy community. Thanks to everyone running a public server. **You guys rock!**
| URL | Country | | URL | Country |
|---------------------------------------------------|--------------------| | ------------------------------------------------- | ---------------- |
| [ntfy.sh](https://ntfy.sh/) (*Official*) | 🇺🇸 United States | | [ntfy.sh](https://ntfy.sh/) (_Official_) | 🇺🇸 United States |
| [ntfy.tedomum.net](https://ntfy.tedomum.net/) | 🇫🇷 France | | [ntfy.tedomum.net](https://ntfy.tedomum.net/) | 🇫🇷 France |
| [ntfy.jae.fi](https://ntfy.jae.fi/) | 🇫🇮 Finland | | [ntfy.jae.fi](https://ntfy.jae.fi/) | 🇫🇮 Finland |
| [ntfy.adminforge.de](https://ntfy.adminforge.de/) | 🇩🇪 Germany | | [ntfy.adminforge.de](https://ntfy.adminforge.de/) | 🇩🇪 Germany |

View file

@ -1,9 +1,11 @@
# Known issues # Known issues
This is an incomplete list of known issues with the ntfy server, Android app, and iOS app. You can find a complete This is an incomplete list of known issues with the ntfy server, 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 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. to have the prominent ones here to link to.
## iOS app not refreshing (see [#267](https://github.com/binwiederhier/ntfy/issues/267)) ## 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 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. swipe down, you do not see the newly arrived messages, even though the popup appeared before.
@ -13,16 +15,17 @@ clueless on how to fix it, sadly, as it is ephemeral and not clear to me what is
Please send experienced iOS developers my way to help me figure this out. Please send experienced iOS developers my way to help me figure this out.
## iOS app not receiving notifications (anymore) ## 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): 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**: **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 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. re-subscribe to the Firebase topic.
**Self-hosted only: No `upstream-base-url` set, or `base-url` mismatch**: **Self-hosted only: No `upstream-base-url` set, or `base-url` mismatch**:
To make self-hosted servers work with the iOS 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). 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: Be sure that in your selfhosted server:
* Set `upstream-base-url: "https://ntfy.sh"` (**not your own hostname!**) - 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 - Ensure that the URL you set in `base-url` **matches exactly** what you set the Default Server in iOS to

View file

@ -8,5 +8,5 @@ any outside service. All data is exclusively used to make the service function p
I use is Firebase Cloud Messaging (FCM) service, which is required to provide instant Android notifications (see I use is Firebase Cloud Messaging (FCM) service, which is required to provide instant Android notifications (see
[FAQ](faq.md) for details). To avoid FCM altogether, download the F-Droid version. [FAQ](faq.md) for details). To avoid FCM altogether, download the F-Droid version.
For debugging purposes, the ntfy server may temporarily log request paths, remote IP addresses or even topics For debugging purposes, the ntfy server may temporarily log request paths, remote IP addresses or even topics
or messages, though typically this is turned off. or messages, though typically this is turned off.

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,216 +1,221 @@
:root > * { :root > * {
--md-primary-fg-color: #338574; --md-primary-fg-color: #338574;
--md-primary-fg-color--light: #338574; --md-primary-fg-color--light: #338574;
--md-primary-fg-color--dark: #338574; --md-primary-fg-color--dark: #338574;
--md-footer-bg-color: #353744; --md-footer-bg-color: #353744;
--md-text-font: "Roboto"; --md-text-font: "Roboto";
--md-code-font: "Roboto Mono"; --md-code-font: "Roboto Mono";
} }
.md-header__button.md-logo :is(img, svg) { .md-header__button.md-logo :is(img, svg) {
width: unset !important; width: unset !important;
} }
.md-header__topic:first-child { .md-header__topic:first-child {
font-weight: 400; font-weight: 400;
} }
.md-typeset h4 { .md-typeset h4 {
font-weight: 500 !important; font-weight: 500 !important;
margin: 0 !important; margin: 0 !important;
font-size: 1.1em !important; font-size: 1.1em !important;
} }
.admonition { .admonition {
font-size: .74rem !important; font-size: 0.74rem !important;
} }
article { article {
padding-bottom: 50px; padding-bottom: 50px;
} }
figure img, figure video { figure img,
border-radius: 7px; figure video {
border-radius: 7px;
} }
header { header {
background: linear-gradient(150deg, rgba(51,133,116,1) 0%, rgba(86,189,168,1) 100%); background: linear-gradient(
150deg,
rgba(51, 133, 116, 1) 0%,
rgba(86, 189, 168, 1) 100%
);
} }
body[data-md-color-scheme="default"] header { body[data-md-color-scheme="default"] header {
filter: drop-shadow(0 5px 10px #ccc); filter: drop-shadow(0 5px 10px #ccc);
} }
body[data-md-color-scheme="slate"] header { body[data-md-color-scheme="slate"] header {
filter: drop-shadow(0 5px 10px #333); filter: drop-shadow(0 5px 10px #333);
} }
body[data-md-color-scheme="default"] figure img, body[data-md-color-scheme="default"] figure img,
body[data-md-color-scheme="default"] figure video, body[data-md-color-scheme="default"] figure video,
body[data-md-color-scheme="default"] .screenshots img, body[data-md-color-scheme="default"] .screenshots img,
body[data-md-color-scheme="default"] .screenshots video { body[data-md-color-scheme="default"] .screenshots video {
filter: drop-shadow(3px 3px 3px #ccc); filter: drop-shadow(3px 3px 3px #ccc);
} }
body[data-md-color-scheme="slate"] figure img, body[data-md-color-scheme="slate"] figure img,
body[data-md-color-scheme="slate"] figure video, body[data-md-color-scheme="slate"] figure video,
body[data-md-color-scheme="slate"] .screenshots img, body[data-md-color-scheme="slate"] .screenshots img,
body[data-md-color-scheme="slate"] .screenshots video { body[data-md-color-scheme="slate"] .screenshots video {
filter: drop-shadow(3px 3px 3px #353744); filter: drop-shadow(3px 3px 3px #353744);
} }
figure video { figure video {
width: 100%; width: 100%;
max-height: 450px; max-height: 450px;
} }
.remove-md-box { .remove-md-box {
background: none; background: none;
border: none; border: none;
margin: 0 auto; margin: 0 auto;
} }
.remove-md-box td { .remove-md-box td {
padding: 0 10px; padding: 0 10px;
} }
.emoji-table .c { .emoji-table .c {
vertical-align: middle !important; vertical-align: middle !important;
} }
.emoji-table .e { .emoji-table .e {
font-size: 2.5em; font-size: 2.5em;
padding: 0 2px !important; padding: 0 2px !important;
text-align: center !important; text-align: center !important;
vertical-align: middle !important; vertical-align: middle !important;
} }
/* Lightbox; thanks to https://yossiabramov.com/blog/vanilla-js-lightbox */ /* Lightbox; thanks to https://yossiabramov.com/blog/vanilla-js-lightbox */
.screenshots { .screenshots {
text-align: center; text-align: center;
} }
.screenshots img { .screenshots img {
max-height: 230px; max-height: 230px;
max-width: 300px; max-width: 300px;
margin: 3px; margin: 3px;
border-radius: 5px; border-radius: 5px;
filter: drop-shadow(2px 2px 2px #ddd); filter: drop-shadow(2px 2px 2px #ddd);
} }
.screenshots .nowrap { .screenshots .nowrap {
white-space: nowrap; white-space: nowrap;
} }
.lightbox { .lightbox {
opacity: 0; opacity: 0;
visibility: hidden; visibility: hidden;
position: fixed; position: fixed;
left:0; left: 0;
right: 0; right: 0;
top: 0; top: 0;
bottom: 0; bottom: 0;
z-index: -1; z-index: -1;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
transition: all 0.15s ease-in; transition: all 0.15s ease-in;
} }
.lightbox.show { .lightbox.show {
background-color: rgba(0,0,0, 0.75); background-color: rgba(0, 0, 0, 0.75);
opacity: 1; opacity: 1;
visibility: visible; visibility: visible;
z-index: 1000; z-index: 1000;
} }
.lightbox img { .lightbox img {
max-width: 90%; max-width: 90%;
max-height: 90%; max-height: 90%;
filter: drop-shadow(5px 5px 10px #222); filter: drop-shadow(5px 5px 10px #222);
border-radius: 5px; border-radius: 5px;
} }
.lightbox .close-lightbox { .lightbox .close-lightbox {
cursor: pointer; cursor: pointer;
position: absolute; position: absolute;
top: 30px; top: 30px;
right: 30px; right: 30px;
width: 20px; width: 20px;
height: 20px; height: 20px;
} }
.lightbox .close-lightbox::after, .lightbox .close-lightbox::after,
.lightbox .close-lightbox::before { .lightbox .close-lightbox::before {
content: ''; content: "";
width: 3px; width: 3px;
height: 20px; height: 20px;
background-color: #ddd; background-color: #ddd;
position: absolute; position: absolute;
border-radius: 5px; border-radius: 5px;
transform: rotate(45deg); transform: rotate(45deg);
} }
.lightbox .close-lightbox::before { .lightbox .close-lightbox::before {
transform: rotate(-45deg); transform: rotate(-45deg);
} }
.lightbox .close-lightbox:hover::after, .lightbox .close-lightbox:hover::after,
.lightbox .close-lightbox:hover::before { .lightbox .close-lightbox:hover::before {
background-color: #fff; background-color: #fff;
} }
/* roboto-300 - latin */ /* roboto-300 - latin */
@font-face { @font-face {
font-display: swap; font-display: swap;
font-family: 'Roboto'; font-family: "Roboto";
font-style: normal; font-style: normal;
font-weight: 300; font-weight: 300;
src: url('../fonts/roboto-v30-latin-300.woff2') format('woff2'); src: url("../fonts/roboto-v30-latin-300.woff2") format("woff2");
} }
/* roboto-regular - latin */ /* roboto-regular - latin */
@font-face { @font-face {
font-display: swap; font-display: swap;
font-family: 'Roboto'; font-family: "Roboto";
font-style: normal; font-style: normal;
font-weight: 400; font-weight: 400;
src: url('../fonts/roboto-v30-latin-regular.woff2') format('woff2'); src: url("../fonts/roboto-v30-latin-regular.woff2") format("woff2");
} }
/* roboto-italic - latin */ /* roboto-italic - latin */
@font-face { @font-face {
font-display: swap; font-display: swap;
font-family: 'Roboto'; font-family: "Roboto";
font-style: italic; font-style: italic;
font-weight: 400; font-weight: 400;
src: url('../fonts/roboto-v30-latin-italic.woff2') format('woff2'); src: url("../fonts/roboto-v30-latin-italic.woff2") format("woff2");
} }
/* roboto-500 - latin */ /* roboto-500 - latin */
@font-face { @font-face {
font-display: swap; font-display: swap;
font-family: 'Roboto'; font-family: "Roboto";
font-style: normal; font-style: normal;
font-weight: 500; font-weight: 500;
src: url('../fonts/roboto-v30-latin-500.woff2') format('woff2'); src: url("../fonts/roboto-v30-latin-500.woff2") format("woff2");
} }
/* roboto-700 - latin */ /* roboto-700 - latin */
@font-face { @font-face {
font-display: swap; font-display: swap;
font-family: 'Roboto'; font-family: "Roboto";
font-style: normal; font-style: normal;
font-weight: 700; font-weight: 700;
src: url('../fonts/roboto-v30-latin-700.woff2') format('woff2'); src: url("../fonts/roboto-v30-latin-700.woff2") format("woff2");
} }
/* roboto-mono - latin */ /* roboto-mono - latin */
@font-face { @font-face {
font-display: swap; font-display: swap;
font-family: 'Roboto Mono'; font-family: "Roboto Mono";
font-style: normal; font-style: normal;
font-weight: 400; font-weight: 400;
src: url('../fonts/roboto-mono-v22-latin-regular.woff2') format('woff2'); src: url("../fonts/roboto-mono-v22-latin-regular.woff2") format("woff2");
} }

View file

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

View file

@ -1,27 +1,31 @@
# Subscribe via API # Subscribe via API
You can create and subscribe to a topic in the [web UI](web.md), via the [phone app](phone.md), via the [ntfy CLI](cli.md), You can create and subscribe to a topic in the [web UI](web.md), via the [phone app](phone.md), via the [ntfy CLI](cli.md),
or in your own app or script by subscribing the API. This page describes how to subscribe via API. You may also want to or in your own app or script by subscribing the API. This page describes how to subscribe via API. You may also want to
check out the page that describes how to [publish messages](../publish.md). check out the page that describes how to [publish messages](../publish.md).
You can consume the subscription API as either a **[simple HTTP stream (JSON, SSE or raw)](#http-stream)**, or You can consume the subscription API as either a **[simple HTTP stream (JSON, SSE or raw)](#http-stream)**, or
**[via WebSockets](#websockets)**. Both are incredibly simple to use. **[via WebSockets](#websockets)**. Both are incredibly simple to use.
## HTTP stream ## HTTP stream
The HTTP stream-based API relies on a simple GET request with a streaming HTTP response, i.e **you open a GET request and The HTTP stream-based API relies on a simple GET request with a streaming HTTP response, i.e **you open a GET request and
the connection stays open forever**, sending messages back as they come in. There are three different API endpoints, which the connection stays open forever**, sending messages back as they come in. There are three different API endpoints, which
only differ in the response format: only differ in the response format:
* [JSON stream](#subscribe-as-json-stream): `<topic>/json` returns a JSON stream, with one JSON message object per line - [JSON stream](#subscribe-as-json-stream): `<topic>/json` returns a JSON stream, with one JSON message object per line
* [SSE stream](#subscribe-as-sse-stream): `<topic>/sse` returns messages as [Server-Sent Events (SSE)](https://en.wikipedia.org/wiki/Server-sent_events), which - [SSE stream](#subscribe-as-sse-stream): `<topic>/sse` returns messages as [Server-Sent Events (SSE)](https://en.wikipedia.org/wiki/Server-sent_events), which
can be used with [EventSource](https://developer.mozilla.org/en-US/docs/Web/API/EventSource) can be used with [EventSource](https://developer.mozilla.org/en-US/docs/Web/API/EventSource)
* [Raw stream](#subscribe-as-raw-stream): `<topic>/raw` returns messages as raw text, with one line per message - [Raw stream](#subscribe-as-raw-stream): `<topic>/raw` returns messages as raw text, with one line per message
### Subscribe as JSON stream ### Subscribe as JSON stream
Here are a few examples of how to consume the JSON endpoint (`<topic>/json`). For almost all languages, **this is the
recommended way to subscribe to a topic**. The notable exception is JavaScript, for which the Here are a few examples of how to consume the JSON endpoint (`<topic>/json`). For almost all languages, **this is the
recommended way to subscribe to a topic**. The notable exception is JavaScript, for which the
[SSE/EventSource stream](#subscribe-as-sse-stream) is much easier to work with. [SSE/EventSource stream](#subscribe-as-sse-stream) is much easier to work with.
=== "Command line (curl)" === "Command line (curl)"
``` ```
$ curl -s ntfy.sh/disk-alerts/json $ curl -s ntfy.sh/disk-alerts/json
{"id":"SLiKI64DOt","time":1635528757,"event":"open","topic":"mytopic"} {"id":"SLiKI64DOt","time":1635528757,"event":"open","topic":"mytopic"}
@ -31,6 +35,7 @@ recommended way to subscribe to a topic**. The notable exception is JavaScript,
``` ```
=== "ntfy CLI" === "ntfy CLI"
``` ```
$ ntfy subcribe disk-alerts $ ntfy subcribe disk-alerts
{"id":"hwQ2YpKdmg","time":1635528741,"event":"message","topic":"mytopic","message":"Disk full"} {"id":"hwQ2YpKdmg","time":1635528741,"event":"message","topic":"mytopic","message":"Disk full"}
@ -38,14 +43,15 @@ recommended way to subscribe to a topic**. The notable exception is JavaScript,
``` ```
=== "HTTP" === "HTTP"
``` http
```http
GET /disk-alerts/json HTTP/1.1 GET /disk-alerts/json HTTP/1.1
Host: ntfy.sh Host: ntfy.sh
HTTP/1.1 200 OK HTTP/1.1 200 OK
Content-Type: application/x-ndjson; charset=utf-8 Content-Type: application/x-ndjson; charset=utf-8
Transfer-Encoding: chunked Transfer-Encoding: chunked
{"id":"SLiKI64DOt","time":1635528757,"event":"open","topic":"mytopic"} {"id":"SLiKI64DOt","time":1635528757,"event":"open","topic":"mytopic"}
{"id":"hwQ2YpKdmg","time":1635528741,"event":"message","topic":"mytopic","message":"Disk full"} {"id":"hwQ2YpKdmg","time":1635528741,"event":"message","topic":"mytopic","message":"Disk full"}
{"id":"DGUDShMCsc","time":1635528787,"event":"keepalive","topic":"mytopic"} {"id":"DGUDShMCsc","time":1635528787,"event":"keepalive","topic":"mytopic"}
@ -53,7 +59,8 @@ recommended way to subscribe to a topic**. The notable exception is JavaScript,
``` ```
=== "Go" === "Go"
``` go
```go
resp, err := http.Get("https://ntfy.sh/disk-alerts/json") resp, err := http.Get("https://ntfy.sh/disk-alerts/json")
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
@ -66,7 +73,8 @@ recommended way to subscribe to a topic**. The notable exception is JavaScript,
``` ```
=== "Python" === "Python"
``` python
```python
resp = requests.get("https://ntfy.sh/disk-alerts/json", stream=True) resp = requests.get("https://ntfy.sh/disk-alerts/json", stream=True)
for line in resp.iter_lines(): for line in resp.iter_lines():
if line: if line:
@ -74,7 +82,8 @@ recommended way to subscribe to a topic**. The notable exception is JavaScript,
``` ```
=== "PHP" === "PHP"
``` php-inline
```php-inline
$fp = fopen('https://ntfy.sh/disk-alerts/json', 'r'); $fp = fopen('https://ntfy.sh/disk-alerts/json', 'r');
if (!$fp) die('cannot open stream'); if (!$fp) die('cannot open stream');
while (!feof($fp)) { while (!feof($fp)) {
@ -85,25 +94,28 @@ recommended way to subscribe to a topic**. The notable exception is JavaScript,
``` ```
### Subscribe as SSE stream ### Subscribe as SSE stream
Using [EventSource](https://developer.mozilla.org/en-US/docs/Web/API/EventSource) in JavaScript, you can consume Using [EventSource](https://developer.mozilla.org/en-US/docs/Web/API/EventSource) in JavaScript, you can consume
notifications via a [Server-Sent Events (SSE)](https://en.wikipedia.org/wiki/Server-sent_events) stream. It's incredibly notifications via a [Server-Sent Events (SSE)](https://en.wikipedia.org/wiki/Server-sent_events) stream. It's incredibly
easy to use. Here's what it looks like. You may also want to check out the [full example on GitHub](https://github.com/binwiederhier/ntfy/tree/main/examples/web-example-eventsource). easy to use. Here's what it looks like. You may also want to check out the [full example on GitHub](https://github.com/binwiederhier/ntfy/tree/main/examples/web-example-eventsource).
=== "Command line (curl)" === "Command line (curl)"
``` ```
$ curl -s ntfy.sh/mytopic/sse $ curl -s ntfy.sh/mytopic/sse
event: open event: open
data: {"id":"weSj9RtNkj","time":1635528898,"event":"open","topic":"mytopic"} data: {"id":"weSj9RtNkj","time":1635528898,"event":"open","topic":"mytopic"}
data: {"id":"p0M5y6gcCY","time":1635528909,"event":"message","topic":"mytopic","message":"Hi!"} data: {"id":"p0M5y6gcCY","time":1635528909,"event":"message","topic":"mytopic","message":"Hi!"}
event: keepalive event: keepalive
data: {"id":"VNxNIg5fpt","time":1635528928,"event":"keepalive","topic":"test"} data: {"id":"VNxNIg5fpt","time":1635528928,"event":"keepalive","topic":"test"}
... ...
``` ```
=== "HTTP" === "HTTP"
``` http
```http
GET /mytopic/sse HTTP/1.1 GET /mytopic/sse HTTP/1.1
Host: ntfy.sh Host: ntfy.sh
@ -113,16 +125,17 @@ easy to use. Here's what it looks like. You may also want to check out the [full
event: open event: open
data: {"id":"weSj9RtNkj","time":1635528898,"event":"open","topic":"mytopic"} data: {"id":"weSj9RtNkj","time":1635528898,"event":"open","topic":"mytopic"}
data: {"id":"p0M5y6gcCY","time":1635528909,"event":"message","topic":"mytopic","message":"Hi!"} data: {"id":"p0M5y6gcCY","time":1635528909,"event":"message","topic":"mytopic","message":"Hi!"}
event: keepalive event: keepalive
data: {"id":"VNxNIg5fpt","time":1635528928,"event":"keepalive","topic":"test"} data: {"id":"VNxNIg5fpt","time":1635528928,"event":"keepalive","topic":"test"}
... ...
``` ```
=== "JavaScript" === "JavaScript"
``` javascript
```javascript
const eventSource = new EventSource('https://ntfy.sh/mytopic/sse'); const eventSource = new EventSource('https://ntfy.sh/mytopic/sse');
eventSource.onmessage = (e) => { eventSource.onmessage = (e) => {
console.log(e.data); console.log(e.data);
@ -130,21 +143,24 @@ easy to use. Here's what it looks like. You may also want to check out the [full
``` ```
### Subscribe as raw stream ### Subscribe as raw stream
The `/raw` endpoint will output one line per message, and **will only include the message body**. It's useful for extremely The `/raw` endpoint will output one line per message, and **will only include the message body**. It's useful for extremely
simple scripts, and doesn't include all the data. Additional fields such as [priority](../publish.md#message-priority), simple scripts, and doesn't include all the data. Additional fields such as [priority](../publish.md#message-priority),
[tags](../publish.md#tags--emojis--) or [message title](../publish.md#message-title) are not included in this output [tags](../publish.md#tags--emojis--) or [message title](../publish.md#message-title) are not included in this output
format. Keepalive messages are sent as empty lines. format. Keepalive messages are sent as empty lines.
=== "Command line (curl)" === "Command line (curl)"
``` ```
$ curl -s ntfy.sh/disk-alerts/raw $ curl -s ntfy.sh/disk-alerts/raw
Disk full Disk full
... ...
``` ```
=== "HTTP" === "HTTP"
``` http
```http
GET /disk-alerts/raw HTTP/1.1 GET /disk-alerts/raw HTTP/1.1
Host: ntfy.sh Host: ntfy.sh
@ -157,7 +173,8 @@ format. Keepalive messages are sent as empty lines.
``` ```
=== "Go" === "Go"
``` go
```go
resp, err := http.Get("https://ntfy.sh/disk-alerts/raw") resp, err := http.Get("https://ntfy.sh/disk-alerts/raw")
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
@ -170,7 +187,8 @@ format. Keepalive messages are sent as empty lines.
``` ```
=== "Python" === "Python"
``` python
```python
resp = requests.get("https://ntfy.sh/disk-alerts/raw", stream=True) resp = requests.get("https://ntfy.sh/disk-alerts/raw", stream=True)
for line in resp.iter_lines(): for line in resp.iter_lines():
if line: if line:
@ -178,7 +196,8 @@ format. Keepalive messages are sent as empty lines.
``` ```
=== "PHP" === "PHP"
``` php-inline
```php-inline
$fp = fopen('https://ntfy.sh/disk-alerts/raw', 'r'); $fp = fopen('https://ntfy.sh/disk-alerts/raw', 'r');
if (!$fp) die('cannot open stream'); if (!$fp) die('cannot open stream');
while (!feof($fp)) { while (!feof($fp)) {
@ -189,15 +208,17 @@ format. Keepalive messages are sent as empty lines.
``` ```
## WebSockets ## WebSockets
You may also subscribe to topics via [WebSockets](https://en.wikipedia.org/wiki/WebSocket), which is also widely
supported in many languages. Most notably, WebSockets are natively supported in JavaScript. On the command line,
I recommend [websocat](https://github.com/vi/websocat), a fantastic tool similar to `socat` or `curl`, but specifically
for WebSockets.
The WebSockets endpoint is available at `<topic>/ws` and returns messages as JSON objects similar to the You may also subscribe to topics via [WebSockets](https://en.wikipedia.org/wiki/WebSocket), which is also widely
[JSON stream endpoint](#subscribe-as-json-stream). supported in many languages. Most notably, WebSockets are natively supported in JavaScript. On the command line,
I recommend [websocat](https://github.com/vi/websocat), a fantastic tool similar to `socat` or `curl`, but specifically
for WebSockets.
The WebSockets endpoint is available at `<topic>/ws` and returns messages as JSON objects similar to the
[JSON stream endpoint](#subscribe-as-json-stream).
=== "Command line (websocat)" === "Command line (websocat)"
``` ```
$ websocat wss://ntfy.sh/mytopic/ws $ websocat wss://ntfy.sh/mytopic/ws
{"id":"qRHUCCvjj8","time":1642307388,"event":"open","topic":"mytopic"} {"id":"qRHUCCvjj8","time":1642307388,"event":"open","topic":"mytopic"}
@ -205,7 +226,8 @@ The WebSockets endpoint is available at `<topic>/ws` and returns messages as JSO
``` ```
=== "HTTP" === "HTTP"
``` http
```http
GET /disk-alerts/ws HTTP/1.1 GET /disk-alerts/ws HTTP/1.1
Host: ntfy.sh Host: ntfy.sh
Upgrade: websocket Upgrade: websocket
@ -218,15 +240,17 @@ The WebSockets endpoint is available at `<topic>/ws` and returns messages as JSO
``` ```
=== "Go" === "Go"
``` go
```go
import "github.com/gorilla/websocket" import "github.com/gorilla/websocket"
ws, _, _ := websocket.DefaultDialer.Dial("wss://ntfy.sh/mytopic/ws", nil) ws, _, _ := websocket.DefaultDialer.Dial("wss://ntfy.sh/mytopic/ws", nil)
messageType, data, err := ws.ReadMessage() messageType, data, err := ws.ReadMessage()
... ...
``` ```
=== "JavaScript" === "JavaScript"
``` javascript
```javascript
const socket = new WebSocket('wss://ntfy.sh/mytopic/ws'); const socket = new WebSocket('wss://ntfy.sh/mytopic/ws');
socket.addEventListener('message', function (event) { socket.addEventListener('message', function (event) {
console.log(event.data); console.log(event.data);
@ -236,6 +260,7 @@ The WebSockets endpoint is available at `<topic>/ws` and returns messages as JSO
## Advanced features ## Advanced features
### Poll for messages ### Poll for messages
You can also just poll for messages if you don't like the long-standing connection using the `poll=1` You can also just poll for messages if you don't like the long-standing connection using the `poll=1`
query parameter. The connection will end after all available messages have been read. This parameter can be query parameter. The connection will end after all available messages have been read. This parameter can be
combined with `since=` (defaults to `since=all`). combined with `since=` (defaults to `since=all`).
@ -245,8 +270,9 @@ curl -s "ntfy.sh/mytopic/json?poll=1"
``` ```
### Fetch cached messages ### Fetch cached messages
Messages may be cached for a couple of hours (see [message caching](../config.md#message-cache)) to account for network Messages may be cached for a couple of hours (see [message caching](../config.md#message-cache)) to account for network
interruptions of subscribers. If the server has configured message caching, you can read back what you missed by using interruptions of subscribers. If the server has configured message caching, you can read back what you missed by using
the `since=` query parameter. It takes a duration (e.g. `10m` or `30s`), a Unix timestamp (e.g. `1635528757`), the `since=` query parameter. It takes a duration (e.g. `10m` or `30s`), a Unix timestamp (e.g. `1635528757`),
a message ID (e.g. `nFS3knfcQ1xe`), or `all` (all cached messages). a message ID (e.g. `nFS3knfcQ1xe`), or `all` (all cached messages).
@ -257,9 +283,10 @@ curl -s "ntfy.sh/mytopic/json?since=nFS3knfcQ1xe"
``` ```
### Fetch scheduled messages ### Fetch scheduled messages
Messages that are [scheduled to be delivered](../publish.md#scheduled-delivery) at a later date are not typically
returned when subscribing via the API, which makes sense, because after all, the messages have technically not been Messages that are [scheduled to be delivered](../publish.md#scheduled-delivery) at a later date are not typically
delivered yet. To also return scheduled messages from the API, you can use the `scheduled=1` (alias: `sched=1`) returned when subscribing via the API, which makes sense, because after all, the messages have technically not been
delivered yet. To also return scheduled messages from the API, you can use the `scheduled=1` (alias: `sched=1`)
parameter (makes most sense with the `poll=1` parameter): parameter (makes most sense with the `poll=1` parameter):
``` ```
@ -267,9 +294,10 @@ curl -s "ntfy.sh/mytopic/json?poll=1&sched=1"
``` ```
### Filter messages ### Filter messages
You can filter which messages are returned based on the well-known message fields `id`, `message`, `title`, `priority` and You can filter which messages are returned based on the well-known message fields `id`, `message`, `title`, `priority` and
`tags`. Here's an example that only returns messages of high or urgent priority that contains the both tags `tags`. Here's an example that only returns messages of high or urgent priority that contains the both tags
"zfs-error" and "error". Note that the `priority` filter is a logical OR and the `tags` filter is a logical AND. "zfs-error" and "error". Note that the `priority` filter is a logical OR and the `tags` filter is a logical AND.
``` ```
$ curl "ntfy.sh/alerts/json?priority=high&tags=zfs-error" $ curl "ntfy.sh/alerts/json?priority=high&tags=zfs-error"
@ -281,15 +309,16 @@ $ curl "ntfy.sh/alerts/json?priority=high&tags=zfs-error"
Available filters (all case-insensitive): Available filters (all case-insensitive):
| Filter variable | Alias | Example | Description | | Filter variable | Alias | Example | Description |
|-----------------|---------------------------|-----------------------------------------------|-------------------------------------------------------------------------| | --------------- | ------------------------- | --------------------------------------------- | ----------------------------------------------------------------------- |
| `id` | `X-ID` | `ntfy.sh/mytopic/json?poll=1&id=pbkiz8SD7ZxG` | Only return messages that match this exact message ID | | `id` | `X-ID` | `ntfy.sh/mytopic/json?poll=1&id=pbkiz8SD7ZxG` | Only return messages that match this exact message ID |
| `message` | `X-Message`, `m` | `ntfy.sh/mytopic/json?message=lalala` | Only return messages that match this exact message string | | `message` | `X-Message`, `m` | `ntfy.sh/mytopic/json?message=lalala` | Only return messages that match this exact message string |
| `title` | `X-Title`, `t` | `ntfy.sh/mytopic/json?title=some+title` | Only return messages that match this exact title string | | `title` | `X-Title`, `t` | `ntfy.sh/mytopic/json?title=some+title` | Only return messages that match this exact title string |
| `priority` | `X-Priority`, `prio`, `p` | `ntfy.sh/mytopic/json?p=high,urgent` | Only return messages that match *any priority listed* (comma-separated) | | `priority` | `X-Priority`, `prio`, `p` | `ntfy.sh/mytopic/json?p=high,urgent` | Only return messages that match _any priority listed_ (comma-separated) |
| `tags` | `X-Tags`, `tag`, `ta` | `ntfy.sh/mytopic?/jsontags=error,alert` | Only return messages that match *all listed tags* (comma-separated) | | `tags` | `X-Tags`, `tag`, `ta` | `ntfy.sh/mytopic?/jsontags=error,alert` | Only return messages that match _all listed tags_ (comma-separated) |
### Subscribe to multiple topics ### Subscribe to multiple topics
It's possible to subscribe to multiple topics in one HTTP call by providing a comma-separated list of topics
It's possible to subscribe to multiple topics in one HTTP call by providing a comma-separated list of topics
in the URL. This allows you to reduce the number of connections you have to maintain: in the URL. This allows you to reduce the number of connections you have to maintain:
``` ```
@ -300,50 +329,53 @@ $ curl -s ntfy.sh/mytopic1,mytopic2/json
``` ```
### Authentication ### Authentication
Depending on whether the server is configured to support [access control](../config.md#access-control), some topics Depending on whether the server is configured to support [access control](../config.md#access-control), some topics
may be read/write protected so that only users with the correct credentials can subscribe or publish to them. may be read/write protected so that only users with the correct credentials can subscribe or publish to them.
To publish/subscribe to protected topics, you can: To publish/subscribe to protected topics, you can:
* Use [basic auth](../publish.md#basic-auth), e.g. `Authorization: Basic dGVzdHVzZXI6ZmFrZXBhc3N3b3Jk` - Use [basic auth](../publish.md#basic-auth), e.g. `Authorization: Basic dGVzdHVzZXI6ZmFrZXBhc3N3b3Jk`
* or use the [`auth` query parameter](../publish.md#query-param), e.g. `?auth=QmFzaWMgZEdWemRIVnpaWEk2Wm1GclpYQmhjM04zYjNKaw` - or use the [`auth` query parameter](../publish.md#query-param), e.g. `?auth=QmFzaWMgZEdWemRIVnpaWEk2Wm1GclpYQmhjM04zYjNKaw`
Please refer to the [publishing documentation](../publish.md#authentication) for additional details. Please refer to the [publishing documentation](../publish.md#authentication) for additional details.
## JSON message format ## JSON message format
Both the [`/json` endpoint](#subscribe-as-json-stream) and the [`/sse` endpoint](#subscribe-as-sse-stream) return a JSON Both the [`/json` endpoint](#subscribe-as-json-stream) and the [`/sse` endpoint](#subscribe-as-sse-stream) return a JSON
format of the message. It's very straight forward: format of the message. It's very straight forward:
**Message**: **Message**:
| Field | Required | Type | Example | Description | | Field | Required | Type | Example | Description |
|--------------|----------|---------------------------------------------------|-------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------| | ------------ | -------- | ------------------------------------------------- | ----------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| `id` | ✔️ | *string* | `hwQ2YpKdmg` | Randomly chosen message identifier | | `id` | ✔️ | _string_ | `hwQ2YpKdmg` | Randomly chosen message identifier |
| `time` | ✔️ | *number* | `1635528741` | Message date time, as Unix time stamp | | `time` | ✔️ | _number_ | `1635528741` | Message date time, as Unix time stamp |
| `expires` | (✔) | *number* | `1673542291` | Unix time stamp indicating when the message will be deleted, not set if `Cache: no` is sent | | `expires` | (✔) | _number_ | `1673542291` | Unix time stamp indicating when the message will be deleted, not set if `Cache: no` is sent |
| `event` | ✔️ | `open`, `keepalive`, `message`, or `poll_request` | `message` | Message type, typically you'd be only interested in `message` | | `event` | ✔️ | `open`, `keepalive`, `message`, or `poll_request` | `message` | Message type, typically you'd be only interested in `message` |
| `topic` | ✔️ | *string* | `topic1,topic2` | Comma-separated list of topics the message is associated with; only one for all `message` events, but may be a list in `open` events | | `topic` | ✔️ | _string_ | `topic1,topic2` | Comma-separated list of topics the message is associated with; only one for all `message` events, but may be a list in `open` events |
| `message` | - | *string* | `Some message` | Message body; always present in `message` events | | `message` | - | _string_ | `Some message` | Message body; always present in `message` events |
| `title` | - | *string* | `Some title` | Message [title](../publish.md#message-title); if not set defaults to `ntfy.sh/<topic>` | | `title` | - | _string_ | `Some title` | Message [title](../publish.md#message-title); if not set defaults to `ntfy.sh/<topic>` |
| `tags` | - | *string array* | `["tag1","tag2"]` | List of [tags](../publish.md#tags-emojis) that may or not map to emojis | | `tags` | - | _string array_ | `["tag1","tag2"]` | List of [tags](../publish.md#tags-emojis) that may or not map to emojis |
| `priority` | - | *1, 2, 3, 4, or 5* | `4` | Message [priority](../publish.md#message-priority) with 1=min, 3=default and 5=max | | `priority` | - | _1, 2, 3, 4, or 5_ | `4` | Message [priority](../publish.md#message-priority) with 1=min, 3=default and 5=max |
| `click` | - | *URL* | `https://example.com` | Website opened when notification is [clicked](../publish.md#click-action) | | `click` | - | _URL_ | `https://example.com` | Website opened when notification is [clicked](../publish.md#click-action) |
| `actions` | - | *JSON array* | *see [actions buttons](../publish.md#action-buttons)* | [Action buttons](../publish.md#action-buttons) that can be displayed in the notification | | `actions` | - | _JSON array_ | _see [actions buttons](../publish.md#action-buttons)_ | [Action buttons](../publish.md#action-buttons) that can be displayed in the notification |
| `attachment` | - | *JSON object* | *see below* | Details about an attachment (name, URL, size, ...) | | `attachment` | - | _JSON object_ | _see below_ | Details about an attachment (name, URL, size, ...) |
**Attachment** (part of the message, see [attachments](../publish.md#attachments) for details): **Attachment** (part of the message, see [attachments](../publish.md#attachments) for details):
| Field | Required | Type | Example | Description | | Field | Required | Type | Example | Description |
|-----------|----------|-------------|--------------------------------|-----------------------------------------------------------------------------------------------------------| | --------- | -------- | ----------- | ------------------------------ | --------------------------------------------------------------------------------------------------------- |
| `name` | ✔️ | *string* | `attachment.jpg` | Name of the attachment, can be overridden with `X-Filename`, see [attachments](../publish.md#attachments) | | `name` | ✔️ | _string_ | `attachment.jpg` | Name of the attachment, can be overridden with `X-Filename`, see [attachments](../publish.md#attachments) |
| `url` | ✔️ | *URL* | `https://example.com/file.jpg` | URL of the attachment | | `url` | ✔️ | _URL_ | `https://example.com/file.jpg` | URL of the attachment |
| `type` | - | *mime type* | `image/jpeg` | Mime type of the attachment, only defined if attachment was uploaded to ntfy server | | `type` | - | _mime type_ | `image/jpeg` | Mime type of the attachment, only defined if attachment was uploaded to ntfy server |
| `size` | - | *number* | `33848` | Size of the attachment in bytes, only defined if attachment was uploaded to ntfy server | | `size` | - | _number_ | `33848` | Size of the attachment in bytes, only defined if attachment was uploaded to ntfy server |
| `expires` | - | *number* | `1635528741` | Attachment expiry date as Unix time stamp, only defined if attachment was uploaded to ntfy server | | `expires` | - | _number_ | `1635528741` | Attachment expiry date as Unix time stamp, only defined if attachment was uploaded to ntfy server |
Here's an example for each message type: Here's an example for each message type:
=== "Notification message" === "Notification message"
``` json
```json
{ {
"id": "sPs71M8A2T", "id": "sPs71M8A2T",
"time": 1643935928, "time": 1643935928,
@ -368,9 +400,9 @@ Here's an example for each message type:
} }
``` ```
=== "Notification message (minimal)" === "Notification message (minimal)"
``` json
```json
{ {
"id": "wze9zgqK41", "id": "wze9zgqK41",
"time": 1638542110, "time": 1638542110,
@ -382,7 +414,8 @@ Here's an example for each message type:
``` ```
=== "Open message" === "Open message"
``` json
```json
{ {
"id": "2pgIAaGrQ8", "id": "2pgIAaGrQ8",
"time": 1638542215, "time": 1638542215,
@ -392,17 +425,19 @@ Here's an example for each message type:
``` ```
=== "Keepalive message" === "Keepalive message"
``` json
```json
{ {
"id": "371sevb0pD", "id": "371sevb0pD",
"time": 1638542275, "time": 1638542275,
"event": "keepalive", "event": "keepalive",
"topic": "phil_alerts" "topic": "phil_alerts"
} }
``` ```
=== "Poll request message" === "Poll request message"
``` json
```json
{ {
"id": "371sevb0pD", "id": "371sevb0pD",
"time": 1638542275, "time": 1638542275,
@ -412,16 +447,17 @@ Here's an example for each message type:
``` ```
## List of all parameters ## List of all parameters
The following is a list of all parameters that can be passed **when subscribing to a message**. Parameter names are **case-insensitive**, The following is a list of all parameters that can be passed **when subscribing to a message**. Parameter names are **case-insensitive**,
and can be passed as **HTTP headers** or **query parameters in the URL**. They are listed in the table in their canonical form. and can be passed as **HTTP headers** or **query parameters in the URL**. They are listed in the table in their canonical form.
| Parameter | Aliases (case-insensitive) | Description | | Parameter | Aliases (case-insensitive) | Description |
|-------------|----------------------------|---------------------------------------------------------------------------------| | ----------- | -------------------------- | ------------------------------------------------------------------------------- |
| `poll` | `X-Poll`, `po` | Return cached messages and close connection | | `poll` | `X-Poll`, `po` | Return cached messages and close connection |
| `since` | `X-Since`, `si` | Return cached messages since timestamp, duration or message ID | | `since` | `X-Since`, `si` | Return cached messages since timestamp, duration or message ID |
| `scheduled` | `X-Scheduled`, `sched` | Include scheduled/delayed messages in message list | | `scheduled` | `X-Scheduled`, `sched` | Include scheduled/delayed messages in message list |
| `id` | `X-ID` | Filter: Only return messages that match this exact message ID | | `id` | `X-ID` | Filter: Only return messages that match this exact message ID |
| `message` | `X-Message`, `m` | Filter: Only return messages that match this exact message string | | `message` | `X-Message`, `m` | Filter: Only return messages that match this exact message string |
| `title` | `X-Title`, `t` | Filter: Only return messages that match this exact title string | | `title` | `X-Title`, `t` | Filter: Only return messages that match this exact title string |
| `priority` | `X-Priority`, `prio`, `p` | Filter: Only return messages that match *any priority listed* (comma-separated) | | `priority` | `X-Priority`, `prio`, `p` | Filter: Only return messages that match _any priority listed_ (comma-separated) |
| `tags` | `X-Tags`, `tag`, `ta` | Filter: Only return messages that match *all listed tags* (comma-separated) | | `tags` | `X-Tags`, `tag`, `ta` | Filter: Only return messages that match _all listed tags_ (comma-separated) |

View file

@ -1,22 +1,24 @@
# Subscribe via ntfy CLI # Subscribe via ntfy CLI
In addition to subscribing via the [web UI](web.md), the [phone app](phone.md), or the [API](api.md), you can subscribe In addition to subscribing via the [web UI](web.md), the [phone app](phone.md), or the [API](api.md), you can subscribe
to topics via the ntfy CLI. The CLI is included in the same `ntfy` binary that can be used to [self-host a server](../install.md). to topics via the ntfy CLI. The CLI is included in the same `ntfy` binary that can be used to [self-host a server](../install.md).
!!! info !!! info
The **ntfy CLI is not required to send or receive messages**. You can instead [send messages with curl](../publish.md), The **ntfy CLI is not required to send or receive messages**. You can instead [send messages with curl](../publish.md),
and even use it to [subscribe to topics](api.md). It may be a little more convenient to use the ntfy CLI than writing and even use it to [subscribe to topics](api.md). It may be a little more convenient to use the ntfy CLI than writing
your own script. It all depends on the use case. 😀 your own script. It all depends on the use case. 😀
## Install + configure ## Install + configure
To install the ntfy CLI, simply **follow the steps outlined on the [install page](../install.md)**. The ntfy server and
client are the same binary, so it's all very convenient. After installing, you can (optionally) configure the client To install the ntfy CLI, simply **follow the steps outlined on the [install page](../install.md)**. The ntfy server and
by creating `~/.config/ntfy/client.yml` (for the non-root user), or `/etc/ntfy/client.yml` (for the root user). You client are the same binary, so it's all very convenient. After installing, you can (optionally) configure the client
can find a [skeleton config](https://github.com/binwiederhier/ntfy/blob/main/client/client.yml) on GitHub. by creating `~/.config/ntfy/client.yml` (for the non-root user), or `/etc/ntfy/client.yml` (for the root user). You
can find a [skeleton config](https://github.com/binwiederhier/ntfy/blob/main/client/client.yml) on GitHub.
If you just want to use [ntfy.sh](https://ntfy.sh), you don't have to change anything. If you **self-host your own server**, If you just want to use [ntfy.sh](https://ntfy.sh), you don't have to change anything. If you **self-host your own server**,
you may want to edit the `default-host` option: you may want to edit the `default-host` option:
``` yaml ```yaml
# Base URL used to expand short topic names in the "ntfy publish" and "ntfy subscribe" commands. # Base URL used to expand short topic names in the "ntfy publish" and "ntfy subscribe" commands.
# If you self-host a ntfy server, you'll likely want to change this. # If you self-host a ntfy server, you'll likely want to change this.
# #
@ -24,19 +26,22 @@ default-host: https://ntfy.myhost.com
``` ```
## Publish messages ## Publish messages
You can send messages with the ntfy CLI using the `ntfy publish` command (or any of its aliases `pub`, `send` or
You can send messages with the ntfy CLI using the `ntfy publish` command (or any of its aliases `pub`, `send` or
`trigger`). There are a lot of examples on the page about [publishing messages](../publish.md), but here are a few `trigger`). There are a lot of examples on the page about [publishing messages](../publish.md), but here are a few
quick ones: quick ones:
=== "Simple send" === "Simple send"
```
```sh
ntfy publish mytopic This is a message ntfy publish mytopic This is a message
ntfy publish mytopic "This is a message" ntfy publish mytopic "This is a message"
ntfy pub mytopic "This is a message" ntfy pub mytopic "This is a message"
``` ```
=== "Send with title, priority, and tags" === "Send with title, priority, and tags"
```
```sh
ntfy publish \ ntfy publish \
--title="Thing sold on eBay" \ --title="Thing sold on eBay" \
--priority=high \ --priority=high \
@ -46,20 +51,23 @@ quick ones:
``` ```
=== "Send at 8:30am" === "Send at 8:30am"
```
```sh
ntfy pub --at=8:30am delayed_topic Laterzz ntfy pub --at=8:30am delayed_topic Laterzz
``` ```
=== "Triggering a webhook" === "Triggering a webhook"
```
```sh
ntfy trigger mywebhook ntfy trigger mywebhook
ntfy pub mywebhook ntfy pub mywebhook
``` ```
### Attaching a local file ### Attaching a local file
You can easily upload and attach a local file to a notification: You can easily upload and attach a local file to a notification:
``` ```sh
$ ntfy pub --file README.md mytopic | jq . $ ntfy pub --file README.md mytopic | jq .
{ {
"id": "meIlClVLABJQ", "id": "meIlClVLABJQ",
@ -78,13 +86,14 @@ $ ntfy pub --file README.md mytopic | jq .
``` ```
### Wait for PID/command ### Wait for PID/command
If you have a long-running command and want to **publish a notification when the command completes**,
If you have a long-running command and want to **publish a notification when the command completes**,
you may wrap it with `ntfy publish --wait-cmd` (aliases: `--cmd`, `--done`). Or, if you forgot to wrap it, and the you may wrap it with `ntfy publish --wait-cmd` (aliases: `--cmd`, `--done`). Or, if you forgot to wrap it, and the
command is already running, you can wait for the process to complete with `ntfy publish --wait-pid` (alias: `--pid`). command is already running, you can wait for the process to complete with `ntfy publish --wait-pid` (alias: `--pid`).
Run a command and wait for it to complete (here: `rsync ...`): Run a command and wait for it to complete (here: `rsync ...`):
``` ```sh
$ ntfy pub --wait-cmd mytopic rsync -av ./ root@example.com:/backups/ | jq . $ ntfy pub --wait-cmd mytopic rsync -av ./ root@example.com:/backups/ | jq .
{ {
"id": "Re0rWXZQM8WB", "id": "Re0rWXZQM8WB",
@ -98,7 +107,8 @@ $ ntfy pub --wait-cmd mytopic rsync -av ./ root@example.com:/backups/ | jq .
Or, if you already started the long-running process and want to wait for it using its process ID (PID), you can do this: Or, if you already started the long-running process and want to wait for it using its process ID (PID), you can do this:
=== "Using a PID directly" === "Using a PID directly"
```
```sh
$ ntfy pub --wait-pid 8458 mytopic | jq . $ ntfy pub --wait-pid 8458 mytopic | jq .
{ {
"id": "orM6hJKNYkWb", "id": "orM6hJKNYkWb",
@ -110,7 +120,8 @@ Or, if you already started the long-running process and want to wait for it usin
``` ```
=== "Using a `pidof`" === "Using a `pidof`"
```
```sh
$ ntfy pub --wait-pid $(pidof rsync) mytopic | jq . $ ntfy pub --wait-pid $(pidof rsync) mytopic | jq .
{ {
"id": "orM6hJKNYkWb", "id": "orM6hJKNYkWb",
@ -122,19 +133,22 @@ Or, if you already started the long-running process and want to wait for it usin
``` ```
## Subscribe to topics ## Subscribe to topics
You can subscribe to topics using `ntfy subscribe`. Depending on how it is called, this command You can subscribe to topics using `ntfy subscribe`. Depending on how it is called, this command
will either print or execute a command for every arriving message. There are a few different ways will either print or execute a command for every arriving message. There are a few different ways
in which the command can be run: in which the command can be run:
### Stream messages as JSON ### Stream messages as JSON
```
```sh
ntfy subscribe TOPIC ntfy subscribe TOPIC
``` ```
If you run the command like this, it prints the JSON representation of every incoming message. This is useful
when you have a command that wants to stream-read incoming JSON messages. Unless `--poll` is passed, this command If you run the command like this, it prints the JSON representation of every incoming message. This is useful
when you have a command that wants to stream-read incoming JSON messages. Unless `--poll` is passed, this command
stays open forever. stays open forever.
``` ```sh
$ ntfy sub mytopic $ ntfy sub mytopic
{"id":"nZ8PjH5oox","time":1639971913,"event":"message","topic":"mytopic","message":"hi there"} {"id":"nZ8PjH5oox","time":1639971913,"event":"message","topic":"mytopic","message":"hi there"}
{"id":"sekSLWTujn","time":1639972063,"event":"message","topic":"mytopic",priority:5,"message":"Oh no!"} {"id":"sekSLWTujn","time":1639972063,"event":"message","topic":"mytopic",priority:5,"message":"Oh no!"}
@ -147,13 +161,15 @@ $ ntfy sub mytopic
</figure> </figure>
### Run command for every message ### Run command for every message
```
```sh
ntfy subscribe TOPIC COMMAND ntfy subscribe TOPIC COMMAND
``` ```
If you run it like this, a COMMAND is executed for every incoming messages. Scroll down to see a list of available If you run it like this, a COMMAND is executed for every incoming messages. Scroll down to see a list of available
environment variables. Here are a few examples: environment variables. Here are a few examples:
``` ```sh
ntfy sub mytopic 'notify-send "$m"' ntfy sub mytopic 'notify-send "$m"'
ntfy sub topic1 /my/script.sh ntfy sub topic1 /my/script.sh
ntfy sub topic1 'echo "Message $m was received. Its title was $t and it had priority $p' ntfy sub topic1 'echo "Message $m was received. Its title was $t and it had priority $p'
@ -164,12 +180,12 @@ ntfy sub topic1 'echo "Message $m was received. Its title was $t and it had prio
<figcaption>Execute command on incoming messages</figcaption> <figcaption>Execute command on incoming messages</figcaption>
</figure> </figure>
The message fields are passed to the command as environment variables and can be used in scripts. Note that since The message fields are passed to the command as environment variables and can be used in scripts. Note that since
these are environment variables, you typically don't have to worry about quoting too much, as long as you enclose them these are environment variables, you typically don't have to worry about quoting too much, as long as you enclose them
in double-quotes, you should be fine: in double-quotes, you should be fine:
| Variable | Aliases | Description | | Variable | Aliases | Description |
|------------------|----------------------------|----------------------------------------| | ---------------- | -------------------------- | -------------------------------------- |
| `$NTFY_ID` | `$id` | Unique message ID | | `$NTFY_ID` | `$id` | Unique message ID |
| `$NTFY_TIME` | `$time` | Unix timestamp of the message delivery | | `$NTFY_TIME` | `$time` | Unix timestamp of the message delivery |
| `$NTFY_TOPIC` | `$topic` | Topic name | | `$NTFY_TOPIC` | `$topic` | Topic name |
@ -178,17 +194,20 @@ in double-quotes, you should be fine:
| `$NTFY_PRIORITY` | `$priority`, `$prio`, `$p` | Message priority (1=min, 5=max) | | `$NTFY_PRIORITY` | `$priority`, `$prio`, `$p` | Message priority (1=min, 5=max) |
| `$NTFY_TAGS` | `$tags`, `$tag`, `$ta` | Message tags (comma separated list) | | `$NTFY_TAGS` | `$tags`, `$tag`, `$ta` | Message tags (comma separated list) |
| `$NTFY_RAW` | `$raw` | Raw JSON message | | `$NTFY_RAW` | `$raw` | Raw JSON message |
### Subscribe to multiple topics ### Subscribe to multiple topics
```
```sh
ntfy subscribe --from-config ntfy subscribe --from-config
``` ```
To subscribe to multiple topics at once, and run different commands for each one, you can use `ntfy subscribe --from-config`, To subscribe to multiple topics at once, and run different commands for each one, you can use `ntfy subscribe --from-config`,
which will read the `subscribe` config from the config file. Please also check out the [ntfy-client systemd service](#using-the-systemd-service). which will read the `subscribe` config from the config file. Please also check out the [ntfy-client systemd service](#using-the-systemd-service).
Here's an example config file that subscribes to three different topics, executing a different command for each of them: Here's an example config file that subscribes to three different topics, executing a different command for each of them:
=== "~/.config/ntfy/client.yml (Linux)" === "~/.config/ntfy/client.yml (Linux)"
```yaml ```yaml
subscribe: subscribe:
- topic: echo-this - topic: echo-this
@ -210,8 +229,8 @@ Here's an example config file that subscribes to three different topics, executi
fi fi
``` ```
=== "~/Library/Application Support/ntfy/client.yml (macOS)" === "~/Library/Application Support/ntfy/client.yml (macOS)"
```yaml ```yaml
subscribe: subscribe:
- topic: echo-this - topic: echo-this
@ -225,6 +244,7 @@ Here's an example config file that subscribes to three different topics, executi
``` ```
=== "%AppData%\ntfy\client.yml (Windows)" === "%AppData%\ntfy\client.yml (Windows)"
```yaml ```yaml
subscribe: subscribe:
- topic: echo-this - topic: echo-this
@ -241,11 +261,11 @@ Here's an example config file that subscribes to three different topics, executi
In this example, when `ntfy subscribe --from-config` is executed: In this example, when `ntfy subscribe --from-config` is executed:
* Messages to `echo-this` simply echos to standard out - Messages to `echo-this` simply echos to standard out
* Messages to `alerts` display as desktop notification for high priority messages using [notify-send](https://manpages.ubuntu.com/manpages/focal/man1/notify-send.1.html) (Linux), - Messages to `alerts` display as desktop notification for high priority messages using [notify-send](https://manpages.ubuntu.com/manpages/focal/man1/notify-send.1.html) (Linux),
[notifu](https://www.paralint.com/projects/notifu/) (Windows) or `osascript` (macOS) [notifu](https://www.paralint.com/projects/notifu/) (Windows) or `osascript` (macOS)
* Messages to `calc` open the calculator 😀 (*because, why not*) - Messages to `calc` open the calculator 😀 (_because, why not_)
* Messages to `print-temp` execute an inline script and print the CPU temperature (Linux version only) - Messages to `print-temp` execute an inline script and print the CPU temperature (Linux version only)
I hope this shows how powerful this command is. Here's a short video that demonstrates the above example: I hope this shows how powerful this command is. Here's a short video that demonstrates the above example:
@ -259,20 +279,21 @@ You can also specify a `default-command` that will run when a message is receive
will be used, otherwise, the subscription settings will override the defaults. will be used, otherwise, the subscription settings will override the defaults.
!!! warning !!! warning
Because the `default-user`, `default-password`, and `default-token` will be sent for each topic that does not have its own username/password (even if the topic does not Because the `default-user`, `default-password`, and `default-token` will be sent for each topic that does not have its own username/password (even if the topic does not
require authentication), be sure that the servers/topics you subscribe to use HTTPS to prevent leaking the username and password. require authentication), be sure that the servers/topics you subscribe to use HTTPS to prevent leaking the username and password.
### Using the systemd service ### Using the systemd service
You can use the `ntfy-client` systemd service (see [ntfy-client.service](https://github.com/binwiederhier/ntfy/blob/main/client/ntfy-client.service)) You can use the `ntfy-client` systemd service (see [ntfy-client.service](https://github.com/binwiederhier/ntfy/blob/main/client/ntfy-client.service))
to subscribe to multiple topics just like in the example above. The service is automatically installed (but not started) to subscribe to multiple topics just like in the example above. The service is automatically installed (but not started)
if you install the deb/rpm package. To configure it, simply edit `/etc/ntfy/client.yml` and run `sudo systemctl restart ntfy-client`. if you install the deb/rpm package. To configure it, simply edit `/etc/ntfy/client.yml` and run `sudo systemctl restart ntfy-client`.
!!! info !!! info
The `ntfy-client.service` runs as user `ntfy`, meaning that typical Linux permission restrictions apply. See below The `ntfy-client.service` runs as user `ntfy`, meaning that typical Linux permission restrictions apply. See below
for how to fix this. for how to fix this.
If the service runs on your personal desktop machine, you may want to override the service user/group (`User=` and `Group=`), and If the service runs on your personal desktop machine, you may want to override the service user/group (`User=` and `Group=`), and
adjust the `DISPLAY` and `DBUS_SESSION_BUS_ADDRESS` environment variables. This will allow you to run commands in your X session adjust the `DISPLAY` and `DBUS_SESSION_BUS_ADDRESS` environment variables. This will allow you to run commands in your X session
as the primary machine user. as the primary machine user.
You can either manually override these systemd service entries with `sudo systemctl edit ntfy-client`, and add this You can either manually override these systemd service entries with `sudo systemctl edit ntfy-client`, and add this
@ -280,15 +301,17 @@ You can either manually override these systemd service entries with `sudo system
after editing the service file: after editing the service file:
=== "/etc/systemd/system/ntfy-client.service.d/override.conf" === "/etc/systemd/system/ntfy-client.service.d/override.conf"
```
```ini
[Service] [Service]
User=phil User=phil
Group=phil Group=phil
Environment="DISPLAY=:0" "DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus" Environment="DISPLAY=:0" "DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus"
``` ```
Or you can run the following script that creates this override config for you: Or you can run the following script that creates this override config for you:
``` ```sh
sudo sh -c 'cat > /etc/systemd/system/ntfy-client.service.d/override.conf' <<EOF sudo sh -c 'cat > /etc/systemd/system/ntfy-client.service.d/override.conf' <<EOF
[Service] [Service]
User=$USER User=$USER
@ -300,25 +323,27 @@ sudo systemctl daemon-reload
sudo systemctl restart ntfy-client sudo systemctl restart ntfy-client
``` ```
### Authentication ### Authentication
Depending on whether the server is configured to support [access control](../config.md#access-control), some topics Depending on whether the server is configured to support [access control](../config.md#access-control), some topics
may be read/write protected so that only users with the correct credentials can subscribe or publish to them. may be read/write protected so that only users with the correct credentials can subscribe or publish to them.
To publish/subscribe to protected topics, you can use [Basic Auth](https://en.wikipedia.org/wiki/Basic_access_authentication) To publish/subscribe to protected topics, you can use [Basic Auth](https://en.wikipedia.org/wiki/Basic_access_authentication)
with a valid username/password. For your self-hosted server, **be sure to use HTTPS to avoid eavesdropping** and exposing with a valid username/password. For your self-hosted server, **be sure to use HTTPS to avoid eavesdropping** and exposing
your password. your password.
You can either add your username and password to the configuration file: You can either add your username and password to the configuration file:
=== "~/.config/ntfy/client.yml" === "~/.config/ntfy/client.yml"
```yaml
- topic: secret ```yaml
command: 'notify-send "$m"' - topic: secret
user: phill command: 'notify-send "$m"'
password: mypass user: phill
``` password: mypass
```
Or with the `ntfy subscibe` command: Or with the `ntfy subscibe` command:
```
```sh
ntfy subscribe \ ntfy subscribe \
-u phil:mypass \ -u phil:mypass \
ntfy.example.com/mysecrets ntfy.example.com/mysecrets

View file

@ -1,18 +1,20 @@
# Subscribe from your phone # Subscribe from your phone
You can use the ntfy [Android App](https://play.google.com/store/apps/details?id=io.heckel.ntfy) or [iOS app](https://apps.apple.com/us/app/ntfy/id1625396347) You can use the ntfy [Android App](https://play.google.com/store/apps/details?id=io.heckel.ntfy) or [iOS app](https://apps.apple.com/us/app/ntfy/id1625396347)
to receive notifications directly on your phone. Just like the server, this app is also open source, and the code is available to receive notifications directly on your phone. Just like the server, this app is also open source, and the code is available
on GitHub ([Android](https://github.com/binwiederhier/ntfy-android), [iOS](https://github.com/binwiederhier/ntfy-ios)). Feel free to on GitHub ([Android](https://github.com/binwiederhier/ntfy-android), [iOS](https://github.com/binwiederhier/ntfy-ios)). Feel free to
contribute, or [build your own](../develop.md). contribute, or [build your own](../develop.md).
<a href="https://play.google.com/store/apps/details?id=io.heckel.ntfy"><img src="../../static/img/badge-googleplay.png"></a> <a href="https://play.google.com/store/apps/details?id=io.heckel.ntfy"><img src="../../static/img/badge-googleplay.png"></a>
<a href="https://f-droid.org/en/packages/io.heckel.ntfy/"><img src="../../static/img/badge-fdroid.png"></a> <a href="https://f-droid.org/en/packages/io.heckel.ntfy/"><img src="../../static/img/badge-fdroid.png"></a>
<a href="https://apps.apple.com/us/app/ntfy/id1625396347"><img src="../../static/img/badge-appstore.png"></a> <a href="https://apps.apple.com/us/app/ntfy/id1625396347"><img src="../../static/img/badge-appstore.png"></a>
You can get the Android app from both [Google Play](https://play.google.com/store/apps/details?id=io.heckel.ntfy) and You can get the Android app from both [Google Play](https://play.google.com/store/apps/details?id=io.heckel.ntfy) and
from [F-Droid](https://f-droid.org/en/packages/io.heckel.ntfy/). Both are largely identical, with the one exception that from [F-Droid](https://f-droid.org/en/packages/io.heckel.ntfy/). Both are largely identical, with the one exception that
the F-Droid flavor does not use Firebase. The iOS app can be downloaded from the [App Store](https://apps.apple.com/us/app/ntfy/id1625396347). the F-Droid flavor does not use Firebase. The iOS app can be downloaded from the [App Store](https://apps.apple.com/us/app/ntfy/id1625396347).
## Overview ## Overview
A picture is worth a thousand words. Here are a few screenshots showing what the app looks like. It's all pretty A picture is worth a thousand words. Here are a few screenshots showing what the app looks like. It's all pretty
straight forward. You can add topics and as soon as you add them, you can [publish messages](../publish.md) to them. straight forward. You can add topics and as soon as you add them, you can [publish messages](../publish.md) to them.
@ -33,6 +35,7 @@ If those screenshots are still not enough, here's a video:
</figure> </figure>
## Message priority ## Message priority
_Supported on:_ :material-android: :material-apple: _Supported on:_ :material-android: :material-apple:
When you [publish messages](../publish.md#message-priority) to a topic, you can **define a priority**. This priority defines When you [publish messages](../publish.md#message-priority) to a topic, you can **define a priority**. This priority defines
@ -63,10 +66,11 @@ setting, and other settings such as popover or notification dot:
</figure> </figure>
## Instant delivery ## Instant delivery
_Supported on:_ :material-android: _Supported on:_ :material-android:
Instant delivery allows you to receive messages on your phone instantly, **even when your phone is in doze mode**, i.e. Instant delivery allows you to receive messages on your phone instantly, **even when your phone is in doze mode**, i.e.
when the screen turns off, and you leave it on the desk for a while. This is achieved with a foreground service, which when the screen turns off, and you leave it on the desk for a while. This is achieved with a foreground service, which
you'll see as a permanent notification that looks like this: you'll see as a permanent notification that looks like this:
<figure markdown> <figure markdown>
@ -75,7 +79,7 @@ you'll see as a permanent notification that looks like this:
</figure> </figure>
Android does not allow you to dismiss this notification, unless you turn off the notification channel in the settings. Android does not allow you to dismiss this notification, unless you turn off the notification channel in the settings.
To do so, long-press on the foreground notification (screenshot above) and navigate to the settings. Then toggle the To do so, long-press on the foreground notification (screenshot above) and navigate to the settings. Then toggle the
"Subscription Service" off: "Subscription Service" off:
<figure markdown> <figure markdown>
@ -83,18 +87,19 @@ To do so, long-press on the foreground notification (screenshot above) and navig
<figcaption>Turning off the persistent instant delivery notification</figcaption> <figcaption>Turning off the persistent instant delivery notification</figcaption>
</figure> </figure>
**Limitations without instant delivery**: Without instant delivery, **messages may arrive with a significant delay** **Limitations without instant delivery**: Without instant delivery, **messages may arrive with a significant delay**
(sometimes many minutes, or even hours later). If you've ever picked up your phone and (sometimes many minutes, or even hours later). If you've ever picked up your phone and
suddenly had 10 messages that were sent long before you know what I'm talking about. suddenly had 10 messages that were sent long before you know what I'm talking about.
The reason for this is [Firebase Cloud Messaging (FCM)](https://firebase.google.com/docs/cloud-messaging). FCM is the The reason for this is [Firebase Cloud Messaging (FCM)](https://firebase.google.com/docs/cloud-messaging). FCM is the
*only* Google approved way to send push messages to Android devices, and it's what pretty much all apps use to deliver push _only_ Google approved way to send push messages to Android devices, and it's what pretty much all apps use to deliver push
notifications. Firebase is overall pretty bad at delivering messages in time, but on Android, most apps are stuck with it. notifications. Firebase is overall pretty bad at delivering messages in time, but on Android, most apps are stuck with it.
The ntfy Android app uses Firebase only for the main host `ntfy.sh`, and only in the Google Play flavor of the app. The ntfy Android app uses Firebase only for the main host `ntfy.sh`, and only in the Google Play flavor of the app.
It won't use Firebase for any self-hosted servers, and not at all in the the F-Droid flavor. It won't use Firebase for any self-hosted servers, and not at all in the the F-Droid flavor.
## Share to topic ## Share to topic
_Supported on:_ :material-android: _Supported on:_ :material-android:
You can share files to a topic using Android's "Share" feature. This works in almost any app that supports sharing files You can share files to a topic using Android's "Share" feature. This works in almost any app that supports sharing files
@ -109,33 +114,35 @@ The feature is pretty self-explanatory, and one picture says more than a thousan
</div> </div>
## ntfy:// links ## ntfy:// links
_Supported on:_ :material-android: _Supported on:_ :material-android:
The ntfy Android app supports deep linking directly to topics. This is useful when integrating with [automation apps](#automation-apps) The ntfy Android app supports deep linking directly to topics. This is useful when integrating with [automation apps](#automation-apps)
such as [MacroDroid](https://play.google.com/store/apps/details?id=com.arlosoft.macrodroid) or [Tasker](https://play.google.com/store/apps/details?id=net.dinglisch.android.taskerm), such as [MacroDroid](https://play.google.com/store/apps/details?id=com.arlosoft.macrodroid) or [Tasker](https://play.google.com/store/apps/details?id=net.dinglisch.android.taskerm),
or to simply directly link to a topic from a mobile website. or to simply directly link to a topic from a mobile website.
!!! info !!! info
Android deep linking of http/https links is very brittle and limited, which is why something like `https://<host>/<topic>/subscribe` is Android deep linking of http/https links is very brittle and limited, which is why something like `https://<host>/<topic>/subscribe` is
**not possible**, and instead `ntfy://` links have to be used. More details in [issue #20](https://github.com/binwiederhier/ntfy/issues/20). **not possible**, and instead `ntfy://` links have to be used. More details in [issue #20](https://github.com/binwiederhier/ntfy/issues/20).
**Supported link formats:** **Supported link formats:**
| Link format | Example | Description | | Link format | Example | Description |
|-------------------------------------------------------------------------------|-------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | ----------------------------------------------------------------------------- | ----------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| <span style="white-space: nowrap">`ntfy://<host>/<topic>`</span> | `ntfy://ntfy.sh/mytopic` | Directly opens the Android app detail view for the given topic and server. Subscribes to the topic if not already subscribed. This is equivalent to the web view `https://ntfy.sh/mytopic` (HTTPS!) | | <span style="white-space: nowrap">`ntfy://<host>/<topic>`</span> | `ntfy://ntfy.sh/mytopic` | Directly opens the Android app detail view for the given topic and server. Subscribes to the topic if not already subscribed. This is equivalent to the web view `https://ntfy.sh/mytopic` (HTTPS!) |
| <span style="white-space: nowrap">`ntfy://<host>/<topic>?secure=false`</span> | `ntfy://example.com/mytopic?secure=false` | Same as above, except that this will use HTTP instead of HTTPS as topic URL. This is equivalent to the web view `http://example.com/mytopic` (HTTP!) | | <span style="white-space: nowrap">`ntfy://<host>/<topic>?secure=false`</span> | `ntfy://example.com/mytopic?secure=false` | Same as above, except that this will use HTTP instead of HTTPS as topic URL. This is equivalent to the web view `http://example.com/mytopic` (HTTP!) |
## Integrations ## Integrations
### UnifiedPush ### UnifiedPush
_Supported on:_ :material-android: _Supported on:_ :material-android:
[UnifiedPush](https://unifiedpush.org) is a standard for receiving push notifications without using the Google-owned [UnifiedPush](https://unifiedpush.org) is a standard for receiving push notifications without using the Google-owned
[Firebase Cloud Messaging (FCM)](https://firebase.google.com/docs/cloud-messaging) service. It puts push notifications [Firebase Cloud Messaging (FCM)](https://firebase.google.com/docs/cloud-messaging) service. It puts push notifications
in the control of the user. ntfy can act as a **UnifiedPush distributor**, forwarding messages to apps that support it. in the control of the user. ntfy can act as a **UnifiedPush distributor**, forwarding messages to apps that support it.
To use ntfy as a distributor, simply select it in one of the [supported apps](https://unifiedpush.org/users/apps/). To use ntfy as a distributor, simply select it in one of the [supported apps](https://unifiedpush.org/users/apps/).
That's it. It's a one-step installation 😀. If desired, you can select your own [selfhosted ntfy server](../install.md) That's it. It's a one-step installation 😀. If desired, you can select your own [selfhosted ntfy server](../install.md)
to handle messages. Here's an example with [FluffyChat](https://fluffychat.im/): to handle messages. Here's an example with [FluffyChat](https://fluffychat.im/):
@ -146,6 +153,7 @@ to handle messages. Here's an example with [FluffyChat](https://fluffychat.im/):
</div> </div>
### Automation apps ### Automation apps
_Supported on:_ :material-android: _Supported on:_ :material-android:
The ntfy Android app integrates nicely with automation apps such as [MacroDroid](https://play.google.com/store/apps/details?id=com.arlosoft.macrodroid) The ntfy Android app integrates nicely with automation apps such as [MacroDroid](https://play.google.com/store/apps/details?id=com.arlosoft.macrodroid)
@ -153,10 +161,11 @@ or [Tasker](https://play.google.com/store/apps/details?id=net.dinglisch.android.
**react to incoming messages**, as well as **send messages**. **react to incoming messages**, as well as **send messages**.
#### React to incoming messages #### React to incoming messages
To react on incoming notifications, you have to register to intents with the `io.heckel.ntfy.MESSAGE_RECEIVED` action (see To react on incoming notifications, you have to register to intents with the `io.heckel.ntfy.MESSAGE_RECEIVED` action (see
[code for details](https://github.com/binwiederhier/ntfy-android/blob/main/app/src/main/java/io/heckel/ntfy/msg/BroadcastService.kt)). [code for details](https://github.com/binwiederhier/ntfy-android/blob/main/app/src/main/java/io/heckel/ntfy/msg/BroadcastService.kt)).
Here's an example using [MacroDroid](https://play.google.com/store/apps/details?id=com.arlosoft.macrodroid) Here's an example using [MacroDroid](https://play.google.com/store/apps/details?id=com.arlosoft.macrodroid)
and [Tasker](https://play.google.com/store/apps/details?id=net.dinglisch.android.taskerm), but any app that can catch and [Tasker](https://play.google.com/store/apps/details?id=net.dinglisch.android.taskerm), but any app that can catch
broadcasts is supported: broadcasts is supported:
<div id="integration-screenshots-receive" class="screenshots"> <div id="integration-screenshots-receive" class="screenshots">
@ -170,7 +179,7 @@ broadcasts is supported:
</div> </div>
For MacroDroid, be sure to type in the package name `io.heckel.ntfy`, otherwise intents may be silently swallowed. For MacroDroid, be sure to type in the package name `io.heckel.ntfy`, otherwise intents may be silently swallowed.
If you're using topics to drive automation, you'll likely want to mute the topic in the ntfy app. This will prevent If you're using topics to drive automation, you'll likely want to mute the topic in the ntfy app. This will prevent
notification popups: notification popups:
<figure markdown> <figure markdown>
@ -181,33 +190,34 @@ notification popups:
Here's a list of extras you can access. Most likely, you'll want to filter for `topic` and react on `message`: Here's a list of extras you can access. Most likely, you'll want to filter for `topic` and react on `message`:
| Extra name | Type | Example | Description | | Extra name | Type | Example | Description |
|----------------------|------------------------------|------------------------------------------|------------------------------------------------------------------------------------| | -------------------- | ---------------------------- | ---------------------------------------- | ---------------------------------------------------------------------------------- |
| `id` | *String* | `bP8dMjO8ig` | Randomly chosen message identifier (likely not very useful for task automation) | | `id` | _String_ | `bP8dMjO8ig` | Randomly chosen message identifier (likely not very useful for task automation) |
| `base_url` | *String* | `https://ntfy.sh` | Root URL of the ntfy server this message came from | | `base_url` | _String_ | `https://ntfy.sh` | Root URL of the ntfy server this message came from |
| `topic` ❤️ | *String* | `mytopic` | Topic name; **you'll likely want to filter for a specific topic** | | `topic` ❤️ | _String_ | `mytopic` | Topic name; **you'll likely want to filter for a specific topic** |
| `muted` | *Boolean* | `true` | Indicates whether the subscription was muted in the app | | `muted` | _Boolean_ | `true` | Indicates whether the subscription was muted in the app |
| `muted_str` | *String (`true` or `false`)* | `true` | Same as `muted`, but as string `true` or `false` | | `muted_str` | _String (`true` or `false`)_ | `true` | Same as `muted`, but as string `true` or `false` |
| `time` | *Int* | `1635528741` | Message date time, as Unix time stamp | | `time` | _Int_ | `1635528741` | Message date time, as Unix time stamp |
| `title` | *String* | `Some title` | Message [title](../publish.md#message-title); may be empty if not set | | `title` | _String_ | `Some title` | Message [title](../publish.md#message-title); may be empty if not set |
| `message` ❤️ | *String* | `Some message` | Message body; **this is likely what you're interested in** | | `message` ❤️ | _String_ | `Some message` | Message body; **this is likely what you're interested in** |
| `message_bytes` | *ByteArray* | `(binary data)` | Message body as binary data | | `message_bytes` | _ByteArray_ | `(binary data)` | Message body as binary data |
| `encoding` | *String* | - | Message encoding (empty or "base64") | | `encoding` | _String_ | - | Message encoding (empty or "base64") |
| `tags` | *String* | `tag1,tag2,..` | Comma-separated list of [tags](../publish.md#tags-emojis) | | `tags` | _String_ | `tag1,tag2,..` | Comma-separated list of [tags](../publish.md#tags-emojis) |
| `tags_map` | *String* | `0=tag1,1=tag2,..` | Map of tags to make it easier to map first, second, ... tag | | `tags_map` | _String_ | `0=tag1,1=tag2,..` | Map of tags to make it easier to map first, second, ... tag |
| `priority` | *Int (between 1-5)* | `4` | Message [priority](../publish.md#message-priority) with 1=min, 3=default and 5=max | | `priority` | _Int (between 1-5)_ | `4` | Message [priority](../publish.md#message-priority) with 1=min, 3=default and 5=max |
| `click` | *String* | `https://google.com` | [Click action](../publish.md#click-action) URL, or empty if not set | | `click` | _String_ | `https://google.com` | [Click action](../publish.md#click-action) URL, or empty if not set |
| `attachment_name` | *String* | `attachment.jpg` | Filename of the attachment; may be empty if not set | | `attachment_name` | _String_ | `attachment.jpg` | Filename of the attachment; may be empty if not set |
| `attachment_type` | *String* | `image/jpeg` | Mime type of the attachment; may be empty if not set | | `attachment_type` | _String_ | `image/jpeg` | Mime type of the attachment; may be empty if not set |
| `attachment_size` | *Long* | `9923111` | Size in bytes of the attachment; may be zero if not set | | `attachment_size` | _Long_ | `9923111` | Size in bytes of the attachment; may be zero if not set |
| `attachment_expires` | *Long* | `1655514244` | Expiry date as Unix timestamp of the attachment URL; may be zero if not set | | `attachment_expires` | _Long_ | `1655514244` | Expiry date as Unix timestamp of the attachment URL; may be zero if not set |
| `attachment_url` | *String* | `https://ntfy.sh/file/afUbjadfl7ErP.jpg` | URL of the attachment; may be empty if not set | | `attachment_url` | _String_ | `https://ntfy.sh/file/afUbjadfl7ErP.jpg` | URL of the attachment; may be empty if not set |
#### Send messages using intents #### Send messages using intents
To send messages from other apps (such as [MacroDroid](https://play.google.com/store/apps/details?id=com.arlosoft.macrodroid) To send messages from other apps (such as [MacroDroid](https://play.google.com/store/apps/details?id=com.arlosoft.macrodroid)
and [Tasker](https://play.google.com/store/apps/details?id=net.dinglisch.android.taskerm)), you can and [Tasker](https://play.google.com/store/apps/details?id=net.dinglisch.android.taskerm)), you can
broadcast an intent with the `io.heckel.ntfy.SEND_MESSAGE` action. The ntfy Android app will forward the intent as a HTTP broadcast an intent with the `io.heckel.ntfy.SEND_MESSAGE` action. The ntfy Android app will forward the intent as a HTTP
POST request to [publish a message](../publish.md). This is primarily useful for apps that do not support HTTP POST/PUT POST request to [publish a message](../publish.md). This is primarily useful for apps that do not support HTTP POST/PUT
(like MacroDroid). In Tasker, you can simply use the "HTTP Request" action, which is a little easier and also works if (like MacroDroid). In Tasker, you can simply use the "HTTP Request" action, which is a little easier and also works if
ntfy is not installed. ntfy is not installed.
Here's what that looks like: Here's what that looks like:
@ -223,10 +233,10 @@ Here's what that looks like:
The following intent extras are supported when for the intent with the `io.heckel.ntfy.SEND_MESSAGE` action: The following intent extras are supported when for the intent with the `io.heckel.ntfy.SEND_MESSAGE` action:
| Extra name | Required | Type | Example | Description | | Extra name | Required | Type | Example | Description |
|--------------|----------|-------------------------------|-------------------|------------------------------------------------------------------------------------| | ------------ | -------- | ----------------------------- | ----------------- | ---------------------------------------------------------------------------------- |
| `base_url` | - | *String* | `https://ntfy.sh` | Root URL of the ntfy server this message came from, defaults to `https://ntfy.sh` | | `base_url` | - | _String_ | `https://ntfy.sh` | Root URL of the ntfy server this message came from, defaults to `https://ntfy.sh` |
| `topic` ❤️ | ✔ | *String* | `mytopic` | Topic name; **you must set this** | | `topic` ❤️ | ✔ | _String_ | `mytopic` | Topic name; **you must set this** |
| `title` | - | *String* | `Some title` | Message [title](../publish.md#message-title); may be empty if not set | | `title` | - | _String_ | `Some title` | Message [title](../publish.md#message-title); may be empty if not set |
| `message` ❤️ | ✔ | *String* | `Some message` | Message body; **you must set this** | | `message` ❤️ | ✔ | _String_ | `Some message` | Message body; **you must set this** |
| `tags` | - | *String* | `tag1,tag2,..` | Comma-separated list of [tags](../publish.md#tags-emojis) | | `tags` | - | _String_ | `tag1,tag2,..` | Comma-separated list of [tags](../publish.md#tags-emojis) |
| `priority` | - | *String or Int (between 1-5)* | `4` | Message [priority](../publish.md#message-priority) with 1=min, 3=default and 5=max | | `priority` | - | _String or Int (between 1-5)_ | `4` | Message [priority](../publish.md#message-priority) with 1=min, 3=default and 5=max |

View file

@ -1,6 +1,7 @@
# Subscribe from the Web UI # Subscribe from the Web UI
You can use the Web UI to subscribe to topics as well. If you do, and you keep the website open, **notifications will You can use the Web UI to subscribe to topics as well. If you do, and you keep the website open, **notifications will
pop up as desktop notifications**. Simply type in the topic name and click the *Subscribe* button. The browser will pop up as desktop notifications**. Simply type in the topic name and click the _Subscribe_ button. The browser will
keep a connection open and listen for incoming notifications. keep a connection open and listen for incoming notifications.
To learn how to send messages, check out the [publishing page](../publish.md). To learn how to send messages, check out the [publishing page](../publish.md).

View file

@ -1,20 +1,24 @@
# Troubleshooting # Troubleshooting
This page lists a few suggestions of what to do when things don't work as expected. This is not a complete list.
This page lists a few suggestions of what to do when things don't work as expected. This is not a complete list.
If this page does not help, feel free to drop by the [Discord](https://discord.gg/cT7ECsZj9w) or [Matrix](https://matrix.to/#/#ntfy:matrix.org) If this page does not help, feel free to drop by the [Discord](https://discord.gg/cT7ECsZj9w) or [Matrix](https://matrix.to/#/#ntfy:matrix.org)
and ask there. We're happy to help. and ask there. We're happy to help.
## ntfy server ## ntfy server
If you host your own ntfy server, and you're having issues with any component, it is always helpful to enable debugging/tracing If you host your own ntfy server, and you're having issues with any component, it is always helpful to enable debugging/tracing
in the server. You can find detailed instructions in the [Logging & Debugging](config.md#logging-debugging) section, but it ultimately in the server. You can find detailed instructions in the [Logging & Debugging](config.md#logging-debugging) section, but it ultimately
boils down to setting `log-level: debug` or `log-level: trace` in the `server.yml` file: boils down to setting `log-level: debug` or `log-level: trace` in the `server.yml` file:
=== "server.yml (debug)" === "server.yml (debug)"
``` yaml
```yaml
log-level: debug log-level: debug
``` ```
=== "server.yml (trace)" === "server.yml (trace)"
``` yaml
```yaml
log-level: trace log-level: trace
``` ```
@ -23,6 +27,7 @@ to the `ntfy serve` command, e.g. `ntfy serve --trace`. If you're using systemd
the logs using `journalctl -u ntfy -f`. The logs will look something like this: the logs using `journalctl -u ntfy -f`. The logs will look something like this:
=== "Example logs (debug)" === "Example logs (debug)"
``` ```
$ ntfy serve --debug $ ntfy serve --debug
2023/03/20 14:45:38 INFO Listening on :2586[http] :1025[smtp], ntfy 2.1.2, log level is DEBUG (tag=startup) 2023/03/20 14:45:38 INFO Listening on :2586[http] :1025[smtp], ntfy 2.1.2, log level is DEBUG (tag=startup)
@ -33,10 +38,11 @@ the logs using `journalctl -u ntfy -f`. The logs will look something like this:
2023/03/20 14:45:39 DEBUG Adding message to cache (http_method=POST, http_path=/mytopic, message_body_size=2, message_event=message, message_id=EZu6i2WZjH0v, message_sender=127.0.0.1, message_time=1679337939, tag=publish, topic=mytopic, visitor_auth_limiter_limit=0.016666666666666666, visitor_auth_limiter_tokens=10, visitor_emails=0, visitor_emails_limit=12, visitor_emails_remaining=12, visitor_id=ip:127.0.0.1, visitor_ip=127.0.0.1, visitor_messages=1, visitor_messages_limit=500, visitor_messages_remaining=499, visitor_request_limiter_limit=0.2, visitor_request_limiter_tokens=59.000259165, visitor_seen=2023-03-20T14:45:39.7-04:00) 2023/03/20 14:45:39 DEBUG Adding message to cache (http_method=POST, http_path=/mytopic, message_body_size=2, message_event=message, message_id=EZu6i2WZjH0v, message_sender=127.0.0.1, message_time=1679337939, tag=publish, topic=mytopic, visitor_auth_limiter_limit=0.016666666666666666, visitor_auth_limiter_tokens=10, visitor_emails=0, visitor_emails_limit=12, visitor_emails_remaining=12, visitor_id=ip:127.0.0.1, visitor_ip=127.0.0.1, visitor_messages=1, visitor_messages_limit=500, visitor_messages_remaining=499, visitor_request_limiter_limit=0.2, visitor_request_limiter_tokens=59.000259165, visitor_seen=2023-03-20T14:45:39.7-04:00)
2023/03/20 14:45:39 DEBUG HTTP request finished (http_method=POST, http_path=/mytopic, tag=http, time_taken_ms=2, visitor_auth_limiter_limit=0.016666666666666666, visitor_auth_limiter_tokens=10, visitor_emails=0, visitor_emails_limit=12, visitor_emails_remaining=12, visitor_id=ip:127.0.0.1, visitor_ip=127.0.0.1, visitor_messages=1, visitor_messages_limit=500, visitor_messages_remaining=499, visitor_request_limiter_limit=0.2, visitor_request_limiter_tokens=59.0004147334, visitor_seen=2023-03-20T14:45:39.7-04:00) 2023/03/20 14:45:39 DEBUG HTTP request finished (http_method=POST, http_path=/mytopic, tag=http, time_taken_ms=2, visitor_auth_limiter_limit=0.016666666666666666, visitor_auth_limiter_tokens=10, visitor_emails=0, visitor_emails_limit=12, visitor_emails_remaining=12, visitor_id=ip:127.0.0.1, visitor_ip=127.0.0.1, visitor_messages=1, visitor_messages_limit=500, visitor_messages_remaining=499, visitor_request_limiter_limit=0.2, visitor_request_limiter_tokens=59.0004147334, visitor_seen=2023-03-20T14:45:39.7-04:00)
2023/03/20 14:45:39 DEBUG Wrote 1 message(s) in 8.285712ms (tag=message_cache) 2023/03/20 14:45:39 DEBUG Wrote 1 message(s) in 8.285712ms (tag=message_cache)
... ...
``` ```
=== "Example logs (trace)" === "Example logs (trace)"
``` ```
$ ntfy serve --trace $ ntfy serve --trace
2023/03/20 14:40:42 INFO Listening on :2586[http] :1025[smtp], ntfy 2.1.2, log level is TRACE (tag=startup) 2023/03/20 14:40:42 INFO Listening on :2586[http] :1025[smtp], ntfy 2.1.2, log level is TRACE (tag=startup)
@ -47,7 +53,7 @@ the logs using `journalctl -u ntfy -f`. The logs will look something like this:
Accept: */* Accept: */*
Content-Length: 2 Content-Length: 2
Content-Type: application/x-www-form-urlencoded Content-Type: application/x-www-form-urlencoded
hi, tag=http, visitor_auth_limiter_limit=0.016666666666666666, visitor_auth_limiter_tokens=10, visitor_emails=0, visitor_emails_limit=12, visitor_emails_remaining=12, visitor_id=ip:127.0.0.1, visitor_ip=127.0.0.1, visitor_messages=0, visitor_messages_limit=500, visitor_messages_remaining=500, visitor_request_limiter_limit=0.2, visitor_request_limiter_tokens=60, visitor_seen=2023-03-20T14:40:59.893-04:00) hi, tag=http, visitor_auth_limiter_limit=0.016666666666666666, visitor_auth_limiter_tokens=10, visitor_emails=0, visitor_emails_limit=12, visitor_emails_remaining=12, visitor_id=ip:127.0.0.1, visitor_ip=127.0.0.1, visitor_messages=0, visitor_messages_limit=500, visitor_messages_remaining=500, visitor_request_limiter_limit=0.2, visitor_request_limiter_tokens=60, visitor_seen=2023-03-20T14:40:59.893-04:00)
2023/03/20 14:40:59 TRACE Received message (http_method=POST, http_path=/mytopic, message_body={ 2023/03/20 14:40:59 TRACE Received message (http_method=POST, http_path=/mytopic, message_body={
"id": "Khaup1RVclU3", "id": "Khaup1RVclU3",
@ -65,8 +71,9 @@ the logs using `journalctl -u ntfy -f`. The logs will look something like this:
``` ```
## Android app ## Android app
On Android, you can turn on logging in the settings under **Settings → Record logs**. This will store up to 1,000 log On Android, you can turn on logging in the settings under **Settings → Record logs**. This will store up to 1,000 log
entries, which you can then copy or upload. entries, which you can then copy or upload.
<figure markdown> <figure markdown>
![Recording logs on Android](static/img/android-screenshot-logs.jpg){ width=400 } ![Recording logs on Android](static/img/android-screenshot-logs.jpg){ width=400 }
@ -119,7 +126,8 @@ adb -s 192.168.1.137:39539 logcat --pid=$(adb -s 192.168.1.137:39539 shell pidof
``` ```
## Web app ## Web app
The web app logs everything to the **developer console**, which you can open by **pressing the F12 key** on your
The web app logs everything to the **developer console**, which you can open by **pressing the F12 key** on your
keyboard. keyboard.
<figure markdown> <figure markdown>
@ -128,4 +136,5 @@ keyboard.
</figure> </figure>
## iOS app ## iOS app
Sorry, there is no way to debug or get the logs from the iOS app (yet), outside of running the app in Xcode. Sorry, there is no way to debug or get the logs from the iOS app (yet), outside of running the app in Xcode.

View file

@ -114,9 +114,7 @@
"justifyMode": "auto", "justifyMode": "auto",
"orientation": "auto", "orientation": "auto",
"reduceOptions": { "reduceOptions": {
"calcs": [ "calcs": ["last"],
"last"
],
"fields": "", "fields": "",
"values": false "values": false
}, },
@ -175,9 +173,7 @@
"justifyMode": "auto", "justifyMode": "auto",
"orientation": "auto", "orientation": "auto",
"reduceOptions": { "reduceOptions": {
"calcs": [ "calcs": ["last"],
"last"
],
"fields": "", "fields": "",
"values": false "values": false
}, },
@ -236,9 +232,7 @@
"justifyMode": "auto", "justifyMode": "auto",
"orientation": "auto", "orientation": "auto",
"reduceOptions": { "reduceOptions": {
"calcs": [ "calcs": ["last"],
"last"
],
"fields": "", "fields": "",
"values": false "values": false
}, },
@ -302,9 +296,7 @@
"justifyMode": "auto", "justifyMode": "auto",
"orientation": "auto", "orientation": "auto",
"reduceOptions": { "reduceOptions": {
"calcs": [ "calcs": ["last"],
"last"
],
"fields": "", "fields": "",
"values": false "values": false
}, },
@ -364,9 +356,7 @@
"justifyMode": "auto", "justifyMode": "auto",
"orientation": "auto", "orientation": "auto",
"reduceOptions": { "reduceOptions": {
"calcs": [ "calcs": ["last"],
"last"
],
"fields": "", "fields": "",
"values": false "values": false
}, },
@ -426,9 +416,7 @@
"justifyMode": "auto", "justifyMode": "auto",
"orientation": "auto", "orientation": "auto",
"reduceOptions": { "reduceOptions": {
"calcs": [ "calcs": ["last"],
"last"
],
"fields": "", "fields": "",
"values": false "values": false
}, },
@ -1083,9 +1071,7 @@
"id": 41, "id": 41,
"options": { "options": {
"legend": { "legend": {
"calcs": [ "calcs": ["mean"],
"mean"
],
"displayMode": "table", "displayMode": "table",
"placement": "right", "placement": "right",
"showLegend": true, "showLegend": true,
@ -1179,9 +1165,7 @@
"id": 16, "id": 16,
"options": { "options": {
"legend": { "legend": {
"calcs": [ "calcs": ["mean"],
"mean"
],
"displayMode": "table", "displayMode": "table",
"placement": "right", "placement": "right",
"showLegend": true, "showLegend": true,
@ -2397,4 +2381,4 @@
"uid": "TO6HgexVz", "uid": "TO6HgexVz",
"version": 24, "version": 24,
"weekStart": "" "weekStart": ""
} }

View file

@ -1,56 +1,64 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8" />
<title>ntfy.sh: EventSource Example</title> <title>ntfy.sh: EventSource Example</title>
<meta name="robots" content="noindex, nofollow" /> <meta name="robots" content="noindex, nofollow" />
<style> <style>
body { font-size: 1.2em; line-height: 130%; } body {
#events { font-family: monospace; } font-size: 1.2em;
line-height: 130%;
}
#events {
font-family: monospace;
}
</style> </style>
</head> </head>
<body> <body>
<h1>ntfy.sh: EventSource Example</h1> <h1>ntfy.sh: EventSource Example</h1>
<p> <p>
This is an example showing how to use <a href="https://ntfy.sh">ntfy.sh</a> with This is an example showing how to use
<a href="https://developer.mozilla.org/en-US/docs/Web/API/EventSource">EventSource</a>.<br/> <a href="https://ntfy.sh">ntfy.sh</a> with
This example doesn't need a server. You can just save the HTML page and run it from anywhere. <a href="https://developer.mozilla.org/en-US/docs/Web/API/EventSource"
</p> >EventSource</a
<button id="publishButton">Send test notification</button> >.<br />
<p><b>Log:</b></p> This example doesn't need a server. You can just save the HTML page and
<div id="events"></div> run it from anywhere.
</p>
<button id="publishButton">Send test notification</button>
<p><b>Log:</b></p>
<div id="events"></div>
<script type="text/javascript"> <script type="text/javascript">
const publishURL = `https://ntfy.sh/example`; const publishURL = `https://ntfy.sh/example`;
const subscribeURL = `https://ntfy.sh/example/sse`; const subscribeURL = `https://ntfy.sh/example/sse`;
const events = document.getElementById('events'); const events = document.getElementById("events");
const eventSource = new EventSource(subscribeURL); const eventSource = new EventSource(subscribeURL);
// Publish button // Publish button
document.getElementById("publishButton").onclick = () => { document.getElementById("publishButton").onclick = () => {
fetch(publishURL, { fetch(publishURL, {
method: 'POST', // works with PUT as well, though that sends an OPTIONS request too! method: "POST", // works with PUT as well, though that sends an OPTIONS request too!
body: `It is ${new Date().toString()}. This is a test.` body: `It is ${new Date().toString()}. This is a test.`,
}) });
}; };
// Incoming events // Incoming events
eventSource.onopen = () => { eventSource.onopen = () => {
let event = document.createElement('div'); let event = document.createElement("div");
event.innerHTML = `EventSource connected to ${subscribeURL}`; event.innerHTML = `EventSource connected to ${subscribeURL}`;
events.appendChild(event); events.appendChild(event);
}; };
eventSource.onerror = (e) => { eventSource.onerror = (e) => {
let event = document.createElement('div'); let event = document.createElement("div");
event.innerHTML = `EventSource error: Failed to connect to ${subscribeURL}`; event.innerHTML = `EventSource error: Failed to connect to ${subscribeURL}`;
events.appendChild(event); events.appendChild(event);
}; };
eventSource.onmessage = (e) => { eventSource.onmessage = (e) => {
let event = document.createElement('div'); let event = document.createElement("div");
event.innerHTML = e.data; event.innerHTML = e.data;
events.appendChild(event); events.appendChild(event);
}; };
</script> </script>
</body>
</body>
</html> </html>

View file

@ -17,12 +17,12 @@ theme:
include_search_page: false include_search_page: false
search_index_only: true search_index_only: true
palette: palette:
- media: "(prefers-color-scheme: light)" # Light mode - media: "(prefers-color-scheme: light)" # Light mode
scheme: default scheme: default
toggle: toggle:
icon: material/lightbulb-outline icon: material/lightbulb-outline
name: Switch to dark mode name: Switch to dark mode
- media: "(prefers-color-scheme: dark)" # Dark mode - media: "(prefers-color-scheme: dark)" # Dark mode
scheme: slate scheme: slate
accent: indigo accent: indigo
toggle: toggle:
@ -76,27 +76,25 @@ plugins:
on_post_build: "docs.hooks:copy_fonts" on_post_build: "docs.hooks:copy_fonts"
nav: nav:
- "Getting started": index.md - "Getting started": index.md
- "Publishing": - "Publishing":
- "Sending messages": publish.md - "Sending messages": publish.md
- "Subscribing": - "Subscribing":
- "From your phone": subscribe/phone.md - "From your phone": subscribe/phone.md
- "From the Web app": subscribe/web.md - "From the Web app": subscribe/web.md
- "From the CLI": subscribe/cli.md - "From the CLI": subscribe/cli.md
- "Using the API": subscribe/api.md - "Using the API": subscribe/api.md
- "Self-hosting": - "Self-hosting":
- "Installation": install.md - "Installation": install.md
- "Configuration": config.md - "Configuration": config.md
- "Other things": - "Other things":
- "FAQs": faq.md - "FAQs": faq.md
- "Examples": examples.md - "Examples": examples.md
- "Integrations + projects": integrations.md - "Integrations + projects": integrations.md
- "Release notes": releases.md - "Release notes": releases.md
- "Emojis 🥳 🎉": emojis.md - "Emojis 🥳 🎉": emojis.md
- "Troubleshooting": troubleshooting.md - "Troubleshooting": troubleshooting.md
- "Known issues": known-issues.md - "Known issues": known-issues.md
- "Deprecation notices": deprecations.md - "Deprecation notices": deprecations.md
- "Development": develop.md - "Development": develop.md
- "Privacy policy": privacy.md - "Privacy policy": privacy.md

View file

@ -1,2 +1,3 @@
# fbsend # fbsend
fbsend is a tiny tool to send data messages to Firebase. It's only used for testing. fbsend is a tiny tool to send data messages to Firebase. It's only used for testing.