Some checks failed
Build and Push Docker Image / build (push) Successful in 59s
CI / Security audit (push) Successful in 1m5s
CI / Tests & coverage (push) Successful in 1m24s
Docs Check / Markdown lint (push) Failing after 45s
Docs Check / Mermaid diagram parse check (push) Successful in 1m27s
CI / Security audit (pull_request) Successful in 51s
CI / Tests & coverage (pull_request) Successful in 1m1s
Docs Check / Markdown lint (pull_request) Failing after 39s
Docs Check / Mermaid diagram parse check (pull_request) Successful in 1m12s
Phase 1 - Licensing & Compliance: - Add MIT LICENSE file - Add copyright headers to server/index.js, poller.js, config.js, sanitizeError.js, and new loadSecrets.js Phase 2 - Security Hardening: - Add server/utils/loadSecrets.js: Docker secrets support via _FILE env var pattern (COOKIE_SECRET_FILE, EMBY_API_KEY_FILE, etc.) - Add SSRF/URL validation in config.js: validates all configured service instance URLs for scheme and well-formedness at startup - Add SIGTERM/SIGINT graceful shutdown: stops poller, drains HTTP connections, 10s force-exit fallback - Warn at startup if COOKIE_SECRET is shorter than 32 characters - Validate EMBY_URL scheme at startup - Improve sanitizeError: redact host:port from axios error URLs while preserving path/query for other redaction patterns Phase 3 - Config Robustness: - Weak COOKIE_SECRET warning (< 32 chars) - EMBY_URL validated via validateInstanceUrl on startup Phase 4 - Docker & Deployment: - .dockerignore: add tests/, coverage/, vitest.config.js, CHANGELOG.md, SECURITY.md, LICENSE, .markdownlint.json - docker-compose.yaml: add commented Option B (Docker secrets _FILE pattern) alongside existing plain-env Option A Phase 5 - Docs & Release Readiness: - Add CHANGELOG.md with entries from v1.0.0 to v1.2.0 - Update SECURITY.md: supported versions table, fix Docker secrets note to reflect _FILE support now implemented - Add public/.well-known/security.txt for responsible disclosure - Bump version to 1.2.0
79 lines
3.8 KiB
YAML
79 lines
3.8 KiB
YAML
services:
|
|
sofarr:
|
|
image: docker.i3omb.com/sofarr:latest
|
|
container_name: sofarr
|
|
restart: unless-stopped
|
|
ports:
|
|
# 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
|
|
# --- 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
|
|
# --- Secrets: use _FILE variants (Docker secrets) in production -------
|
|
# Option A — plain environment variables (simple, less secure):
|
|
- COOKIE_SECRET=change-me-generate-with-openssl-rand-hex-32
|
|
- EMBY_URL=https://emby.example.com
|
|
- EMBY_API_KEY=your-emby-api-key
|
|
- SONARR_INSTANCES=[{"name":"main","url":"https://sonarr.example.com","apiKey":"your-sonarr-api-key"}]
|
|
- RADARR_INSTANCES=[{"name":"main","url":"https://radarr.example.com","apiKey":"your-radarr-api-key"}]
|
|
- 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"}]
|
|
# Option B — Docker secrets (_FILE pattern, recommended for production):
|
|
# Uncomment the lines below and comment out Option A above.
|
|
# Create secret files with: echo -n "value" > ./secrets/cookie_secret.txt
|
|
# - COOKIE_SECRET_FILE=/run/secrets/cookie_secret
|
|
# - EMBY_API_KEY_FILE=/run/secrets/emby_api_key
|
|
# - SONARR_API_KEY_FILE=/run/secrets/sonarr_api_key # legacy single-instance only
|
|
# - RADARR_API_KEY_FILE=/run/secrets/radarr_api_key # legacy single-instance only
|
|
# - SABNZBD_API_KEY_FILE=/run/secrets/sabnzbd_api_key # legacy single-instance only
|
|
# secrets: # uncomment when using Option B
|
|
# - cookie_secret
|
|
# - emby_api_key
|
|
volumes:
|
|
# 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
|
|
read_only: true
|
|
tmpfs:
|
|
- /tmp # Node.js needs a writable /tmp
|
|
security_opt:
|
|
- no-new-privileges:true # prevent privilege escalation via setuid binaries
|
|
cap_drop:
|
|
- ALL # drop all Linux capabilities
|
|
cap_add: [] # add back none — Node.js needs no special caps
|
|
healthcheck:
|
|
# Respects TLS_ENABLED: uses http when set to false, https otherwise.
|
|
# --no-check-certificate handles self-signed / snakeoil certs.
|
|
test: ["CMD", "/bin/sh", "-c", "[ \"${TLS_ENABLED:-true}\" = \"false\" ] && wget -qO- http://localhost:${PORT:-3001}/health || wget -qO- --no-check-certificate https://localhost:${PORT:-3001}/health"]
|
|
interval: 30s
|
|
timeout: 5s
|
|
retries: 3
|
|
start_period: 10s
|
|
|
|
volumes:
|
|
sofarr-data:
|
|
|
|
# Docker secrets definitions (uncomment and populate when using Option B above)
|
|
# secrets:
|
|
# cookie_secret:
|
|
# file: ./secrets/cookie_secret.txt
|
|
# emby_api_key:
|
|
# file: ./secrets/emby_api_key.txt
|