Docs docs docs
This commit is contained in:
@@ -11,6 +11,8 @@ import (
|
||||
// Defines default config settings (excluding limits, see below)
|
||||
const (
|
||||
DefaultListenHTTP = ":80"
|
||||
DefaultConfigFile = "/etc/ntfy/server.yml"
|
||||
DefaultTemplateDir = "/etc/ntfy/templates"
|
||||
DefaultCacheDuration = 12 * time.Hour
|
||||
DefaultCacheBatchTimeout = time.Duration(0)
|
||||
DefaultKeepaliveInterval = 45 * time.Second // Not too frequently to save battery (Android read timeout used to be 77s!)
|
||||
@@ -173,7 +175,7 @@ type Config struct {
|
||||
// NewConfig instantiates a default new server config
|
||||
func NewConfig() *Config {
|
||||
return &Config{
|
||||
File: "", // Only used for testing
|
||||
File: DefaultConfigFile, // Only used for testing
|
||||
BaseURL: "",
|
||||
ListenHTTP: DefaultListenHTTP,
|
||||
ListenHTTPS: "",
|
||||
@@ -196,6 +198,7 @@ func NewConfig() *Config {
|
||||
AttachmentTotalSizeLimit: DefaultAttachmentTotalSizeLimit,
|
||||
AttachmentFileSizeLimit: DefaultAttachmentFileSizeLimit,
|
||||
AttachmentExpiryDuration: DefaultAttachmentExpiryDuration,
|
||||
TemplateDir: DefaultTemplateDir,
|
||||
KeepaliveInterval: DefaultKeepaliveInterval,
|
||||
ManagerInterval: DefaultManagerInterval,
|
||||
DisallowedTopics: DefaultDisallowedTopics,
|
||||
@@ -258,6 +261,5 @@ func NewConfig() *Config {
|
||||
WebPushEmailAddress: "",
|
||||
WebPushExpiryDuration: DefaultWebPushExpiryDuration,
|
||||
WebPushExpiryWarningDuration: DefaultWebPushExpiryWarningDuration,
|
||||
TemplateDir: "",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,6 +126,26 @@
|
||||
# attachment-file-size-limit: "15M"
|
||||
# attachment-expiry-duration: "3h"
|
||||
|
||||
# Template directory for message templates.
|
||||
#
|
||||
# When "X-Template: <name>" (aliases: "Template: <name>", "Tpl: <name>") or "?template=<name>" is set, transform the message
|
||||
# based on one of the built-in pre-defined templates, or on a template defined in the "template-dir" directory.
|
||||
#
|
||||
# Template files must have the ".yml" extension and must be formatted as YAML. They may contain "title" and "message" keys,
|
||||
# which are interpreted as Go templates.
|
||||
#
|
||||
# Example template file (e.g. /etc/ntfy/templates/grafana.yml):
|
||||
# title: |
|
||||
# {{- if eq .status "firing" }}
|
||||
# {{ .title | default "Alert firing" }}
|
||||
# {{- else if eq .status "resolved" }}
|
||||
# {{ .title | default "Alert resolved" }}
|
||||
# {{- end }}
|
||||
# message: |
|
||||
# {{ .message | trunc 2000 }}
|
||||
#
|
||||
# template-dir: "/etc/ntfy/templates"
|
||||
|
||||
# If enabled, allow outgoing e-mail notifications via the 'X-Email' header. If this header is set,
|
||||
# messages will additionally be sent out as e-mail using an external SMTP server.
|
||||
#
|
||||
|
||||
@@ -2918,7 +2918,7 @@ func TestServer_MessageTemplate_Range(t *testing.T) {
|
||||
|
||||
require.Equal(t, 200, response.Code)
|
||||
m := toMessage(t, response.Body.String())
|
||||
require.Equal(t, "Severe URLs:\n- https://severe1.com\n- https://severe2.com\n", m.Message)
|
||||
require.Equal(t, "Severe URLs:\n- https://severe1.com\n- https://severe2.com", m.Message)
|
||||
}
|
||||
|
||||
func TestServer_MessageTemplate_ExceedMessageSize_TemplatedMessageOK(t *testing.T) {
|
||||
@@ -2971,8 +2971,7 @@ Labels:
|
||||
Annotations:
|
||||
- summary = 15m load average too high
|
||||
Source: localhost:3000/alerting/grafana/NW9oDw-4z/view
|
||||
Silence: localhost:3000/alerting/silence/new?alertmanager=grafana&matcher=alertname%3DLoad+avg+15m+too+high&matcher=grafana_folder%3DNode+alerts&matcher=instance%3D10.108.0.2%3A9100&matcher=job%3Dnode-exporter
|
||||
`, m.Message)
|
||||
Silence: localhost:3000/alerting/silence/new?alertmanager=grafana&matcher=alertname%3DLoad+avg+15m+too+high&matcher=grafana_folder%3DNode+alerts&matcher=instance%3D10.108.0.2%3A9100&matcher=job%3Dnode-exporter`, m.Message)
|
||||
}
|
||||
|
||||
func TestServer_MessageTemplate_GitHub(t *testing.T) {
|
||||
@@ -3073,18 +3072,75 @@ func TestServer_MessageTemplate_UnsafeSprigFunctions(t *testing.T) {
|
||||
var (
|
||||
//go:embed testdata/webhook_github_comment_created.json
|
||||
githubCommentCreatedJSON string
|
||||
|
||||
//go:embed testdata/webhook_github_issue_opened.json
|
||||
githubIssueOpenedJSON string
|
||||
)
|
||||
|
||||
func TestServer_MessageTemplate_FromNamedTemplate(t *testing.T) {
|
||||
func TestServer_MessageTemplate_FromNamedTemplate_GitHubCommentCreated(t *testing.T) {
|
||||
t.Parallel()
|
||||
s := newTestServer(t, newTestConfig(t))
|
||||
response := request(t, s, "POST", "/mytopic", githubCommentCreatedJSON, map[string]string{
|
||||
"Template": "github",
|
||||
})
|
||||
response := request(t, s, "POST", "/mytopic?template=github", githubCommentCreatedJSON, nil)
|
||||
require.Equal(t, 200, response.Code)
|
||||
m := toMessage(t, response.Body.String())
|
||||
require.Equal(t, "💬 New comment on issue #1389 — instant alerts without Pull to refresh", m.Title)
|
||||
require.Equal(t, "💬 New comment on issue #1389 — instant alerts without Pull to refresh", m.Message)
|
||||
require.Equal(t, "💬 [ntfy] New comment on issue #1389 instant alerts without Pull to refresh", m.Title)
|
||||
require.Equal(t, `Commenter: https://github.com/wunter8
|
||||
Repository: https://github.com/binwiederhier/ntfy
|
||||
Comment link: https://github.com/binwiederhier/ntfy/issues/1389#issuecomment-3078214289
|
||||
|
||||
Comment:
|
||||
These are the things you need to do to get iOS push notifications to work:
|
||||
1. open a browser to the web app of your ntfy instance and copy the URL (including "http://" or "https://", your domain or IP address, and any ports, and excluding any trailing slashes)
|
||||
2. put the URL you copied in the ntfy `+"`"+`base-url`+"`"+` config in server.yml or NTFY_BASE_URL in env variables
|
||||
3. put the URL you copied in the default server URL setting in the iOS ntfy app
|
||||
4. set `+"`"+`upstream-base-url`+"`"+` in server.yml or NTFY_UPSTREAM_BASE_URL in env variables to "https://ntfy.sh" (without a trailing slash)`, m.Message)
|
||||
}
|
||||
|
||||
func TestServer_MessageTemplate_FromNamedTemplate_GitHubIssueOpened(t *testing.T) {
|
||||
t.Parallel()
|
||||
s := newTestServer(t, newTestConfig(t))
|
||||
response := request(t, s, "POST", "/mytopic?template=github", githubIssueOpenedJSON, nil)
|
||||
require.Equal(t, 200, response.Code)
|
||||
m := toMessage(t, response.Body.String())
|
||||
require.Equal(t, "🐛 [ntfy] Issue opened: #1391 http 500 error (ntfy error 50001)", m.Title)
|
||||
require.Equal(t, `Opened by: https://github.com/TheUser-dev
|
||||
Repository: https://github.com/binwiederhier/ntfy
|
||||
Issue link: https://github.com/binwiederhier/ntfy/issues/1391
|
||||
Labels: 🪲 bug
|
||||
|
||||
Description:
|
||||
:lady_beetle: **Describe the bug**
|
||||
When sending a notification (especially when it happens with multiple requests) this error occurs
|
||||
|
||||
:computer: **Components impacted**
|
||||
ntfy server 2.13.0 in docker, debian 12 arm64
|
||||
|
||||
:bulb: **Screenshots and/or logs**
|
||||
`+"```"+`
|
||||
closed with HTTP 500 (ntfy error 50001) (error=database table is locked, http_method=POST, http_path=/_matrix/push/v1/notify, tag=http, visitor_auth_limiter_limit=0.016666666666666666, visitor_auth_limiter_tokens=30, visitor_id=ip:<edited>, visitor_ip=<edited>, visitor_messages=448, visitor_messages_limit=17280, visitor_messages_remaining=16832, visitor_request_limiter_limit=0.2, visitor_request_limiter_tokens=57.049697891799994, visitor_seen=2025-07-16T15:06:35.429Z)
|
||||
`+"```"+`
|
||||
|
||||
:crystal_ball: **Additional context**
|
||||
Looks like this has already been fixed by #498, regression?`, m.Message)
|
||||
}
|
||||
|
||||
func TestServer_MessageTemplate_FromNamedTemplate_GitHubIssueOpened_OverrideConfigTemplate(t *testing.T) {
|
||||
t.Parallel()
|
||||
c := newTestConfig(t)
|
||||
c.TemplateDir = t.TempDir()
|
||||
require.NoError(t, os.WriteFile(filepath.Join(c.TemplateDir, "github.yml"), []byte(`
|
||||
title: |
|
||||
Custom title: action={{ .action }} trunctitle={{ .issue.title | trunc 10 }}
|
||||
message: |
|
||||
Custom message {{ .issue.number }}
|
||||
`), 0644))
|
||||
s := newTestServer(t, c)
|
||||
response := request(t, s, "POST", "/mytopic?template=github", githubIssueOpenedJSON, nil)
|
||||
fmt.Println(response.Body.String())
|
||||
require.Equal(t, 200, response.Code)
|
||||
m := toMessage(t, response.Body.String())
|
||||
require.Equal(t, "Custom title: action=opened trunctitle=http 500 e", m.Title)
|
||||
require.Equal(t, "Custom message 1391", m.Message)
|
||||
}
|
||||
|
||||
func newTestConfig(t *testing.T) *Config {
|
||||
@@ -3093,6 +3149,7 @@ func newTestConfig(t *testing.T) *Config {
|
||||
conf.CacheFile = filepath.Join(t.TempDir(), "cache.db")
|
||||
conf.CacheStartupQueries = "pragma journal_mode = WAL; pragma synchronous = normal; pragma temp_store = memory;"
|
||||
conf.AttachmentCacheDir = t.TempDir()
|
||||
conf.TemplateDir = t.TempDir()
|
||||
return conf
|
||||
}
|
||||
|
||||
|
||||
29
server/templates/alertmanager.yml
Normal file
29
server/templates/alertmanager.yml
Normal file
@@ -0,0 +1,29 @@
|
||||
title: |
|
||||
{{- if eq .status "firing" }}
|
||||
🚨 Alert: {{ (first .alerts).labels.alertname }}
|
||||
{{- else if eq .status "resolved" }}
|
||||
✅ Resolved: {{ (first .alerts).labels.alertname }}
|
||||
{{- else }}
|
||||
{{ fail "Unsupported Alertmanager status." }}
|
||||
{{- end }}
|
||||
message: |
|
||||
Status: {{ .status | title }}
|
||||
Receiver: {{ .receiver }}
|
||||
|
||||
{{- range .alerts }}
|
||||
Alert: {{ .labels.alertname }}
|
||||
Instance: {{ .labels.instance }}
|
||||
Severity: {{ .labels.severity }}
|
||||
Starts at: {{ .startsAt }}
|
||||
{{- if .endsAt }}Ends at: {{ .endsAt }}{{ end }}
|
||||
{{- if .annotations.summary }}
|
||||
Summary: {{ .annotations.summary }}
|
||||
{{- end }}
|
||||
{{- if .annotations.description }}
|
||||
Description: {{ .annotations.description }}
|
||||
{{- end }}
|
||||
Source: {{ .generatorURL }}
|
||||
|
||||
{{ end }}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ title: |
|
||||
👀 {{ .sender.login }} started watching {{ .repository.name }}
|
||||
|
||||
{{- else if and .comment (eq .action "created") }}
|
||||
💬 New comment on #{{ .issue.number }}: {{ .issue.title }}
|
||||
💬 New comment on issue #{{ .issue.number }} {{ .issue.title }}
|
||||
|
||||
{{- else if .pull_request }}
|
||||
🔀 Pull request {{ .action }}: #{{ .pull_request.number }} {{ .pull_request.title }}
|
||||
@@ -47,6 +47,7 @@ message: |
|
||||
{{ .action | title }} by: {{ .issue.user.html_url }}
|
||||
Repository: {{ .repository.html_url }}
|
||||
Issue link: {{ .issue.html_url }}
|
||||
{{ if .issue.labels }}Labels: {{ range .issue.labels }}{{ .name }} {{ end }}{{ end }}
|
||||
{{ if .issue.body }}
|
||||
Description:
|
||||
{{ .issue.body | trunc 2000 }}{{ end }}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
message: |
|
||||
{{if .alerts}}
|
||||
{{.alerts | len}} alert(s) triggered
|
||||
{{else}}
|
||||
No alerts triggered.
|
||||
{{end}}
|
||||
title: |
|
||||
⚠️ Grafana alert: {{.title}}
|
||||
{{- if eq .status "firing" }}
|
||||
🚨 {{ .title | default "Alert firing" }}
|
||||
{{- else if eq .status "resolved" }}
|
||||
✅ {{ .title | default "Alert resolved" }}
|
||||
{{- else }}
|
||||
⚠️ Unknown alert: {{ .title | default "Alert" }}
|
||||
{{- end }}
|
||||
message: |
|
||||
{{ .message | trunc 2000 }}
|
||||
|
||||
|
||||
33
server/testdata/webhook_alertmanager_firing.json
vendored
Normal file
33
server/testdata/webhook_alertmanager_firing.json
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"version": "4",
|
||||
"groupKey": "...",
|
||||
"status": "firing",
|
||||
"receiver": "webhook-receiver",
|
||||
"groupLabels": {
|
||||
"alertname": "HighCPUUsage"
|
||||
},
|
||||
"commonLabels": {
|
||||
"alertname": "HighCPUUsage",
|
||||
"instance": "server01",
|
||||
"severity": "critical"
|
||||
},
|
||||
"commonAnnotations": {
|
||||
"summary": "High CPU usage detected"
|
||||
},
|
||||
"alerts": [
|
||||
{
|
||||
"status": "firing",
|
||||
"labels": {
|
||||
"alertname": "HighCPUUsage",
|
||||
"instance": "server01",
|
||||
"severity": "critical"
|
||||
},
|
||||
"annotations": {
|
||||
"summary": "High CPU usage detected"
|
||||
},
|
||||
"startsAt": "2025-07-17T07:00:00Z",
|
||||
"endsAt": "0001-01-01T00:00:00Z",
|
||||
"generatorURL": "http://prometheus.local/graph?g0.expr=..."
|
||||
}
|
||||
]
|
||||
}
|
||||
51
server/testdata/webhook_grafana_resolved.json
vendored
Normal file
51
server/testdata/webhook_grafana_resolved.json
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
{
|
||||
"receiver": "ntfy\\.example\\.com/alerts",
|
||||
"status": "resolved",
|
||||
"alerts": [
|
||||
{
|
||||
"status": "resolved",
|
||||
"labels": {
|
||||
"alertname": "Load avg 15m too high",
|
||||
"grafana_folder": "Node alerts",
|
||||
"instance": "10.108.0.2:9100",
|
||||
"job": "node-exporter"
|
||||
},
|
||||
"annotations": {
|
||||
"summary": "15m load average too high"
|
||||
},
|
||||
"startsAt": "2024-03-15T02:28:00Z",
|
||||
"endsAt": "2024-03-15T02:42:00Z",
|
||||
"generatorURL": "localhost:3000/alerting/grafana/NW9oDw-4z/view",
|
||||
"fingerprint": "becbfb94bd81ef48",
|
||||
"silenceURL": "localhost:3000/alerting/silence/new?alertmanager=grafana&matcher=alertname%3DLoad+avg+15m+too+high&matcher=grafana_folder%3DNode+alerts&matcher=instance%3D10.108.0.2%3A9100&matcher=job%3Dnode-exporter",
|
||||
"dashboardURL": "",
|
||||
"panelURL": "",
|
||||
"values": {
|
||||
"B": 18.98211314475876,
|
||||
"C": 0
|
||||
},
|
||||
"valueString": "[ var='B' labels={__name__=node_load15, instance=10.108.0.2:9100, job=node-exporter} value=18.98211314475876 ], [ var='C' labels={__name__=node_load15, instance=10.108.0.2:9100, job=node-exporter} value=0 ]"
|
||||
}
|
||||
],
|
||||
"groupLabels": {
|
||||
"alertname": "Load avg 15m too high",
|
||||
"grafana_folder": "Node alerts"
|
||||
},
|
||||
"commonLabels": {
|
||||
"alertname": "Load avg 15m too high",
|
||||
"grafana_folder": "Node alerts",
|
||||
"instance": "10.108.0.2:9100",
|
||||
"job": "node-exporter"
|
||||
},
|
||||
"commonAnnotations": {
|
||||
"summary": "15m load average too high"
|
||||
},
|
||||
"externalURL": "localhost:3000/",
|
||||
"version": "1",
|
||||
"groupKey": "{}:{alertname=\"Load avg 15m too high\", grafana_folder=\"Node alerts\"}",
|
||||
"truncatedAlerts": 0,
|
||||
"orgId": 1,
|
||||
"title": "[RESOLVED] Load avg 15m too high Node alerts (10.108.0.2:9100 node-exporter)",
|
||||
"state": "ok",
|
||||
"message": "**Resolved**\n\nValue: B=18.98211314475876, C=0\nLabels:\n - alertname = Load avg 15m too high\n - grafana_folder = Node alerts\n - instance = 10.108.0.2:9100\n - job = node-exporter\n"
|
||||
}
|
||||
Reference in New Issue
Block a user