errHTTP.WithLimitDecision replaces RetryAfter: handleError writes Retry-After and the RateLimit headers from it, and the decision names its own kind. A request that is allowed gets its RateLimit headers right away; a denied one gets them with the error.
Every app server that drops a subscription on a 404 also drops it on a 410, and Misskey and its forks only do on a 410. 410 is also what Mozilla's and Apple's push services answer for an expired subscription.
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.
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.
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.
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.
- Use new notification request/opt-in flow for push
- Implement unsubscribing
- Implement muting
- Implement emojis in title
- Add iOS specific PWA warning
- Don’t use websockets when web push is enabled
- Fix duplicate notifications
- Implement default web push setting
- Implement changing subscription type
- Implement web push subscription refresh
- Implement web push notification click