This commit is contained in:
Philipp Heckel 2021-12-20 20:46:51 -05:00
parent edb6b0cf06
commit 85b4abde6c
5 changed files with 123 additions and 55 deletions

View file

@ -5,14 +5,25 @@
# #
# default-host: https://ntfy.sh # default-host: https://ntfy.sh
# Subscriptions to topics and their actions. This option is only used by the "ntfy subscribe --from-config" # Subscriptions to topics and their actions. This option is primarily used by the systemd service,
# command. # or if you cann "ntfy subscribe --from-config" directly.
# #
# Here's a (hopefully self-explanatory) example: # Example:
# subscribe: # subscribe:
# - topic: mytopic # - topic: mytopic
# command: /usr/local/bin/mytopic-triggered.sh # command: /usr/local/bin/mytopic-triggered.sh
# - topic: myserver.com/anothertopic # - topic: myserver.com/anothertopic
# command: 'echo "$message"' # command: 'echo "$message"'
# #
# Variables:
# 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, $p Message priority (1=min, 5=max)
# $NTFY_TAGS $tags, $ta Message tags (comma separated list)
#
# subscribe: # subscribe:

View file

@ -7,12 +7,12 @@ const (
// Config is the config struct for a Client // Config is the config struct for a Client
type Config struct { type Config struct {
DefaultHost string DefaultHost string `yaml:"default-host"`
Subscribe []struct { Subscribe []struct {
Topic string Topic string `yaml:"topic"`
Command string Command string `yaml:"command"`
// If []map[string]string TODO This would be cool // If []map[string]string TODO This would be cool
} } `yaml:"subscribe"`
} }
// NewConfig creates a new Config struct for a Client // NewConfig creates a new Config struct for a Client

View file

@ -20,11 +20,6 @@ var cmdSubscribe = &cli.Command{
Usage: "Subscribe to one or more topics on a ntfy server", Usage: "Subscribe to one or more topics on a ntfy server",
UsageText: "ntfy subscribe [OPTIONS..] [TOPIC]", UsageText: "ntfy subscribe [OPTIONS..] [TOPIC]",
Action: execSubscribe, Action: execSubscribe,
OnUsageError: func(context *cli.Context, err error, isSubcommand bool) error {
println("ee")
return nil
},
Flags: []cli.Flag{ Flags: []cli.Flag{
&cli.StringFlag{Name: "config", Aliases: []string{"c"}, Usage: "client config file"}, &cli.StringFlag{Name: "config", Aliases: []string{"c"}, Usage: "client config file"},
&cli.StringFlag{Name: "since", Aliases: []string{"s"}, Usage: "return events since `SINCE` (Unix timestamp, or all)"}, &cli.StringFlag{Name: "since", Aliases: []string{"s"}, Usage: "return events since `SINCE` (Unix timestamp, or all)"},
@ -109,26 +104,26 @@ func execSubscribe(c *cli.Context) error {
// Execute poll or subscribe // Execute poll or subscribe
if poll { if poll {
return execPoll(c, cl, conf, topic, command, options...) return doPoll(c, cl, conf, topic, command, options...)
} }
return execSubscribeInternal(c, cl, conf, topic, command, options...) return doSubscribe(c, cl, conf, topic, command, options...)
} }
func execPoll(c *cli.Context, cl *client.Client, conf *client.Config, topic, command string, options ...client.SubscribeOption) error { func doPoll(c *cli.Context, cl *client.Client, conf *client.Config, topic, command string, options ...client.SubscribeOption) error {
for _, s := range conf.Subscribe { // may be nil for _, s := range conf.Subscribe { // may be nil
if err := execPollSingle(c, cl, s.Topic, s.Command, options...); err != nil { if err := doPollSingle(c, cl, s.Topic, s.Command, options...); err != nil {
return err return err
} }
} }
if topic != "" { if topic != "" {
if err := execPollSingle(c, cl, topic, command, options...); err != nil { if err := doPollSingle(c, cl, topic, command, options...); err != nil {
return err return err
} }
} }
return nil return nil
} }
func execPollSingle(c *cli.Context, cl *client.Client, topic, command string, options ...client.SubscribeOption) error { func doPollSingle(c *cli.Context, cl *client.Client, topic, command string, options ...client.SubscribeOption) error {
messages, err := cl.Poll(topic, options...) messages, err := cl.Poll(topic, options...)
if err != nil { if err != nil {
return err return err
@ -139,7 +134,7 @@ func execPollSingle(c *cli.Context, cl *client.Client, topic, command string, op
return nil return nil
} }
func execSubscribeInternal(c *cli.Context, cl *client.Client, conf *client.Config, topic, command string, options ...client.SubscribeOption) error { func doSubscribe(c *cli.Context, cl *client.Client, conf *client.Config, topic, command string, options ...client.SubscribeOption) error {
commands := make(map[string]string) commands := make(map[string]string)
for _, s := range conf.Subscribe { // May be nil for _, s := range conf.Subscribe { // May be nil
topicURL := cl.Subscribe(s.Topic, options...) topicURL := cl.Subscribe(s.Topic, options...)

Binary file not shown.

View file

@ -5,10 +5,10 @@ to topics via the ntfy CLI. The CLI is included in the same `ntfy` binary that c
!!! 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. Or it may not be. 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 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 client are the same binary, so it's all very convenient. After installing, you can (optionally) configure the client
by creating `~/.config/ntfy/client.yml` (for the non-root user), or `/etc/ntfy/client.yml` (for the root user). You by creating `~/.config/ntfy/client.yml` (for the non-root user), 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. can find a [skeleton config](https://github.com/binwiederhier/ntfy/blob/main/client/client.yml) on GitHub.
@ -23,7 +23,7 @@ you may want to edit the `default-host` option:
default-host: https://ntfy.myhost.com default-host: https://ntfy.myhost.com
``` ```
## Publish using the ntfy CLI ## 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:
@ -56,20 +56,23 @@ quick ones:
ntfy pub mywebhook ntfy pub mywebhook
``` ```
## Subscribe using the ntfy CLI ## 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 and print JSON ### Stream messages as JSON
If run like this `ntfy subscribe TOPIC`, the command prints the JSON representation of every incoming ```
message. This is useful when you have a command that wants to stream-read incoming JSON messages. ntfy subscribe TOPIC
Unless `--poll` is passed, this command stays open forever. ```
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.
``` ```
$ 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","tags":["warning","skull"],"message":"Oh no, something went wrong"} {"id":"sekSLWTujn","time":1639972063,"event":"message","topic":"mytopic",priority:5,"message":"Oh no!"}
``` ```
<figure> <figure>
@ -77,15 +80,28 @@ $ ntfy sub mytopic
<figcaption>Subscribe in JSON mode</figcaption> <figcaption>Subscribe in JSON mode</figcaption>
</figure> </figure>
### Execute a command for every incoming message ### Run command for every message
If run like this `ntfy subscribe TOPIC COMMAND`, a COMMAND is executed for every incoming messages. ```
The message fields are passed to the command as environment variables and can be used in scripts: ntfy subscribe TOPIC COMMAND
```
If you run it like this, a COMMAND is executed for every incoming messages. Here are a few
examples:
```
ntfy sub mytopic 'notify-send "$m"'
ntfy sub topic1 /my/script.sh
ntfy sub topic1 'echo "Message $m was received. Its title was $t and it had priority $p'
```
<figure> <figure>
<video controls muted autoplay loop width="650" src="../../static/img/cli-subscribe-video-2.webm"></video> <video controls muted autoplay loop width="650" src="../../static/img/cli-subscribe-video-2.webm"></video>
<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
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 | | Variable | Aliases | Description |
|---|---|--- |---|---|---
| `$NTFY_ID` | `$id` | Unique message ID | | `$NTFY_ID` | `$id` | Unique message ID |
@ -96,32 +112,78 @@ The message fields are passed to the command as environment variables and can be
| `$NTFY_PRIORITY` | `$priority`, `$p` | Message priority (1=min, 5=max) | | `$NTFY_PRIORITY` | `$priority`, `$p` | Message priority (1=min, 5=max) |
| `$NTFY_TAGS` | `$tags`, `$ta` | Message tags (comma separated list) | | `$NTFY_TAGS` | `$tags`, `$ta` | Message tags (comma separated list) |
Examples: ### Subscribing to multiple topics
ntfy sub mytopic 'notify-send "$m"' # Execute command for incoming messages ```
ntfy sub topic1 /my/script.sh # Execute script for incoming messages
### Using a config file
ntfy subscribe --from-config ntfy subscribe --from-config
Service mode (used in ntfy-client.service). This reads the config file (/etc/ntfy/client.yml ```
or ~/.config/ntfy/client.yml) and sets up subscriptions for every topic in the "subscribe:" To subscribe to multiple topics at once, and run different commands for each one, you can use `ntfy subscribe --from-config`,
block (see config file). which will read the `subscribe` config from the config file. Please also check out the [ntfy-client systemd service](#using-the-systemd-service).
Examples: Here's an example config file that subscribes to three different topics, executing a different command for each of them:
ntfy sub --from-config # Read topics from config file
ntfy sub --config=/my/client.yml --from-config # Read topics from alternate config file
The default config file for all client commands is /etc/ntfy/client.yml (if root user), === "~/.config/ntfy/client.yml"
or ~/.config/ntfy/client.yml for all other users. ```yaml
subscribe:
- topic: echo-this
command: 'echo "Message received: $message"'
- topic: get-temp
command: |
temp="$(sensors | awk '/Package/ { print $4 }')"
ntfy publish --quiet temp "$temp";
echo "CPU temp is $temp; published to topic 'temp'"
- topic: alerts
command: notify-send "$m"
- topic: calc
command: 'gnome-calculator 2>/dev/null &'
```
In this example, when `ntfy subscribe --from-config` is executed:
* Messages to topic `echo-this` will be simply echoed to standard out
* Messages to topic `get-temp` will publish the CPU core temperature to topic `temp`
* Messages to topic `alerts` will be displayed as desktop notification using `notify-send`
* And messages to topic `calc` will open the gnome calculator 😀 (*because, why not*)
I hope this shows how powerful this command is. Here's a short video that demonstrates the above example:
<figure>
<video controls muted autoplay loop width="650" src="../../static/img/cli-subscribe-video-3.webm"></video>
<figcaption>Execute all the things</figcaption>
</figure>
### 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))
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`.
!!! info
The `ntfy-client.service` runs as user `ntfy`, meaning that typical Linux permission restrictions apply. See below
for how to fix this.
If it runs on your personal desktop machine, you may want to override the service user/group (`User=` and `Group=`), and
adjust the `DISPLAY` and DBUS environment variables. This will allow you to run commands in your X session as the primary
machine user.
You can either manually override these systemd service entries with `sudo systemctl edit ntfy-client`, and add this
(assuming your user is `pheckel`):
=== "/etc/systemd/system/ntfy-client.service.d/override.conf"
``` ```
[Service] [Service]
User=pheckel User=pheckel
Group=pheckel Group=pheckel
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:
Here's an example for a complete client config for a self-hosted server: ```
sudo sh -c 'cat > /etc/systemd/system/ntfy-client.service.d/override.conf' <<EOF
[Service]
User=$USER
Group=$USER
Environment="DISPLAY=:0" "DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/$(id -u)/bus"
EOF
sudo systemctl daemon-reload
sudo systemctl restart ntfy-client
```