mirror of
https://github.com/binwiederhier/ntfy.git
synced 2026-11-10 14:44:04 +08:00
Merge remote-tracking branch 'origin/main' into up-payer
# Conflicts: # docs/releases.md
This commit is contained in:
commit
b65e642d05
16 files changed
+149
-97
No files matched your search
@@ -15,9 +15,5 @@ LABEL org.opencontainers.image.version="$VERSION"
|
||||
RUN apk add --no-cache tzdata
|
||||
COPY ntfy /usr/bin
|
||||
|
||||
# Admin socket directory, writable by any uid (sticky, like /tmp), so the admin socket also works
|
||||
# when the container runs as a non-root user (-u UID:GID); the socket itself is created with mode 0660
|
||||
RUN mkdir -p /run/ntfy && chmod 1777 /run/ntfy
|
||||
|
||||
EXPOSE 80/tcp
|
||||
ENTRYPOINT ["ntfy"]
|
||||
@@ -17,9 +17,5 @@ LABEL org.opencontainers.image.version="$VERSION"
|
||||
|
||||
COPY ntfy /usr/bin
|
||||
|
||||
# Admin socket directory, writable by any uid (sticky, like /tmp), so the admin socket also works
|
||||
# when the container runs as a non-root user (-u UID:GID); the socket itself is created with mode 0660
|
||||
RUN mkdir -p /run/ntfy && chmod 1777 /run/ntfy
|
||||
|
||||
EXPOSE 80/tcp
|
||||
ENTRYPOINT ["ntfy"]
|
||||
@@ -76,9 +76,5 @@ LABEL org.opencontainers.image.version="$VERSION"
|
||||
|
||||
COPY --from=builder /app/dist/ntfy_linux_server/ntfy /usr/bin/ntfy
|
||||
|
||||
# Admin socket directory, writable by any uid (sticky, like /tmp), so the admin socket also works
|
||||
# when the container runs as a non-root user (-u UID:GID); the socket itself is created with mode 0660
|
||||
RUN mkdir -p /run/ntfy && chmod 1777 /run/ntfy
|
||||
|
||||
EXPOSE 80/tcp
|
||||
ENTRYPOINT ["ntfy"]
|
||||
+2
-2
@@ -39,7 +39,7 @@ var flagsServe = append(
|
||||
altsrc.NewStringFlag(&cli.StringFlag{Name: "listen-https", Aliases: []string{"listen_https", "L"}, EnvVars: []string{"NTFY_LISTEN_HTTPS"}, Usage: "ip:port used as HTTPS listen address"}),
|
||||
altsrc.NewStringFlag(&cli.StringFlag{Name: "listen-unix", Aliases: []string{"listen_unix", "U"}, EnvVars: []string{"NTFY_LISTEN_UNIX"}, Usage: "listen on unix socket path"}),
|
||||
altsrc.NewIntFlag(&cli.IntFlag{Name: "listen-unix-mode", Aliases: []string{"listen_unix_mode"}, EnvVars: []string{"NTFY_LISTEN_UNIX_MODE"}, DefaultText: "system default", Usage: "file permissions of unix socket, e.g. 0700"}),
|
||||
altsrc.NewStringFlag(&cli.StringFlag{Name: "listen-admin-unix", Aliases: []string{"listen_admin_unix"}, EnvVars: []string{"NTFY_LISTEN_ADMIN_UNIX"}, Value: server.DefaultListenAdminUnix(), DefaultText: "/run/ntfy/admin.sock (empty on Windows)", Usage: "unix socket path for credential-less local admin access (ntfy user/access/token/tier); \"-\" disables it"}),
|
||||
altsrc.NewStringFlag(&cli.StringFlag{Name: "experimental-listen-admin-unix", Aliases: []string{"experimental_listen_admin_unix"}, EnvVars: []string{"NTFY_EXPERIMENTAL_LISTEN_ADMIN_UNIX"}, Usage: "experimental: unix socket path for credential-less local admin API access, e.g. /run/ntfy/admin.sock; empty or \"-\" disables it"}),
|
||||
altsrc.NewStringFlag(&cli.StringFlag{Name: "key-file", Aliases: []string{"key_file", "K"}, EnvVars: []string{"NTFY_KEY_FILE"}, Usage: "private key file, if listen-https is set"}),
|
||||
altsrc.NewStringFlag(&cli.StringFlag{Name: "cert-file", Aliases: []string{"cert_file", "E"}, EnvVars: []string{"NTFY_CERT_FILE"}, Usage: "certificate file, if listen-https is set"}),
|
||||
altsrc.NewStringFlag(&cli.StringFlag{Name: "firebase-key-file", Aliases: []string{"firebase_key_file", "F"}, EnvVars: []string{"NTFY_FIREBASE_KEY_FILE"}, Usage: "Firebase credentials file; if set additionally publish to FCM topic"}),
|
||||
@@ -162,7 +162,7 @@ func execServe(c *cli.Context) error {
|
||||
listenHTTPS := c.String("listen-https")
|
||||
listenUnix := c.String("listen-unix")
|
||||
listenUnixMode := c.Int("listen-unix-mode")
|
||||
listenAdminUnix := c.String("listen-admin-unix")
|
||||
listenAdminUnix := c.String("experimental-listen-admin-unix")
|
||||
keyFile := c.String("key-file")
|
||||
certFile := c.String("cert-file")
|
||||
firebaseKeyFile := c.String("firebase-key-file")
|
||||
|
||||
+16
-1
@@ -498,7 +498,7 @@ func TestCLI_Serve_ListenAdminUnixDisabled(t *testing.T) {
|
||||
configFile := newEmptyFile(t)
|
||||
go func() {
|
||||
app, _, _, _ := newTestApp()
|
||||
err := app.Run([]string{"ntfy", "serve", "--config=" + configFile, "--listen-http=-", "--listen-unix=" + sockFile, "--auth-file=" + filepath.Join(dir, "user.db"), "--listen-admin-unix=-"})
|
||||
err := app.Run([]string{"ntfy", "serve", "--config=" + configFile, "--listen-http=-", "--listen-unix=" + sockFile, "--auth-file=" + filepath.Join(dir, "user.db"), "--experimental-listen-admin-unix=-"})
|
||||
require.Nil(t, err)
|
||||
}()
|
||||
for i := 0; i < 40 && !util.FileExists(sockFile); i++ {
|
||||
@@ -508,6 +508,21 @@ func TestCLI_Serve_ListenAdminUnixDisabled(t *testing.T) {
|
||||
require.False(t, util.FileExists(filepath.Join(dir, "-")))
|
||||
}
|
||||
|
||||
func TestCLI_Serve_ExperimentalListenAdminUnix(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
adminSockFile := filepath.Join(dir, "admin.sock")
|
||||
configFile := newEmptyFile(t)
|
||||
go func() {
|
||||
app, _, _, _ := newTestApp()
|
||||
err := app.Run([]string{"ntfy", "serve", "--config=" + configFile, "--listen-http=-", "--listen-unix=" + filepath.Join(dir, "ntfy.sock"), "--auth-file=" + filepath.Join(dir, "user.db"), "--experimental-listen-admin-unix=" + adminSockFile})
|
||||
require.Nil(t, err)
|
||||
}()
|
||||
for i := 0; i < 40 && !util.FileExists(adminSockFile); i++ {
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
}
|
||||
require.True(t, util.FileExists(adminSockFile))
|
||||
}
|
||||
|
||||
func TestCLI_Serve_WebSocket(t *testing.T) {
|
||||
port := 10000 + rand.Intn(20000)
|
||||
go func() {
|
||||
|
||||
+12
-17
@@ -919,27 +919,22 @@ defines access tokens for these users. `phil` has a token `tk_3gd7d2yftt4b8ixyfe
|
||||
has a token `tk_f099we8uzj7xi5qshzajwp6jffvkz` with the label "Backup script".
|
||||
|
||||
### Admin socket
|
||||
The `ntfy user`, `ntfy access`, `ntfy token` and `ntfy tier` commands talk to the running ntfy server through a local
|
||||
admin socket, by default `/run/ntfy/admin.sock` (`/var/run/ntfy/admin.sock` on macOS). That way, changes take effect
|
||||
immediately: a removed user is disconnected right away, a new tier applies to the user's next request, and there is no
|
||||
delay until the server reloads its access control list.
|
||||
!!! warning
|
||||
The admin socket is **experimental**. Options and behavior may still change between releases. The `ntfy user`,
|
||||
`ntfy access`, `ntfy token` and `ntfy tier` commands do not use it yet; they still write to the database directly.
|
||||
|
||||
ntfy can serve its admin API on a local unix socket, set with `experimental-listen-admin-unix` (e.g.
|
||||
`/run/ntfy/admin.sock`). It is off by default.
|
||||
|
||||
The socket needs no password or token: **being able to open the socket is the authorization**. It is created with
|
||||
mode `0660` in a directory with mode `0750`, both owned by the user ntfy runs as, so only that user, members of its group
|
||||
and `root` can use it. With the Debian/Ubuntu/Fedora packages, the shipped systemd unit creates `/run/ntfy` (via
|
||||
`RuntimeDirectory=ntfy`), so `sudo ntfy user add ...` and members of the `ntfy` group just work.
|
||||
`RuntimeDirectory=ntfy`), so a socket there just works.
|
||||
|
||||
The socket is only opened if [access control](#access-control) is configured (`auth-file` or `database-url`). If the
|
||||
server cannot create the socket, it logs a warning at startup and runs without it:
|
||||
|
||||
```
|
||||
WARN Admin socket /run/ntfy/admin.sock not available, 'ntfy user/access/token/tier' will write to the database directly, which is deprecated, see https://ntfy.sh/docs/deprecations/ (error=mkdir /run/ntfy: permission denied, tag=startup)
|
||||
```
|
||||
|
||||
To change the path, set `listen-admin-unix` in the `server.yml` file; the commands read it from there, too. To disable the
|
||||
socket, set `listen-admin-unix: "-"`. The admin socket is not available on Windows. If you change the path, pick a
|
||||
directory that only the ntfy user can write to; otherwise the server logs a warning at startup, since other users may be
|
||||
able to use or replace the socket.
|
||||
server cannot create the socket, it logs a warning at startup and runs without it. The admin socket is not available on
|
||||
Windows. Pick a directory that only the ntfy user can write to; otherwise the server logs a warning at startup, since
|
||||
other users may be able to use or replace the socket.
|
||||
|
||||
### Example: Private instance
|
||||
The easiest way to configure a private instance is to set `auth-default-access` to `deny-all` in the `server.yml`,
|
||||
@@ -2554,7 +2549,7 @@ variable before running the `ntfy` command (e.g. `export NTFY_LISTEN_HTTP=:80`).
|
||||
| `listen-https` | `NTFY_LISTEN_HTTPS` | `[host]:port` | - | Listen address for the HTTPS web server. If set, you also need to set `key-file` and `cert-file`. |
|
||||
| `listen-unix` | `NTFY_LISTEN_UNIX` | *filename* | - | Path to a Unix socket to listen on |
|
||||
| `listen-unix-mode` | `NTFY_LISTEN_UNIX_MODE` | *file mode* | *system default* | File mode of the Unix socket, e.g. 0700 or 0777 |
|
||||
| `listen-admin-unix` | `NTFY_LISTEN_ADMIN_UNIX` | *filename* | `/run/ntfy/admin.sock` | Path of the local [admin socket](#admin-socket) the `ntfy user/access/token/tier` commands use; `"-"` disables it. Only opened if auth is configured; not available on Windows |
|
||||
| `experimental-listen-admin-unix` | `NTFY_EXPERIMENTAL_LISTEN_ADMIN_UNIX` | *filename* | - | Experimental. Path of the local [admin socket](#admin-socket), e.g. `/run/ntfy/admin.sock`; off if empty or `"-"`. Only opened if auth is configured; not available on Windows |
|
||||
| `key-file` | `NTFY_KEY_FILE` | *filename* | - | HTTPS/TLS private key file, only used if `listen-https` is set. |
|
||||
| `cert-file` | `NTFY_CERT_FILE` | *filename* | - | HTTPS/TLS certificate file, only used if `listen-https` is set. |
|
||||
| `firebase-key-file` | `NTFY_FIREBASE_KEY_FILE` | *filename* | - | If set, also publish messages to a Firebase Cloud Messaging (FCM) topic for your app. This is optional and only required to save battery when using the Android app. See [Firebase (FCM)](#firebase-fcm). |
|
||||
@@ -2677,7 +2672,7 @@ OPTIONS:
|
||||
--listen-https value, --listen_https value, -L value ip:port used as HTTPS listen address [$NTFY_LISTEN_HTTPS]
|
||||
--listen-unix value, --listen_unix value, -U value listen on unix socket path [$NTFY_LISTEN_UNIX]
|
||||
--listen-unix-mode value, --listen_unix_mode value file permissions of unix socket, e.g. 0700 (default: system default) [$NTFY_LISTEN_UNIX_MODE]
|
||||
--listen-admin-unix value, --listen_admin_unix value unix socket path for credential-less local admin access (ntfy user/access/token/tier); "-" disables it (default: /run/ntfy/admin.sock (empty on Windows)) [$NTFY_LISTEN_ADMIN_UNIX]
|
||||
--experimental-listen-admin-unix value, --experimental_listen_admin_unix value experimental: unix socket path for credential-less local admin API access, e.g. /run/ntfy/admin.sock; empty or "-" disables it [$NTFY_EXPERIMENTAL_LISTEN_ADMIN_UNIX]
|
||||
--key-file value, --key_file value, -K value private key file, if listen-https is set [$NTFY_KEY_FILE]
|
||||
--cert-file value, --cert_file value, -E value certificate file, if listen-https is set [$NTFY_CERT_FILE]
|
||||
--firebase-key-file value, --firebase_key_file value, -F value Firebase credentials file; if set additionally publish to FCM topic [$NTFY_FIREBASE_KEY_FILE]
|
||||
|
||||
+38
-38
@@ -34,37 +34,37 @@ as a service starting at boot time.
|
||||
|
||||
=== "x86_64/amd64"
|
||||
```bash
|
||||
wget https://github.com/binwiederhier/ntfy/releases/download/v2.35.0/ntfy_2.35.0_linux_amd64.tar.gz
|
||||
tar zxvf ntfy_2.35.0_linux_amd64.tar.gz
|
||||
sudo cp -a ntfy_2.35.0_linux_amd64/ntfy /usr/local/bin/ntfy
|
||||
sudo mkdir /etc/ntfy && sudo cp ntfy_2.35.0_linux_amd64/{client,server}/*.yml /etc/ntfy
|
||||
wget https://github.com/binwiederhier/ntfy/releases/download/v2.36.0/ntfy_2.36.0_linux_amd64.tar.gz
|
||||
tar zxvf ntfy_2.36.0_linux_amd64.tar.gz
|
||||
sudo cp -a ntfy_2.36.0_linux_amd64/ntfy /usr/local/bin/ntfy
|
||||
sudo mkdir /etc/ntfy && sudo cp ntfy_2.36.0_linux_amd64/{client,server}/*.yml /etc/ntfy
|
||||
sudo ntfy serve
|
||||
```
|
||||
|
||||
=== "armv6"
|
||||
```bash
|
||||
wget https://github.com/binwiederhier/ntfy/releases/download/v2.35.0/ntfy_2.35.0_linux_armv6.tar.gz
|
||||
tar zxvf ntfy_2.35.0_linux_armv6.tar.gz
|
||||
sudo cp -a ntfy_2.35.0_linux_armv6/ntfy /usr/bin/ntfy
|
||||
sudo mkdir /etc/ntfy && sudo cp ntfy_2.35.0_linux_armv6/{client,server}/*.yml /etc/ntfy
|
||||
wget https://github.com/binwiederhier/ntfy/releases/download/v2.36.0/ntfy_2.36.0_linux_armv6.tar.gz
|
||||
tar zxvf ntfy_2.36.0_linux_armv6.tar.gz
|
||||
sudo cp -a ntfy_2.36.0_linux_armv6/ntfy /usr/bin/ntfy
|
||||
sudo mkdir /etc/ntfy && sudo cp ntfy_2.36.0_linux_armv6/{client,server}/*.yml /etc/ntfy
|
||||
sudo ntfy serve
|
||||
```
|
||||
|
||||
=== "armv7/armhf"
|
||||
```bash
|
||||
wget https://github.com/binwiederhier/ntfy/releases/download/v2.35.0/ntfy_2.35.0_linux_armv7.tar.gz
|
||||
tar zxvf ntfy_2.35.0_linux_armv7.tar.gz
|
||||
sudo cp -a ntfy_2.35.0_linux_armv7/ntfy /usr/bin/ntfy
|
||||
sudo mkdir /etc/ntfy && sudo cp ntfy_2.35.0_linux_armv7/{client,server}/*.yml /etc/ntfy
|
||||
wget https://github.com/binwiederhier/ntfy/releases/download/v2.36.0/ntfy_2.36.0_linux_armv7.tar.gz
|
||||
tar zxvf ntfy_2.36.0_linux_armv7.tar.gz
|
||||
sudo cp -a ntfy_2.36.0_linux_armv7/ntfy /usr/bin/ntfy
|
||||
sudo mkdir /etc/ntfy && sudo cp ntfy_2.36.0_linux_armv7/{client,server}/*.yml /etc/ntfy
|
||||
sudo ntfy serve
|
||||
```
|
||||
|
||||
=== "arm64"
|
||||
```bash
|
||||
wget https://github.com/binwiederhier/ntfy/releases/download/v2.35.0/ntfy_2.35.0_linux_arm64.tar.gz
|
||||
tar zxvf ntfy_2.35.0_linux_arm64.tar.gz
|
||||
sudo cp -a ntfy_2.35.0_linux_arm64/ntfy /usr/bin/ntfy
|
||||
sudo mkdir /etc/ntfy && sudo cp ntfy_2.35.0_linux_arm64/{client,server}/*.yml /etc/ntfy
|
||||
wget https://github.com/binwiederhier/ntfy/releases/download/v2.36.0/ntfy_2.36.0_linux_arm64.tar.gz
|
||||
tar zxvf ntfy_2.36.0_linux_arm64.tar.gz
|
||||
sudo cp -a ntfy_2.36.0_linux_arm64/ntfy /usr/bin/ntfy
|
||||
sudo mkdir /etc/ntfy && sudo cp ntfy_2.36.0_linux_arm64/{client,server}/*.yml /etc/ntfy
|
||||
sudo ntfy serve
|
||||
```
|
||||
|
||||
@@ -84,25 +84,25 @@ Install the ntfy server unit file (which contains parameters to start the servic
|
||||
|
||||
=== "x86_64/amd64"
|
||||
```bash
|
||||
sudo mv ntfy_2.35.0_linux_amd64/server/ntfy.service /etc/systemd/system/
|
||||
sudo mv ntfy_2.36.0_linux_amd64/server/ntfy.service /etc/systemd/system/
|
||||
sudo chmod 644 /etc/systemd/system/ntfy.service
|
||||
```
|
||||
|
||||
=== "armv6"
|
||||
```bash
|
||||
sudo mv ntfy_2.35.0_linux_armv6/server/ntfy.service /etc/systemd/system/
|
||||
sudo mv ntfy_2.36.0_linux_armv6/server/ntfy.service /etc/systemd/system/
|
||||
sudo chmod 644 /etc/systemd/system/ntfy.service
|
||||
```
|
||||
|
||||
=== "armv7/armhf"
|
||||
```bash
|
||||
sudo mv ntfy_2.35.0_linux_armv7/server/ntfy.service /etc/systemd/system/
|
||||
sudo mv ntfy_2.36.0_linux_armv7/server/ntfy.service /etc/systemd/system/
|
||||
sudo chmod 644 /etc/systemd/system/ntfy.service
|
||||
```
|
||||
|
||||
=== "arm64"
|
||||
```bash
|
||||
sudo mv ntfy_2.35.0_linux_arm64/server/ntfy.service /etc/systemd/system/
|
||||
sudo mv ntfy_2.36.0_linux_arm64/server/ntfy.service /etc/systemd/system/
|
||||
sudo chmod 644 /etc/systemd/system/ntfy.service
|
||||
```
|
||||
|
||||
@@ -118,25 +118,25 @@ Install the ntfy server service script:
|
||||
|
||||
=== "x86_64/amd64"
|
||||
```bash
|
||||
sudo mv ntfy_2.35.0_linux_amd64/server/ntfy.openrc /etc/init.d/ntfy
|
||||
sudo mv ntfy_2.36.0_linux_amd64/server/ntfy.openrc /etc/init.d/ntfy
|
||||
sudo chmod 755 /etc/init.d/ntfy
|
||||
```
|
||||
|
||||
=== "armv6"
|
||||
```bash
|
||||
sudo mv ntfy_2.35.0_linux_armv6/server/ntfy.openrc /etc/init.d/ntfy
|
||||
sudo mv ntfy_2.36.0_linux_armv6/server/ntfy.openrc /etc/init.d/ntfy
|
||||
sudo chmod 755 /etc/init.d/ntfy
|
||||
```
|
||||
|
||||
=== "armv7/armhf"
|
||||
```bash
|
||||
sudo mv ntfy_2.35.0_linux_armv7/server/ntfy.openrc /etc/init.d/ntfy
|
||||
sudo mv ntfy_2.36.0_linux_armv7/server/ntfy.openrc /etc/init.d/ntfy
|
||||
sudo chmod 755 /etc/init.d/ntfy
|
||||
```
|
||||
|
||||
=== "arm64"
|
||||
```bash
|
||||
sudo mv ntfy_2.35.0_linux_arm64/server/ntfy.openrc /etc/init.d/ntfy
|
||||
sudo mv ntfy_2.36.0_linux_arm64/server/ntfy.openrc /etc/init.d/ntfy
|
||||
sudo chmod 755 /etc/init.d/ntfy
|
||||
```
|
||||
|
||||
@@ -204,7 +204,7 @@ Manually installing the .deb file:
|
||||
|
||||
=== "x86_64/amd64"
|
||||
```bash
|
||||
wget https://github.com/binwiederhier/ntfy/releases/download/v2.35.0/ntfy_2.35.0_linux_amd64.deb
|
||||
wget https://github.com/binwiederhier/ntfy/releases/download/v2.36.0/ntfy_2.36.0_linux_amd64.deb
|
||||
sudo dpkg -i ntfy_*.deb
|
||||
sudo systemctl enable ntfy
|
||||
sudo systemctl start ntfy
|
||||
@@ -212,7 +212,7 @@ Manually installing the .deb file:
|
||||
|
||||
=== "armv6"
|
||||
```bash
|
||||
wget https://github.com/binwiederhier/ntfy/releases/download/v2.35.0/ntfy_2.35.0_linux_armv6.deb
|
||||
wget https://github.com/binwiederhier/ntfy/releases/download/v2.36.0/ntfy_2.36.0_linux_armv6.deb
|
||||
sudo dpkg -i ntfy_*.deb
|
||||
sudo systemctl enable ntfy
|
||||
sudo systemctl start ntfy
|
||||
@@ -220,7 +220,7 @@ Manually installing the .deb file:
|
||||
|
||||
=== "armv7/armhf"
|
||||
```bash
|
||||
wget https://github.com/binwiederhier/ntfy/releases/download/v2.35.0/ntfy_2.35.0_linux_armv7.deb
|
||||
wget https://github.com/binwiederhier/ntfy/releases/download/v2.36.0/ntfy_2.36.0_linux_armv7.deb
|
||||
sudo dpkg -i ntfy_*.deb
|
||||
sudo systemctl enable ntfy
|
||||
sudo systemctl start ntfy
|
||||
@@ -228,7 +228,7 @@ Manually installing the .deb file:
|
||||
|
||||
=== "arm64"
|
||||
```bash
|
||||
wget https://github.com/binwiederhier/ntfy/releases/download/v2.35.0/ntfy_2.35.0_linux_arm64.deb
|
||||
wget https://github.com/binwiederhier/ntfy/releases/download/v2.36.0/ntfy_2.36.0_linux_arm64.deb
|
||||
sudo dpkg -i ntfy_*.deb
|
||||
sudo systemctl enable ntfy
|
||||
sudo systemctl start ntfy
|
||||
@@ -238,28 +238,28 @@ Manually installing the .deb file:
|
||||
|
||||
=== "x86_64/amd64"
|
||||
```bash
|
||||
sudo rpm -ivh https://github.com/binwiederhier/ntfy/releases/download/v2.35.0/ntfy_2.35.0_linux_amd64.rpm
|
||||
sudo rpm -ivh https://github.com/binwiederhier/ntfy/releases/download/v2.36.0/ntfy_2.36.0_linux_amd64.rpm
|
||||
sudo systemctl enable ntfy
|
||||
sudo systemctl start ntfy
|
||||
```
|
||||
|
||||
=== "armv6"
|
||||
```bash
|
||||
sudo rpm -ivh https://github.com/binwiederhier/ntfy/releases/download/v2.35.0/ntfy_2.35.0_linux_armv6.rpm
|
||||
sudo rpm -ivh https://github.com/binwiederhier/ntfy/releases/download/v2.36.0/ntfy_2.36.0_linux_armv6.rpm
|
||||
sudo systemctl enable ntfy
|
||||
sudo systemctl start ntfy
|
||||
```
|
||||
|
||||
=== "armv7/armhf"
|
||||
```bash
|
||||
sudo rpm -ivh https://github.com/binwiederhier/ntfy/releases/download/v2.35.0/ntfy_2.35.0_linux_armv7.rpm
|
||||
sudo rpm -ivh https://github.com/binwiederhier/ntfy/releases/download/v2.36.0/ntfy_2.36.0_linux_armv7.rpm
|
||||
sudo systemctl enable ntfy
|
||||
sudo systemctl start ntfy
|
||||
```
|
||||
|
||||
=== "arm64"
|
||||
```bash
|
||||
sudo rpm -ivh https://github.com/binwiederhier/ntfy/releases/download/v2.35.0/ntfy_2.35.0_linux_arm64.rpm
|
||||
sudo rpm -ivh https://github.com/binwiederhier/ntfy/releases/download/v2.36.0/ntfy_2.36.0_linux_arm64.rpm
|
||||
sudo systemctl enable ntfy
|
||||
sudo systemctl start ntfy
|
||||
```
|
||||
@@ -301,18 +301,18 @@ pkg install go-ntfy
|
||||
|
||||
## macOS
|
||||
The [ntfy CLI](subscribe/cli.md) (`ntfy publish` and `ntfy subscribe` only) is supported on macOS as well.
|
||||
To install, please [download the tarball](https://github.com/binwiederhier/ntfy/releases/download/v2.35.0/ntfy_2.35.0_darwin_all.tar.gz),
|
||||
To install, please [download the tarball](https://github.com/binwiederhier/ntfy/releases/download/v2.36.0/ntfy_2.36.0_darwin_all.tar.gz),
|
||||
extract it and place it somewhere in your `PATH` (e.g. `/usr/local/bin/ntfy`).
|
||||
|
||||
If run as `root`, ntfy will look for its config at `/etc/ntfy/client.yml`. For all other users, it'll look for it at
|
||||
`~/Library/Application Support/ntfy/client.yml` (sample included in the tarball).
|
||||
|
||||
```bash
|
||||
curl -L https://github.com/binwiederhier/ntfy/releases/download/v2.35.0/ntfy_2.35.0_darwin_all.tar.gz > ntfy_2.35.0_darwin_all.tar.gz
|
||||
tar zxvf ntfy_2.35.0_darwin_all.tar.gz
|
||||
sudo cp -a ntfy_2.35.0_darwin_all/ntfy /usr/local/bin/ntfy
|
||||
curl -L https://github.com/binwiederhier/ntfy/releases/download/v2.36.0/ntfy_2.36.0_darwin_all.tar.gz > ntfy_2.36.0_darwin_all.tar.gz
|
||||
tar zxvf ntfy_2.36.0_darwin_all.tar.gz
|
||||
sudo cp -a ntfy_2.36.0_darwin_all/ntfy /usr/local/bin/ntfy
|
||||
mkdir ~/Library/Application\ Support/ntfy
|
||||
cp ntfy_2.35.0_darwin_all/client/client.yml ~/Library/Application\ Support/ntfy/client.yml
|
||||
cp ntfy_2.36.0_darwin_all/client/client.yml ~/Library/Application\ Support/ntfy/client.yml
|
||||
ntfy --help
|
||||
```
|
||||
|
||||
@@ -333,7 +333,7 @@ brew install ntfy
|
||||
The ntfy server and CLI are fully supported on Windows. You can run the ntfy server directly or as a Windows service.
|
||||
To install, you can either
|
||||
|
||||
* [Download the latest ZIP](https://github.com/binwiederhier/ntfy/releases/download/v2.35.0/ntfy_2.35.0_windows_amd64.zip),
|
||||
* [Download the latest ZIP](https://github.com/binwiederhier/ntfy/releases/download/v2.36.0/ntfy_2.36.0_windows_amd64.zip),
|
||||
extract it and place the `ntfy.exe` binary somewhere in your `%Path%`.
|
||||
* Or install ntfy from the [Scoop](https://scoop.sh) main repository via `scoop install ntfy`
|
||||
|
||||
|
||||
+30
-14
@@ -6,12 +6,38 @@ and the [ntfy Android app](https://github.com/binwiederhier/ntfy-android/release
|
||||
|
||||
| Component | Version | Release date |
|
||||
|------------------|---------|---------------|
|
||||
| ntfy server | v2.35.0 | Oct 30, 2026 |
|
||||
| ntfy server | v2.36.0 | Oct 31, 2026 |
|
||||
| ntfy Android app | v1.25.2 | July 23, 2026 |
|
||||
| ntfy iOS app | v1.7.0 | May 30, 2026 |
|
||||
|
||||
Please check out the release notes for [upcoming releases](#not-released-yet) below.
|
||||
|
||||
## ntfy server v2.36.0
|
||||
Released October 31, 2026
|
||||
|
||||
This release keeps the daily limits across restarts and, with PostgreSQL, shares them between servers, so a visitor
|
||||
gets one daily allowance in total. Each visitor's day now starts at its own time instead of midnight UTC. It also adds
|
||||
an experimental local admin socket.
|
||||
|
||||
**Features:**
|
||||
|
||||
* Daily limits (messages, e-mails, phone calls) are stored in the user database, so they survive restarts; with PostgreSQL, all servers share them. Each visitor's day starts at its own time instead of midnight UTC, and the account page and `/v1/account` (`stats.reset_at`) show when it resets ([#2131](https://github.com/binwiederhier/ntfy/pull/2131), see [message limits](config.md#message-limits))
|
||||
* Experimental local admin socket (`experimental-listen-admin-unix`, off by default) that serves the admin API without credentials to whoever can open it; the shipped systemd unit sets `RuntimeDirectory=ntfy` for it (see [admin socket](config.md#admin-socket))
|
||||
* Export `ntfy_db_up` per PostgreSQL pool, and health-check the primary like the replicas ([#2133](https://github.com/binwiederhier/ntfy/pull/2133))
|
||||
|
||||
**Upgrade notes:**
|
||||
|
||||
* Everyone's daily counts start over at the upgrade: the `stats_*` columns of the user table are zeroed and no longer used
|
||||
* Servers that share a PostgreSQL database must keep their clocks in sync (NTP)
|
||||
* Visitors without a tier have a daily e-mail limit again (24h / `visitor-email-limit-replenish`)
|
||||
* The whole server config is checked again when `base-url` or a `message-size-limit` above 4K is set, so an invalid config that used to start now fails at startup ([#1969](https://github.com/binwiederhier/ntfy/pull/1969), thanks to [@mikejgray](https://github.com/mikejgray) for the contribution)
|
||||
|
||||
**Bug fixes + maintenance:**
|
||||
|
||||
* Web app: Messages replayed by a poll or a reconnect no longer bring back deleted notifications, lose their read state or notify again ([#2098](https://github.com/binwiederhier/ntfy/issues/2098), [#2090](https://github.com/binwiederhier/ntfy/issues/2090), thanks to [@mattford63](https://github.com/mattford63), [@arminus](https://github.com/arminus) and [@tobykaspar79](https://github.com/tobykaspar79) for reporting)
|
||||
* CLI: `ntfy pub --wait-pid` now waits for processes owned by other users instead of failing with "not running" ([#1710](https://github.com/binwiederhier/ntfy/pull/1710), thanks to [@bekoeppel](https://github.com/bekoeppel) for the contribution)
|
||||
* Admin API (internal): manage other users' tokens, tiers and password reset links, return 4xx instead of 500 for invalid input, and remove a deleted user's web push subscriptions
|
||||
|
||||
## ntfy server v2.35.0
|
||||
Released October 30, 2026
|
||||
|
||||
@@ -2288,31 +2314,21 @@ and the [ntfy Android app](https://github.com/binwiederhier/ntfy-android/release
|
||||
|
||||
## Not released yet
|
||||
|
||||
### ntfy server v2.36.0 (UNRELEASED)
|
||||
### ntfy server v2.36.1 (UNRELEASED)
|
||||
|
||||
**Features:**
|
||||
|
||||
* Daily limits (messages, e-mails, phone calls) are kept in the user database (`auth-file` for SQLite, or `database-url` for PostgreSQL) and survive restarts; with PostgreSQL, all servers share them, so a visitor gets one daily allowance in total, not one per server (see [message limits](config.md#message-limits))
|
||||
* Local admin socket (`listen-admin-unix`, default `/run/ntfy/admin.sock`) that serves the admin API without credentials to whoever can open it (the ntfy user and group, and root); the shipped systemd unit now sets `RuntimeDirectory=ntfy` so the server can create it. Custom unit files without it log a warning at startup and keep working as before (see [admin socket](config.md#admin-socket))
|
||||
* Admin API (internal, undocumented): manage tokens of other users, tiers and password reset links
|
||||
* Export `ntfy_db_up` (1 if the last health check of a PostgreSQL pool's database succeeded, per pool) and health-check the primary like the replicas, to alert on a server that is cut off from its database (see [PostgreSQL](config.md#postgresql))
|
||||
* With `visitor-subscriber-rate-limiting`, the subscriber of a UnifiedPush topic (who pays for its pushes) is kept in the user database and shared by all servers, so a push to any server is billed to the subscriber instead of answered with 507 (see [subscriber-based rate limiting](config.md#subscriber-based-rate-limiting))
|
||||
|
||||
**Behavior changes:**
|
||||
|
||||
* Daily limits now reset on a rolling 24-hour period per account or IP address instead of for everyone at midnight UTC: each visitor's period starts at its own time of day, which spreads the resets over the day. The account page and the `/v1/account` API (`stats.reset_at`) show when the visitor's counts reset
|
||||
* Visitors without a tier have a daily e-mail limit again (24h / `visitor-email-limit-replenish`), now that it is shared between servers and survives restarts
|
||||
* The daily reset job is gone; the `stats_*` columns of the user table are zeroed by the upgrade and no longer read or written
|
||||
* With `visitor-subscriber-rate-limiting`, a UnifiedPush push to a topic that has had no subscriber (subscribe, poll or open stream) for 7 days is answered with `404` (error code 40402) instead of `507`, so app servers like Mastodon remove the expired subscription ([#664](https://github.com/binwiederhier/ntfy/issues/664)); a Matrix push to such a topic gets its pushkey rejected (before: after 12 hours without a subscriber on the server that got the push)
|
||||
* With `visitor-subscriber-rate-limiting`, UnifiedPush messages are counted against the subscriber's account only while it has a tier, and against the subscriber's IP address otherwise (also after the account lost its tier or was deleted); a tier change applies within a minute
|
||||
|
||||
**Bug fixes + maintenance:**
|
||||
|
||||
* Admin API: return 4xx errors instead of a 500 for invalid input, and remove a deleted user's web push subscriptions
|
||||
* Web app: Deleted notifications no longer come back when a poll replays messages published in the same second as the last one ([#2098](https://github.com/binwiederhier/ntfy/issues/2098), thanks to [@mattford63](https://github.com/mattford63) and [@arminus](https://github.com/arminus) for reporting)
|
||||
* Web app: A message replayed after a reconnect keeps its read state and no longer notifies again, and an older version of a sequence no longer replaces a newer one ([#2090](https://github.com/binwiederhier/ntfy/issues/2090), thanks to [@tobykaspar79](https://github.com/tobykaspar79) for reporting)
|
||||
* Check the whole server config again when `base-url` or a `message-size-limit` above 4K is set; most checks after them were skipped, so an invalid config (e.g. `upstream-base-url` equal to `base-url`) now fails to start ([#1969](https://github.com/binwiederhier/ntfy/pull/1969), thanks to [@mikejgray](https://github.com/mikejgray) for the contribution)
|
||||
* CLI: `ntfy pub --wait-pid` now waits for processes owned by other users instead of failing with "not running" ([#1710](https://github.com/binwiederhier/ntfy/pull/1710), thanks to [@bekoeppel](https://github.com/bekoeppel) for the contribution)
|
||||
* With PostgreSQL and several servers, daily usage a server counted in the last seconds of a visitor's 24-hour period is no longer added to the next period (a double count); it is dropped instead
|
||||
* Cluster: a forwarded message for a topic without subscribers on the receiving node now counts as wasted (`ntfy_cluster_messages_wasted_total`) and no longer keeps that idle topic in memory
|
||||
|
||||
### ntfy iOS app v1.8.0 (UNRELEASED)
|
||||
|
||||
|
||||
+1
-1
@@ -272,7 +272,7 @@ func NewConfig() *Config {
|
||||
ListenHTTPS: "",
|
||||
ListenUnix: "",
|
||||
ListenUnixMode: 0,
|
||||
ListenAdminUnix: DefaultListenAdminUnix(),
|
||||
ListenAdminUnix: "", // Experimental, off unless configured
|
||||
KeyFile: "",
|
||||
CertFile: "",
|
||||
DatabaseURL: "",
|
||||
|
||||
@@ -11,6 +11,7 @@ func TestConfig_New(t *testing.T) {
|
||||
c := server.NewConfig()
|
||||
assert.Equal(t, []string{server.DefaultListenHTTP}, c.ListenHTTP)
|
||||
assert.Equal(t, server.DefaultKeepaliveInterval, c.KeepaliveInterval)
|
||||
assert.Equal(t, "", c.ListenAdminUnix) // Experimental, off unless configured
|
||||
}
|
||||
|
||||
func TestConfig_HashExcludesSecrets(t *testing.T) {
|
||||
|
||||
+2
-2
@@ -563,8 +563,8 @@ func (s *Server) Run() error {
|
||||
s.adminUnixListener, err = s.listenAdminSocket()
|
||||
if err != nil {
|
||||
s.mu.Unlock()
|
||||
// Not fatal, unlike the other listeners: the CLI falls back to writing to the database (deprecated)
|
||||
log.Tag(tagStartup).Err(err).Warn("Admin socket %s not available, 'ntfy user/access/token/tier' will write to the database directly, which is deprecated, see https://ntfy.sh/docs/deprecations/", s.config.ListenAdminUnix)
|
||||
// Not fatal, unlike the other listeners: nothing depends on the (experimental) admin socket yet
|
||||
log.Tag(tagStartup).Err(err).Warn("Admin socket %s not available", s.config.ListenAdminUnix)
|
||||
return
|
||||
}
|
||||
defer s.adminUnixListener.Close()
|
||||
|
||||
+3
-4
@@ -31,12 +31,11 @@
|
||||
# listen-unix: <socket-path>
|
||||
# listen-unix-mode: <linux permissions, e.g. 0700>
|
||||
|
||||
# Local admin socket for the "ntfy user", "ntfy access", "ntfy token" and "ntfy tier" commands. Whoever can open
|
||||
# the socket (mode 0660: the ntfy user and group, and root) can manage users without a password. The commands
|
||||
# fall back to writing to the database directly if no server listens on it. Set to "-" to disable.
|
||||
# Experimental: local admin socket that serves the admin API without credentials. Whoever can open the
|
||||
# socket (mode 0660: the ntfy user and group, and root) can manage users without a password. Off by default.
|
||||
# The socket is only opened if auth-file or database-url is set. Not available on Windows.
|
||||
#
|
||||
# listen-admin-unix: "/run/ntfy/admin.sock"
|
||||
# experimental-listen-admin-unix: "/run/ntfy/admin.sock"
|
||||
|
||||
# Path to the private key & cert file for the HTTPS web server. Not used if "listen-https" is not set.
|
||||
#
|
||||
|
||||
@@ -527,7 +527,7 @@ func (s *Server) handleAdminTiersDelete(w http.ResponseWriter, r *http.Request,
|
||||
|
||||
// listenAdminSocket creates the socket's directory, removes a stale socket file (one with no
|
||||
// server behind it) and listens. Anything at the path that is not a socket is left alone, in case
|
||||
// listen-admin-unix points somewhere it should not (e.g. at the database). A socket that answers
|
||||
// experimental-listen-admin-unix points somewhere it should not (e.g. at the database). A socket that answers
|
||||
// belongs to another running server; in that case this one does not take it over.
|
||||
func (s *Server) listenAdminSocket() (net.Listener, error) {
|
||||
path := s.config.ListenAdminUnix
|
||||
@@ -542,7 +542,7 @@ func (s *Server) listenAdminSocket() (net.Listener, error) {
|
||||
conn.Close()
|
||||
return nil, fmt.Errorf("another server is listening on %s", path)
|
||||
} else if errors.Is(err, fs.ErrPermission) {
|
||||
return nil, fmt.Errorf("%s belongs to a server of another user, set listen-admin-unix to a path of your own", path)
|
||||
return nil, fmt.Errorf("%s belongs to a server of another user, set experimental-listen-admin-unix to a path of your own", path)
|
||||
}
|
||||
if stat, err := os.Lstat(path); err == nil {
|
||||
if stat.Mode().Type() != fs.ModeSocket {
|
||||
|
||||
@@ -1458,8 +1458,8 @@ func TestAdminSocket_UnwritableDirectory(t *testing.T) {
|
||||
output := captureLogUntil(t, "not available", func() {
|
||||
runTestServer(t, s)
|
||||
})
|
||||
require.Contains(t, output, "Admin socket")
|
||||
require.Contains(t, output, "directly, which is deprecated, see https://ntfy.sh/docs/deprecations/")
|
||||
require.Contains(t, output, "Admin socket "+conf.ListenAdminUnix+" not available")
|
||||
require.NotContains(t, output, "deprecated") // The CLI does not use the socket yet, so there is nothing to fall back from
|
||||
require.False(t, adminSocketListening(s))
|
||||
}
|
||||
|
||||
|
||||
@@ -12,12 +12,17 @@ import (
|
||||
// node's local subscribers. It is the receive-side counterpart to Cluster.ForwardMessage: local
|
||||
// delivery and all global side effects (Firebase, email, web push, upstream) already ran on the
|
||||
// origin node, so this only publishes to the local topic, and never re-relays.
|
||||
//
|
||||
// A message whose topic has no subscriber here is counted as wasted and not published: publishing
|
||||
// would keep the idle topic alive (Keepalive), so a node would never expunge a topic the other
|
||||
// nodes keep publishing to. A subscriber that comes along later gets it from its since= read of
|
||||
// the cache, or from the tail (which sends what never went out here).
|
||||
func (s *Server) deliverFromBus(m *model.Message) {
|
||||
s.mu.RLock()
|
||||
t, ok := s.topics[m.Topic]
|
||||
s.mu.RUnlock()
|
||||
if !ok {
|
||||
metrics.ClusterMessagesWasted.Inc() // Relayed here needlessly: this node had no use for the message
|
||||
if !ok || t.SubscribersCount() == 0 {
|
||||
metrics.ClusterMessagesWasted.Inc() // Relayed here needlessly: no subscriber here wanted the message
|
||||
return
|
||||
}
|
||||
v := s.visitor(m.Sender, nil)
|
||||
|
||||
@@ -11,11 +11,13 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus/testutil"
|
||||
"github.com/stretchr/testify/require"
|
||||
"heckel.io/ntfy/v2/cluster"
|
||||
"heckel.io/ntfy/v2/db"
|
||||
"heckel.io/ntfy/v2/db/pg"
|
||||
dbtest "heckel.io/ntfy/v2/db/test"
|
||||
"heckel.io/ntfy/v2/metrics"
|
||||
"heckel.io/ntfy/v2/model"
|
||||
"heckel.io/ntfy/v2/user"
|
||||
)
|
||||
@@ -389,6 +391,37 @@ func TestServer_Cluster_DeliverFromBus(t *testing.T) {
|
||||
require.Empty(t, b.Messages()) // Peer messages are never re-relayed
|
||||
}
|
||||
|
||||
func TestServer_Cluster_DeliverFromBus_WastedWithoutSubscribers(t *testing.T) {
|
||||
// A fan-out message that no subscriber here wants is counted as wasted: its topic is unknown
|
||||
// here, or known but without subscribers. The latter is not published either, so the fan-out
|
||||
// does not keep an idle topic alive (see topicExpungeAfter).
|
||||
s := newTestServer(t, newTestConfig(t, ""))
|
||||
s.cluster = &fakeCluster{}
|
||||
topics, err := s.topicsFromIDs(nil, "idletopic")
|
||||
require.Nil(t, err)
|
||||
idle := topics[0]
|
||||
idle.mu.Lock()
|
||||
idle.lastAccess = time.Now().Add(-topicExpungeAfter - time.Minute)
|
||||
idle.mu.Unlock()
|
||||
wasted := testutil.ToFloat64(metrics.ClusterMessagesWasted)
|
||||
|
||||
// Unknown topic
|
||||
s.deliverFromBus(model.NewDefaultMessage("unknowntopic", "from peer"))
|
||||
require.Equal(t, wasted+1, testutil.ToFloat64(metrics.ClusterMessagesWasted))
|
||||
|
||||
// Known topic without subscribers: wasted, and still stale
|
||||
s.deliverFromBus(model.NewDefaultMessage("idletopic", "from peer"))
|
||||
require.Equal(t, wasted+2, testutil.ToFloat64(metrics.ClusterMessagesWasted))
|
||||
require.True(t, idle.Stale())
|
||||
|
||||
// Known topic with a subscriber: delivered, not wasted
|
||||
idle.Subscribe(func(_ *visitor, m *model.Message) error {
|
||||
return nil
|
||||
}, nil, func() {})
|
||||
s.deliverFromBus(model.NewDefaultMessage("idletopic", "from peer"))
|
||||
require.Equal(t, wasted+2, testutil.ToFloat64(metrics.ClusterMessagesWasted))
|
||||
}
|
||||
|
||||
func TestServer_Cluster_FirebaseKeepaliverOnlyOnLeader(t *testing.T) {
|
||||
// Every FCM keepalive wakes all subscribed phones, so only the leader may send them;
|
||||
// N nodes sending N keepalives would multiply the battery cost for every user
|
||||
|
||||
Reference in new issue
Block a user