# python-runner — Docker kontejner na Tower ## Základní info | Parametr | Hodnota | |----------------|----------------------------------------------| | Název | python-runner | | Image | python-runner (vlastní) | | Status | running (unless-stopped) | | Python | 3.12.13 | | Spouštěcí cmd | `tail -f /dev/null` — container jen běží, skripty se spouštějí ručně | | Working dir | `/scripts` | | Vytvořen | 2026-06-02 | --- ## Tower — SSH přístup | Parametr | Hodnota | |----------|------------------| | Host | tower / 192.168.1.76 | | Port | 22 | | User | root | | Heslo | 7309208104 | **Připojení přes Python (paramiko)** — Docker CLI není lokálně dostupný: ```python import paramiko c = paramiko.SSHClient() c.set_missing_host_key_policy(paramiko.AutoAddPolicy()) c.connect('192.168.1.76', username='root', password='7309208104') _, out, _ = c.exec_command('...') print(out.read().decode()) c.close() ``` --- ## Volume mounty | Host (Unraid) | Kontejner | Popis | |-----------------------|-------------------|----------------------------------| | `/mnt/user/Scripts` | `/scripts` | Skripty, logy — working dir | | `/mnt/user/Emails` | `/mnt/Emails` | Stažené přílohy `/Attachments/` | > Skripty čtou emaily **přímo přes Microsoft Graph API** — lokální `.msg` soubory už nejsou potřeba. --- ## Pipeline — 5 skriptů v pořadí spouštění Prefix `1_` … `5_` indikuje pořadí v pipeline. Bezpečné opakovat každý krok (idempotentní upserty). | # | Skript | Účel | Zdroj → Cíl | |---|---|---|---| | 1 | `1_parse_emails_graph_v1.4.py` | Import emailů z Graph API | Graph → Mongo `emaily.` | | 2 | `2_refetch_text_bodies_v1.0.py` | ONETIME oprava starých plain-text emailů (v1.3 ukládal jen 2000 znaků do `body_preview`) | Graph → Mongo `body_text` | | 3 | `3_download_attachments_v1.3.py` | Stažení binárek příloh + SHA256 dedup | Graph → `/mnt/Emails//Attachments/` + Mongo `attachments_index` | | 4 | `4_unwrap_smime_v1.0.py` | Rozbalení S/MIME wrapper (`smime.p7m`) na vnitřní MIME tělo | Graph → Mongo `smime_body_text/html`, `smime_inner_attachments` | | 5 | `5_enrich_fulltext_emails_v1.2.py`| Plný text emailů do PG fulltext indexu | Mongo → PG `MongoEmaily.emails` | Doplňkové soubory v `/scripts/`: | Soubor | Popis | |---|---| | `python_runner.md` | Tato dokumentace | | `*.log` | Výstupy běhů (`parse_emails.log`, `download_attachments.log`, `unwrap_smime.log`, `refetch.log`) | | `*_errors.log` | Chyby konkrétních zpráv/příloh | | `Trash/` | Staré verze skriptů | > **POZOR:** všechny skripty pouze **čtou** ze schránky — žádný zápis do schránky. --- ## Microsoft Graph API — konfigurace (sdílená všemi skripty) | Parametr | Hodnota | |-----------------|----------------------------------------| | Graph URL | `https://graph.microsoft.com/v1.0` | | Tenant ID | `7d269944-37a4-43a1-8140-c7517dc426e9` | | Client ID | `4b222bfd-78c9-4239-a53f-43006b3ed07f` | | Auth | client credentials (msal) | | MongoDB | Hodnota | |-----------------|----------------------------------------| | URI | `mongodb://192.168.1.76:27017` | | DB | `emaily` | | Kolekce emailů | `` (např. `ordinace@buzalkova.cz`) | | Index příloh | `attachments_index` | | PostgreSQL | Hodnota | |-----------------|----------------------------------------| | Host | `192.168.1.76` | | DB | `MongoEmaily` | | Tabulka | `emails` (GIN tsvector, config `soubory`) | --- ## 1) `1_parse_emails_graph_v1.4.py` — import emailů → MongoDB Čte **všechny složky** schránky rekurzivně (Inbox, Sent, Deleted, archivy …) přes Graph API a importuje každou zprávu jako dokument do MongoDB. `_id` = Internet Message-ID (fallback `graphid:`). Upsert → bezpečné přerušit a opakovat. Z každé zprávy extrahuje: předmět, odesílatel, příjemci To/CC/BCC, časy (UTC), HTML tělo (max 2 MB) + text preview, **plné plain-text tělo (`body_text`, max 2 MB)**, přílohy (metadata + `graph_att_id`), internet headers (SPF/DKIM/Received/X-*), MAPI-ekvivalenty (důležitost, příznak, konverzační vlákno, kategorie, In-Reply-To, References), `isRead`, `isDraft`, `folder_path`, `inferenceClassification`. ```bash # První import (vše): docker exec -it python-runner python /scripts/1_parse_emails_graph_v1.4.py --mailbox ordinace@buzalkova.cz # Test na 50 zprávách bez indexů: docker exec -it python-runner python /scripts/1_parse_emails_graph_v1.4.py --mailbox ordinace@buzalkova.cz --limit 50 --no-indexes # Pravidelný sync na pozadí (log do souboru): docker exec -d python-runner bash -c "python /scripts/1_parse_emails_graph_v1.4.py --mailbox ordinace@buzalkova.cz --mode sync > /scripts/parse_emails.log 2>&1" ``` > **`-d` = detached:** příkaz se hned vrátí a skript běží dál v kontejneru i po > zavření terminálu / odpojení SSH. Bez `-d` (resp. s `-it`) skript skončí ve chvíli, > kdy se spojení zavře. Pro dlouhé běhy vždy pouštěj s `-d` a logem do souboru, > průběh pak sleduj přes `tail -f` (viz [Sledování průběhu](#sledování-průběhu)). ### Parametry | Parametr | Popis | |---|---| | `--mailbox` | **Povinný.** Schránka (e-mail), zároveň název kolekce v MongoDB. | | `--mode` | `full` (výchozí — plný upsert), `new-only` (jen nové), `sync` (existující: aktualizuje `is_read`/`flag_status`/`categories`/`modified_at`/`folder_path`; nové importuje celé — ideální pro pravidelné spouštění). | | `--folder` | Import jen jedné složky (např. `Inbox`). | | `--limit N` | Zpracuje jen prvních N zpráv (test). | | `--no-indexes` | Nevytváří indexy na konci. | --- ## 2) `2_refetch_text_bodies_v1.0.py` — dohnání plain-text těl **ONETIME oprava.** Starý `parse_emails_graph_v1.3` ukládal plain-text emaily jen jako prvních 2000 znaků do `body_preview` — plné tělo se zahazovalo. Tenhle skript v Mongo najde emaily kde `body_html` chybí a re-fetchne plné tělo z Graphu do nového pole `body_text` (max 2 MB). ```bash docker exec -d python-runner bash -c "python /scripts/2_refetch_text_bodies_v1.0.py --mailbox ordinace@buzalkova.cz > /scripts/refetch.log 2>&1" ``` > Po importu schránky přes v1.4 už tenhle skript prakticky nemá co dělat > (kandidátů 0). Drží se kvůli archivním schránkám, které byly importovány v1.3. --- ## 3) `3_download_attachments_v1.3.py` — stažení příloh → /mnt/Emails Stahuje skutečné přílohy (`is_inline=False`) všech emailů z MongoDB přes Graph API do `/mnt/Emails//Attachments/`. Primárně přes `graph_att_id` (přímé ID), name-matching jako fallback pro staré emaily. Deduplikace podle **SHA256** obsahu: - stejný hash → soubor už existuje → přeskočí - kolize názvu (stejný název, jiný hash) → `faktura_2.pdf`, `faktura_3.pdf` … Po uložení aktualizuje MongoDB: každá příloha dostane `file_hash` + `local_path`; kolekce `emaily.attachments_index` (`_id`=hash, filename, path, size_bytes, mime_type, mailbox, first_seen_at, ref_count). Emaily kde mají všechny přílohy `file_hash` se přeskočí → bezpečné opakovat. ```bash # Interaktivně: docker exec -it python-runner python /scripts/3_download_attachments_v1.3.py --mailbox ordinace@buzalkova.cz # Na pozadí: docker exec -d python-runner bash -c "python /scripts/3_download_attachments_v1.3.py --mailbox ordinace@buzalkova.cz > /scripts/download_attachments.log 2>&1" ``` ### Parametry | Parametr | Popis | |---|---| | `--mailbox` | **Povinný.** Schránka (e-mail) = kolekce v MongoDB. | | `--limit N` | Zpracuje jen prvních N emailů (test). | | `--force-recheck` | Znovu ověří i už stažené přílohy. | | `--no-indexes` | Nevytváří indexy na konci. | --- ## 4) `4_unwrap_smime_v1.0.py` — rozbalení S/MIME zpráv Některé emaily (Datová schránka, mBank, ComGate, PayU, PostSignum …) přicházejí jako S/MIME signed-data wrapper: viditelné tělo je jen *"This is an S/MIME signed message"*, skutečný obsah je zabalený uvnitř přílohy `smime.p7m`. Skript najde tyto emaily, stáhne binárku `smime.p7m` z Graphu, rozbalí PKCS7 SignedData (`asn1crypto.cms`), extrahuje vnitřní MIME zprávu a doplní do Mongo: | Pole | Obsah | |---|---| | `smime_unwrapped: True` | flag — už rozbaleno | | `smime_subject` | Subject z vnitřní MIME hlavičky | | `smime_body_text` | plain text vnitřního těla | | `smime_body_html` | HTML vnitřního těla (pokud je) | | `smime_inner_attachments[]` | `{filename, content_type, size_bytes}` vnitřních příloh | Pole pak používá `5_enrich_fulltext_emails_v1.2` — preferuje `smime_body_*` před prázdným wrapper tělem a názvy vnitřních příloh přidá do `attachments_summary` (takže je najde MCP `emaily.find_attachment`). ```bash docker exec -it python-runner python /scripts/4_unwrap_smime_v1.0.py # vsechny schránky docker exec -it python-runner python /scripts/4_unwrap_smime_v1.0.py --mailbox ordinace@buzalkova.cz docker exec -it python-runner python /scripts/4_unwrap_smime_v1.0.py --limit 10 # test ``` ### POZOR: `smime.p7m` vs `smime.p7s` — dva různé typy | Příloha | Co to je | Skript dělá | |---|---|---| | `smime.p7m` | **Enveloped/signed-data wrapper** — vnější obal kolem celé MIME zprávy. Bez rozbalení je viditelné jen *"This is an S/MIME signed message"*. | **Rozbalí** → extrahuje vnitřní tělo + přílohy do Mongo. | | `smime.p7s` | **Detached signature** — jen digitální podpis vedle čistého emailu. Vlastní `body_html` / `body_text` je normálně dostupné. | **Ignoruje** — není co rozbalovat. Mail je už čitelný. | Filtr ve skriptu (`SMIME_FILTER`) je proto explicitně `^smime\.p7m$`. Pokud při auditu vidíš email s přílohou `smime.p7s` a `smime_unwrapped != True`, je to **správně** — žádná akce není potřeba. ### Závislosti ```bash pip install asn1crypto ``` --- ## 5) `5_enrich_fulltext_emails_v1.2.py` — fulltext do PostgreSQL Vytáhne plný text z emailů v MongoDB a uloží do PostgreSQL (`MongoEmaily.emails`) s GIN `tsvector` indexem (config `soubory` — simple + unaccent). Emaily se **nestahují znovu** — tělo už je v Mongo z kroků 1/2/4. **Priorita zdroje těla** (`body_source`): 1. `smime` — `smime_body_text` / `smime_body_html` (pokud unwrap proběhl) 2. `html` — `body_html` 3. `text` — `body_text` (z parse v1.4 nebo refetch v1.0) 4. `preview` — `body_preview` (fallback) Inkrementalita: pokud `(mailbox, message_id)` existuje a `extractor_version` je aktuální a `modified_at` v Mongo není novější → skip. Bump `EXTRACTOR_VERSION` přeparsuje vše. ```bash docker exec -d python-runner bash -c "python /scripts/5_enrich_fulltext_emails_v1.2.py > /scripts/enrich.log 2>&1" docker exec -it python-runner python /scripts/5_enrich_fulltext_emails_v1.2.py --mailbox ordinace@buzalkova.cz docker exec -it python-runner python /scripts/5_enrich_fulltext_emails_v1.2.py --limit 500 # test ``` --- ## Sledování průběhu ```bash docker exec -it python-runner tail -f /scripts/parse_emails.log docker exec -it python-runner tail -f /scripts/download_attachments.log docker exec -it python-runner tail -f /scripts/unwrap_smime.log ``` --- ## Nainstalované Python balíčky ``` msal (Graph API auth) requests pymongo 4.17.0 python-dateutil 2.9.0.post0 extract-msg 0.55.0 cryptography 48.0.0 asn1crypto (S/MIME unwrap) beautifulsoup4 4.13.5 oletools 0.60.2 msoffcrypto-tool 6.0.0 olefile 0.47 RTFDE 0.1.2.2 compressed-rtf 1.0.7 lark 1.3.1 pcodedmp 1.2.6 tzlocal 5.3.1 six 1.17.0 pip 25.0.1 psycopg (PG klient pro krok 5) ``` --- ## Přidání nového balíčku ```bash docker exec python-runner pip install ``` > Pozor: instalace se ztratí při recreate kontejneru — je třeba přidat do Dockerfile nebo do setup skriptu. --- ## Historie | Datum | Změna | |---|---| | 2026-06-02 | Přechod z `.msg` souborů na Microsoft Graph API. `parse_emails_tower_v1.1.py` (import lokálních `.msg`) nahrazen `parse_emails_graph_v1.3.py`; přidán `download_attachments_v1.3.py`. Staré verze v `Trash/`. | | 2026-06-03 | `parse_emails_graph_v1.4` (ukládá i plné plain-text tělo do `body_text`). Přidán `refetch_text_bodies_v1.0` (dohnání starých plain-text). Přidán `unwrap_smime_v1.0` (rozbalení `smime.p7m`). `enrich_fulltext_emails_v1.2` (preferuje `smime_body_*`, body_source `smime`/`text`). | | 2026-06-04 | Skripty přejmenovány s prefixem `1_…5_` podle pořadí v pipeline. `enrich_v1.1` + `parse_emails_tower_v1.1*` do `Trash/`. |