WIP: Logging
This commit is contained in:
parent
1f38a4a531
commit
dc0e699fb5
8 changed files with 184 additions and 71 deletions
|
@ -19,7 +19,7 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
var flagsAccess = append(
|
var flagsAccess = append(
|
||||||
userCommandFlags(),
|
flagsUser,
|
||||||
&cli.BoolFlag{Name: "reset", Aliases: []string{"r"}, Usage: "reset access for user (and topic)"},
|
&cli.BoolFlag{Name: "reset", Aliases: []string{"r"}, Usage: "reset access for user (and topic)"},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -28,7 +28,7 @@ var cmdAccess = &cli.Command{
|
||||||
Usage: "Grant/revoke access to a topic, or show access",
|
Usage: "Grant/revoke access to a topic, or show access",
|
||||||
UsageText: "ntfy access [USERNAME [TOPIC [PERMISSION]]]",
|
UsageText: "ntfy access [USERNAME [TOPIC [PERMISSION]]]",
|
||||||
Flags: flagsAccess,
|
Flags: flagsAccess,
|
||||||
Before: initConfigFileInputSourceFunc("config", flagsAccess),
|
Before: initLogFunc(initConfigFileInputSourceFunc("config", flagsAccess)),
|
||||||
Action: execUserAccess,
|
Action: execUserAccess,
|
||||||
Category: categoryServer,
|
Category: categoryServer,
|
||||||
Description: `Manage the access control list for the ntfy server.
|
Description: `Manage the access control list for the ntfy server.
|
||||||
|
|
24
cmd/app.go
24
cmd/app.go
|
@ -3,6 +3,7 @@ package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
|
"heckel.io/ntfy/log"
|
||||||
"os"
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -13,6 +14,11 @@ const (
|
||||||
|
|
||||||
var commands = make([]*cli.Command, 0)
|
var commands = make([]*cli.Command, 0)
|
||||||
|
|
||||||
|
var flagsDefault = []cli.Flag{
|
||||||
|
&cli.BoolFlag{Name: "debug", Aliases: []string{"d"}, EnvVars: []string{"NTFY_DEBUG"}, Usage: "enable debug logging"},
|
||||||
|
&cli.StringFlag{Name: "log-level", Aliases: []string{"log_level"}, Value: log.InfoLevel.String(), EnvVars: []string{"NTFY_LOG_LEVEL"}, Usage: "set log level"},
|
||||||
|
}
|
||||||
|
|
||||||
// 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{
|
||||||
|
@ -25,5 +31,23 @@ func New() *cli.App {
|
||||||
Writer: os.Stdout,
|
Writer: os.Stdout,
|
||||||
ErrWriter: os.Stderr,
|
ErrWriter: os.Stderr,
|
||||||
Commands: commands,
|
Commands: commands,
|
||||||
|
Flags: flagsDefault,
|
||||||
|
Before: initLogFunc(nil),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func initLogFunc(next cli.BeforeFunc) cli.BeforeFunc {
|
||||||
|
return func(c *cli.Context) error {
|
||||||
|
if c.Bool("debug") {
|
||||||
|
log.SetLevel(log.DebugLevel)
|
||||||
|
} else {
|
||||||
|
log.SetLevel(log.ToLevel(c.String("log-level")))
|
||||||
|
}
|
||||||
|
if next != nil {
|
||||||
|
if err := next(c); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,31 +16,35 @@ func init() {
|
||||||
commands = append(commands, cmdPublish)
|
commands = append(commands, cmdPublish)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var flagsPublish = append(
|
||||||
|
flagsDefault,
|
||||||
|
&cli.StringFlag{Name: "config", Aliases: []string{"c"}, EnvVars: []string{"NTFY_CONFIG"}, Usage: "client config file"},
|
||||||
|
&cli.StringFlag{Name: "title", Aliases: []string{"t"}, EnvVars: []string{"NTFY_TITLE"}, Usage: "message title"},
|
||||||
|
&cli.StringFlag{Name: "priority", Aliases: []string{"p"}, EnvVars: []string{"NTFY_PRIORITY"}, Usage: "priority of the message (1=min, 2=low, 3=default, 4=high, 5=max)"},
|
||||||
|
&cli.StringFlag{Name: "tags", Aliases: []string{"tag", "T"}, EnvVars: []string{"NTFY_TAGS"}, Usage: "comma separated list of tags and emojis"},
|
||||||
|
&cli.StringFlag{Name: "delay", Aliases: []string{"at", "in", "D"}, EnvVars: []string{"NTFY_DELAY"}, Usage: "delay/schedule message"},
|
||||||
|
&cli.StringFlag{Name: "click", Aliases: []string{"U"}, EnvVars: []string{"NTFY_CLICK"}, Usage: "URL to open when notification is clicked"},
|
||||||
|
&cli.StringFlag{Name: "actions", Aliases: []string{"A"}, EnvVars: []string{"NTFY_ACTIONS"}, Usage: "actions JSON array or simple definition"},
|
||||||
|
&cli.StringFlag{Name: "attach", Aliases: []string{"a"}, EnvVars: []string{"NTFY_ATTACH"}, Usage: "URL to send as an external attachment"},
|
||||||
|
&cli.StringFlag{Name: "filename", Aliases: []string{"name", "n"}, EnvVars: []string{"NTFY_FILENAME"}, Usage: "filename for the attachment"},
|
||||||
|
&cli.StringFlag{Name: "file", Aliases: []string{"f"}, EnvVars: []string{"NTFY_FILE"}, Usage: "file to upload as an attachment"},
|
||||||
|
&cli.StringFlag{Name: "email", Aliases: []string{"mail", "e"}, EnvVars: []string{"NTFY_EMAIL"}, Usage: "also send to e-mail address"},
|
||||||
|
&cli.StringFlag{Name: "user", Aliases: []string{"u"}, EnvVars: []string{"NTFY_USER"}, Usage: "username[:password] used to auth against the server"},
|
||||||
|
&cli.BoolFlag{Name: "no-cache", Aliases: []string{"C"}, EnvVars: []string{"NTFY_NO_CACHE"}, Usage: "do not cache message server-side"},
|
||||||
|
&cli.BoolFlag{Name: "no-firebase", Aliases: []string{"F"}, EnvVars: []string{"NTFY_NO_FIREBASE"}, Usage: "do not forward message to Firebase"},
|
||||||
|
&cli.BoolFlag{Name: "env-topic", Aliases: []string{"P"}, EnvVars: []string{"NTFY_ENV_TOPIC"}, Usage: "use topic from NTFY_TOPIC env variable"},
|
||||||
|
&cli.BoolFlag{Name: "quiet", Aliases: []string{"q"}, EnvVars: []string{"NTFY_QUIET"}, Usage: "do not print message"},
|
||||||
|
)
|
||||||
|
|
||||||
var cmdPublish = &cli.Command{
|
var cmdPublish = &cli.Command{
|
||||||
Name: "publish",
|
Name: "publish",
|
||||||
Aliases: []string{"pub", "send", "trigger"},
|
Aliases: []string{"pub", "send", "trigger"},
|
||||||
Usage: "Send message via a ntfy server",
|
Usage: "Send message via a ntfy server",
|
||||||
UsageText: "ntfy send [OPTIONS..] TOPIC [MESSAGE]\n NTFY_TOPIC=.. ntfy send [OPTIONS..] -P [MESSAGE]",
|
UsageText: "ntfy publish [OPTIONS..] TOPIC [MESSAGE]\nNTFY_TOPIC=.. ntfy publish [OPTIONS..] -P [MESSAGE]",
|
||||||
Action: execPublish,
|
Action: execPublish,
|
||||||
Category: categoryClient,
|
Category: categoryClient,
|
||||||
Flags: []cli.Flag{
|
Flags: flagsPublish,
|
||||||
&cli.StringFlag{Name: "config", Aliases: []string{"c"}, EnvVars: []string{"NTFY_CONFIG"}, Usage: "client config file"},
|
Before: initLogFunc(nil),
|
||||||
&cli.StringFlag{Name: "title", Aliases: []string{"t"}, EnvVars: []string{"NTFY_TITLE"}, Usage: "message title"},
|
|
||||||
&cli.StringFlag{Name: "priority", Aliases: []string{"p"}, EnvVars: []string{"NTFY_PRIORITY"}, Usage: "priority of the message (1=min, 2=low, 3=default, 4=high, 5=max)"},
|
|
||||||
&cli.StringFlag{Name: "tags", Aliases: []string{"tag", "T"}, EnvVars: []string{"NTFY_TAGS"}, Usage: "comma separated list of tags and emojis"},
|
|
||||||
&cli.StringFlag{Name: "delay", Aliases: []string{"at", "in", "D"}, EnvVars: []string{"NTFY_DELAY"}, Usage: "delay/schedule message"},
|
|
||||||
&cli.StringFlag{Name: "click", Aliases: []string{"U"}, EnvVars: []string{"NTFY_CLICK"}, Usage: "URL to open when notification is clicked"},
|
|
||||||
&cli.StringFlag{Name: "actions", Aliases: []string{"A"}, EnvVars: []string{"NTFY_ACTIONS"}, Usage: "actions JSON array or simple definition"},
|
|
||||||
&cli.StringFlag{Name: "attach", Aliases: []string{"a"}, EnvVars: []string{"NTFY_ATTACH"}, Usage: "URL to send as an external attachment"},
|
|
||||||
&cli.StringFlag{Name: "filename", Aliases: []string{"name", "n"}, EnvVars: []string{"NTFY_FILENAME"}, Usage: "filename for the attachment"},
|
|
||||||
&cli.StringFlag{Name: "file", Aliases: []string{"f"}, EnvVars: []string{"NTFY_FILE"}, Usage: "file to upload as an attachment"},
|
|
||||||
&cli.StringFlag{Name: "email", Aliases: []string{"mail", "e"}, EnvVars: []string{"NTFY_EMAIL"}, Usage: "also send to e-mail address"},
|
|
||||||
&cli.StringFlag{Name: "user", Aliases: []string{"u"}, EnvVars: []string{"NTFY_USER"}, Usage: "username[:password] used to auth against the server"},
|
|
||||||
&cli.BoolFlag{Name: "no-cache", Aliases: []string{"C"}, EnvVars: []string{"NTFY_NO_CACHE"}, Usage: "do not cache message server-side"},
|
|
||||||
&cli.BoolFlag{Name: "no-firebase", Aliases: []string{"F"}, EnvVars: []string{"NTFY_NO_FIREBASE"}, Usage: "do not forward message to Firebase"},
|
|
||||||
&cli.BoolFlag{Name: "env-topic", Aliases: []string{"P"}, EnvVars: []string{"NTFY_ENV_TOPIC"}, Usage: "use topic from NTFY_TOPIC env variable"},
|
|
||||||
&cli.BoolFlag{Name: "quiet", Aliases: []string{"q"}, EnvVars: []string{"NTFY_QUIET"}, Usage: "do print message"},
|
|
||||||
},
|
|
||||||
Description: `Publish a message to a ntfy server.
|
Description: `Publish a message to a ntfy server.
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
|
|
17
cmd/serve.go
17
cmd/serve.go
|
@ -5,7 +5,7 @@ package cmd
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"heckel.io/ntfy/log"
|
||||||
"math"
|
"math"
|
||||||
"net"
|
"net"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -21,7 +21,8 @@ func init() {
|
||||||
commands = append(commands, cmdServe)
|
commands = append(commands, cmdServe)
|
||||||
}
|
}
|
||||||
|
|
||||||
var flagsServe = []cli.Flag{
|
var flagsServe = append(
|
||||||
|
flagsDefault,
|
||||||
&cli.StringFlag{Name: "config", Aliases: []string{"c"}, EnvVars: []string{"NTFY_CONFIG_FILE"}, Value: "/etc/ntfy/server.yml", DefaultText: "/etc/ntfy/server.yml", Usage: "config file"},
|
&cli.StringFlag{Name: "config", Aliases: []string{"c"}, EnvVars: []string{"NTFY_CONFIG_FILE"}, Value: "/etc/ntfy/server.yml", DefaultText: "/etc/ntfy/server.yml", Usage: "config file"},
|
||||||
altsrc.NewStringFlag(&cli.StringFlag{Name: "base-url", Aliases: []string{"base_url", "B"}, EnvVars: []string{"NTFY_BASE_URL"}, Usage: "externally visible base URL for this host (e.g. https://ntfy.sh)"}),
|
altsrc.NewStringFlag(&cli.StringFlag{Name: "base-url", Aliases: []string{"base_url", "B"}, EnvVars: []string{"NTFY_BASE_URL"}, Usage: "externally visible base URL for this host (e.g. https://ntfy.sh)"}),
|
||||||
altsrc.NewStringFlag(&cli.StringFlag{Name: "listen-http", Aliases: []string{"listen_http", "l"}, EnvVars: []string{"NTFY_LISTEN_HTTP"}, Value: server.DefaultListenHTTP, Usage: "ip:port used to as HTTP listen address"}),
|
altsrc.NewStringFlag(&cli.StringFlag{Name: "listen-http", Aliases: []string{"listen_http", "l"}, EnvVars: []string{"NTFY_LISTEN_HTTP"}, Value: server.DefaultListenHTTP, Usage: "ip:port used to as HTTP listen address"}),
|
||||||
|
@ -59,7 +60,7 @@ var flagsServe = []cli.Flag{
|
||||||
altsrc.NewIntFlag(&cli.IntFlag{Name: "visitor-email-limit-burst", Aliases: []string{"visitor_email_limit_burst"}, EnvVars: []string{"NTFY_VISITOR_EMAIL_LIMIT_BURST"}, Value: server.DefaultVisitorEmailLimitBurst, Usage: "initial limit of e-mails per visitor"}),
|
altsrc.NewIntFlag(&cli.IntFlag{Name: "visitor-email-limit-burst", Aliases: []string{"visitor_email_limit_burst"}, EnvVars: []string{"NTFY_VISITOR_EMAIL_LIMIT_BURST"}, Value: server.DefaultVisitorEmailLimitBurst, Usage: "initial limit of e-mails per visitor"}),
|
||||||
altsrc.NewDurationFlag(&cli.DurationFlag{Name: "visitor-email-limit-replenish", Aliases: []string{"visitor_email_limit_replenish"}, EnvVars: []string{"NTFY_VISITOR_EMAIL_LIMIT_REPLENISH"}, Value: server.DefaultVisitorEmailLimitReplenish, Usage: "interval at which burst limit is replenished (one per x)"}),
|
altsrc.NewDurationFlag(&cli.DurationFlag{Name: "visitor-email-limit-replenish", Aliases: []string{"visitor_email_limit_replenish"}, EnvVars: []string{"NTFY_VISITOR_EMAIL_LIMIT_REPLENISH"}, Value: server.DefaultVisitorEmailLimitReplenish, Usage: "interval at which burst limit is replenished (one per x)"}),
|
||||||
altsrc.NewBoolFlag(&cli.BoolFlag{Name: "behind-proxy", Aliases: []string{"behind_proxy", "P"}, EnvVars: []string{"NTFY_BEHIND_PROXY"}, Value: false, Usage: "if set, use X-Forwarded-For header to determine visitor IP address (for rate limiting)"}),
|
altsrc.NewBoolFlag(&cli.BoolFlag{Name: "behind-proxy", Aliases: []string{"behind_proxy", "P"}, EnvVars: []string{"NTFY_BEHIND_PROXY"}, Value: false, Usage: "if set, use X-Forwarded-For header to determine visitor IP address (for rate limiting)"}),
|
||||||
}
|
)
|
||||||
|
|
||||||
var cmdServe = &cli.Command{
|
var cmdServe = &cli.Command{
|
||||||
Name: "serve",
|
Name: "serve",
|
||||||
|
@ -68,7 +69,7 @@ var cmdServe = &cli.Command{
|
||||||
Action: execServe,
|
Action: execServe,
|
||||||
Category: categoryServer,
|
Category: categoryServer,
|
||||||
Flags: flagsServe,
|
Flags: flagsServe,
|
||||||
Before: initConfigFileInputSourceFunc("config", flagsServe),
|
Before: initLogFunc(initConfigFileInputSourceFunc("config", flagsServe)),
|
||||||
Description: `Run the ntfy server and listen for incoming requests
|
Description: `Run the ntfy server and listen for incoming requests
|
||||||
|
|
||||||
The command will load the configuration from /etc/ntfy/server.yml. Config options can
|
The command will load the configuration from /etc/ntfy/server.yml. Config options can
|
||||||
|
@ -192,7 +193,7 @@ func execServe(c *cli.Context) error {
|
||||||
for _, host := range visitorRequestLimitExemptHosts {
|
for _, host := range visitorRequestLimitExemptHosts {
|
||||||
ips, err := net.LookupIP(host)
|
ips, err := net.LookupIP(host)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("cannot resolve host %s: %s, ignoring visitor request exemption", host, err.Error())
|
log.Warn("cannot resolve host %s: %s, ignoring visitor request exemption", host, err.Error())
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
for _, ip := range ips {
|
for _, ip := range ips {
|
||||||
|
@ -242,12 +243,12 @@ func execServe(c *cli.Context) error {
|
||||||
conf.EnableWeb = enableWeb
|
conf.EnableWeb = enableWeb
|
||||||
s, err := server.New(conf)
|
s, err := server.New(conf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalln(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
if err := s.Run(); err != nil {
|
if err := s.Run(); err != nil {
|
||||||
log.Fatalln(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
log.Printf("Exiting.")
|
log.Info("Exiting.")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,17 @@ const (
|
||||||
clientUserConfigFileWindowsRelative = "ntfy\\client.yml"
|
clientUserConfigFileWindowsRelative = "ntfy\\client.yml"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var flagsSubscribe = append(
|
||||||
|
flagsDefault,
|
||||||
|
&cli.StringFlag{Name: "config", Aliases: []string{"c"}, Usage: "client config file"},
|
||||||
|
&cli.StringFlag{Name: "since", Aliases: []string{"s"}, Usage: "return events since `SINCE` (Unix timestamp, or all)"},
|
||||||
|
&cli.StringFlag{Name: "user", Aliases: []string{"u"}, Usage: "username[:password] used to auth against the server"},
|
||||||
|
&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"},
|
||||||
|
&cli.BoolFlag{Name: "verbose", Aliases: []string{"v"}, Usage: "print verbose output"},
|
||||||
|
)
|
||||||
|
|
||||||
var cmdSubscribe = &cli.Command{
|
var cmdSubscribe = &cli.Command{
|
||||||
Name: "subscribe",
|
Name: "subscribe",
|
||||||
Aliases: []string{"sub"},
|
Aliases: []string{"sub"},
|
||||||
|
@ -31,15 +42,8 @@ var cmdSubscribe = &cli.Command{
|
||||||
UsageText: "ntfy subscribe [OPTIONS..] [TOPIC]",
|
UsageText: "ntfy subscribe [OPTIONS..] [TOPIC]",
|
||||||
Action: execSubscribe,
|
Action: execSubscribe,
|
||||||
Category: categoryClient,
|
Category: categoryClient,
|
||||||
Flags: []cli.Flag{
|
Flags: flagsSubscribe,
|
||||||
&cli.StringFlag{Name: "config", Aliases: []string{"c"}, Usage: "client config file"},
|
Before: initLogFunc(nil),
|
||||||
&cli.StringFlag{Name: "since", Aliases: []string{"s"}, Usage: "return events since `SINCE` (Unix timestamp, or all)"},
|
|
||||||
&cli.StringFlag{Name: "user", Aliases: []string{"u"}, Usage: "username[:password] used to auth against the server"},
|
|
||||||
&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"},
|
|
||||||
&cli.BoolFlag{Name: "verbose", Aliases: []string{"v"}, Usage: "print verbose output"},
|
|
||||||
},
|
|
||||||
Description: `Subscribe to a topic from a ntfy server, and either print or execute a command for
|
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:
|
every arriving message. There are 3 modes in which the command can be run:
|
||||||
|
|
||||||
|
|
17
cmd/user.go
17
cmd/user.go
|
@ -17,14 +17,19 @@ func init() {
|
||||||
commands = append(commands, cmdUser)
|
commands = append(commands, cmdUser)
|
||||||
}
|
}
|
||||||
|
|
||||||
var flagsUser = userCommandFlags()
|
var flagsUser = append(
|
||||||
|
flagsDefault,
|
||||||
|
&cli.StringFlag{Name: "config", Aliases: []string{"c"}, EnvVars: []string{"NTFY_CONFIG_FILE"}, Value: "/etc/ntfy/server.yml", DefaultText: "/etc/ntfy/server.yml", Usage: "config file"},
|
||||||
|
altsrc.NewStringFlag(&cli.StringFlag{Name: "auth-file", Aliases: []string{"H"}, EnvVars: []string{"NTFY_AUTH_FILE"}, Usage: "auth database file used for access control"}),
|
||||||
|
altsrc.NewStringFlag(&cli.StringFlag{Name: "auth-default-access", Aliases: []string{"p"}, EnvVars: []string{"NTFY_AUTH_DEFAULT_ACCESS"}, Value: "read-write", Usage: "default permissions if no matching entries in the auth database are found"}),
|
||||||
|
)
|
||||||
|
|
||||||
var cmdUser = &cli.Command{
|
var cmdUser = &cli.Command{
|
||||||
Name: "user",
|
Name: "user",
|
||||||
Usage: "Manage/show users",
|
Usage: "Manage/show users",
|
||||||
UsageText: "ntfy user [list|add|remove|change-pass|change-role] ...",
|
UsageText: "ntfy user [list|add|remove|change-pass|change-role] ...",
|
||||||
Flags: flagsUser,
|
Flags: flagsUser,
|
||||||
Before: initConfigFileInputSourceFunc("config", flagsUser),
|
Before: initLogFunc(initConfigFileInputSourceFunc("config", flagsUser)),
|
||||||
Category: categoryServer,
|
Category: categoryServer,
|
||||||
Subcommands: []*cli.Command{
|
Subcommands: []*cli.Command{
|
||||||
{
|
{
|
||||||
|
@ -269,11 +274,3 @@ func readPasswordAndConfirm(c *cli.Context) (string, error) {
|
||||||
}
|
}
|
||||||
return string(password), nil
|
return string(password), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func userCommandFlags() []cli.Flag {
|
|
||||||
return []cli.Flag{
|
|
||||||
&cli.StringFlag{Name: "config", Aliases: []string{"c"}, EnvVars: []string{"NTFY_CONFIG_FILE"}, Value: "/etc/ntfy/server.yml", DefaultText: "/etc/ntfy/server.yml", Usage: "config file"},
|
|
||||||
altsrc.NewStringFlag(&cli.StringFlag{Name: "auth-file", Aliases: []string{"H"}, EnvVars: []string{"NTFY_AUTH_FILE"}, Usage: "auth database file used for access control"}),
|
|
||||||
altsrc.NewStringFlag(&cli.StringFlag{Name: "auth-default-access", Aliases: []string{"p"}, EnvVars: []string{"NTFY_AUTH_DEFAULT_ACCESS"}, Value: "read-write", Usage: "default permissions if no matching entries in the auth database are found"}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
79
log/log.go
Normal file
79
log/log.go
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
package log
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Level int
|
||||||
|
|
||||||
|
const (
|
||||||
|
DebugLevel Level = iota
|
||||||
|
InfoLevel
|
||||||
|
WarnLevel
|
||||||
|
ErrorLevel
|
||||||
|
)
|
||||||
|
|
||||||
|
func (l Level) String() string {
|
||||||
|
switch l {
|
||||||
|
case DebugLevel:
|
||||||
|
return "DEBUG"
|
||||||
|
case InfoLevel:
|
||||||
|
return "INFO"
|
||||||
|
case WarnLevel:
|
||||||
|
return "WARN"
|
||||||
|
case ErrorLevel:
|
||||||
|
return "ERROR"
|
||||||
|
}
|
||||||
|
return "unknown"
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
level = InfoLevel
|
||||||
|
)
|
||||||
|
|
||||||
|
func Debug(message string, v ...interface{}) {
|
||||||
|
logIf(DebugLevel, message, v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Info(message string, v ...interface{}) {
|
||||||
|
logIf(InfoLevel, message, v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Warn(message string, v ...interface{}) {
|
||||||
|
logIf(WarnLevel, message, v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Error(message string, v ...interface{}) {
|
||||||
|
logIf(ErrorLevel, message, v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Fatal(v ...interface{}) {
|
||||||
|
log.Fatalln(v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func SetLevel(newLevel Level) {
|
||||||
|
level = newLevel
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToLevel(s string) Level {
|
||||||
|
switch strings.ToLower(s) {
|
||||||
|
case "debug":
|
||||||
|
return DebugLevel
|
||||||
|
case "info":
|
||||||
|
return InfoLevel
|
||||||
|
case "warn", "warning":
|
||||||
|
return WarnLevel
|
||||||
|
case "error":
|
||||||
|
return ErrorLevel
|
||||||
|
default:
|
||||||
|
log.Fatalf("unknown log level: %s", s)
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func logIf(l Level, message string, v ...interface{}) {
|
||||||
|
if level <= l {
|
||||||
|
log.Printf(l.String()+" "+message, v...)
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,8 +9,8 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"heckel.io/ntfy/log"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
|
@ -181,7 +181,7 @@ func (s *Server) Run() error {
|
||||||
if s.config.SMTPServerListen != "" {
|
if s.config.SMTPServerListen != "" {
|
||||||
listenStr += fmt.Sprintf(" %s[smtp]", s.config.SMTPServerListen)
|
listenStr += fmt.Sprintf(" %s[smtp]", s.config.SMTPServerListen)
|
||||||
}
|
}
|
||||||
log.Printf("Listening on%s", listenStr)
|
log.Info("Listening on%s", listenStr)
|
||||||
mux := http.NewServeMux()
|
mux := http.NewServeMux()
|
||||||
mux.HandleFunc("/", s.handle)
|
mux.HandleFunc("/", s.handle)
|
||||||
errChan := make(chan error)
|
errChan := make(chan error)
|
||||||
|
@ -221,7 +221,7 @@ func (s *Server) Run() error {
|
||||||
}
|
}
|
||||||
s.mu.Unlock()
|
s.mu.Unlock()
|
||||||
go s.runManager()
|
go s.runManager()
|
||||||
go s.runAtSender()
|
go s.runDelaySender()
|
||||||
go s.runFirebaseKeepaliver()
|
go s.runFirebaseKeepaliver()
|
||||||
|
|
||||||
return <-errChan
|
return <-errChan
|
||||||
|
@ -248,16 +248,18 @@ func (s *Server) Stop() {
|
||||||
|
|
||||||
func (s *Server) handle(w http.ResponseWriter, r *http.Request) {
|
func (s *Server) handle(w http.ResponseWriter, r *http.Request) {
|
||||||
v := s.visitor(r)
|
v := s.visitor(r)
|
||||||
|
log.Debug("[%s] %s %s", v.ip, r.Method, r.URL.Path)
|
||||||
|
|
||||||
if err := s.handleInternal(w, r, v); err != nil {
|
if err := s.handleInternal(w, r, v); err != nil {
|
||||||
if websocket.IsWebSocketUpgrade(r) {
|
if websocket.IsWebSocketUpgrade(r) {
|
||||||
log.Printf("[%s] WS %s %s - %s", v.ip, r.Method, r.URL.Path, err.Error())
|
log.Info("[%s] WS %s %s - %s", v.ip, r.Method, r.URL.Path, err.Error())
|
||||||
return // Do not attempt to write to upgraded connection
|
return // Do not attempt to write to upgraded connection
|
||||||
}
|
}
|
||||||
httpErr, ok := err.(*errHTTP)
|
httpErr, ok := err.(*errHTTP)
|
||||||
if !ok {
|
if !ok {
|
||||||
httpErr = errHTTPInternalError
|
httpErr = errHTTPInternalError
|
||||||
}
|
}
|
||||||
log.Printf("[%s] HTTP %s %s - %d - %d - %s", v.ip, r.Method, r.URL.Path, httpErr.HTTPCode, httpErr.Code, err.Error())
|
log.Info("[%s] HTTP %s %s - %d - %d - %s", v.ip, r.Method, r.URL.Path, httpErr.HTTPCode, httpErr.Code, err.Error())
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
w.Header().Set("Access-Control-Allow-Origin", "*") // CORS, allow cross-origin requests
|
w.Header().Set("Access-Control-Allow-Origin", "*") // CORS, allow cross-origin requests
|
||||||
w.WriteHeader(httpErr.HTTPCode)
|
w.WriteHeader(httpErr.HTTPCode)
|
||||||
|
@ -434,6 +436,8 @@ func (s *Server) handlePublish(w http.ResponseWriter, r *http.Request, v *visito
|
||||||
m.Message = emptyMessageBody
|
m.Message = emptyMessageBody
|
||||||
}
|
}
|
||||||
delayed := m.Time > time.Now().Unix()
|
delayed := m.Time > time.Now().Unix()
|
||||||
|
log.Debug("[%s] %s %s: ev=%s, body=%d bytes, delayed=%t, fb=%t, cache=%t, up=%t, email=%s",
|
||||||
|
v.ip, r.Method, r.URL.Path, m.Event, len(body.PeekedBytes), delayed, firebase, cache, unifiedpush, email)
|
||||||
if !delayed {
|
if !delayed {
|
||||||
if err := t.Publish(m); err != nil {
|
if err := t.Publish(m); err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -466,13 +470,13 @@ func (s *Server) handlePublish(w http.ResponseWriter, r *http.Request, v *visito
|
||||||
|
|
||||||
func (s *Server) sendToFirebase(v *visitor, m *message) {
|
func (s *Server) sendToFirebase(v *visitor, m *message) {
|
||||||
if err := s.firebase(m); err != nil {
|
if err := s.firebase(m); err != nil {
|
||||||
log.Printf("[%s] FB - Unable to publish to Firebase: %v", v.ip, err.Error())
|
log.Warn("[%s] FB - Unable to publish to Firebase: %v", v.ip, err.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) sendEmail(v *visitor, m *message, email string) {
|
func (s *Server) sendEmail(v *visitor, m *message, email string) {
|
||||||
if err := s.mailer.Send(v.ip, email, m); err != nil {
|
if err := s.mailer.Send(v.ip, email, m); err != nil {
|
||||||
log.Printf("[%s] MAIL - Unable to send email: %v", v.ip, err.Error())
|
log.Warn("[%s] MAIL - Unable to send email: %v", v.ip, err.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -482,16 +486,16 @@ func (s *Server) forwardPollRequest(v *visitor, m *message) {
|
||||||
forwardURL := fmt.Sprintf("%s/%s", s.config.UpstreamBaseURL, topicHash)
|
forwardURL := fmt.Sprintf("%s/%s", s.config.UpstreamBaseURL, topicHash)
|
||||||
req, err := http.NewRequest("POST", forwardURL, strings.NewReader(""))
|
req, err := http.NewRequest("POST", forwardURL, strings.NewReader(""))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("[%s] FWD - Unable to forward poll request: %v", v.ip, err.Error())
|
log.Warn("[%s] FWD - Unable to forward poll request: %v", v.ip, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
req.Header.Set("X-Poll-ID", m.ID)
|
req.Header.Set("X-Poll-ID", m.ID)
|
||||||
response, err := http.DefaultClient.Do(req)
|
response, err := http.DefaultClient.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("[%s] FWD - Unable to forward poll request: %v", v.ip, err.Error())
|
log.Warn("[%s] FWD - Unable to forward poll request: %v", v.ip, err.Error())
|
||||||
return
|
return
|
||||||
} else if response.StatusCode != http.StatusOK {
|
} else if response.StatusCode != http.StatusOK {
|
||||||
log.Printf("[%s] FWD - Unable to forward poll request, unexpected status: %d", v.ip, response.StatusCode)
|
log.Warn("[%s] FWD - Unable to forward poll request, unexpected status: %d", v.ip, response.StatusCode)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1015,17 +1019,17 @@ func (s *Server) updateStatsAndPrune() {
|
||||||
ids, err := s.messageCache.AttachmentsExpired()
|
ids, err := s.messageCache.AttachmentsExpired()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
if err := s.fileCache.Remove(ids...); err != nil {
|
if err := s.fileCache.Remove(ids...); err != nil {
|
||||||
log.Printf("error while deleting attachments: %s", err.Error())
|
log.Warn("Error deleting attachments: %s", err.Error())
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log.Printf("error retrieving expired attachments: %s", err.Error())
|
log.Warn("Error retrieving expired attachments: %s", err.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prune message cache
|
// Prune message cache
|
||||||
olderThan := time.Now().Add(-1 * s.config.CacheDuration)
|
olderThan := time.Now().Add(-1 * s.config.CacheDuration)
|
||||||
if err := s.messageCache.Prune(olderThan); err != nil {
|
if err := s.messageCache.Prune(olderThan); err != nil {
|
||||||
log.Printf("error pruning cache: %s", err.Error())
|
log.Warn("Error pruning cache: %s", err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prune old topics, remove subscriptions without subscribers
|
// Prune old topics, remove subscriptions without subscribers
|
||||||
|
@ -1034,7 +1038,7 @@ func (s *Server) updateStatsAndPrune() {
|
||||||
subs := t.Subscribers()
|
subs := t.Subscribers()
|
||||||
msgs, err := s.messageCache.MessageCount(t.ID)
|
msgs, err := s.messageCache.MessageCount(t.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("cannot get stats for topic %s: %s", t.ID, err.Error())
|
log.Warn("Cannot get stats for topic %s: %s", t.ID, err.Error())
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if msgs == 0 && subs == 0 {
|
if msgs == 0 && subs == 0 {
|
||||||
|
@ -1052,7 +1056,7 @@ func (s *Server) updateStatsAndPrune() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Print stats
|
// Print stats
|
||||||
log.Printf("Stats: %d message(s) published, %d in cache, %d successful mails, %d failed, %d topic(s) active, %d subscriber(s), %d visitor(s)",
|
log.Info("Stats: %d message(s) published, %d in cache, %d successful mails, %d failed, %d topic(s) active, %d subscriber(s), %d visitor(s)",
|
||||||
s.messages, messages, mailSuccess, mailFailure, len(s.topics), subscribers, len(s.visitors))
|
s.messages, messages, mailSuccess, mailFailure, len(s.topics), subscribers, len(s.visitors))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1096,12 +1100,12 @@ func (s *Server) runManager() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) runAtSender() {
|
func (s *Server) runDelaySender() {
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-time.After(s.config.AtSenderInterval):
|
case <-time.After(s.config.AtSenderInterval):
|
||||||
if err := s.sendDelayedMessages(); err != nil {
|
if err := s.sendDelayedMessages(); err != nil {
|
||||||
log.Printf("error sending scheduled messages: %s", err.Error())
|
log.Warn("error sending scheduled messages: %s", err.Error())
|
||||||
}
|
}
|
||||||
case <-s.closeChan:
|
case <-s.closeChan:
|
||||||
return
|
return
|
||||||
|
@ -1117,11 +1121,11 @@ func (s *Server) runFirebaseKeepaliver() {
|
||||||
select {
|
select {
|
||||||
case <-time.After(s.config.FirebaseKeepaliveInterval):
|
case <-time.After(s.config.FirebaseKeepaliveInterval):
|
||||||
if err := s.firebase(newKeepaliveMessage(firebaseControlTopic)); err != nil {
|
if err := s.firebase(newKeepaliveMessage(firebaseControlTopic)); err != nil {
|
||||||
log.Printf("error sending Firebase keepalive message to %s: %s", firebaseControlTopic, err.Error())
|
log.Info("error sending Firebase keepalive message to %s: %s", firebaseControlTopic, err.Error())
|
||||||
}
|
}
|
||||||
case <-time.After(s.config.FirebasePollInterval):
|
case <-time.After(s.config.FirebasePollInterval):
|
||||||
if err := s.firebase(newKeepaliveMessage(firebasePollTopic)); err != nil {
|
if err := s.firebase(newKeepaliveMessage(firebasePollTopic)); err != nil {
|
||||||
log.Printf("error sending Firebase keepalive message to %s: %s", firebasePollTopic, err.Error())
|
log.Info("error sending Firebase keepalive message to %s: %s", firebasePollTopic, err.Error())
|
||||||
}
|
}
|
||||||
case <-s.closeChan:
|
case <-s.closeChan:
|
||||||
return
|
return
|
||||||
|
@ -1140,12 +1144,12 @@ func (s *Server) sendDelayedMessages() error {
|
||||||
t, ok := s.topics[m.Topic] // If no subscribers, just mark message as published
|
t, ok := s.topics[m.Topic] // If no subscribers, just mark message as published
|
||||||
if ok {
|
if ok {
|
||||||
if err := t.Publish(m); err != nil {
|
if err := t.Publish(m); err != nil {
|
||||||
log.Printf("unable to publish message %s to topic %s: %v", m.ID, m.Topic, err.Error())
|
log.Info("unable to publish message %s to topic %s: %v", m.ID, m.Topic, err.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if s.firebase != nil { // Firebase subscribers may not show up in topics map
|
if s.firebase != nil { // Firebase subscribers may not show up in topics map
|
||||||
if err := s.firebase(m); err != nil {
|
if err := s.firebase(m); err != nil {
|
||||||
log.Printf("unable to publish to Firebase: %v", err.Error())
|
log.Info("unable to publish to Firebase: %v", err.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := s.messageCache.MarkPublished(m); err != nil {
|
if err := s.messageCache.MarkPublished(m); err != nil {
|
||||||
|
@ -1252,13 +1256,13 @@ func (s *Server) withAuth(next handleFunc, perm auth.Permission) handleFunc {
|
||||||
username, password, ok := extractUserPass(r)
|
username, password, ok := extractUserPass(r)
|
||||||
if ok {
|
if ok {
|
||||||
if user, err = s.auth.Authenticate(username, password); err != nil {
|
if user, err = s.auth.Authenticate(username, password); err != nil {
|
||||||
log.Printf("authentication failed: %s", err.Error())
|
log.Info("authentication failed: %s", err.Error())
|
||||||
return errHTTPUnauthorized
|
return errHTTPUnauthorized
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, t := range topics {
|
for _, t := range topics {
|
||||||
if err := s.auth.Authorize(user, t.ID, perm); err != nil {
|
if err := s.auth.Authorize(user, t.ID, perm); err != nil {
|
||||||
log.Printf("unauthorized: %s", err.Error())
|
log.Info("unauthorized: %s", err.Error())
|
||||||
return errHTTPForbidden
|
return errHTTPForbidden
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue