4690 Commits
Author SHA1 Message Date
Alvaro e65b2fb7b2 Translated using Weblate (Catalan)
Currently translated at 100.0% (459 of 459 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/ca/
2026-10-24 01:05:18 +02: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 b66ad90f84 Coalesce access cache reloads requested by peers
A burst of "access changed" signals no longer queues one full reload per signal on each peer: RequestAccessCacheReload returns at once and hands the request to the reload loop through a channel with room for one, so requests that arrive while a reload runs collapse into one more reload, which still sees every change. The node making the change keeps its synchronous reload (read-after-write).
2026-10-23 22:15:48 +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 626ec1f4fa Fix lint: one comment per cluster type alias 2026-10-23 21:07:08 +00:00
binwiederhier 86803803b1 Allow turning maintenance mode off again
Maintenance mode is now a state that can be entered and left without a restart: Server.SetMaintenance(on) runs the phases in the background and returns at once; off stops them wherever they are and takes the node back into service (ready, writable, active in the cluster). WaitDrained waits for the drain. On SIGTERM, Drain is SetMaintenance(true) plus WaitDrained, as before. SetMaintenance(true) is idempotent and refuses with drain duration 0, on a passive node, or without another active node. This is what a later 'ntfy cluster maintenance on|off' needs.
2026-10-23 20:54:29 +00:00
binwiederhier 7ccc880c12 Shorten the maintenance mode notes in the docs 2026-10-23 20:39:17 +00:00
roypadina 78c1568cc8 docs: add NtfyBar and MeetAlert to integrations 2026-10-23 23:38:50 +03:00
binwiederhier 75182d139d Replace Cluster.HasActivePeer with Cluster.Peers
registry.Peers is now a defined type with HasActive (cluster.Peer and cluster.Peers alias the registry types, like Role), and the Cluster interface exposes the live peers instead of a single yes/no; the server asks s.cluster.Peers().HasActive(). Replaces the mesh's hasActivePeer helper.
2026-10-23 20:31:28 +00:00
binwiederhier 3e57bbefde Serialize SetMaintenance writers
SetMaintenance stores the flag and then sets the registered role; two concurrent calls could interleave and leave them disagreeing. Writers now hold c.mu; reads stay lock-free (atomic), since Role is called on paths that already hold c.mu.
2026-10-23 20:26:58 +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 62fb43b44b Make the cluster the owner of maintenance mode
The cluster now owns the maintenance state (Cluster.Maintenance) instead of the server keeping its own copy; the server keeps only the draining phase. Role() returns the role the node acts in (passive in maintenance mode), so /v1/ready says "role":"passive","maintenance":true. Cluster.HasActivePeer replaces the server's own walk over Members. A single-node server no longer logs about maintenance mode on every shutdown. The stream registry moves to its own file, with unit tests.
2026-10-23 19:42:18 +00:00
binwiederhier 0c02bd8ea1 Merge remote-tracking branch 'origin/main' into cluster-drain 2026-10-23 13:21:16 +00:00
binwiederhier 958c8baf03 Also switch the runner's apt mirror list away from Azure
GitHub's ubuntu-24.04 runners point ubuntu.sources at mirror+file:/etc/apt/apt-mirrors.txt, which lists the Azure mirror first, so rewriting ubuntu.sources alone changed nothing (PR #2095's build still fetched from azure.archive.ubuntu.com). build-deps-ubuntu now rewrites both files.
2026-10-23 13:21:14 +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
binwiederhier b197d5d8ca Merge remote-tracking branch 'origin/main' into cluster-drain 2026-10-23 13:06:07 +00:00
binwiederhier 3a768188c4 Use the main Ubuntu archive instead of the Azure mirror in CI
The Azure apt mirror on GitHub runners trickles for minutes without stalling, which apt's timeouts do not catch: PR #2095's build hit the new 10 minute step timeout with one package taking 4.5 minutes. build-deps-ubuntu now points the runner's ubuntu.sources at archive.ubuntu.com before apt-get update; a no-op without the file or without Azure entries.
2026-10-23 13:05:58 +00:00
binwiederhier 0a10a13511 Act as passive in maintenance mode, and claim no delayed messages
A node in maintenance mode now registers as passive (registry.SetRole at runtime), so peers stop counting it as a place for clients and leadership moves to the next active node; it is not ready, does not lead (at once, before its row says passive), and never fences. Its configured role is unchanged. It also claims no due delayed messages, which the next leader sends. Also tests that a drain duration of 0 is accepted.
2026-10-23 12:52:00 +00:00
binwiederhier b76ad0c037 Call the shutdown drain maintenance mode
A node draining before a shutdown is in maintenance mode: /v1/ready reports "maintenance": true (was "draining"), and the 503 says the node is in maintenance mode. Maintenance is a runtime state next to the configured role, not a role: a later admin command can enter and leave it without a shutdown, with the same semantics. server_drain.go is now server_maintenance.go.
2026-10-23 12:38:39 +00:00
binwiederhier 5f3b0f823c Scope the index test to its own schema
pg_indexes spans every schema, so TestPostgresStore_CreatesTopicPublishedIDIndex also matched the message tables of parallel tests, and failed with 'could not open relation with OID' when one of them dropped its schema at that moment (seen once in CI on this branch).
2026-10-23 12:29:08 +00:00
binwiederhier 156fe22373 Merge remote-tracking branch 'origin/main' into cluster-drain
# Conflicts:
#	docs/releases.md
2026-10-23 12:18:59 +00:00
binwiederhier b0c0b4c980 Time out and retry stalled apt downloads in CI
The Azure apt mirrors on GitHub runners sometimes stall mid-download, and apt waits forever (three hung runs on 2026-10-23, two of them releases). build-deps-ubuntu now passes Acquire::Retries=3 and a 30s http/https timeout, and the Install dependencies step of the build, test and release workflows fails after 10 minutes instead of hanging.
2026-10-23 12:18:20 +00:00
Philipp C. Heckel a3284efad7 Merge pull request #2094 from binwiederhier/multi-listen
Allow several comma-separated addresses in listen-http
2026-10-23 06:14:24 -04:00
binwiederhier af9ade095e Drain cluster nodes on shutdown
On SIGTERM, an active cluster node with another healthy active node first drains: new subscriptions (HTTP streams and WebSockets) get 503, which a load balancer retries on another node, /v1/ready fails with "draining": true, and the open streams are closed evenly over experimental-cluster-drain-duration (default 1m, fits systemd's 90s stop timeout; 0 disables). Clients reconnect a few at a time and resume with since=, instead of all at once. Publishes and polls are still served, since a load balancer does not retry a POST. A second signal skips the rest of the drain.

A stream registry on the server tracks open subscriber connections once each, however many topics they subscribe to.
2026-10-23 04:27:01 +00:00
binwiederhier 6f94317377 Allow several comma-separated addresses in listen-http
A load balancer connects to each upstream address from one source IP, so one address caps it at ~63k connections to a node. listen-http now takes a comma-separated list (whitespace trimmed, empty and duplicate entries rejected, "-" still unsets it); Config.ListenHTTP becomes a slice, Run starts one HTTP server per address on the same handler, and stop closes all of them.
2026-10-23 04:12:25 +00:00
Philipp C. Heckel 2012d3e09a Merge pull request #2093 from binwiederhier/sigill-2092
Check the amd64 binary on a baseline x86-64 CPU after every build
2026-10-22 22:33:03 -04:00
binwiederhier 53f2046caf Check the amd64 binary on a baseline x86-64 CPU after every build
A goreleaser post-build hook runs the amd64 binary under qemu-x86_64 -cpu qemu64 (no AVX2, BMI2, MOVBE), so x86-64-v3 code in the statically linked C parts (#2092) fails make release before anything is published, and fails CI builds. Without qemu the check fails in CI and is skipped with a warning locally. build-deps-ubuntu installs qemu-user.
2026-10-23 02:32:04 +00:00
binwiederhier b0b3bde4f4 Build on ubuntu-24.04 to fix SIGILL on older amd64 CPUs, prep v2.33.1
v2.33.0 was built on the ubuntu-26.04 runner image (via ubuntu-latest), whose toolchain made the statically linked C code (glibc, SQLite) use x86-64-v3 instructions, crashing on CPUs without AVX2/BMI2 (#2092). Pin the release, build and test workflows to ubuntu-24.04, add v2.33.1 release notes and bump install.md.
v2.33.1
2026-10-23 02:08:37 +00:00
binwiederhier 04b208cf12 Drop another comment 2026-10-23 01:04:42 +00:00
binwiederhier 72eaa15cfd Drop a comment 2026-10-23 01:02:53 +00:00
binwiederhier babe37c8e7 Drop two comments in server.New 2026-10-23 00:59:55 +00:00
binwiederhier 7f8776f981 Merge remote-tracking branch 'origin/main' into cluster-cancel-relay
# Conflicts:
#	docs/releases.md
2026-10-23 00:41:50 +00:00
binwiederhier 2a5bbbb933 Prep v2.33.0 release: release notes, install docs
Move the unreleased server notes to v2.33.0 (released October 23, 2026) with a short intro, and bump install.md to 2.33.0.
v2.33.0
2026-10-23 00:41:38 +00:00
binwiederhier 6c057076f4 AccessChangedFunc in user.Config; explicit cancel for anonymous subscribers 2026-10-22 23:50:16 +00:00
binwiederhier 0555b94145 Style: one field per line in struct literals, shorter comments 2026-10-22 11:35:19 +00:00
binwiederhier 412f012ffc Admin socket: serve the admin API on a local unix socket without credentials
The server listens on admin-socket (default /run/ntfy/admin.sock, none
on Windows) when auth is configured. Requests on it run as a synthetic
admin, skip rate limiting and the ban feed, and may do what the network
API refuses (roles, changing or deleting admins). File mode 0660 in a
0750 directory is the authorization; peer credentials go into the audit
log on Linux and the BSDs.

A stale socket file is replaced, a socket another server answers on is
left alone, and failing to listen only logs a warning so the CLI can
fall back to the database. The systemd unit gets RuntimeDirectory=ntfy.
2026-10-22 03:04:21 +00:00
binwiederhier f468f74492 Admin API: tokens, tiers, password reset links, role changes over the admin socket
Fill the gaps between the admin HTTP API and what the ntfy CLI does
directly against the database: list/add/remove other users' tokens,
manage tiers (ported from the user-management branch), create password
reset links (optionally emailed), remove a user's tier, reset a user's
or the whole ACL, and return provisioned flags, tier names and the
default access in GET /v1/users.

All admin routes go through adminHandler, which the admin socket will
share. Requests marked as coming over the socket may set roles and
change or delete admins; the network API still refuses that. User
manager errors now map to 4xx errors instead of 500s.
2026-10-22 02:55:52 +00:00
binwiederhier e6e236359d Merge remote-tracking branch 'origin/main' into cluster-cancel-relay
# Conflicts:
#	docs/releases.md
#	server/server.go
2026-10-22 02:03:26 +00:00
Philipp C. Heckel fe037692e6 Merge pull request #2085 from binwiederhier/db-pool-metrics
Export PostgreSQL connection pool metrics
2026-10-21 21:51:34 -04:00
binwiederhier 462ad50f3a Pool metrics collector: one table of metrics instead of six fields 2026-10-22 01:39:58 +00:00
binwiederhier b49e2c319a Pool roles as constants 2026-10-22 01:33:48 +00:00
binwiederhier 97ec662d5d One field per line in struct literals 2026-10-22 01:29:44 +00:00
Philipp C. Heckel 721997e851 Merge pull request #2086 from binwiederhier/go-1273
Build with Go 1.27.3
2026-10-21 21:25:39 -04:00
binwiederhier 10c0cb5593 Build with Go 1.27.3
Go 1.27.2 and 1.27.3 are security releases; 1.27.3 makes crypto/x509
reject chains through the eight compromised eMudhra intermediate CAs.
ntfy's outbound TLS (Firebase, web push, Stripe, Twilio, S3, upstream)
only gets that by being rebuilt.

The bump was held at 1.27.1 because staticcheck v0.8.1 could not read
Go 1.27.2's export data; staticcheck 2026.2.2 (v0.8.2) can, and is
clean. text/template is unchanged, so only GENERATED_FROM moves.
2026-10-22 01:18:09 +00:00
binwiederhier dbeeaa72c1 Export PostgreSQL connection pool metrics
ntfy_db_pool_connections_{max,open,in_use,idle}, ntfy_db_pool_waits_total and
ntfy_db_pool_wait_seconds_total, one series per pool (role primary or
replica, host). Read from database/sql's pool stats at scrape time, so a
burst that exhausts a pool between two scrapes still shows up as waits.

Needed to size pool_max_conns for several active cluster nodes: the
managed database allows 97 connections, and the open count alone says
nothing, since the pool rotates through all its connections.
2026-10-22 01:14:41 +00:00
dependabot[bot] 4d551db6d8 Bump i18next from 26.4.2 to 26.4.4 in /web
Bumps [i18next](https://github.com/i18next/i18next) from 26.4.2 to 26.4.4.
- [Release notes](https://github.com/i18next/i18next/releases)
- [Changelog](https://github.com/i18next/i18next/blob/master/CHANGELOG.md)
- [Commits](https://github.com/i18next/i18next/compare/v26.4.2...v26.4.4)

---
updated-dependencies:
- dependency-name: i18next
  dependency-version: 26.4.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-10-20 21:43:00 +00:00
dependabot[bot] b84e67ebc9 Bump vitest from 5.0.3 to 5.0.4 in /web
Bumps [vitest](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest) from 5.0.3 to 5.0.4.
- [Release notes](https://github.com/vitest-dev/vitest/releases)
- [Changelog](https://github.com/vitest-dev/vitest/blob/main/docs/releases.md)
- [Commits](https://github.com/vitest-dev/vitest/commits/v5.0.4/packages/vitest)

---
updated-dependencies:
- dependency-name: vitest
  dependency-version: 5.0.4
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-10-20 21:42:56 +00:00
cangen185-sudo a67aee787f fix(util): accept terabyte size units without overflow 2026-10-20 16:10:57 +08:00
binwiederhier 2734662e79 Relay subscriber cancels to cluster peers and re-check access changes
Reserving a topic or revoking access only canceled the subscribers on the
node that made the change; on other cluster nodes, an anonymous stream kept
receiving a topic that had just become private.

- Relay reservation takeovers and access revocations to peers (cluster
  CancelFunc). The peer reloads its access cache for everyone and the user
  concerned before canceling, so an immediate reconnect is denied.
- Access cache reloads now report the entries they found changed, and the
  server re-checks only the subscribers on matching topics. This catches a
  lost relay and changes made with the ntfy access CLI within one reload
  interval (auth-access-cache-reload-interval).
- CancelSubscriberUser ends all of a user's streams on a topic, not just
  the first one.
2026-10-19 12:14:00 +00:00