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
Time int64
Topic string
BaseURL string
TopicURL string
Message string
Title string
Priority int
Tags []string
BaseURL string
TopicURL string
Raw string
}
@ -73,7 +73,23 @@ func (c *Client) Publish(topicURL, message string, options ...PublishOption) 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()
defer c.mu.Unlock()
if _, ok := c.subscriptions[topicURL]; ok {
@ -81,7 +97,7 @@ func (c *Client) Subscribe(topicURL string) {
}
ctx, cancel := context.WithCancel(context.Background())
c.subscriptions[topicURL] = &subscription{cancel}
go handleConnectionLoop(ctx, c.Messages, topicURL)
go handleSubscribeConnLoop(ctx, c.Messages, topicURL, options...)
}
func (c *Client) Unsubscribe(topicURL string) {
@ -95,25 +111,30 @@ func (c *Client) Unsubscribe(topicURL string) {
return
}
func handleConnectionLoop(ctx context.Context, msgChan chan *Message, topicURL string) {
func handleSubscribeConnLoop(ctx context.Context, msgChan chan *Message, topicURL string, options ...SubscribeOption) {
for {
if err := handleConnection(ctx, msgChan, topicURL); err != nil {
log.Printf("connection to %s failed: %s", topicURL, err.Error())
if err := performSubscribeRequest(ctx, msgChan, topicURL, options...); err != nil {
log.Printf("Connection to %s failed: %s", topicURL, err.Error())
}
select {
case <-ctx.Done():
log.Printf("connection to %s exited", topicURL)
log.Printf("Connection to %s exited", topicURL)
return
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)
if err != nil {
return err
}
for _, option := range options {
if err := option(req); err != nil {
return err
}
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
return err

View file

@ -4,42 +4,24 @@ import (
"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 {
return func(r *http.Request) error {
if title != "" {
r.Header.Set("X-Title", title)
}
return nil
}
return WithHeader("X-Title", title)
}
func WithPriority(priority string) PublishOption {
return func(r *http.Request) error {
if priority != "" {
r.Header.Set("X-Priority", priority)
}
return nil
}
return WithHeader("X-Priority", priority)
}
func WithTags(tags string) PublishOption {
return func(r *http.Request) error {
if tags != "" {
r.Header.Set("X-Tags", tags)
}
return nil
}
return WithHeader("X-Tags", tags)
}
func WithDelay(delay string) PublishOption {
return func(r *http.Request) error {
if delay != "" {
r.Header.Set("X-Delay", delay)
}
return nil
}
return WithHeader("X-Delay", delay)
}
func WithNoCache() PublishOption {
@ -50,20 +32,32 @@ func WithNoFirebase() PublishOption {
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 {
r.Header.Set(header, value)
if value != "" {
r.Header.Set(header, value)
}
return nil
}
}
type SubscribeOption func(r *http.Request) error
func WithSince(since string) PublishOption {
func WithQueryParam(param, value string) RequestOption {
return func(r *http.Request) error {
if since != "" {
if value != "" {
q := r.URL.Query()
q.Add("since", since)
q.Add(param, value)
r.URL.RawQuery = q.Encode()
}
return nil

View file

@ -36,7 +36,7 @@ func New() *cli.App {
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;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)
}

View file

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

View file

@ -21,6 +21,8 @@ var cmdSubscribe = &cli.Command{
Flags: []cli.Flag{
&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.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.)
@ -45,7 +47,8 @@ are passed to the command as environment variables:
Examples:
ntfy subscribe mytopic # Prints JSON for incoming messages to stdout
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")
cl := client.DefaultClient
command := c.String("exec")
for _, topic := range c.Args().Slice() {
cl.Subscribe(expandTopicURL(topic))
since := c.String("since")
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 {
_ = dispatchMessage(c, command, m)
if poll {
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
}
@ -77,11 +106,9 @@ func execCommand(c *cli.Context, command string, m *client.Message) error {
if m.Event == client.OpenEvent {
log.Printf("[%s] Connection opened, subscribed to topic", collapseTopicURL(m.TopicURL))
} else if m.Event == client.MessageEvent {
go func() {
if err := runCommandInternal(c, command, m); err != nil {
log.Printf("[%s] Command failed: %s", collapseTopicURL(m.TopicURL), err.Error())
}
}()
if err := runCommandInternal(c, command, m); err != nil {
log.Printf("[%s] Command failed: %s", collapseTopicURL(m.TopicURL), err.Error())
}
}
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
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
Please check out the [releases page](https://github.com/binwiederhier/ntfy/releases) for binaries and
@ -22,21 +22,21 @@ deb/rpm packages.
```bash
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 ./ntfy
sudo ./ntfy serve
```
=== "armv7/armhf"
```bash
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 ./ntfy
sudo ./ntfy serve
```
=== "arm64"
```bash
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 ./ntfy
sudo ./ntfy serve
```
## 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.
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`,
so you can edit `/etc/ntfy/config.yml`.
[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/config.yml`.
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):
@ -147,7 +147,8 @@ docker run \
-p 80:80 \
-it \
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):
@ -156,7 +157,8 @@ docker run \
-v /etc/ntfy:/etc/ntfy \
-p 80:80 \
-it \
binwiederhier/ntfy
binwiederhier/ntfy \
serve
```
## 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_name: ntfy
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
repo_name: binwiederhier/ntfy
repo_url: https://github.com/binwiederhier/ntfy
edit_uri: edit/main/docs/
edit_uri: blob/main/docs/
theme:
name: material
@ -31,7 +31,6 @@ theme:
- search.highlight
- search.share
- navigation.sections
# - navigation.instant
- toc.integrate
- content.tabs.link
extra:
@ -75,6 +74,7 @@ nav:
- "Subscribing":
- "From your phone": subscribe/phone.md
- "From the Web UI": subscribe/web.md
- "Using the CLI": subscribe/cli.md
- "Using the API": subscribe/api.md
- "Self-hosting":
- "Installation": install.md
@ -83,6 +83,7 @@ nav:
- "FAQs": faq.md
- "Examples": examples.md
- "Emojis 🥳 🎉": emojis.md
- "Deprecation notices": deprecations.md
- "Development": develop.md
- "Privacy policy": privacy.md