Merge feature/ops-fixes: DataProtection-Key-Persistenz (AR-3) + prod-compose AI-Env (AR-4)
- AR-3: AddDataProtection().PersistKeysToFileSystem + compose keys-Volume → Gmail-App-Passwort ueberlebt Redeploy (war ephemer → Inbox waere still gebrochen). Dev-Fallback .data-protection-keys. - AR-4: prod compose.yaml ANTHROPIC_API_KEY → AI__BaseUrl/ApiKey/Model + .env.example aktualisiert. 139/139, has-pending=No, kein Schema-Change. [god-QA: config/Program.cs only, disjunkt] Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
9
.gitignore
vendored
9
.gitignore
vendored
@@ -131,6 +131,9 @@ $RECYCLE.BIN/
|
||||
# MemPalace per-project files (issue #185)
|
||||
mempalace.yaml
|
||||
entities.json
|
||||
|
||||
# Runtime photo store (uploaded/imported gerbil photos) — never commit
|
||||
GerbilManagerWebAPI/photo-storage/
|
||||
|
||||
# Runtime photo store (uploaded/imported gerbil photos) — never commit
|
||||
GerbilManagerWebAPI/photo-storage/
|
||||
|
||||
# AR-3: Data Protection key ring (dev-only ephemeral keys) — never commit
|
||||
GerbilManagerWebAPI/.data-protection-keys/
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.Text.Json.Serialization;
|
||||
using GerbilManagerWebAPI.Endpoints;
|
||||
using Microsoft.AspNetCore.DataProtection;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Scalar.AspNetCore;
|
||||
|
||||
@@ -46,7 +47,17 @@ builder.Services.AddHttpClient<GerbilManagerWebAPI.Inbox.DraftReplyService>(
|
||||
http => http.Timeout = TimeSpan.FromSeconds(60));
|
||||
|
||||
// INBOX-0: Gmail inbox. App Password encrypted at rest via Data Protection.
|
||||
builder.Services.AddDataProtection();
|
||||
// AR-3: persist the key ring so encrypted passwords survive image redeployments.
|
||||
// In prod the path is mounted to a persistent volume (compose DataProtection__KeyRingPath).
|
||||
// In dev (Aspire) keys live in the content root — ephemeral, which is fine there.
|
||||
{
|
||||
var keyRingPath = builder.Configuration["DataProtection:KeyRingPath"]
|
||||
?? Path.Combine(builder.Environment.ContentRootPath, ".data-protection-keys");
|
||||
Directory.CreateDirectory(keyRingPath);
|
||||
builder.Services.AddDataProtection()
|
||||
.PersistKeysToFileSystem(new DirectoryInfo(keyRingPath))
|
||||
.SetApplicationName("GerbilManager");
|
||||
}
|
||||
builder.Services.AddScoped<GerbilManagerWebAPI.Inbox.MailSettingsService>();
|
||||
builder.Services.AddScoped<GerbilManagerWebAPI.Inbox.IGmailMailReader, GerbilManagerWebAPI.Inbox.GmailMailReader>();
|
||||
builder.Services.AddScoped<GerbilManagerWebAPI.Inbox.RequestSyncService>();
|
||||
|
||||
@@ -15,9 +15,15 @@ TAG=latest
|
||||
PGDATA_PATH=/mnt/SSD/gerbil/pgdata
|
||||
PHOTOS_PATH=/mnt/SSD/gerbil/photos
|
||||
BACKUPS_PATH=/mnt/SSD/gerbil/backups
|
||||
# AR-3: Data Protection Key-Ring (Gmail-App-Passwort-Verschlüsselung)
|
||||
KEYS_PATH=/mnt/SSD/gerbil/keys
|
||||
|
||||
# Backup-Rotation: Anzahl Tage (Standard: 7)
|
||||
BACKUP_KEEP_DAYS=7
|
||||
|
||||
# Claude-API-Key fuer KI-Verkaufstext (FEAT-12a; leer lassen wenn nicht vorhanden)
|
||||
ANTHROPIC_API_KEY=
|
||||
# KI-Funktionen (Verkaufstext + Posteingang-Entwurf)
|
||||
# Beliebiger OpenAI-kompatibler Anbieter — Optionen in docs/ai-provider.md
|
||||
# Leer lassen = KI deaktiviert (kein Fehler, nur 503 AiKeyMissing)
|
||||
AI__BaseUrl=
|
||||
AI__ApiKey=
|
||||
AI__Model=gemini-2.0-flash
|
||||
|
||||
@@ -40,10 +40,17 @@ services:
|
||||
ConnectionStrings__gerbilmanager: "Host=db;Port=5432;Database=gerbilmanager;Username=postgres;Password=${POSTGRES_PASSWORD}"
|
||||
# Speicherort der hochgeladenen Fotos (NAS-Dataset gemounted unter /data/photos)
|
||||
Photos__RootPath: /data/photos
|
||||
# KI-Verkaufstext (FEAT-12a stub; leer lassen wenn kein Key vorhanden)
|
||||
ANTHROPIC_API_KEY: "${ANTHROPIC_API_KEY:-}"
|
||||
# AR-3: Data Protection Key-Ring (persistiert Gmail-App-Passwort-Verschlüsselung über Redeployments)
|
||||
DataProtection__KeyRingPath: /data/keys
|
||||
# AR-4: KI-Funktionen (Verkaufstext + Posteingang-Entwurf, Sektion AI; beliebiger OpenAI-kompatibler Anbieter)
|
||||
# Anbieter-Optionen und Schlüssel-Beispiele: docs/ai-provider.md
|
||||
# Leer lassen = KI deaktiviert (503 AiKeyMissing statt Fehler)
|
||||
AI__BaseUrl: "${AI__BaseUrl:-}"
|
||||
AI__ApiKey: "${AI__ApiKey:-}"
|
||||
AI__Model: "${AI__Model:-gemini-2.0-flash}"
|
||||
volumes:
|
||||
- photos:/data/photos
|
||||
- keys:/data/keys
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
@@ -101,6 +108,14 @@ volumes:
|
||||
type: none
|
||||
o: bind
|
||||
device: "${PHOTOS_PATH:-/mnt/gerbil/photos}"
|
||||
# AR-3: Data Protection key ring — persistiert Gmail-App-Passwort-Verschlüsselung.
|
||||
# Muss ein persistentes NAS-Dataset sein (nicht dasselbe wie photos).
|
||||
keys:
|
||||
driver: local
|
||||
driver_opts:
|
||||
type: none
|
||||
o: bind
|
||||
device: "${KEYS_PATH:-/mnt/gerbil/keys}"
|
||||
backups:
|
||||
driver: local
|
||||
driver_opts:
|
||||
|
||||
Reference in New Issue
Block a user