feat: add encryption key migration script and document it in README

Add server/scripts/migrate-encryption.ts — a standalone script that
re-encrypts all at-rest secrets (OIDC client secret, SMTP password,
Maps/OpenWeather/Immich API keys, MFA secrets) when rotating
ENCRYPTION_KEY, without requiring the app to be running.

- Prompts for old and new keys interactively; input is never echoed,
  handles copy-pasted keys correctly via a shared readline interface
  with a line queue to prevent race conditions on piped/pasted input
- Creates a timestamped DB backup before any changes
- Idempotent: detects already-migrated values by trying the new key
- Exits non-zero and retains the backup if any field fails

README updates:
- Add .env setup step (openssl rand -hex 32) before the Docker Compose
  snippet so ENCRYPTION_KEY is set before first start
- Add ENCRYPTION_KEY to the docker run one-liner
- Add "Rotating the Encryption Key" section documenting the script,
  the docker exec command, and the upgrade path via ./data/.jwt_secret

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
jubnl
2026-04-01 09:35:32 +02:00
parent 19350fbc3e
commit 4d596f2ff9
2 changed files with 321 additions and 2 deletions

View File

@@ -98,7 +98,9 @@
## Quick Start
```bash
docker run -d -p 3000:3000 -v ./data:/app/data -v ./uploads:/app/uploads mauriceboe/trek
ENCRYPTION_KEY=$(openssl rand -hex 32) docker run -d -p 3000:3000 \
-e ENCRYPTION_KEY=$ENCRYPTION_KEY \
-v ./data:/app/data -v ./uploads:/app/uploads mauriceboe/trek
```
The app runs on port `3000`. The first user to register becomes the admin.
@@ -115,6 +117,13 @@ TREK works as a Progressive Web App — no App Store needed:
<details>
<summary>Docker Compose (recommended for production)</summary>
First, create a `.env` file next to your `docker-compose.yml`:
```bash
# Generate a random encryption key (required)
echo "ENCRYPTION_KEY=$(openssl rand -hex 32)" >> .env
```
```yaml
services:
app:
@@ -136,7 +145,7 @@ services:
environment:
- NODE_ENV=production
- PORT=3000
- ENCRYPTION_KEY=${ENCRYPTION_KEY} # Required. Generate with: openssl rand -hex 32. Upgrading? Set this to the contents of ./data/.jwt_secret to keep existing encrypted secrets readable.
- ENCRYPTION_KEY=${ENCRYPTION_KEY} # Required — see .env setup above
- ALLOWED_ORIGINS=${ALLOWED_ORIGINS:-} # Comma-separated origins for CORS and email notification links
- TZ=${TZ:-UTC} # Timezone for logs, reminders and scheduled tasks (e.g. Europe/Berlin)
- LOG_LEVEL=${LOG_LEVEL:-info} # info = concise user actions; debug = verbose admin-level details
@@ -179,6 +188,18 @@ docker run -d --name trek -p 3000:3000 -v ./data:/app/data -v ./uploads:/app/upl
Your data is persisted in the mounted `data` and `uploads` volumes — updates never touch your existing data.
### Rotating the Encryption Key
If you need to rotate `ENCRYPTION_KEY` (e.g. you are upgrading from a version that derived encryption from `JWT_SECRET`), use the migration script to re-encrypt all stored secrets under the new key without starting the app:
```bash
docker exec -it trek node --import tsx scripts/migrate-encryption.ts
```
The script will prompt for your old and new keys interactively (input is not echoed). It creates a timestamped database backup before making any changes and exits with a non-zero code if anything fails.
**Upgrading from a previous version?** Your old JWT secret is in `./data/.jwt_secret`. Use its contents as the "old key" and your new `ENCRYPTION_KEY` value as the "new key".
### Reverse Proxy (recommended)
For production, put TREK behind a reverse proxy with HTTPS (e.g. Nginx, Caddy, Traefik).