App Distribution — Specifikace¶
Souvisí s:
docs/spec/app-catalog.md(consumer-side — manifest update flow v launcheru, kanály, rollback) Tento dokument pokrývá: publisher-side — kam a jak vendoři / Avaxis tým nahrávají binárky aplikací, jak je registrují v katalogu, a jak se nasazuje sám backend AVAX Platform. Status: Spec hotov 2026-05-12. Implementace čeká.
1. Účel¶
app-catalog.md definuje jak launcher stahuje aplikace (manifest diff, kanály).
app-distribution.md definuje jak se aplikace publikují — kdo upload, kam, jak vznikne manifest, jak se aktivuje verze.
Dále popisuje deployment samotného backendu AVAX Platform stejným tokem
(bucket app-backend, verze, deploy skript).
2. Architektura — bucket per app¶
S3 (Ceph RGW, https://s3.avaxis.cz)
├── app-fakturace-pro/ ← bucket per aplikace (slug s prefixem app-)
├── app-mzdy-cz/
├── app-avax-legal-client/ ← AVAX Legal je dvojí entita:
│ - tento bucket = distribuce klienta
│ - bucket `avaxlegal` (zvlášť) = data aplikace
├── app-backend/ ← FastAPI backend AVAX Platform sám sebe distribuuje stejným tokem
└── app-avax/ ← launcher (AVAX Platform launcher2 build) stejným tokem,
do v0.3.9 v `claudeai/launcher/` — migrace dle sekce 9.2
(slug `avax` — produktový název v katalogu, nehledě na interní
codename `launcher2`)
2.1 Pojmenování bucketu¶
slug≡ slug aplikace v tabulceapps(fakturace-pro,avax-legal-client)- Prefix
app-izoluje od data bucketů (claudeai,avaxlegal,backup-{ico}) - Backend =
app-backend(rezervovaný slug, žádná aplikace nesmí mít slugbackend)
2.2 Vlastník bucketu¶
Owner (Ceph RGW user): appdistributon
Access key (poolu): AVXKEY_APPDIST (label: appdistributon — App Distribution AVAX)
Status v s3_key_pool: assigned / system_appdist
Vendoři, CI pipeline i backend používají tytéž credentials (appdistributon).
Backend .env:
APPDIST_S3_ENDPOINT=https://s3.avaxis.cz
APPDIST_S3_ACCESS_KEY=... # z s3_key_pool/system_appdist po dešifrování
APPDIST_S3_SECRET_KEY=...
APPDIST_S3_REGION=us-east-1
Bezpečnostní predispozice (musí být splněno před implementací): Rados user
appdistributonmusí mít privilegiums3:CreateBucket(Ceph default = ano, RGW user může vytvářet vlastní buckety). Otestovat:Pokud selže (Ceph RGW politikapython3 -c "import boto3; from botocore.config import Config; \ s3=boto3.client('s3', endpoint_url='https://s3.avaxis.cz', \ aws_access_key_id='AK', aws_secret_access_key='SK', \ config=Config(signature_version='s3v4')); \ s3.create_bucket(Bucket='_perm-test-appdist'); s3.delete_bucket(Bucket='_perm-test-appdist')"[bucket_max_per_user]nebo absenceAllow s3:CreateBucket), potřeba admin viaradosgw-admin user modify --uid=appdistributon --max-buckets=-1.
3. Layout uvnitř app bucketu¶
app-fakturace-pro/
├── versions/
│ ├── 2.1.4/
│ │ ├── files/
│ │ │ ├── win/
│ │ │ │ ├── app.exe
│ │ │ │ ├── lib/runtime.dll
│ │ │ │ └── assets/logo.png
│ │ │ ├── linux/
│ │ │ │ └── app.AppImage
│ │ │ └── any/
│ │ │ └── config/defaults.json
│ │ ├── version-manifest.json
│ │ └── changelog.md (volitelně, markdown z avax-publish)
│ ├── 2.1.5/
│ │ └── ...
│ └── 2.0.0/ (starší verze, retention dle sekce 12)
├── channels/
│ ├── alpha.json {"version": "2.1.5"}
│ ├── beta.json {"version": "2.1.4"}
│ └── stable.json {"version": "2.0.0"}
└── icon.png (volitelně, statické metadata)
3.1 S3 versioning¶
OFF — verze jsou v cestě (versions/{ver}/), S3 native versioning by jen
duplikoval. Výjimka: channels/{channel}.json se mění často — zvážit ON pro
audit, defaultně OFF.
3.2 version-manifest.json¶
Identický s formátem v app-catalog.md (sekce Manifest verze) — nezavadíme nový schéma:
{
"app": "fakturace-pro",
"version": "2.1.4",
"channel": "alpha",
"released_at": "2026-05-12T12:00:00Z",
"changelog": "...",
"min_os_win": "Windows 10 1903",
"min_os_linux": "Ubuntu 20.04",
"files": [
{"path": "app.exe", "checksum": "sha256:a1b2c3...", "size": 5242880, "platform": "win"},
{"path": "lib/runtime.dll", "checksum": "sha256:d4e5...", "size": 1048576, "platform": "win"}
],
"total_size_win": 52428800,
"total_size_linux": 48000000
}
3.3 channels/{channel}.json¶
Atomicita: zápis přes put_object (S3 single-object atomic). Launcher
poll-uje tento soubor (GET /catalog/apps/{slug}/version → backend čte
channels/{channel}.json z app-{slug}/).
4. CLI nástroj — avax-publish¶
4.1 Umístění a distribuce¶
avax/tools/publish/
├── publish.py entry point
├── requirements.txt boto3, requests, click, hashlib
├── README.md vendor docs
└── examples/
├── publish-app.sh
└── publish-backend.sh
Vendor naklonuje subtree nebo si stáhne tar release z Gitea
(git.avaxis.cz/claudAI/avax-platform/releases/tools-publish-v1.0.0.tar.gz).
Předpoklad: Python 3.12+ na vendor stroji.
4.2 Konfigurace¶
export APPDIST_S3_ENDPOINT=https://s3.avaxis.cz
export APPDIST_S3_ACCESS_KEY=...
export APPDIST_S3_SECRET_KEY=...
export AVAX_BACKEND_URL=https://api.avaxis.cz # pro finalize/promote
export AVAX_API_TOKEN=... # vendor JWT (role catalog_publisher)
4.3 Příkazy¶
# 1. Publish nové verze do alpha
avax-publish app \
--slug fakturace-pro \
--version 2.1.5 \
--source ./dist/ \
--platform win \
--changelog ./CHANGELOG-2.1.5.md \
--channel alpha
# Pod kapotou:
# 1. Vyresolve slug → bucket `app-fakturace-pro` (pokud neexistuje → create_bucket)
# 2. Walk ./dist/, spočítá sha256 + size pro každý soubor
# 3. put_object pro každý soubor → s3://app-fakturace-pro/versions/2.1.5/files/win/{path}
# 4. Sestaví version-manifest.json, put_object
# 5. Volitelně upload changelog.md
# 6. POST AVAX_BACKEND_URL/admin/apps/fakturace-pro/versions/finalize
# body: {"version": "2.1.5", "channel": "alpha", "platforms": ["win"]}
# backend ověří přítomnost manifestu na S3 + zapíše app_versions row
# 7. POST .../channels/alpha → "version=2.1.5"
# 2. Promote alpha → beta (vendor pravomoc)
avax-publish promote --slug fakturace-pro --version 2.1.5 --to beta
# 3. Promote beta → stable: vendor dostane 403 z backendu, musí přes portal UI
avax-publish promote --slug fakturace-pro --version 2.1.5 --to stable
# > FAIL: vendor role nemá oprávnění promote do stable. Použij Správa portálu → Apps → fakturace-pro → Promote.
# 4. List versions / current channels
avax-publish status --slug fakturace-pro
# 5. Multi-platform v jednom run
avax-publish app --slug fakturace-pro --version 2.1.5 \
--source ./dist-win/ --platform win \
--source ./dist-linux/ --platform linux \
--channel alpha
4.4 Dry-run mode¶
--dry-run provede walk + sha256 + zobrazí seznam plánovaných S3 operací,
bez S3 put a backend volání. Pro CI smoke testy.
5. Backend — finalize & promote endpointy¶
5.1 Datový model — beze změny¶
apps, app_versions, app_version_files z app-catalog.md/sekce Datový model.
Nově:
apps.distribution_bucket VARCHAR(100)— defaultapp-{slug}, override jen výjimečněapp_versions.uploaded_by_user_id UUID— kdo zavolal finalize (audit)app_versions.changelog TEXT— markdown zchangelog.md
5.2 Endpointy¶
| Metoda | Endpoint | Role | Popis |
|---|---|---|---|
| POST | /admin/apps/{slug}/versions/finalize |
catalog_publisher | Vendor po S3 uploadu zaregistruje verzi. Backend čte manifest z S3, ověří checksumy (head_object size + případně random sample), zapíše app_versions + app_version_files rows. |
| PUT | /admin/apps/{slug}/versions/{version}/promote |
catalog_publisher (→ alpha/beta), super_admin (→ stable) | Přepíše channels/{channel}.json. Validace: cílová verze musí existovat a procházet smoke checkem. |
| GET | /admin/apps/{slug}/versions |
catalog_admin | Seznam všech verzí v bucketu (čteno z DB, ne S3). |
| DELETE | /admin/apps/{slug}/versions/{version} |
super_admin | Smaže verzi z bucketu + DB row. Tvrdé — žádný soft delete. Pro retention sekce 12 jen označuje pro pozdější mazání. |
5.3 Role¶
catalog_publisher ← nová role, přiřaditelná v Správě portálu
Vendor (externí firma vyvíjející aplikaci v katalogu)
Pravomoci: finalize, promote alpha↔beta na svých aplikacích
Filtr: jen apps kde apps.publisher_id = company_id usera
catalog_admin ← stávající (super_admin nebo Avaxis interní)
Pravomoci: vše + apps na všech publisherech
super_admin ← stávající (Avaxis)
Pravomoci: promote do stable, delete version
5.4 Finalize ověření¶
Backend při finalize:
head_object(versions/{ver}/version-manifest.json)— existuje?- Parse JSON, validace schema (Pydantic)
- Pro každý
files[].path→head_object→ ověřsize(S3 ContentLength == manifest.size) - Random sample 3 souborů: stáhne body, ověří
sha256(body) == files[i].checksum - Zápis DB rows v transakci
Pokud kterýkoli krok selže → 422 + ne-zápis do DB. Vendor opraví a zavolá znovu.
6. AVAX Legal — special case¶
app-avax-legal-client/ ← distribuce klientského .exe (standardní app-distribution flow)
avaxlegal/ ← data bucket (raw/, bundles/, pdf/, ai-cache/) — viz docs/spec/...
(samostatný Rados user `avaxlegal`, vlastní LEGAL_S3_* env)
Klient AVAX Legal sdílí backend = avax-platform FastAPI + router legal/*
(mountovaný do app.main). Backend čte/píše do avaxlegal/ přes LEGAL_S3_*.
Distribuce klienta běží přes app-avax-legal-client/ přes APPDIST_S3_*.
Backend deploy (sekce 7) zahrnuje i Legal router (je část backendu),
takže app-backend distribuuje včetně Legal kódu.
7. Backend deploy z S3¶
Backend AVAX Platform používá tu samou bucket-per-app distribuci se slug
backend (bucket app-backend), ale s odlišnostmi: Docker compose runtime,
image jako tarball v S3, sealed secrets, dvě cílová prostředí (dev/beta vs
prod/stable), hotfix kanál, atd.
Kompletní spec viz docs/spec/backend-deploy.md —
Docker compose layout, sealed secrets, deploy skript, systemd triggers,
bootstrap, rollback, portal UI, 8-fázový implementační plán.
Stručný přehled:
| Aspekt | Volba |
|---|---|
| Runtime | Docker compose (api + worker + migrate + db + redis) |
| Image | tarball v app-backend/versions/{ver}/files/any/image.tar.gz |
| Secrets | AES-256-GCM .env.enc v app-backend/secrets/{env}/ |
| Trigger beta | systemd timer 5min poll (dev/avaxdev) |
| Trigger stable | webhook z portálu (prod/vm-api) |
| Trigger hotfix | systemd timer 1min poll všude, bypassuje normal flow |
| DB backup | pg_dump před každou migrací, 30-day retention |
| CI | Gitea Actions auto-build na push master → publish beta |
8. Launcher download — bezpečnost¶
Launcher NIKDY nedostane APPDIST_S3_* credentials. Stahuje výhradně přes:
GET /catalog/apps/{slug}/version → vrací aktuální version pro launcher's kanál
GET /catalog/apps/{slug}/manifest → vrací version-manifest.json
GET /catalog/apps/{slug}/file?path=... → vrací presigned GET URL (TTL 15 min)
backend signuje appdist creds
Vendor app nepotřebuje credentials pro Spawn — launcher zajistí distribuci.
AVAX Legal data bucket (avaxlegal/) má svůj samostatný flow (presigned přes
/legal/sync/bundle/{checksum} — viz Legal spec).
9. Migrace existujících dat¶
9.1 fakturace-pro (test data)¶
Současný stav: claudeai/apps/catalog/fakturace-pro/versions/{1.0.0,2.0.0}/...
# Jednorázová migrace (na dev serveru)
python avax/scripts/migrate_app_catalog.py \
--src-bucket claudeai --src-prefix apps/catalog/fakturace-pro/ \
--dst-bucket app-fakturace-pro --dst-prefix ""
# (vytvoří app-fakturace-pro, copy_object, ověří, smaže source)
Backend services/catalog.py update: hardcoded prefix apps/catalog/{slug}/
→ použít app-{slug}/ jako bucket name + relativní cesty.
S3 client v service swap z S3_* na APPDIST_S3_*.
9.2 launcher (AVAX) — migrace claudeai/launcher/ → app-avax/¶
Současný stav: claudeai/launcher/avax-launcher2-0.3.{3-6}.exe + latest.json
Cílová struktura: app-avax/versions/{ver}/files/win/avax-launcher2.exe
+ standard manifest + channels. Launcher self-update tah ze stejného toku.
Slug:
avax(produktový název v katalogu — launcher JE the AVAX appka). Interní codenamelauncher2se ponechává v cestě/repu (desktop/launcher2/) a v souboruavax-launcher2.exe(zachování pro auto-update kontinuitu).Pozn.: Bootstrapper (sekce v
docs/spec/deployment.md) stahuje launcher zhttps://s3.avaxis.cz/app-avax/channels/stable.json→ manifest → exe. Bez backend volání (bootstrapper není autentizován) — viz § 10 Bezpečnost.
Compat shim — latest.json v rootu bucketu (přechodné řešení do migrace
updater.py na manifest-based protokol, F-AppDist-7+):
// app-avax/latest.json
{
"version": "0.3.10",
"filename": "avax-launcher2-0.3.10.exe",
"url": "https://s3.avaxis.cz/app-avax/versions/0.3.10/files/win/avax-launcher2.exe",
"notes": "Migrace na app-avax bucket"
}
Stávající updater.py v launcheru spotřebovává tento formát; po migraci
v F-AppDist-7 přejde na čtení channels/stable.json + version-manifest.json.
Bridge pro 0.3.{3-6} v terénu — claudeai/launcher/latest.json se
aktualizuje na novou verzi 0.3.10 (s URL ukazujícím do app-avax/),
takže stávající instalovaní launcheři při příští kontrole stáhnou 0.3.10
a od příští kontroly už čtou app-avax/latest.json.
10. Bezpečnost — souhrn¶
| Vrstva | Kontrola |
|---|---|
| Vendor upload | APPDIST_S3_* creds (Bitwarden vault per vendor org), MFA na portálu při generování API tokenu pro /admin/... |
| Vendor scope | DB filtr apps.publisher_id == vendor.company_id v _require_catalog_publisher |
| Promote → stable | Vyžaduje super_admin JWT z portálu — vendor CLI vrátí 403 |
| Launcher | Bez S3 creds. Vždy presigned URL z backendu (15 min TTL). |
| Audit | app_versions.uploaded_by_user_id, channels/*.json má promoted_by field, S3 access log (Ceph RGW) |
| Klíč rotace | Při kompromitaci appdist creds: revoke v s3_key_pool + nový key přes radosgw-admin + update .env na all consumers |
11. Retention a cleanup¶
Per app bucket:
versions/{ver}/ drží navždy pokud is_current=true v některém kanálu
versions/{ver}/ drží 90 dní po opuštění všech kanálů → archiv (lifecycle policy)
S3 lifecycle policy (per bucket):
Rule: archive-old-versions
Filter: prefix versions/
Transition: po 90 dnech do Cold (Ceph storage class "COLD" pokud existuje)
Expiration: nikdy (rollback může vyžadovat starou verzi)
Rule: drop-incomplete-uploads
AbortIncompleteMultipartUpload: po 7 dnech
DB app_versions.is_archived BOOLEAN flag — backend Celery task (1x denně)
prochází channels/*.json a označuje opuštěné verze.
12. Implementační plán (po schválení specu)¶
F-AppDist-1 CLI tool avax-publish (Python, click, boto3, requests)
- publish + promote + status + dry-run
- tests s LocalStack/MinIO
F-AppDist-2 Backend role catalog_publisher
- DB migration (apps.publisher_id, apps.distribution_bucket)
- _require_catalog_publisher dependency
- app_versions.uploaded_by_user_id, .changelog
F-AppDist-3 Backend endpoints — finalize, promote (rozšíření)
- POST /admin/apps/{slug}/versions/finalize
- PUT /admin/apps/{slug}/versions/{ver}/promote (role-aware)
- services/catalog.py refactor S3_BUCKET → app-{slug} bucket
F-AppDist-4 Migrace fakturace-pro test dat
- scripts/migrate_app_catalog.py
- regresní test launcher2 stahuje correct soubory
F-AppDist-5 Backend deploy bucket
- app-backend bucket setup (CreateBucket test)
- deploy.sh + systemd timer template
- publish backend 1.0.0 jako prvotní baseline
F-AppDist-6 Migrace launcher2 distribuce
- claudeai/launcher/* → app-avax/
- Bootstrapper update URL (až bude bootstrapper)
- Compat latest.json shim, bridge pointer v claudeai/launcher/
- Test self-update (rebuild 0.3.10 s novým updater URL)
13. Otevřené otázky (k dořešení během implementace, ne blokující spec)¶
- Vendor onboarding flow — UI v portálu pro
super_adminpřiřadit rolicatalog_publishercompany X. Wireframe v F-AppDist-2. - CI integrace — example Gitea Actions yaml pro auto-publish on tag push. Doplnit do
tools/publish/examples/. - Notifikace promote → stable — všem catalog_publisher emailem? Webhook Slack/Discord? V MVP jen audit log.
- Cross-bucket statistika — total storage per publisher, počty downloadů. Prometheus exporter (vm-monitor) v F-AppDist-7+.
13a. Operační detaily (doplněno po review)¶
13a.1 File permissions po stažení¶
Launcher po stažení souboru ze S3 nastavuje executable bit podle platformy:
| Platforma | Pravidlo |
|---|---|
| Windows | Nic (executable detekce přes extension .exe). |
| Linux / macOS | os.chmod(path, 0o755) pro soubory v files/{linux,mac}/ jejichž path matchuje *.AppImage, *.sh, *.bin, nebo nemá extension. |
any/ |
Stejné pravidlo jako pro cílovou platformu. |
Manifest může deklarovat "executable": true na files[] záznamu jako
explicit override (přebíjí heuristiku):
avax-publish CLI doplní executable: true automaticky pro soubory které
mají na vendor filesystem už nastavený exec bit (path.stat().st_mode & 0o111).
13a.2 Validace verze (semver)¶
Backend finalize endpoint odmítne version které nematchuje regex:
Tedy 2.1.4, 2.1.4-beta.1, 2.1.4+build.123 projdou. latest-build-99,
v2.1 neprojdou. Backend vrátí 422 s detail: "version musí mít formát semver".
CLI avax-publish validuje stejným regexem před uploadem (rychlý fail bez S3 operace).
13a.3 Backend deploy — healthcheck rollback¶
deploy.sh (§ 7.2) po systemctl restart avax-backend:
# Healthcheck loop
for i in 1 2 3 4 5; do
sleep 2
if curl -fsS http://localhost:8000/health > /dev/null; then
echo "Deploy ${CHANNEL} → ${TARGET} OK"; exit 0
fi
done
# Failure → rollback na předchozí verzi
PREV=$(cat ${BACKEND_HOME}/previous_version 2>/dev/null)
if [[ -z "$PREV" ]]; then
echo "FATAL: healthcheck failed and no previous version to roll back to"; exit 2
fi
ln -sfn ${BACKEND_HOME}/releases/${PREV} ${BACKEND_HOME}/current
echo "$PREV" > ${BACKEND_HOME}/current_version
systemctl restart avax-backend
echo "FAIL: deploy ${TARGET} failed healthcheck, rolled back to ${PREV}"
exit 1
current_version je atomicky nahrazen po úspěšném healthchecku, previous_version
drží předchozí hodnotu. Tím rollback i při katastrofálním nestartu app.
13a.4 App deprecation flow¶
Když super_admin smaže aplikaci z katalogu (is_active=false):
- Den 0:
is_active=falsevappsrow. Launcher dotahuje předchozíversion-manifest.jsonale UI ji nezobrazuje v katalogu. - Den 30: Backend Celery task označí
app-{slug}/versions/*lifecycle =Pending-Deletion. Channels/* se zachovají (rollback). Apps zůstává v DBis_active=false,archived_atse nastaví. - Den 365: Bucket
app-{slug}smazán (rmrf), DB row + související subscriptions/installs anonymizovány (GDPR).
super_admin může kterýkoli krok urychlit přes POST /admin/apps/{slug}/purge
(potřebuje confirmation challenge — vepsat slug znovu).
Pozn.: AVAX Legal data bucket
avaxlegal/má vlastní deprecation (per specdocs/spec/legal/*), tento flow se ho netýká.
13a.5 Public-read policy pro app-avax/ (special case)¶
Bootstrapper a aktuální updater.py stahují bez autentizace → app-avax/
potřebuje selektivní public-read na:
latest.jsonchannels/*.jsonversions/*/version-manifest.jsonversions/*/files/**/*
Bucket policy (set přes appdistributon credentials):
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": [
"arn:aws:s3:::app-avax/latest.json",
"arn:aws:s3:::app-avax/channels/*",
"arn:aws:s3:::app-avax/versions/*/version-manifest.json",
"arn:aws:s3:::app-avax/versions/*/files/*"
]
}]
}
Ostatní app-{slug}/ buckety nejsou public-read — launcher si vyžádá
presigned URL přes backend (autentizovaný uživatel). Public-read je výjimka
jen pro bootstrapper-cílový bucket.
14. Glosář¶
| Pojem | Význam |
|---|---|
| Vendor / Publisher | Firma vyvíjející aplikaci v katalogu AVAX (Avaxis interní nebo externí). |
app-{slug} bucket |
S3 bucket vlastněný appdistributon userem, určený pro distribuci jedné aplikace. |
app-backend |
Speciální bucket pro distribuci FastAPI backendu AVAX Platform. |
appdistributon |
Rados user na Ceph RGW (s3.avaxis.cz) sdílený mezi všemi publishery a backendem pro S3 operace nad app-*. |
| Manifest | version-manifest.json se seznamem souborů + sha256 + size pro jednu verzi. |
| Kanál | alpha / beta / stable — pointer (channels/{channel}.json) na aktuální verzi pro danou cílovou skupinu. |
| Finalize | Backend zápis DB rows pro novou verzi po S3 uploadu (validace přítomnosti + checksumů). |
| Promote | Přepsání channels/{channel}.json na novou verzi (vendor CLI pro alpha/beta, portal pro stable). |
Aktualizováno: 2026-05-12