This commit is contained in:
JiriUhlir
2026-06-17 12:47:53 +02:00
parent 223f59d750
commit bd256d565c
8 changed files with 550 additions and 34 deletions
+375
View File
@@ -104,6 +104,14 @@ def developers_page(user=Depends(require_user)):
</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>
@@ -181,3 +189,370 @@ def developers_page(user=Depends(require_user)):
""",
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">&larr; 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)