Commit Graph
700 Commits
Author SHA1 Message Date
binwiederhier b65e642d05 Merge remote-tracking branch 'origin/main' into up-payer
# Conflicts:
#	docs/releases.md
2026-11-01 11:13:01 +00:00
binwiederhier 859e6443c7 Count forwarded messages for topics without local subscribers as wasted, and do not keep those topics alive
A cluster message whose topic is known on the receiving node but has no subscriber there was published to nobody and uncounted, and its Keepalive kept the topic from ever expiring: a passive node held every topic the active one published to. It is now counted in ntfy_cluster_messages_wasted_total and not published, so idle topics expire after topicExpungeAfter.
2026-10-31 19:43:29 +00:00
binwiederhier 3938bc6886 Shorten the v2.36.0 release notes, install docs for v2.36.0 2026-10-31 15:02:51 +00:00
binwiederhier ea71722ba7 Release notes for v2.36.0 2026-10-31 15:00:21 +00:00
binwiederhier 31f2143a3a Document the rate visitor key, the 7-day Matrix rule and the tier refresh 2026-10-31 14:05:37 +00:00
binwiederhier 1c72a97736 Document shared UnifiedPush payers and the 404 for abandoned topics 2026-10-31 11:31:32 +00:00
binwiederhier ffd16e16b7 Credit Jonathan Kats for reporting the same-IP visitor race 2026-10-31 11:28:49 +00:00
binwiederhier 0f3a57a2a2 Merge remote-tracking branch 'origin/main' into shared-daily-limits
# Conflicts:
#	docs/releases.md
#	server/server.go
2026-10-31 03:14:26 +00:00
binwiederhier 9ecbf4dc16 Export ntfy_db_up and health-check the primary like the replicas
A node cut off from its database only showed in the logs. The health check loop now always
runs and pings the primary too; ntfy_db_up (1/0, per pool, next to ntfy_db_pool_*) is the
result of the last check, so an alert can fire on ntfy_db_up{role="primary"} == 0.
2026-10-31 01:31:13 +00:00
Philipp C. Heckel c62439f8fa Merge pull request #2088 from binwiederhier/adminsock-socket
Admin socket: serve the admin API on a local unix socket
2026-10-30 20:46:49 -04:00
binwiederhier c30365bc7b Test --wait-pid on processes owned by other users, add release note 2026-10-30 23:39:46 +00:00
binwiederhier 0f49391494 Drop the deprecation of starting without the admin socket, just warn 2026-10-30 23:22:08 +00:00
binwiederhier e67d24b36a Merge remote-tracking branch 'origin/main' into pr-1969
# Conflicts:
#	docs/releases.md
2026-10-30 21:29:48 +00:00
binwiederhier fd3a48436b Keep the serve config checks running after base-url and message-size-limit
Move the base-url check out of the else-if chain instead of into a
helper, and test both cases with an invalid auth-default-access, which
fails right after the checks, instead of starting a server.
2026-10-30 21:27:59 +00:00
binwiederhier 5737b0bd8c Start the admin socket in Run like the other listeners, and deprecate starting without it 2026-10-30 21:26:40 +00:00
binwiederhier 59775edc72 Rename admin-socket to listen-admin-unix 2026-10-30 20:52:50 +00:00
binwiederhier 851d98c475 Merge remote-tracking branch 'origin/main' into replay-tombstones
# Conflicts:
#	docs/releases.md
2026-10-30 20:47:27 +00:00
binwiederhier a96b35e242 Web app: keep read state of a replayed WebSocket message
The WebSocket handler deleted every version of a message's sequence
before adding it, so a replayed message that was already stored came
back as unread and notified again, and a replayed older version
replaced a newer one. addNotification now replaces older versions of
the sequence itself, and skips a message if a newer version is stored.

Fixes #2090
2026-10-30 20:45:30 +00:00
binwiederhier c3ecea55c4 Show the reset time and the sync delay on the account page, document shared daily limits
Claude-Session: https://claude.ai/code/session_01DeXuhSjq5Gta5qPJgSceFe
2026-10-30 20:42:27 +00:00
binwiederhier e73282539a Merge branch 'adminsock-api' into adminsock-socket
# Conflicts:
#	docs/releases.md
2026-10-30 20:40:54 +00:00
binwiederhier ceb0666d36 Shorter release notes for the internal admin API 2026-10-30 20:40:41 +00:00
binwiederhier f8f0d87253 Merge branch 'adminsock-api' into adminsock-socket
# Conflicts:
#	docs/releases.md
#	server/server.go
#	server/server_admin_socket.go
#	server/server_admin_test.go
2026-10-30 20:40:33 +00:00
binwiederhier 862bc5e14f Merge remote-tracking branch 'origin/main' into adminsock-api
# Conflicts:
#	docs/releases.md
2026-10-30 20:37:09 +00:00
binwiederhier 5c2a1ddeef Release notes and install docs, prep v2.35.0
Claude-Session: https://claude.ai/code/session_01DeXuhSjq5Gta5qPJgSceFe
2026-10-30 13:24:49 +00:00
binwiederhier be440b62ae Make the v2.35.0 release notes briefer and accurate
Tier to tier changes keep spend (no tier to a tier never did), only a tier's daily email limit is a cap, only the read over the size limit is refunded, and the #2119 bullet describes the fix.

Claude-Session: https://claude.ai/code/session_01DeXuhSjq5Gta5qPJgSceFe
2026-10-29 21:10:24 +00:00
binwiederhier d397e9d075 Put security first in the v2.35.0 release notes and merge the bug fix sections
Claude-Session: https://claude.ai/code/session_01DeXuhSjq5Gta5qPJgSceFe
2026-10-29 16:44:17 +00:00
Philipp C. Heckel b9561e8088 Merge pull request #2118 from binwiederhier/visitor-per-request
Build the visitor per request instead of sharing it
2026-10-29 12:23:48 -04:00
binwiederhier 1e54713e29 Credit the reporter of the Matrix push key fix 2026-10-29 13:49:36 +00:00
binwiederhier 02be719ce2 Web app: don't add deleted notifications again when a poll replays them
The server replays the whole second of a since=<id> marker, so a poll
returns messages the user deleted. Remember deleted notifications and
skip them (and older versions of their sequence), and don't move the
marker back when a poll only replayed known messages, which made each
poll replay a different part of that second.

Fixes #2098
2026-10-29 11:31:45 +00:00
binwiederhier 89cee1cd11 Keep a topic's rate visitor for as long as its subscriber is, not its key
Stale read the limiter key's last use, which every charged publish refreshes, so a topic kept
a vanished subscriber's limits (and its UnifiedPush subscription) for as long as anyone
published to it. The visitor now carries its own keepalive, set when it is built and by the
subscribe handlers' keepalive loops, like the shared visitor's seen was.

Also from the review: Prune returns and the manager logs the number of forgotten keys, the
unused visitorID wrapper goes, a stale comment and test name are updated, and the release
notes get the #1051 entry.

Claude-Session: https://claude.ai/code/session_01DeXuhSjq5Gta5qPJgSceFe
2026-10-29 03:21:37 +00:00
Philipp C. Heckel 08566a0f0d Merge pull request #2122 from binwiederhier/limit-fixes
Close three gaps in the limit rework and pin its behavior with tests
2026-10-28 22:41:29 -04:00
binwiederhier aa9b89ef7b Add release note for terabyte size units 2026-10-29 02:29:50 +00:00
binwiederhier 62abb9b729 Close three gaps in the limit rework and pin its behavior with tests
A tier with a daily email limit of zero sends no emails (the limit service reads a daily
limit of zero as "no cap"), a tier edited in place flushes the visitor's pending user stats
before resetting its local counts like a tier ID change does, and a bucket refill with a
timestamp older than its last one neither charges tokens nor moves the refill point back.

The tests pin what the rework changed and what it must keep: shared keys count once in the
user stats, an expunged visitor continues its counts, the daily reset keeps the buckets,
zero bursts admit nothing, seeds apply once, prefix keying, tier changes in all directions,
concurrency on a shared key, and the account page's remaining values matching admissions.

Claude-Session: https://claude.ai/code/session_01DeXuhSjq5Gta5qPJgSceFe
2026-10-29 02:28:25 +00:00
binwiederhier 8baa89871f Reset the whole ACL and list all users' tokens only over the admin socket 2026-10-29 02:21:01 +00:00
binwiederhier e0ee4b090f Merge remote-tracking branch 'origin/main' into adminsock-api
# Conflicts:
#	docs/releases.md
#	server/server_admin.go
#	server/server_admin_test.go
2026-10-29 01:46:43 +00:00
binwiederhier 601900cf16 Reject Matrix push keys that do not name a valid topic
The Matrix gateway built its publish request from the push key without the
router's topic path check. Publishing sanitizes the topic after authorization
(it strips NUL bytes and invalid UTF-8), so an anonymous push key like
/sec%00ret was authorized against an unmatched topic and stored under the
protected topic "secret".
2026-10-29 00:36:23 +00:00
binwiederhier 3c7cbc89db Merge branch 'adminsock-api' into adminsock-socket
# Conflicts:
#	docs/releases.md
2026-10-25 00:55:33 +00:00
binwiederhier fe5417b2c9 Price list at /v1/account/billing/tiers, next to the other billing routes
The /v1 API was never published as stable, so the fallback for older
web apps is not a deprecation, just a temporary route.
2026-10-25 00:55:17 +00:00
binwiederhier 64b3be3581 Merge branch 'adminsock-api' into adminsock-socket
# Conflicts:
#	docs/releases.md
#	server/server_admin_socket.go
2026-10-25 00:25:53 +00:00
binwiederhier d0dbd1dcbe Move the price list to /v1/billing/tiers, manage tiers at /v1/tiers
/v1/tiers is now the admin API for tiers (GET/POST/PUT/DELETE), instead
of the unreleased /v1/admin/tiers. The public price list for the upgrade
dialog moved to /v1/billing/tiers; GET /v1/tiers still returns it to
non-admins until 2026-11-22, for web apps from before v2.33.0.
2026-10-25 00:25:35 +00:00
binwiederhier 9f1d2ea44e Release notes and install docs, prep v2.34.0 2026-10-24 19:08:22 +00:00
binwiederhier 8d1e7f129c Call the first drain phase withdrawing, and label both phases in Drain 2026-10-24 18:42:26 +00:00
binwiederhier 70781daf2d Merge remote-tracking branch 'origin/main' into cluster-drain
# Conflicts:
#	cluster/cluster_mesh_test.go
#	server/server.go
#	server/server_cluster_test.go
2026-10-24 18:06:11 +00:00
binwiederhier 6a0ae705a3 Reload the access cache every 67 s 2026-10-24 17:11:51 +00:00
binwiederhier 04ef414981 Derive a subscriber's user ID from its user, hoist loop expressions, reload every 63 s
topicSubscriber no longer stores the user ID next to the user (userID() derives it), which also brings the struct below its size before the user was added. The cancel loops assign the matching subscribers to a variable instead of calling subscribersMatching inside the for header. The periodic access cache reload runs every 63 s: access changes are rare and peers are told directly, so the interval only bounds a missed signal.
2026-10-24 13:45:18 +00:00
binwiederhier 3437cf6169 Fix a deadlock when canceling subscribers with debug logging, and a log override race
Canceling a subscriber logs the topic's context, which takes the topic lock. The cancels held that lock while they logged: CancelSubscribersExceptUser held the write lock (deadlock at once), the per-user cancels a read lock (deadlock as soon as a canceled stream's Unsubscribe waited for the write lock). The context is only evaluated with debug logging or log-level-overrides on. In the lab this froze the access cache reload loop after its first re-check. The cancels now collect the matching subscribers under the lock and cancel them after releasing it.

globalLevelWithOverride iterated the overrides map without the log lock while SetLevelOverride changes it in place; it now holds the read lock while iterating.
2026-10-23 22:38:59 +00:00
binwiederhier 1bd90cf924 Reload the whole access cache on every access change, and tell the peers
Access changes are rare and a full reload is cheap (about 15 ms on ntfy.sh's 5,500 entries), so the access cache no longer reloads per user. Every reload is full and serialized (which replaces the sequence-number race guard), and the diff after each reload re-checks the live subscribers on the changed topics.

An access write (reservation, allow, reset, user removal, role change) reloads the local cache and tells the other cluster nodes "access changed" (cluster.State.AccessChanged), which reload theirs; their diff ends the streams that lost access there. This replaces the relayed per-topic cancels and the targeted reloads. Only a deleted user's cancel is still relayed: a user without access entries (e.g. an admin) leaves nothing in the cache to compare. The periodic reload, the backstop for a lost signal and for writes by other processes (ntfy access CLI), now runs every 31 s instead of 87 s.
2026-10-23 21:39:00 +00:00
binwiederhier 1fd5477981 Call it draining on shutdown, not maintenance mode
Other systems keep two things apart: draining before a shutdown (ephemeral, cleared by the restart: RabbitMQ drain, CockroachDB node drain) and an operator's maintenance flag (persistent cluster state: Kubernetes cordon, Nomad eligibility, Consul maint). This PR is the former, so it is named that way: Server.Drain (synchronous again; the on/off API is gone until the persistent maintenance flag exists), Cluster.SetDraining/Draining, the read-only phase flag readOnly, /v1/ready reports "draining": true, error 50301 says the node is shutting down, and docs/config.md has a "Draining on shutdown" section. A drain duration of 0 now closes all streams at once instead of disabling the drain.
2026-10-23 21:20:57 +00:00
binwiederhier 49a28e8cdb Merge remote-tracking branch 'origin/main' into cluster-cancel-relay
# Conflicts:
#	docs/releases.md
2026-10-23 20:23:37 +00:00
binwiederhier 30eb5db6d8 Maintenance mode: enter, then drain read-only
Maintenance mode now has two phases. Entering (experimental-cluster-drain-delay, default 15s): /v1/ready fails and the node acts as passive in the cluster, but it still serves everything, so the load balancers can stop sending it requests without any request failing. Draining (experimental-cluster-drain-duration, default 1m): read-only, i.e. new subscriptions and all publishes get 503 (email gets SMTP 451, so mail servers retry instead of bouncing), and the streams are closed evenly. The defaults together fit systemd's 90s stop timeout. docs/config.md gets a maintenance mode section with the behavior table.
2026-10-23 13:20:33 +00:00