diff --git a/ai/cmd/ai-app/main.go b/ai/cmd/ai-app/main.go index c039e182..362ae707 100644 --- a/ai/cmd/ai-app/main.go +++ b/ai/cmd/ai-app/main.go @@ -49,7 +49,7 @@ import ( const workerPollInterval = 2 * time.Second -const defaultListenAddr = "127.0.0.1:9600" +const defaultListenAddr = "127.0.0.1:8900" const defaultModel = "default" type config struct { diff --git a/ai/cmd/migrate/main.go b/ai/cmd/migrate/main.go new file mode 100644 index 00000000..6ec48371 --- /dev/null +++ b/ai/cmd/migrate/main.go @@ -0,0 +1,40 @@ +// Command migrate wendet die offenen Postgres-Migrationen des AI-Moduls an. +// Einmal gegen eine frische Datenbank ausfuehren, bevor ai-app startet. +package main + +import ( + "context" + "log" + "os" + + "github.com/jackc/pgx/v5/pgxpool" + + "gitea.perlbach24.de/scripte/nexarch/ai/internal/migrate" +) + +func main() { + dsn := os.Getenv("NEXARCH_AI_APP_TENANT_DSN") + if dsn == "" { + log.Fatal("migrate: NEXARCH_AI_APP_TENANT_DSN ist erforderlich") + } + dir := "migrations" + if len(os.Args) > 1 { + dir = os.Args[1] + } + ctx := context.Background() + pool, err := pgxpool.New(ctx, dsn) + if err != nil { + log.Fatalf("migrate: datenbankverbindung: %v", err) + } + defer pool.Close() + + migrations, err := migrate.Load(dir) + if err != nil { + log.Fatalf("migrate: migrationen laden: %v", err) + } + applied, err := migrate.Up(ctx, pool, migrations) + if err != nil { + log.Fatalf("migrate: migrationen anwenden: %v", err) + } + log.Printf("migrate: %d migration(en) angewendet", len(applied)) +} diff --git a/ai/docs/BETRIEB-UMGEBUNGSVARIABLEN.md b/ai/docs/BETRIEB-UMGEBUNGSVARIABLEN.md new file mode 100644 index 00000000..8fbf0a1a --- /dev/null +++ b/ai/docs/BETRIEB-UMGEBUNGSVARIABLEN.md @@ -0,0 +1,26 @@ +# Betrieb: ai-app + +Port-Bereich 8900 bis 8999 (`deploy/ports.tsv`), Routenpräfix `/api/ai/v1` (`deploy/routes.tsv`). Secrets gehören ausschließlich in die Umgebung (`/etc/nexarch/ai-app.env`, Rechte 600), nie in Code, Logs oder Dokumentation. + +## Einrichten + +1. Datenbank und Rolle anlegen (nicht Teil der Units). +2. `/etc/nexarch/ai-app.env` mit den Variablen unten anlegen. +3. Migrationen anwenden: `NEXARCH_AI_APP_TENANT_DSN=... ./migrate ai/migrations` (Fortschrittstabelle `ai_schema_migrations`). +4. Unit aus `deploy/systemd/nexarch-ai-app.service.tmpl` installieren (`__INSTALL_DIR__` ersetzen), `systemctl enable --now nexarch-ai-app`. +5. Health: `GET /healthz`. + +Eine Datenbank, die vor ENG-10 mit den AI-Migrationen angelegt wurde, führte den Fortschritt in `connect_schema_migrations`. Ist sie ausschließlich AI-Datenbank, vor dem ersten Lauf `ALTER TABLE connect_schema_migrations RENAME TO ai_schema_migrations;`. Bei gemeinsamer Datenbank mit Connect nicht umbenennen, sondern die AI-Einträge von Hand in die neue Tabelle übertragen. + +## Variablen + +| Variable | Standard | Wirkung | +|---|---|---| +| `NEXARCH_AI_APP_TENANT_DSN` | Pflicht | Datenbank des AI-Moduls | +| `NEXARCH_AI_APP_SERVICE_TOKEN` | Pflicht | Service-Token, das alle Aufrufer im Header `X-Service-Token` senden | +| `NEXARCH_AI_APP_LISTEN_ADDR` | `127.0.0.1:8900` | Lauschadresse | +| `NEXARCH_AI_APP_MODEL` | `default` | Modellname für Inferenzaufrufe | +| `NEXARCH_AI_APP_POLICY_URL` / `_POLICY_SERVICE_TOKEN` | Pflicht, oder Übergangsschalter | Rechteprüfung der semantischen Suche; Token Pflicht, sobald URL gesetzt | +| `NEXARCH_AI_APP_POLICY_DISABLED` | aus | `1` startet ohne Rechteprüfung (Übergang, Warnlog) | +| `NEXARCH_AI_APP_MODULE_BASE_URLS` | leer | `modul=url,modul2=url2`, Basis-URLs für den Rückschreib-Push | +| `NEXARCH_AI_APP_CORE_BASE_URL` / `_CORE_SERVICE_TOKEN` | leer | Core-Anbindung; ohne sie ist die Objekttyp-Registrierung (GOV-01) deaktiviert | diff --git a/ai/docs/ENG-10-PRUEFPROTOKOLL.md b/ai/docs/ENG-10-PRUEFPROTOKOLL.md new file mode 100644 index 00000000..03748698 --- /dev/null +++ b/ai/docs/ENG-10-PRUEFPROTOKOLL.md @@ -0,0 +1,40 @@ +# ENG-10 – ai-app: Deployment-Anbindung – Prüfprotokoll + +Datum: 2026-11-06. Host: Entwicklungshost 192.168.1.204 (Wegwerf-Rolle/DB `eng10_test`, nach Abschluss entfernt). + +## Befund (Ausgangslage) + +- `ai-app` lauschte standardmäßig auf `127.0.0.1:9600`, außerhalb des für `ai` reservierten Bereichs 8900 bis 8999 (`deploy/ports.tsv`). 9600 belegt außerdem `workflow-app` (`docs/BETRIEB-PORTAL.md`). `scripts/check-port-defaults.sh` erkennt nur `":NNNN"`-Literale und meldete den Verstoß nicht. +- Kein systemd-Template für `ai-app` (Connect und DMS haben eines). +- Kein Migrationswerkzeug für das AI-Modul; `ai/internal/migrate` führte den Fortschritt in der Tabelle `connect_schema_migrations` (Kopierfehler aus dem Connect-Modul). + +## Umsetzung + +- Standard-Lauschadresse `127.0.0.1:8900`. +- `ai/cmd/migrate` (Muster `connect/cmd/migrate`, DSN aus `NEXARCH_AI_APP_TENANT_DSN`, Verzeichnis als Argument). +- Fortschrittstabelle `ai_schema_migrations`. +- `deploy/systemd/nexarch-ai-app.service.tmpl` nach dem Muster von `nexarch-connect-app` (gleiche Härtung). +- `ai/docs/BETRIEB-UMGEBUNGSVARIABLEN.md` mit Einrichtungsschritten und Variablen. + +## Akzeptanzkriterien + +| # | Kriterium | Ergebnis | +|---|---|---| +| 1 | Standard-Port liegt im Bereich von `ai` | **Erfüllt.** 8900. `scripts/check-port-defaults.sh` meldet für `ai/cmd/ai-app` nichts (dort erkennt es allerdings das Format `127.0.0.1:NNNN` ohnehin nicht, siehe Einschränkungen). | +| 2 | systemd-Template vorhanden und gehärtet | **Erfüllt.** `scripts/check-systemd-hardening.sh`: ok, 18 Dienste geprüft. Start der Unit selbst nicht getestet. | +| 3 | Migrationen anwendbar, idempotent | **Erfüllt.** Frische DB: 16 Migrationen angewendet, zweiter Lauf 0; ohne DSN klare Fehlermeldung, Abbruch. | +| 4 | Eigene Fortschrittstabelle | **Erfüllt.** `ai_schema_migrations` existiert, `connect_schema_migrations` nicht (`TestUp_UsesOwnTrackingTable`, echte DB). | + +## Prüfungen vor der Abnahme + +| # | Prüfung | Ergebnis | +|---|---|---| +| 1 | Migrationswerkzeug gegen frische Wegwerf-DB | **Bestanden** (siehe AK3). | +| 2 | Bestehende Modultests | **Bestanden.** `./internal/migrate/`, `./cmd/ai-app/` grün, `go build ./...` und `go vet` sauber. | +| 3 | Unit startet `ai-app` real | **Offen.** Template nicht auf einem Host installiert, `systemctl` nicht ausgeführt. | + +## Einschränkungen + +- Umbenennung der Fortschrittstabelle betrifft bestehende AI-Datenbanken; Hinweis in `BETRIEB-UMGEBUNGSVARIABLEN.md`. Eine automatische Übernahme gibt es nicht. +- Nicht Teil der Kachel: Aufnahme in `deploy/rollout-131-*.sh` (Host-131-Rollout), Provisionierung von DB, Rolle und Secrets. +- Weitere Port-Verstöße außerhalb von `ai`, die beim Prüfen auffielen und nicht behoben wurden: `workflow-app` und `tasks-a11y-fixture` auf 9600 (Bereich 8800 bis 8899), Connect-Fixtures doppelt auf 9500, mehrere Fixtures und `verapdf-sidecar` außerhalb ihrer Bereiche. `check-port-defaults.sh` erkennt `127.0.0.1:NNNN`-Literale nicht; eine Erweiterung würde diese Verstöße sichtbar machen und braucht eine eigene Entscheidung. diff --git a/ai/internal/migrate/migrate.go b/ai/internal/migrate/migrate.go index f21ebbec..e0008c5a 100644 --- a/ai/internal/migrate/migrate.go +++ b/ai/internal/migrate/migrate.go @@ -12,7 +12,7 @@ import ( type Migration = cmigrate.Migration -const TrackingTable = "connect_schema_migrations" +const TrackingTable = "ai_schema_migrations" var migrator = cmigrate.MustNew(TrackingTable) diff --git a/ai/internal/migrate/migrate_test.go b/ai/internal/migrate/migrate_test.go new file mode 100644 index 00000000..3b2b8003 --- /dev/null +++ b/ai/internal/migrate/migrate_test.go @@ -0,0 +1,41 @@ +package migrate + +import ( + "context" + "os" + "path/filepath" + "testing" + + "gitea.perlbach24.de/scripte/nexarch/common/testdb" +) + +func TestUp_UsesOwnTrackingTable(t *testing.T) { + dsn := os.Getenv("TEST_TENANT_DSN") + if dsn == "" { + t.Skip("TEST_TENANT_DSN nicht gesetzt, Integrationstest uebersprungen") + } + pool := testdb.Pool(t, dsn) + dir, err := filepath.Abs("../../migrations") + if err != nil { + t.Fatal(err) + } + migrations, err := Load(dir) + if err != nil { + t.Fatal(err) + } + ctx := context.Background() + if _, err := Up(ctx, pool, migrations); err != nil { + t.Fatal(err) + } + again, err := Up(ctx, pool, migrations) + if err != nil || len(again) != 0 { + t.Fatalf("zweiter lauf soll nichts anwenden: %v %v", again, err) + } + var own, foreign bool + if err := pool.QueryRow(ctx, `SELECT to_regclass('ai_schema_migrations') IS NOT NULL, to_regclass('connect_schema_migrations') IS NOT NULL`).Scan(&own, &foreign); err != nil { + t.Fatal(err) + } + if !own || foreign { + t.Fatalf("fortschrittstabelle: ai=%v connect=%v", own, foreign) + } +} diff --git a/deploy/systemd/nexarch-ai-app.service.tmpl b/deploy/systemd/nexarch-ai-app.service.tmpl new file mode 100644 index 00000000..f05660bc --- /dev/null +++ b/deploy/systemd/nexarch-ai-app.service.tmpl @@ -0,0 +1,23 @@ +[Unit] +Description=NEXARCH AI - ai-app (ENG-10) +After=network.target postgresql.service +Wants=postgresql.service + +[Service] +Type=simple +User=nexarch +Group=nexarch +EnvironmentFile=/etc/nexarch/ai-app.env +ExecStart=__INSTALL_DIR__/bin/ai-app +Restart=on-failure +RestartSec=3 +WorkingDirectory=__INSTALL_DIR__ +# QA-15 F-13/OPS-08: reiner Netzwerkdienst (HTTP + Postgres-DSN aus +# EnvironmentFile), keine eigene Schreibarbeit im Dateisystem. +NoNewPrivileges=true +ProtectSystem=strict +ProtectHome=true +PrivateTmp=true + +[Install] +WantedBy=multi-user.target