Commit Graph
939 Commits
Author SHA1 Message Date
binwiederhier 87a27188f1 Test that a scheduled message is replaced when the sequence ID comes as a parameter
The existing update tests only set the sequence ID in the path; this covers the ?sid= form,
which takes the same DeleteScheduledBySequenceID path.
2026-10-07 13:41:50 +00:00
binwiederhier 99d55175d4 Keep the message batch writer ahead of publish bursts
On cron bursts (top of the hour), the single batch writer fell behind: it wrote one INSERT round
trip per message, so against a busy remote Postgres it managed ~100 rows/s while ~300/s arrived.
New messages took up to ~40s to reach the database (polls missed them), and publishes blocked on
the unbuffered queue for up to ~25s.

- Postgres: write each batch with one multi-row INSERT (up to 1000 rows per statement) instead of
  one statement per row. SQLite keeps the row-by-row path.
- Skip DeleteScheduledBySequenceID when the client did not set a sequence ID: the sequence ID is
  then the message's own fresh ID, so no scheduled message can match. This saves a transaction on
  the primary for almost every publish.
- Buffer up to 100 batches in the BatchingQueue, so a briefly slow writer does not stall publishes.
- dbtest: add a latency proxy (CreateTestPostgresWithLatency, WithLatency) to test round trips.
2026-10-07 11:54:46 +00:00
Philipp C. Heckel ea5912b640 Merge pull request #1946 from justadityaraj/fix/1940-alertmanager-end-time
Omit zero end times from Alertmanager notifications
2026-10-06 23:58:13 -04:00
binwiederhier 0ccbf5ad87 Reserve the experimental clustering options
Five option names and their validation, ahead of the implementation, so the
config surface is settled and the cluster change is only code. The
experimental- prefix is there because the names may still change.

The options parse into server.Config and the cross-field rules are enforced:
a listen address needs database-url, a secret and a node id; a secret without
a listen address is an error rather than clustering being silently off; a
wildcard bind needs an explicit advertise URL; a negative linger is rejected.

Setting experimental-cluster-listen then refuses to start the server, since
accepting the options and running single-node would look like a working
cluster. The cluster change deletes that one line.
2026-10-07 00:06:19 +00:00
Philipp C. Heckel 5a41860bd6 Merge pull request #1967 from beemines/feat/delayed-notification-timezone
Add timezone support for delayed notifications
2026-10-05 17:46:23 -04:00
binwiederhier fa9ff8c68a Add uppercase Content-Type test and release notes for #1995 2026-10-05 21:14:27 +00:00
binwiederhier e199f010ea Fall back to plain comparison if Content-Type can't be parsed, lowercase media type 2026-10-05 21:09:30 +00:00
cipherprofessor 3e9fe9946b Detect Markdown when Content-Type has parameters, e.g. text/markdown; charset=utf-8
The Content-Type header was compared to "text/markdown" as a whole string, so any media type parameter (most commonly charset) made ntfy treat the message as plain text. Parse the header with mime.ParseMediaType and compare only the media type.

Fixes #1992
2026-10-05 11:22:20 +05:30
Philipp C. Heckel 998560dfa5 Merge pull request #1984 from binwiederhier/fix-shutdown-message-loss
Persist batched messages on shutdown, handle SIGTERM in serve
2026-10-03 12:35:26 -04:00
binwiederhier 2153f646be Keep shutdown bounded, and do not start listeners after Stop
Now that the signal handler waits for Stop, two holes in Stop matter:

- Stop waits for every store to close, and those waits have no deadline of
  their own: attachment.Store.Close waits for the sync loop, whose
  AttachmentsWithSizes query runs against the database (and S3 deletes are in
  there too). A wedged database parked Stop before it ever reached the message
  cache, whose own 5s bound was then useless. Stop now gives up after 10s.
- A signal can arrive before Run has set up its listeners. Stop closed the
  stores and consumed stopOnce, and Run then started listeners against closed
  stores and blocked on the listener error, so the process neither served nor
  exited and a second signal could not stop it. Run bails out when the server
  is already stopped, outside the lock (a stuck shutdown holds it) and again
  under it.
2026-10-03 16:18:06 +00:00
binwiederhier e63ec41b2e Close the message cache before the other stores on shutdown
On Postgres all stores share one pool, and closeDatabases closed the user
manager first, which closed the pool under the message cache's final batch
write ("Cannot write message batch: sql: database is closed").
2026-10-03 15:06:45 +00:00
binwiederhier 303230e5dc Guard handleAccountTokenUpdate against the nil-user race (recurring prod panic)
ensureUser checks v.User(), the handler re-reads it, and a concurrent
anonymous request on the same shared IP-keyed visitor can SetUser(nil) in
between -> nil dereference (~8x/day on ntfy.sh). Return 401 instead. The
regression test makes the race deterministic.
2026-10-03 15:06:45 +00:00
binwiederhier bc1e879dc4 Persist batched messages on shutdown; graceful SIGTERM in serve
With cache-batch-size/-timeout set (ntfy.sh: 100 / 1500ms), accepted messages
wait in a BatchingQueue before they are written. Nothing drained that queue on
shutdown, and `ntfy serve` did not handle SIGTERM at all (only SIGHUP), so
every restart dropped up to one batch timeout's worth of accepted messages
from the cache: live subscribers got them, pollers and since= replays never
did.

- util.BatchingQueue.Close emits the pending elements and closes the output
  channel (Enqueue after Close is a no-op)
- message.Cache.Close closes the queue and waits for the batch writer before
  closing the database
- serve stops the server on SIGTERM/SIGINT and waits for Stop to finish (Run
  returns as soon as the listeners close); Stop is idempotent now, since the
  signal handler and serve both call it
2026-10-03 15:06:09 +00:00
Bee mines 7961282490 Add timezone support for delayed notifications 2026-09-21 14:27:20 +08:00
Aditya 535e154666 Fix Alertmanager template end time 2026-09-10 03:19:47 +05:30
binwiederhier 7826c86f67 Remove message-poll-limit again, hardcode title/tags/etc limits, count polls toward bandwidth limit 2026-08-27 19:42:30 +00:00
binwiederhier cae7816e5d Add message-poll-limit ... 2026-08-27 16:38:21 +00:00
binwiederhier fbcd4b6290 Expands bandwidth limit to also include excessive polling 2026-08-27 14:12:01 +00:00
binwiederhier 7ed7fea081 Limit memory usage in templates 2026-08-03 18:34:29 +02:00
binwiederhier 0ecba37334 Combine message dispatching into a dispatch function 2026-08-03 08:11:51 +02:00
binwiederhier f2d5c1ce6c Do not include secrets in the config hash 2026-07-23 08:13:27 +02:00
binwiederhier 2bc145f3ae Merge branch 'release-2.26.x' 2026-07-20 23:42:03 +02:00
binwiederhier 311138ef7b Derp 2026-07-20 23:23:42 +02:00
binwiederhier c674985699 Redo the banning logic, ban.Service 2026-07-20 21:34:47 +02:00
binwiederhier 469d263a5c Hotfix: Add ban-file/ban-threshold/ban-weights as a more lightweight mechanism abuse counter 2026-07-18 08:41:25 +02:00
binwiederhier f8d2fcd7a6 Move metrics to metrics/ pacakge 2026-07-17 22:08:21 +02:00
binwiederhier ac63a2eea0 Move Twilio to twilio/ package 2026-07-17 13:48:48 +02:00
binwiederhier 24bc50b585 Allow logging in via email 2026-07-16 21:42:42 +02:00
binwiederhier 3f56dae54a Template exec context, redone 2026-07-10 13:18:11 +02:00
binwiederhier 165f012ae6 Add AST check to catch calls, remove regex for block and template 2026-07-08 21:56:57 +02:00
binwiederhier 1d69ebaf58 Additional test 2026-07-08 21:18:19 +02:00
binwiederhier a001ac5195 Template execution rework 2026-07-08 21:08:07 +02:00
binwiederhier 7b54850c16 Make twilioClient 2026-07-03 21:04:06 -04:00
binwiederhier be3dbdcb48 Move action parsing to action package 2026-07-03 20:54:45 -04:00
binwiederhier d0054ea394 Split out auth to server_auth.go 2026-07-03 18:32:25 -04:00
binwiederhier 69c2c18a9e Moved template functions 2026-07-02 17:17:39 -04:00
binwiederhier 479e406493 Split out web functions to server_web.go 2026-07-02 17:11:24 -04:00
jvoisin 7c0ab6e6b0 Remove a useless loop
It's functionally equivalent to the lines above it.
2026-07-02 18:13:06 +02:00
binwiederhier c873064cad Date / time format 2026-06-29 22:24:04 -04:00
binwiederhier 5e32f05302 Read /account from primary 2026-06-25 20:24:50 -04:00
binwiederhier c5e5be0746 Merge branch 'main' of github.com:binwiederhier/ntfy into 1511-ampersand 2026-06-24 05:42:12 -04:00
binwiederhier f121f2ba8d Merge branch 'main' of github.com:binwiederhier/ntfy into 1771-delete-clear-via-get 2026-06-23 11:51:39 -04:00
binwiederhier 4313b02fc6 Allow primary email for provisioend users 2026-06-22 12:59:50 -04:00
binwiederhier d8666b66ec Change "Email: yes" behavior to make more sense 2026-06-22 10:13:27 -04:00
binwiederhier 1979dbc7c3 Rename 2026-06-21 11:27:02 -04:00
binwiederhier d7dea6d250 Review 2026-06-21 11:19:49 -04:00
binwiederhier bb2ca0facf Send "X-Email: yes" to primary 2026-06-20 14:15:12 -04:00
binwiederhier 01eabb288a Review 2026-06-16 21:35:03 -04:00
binwiederhier 99bc803271 Code review 2026-06-16 21:18:58 -04:00
binwiederhier d8c87d04e7 Update privacy policy, code review 2026-06-16 20:56:22 -04:00