mirror of
https://github.com/idanoo/autobrr
synced 2025-07-22 08:19:12 +00:00
feat(database): connect postgres via socket and read config from env _FILE secrets (#2061)
* feat(database): connect postgres via socket * feat(config): read env var secrets from file * docs: explain env var secrets * refactor: generate postgres dsn
This commit is contained in:
parent
24648e45f7
commit
fe4f385a22
9 changed files with 345 additions and 76 deletions
|
@ -1,6 +1,8 @@
|
||||||
# build web
|
# build web
|
||||||
FROM node:20.17.0-alpine3.20 AS web-builder
|
FROM node:20.17.0-alpine3.20 AS web-builder
|
||||||
RUN corepack enable
|
# Update and enable Corepack
|
||||||
|
RUN npm install -g corepack@latest && \
|
||||||
|
corepack enable
|
||||||
|
|
||||||
WORKDIR /web
|
WORKDIR /web
|
||||||
|
|
||||||
|
|
52
README.md
52
README.md
|
@ -317,7 +317,7 @@ If you are not running a reverse proxy change `host` in the `config.toml` to `0.
|
||||||
The following environment variables can be used:
|
The following environment variables can be used:
|
||||||
|
|
||||||
| Variable | Description | Default |
|
| Variable | Description | Default |
|
||||||
| -------------------------------------- | -------------------------------------------------------- | ---------------------------------------- |
|
|----------------------------------------|----------------------------------------------------------|------------------------------------------|
|
||||||
| `AUTOBRR__HOST` | Listen address | `127.0.0.1` |
|
| `AUTOBRR__HOST` | Listen address | `127.0.0.1` |
|
||||||
| `AUTOBRR__PORT` | Listen port | `7474` |
|
| `AUTOBRR__PORT` | Listen port | `7474` |
|
||||||
| `AUTOBRR__BASE_URL` | Base URL for reverse proxy | `/` |
|
| `AUTOBRR__BASE_URL` | Base URL for reverse proxy | `/` |
|
||||||
|
@ -329,12 +329,16 @@ The following environment variables can be used:
|
||||||
| `AUTOBRR__CUSTOM_DEFINITIONS` | Path to custom indexer definitions | - |
|
| `AUTOBRR__CUSTOM_DEFINITIONS` | Path to custom indexer definitions | - |
|
||||||
| `AUTOBRR__CHECK_FOR_UPDATES` | Enable update checks | `true` |
|
| `AUTOBRR__CHECK_FOR_UPDATES` | Enable update checks | `true` |
|
||||||
| `AUTOBRR__DATABASE_TYPE` | Database type (sqlite/postgres) | `sqlite` |
|
| `AUTOBRR__DATABASE_TYPE` | Database type (sqlite/postgres) | `sqlite` |
|
||||||
|
| `AUTOBRR__DATABASE_DSN` | Database connection string. Use this or individual vars | - |
|
||||||
| `AUTOBRR__POSTGRES_HOST` | PostgreSQL host | - |
|
| `AUTOBRR__POSTGRES_HOST` | PostgreSQL host | - |
|
||||||
| `AUTOBRR__POSTGRES_PORT` | PostgreSQL port | `5432` |
|
| `AUTOBRR__POSTGRES_PORT` | PostgreSQL port | `5432` |
|
||||||
| `AUTOBRR__POSTGRES_DATABASE` | PostgreSQL database name | - |
|
| `AUTOBRR__POSTGRES_DATABASE` | PostgreSQL database name | - |
|
||||||
|
| `AUTOBRR__POSTGRES_DB` | PostgreSQL database name | - |
|
||||||
| `AUTOBRR__POSTGRES_USER` | PostgreSQL username | - |
|
| `AUTOBRR__POSTGRES_USER` | PostgreSQL username | - |
|
||||||
| `AUTOBRR__POSTGRES_PASS` | PostgreSQL password | - |
|
| `AUTOBRR__POSTGRES_PASS` | PostgreSQL password | - |
|
||||||
|
| `AUTOBRR__POSTGRES_PASSWORD` | PostgreSQL password | - |
|
||||||
| `AUTOBRR__POSTGRES_SSLMODE` | PostgreSQL SSL mode | `disable` |
|
| `AUTOBRR__POSTGRES_SSLMODE` | PostgreSQL SSL mode | `disable` |
|
||||||
|
| `AUTOBRR__POSTGRES_SOCKET` | PostgreSQL unix socket | - |
|
||||||
| `AUTOBRR__POSTGRES_EXTRA_PARAMS` | Additional PostgreSQL parameters | - |
|
| `AUTOBRR__POSTGRES_EXTRA_PARAMS` | Additional PostgreSQL parameters | - |
|
||||||
| `AUTOBRR__OIDC_ENABLED` | Enable OpenID Connect authentication | `false` |
|
| `AUTOBRR__OIDC_ENABLED` | Enable OpenID Connect authentication | `false` |
|
||||||
| `AUTOBRR__OIDC_ISSUER` | OIDC issuer URL | - |
|
| `AUTOBRR__OIDC_ISSUER` | OIDC issuer URL | - |
|
||||||
|
@ -347,6 +351,52 @@ The following environment variables can be used:
|
||||||
| `AUTOBRR__METRICS_PORT` | Metrics listen port | `9074` |
|
| `AUTOBRR__METRICS_PORT` | Metrics listen port | `9074` |
|
||||||
| `AUTOBRR__METRICS_BASIC_AUTH_USERS` | Metrics basic auth users | - |
|
| `AUTOBRR__METRICS_BASIC_AUTH_USERS` | Metrics basic auth users | - |
|
||||||
|
|
||||||
|
#### Docker secrets
|
||||||
|
|
||||||
|
All ENV vars have a `_FILE` option where it can read contents from a file, such as docker secrets. See the example below:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
services:
|
||||||
|
autobrr:
|
||||||
|
image: ghcr.io/autobrr/autobrr:latest
|
||||||
|
container_name: autobrr
|
||||||
|
volumes:
|
||||||
|
- ./config:/config
|
||||||
|
ports:
|
||||||
|
- "7474:7474"
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
- AUTOBRR__DATABASE_TYPE=postgres
|
||||||
|
- AUTOBRR__POSTGRES_HOST=postgres
|
||||||
|
- AUTOBRR__POSTGRES_PORT=5432
|
||||||
|
- AUTOBRR__POSTGRES_USER=autobrr
|
||||||
|
- AUTOBRR__POSTGRES_PASSWORD_FILE=/run/secrets/db_password
|
||||||
|
- AUTOBRR__POSTGRES_DB=autobrr
|
||||||
|
secrets:
|
||||||
|
- db_password
|
||||||
|
|
||||||
|
postgres:
|
||||||
|
image: postgres:12.10
|
||||||
|
container_name: postgres
|
||||||
|
volumes:
|
||||||
|
- postgres:/var/lib/postgresql/data
|
||||||
|
ports:
|
||||||
|
- "5432:5432"
|
||||||
|
environment:
|
||||||
|
- POSTGRES_USER=autobrr
|
||||||
|
- POSTGRES_PASSWORD_FILE=/run/secrets/db_password
|
||||||
|
- POSTGRES_DB=autobrr
|
||||||
|
secrets:
|
||||||
|
- db_password
|
||||||
|
|
||||||
|
secrets:
|
||||||
|
db_password:
|
||||||
|
file: db_password.txt
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
postgres:
|
||||||
|
```
|
||||||
|
|
||||||
## Community
|
## Community
|
||||||
|
|
||||||
Join our friendly and welcoming community on [Discord](https://discord.gg/WQ2eUycxyT)! Connect with fellow autobrr users, get advice, and share your experiences. Whether you're seeking help, wanting to contribute, or just looking to discuss your ideas, our community is a hub of discussion and support. We're all here to help each other out, so don't hesitate to jump in!
|
Join our friendly and welcoming community on [Discord](https://discord.gg/WQ2eUycxyT)! Connect with fellow autobrr users, get advice, and share your experiences. Whether you're seeking help, wanting to contribute, or just looking to discuss your ideas, our community is a hub of discussion and support. We're all here to help each other out, so don't hesitate to jump in!
|
||||||
|
|
30
config.toml
30
config.toml
|
@ -69,6 +69,36 @@ checkForUpdates = true
|
||||||
#
|
#
|
||||||
sessionSecret = "secret-session-key"
|
sessionSecret = "secret-session-key"
|
||||||
|
|
||||||
|
# Database configuration
|
||||||
|
#
|
||||||
|
#databaseType = "postgres"
|
||||||
|
#databaseDSN = "postgresql://autobrr:postgres@localhost:5432/autobrr?sslmode=disable"
|
||||||
|
#
|
||||||
|
#databaseType = "sqlite"
|
||||||
|
#databaseDSN = "file:/config/data/autobrr.db"
|
||||||
|
#
|
||||||
|
# Use databaseDSN or the individual fields
|
||||||
|
#
|
||||||
|
#postgresHost = "localhost"
|
||||||
|
#
|
||||||
|
#postgresPort = 5432
|
||||||
|
#
|
||||||
|
#postgresUser = "autobrr"
|
||||||
|
#
|
||||||
|
#postgresPass = "postgres"
|
||||||
|
#
|
||||||
|
#postgresDatabase = "autobrr"
|
||||||
|
#
|
||||||
|
#postgresSSLMode = "disable"
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
#
|
||||||
|
#postgresSocket = "/run/socket.sock"
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
#
|
||||||
|
#postgresExtraParams = "connect_timeout=10"
|
||||||
|
|
||||||
# OpenID Connect Configuration
|
# OpenID Connect Configuration
|
||||||
#
|
#
|
||||||
# Enable OIDC authentication
|
# Enable OIDC authentication
|
||||||
|
|
|
@ -1,14 +1,21 @@
|
||||||
---
|
---
|
||||||
version: "2.1"
|
|
||||||
services:
|
services:
|
||||||
autobrr:
|
autobrr:
|
||||||
image: autobrr:dev
|
# image: ghcr.io/autobrr/autobrr:develop
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile
|
||||||
container_name: autobrr
|
container_name: autobrr
|
||||||
volumes:
|
volumes:
|
||||||
- ./config:/config
|
- ./config:/config
|
||||||
ports:
|
ports:
|
||||||
- "7474:7474"
|
- "7474:7474"
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
# environment:
|
||||||
|
# AUTOBRR__POSTGRES_PASSWORD_FILE: /run/secrets/db_password
|
||||||
|
# secrets:
|
||||||
|
# - db_password
|
||||||
|
|
||||||
postgres:
|
postgres:
|
||||||
image: postgres:12.10
|
image: postgres:12.10
|
||||||
container_name: postgres
|
container_name: postgres
|
||||||
|
@ -20,6 +27,7 @@ services:
|
||||||
- POSTGRES_USER=autobrr
|
- POSTGRES_USER=autobrr
|
||||||
- POSTGRES_PASSWORD=postgres
|
- POSTGRES_PASSWORD=postgres
|
||||||
- POSTGRES_DB=autobrr
|
- POSTGRES_DB=autobrr
|
||||||
|
|
||||||
test_postgres:
|
test_postgres:
|
||||||
image: postgres:12.10
|
image: postgres:12.10
|
||||||
container_name: autobrr_postgres_test
|
container_name: autobrr_postgres_test
|
||||||
|
@ -32,6 +40,9 @@ services:
|
||||||
- POSTGRES_PASSWORD=testdb
|
- POSTGRES_PASSWORD=testdb
|
||||||
- POSTGRES_DB=autobrr
|
- POSTGRES_DB=autobrr
|
||||||
|
|
||||||
|
#secrets:
|
||||||
|
# db_password:
|
||||||
|
# file: db_password.txt
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
postgres:
|
postgres:
|
||||||
|
|
|
@ -24,6 +24,8 @@ import (
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var EnvVarPrefix = "AUTOBRR__"
|
||||||
|
|
||||||
var configTemplate = `# config.toml
|
var configTemplate = `# config.toml
|
||||||
|
|
||||||
# Hostname / IP
|
# Hostname / IP
|
||||||
|
@ -281,6 +283,7 @@ func (c *AppConfig) defaults() {
|
||||||
CustomDefinitions: "",
|
CustomDefinitions: "",
|
||||||
CheckForUpdates: true,
|
CheckForUpdates: true,
|
||||||
DatabaseType: "sqlite",
|
DatabaseType: "sqlite",
|
||||||
|
DatabaseDSN: "",
|
||||||
PostgresHost: "",
|
PostgresHost: "",
|
||||||
PostgresPort: 0,
|
PostgresPort: 0,
|
||||||
PostgresDatabase: "",
|
PostgresDatabase: "",
|
||||||
|
@ -288,6 +291,7 @@ func (c *AppConfig) defaults() {
|
||||||
PostgresPass: "",
|
PostgresPass: "",
|
||||||
PostgresSSLMode: "disable",
|
PostgresSSLMode: "disable",
|
||||||
PostgresExtraParams: "",
|
PostgresExtraParams: "",
|
||||||
|
PostgresSocket: "",
|
||||||
ProfilingEnabled: false,
|
ProfilingEnabled: false,
|
||||||
ProfilingHost: "127.0.0.1",
|
ProfilingHost: "127.0.0.1",
|
||||||
ProfilingPort: 6060,
|
ProfilingPort: 6060,
|
||||||
|
@ -300,165 +304,187 @@ func (c *AppConfig) defaults() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *AppConfig) loadFromEnv() {
|
func (c *AppConfig) loadFromEnv() {
|
||||||
prefix := "AUTOBRR__"
|
if v := GetEnvStr("HOST"); v != "" {
|
||||||
|
|
||||||
if v := os.Getenv(prefix + "HOST"); v != "" {
|
|
||||||
c.Config.Host = v
|
c.Config.Host = v
|
||||||
}
|
}
|
||||||
|
|
||||||
if v := os.Getenv(prefix + "PORT"); v != "" {
|
if v := GetEnvInt("PORT"); v > 0 {
|
||||||
i, _ := strconv.ParseInt(v, 10, 32)
|
c.Config.Port = v
|
||||||
if i > 0 {
|
|
||||||
c.Config.Port = int(i)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if v := os.Getenv(prefix + "BASE_URL"); v != "" {
|
if v := GetEnvStr("BASE_URL"); v != "" {
|
||||||
c.Config.BaseURL = v
|
c.Config.BaseURL = v
|
||||||
}
|
}
|
||||||
|
|
||||||
if v := os.Getenv(prefix + "BASE_URL_MODE_LEGACY"); v != "" {
|
if v := GetEnvStr("BASE_URL_MODE_LEGACY"); v != "" {
|
||||||
c.Config.BaseURLModeLegacy = strings.EqualFold(strings.ToLower(v), "true")
|
c.Config.BaseURLModeLegacy = strings.EqualFold(strings.ToLower(v), "true")
|
||||||
}
|
}
|
||||||
|
|
||||||
if v := os.Getenv(prefix + "LOG_LEVEL"); v != "" {
|
if v := GetEnvStr("LOG_LEVEL"); v != "" {
|
||||||
c.Config.LogLevel = v
|
c.Config.LogLevel = v
|
||||||
}
|
}
|
||||||
|
|
||||||
if v := os.Getenv(prefix + "LOG_PATH"); v != "" {
|
if v := GetEnvStr("LOG_PATH"); v != "" {
|
||||||
c.Config.LogPath = v
|
c.Config.LogPath = v
|
||||||
}
|
}
|
||||||
|
|
||||||
if v := os.Getenv(prefix + "LOG_MAX_SIZE"); v != "" {
|
if v := GetEnvInt("LOG_MAX_SIZE"); v > 0 {
|
||||||
i, _ := strconv.ParseInt(v, 10, 32)
|
c.Config.LogMaxSize = v
|
||||||
if i > 0 {
|
|
||||||
c.Config.LogMaxSize = int(i)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if v := os.Getenv(prefix + "LOG_MAX_BACKUPS"); v != "" {
|
if v := GetEnvInt("LOG_MAX_BACKUPS"); v > 0 {
|
||||||
i, _ := strconv.ParseInt(v, 10, 32)
|
c.Config.LogMaxBackups = v
|
||||||
if i > 0 {
|
|
||||||
c.Config.LogMaxBackups = int(i)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if v := os.Getenv(prefix + "SESSION_SECRET"); v != "" {
|
if v := GetEnvStr("SESSION_SECRET"); v != "" {
|
||||||
c.Config.SessionSecret = v
|
c.Config.SessionSecret = v
|
||||||
}
|
}
|
||||||
|
|
||||||
if v := os.Getenv(prefix + "CUSTOM_DEFINITIONS"); v != "" {
|
if v := GetEnvStr("CUSTOM_DEFINITIONS"); v != "" {
|
||||||
c.Config.CustomDefinitions = v
|
c.Config.CustomDefinitions = v
|
||||||
}
|
}
|
||||||
|
|
||||||
if v := os.Getenv(prefix + "CHECK_FOR_UPDATES"); v != "" {
|
if v := GetEnvStr("CHECK_FOR_UPDATES"); v != "" {
|
||||||
c.Config.CheckForUpdates = strings.EqualFold(strings.ToLower(v), "true")
|
c.Config.CheckForUpdates = strings.EqualFold(strings.ToLower(v), "true")
|
||||||
}
|
}
|
||||||
|
|
||||||
if v := os.Getenv(prefix + "DATABASE_TYPE"); v != "" {
|
if v := GetEnvStr("DATABASE_DSN"); v != "" {
|
||||||
|
c.Config.DatabaseDSN = v
|
||||||
|
}
|
||||||
|
|
||||||
|
if v := GetEnvStr("DATABASE_TYPE"); v != "" {
|
||||||
if validDatabaseType(v) {
|
if validDatabaseType(v) {
|
||||||
c.Config.DatabaseType = v
|
c.Config.DatabaseType = v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if v := os.Getenv(prefix + "DATABASE_MAX_BACKUPS"); v != "" {
|
if v := GetEnvInt("DATABASE_MAX_BACKUPS"); v > 0 {
|
||||||
i, _ := strconv.ParseInt(v, 10, 32)
|
c.Config.DatabaseMaxBackups = v
|
||||||
if i > 0 {
|
|
||||||
c.Config.DatabaseMaxBackups = int(i)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if v := os.Getenv(prefix + "POSTGRES_HOST"); v != "" {
|
if v := GetEnvStr("POSTGRES_HOST"); v != "" {
|
||||||
c.Config.PostgresHost = v
|
c.Config.PostgresHost = v
|
||||||
}
|
}
|
||||||
|
|
||||||
if v := os.Getenv(prefix + "POSTGRES_PORT"); v != "" {
|
if v := GetEnvInt("POSTGRES_PORT"); v > 0 {
|
||||||
i, _ := strconv.ParseInt(v, 10, 32)
|
c.Config.PostgresPort = v
|
||||||
if i > 0 {
|
|
||||||
c.Config.PostgresPort = int(i)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if v := os.Getenv(prefix + "POSTGRES_DATABASE"); v != "" {
|
if v := GetEnvStr("POSTGRES_DATABASE"); v != "" {
|
||||||
c.Config.PostgresDatabase = v
|
c.Config.PostgresDatabase = v
|
||||||
}
|
}
|
||||||
|
|
||||||
if v := os.Getenv(prefix + "POSTGRES_USER"); v != "" {
|
if v := GetEnvStr("POSTGRES_DB"); v != "" {
|
||||||
|
c.Config.PostgresDatabase = v
|
||||||
|
}
|
||||||
|
|
||||||
|
if v := GetEnvStr("POSTGRES_USER"); v != "" {
|
||||||
c.Config.PostgresUser = v
|
c.Config.PostgresUser = v
|
||||||
}
|
}
|
||||||
|
|
||||||
if v := os.Getenv(prefix + "POSTGRES_PASS"); v != "" {
|
if v := GetEnvStr("POSTGRES_PASS"); v != "" {
|
||||||
c.Config.PostgresPass = v
|
c.Config.PostgresPass = v
|
||||||
}
|
}
|
||||||
|
|
||||||
if v := os.Getenv(prefix + "POSTGRES_SSLMODE"); v != "" {
|
if v := GetEnvStr("POSTGRES_PASSWORD"); v != "" {
|
||||||
|
c.Config.PostgresPass = v
|
||||||
|
}
|
||||||
|
|
||||||
|
if v := GetEnvStr("POSTGRES_SSLMODE"); v != "" {
|
||||||
c.Config.PostgresSSLMode = v
|
c.Config.PostgresSSLMode = v
|
||||||
}
|
}
|
||||||
|
|
||||||
if v := os.Getenv(prefix + "POSTGRES_EXTRA_PARAMS"); v != "" {
|
if v := GetEnvStr("POSTGRES_SOCKET"); v != "" {
|
||||||
|
c.Config.PostgresSocket = v
|
||||||
|
}
|
||||||
|
|
||||||
|
if v := GetEnvStr("POSTGRES_EXTRA_PARAMS"); v != "" {
|
||||||
c.Config.PostgresExtraParams = v
|
c.Config.PostgresExtraParams = v
|
||||||
}
|
}
|
||||||
|
|
||||||
if v := os.Getenv(prefix + "PROFILING_ENABLED"); v != "" {
|
if v := GetEnvStr("PROFILING_ENABLED"); v != "" {
|
||||||
c.Config.ProfilingEnabled = strings.EqualFold(strings.ToLower(v), "true")
|
c.Config.ProfilingEnabled = strings.EqualFold(strings.ToLower(v), "true")
|
||||||
}
|
}
|
||||||
|
|
||||||
if v := os.Getenv(prefix + "PROFILING_HOST"); v != "" {
|
if v := GetEnvStr("PROFILING_HOST"); v != "" {
|
||||||
c.Config.ProfilingHost = v
|
c.Config.ProfilingHost = v
|
||||||
}
|
}
|
||||||
|
|
||||||
if v := os.Getenv(prefix + "PROFILING_PORT"); v != "" {
|
if v := GetEnvInt("PROFILING_PORT"); v > 0 {
|
||||||
i, _ := strconv.ParseInt(v, 10, 32)
|
c.Config.ProfilingPort = v
|
||||||
if i > 0 {
|
|
||||||
c.Config.ProfilingPort = int(i)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// OIDC Configuration
|
// OIDC Configuration
|
||||||
if v := os.Getenv(prefix + "OIDC_ENABLED"); v != "" {
|
if v := GetEnvStr("OIDC_ENABLED"); v != "" {
|
||||||
c.Config.OIDCEnabled = strings.EqualFold(strings.ToLower(v), "true")
|
c.Config.OIDCEnabled = strings.EqualFold(strings.ToLower(v), "true")
|
||||||
}
|
}
|
||||||
|
|
||||||
if v := os.Getenv(prefix + "OIDC_ISSUER"); v != "" {
|
if v := GetEnvStr("OIDC_ISSUER"); v != "" {
|
||||||
c.Config.OIDCIssuer = v
|
c.Config.OIDCIssuer = v
|
||||||
}
|
}
|
||||||
|
|
||||||
if v := os.Getenv(prefix + "OIDC_CLIENT_ID"); v != "" {
|
if v := GetEnvStr("OIDC_CLIENT_ID"); v != "" {
|
||||||
c.Config.OIDCClientID = v
|
c.Config.OIDCClientID = v
|
||||||
}
|
}
|
||||||
|
|
||||||
if v := os.Getenv(prefix + "OIDC_CLIENT_SECRET"); v != "" {
|
if v := GetEnvStr("OIDC_CLIENT_SECRET"); v != "" {
|
||||||
c.Config.OIDCClientSecret = v
|
c.Config.OIDCClientSecret = v
|
||||||
}
|
}
|
||||||
|
|
||||||
if v := os.Getenv(prefix + "OIDC_REDIRECT_URL"); v != "" {
|
if v := GetEnvStr("OIDC_REDIRECT_URL"); v != "" {
|
||||||
c.Config.OIDCRedirectURL = v
|
c.Config.OIDCRedirectURL = v
|
||||||
}
|
}
|
||||||
|
|
||||||
if v := os.Getenv(prefix + "OIDC_DISABLE_BUILT_IN_LOGIN"); v != "" {
|
if v := GetEnvStr("OIDC_DISABLE_BUILT_IN_LOGIN"); v != "" {
|
||||||
c.Config.OIDCDisableBuiltInLogin = strings.EqualFold(strings.ToLower(v), "true")
|
c.Config.OIDCDisableBuiltInLogin = strings.EqualFold(strings.ToLower(v), "true")
|
||||||
}
|
}
|
||||||
|
|
||||||
if v := os.Getenv(prefix + "METRICS_ENABLED"); v != "" {
|
if v := GetEnvStr("METRICS_ENABLED"); v != "" {
|
||||||
c.Config.MetricsEnabled = strings.EqualFold(strings.ToLower(v), "true")
|
c.Config.MetricsEnabled = strings.EqualFold(strings.ToLower(v), "true")
|
||||||
}
|
}
|
||||||
|
|
||||||
if v := os.Getenv(prefix + "METRICS_HOST"); v != "" {
|
if v := GetEnvStr("METRICS_HOST"); v != "" {
|
||||||
c.Config.MetricsHost = v
|
c.Config.MetricsHost = v
|
||||||
}
|
}
|
||||||
|
|
||||||
if v := os.Getenv(prefix + "METRICS_PORT"); v != "" {
|
if v := GetEnvInt("METRICS_PORT"); v > 0 {
|
||||||
i, _ := strconv.ParseInt(v, 10, 32)
|
c.Config.MetricsPort = v
|
||||||
if i > 0 {
|
|
||||||
c.Config.MetricsPort = int(i)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if v := os.Getenv(prefix + "METRICS_BASIC_AUTH_USERS"); v != "" {
|
if v := GetEnvStr("METRICS_BASIC_AUTH_USERS"); v != "" {
|
||||||
c.Config.MetricsBasicAuthUsers = v
|
c.Config.MetricsBasicAuthUsers = v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetEnvStr(key string) string {
|
||||||
|
// first check if we have a variable with a _FILE ending
|
||||||
|
// commonly used for docker secrets and similar
|
||||||
|
if filePath := os.Getenv(EnvVarPrefix + key + "_FILE"); filePath != "" {
|
||||||
|
content, err := os.ReadFile(filePath)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Could not read file: %s err: %q", filePath, err)
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return strings.TrimSpace(string(content))
|
||||||
|
}
|
||||||
|
|
||||||
|
if v := os.Getenv(EnvVarPrefix + key); v != "" {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetEnvInt(key string) int {
|
||||||
|
value := GetEnvStr(key)
|
||||||
|
|
||||||
|
i, err := strconv.ParseInt(value, 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return int(i)
|
||||||
|
}
|
||||||
|
|
||||||
func validDatabaseType(v string) bool {
|
func validDatabaseType(v string) bool {
|
||||||
valid := []string{"sqlite", "postgres"}
|
valid := []string{"sqlite", "postgres"}
|
||||||
for _, s := range valid {
|
for _, s := range valid {
|
||||||
|
|
|
@ -6,8 +6,8 @@ package database
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"fmt"
|
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/autobrr/autobrr/internal/domain"
|
"github.com/autobrr/autobrr/internal/domain"
|
||||||
|
@ -35,13 +35,33 @@ type DB struct {
|
||||||
|
|
||||||
func NewDB(cfg *domain.Config, log logger.Logger) (*DB, error) {
|
func NewDB(cfg *domain.Config, log logger.Logger) (*DB, error) {
|
||||||
db := &DB{
|
db := &DB{
|
||||||
// set default placeholder for squirrel to support both sqlite and postgres
|
// set a default placeholder for squirrel to support both sqlite and postgres
|
||||||
squirrel: sq.StatementBuilder.PlaceholderFormat(sq.Dollar),
|
squirrel: sq.StatementBuilder.PlaceholderFormat(sq.Dollar),
|
||||||
log: log.With().Str("module", "database").Str("type", cfg.DatabaseType).Logger(),
|
log: log.With().Str("module", "database").Str("type", cfg.DatabaseType).Logger(),
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
}
|
}
|
||||||
db.ctx, db.cancel = context.WithCancel(context.Background())
|
db.ctx, db.cancel = context.WithCancel(context.Background())
|
||||||
|
|
||||||
|
// Check for directly configured DSN in config
|
||||||
|
if cfg.DatabaseDSN != "" {
|
||||||
|
if strings.HasPrefix(cfg.DatabaseDSN, "postgres://") || strings.HasPrefix(cfg.DatabaseDSN, "postgresql://") {
|
||||||
|
db.Driver = "postgres"
|
||||||
|
db.DSN = cfg.DatabaseDSN
|
||||||
|
return db, nil
|
||||||
|
} else if strings.HasPrefix(cfg.DatabaseDSN, "file:") || cfg.DatabaseDSN == ":memory:" || strings.HasSuffix(cfg.DatabaseDSN, ".db") {
|
||||||
|
db.Driver = "sqlite"
|
||||||
|
if strings.HasPrefix(cfg.DatabaseDSN, "file:") && strings.HasSuffix(cfg.DatabaseDSN, ".db") {
|
||||||
|
db.DSN = strings.TrimPrefix(cfg.DatabaseDSN, "file:")
|
||||||
|
} else {
|
||||||
|
db.DSN = cfg.DatabaseDSN
|
||||||
|
}
|
||||||
|
return db, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, errors.New("unsupported database DSN: %s", cfg.DatabaseDSN)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no direct DSN is provided, build it from individual settings
|
||||||
switch cfg.DatabaseType {
|
switch cfg.DatabaseType {
|
||||||
case "sqlite":
|
case "sqlite":
|
||||||
db.Driver = "sqlite"
|
db.Driver = "sqlite"
|
||||||
|
@ -51,14 +71,19 @@ func NewDB(cfg *domain.Config, log logger.Logger) (*DB, error) {
|
||||||
db.DSN = dataSourceName(cfg.ConfigPath, "autobrr.db")
|
db.DSN = dataSourceName(cfg.ConfigPath, "autobrr.db")
|
||||||
}
|
}
|
||||||
case "postgres":
|
case "postgres":
|
||||||
if cfg.PostgresHost == "" || cfg.PostgresPort == 0 || cfg.PostgresDatabase == "" {
|
|
||||||
return nil, errors.New("postgres: bad variables")
|
|
||||||
}
|
|
||||||
db.DSN = fmt.Sprintf("postgres://%v:%v@%v:%d/%v?sslmode=%v", cfg.PostgresUser, cfg.PostgresPass, cfg.PostgresHost, cfg.PostgresPort, cfg.PostgresDatabase, cfg.PostgresSSLMode)
|
|
||||||
if cfg.PostgresExtraParams != "" {
|
|
||||||
db.DSN = fmt.Sprintf("%s&%s", db.DSN, cfg.PostgresExtraParams)
|
|
||||||
}
|
|
||||||
db.Driver = "postgres"
|
db.Driver = "postgres"
|
||||||
|
|
||||||
|
// If no database-specific settings are provided, return an error
|
||||||
|
if cfg.PostgresDatabase == "" && cfg.DatabaseDSN == "" {
|
||||||
|
return nil, errors.New("postgres: database name is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
pgDsn, err := PostgresDSN(cfg.PostgresHost, cfg.PostgresPort, cfg.PostgresUser, cfg.PostgresPass, cfg.PostgresDatabase, cfg.PostgresSocket, cfg.PostgresSSLMode, cfg.PostgresExtraParams)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "postgres: failed to build DSN")
|
||||||
|
}
|
||||||
|
db.DSN = pgDsn
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return nil, errors.New("unsupported database: %v", cfg.DatabaseType)
|
return nil, errors.New("unsupported database: %v", cfg.DatabaseType)
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,9 @@ package database
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
"github.com/autobrr/autobrr/pkg/errors"
|
"github.com/autobrr/autobrr/pkg/errors"
|
||||||
|
|
||||||
|
@ -87,3 +90,56 @@ func (db *DB) migratePostgres() error {
|
||||||
|
|
||||||
return tx.Commit()
|
return tx.Commit()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PostgresDSN build postgres dsn connect string
|
||||||
|
func PostgresDSN(host string, port int, user, pass, database, socket, sslMode, extraParams string) (string, error) {
|
||||||
|
// If no database is provided, return an error
|
||||||
|
if database == "" {
|
||||||
|
return "", errors.New("postgres: database name is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
pgDsn, err := url.Parse("postgres://")
|
||||||
|
if err != nil {
|
||||||
|
return "", errors.Wrap(err, "could not parse postgres DSN")
|
||||||
|
}
|
||||||
|
|
||||||
|
pgDsn.Path = database
|
||||||
|
if user != "" {
|
||||||
|
pgDsn.User = url.UserPassword(user, pass)
|
||||||
|
}
|
||||||
|
queryParams := pgDsn.Query()
|
||||||
|
|
||||||
|
// Build DSN based on the connection type (TCP vs. Unix socket)
|
||||||
|
if socket != "" {
|
||||||
|
// Unix socket connection via the host param
|
||||||
|
queryParams.Add("host", socket)
|
||||||
|
} else {
|
||||||
|
// TCP connection
|
||||||
|
if host == "" && port == 0 {
|
||||||
|
return "", errors.New("postgres: host and port are required for TCP connection")
|
||||||
|
}
|
||||||
|
if port > 0 {
|
||||||
|
pgDsn.Host = net.JoinHostPort(host, fmt.Sprintf("%d", port))
|
||||||
|
} else {
|
||||||
|
pgDsn.Host = database
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add SSL mode if provided
|
||||||
|
if sslMode != "" {
|
||||||
|
queryParams.Add("sslmode", sslMode)
|
||||||
|
}
|
||||||
|
|
||||||
|
pgDsn.RawQuery = queryParams.Encode()
|
||||||
|
|
||||||
|
// Add any extra parameters
|
||||||
|
if extraParams != "" {
|
||||||
|
values, err := url.ParseQuery(extraParams)
|
||||||
|
if err != nil {
|
||||||
|
return "", errors.Wrap(err, "could not parse extra params")
|
||||||
|
}
|
||||||
|
pgDsn.RawQuery = fmt.Sprintf("%s&%s", pgDsn.RawQuery, values.Encode())
|
||||||
|
}
|
||||||
|
|
||||||
|
return pgDsn.String(), nil
|
||||||
|
}
|
||||||
|
|
67
internal/database/postgres_test.go
Normal file
67
internal/database/postgres_test.go
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
package database
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPostgresDSN(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
host string
|
||||||
|
port int
|
||||||
|
user string
|
||||||
|
pass string
|
||||||
|
database string
|
||||||
|
socket string
|
||||||
|
sslMode string
|
||||||
|
extraParams string
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "default",
|
||||||
|
args: args{
|
||||||
|
host: "localhost",
|
||||||
|
port: 5432,
|
||||||
|
user: "postgres",
|
||||||
|
pass: "PASSWORD",
|
||||||
|
database: "postgres",
|
||||||
|
sslMode: "disable",
|
||||||
|
socket: "",
|
||||||
|
},
|
||||||
|
want: "postgres://postgres:PASSWORD@localhost:5432/postgres?sslmode=disable",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "default",
|
||||||
|
args: args{
|
||||||
|
host: "localhost",
|
||||||
|
port: 5432,
|
||||||
|
user: "postgres",
|
||||||
|
pass: "PASSWORD",
|
||||||
|
database: "postgres",
|
||||||
|
sslMode: "disable",
|
||||||
|
extraParams: "connect_timeout=10",
|
||||||
|
socket: "",
|
||||||
|
},
|
||||||
|
want: "postgres://postgres:PASSWORD@localhost:5432/postgres?sslmode=disable&connect_timeout=10",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "default",
|
||||||
|
args: args{
|
||||||
|
database: "postgres",
|
||||||
|
socket: "/path/to/socket",
|
||||||
|
},
|
||||||
|
want: "postgres://postgres?host=%2Fpath%2Fto%2Fsocket",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got, _ := PostgresDSN(tt.args.host, tt.args.port, tt.args.user, tt.args.pass, tt.args.database, tt.args.socket, tt.args.sslMode, tt.args.extraParams)
|
||||||
|
assert.Equalf(t, tt.want, got, "PostgresDSN(%v, %v, %v, %v, %v, %v, %v, %v)", tt.args.host, tt.args.port, tt.args.user, tt.args.pass, tt.args.database, tt.args.socket, tt.args.sslMode, tt.args.extraParams)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -18,6 +18,7 @@ type Config struct {
|
||||||
CustomDefinitions string `toml:"customDefinitions"`
|
CustomDefinitions string `toml:"customDefinitions"`
|
||||||
CheckForUpdates bool `toml:"checkForUpdates"`
|
CheckForUpdates bool `toml:"checkForUpdates"`
|
||||||
DatabaseType string `toml:"databaseType"`
|
DatabaseType string `toml:"databaseType"`
|
||||||
|
DatabaseDSN string `toml:"databaseDSN"`
|
||||||
DatabaseMaxBackups int `toml:"databaseMaxBackups"`
|
DatabaseMaxBackups int `toml:"databaseMaxBackups"`
|
||||||
PostgresHost string `toml:"postgresHost"`
|
PostgresHost string `toml:"postgresHost"`
|
||||||
PostgresPort int `toml:"postgresPort"`
|
PostgresPort int `toml:"postgresPort"`
|
||||||
|
@ -25,6 +26,7 @@ type Config struct {
|
||||||
PostgresUser string `toml:"postgresUser"`
|
PostgresUser string `toml:"postgresUser"`
|
||||||
PostgresPass string `toml:"postgresPass"`
|
PostgresPass string `toml:"postgresPass"`
|
||||||
PostgresSSLMode string `toml:"postgresSSLMode"`
|
PostgresSSLMode string `toml:"postgresSSLMode"`
|
||||||
|
PostgresSocket string `toml:"postgresSocket"`
|
||||||
PostgresExtraParams string `toml:"postgresExtraParams"`
|
PostgresExtraParams string `toml:"postgresExtraParams"`
|
||||||
ProfilingEnabled bool `toml:"profilingEnabled"`
|
ProfilingEnabled bool `toml:"profilingEnabled"`
|
||||||
ProfilingHost string `toml:"profilingHost"`
|
ProfilingHost string `toml:"profilingHost"`
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue