WIP CLI
This commit is contained in:
		
							parent
							
								
									5639cf7a0f
								
							
						
					
					
						commit
						f266afa1de
					
				
					 12 changed files with 209 additions and 74 deletions
				
			
		|  | @ -12,10 +12,6 @@ import ( | ||||||
| 	"time" | 	"time" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| const ( |  | ||||||
| 	DefaultBaseURL = "https://ntfy.sh" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| const ( | const ( | ||||||
| 	MessageEvent   = "message" | 	MessageEvent   = "message" | ||||||
| 	KeepaliveEvent = "keepalive" | 	KeepaliveEvent = "keepalive" | ||||||
|  | @ -23,8 +19,8 @@ const ( | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| type Client struct { | type Client struct { | ||||||
| 	BaseURL       string |  | ||||||
| 	Messages      chan *Message | 	Messages      chan *Message | ||||||
|  | 	config        *Config | ||||||
| 	subscriptions map[string]*subscription | 	subscriptions map[string]*subscription | ||||||
| 	mu            sync.Mutex | 	mu            sync.Mutex | ||||||
| } | } | ||||||
|  | @ -34,7 +30,6 @@ type Message struct { | ||||||
| 	Event    string | 	Event    string | ||||||
| 	Time     int64 | 	Time     int64 | ||||||
| 	Topic    string | 	Topic    string | ||||||
| 	BaseURL  string |  | ||||||
| 	TopicURL string | 	TopicURL string | ||||||
| 	Message  string | 	Message  string | ||||||
| 	Title    string | 	Title    string | ||||||
|  | @ -47,11 +42,10 @@ type subscription struct { | ||||||
| 	cancel context.CancelFunc | 	cancel context.CancelFunc | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| var DefaultClient = New() | func New(config *Config) *Client { | ||||||
| 
 |  | ||||||
| func New() *Client { |  | ||||||
| 	return &Client{ | 	return &Client{ | ||||||
| 		Messages:      make(chan *Message), | 		Messages:      make(chan *Message), | ||||||
|  | 		config:        config, | ||||||
| 		subscriptions: make(map[string]*subscription), | 		subscriptions: make(map[string]*subscription), | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  | @ -73,11 +67,12 @@ func (c *Client) Publish(topicURL, message string, options ...PublishOption) err | ||||||
| 	return err | 	return err | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (c *Client) Poll(topicURL string, options ...SubscribeOption) ([]*Message, error) { | func (c *Client) Poll(topic string, options ...SubscribeOption) ([]*Message, error) { | ||||||
| 	ctx := context.Background() | 	ctx := context.Background() | ||||||
| 	messages := make([]*Message, 0) | 	messages := make([]*Message, 0) | ||||||
| 	msgChan := make(chan *Message) | 	msgChan := make(chan *Message) | ||||||
| 	errChan := make(chan error) | 	errChan := make(chan error) | ||||||
|  | 	topicURL := c.expandTopicURL(topic) | ||||||
| 	go func() { | 	go func() { | ||||||
| 		err := performSubscribeRequest(ctx, msgChan, topicURL, options...) | 		err := performSubscribeRequest(ctx, msgChan, topicURL, options...) | ||||||
| 		close(msgChan) | 		close(msgChan) | ||||||
|  | @ -89,20 +84,23 @@ func (c *Client) Poll(topicURL string, options ...SubscribeOption) ([]*Message, | ||||||
| 	return messages, <-errChan | 	return messages, <-errChan | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (c *Client) Subscribe(topicURL string, options ...SubscribeOption) { | func (c *Client) Subscribe(topic string, options ...SubscribeOption) string { | ||||||
| 	c.mu.Lock() | 	c.mu.Lock() | ||||||
| 	defer c.mu.Unlock() | 	defer c.mu.Unlock() | ||||||
|  | 	topicURL := c.expandTopicURL(topic) | ||||||
| 	if _, ok := c.subscriptions[topicURL]; ok { | 	if _, ok := c.subscriptions[topicURL]; ok { | ||||||
| 		return | 		return topicURL | ||||||
| 	} | 	} | ||||||
| 	ctx, cancel := context.WithCancel(context.Background()) | 	ctx, cancel := context.WithCancel(context.Background()) | ||||||
| 	c.subscriptions[topicURL] = &subscription{cancel} | 	c.subscriptions[topicURL] = &subscription{cancel} | ||||||
| 	go handleSubscribeConnLoop(ctx, c.Messages, topicURL, options...) | 	go handleSubscribeConnLoop(ctx, c.Messages, topicURL, options...) | ||||||
|  | 	return topicURL | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (c *Client) Unsubscribe(topicURL string) { | func (c *Client) Unsubscribe(topic string) { | ||||||
| 	c.mu.Lock() | 	c.mu.Lock() | ||||||
| 	defer c.mu.Unlock() | 	defer c.mu.Unlock() | ||||||
|  | 	topicURL := c.expandTopicURL(topic) | ||||||
| 	sub, ok := c.subscriptions[topicURL] | 	sub, ok := c.subscriptions[topicURL] | ||||||
| 	if !ok { | 	if !ok { | ||||||
| 		return | 		return | ||||||
|  | @ -111,6 +109,15 @@ func (c *Client) Unsubscribe(topicURL string) { | ||||||
| 	return | 	return | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | func (c *Client) expandTopicURL(topic string) string { | ||||||
|  | 	if strings.HasPrefix(topic, "http://") || strings.HasPrefix(topic, "https://") { | ||||||
|  | 		return topic | ||||||
|  | 	} else if strings.Contains(topic, "/") { | ||||||
|  | 		return fmt.Sprintf("https://%s", topic) | ||||||
|  | 	} | ||||||
|  | 	return fmt.Sprintf("%s/%s", c.config.DefaultHost, topic) | ||||||
|  | } | ||||||
|  | 
 | ||||||
| func handleSubscribeConnLoop(ctx context.Context, msgChan chan *Message, topicURL string, options ...SubscribeOption) { | func handleSubscribeConnLoop(ctx context.Context, msgChan chan *Message, topicURL string, options ...SubscribeOption) { | ||||||
| 	for { | 	for { | ||||||
| 		if err := performSubscribeRequest(ctx, msgChan, topicURL, options...); err != nil { | 		if err := performSubscribeRequest(ctx, msgChan, topicURL, options...); err != nil { | ||||||
|  | @ -147,7 +154,6 @@ func performSubscribeRequest(ctx context.Context, msgChan chan *Message, topicUR | ||||||
| 		if err := json.NewDecoder(strings.NewReader(line)).Decode(&m); err != nil { | 		if err := json.NewDecoder(strings.NewReader(line)).Decode(&m); err != nil { | ||||||
| 			return err | 			return err | ||||||
| 		} | 		} | ||||||
| 		m.BaseURL = strings.TrimSuffix(topicURL, "/"+m.Topic) // FIXME hack! |  | ||||||
| 		m.TopicURL = topicURL | 		m.TopicURL = topicURL | ||||||
| 		m.Raw = line | 		m.Raw = line | ||||||
| 		msgChan <- m | 		msgChan <- m | ||||||
|  |  | ||||||
							
								
								
									
										18
									
								
								client/client.yml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								client/client.yml
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,18 @@ | ||||||
|  | # ntfy client config file | ||||||
|  | 
 | ||||||
|  | # Base URL used to expand short topic names in the "ntfy publish" and "ntfy subscribe" commands. | ||||||
|  | # If you self-host a ntfy server, you'll likely want to change this. | ||||||
|  | # | ||||||
|  | # default-host: https://ntfy.sh | ||||||
|  | 
 | ||||||
|  | # Subscriptions to topics and their actions. This option is only used by the "ntfy subscribe --from-config" | ||||||
|  | # command. | ||||||
|  | # | ||||||
|  | # Here's a (hopefully self-explanatory) example: | ||||||
|  | #   subscribe: | ||||||
|  | #     - topic: mytopic | ||||||
|  | #       exec: /usr/local/bin/mytopic-triggered.sh | ||||||
|  | #     - topic: myserver.com/anothertopic | ||||||
|  | #       exec: 'echo "$message"' | ||||||
|  | # | ||||||
|  | # subscribe: | ||||||
							
								
								
									
										20
									
								
								client/config.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								client/config.go
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,20 @@ | ||||||
|  | package client | ||||||
|  | 
 | ||||||
|  | const ( | ||||||
|  | 	DefaultBaseURL = "https://ntfy.sh" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | type Config struct { | ||||||
|  | 	DefaultHost string | ||||||
|  | 	Subscribe   []struct { | ||||||
|  | 		Topic string | ||||||
|  | 		Exec  string | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func NewConfig() *Config { | ||||||
|  | 	return &Config{ | ||||||
|  | 		DefaultHost: DefaultBaseURL, | ||||||
|  | 		Subscribe:   nil, | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										20
									
								
								cmd/app.go
									
										
									
									
									
								
							
							
						
						
									
										20
									
								
								cmd/app.go
									
										
									
									
									
								
							|  | @ -5,13 +5,16 @@ import ( | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"github.com/urfave/cli/v2" | 	"github.com/urfave/cli/v2" | ||||||
| 	"github.com/urfave/cli/v2/altsrc" | 	"github.com/urfave/cli/v2/altsrc" | ||||||
| 	"heckel.io/ntfy/client" |  | ||||||
| 	"heckel.io/ntfy/util" | 	"heckel.io/ntfy/util" | ||||||
| 	"log" |  | ||||||
| 	"os" | 	"os" | ||||||
| 	"strings" | 	"strings" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | var ( | ||||||
|  | 	defaultClientRootConfigFile = "/etc/ntfy/client.yml" | ||||||
|  | 	defaultClientUserConfigFile = "~/.config/ntfy/client.yml" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
| // New creates a new CLI application | // New creates a new CLI application | ||||||
| func New() *cli.App { | func New() *cli.App { | ||||||
| 	return &cli.App{ | 	return &cli.App{ | ||||||
|  | @ -35,8 +38,8 @@ 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") | 	fmt.Fprintln(c.App.ErrWriter, "\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 March 2022. See https://ntfy.sh/docs/deprecations/ for details.\x1b[0m") | 	fmt.Fprintln(c.App.ErrWriter, "\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) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -58,15 +61,6 @@ func initConfigFileInputSource(configFlag string, flags []cli.Flag) cli.BeforeFu | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func expandTopicURL(s string) string { |  | ||||||
| 	if strings.HasPrefix(s, "http://") || strings.HasPrefix(s, "https://") { |  | ||||||
| 		return s |  | ||||||
| 	} else if strings.Contains(s, "/") { |  | ||||||
| 		return fmt.Sprintf("https://%s", s) |  | ||||||
| 	} |  | ||||||
| 	return fmt.Sprintf("%s/%s", client.DefaultBaseURL, s) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func collapseTopicURL(s string) string { | func collapseTopicURL(s string) string { | ||||||
| 	return strings.TrimPrefix(strings.TrimPrefix(s, "https://"), "http://") | 	return strings.TrimPrefix(strings.TrimPrefix(s, "https://"), "http://") | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -46,7 +46,7 @@ func execPublish(c *cli.Context) error { | ||||||
| 	delay := c.String("delay") | 	delay := c.String("delay") | ||||||
| 	noCache := c.Bool("no-cache") | 	noCache := c.Bool("no-cache") | ||||||
| 	noFirebase := c.Bool("no-firebase") | 	noFirebase := c.Bool("no-firebase") | ||||||
| 	topicURL := expandTopicURL(c.Args().Get(0)) | 	topic := c.Args().Get(0) | ||||||
| 	message := "" | 	message := "" | ||||||
| 	if c.NArg() > 1 { | 	if c.NArg() > 1 { | ||||||
| 		message = strings.Join(c.Args().Slice()[1:], " ") | 		message = strings.Join(c.Args().Slice()[1:], " ") | ||||||
|  | @ -70,5 +70,10 @@ func execPublish(c *cli.Context) error { | ||||||
| 	if noFirebase { | 	if noFirebase { | ||||||
| 		options = append(options, client.WithNoFirebase()) | 		options = append(options, client.WithNoFirebase()) | ||||||
| 	} | 	} | ||||||
| 	return client.DefaultClient.Publish(topicURL, message, options...) | 	conf, err := loadConfig(c) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	cl := client.New(conf) | ||||||
|  | 	return cl.Publish(topic, message, options...) | ||||||
| } | } | ||||||
|  |  | ||||||
							
								
								
									
										124
									
								
								cmd/subscribe.go
									
										
									
									
									
								
							
							
						
						
									
										124
									
								
								cmd/subscribe.go
									
										
									
									
									
								
							|  | @ -4,11 +4,13 @@ import ( | ||||||
| 	"errors" | 	"errors" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"github.com/urfave/cli/v2" | 	"github.com/urfave/cli/v2" | ||||||
|  | 	"gopkg.in/yaml.v2" | ||||||
| 	"heckel.io/ntfy/client" | 	"heckel.io/ntfy/client" | ||||||
| 	"heckel.io/ntfy/util" | 	"heckel.io/ntfy/util" | ||||||
| 	"log" | 	"log" | ||||||
| 	"os" | 	"os" | ||||||
| 	"os/exec" | 	"os/exec" | ||||||
|  | 	"os/user" | ||||||
| 	"strings" | 	"strings" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | @ -16,53 +18,102 @@ var cmdSubscribe = &cli.Command{ | ||||||
| 	Name:      "subscribe", | 	Name:      "subscribe", | ||||||
| 	Aliases:   []string{"sub"}, | 	Aliases:   []string{"sub"}, | ||||||
| 	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, | ||||||
| 	Flags: []cli.Flag{ | 	Flags: []cli.Flag{ | ||||||
|  | 		&cli.StringFlag{Name: "config", Aliases: []string{"c"}, Usage: "config file"}, | ||||||
| 		&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: "from-config", Aliases: []string{"C"}, Usage: "read subscriptions from config file (service mode)"}, | ||||||
| 		&cli.BoolFlag{Name: "poll", Aliases: []string{"p"}, Usage: "return events and exit, do not listen for new events"}, | 		&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"}, | 		&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: `Subscribe to a topic from a ntfy server, and either print or execute a command for  | ||||||
|  | every arriving message. There are 3 modes in which the command can be run: | ||||||
| 
 | 
 | ||||||
| Subscribe to one or more topics on a ntfy server, and either print  | ntfy subscribe TOPIC | ||||||
| or execute commands for every arriving message.  |   This prints the JSON representation of every incoming message. It is useful when you | ||||||
|  |   have a command that wants to stream-read incoming JSON messages. Unless --poll is passed, | ||||||
|  |   this command stays open forever.  | ||||||
| 
 | 
 | ||||||
| By default, the subscribe command just prints the JSON representation of a message.  |   Examples: | ||||||
| When --exec is passed, each incoming message will execute a command. The message fields  |     ntfy subscribe mytopic            # Prints JSON for incoming messages for ntfy.sh/mytopic | ||||||
| are passed to the command as environment variables: |     ntfy sub home.lan/backups         # Subscribe to topic on different server | ||||||
|  |     ntfy sub --poll home.lan/backups  # Just query for latest messages and exit | ||||||
|  |    | ||||||
|  | ntfy subscribe TOPIC COMMAND | ||||||
|  |   This executes COMMAND for every incoming messages. The message fields are passed to the | ||||||
|  |   command as environment variables: | ||||||
| 
 | 
 | ||||||
|     Variable        Aliases         Description |     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_MESSAGE   $message, $m    Message body | ||||||
|     $NTFY_TITLE     $title, $t      Message title |     $NTFY_TITLE     $title, $t      Message title | ||||||
|     $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) | ||||||
|     $NTFY_ID        $id             Unique message ID |  | ||||||
|     $NTFY_TIME      $time           Unix timestamp of the message delivery |  | ||||||
|     $NTFY_TOPIC     $topic          Topic name |  | ||||||
|     $NTFY_EVENT     $event, $ev     Event identifier (always "message") |  | ||||||
| 
 | 
 | ||||||
|   Examples: |   Examples: | ||||||
|   ntfy subscribe mytopic                       # Prints JSON for incoming messages to stdout |     ntfy sub mytopic 'notify-send "$m"'    # Execute command for incoming messages | ||||||
|   ntfy sub home.lan/backups alerts             # Subscribe to two different topics |     ntfy sub topic1 /my/script.sh          # Execute script 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 | 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:"  | ||||||
|  |   block (see config file). | ||||||
|  | 
 | ||||||
|  |   Examples:  | ||||||
|  |     ntfy sub --from-config                           # Read topics from config file | ||||||
|  |     ntfy sub --config=/my/client.yml --from-config   # Read topics from alternate config file | ||||||
| `, | `, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func execSubscribe(c *cli.Context) error { | func execSubscribe(c *cli.Context) error { | ||||||
|  | 	fromConfig := c.Bool("from-config") | ||||||
|  | 	if fromConfig { | ||||||
|  | 		return execSubscribeFromConfig(c) | ||||||
|  | 	} | ||||||
|  | 	return execSubscribeWithoutConfig(c) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func execSubscribeFromConfig(c *cli.Context) error { | ||||||
|  | 	conf, err := loadConfig(c) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	cl := client.New(conf) | ||||||
|  | 	commands := make(map[string]string) | ||||||
|  | 	for _, s := range conf.Subscribe { | ||||||
|  | 		topicURL := cl.Subscribe(s.Topic) | ||||||
|  | 		commands[topicURL] = s.Exec | ||||||
|  | 	} | ||||||
|  | 	for m := range cl.Messages { | ||||||
|  | 		command, ok := commands[m.TopicURL] | ||||||
|  | 		if !ok { | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 		_ = dispatchMessage(c, command, m) | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func execSubscribeWithoutConfig(c *cli.Context) error { | ||||||
| 	if c.NArg() < 1 { | 	if c.NArg() < 1 { | ||||||
| 		return errors.New("topic missing") | 		return errors.New("topic missing") | ||||||
| 	} | 	} | ||||||
| 	fmt.Fprintln(c.App.ErrWriter, "\x1b[1;33mThis command is incubating. The interface may change without notice.\x1b[0m") | 	fmt.Fprintln(c.App.ErrWriter, "\x1b[1;33mThis command is incubating. The interface may change without notice.\x1b[0m") | ||||||
| 	cl := client.DefaultClient | 	conf, err := loadConfig(c) | ||||||
| 	command := c.String("exec") | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	cl := client.New(conf) | ||||||
| 	since := c.String("since") | 	since := c.String("since") | ||||||
| 	poll := c.Bool("poll") | 	poll := c.Bool("poll") | ||||||
| 	scheduled := c.Bool("scheduled") | 	scheduled := c.Bool("scheduled") | ||||||
| 	topics := c.Args().Slice() | 	topic := c.Args().Get(0) | ||||||
|  | 	command := c.Args().Get(1) | ||||||
| 	var options []client.SubscribeOption | 	var options []client.SubscribeOption | ||||||
| 	if since != "" { | 	if since != "" { | ||||||
| 		options = append(options, client.WithSince(since)) | 		options = append(options, client.WithSince(since)) | ||||||
|  | @ -74,19 +125,15 @@ func execSubscribe(c *cli.Context) error { | ||||||
| 		options = append(options, client.WithScheduled()) | 		options = append(options, client.WithScheduled()) | ||||||
| 	} | 	} | ||||||
| 	if poll { | 	if poll { | ||||||
| 		for _, topic := range topics { | 		messages, err := cl.Poll(topic, options...) | ||||||
| 			messages, err := cl.Poll(expandTopicURL(topic), options...) |  | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return err | 			return err | ||||||
| 		} | 		} | ||||||
| 		for _, m := range messages { | 		for _, m := range messages { | ||||||
| 			_ = dispatchMessage(c, command, m) | 			_ = dispatchMessage(c, command, m) | ||||||
| 		} | 		} | ||||||
| 		} |  | ||||||
| 	} else { | 	} else { | ||||||
| 		for _, topic := range topics { | 		cl.Subscribe(topic, options...) | ||||||
| 			cl.Subscribe(expandTopicURL(topic), options...) |  | ||||||
| 		} |  | ||||||
| 		for m := range cl.Messages { | 		for m := range cl.Messages { | ||||||
| 			_ = dispatchMessage(c, command, m) | 			_ = dispatchMessage(c, command, m) | ||||||
| 		} | 		} | ||||||
|  | @ -140,7 +187,6 @@ func createTmpScript(command string) (string, error) { | ||||||
| func envVars(m *client.Message) []string { | func envVars(m *client.Message) []string { | ||||||
| 	env := os.Environ() | 	env := os.Environ() | ||||||
| 	env = append(env, envVar(m.ID, "NTFY_ID", "id")...) | 	env = append(env, envVar(m.ID, "NTFY_ID", "id")...) | ||||||
| 	env = append(env, envVar(m.Event, "NTFY_EVENT", "event", "ev")...) |  | ||||||
| 	env = append(env, envVar(m.Topic, "NTFY_TOPIC", "topic")...) | 	env = append(env, envVar(m.Topic, "NTFY_TOPIC", "topic")...) | ||||||
| 	env = append(env, envVar(fmt.Sprintf("%d", m.Time), "NTFY_TIME", "time")...) | 	env = append(env, envVar(fmt.Sprintf("%d", m.Time), "NTFY_TIME", "time")...) | ||||||
| 	env = append(env, envVar(m.Message, "NTFY_MESSAGE", "message", "m")...) | 	env = append(env, envVar(m.Message, "NTFY_MESSAGE", "message", "m")...) | ||||||
|  | @ -157,3 +203,31 @@ func envVar(value string, vars ...string) []string { | ||||||
| 	} | 	} | ||||||
| 	return env | 	return env | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | func loadConfig(c *cli.Context) (*client.Config, error) { | ||||||
|  | 	filename := c.String("config") | ||||||
|  | 	if filename != "" { | ||||||
|  | 		return loadConfigFromFile(filename) | ||||||
|  | 	} | ||||||
|  | 	u, _ := user.Current() | ||||||
|  | 	configFile := defaultClientRootConfigFile | ||||||
|  | 	if u.Uid != "0" { | ||||||
|  | 		configFile = util.ExpandHome(defaultClientUserConfigFile) | ||||||
|  | 	} | ||||||
|  | 	if s, _ := os.Stat(configFile); s != nil { | ||||||
|  | 		return loadConfigFromFile(configFile) | ||||||
|  | 	} | ||||||
|  | 	return client.NewConfig(), nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func loadConfigFromFile(filename string) (*client.Config, error) { | ||||||
|  | 	b, err := os.ReadFile(filename) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	c := client.NewConfig() | ||||||
|  | 	if err := yaml.Unmarshal(b, c); err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	return c, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -20,8 +20,8 @@ const ( | ||||||
| 
 | 
 | ||||||
| // Defines all the limits | // Defines all the limits | ||||||
| // - global topic limit: max number of topics overall | // - global topic limit: max number of topics overall | ||||||
| // - per visistor request limit: max number of PUT/GET/.. requests (here: 60 requests bucket, replenished at a rate of one per 10 seconds) | // - per visitor request limit: max number of PUT/GET/.. requests (here: 60 requests bucket, replenished at a rate of one per 10 seconds) | ||||||
| // - per visistor subscription limit: max number of subscriptions (active HTTP connections) per per-visitor/IP | // - per visitor subscription limit: max number of subscriptions (active HTTP connections) per per-visitor/IP | ||||||
| const ( | const ( | ||||||
| 	DefaultGlobalTopicLimit             = 5000 | 	DefaultGlobalTopicLimit             = 5000 | ||||||
| 	DefaultVisitorRequestLimitBurst     = 60 | 	DefaultVisitorRequestLimitBurst     = 60 | ||||||
|  |  | ||||||
|  | @ -1,23 +1,15 @@ | ||||||
| # ntfy config file | # ntfy config file | ||||||
| 
 | 
 | ||||||
| # Listen address for the HTTP web server | # Listen address for the HTTP & HTTPS web server. If "listen-https" is set, you must also | ||||||
|  | # set "key-file" and "cert-file". | ||||||
| # Format: <hostname>:<port> | # Format: <hostname>:<port> | ||||||
| # | # | ||||||
| # listen-http: ":80" | # listen-http: ":80" | ||||||
| 
 |  | ||||||
| # Listen address for the HTTPS web server. If set, you must also set "key-file" and "cert-file". |  | ||||||
| # Format: <hostname>:<port> |  | ||||||
| # |  | ||||||
| # listen-https: | # listen-https: | ||||||
| 
 | 
 | ||||||
| # Path to the private key file for the HTTPS web server. Not used if "listen-https" is not set. | # Path to the private key & cert file for the HTTPS web server. Not used if "listen-https" is not set. | ||||||
| # Format: <filename> |  | ||||||
| # | # | ||||||
| # key-file: | # key-file: | ||||||
| 
 |  | ||||||
| # Path to the cert file for the HTTPS web server. Not used if "listen-https" is not set. |  | ||||||
| # Format: <filename> |  | ||||||
| # |  | ||||||
| # cert-file: | # cert-file: | ||||||
| 
 | 
 | ||||||
| # If set, also publish messages to a Firebase Cloud Messaging (FCM) topic for your app. | # If set, also publish messages to a Firebase Cloud Messaging (FCM) topic for your app. | ||||||
|  |  | ||||||
							
								
								
									
										12
									
								
								config/ntfy-client.service
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								config/ntfy-client.service
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,12 @@ | ||||||
|  | [Unit] | ||||||
|  | Description=ntfy client | ||||||
|  | After=network.target | ||||||
|  | 
 | ||||||
|  | [Service] | ||||||
|  | User=ntfy | ||||||
|  | Group=ntfy | ||||||
|  | ExecStart=/usr/bin/ntfy subscribe --config /etc/ntfy/client.yml --from-config | ||||||
|  | Restart=on-failure | ||||||
|  | 
 | ||||||
|  | [Install] | ||||||
|  | WantedBy=multi-user.target | ||||||
							
								
								
									
										2
									
								
								go.mod
									
										
									
									
									
								
							
							
						
						
									
										2
									
								
								go.mod
									
										
									
									
									
								
							|  | @ -15,7 +15,7 @@ require ( | ||||||
| 	golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect | 	golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect | ||||||
| 	golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11 | 	golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11 | ||||||
| 	google.golang.org/api v0.63.0 | 	google.golang.org/api v0.63.0 | ||||||
| 	gopkg.in/yaml.v2 v2.4.0 // indirect | 	gopkg.in/yaml.v2 v2.4.0 | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| require ( | require ( | ||||||
|  |  | ||||||
|  | @ -98,3 +98,8 @@ func ParsePriority(priority string) (int, error) { | ||||||
| 		return 0, errInvalidPriority | 		return 0, errInvalidPriority | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | // ExpandHome replaces "~" with the user's home directory | ||||||
|  | func ExpandHome(path string) string { | ||||||
|  | 	return os.ExpandEnv(strings.ReplaceAll(path, "~", "$HOME")) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -3,6 +3,7 @@ package util | ||||||
| import ( | import ( | ||||||
| 	"github.com/stretchr/testify/require" | 	"github.com/stretchr/testify/require" | ||||||
| 	"io/ioutil" | 	"io/ioutil" | ||||||
|  | 	"os" | ||||||
| 	"path/filepath" | 	"path/filepath" | ||||||
| 	"testing" | 	"testing" | ||||||
| 	"time" | 	"time" | ||||||
|  | @ -54,3 +55,11 @@ func TestInStringList(t *testing.T) { | ||||||
| 	require.True(t, InStringList(s, "two")) | 	require.True(t, InStringList(s, "two")) | ||||||
| 	require.False(t, InStringList(s, "three")) | 	require.False(t, InStringList(s, "three")) | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | func TestExpandHome_WithTilde(t *testing.T) { | ||||||
|  | 	require.Equal(t, os.Getenv("HOME")+"/this/is/a/path", ExpandHome("~/this/is/a/path")) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func TestExpandHome_NoTilde(t *testing.T) { | ||||||
|  | 	require.Equal(t, "/this/is/an/absolute/path", ExpandHome("/this/is/an/absolute/path")) | ||||||
|  | } | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue