Security / Dependency vulnerabilities (push) Successful in 44s
Security / Static analysis (push) Successful in 9s
CI / Format & lint (push) Successful in 10s
CI / Test suite (push) Successful in 26s
Security / License report (push) Successful in 50s
Docs / Validate documentation (push) Successful in 16s
The reverse proxy is a separate VPS on the tailnet, so a loopback-only ntfy was unreachable from it. install_ntfy.sh now binds the host's Tailscale IP (NTFY_LISTEN overrides). New NTFY_PUBLISH_URL: where the monitor POSTs, separate from the public NTFY_SERVER subscribers see, so an alert never waits on DNS or the proxy (first cycle logged 502s from Cloudflare while the domain was not yet proxied).
103 lines
4.0 KiB
Bash
103 lines
4.0 KiB
Bash
#!/usr/bin/env bash
|
|
# Install ntfy (https://ntfy.sh) as the public notification server for the
|
|
# Ping River Monitor. Run as root on the monitor VPS. Idempotent.
|
|
#
|
|
# NTFY_DOMAIN=ntfy.buildfor.life bash scripts/install_ntfy.sh
|
|
#
|
|
# What it does:
|
|
# - installs the ntfy .deb from the official GitHub release (single Go
|
|
# binary, ~30 MB RSS, sqlite message cache)
|
|
# - writes /etc/ntfy/server.yml: listens on the Tailscale address only
|
|
# (the reverse proxy is another VPS on the tailnet; nothing is exposed
|
|
# on a public interface), anonymous READ on all topics, WRITE only with
|
|
# a token. Override with NTFY_LISTEN=host:port.
|
|
# - creates the `monitor` publishing user + token, writes NTFY_SERVER /
|
|
# NTFY_TOKEN into /opt/thailand-water-monitor/.env if not present
|
|
#
|
|
# Reverse proxy (on the Caddy VPS, over Tailscale):
|
|
# ntfy.buildfor.life {
|
|
# reverse_proxy <this host's tailscale ip>:2586
|
|
# }
|
|
# Caddy passes websockets and keeps long-poll connections open by default;
|
|
# subscribers hold one open. ntfy runs with behind-proxy: true so rate
|
|
# limits key on X-Forwarded-For, not on the proxy's address.
|
|
set -euo pipefail
|
|
|
|
NTFY_DOMAIN="${NTFY_DOMAIN:?set NTFY_DOMAIN, e.g. ntfy.buildfor.life}"
|
|
NTFY_VERSION="${NTFY_VERSION:-2.28.0}"
|
|
MONITOR_DIR="${MONITOR_DIR:-/opt/thailand-water-monitor}"
|
|
TS_IP="$(tailscale ip -4 2>/dev/null | head -1 || true)"
|
|
LISTEN="${NTFY_LISTEN:-${TS_IP:-127.0.0.1}:2586}"
|
|
echo "ntfy will listen on ${LISTEN}"
|
|
|
|
if ! command -v ntfy >/dev/null || [[ "$(ntfy --version 2>/dev/null | awk '{print $3}')" != "$NTFY_VERSION" ]]; then
|
|
tmp=$(mktemp -d)
|
|
curl -fsSL -o "$tmp/ntfy.deb" \
|
|
"https://github.com/binwiederhier/ntfy/releases/download/v${NTFY_VERSION}/ntfy_${NTFY_VERSION}_linux_amd64.deb"
|
|
dpkg -i "$tmp/ntfy.deb"
|
|
rm -rf "$tmp"
|
|
fi
|
|
|
|
install -d -m 755 /var/cache/ntfy /var/lib/ntfy
|
|
cat > /etc/ntfy/server.yml <<EOF
|
|
# Ping River Monitor notification server. Managed by scripts/install_ntfy.sh.
|
|
base-url: "https://${NTFY_DOMAIN}"
|
|
listen-http: "${LISTEN}"
|
|
behind-proxy: true
|
|
|
|
# Messages are kept so a phone that was offline still gets the crossing.
|
|
cache-file: "/var/cache/ntfy/cache.db"
|
|
cache-duration: "72h"
|
|
|
|
# Everyone may subscribe; only the monitor (token) may publish.
|
|
auth-file: "/var/lib/ntfy/user.db"
|
|
auth-default-access: "read-only"
|
|
|
|
# The monitor publishes a handful of messages per flood; be strict with
|
|
# everything else so the box cannot be used as a free relay.
|
|
visitor-request-limit-burst: 30
|
|
visitor-request-limit-replenish: "10s"
|
|
visitor-subscription-limit: 60
|
|
visitor-message-daily-limit: 200
|
|
attachment-cache-dir: ""
|
|
enable-signup: false
|
|
enable-login: false
|
|
enable-metrics: false
|
|
EOF
|
|
|
|
systemctl enable --now ntfy
|
|
systemctl restart ntfy
|
|
sleep 1
|
|
curl -fsS "http://${LISTEN}/v1/health" >/dev/null && echo "ntfy up on ${LISTEN}"
|
|
|
|
# Publishing identity for the monitor
|
|
if ! ntfy user list 2>/dev/null | grep -q '^user monitor (role'; then
|
|
NTFY_PASSWORD="$(openssl rand -base64 24)" ntfy user add --role=user monitor
|
|
fi
|
|
ntfy access monitor 'ping-*' write-only >/dev/null
|
|
# 'ping-*' read stays anonymous via auth-default-access
|
|
|
|
token=$(ntfy token list monitor 2>/dev/null | awk '/^- tk_/{print $2; exit}') # '- tk_xxx (label), ...'
|
|
if [[ -z "$token" ]]; then
|
|
token=$(ntfy token add --label "water-monitor" monitor | grep -oE 'tk_[A-Za-z0-9]+' | head -1) # 'token tk_xxx created for user monitor'
|
|
fi
|
|
|
|
env_file="${MONITOR_DIR}/.env"
|
|
if [[ -f "$env_file" ]] && ! grep -q '^NTFY_SERVER=' "$env_file"; then
|
|
{
|
|
echo ""
|
|
echo "# ntfy public notifications (scripts/install_ntfy.sh)"
|
|
echo "NTFY_SERVER=https://${NTFY_DOMAIN}"
|
|
echo "NTFY_PUBLISH_URL=http://${LISTEN}"
|
|
echo "NTFY_TOPIC_PREFIX=ping"
|
|
echo "NTFY_TOKEN=${token}"
|
|
} >> "$env_file"
|
|
echo "wrote NTFY_* to ${env_file}; restart water-monitor to enable"
|
|
else
|
|
echo "NTFY_TOKEN=${token}"
|
|
fi
|
|
|
|
echo
|
|
echo "Subscribe test (anonymous read): curl -s 'http://${LISTEN}/ping-status/json?poll=1'"
|
|
echo "Publish test (needs token): curl -s -H 'Authorization: Bearer ${token}' -d 'hello' http://${LISTEN}/ping-status"
|