Metadata-Version: 2.4
Name: edri
Version: 2026.7.2rc13
Summary: Event Driven Routing Infrastructure
Author-email: Marek Olšan <marek@olsanovi.cz>
License: MIT
Project-URL: Documentation, https://edri.olsanovi.cz
Keywords: event-driven,routing,distributed,parallel,multiprocessing,asyncio,api,framework,websocket
Classifier: Development Status :: 5 - Production/Stable
Classifier: Environment :: Web Environment
Classifier: Framework :: AsyncIO
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Natural Language :: English
Classifier: Operating System :: POSIX
Classifier: Operating System :: POSIX :: Linux
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Programming Language :: Python :: 3.15
Classifier: Topic :: Internet :: WWW/HTTP :: HTTP Servers
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
Classifier: Topic :: System :: Distributed Computing
Classifier: Typing :: Typed
Requires-Python: >=3.12
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: typeguard>=4.0
Requires-Dist: requests>=2.0
Requires-Dist: validators>=0.22.0
Requires-Dist: typing_extensions>=4.0
Requires-Dist: multipart>=1.0
Requires-Dist: jinja2>=3.1
Requires-Dist: watchdog>=6
Requires-Dist: websockets>=14
Requires-Dist: posix-ipc>=1.2.0
Requires-Dist: markdown>=3.0
Requires-Dist: pytz>=2024.1
Provides-Extra: uvicorn
Requires-Dist: uvicorn[standard]>=0.32.0; extra == "uvicorn"
Requires-Dist: wsproto>=1.0.0; extra == "uvicorn"
Provides-Extra: hypercorn
Requires-Dist: hypercorn[h3]>=0.17.0; extra == "hypercorn"
Requires-Dist: uvloop>=0.20.0; extra == "hypercorn"
Provides-Extra: daphne
Requires-Dist: daphne>=4.2.2; extra == "daphne"
Requires-Dist: twisted[http2,tls,websocket]>=24.0.0; extra == "daphne"
Requires-Dist: service-identity>=18.1.0; extra == "daphne"
Requires-Dist: idna>=2.5; extra == "daphne"
Provides-Extra: sqlalchemy
Requires-Dist: sqlalchemy>=2.0; extra == "sqlalchemy"
Provides-Extra: pydantic-ai
Requires-Dist: pydantic-ai-slim[openai]>=0.0.1; extra == "pydantic-ai"
Provides-Extra: dev
Requires-Dist: edri[uvicorn]; extra == "dev"
Requires-Dist: edri[hypercorn]; extra == "dev"
Requires-Dist: edri[sqlalchemy]; extra == "dev"
Requires-Dist: edri[pydantic-ai]; extra == "dev"
Requires-Dist: sphinx>=8.0.0; extra == "dev"
Requires-Dist: myst-parser>=4.0.0; extra == "dev"
Requires-Dist: sphinx-autodoc-typehints>=3.1.0; extra == "dev"
Requires-Dist: furo>=2024.1.29; extra == "dev"
Requires-Dist: sphinxcontrib-mermaid>=2.0.0; extra == "dev"
Requires-Dist: wheel; extra == "dev"
Requires-Dist: coverage; extra == "dev"
Requires-Dist: setuptools; extra == "dev"
Requires-Dist: twine; extra == "dev"
Dynamic: license-file

# EDRI — administrace repozitáře

Tento README je pro lidi, kteří spravují **tento repozitář**: jeho GitLab
projekt, CI/CD pipeline a nasazení verzované dokumentace na server `edri`.
Nepokrývá samotný framework EDRI.

Pro framework — co to je, jak ho nainstalovat, jak ho používat — viz
[`docs/source/README.md`](docs/source/README.md). Po dokončení nastavení
níže se stejný obsah automaticky publikuje i na `edri.olsanovi.cz` při
každém tagovaném releasu — nevyžaduje to žádný zvláštní krok navíc.

---

## Nasazení dokumentace přes GitLab

Každý push SemVer tagu sestaví Sphinx dokumentaci tohoto repozitáře přesně
pro daný checkout a nahraje ji na `edri.olsanovi.cz` přes CI job
`docs:deploy`. Serverová publikační logika (rrsync upload, atomická
publikace, regenerace `versions.json`, výběr `stable`) je již
implementovaná a není součástí tohoto repozitáře. Tato sekce pokrývá vše,
co musí být nastaveno **mimo** tento repozitář — v GitLabu a na serveru —
aby tento job fungoval. Nic z toho neprobíhá automaticky; jde o jednorázové
ruční nastavení.

### Přehled architektury

```mermaid
sequenceDiagram
    participant CI as docs:deploy (GitLab runner)
    participant Jump as jumphost
    participant Edri as edri (192.168.10.81)

    CI->>CI: sphinx-build tag checkout
    CI->>CI: prepare-docs-deployment.py (build deployment.json)
    CI->>Jump: SSH (edri_jumphost_key)
    Jump->>Edri: ProxyJump to 192.168.10.81
    CI->>Edri: rsync site/ (edri_rsync_key, rrsync forced command)
    CI->>Edri: rsync deployment.json
    CI->>Edri: rsync READY (last, alone)
    CI->>Edri: ssh edri-publish (edri_script_key, forced command, no remote args)
    Edri->>Edri: post-deploy.py validates + atomically publishes + regenerates versions.json
```

Na `edri` vedou pouze přes jumphost dva oddělené SSH klíče:

- **Upload klíč** (`EDRI_RSYNC_SSH_KEY`): na serveru omezený forced
  commandem `rrsync` na `/var/www/edri/.incoming`, write-only, no-delete,
  no-overwrite. Nemůže číst soubory, nemůže spustit shell.
- **Post-deploy klíč** (`EDRI_SCRIPT_SSH_KEY`): serverový forced command
  vždy spustí `/opt/edri/post-deploy.py`, bez ohledu na to, co pošle klient.

Obě serverová chování jsou již implementovaná a tímto repozitářem se
**nemění**.

### Potřebné proměnné GitLab CI/CD

Vytvoř tyto proměnné pod **Settings → CI/CD → Variables**:

| Proměnná | Typ | Protected | Poznámka |
|---|---|---|---|
| `EDRI_RSYNC_SSH_KEY` | File | Ano | Privátní klíč pro upload-only `rrsync` účet. |
| `EDRI_SCRIPT_SSH_KEY` | File | Ano | Privátní klíč pro spuštění post-deploy. |
| `EDRI_JUMPHOST_SSH_KEY` | File | Ano | Privátní klíč pro skok přes jumphost. |
| `EDRI_SSH_KNOWN_HOSTS` | File | Ano | Ověřené host keys pro jumphost a `edri.home` (viz "Příprava known_hosts" níže). |
| `EDRI_JUMPHOST_HOST` | Variable | Ano | Hostname/IP jumphostu. |
| `EDRI_JUMPHOST_USER` | Variable | Ano | SSH uživatel na jumphostu. |

Všech šest musí být **Protected** (dostupné jen pipeline běžícím na
protected branchích/tazích) — tím se deploy klíče drží mimo pipeline
nedůvěryhodných merge requestů. Pokud tvoje verze GitLabu nabízí u File
proměnných "Expand variable reference", nech to **vypnuté** — jde o
neprůhledný klíčový materiál, ne šablony.

Uživatelská jména SSH pro rsync/post-deploy (`deploy`) a cílový host
(`192.168.10.81`, `HostKeyAlias edri.home`) jsou pevně dané v
`.ci/ssh_config.template` a **nejsou** konfigurovatelné přes proměnné —
parametrizovaný je jen skok přes jumphost, protože jeho host/uživatel jsou
specifické pro prostředí a tomuto repozitáři neznámé.

Nikdy tyto hodnoty necommituj. `scripts/deploy-docs.sh` v jobu zkopíruje
File proměnné do `~/.ssh/edri_jumphost_key`, `~/.ssh/edri_rsync_key`,
`~/.ssh/edri_script_key` a `~/.ssh/known_hosts`, každou s `chmod 600`, a po
skončení jobu se spolu se zbytkem jeho filesystému zahodí.

### Protected tags

Pod **Settings → Repository → Protected tags** přidej pattern odpovídající
release tagům tohoto projektu (např. `v*`), omezený na roli Maintainer
(nebo tu, které chceš svěřit vydávání releasů). Tím se výše uvedených šest
proměnných skutečně zpřístupní běhu `docs:deploy` — GitLab totiž Protected
proměnné zpřístupňuje jen pipeline na protected refs.

Reálný deployment tak může spustit jen Maintainer schopný pushnout
odpovídající tag; kdokoliv, kdo umí jen otevřít merge request, ne.

### Požadavky na runner

Runner, který spouští `docs:deploy`, potřebuje:

- Síťovou dostupnost k jumphostu a jumphost musí umět forwardovat TCP
  spojení na `192.168.10.81:22`. Samotný runner nepotřebuje přímý přístup
  do domácí sítě — jen k jumphostu.
- Docker (nebo ekvivalentní) executor schopný stáhnout `python:3.13-alpine`
  — `openssh-client`, `rsync`, `gettext` a `bash` si job doinstaluje sám
  přes `apk`.

Pokud se sdílené runnery k jumphostu nedostanou, otaguj `docs:deploy`
runner tagem, který ano (tento repozitář žádný nepřidává, protože správná
hodnota tagu je specifická pro prostředí — přidej `tags: [<tvůj-tag>]` k
jobu v `.gitlab-ci.yml`, pokud potřeba).

### Příprava `known_hosts`

Nedůvěřuj výstupu `ssh-keyscan` naslepo (a nikdy ho nespouštěj přímo v CI
jobu — `docs:deploy` v tomto repozitáři to nedělá). Místo toho:

1. Získej fingerprinty host keys jumphostu a `edri` z důvěryhodného zdroje
   (konzolový přístup, existující konfigurační management, nebo fingerprint
   ověřený jiným kanálem předem).
2. Spusť `ssh-keyscan` proti oběma hostům z důvěryhodného stroje a porovnej
   vrácený fingerprint s tím, co jsi získal v kroku 1.
3. Až po ověření spoj oba záznamy (jumphost a `edri.home` — `HostKeyAlias`
   použitý v `.ci/ssh_config.template`, ne holou IP) do jednoho souboru a
   jeho obsah vlož do File proměnné `EDRI_SSH_KNOWN_HOSTS`.

### První deployment

1. Dokonči nastavení proměnných, protected tags, runneru a `known_hosts`
   výše.
2. Pushni reálný, dosud nepoužitý SemVer tag (např. `v0.1.0`, pokud jde o
   první release v tomto schématu).
3. Sleduj job `docs:deploy` v pipeline pro daný tag.
4. Podle manuálního integračního checklistu v
   [`docs/source/versioned_documentation.md`](docs/source/versioned_documentation.md)
   ověř výsledek.

### Řešení problémů

**`cp: cannot stat '...'` při kopírování klíče**
Proměnná (`EDRI_RSYNC_SSH_KEY`, `EDRI_SCRIPT_SSH_KEY`, `EDRI_JUMPHOST_SSH_KEY`
nebo `EDRI_SSH_KNOWN_HOSTS`) byla v GitLabu založená jako obyčejná
`Variable` místo `File`. `deploy-docs.sh` u všech čtyř očekává, že jejich
hodnota je **cesta** k souboru s obsahem (přesně to GitLab u File
proměnných do jobu předá) — pokud je typ špatně, `$EDRI_RSYNC_SSH_KEY`
apod. obsahuje rovnou text klíče a `cp` selže, protože se snaží najít
soubor, jehož název je ten dlouhý řetězec. Oprava: smaž proměnnou a založ ji
znovu se správným typem `File`.

**Permission denied (SSH)**
Zkontroluj: správný klíč je ve správné File proměnné; proměnná je
Protected; tag je protected; pushnutý tag skutečně odpovídá protected-tag
patternu; lokální oprávnění klíčových souborů skončila na `600` (job to
dělá sám, ale vlastní runner image do toho může zasáhnout).

**Host key verification failed**
Záznam v `known_hosts` chybí nebo je zastaralý, nebo `HostKeyAlias
edri.home` neodpovídá tomu, co je v `EDRI_SSH_KNOWN_HOSTS`, nebo se
skutečně změnil host key serveru. Zopakuj ověřovací postup výše — nenastavuj
`StrictHostKeyChecking no`.

**Rsync odmítne přepsat soubor**
Očekávané: upload klíč je `no-overwrite`. Znamená to, že se použilo znovu
stejné `deployment_id`. `deploy-docs.sh` vždy odvozuje nové ID včetně
`$CI_JOB_ID`, takže prostý retry jobu `docs:deploy` (ne opakované spuštění
téže instance jobu) automaticky vytvoří nové ID. Nezkoušej uklízet vzdálený
staging adresář `.incoming` — nech ho tam pro případnou kontrolu adminem.

**Post-deploy odmítne existující verzi**
Publikované tagy jsou záměrně neměnné. Vydej nový tag; neexistuje
podporovaný způsob, jak znovu publikovat stejnou verzi.

**Post-deploy odmítne build metadata**
Tagy jako `v1.2.3+build.1` jsou záměrně nepodporované (jak pravidly
`docs:deploy` tohoto repa, tak serverem). Použij tag bez `+...`.

**Deployment se nahrál, ale nic se nepublikovalo**
Postupně zkontroluj: že `READY` bylo skutečně nahráno jako poslední (log
jobu); že krok `ssh -F ... edri-publish` proběhl a skončil s exit 0;
serverové logy `post-deploy.py`; že `deployment.json` odpovídá očekávanému
schématu.

**Tag pipeline nemá deploy secrets**
Zkontroluj, že tag odpovídá protected patternu výše, že je všech šest
proměnných označeno jako Protected, a že pipeline skutečně běží v protected
kontextu (vidět v detailu pipeline v GitLabu).

### Rollback

Publikované verze jsou neměnné a jejich URL zůstávají živé — neexistuje
"undo" pro obsah špatného release. Náprava chyby:

- **Běžný případ**: vydej nový, opravený tag. Toto je jediná rollback cesta,
  kterou `docs:deploy` podporuje; záměrně neexistuje CI job, který by mohl
  přesunout `stable` na libovolnou verzi nebo smazat publikovanou verzi.
- **Výjimečný případ**: administrátor může ručně upravit, na kterou verzi
  `stable` ukazuje, přímo na serveru mimo CI, pokud byl release prokazatelně
  rozbitý a nový tag není okamžitě možný. Jde o serverovou, člověkem
  provedenou akci — ne o něco, co vystavuje pipeline tohoto repozitáře.
