559 lines
21 KiB
Python
559 lines
21 KiB
Python
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"""
|
||
<tr>
|
||
<td><a href="{href}"><i class="fa-solid {icon}" aria-hidden="true"></i> {html.escape(title)}</a></td>
|
||
<td><code>{html.escape(href)}</code></td>
|
||
<td>{html.escape(desc)}</td>
|
||
<td><span class="muted">{html.escape(roles)}</span></td>
|
||
</tr>
|
||
"""
|
||
|
||
endpoint_rows = ""
|
||
for method, path, desc in PUBLIC_ENDPOINTS:
|
||
endpoint_rows += f"""
|
||
<tr>
|
||
<td><span class="pill">{html.escape(method)}</span></td>
|
||
<td><code>{path}</code></td>
|
||
<td>{html.escape(desc)}</td>
|
||
</tr>
|
||
"""
|
||
|
||
role_rows = ""
|
||
for role, desc in ROLES:
|
||
role_rows += f"""
|
||
<tr>
|
||
<td><span class="pill pill-muted">{html.escape(role)}</span></td>
|
||
<td>{html.escape(desc)}</td>
|
||
</tr>
|
||
"""
|
||
|
||
gitea_url = get_gitea_public_url()
|
||
gitea_org = read_env_value("GITEA_ORG", DEFAULT_GITEA_ORG)
|
||
clone_base = gitea_url or "<gitea-url>"
|
||
clone_cmd = html.escape(f"git clone {clone_base}/{gitea_org}/<app-id>.git", quote=True)
|
||
|
||
return page(
|
||
"Pro vývojáře",
|
||
f"""
|
||
<div class="card">
|
||
<h2><i class="fa-solid fa-code" aria-hidden="true"></i> Pro vývojáře</h2>
|
||
<p class="muted">
|
||
Přehled všech sekcí portálu, veřejných endpointů a rolí. Portál běží za reverzní proxy
|
||
na cestě <code>/portal</code>; jednotlivé služby na <code>/apps/<app-id></code>.
|
||
</p>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<h2><i class="fa-solid fa-server" aria-hidden="true"></i> Replikace serveru</h2>
|
||
<p class="muted">
|
||
Checklist a kopírovatelné příkazy pro čistou instalaci nebo obnovu AppFactory na nový Linux server.
|
||
</p>
|
||
<p><a class="btn" href="/portal/developers/server-replication"><i class="fa-solid fa-list-check" aria-hidden="true"></i> Replikace serveru / požadavky na nový server</a></p>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<h2><i class="fa-solid fa-cloud-arrow-up" aria-hidden="true"></i> Jak publikovat</h2>
|
||
<p class="muted">Postup od založení nové služby po nasazení změn.</p>
|
||
<ol class="publish-steps">
|
||
<li>
|
||
<strong>Vytvořte novou službu.</strong>
|
||
Otevřete <a href="/portal/new-app" target="_blank" rel="noopener">formulář Nová služba</a>;
|
||
vznikne Gitea repozitář, workspace a první nasazení.
|
||
</li>
|
||
<li>
|
||
<strong>Naklonujte repozitář.</strong>
|
||
<div class="cmd-row">
|
||
<input readonly value="{clone_cmd}" id="publish-clone">
|
||
<button type="button" onclick="return copyText(this)"><i class="fa-solid fa-copy" aria-hidden="true"></i> Kopírovat</button>
|
||
</div>
|
||
<span class="muted">Přesný příkaz pro konkrétní službu (HTTP i SSH) najdete také na stránce
|
||
<a href="/portal/apps">Služby</a>.</span>
|
||
</li>
|
||
<li>
|
||
<strong>Upravte kód.</strong>
|
||
Buď se řiďte souborem <code>AGENTS.md</code> v kořenové složce projektu, nebo úpravy
|
||
nechte provést AI (agent si <code>AGENTS.md</code> přečte sám).
|
||
</li>
|
||
<li>
|
||
<strong>Commit a push.</strong>
|
||
Zacommitujte a pushněte změny — build a nasazení se spustí automaticky
|
||
(worker je vždy spuštěný a změnu rovnou zpracuje).
|
||
</li>
|
||
<li>
|
||
<strong>Zkontrolujte výsledek.</strong>
|
||
Stav nasazení a běh služby ověříte na stránce
|
||
<a href="/portal/apps">Služby</a> (stav, health, dokumentace).
|
||
</li>
|
||
</ol>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<h2><i class="fa-solid fa-diagram-project" aria-hidden="true"></i> Sekce portálu (agendy)</h2>
|
||
<table>
|
||
<tr>
|
||
<th>Sekce</th>
|
||
<th>Odkaz</th>
|
||
<th>Popis</th>
|
||
<th>Role</th>
|
||
</tr>
|
||
{section_rows}
|
||
</table>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<h2><i class="fa-solid fa-plug" aria-hidden="true"></i> Veřejné / API endpointy</h2>
|
||
<table>
|
||
<tr>
|
||
<th>Metoda</th>
|
||
<th>Cesta</th>
|
||
<th>Popis</th>
|
||
</tr>
|
||
{endpoint_rows}
|
||
</table>
|
||
<p class="muted">
|
||
Veřejný katalog <code>/apps</code> vrací JSON se seznamem služeb a odkazy na jejich dokumentaci.
|
||
</p>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<h2><i class="fa-solid fa-user-shield" aria-hidden="true"></i> Role a oprávnění</h2>
|
||
<table>
|
||
<tr>
|
||
<th>Role</th>
|
||
<th>Co může</th>
|
||
</tr>
|
||
{role_rows}
|
||
</table>
|
||
</div>
|
||
""",
|
||
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ý <pre> + tlačítko Kopírovat)."""
|
||
escaped = html.escape(code.strip("\n"))
|
||
return (
|
||
'<div class="code-block">'
|
||
'<button type="button" class="copy-button code-copy" onclick="return copyText(this)">'
|
||
'<i class="fa-solid fa-copy" aria-hidden="true"></i> Kopírovat</button>'
|
||
f'<pre>{escaped}</pre>'
|
||
'</div>'
|
||
)
|
||
|
||
|
||
def _text_block(text: str) -> str:
|
||
"""Blok prostého výpisu (např. chybová hláška) bez kopírování."""
|
||
return f'<pre class="text-block">{html.escape(text.strip(chr(10)))}</pre>'
|
||
|
||
|
||
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([
|
||
"""
|
||
<div class="card">
|
||
<h2><i class="fa-solid fa-server" aria-hidden="true"></i> Replikace serveru / požadavky na nový server</h2>
|
||
<p class="muted">
|
||
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.
|
||
</p>
|
||
<p><a href="/portal/developers">← Zpět na Pro vývojáře</a></p>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<h2><i class="fa-solid fa-clipboard-check" aria-hidden="true"></i> 1. Požadavky na nový server</h2>
|
||
<p class="muted">Nový server musí mít:</p>
|
||
<ul class="req-list">
|
||
<li>Linux server, ideálně Ubuntu/Debian</li>
|
||
<li>Docker</li>
|
||
<li>Docker Compose plugin</li>
|
||
<li>Git</li>
|
||
<li>curl</li>
|
||
<li>bash</li>
|
||
<li>sqlite3</li>
|
||
<li>openssh-client</li>
|
||
<li>přístup k repozitářům v Gitea</li>
|
||
<li>funkční DNS pro:
|
||
<ul>
|
||
<li>services.csbot.cz</li>
|
||
<li>git.csbot.cz</li>
|
||
<li>registry.csbot.cz</li>
|
||
</ul>
|
||
</li>
|
||
<li>adresář <code>/opt/appfactory</code></li>
|
||
<li>Docker síť používanou AppFactory</li>
|
||
<li>volumes/adresáře pro:
|
||
<ul>
|
||
<li>/opt/appfactory/config</li>
|
||
<li>/opt/appfactory/data</li>
|
||
<li>/opt/appfactory/workspace</li>
|
||
<li>/opt/appfactory/backups</li>
|
||
<li>/opt/appfactory/logs</li>
|
||
</ul>
|
||
</li>
|
||
</ul>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<h2><i class="fa-solid fa-download" aria-hidden="true"></i> 2. Základní instalace nového serveru</h2>
|
||
<p class="muted">Před spuštěním bootstrapu musí být na serveru nainstalováno:</p>
|
||
""",
|
||
_code_block(APT_INSTALL),
|
||
"""
|
||
<h3>Docker a Docker Compose plugin</h3>
|
||
""",
|
||
_code_block(DOCKER_INSTALL),
|
||
"<p class=\"muted\">Ověření:</p>",
|
||
_code_block(DOCKER_VERIFY),
|
||
"""
|
||
<h3>Docker skupina</h3>
|
||
""",
|
||
_code_block(DOCKER_GROUP),
|
||
"<p class=\"muted\">Případně i pro administrátora:</p>",
|
||
_code_block(DOCKER_GROUP_ADMIN),
|
||
"""
|
||
<h3>Povinné utility používané AppFactory</h3>
|
||
<p class="muted">Používají je skripty AppFactory:</p>
|
||
""",
|
||
_text_block(UTILITIES),
|
||
"""
|
||
<h3>DNS požadavky</h3>
|
||
<p class="muted">Musí fungovat:</p>
|
||
""",
|
||
_text_block(DNS_LIST),
|
||
"<p class=\"muted\">Kontrola:</p>",
|
||
_code_block(DNS_CHECK),
|
||
"""
|
||
<h3>Firewall</h3>
|
||
<p class="muted">Musí být otevřené:</p>
|
||
""",
|
||
_text_block(FIREWALL),
|
||
"""
|
||
<h3>Struktura AppFactory</h3>
|
||
<p class="muted">Musí existovat:</p>
|
||
""",
|
||
_text_block(STRUCTURE),
|
||
"""
|
||
<h3>Git konfigurace (host)</h3>
|
||
""",
|
||
_code_block(GIT_CONFIG_HOST),
|
||
"""
|
||
<h3>Ověření serveru</h3>
|
||
<p class="muted">Před bootstrapem musí projít:</p>
|
||
""",
|
||
_code_block(SERVER_VERIFY),
|
||
"<p class=\"muted\">Pokud některý příkaz selže, server není připraven pro AppFactory.</p>",
|
||
"</div>",
|
||
"""
|
||
<div class="card">
|
||
<h2><i class="fa-solid fa-lock" aria-hidden="true"></i> 3. Povinné nastavení práv</h2>
|
||
<p class="muted">
|
||
V AppFactory nechceme řešit opakované chyby typu:
|
||
</p>
|
||
<ul class="req-list">
|
||
<li>Permission denied</li>
|
||
<li>Read-only file system</li>
|
||
<li>detected dubious ownership</li>
|
||
<li>operation not permitted</li>
|
||
<li>Git safe.directory problém</li>
|
||
</ul>
|
||
<p class="muted">
|
||
Proto má být pro AppFactory část serveru sjednocené právo zápisu pro relevantní procesy.
|
||
</p>
|
||
<p class="alert alert-danger">
|
||
<strong>Důležité:</strong> Neaplikovat <code>chmod 777</code> na celý Linux server.
|
||
Aplikovat pouze na <code>/opt/appfactory</code> a na konkrétní problematické runtime adresáře.
|
||
</p>
|
||
|
||
<h3>Serverový příkaz (oprava práv)</h3>
|
||
""",
|
||
_code_block(FIX_PERMISSIONS),
|
||
"""
|
||
<h3>Oprava Git safe.directory v kontejnerech</h3>
|
||
""",
|
||
_code_block(CONTAINER_GIT),
|
||
"</div>",
|
||
"""
|
||
<div class="card">
|
||
<h2><i class="fa-solid fa-code-branch" aria-hidden="true"></i> 4. Gitea – sessions</h2>
|
||
<p class="muted">Pokud Gitea spadne na chybu:</p>
|
||
""",
|
||
_text_block(GITEA_PANIC),
|
||
"<p class=\"muted\">Použij:</p>",
|
||
_code_block(GITEA_SESSIONS_FIX),
|
||
"</div>",
|
||
"""
|
||
<div class="card">
|
||
<h2><i class="fa-solid fa-link-slash" aria-hidden="true"></i> 5. Gitea – hooks</h2>
|
||
<p class="muted">Pokud Gitea ukazuje:</p>
|
||
""",
|
||
_text_block(GITEA_HOOKS_BROKEN),
|
||
"<p class=\"muted\">Oprava:</p>",
|
||
_code_block(GITEA_HOOKS_FIX),
|
||
"</div>",
|
||
"""
|
||
<div class="card">
|
||
<h2><i class="fa-solid fa-gears" aria-hidden="true"></i> 6. docker compose – env proměnné</h2>
|
||
<p class="muted">Pokud docker compose hlásí:</p>
|
||
""",
|
||
_text_block(COMPOSE_ENV_ERRORS),
|
||
"<p class=\"muted\">Použij:</p>",
|
||
_code_block(COMPOSE_ENV_EXPORT),
|
||
"</div>",
|
||
"""
|
||
<div class="card">
|
||
<h2><i class="fa-solid fa-hard-drive" aria-hidden="true"></i> 7. Mounty</h2>
|
||
<p class="muted">Portál musí mít RW přístup ke konfiguraci:</p>
|
||
""",
|
||
_code_block(MOUNT_YAML),
|
||
"<p class=\"muted\">Nepoužívat read-only mount pro:</p>",
|
||
_text_block(READONLY_PATH),
|
||
"<p class=\"muted\">protože editor appfactory.env pak končí chybou:</p>",
|
||
_text_block(READONLY_ERROR),
|
||
"</div>",
|
||
])
|
||
|
||
return page("Replikace serveru", body, user=user)
|