feat: native HTTPS support with bundled snakeoil default cert
server/index.js:
- Import http and https modules
- Resolve TLS_ENABLED early (before Helmet) so upgradeInsecureRequests
CSP directive fires when TLS is active directly (not only via proxy)
- loadTlsCredentials() reads TLS_CERT/TLS_KEY (defaulting to bundled
snakeoil) and returns null on failure (graceful HTTP fallback)
- Start https.createServer or http.createServer depending on credentials
- Startup banner now shows protocol, TLS cert path, and snakeoil warning
certs/:
- Add bundled snakeoil self-signed certificate (RSA 2048, 10yr, SAN for
localhost + 127.0.0.1) for out-of-the-box HTTPS without configuration
- .gitignore allows only snakeoil.{crt,key} — real certs must not be
committed
Dockerfile:
- COPY certs/ into image so snakeoil default is always available
- HEALTHCHECK updated to https:// with --no-check-certificate
docker-compose.yaml:
- Port now exposes HTTPS directly by default
- TLS_CERT/TLS_KEY/TLS_ENABLED/TRUST_PROXY documented with Option A/B
- cert volume mount examples added (commented out)
- healthcheck updated to https with --no-check-certificate
.env.sample:
- New TLS/HTTPS section with TLS_ENABLED, TLS_CERT, TLS_KEY
- openssl self-signed cert generation example included
docs/ARCHITECTURE.md:
- Configuration table: TLS_ENABLED, TLS_CERT, TLS_KEY env vars added
- Docker image section: TLS default behaviour documented
- Docker Compose example: Option A (direct TLS) / Option B (proxy) layout
- Security checklist: HTTPS now first item, updated for TLS modes
This commit is contained in:
18
.env.sample
18
.env.sample
@@ -19,6 +19,24 @@ LOG_LEVEL=info
|
||||
# Generate with: openssl rand -hex 32
|
||||
COOKIE_SECRET=your-cookie-secret-here
|
||||
|
||||
# =============================================================================
|
||||
# TLS / HTTPS
|
||||
# =============================================================================
|
||||
|
||||
# TLS is enabled by default using the bundled snakeoil self-signed certificate
|
||||
# (valid for localhost/127.0.0.1, 10-year expiry).
|
||||
# Set TLS_CERT and TLS_KEY to use your own certificate (recommended).
|
||||
# Set TLS_ENABLED=false to run in plain HTTP mode (e.g. behind a TLS proxy).
|
||||
#
|
||||
# To generate a self-signed cert for your own hostname:
|
||||
# openssl req -x509 -newkey rsa:2048 -keyout server.key -out server.crt \
|
||||
# -days 365 -nodes -subj "/CN=yourhostname" \
|
||||
# -addext "subjectAltName=DNS:yourhostname,IP:192.168.x.x"
|
||||
#
|
||||
# TLS_ENABLED=true
|
||||
# TLS_CERT=/path/to/server.crt
|
||||
# TLS_KEY=/path/to/server.key
|
||||
|
||||
# =============================================================================
|
||||
# REVERSE PROXY & DEPLOYMENT
|
||||
# =============================================================================
|
||||
|
||||
@@ -34,6 +34,9 @@ COPY --from=deps /app/node_modules ./node_modules
|
||||
COPY --chown=root:root server/ ./server/
|
||||
COPY --chown=root:root public/ ./public/
|
||||
COPY --chown=root:root package.json ./
|
||||
# Bundled snakeoil certificate for out-of-the-box TLS (self-signed, localhost only).
|
||||
# Mount your own cert/key over /app/certs/ or set TLS_CERT/TLS_KEY env vars.
|
||||
COPY --chown=root:root certs/ ./certs/
|
||||
|
||||
# Persistent data directory owned by node user (token store, logs)
|
||||
RUN mkdir -p /app/data && chown node:node /app/data
|
||||
@@ -47,7 +50,9 @@ USER node
|
||||
EXPOSE 3001
|
||||
|
||||
# HEALTHCHECK — Docker will restart the container if this fails 3 times
|
||||
# --no-check-certificate handles self-signed / snakeoil certs.
|
||||
# Remove that flag when using a CA-signed certificate.
|
||||
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
|
||||
CMD wget -qO- http://localhost:3001/health || exit 1
|
||||
CMD wget -qO- --no-check-certificate https://localhost:3001/health || exit 1
|
||||
|
||||
CMD ["node", "server/index.js"]
|
||||
|
||||
6
certs/.gitignore
vendored
Normal file
6
certs/.gitignore
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
# Ignore all cert/key files EXCEPT the bundled snakeoil development defaults.
|
||||
# Never commit real TLS certificates or private keys to version control.
|
||||
*
|
||||
!.gitignore
|
||||
!snakeoil.crt
|
||||
!snakeoil.key
|
||||
22
certs/snakeoil.crt
Normal file
22
certs/snakeoil.crt
Normal file
@@ -0,0 +1,22 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDoTCCAomgAwIBAgIUPgupZzf+zgMp4ylvf1NBRIWNpeUwDQYJKoZIhvcNAQEL
|
||||
BQAwUjELMAkGA1UEBhMCR0IxDjAMBgNVBAgMBUxvY2FsMQ4wDAYDVQQHDAVMb2Nh
|
||||
bDEPMA0GA1UECgwGc29mYXJyMRIwEAYDVQQDDAlsb2NhbGhvc3QwHhcNMjYwNTE3
|
||||
MDk0NDQ4WhcNMzYwNTE0MDk0NDQ4WjBSMQswCQYDVQQGEwJHQjEOMAwGA1UECAwF
|
||||
TG9jYWwxDjAMBgNVBAcMBUxvY2FsMQ8wDQYDVQQKDAZzb2ZhcnIxEjAQBgNVBAMM
|
||||
CWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL5Y05DF
|
||||
9U3k27SGByJqdbKThRRabX0hsK0zckkbXoaj0U4odZatUicAqkm6yWzpqQyZM6qH
|
||||
XX68LXqJk8r2VIdTTtKe5QU1WUAsW6KD0c514YC1VZ3a9ivQ2/tfdQsm8YxM/ECq
|
||||
e2XFklfvE72HkHF4fM9LCMS/LeazazF0ogNTkyE27g9Ry/ofR2P4MymLtyBbQ1NA
|
||||
B1zYRIhJ5HqFRMszBKhWi1zRgUQQNBuYA5wtxnXA9QNSz6ObtdWStfJ/C1Kuitpe
|
||||
OVrB/TKCp1OKBpZTd4mBKlvdJWos9eZPzP4vLnsReT4pHBx6J2Jd1dC97/MAXLYP
|
||||
mIXP+04zK5sWRUsCAwEAAaNvMG0wHQYDVR0OBBYEFCpYKb3zMgv4qThe8ukFrVIl
|
||||
lhD3MB8GA1UdIwQYMBaAFCpYKb3zMgv4qThe8ukFrVIllhD3MA8GA1UdEwEB/wQF
|
||||
MAMBAf8wGgYDVR0RBBMwEYcEfwAAAYIJbG9jYWxob3N0MA0GCSqGSIb3DQEBCwUA
|
||||
A4IBAQBQ5Jg0pn4XW/560laNl7XAoGuMwrIy5j6zDlIgFE8DWAb0NGNyu/FkCVIZ
|
||||
ruLNktq+w+kTeneAuYW3CGyac2HZo9T60VtQNL/k17hrDw1F6kMpAAYm6aiyFtE9
|
||||
Stw07PMvpvjxKvetPLOQsfk0/hh0Nh2PiVVdqvVE/gGoraboHEfH+eGf/Arzg7s4
|
||||
CzZTEz0OJP5i7VkZAvFygShPx/gHY77ojeHPl2LN3KI7s43TrjYZjxbUDwWDpGp0
|
||||
BIsDFP+9NAvA5I74biChfEEopmBczVbQRtqBxBX1JTa2aT5w/ifUNyKVKIGp49Q8
|
||||
o59gDmbCXhypom7OsyxBLZgyVWU1
|
||||
-----END CERTIFICATE-----
|
||||
28
certs/snakeoil.key
Normal file
28
certs/snakeoil.key
Normal file
@@ -0,0 +1,28 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC+WNOQxfVN5Nu0
|
||||
hgcianWyk4UUWm19IbCtM3JJG16Go9FOKHWWrVInAKpJusls6akMmTOqh11+vC16
|
||||
iZPK9lSHU07SnuUFNVlALFuig9HOdeGAtVWd2vYr0Nv7X3ULJvGMTPxAqntlxZJX
|
||||
7xO9h5BxeHzPSwjEvy3ms2sxdKIDU5MhNu4PUcv6H0dj+DMpi7cgW0NTQAdc2ESI
|
||||
SeR6hUTLMwSoVotc0YFEEDQbmAOcLcZ1wPUDUs+jm7XVkrXyfwtSroraXjlawf0y
|
||||
gqdTigaWU3eJgSpb3SVqLPXmT8z+Ly57EXk+KRwceidiXdXQve/zAFy2D5iFz/tO
|
||||
MyubFkVLAgMBAAECggEAEk0RCyNCJBSdqSKWLtFq8o0rdBsDpugyOymuOQHOjOxu
|
||||
oQo6NnWaNF5kgQVAyfd0EpGFfavyw2iNVaUPo1zoU9ogwuOWSnHOj64UIcp0+PN6
|
||||
VHknohWqdukBLyiGfnJM/0ieaKTbi2i7dGsfDA+rxbUoO6s2GYPC6O9inlUqVJGU
|
||||
fBXKTbCneW1+hJQFcOKPW2+qwiUxbudG9neNTYFOm9a7Bfa6MLJ22mkfYVrHYDzo
|
||||
gESo8sHXbEtvemka9jN0N/GoXgUi7iFXbqslPUoakCF7sgG0cXuUM9duSt67ynLj
|
||||
j7Ix+QiKAZgvSO/83b7vK74Xmd6XFUif41VMZ2iEGQKBgQDqp/ZSCAakarRpBRN4
|
||||
psKuhaYiBKurb5GezTOSfEGWxmng2s0bHcx74alKKNN8Tu2mNx6yafQdRNQdM+fG
|
||||
dn8JnQUGXYpGwQbLHZ/aO0M64WBxfQku4JNGh+VZ3ZyUC+So28vzCYqx8qwJi94L
|
||||
2sF8787hzHO1INzVaeXzQ89pEwKBgQDPqRzsJsP+zZmA4x16CtoWY7Z1d4z0GTkA
|
||||
erlXNinu76AIvra6aUld/65ymMyNIIpLZoci55ZGxBY7g8U29e10iT+xmxAgq3VT
|
||||
Tn438MbDXEgA+HvdRXCFPvNQQk0ZDegazdhTdtuhj+IHcm4M94zfVM3MSJOr0hJf
|
||||
JGaVIGEx6QKBgDZLftckPEU221+haQv1qf4vtm0Qn5gfTJZt7Izsa1CzwDPi7Kpl
|
||||
jrbrU/xwzd5pdNuMzXGCypUrI9lN9Ucai/JxfoQmiKQubZ/5zs7z/25UT7hysflC
|
||||
xVEAiLTubhhjWBkqIlqtzoW2HNBoqIwdpb9+zWO5ptw2KmLHCgnrmsY5AoGBAKOt
|
||||
YCaix4lm9L8qRGmVdCCBp6ce+/LKjqtaEAw1nQe/yBwcdlqn8jQs+4tH9LKoG1kj
|
||||
DxDsCP7uP7fZPPD9FpTsOU/8MNIPUwK+s63UElaZvgdF1BusR+w+mfmAyNQeqfu2
|
||||
k/P1k1fc2QOVpjiCRn8hkLSb4AlmIyTqxBB23SVBAoGATMtuk1r+SS9SDJW73CI1
|
||||
jLkJkPGrBvX0dFZGtedpwLVhUTDnqKIsy2F1iGDRGIAy5IAiLEFx44qQrhUau7zR
|
||||
/2avY0Ua9To9LW0k/matzJUKvXxYm59jh/GDp6LFU89hsL2zSd6z0Aknvh1SryCb
|
||||
OSbN8wfCz53+7qea4NQEB4E=
|
||||
-----END PRIVATE KEY-----
|
||||
@@ -4,14 +4,23 @@ services:
|
||||
container_name: sofarr
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "127.0.0.1:3001:3001" # bind to loopback only — expose via reverse proxy
|
||||
# Direct HTTPS (default — uses bundled snakeoil cert if TLS_CERT not set)
|
||||
- "3001:3001"
|
||||
# Uncomment the line below and comment out the above to bind to loopback
|
||||
# only when using a reverse proxy (set TLS_ENABLED=false in that case):
|
||||
# - "127.0.0.1:3001:3001"
|
||||
environment:
|
||||
- PORT=3001
|
||||
- NODE_ENV=production
|
||||
- LOG_LEVEL=info
|
||||
# Set to 1 when running behind a reverse proxy (Nginx, Caddy, Traefik)
|
||||
# so Express trusts X-Forwarded-For and X-Forwarded-Proto headers.
|
||||
- TRUST_PROXY=1
|
||||
# --- TLS ---
|
||||
# Default: TLS enabled using bundled snakeoil cert (self-signed).
|
||||
# Supply your own cert/key by mounting them and setting these paths:
|
||||
# - TLS_CERT=/app/certs/server.crt
|
||||
# - TLS_KEY=/app/certs/server.key
|
||||
# Set TLS_ENABLED=false if terminating TLS at a reverse proxy instead.
|
||||
# If using a reverse proxy, also set TRUST_PROXY=1 below.
|
||||
# - TRUST_PROXY=1
|
||||
# --- Replace placeholders with real values or use Docker secrets ---
|
||||
- COOKIE_SECRET=change-me-generate-with-openssl-rand-hex-32
|
||||
- EMBY_URL=https://emby.example.com
|
||||
@@ -21,8 +30,11 @@ services:
|
||||
- SABNZBD_INSTANCES=[{"name":"main","url":"https://sabnzbd.example.com","apiKey":"your-sabnzbd-api-key"}]
|
||||
- QBITTORRENT_INSTANCES=[{"name":"main","url":"https://qbittorrent.example.com","username":"admin","password":"your-password"}]
|
||||
volumes:
|
||||
# Persistent volume for SQLite token store and log file
|
||||
# Persistent volume for token store and log file
|
||||
- sofarr-data:/app/data
|
||||
# Mount your own TLS certificate and key (optional — snakeoil used if omitted)
|
||||
# - /path/to/your/server.crt:/app/certs/server.crt:ro
|
||||
# - /path/to/your/server.key:/app/certs/server.key:ro
|
||||
# Run as the built-in non-root 'node' user (UID/GID 1000)
|
||||
user: "1000:1000"
|
||||
# Read-only root filesystem; only the data volume is writable
|
||||
@@ -35,7 +47,9 @@ services:
|
||||
- ALL # drop all Linux capabilities
|
||||
cap_add: [] # add back none — Node.js needs no special caps
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "-qO-", "http://localhost:3001/health"]
|
||||
# Uses --no-check-certificate for self-signed / snakeoil certs.
|
||||
# Remove that flag if using a CA-signed certificate.
|
||||
test: ["CMD", "wget", "-qO-", "--no-check-certificate", "https://localhost:3001/health"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
|
||||
@@ -661,6 +661,9 @@ The status panel refreshes on a fixed 5-second timer and shows each SSE client w
|
||||
| `DATA_DIR` | No | `./data` | Directory for `tokens.json` and `server.log`. Must be writable by the server process. In Docker: `/app/data` (named volume). |
|
||||
| `COOKIE_SECRET` | No | — | If set, signs all session cookies with HMAC-SHA256. Strongly recommended in production. |
|
||||
| `TRUST_PROXY` | No | — | Express `trust proxy` setting. Set to `1` (or a hop count) when behind a reverse proxy (nginx, Traefik, etc.) so `req.ip` and `req.secure` are correct. |
|
||||
| `TLS_ENABLED` | No | `true` | Set to `false` to disable HTTPS and run plain HTTP (e.g. when TLS is terminated by a reverse proxy). |
|
||||
| `TLS_CERT` | No | `certs/snakeoil.crt` | Path to the TLS certificate file (PEM). Defaults to the bundled self-signed snakeoil certificate. |
|
||||
| `TLS_KEY` | No | `certs/snakeoil.key` | Path to the TLS private key file (PEM). Defaults to the bundled snakeoil key. |
|
||||
|
||||
#### Emby
|
||||
|
||||
@@ -726,6 +729,7 @@ The production image uses a two-stage build on `node:22-alpine`:
|
||||
Key environment variables set in the image:
|
||||
- `NODE_ENV=production` — enables production startup validation and logging
|
||||
- `DATA_DIR=/app/data` — token store and log file location
|
||||
- TLS is **enabled by default** using the bundled snakeoil self-signed certificate (`certs/snakeoil.crt`). Set `TLS_CERT`/`TLS_KEY` to your own certificate, or set `TLS_ENABLED=false` when terminating TLS at a reverse proxy.
|
||||
|
||||
### Docker Compose
|
||||
|
||||
@@ -736,12 +740,17 @@ services:
|
||||
container_name: sofarr
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "3001:3001"
|
||||
- "3001:3001" # HTTPS by default (snakeoil cert if no TLS_CERT set)
|
||||
environment:
|
||||
- NODE_ENV=production
|
||||
- DATA_DIR=/app/data
|
||||
- COOKIE_SECRET=change-me-to-a-long-random-string
|
||||
- TRUST_PROXY=1 # set if behind nginx/Traefik
|
||||
# Option A: direct TLS (default). Supply your own cert/key:
|
||||
# - TLS_CERT=/app/certs/server.crt
|
||||
# - TLS_KEY=/app/certs/server.key
|
||||
# Option B: behind a TLS-terminating reverse proxy:
|
||||
# - TLS_ENABLED=false
|
||||
# - TRUST_PROXY=1
|
||||
- EMBY_URL=https://emby.example.com
|
||||
- EMBY_API_KEY=your-emby-api-key
|
||||
- SONARR_INSTANCES=[{"name":"main","url":"...","apiKey":"..."}]
|
||||
@@ -751,7 +760,10 @@ services:
|
||||
- POLL_INTERVAL=5000
|
||||
- LOG_LEVEL=info
|
||||
volumes:
|
||||
- sofarr-data:/app/data # persists tokens.json and server.log
|
||||
- sofarr-data:/app/data
|
||||
# Uncomment to supply your own certificate (Option A):
|
||||
# - /path/to/server.crt:/app/certs/server.crt:ro
|
||||
# - /path/to/server.key:/app/certs/server.key:ro
|
||||
|
||||
volumes:
|
||||
sofarr-data:
|
||||
@@ -759,10 +771,10 @@ volumes:
|
||||
|
||||
### Security hardening checklist
|
||||
|
||||
- **Use HTTPS** — TLS is on by default (snakeoil cert). Supply `TLS_CERT`/`TLS_KEY` pointing to a CA-signed certificate for trusted HTTPS. Alternatively terminate TLS at a reverse proxy and set `TLS_ENABLED=false` + `TRUST_PROXY=1`.
|
||||
- **Set `COOKIE_SECRET`** — enables HMAC-signed cookies, preventing client-side forgery.
|
||||
- **Set `TRUST_PROXY=1`** when behind a reverse proxy — ensures `req.secure` is `true` so the `secure` cookie flag is enforced and HTTPS-upgrade CSP fires.
|
||||
- **Set `TRUST_PROXY=1`** only when a TLS-terminating reverse proxy sits in front — ensures `req.secure` is correct and the CSP `upgrade-insecure-requests` + `secure` cookie flag fire correctly.
|
||||
- **Mount a named volume** for `DATA_DIR` — token store and log file survive container recreates.
|
||||
- **Use HTTPS** — set `TRUST_PROXY=1` to enable the CSP `upgrade-insecure-requests` directive, the `secure` cookie flag, and HSTS (1-year `maxAge`).
|
||||
- **Rate limiting** — the login endpoint is limited to 10 failed attempts per IP per 15 minutes; all API endpoints share a 300 req/15 min window. Set `TRUST_PROXY` correctly so the client IP is not the proxy's IP.
|
||||
|
||||
### CI / CD
|
||||
|
||||
@@ -5,6 +5,8 @@ const helmet = require('helmet');
|
||||
const rateLimit = require('express-rate-limit');
|
||||
const crypto = require('crypto');
|
||||
const fs = require('fs');
|
||||
const http = require('http');
|
||||
const https = require('https');
|
||||
require('dotenv').config();
|
||||
|
||||
// Setup logging with levels
|
||||
@@ -99,6 +101,9 @@ if (!process.env.EMBY_URL && process.env.NODE_ENV === 'production') {
|
||||
const app = express();
|
||||
const PORT = process.env.PORT || 3001;
|
||||
|
||||
// Resolve TLS_ENABLED early — used in Helmet CSP and server startup
|
||||
const TLS_ENABLED = (process.env.TLS_ENABLED || 'true').toLowerCase() !== 'false';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Trust proxy — required when behind Nginx/Caddy/Traefik so that
|
||||
// req.ip reflects the real client IP (not 127.0.0.1) and
|
||||
@@ -137,7 +142,7 @@ app.use((req, res, next) => {
|
||||
baseUri: ["'self'"],
|
||||
frameAncestors: ["'none'"],
|
||||
formAction: ["'self'"],
|
||||
upgradeInsecureRequests: process.env.TRUST_PROXY ? [] : null
|
||||
upgradeInsecureRequests: (process.env.TRUST_PROXY || TLS_ENABLED) ? [] : null
|
||||
}
|
||||
},
|
||||
hsts: {
|
||||
@@ -258,10 +263,52 @@ app.use((err, req, res, next) => {
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
});
|
||||
|
||||
app.listen(PORT, () => {
|
||||
// ---------------------------------------------------------------------------
|
||||
// TLS / HTTPS support
|
||||
// Set TLS_CERT and TLS_KEY to paths of your certificate and private key.
|
||||
// If unset, defaults to the bundled snakeoil self-signed certificate
|
||||
// (localhost/127.0.0.1 only — suitable for local testing).
|
||||
// Set TLS_ENABLED=false to force plain HTTP even if cert files exist.
|
||||
// ---------------------------------------------------------------------------
|
||||
const CERTS_DIR = path.join(__dirname, '../certs');
|
||||
const TLS_CERT_PATH = process.env.TLS_CERT || path.join(CERTS_DIR, 'snakeoil.crt');
|
||||
const TLS_KEY_PATH = process.env.TLS_KEY || path.join(CERTS_DIR, 'snakeoil.key');
|
||||
|
||||
function loadTlsCredentials() {
|
||||
if (!TLS_ENABLED) return null;
|
||||
try {
|
||||
return {
|
||||
cert: fs.readFileSync(TLS_CERT_PATH),
|
||||
key: fs.readFileSync(TLS_KEY_PATH)
|
||||
};
|
||||
} catch (err) {
|
||||
console.warn(`[TLS] Could not load certificate files — falling back to HTTP. (${err.message})`);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
const tlsCredentials = loadTlsCredentials();
|
||||
const server = tlsCredentials
|
||||
? https.createServer(tlsCredentials, app)
|
||||
: http.createServer(app);
|
||||
|
||||
const protocol = tlsCredentials ? 'https' : 'http';
|
||||
const isSnakeoil = TLS_ENABLED &&
|
||||
(!process.env.TLS_CERT || process.env.TLS_CERT === TLS_CERT_PATH);
|
||||
|
||||
server.listen(PORT, () => {
|
||||
console.log(`=================================`);
|
||||
console.log(` sofarr - Your Downloads Dashboard`);
|
||||
console.log(` Server running on port ${PORT}`);
|
||||
console.log(` Server running on ${protocol}://localhost:${PORT}`);
|
||||
if (tlsCredentials && isSnakeoil) {
|
||||
console.warn(` [TLS] Using bundled snakeoil certificate (self-signed).`);
|
||||
console.warn(` [TLS] Set TLS_CERT and TLS_KEY for a trusted certificate.`);
|
||||
console.warn(` [TLS] Set TLS_ENABLED=false to disable TLS entirely.`);
|
||||
} else if (tlsCredentials) {
|
||||
console.log(` [TLS] Certificate: ${TLS_CERT_PATH}`);
|
||||
} else {
|
||||
console.warn(` [TLS] Running in plain HTTP mode — not suitable for production.`);
|
||||
}
|
||||
console.log(` Log level: ${process.env.LOG_LEVEL || 'info'}`);
|
||||
console.log(` Polling: ${POLLING_ENABLED ? POLL_INTERVAL + 'ms' : 'disabled (on-demand)'}`);
|
||||
console.log(` Trust proxy: ${process.env.TRUST_PROXY || 'disabled'}`);
|
||||
|
||||
Reference in New Issue
Block a user