homelab-codex-ws/docs/sessions/2026-06-24-kb-gmail-importer.md
oskar 7a2d7bdee3 feat(kb): frontmatter OKF dla 39 session logow
Session logi zostaja w docs/sessions/ (decyzja z etapu 1). Dodany wylacznie
blok frontmattera: type: session-log, visibility: private, status: active,
updated = data ostatniego commita pliku.

Tresc nietknieta — kazdy plik to +9/-0 linii.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-04 16:19:30 +02:00

5.9 KiB

okf type visibility status updated links
0.1 session-log private active 2026-06-24

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/<name>/

Jednorazowe i periodyczne joby (nie serwisy) żyją w jobs/<name>/.

Katalog Co tu trafia
packages/<lib>/ reużywalne biblioteki Python (nie deployowane samodzielnie)
services/<svc>/ długo żyjące serwisy Docker
jobs/<job>/ 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[]:

{
  "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

# 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:<pw>@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:<pw>@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/ | wcSELECT 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>  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