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(href)}{path}
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>.
Checklist a kopírovatelné příkazy pro čistou instalaci nebo obnovu AppFactory na nový Linux server.
Postup od založení nové služby po nasazení změn.
AGENTS.md v kořenové složce projektu, nebo úpravy
nechte provést AI (agent si AGENTS.md přečte sám).
| Sekce | Odkaz | Popis | Role |
|---|
| Metoda | Cesta | Popis |
|---|
Veřejný katalog /apps vrací JSON se seznamem služeb a odkazy na jejich dokumentaci.
| Role | Co může |
|---|
+ 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.
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)