More docs, more tests, more docs
This commit is contained in:
		
							parent
							
								
									29c2fc5472
								
							
						
					
					
						commit
						d714af43c9
					
				
					 6 changed files with 155 additions and 60 deletions
				
			
		|  | @ -35,7 +35,7 @@ The command allows you to show the access control list, as well as change it, de | |||
| it is called. | ||||
| 
 | ||||
| Usage: | ||||
|   ntfy access                            # Shows the entire access control list | ||||
|   ntfy access                            # Shows access control list (alias: 'ntfy user list') | ||||
|   ntfy access USERNAME                   # Shows access control entries for USERNAME | ||||
|   ntfy access USERNAME TOPIC PERMISSION  # Allow/deny access for USERNAME to TOPIC | ||||
| 
 | ||||
|  | @ -50,7 +50,7 @@ Arguments: | |||
|                - deny (alias: none) | ||||
| 
 | ||||
| Examples: | ||||
|   ntfy access                        # Shows entire access control list | ||||
|   ntfy access                        # Shows access control list (alias: 'ntfy user list') | ||||
|   ntfy access phil                   # Shows access for user phil | ||||
|   ntfy access phil mytopic rw        # Allow read-write access to mytopic for user phil | ||||
|   ntfy access everyone mytopic rw    # Allow anonymous read-write access to mytopic | ||||
|  | @ -82,6 +82,9 @@ func execUserAccess(c *cli.Context) error { | |||
| 		} | ||||
| 		return resetAccess(c, manager, username, topic) | ||||
| 	} else if perms == "" { | ||||
| 		if topic != "" { | ||||
| 			return errors.New("invalid syntax, please check 'ntfy access --help' for usage details") | ||||
| 		} | ||||
| 		return showAccess(c, manager, username) | ||||
| 	} | ||||
| 	return changeAccess(c, manager, username, topic, perms) | ||||
|  | @ -97,13 +100,13 @@ func changeAccess(c *cli.Context, manager auth.Manager, username string, topic s | |||
| 		return err | ||||
| 	} | ||||
| 	if read && write { | ||||
| 		fmt.Fprintf(c.App.Writer, "Granted read-write access to topic %s\n\n", topic) | ||||
| 		fmt.Fprintf(c.App.ErrWriter, "Granted read-write access to topic %s\n\n", topic) | ||||
| 	} else if read { | ||||
| 		fmt.Fprintf(c.App.Writer, "Granted read-only access to topic %s\n\n", topic) | ||||
| 		fmt.Fprintf(c.App.ErrWriter, "Granted read-only access to topic %s\n\n", topic) | ||||
| 	} else if write { | ||||
| 		fmt.Fprintf(c.App.Writer, "Granted write-only access to topic %s\n\n", topic) | ||||
| 		fmt.Fprintf(c.App.ErrWriter, "Granted write-only access to topic %s\n\n", topic) | ||||
| 	} else { | ||||
| 		fmt.Fprintf(c.App.Writer, "Revoked all access to topic %s\n\n", topic) | ||||
| 		fmt.Fprintf(c.App.ErrWriter, "Revoked all access to topic %s\n\n", topic) | ||||
| 	} | ||||
| 	return showUserAccess(c, manager, username) | ||||
| } | ||||
|  | @ -121,7 +124,7 @@ func resetAllAccess(c *cli.Context, manager auth.Manager) error { | |||
| 	if err := manager.ResetAccess("", ""); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	fmt.Fprintln(c.App.Writer, "Reset access for all users") | ||||
| 	fmt.Fprintln(c.App.ErrWriter, "Reset access for all users") | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
|  | @ -129,7 +132,7 @@ func resetUserAccess(c *cli.Context, manager auth.Manager, username string) erro | |||
| 	if err := manager.ResetAccess(username, ""); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	fmt.Fprintf(c.App.Writer, "Reset access for user %s\n\n", username) | ||||
| 	fmt.Fprintf(c.App.ErrWriter, "Reset access for user %s\n\n", username) | ||||
| 	return showUserAccess(c, manager, username) | ||||
| } | ||||
| 
 | ||||
|  | @ -137,7 +140,7 @@ func resetUserTopicAccess(c *cli.Context, manager auth.Manager, username string, | |||
| 	if err := manager.ResetAccess(username, topic); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	fmt.Fprintf(c.App.Writer, "Reset access for user %s and topic %s\n\n", username, topic) | ||||
| 	fmt.Fprintf(c.App.ErrWriter, "Reset access for user %s and topic %s\n\n", username, topic) | ||||
| 	return showUserAccess(c, manager, username) | ||||
| } | ||||
| 
 | ||||
|  | @ -158,7 +161,9 @@ func showAllAccess(c *cli.Context, manager auth.Manager) error { | |||
| 
 | ||||
| func showUserAccess(c *cli.Context, manager auth.Manager, username string) error { | ||||
| 	users, err := manager.User(username) | ||||
| 	if err != nil { | ||||
| 	if err == auth.ErrNotFound { | ||||
| 		return fmt.Errorf("user %s does not exist", username) | ||||
| 	} else if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	return showUsers(c, manager, []*auth.User{users}) | ||||
|  | @ -166,7 +171,7 @@ func showUserAccess(c *cli.Context, manager auth.Manager, username string) error | |||
| 
 | ||||
| func showUsers(c *cli.Context, manager auth.Manager, users []*auth.User) error { | ||||
| 	for _, user := range users { | ||||
| 		fmt.Fprintf(c.App.Writer, "User %s (%s)\n", user.Name, user.Role) | ||||
| 		fmt.Fprintf(c.App.ErrWriter, "User %s (%s)\n", user.Name, user.Role) | ||||
| 		if user.Role == auth.RoleAdmin { | ||||
| 			fmt.Fprintf(c.App.ErrWriter, "- read-write access to all topics (admin role)\n") | ||||
| 		} else if len(user.Grants) > 0 { | ||||
|  |  | |||
|  | @ -99,6 +99,13 @@ Example: | |||
| 			Usage:   "Shows a list of users", | ||||
| 			Before:  inheritRootReaderFunc, | ||||
| 			Action:  execUserList, | ||||
| 			Description: `Shows a list of all configured users, including the everyone ('*') user. | ||||
| 
 | ||||
| This is a server-only command. It directly reads from the user.db as defined in the server config | ||||
| file server.yml. The command only works if 'auth-file' is properly defined.  | ||||
| 
 | ||||
| This command is an alias to calling 'ntfy access' (display access control list). | ||||
| `, | ||||
| 		}, | ||||
| 	}, | ||||
| 	Description: `Manage users of the ntfy server. | ||||
|  | @ -111,7 +118,7 @@ The command allows you to add/remove/change users in the ntfy user database, as | |||
| passwords or roles. | ||||
| 
 | ||||
| Examples: | ||||
|   ntfy user list                     # Shows list of users                         | ||||
|   ntfy user list                     # Shows list of users (alias: 'ntfy access')                       | ||||
|   ntfy user add phil                 # Add regular user phil   | ||||
|   ntfy user add --role=admin phil    # Add admin user phil | ||||
|   ntfy user del phil                 # Delete user phil | ||||
|  |  | |||
|  | @ -28,7 +28,7 @@ func TestCLI_User_Add_Exists(t *testing.T) { | |||
| 	require.Nil(t, runUserCommand(app, conf, "add", "phil")) | ||||
| 	require.Contains(t, stderr.String(), "user phil added with role user") | ||||
| 
 | ||||
| 	app, stdin, _, stderr = newTestApp() | ||||
| 	app, stdin, _, _ = newTestApp() | ||||
| 	stdin.WriteString("mypass\nmypass") | ||||
| 	err := runUserCommand(app, conf, "add", "phil") | ||||
| 	require.Error(t, err) | ||||
|  | @ -73,6 +73,44 @@ func TestCLI_User_ChangePass(t *testing.T) { | |||
| 	require.Contains(t, stderr.String(), "changed password for user phil") | ||||
| } | ||||
| 
 | ||||
| func TestCLI_User_ChangeRole(t *testing.T) { | ||||
| 	s, conf, port := newTestServerWithAuth(t) | ||||
| 	defer test.StopServer(t, s, port) | ||||
| 
 | ||||
| 	// Add user | ||||
| 	app, stdin, _, stderr := newTestApp() | ||||
| 	stdin.WriteString("mypass\nmypass") | ||||
| 	require.Nil(t, runUserCommand(app, conf, "add", "phil")) | ||||
| 	require.Contains(t, stderr.String(), "user phil added with role user") | ||||
| 
 | ||||
| 	// Change role | ||||
| 	app, _, _, stderr = newTestApp() | ||||
| 	require.Nil(t, runUserCommand(app, conf, "change-role", "phil", "admin")) | ||||
| 	require.Contains(t, stderr.String(), "changed role for user phil to admin") | ||||
| } | ||||
| 
 | ||||
| func TestCLI_User_Delete(t *testing.T) { | ||||
| 	s, conf, port := newTestServerWithAuth(t) | ||||
| 	defer test.StopServer(t, s, port) | ||||
| 
 | ||||
| 	// Add user | ||||
| 	app, stdin, _, stderr := newTestApp() | ||||
| 	stdin.WriteString("mypass\nmypass") | ||||
| 	require.Nil(t, runUserCommand(app, conf, "add", "phil")) | ||||
| 	require.Contains(t, stderr.String(), "user phil added with role user") | ||||
| 
 | ||||
| 	// Delete user | ||||
| 	app, _, _, stderr = newTestApp() | ||||
| 	require.Nil(t, runUserCommand(app, conf, "del", "phil")) | ||||
| 	require.Contains(t, stderr.String(), "user phil removed") | ||||
| 
 | ||||
| 	// Delete user again (does not exist) | ||||
| 	app, _, _, _ = newTestApp() | ||||
| 	err := runUserCommand(app, conf, "del", "phil") | ||||
| 	require.Error(t, err) | ||||
| 	require.Contains(t, err.Error(), "user phil does not exist") | ||||
| } | ||||
| 
 | ||||
| func newTestServerWithAuth(t *testing.T) (s *server.Server, conf *server.Config, port int) { | ||||
| 	conf = server.NewConfig() | ||||
| 	conf.AuthFile = filepath.Join(t.TempDir(), "user.db") | ||||
|  |  | |||
|  | @ -155,7 +155,7 @@ user with `ntfy user add --role=admin ...` and be done with all this (see [examp | |||
| **Example commands** (type `ntfy user --help` or `ntfy user COMMAND --help` for more details): | ||||
| 
 | ||||
| ``` | ||||
| ntfy user list                     # Shows list of users                         | ||||
| ntfy user list                     # Shows list of users (alias: 'ntfy access') | ||||
| ntfy user add phil                 # Add regular user phil   | ||||
| ntfy user add --role=admin phil    # Add admin user phil | ||||
| ntfy user del phil                 # Delete user phil | ||||
|  | @ -164,13 +164,13 @@ ntfy user change-role phil admin   # Make user phil an admin | |||
| ``` | ||||
| 
 | ||||
| ### Access control list (ACL) | ||||
| The access control list (ACL) **manages access to topics for non-admin users, and for anonymous access**. Each entry  | ||||
| represents the access permissions for a user to a specific topic or topic pattern.  | ||||
| The access control list (ACL) **manages access to topics for non-admin users, and for anonymous access (`everyone`/`*`)**. | ||||
| Each entry represents the access permissions for a user to a specific topic or topic pattern.  | ||||
| 
 | ||||
| The ACL can be displayed or modified with the `ntfy access` command: | ||||
| 
 | ||||
| ``` | ||||
| ntfy access                            # Shows the entire access control list | ||||
| ntfy access                            # Shows access control list (alias: 'ntfy user list') | ||||
| ntfy access USERNAME                   # Shows access control entries for USERNAME | ||||
| ntfy access USERNAME TOPIC PERMISSION  # Allow/deny access for USERNAME to TOPIC | ||||
| ``` | ||||
|  | @ -225,10 +225,11 @@ to topic `garagedoor` and all topics starting with the word `alerts` (wildcards) | |||
| ### Example: Private instance | ||||
| The easiest way to configure a private instance is to set `auth-default-access` to `deny-all` in the `server.yml`: | ||||
| 
 | ||||
| ``` yaml | ||||
| auth-file "/var/lib/ntfy/user.db" | ||||
| auth-default-access: "deny-all" | ||||
| ``` | ||||
| === "/etc/ntfy/server.yml" | ||||
|     ``` yaml | ||||
|     auth-file "/var/lib/ntfy/user.db" | ||||
|     auth-default-access: "deny-all" | ||||
|     ``` | ||||
| 
 | ||||
| After that, simply create an `admin` user: | ||||
| 
 | ||||
|  |  | |||
|  | @ -278,12 +278,12 @@ $ curl "ntfy.sh/alerts/json?priority=high&tags=zfs-error" | |||
| 
 | ||||
| Available filters (all case-insensitive): | ||||
| 
 | ||||
| | Filter variable | Alias | Example | Description | | ||||
| |---|---|---|---| | ||||
| | `message` | `X-Message`, `m` | `ntfy.sh/mytopic?message=lalala` | Only return messages that match this exact message string | | ||||
| | `title` | `X-Title`, `t` | `ntfy.sh/mytopic?title=some+title` | Only return messages that match this exact title string | | ||||
| | `priority` | `X-Priority`, `prio`, `p` | `ntfy.sh/mytopic?p=high,urgent` | Only return messages that match *any priority listed* (comma-separated) | | ||||
| | `tags` | `X-Tags`, `tag`, `ta` | `ntfy.sh/mytopic?tags=error,alert` | Only return messages that match *all listed tags* (comma-separated) | | ||||
| | Filter variable | Alias                     | Example                            | Description                                                             | | ||||
| |-----------------|---------------------------|------------------------------------|-------------------------------------------------------------------------| | ||||
| | `message`       | `X-Message`, `m`          | `ntfy.sh/mytopic?message=lalala`   | Only return messages that match this exact message string               | | ||||
| | `title`         | `X-Title`, `t`            | `ntfy.sh/mytopic?title=some+title` | Only return messages that match this exact title string                 | | ||||
| | `priority`      | `X-Priority`, `prio`, `p` | `ntfy.sh/mytopic?p=high,urgent`    | Only return messages that match *any priority listed* (comma-separated) | | ||||
| | `tags`          | `X-Tags`, `tag`, `ta`     | `ntfy.sh/mytopic?tags=error,alert` | Only return messages that match *all listed tags* (comma-separated)     | | ||||
| 
 | ||||
| ### Subscribe to multiple topics | ||||
| It's possible to subscribe to multiple topics in one HTTP call by providing a comma-separated list of topics  | ||||
|  | @ -296,37 +296,70 @@ $ curl -s ntfy.sh/mytopic1,mytopic2/json | |||
| {"id":"Cm02DsxUHb","time":1637182643,"event":"message","topic":"mytopic2","message":"for topic 2"} | ||||
| ``` | ||||
| 
 | ||||
| ### Authentication | ||||
| 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. | ||||
| 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 | ||||
| your password. | ||||
| 
 | ||||
| ``` | ||||
| curl -u phil:mypass -s "https://ntfy.example.com/mytopic/json" | ||||
| ``` | ||||
| 
 | ||||
| ## JSON message format | ||||
| 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: | ||||
| 
 | ||||
| | Field | Required | Type | Example | Description | | ||||
| |---|---|---|---|---| | ||||
| | `id` | ✔️ | *string* | `hwQ2YpKdmg` | Randomly chosen message identifier | | ||||
| | `time` | ✔️ | *int* | `1635528741` | Message date time, as Unix time stamp |   | ||||
| | `event` | ✔️ | `open`, `keepalive` or `message` | `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 | | ||||
| | `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>` | | ||||
| | `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 | | ||||
| **Message**: | ||||
| 
 | ||||
| | Field        | Required | Type                                              | Example               | Description                                                                                                                          | | ||||
| |--------------|----------|---------------------------------------------------|-----------------------|--------------------------------------------------------------------------------------------------------------------------------------| | ||||
| | `id`         | ✔️       | *string*                                          | `hwQ2YpKdmg`          | Randomly chosen message identifier                                                                                                   | | ||||
| | `time`       | ✔️       | *number*                                          | `1635528741`          | Message date time, as Unix time stamp                                                                                                |   | ||||
| | `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 | | ||||
| | `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>`                                               | | ||||
| | `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                                                   | | ||||
| | `click`      | -        | *URL*                                             | `https://example.com` | Website opened when notification is [clicked](../publish.md#click-action)                                                            | | ||||
| | `attachment` | -        | *JSON object*                                     | *see below*           | Details about an attachment (name, URL, size, ...)                                                                                   | | ||||
| 
 | ||||
| **Attachment** (part of the message, see [attachments](../publish.md#attachments) for details): | ||||
| 
 | ||||
| | Field     | Required | Type        | Example                        | Description                                                                                               | | ||||
| |-----------|----------|-------------|--------------------------------|-----------------------------------------------------------------------------------------------------------| | ||||
| | `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                                                                                     |   | ||||
| | `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                   | | ||||
| | `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: | ||||
| 
 | ||||
| === "Notification message" | ||||
|     ``` json | ||||
|     { | ||||
|         "id": "wze9zgqK41", | ||||
|         "time": 1638542110, | ||||
|         "id": "sPs71M8A2T", | ||||
|         "time": 1643935928, | ||||
|         "event": "message", | ||||
|         "topic": "phil_alerts", | ||||
|         "topic": "mytopic", | ||||
|         "priority": 5, | ||||
|         "tags": [ | ||||
|             "warning", | ||||
|             "skull" | ||||
|         ], | ||||
|         "click": "https://homecam.mynet.lan/incident/1234", | ||||
|         "attachment": { | ||||
|             "name": "camera.jpg", | ||||
|             "type": "image/png", | ||||
|             "size": 33848, | ||||
|             "expires": 1643946728, | ||||
|             "url": "https://ntfy.sh/file/sPs71M8A2T.png" | ||||
|         }, | ||||
|         "title": "Unauthorized access detected", | ||||
|         "message": "Remote access to phils-laptop detected. Act right away." | ||||
|         "message": "Movement detected in the yard. You better go check" | ||||
|     } | ||||
|     ``` | ||||
| 
 | ||||
|  | @ -362,15 +395,26 @@ Here's an example for each message type: | |||
|     } | ||||
|     ```     | ||||
| 
 | ||||
| 
 | ||||
| === "Poll request message" | ||||
|     ``` json | ||||
|     { | ||||
|         "id": "371sevb0pD", | ||||
|         "time": 1638542275, | ||||
|         "event": "poll_request", | ||||
|         "topic": "phil_alerts" | ||||
|     } | ||||
|     ``` | ||||
| 
 | ||||
| ## 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. | ||||
| 
 | ||||
| | Parameter | Aliases (case-insensitive) | Description | | ||||
| |---|---|---| | ||||
| | `poll` | `X-Poll`, `po` | Return cached messages and close connection | | ||||
| | `scheduled` | `X-Scheduled`, `sched` | Include scheduled/delayed messages in message list | | ||||
| | `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 | | ||||
| | `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) | | ||||
| | Parameter   | Aliases (case-insensitive) | Description                                                                     | | ||||
| |-------------|----------------------------|---------------------------------------------------------------------------------| | ||||
| | `poll`      | `X-Poll`, `po`             | Return cached messages and close connection                                     | | ||||
| | `scheduled` | `X-Scheduled`, `sched`     | Include scheduled/delayed messages in message list                              | | ||||
| | `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                 | | ||||
| | `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)     | | ||||
|  |  | |||
|  | @ -103,16 +103,16 @@ The message fields are passed to the command as environment variables and can be | |||
| 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: | ||||
| 
 | ||||
| | Variable | Aliases | Description | | ||||
| |---|---|--- | ||||
| | `$NTFY_ID` | `$id` | Unique message ID | | ||||
| | `$NTFY_TIME` | `$time` | Unix timestamp of the message delivery | | ||||
| | `$NTFY_TOPIC` | `$topic` | Topic name | | ||||
| | `$NTFY_MESSAGE` | `$message`, `$m` | Message body | | ||||
| | `$NTFY_TITLE` | `$title`, `$t` | Message title | | ||||
| | `$NTFY_PRIORITY` | `$priority`, `$prio`, `$p` | Message priority (1=min, 5=max) | | ||||
| | `$NTFY_TAGS` | `$tags`, `$tag`, `$ta` | Message tags (comma separated list) | | ||||
| | `$NTFY_RAW` | `$raw` | Raw JSON message | | ||||
| | Variable         | Aliases                    | Description                            | | ||||
| |------------------|----------------------------|----------------------------------------| | ||||
| | `$NTFY_ID`       | `$id`                      | Unique message ID                      | | ||||
| | `$NTFY_TIME`     | `$time`                    | Unix timestamp of the message delivery | | ||||
| | `$NTFY_TOPIC`    | `$topic`                   | Topic name                             | | ||||
| | `$NTFY_MESSAGE`  | `$message`, `$m`           | Message body                           | | ||||
| | `$NTFY_TITLE`    | `$title`, `$t`             | Message title                          | | ||||
| | `$NTFY_PRIORITY` | `$priority`, `$prio`, `$p` | Message priority (1=min, 5=max)        | | ||||
| | `$NTFY_TAGS`     | `$tags`, `$tag`, `$ta`     | Message tags (comma separated list)    | | ||||
| | `$NTFY_RAW`      | `$raw`                     | Raw JSON message                       | | ||||
|     | ||||
| ### Subscribe to multiple topics | ||||
| ``` | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue