WIP CLI
parent
5639cf7a0f
commit
f266afa1de
|
@ -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
|
||||||
|
|
|
@ -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:
|
|
@ -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...)
|
||||||
}
|
}
|
||||||
|
|
136
cmd/subscribe.go
136
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.
|
||||||
|
|
|
@ -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…
Reference in New Issue