mirror of
				https://github.com/binwiederhier/ntfy.git
				synced 2025-10-31 13:02:24 +01:00 
			
		
		
		
	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" | ||||
| ) | ||||
| 
 | ||||
| const ( | ||||
| 	DefaultBaseURL = "https://ntfy.sh" | ||||
| ) | ||||
| 
 | ||||
| const ( | ||||
| 	MessageEvent   = "message" | ||||
| 	KeepaliveEvent = "keepalive" | ||||
|  | @ -23,8 +19,8 @@ const ( | |||
| ) | ||||
| 
 | ||||
| type Client struct { | ||||
| 	BaseURL       string | ||||
| 	Messages      chan *Message | ||||
| 	config        *Config | ||||
| 	subscriptions map[string]*subscription | ||||
| 	mu            sync.Mutex | ||||
| } | ||||
|  | @ -34,7 +30,6 @@ type Message struct { | |||
| 	Event    string | ||||
| 	Time     int64 | ||||
| 	Topic    string | ||||
| 	BaseURL  string | ||||
| 	TopicURL string | ||||
| 	Message  string | ||||
| 	Title    string | ||||
|  | @ -47,11 +42,10 @@ type subscription struct { | |||
| 	cancel context.CancelFunc | ||||
| } | ||||
| 
 | ||||
| var DefaultClient = New() | ||||
| 
 | ||||
| func New() *Client { | ||||
| func New(config *Config) *Client { | ||||
| 	return &Client{ | ||||
| 		Messages:      make(chan *Message), | ||||
| 		config:        config, | ||||
| 		subscriptions: make(map[string]*subscription), | ||||
| 	} | ||||
| } | ||||
|  | @ -73,11 +67,12 @@ func (c *Client) Publish(topicURL, message string, options ...PublishOption) 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() | ||||
| 	messages := make([]*Message, 0) | ||||
| 	msgChan := make(chan *Message) | ||||
| 	errChan := make(chan error) | ||||
| 	topicURL := c.expandTopicURL(topic) | ||||
| 	go func() { | ||||
| 		err := performSubscribeRequest(ctx, msgChan, topicURL, options...) | ||||
| 		close(msgChan) | ||||
|  | @ -89,20 +84,23 @@ func (c *Client) Poll(topicURL string, options ...SubscribeOption) ([]*Message, | |||
| 	return messages, <-errChan | ||||
| } | ||||
| 
 | ||||
| func (c *Client) Subscribe(topicURL string, options ...SubscribeOption) { | ||||
| func (c *Client) Subscribe(topic string, options ...SubscribeOption) string { | ||||
| 	c.mu.Lock() | ||||
| 	defer c.mu.Unlock() | ||||
| 	topicURL := c.expandTopicURL(topic) | ||||
| 	if _, ok := c.subscriptions[topicURL]; ok { | ||||
| 		return | ||||
| 		return topicURL | ||||
| 	} | ||||
| 	ctx, cancel := context.WithCancel(context.Background()) | ||||
| 	c.subscriptions[topicURL] = &subscription{cancel} | ||||
| 	go handleSubscribeConnLoop(ctx, c.Messages, topicURL, options...) | ||||
| 	return topicURL | ||||
| } | ||||
| 
 | ||||
| func (c *Client) Unsubscribe(topicURL string) { | ||||
| func (c *Client) Unsubscribe(topic string) { | ||||
| 	c.mu.Lock() | ||||
| 	defer c.mu.Unlock() | ||||
| 	topicURL := c.expandTopicURL(topic) | ||||
| 	sub, ok := c.subscriptions[topicURL] | ||||
| 	if !ok { | ||||
| 		return | ||||
|  | @ -111,6 +109,15 @@ func (c *Client) Unsubscribe(topicURL string) { | |||
| 	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) { | ||||
| 	for { | ||||
| 		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 { | ||||
| 			return err | ||||
| 		} | ||||
| 		m.BaseURL = strings.TrimSuffix(topicURL, "/"+m.Topic) // FIXME hack! | ||||
| 		m.TopicURL = topicURL | ||||
| 		m.Raw = line | ||||
| 		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" | ||||
| 	"github.com/urfave/cli/v2" | ||||
| 	"github.com/urfave/cli/v2/altsrc" | ||||
| 	"heckel.io/ntfy/client" | ||||
| 	"heckel.io/ntfy/util" | ||||
| 	"log" | ||||
| 	"os" | ||||
| 	"strings" | ||||
| ) | ||||
| 
 | ||||
| var ( | ||||
| 	defaultClientRootConfigFile = "/etc/ntfy/client.yml" | ||||
| 	defaultClientUserConfigFile = "~/.config/ntfy/client.yml" | ||||
| ) | ||||
| 
 | ||||
| // New creates a new CLI application | ||||
| func New() *cli.App { | ||||
| 	return &cli.App{ | ||||
|  | @ -35,8 +38,8 @@ 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 March 2022. See https://ntfy.sh/docs/deprecations/ for details.\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") | ||||
| 	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) | ||||
| } | ||||
| 
 | ||||
|  | @ -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 { | ||||
| 	return strings.TrimPrefix(strings.TrimPrefix(s, "https://"), "http://") | ||||
| } | ||||
|  |  | |||
|  | @ -46,7 +46,7 @@ func execPublish(c *cli.Context) error { | |||
| 	delay := c.String("delay") | ||||
| 	noCache := c.Bool("no-cache") | ||||
| 	noFirebase := c.Bool("no-firebase") | ||||
| 	topicURL := expandTopicURL(c.Args().Get(0)) | ||||
| 	topic := c.Args().Get(0) | ||||
| 	message := "" | ||||
| 	if c.NArg() > 1 { | ||||
| 		message = strings.Join(c.Args().Slice()[1:], " ") | ||||
|  | @ -70,5 +70,10 @@ func execPublish(c *cli.Context) error { | |||
| 	if noFirebase { | ||||
| 		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...) | ||||
| } | ||||
|  |  | |||
							
								
								
									
										136
									
								
								cmd/subscribe.go
									
										
									
									
									
								
							
							
						
						
									
										136
									
								
								cmd/subscribe.go
									
										
									
									
									
								
							|  | @ -4,11 +4,13 @@ import ( | |||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"github.com/urfave/cli/v2" | ||||
| 	"gopkg.in/yaml.v2" | ||||
| 	"heckel.io/ntfy/client" | ||||
| 	"heckel.io/ntfy/util" | ||||
| 	"log" | ||||
| 	"os" | ||||
| 	"os/exec" | ||||
| 	"os/user" | ||||
| 	"strings" | ||||
| ) | ||||
| 
 | ||||
|  | @ -16,53 +18,102 @@ var cmdSubscribe = &cli.Command{ | |||
| 	Name:      "subscribe", | ||||
| 	Aliases:   []string{"sub"}, | ||||
| 	Usage:     "Subscribe to one or more topics on a ntfy server", | ||||
| 	UsageText: "ntfy subscribe [OPTIONS..] TOPIC", | ||||
| 	UsageText: "ntfy subscribe [OPTIONS..] [TOPIC]", | ||||
| 	Action:    execSubscribe, | ||||
| 	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: "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: "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  | ||||
| or execute commands for every arriving message.  | ||||
| ntfy subscribe TOPIC | ||||
|   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.  | ||||
| When --exec is passed, each incoming message will execute a command. The message fields  | ||||
| are passed to the command as environment variables: | ||||
|   Examples: | ||||
|     ntfy subscribe mytopic            # Prints JSON for incoming messages for ntfy.sh/mytopic | ||||
|     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 | ||||
|     --------------- --------------- ----------------------------------- | ||||
|     $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) | ||||
|     $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: | ||||
|   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=/my/script topic1 topic2     # Subscribe to two topics and execute command for each message | ||||
|   Examples: | ||||
|     ntfy sub mytopic 'notify-send "$m"'    # Execute command for incoming messages | ||||
|     ntfy sub topic1 /my/script.sh          # Execute script for incoming messages | ||||
| 
 | ||||
| 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 { | ||||
| 	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 { | ||||
| 		return errors.New("topic missing") | ||||
| 	} | ||||
| 	fmt.Fprintln(c.App.ErrWriter, "\x1b[1;33mThis command is incubating. The interface may change without notice.\x1b[0m") | ||||
| 	cl := client.DefaultClient | ||||
| 	command := c.String("exec") | ||||
| 	conf, err := loadConfig(c) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	cl := client.New(conf) | ||||
| 	since := c.String("since") | ||||
| 	poll := c.Bool("poll") | ||||
| 	scheduled := c.Bool("scheduled") | ||||
| 	topics := c.Args().Slice() | ||||
| 	topic := c.Args().Get(0) | ||||
| 	command := c.Args().Get(1) | ||||
| 	var options []client.SubscribeOption | ||||
| 	if since != "" { | ||||
| 		options = append(options, client.WithSince(since)) | ||||
|  | @ -74,19 +125,15 @@ func execSubscribe(c *cli.Context) error { | |||
| 		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) | ||||
| 			} | ||||
| 		messages, err := cl.Poll(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...) | ||||
| 		} | ||||
| 		cl.Subscribe(topic, options...) | ||||
| 		for m := range cl.Messages { | ||||
| 			_ = dispatchMessage(c, command, m) | ||||
| 		} | ||||
|  | @ -140,7 +187,6 @@ func createTmpScript(command string) (string, error) { | |||
| func envVars(m *client.Message) []string { | ||||
| 	env := os.Environ() | ||||
| 	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(fmt.Sprintf("%d", m.Time), "NTFY_TIME", "time")...) | ||||
| 	env = append(env, envVar(m.Message, "NTFY_MESSAGE", "message", "m")...) | ||||
|  | @ -157,3 +203,31 @@ func envVar(value string, vars ...string) []string { | |||
| 	} | ||||
| 	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 | ||||
| // - 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 visistor subscription limit: max number of subscriptions (active HTTP connections) per per-visitor/IP | ||||
| // - per visitor request limit: max number of PUT/GET/.. requests (here: 60 requests bucket, replenished at a rate of one per 10 seconds) | ||||
| // - per visitor subscription limit: max number of subscriptions (active HTTP connections) per per-visitor/IP | ||||
| const ( | ||||
| 	DefaultGlobalTopicLimit             = 5000 | ||||
| 	DefaultVisitorRequestLimitBurst     = 60 | ||||
|  |  | |||
|  | @ -1,23 +1,15 @@ | |||
| # 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> | ||||
| # | ||||
| # 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: | ||||
| 
 | ||||
| # Path to the private key file for the HTTPS web server. Not used if "listen-https" is not set. | ||||
| # Format: <filename> | ||||
| # Path to the private key & cert file for the HTTPS web server. Not used if "listen-https" is not set. | ||||
| # | ||||
| # key-file: | ||||
| 
 | ||||
| # Path to the cert file for the HTTPS web server. Not used if "listen-https" is not set. | ||||
| # Format: <filename> | ||||
| # | ||||
| # cert-file: | ||||
| 
 | ||||
| # 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/time v0.0.0-20211116232009-f0f3c7e86c11 | ||||
| 	google.golang.org/api v0.63.0 | ||||
| 	gopkg.in/yaml.v2 v2.4.0 // indirect | ||||
| 	gopkg.in/yaml.v2 v2.4.0 | ||||
| ) | ||||
| 
 | ||||
| require ( | ||||
|  |  | |||
|  | @ -98,3 +98,8 @@ func ParsePriority(priority string) (int, error) { | |||
| 		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 ( | ||||
| 	"github.com/stretchr/testify/require" | ||||
| 	"io/ioutil" | ||||
| 	"os" | ||||
| 	"path/filepath" | ||||
| 	"testing" | ||||
| 	"time" | ||||
|  | @ -54,3 +55,11 @@ func TestInStringList(t *testing.T) { | |||
| 	require.True(t, InStringList(s, "two")) | ||||
| 	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
	
	 Philipp Heckel
						Philipp Heckel