From 60d48a43b480ffa8b297e969ce40e9f62c664d98 Mon Sep 17 00:00:00 2001 From: orfelorfel23 Date: Thu, 1 Oct 2026 19:57:17 +0200 Subject: [PATCH] refactor: Single source of truth for API keys via services/shared.env MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- .gitignore | 1 + README.md | 15 +++++++++--- deploy_script.sh | 33 +++++++++++++++++++------- services/hermes/.env.age | Bin 349 -> 0 bytes services/hermes/docker-compose.yml | 4 ++-- services/paperclip/.env.age | Bin 801 -> 408 bytes services/paperclip/docker-compose.yml | 10 +++----- services/shared.env.age | Bin 0 -> 636 bytes 8 files changed, 43 insertions(+), 20 deletions(-) delete mode 100644 services/hermes/.env.age create mode 100644 services/shared.env.age diff --git a/.gitignore b/.gitignore index 6538287..d17e492 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ # Environment files (contain secrets) **/.env +services/shared.env # AGE private key (never commit) **/age-key.txt diff --git a/README.md b/README.md index 12e2a64..249f1fa 100644 --- a/README.md +++ b/README.md @@ -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//.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 -o services/paperclip/.env.age services/paperclip/.env +# Geteilte API Keys (alle Services) +age -r -o services/shared.env.age services/shared.env + +# Service-spezifisch (z. B. PaperClip) +age -r -o services/paperclip/.env.age services/paperclip/.env ``` ## LLM Konfiguration & Onboarding Workaround diff --git a/deploy_script.sh b/deploy_script.sh index dc89f25..b333536 100644 --- a/deploy_script.sh +++ b/deploy_script.sh @@ -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 diff --git a/services/hermes/.env.age b/services/hermes/.env.age deleted file mode 100644 index fe5691bec6445ab6753d1030548fcf079f13a9ae..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 349 zcmV-j0iyn4XJsvAZewzJaCB*JZZ2XnJ~KV@fhXT6%RgWmjZD zXJuzKIZbs=PBl_5OKV3^HZLz?cXtXcEiE8eYh-##RbxeKMmcI^Hf%Obcr#5kcu`7m zLv1ric6Mn*XK^?$T2V_;L^ujcGu|#3(AV~&g;pwnn{4P>Y2#XbL`}-?iS;4j091am z8iMJ6=XL>LT$vk!h2zi&YHF$my{I5EG2XsQ{L8sw7H?eEqdRSdOt;m7JULEH z=eKG3Uzao@FyYG*NhfS}WL?}mCUf%jR~WD5=HPEHfm_s)3b6aM-+WcyT#0Oie3EV+t)T zEg)7(W?@1@ZelP(L3L3wo1W;~4LBt(=xKs8C*^2a3CT`}I ztU)DX(J5pbYkGiXc=cWyZU*9;uq&~LBCMC&KRbHcL0x005)N3KDWb}JJjqWVIX}G# z-33mS#BY<~DehPXphq4C%LRDMvqv~pop0jQhneP&5yzn-a9MnXekK1Tn2-lGYjPrM fe}B7QbzH)R@oULt(xjNZ!sPn5!1rn~@5FMl-d3YK delta 782 zcmV+p1M&Qr1EB_xAb)9Xa8Yt&cW5+cG*EU~dRQxFF=Sauc~NXaV?$yvPfIs6Ffdtj zD?(9jV+vI;SZ8%`dSNj(VJmD+R6=cLN@{g8LPa)bd2%^OD@9djSx;v-FLioQQ3@?B zEg*R@D{xp+VKqTSdN^2GQ)@9nZZbtNbx}}jN>giDWl&~UcYjJoM@lePWl;)>*PmL1 zi9-5IU?~*IIi?u@5WLa5xJbp+uU0ZQg4>9me@ev<&0i-@w&G#nX06;xy${KcFc!n~?ke{Cl za=0oL>i$Oj7JpVk^CNckr+H}?`ezK(-SqE03g5k;ZXhWTYB4}=+2THWPlMJtdVG0s zS>ioRrekD%pI26P;f;@O>-msdr+~~-dDYN;(HQ{jJopWcp%a;QAJBvoQM9bzQEU6S zQN^nw6y|OynsO;F;Nxm~oyF;biq}$9hI#79hxb5H;(ug-K+l1>kyq0;hZG^lV!`=T z3)Y1-CZpY17hjy*Mf!#W39S|0ew_BBo80*o9_5u)onhRX>xZDg8)f_X;U)Vu>+lpO zux_c(b^-b8xN#yb(=5ovP+O%9%fr>_@~dCkhOL>6e*AlEtP0$XAs~xde0t!J6;0j$ z1OWZu5PzCJSGYC@zoZ-!9fJ4v@uhM$VxMW}gK4wEIzA5cYsO(egKIqtgVX=u!UZ=e zo*qxL(%=Hv%z9$87+RUUD_hG`1GG9LjtZkT?jzEanoX3*aS;a=u?}T%E-srpJu#cc z@}HmSdwdxucGtrui>{dAdq}py5#-MOE6S}k)PGkRAQ(6m6D<2IY_1Gpz+^i6l^(Nor1XZZ|J0Y)CIX7aC9F&C3WCK>)L^ci zF6vRWH*s*EvLr=;8XI*MZIj9Z>8fJQIB*RTiE-)hvL!7HhvM(W5R-CFnD3+JFRb>% zyxw$UJ!L8eSp*2ecF$w<9T6O41H;3vQ4boR&M$j{Lb@Qj|PxBWyI$ z$tgIENG}o}HhL!*e|Fd)Y+*_6YnvQ>;DF8N0FV}W^qVni>1N4x=|bUT?}n@g=|!0_ zte=nbq1slcibMa$0_V13H5&gi24&zvL7d6u%ggj*()bvb%g^W+)_ zr^%IW)pFsjNGXgNdf;MTo0>+UgxRP>_Di9ftc|k&FN;>)>*r$TqDKUUP1Sn`0ezun zr()9jGT5ZZ;59sqh?98WjF)Dc&+`EEU_4hIsve~5hXMB+%czN*{3rlmq%2!?Y Wi1hm$FHzG}*@;c(!)PVe446=e;3bLx literal 0 HcmV?d00001