import html from fastapi import APIRouter, Depends from fastapi.responses import HTMLResponse from ..auth import require_user from ..config import DEFAULT_GITEA_ORG, get_gitea_public_url, read_env_value from ..templates.layout import page router = APIRouter() # Portal sections (the admin/agenda links shown in the navigation). role: minimum role that can # actually use the section; viewers may see read-only data where a section allows it. PORTAL_SECTIONS = [ ("fa-server", "Služby", "/portal/apps", "Přehled nasazených služeb: stav, dokumentace, prostředky (RAM/CPU), Git a akce.", "viewer (čtení) / developer / admin"), ("fa-list-check", "Úlohy", "/portal/jobs", "Fronta a historie úloh (nasazení, skripty) včetně stavů a logů.", "developer / admin"), ("fa-file-lines", "Logy", "/portal/logs", "Souhrnné čtení logů napříč všemi úlohami; filtr chyb a proklik na konkrétní řádek úlohy.", "developer / admin"), ("fa-calendar-days", "Plánované skripty", "/portal/scheduled-scripts", "Cron-like skripty spouštěné na pozadí; úpravy jen pro administrátory.", "developer / admin"), ("fa-bell", "Alerting", "/portal/alerting/rules", "Pravidla alertů a jejich skripty; úpravy jen pro administrátory.", "developer / admin"), ("fa-sliders", "Environment", "/portal/admin/environment", "Bezpečná úprava hlavního appfactory.env (backup, validace) + navazující redeploy core služeb (Portal, Worker, Webhook, Monitor, Gateway, Gitea, Registry) přes job frontu.", "admin"), ("fa-rocket", "Nasazení", "/portal/deployments", "Historie deploy běhů, stavů a výstupů z deploy procesu.", "developer / admin"), ("fa-triangle-exclamation", "Incidenty", "/portal/incidents", "Evidované incidenty služeb.", "developer / admin"), ("fa-gears", "Workery", "/portal/workers", "Stav workerů zpracovávajících úlohy.", "admin"), ("fa-users", "Users", "/portal/admin/users", "Správa uživatelů a rolí (viewer / developer / admin).", "admin"), ("fa-clipboard-list", "Audit", "/portal/audit", "Auditní log akcí provedených v portálu.", "admin"), ("fa-box-archive", "Zálohy", "/portal/backups", "Vytváření, stažení a obnova záloh AppFactory. Obnova je jen pro administrátory.", "admin"), ] # Public / API endpoints not gated by the portal login. PUBLIC_ENDPOINTS = [ ("GET", "/apps", "Veřejný JSON katalog služeb (název + odkaz na dokumentaci). Routuje se přes Caddy."), ("GET", "/portal/health", "Health endpoint portálu (HTTP 200, pokud portál běží)."), ("GET", "/apps/<app-id>/docs", "Swagger/OpenAPI dokumentace konkrétní služby."), ("GET", "/apps/<app-id>/health", "Health endpoint konkrétní služby."), ] # Roles model. ROLES = [ ("guest", "Výchozí role nového účtu po prvním přihlášení. Bez přístupu – vidí jen výzvu požádat administrátora o přidělení práv."), ("viewer", "Pouze pro čtení: vidí jaké služby běží a jejich dokumentaci (Swagger). Bez Gitu, úprav, nasazení a mazání."), ("developer", "Vše co viewer + provozní workflow služeb: Git/klonování, nasazení, proměnné, prostředky, metadata. Nemůže mazat služby ani do Administrace."), ("admin", "Plný přístup: vše co developer + mazání služeb, správa záloh a jejich obnova, správa uživatelů, audit a admin-only operace."), ] @router.get("/developers", response_class=HTMLResponse) def developers_page(user=Depends(require_user)): section_rows = "" for icon, title, href, desc, roles in PORTAL_SECTIONS: section_rows += f""" {html.escape(title)} {html.escape(href)} {html.escape(desc)} {html.escape(roles)} """ endpoint_rows = "" for method, path, desc in PUBLIC_ENDPOINTS: endpoint_rows += f""" {html.escape(method)} {path} {html.escape(desc)} """ role_rows = "" for role, desc in ROLES: role_rows += f""" {html.escape(role)} {html.escape(desc)} """ gitea_url = get_gitea_public_url() gitea_org = read_env_value("GITEA_ORG", DEFAULT_GITEA_ORG) clone_base = gitea_url or "" clone_cmd = html.escape(f"git clone {clone_base}/{gitea_org}/.git", quote=True) return page( "Pro vývojáře", f"""

Pro vývojáře

Přehled všech sekcí portálu, veřejných endpointů a rolí. Portál běží za reverzní proxy na cestě /portal; jednotlivé služby na /apps/<app-id>.

Replikace serveru

Checklist a kopírovatelné příkazy pro čistou instalaci nebo obnovu AppFactory na nový Linux server.

Replikace serveru / požadavky na nový server

Jak publikovat

Postup od založení nové služby po nasazení změn.

  1. Vytvořte novou službu. Otevřete formulář Nová služba; vznikne Gitea repozitář, workspace a první nasazení.
  2. Naklonujte repozitář.
    Přesný příkaz pro konkrétní službu (HTTP i SSH) najdete také na stránce Služby.
  3. Upravte kód. Buď se řiďte souborem AGENTS.md v kořenové složce projektu, nebo úpravy nechte provést AI (agent si AGENTS.md přečte sám).
  4. Commit a push. Zacommitujte a pushněte změny — build a nasazení se spustí automaticky (worker je vždy spuštěný a změnu rovnou zpracuje).
  5. Zkontrolujte výsledek. Stav nasazení a běh služby ověříte na stránce Služby (stav, health, dokumentace).

Sekce portálu (agendy)

{section_rows}
Sekce Odkaz Popis Role

Veřejné / API endpointy

{endpoint_rows}
Metoda Cesta Popis

Veřejný katalog /apps vrací JSON se seznamem služeb a odkazy na jejich dokumentaci.

Role a oprávnění

{role_rows}
Role Co může
""", user=user, ) # --- Replikace serveru / požadavky na nový server ------------------------------------------------- # Čistě dokumentační stránka (checklist + kopírovatelné příkazy). Žádná Docker logika v portálu. # Bash/text bloky jsou běžné (ne f-string) řetězce, aby složené závorky ({}, {{.Names}}) zůstaly # literálně a copy tlačítka kopírovala přesný obsah. def _code_block(code: str) -> str: """Kopírovatelný blok příkazu (tmavý
 + tlačítko Kopírovat)."""
    escaped = html.escape(code.strip("\n"))
    return (
        '
' '' f'
{escaped}
' '
' ) def _text_block(text: str) -> str: """Blok prostého výpisu (např. chybová hláška) bez kopírování.""" return f'
{html.escape(text.strip(chr(10)))}
' APT_INSTALL = r"""apt update apt install -y \ git \ curl \ wget \ jq \ sqlite3 \ unzip \ zip \ tar \ gzip \ ca-certificates \ gnupg \ lsb-release \ bash \ sudo \ openssh-client \ openssh-server \ nano \ vim \ rsync \ cron \ procps \ net-tools \ dnsutils \ iputils-ping \ software-properties-common""" DOCKER_INSTALL = r"""curl -fsSL https://get.docker.com | sh apt install -y docker-compose-plugin""" DOCKER_VERIFY = r"""docker --version docker compose version""" DOCKER_GROUP = r"""groupadd docker || true usermod -aG docker root""" DOCKER_GROUP_ADMIN = r"""usermod -aG docker jiri""" UTILITIES = """git curl wget jq sqlite3 tar zip unzip bash cron rsync ssh docker docker compose""" DNS_LIST = """services.csbot.cz git.csbot.cz registry.csbot.cz""" DNS_CHECK = r"""nslookup services.csbot.cz nslookup git.csbot.cz nslookup registry.csbot.cz""" FIREWALL = """22 SSH 80 HTTP 443 HTTPS""" STRUCTURE = """/opt/appfactory /opt/appfactory/config /opt/appfactory/data /opt/appfactory/workspace /opt/appfactory/backups /opt/appfactory/logs /opt/appfactory/tools""" GIT_CONFIG_HOST = r"""git config --system --add safe.directory "*" """ SERVER_VERIFY = r"""git --version docker --version docker compose version jq --version sqlite3 --version curl --version""" FIX_PERMISSIONS = r"""cat > /opt/appfactory/tools/fix-appfactory-server-permissions.sh <<'EOF' #!/usr/bin/env bash set -euo pipefail APPFACTORY_ROOT="/opt/appfactory" echo "[1/9] Create appfactory group..." getent group appfactory >/dev/null || groupadd appfactory echo "[2/9] Add known users to appfactory group..." for u in root jiri git caddy www-data; do if id "$u" >/dev/null 2>&1; then usermod -aG appfactory "$u" || true fi done echo "[3/9] Ensure base directories exist..." mkdir -p "$APPFACTORY_ROOT/config" mkdir -p "$APPFACTORY_ROOT/data" mkdir -p "$APPFACTORY_ROOT/workspace" mkdir -p "$APPFACTORY_ROOT/backups" mkdir -p "$APPFACTORY_ROOT/logs" mkdir -p "$APPFACTORY_ROOT/tools" echo "[4/9] Set ownership under /opt/appfactory..." chown -R root:appfactory "$APPFACTORY_ROOT" echo "[5/9] Set directory permissions..." find "$APPFACTORY_ROOT" -type d -exec chmod 2775 {} \; echo "[6/9] Set file permissions..." find "$APPFACTORY_ROOT" -type f -exec chmod 664 {} \; echo "[7/9] Make shell scripts executable..." find "$APPFACTORY_ROOT" -type f -name "*.sh" -exec chmod 775 {} \; echo "[8/9] Fix Git safe.directory on host..." git config --system --add safe.directory "*" || true echo "[9/9] Fix Gitea sessions directory if present..." if [ -d "$APPFACTORY_ROOT/data/gitea/gitea" ]; then rm -rf "$APPFACTORY_ROOT/data/gitea/gitea/sessions" mkdir -p "$APPFACTORY_ROOT/data/gitea/gitea/sessions" chmod 777 "$APPFACTORY_ROOT/data/gitea/gitea/sessions" fi echo "Done." EOF bash /opt/appfactory/tools/fix-appfactory-server-permissions.sh""" CONTAINER_GIT = r"""for c in appfactory-portal appfactory-worker appfactory-gitea; do if docker ps -a --format '{{.Names}}' | grep -qx "$c"; then docker exec "$c" sh -lc 'git config --system --add safe.directory "*" || git config --global --add safe.directory "*" || true' || true fi done""" GITEA_PANIC = """PANIC: session(start): chtimes /data/gitea/sessions/... operation not permitted""" GITEA_SESSIONS_FIX = r"""docker stop appfactory-gitea rm -rf /opt/appfactory/data/gitea/gitea/sessions mkdir -p /opt/appfactory/data/gitea/gitea/sessions chmod 777 /opt/appfactory/data/gitea/gitea/sessions docker start appfactory-gitea""" GITEA_HOOKS_BROKEN = """Git hooks of this repository seem to be broken""" GITEA_HOOKS_FIX = r"""docker exec -u git -it appfactory-gitea sh -lc ' gitea admin regenerate hooks --config /data/gitea/conf/app.ini ' docker restart appfactory-gitea""" COMPOSE_ENV_ERRORS = """APPFACTORY_UID variable is not set APPFACTORY_GID variable is not set APPFACTORY_DOCKER_GID variable is not set Unable to find group""" COMPOSE_ENV_EXPORT = r"""export APPFACTORY_UID="$(id -u)" export APPFACTORY_GID="$(id -g)" export APPFACTORY_DOCKER_GID="$(getent group docker | cut -d: -f3)" """ MOUNT_YAML = """volumes: - /opt/appfactory/config:/opt/appfactory/config:rw""" READONLY_PATH = """/opt/appfactory/config/appfactory.env""" READONLY_ERROR = """Read-only file system""" @router.get("/developers/server-replication", response_class=HTMLResponse) def server_replication_page(user=Depends(require_user)): body = "".join([ """

Replikace serveru / požadavky na nový server

Checklist pro čistou obnovu nebo instalaci AppFactory na nový Linux server. Jde o dokumentaci pro administrátora – příkazy se spouštějí na serveru, ne z portálu.

← Zpět na Pro vývojáře

1. Požadavky na nový server

Nový server musí mít:

  • Linux server, ideálně Ubuntu/Debian
  • Docker
  • Docker Compose plugin
  • Git
  • curl
  • bash
  • sqlite3
  • openssh-client
  • přístup k repozitářům v Gitea
  • funkční DNS pro:
    • services.csbot.cz
    • git.csbot.cz
    • registry.csbot.cz
  • adresář /opt/appfactory
  • Docker síť používanou AppFactory
  • volumes/adresáře pro:
    • /opt/appfactory/config
    • /opt/appfactory/data
    • /opt/appfactory/workspace
    • /opt/appfactory/backups
    • /opt/appfactory/logs

2. Základní instalace nového serveru

Před spuštěním bootstrapu musí být na serveru nainstalováno:

""", _code_block(APT_INSTALL), """

Docker a Docker Compose plugin

""", _code_block(DOCKER_INSTALL), "

Ověření:

", _code_block(DOCKER_VERIFY), """

Docker skupina

""", _code_block(DOCKER_GROUP), "

Případně i pro administrátora:

", _code_block(DOCKER_GROUP_ADMIN), """

Povinné utility používané AppFactory

Používají je skripty AppFactory:

""", _text_block(UTILITIES), """

DNS požadavky

Musí fungovat:

""", _text_block(DNS_LIST), "

Kontrola:

", _code_block(DNS_CHECK), """

Firewall

Musí být otevřené:

""", _text_block(FIREWALL), """

Struktura AppFactory

Musí existovat:

""", _text_block(STRUCTURE), """

Git konfigurace (host)

""", _code_block(GIT_CONFIG_HOST), """

Ověření serveru

Před bootstrapem musí projít:

""", _code_block(SERVER_VERIFY), "

Pokud některý příkaz selže, server není připraven pro AppFactory.

", "
", """

3. Povinné nastavení práv

V AppFactory nechceme řešit opakované chyby typu:

  • Permission denied
  • Read-only file system
  • detected dubious ownership
  • operation not permitted
  • Git safe.directory problém

Proto má být pro AppFactory část serveru sjednocené právo zápisu pro relevantní procesy.

Důležité: Neaplikovat chmod 777 na celý Linux server. Aplikovat pouze na /opt/appfactory a na konkrétní problematické runtime adresáře.

Serverový příkaz (oprava práv)

""", _code_block(FIX_PERMISSIONS), """

Oprava Git safe.directory v kontejnerech

""", _code_block(CONTAINER_GIT), "
", """

4. Gitea – sessions

Pokud Gitea spadne na chybu:

""", _text_block(GITEA_PANIC), "

Použij:

", _code_block(GITEA_SESSIONS_FIX), "
", """

5. Gitea – hooks

Pokud Gitea ukazuje:

""", _text_block(GITEA_HOOKS_BROKEN), "

Oprava:

", _code_block(GITEA_HOOKS_FIX), "
", """

6. docker compose – env proměnné

Pokud docker compose hlásí:

""", _text_block(COMPOSE_ENV_ERRORS), "

Použij:

", _code_block(COMPOSE_ENV_EXPORT), "
", """

7. Mounty

Portál musí mít RW přístup ke konfiguraci:

""", _code_block(MOUNT_YAML), "

Nepoužívat read-only mount pro:

", _text_block(READONLY_PATH), "

protože editor appfactory.env pak končí chybou:

", _text_block(READONLY_ERROR), "
", ]) return page("Replikace serveru", body, user=user)