diff --git a/docs/kb/kb-00-overview.md b/docs/kb/kb-00-overview.md index f2b5705..bcf4af5 100644 --- a/docs/kb/kb-00-overview.md +++ b/docs/kb/kb-00-overview.md @@ -103,24 +103,35 @@ Hardening homelabu / stabilizacja control-plane — osobny wątek. --- -## Konwencja packages/ +## Konwencje katalogów Python -Reużywalne biblioteki Python (nie deploy-osobnych serwisów) żyją w `packages//`. -Instalacja w Dockerfile serwisu/joba: `pip install /repo/packages//`. -Biblioteki nie mają `docker-compose.yml` ani `service.yaml` — to nie są serwisy. +| Katalog | Co tu trafia | +|---------|-------------| +| `packages//` | Reużywalne biblioteki (nie deployowane samodzielnie). Instalacja: `pip install /repo/packages//`. Nie mają `docker-compose.yml` ani `service.yaml`. | +| `services//` | Długo żyjące serwisy Docker z `docker-compose.yml` + `service.yaml`. | +| `jobs//` | Jednorazowe i periodyczne joby CLI — bez Dockera, odpalane bezpośrednio na węźle (`pip install -e`). | Pierwsza biblioteka: `packages/kb-mail/` — model koperty, helpery DB (asyncpg), helper archiwum. +Pierwszy job: `jobs/gmail-bulk-import/` — jednorazowy bulk importer Gmail Takeout. --- -## Następny krok +## Stan etapów ~~Etap 1 maili: zamroź kopertę + postaw Postgres+pgvector + szkielet repo.~~ **ZROBIONE** (2026-06-17). -- ✅ `services/kb-postgres` — pgvector/pgvector:pg16 na PIHA (:5433, always-on), `init/001_envelope.sql` +- ✅ `services/kb-postgres` — pgvector/pgvector:pg16 na **PIHA** (:5433, always-on), `init/001_envelope.sql` - ✅ Zamrożona koperta — tabela `envelope` + `@dataclass Envelope` (tz-aware) - ✅ `packages/kb-mail` — `envelope` / `db` (asyncpg) / `archive` (append-only .eml) -- ✅ 15 testów unit + 5 integration (mark `integration`, wymaga KB_TEST_DSN) +- ✅ 15 testów unit + 5 integration (mark `integration`, wymaga `KB_TEST_DSN`) -**Etap 2: jednorazowy bulk importer Gmail** (mbox/Takeout → archiwum). +~~Etap 2: jednorazowy bulk importer Gmail (mbox/Takeout → archiwum).~~ **KOD GOTOWY** (2026-06-24) — nie uruchomiony. + +- ✅ `jobs/gmail-bulk-import/` — CLI importer bez Dockera (pip install -e na PIHA) +- ✅ entities[]: manifest załączników (filename, content_type, size, sha256) od dnia zero +- ✅ batch inserty, idempotentny, --limit, epoch_fallback, statystyki załączników +- ✅ 24 testy jednostkowe, wszystkie zielone +- ⏳ Google Takeout ~27 GB mbox — do transferu SOLARIA→PIHA + uruchomienia + +**Następny krok**: transfer Takeout SOLARIA→PIHA (rsync Tailscale) → dry-run → próbka --limit 200 → pełny wlew `ionice -c 3 nice -n 19`. Szczegóły kolejności: `kb-01-email-design.md` §8. diff --git a/docs/kb/kb-01-email-design.md b/docs/kb/kb-01-email-design.md index b3a1d1b..39fd6b1 100644 --- a/docs/kb/kb-01-email-design.md +++ b/docs/kb/kb-01-email-design.md @@ -79,8 +79,8 @@ Załączniki: **II tura** (MVP = czysty tekst + nagłówki). ## 8. Kolejność budowy (w obrębie projektu) -1. Zamroź kopertę + postaw Postgres+pgvector. -2. **Bulk Gmail historyczny → archiwum** *(pierwsze, niezależne)*. +1. ✅ Zamroź kopertę + postaw Postgres+pgvector. *(2026-06-17)* +2. ✅ **Bulk Gmail historyczny → archiwum** — `jobs/gmail-bulk-import/` — **KOD GOTOWY** *(2026-06-24)*; nie uruchomiony (Takeout ~27 GB na SOLARIA, do transferu na PIHA). 3. Fastmail JMAP live ingest → archiwum. 4. Gmail IMAP live sync → archiwum. 5. Filtr archiwum→indeks. diff --git a/docs/sessions/2026-06-24-kb-gmail-importer.md b/docs/sessions/2026-06-24-kb-gmail-importer.md new file mode 100644 index 0000000..559db26 --- /dev/null +++ b/docs/sessions/2026-06-24-kb-gmail-importer.md @@ -0,0 +1,168 @@ +# Sesja 2026-06-24 — KB etap 2: importer Gmail (kod gotowy) + +## Cel + +Zbudowanie jednorazowego bulk importera Gmail Takeout (`mbox → archiwum .eml + envelope DB`). +Import nie uruchomiony w tej sesji — Takeout na SOLARIA, transfer+wlew = następny krok. + +--- + +## Stan infrastruktury po sesji + +### kb-postgres na PIHA (korekta z etapu 1) + +Baza **przeniesiona na PIHA**, nie na SOLARIA jak zakładał pierwotny plan. +- `Memory: hard=1g` zaaplikowany po reboocie (wymagał `cgroup_enable=memory` w cmdline + `docker-compose up --force-recreate`). +- Status: `healthy`, port `5433`. + +### Google Takeout (Gmail „All Mail") + +- Pobrany: **~15 GB zip**, po rozpakowaniu **~27 GB mbox**, jeden plik. +- Lokalizacja: `SOLARIA ~/Downloads` — jeszcze nie rozpakowany, nie zaimportowany. + +--- + +## Etap 2 — ZBUDOWANY (kod gotowy) + +### Nowa konwencja: `jobs//` + +Jednorazowe i periodyczne joby (nie serwisy) żyją w `jobs//`. + +| Katalog | Co tu trafia | +|---------|-------------| +| `packages//` | reużywalne biblioteki Python (nie deployowane samodzielnie) | +| `services//` | długo żyjące serwisy Docker | +| `jobs//` | one-shot i periodyczne joby CLI | + +Instalacja lokalnie: `pip install -e packages/kb-mail/ && pip install -e jobs/gmail-bulk-import/` +Bez Dockera — odpalany bezpośrednio na PIHA pod `nice`/`ionice`. + +### jobs/gmail-bulk-import + +Wejście: plik `.mbox` z Google Takeout. +Wyjście: `.eml` w archiwum (append-only, PIHA NVMe) + wiersze `envelope` w kb-postgres. + +Kluczowe decyzje implementacyjne: + +| Kwestia | Decyzja | +|---------|---------| +| ID wiadomości | `Message-ID` header (stripped `<>`); fallback: `sha256-<32hex>` treści | +| Timestamp | `Date` header → UTC; fallback epoch 1970-01-01 + licznik `epoch_fallback` | +| Źródło | `source=gmail` | +| Idempotencja | `FileExistsError` z archiwum → skip; `ON CONFLICT DO NOTHING` w DB | +| Wznawialność | mbox iterowany od początku; już zarchiwizowane = skip; already in DB = noop | +| Batch inserty | `executemany` co 500 wpisów (+ flush na końcu); `_eml_ref` rekonstruuje `raw_ref` dla skipped | +| Docker | **brak** — CLI bez konteneryzacji | + +### Załączniki → entities[] + +W tym etapie bajty załączników **zostają w .eml** — nie są ekstrahowane ani OCR-owane. + +Importer zapisuje **manifest** do `Envelope.entities[]`: + +```json +{ + "type": "attachment", + "filename": "faktura.pdf", + "content_type": "application/pdf", + "size": 42387, + "sha256": "a3f4..." +} +``` + +Powiązanie mail↔załącznik w bazie od dnia zero. +Ekstrakcja/OCR = **faza 2** (możliwe przekierowanie faktur/umów do Paperless, filar dokumentów). + +### CLI + +```bash +# Dry run — tylko liczenie i parsowanie, bez zapisów: +gmail-bulk-import --mbox ~/takeout/allmail.mbox --archive /data/kb/archive --dry-run + +# Próbka 200 wiadomości (przed pełnym wlewem): +gmail-bulk-import --mbox ~/takeout/allmail.mbox --archive /data/kb/archive \ + --dsn postgresql://kb:@localhost:5433/kb --limit 200 + +# Pełny wlew na PIHA pod nice/ionice: +ionice -c 3 nice -n 19 gmail-bulk-import \ + --mbox ~/takeout/allmail.mbox \ + --archive /data/kb/archive \ + --dsn postgresql://kb:@localhost:5433/kb +``` + +### Statystyki zwracane przez importer + +``` +processed, imported, skipped, errors, +epoch_fallback, ← maile bez parsowalnej daty +msgs_with_attachments, ← sizing fazy 2 +total_attachments, +total_attachment_bytes +``` + +### Testy + +- 24 testy jednostkowe (bez DB, bez sieci) — wszystkie zielone. +- Pokrycie: `_message_id`, `_parse_date`, `_parse_attachments`, `run_import` + (dry-run, archiwum, idempotencja, --limit, epoch_fallback, statystyki załączników, batch). + +--- + +## GOTCHA tej sesji + +Pierwsza iteracja importera (commit 57a27af) celowała w `solaria:5433` zamiast PIHA, +pominęła `entities[]` załączników, `--limit`, batch inserty i `epoch_fallback`. +Złapane w review, poprawione w osobnym commicie. + +**Lekcja**: prompt musi explicite podać host bazy = PIHA (nie SOLARIA). + +--- + +## Następny krok + +1. `rsync` Takeout SOLARIA → PIHA po Tailscale (27 GB mbox na NVMe PIHA). +2. `gmail-bulk-import ... --dry-run` — weryfikacja liczby wiadomości. +3. `gmail-bulk-import ... --limit 200` — próbka, weryfikacja jakości. +4. Pełny wlew pod `ionice -c 3 nice -n 19`. +5. Weryfikacja: `ls archive/gmail/ | wc` ≈ `SELECT count(*) FROM envelope WHERE source='gmail'` ≈ liczba wiadomości w mboxie. + +--- + +## Backlog (osobne zadania) + +- `packages/kb-mail/tests/test_db.py`: `KB_TEST_DSN` defaultuje do `localhost:5433/kb` — poprawne dla PIHA, ale warto sprawdzić wszystkie occurrences `solaria:5433` w testach. +- Etap 3: Fastmail JMAP live ingest → `jobs/fastmail-poller/`. +- Etap 4: Gmail IMAP live sync → `jobs/gmail-imap-poller/`. +- Zapis deklaratywny `cgroup_enable=memory + swapaccount=1` dla PIHA (firmware/cmdline — poza GitOps, udokumentować w `hosts/piha/host.yaml` lub README). + +--- + +## Higiena git + +- Praca w worktree `task/kb-gmail-import`; master deploy-only, czysty przez cały czas. +- 4 commity na branchu po zamknięciu sesji. + +--- + +## Commits + +``` +57a27af feat(kb-mail): etap 2 — jednorazowy bulk importer Gmail (mbox → archiwum) +f0e4d90 refactor(kb-mail): importer Gmail — entities załączników, --limit, batch, bez Dockera, DSN→PIHA +c5dd8f3 fix(piha): capabilities — realny RAM/NVMe + gitignore build dirs + docs(kb): sesja 2026-06-24 — importer Gmail gotowy + konwencja jobs/ +``` + +## Files changed (etap 2) + +``` +jobs/gmail-bulk-import/src/gmail_bulk_import/__init__.py +jobs/gmail-bulk-import/src/gmail_bulk_import/importer.py +jobs/gmail-bulk-import/pyproject.toml +jobs/gmail-bulk-import/tests/test_importer.py +hosts/piha/capabilities.yaml +.gitignore +docs/kb/kb-00-overview.md (etap 2 gotowy, konwencja jobs/) +docs/kb/kb-01-email-design.md (§8 krok 2 = kod gotowy) +docs/sessions/2026-06-24-kb-gmail-importer.md +```