Skip to main content

Shenzhen, China

nfty

Provision with Docker

Create the work directory:

sudo useradd ntfy

The user ID, e.g. 1002, has to be used inside the docker compose file below:

id ntfy
uid=1002(ntfy) gid=1002(ntfy) groups=1002(ntfy)
sudo mkdir -p /opt/ntfy/{config,log,lib,cache/attachments}
sudo chown -R ntfy:ntfy /opt/ntfy

Add the configuration file - by copying the content of this example configuration

sudo wget https://raw.githubusercontent.com/binwiederhier/ntfy/main/server/server.yml -P /opt/ntfy/config

Now we can add the docker-compose file:

cd /opt/ntfy
sudo nano docker-compose.yml

docker-compose.yml

version: "3"

services:
  ntfy:
    image: binwiederhier/ntfy
    container_name: ntfy
    command:
      - serve
    environment:
      - TZ=UTC    # optional: set desired timezone
    user: 1002:1002 # replace with the user/group or uid/gid
    volumes:
      - ./cache:/var/cache/ntfy
      - ./config:/etc/ntfy
      - ./db:/var/lib/ntfy/
    ports:
      - 8000:80 # exposed on port 8000 (you can change it)
    restart: unless-stopped
docker pull binwiederhier/ntfy:latest
docker-compose up -d

The user interface will now be accessible on localhost:8000 and allows you to subscribe to a notification topic:

ntfy Notification Service

For example, we can use hello_world:

ntfy Notification Service

This topic is now publicly available to use. Which brings us to user authentication.

User Management

Start by editing the downloaded ./config/server.yml template - for now I will only use a local IP without encryption:

# ntfy server config file
base-url: "http://192.168.2.112"
listen-http: ":80"
# listen-https: ":443"
# key-file: <filename>
# cert-file: <filename>
cache-file: "/var/cache/ntfy/cache.db"
cache-duration: "12h"
auth-file: /var/lib/ntfy/user.db
auth-default-access: "deny-all"
behind-proxy: false
attachment-cache-dir: "/var/cache/ntfy/attachments"
attachment-total-size-limit: "5G"
attachment-file-size-limit: "15M"
attachment-expiry-duration: "3h"
web-root: /ntfy
enable-signup: false
enable-login: true
enable-reservations: true
log-level: info
log-format: json
log-file: /var/log/ntfy.log

Modify the docker-compose file to add all necessary volumen mount to persist the generated data:

./docker-compose.yml

version: "3"

services:
  ntfy:
    image: binwiederhier/ntfy:latest
    container_name: ntfy
    command:
      - serve
    environment:
      - TZ=UTC    # optional: set desired timezone
    user: 1002:1002 # replace with the user/group or uid/gid
    volumes:
      - ./cache:/var/cache/ntfy
      - ./config:/etc/ntfy
      - ./lib:/var/lib/ntfy/
      - ./log:/var/log
    ports:
      - 8000:80 # exposed on port 8000 (you can change it)
    restart: unless-stopped

ntfy CLI

Having set the default access right to deny-all we are now no longer able to interact with the created topic:

curl -d "Hi" localhost:8000/hello_world
{"code":40301,"http":403,"error":"forbidden","link":"https://ntfy.sh/docs/publish/#authentication"}

Having set the enable-signup option to false we are also unable to create a new user through the UI or the REST interface. So we need to use the CLI inside our docker container to generate an admin and regular user login. Here are a few examples for available CLI commands (that need to be executed inside the docker container!):

ntfy user list                     # Shows list of users (alias: 'ntfy access')
ntfy user add phil                 # Add regular user phil  
ntfy user add --role=admin phil    # Add admin user phil
ntfy user del phil                 # Delete user phil
ntfy user change-pass phil         # Change password for user phil
ntfy user change-role phil admin   # Make user phil an admin
ntfy user change-tier phil pro     # Change phil's tier to "pro"
ntfy access                            # Shows access control list (alias: 'ntfy user list')
ntfy access USERNAME                   # Shows access control entries for USERNAME
ntfy access USERNAME TOPIC PERMISSION  # Allow/deny access for USERNAME to TOPIC
ntfy access                        # Shows entire access control list
ntfy access phil                   # Shows access for user phil
ntfy access phil mytopic rw        # Allow read-write access to mytopic for user phil
ntfy access everyone mytopic rw    # Allow anonymous read-write access to mytopic
ntfy access everyone "up*" write   # Allow anonymous write-only access to topics "up..."
ntfy access --reset                # Reset entire access control list
ntfy access --reset phil           # Reset all access for user phil
ntfy access --reset phil mytopic   # Reset access for user phil and topic mytopic

Adding an admin user inside the Docker container:

docker exec -ti ntfy ntfy user add --role=admin admin
~ $ password: mypassword
~ $ confirm: mypassword
~ $ user admin added with role admin

The dashboard is now accessible under http://localhost:8000/ntfy and http://192.168.2.112:8000/ntfy and now has a Sign In button:

ntfy Notification Service

ntfy Notification Service

We can now add a regular user notifier through the cli:

docker exec -ti ntfy ntfy user add notifier

This user is currently not allowed to subscribe to any topics. But logged in as the administrator we can create a notification topic and grant access to it:

ntfy Notification Service

Which would open this topic to everyone. To make it more secure we can lock the topic down to subscription only:

ntfy Notification Service

And then change the access permission for the user notifier to read-write, read-only, write-only, or deny for the hello_world topic through the cli:

docker exec -ti ntfy ntfy access notifier test read-only
~ $ granted read-only access to topic test

And for the next tests I am also going to add a topic the notifier user only has publishing rights to:

ntfy Notification Service

docker exec -ti ntfy ntfy access notifier notify write-only
~ $ granted read-only access to topic test
docker exec -ti ntfy ntfy user list
~ $ user admin (role: admin, tier: none)
~ $ - read-write access to all topics (admin role)
~ $ user notifier (role: user, tier: none)
~ $ - write-only access to topic notify
~ $ - read-only access to topic test
~ $ user * (role: anonymous, tier: none)
~ $ - write-only access to topic notify
~ $ - read-only access to topic test
~ $ - no access to any (other) topics (server config)
~ $ - no access to any (other) topics (server config)

Add the notifier user to your Android app - or log in with notifier login in another browser. You should now be able to subscribe to the test topic, receive notification but be unable to publish updates. Trying to subscribe to notify will fail completely.

What I am not sure about is - why does the anonymous user have the same rights as my basic user? This makes logging in useless - unless you want to use the admin user. So let's see what happens when you created a restricted topic that only the admin user has access to:

ntfy Notification Service

And give the basic use access through the CLI:

docker exec -ti ntfy ntfy access notifier restricted write-only
~ $ granted read-only access to topic test

Now this worked - the basic user can now publish updates to the restricted topic - but the anonymous user is locked out:

docker exec -ti ntfy ntfy user list
~ $ user admin (role: admin, tier: none)
~ $ - read-write access to all topics (admin role)
~ $ user notifier (role: user, tier: none)
~ $ - write-only access to topic restricted
~ $ - write-only access to topic notify
~ $ - read-only access to topic test
~ $ user * (role: anonymous, tier: none)
~ $ - no access to topic restricted
~ $ - write-only access to topic notify
~ $ - read-only access to topic test
~ $ - no access to any (other) topics (server config)

We can now trigger an update to this topic by sending a curl HTTP GET request:

curl -d "Test 1,2,3" localhost:8000/hello_world

{"id":"qgoDRDQGpjQw","time":1705662731,"expires":1705705931,"event":"message","topic":"hello_world","message":"Test 1,2,3"}

And the notification should appear inside the nfty dashboard:

ntfy Notification Service

Mobile Notification

Download the latest version of the Android app:

ntfy Notification Service

Set the default server your App should connect to. Note that I am using the local IP of my linux server and this will only work when my phone is connected to the same network - either directly or through an VPN tunnel:

ntfy Notification Service

Now I need to create a subscription to the same hello_world topic I created in the server webUI:

ntfy Notification Service

Back on the server I can send an update to the hello_world topic:

ntfy Notification Service

And it should now also pop up on the connected Android phone:

ntfy Notification Service

REST API

Trying to publish to an read-only topic returns a 403 - forbidden:

curl -v -u notifier:instar -d "Hello" localhost:8000/test

* Host localhost:8000 was resolved.
* IPv6: ::1
* IPv4: 127.0.0.1
*   Trying [::1]:8000...
* Connected to localhost (::1) port 8000
* Server auth using Basic with user 'notifier'
> POST /test HTTP/1.1
> Host: localhost:8000
> Authorization: Basic bm90aWZpZXI6aW5zdGFy
> User-Agent: curl/8.5.0
> Accept: */*
> Content-Length: 5
> Content-Type: application/x-www-form-urlencoded
> 
< HTTP/1.1 403 Forbidden
< Access-Control-Allow-Origin: *
< Content-Type: application/json
< Date: Sat, 27 Jan 2024 10:13:52 GMT
< Content-Length: 100
< 
{"code":40301,"http":403,"error":"forbidden","link":"https://ntfy.sh/docs/publish/#authentication"}
* Connection #0 to host localhost left intact

While the write-only topic is open for business. But - as configures above - only if you use the correct user login:

curl -d "Hello" localhost:8000/restricted
{"code":40301,"http":403,"error":"forbidden","link":"https://ntfy.sh/docs/publish/#authentication"}
curl \
  -v -u notifier:instar \
  -H "Title: Update to the Restricted Topic" \
  -H "Priority: urgent" \
  -H "Tags: warning" \
  -d "This is a message update using the basic user authentication." \
  localhost:8000/restricted

ntfy Notification Service

INSTAR IP Cameras

I am now interested in connecting my INSTAR 2k+ WQHD IP camera's HTTP alarm service. Let's use curl to find out the request that we can use.

Here I am going to emply custom headers provided by ntfy:

curl -u notifier:instar -d "Hi" -H "X-Attach: http://192.168.2.125/snap.cgi?user=admin&pwd=instar" localhost:8000/test
{"id":"l4Er2pCeCTLP","time":1708428507,"expires":1708471707,"event":"message","topic":"test","message":"Hi","attachment":{"name":"snap.cgi","url":"http://192.168.2.125/snap.cgi?user=admin\u0026pwd=instar"}}

This adds the URL of the current camera snapshot to the notification which results in two issues. The URL as provided above is only accessible on my local network - so I either need to forward the HTTP port of my camera to the internet, only load the image when I am in the same network or use a VPN to always have access to the local IP.

The second issue is, that the URL will always show the latest image - and not the one that was recorded when the alarm was triggered:

ntfy Notification Service

The same works with Icon :

curl -u notifier:instar -d "Hi" -H "X-Icon: http://192.168.2.125/snap.cgi?user=admin&pwd=instar" localhost:8000/test
{"id":"OBy1UXYLq5rj","time":1708428665,"expires":1708471865,"event":"message","topic":"test","message":"Hi","icon":"http://192.168.2.125/snap.cgi?user=admin\u0026pwd=instar"}

ntfy Notification Service

Using Filename instead of Attach will add the file to the message - which seems to be the better choice. But the service does not download the image from my camera. Instead it just tries to save the string to text:

curl -u notifier:instar -d "Hi" -H "X-Filename: http://192.168.2.125/snap.cgi?user=admin&pwd=instar" localhost:8000/test
{"id":"iFdDVyvhtPSf","time":1708428544,"expires":1708471744,"event":"message","topic":"test","message":"You received a file: http://192.168.2.125/snap.cgi?user=admin\u0026pwd=instar","attachment":{"name":"http://192.168.2.125/snap.cgi?user=admin\u0026pwd=instar","type":"text/plain; charset=utf-8","size":2,"expires":1708439344,"url":"http://192.168.2.112/file/iFdDVyvhtPSf.txt"}}

ntfy Notification Service

FTP Upload: The option above should work in combination with the alarm FTP upload provided by the IP camera. Uploading the snapshot to the ntfy server and adding a local location above should give us access to the image inside the notification.

Ok, so now let's use the camera UI to reproduce those results to be notified whenever an Alarm is triggered:

ntfy Notification Service

The headers are currently not yet configurable through the UI. But we can already set them using CGI commands:

/param.cgi?cmd=setasattr&as_index=1&as_header1=1&as_headerattr1=X-Title&as_headerval1=Alarm%20125

Testing the configuration will give us the following notification:

ntfy Notification Service

Ok, so le't extend the configuration by adding all the headers to configure a title, tags, a click target (to directly open the camera webUI), an snapshot icon and a priority:

as_headerattr1="X-Title";
as_headerval1="Unauthorized access detected";
as_header1="1";
as_headerattr2="X-Tags";
as_headerval2="warning,skull";
as_header2="1";
as_headerattr3="X-Click";
as_headerval3="http://192.168.2.125/";
as_header3="1";
as_headerattr4="X-Icon";
as_headerval4="http://192.168.2.125/snap.cgi?chn=13&user=admin&pwd=instar";
as_header4="1";
as_headerattr5="X-Priority";
as_headerval5="5";
as_header5="1";
/param.cgi?cmd=setasattr&as_index=1&as_header1=1&as_headerattr1=X-Title&as_headerval1=Unauthorized%20access%20detected&as_header2=1&as_headerattr2=X-Tags&as_headerval2=warning%2Cskull&as_header3=1&as_headerattr3=X-Click&as_headerval3=http://192.168.2.125/&as_header4=1&as_headerattr4=X-Icon&as_headerval4=http://192.168.2.125/snap.cgi%3Fchn=13%26user=admin%26pwd=instar&as_header5=1&as_headerattr5=X-Priority&as_headerval5=5

ntfy Notification Service