runtimeterror/content/posts/easy-push-notifications-with-ntfy/index.md
2023-09-11 17:42:01 -05:00

5.5 KiB

title date draft description featured toc comment series tags
Easy Push Notifications With ntfy.sh 2023-09-11 true Deploying and configuring a self-hosted pub-sub notification handler, getting another server to send a notifcation when it boots, and integrating the notification handler in Home Assistant. false true true Projects
android
api
automation
containers
docker
homeassistant
linux
rest
selfhosting
shell

Wouldn't it be great if there was a simple way to send a notification to your phone(s) with a simple curl call? Then you could get notified when a script completes, or a server reboots, a user logs in to a system, or a sensor connected to Home Assistant changes state. How great would that be??

ntfy.sh (pronounced notify) provides just that. It's an open-source, easy-to-use, HTTP-based pub-sub notification service, and it can notify using either mobile apps for Android (Play or F-Droid) or iOS (App Store) or a web app. I thought it sounded pretty compelling - and then I notice that ntfy's docs made it sound really easy to self-host the server component.

So let's take it for a spin!

Installation

I'm going to use the Docker setup on an existing cloud server and use Caddy as a reverse proxy.1

So I'll start by creating a new directory at /opt/ntfy/ to hold the goods, and create a compose config. /opt/ntfy/docker-compose.yml:

version: "2.3"

services:
  ntfy:
    image: binwiederhier/ntfy
    container_name: ntfy
    command:
      - serve
    environment:
      - TZ=UTC    # optional: set desired timezone
    volumes:
      - ./cache/ntfy:/var/cache/ntfy
      - ./etc/ntfy:/etc/ntfy
      - ./lib/ntf:/var/lib/ntfy
    ports:
      - 8080:80
    healthcheck: # optional: remember to adapt the host:port to your environment
        test: ["CMD-SHELL", "wget -q --tries=1 http://localhost:8080/v1/health -O - | grep -Eo '\"healthy\"\\s*:\\s*true' || exit 1"]
        interval: 60s
        timeout: 10s
        retries: 3
        start_period: 40s
    restart: unless-stopped

This config will create/mount folders in the working directory to store the ntfy cache and config. It also maps localhost:8080 to port 80 on the container, and enables a simple healthcheck against the ntfy health API endpoint. This will ensure that the service stays healthy.

I can go ahead and bring it up:

sudo docker-compose up -d

I'll also want to add the following to my Caddy config: /etc/caddy/Caddyfile:

ntfy.example.com, http://ntfy.example.com {
  reverse_proxy localhost:8080
  @httpget {
    protocol http
    method GET
    path_regexp ^/([-_a-z0-9]{0,64}$|docs/|static/)
  }
  redir @httpget https://{host}{uri}
}

And I'll restart Caddy to apply the config:

sudo systemctl restart caddy

Configuration

/opt/ntfy/etc/ntfy/server.yml:

auth-file: "/var/lib/ntfy/user.db"
auth-default-access: "deny-all"
base-url: "https://ntfy.example.com"
sudo docker-compose down && sudo docker-compose up -d
sudo docker exec -it ntfy /bin/sh
ntfy user add --role=admin admin_user
ntfy user add writer
ntfy token add writer
ntfy access writer ping write-only

Usage

curl \
  -u "writer:$password" \
  -d "My first notification" \
  https://ntfy.example.com/ping

curl \
  -H "Authorization: Bearer $token" \
  -H "Title: Here's a Message Title" \
  -d "This is the message body" \
  https://ntfy.example.com/ping

Notify on boot

/usr/local/bin/ntfy_push.sh:

#!/usr/bin/env bash

curl \
  -H "Authorization: Bearer $TOKEN" \
  -H "Title: $1" \
  -d "$2" \
  https://ntfy.example.com/ping

/usr/local/bin/ntfy_boot_complete.sh:

#!/usr/bin/env bash

TITLE="$(hostname -s)"
MESSAGE="System boot complete"

/usr/local/bin/ntfy_push.sh "$TITLE" "$MESSAGE"

/etc/systemd/system/ntfy_boot_complete.service:

[Unit]
After=network.target

[Service]
ExecStart=/usr/local/bin/ntfy_boot_complete.sh

[Install]
WantedBy=default.target
sudo systemctl daemon-reload
sudo systemctl enable --now ntfy_boot_complete.service

Home Assistant

configuration.yaml:

notify:
  - name: ntfy
    platform: rest
    method: POST_JSON
    authentication: basic
    username: writer
    password: $PASSWORD
    data:
      topic: ping
    title_param_name: title
    message_param_name: message
    resource: https://ntfy.example.com

automations.yaml:

- alias: Water Leak Detection
  description: ''
  trigger:
  - platform: state
    entity_id:
    - binary_sensor.water_6
    - binary_sensor.water_3
    - binary_sensor.water_5
    from: 'off'
    to: 'on'
  condition: []
  action:
  - service: notify.ntfy
    data:
      title: Leak detected!
      message: '{{ trigger.to_state.attributes.friendly_name }} detected.'

  1. I'm a big fan of Caddy. It may not be quite as capable/flexible as nginx but I love how simple it makes most configurations. Using Caddy in this will will not only enable HTTPS for the new web service but will also automatically obtain/renew LetsEncrypt certs so that I don't even have to think about it. ↩︎