---
# System prepended metadata

title: Ntfy介紹_自行架設及使用說明
tags: [Python]

---

---
disqus: ahb0222
GA : G-VF9ZT413CG
---
> [color=#40f1ef][name=LHB阿好伯, 2024/10/22][:earth_africa:](https://www.facebook.com/LHB0222/)


[TOC]

![image](https://hackmd.io/_uploads/HkzcegpSkl.png)


# Ntfy介紹
Ntfy 是一個簡單但功能強大的發布-訂閱（pub-sub）通知服務
通過 HTTP PUT/POST 請求發送通知到任何設備
這是一個自託管的替代方案
可以取代 Line Notify 等服務
## 主要特點：
- ### 通知方式多元化：
    - 手機推播通知（Android / iOS）
    - 電子郵件通知
    - WebSocket 即時通知
    - Webhook 整合

- ### 多種格式支援：

    - 純文字訊息
    - 結構化 JSON 資料
    - 附加檔案傳輸
    - 支援 Markdown 格式


- ### 安全性功能：
    - 訪問控制與身份驗證
    - 端到端加密
    - 即時訊息傳遞確認
    - 支援 HTTPS 加密傳輸

- ### 整合能力：
    - REST API 支援
    - 命令列工具
    - 支援 Docker 部署
    - 提供各種程式語言的套件(python/Go/PHP/C#/.NET/R/JS/Java/Node)

# Ntfy 相較於 Line Notify 的優點:

* 開源且可自行架設
* 支援多種通知方式
* 彈性的訂閱方案
* 具備檔案傳輸功能
* API 介面完整
* 不需要辦理帳號即可使用

使用上若是無自己架設的需求
使用官方伺服器路徑**https://ntfy.sh/** 即可
想一個不容易被猜到的topic(訊息主題)即可開始享用
其實跟MQTT很像
只是應用的方向不太一樣
有配套的APP使用上是能取代Line Notify

- 電腦端
    - https://ntfy.sh/app
![image](https://hackmd.io/_uploads/rkTglgTByl.png)

- 手機端
    - 安卓
    [![image](https://hackmd.io/_uploads/BJwwPgprJe.png)
](https://play.google.com/store/apps/details?id=io.heckel.ntfy)
    - 蘋果
    [![image](https://hackmd.io/_uploads/S1uIDeaSkl.png)
](https://apps.apple.com/us/app/ntfy/id1625396347)
![image](https://hackmd.io/_uploads/r1Y7gl6S1x.png)


# 結合Node-Red
![image](https://hackmd.io/_uploads/BJ1Qke6Byg.png)

```json=
[
    {
        "id": "c956e688cc74ad8e",
        "type": "http request",
        "z": "fabdd7a3.4045a",
        "name": "ntfy.sh",
        "method": "POST",
        "ret": "txt",
        "paytoqs": "ignore",
        "url": "https://ntfy.sh/mytopic",
        "tls": "",
        "persist": false,
        "proxy": "",
        "authType": "",
        "senderr": false,
        "credentials":
        {
            "user": "",
            "password": ""
        },
        "x": 590,
        "y": 3160,
        "wires":
        [
            []
        ]
    },
    {
        "id": "32ee1eade51fae50",
        "type": "function",
        "z": "fabdd7a3.4045a",
        "name": "data",
        "func": "msg.payload = \"Something happened\";\nmsg.headers = {};\nmsg.headers['tags'] = 'house';\nmsg.headers['X-Title'] = 'Home Assistant';\n\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 470,
        "y": 3160,
        "wires":
        [
            [
                "c956e688cc74ad8e"
            ]
        ]
    },
    {
        "id": "b287e59cd2311815",
        "type": "inject",
        "z": "fabdd7a3.4045a",
        "name": "Manual start",
        "props":
        [
            {
                "p": "payload"
            },
            {
                "p": "topic",
                "vt": "str"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": "20",
        "topic": "",
        "payload": "",
        "payloadType": "date",
        "x": 330,
        "y": 3160,
        "wires":
        [
            [
                "32ee1eade51fae50"
            ]
        ]
    }
]
```

```javascript=
// 使用 Base64 編碼中文標題
function encodeHeaderAsRFC2047(text) {
    // 轉換成 Base64
    const base64 = Buffer.from(text).toString('base64');
    return `=?UTF-8?B?${base64}?=`;
}

msg.payload = `測站: ${msg.payload.測站}
⏰ 時間: ${msg.payload.時間}
📊 偵測值: ${msg.payload.偵測值}
⚠️ 預警值: ${msg.payload.預警值}
`;

msg.headers = {};
msg.headers['tags'] = 'rotating_light';  // 加入警告和警報 emoji
msg.headers['X-Title'] = encodeHeaderAsRFC2047('監測警報');
msg.headers['Priority'] = 5;  // 設定最高優先級 (5 = max/urgent)

return msg;
```

# Home Assistant

結合到HA也十分方便
```yaml=
notify:
  - name: ntfy
    platform: rest
    method: POST_JSON
    data:
      topic: YOUR_NTFY_TOPIC
    title_param_name: title
    message_param_name: message
    resource: https://ntfy.sh
```

# 收費方案
主要是可以辦帳號用於保存相關的資料
![image](https://hackmd.io/_uploads/ry7jsyaryg.png)

# 是否可以商用?
最後最重要一點
Ntfy 是以  Apache 許可證 2.0 和 GPLv2 許可證下獲得雙重許可發布的開源軟體
Apache License 2.0 主要特點：
* 完全允許商業使用：
    * 可以免費用於商業目的
    * 可以修改原始碼
    * 可以自由分發
    * 可以私有使用

* 主要要求：
    * 必須保留原始版權聲明
    * 必須包含 Apache License 的副本
    * 若修改了代碼，需要明確說明


* 商業應用場景：
    * 企業內部部署
    * 整合到商業產品中
    * 提供託管服務
    * 二次開發後商用

* 授權相容性：
    * 與大多數開源授權相容
    * 可以與專有軟體結合
    * 可以改變授權條款

# 相關文章
[Line Notify替代方案 - discord|  Telegram Bot 以Node-Red 進行測試](/1j54odKDTvit8O3q5TktUw)


# 自架伺服器啟用文件發送

##創建緩存目錄：
```
sudo mkdir -p /var/cache/ntfy/attachments
sudo chown ntfy:ntfy /var/cache/ntfy/attachments
```

## 修改配置文件：
```
sudo nano /etc/ntfy/server.yml
```
添加以下內容：
```
base-url: "http://localhost:8080"
# 將 base-url 改為外部可訪問的 URL
#base-url: "URL"
attachment-cache-dir: "/var/cache/ntfy/attachments"
attachment-file-size-limit: "15M"
attachment-total-size-limit: "5G"
attachment-expiry-duration: "3h"

# 預設存取控制
auth-default-access: "read-write"

# 如果使用了 Cloudflare，建議添加以下設定
behind-proxy: true
```

## 預設存取控制（可選）

設定正確的權限：

```
sudo chown ntfy:ntfy /etc/ntfy/server.yml
sudo chmod 644 /etc/ntfy/server.yml
```

重啟服務：
```
sudo systemctl restart ntfy
```

確認服務狀態：
```
sudo systemctl status ntfy
```
## 啟動監測
### ntfy 服務配置
```
# 創建配置文件
sudo nano /etc/ntfy/server.yml

# 添加以下內容
base-url: "http://localhost:8080"
listen-http: ":8080"
log-level: info
log-file: /var/log/ntfy.log
enable-metrics: true
metrics-listen-http: ":9090"

# 創建所需目錄和設置權限
sudo mkdir -p /var/log/ntfy
sudo mkdir -p /var/cache/ntfy
sudo useradd -r -s /bin/false ntfy
sudo chown -R ntfy:ntfy /var/log/ntfy
sudo chown -R ntfy:ntfy /var/cache/ntfy
sudo chown -R ntfy:ntfy /etc/ntfy

# 重啟 ntfy 服務
sudo systemctl restart ntfy
```
### Prometheus 安裝和配置
```
# 安裝 Prometheus
sudo apt update
sudo apt install prometheus

# 配置 Prometheus
sudo nano /etc/prometheus/prometheus.yml

# 添加以下配置
global:
  scrape_interval: 15s

scrape_configs:
  - job_name: "prometheus"
    static_configs:
      - targets: ["localhost:9091"]

  - job_name: "ntfy"
    static_configs:
      - targets: ["localhost:9090"]

# 修改啟動配置
sudo nano /etc/default/prometheus
ARGS="--web.listen-address=:9091"

# 設置權限
sudo chown prometheus:prometheus /etc/prometheus/prometheus.yml
sudo chmod 644 /etc/prometheus/prometheus.yml

# 重啟 Prometheus
sudo systemctl restart prometheus
```

### Grafana 安裝和配置
```
# 添加 Grafana GPG 密鑰
sudo apt-get install -y software-properties-common wget
sudo wget -q -O /usr/share/keyrings/grafana.key https://packages.grafana.com/gpg.key

# 添加 Grafana 倉庫
echo "deb [signed-by=/usr/share/keyrings/grafana.key] https://packages.grafana.com/oss/deb stable main" | sudo tee -a /etc/apt/sources.list.d/grafana.list

# 更新並安裝 Grafana
sudo apt-get update
sudo apt-get install grafana

# 啟動 Grafana
sudo systemctl enable grafana-server
sudo systemctl start grafana-server
```
>[!Note]
>Grafana配置檔
>https://raw.githubusercontent.com/binwiederhier/ntfy/main/examples/grafana-dashboard/ntfy-grafana.json

![image](https://hackmd.io/_uploads/r1uib7AvJg.png)

# python使用範例
```python=
import requests
import os
from pathlib import Path
import mimetypes

def get_mime_type(file_path):
    """
    取得檔案的 MIME 類型
    """
    mime_type, _ = mimetypes.guess_type(file_path)
    return mime_type or 'application/octet-stream'

def send_to_ntfy(server_url, topic, message=None, image_path=None):
    """
    發送訊息和圖片到NTFY服務器
    
    參數:
        server_url (str): NTFY服務器的URL (不包含結尾的斜線)
        topic (str): NTFY的主題名稱
        message (str, optional): 要發送的文字訊息
        image_path (str, optional): 圖片檔案的路徑
    """
    # 確保 server_url 不以斜線結尾
    server_url = server_url.rstrip('/')
    
    # 構建完整的URL
    full_url = f"{server_url}/{topic}"
    
    try:
        # 如果有文字訊息要發送
        if message:
            response = requests.post(
                full_url,
                data=message.encode('utf-8'),
                headers={"Content-Type": "text/plain"}
            )
            print(f"訊息發送狀態碼: {response.status_code}")
            print(f"訊息發送回應: {response.text}")
            
            if response.status_code != 200:
                print(f"發送訊息失敗: {response.status_code}")
                return False
        
        # 如果有圖片要發送
        if image_path and os.path.exists(image_path):
            # 檢查檔案是否存在
            if not os.path.exists(image_path):
                print(f"找不到檔案: {image_path}")
                return False
                
            # 取得檔案大小
            file_size = os.path.getsize(image_path)
            print(f"檔案大小: {file_size} bytes")
            
            # 取得檔案的 MIME 類型
            content_type = get_mime_type(image_path)
            print(f"檔案類型: {content_type}")
            
            with open(image_path, 'rb') as image_file:
                filename = Path(image_path).name
                headers = {
                    "Filename": filename,
                    "Content-Type": content_type,
                    # 避免使用中文標頭
                    "Title": "Image Upload",
                    "Tags": "upload,image"
                }
                
                print(f"發送請求到: {full_url}")
                print(f"使用標頭: {headers}")
                
                response = requests.put(
                    full_url,
                    data=image_file,
                    headers=headers
                )
                
                print(f"圖片發送狀態碼: {response.status_code}")
                if response.text:
                    print(f"圖片發送回應: {response.text}")
                
                if response.status_code != 200:
                    print(f"發送圖片失敗: {response.status_code}")
                    return False
        
        return True
    
    except Exception as e:
        print(f"發送過程中發生錯誤: {str(e)}")
        return False

# 使用範例
if __name__ == "__main__":
    # 設定參數
    SERVER_URL = "http://localhost:8080"  # 不要在結尾加斜線
    TOPIC = "FTIR_OCR"
    MESSAGE = "這是一個測試訊息"
    IMAGE_PATH = "/home/huan-yu/processed_image.png"
    
    # 發送訊息和圖片
    success = send_to_ntfy(
        server_url=SERVER_URL,
        topic=TOPIC,
        message=MESSAGE,
        image_path=IMAGE_PATH
    )
    
    if success:
        print("訊息和圖片發送成功！")
    else:
        print("發送失敗，請檢查錯誤訊息。")
```

# 問題排除
##  IOS 無法進行警訊通知
查看網路上討論上也有許多人發生一樣的問題
https://github.com/binwiederhier/ntfy/issues/880
https://github.com/binwiederhier/ntfy/issues/1191

https://docs.ntfy.sh/config/#web-push

目前最佳的解決方案為使用PWA
:::success
需求條件
* 有效的 HTTPS 憑證
* 需設定 VAPID 金鑰對
* 資料庫儲存訪客訂閱資訊
* 管理員郵箱
:::

```bash=
# 產生 VAPID 金鑰
sudo ntfy webpush keys

# 編輯設定檔
sudo nano /etc/ntfy/server.yml

# 重啟服務
sudo systemctl restart ntfy
```
server.yml中增加以下資訊
:::info
web-push-public-key: [公鑰]
web-push-private-key: [私鑰]
web-push-file: /var/cache/ntfy/webpush.db
web-push-email-address: [管理員郵箱]
:::

🌟全文可以至下方連結觀看或是補充


全文分享至

https://www.facebook.com/LHB0222/

https://www.instagram.com/ahb0222/

有疑問想討論的都歡迎於下方留言

喜歡的幫我分享給所有的朋友 \o/

有所錯誤歡迎指教

# [:page_with_curl: 全部文章列表](https://hackmd.io/@LHB-0222/AllWritings)



![](https://i.imgur.com/nHEcVmm.jpg)


