4690 Commits
Author SHA1 Message Date
dependabot[bot] 03b8fd103a Bump github.com/gabriel-vasile/mimetype from 1.4.15 to 1.4.16
Bumps [github.com/gabriel-vasile/mimetype](https://github.com/gabriel-vasile/mimetype) from 1.4.15 to 1.4.16.
- [Release notes](https://github.com/gabriel-vasile/mimetype/releases)
- [Commits](https://github.com/gabriel-vasile/mimetype/compare/v1.4.15...v1.4.16)

---
updated-dependencies:
- dependency-name: github.com/gabriel-vasile/mimetype
  dependency-version: 1.4.16
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-10-27 21:42:41 +00:00
binwiederhier 3cb7a98d01 Revert "Fix dead links in the contributing guide as rendered on GitHub"
This reverts commit dbe33ba495.
2026-10-27 00:31:34 +00:00
binwiederhier dbe33ba495 Fix dead links in the contributing guide as rendered on GitHub 2026-10-27 00:26:30 +00:00
Philipp C. Heckel 87d20d52ad Merge pull request #2104 from binwiederhier/step1-visitor
Shared rate limits, step 1: package visitor for keying and limit resolution
2026-10-26 09:24:17 -04:00
binwiederhier 63d3bb533b Add the limit package to the Docker build
Claude-Session: https://claude.ai/code/session_01DeXuhSjq5Gta5qPJgSceFe
2026-10-26 13:09:39 +00:00
binwiederhier c443886193 Merge branch 'step1-visitor' into step2-limit 2026-10-26 13:09:39 +00:00
binwiederhier cf20182de7 Add the visitor package to the Docker build
Claude-Session: https://claude.ai/code/session_01DeXuhSjq5Gta5qPJgSceFe
2026-10-26 13:09:38 +00:00
dependabot[bot] d4c2c3083f Bump fast-uri from 3.1.8 to 3.1.9 in /web
Bumps [fast-uri](https://github.com/fastify/fast-uri) from 3.1.8 to 3.1.9.
- [Release notes](https://github.com/fastify/fast-uri/releases)
- [Commits](https://github.com/fastify/fast-uri/compare/v3.1.8...v3.1.9)

---
updated-dependencies:
- dependency-name: fast-uri
  dependency-version: 3.1.9
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-10-25 23:33:00 +00:00
dependabot[bot] eca3eb9d49 Bump serialize-javascript from 7.1.2 to 7.1.4 in /web
Bumps [serialize-javascript](https://github.com/yahoo/serialize-javascript) from 7.1.2 to 7.1.4.
- [Release notes](https://github.com/yahoo/serialize-javascript/releases)
- [Commits](https://github.com/yahoo/serialize-javascript/compare/v7.1.2...v7.1.4)

---
updated-dependencies:
- dependency-name: serialize-javascript
  dependency-version: 7.1.4
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-10-25 23:32:59 +00:00
binwiederhier f16baadec4 Let a zero message, call or bandwidth limit admit nothing again
The limit service reads a zero cap as no cap, so a tier with no calls
(or no messages, or no attachment bandwidth on upload) became unlimited
where main's FixedLimiter(0) and bytes limiter admitted nothing. The
visitor now answers zero limits itself before asking the service, like
it already did for requests, emails and bandwidth downloads.
2026-10-25 21:11:36 +00:00
binwiederhier 4bbb4418a5 One limiter model in package limit, replacing the three limiter types in the visitor
Allow(key, rule, n), Refund, Tokens and Usage on a service that keeps a
token bucket and a daily count per key and kind, sharded by key, with the
rule passed in on every call. The server's visitor keeps its identity and
the resolved limits and asks the service; rate.Limiter, util.RateLimiter
and util.FixedLimiter are gone from it (subscriptions stay a per-node
gauge). util.RateLimiter, NewBytesLimiter and BurnTokens had no users
left and are removed.

Behavior changes: refunds work for every kind (a revert on the bandwidth
limiter hands the bytes back), a tier change applies on the next request
without handing back full buckets, a changed tier limit applies without
a tier ID change, and the daily email limit is a real daily cap on top
of the bucket. Step 2 of plans/261025-shared-rate-limits.md.
2026-10-25 15:50:03 +00:00
binwiederhier 2243c96812 Move visitor keying and limit resolution into package visitor
The visitor key (ip:<prefix> with the configured prefix bits, or user:<id>
for a user with a tier) and the resolution of a visitor's limits from the
server defaults or the user's tier move out of server/visitor.go into the
new package visitor, unchanged. The server keeps thin wrappers, so its
tests are untouched. No behavior change. First step of the shared rate
limits design (plans/261025-shared-rate-limits.md).
2026-10-25 15:36:10 +00:00
binwiederhier 5d2f232fcb Merge branch 'adminsock-api' into adminsock-socket 2026-10-25 15:29:45 +00:00
binwiederhier bff94cc2e2 Rename mapAdminUserError to mapUserErrorToHTTP 2026-10-25 15:29:45 +00:00
binwiederhier 24827e1019 Merge branch 'adminsock-api' into adminsock-socket 2026-10-25 15:21:31 +00:00
binwiederhier a6513441e9 Rename readAdminRole to checkRoleChange 2026-10-25 15:21:31 +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 494962da52 Move the admin socket code into server_admin.go
One file for the admin API, whether it arrives over HTTP or the socket.
2026-10-24 19:24:42 +00:00
binwiederhier cf4de9358b Merge branch 'adminsock-api' into adminsock-socket
# Conflicts:
#	server/server_admin_socket.go
2026-10-24 19:23:37 +00:00
binwiederhier cb34deee5d Keep the admin routes inline in handleInternal
The admin socket reuses handleInternal behind a path allowlist
(adminSocketPaths) instead of a separate adminHandler switch, so all
routes are listed in one place.
2026-10-24 19:23:08 +00:00
binwiederhier 9f1d2ea44e Release notes and install docs, prep v2.34.0 v2.34.0 2026-10-24 19:08:22 +00:00
Philipp C. Heckel 313b6da7f5 Merge pull request #2095 from binwiederhier/cluster-drain
Drain cluster nodes on shutdown
2026-10-24 14:56:09 -04:00
binwiederhier 8d1e7f129c Call the first drain phase withdrawing, and label both phases in Drain 2026-10-24 18:42:26 +00:00
binwiederhier 7a8c9cf83c A draining node is not ready: let Ready say so, not handleReady
meshCluster.Ready is already false while draining, since the node acts as passive; the extra check in handleReady was redundant. The fake cluster now follows the same contract.
2026-10-24 18:35:10 +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 97666f2897 Merge branch 'main' of https://hosted.weblate.org/git/ntfy/web
# Conflicts:
#	web/public/static/langs/sv.json
2026-10-24 18:05:37 +00:00
Philipp C. Heckel 0bcf0bb519 Merge pull request #2077 from binwiederhier/cluster-cancel-relay
Reload the access cache on access changes across cluster nodes
2026-10-24 13:58:09 -04:00
binwiederhier 5323192605 Move the access cache reload loop and coalescing into the cache
accessCache now owns everything about reloading: Reload (synchronous, reports changes to the callback after releasing reloadMu), RequestReload (coalesced, at most one pending), the periodic loop, and Close. The Manager only tells the peers and forwards.
2026-10-24 17:49:02 +00:00
Philipp C. Heckel 3adc7fcc3f Merge pull request #2099 from roypadina/docs/integrations-ntfybar-meetalert
docs: add NtfyBar and MeetAlert to integrations
2026-10-24 13:44:55 -04:00
binwiederhier e57c755e7c Name stored ACL topics escapedTopicPattern, and AccessChange.TopicPattern 2026-10-24 17:21:03 +00:00
binwiederhier 6a0ae705a3 Reload the access cache every 67 s 2026-10-24 17:11:51 +00:00
binwiederhier b327c520a4 Test that a write during a reload reaches the cache after a burst
The coalescing test made its write before the burst, so it passed even if requests arriving during a reload were dropped. It now holds the first reload after its scan, writes and bursts while it is held, and requires the follow-up reload to see the write. Found by a Codex review.
2026-10-24 15:57:42 +00:00
binwiederhier 4ff26f78a6 Scan topics and diff the access cache outside the locks
topicsFromPattern held Server.mu while regex-matching every topic (~313k on ntfy.sh, ~89 ms), blocking every request that creates a visitor; allTopics copies the pointers under the lock and the match runs outside it (also used for deleted users). The access diff (~2.3 ms) no longer runs under the cache's write lock: only reloads replace the maps, and they are serialized. Found by a Codex review.
2026-10-24 15:55:30 +00:00
binwiederhier 0630993ba7 End a user's streams everywhere when they delete their own account
Account self-deletion (MarkUserRemoved) sent no deleted-user signal and did not end the user's local streams; an admin without access entries kept them, here and on peers. Both deletes now go through userDeleted. Found by a Codex review.
2026-10-24 15:52:57 +00:00
binwiederhier bc88121f80 Authorize a new stream once more after it subscribed
A revoke whose re-check ran between a request's authorization and its stream subscribing missed the stream for good: later reloads find no difference. With the access cache on, the subscribe handlers now authorize once more right after subscribing (an in-memory lookup); the re-check runs after the cache swap, so one of the two always sees the other. Found by a Codex review.
2026-10-24 15:51:31 +00:00
binwiederhier 70f35dc33d Re-check each subscription against its own user
SubscriberUsers kept one user per user ID, so streams of one user with different users as of subscribing (e.g. before and after a demotion) were all judged by whichever came last. topic.CancelSubscribersWhere checks each subscription's own user, outside the topic lock; SubscriberUsers and CancelAnonymousSubscribers are gone. Found by a Codex review.
2026-10-24 15:49:08 +00:00
binwiederhier e127f081e3 Test diffACLEntries directly
Added, removed and changed entries, exact and wildcard, per user, and the * form of wildcard topics. The loadCache test helper now builds entries with toACLEntry, as Reload does, so wildcard entries carry their stored topic.
2026-10-24 14:53:55 +00:00
binwiederhier 22aab4e0bb Signal deleted users instead of subscriber cancels
The only cancel still relayed was a deleted user's, so the cluster now signals the event, like AccessChanged: State.DeletedUserIDs (deleted_user_ids on the wire) and Config.UsersDeletedFunc replace SubscriberCancels, the SubscriberCancel type and CancelFunc. The server's handleUsersDeleted ends those users' streams on every topic, for a local delete and a peer's.
2026-10-24 14:37:01 +00:00
binwiederhier 99f85b9e25 Give the cluster's AccessChangedFunc and IsolatedFunc their own types 2026-10-24 14:29:57 +00:00
binwiederhier 01d2ea6e0c Less code: flat access diff, no exported full reload, one cancel struct
The reload diff flattens both snapshots into one map keyed by user and stored topic, replacing four helpers. ReloadAccessCache was only used by tests (the server requests coalesced reloads), so they call maybeReloadAccessCache. killUserSubscriberEverywhere builds the cancel once, applies it locally and broadcasts it.
2026-10-24 13:54:32 +00:00
binwiederhier 5e305791c1 SubscriberCancel only carries the user ID
The only cancel still relayed is a deleted user's, on every topic; access changes go through the access cache reload. Topic and ExceptUserID are gone, and cancelUserSubscribersLocal ends a user's streams on all local topics for both the local delete and the peer cancel. No released version sends these cancels, so the wire format can change.
2026-10-24 13:49:56 +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 4f3037530b Re-check subscribers against their user as of subscribing, without the database
topic.Subscribe now takes the subscriber's *user.User (nil if anonymous) and keeps it on the subscription. reauthorizeSubscribers authorizes that user against the access cache, so an access change no longer costs one UserByID query per distinct subscriber user on every matching topic, on every node (a wildcard change could mean thousands). A deleted user's streams end through the relayed cancel (killUserSubscriberEverywhere), as before.
2026-10-24 13:19:55 +00:00
binwiederhier c296487f64 Rename AccessChangedFunc to AccessCacheChangedFunc
It fires when an access cache reload found a difference, whoever made the change; the cache's content changed, not necessarily because of this node. The cluster's AccessChangedFunc (a peer changed access) keeps its name.
2026-10-24 12:43:27 +00:00
binwiederhier 138922526a Tell the peers about an access write before reloading locally
accessWritten now calls AccessWrittenFunc first, so the other nodes start their reloads without waiting for this node's (about one reload, ~20 ms on prod), then reloads the local cache before the write returns.
2026-10-24 12:42:09 +00:00
binwiederhier c81645755b Give AccessWrittenFunc a type, and explain when each access callback fires 2026-10-24 12:37:36 +00:00
binwiederhier e9d089f00f Access cache: db and query in the constructor, clearer names and order
newAccessCache takes the database and the reload query, so Reload needs no arguments. reloadRequests is now accessCacheReloadRequests, above quit. AccessChangedFunc and AccessWrittenFunc sit below AccessCacheReloadInterval in user.Config, with comments.
2026-10-24 12:33:10 +00:00