Skip to content

Commit a60d492

Browse files
feat: --localhost flag for reverse-proxy auth, split config files, Google demo target
- --localhost binds 127.0.0.1 only; auth/TLS delegated to Caddy/Nginx/Apache (DEPLOY.md examples) - optional split configs: webhooks.jsonc (replaces section), alerts.jsonc (partial override over defaults) - shipped examples: webhooks.example.jsonc, alerts.example.jsonc, targets/*.list.example - zero-config demo target: www.baidu.com -> www.google.com
1 parent aac98bd commit a60d492

9 files changed

Lines changed: 121 additions & 6 deletions

DEPLOY.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,36 @@ systemctl enable --now pingping
4242
# crontab,配合 healthchecks.io 或内网等价物
4343
*/5 * * * * curl -fsS http://127.0.0.1:8517/api/targets >/dev/null && curl -fsS https://hc-ping.com/<uuid> >/dev/null
4444
```
45+
46+
## 反向代理认证(--localhost)
47+
48+
不想让 pingping 自己管密码?加 `--localhost` 参数只绑定 127.0.0.1,认证和 TLS 交给前端反代:
49+
50+
```bash
51+
./pingping --localhost -c pingping.jsonc
52+
```
53+
54+
Caddy(自动 HTTPS + basic auth):
55+
56+
```
57+
ping.example.com {
58+
basic_auth {
59+
yong $2a$14$... # caddy hash-password 生成
60+
}
61+
reverse_proxy 127.0.0.1:8517
62+
}
63+
```
64+
65+
Nginx:
66+
67+
```nginx
68+
server {
69+
listen 443 ssl;
70+
server_name ping.example.com;
71+
auth_basic "pingping";
72+
auth_basic_user_file /etc/nginx/htpasswd; # htpasswd -c 生成
73+
location / { proxy_pass http://127.0.0.1:8517; }
74+
}
75+
```
76+
77+
此时可不设 `web_password`(反代已挡在前面),或两层都开当双保险。

alerts.example.jsonc

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
// 告警单独配置文件 —— 复制为 alerts.jsonc(与 pingping.jsonc 同目录)后生效,
3+
// 只需写想改的字段,未写的保持内置默认;目标级 sensitivity 三档在此基础上缩放。
4+
"alerts": {
5+
"cooldown_min": 30, // 同一告警重复推送冷却
6+
"recover_min": 15, // 持续正常多久才发恢复
7+
"degrade_ratio": 1.5, // P99 超基线倍数判劣化
8+
"degrade_min_ms": 10, // 且绝对增量下限,防低延迟链路误报
9+
"burst_z": 3.5, // 丢包突发 robust z-score 阈值
10+
"burst_alert_n": 3 // 30 分钟内突发 N 次升级为告警
11+
}
12+
}

config.example.jsonc

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
{
2-
// pingping 配置示例 —— 复制为 pingping.jsonc 后修改
2+
// pingping 主配置示例 —— 复制为 pingping.jsonc 后修改
3+
// 配置可拆分(可选):webhooks → webhooks.jsonc,告警阈值 → alerts.jsonc(与本文件同目录,
4+
// 存在即优先);探测目标推荐放 targets/ping.list、tcp.list(见 targets/*.example)
35
// 支持 // 和 /* */ 注释。所有字段都有合理默认值,最小配置只需 targets + webhooks。
46

57
"listen": "0.0.0.0:8517", // Web 监听地址

config.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,29 @@ func LoadConfig(path string) (*Config, error) {
171171
if err := json.Unmarshal(stripJSONC(raw), cfg); err != nil {
172172
return nil, fmt.Errorf("解析 %s: %w", path, err)
173173
}
174+
// 拆分配置:主配置同目录下的 webhooks.jsonc / alerts.jsonc 若存在则优先。
175+
// webhooks.jsonc 整体替换 webhooks;alerts.jsonc 在默认值之上做局部覆盖。
176+
confDir := filepath.Dir(path)
177+
if raw, err := os.ReadFile(filepath.Join(confDir, "webhooks.jsonc")); err == nil {
178+
var w struct {
179+
Webhooks []WebhookCfg `json:"webhooks"`
180+
}
181+
if err := json.Unmarshal(stripJSONC(raw), &w); err != nil {
182+
return nil, fmt.Errorf("解析 webhooks.jsonc: %w", err)
183+
}
184+
cfg.Webhooks = w.Webhooks
185+
}
186+
if raw, err := os.ReadFile(filepath.Join(confDir, "alerts.jsonc")); err == nil {
187+
tmp := cfg.Alerts
188+
var a struct {
189+
Alerts *AlertCfg `json:"alerts"`
190+
}
191+
a.Alerts = &tmp
192+
if err := json.Unmarshal(stripJSONC(raw), &a); err != nil {
193+
return nil, fmt.Errorf("解析 alerts.jsonc: %w", err)
194+
}
195+
cfg.Alerts = tmp
196+
}
174197
if cfg.TargetsDir == "" {
175198
cfg.TargetsDir = "./targets"
176199
}

main.go

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ const demoConfig = `{
3030
const demoPingList = `# 一行一个 ICMP 探测目标,# 是注释,改完重启生效
3131
# 格式:host [名称] [pace=fast|slow] [sensitivity=strict|relaxed] [interval=秒] [alerts=false] [自定义k=v]
3232
# 例:59.43.247.1 香港CN2 pace=fast sensitivity=strict 机房=GZ1 负责人=张三
33-
www.baidu.com 演示·百度 pace=fast
33+
www.google.com Demo·Google pace=fast
3434
`
3535

3636
// portOf 从监听地址提取给人看的端口后缀。
@@ -44,8 +44,9 @@ func portOf(listen string) string {
4444
}
4545

4646
func main() {
47-
cfgPath := flag.String("c", "pingping.jsonc", "配置文件路径 (JSONC)")
48-
showVer := flag.Bool("version", false, "打印版本")
47+
cfgPath := flag.String("c", "pingping.jsonc", "config file path (JSONC)")
48+
localOnly := flag.Bool("localhost", false, "bind 127.0.0.1 only — put Caddy/Nginx/Apache in front for auth/TLS")
49+
showVer := flag.Bool("version", false, "print version")
4950
flag.Parse()
5051

5152
if *showVer {
@@ -55,15 +56,15 @@ func main() {
5556

5657
cfg, err := LoadConfig(*cfgPath)
5758
if err != nil {
58-
// 零配置开箱:找不到配置文件时自动生成演示配置(探测 www.baidu.com),
59+
// 零配置开箱:找不到配置文件时自动生成演示配置(探测 www.google.com),
5960
// 下载解压、执行、开浏览器,三十秒看到第一缕烟。
6061
if os.IsNotExist(err) {
6162
if werr := os.WriteFile(*cfgPath, []byte(demoConfig), 0o644); werr == nil {
6263
os.MkdirAll("targets", 0o755)
6364
if _, serr := os.Stat("targets/ping.list"); os.IsNotExist(serr) {
6465
os.WriteFile("targets/ping.list", []byte(demoPingList), 0o644)
6566
}
66-
log.Printf("未找到配置文件,已生成演示配置 %s + targets/ping.list(探测 www.baidu.com)", *cfgPath)
67+
log.Printf("未找到配置文件,已生成演示配置 %s + targets/ping.list(探测 www.google.com)", *cfgPath)
6768
log.Printf("加监控就一行:echo \"1.2.3.4 广州电信\" >> targets/ping.list,重启生效")
6869
cfg, err = LoadConfig(*cfgPath)
6970
}
@@ -73,6 +74,10 @@ func main() {
7374
}
7475
}
7576

77+
if *localOnly {
78+
cfg.Listen = "127.0.0.1" + portOf(cfg.Listen)
79+
}
80+
7681
store, err := NewStore(cfg.DataDir, cfg.Targets)
7782
if err != nil {
7883
log.Fatalf("存储初始化失败: %v", err)

notify_test.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package main
22

33
import (
4+
"os"
45
"strings"
56
"testing"
67
)
@@ -71,3 +72,21 @@ func TestParseListLine(t *testing.T) {
7172
t.Fatal("tcp 缺端口应报错")
7273
}
7374
}
75+
76+
func TestSplitConfigFiles(t *testing.T) {
77+
dir := t.TempDir()
78+
os.WriteFile(dir+"/pingping.jsonc", []byte(`{"targets":[{"name":"t","host":"127.0.0.1"}],
79+
"webhooks":[{"name":"inline","url":"http://x/1"}]}`), 0o644)
80+
os.WriteFile(dir+"/webhooks.jsonc", []byte(`{"webhooks":[{"name":"split","url":"http://x/2","format":"json"}]}`), 0o644)
81+
os.WriteFile(dir+"/alerts.jsonc", []byte(`{"alerts":{"cooldown_min":99}}`), 0o644)
82+
cfg, err := LoadConfig(dir + "/pingping.jsonc")
83+
if err != nil {
84+
t.Fatal(err)
85+
}
86+
if len(cfg.Webhooks) != 1 || cfg.Webhooks[0].Name != "split" || cfg.Webhooks[0].Format != "json" {
87+
t.Fatalf("webhooks.jsonc 应整体替换: %+v", cfg.Webhooks)
88+
}
89+
if cfg.Alerts.CooldownMin != 99 || cfg.Alerts.DegradeRatio != 1.5 {
90+
t.Fatalf("alerts.jsonc 应局部覆盖且保留默认: %+v", cfg.Alerts)
91+
}
92+
}

targets/ping.list.example

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# ICMP targets — one per line, restart to apply. Copy to ping.list.
2+
# format: host [name] [pace=fast|slow] [sensitivity=strict|relaxed] [interval=sec] [alerts=false] [custom k=v]
3+
www.google.com Demo·Google pace=fast
4+
59.43.247.1 HK-CN2 pace=fast sensitivity=strict dc=GZ1 owner=zhang
5+
203.0.113.10 Branch-Office pace=slow alerts=false

targets/tcp.list.example

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# TCP targets — host:port per line, restart to apply. Copy to tcp.list.
2+
10.0.0.5:443 API-Gateway interval=30
3+
203.0.113.20:22 Backup-SSH pace=slow

webhooks.example.jsonc

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
// Webhook 单独配置文件 —— 复制为 webhooks.jsonc(与 pingping.jsonc 同目录)后生效,
3+
// 存在时整体替代主配置中的 webhooks 段。
4+
// secret: 飞书"签名校验"密钥;kinds: alert recovery heartbeat daily manual(不填全收)
5+
// format: "feishu"(默认,交互卡片)| "json"(裸 JSON 事件,任意系统可接)
6+
"webhooks": [
7+
{ "name": "Ops", "url": "https://open.feishu.cn/open-apis/bot/v2/hook/xxxxxxxx",
8+
"secret": "", "kinds": ["alert", "recovery", "heartbeat"] },
9+
{ "name": "Managers", "url": "https://open.feishu.cn/open-apis/bot/v2/hook/yyyyyyyy",
10+
"kinds": ["daily"] },
11+
{ "name": "Platform", "url": "https://your-platform/hook", "format": "json" }
12+
]
13+
}

0 commit comments

Comments
 (0)