This commit is contained in:
Philipp Heckel 2021-12-17 09:32:59 -05:00
parent 1e8421e8ce
commit a1f513f6a5
9 changed files with 138 additions and 65 deletions

View file

@ -34,12 +34,12 @@ type Message struct {
Event string Event string
Time int64 Time int64
Topic string Topic string
BaseURL string
TopicURL string
Message string Message string
Title string Title string
Priority int Priority int
Tags []string Tags []string
BaseURL string
TopicURL string
Raw string Raw string
} }
@ -73,7 +73,23 @@ func (c *Client) Publish(topicURL, message string, options ...PublishOption) err
return err return err
} }
func (c *Client) Subscribe(topicURL string) { func (c *Client) Poll(topicURL string, options ...SubscribeOption) ([]*Message, error) {
ctx := context.Background()
messages := make([]*Message, 0)
msgChan := make(chan *Message)
errChan := make(chan error)
go func() {
err := performSubscribeRequest(ctx, msgChan, topicURL, options...)
close(msgChan)
errChan <- err
}()
for m := range msgChan {
messages = append(messages, m)
}
return messages, <-errChan
}
func (c *Client) Subscribe(topicURL string, options ...SubscribeOption) {
c.mu.Lock() c.mu.Lock()
defer c.mu.Unlock() defer c.mu.Unlock()
if _, ok := c.subscriptions[topicURL]; ok { if _, ok := c.subscriptions[topicURL]; ok {
@ -81,7 +97,7 @@ func (c *Client) Subscribe(topicURL string) {
} }
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
c.subscriptions[topicURL] = &subscription{cancel} c.subscriptions[topicURL] = &subscription{cancel}
go handleConnectionLoop(ctx, c.Messages, topicURL) go handleSubscribeConnLoop(ctx, c.Messages, topicURL, options...)
} }
func (c *Client) Unsubscribe(topicURL string) { func (c *Client) Unsubscribe(topicURL string) {
@ -95,25 +111,30 @@ func (c *Client) Unsubscribe(topicURL string) {
return return
} }
func handleConnectionLoop(ctx context.Context, msgChan chan *Message, topicURL string) { func handleSubscribeConnLoop(ctx context.Context, msgChan chan *Message, topicURL string, options ...SubscribeOption) {
for { for {
if err := handleConnection(ctx, msgChan, topicURL); err != nil { if err := performSubscribeRequest(ctx, msgChan, topicURL, options...); err != nil {
log.Printf("connection to %s failed: %s", topicURL, err.Error()) log.Printf("Connection to %s failed: %s", topicURL, err.Error())
} }
select { select {
case <-ctx.Done(): case <-ctx.Done():
log.Printf("connection to %s exited", topicURL) log.Printf("Connection to %s exited", topicURL)
return return
case <-time.After(5 * time.Second): case <-time.After(5 * time.Second):
} }
} }
} }
func handleConnection(ctx context.Context, msgChan chan *Message, topicURL string) error { func performSubscribeRequest(ctx context.Context, msgChan chan *Message, topicURL string, options ...SubscribeOption) error {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("%s/json", topicURL), nil) req, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("%s/json", topicURL), nil)
if err != nil { if err != nil {
return err return err
} }
for _, option := range options {
if err := option(req); err != nil {
return err
}
}
resp, err := http.DefaultClient.Do(req) resp, err := http.DefaultClient.Do(req)
if err != nil { if err != nil {
return err return err

View file

@ -4,42 +4,24 @@ import (
"net/http" "net/http"
) )
type PublishOption func(r *http.Request) error type RequestOption func(r *http.Request) error
type PublishOption = RequestOption
type SubscribeOption = RequestOption
func WithTitle(title string) PublishOption { func WithTitle(title string) PublishOption {
return func(r *http.Request) error { return WithHeader("X-Title", title)
if title != "" {
r.Header.Set("X-Title", title)
}
return nil
}
} }
func WithPriority(priority string) PublishOption { func WithPriority(priority string) PublishOption {
return func(r *http.Request) error { return WithHeader("X-Priority", priority)
if priority != "" {
r.Header.Set("X-Priority", priority)
}
return nil
}
} }
func WithTags(tags string) PublishOption { func WithTags(tags string) PublishOption {
return func(r *http.Request) error { return WithHeader("X-Tags", tags)
if tags != "" {
r.Header.Set("X-Tags", tags)
}
return nil
}
} }
func WithDelay(delay string) PublishOption { func WithDelay(delay string) PublishOption {
return func(r *http.Request) error { return WithHeader("X-Delay", delay)
if delay != "" {
r.Header.Set("X-Delay", delay)
}
return nil
}
} }
func WithNoCache() PublishOption { func WithNoCache() PublishOption {
@ -50,20 +32,32 @@ func WithNoFirebase() PublishOption {
return WithHeader("X-Firebase", "no") return WithHeader("X-Firebase", "no")
} }
func WithHeader(header, value string) PublishOption { func WithSince(since string) SubscribeOption {
return WithQueryParam("since", since)
}
func WithPoll() SubscribeOption {
return WithQueryParam("poll", "1")
}
func WithScheduled() SubscribeOption {
return WithQueryParam("scheduled", "1")
}
func WithHeader(header, value string) RequestOption {
return func(r *http.Request) error { return func(r *http.Request) error {
r.Header.Set(header, value) if value != "" {
r.Header.Set(header, value)
}
return nil return nil
} }
} }
type SubscribeOption func(r *http.Request) error func WithQueryParam(param, value string) RequestOption {
func WithSince(since string) PublishOption {
return func(r *http.Request) error { return func(r *http.Request) error {
if since != "" { if value != "" {
q := r.URL.Query() q := r.URL.Query()
q.Add("since", since) q.Add(param, value)
r.URL.RawQuery = q.Encode() r.URL.RawQuery = q.Encode()
} }
return nil return nil

View file

@ -36,7 +36,7 @@ func New() *cli.App {
func execMainApp(c *cli.Context) error { func execMainApp(c *cli.Context) error {
log.Printf("\x1b[1;33mDeprecation notice: Please run the server using 'ntfy serve'; see 'ntfy -h' for help.\x1b[0m") log.Printf("\x1b[1;33mDeprecation notice: Please run the server using 'ntfy serve'; see 'ntfy -h' for help.\x1b[0m")
log.Printf("\x1b[1;33mThis way of running the server will be removed Feb 2022.\x1b[0m") log.Printf("\x1b[1;33mThis way of running the server will be removed March 2022. See https://ntfy.sh/docs/deprecations/ for details.\x1b[0m")
return execServe(c) return execServe(c)
} }

View file

@ -9,7 +9,7 @@ import (
var cmdPublish = &cli.Command{ var cmdPublish = &cli.Command{
Name: "publish", Name: "publish",
Aliases: []string{"pub", "send"}, Aliases: []string{"pub", "send", "push"},
Usage: "Send message via a ntfy server", Usage: "Send message via a ntfy server",
UsageText: "ntfy send [OPTIONS..] TOPIC MESSAGE", UsageText: "ntfy send [OPTIONS..] TOPIC MESSAGE",
Action: execPublish, Action: execPublish,

View file

@ -21,6 +21,8 @@ var cmdSubscribe = &cli.Command{
Flags: []cli.Flag{ Flags: []cli.Flag{
&cli.StringFlag{Name: "exec", Aliases: []string{"e"}, Usage: "execute command for each message event"}, &cli.StringFlag{Name: "exec", Aliases: []string{"e"}, Usage: "execute command for each message event"},
&cli.StringFlag{Name: "since", Aliases: []string{"s"}, Usage: "return events since (Unix timestamp, or all)"}, &cli.StringFlag{Name: "since", Aliases: []string{"s"}, Usage: "return events since (Unix timestamp, or all)"},
&cli.BoolFlag{Name: "poll", Aliases: []string{"p"}, Usage: "return events and exit, do not listen for new events"},
&cli.BoolFlag{Name: "scheduled", Aliases: []string{"sched", "S"}, Usage: "also return scheduled/delayed events"},
}, },
Description: `(THIS COMMAND IS INCUBATING. IT MAY CHANGE WITHOUT NOTICE.) Description: `(THIS COMMAND IS INCUBATING. IT MAY CHANGE WITHOUT NOTICE.)
@ -45,7 +47,8 @@ are passed to the command as environment variables:
Examples: Examples:
ntfy subscribe mytopic # Prints JSON for incoming messages to stdout ntfy subscribe mytopic # Prints JSON for incoming messages to stdout
ntfy sub home.lan/backups alerts # Subscribe to two different topics ntfy sub home.lan/backups alerts # Subscribe to two different topics
ntfy sub --exec='notify-send "$m"' mytopic # Execute command for incoming messages' ntfy sub --exec='notify-send "$m"' mytopic # Execute command for incoming messages
ntfy sub --exec=/my/script topic1 topic2 # Subscribe to two topics and execute command for each message
`, `,
} }
@ -56,11 +59,37 @@ func execSubscribe(c *cli.Context) error {
log.Printf("\x1b[1;33mThis command is incubating. The interface may change without notice.\x1b[0m") log.Printf("\x1b[1;33mThis command is incubating. The interface may change without notice.\x1b[0m")
cl := client.DefaultClient cl := client.DefaultClient
command := c.String("exec") command := c.String("exec")
for _, topic := range c.Args().Slice() { since := c.String("since")
cl.Subscribe(expandTopicURL(topic)) poll := c.Bool("poll")
scheduled := c.Bool("scheduled")
topics := c.Args().Slice()
var options []client.SubscribeOption
if since != "" {
options = append(options, client.WithSince(since))
} }
for m := range cl.Messages { if poll {
_ = dispatchMessage(c, command, m) options = append(options, client.WithPoll())
}
if scheduled {
options = append(options, client.WithScheduled())
}
if poll {
for _, topic := range topics {
messages, err := cl.Poll(expandTopicURL(topic), options...)
if err != nil {
return err
}
for _, m := range messages {
_ = dispatchMessage(c, command, m)
}
}
} else {
for _, topic := range topics {
cl.Subscribe(expandTopicURL(topic), options...)
}
for m := range cl.Messages {
_ = dispatchMessage(c, command, m)
}
} }
return nil return nil
} }
@ -77,11 +106,9 @@ func execCommand(c *cli.Context, command string, m *client.Message) error {
if m.Event == client.OpenEvent { if m.Event == client.OpenEvent {
log.Printf("[%s] Connection opened, subscribed to topic", collapseTopicURL(m.TopicURL)) log.Printf("[%s] Connection opened, subscribed to topic", collapseTopicURL(m.TopicURL))
} else if m.Event == client.MessageEvent { } else if m.Event == client.MessageEvent {
go func() { if err := runCommandInternal(c, command, m); err != nil {
if err := runCommandInternal(c, command, m); err != nil { log.Printf("[%s] Command failed: %s", collapseTopicURL(m.TopicURL), err.Error())
log.Printf("[%s] Command failed: %s", collapseTopicURL(m.TopicURL), err.Error()) }
}
}()
} }
return nil return nil
} }

25
docs/deprecations.md Normal file
View file

@ -0,0 +1,25 @@
# Deprecation notices
This page is used to list deprecation notices for ntfy. Deprecated commands and options will be
**removed after ~3 months** from the time they were deprecated.
## Active deprecations
### Running server via `ntfy` (instead of `ntfy serve`)
> since 2021-12-17
As more commands are added to the `ntfy` CLI tool, using just `ntfy` to run the server is not practical
anymore. Please use `ntfy serve` instead. This also applies to Docker images, as they can also execute more than
just the server.
=== "Before"
```
$ ntfy
2021/12/17 08:16:01 Listening on :80/http
```
=== "After"
```
$ ntfy serve
2021/12/17 08:16:01 Listening on :80/http
```

View file

@ -12,7 +12,7 @@ We support amd64, armv7 and arm64.
1. Install ntfy using one of the methods described below 1. Install ntfy using one of the methods described below
2. Then (optionally) edit `/etc/ntfy/config.yml` (see [configuration](config.md)) 2. Then (optionally) edit `/etc/ntfy/config.yml` (see [configuration](config.md))
3. Then just run it with `ntfy` (or `systemctl start ntfy` when using the deb/rpm). 3. Then just run it with `ntfy serve` (or `systemctl start ntfy` when using the deb/rpm).
## Binaries and packages ## Binaries and packages
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
@ -22,21 +22,21 @@ deb/rpm packages.
```bash ```bash
wget https://github.com/binwiederhier/ntfy/releases/download/v1.7.0/ntfy_1.7.0_linux_x86_64.tar.gz wget https://github.com/binwiederhier/ntfy/releases/download/v1.7.0/ntfy_1.7.0_linux_x86_64.tar.gz
sudo tar -C /usr/bin -zxf ntfy_*.tar.gz ntfy sudo tar -C /usr/bin -zxf ntfy_*.tar.gz ntfy
sudo ./ntfy sudo ./ntfy serve
``` ```
=== "armv7/armhf" === "armv7/armhf"
```bash ```bash
wget https://github.com/binwiederhier/ntfy/releases/download/v1.7.0/ntfy_1.7.0_linux_armv7.tar.gz wget https://github.com/binwiederhier/ntfy/releases/download/v1.7.0/ntfy_1.7.0_linux_armv7.tar.gz
sudo tar -C /usr/bin -zxf ntfy_*.tar.gz ntfy sudo tar -C /usr/bin -zxf ntfy_*.tar.gz ntfy
sudo ./ntfy sudo ./ntfy serve
``` ```
=== "arm64" === "arm64"
```bash ```bash
wget https://github.com/binwiederhier/ntfy/releases/download/v1.7.0/ntfy_1.7.0_linux_arm64.tar.gz wget https://github.com/binwiederhier/ntfy/releases/download/v1.7.0/ntfy_1.7.0_linux_arm64.tar.gz
sudo tar -C /usr/bin -zxf ntfy_*.tar.gz ntfy sudo tar -C /usr/bin -zxf ntfy_*.tar.gz ntfy
sudo ./ntfy sudo ./ntfy serve
``` ```
## Debian/Ubuntu repository ## Debian/Ubuntu repository
@ -132,12 +132,12 @@ The [ntfy image](https://hub.docker.com/r/binwiederhier/ntfy) is available for a
straight forward to use. 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, you should map `/etc/ntfy`, [message cache](config.md#message-cache), you also need to map a volume to `/var/cache/ntfy`. To change other settings,
so you can edit `/etc/ntfy/config.yml`. you should map `/etc/ntfy`, so you can edit `/etc/ntfy/config.yml`.
Basic usage (no cache or additional config): Basic usage (no cache or additional config):
``` ```
docker run -p 80:80 -it binwiederhier/ntfy 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):
@ -147,7 +147,8 @@ docker run \
-p 80:80 \ -p 80:80 \
-it \ -it \
binwiederhier/ntfy \ binwiederhier/ntfy \
--cache-file /var/cache/ntfy/cache.db --cache-file /var/cache/ntfy/cache.db \
serve
``` ```
With other config options (configured via `/etc/ntfy/config.yml`, see [configuration](config.md) for details): With other config options (configured via `/etc/ntfy/config.yml`, see [configuration](config.md) for details):
@ -156,7 +157,8 @@ docker run \
-v /etc/ntfy:/etc/ntfy \ -v /etc/ntfy:/etc/ntfy \
-p 80:80 \ -p 80:80 \
-it \ -it \
binwiederhier/ntfy binwiederhier/ntfy \
serve
``` ```
## Go ## Go

3
docs/subscribe/cli.md Normal file
View file

@ -0,0 +1,3 @@
# Subscribe via CLI
XXXXXXXXXxxx

View file

@ -1,11 +1,11 @@
site_dir: server/docs site_dir: server/docs
site_name: ntfy site_name: ntfy
site_url: https://ntfy.sh site_url: https://ntfy.sh
site_description: simple HTTP-based pub-sub site_description: Send push notifications to your phone via PUT/POST
copyright: Made with ❤️ by Philipp C. Heckel copyright: Made with ❤️ by Philipp C. Heckel
repo_name: binwiederhier/ntfy repo_name: binwiederhier/ntfy
repo_url: https://github.com/binwiederhier/ntfy repo_url: https://github.com/binwiederhier/ntfy
edit_uri: edit/main/docs/ edit_uri: blob/main/docs/
theme: theme:
name: material name: material
@ -31,7 +31,6 @@ theme:
- search.highlight - search.highlight
- search.share - search.share
- navigation.sections - navigation.sections
# - navigation.instant
- toc.integrate - toc.integrate
- content.tabs.link - content.tabs.link
extra: extra:
@ -75,6 +74,7 @@ nav:
- "Subscribing": - "Subscribing":
- "From your phone": subscribe/phone.md - "From your phone": subscribe/phone.md
- "From the Web UI": subscribe/web.md - "From the Web UI": subscribe/web.md
- "Using 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
@ -83,6 +83,7 @@ nav:
- "FAQs": faq.md - "FAQs": faq.md
- "Examples": examples.md - "Examples": examples.md
- "Emojis 🥳 🎉": emojis.md - "Emojis 🥳 🎉": emojis.md
- "Deprecation notices": deprecations.md
- "Development": develop.md - "Development": develop.md
- "Privacy policy": privacy.md - "Privacy policy": privacy.md