Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 11 additions & 9 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,19 +59,21 @@ jobs:
git-config-name: github-pages-deploy-action
git-config-email: noreply@github.com

- name: "⚠️ Notify Unavailable Services"
- name: "⚠️ Handle Service Notifications"
run: |
if [ -f data/notify.txt ] && [ -s data/notify.txt ]; then
echo "Unavailable services found:"
echo "Service issues detected:"
cat data/notify.txt
if [ -f "./notify.sh" ]; then
echo "Running custom notification script..."
chmod +x ./notify.sh
./notify.sh
else
echo "No custom notification script found, using default notification."
echo "Service issues have been reported through the configured notification channels."
echo "If no notifications were received, please check your notification configuration in config.yaml"
if [ "$PONGHUB_HAS_ALERTS" = "true" ]; then
echo ""
echo "🚨 DEFAULT NOTIFICATION: GitHub Actions failure triggered as notification method"
echo "This workflow is failing intentionally to alert you of service issues."
echo "To receive notifications through other channels, configure them in config.yaml"
echo ""
exit 1
fi
else
echo "No unavailable services."
echo "No service issues detected."
fi
324 changes: 242 additions & 82 deletions README.md

Large diffs are not rendered by default.

165 changes: 163 additions & 2 deletions README_CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ PongHub 是一个开源的服务状态监控网站,旨在帮助用户监控和
| `services.endpoints.body` | 字符串 | 请求体内容 | ✖️ | 仅在 `POST`/`PUT` 请求时使用 |
| `services.endpoints.status_code` | 整数 | 响应体期望的 HTTP 状态码(默认 `200`) | ✖️ | 默认 `200` |
| `services.endpoints.response_regex` | 字符串 | 响应体内容的正则表达式匹配 | ✖️ | |
| `notifications` | 对象 | 通知配置 | ✖️ | 详见 [自定义通知](#自定义通知) |

下面是一个示例配置文件:

Expand Down Expand Up @@ -257,9 +258,163 @@ services:

### 自定义通知

PongHub 默认利用 GitHub Actions 报错实现异常告警通知
PongHub 现在支持多种通知方式,当服务出现问题或证书即将过期时,可以通过多个渠道发送警报通知

如果需要自定义通知,可以在根目录下创建 `notify.sh` 脚本,脚本可以读取 `data/notify.txt` 文件中的内容,并通过邮件、短信或其他方式发送通知。如果脚本使用到了环境变量,请确保在 GitHub 仓库的 "Settings" -> "Secrets and variables" -> "Actions" 中正确设置这些变量。
<details>
<summary>点击展开查看支持的通知类型</summary>

<div markdown="1">

PongHub 支持以下通知方式:

- **默认通知** - 通过GitHub Actions工作流失败进行通知
- **邮件通知** - 通过SMTP发送邮件
- **Discord** - 通过Webhook发送到Discord频道
- **Slack** - 通过Webhook发送到Slack频道
- **Telegram** - 通过Bot API发送消息
- **企业微信** - 通过企业微信群机器人发送消息
- **自定义Webhook** - 发送到任意HTTP端点

使用时,在 `config.yaml` 文件中添加 `notifications` 配置块:

```yaml
notifications:
enabled: true # 启用通知功能
methods: # 要启用的通知方式
- email
- discord
- slack
- telegram
- wechat
- webhook

# 各种通知方式的具体配置...
```

#### ⚙️ 默认通知

默认情况下,PongHub 会在 GitHub Actions 工作流失败时发送通知。

默认通知会在以下情况自动启用:

- 没有配置 `notifications` 字段
- `notifications.enabled: true` 但没有指定 `methods`
- 显式配置 `methods: ["default"]`

#### 📧 邮件通知

```yaml
email:
smtp_host: "smtp.gmail.com" # SMTP服务器地址
smtp_port: 587 # SMTP端口
from: "alerts@yourdomain.com" # 发件人邮箱
to: # 收件人列表
- "admin@yourdomain.com"
- "ops@yourdomain.com"
subject: "PongHub Service Alert" # 邮件主题(可选)
use_tls: true # 使用 TLS(可选)
use_starttls: true # 使用 StartTLS(可选)
skip_verify: false # 跳过证书验证(可选)
```

所需环境变量:

- `SMTP_USERNAME` - SMTP用户名
- `SMTP_PASSWORD` - SMTP密码

#### 💬 Discord 配置

```yaml
discord:
webhook_url: "https://discord.com/api/webhooks/your_webhook_id/your_webhook_token" # 留空则从环境变量读取
username: "PongHub Bot" # 发送消息的用户名(可选)
avatar_url: "" # 发送消息的头像URL(可选)
```

所需环境变量:

- `DISCORD_WEBHOOK_URL` - Discord Webhook URL

#### 💬 Slack 配置

```yaml
slack:
webhook_url: "https://hooks.slack.com/services/your/webhook/url" # 留空则从环境变量读取
channel: "#alerts" # 发送消息的频道(可选)
username: "PongHub Bot" # 发送消息的用户名(可选)
icon_emoji: ":robot_face:" # 消息图标(可选)
```

所需环境变量:

- `SLACK_WEBHOOK_URL` - Slack Webhook URL

#### 💬 Telegram 配置

```yaml
telegram:
bot_token: "your_bot_token" # 留空则从环境变量读取
chat_id: "your_chat_id" # 留空则从环境变量读取
```

所需环境变量:

- `TELEGRAM_BOT_TOKEN` - Telegram 机器人 Token
- `TELEGRAM_CHAT_ID` - Telegram 聊天 ID

#### 💬 企业微信配置

```yaml
wechat:
webhook_url: "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=your_key" # 留空则从环境变量读取
```

所需环境变量:

- `WECHAT_WEBHOOK_URL` - 企业微信群机器人 Webhook URL

#### 💬 自定义Webhook配置

```yaml
webhook:
url: "https://your-webhook-endpoint.com/notify" # 留空则从环境变量读取
method: "POST" # HTTP方法(可选,默认POST)
headers: # 自定义请求头(可选)
Content-Type: "application/json"
```

所需环境变量:

- `WEBHOOK_URL` - 自定义 Webhook URL

</div>
</details>

以上所需的环境变量均可通过 GitHub Actions 的 Repository Secrets 设置。

下面是一个示例配置文件:

```yaml
services:
- name: "Example Service"
endpoints:
- url: "https://example.com/health"
notifications:
enabled: true
methods:
- email
- discord
email:
smtp_host: "smtp.gmail.com"
smtp_port: 587
from: "alerts@yourdomain.com"
to:
- "admin@yourdomain.com"
- "ops@yourdomain.com"
discord:
webhook_url: "https://discord.com/api/webhooks/your_webhook_id/your_webhook_token"
username: "PongHub Bot"
```

## 本地开发

Expand All @@ -269,6 +424,12 @@ PongHub 默认利用 GitHub Actions 报错实现异常告警通知。
make run
```

项目有一些测试用例,可以通过以下命令运行测试:

```bash
make test
```

## 免责声明

[PongHub](https://github.com/WCY-dt/ponghub) 仅用于个人学习和研究,不对程序的使用行为或结果负责。请勿将其用于商业用途或非法活动。
3 changes: 2 additions & 1 deletion cmd/ponghub/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,9 @@ func main() {
// check services based on the configuration
checkResult := checker.CheckServices(cfg)

// write notifications based on the check results
// notify the result
notifier.WriteNotifications(checkResult, cfg.CertNotifyDays)
notifier.SendNotifications(checkResult, cfg.CertNotifyDays, cfg.Notifications)

// get and write log results
logResult, err := logger.GetLog(checkResult, cfg.MaxLogDays, default_config.GetLogPath())
Expand Down
62 changes: 17 additions & 45 deletions cmd/ponghub/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,65 +17,37 @@ import (

// TestMain_append tests the main functionality when appending to an existing log file.
func TestMain_append(t *testing.T) {
// load the default configuration
cfg, err := configure.ReadConfigs(default_config.GetConfigPath())
if err != nil {
log.Fatalln("Error loading config at", default_config.GetConfigPath(), ":", err)
}

// copy log file to a temporary location for testing
logPath := default_config.GetLogPath()
if err := copyLogFile(logPath, tmpLogPath); err != nil {
log.Fatalln("Error copying log file:", err)
}

// check services based on the configuration
checkResult := checker.CheckServices(cfg)

// write notifications based on the check results
notifier.WriteNotifications(checkResult, cfg.CertNotifyDays)

// get and write log results
logResult, err := logger.GetLog(checkResult, cfg.MaxLogDays, tmpLogPath)
if err != nil {
log.Fatalln("Error outputting checkResult:", err)
}
if err := logger.WriteLog(logResult, tmpLogPath); err != nil {
log.Fatalln("Error writing logs to", tmpLogPath, ":", err)
} else {
log.Println("Logs written to", tmpLogPath)
}

// generate the report based on the checkResult
reportResult, err := reporter.GetReport(checkResult, tmpLogPath, cfg)
if err != nil {
log.Fatalln("Error generating report data:", err)
}
if err := reporter.WriteReport(reportResult, default_config.GetReportPath(), cfg.DisplayNum); err != nil {
log.Fatalln("Error generating report:", err)
} else {
log.Println("Report generated at", default_config.GetReportPath())
}

// Remove the temporary log file after tests
if err := os.Remove(tmpLogPath); err != nil {
log.Println("Error removing temporary log file:", err)
}
runMainFunctionality(true)
}

// TestMain_new tests the main functionality when creating a new log file.
func TestMain_new(t *testing.T) {
runMainFunctionality(false)
}

// runMainFunctionality runs the main functionality for testing purposes.
// If copyExistingLog is true, it copies the existing log file to a temporary location.
func runMainFunctionality(copyExistingLog bool) {
// load the default configuration
cfg, err := configure.ReadConfigs(default_config.GetConfigPath())
if err != nil {
log.Fatalln("Error loading config at", default_config.GetConfigPath(), ":", err)
}

// copy log file to a temporary location for testing (only if copyExistingLog is true)
if copyExistingLog {
logPath := default_config.GetLogPath()
if err := copyLogFile(logPath, tmpLogPath); err != nil {
log.Fatalln("Error copying log file:", err)
}
}

// check services based on the configuration
checkResult := checker.CheckServices(cfg)

// write notifications based on the check results
// notify the result
notifier.WriteNotifications(checkResult, cfg.CertNotifyDays)
notifier.SendNotifications(checkResult, cfg.CertNotifyDays, cfg.Notifications)

// get and write log results
logResult, err := logger.GetLog(checkResult, cfg.MaxLogDays, tmpLogPath)
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ go 1.24.5

require gopkg.in/yaml.v3 v3.0.1

require github.com/google/uuid v1.6.0 // indirect
require github.com/google/uuid v1.6.0
45 changes: 45 additions & 0 deletions internal/notifier/channels/default.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package channels

import (
"fmt"
"log"
"os"

"github.com/wcy-dt/ponghub/internal/types/structures/configure"
)

// DefaultNotifier implements the NotificationService interface for default GitHub Actions notifications
type DefaultNotifier struct {
config *configure.DefaultConfig
}

// NewDefaultNotifier creates a new default notifier
func NewDefaultNotifier(config *configure.DefaultConfig) *DefaultNotifier {
return &DefaultNotifier{
config: config,
}
}

// Send implements the NotificationService interface
// For default notifications, we write to stderr and set an exit flag
func (d *DefaultNotifier) Send(title, message string) error {
if d.config == nil {
return fmt.Errorf("default notifier config is nil")
}

log.Println("🚨 DEFAULT NOTIFICATION TRIGGERED 🚨")
log.Printf("Title: %s", title)
log.Printf("Message:\n%s", message)

// Write to stderr for GitHub Actions to capture
_, _ = fmt.Fprintf(os.Stderr, "\n=== PongHub Alert ===\n")
_, _ = fmt.Fprintf(os.Stderr, "%s\n\n", title)
_, _ = fmt.Fprintf(os.Stderr, "%s\n", message)
_, _ = fmt.Fprintf(os.Stderr, "=====================\n\n")

// Set environment variable to indicate failure should occur
_ = os.Setenv("PONGHUB_HAS_ALERTS", "true")

log.Println("Default notification sent - GitHub Actions will be notified of service issues")
return nil
}
Loading
Loading