From eaa9c12ebc30b34c7dd93f4f8b2597d500d775ee Mon Sep 17 00:00:00 2001 From: gitea-actions Date: Sat, 22 Aug 2026 19:12:57 +0200 Subject: [PATCH] =?UTF-8?q?Nasazen=C3=AD=20na=20server:=20Proxmox=20LXC=20?= =?UTF-8?q?a=20Docker?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Aby se to dalo nechat běžet pořád, ne jen spouštět večer na notebooku. pve-kostky.sh se pouští na uzlu Proxmoxu, založí kontejner a předá řízení lxc-install.sh, který uvnitř stáhne vydanou binárku, ověří kontrolní součet a zapíše službu. Ty dva jsou oddělené schválně: instalátor o Proxmoxu nic neví, takže se dá pustit i v kontejneru, který sis založil sám. Podoba je odkoukaná od community-scripts.org, ale nic z jejich frameworku se nestahuje — jejich build.func si instalační skript hledá natvrdo ve vlastním repozitáři, takže mimo něj nefunguje. Docker se staví ze zdrojáků, ne z binárky: image pak jde sestavit pro obě architektury a nezávisí na tom, jestli release proběhl. Zamčené závislosti z minula dělají sestavení opakovatelné. Compose používá síť hostitele, a to je podstatné. V bridge režimu vidí kontejner uvnitř adresu 172.17.0.2, tabule ji poctivě nabídne a udělá na ni QR kód — jenže z telefonu je nedosažitelná. Ověřeno, ne odhadnuto; kdo bridge potřebuje, musí nastavit DICE_HOST. Aplikace sama nepotřebovala změnit nic. DICE_DB, DICE_PORT a DICE_HOST pokrývají obojí a v LXC má kontejner vlastní adresu v LAN, takže hledání adres i QR kód fungují beze změny. --- .dockerignore | 20 ++++++ README.md | 60 +++++++++++++++++ deploy/Dockerfile | 47 +++++++++++++ deploy/compose.yaml | 45 +++++++++++++ deploy/lxc-install.sh | 124 ++++++++++++++++++++++++++++++++++ deploy/pve-kostky.sh | 153 ++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 449 insertions(+) create mode 100644 .dockerignore create mode 100644 deploy/Dockerfile create mode 100644 deploy/compose.yaml create mode 100755 deploy/lxc-install.sh create mode 100755 deploy/pve-kostky.sh diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..4177d60 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,20 @@ +# Do image patří jen to, co appka za běhu potřebuje. +.git/ +.github/ +.gitea/ +.venv/ +.build/ +.pytest_cache/ +__pycache__/ +dist/ +tests/ +deploy/ +*.db +*.spec +build.sh +dice.py +verze.txt +README.md +requirements*.in +requirements-dev.txt +requirements-build.txt diff --git a/README.md b/README.md index a9b4069..c96a1c6 100644 --- a/README.md +++ b/README.md @@ -116,6 +116,65 @@ hlášku vysypal varování, že takhle se to nemá, a měl by pravdu. Počítá se s domácí sítí — appka nemá přihlašování a kdokoli na stejné WiFi může zapisovat. +## Nasazení na server + +Kdo nechce každý večer spouštět binárku na notebooku, může to nechat běžet +pořád. Obojí níž počítá **jen s domácí sítí** — appka nemá přihlašování +a ani jedno nasazení ji nijak nezabezpečuje. Do internetu to nepatří. + +### Proxmox (LXC) + +Skript se pouští v terminálu uzlu Proxmoxu jako root. Založí kontejner, +nastaví ho, stáhne poslední vydanou binárku, ověří kontrolní součet a zapne +službu: + +```bash +bash deploy/pve-kostky.sh +``` + +Na konci vypíše adresu, na které počitadlo poslouchá. Výchozí nastavení je +1 jádro, 512 MB, 4 GB disku a Debian 13; přebíjí se proměnnými: + +```bash +MEMORY=1024 STORAGE=local-zfs PORT=8080 bash deploy/pve-kostky.sh +``` + +Aktualizace na novější vydání je totéž s číslem kontejneru: + +```bash +bash deploy/pve-kostky.sh update 123 +``` + +Podoba je odkoukaná od [community-scripts.org](https://community-scripts.org), +ale nic z jejich frameworku se nestahuje — jejich `build.func` si instalační +skript hledá natvrdo ve vlastním repozitáři, takže mimo něj nefunguje. + +`deploy/lxc-install.sh` o Proxmoxu nic neví, takže se dá pustit i v LXC nebo +virtuálu, který sis založil sám. Je idempotentní; druhé spuštění jen vymění +binárku za nejnovější. + +### Docker + +```bash +cd deploy && docker compose up -d +``` + +Staví se ze zdrojáků, takže image jde sestavit pro amd64 i arm64 a nezávisí +na tom, jestli release pro danou architekturu proběhl. Databáze leží +v pojmenovaném svazku, appka běží pod nerootovým uživatelem. + +**Pozor na síť.** Compose schválně používá `network_mode: host`. V bridge +režimu kontejner uvnitř vidí adresu jako `172.17.0.2`, tabule ji poctivě +nabídne a vygeneruje na ni QR kód — jenže z telefonu je nedosažitelná. +Kdo bridge potřebuje, musí nastavit `DICE_HOST` na adresu hostitele v LAN. + +| | LXC | Docker | +|---|---|---| +| Odkud | vydaná binárka z releasu | zdrojáky | +| Databáze | `/var/lib/kostky/dice.db` | svazek `kostky-data` | +| Aktualizace | `pve-kostky.sh update ` | `docker compose build --pull` | +| Adresa a QR | funguje samo | potřeba síť hostitele | + ## Když se telefon nepřipojí Adresy se hledají při každém načtení tabule, takže přechod z domácí WiFi na @@ -154,6 +213,7 @@ při remíze na prvním místě se hraje dál. | `build.sh`, `kostky.spec` | stavba binárky | | `.gitea/workflows/` | testy a linuxová binárka doma | | `.github/workflows/` | binárky pro Windows a macOS | +| `deploy/` | nasazení na server: Proxmox LXC a Docker | Terminálová verze zůstává funkční jako záloha: diff --git a/deploy/Dockerfile b/deploy/Dockerfile new file mode 100644 index 0000000..4342e00 --- /dev/null +++ b/deploy/Dockerfile @@ -0,0 +1,47 @@ +# Počitadlo kostek jako kontejner. +# +# Staví se ze zdrojáků, ne z vydané binárky: image jde pak sestavit pro +# amd64 i arm64 a nezávisí na tom, jestli pro danou architekturu release +# proběhl. Závislosti jsou zamčené v requirements.txt, takže je sestavení +# opakovatelné. +# +# docker build -f deploy/Dockerfile -t kostky . +# +# Sestavuje se z kořene repozitáře, ne z deploy/. +FROM python:3.11-slim + +# Verze se jinak nemá kde vzít — v image není git ani zabalená binárka. +ARG VERSION=neznámá + +RUN useradd --system --uid 1000 --home-dir /app --shell /usr/sbin/nologin kostky + +WORKDIR /app + +# Závislosti zvlášť a dřív než kód, ať se vrstva s pipem přestavuje jen tehdy, +# když se opravdu změnily. +COPY requirements.txt ./ +RUN pip install --no-cache-dir -r requirements.txt + +COPY console.py core.py net.py stats.py storage.py version.py web.py ./ +COPY templates/ templates/ +COPY static/ static/ +RUN echo "$VERSION" > verze.txt + +# Adresář pro databázi musí vzniknout už tady a patřit správnému uživateli — +# svazek, který Docker založí sám, by jinak patřil rootovi a appka běžící pod +# kostky by do něj nezapsala. +RUN install -d -o kostky -g kostky /data +VOLUME ["/data"] + +ENV DICE_DB=/data/dice.db \ + DICE_PORT=8000 + +EXPOSE 8000 +USER kostky + +# Bez curlu, ten ve slim image není. Stačí, že stránka odpoví — sahá i do +# databáze, takže se tím kontroluje i ta. +HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \ + CMD python -c "import os,urllib.request; urllib.request.urlopen('http://127.0.0.1:' + os.environ.get('DICE_PORT','8000') + '/')" + +CMD ["python", "web.py"] diff --git a/deploy/compose.yaml b/deploy/compose.yaml new file mode 100644 index 0000000..afffe17 --- /dev/null +++ b/deploy/compose.yaml @@ -0,0 +1,45 @@ +# Počitadlo kostek v Dockeru. +# +# cd deploy && docker compose up -d +# +# Počítá se s domácí sítí. Appka nemá přihlašování, tohle ji nijak +# nezabezpečuje a do internetu nepatří. + +services: + kostky: + build: + context: .. + dockerfile: deploy/Dockerfile + args: + VERSION: ${VERSION:-vlastní sestavení} + image: kostky:latest + container_name: kostky + restart: unless-stopped + + # Python jako PID 1 nesklízí osiřelé procesy a hůř reaguje na signály. + init: true + + # Síť hostitele schválně. V bridge režimu kontejner uvnitř vidí adresu + # jako 172.17.0.2, tabule ji poctivě nabídne a vygeneruje na ni QR kód — + # jenže z telefonu je nedosažitelná. Se sítí hostitele appka najde tu + # adresu, na kterou se telefon opravdu dovolá. + # + # Kdo bridge potřebuje, ať zakomentuje řádek níž, odkomentuje ports + # a nastaví DICE_HOST na adresu hostitele v LAN: + # + # ports: + # - "8000:8000" + # environment: + # DICE_HOST: 192.168.1.10 + network_mode: host + + environment: + DICE_PORT: 8000 + DICE_DB: /data/dice.db + + volumes: + - kostky-data:/data + +volumes: + # Zálohu si stáhneš rovnou z appky přes /export, takže se sem nemusí lézt. + kostky-data: diff --git a/deploy/lxc-install.sh b/deploy/lxc-install.sh new file mode 100755 index 0000000..fbdc1c5 --- /dev/null +++ b/deploy/lxc-install.sh @@ -0,0 +1,124 @@ +#!/usr/bin/env bash +# Instalace počitadla uvnitř kontejneru. Pouští se přes pve-kostky.sh, ne ručně. +# +# Oddělené od skriptu pro Proxmox schválně: tenhle běží uvnitř Debianu a nic +# o Proxmoxu neví, takže se dá pustit i v LXC, který sis založil sám, nebo ve +# virtuálu. Je idempotentní — po druhém spuštění máš nejnovější binárku. +set -euo pipefail + +GITEA="${GITEA:-https://gitea.spacilovi.eu}" +REPO="${REPO:-david-spacil/dice-counter}" +PORT="${PORT:-8000}" +VERSION="${VERSION:-}" # prázdné = poslední release + +USER_NAME=kostky +HOME_DIR=/opt/kostky +DATA_DIR=/var/lib/kostky + +msg() { echo -e "\e[1;34m →\e[0m $*"; } +ok() { echo -e "\e[1;32m ✓\e[0m $*"; } +die() { echo -e "\e[1;31m ✗\e[0m $*" >&2; exit 1; } + +# --- co vlastně stahovat ----------------------------------------------------- + +case "$(dpkg --print-architecture)" in + amd64) ASSET=kostky-linux-x86_64 ;; + arm64) ASSET=kostky-linux-arm64 ;; + *) die "Pro architekturu $(dpkg --print-architecture) binárka není." ;; +esac + +msg "Doinstalovávám, co je potřeba ke stažení" +export DEBIAN_FRONTEND=noninteractive +apt-get update -qq +apt-get install -y -qq curl ca-certificates >/dev/null +ok "Základ je hotový" + +if [ -z "$VERSION" ]; then + msg "Zjišťuji poslední vydanou verzi" + # Jen tag; adresa přílohy se z něj složí, takže tu nepotřebujeme jq. + VERSION=$(curl -fsSL "$GITEA/api/v1/repos/$REPO/releases/latest" \ + | grep -o '"tag_name":[[:space:]]*"[^"]*"' | head -1 | cut -d'"' -f4) + [ -n "$VERSION" ] || die "Nepodařilo se zjistit poslední verzi z $GITEA." +fi +ok "Instaluje se $VERSION ($ASSET)" + +# --- stažení a ověření ------------------------------------------------------- + +BASE="$GITEA/$REPO/releases/download/$VERSION" +TMP=$(mktemp -d) +trap 'rm -rf "$TMP"' EXIT + +msg "Stahuji binárku" +curl -fsSL -o "$TMP/$ASSET" "$BASE/$ASSET" \ + || die "$ASSET pro $VERSION na releasu není. Vydání pro tuhle architekturu možná ještě neproběhlo." +curl -fsSL -o "$TMP/$ASSET.sha256" "$BASE/$ASSET.sha256" \ + || die "Chybí kontrolní součet — radši nic neinstaluju." + +msg "Ověřuji kontrolní součet" +( cd "$TMP" && sha256sum -c "$ASSET.sha256" >/dev/null ) \ + || die "Kontrolní součet nesedí. Stažený soubor zahazuju." +ok "Součet sedí" + +# --- uživatel, soubory, služba ---------------------------------------------- + +id -u "$USER_NAME" >/dev/null 2>&1 \ + || useradd --system --home-dir "$HOME_DIR" --shell /usr/sbin/nologin "$USER_NAME" + +install -d -o root -g root -m 755 "$HOME_DIR" + +# Služba se zastaví, jen když už běží — při první instalaci ještě neexistuje. +systemctl is-active --quiet kostky && systemctl stop kostky +install -o root -g root -m 755 "$TMP/$ASSET" "$HOME_DIR/kostky" +echo "$VERSION" > "$HOME_DIR/verze" +ok "Binárka je na místě" + +msg "Zapisuji službu" +cat > /etc/systemd/system/kostky.service <&2; exit 1; } + +header() { + echo + echo -e "\e[1;36m $APP\e[0m — počitadlo kostek do LXC" + echo -e " \e[2m$REPO_URL\e[0m" + echo +} + +# --- kontroly, než se něco založí ------------------------------------------- + +command -v pct >/dev/null || die "Tohle patří na uzel Proxmoxu — pct tu není." +[ "$(id -u)" -eq 0 ] || die "Spusť to jako root." +[ -f "$INSTALLER" ] || die "Vedle skriptu chybí lxc-install.sh." + +# --- instalace do kontejneru ------------------------------------------------- + +install_into() { + local ctid="$1" + + msg "Posílám instalátor do kontejneru $ctid" + pct push "$ctid" "$INSTALLER" /root/lxc-install.sh --perms 0755 + + pct exec "$ctid" -- env PORT="$PORT" bash /root/lxc-install.sh +} + +address_of() { + local ctid="$1" + pct exec "$ctid" -- hostname -I 2>/dev/null | awk '{print $1}' +} + +hotovo() { + local ctid="$1" ip jmeno + ip=$(address_of "$ctid") + jmeno=$(pct config "$ctid" | awk -F': ' '/^hostname:/ {print $2}') + + echo + ok "Hotovo. Kontejner $ctid ($jmeno) běží." + echo + echo -e " Zápisník na notebook: \e[1;32mhttp://${ip}:${PORT}/board\e[0m" + echo -e " Zadávání z telefonu: \e[1;32mhttp://${ip}:${PORT}/\e[0m" + echo + echo -e " \e[2mDatabáze: /var/lib/kostky/dice.db (uvnitř kontejneru)" + echo -e " Log: pct exec $ctid -- journalctl -u kostky -f" + echo -e " Konzole: pct enter $ctid\e[0m" + echo +} + +# --- aktualizace ------------------------------------------------------------- + +if [ "${1:-}" = "update" ]; then + ctid="${2:-}" + [ -n "$ctid" ] || die "Řekni který: bash pve-kostky.sh update " + pct status "$ctid" >/dev/null 2>&1 || die "Kontejner $ctid neexistuje." + + header + [ "$(pct status "$ctid")" = "status: running" ] || { + msg "Startuji kontejner" + pct start "$ctid" + sleep 5 + } + + install_into "$ctid" + hotovo "$ctid" + exit 0 +fi + +# --- nový kontejner ---------------------------------------------------------- + +header + +CTID="${CTID:-$(pvesh get /cluster/nextid)}" +msg "Kontejner dostane číslo $CTID" + +msg "Hledám šablonu Debianu $OSVERSION" +pveam update >/dev/null 2>&1 || true +TEMPLATE=$(pveam available --section system \ + | awk '{print $2}' | grep "^debian-${OSVERSION}-standard" | sort -V | tail -1) +[ -n "$TEMPLATE" ] || die "Šablona pro Debian $OSVERSION není k dispozici." + +if ! pveam list "$TEMPLATE_STORAGE" 2>/dev/null | grep -q "$TEMPLATE"; then + msg "Stahuji $TEMPLATE" + pveam download "$TEMPLATE_STORAGE" "$TEMPLATE" >/dev/null +fi +ok "Šablona $TEMPLATE" + +msg "Zakládám kontejner" +pct create "$CTID" "$TEMPLATE_STORAGE:vztmpl/$TEMPLATE" \ + --hostname "$CT_HOSTNAME" \ + --cores "$CORES" \ + --memory "$MEMORY" \ + --swap 256 \ + --rootfs "$STORAGE:$DISK" \ + --net0 "name=eth0,bridge=$BRIDGE,ip=dhcp" \ + --unprivileged "$UNPRIVILEGED" \ + --ostype debian \ + --onboot 1 \ + --description "$APP — $REPO_URL" >/dev/null +ok "Kontejner $CTID založen" + +msg "Startuji" +pct start "$CTID" + +msg "Čekám na síť" +for _ in $(seq 60); do + [ -n "$(address_of "$CTID")" ] \ + && pct exec "$CTID" -- getent hosts deb.debian.org >/dev/null 2>&1 \ + && break + sleep 2 +done +[ -n "$(address_of "$CTID")" ] || die "Kontejner nedostal adresu. Sedí bridge $BRIDGE?" +ok "Adresa $(address_of "$CTID")" + +install_into "$CTID" +hotovo "$CTID"