This commit is contained in:
binwiederhier
2026-02-20 15:57:47 -05:00
parent a4c836b531
commit eb6e1ac44a
6 changed files with 38 additions and 21 deletions

View File

@@ -325,8 +325,8 @@ func execServe(c *cli.Context) error {
return errors.New("if upstream-base-url is set, base-url must also be set") return errors.New("if upstream-base-url is set, base-url must also be set")
} else if upstreamBaseURL != "" && baseURL != "" && baseURL == upstreamBaseURL { } else if upstreamBaseURL != "" && baseURL != "" && baseURL == upstreamBaseURL {
return errors.New("base-url and upstream-base-url cannot be identical, you'll likely want to set upstream-base-url to https://ntfy.sh, see https://ntfy.sh/docs/config/#ios-instant-notifications") return errors.New("base-url and upstream-base-url cannot be identical, you'll likely want to set upstream-base-url to https://ntfy.sh, see https://ntfy.sh/docs/config/#ios-instant-notifications")
} else if authFile == "" && (enableSignup || enableLogin || requireLogin || enableReservations || stripeSecretKey != "") { } else if authFile == "" && databaseURL == "" && (enableSignup || enableLogin || requireLogin || enableReservations || stripeSecretKey != "") {
return errors.New("cannot set enable-signup, enable-login, require-login, enable-reserve-topics, or stripe-secret-key if auth-file is not set") return errors.New("cannot set enable-signup, enable-login, require-login, enable-reserve-topics, or stripe-secret-key if auth-file or database-url is not set")
} else if enableSignup && !enableLogin { } else if enableSignup && !enableLogin {
return errors.New("cannot set enable-signup without also setting enable-login") return errors.New("cannot set enable-signup without also setting enable-login")
} else if requireLogin && !enableLogin { } else if requireLogin && !enableLogin {
@@ -335,8 +335,8 @@ func execServe(c *cli.Context) error {
return errors.New("cannot set stripe-secret-key or stripe-webhook-key, support for payments is not available in this build (nopayments)") return errors.New("cannot set stripe-secret-key or stripe-webhook-key, support for payments is not available in this build (nopayments)")
} else if stripeSecretKey != "" && (stripeWebhookKey == "" || baseURL == "") { } else if stripeSecretKey != "" && (stripeWebhookKey == "" || baseURL == "") {
return errors.New("if stripe-secret-key is set, stripe-webhook-key and base-url must also be set") return errors.New("if stripe-secret-key is set, stripe-webhook-key and base-url must also be set")
} else if twilioAccount != "" && (twilioAuthToken == "" || twilioPhoneNumber == "" || twilioVerifyService == "" || baseURL == "" || authFile == "") { } else if twilioAccount != "" && (twilioAuthToken == "" || twilioPhoneNumber == "" || twilioVerifyService == "" || baseURL == "" || (authFile == "" && databaseURL == "")) {
return errors.New("if twilio-account is set, twilio-auth-token, twilio-phone-number, twilio-verify-service, base-url, and auth-file must also be set") return errors.New("if twilio-account is set, twilio-auth-token, twilio-phone-number, twilio-verify-service, base-url, and auth-file (or database-url) must also be set")
} else if messageSizeLimit > server.DefaultMessageSizeLimit { } else if messageSizeLimit > server.DefaultMessageSizeLimit {
log.Warn("message-size-limit is greater than 4K, this is not recommended and largely untested, and may lead to issues with some clients") log.Warn("message-size-limit is greater than 4K, this is not recommended and largely untested, and may lead to issues with some clients")
if messageSizeLimit > 5*1024*1024 { if messageSizeLimit > 5*1024*1024 {

View File

@@ -10,7 +10,14 @@ import (
_ "github.com/jackc/pgx/v5/stdlib" // PostgreSQL driver _ "github.com/jackc/pgx/v5/stdlib" // PostgreSQL driver
) )
const defaultMaxOpenConns = 10 const (
paramMaxOpenConns = "pool_max_conns"
paramMaxIdleConns = "pool_max_idle_conns"
paramConnMaxLifetime = "pool_conn_max_lifetime"
paramConnMaxIdleTime = "pool_conn_max_idle_time"
defaultMaxOpenConns = 10
)
// Open opens a PostgreSQL database connection pool from a DSN string. It supports custom // Open opens a PostgreSQL database connection pool from a DSN string. It supports custom
// query parameters for pool configuration: pool_max_conns (default 10), pool_max_idle_conns, // query parameters for pool configuration: pool_max_conns (default 10), pool_max_idle_conns,
@@ -22,19 +29,19 @@ func Open(dsn string) (*sql.DB, error) {
return nil, fmt.Errorf("invalid database URL: %w", err) return nil, fmt.Errorf("invalid database URL: %w", err)
} }
q := u.Query() q := u.Query()
maxOpenConns, err := extractIntParam(q, "pool_max_conns", defaultMaxOpenConns) maxOpenConns, err := extractIntParam(q, paramMaxOpenConns, defaultMaxOpenConns)
if err != nil { if err != nil {
return nil, err return nil, err
} }
maxIdleConns, err := extractIntParam(q, "pool_max_idle_conns", 0) maxIdleConns, err := extractIntParam(q, paramMaxIdleConns, 0)
if err != nil { if err != nil {
return nil, err return nil, err
} }
connMaxLifetime, err := extractDurationParam(q, "pool_conn_max_lifetime", 0) connMaxLifetime, err := extractDurationParam(q, paramConnMaxLifetime, 0)
if err != nil { if err != nil {
return nil, err return nil, err
} }
connMaxIdleTime, err := extractDurationParam(q, "pool_conn_max_idle_time", 0) connMaxIdleTime, err := extractDurationParam(q, paramConnMaxIdleTime, 0)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@@ -153,8 +153,12 @@ database-url: "postgres://user:pass@host:5432/ntfy"
``` ```
When `database-url` is set, ntfy will use PostgreSQL for all database-backed stores (message cache, user manager, When `database-url` is set, ntfy will use PostgreSQL for all database-backed stores (message cache, user manager,
and web push subscriptions) instead of SQLite. The `cache-file`, `auth-file`, and `web-push-file` options are not and web push subscriptions) instead of SQLite. The `cache-file`, `auth-file`, and `web-push-file` options must not
required in this case. be set in this case.
Note that setting `database-url` implicitly enables authentication and access control (equivalent to setting
`auth-file` with SQLite). The default access is `read-write`, so anonymous users can still read and write to all
topics. To restrict access, set `auth-default-access` to `deny-all` (see [access control](#access-control)).
You can also set this via the environment variable `NTFY_DATABASE_URL` or the command line flag `--database-url`. You can also set this via the environment variable `NTFY_DATABASE_URL` or the command line flag `--database-url`.
@@ -215,14 +219,15 @@ and `visitor-attachment-daily-bandwidth-limit`. Setting these conservatively is
By default, the ntfy server is open for everyone, meaning **everyone can read and write to any topic** (this is how By default, the ntfy server is open for everyone, meaning **everyone can read and write to any topic** (this is how
ntfy.sh is configured). To restrict access to your own server, you can optionally configure authentication and authorization. ntfy.sh is configured). To restrict access to your own server, you can optionally configure authentication and authorization.
ntfy's auth is implemented with a simple [SQLite](https://www.sqlite.org/)-based backend. It implements two roles ntfy's auth implements two roles (`user` and `admin`) and per-topic `read` and `write` permissions using an
(`user` and `admin`) and per-topic `read` and `write` permissions using an [access control list (ACL)](https://en.wikipedia.org/wiki/Access-control_list). [access control list (ACL)](https://en.wikipedia.org/wiki/Access-control_list). Access control entries can be applied
Access control entries can be applied to users as well as the special everyone user (`*`), which represents anonymous API access. to users as well as the special everyone user (`*`), which represents anonymous API access.
To set up auth, **configure the following options**: To set up auth, **configure the following options**:
* `auth-file` is the user/access database; it is created automatically if it doesn't already exist; suggested * `auth-file` is the user/access database (SQLite); it is created automatically if it doesn't already exist; suggested
location `/var/lib/ntfy/user.db` (easiest if deb/rpm package is used) location `/var/lib/ntfy/user.db` (easiest if deb/rpm package is used). Alternatively, if `database-url` is set,
auth is automatically enabled using PostgreSQL (see [PostgreSQL database](#postgresql-database)).
* `auth-default-access` defines the default/fallback access if no access control entry is found; it can be * `auth-default-access` defines the default/fallback access if no access control entry is found; it can be
set to `read-write` (default), `read-only`, `write-only` or `deny-all`. **If you are setting up a private instance, set to `read-write` (default), `read-only`, `write-only` or `deny-all`. **If you are setting up a private instance,
you'll want to set this to `deny-all`** (see [private instance example](#example-private-instance)). you'll want to set this to `deny-all`** (see [private instance example](#example-private-instance)).
@@ -1795,7 +1800,7 @@ variable before running the `ntfy` command (e.g. `export NTFY_LISTEN_HTTP=:80`).
| `cache-startup-queries` | `NTFY_CACHE_STARTUP_QUERIES` | *string (SQL queries)* | - | SQL queries to run during database startup; this is useful for tuning and [enabling WAL mode](#message-cache) | | `cache-startup-queries` | `NTFY_CACHE_STARTUP_QUERIES` | *string (SQL queries)* | - | SQL queries to run during database startup; this is useful for tuning and [enabling WAL mode](#message-cache) |
| `cache-batch-size` | `NTFY_CACHE_BATCH_SIZE` | *int* | 0 | Max size of messages to batch together when writing to message cache (if zero, writes are synchronous) | | `cache-batch-size` | `NTFY_CACHE_BATCH_SIZE` | *int* | 0 | Max size of messages to batch together when writing to message cache (if zero, writes are synchronous) |
| `cache-batch-timeout` | `NTFY_CACHE_BATCH_TIMEOUT` | *duration* | 0s | Timeout for batched async writes to the message cache (if zero, writes are synchronous) | | `cache-batch-timeout` | `NTFY_CACHE_BATCH_TIMEOUT` | *duration* | 0s | Timeout for batched async writes to the message cache (if zero, writes are synchronous) |
| `auth-file` | `NTFY_AUTH_FILE` | *filename* | - | Auth database file used for access control. If set, enables authentication and access control. See [access control](#access-control). | | `auth-file` | `NTFY_AUTH_FILE` | *filename* | - | Auth database file used for access control (SQLite). If set, enables authentication and access control. Not required if `database-url` is set. See [access control](#access-control). |
| `auth-default-access` | `NTFY_AUTH_DEFAULT_ACCESS` | `read-write`, `read-only`, `write-only`, `deny-all` | `read-write` | Default permissions if no matching entries in the auth database are found. Default is `read-write`. | | `auth-default-access` | `NTFY_AUTH_DEFAULT_ACCESS` | `read-write`, `read-only`, `write-only`, `deny-all` | `read-write` | Default permissions if no matching entries in the auth database are found. Default is `read-write`. |
| `behind-proxy` | `NTFY_BEHIND_PROXY` | *bool* | false | If set, use forwarded header (e.g. X-Forwarded-For, X-Client-IP) to determine visitor IP address (for rate limiting) | | `behind-proxy` | `NTFY_BEHIND_PROXY` | *bool* | false | If set, use forwarded header (e.g. X-Forwarded-For, X-Client-IP) to determine visitor IP address (for rate limiting) |
| `proxy-forwarded-header` | `NTFY_PROXY_FORWARDED_HEADER` | *string* | `X-Forwarded-For` | Use specified header to determine visitor IP address (for rate limiting) | | `proxy-forwarded-header` | `NTFY_PROXY_FORWARDED_HEADER` | *string* | `X-Forwarded-For` | Use specified header to determine visitor IP address (for rate limiting) |

View File

@@ -1720,4 +1720,4 @@ and the [ntfy Android app](https://github.com/binwiederhier/ntfy-android/release
**Features:** **Features:**
* Add PostgreSQL as an alternative database backend for the web push subscription store via `database-url` config option * Add PostgreSQL as an alternative database backend for all stores (message cache, user manager, web push subscriptions) via `database-url` config option, using a single shared connection pool with configurable pool settings (`pool_max_conns`, `pool_max_idle_conns`, `pool_conn_max_lifetime`, `pool_conn_max_idle_time`)

View File

@@ -88,7 +88,6 @@ var (
// Config is the main config struct for the application. Use New to instantiate a default config struct. // Config is the main config struct for the application. Use New to instantiate a default config struct.
type Config struct { type Config struct {
File string // Config file, only used for testing File string // Config file, only used for testing
DatabaseURL string // PostgreSQL connection string (e.g. "postgres://user:pass@host:5432/ntfy")
BaseURL string BaseURL string
ListenHTTP string ListenHTTP string
ListenHTTPS string ListenHTTPS string
@@ -96,6 +95,7 @@ type Config struct {
ListenUnixMode fs.FileMode ListenUnixMode fs.FileMode
KeyFile string KeyFile string
CertFile string CertFile string
DatabaseURL string // PostgreSQL connection string (e.g. "postgres://user:pass@host:5432/ntfy")
FirebaseKeyFile string FirebaseKeyFile string
CacheFile string CacheFile string
CacheDuration time.Duration CacheDuration time.Duration
@@ -193,7 +193,6 @@ type Config struct {
func NewConfig() *Config { func NewConfig() *Config {
return &Config{ return &Config{
File: DefaultConfigFile, // Only used for testing File: DefaultConfigFile, // Only used for testing
DatabaseURL: "",
BaseURL: "", BaseURL: "",
ListenHTTP: DefaultListenHTTP, ListenHTTP: DefaultListenHTTP,
ListenHTTPS: "", ListenHTTPS: "",
@@ -201,6 +200,7 @@ func NewConfig() *Config {
ListenUnixMode: 0, ListenUnixMode: 0,
KeyFile: "", KeyFile: "",
CertFile: "", CertFile: "",
DatabaseURL: "",
FirebaseKeyFile: "", FirebaseKeyFile: "",
CacheFile: "", CacheFile: "",
CacheDuration: DefaultCacheDuration, CacheDuration: DefaultCacheDuration,

View File

@@ -40,7 +40,10 @@
# If "database-url" is set, ntfy will use PostgreSQL for all database-backed stores (message cache, # If "database-url" is set, ntfy will use PostgreSQL for all database-backed stores (message cache,
# user manager, and web push subscriptions) instead of SQLite. When set, the "cache-file", # user manager, and web push subscriptions) instead of SQLite. When set, the "cache-file",
# "auth-file", and "web-push-file" options are not required. # "auth-file", and "web-push-file" options must not be set.
#
# Note: Setting "database-url" implicitly enables authentication and access control.
# The default access is "read-write" (see "auth-default-access").
# #
# You can append connection pool parameters as query parameters: # You can append connection pool parameters as query parameters:
# pool_max_conns=10 - Maximum number of open connections (default: 10) # pool_max_conns=10 - Maximum number of open connections (default: 10)
@@ -90,6 +93,8 @@
# If set, access to the ntfy server and API can be controlled on a granular level using # If set, access to the ntfy server and API can be controlled on a granular level using
# the 'ntfy user' and 'ntfy access' commands. See the --help pages for details, or check the docs. # the 'ntfy user' and 'ntfy access' commands. See the --help pages for details, or check the docs.
# #
# Note: If "database-url" is set, auth is implicitly enabled and "auth-file" must not be set.
#
# - auth-file is the SQLite user/access database; it is created automatically if it doesn't already exist # - auth-file is the SQLite user/access database; it is created automatically if it doesn't already exist
# - auth-default-access defines the default/fallback access if no access control entry is found; it can be # - auth-default-access defines the default/fallback access if no access control entry is found; it can be
# set to "read-write" (default), "read-only", "write-only" or "deny-all". # set to "read-write" (default), "read-only", "write-only" or "deny-all".