Přeskočit obsah

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

app-{slug}
  • slug ≡ slug aplikace v tabulce apps (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 slug backend)

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 appdistributon musí mít privilegium s3:CreateBucket (Ceph default = ano, RGW user může vytvářet vlastní buckety). Otestovat:

python3 -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')"
Pokud selže (Ceph RGW politika [bucket_max_per_user] nebo absence Allow s3:CreateBucket), potřeba admin via radosgw-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

{
  "version": "2.1.4",
  "promoted_at": "2026-05-12T15:30:00Z",
  "promoted_by": "michal@avaxis.cz"
}

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) — default app-{slug}, override jen výjimečně
  • app_versions.uploaded_by_user_id UUID — kdo zavolal finalize (audit)
  • app_versions.changelog TEXT — markdown z changelog.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:

  1. head_object(versions/{ver}/version-manifest.json) — existuje?
  2. Parse JSON, validace schema (Pydantic)
  3. Pro každý files[].pathhead_object → ověř size (S3 ContentLength == manifest.size)
  4. Random sample 3 souborů: stáhne body, ověří sha256(body) == files[i].checksum
  5. Zápis DB rows v transakci

Pokud kterýkoli krok selže → 422 + ne-zápis do DB. Vendor opraví a zavolá znovu.


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í codename launcher2 se ponechává v cestě/repu (desktop/launcher2/) a v souboru avax-launcher2.exe (zachování pro auto-update kontinuitu).

Pozn.: Bootstrapper (sekce v docs/spec/deployment.md) stahuje launcher z https://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énuclaudeai/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/*.jsonpromoted_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)

  1. Vendor onboarding flow — UI v portálu pro super_admin přiřadit roli catalog_publisher company X. Wireframe v F-AppDist-2.
  2. CI integrace — example Gitea Actions yaml pro auto-publish on tag push. Doplnit do tools/publish/examples/.
  3. Notifikace promote → stable — všem catalog_publisher emailem? Webhook Slack/Discord? V MVP jen audit log.
  4. 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):

{"path": "bin/runner", "checksum": "...", "size": ..., "platform": "linux", "executable": true}

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:

^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.]+)?(\+[a-zA-Z0-9.]+)?$

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):

  1. Den 0: is_active=false v apps row. Launcher dotahuje předchozí version-manifest.json ale UI ji nezobrazuje v katalogu.
  2. Den 30: Backend Celery task označí app-{slug}/versions/* lifecycle = Pending-Deletion. Channels/* se zachovají (rollback). Apps zůstává v DB is_active=false, archived_at se nastaví.
  3. 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 spec docs/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.json
  • channels/*.json
  • versions/*/version-manifest.json
  • versions/*/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