# 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 `<schránka>/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.<mailbox>` |
| 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/<mailbox>/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ů  | `<mailbox>` (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:<id>`). 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/<schránka>/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 <balicek>
```

> 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/`. |
