diff --git a/client/client_test.go b/client/client_test.go index 5b589da..4ce0067 100644 --- a/client/client_test.go +++ b/client/client_test.go @@ -37,6 +37,8 @@ func TestClient_Publish_Subscribe(t *testing.T) { require.Equal(t, "some delayed message", msg.Message) require.True(t, time.Now().Add(24*time.Hour).Unix() < msg.Time) + time.Sleep(200 * time.Millisecond) + msg = nextMessage(c) require.NotNil(t, msg) require.Equal(t, "some message", msg.Message) diff --git a/cmd/serve.go b/cmd/serve.go index ddc2eed..550e26a 100644 --- a/cmd/serve.go +++ b/cmd/serve.go @@ -22,7 +22,7 @@ var flagsServe = []cli.Flag{ altsrc.NewDurationFlag(&cli.DurationFlag{Name: "cache-duration", Aliases: []string{"b"}, EnvVars: []string{"NTFY_CACHE_DURATION"}, Value: server.DefaultCacheDuration, Usage: "buffer messages for this time to allow `since` requests"}), altsrc.NewDurationFlag(&cli.DurationFlag{Name: "keepalive-interval", Aliases: []string{"k"}, EnvVars: []string{"NTFY_KEEPALIVE_INTERVAL"}, Value: server.DefaultKeepaliveInterval, Usage: "interval of keepalive messages"}), altsrc.NewDurationFlag(&cli.DurationFlag{Name: "manager-interval", Aliases: []string{"m"}, EnvVars: []string{"NTFY_MANAGER_INTERVAL"}, Value: server.DefaultManagerInterval, Usage: "interval of for message pruning and stats printing"}), - altsrc.NewStringFlag(&cli.StringFlag{Name: "smtp-addr", EnvVars: []string{"NTFY_SMTP_ADDR"}, Usage: "SMTP address (host:port) to allow email sending"}), + altsrc.NewStringFlag(&cli.StringFlag{Name: "smtp-addr", EnvVars: []string{"NTFY_SMTP_ADDR"}, Usage: "SMTP server address (host:port) to allow email sending"}), altsrc.NewStringFlag(&cli.StringFlag{Name: "smtp-user", EnvVars: []string{"NTFY_SMTP_USER"}, Usage: "SMTP user (if e-mail sending is enabled)"}), altsrc.NewStringFlag(&cli.StringFlag{Name: "smtp-pass", EnvVars: []string{"NTFY_SMTP_PASS"}, Usage: "SMTP password (if e-mail sending is enabled)"}), altsrc.NewStringFlag(&cli.StringFlag{Name: "smtp-from", EnvVars: []string{"NTFY_SMTP_FROM"}, Usage: "SMTP sender address (if e-mail sending is enabled)"}), diff --git a/docs/config.md b/docs/config.md index 3cd6cb4..f7cfe6b 100644 --- a/docs/config.md +++ b/docs/config.md @@ -300,6 +300,7 @@ variable before running the `ntfy` command (e.g. `export NTFY_LISTEN_HTTP=:80`). | Config option | Env variable | Format | Default | Description | |---|---|---|---|---| +| `base-url` | `NTFY_BASE_URL` | *URL* | - | Public facing base URL of the service (e.g. `https://ntfy.sh`) | | `listen-http` | `NTFY_LISTEN_HTTP` | `[host]:port` | `:80` | Listen address for the HTTP web server | | `listen-https` | `NTFY_LISTEN_HTTPS` | `[host]:port` | - | Listen address for the HTTPS web server. If set, you also need to set `key-file` and `cert-file`. | | `key-file` | `NTFY_KEY_FILE` | *filename* | - | HTTPS/TLS private key file, only used if `listen-https` is set. | @@ -307,42 +308,64 @@ variable before running the `ntfy` command (e.g. `export NTFY_LISTEN_HTTP=:80`). | `firebase-key-file` | `NTFY_FIREBASE_KEY_FILE` | *filename* | - | If set, also publish messages to a Firebase Cloud Messaging (FCM) topic for your app. This is optional and only required to save battery when using the Android app. See [Firebase (FCM](#firebase-fcm). | | `cache-file` | `NTFY_CACHE_FILE` | *filename* | - | If set, messages are cached in a local SQLite database instead of only in-memory. This allows for service restarts without losing messages in support of the since= parameter. See [message cache](#message-cache). | | `cache-duration` | `NTFY_CACHE_DURATION` | *duration* | 12h | Duration for which messages will be buffered before they are deleted. This is required to support the `since=...` and `poll=1` parameter. Set this to `0` to disable the cache entirely. | +| `behind-proxy` | `NTFY_BEHIND_PROXY` | *bool* | false | If set, the X-Forwarded-For header is used to determine the visitor IP address instead of the remote address of the connection. | +| `smtp-addr` | `NTFY_SMTP_ADDR` | `host:port` | - | SMTP server address to allow email sending | +| `smtp-user` | `NTFY_SMTP_USER` | *string* | - | SMTP user; only used if e-mail sending is enabled | +| `smtp-pass` | `NTFY_SMTP_PASS` | *string* | - | SMTP password; only used if e-mail sending is enabled | +| `smtp-from` | `NTFY_SMTP_FROM` | *e-mail address* | - | SMTP sender e-mail address; only used if e-mail sending is enabled | | `keepalive-interval` | `NTFY_KEEPALIVE_INTERVAL` | *duration* | 30s | Interval in which keepalive messages are sent to the client. This is to prevent intermediaries closing the connection for inactivity. Note that the Android app has a hardcoded timeout at 77s, so it should be less than that. | | `manager-interval` | `$NTFY_MANAGER_INTERVAL` | *duration* | 1m | Interval in which the manager prunes old messages, deletes topics and prints the stats. | | `global-topic-limit` | `NTFY_GLOBAL_TOPIC_LIMIT` | *number* | 5000 | Rate limiting: Total number of topics before the server rejects new topics. | | `visitor-subscription-limit` | `NTFY_VISITOR_SUBSCRIPTION_LIMIT` | *number* | 30 | Rate limiting: Number of subscriptions per visitor (IP address) | | `visitor-request-limit-burst` | `NTFY_VISITOR_REQUEST_LIMIT_BURST` | *number* | 60 | Allowed GET/PUT/POST requests per second, per visitor. This setting is the initial bucket of requests each visitor has | | `visitor-request-limit-replenish` | `NTFY_VISITOR_REQUEST_LIMIT_REPLENISH` | *duration* | 10s | Strongly related to `visitor-request-limit-burst`: The rate at which the bucket is refilled | -| `behind-proxy` | `NTFY_BEHIND_PROXY` | *bool* | false | If set, the X-Forwarded-For header is used to determine the visitor IP address instead of the remote address of the connection. | +| `visitor-email-limit-burst` | `NTFY_VISITOR_EMAIL_LIMIT_BURST` | *number* | 16 |Initial limit of e-mails per visitor | +| `visitor-email-limit-replenish` | `NTFY_VISITOR_EMAIL_LIMIT_REPLENISH` | *duration* | 1h | Strongly related to `visitor-email-limit-burst`: The rate at which the bucket is refilled | The format for a *duration* is: `(smh)`, e.g. 30s, 20m or 1h. ## Command line options ``` -$ ntfy --help +$ ntfy serve --help NAME: - ntfy - Simple pub-sub notification service + main serve - Run the ntfy server USAGE: - ntfy [OPTION..] + ntfy serve [OPTIONS..] -GLOBAL OPTIONS: - --config value, -c value config file (default: /etc/ntfy/server.yml) [$NTFY_CONFIG_FILE] - --listen-http value, -l value ip:port used to as listen address (default: ":80") [$NTFY_LISTEN_HTTP] - --firebase-key-file value, -F value Firebase credentials file; if set additionally publish to FCM topic [$NTFY_FIREBASE_KEY_FILE] - --cache-file value, -C value cache file used for message caching [$NTFY_CACHE_FILE] - --cache-duration since, -b since buffer messages for this time to allow since requests (default: 12h0m0s) [$NTFY_CACHE_DURATION] - --keepalive-interval value, -k value interval of keepalive messages (default: 30s) [$NTFY_KEEPALIVE_INTERVAL] - --manager-interval value, -m value interval of for message pruning and stats printing (default: 1m0s) [$NTFY_MANAGER_INTERVAL] - --global-topic-limit value, -T value total number of topics allowed (default: 5000) [$NTFY_GLOBAL_TOPIC_LIMIT] - --visitor-subscription-limit value, -V value number of subscriptions per visitor (default: 30) [$NTFY_VISITOR_SUBSCRIPTION_LIMIT] - --visitor-request-limit-burst value, -B value initial limit of requests per visitor (default: 60) [$NTFY_VISITOR_REQUEST_LIMIT_BURST] - --visitor-request-limit-replenish value, -R value interval at which burst limit is replenished (one per x) (default: 10s) [$NTFY_VISITOR_REQUEST_LIMIT_REPLENISH] - --behind-proxy, -P if set, use X-Forwarded-For header to determine visitor IP address (for rate limiting) (default: false) [$NTFY_BEHIND_PROXY] +DESCRIPTION: + Run the ntfy server and listen for incoming requests + + The command will load the configuration from /etc/ntfy/server.yml. Config options can + be overridden using the command line options. + + Examples: + ntfy serve # Starts server in the foreground (on port 80) + ntfy serve --listen-http :8080 # Starts server with alternate port -Try 'ntfy COMMAND --help' for more information. - -ntfy v1.4.8 (7b8185c), runtime go1.17, built at 1637872539 -Copyright (C) 2021 Philipp C. Heckel, distributed under the Apache License 2.0 +OPTIONS: + --config value, -c value config file (default: /etc/ntfy/server.yml) [$NTFY_CONFIG_FILE] + --base-url value, -B value externally visible base URL for this host (e.g. https://ntfy.sh) [$NTFY_BASE_URL] + --listen-http value, -l value ip:port used to as HTTP listen address (default: ":80") [$NTFY_LISTEN_HTTP] + --listen-https value, -L value ip:port used to as HTTPS listen address [$NTFY_LISTEN_HTTPS] + --key-file value, -K value private key file, if listen-https is set [$NTFY_KEY_FILE] + --cert-file value, -E value certificate file, if listen-https is set [$NTFY_CERT_FILE] + --firebase-key-file value, -F value Firebase credentials file; if set additionally publish to FCM topic [$NTFY_FIREBASE_KEY_FILE] + --cache-file value, -C value cache file used for message caching [$NTFY_CACHE_FILE] + --cache-duration since, -b since buffer messages for this time to allow since requests (default: 12h0m0s) [$NTFY_CACHE_DURATION] + --keepalive-interval value, -k value interval of keepalive messages (default: 30s) [$NTFY_KEEPALIVE_INTERVAL] + --manager-interval value, -m value interval of for message pruning and stats printing (default: 1m0s) [$NTFY_MANAGER_INTERVAL] + --smtp-addr value SMTP server address (host:port) to allow email sending [$NTFY_SMTP_ADDR] + --smtp-user value SMTP user (if e-mail sending is enabled) [$NTFY_SMTP_USER] + --smtp-pass value SMTP password (if e-mail sending is enabled) [$NTFY_SMTP_PASS] + --smtp-from value SMTP sender address (if e-mail sending is enabled) [$NTFY_SMTP_FROM] + --global-topic-limit value, -T value total number of topics allowed (default: 5000) [$NTFY_GLOBAL_TOPIC_LIMIT] + --visitor-subscription-limit value number of subscriptions per visitor (default: 30) [$NTFY_VISITOR_SUBSCRIPTION_LIMIT] + --visitor-request-limit-burst value initial limit of requests per visitor (default: 60) [$NTFY_VISITOR_REQUEST_LIMIT_BURST] + --visitor-request-limit-replenish value interval at which burst limit is replenished (one per x) (default: 10s) [$NTFY_VISITOR_REQUEST_LIMIT_REPLENISH] + --visitor-email-limit-burst value initial limit of e-mails per visitor (default: 16) [$NTFY_VISITOR_EMAIL_LIMIT_BURST] + --visitor-email-limit-replenish value interval at which burst limit is replenished (one per x) (default: 1h0m0s) [$NTFY_VISITOR_EMAIL_LIMIT_REPLENISH] + --behind-proxy, -P if set, use X-Forwarded-For header to determine visitor IP address (for rate limiting) (default: false) [$NTFY_BEHIND_PROXY] + --help, -h show help (default: false) ``` diff --git a/docs/publish.md b/docs/publish.md index 97bd7ac..85cfaf8 100644 --- a/docs/publish.md +++ b/docs/publish.md @@ -592,16 +592,26 @@ Here's an example with a custom message, tags and a priority: file_get_contents('https://ntfy.sh/mywebhook/publish?message=Webhook+triggered&priority=high&tags=warning,skull'); ``` -## Publish as e-mail +## E-mail notifications You can forward messages to e-mail by specifying an address in the header. This can be useful for messages that -you'd like to persist longer, or to blast-notify yourself on all possible channels. Since ntfy does not provide auth, -the [rate limiting](#limitations) is pretty strict (see below). In the default configuration, you get 16 e-mails per -visitor (IP address) and then after that one per hour. On top of that, your IP address appears in the e-mail body. This -is to prevent abuse. +you'd like to persist longer, or to blast-notify yourself on all possible channels. + +Usage is easy: Simply pass the `X-Email` header (or any of its aliases: `X-E-mail`, `Email`, `E-mail`, `Mail`, or `e`). +Only one e-mail address is supported. + +Since ntfy does not provide auth (yet), the rate limiting is pretty strict (see [limitations](#limitations)). In the +default configuration, you get **16 e-mails per visitor** (IP address) and then after that one per hour. On top of +that, your IP address appears in the e-mail body. This is to prevent abuse. === "Command line (curl)" ``` - curl -H "Email: phil@example.com" -d "You've Got Mail" ntfy.sh/alerts + curl \ + -H "Email: phil@example.com" \ + -H "Tags: warning,skull,backup-host,ssh-login" \ + -H "Priority: high" \ + -d "Unknown login from 5.31.23.83 to backups.example.com" \ + ntfy.sh/alerts + curl -H "Email: phil@example.com" -d "You've Got Mail" curl -d "You've Got Mail" "ntfy.sh/alerts?email=phil@example.com" ``` @@ -609,7 +619,9 @@ is to prevent abuse. ``` ntfy publish \ --email=phil@example.com \ - alerts "You've Got Mail" + --tags=warning,skull,backup-host,ssh-login \ + --priority=high \ + alerts "Unknown login from 5.31.23.83 to backups.example.com" ``` === "HTTP" @@ -617,31 +629,44 @@ is to prevent abuse. POST /alerts HTTP/1.1 Host: ntfy.sh Email: phil@example.com + Tags: warning,skull,backup-host,ssh-login + Priority: high - You've Got Mail + Unknown login from 5.31.23.83 to backups.example.com ``` === "JavaScript" ``` javascript fetch('https://ntfy.sh/alerts', { method: 'POST', - body: "You've Got Mail", - headers: { 'Email': 'phil@example.com' } + body: "Unknown login from 5.31.23.83 to backups.example.com", + headers: { + 'Email': 'phil@example.com', + 'Tags': 'warning,skull,backup-host,ssh-login', + 'Priority': 'high' + } }) ``` === "Go" ``` go - req, _ := http.NewRequest("POST", "https://ntfy.sh/alerts", strings.NewReader("You've Got Mail")) + req, _ := http.NewRequest("POST", "https://ntfy.sh/alerts", + strings.NewReader("Unknown login from 5.31.23.83 to backups.example.com")) req.Header.Set("Email", "phil@example.com") + req.Header.Set("Tags", "warning,skull,backup-host,ssh-login") + req.Header.Set("Priority", "high") http.DefaultClient.Do(req) ``` === "Python" ``` python requests.post("https://ntfy.sh/alerts", - data="You've Got Mail", - headers={ "Email": "phil@example.com" }) + data="Unknown login from 5.31.23.83 to backups.example.com", + headers={ + "Email": "phil@example.com", + "Tags": "warning,skull,backup-host,ssh-login", + "Priority": "high" + }) ``` === "PHP" @@ -651,12 +676,21 @@ is to prevent abuse. 'method' => 'POST', 'header' => "Content-Type: text/plain\r\n" . - "Email: phil@example.com", - 'content' => 'You've Got Mail' + "Email: phil@example.com\r\n" . + "Tags: warning,skull,backup-host,ssh-login\r\n" . + "Priority: high", + 'content' => 'Unknown login from 5.31.23.83 to backups.example.com' ] ])); ``` +Here's what that looks like in Google Mail: + +
+ ![e-mail notification](static/img/screenshot-email.png){ width=600 } +
E-mail notification
+
+ ## Advanced features ### Message caching @@ -827,6 +861,6 @@ and can be passed as **HTTP headers** or **query parameters in the URL**. They a | `X-Priority` | `Priority`, `prio`, `p` | [Message priority](#message-priority) | | `X-Tags` | `Tags`, `Tag`, `ta` | [Tags and emojis](#tags-emojis) | | `X-Delay` | `Delay`, `X-At`, `At`, `X-In`, `In` | Timestamp or duration for [delayed delivery](#scheduled-delivery) | -| `X-Email` | `X-E-Mail`, `Email`, `E-Mail`, `mail`, `e` | E-mail address for [e-mail delivery](#publish-as-e-mail) | +| `X-Email` | `X-E-Mail`, `Email`, `E-Mail`, `mail`, `e` | E-mail address for [e-mail notifications](#e-mail-notifications) | | `X-Cache` | `Cache` | Allows disabling [message caching](#message-caching) | | `X-Firebase` | `Firebase` | Allows disabling [sending to Firebase](#disable-firebase) | diff --git a/docs/static/img/screenshot-email.png b/docs/static/img/screenshot-email.png new file mode 100644 index 0000000..48c1f39 Binary files /dev/null and b/docs/static/img/screenshot-email.png differ diff --git a/server/server.yml b/server/server.yml index 8bf686d..8f8930f 100644 --- a/server/server.yml +++ b/server/server.yml @@ -1,8 +1,12 @@ # ntfy server config file +# Public facing base URL of the service (e.g. https://ntfy.sh or https://ntfy.example.com) +# This setting is currently only used by the e-mail feature. +# +# base-url: + # Listen address for the HTTP & HTTPS web server. If "listen-https" is set, you must also -# set "key-file" and "cert-file". -# Format: : +# set "key-file" and "cert-file". Format: : # # listen-http: ":80" # listen-https: @@ -34,6 +38,27 @@ # # cache-duration: 12h +# If set, the X-Forwarded-For header is used to determine the visitor IP address +# instead of the remote address of the connection. +# +# WARNING: If you are behind a proxy, you must set this, otherwise all visitors are rate limited +# as if they are one. +# +# behind-proxy: false + +# If enabled, allow e-mail notifications via the 'X-Email' header. As of today, only SMTP servers +# with plain text auth and STARTLS are supported. Please also refer to the rate limiting settings +# below (visitor-email-limit-burst & visitor-email-limit-burst). +# +# - smtp-addr is the hostname:port of the SMTP server +# - smtp-user/smtp-pass are the username and password of the SMTP user +# - smtp-from is the e-mail address of the sender +# +# smtp-addr: +# smtp-user: +# smtp-pass: +# smtp-from: + # Interval in which keepalive messages are sent to the client. This is to prevent # intermediaries closing the connection for inactivity. # @@ -67,11 +92,3 @@ # # visitor-email-limit-burst: 16 # visitor-email-limit-replenish: 1h - -# If set, the X-Forwarded-For header is used to determine the visitor IP address -# instead of the remote address of the connection. -# -# WARNING: If you are behind a proxy, you must set this, otherwise all visitors are rate limited -# as if they are one. -# -# behind-proxy: false diff --git a/util/util.go b/util/util.go index 440b61e..38e28d5 100644 --- a/util/util.go +++ b/util/util.go @@ -148,7 +148,7 @@ func PriorityString(priority int) (string, error) { case 4: return "high", nil case 5: - return "urgent", nil + return "max", nil default: return "", errInvalidPriority } diff --git a/util/util_test.go b/util/util_test.go index 79f4fc6..c70e45c 100644 --- a/util/util_test.go +++ b/util/util_test.go @@ -100,3 +100,24 @@ func TestParsePriority_Invalid(t *testing.T) { require.Equal(t, errInvalidPriority, err) } } + +func TestPriorityString(t *testing.T) { + priorities := []int{0, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 5} + expected := []string{"default", "min", "low", "default", "high", "max"} + for i, priority := range priorities { + actual, err := PriorityString(priority) + require.Nil(t, err) + require.Equal(t, expected[i], actual) + } +} + +func TestPriorityString_Invalid(t *testing.T) { + _, err := PriorityString(99) + require.Equal(t, err, errInvalidPriority) +} + +func TestShortTopicURL(t *testing.T) { + require.Equal(t, "ntfy.sh/mytopic", ShortTopicURL("https://ntfy.sh/mytopic")) + require.Equal(t, "ntfy.sh/mytopic", ShortTopicURL("http://ntfy.sh/mytopic")) + require.Equal(t, "lalala", ShortTopicURL("lalala")) +}