Files
ApiCore/deploy/k8s/scripts/ensure-secrets.sh
T
Nick SzaunigandClaude Opus 5.5 9795f98006 feat: Schlüssel und gemeinsamer Kern für Sprachnotizen (T7 L2 Tasks 5+6)
Task 5: Schlüsselbund-Zweck voice (VOICE_ENCRYPTION_KEY), AES-256-GCM-
Hilfen für Text und Bytes mit AAD capture:<id>:<art>, voice-config
(Modell, Tageslimit, Aufnahme, Whisper-Grösse) und anthropic-config
(nie geloggt, nur worker/all), Startprüfung verlangt den Schlüssel in
Produktion in jeder Rolle, rekey versiegelt offene Sprachnotizen neu
(Audio einzeln gelesen, CAS mit Status), Secret-Vorlagen und
ensure-secrets.sh ergänzt.

Task 6: Text-Normalisierung und Bereinigung (Steuer- und Formatzeichen,
Codepoint-Kürzung), Zitatprüfung mit Dezimalzahlen als ein Wort
(Ruling 7), Felderliste passend zu CreateInspectionDto, versiegeltes
Transkript mit Grenzen, Fachwörter und Skalen mit Paritätstests.

Rollout: Ein Image mit diesem Stand startet in Produktion nur mit
VOICE_ENCRYPTION_KEY in apicore-backend-secret (ARCHITECTURE Annahme 22).

Gates: Backend tsc, eslint, 922 Unit-Tests, e2e 100 Suites / 1004 Tests.

Refs #27 #28

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-10-16 09:41:47 +02:00

100 lines
3.8 KiB
Bash

#!/usr/bin/env bash
# Ergaenzt fehlende Schluessel in den ApiCore-Secrets, ohne vorhandene Werte
# zu aendern. Gibt nur Schluesselnamen aus, nie Werte. Mehrfach ausfuehrbar.
#
# bash deploy/k8s/scripts/ensure-secrets.sh # Namespace apicore
# bash deploy/k8s/scripts/ensure-secrets.sh --dry-run # nur anzeigen
#
# Werte:
# - MFA_/WEBHOOK_/BACKUP_/VOICE_ENCRYPTION_KEY: zufaellig (48 Byte, base64url). Die
# App verlangt mindestens 32 Zeichen; "dummy" wuerde den Start abbrechen.
# Diese Schluessel nie wieder aendern ohne *_PREVIOUS (sonst sind MFA-
# Geheimnisse, Webhook-Secrets, Backups und offene Sprachnotizen nicht mehr
# lesbar). Sichern!
# VOICE_ENCRYPTION_KEY muss vor dem Rollout des T7-Backend-Images existieren:
# die App verlangt ihn in Produktion in jeder Rolle und startet sonst nicht.
# - OPEN_METEO_API_KEY: leer = freie Open-Meteo-API (ein Fantasiewert wuerde
# die kostenpflichtige API mit falschem Schluessel ansprechen).
# - TELEGRAM_BOT_TOKEN (Secret apicore-telegram, nur Worker): "dummy". Die App
# erkennt ein ungueltiges Format und schaltet Telegram ab, bis das echte
# Token von BotFather eingetragen ist.
set -euo pipefail
NS="${NAMESPACE:-apicore}"
DRY_RUN=false
[[ "${1:-}" == "--dry-run" ]] && DRY_RUN=true
# Secret values never appear on a command line or in the environment (both
# are visible to other processes): node writes the patch straight into a
# private temp file, kubectl reads it with --patch-file.
umask 077
PATCH_FILE="$(mktemp)"
trap 'rm -f "$PATCH_FILE"' EXIT
# Windows tools (node, kubectl) in Git Bash need the native path.
PATCH_FILE_NATIVE="$(cygpath -w "$PATCH_FILE" 2>/dev/null || echo "$PATCH_FILE")"
existing_keys() {
kubectl get secret "$1" -n "$NS" -o jsonpath='{.data}' 2>/dev/null \
| node -e "let s='';process.stdin.on('data',d=>s+=d).on('end',()=>{try{console.log(Object.keys(JSON.parse(s||'{}')).join('\n'))}catch{}})"
}
# ensure_keys <secret> <key> <random|empty|dummy> [<key> <mode> ...]
ensure_keys() {
local secret="$1"; shift
local have
have="$(existing_keys "$secret")"
local spec=() added=()
while (($#)); do
local key="$1" mode="$2"; shift 2
if grep -qx "$key" <<<"$have"; then
continue
fi
spec+=("$key=$mode")
added+=("$key")
done
if ((${#added[@]} == 0)); then
echo "$secret: alle Schluessel vorhanden"
return
fi
if $DRY_RUN; then
echo "$secret: wuerde ergaenzen: ${added[*]}"
return
fi
# Only key names and modes go on the command line; values are created here.
PATCH_FILE_PATH="$PATCH_FILE_NATIVE" node -e "
const crypto = require('crypto');
const value = (mode) =>
mode === 'random' ? crypto.randomBytes(48).toString('base64url')
: mode === 'dummy' ? 'dummy'
: '';
const stringData = {};
for (const entry of process.argv.slice(1)) {
const [key, mode] = entry.split('=');
stringData[key] = value(mode);
}
require('fs').writeFileSync(process.env.PATCH_FILE_PATH, JSON.stringify({ stringData }), { mode: 0o600 });
" "${spec[@]}"
if ! kubectl get secret "$secret" -n "$NS" >/dev/null 2>&1; then
kubectl create secret generic "$secret" -n "$NS" >/dev/null
fi
kubectl patch secret "$secret" -n "$NS" --type merge --patch-file "$PATCH_FILE_NATIVE" >/dev/null
: >"$PATCH_FILE"
echo "$secret: ergaenzt: ${added[*]}"
}
ensure_keys apicore-backend-secret \
MFA_ENCRYPTION_KEY random \
WEBHOOK_ENCRYPTION_KEY random \
BACKUP_ENCRYPTION_KEY random \
VOICE_ENCRYPTION_KEY random \
OPEN_METEO_API_KEY empty
ensure_keys apicore-telegram \
TELEGRAM_BOT_TOKEN dummy
# apicore-anthropic (ANTHROPIC_API_KEY, nur Worker, optional) legt der Inhaber
# selbst an; dieses Skript erzeugt es nicht. Ohne Schluessel bleibt die
# Auswertung der Sprachnotizen aus (EXTRACT_DISABLED), alles andere laeuft.
echo "Fertig. Laufende Pods lesen neue Werte erst nach einem Neustart."