发布于

ntfy 消息推送服务完全指南:简单高效的跨平台通知解决方案

作者

ntfy 消息推送服务完全指南:简单高效的跨平台通知解决方案

ntfy 是一个简单而强大的开源消息推送服务,它让发送通知变得极其简单。无需注册、无需 API 密钥,只需一个 HTTP 请求就能发送通知到手机、桌面或任何支持的设备。

ntfy 简介

什么是 ntfy

ntfy(发音为 "notify")是一个基于 HTTP 的发布-订阅通知服务。它允许你通过简单的 HTTP 请求发送消息到手机、桌面或其他设备。

核心特性

# 最简单的使用方式
curl -d "Hello World" ntfy.sh/mytopic

# 带标题的消息
curl -H "Title: 重要通知" -d "服务器重启完成" ntfy.sh/alerts

# 带优先级的消息
curl -H "Priority: urgent" -d "紧急:数据库连接失败" ntfy.sh/monitoring

主要优势

  • 零配置:无需注册账号或 API 密钥
  • 跨平台:支持 Android、iOS、Web、桌面
  • 开源免费:完全开源,可自托管
  • 简单易用:一行命令即可发送通知
  • 功能丰富:支持附件、图片、按钮等

快速开始

安装客户端

# Android
# 从 Google Play Store 或 F-Droid 下载 ntfy 应用

# iOS
# 从 App Store 下载 ntfy 应用

# Linux (Snap)
sudo snap install ntfy

# macOS (Homebrew)
brew install ntfy

# Windows
# 下载 .exe 文件或使用 Scoop
scoop install ntfy

基础使用

# 1. 发送简单消息
curl -d "Hello from server" ntfy.sh/myalerts

# 2. 使用 POST 方法
curl -X POST -d "Backup completed" ntfy.sh/backups

# 3. 使用 PUT 方法
echo "System update available" | curl -T- ntfy.sh/updates

# 4. 使用 GET 方法(URL 编码)
curl "ntfy.sh/alerts?message=Server+is+down"

订阅主题

# 命令行订阅
ntfy subscribe ntfy.sh/myalerts

# 或者使用 curl 持续监听
curl -s ntfy.sh/myalerts/raw

# WebSocket 连接
curl -s -N -H "Accept: text/event-stream" ntfy.sh/myalerts/sse

高级功能

消息格式化

# 设置消息标题
curl -H "Title: 系统监控" \
     -d "CPU 使用率超过 80%" \
     ntfy.sh/monitoring

# 设置消息优先级
curl -H "Priority: high" \
     -H "Title: 警告" \
     -d "磁盘空间不足" \
     ntfy.sh/alerts

# 添加标签
curl -H "Tags: warning,server" \
     -d "服务器负载过高" \
     ntfy.sh/monitoring

# 延迟发送
curl -H "Delay: 30min" \
     -d "定时提醒:会议开始" \
     ntfy.sh/reminders

优先级设置

# 优先级级别:1(min) - 5(max)
curl -H "Priority: 1" -d "低优先级消息" ntfy.sh/topic    # 最小
curl -H "Priority: 2" -d "普通消息" ntfy.sh/topic        # 低
curl -H "Priority: 3" -d "默认消息" ntfy.sh/topic        # 默认
curl -H "Priority: 4" -d "重要消息" ntfy.sh/topic        # 高
curl -H "Priority: 5" -d "紧急消息" ntfy.sh/topic        # 最大

# 使用名称
curl -H "Priority: urgent" -d "紧急通知" ntfy.sh/alerts
curl -H "Priority: high" -d "高优先级" ntfy.sh/alerts
curl -H "Priority: default" -d "普通消息" ntfy.sh/alerts
curl -H "Priority: low" -d "低优先级" ntfy.sh/alerts
curl -H "Priority: min" -d "最低优先级" ntfy.sh/alerts

添加操作按钮

# 单个按钮
curl -H "Actions: view, 查看详情, https://example.com" \
     -d "新订单已创建" \
     ntfy.sh/orders

# 多个按钮
curl -H "Actions: view, 查看, https://example.com; http, 重启, https://api.example.com/restart, method=POST" \
     -d "服务异常,需要处理" \
     ntfy.sh/alerts

# 带确认的按钮
curl -H "Actions: http, 删除数据, https://api.example.com/delete, method=DELETE, confirm=true" \
     -d "发现恶意文件" \
     ntfy.sh/security

发送附件

# 发送文件
curl -T /path/to/file.pdf \
     -H "Filename: report.pdf" \
     ntfy.sh/documents

# 发送图片
curl -T /path/to/image.jpg \
     -H "Title: 监控截图" \
     ntfy.sh/monitoring

# 从 URL 发送附件
curl -H "Attach: https://example.com/file.zip" \
     -H "Filename: backup.zip" \
     -d "备份文件已准备" \
     ntfy.sh/backups

实际应用场景

服务器监控

#!/bin/bash
# server-monitor.sh

TOPIC="server-monitoring"
SERVER_NAME="Web-Server-01"

# 检查 CPU 使用率
CPU_USAGE=$(top -bn1 | grep "Cpu(s)" | awk '{print $2}' | cut -d'%' -f1)
if (( $(echo "$CPU_USAGE > 80" | bc -l) )); then
    curl -H "Title: ⚠️ CPU 警告" \
         -H "Priority: high" \
         -H "Tags: warning,cpu" \
         -d "服务器 $SERVER_NAME CPU 使用率: ${CPU_USAGE}%" \
         ntfy.sh/$TOPIC
fi

# 检查磁盘空间
DISK_USAGE=$(df -h / | awk 'NR==2{print $5}' | cut -d'%' -f1)
if [ $DISK_USAGE -gt 90 ]; then
    curl -H "Title: 🚨 磁盘空间警告" \
         -H "Priority: urgent" \
         -H "Tags: critical,disk" \
         -d "服务器 $SERVER_NAME 磁盘使用率: ${DISK_USAGE}%" \
         ntfy.sh/$TOPIC
fi

# 检查内存使用
MEM_USAGE=$(free | grep Mem | awk '{printf("%.2f", $3/$2 * 100.0)}')
if (( $(echo "$MEM_USAGE > 85" | bc -l) )); then
    curl -H "Title: ⚠️ 内存警告" \
         -H "Priority: high" \
         -H "Tags: warning,memory" \
         -d "服务器 $SERVER_NAME 内存使用率: ${MEM_USAGE}%" \
         ntfy.sh/$TOPIC
fi

备份通知

#!/bin/bash
# backup-notify.sh

TOPIC="backup-alerts"
BACKUP_DIR="/backup"
DATE=$(date +"%Y-%m-%d %H:%M:%S")

# 执行备份
if mysqldump -u root -p$DB_PASSWORD mydb > $BACKUP_DIR/mydb_$(date +%Y%m%d).sql; then
    # 备份成功
    BACKUP_SIZE=$(du -h $BACKUP_DIR/mydb_$(date +%Y%m%d).sql | cut -f1)
    
    curl -H "Title: ✅ 备份成功" \
         -H "Priority: default" \
         -H "Tags: success,backup" \
         -d "数据库备份完成
时间: $DATE
大小: $BACKUP_SIZE
位置: $BACKUP_DIR" \
         ntfy.sh/$TOPIC
else
    # 备份失败
    curl -H "Title: ❌ 备份失败" \
         -H "Priority: urgent" \
         -H "Tags: error,backup" \
         -H "Actions: view, 查看日志, https://monitor.example.com/logs" \
         -d "数据库备份失败
时间: $DATE
请立即检查系统状态" \
         ntfy.sh/$TOPIC
fi

应用程序集成

# Python 示例
import requests
import json

class NtfyNotifier:
    def __init__(self, base_url="https://ntfy.sh", topic="myapp"):
        self.base_url = base_url
        self.topic = topic
    
    def send_notification(self, message, title=None, priority="default", tags=None, actions=None):
        url = f"{self.base_url}/{self.topic}"
        headers = {"Content-Type": "text/plain; charset=utf-8"}
        
        if title:
            headers["Title"] = title
        if priority:
            headers["Priority"] = priority
        if tags:
            headers["Tags"] = ",".join(tags) if isinstance(tags, list) else tags
        if actions:
            headers["Actions"] = actions
        
        response = requests.post(url, data=message.encode('utf-8'), headers=headers)
        return response.status_code == 200
    
    def send_error(self, error_message, context=None):
        title = "🚨 应用程序错误"
        message = f"错误信息: {error_message}"
        if context:
            message += f"\n上下文: {context}"
        
        return self.send_notification(
            message=message,
            title=title,
            priority="urgent",
            tags=["error", "app"],
            actions="view, 查看日志, https://logs.example.com"
        )
    
    def send_success(self, operation, details=None):
        title = "✅ 操作成功"
        message = f"操作: {operation}"
        if details:
            message += f"\n详情: {details}"
        
        return self.send_notification(
            message=message,
            title=title,
            priority="default",
            tags=["success", "app"]
        )

# 使用示例
notifier = NtfyNotifier(topic="myapp-alerts")

try:
    # 执行某些操作
    result = perform_critical_operation()
    notifier.send_success("数据处理", f"处理了 {result['count']} 条记录")
except Exception as e:
    notifier.send_error(str(e), "数据处理模块")

Node.js 集成

// ntfy-client.js
const axios = require('axios');

class NtfyClient {
    constructor(baseUrl = 'https://ntfy.sh', topic = 'myapp') {
        this.baseUrl = baseUrl;
        this.topic = topic;
    }

    async sendNotification(options) {
        const {
            message,
            title,
            priority = 'default',
            tags,
            actions,
            delay,
            attach,
            filename
        } = options;

        const url = `${this.baseUrl}/${this.topic}`;
        const headers = {
            'Content-Type': 'text/plain; charset=utf-8'
        };

        if (title) headers['Title'] = title;
        if (priority) headers['Priority'] = priority;
        if (tags) headers['Tags'] = Array.isArray(tags) ? tags.join(',') : tags;
        if (actions) headers['Actions'] = actions;
        if (delay) headers['Delay'] = delay;
        if (attach) headers['Attach'] = attach;
        if (filename) headers['Filename'] = filename;

        try {
            const response = await axios.post(url, message, { headers });
            return response.status === 200;
        } catch (error) {
            console.error('Failed to send notification:', error.message);
            return false;
        }
    }

    async sendAlert(level, message, context = {}) {
        const priorities = {
            info: 'default',
            warning: 'high',
            error: 'urgent',
            critical: 'urgent'
        };

        const emojis = {
            info: 'ℹ️',
            warning: '⚠️',
            error: '❌',
            critical: '🚨'
        };

        const title = `${emojis[level]} ${level.toUpperCase()}`;
        let fullMessage = message;

        if (Object.keys(context).length > 0) {
            fullMessage += '\n\n详细信息:\n';
            for (const [key, value] of Object.entries(context)) {
                fullMessage += `${key}: ${value}\n`;
            }
        }

        return this.sendNotification({
            message: fullMessage,
            title,
            priority: priorities[level],
            tags: [level, 'app']
        });
    }
}

// 使用示例
const ntfy = new NtfyClient('https://ntfy.sh', 'webapp-monitoring');

// 发送不同级别的警报
ntfy.sendAlert('info', '应用启动成功', {
    version: '1.2.3',
    environment: 'production',
    timestamp: new Date().toISOString()
});

ntfy.sendAlert('error', '数据库连接失败', {
    database: 'mysql-prod',
    error: 'Connection timeout',
    retries: 3
});

// 发送带操作按钮的通知
ntfy.sendNotification({
    message: '新用户注册需要审核',
    title: '👤 用户管理',
    priority: 'default',
    tags: ['user', 'admin'],
    actions: 'view, 审核用户, https://admin.example.com/users/pending; http, 自动批准, https://api.example.com/users/approve, method=POST'
});

自托管部署

Docker 部署

# docker-compose.yml
version: '3.8'

services:
  ntfy:
    image: binwiederhier/ntfy
    container_name: ntfy
    command:
      - serve
    environment:
      - NTFY_BASE_URL=https://ntfy.yourdomain.com
      - NTFY_CACHE_FILE=/var/cache/ntfy/cache.db
      - NTFY_AUTH_FILE=/var/lib/ntfy/auth.db
      - NTFY_AUTH_DEFAULT_ACCESS=deny-all
      - NTFY_BEHIND_PROXY=true
    volumes:
      - ./data/cache:/var/cache/ntfy
      - ./data/lib:/var/lib/ntfy
      - ./server.yml:/etc/ntfy/server.yml:ro
    ports:
      - "8080:80"
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:80/v1/health"]
      interval: 60s
      timeout: 10s
      retries: 3
      start_period: 40s

  nginx:
    image: nginx:alpine
    container_name: ntfy-nginx
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
      - ./ssl:/etc/nginx/ssl:ro
    ports:
      - "80:80"
      - "443:443"
    depends_on:
      - ntfy
    restart: unless-stopped

配置文件

# server.yml
base-url: "https://ntfy.yourdomain.com"

# 监听配置
listen-http: ":80"
listen-https: ""

# 缓存配置
cache-file: "/var/cache/ntfy/cache.db"
cache-duration: "12h"

# 认证配置
auth-file: "/var/lib/ntfy/auth.db"
auth-default-access: "deny-all"

# 速率限制
visitor-request-limit-burst: 60
visitor-request-limit-replenish: "5s"
visitor-email-limit-burst: 16
visitor-email-limit-replenish: "1h"

# 附件配置
attachment-cache-dir: "/var/cache/ntfy/attachments"
attachment-total-size-limit: "5G"
attachment-file-size-limit: "15M"
attachment-expiry-duration: "3h"

# Web 推送
web-push-public-key: "your-vapid-public-key"
web-push-private-key: "your-vapid-private-key"
web-push-file: "/var/lib/ntfy/webpush.db"
web-push-email-address: "admin@yourdomain.com"

# 日志配置
log-level: "INFO"
log-format: "text"

# SMTP 配置(可选)
smtp-sender-addr: "smtp.gmail.com:587"
smtp-sender-user: "your-email@gmail.com"
smtp-sender-pass: "your-app-password"
smtp-sender-from: "ntfy@yourdomain.com"

# 主题配置
global-topic-limit: 15000
visitor-subscription-limit: 30

Nginx 反向代理

# nginx.conf
events {
    worker_connections 1024;
}

http {
    upstream ntfy {
        server ntfy:80;
    }

    server {
        listen 80;
        server_name ntfy.yourdomain.com;
        return 301 https://$server_name$request_uri;
    }

    server {
        listen 443 ssl http2;
        server_name ntfy.yourdomain.com;

        ssl_certificate /etc/nginx/ssl/cert.pem;
        ssl_certificate_key /etc/nginx/ssl/key.pem;

        # SSL 配置
        ssl_protocols TLSv1.2 TLSv1.3;
        ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384;
        ssl_prefer_server_ciphers off;

        # 安全头
        add_header Strict-Transport-Security "max-age=63072000" always;
        add_header X-Content-Type-Options nosniff;
        add_header X-Frame-Options DENY;
        add_header X-XSS-Protection "1; mode=block";

        location / {
            proxy_pass http://ntfy;
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "upgrade";
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;

            # WebSocket 支持
            proxy_read_timeout 3600s;
            proxy_send_timeout 3600s;
        }
    }
}

安全和最佳实践

主题命名

# ✅ 好的主题名
ntfy.sh/myapp-prod-alerts
ntfy.sh/server-monitoring-2024
ntfy.sh/backup-notifications

# ❌ 避免的主题名
ntfy.sh/test
ntfy.sh/alerts
ntfy.sh/notifications

访问控制

# 创建用户
ntfy user add --role=admin admin
ntfy user add --role=user alice

# 设置主题权限
ntfy access alice myapp-alerts rw
ntfy access alice monitoring r

# 使用认证发送消息
curl -u alice:password \
     -d "Authenticated message" \
     https://ntfy.yourdomain.com/myapp-alerts

速率限制

# 避免频繁发送
# 使用批量发送或聚合消息

# 示例:聚合多个事件
#!/bin/bash
EVENTS=()
EVENTS+=("Event 1 occurred")
EVENTS+=("Event 2 occurred")
EVENTS+=("Event 3 occurred")

# 聚合发送
MESSAGE=$(printf '%s\n' "${EVENTS[@]}")
curl -H "Title: 批量事件通知" \
     -d "$MESSAGE" \
     ntfy.sh/events

总结

ntfy 的核心优势和最佳实践:

🎯 核心优势

  1. 简单易用:无需注册,一行命令发送通知
  2. 跨平台支持:Android、iOS、Web、桌面全覆盖
  3. 功能丰富:支持优先级、附件、按钮等高级功能
  4. 开源免费:完全开源,可自托管

✅ 适用场景

  • 服务器监控和告警
  • 应用程序状态通知
  • 自动化脚本反馈
  • 个人提醒和通知
  • 团队协作通知

🚀 最佳实践

  • 使用有意义的主题名称
  • 合理设置消息优先级
  • 避免频繁发送消息
  • 自托管提高可靠性
  • 配置适当的访问控制

💡 高级技巧

  • 结合脚本实现自动化监控
  • 使用操作按钮提高交互性
  • 集成到 CI/CD 流程
  • 配置多个主题分类管理

掌握 ntfy,让你的通知系统变得简单而强大!


ntfy 让消息推送回归简单,是现代开发者不可或缺的工具。