diff --git a/.goreleaser.yml b/.goreleaser.yml index 7148ef61..d0cc958f 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -47,14 +47,20 @@ nfpms: - rpm bindir: /usr/bin contents: - - src: config/config.yml - dst: /etc/ntfy/config.yml + - src: server/server.yml + dst: /etc/ntfy/server.yml type: config - - src: config/ntfy.service + - src: server/ntfy.service dst: /lib/systemd/system/ntfy.service + - src: client/client.yml + dst: /etc/ntfy/client.yml + type: config + - src: client/ntfy-client.service + dst: /lib/systemd/system/ntfy-client.service - dst: /var/cache/ntfy type: dir scripts: + preinstall: "scripts/preinst.sh" postinstall: "scripts/postinst.sh" preremove: "scripts/prerm.sh" postremove: "scripts/postrm.sh" @@ -64,8 +70,10 @@ archives: files: - LICENSE - README.md - - config/config.yml - - config/ntfy.service + - server/server.yml + - server/ntfy.service + - client/client.yml + - client/ntfy-client.service replacements: 386: i386 amd64: x86_64 diff --git a/Makefile b/Makefile index 006e6381..1744dfd9 100644 --- a/Makefile +++ b/Makefile @@ -115,7 +115,7 @@ build-simple: clean "-linkmode=external -extldflags=-static -s -w -X main.version=$(VERSION) -X main.commit=$(shell git rev-parse --short HEAD) -X main.date=$(shell date +%s)" clean: .PHONY - rm -rf dist build + rm -rf dist build server/docs # Releasing targets diff --git a/client/client.go b/client/client.go index 31e7c894..3b83f28b 100644 --- a/client/client.go +++ b/client/client.go @@ -50,7 +50,8 @@ func New(config *Config) *Client { } } -func (c *Client) Publish(topicURL, message string, options ...PublishOption) error { +func (c *Client) Publish(topic, message string, options ...PublishOption) error { + topicURL := c.expandTopicURL(topic) req, _ := http.NewRequest("POST", topicURL, strings.NewReader(message)) for _, option := range options { if err := option(req); err != nil { diff --git a/config/ntfy-client.service b/client/ntfy-client.service similarity index 100% rename from config/ntfy-client.service rename to client/ntfy-client.service diff --git a/cmd/serve.go b/cmd/serve.go index 2cdc8425..ef963ebe 100644 --- a/cmd/serve.go +++ b/cmd/serve.go @@ -5,7 +5,6 @@ import ( "errors" "github.com/urfave/cli/v2" "github.com/urfave/cli/v2/altsrc" - "heckel.io/ntfy/config" "heckel.io/ntfy/server" "heckel.io/ntfy/util" "log" @@ -13,20 +12,20 @@ import ( ) var flagsServe = []cli.Flag{ - &cli.StringFlag{Name: "config", Aliases: []string{"c"}, EnvVars: []string{"NTFY_CONFIG_FILE"}, Value: "/etc/ntfy/config.yml", DefaultText: "/etc/ntfy/config.yml", Usage: "config file"}, - altsrc.NewStringFlag(&cli.StringFlag{Name: "listen-http", Aliases: []string{"l"}, EnvVars: []string{"NTFY_LISTEN_HTTP"}, Value: config.DefaultListenHTTP, Usage: "ip:port used to as HTTP listen address"}), + &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: "listen-http", Aliases: []string{"l"}, EnvVars: []string{"NTFY_LISTEN_HTTP"}, Value: server.DefaultListenHTTP, Usage: "ip:port used to as HTTP listen address"}), altsrc.NewStringFlag(&cli.StringFlag{Name: "listen-https", Aliases: []string{"L"}, EnvVars: []string{"NTFY_LISTEN_HTTPS"}, Usage: "ip:port used to as HTTPS listen address"}), altsrc.NewStringFlag(&cli.StringFlag{Name: "key-file", Aliases: []string{"K"}, EnvVars: []string{"NTFY_KEY_FILE"}, Usage: "private key file, if listen-https is set"}), altsrc.NewStringFlag(&cli.StringFlag{Name: "cert-file", Aliases: []string{"E"}, EnvVars: []string{"NTFY_CERT_FILE"}, Usage: "certificate file, if listen-https is set"}), altsrc.NewStringFlag(&cli.StringFlag{Name: "firebase-key-file", Aliases: []string{"F"}, EnvVars: []string{"NTFY_FIREBASE_KEY_FILE"}, Usage: "Firebase credentials file; if set additionally publish to FCM topic"}), altsrc.NewStringFlag(&cli.StringFlag{Name: "cache-file", Aliases: []string{"C"}, EnvVars: []string{"NTFY_CACHE_FILE"}, Usage: "cache file used for message caching"}), - altsrc.NewDurationFlag(&cli.DurationFlag{Name: "cache-duration", Aliases: []string{"b"}, EnvVars: []string{"NTFY_CACHE_DURATION"}, Value: config.DefaultCacheDuration, Usage: "buffer messages for this time to allow `since` requests"}), - altsrc.NewDurationFlag(&cli.DurationFlag{Name: "keepalive-interval", Aliases: []string{"k"}, EnvVars: []string{"NTFY_KEEPALIVE_INTERVAL"}, Value: config.DefaultKeepaliveInterval, Usage: "interval of keepalive messages"}), - altsrc.NewDurationFlag(&cli.DurationFlag{Name: "manager-interval", Aliases: []string{"m"}, EnvVars: []string{"NTFY_MANAGER_INTERVAL"}, Value: config.DefaultManagerInterval, Usage: "interval of for message pruning and stats printing"}), - altsrc.NewIntFlag(&cli.IntFlag{Name: "global-topic-limit", Aliases: []string{"T"}, EnvVars: []string{"NTFY_GLOBAL_TOPIC_LIMIT"}, Value: config.DefaultGlobalTopicLimit, Usage: "total number of topics allowed"}), - altsrc.NewIntFlag(&cli.IntFlag{Name: "visitor-subscription-limit", Aliases: []string{"V"}, EnvVars: []string{"NTFY_VISITOR_SUBSCRIPTION_LIMIT"}, Value: config.DefaultVisitorSubscriptionLimit, Usage: "number of subscriptions per visitor"}), - altsrc.NewIntFlag(&cli.IntFlag{Name: "visitor-request-limit-burst", Aliases: []string{"B"}, EnvVars: []string{"NTFY_VISITOR_REQUEST_LIMIT_BURST"}, Value: config.DefaultVisitorRequestLimitBurst, Usage: "initial limit of requests per visitor"}), - altsrc.NewDurationFlag(&cli.DurationFlag{Name: "visitor-request-limit-replenish", Aliases: []string{"R"}, EnvVars: []string{"NTFY_VISITOR_REQUEST_LIMIT_REPLENISH"}, Value: config.DefaultVisitorRequestLimitReplenish, Usage: "interval at which burst limit is replenished (one per x)"}), + altsrc.NewDurationFlag(&cli.DurationFlag{Name: "cache-duration", Aliases: []string{"b"}, EnvVars: []string{"NTFY_CACHE_DURATION"}, Value: server.DefaultCacheDuration, Usage: "buffer messages for this time to allow `since` requests"}), + altsrc.NewDurationFlag(&cli.DurationFlag{Name: "keepalive-interval", Aliases: []string{"k"}, EnvVars: []string{"NTFY_KEEPALIVE_INTERVAL"}, Value: server.DefaultKeepaliveInterval, Usage: "interval of keepalive messages"}), + altsrc.NewDurationFlag(&cli.DurationFlag{Name: "manager-interval", Aliases: []string{"m"}, EnvVars: []string{"NTFY_MANAGER_INTERVAL"}, Value: server.DefaultManagerInterval, Usage: "interval of for message pruning and stats printing"}), + altsrc.NewIntFlag(&cli.IntFlag{Name: "global-topic-limit", Aliases: []string{"T"}, EnvVars: []string{"NTFY_GLOBAL_TOPIC_LIMIT"}, Value: server.DefaultGlobalTopicLimit, Usage: "total number of topics allowed"}), + altsrc.NewIntFlag(&cli.IntFlag{Name: "visitor-subscription-limit", Aliases: []string{"V"}, EnvVars: []string{"NTFY_VISITOR_SUBSCRIPTION_LIMIT"}, Value: server.DefaultVisitorSubscriptionLimit, Usage: "number of subscriptions per visitor"}), + altsrc.NewIntFlag(&cli.IntFlag{Name: "visitor-request-limit-burst", Aliases: []string{"B"}, EnvVars: []string{"NTFY_VISITOR_REQUEST_LIMIT_BURST"}, Value: server.DefaultVisitorRequestLimitBurst, Usage: "initial limit of requests per visitor"}), + altsrc.NewDurationFlag(&cli.DurationFlag{Name: "visitor-request-limit-replenish", Aliases: []string{"R"}, EnvVars: []string{"NTFY_VISITOR_REQUEST_LIMIT_REPLENISH"}, Value: server.DefaultVisitorRequestLimitReplenish, Usage: "interval at which burst limit is replenished (one per x)"}), altsrc.NewBoolFlag(&cli.BoolFlag{Name: "behind-proxy", Aliases: []string{"P"}, EnvVars: []string{"NTFY_BEHIND_PROXY"}, Value: false, Usage: "if set, use X-Forwarded-For header to determine visitor IP address (for rate limiting)"}), } @@ -39,7 +38,7 @@ var cmdServe = &cli.Command{ Before: initConfigFileInputSource("config", flagsServe), Description: `Run the ntfy server and listen for incoming requests -The command will load the configuration from /etc/ntfy/config.yml. Config options can +The command will load the configuration from /etc/ntfy/server.yml. Config options can be overridden using the command line options. Examples: @@ -82,7 +81,7 @@ func execServe(c *cli.Context) error { } // Run server - conf := config.New(listenHTTP) + conf := server.NewConfig(listenHTTP) conf.ListenHTTPS = listenHTTPS conf.KeyFile = keyFile conf.CertFile = certFile diff --git a/cmd/subscribe.go b/cmd/subscribe.go index bbc36d28..a967daec 100644 --- a/cmd/subscribe.go +++ b/cmd/subscribe.go @@ -20,10 +20,14 @@ var cmdSubscribe = &cli.Command{ Usage: "Subscribe to one or more topics on a ntfy server", UsageText: "ntfy subscribe [OPTIONS..] [TOPIC]", Action: execSubscribe, + OnUsageError: func(context *cli.Context, err error, isSubcommand bool) error { + println("ee") + return nil + }, + 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.StringFlag{Name: "config", Aliases: []string{"c"}, Usage: "config file `FILE`"}, + &cli.StringFlag{Name: "since", Aliases: []string{"s"}, Usage: "return events since `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"}, @@ -72,8 +76,6 @@ ntfy subscribe --from-config } func execSubscribe(c *cli.Context) error { - fmt.Fprintln(c.App.ErrWriter, "\x1b[1;33mThis command is incubating. The interface may change without notice.\x1b[0m") - // Read config and options conf, err := loadConfig(c) if err != nil { @@ -100,7 +102,7 @@ func execSubscribe(c *cli.Context) error { options = append(options, client.WithScheduled()) } if topic == "" && len(conf.Subscribe) == 0 { - return errors.New("must specify topic, or have at least one topic defined in config") + return errors.New("must specify topic, type 'ntfy subscribe --help' for help") } // Execute poll or subscribe diff --git a/docs/config.md b/docs/config.md index 4f83e96c..3917825d 100644 --- a/docs/config.md +++ b/docs/config.md @@ -1,6 +1,6 @@ # Configuring the ntfy server -The ntfy server can be configured in three ways: using a config file (typically at `/etc/ntfy/config.yml`, -see [config.yml](https://github.com/binwiederhier/ntfy/blob/main/config/config.yml)), via command line arguments +The ntfy server can be configured in three ways: using a config file (typically at `/etc/ntfy/server.yml`, +see [server.yml](https://github.com/binwiederhier/ntfy/blob/main/config/server.yml)), via command line arguments or using environment variables. ## Quick start @@ -50,7 +50,7 @@ flag. This will instruct the [rate limiting](#rate-limiting) logic to use the `X identifier for a visitor, as opposed to the remote IP address. If the `behind-proxy` flag is not set, all visitors will be counted as one, because from the perspective of the ntfy server, they all share the proxy's IP address. -=== "/etc/ntfy/config.yml" +=== "/etc/ntfy/server.yml" ``` # Tell ntfy to use "X-Forwarded-For" to identify visitors behind-proxy: true @@ -200,7 +200,7 @@ To configure FCM for your self-hosted instance of the ntfy server, follow these 1. Sign up for a [Firebase account](https://console.firebase.google.com/) 2. Create a Firebase app and download the key file (e.g. `myapp-firebase-adminsdk-...json`) -3. Place the key file in `/etc/ntfy`, set the `firebase-key-file` in `config.yml` accordingly and restart the ntfy server +3. Place the key file in `/etc/ntfy`, set the `firebase-key-file` in `server.yml` accordingly and restart the ntfy server 4. Build your own Android .apk following [these instructions](develop.md#android-app) Example: @@ -294,7 +294,7 @@ to maintain the client connection and the connection to ntfy. ``` ## Config options -Each config option can be set in the config file `/etc/ntfy/config.yml` (e.g. `listen-http: :80`) or as a +Each config option can be set in the config file `/etc/ntfy/server.yml` (e.g. `listen-http: :80`) or as a CLI option (e.g. `--listen-http :80`. Here's a list of all available options. Alternatively, you can set an environment variable before running the `ntfy` command (e.g. `export NTFY_LISTEN_HTTP=:80`). @@ -327,7 +327,7 @@ USAGE: ntfy [OPTION..] GLOBAL OPTIONS: - --config value, -c value config file (default: /etc/ntfy/config.yml) [$NTFY_CONFIG_FILE] + --config value, -c value config file (default: /etc/ntfy/server.yml) [$NTFY_CONFIG_FILE] --listen-http value, -l value ip:port used to as listen address (default: ":80") [$NTFY_LISTEN_HTTP] --firebase-key-file value, -F value Firebase credentials file; if set additionally publish to FCM topic [$NTFY_FIREBASE_KEY_FILE] --cache-file value, -C value cache file used for message caching [$NTFY_CACHE_FILE] diff --git a/docs/faq.md b/docs/faq.md index 351ad5ee..5105cc85 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -17,7 +17,7 @@ subscribed to a topic. ## Will you know what topics exist, can you spy on me? If you don't trust me or your messages are sensitive, run your own server. It's open source. That said, the logs do not contain any topic names or other details about you. -Messages are cached for the duration configured in `config.yml` (12h by default) to facilitate service restarts, message polling and to overcome +Messages are cached for the duration configured in `server.yml` (12h by default) to facilitate service restarts, message polling and to overcome client network disruptions. ## Can I self-host it? diff --git a/docs/install.md b/docs/install.md index 4f3eea13..379f8559 100644 --- a/docs/install.md +++ b/docs/install.md @@ -13,7 +13,7 @@ The ntfy server comes as a statically linked binary and is shipped as tarball, d We support amd64, armv7 and arm64. 1. Install ntfy using one of the methods described below -2. Then (optionally) edit `/etc/ntfy/config.yml` (see [configuration](config.md)) +2. Then (optionally) edit `/etc/ntfy/server.yml` (see [configuration](config.md)) To run the ntfy server, then just run `ntfy serve` (or `systemctl start ntfy` when using the deb/rpm). To send messages, use `ntfy publish`. To subscribe to topics, use `ntfy subscribe` (see [subscribing via CLI][subscribe/cli.md] @@ -138,7 +138,7 @@ straight forward to use. The server exposes its web UI and the API on port 80, so you need to expose that in Docker. To use the persistent [message cache](config.md#message-cache), you also need to map a volume to `/var/cache/ntfy`. To change other settings, -you should map `/etc/ntfy`, so you can edit `/etc/ntfy/config.yml`. +you should map `/etc/ntfy`, so you can edit `/etc/ntfy/server.yml`. Basic usage (no cache or additional config): ``` @@ -156,7 +156,7 @@ docker run \ serve ``` -With other config options (configured via `/etc/ntfy/config.yml`, see [configuration](config.md) for details): +With other config options (configured via `/etc/ntfy/server.yml`, see [configuration](config.md) for details): ```bash docker run \ -v /etc/ntfy:/etc/ntfy \ diff --git a/scripts/postinst.sh b/scripts/postinst.sh index 843805c3..dabc0a10 100755 --- a/scripts/postinst.sh +++ b/scripts/postinst.sh @@ -13,7 +13,7 @@ if [ "$1" = "configure" ] && [ -d /run/systemd/system ]; then chmod 700 /var/cache/ntfy # Hack to change permissions on cache file - configfile="/etc/ntfy/config.yml" + configfile="/etc/ntfy/server.yml" if [ -f "$configfile" ]; then cachefile="$(cat "$configfile" | perl -n -e'/^\s*cache-file: ["'"'"']?([^"'"'"']+)["'"'"']?/ && print $1')" # Oh my, see #47 if [ -n "$cachefile" ]; then diff --git a/scripts/postrm.sh b/scripts/postrm.sh index 78db62e8..696e8a17 100755 --- a/scripts/postrm.sh +++ b/scripts/postrm.sh @@ -4,7 +4,7 @@ set -e # Delete the config if package is purged if [ "$1" = "purge" ]; then id ntfy >/dev/null 2>&1 && userdel ntfy - rm -f /etc/ntfy/config.yml + rm -f /etc/ntfy/server.yml rmdir /etc/ntfy || true fi diff --git a/scripts/preinst.sh b/scripts/preinst.sh new file mode 100755 index 00000000..d09528c4 --- /dev/null +++ b/scripts/preinst.sh @@ -0,0 +1,11 @@ +#!/bin/sh +set -e + +if [ "$1" = "install" ] || [ "$1" = "upgrade" ]; then + # Migration of old to new config file name + oldconfigfile="/etc/ntfy/config.yml" + configfile="/etc/ntfy/server.yml" + if [ -f "$oldconfigfile" ] && [ ! -f "$configfile" ]; then + mv "$oldconfigfile" "$configfile" || true + fi +fi diff --git a/config/config.go b/server/config.go similarity index 96% rename from config/config.go rename to server/config.go index a78ec5a9..fac1658b 100644 --- a/config/config.go +++ b/server/config.go @@ -1,5 +1,4 @@ -// Package config provides the main configuration -package config +package server import ( "time" @@ -53,7 +52,7 @@ type Config struct { } // New instantiates a default new config -func New(listenHTTP string) *Config { +func NewConfig(listenHTTP string) *Config { return &Config{ ListenHTTP: listenHTTP, ListenHTTPS: "", diff --git a/config/config_test.go b/server/config_test.go similarity index 64% rename from config/config_test.go rename to server/config_test.go index d7282511..902549d5 100644 --- a/config/config_test.go +++ b/server/config_test.go @@ -1,12 +1,12 @@ -package config_test +package server_test import ( "github.com/stretchr/testify/assert" - "heckel.io/ntfy/config" + "heckel.io/ntfy/server" "testing" ) func TestConfig_New(t *testing.T) { - c := config.New(":1234") + c := server.NewConfig(":1234") assert.Equal(t, ":1234", c.ListenHTTP) } diff --git a/config/ntfy.service b/server/ntfy.service similarity index 100% rename from config/ntfy.service rename to server/ntfy.service diff --git a/server/server.go b/server/server.go index 30342098..d4b9f939 100644 --- a/server/server.go +++ b/server/server.go @@ -9,7 +9,6 @@ import ( "firebase.google.com/go/messaging" "fmt" "google.golang.org/api/option" - "heckel.io/ntfy/config" "heckel.io/ntfy/util" "html/template" "io" @@ -28,7 +27,7 @@ import ( // Server is the main server, providing the UI and API for ntfy type Server struct { - config *config.Config + config *Config topics map[string]*topic visitors map[string]*visitor firebase subscriber @@ -112,7 +111,7 @@ const ( // New instantiates a new Server. It creates the cache and adds a Firebase // subscriber (if configured). -func New(conf *config.Config) (*Server, error) { +func New(conf *Config) (*Server, error) { var firebaseSubscriber subscriber if conf.FirebaseKeyFile != "" { var err error @@ -138,7 +137,7 @@ func New(conf *config.Config) (*Server, error) { }, nil } -func createCache(conf *config.Config) (cache, error) { +func createCache(conf *Config) (cache, error) { if conf.CacheDuration == 0 { return newNopCache(), nil } else if conf.CacheFile != "" { @@ -147,7 +146,7 @@ func createCache(conf *config.Config) (cache, error) { return newMemCache(), nil } -func createFirebaseSubscriber(conf *config.Config) (subscriber, error) { +func createFirebaseSubscriber(conf *Config) (subscriber, error) { fb, err := firebase.NewApp(context.Background(), nil, option.WithCredentialsFile(conf.FirebaseKeyFile)) if err != nil { return nil, err diff --git a/config/config.yml b/server/server.yml similarity index 98% rename from config/config.yml rename to server/server.yml index 0829778c..1ecc5de6 100644 --- a/config/config.yml +++ b/server/server.yml @@ -1,4 +1,4 @@ -# ntfy config file +# ntfy server config file # Listen address for the HTTP & HTTPS web server. If "listen-https" is set, you must also # set "key-file" and "cert-file". diff --git a/server/visitor.go b/server/visitor.go index 7c23f89b..9d99e94a 100644 --- a/server/visitor.go +++ b/server/visitor.go @@ -2,7 +2,6 @@ package server import ( "golang.org/x/time/rate" - "heckel.io/ntfy/config" "heckel.io/ntfy/util" "sync" "time" @@ -14,14 +13,14 @@ const ( // visitor represents an API user, and its associated rate.Limiter used for rate limiting type visitor struct { - config *config.Config + config *Config limiter *rate.Limiter subscriptions *util.Limiter seen time.Time mu sync.Mutex } -func newVisitor(conf *config.Config) *visitor { +func newVisitor(conf *Config) *visitor { return &visitor{ config: conf, limiter: rate.NewLimiter(rate.Every(conf.VisitorRequestLimitReplenish), conf.VisitorRequestLimitBurst),