refactor: Single source of truth for API keys via services/shared.env

- NEW: services/shared.env.age — encrypted shared API keys (OpenRouter, Mistral, Minimax, Poe)
- REMOVE: services/hermes/.env.age — no service-specific secrets needed
- UPDATE: services/paperclip/.env.age — now only DB password + auth secret
- UPDATE: docker-compose.yml (paperclip + hermes) — use env_file for shared.env
- UPDATE: deploy_script.sh — decrypt shared.env.age first, read API keys from there
- UPDATE: .gitignore — add services/shared.env
- UPDATE: README.md — document shared.env pattern
This commit is contained in:
Jannik committed 2026-10-01 19:57:17 +02:00
1 parent 2df8b29470
commit 60d48a43b4
8 files changed
+43 -20

No files matched your search

+1
View File
@@ -1,5 +1,6 @@
# Environment files (contain secrets)
**/.env
services/shared.env
# AGE private key (never commit)
**/age-key.txt
+12 -3
View File
@@ -34,12 +34,21 @@ Das Skript ist idempotent und kümmert sich um:
## Umgebungsvariablen hinzufügen oder ändern
Die Umgebungsvariablen liegen lokal unter `services/paperclip/.env`. Da dieses File per `.gitignore` ausgeschlossen ist (aus Sicherheitsgründen), wird stattdessen `.env.age` in das Repository gepusht.
Es gibt zwei Arten von `.env`-Dateien:
Sobald du Änderungen an deiner lokalen `.env` machst, musst du diese neu verschlüsseln, bevor du den Commit erstellst:
| Datei | Inhalt | Gilt für |
|---|---|---|
| `services/shared.env(.age)` | **API Keys** (OpenRouter, Mistral, etc.) | **Alle** Services |
| `services/<name>/.env(.age)` | Service-spezifische Secrets (DB-Passwort, Auth-Secret) | Nur dieser Service |
Da `.env`-Dateien per `.gitignore` ausgeschlossen sind, wird stattdessen jeweils die `.age`-Variante ins Repository gepusht. Nach Änderungen neu verschlüsseln:
```bash
age -r <DEIN_AGE_PUBLIC_KEY> -o services/paperclip/.env.age services/paperclip/.env
# Geteilte API Keys (alle Services)
age -r <AGE_PUBLIC_KEY> -o services/shared.env.age services/shared.env
# Service-spezifisch (z. B. PaperClip)
age -r <AGE_PUBLIC_KEY> -o services/paperclip/.env.age services/paperclip/.env
```
## LLM Konfiguration & Onboarding Workaround
+25 -8
View File
@@ -320,32 +320,49 @@ stop_all_services() {
# 7. Secrets & Data
###############################################################################
decrypt_envs() {
log "Decrypting .env.age files..."
# -------------------------------------------------------------------------
# 1. Geteilte API Keys entschlüsseln (Single Source of Truth)
# -------------------------------------------------------------------------
local shared_env="${SERVICES_DIR}/shared.env"
local shared_env_age="${SERVICES_DIR}/shared.env.age"
if [[ -f "${shared_env_age}" ]]; then
log "Decrypting shared API keys (shared.env.age)..."
age --decrypt -i "${AGE_PRIVATE_KEY}" -o "${shared_env}" "${shared_env_age}"
sed -i 's/\r$//' "${shared_env}"
chmod 600 "${shared_env}"
else
warn "shared.env.age not found — no shared API keys loaded."
fi
# -------------------------------------------------------------------------
# 2. Service-spezifische .env.age Dateien entschlüsseln
# -------------------------------------------------------------------------
log "Decrypting service-specific .env.age files..."
for svc_name in "${ACTIVE_SERVICES[@]}"; do
local svc_dir="${SERVICES_DIR}/${svc_name}"
if [[ -f "${svc_dir}/.env.age" ]]; then
age --decrypt -i "${AGE_PRIVATE_KEY}" -o "${svc_dir}/.env" "${svc_dir}/.env.age"
sed -i 's/\r$//' "${svc_dir}/.env"
# Ensure BETTER_AUTH_SECRET is set for Paperclip
if [[ "${svc_name}" == "paperclip" ]]; then
if ! grep -q "BETTER_AUTH_SECRET" "${svc_dir}/.env"; then
log "Generating missing BETTER_AUTH_SECRET in .env..."
echo "BETTER_AUTH_SECRET=\"$(openssl rand -hex 32)\"" >> "${svc_dir}/.env"
fi
# Auto-configure default LLM to bypass UI Onboarding
# Auto-configure default LLM (OPENAI_* Overrides) aus shared.env lesen
if ! grep -q "OPENAI_API_KEY=" "${svc_dir}/.env" && ! grep -q "ANTHROPIC_API_KEY=" "${svc_dir}/.env"; then
if grep -q "OPENROUTER_API_KEY=" "${svc_dir}/.env"; then
if [[ -f "${shared_env}" ]] && grep -q "OPENROUTER_API_KEY=" "${shared_env}"; then
log "Auto-configuring OpenRouter as default LLM..."
echo "" >> "${svc_dir}/.env"
echo "OPENAI_BASE_URL=https://openrouter.ai/api/v1" >> "${svc_dir}/.env"
grep "OPENROUTER_API_KEY=" "${svc_dir}/.env" | sed 's/OPENROUTER_API_KEY=/OPENAI_API_KEY=/' >> "${svc_dir}/.env"
elif grep -q "MISTRAL_API_KEY=" "${svc_dir}/.env"; then
grep "OPENROUTER_API_KEY=" "${shared_env}" | sed 's/OPENROUTER_API_KEY=/OPENAI_API_KEY=/' >> "${svc_dir}/.env"
elif [[ -f "${shared_env}" ]] && grep -q "MISTRAL_API_KEY=" "${shared_env}"; then
log "Auto-configuring Mistral as default LLM..."
echo "" >> "${svc_dir}/.env"
echo "OPENAI_BASE_URL=https://api.mistral.ai/v1" >> "${svc_dir}/.env"
grep "MISTRAL_API_KEY=" "${svc_dir}/.env" | sed 's/MISTRAL_API_KEY=/OPENAI_API_KEY=/' >> "${svc_dir}/.env"
grep "MISTRAL_API_KEY=" "${shared_env}" | sed 's/MISTRAL_API_KEY=/OPENAI_API_KEY=/' >> "${svc_dir}/.env"
fi
fi
fi
Binary file not shown.
+2 -2
View File
@@ -2,8 +2,8 @@ services:
hermes:
image: nousresearch/hermes-agent:latest
container_name: hermes
environment:
OPENROUTER_API_KEY: "${OPENROUTER_API_KEY}"
env_file:
- ../shared.env # Geteilte API Keys (OpenRouter, Mistral, etc.)
volumes:
- /opt/PaperClip-Data/hermes:/opt/data
networks:
Binary file not shown.
+3 -7
View File
@@ -21,6 +21,9 @@ services:
image: ghcr.io/paperclipai/paperclip:latest
container_name: paperclip_app
pids_limit: 2048
env_file:
- ../shared.env # Geteilte API Keys (OpenRouter, Mistral, etc.)
- .env # Service-spezifisch (DB-Passwort, Auth-Secret, OPENAI_* Overrides)
environment:
DATABASE_URL: postgres://paperclip:${POSTGRES_PASSWORD}@paperclip-postgres:5432/paperclip
PORT: "3100"
@@ -31,13 +34,6 @@ services:
PAPERCLIP_DEPLOYMENT_EXPOSURE: "public"
PAPERCLIP_AUTH_BASE_URL_MODE: "explicit"
PAPERCLIP_PUBLIC_URL: "https://paperclip.orfel.de"
BETTER_AUTH_SECRET: "${BETTER_AUTH_SECRET}"
OPENAI_API_KEY: "${OPENAI_API_KEY}"
OPENAI_BASE_URL: "${OPENAI_BASE_URL}"
OPENROUTER_API_KEY: "${OPENROUTER_API_KEY}"
MISTRAL_API_KEY: "${MISTRAL_API_KEY}"
MINIMAX_API_KEY: "${MINIMAX_API_KEY}"
POE_API_KEY: "${POE_API_KEY}"
PAPERCLIP_OPENCODE_PROVIDERS: '{"mistral":{"npm":"@ai-sdk/openai-compatible","options":{"apiKey":"{env:MISTRAL_API_KEY}"},"models":{"mistral/mistral-large":{}}},"minimax":{"npm":"@ai-sdk/openai-compatible","options":{"apiKey":"{env:MINIMAX_API_KEY}"},"models":{"minimax/abab6.5s":{}}},"poe":{"npm":"@ai-sdk/openai-compatible","options":{"apiKey":"{env:POE_API_KEY}"},"models":{"poe/claude-3-opus":{}}}}'
volumes:
- /opt/PaperClip-Data/paperclip:/paperclip
Binary file not shown.