From fcfa0e489bdeba6563002c252d2632e0a0855a04 Mon Sep 17 00:00:00 2001 From: gitea-actions Date: Fri, 21 Aug 2026 17:12:25 +0200 Subject: [PATCH 1/5] =?UTF-8?q?bin=C3=A1rka=20ke=20sta=C5=BEen=C3=AD=20a?= =?UTF-8?q?=20jasn=C3=A9=20m=C3=ADsto=20pro=20datab=C3=A1zi?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Stáhni a spusť: jednosouborová binárka pro Linux, uvnitř Python, Flask, šablony i styly. Staví se ./build.sh v kontejneru na AlmaLinux 8, protože binárka slinkovaná proti glibc z Fedory 44 by nešla spustit nikde se starším systémem. Databáze tím dostává dvě různá místa. Ze zdrojáků zůstává dice.db v pracovním adresáři, ať se dá mít víc sad vedle sebe. Z binárky jde do ~/.local/share/kostky/ — binárka se rozbaluje do dočasného adresáře a pouští se odkudkoli, takže relativní cesta by databázi rozsypala po disku. DICE_DB přebije obojí. Server teď cestu k databázi vypisuje při startu, ať se na ni nemusí ptát. Co-Authored-By: Claude Opus 5 --- .gitignore | 2 ++ README.md | 54 +++++++++++++++++++++++++++++++++++++++---- build.sh | 30 ++++++++++++++++++++++++ kostky.spec | 26 +++++++++++++++++++++ storage.py | 24 +++++++++++++++++-- tests/test_storage.py | 33 ++++++++++++++++++++++++++ web.py | 18 ++++++++++++++- 7 files changed, 180 insertions(+), 7 deletions(-) create mode 100755 build.sh create mode 100644 kostky.spec diff --git a/.gitignore b/.gitignore index 5351aa3..d2f10d9 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ __pycache__/ .venv/ *.db .pytest_cache/ +dist/ +build/ diff --git a/README.md b/README.md index 3803366..d4cdec2 100644 --- a/README.md +++ b/README.md @@ -8,12 +8,25 @@ ten samý Adam jako z minulého měsíce. Žádné účty, žádná hesla. ## Spuštění +Stáhni binárku z [releases](https://gitea.spacilovi.eu/david-spacil/dice-counter/releases) +a spusť ji: + +```bash +chmod +x kostky +./kostky +``` + +Nic se neinstaluje — Python, Flask i šablony jsou uvnitř. Zatím jen pro Linux +na x86-64; postavené proti glibc 2.28, takže jede na všem od RHEL 8, +Debianu 10 a Ubuntu 20.04 výš. + +Ze zdrojáků to je stejně krátké: + ```bash uv run web.py ``` -To je celé. Závislosti si skript nese v hlavičce (PEP 723) a `uv` je obstará -sám — není co instalovat ani aktivovat. +Závislosti si skript nese v hlavičce (PEP 723) a `uv` je obstará sám. Server vypíše všechny adresy, na kterých je dostupný: @@ -25,14 +38,33 @@ Počitadlo je dostupné na: ``` Na notebooku otevři `/board`, naskenuj QR kód telefonem a hraj. Tabule -zobrazuje i zbylé adresy — když jedna nefunguje, zkus další. +zobrazuje i zbylé adresy — když jedna nefunguje, zkus další. Zastavuje se +`Ctrl+C`. | Proměnná | K čemu je | Výchozí | |---|---|---| -| `DICE_DB` | soubor s databází | `dice.db` | +| `DICE_DB` | soubor s databází | podle způsobu spuštění, viz níž | | `DICE_PORT` | port | `8000` | | `DICE_HOST` | pevná adresa; vypne hledání | hledá se za běhu | +## Kde jsou data + +Jeden soubor SQLite. Založí se sám při prvním načtení stránky, cestu k němu +server vypíše při startu. + +| Spuštěno | Databáze | +|---|---| +| binárkou | `~/.local/share/kostky/dice.db` | +| ze zdrojáků | `dice.db` v pracovním adresáři | + +Binárka se při každém spuštění rozbaluje do dočasného adresáře a pouští se +odkudkoli, takže relativní cesta by databázi rozsypala po disku — proto +napevno domovský adresář. Ze zdrojáků zůstává relativní, ať se dá mít víc +sad vedle sebe. + +Zálohovat i stěhovat jde prostým zkopírováním souboru; `DICE_DB` si ho +najde kdekoli. + Počítá se s domácí sítí — appka nemá přihlašování a kdokoli na stejné WiFi může zapisovat. @@ -69,6 +101,7 @@ při remíze na prvním místě se hraje dál. | `stats.py` | síň slávy a kariérní statistiky | | `web.py` | Flask aplikace | | `dice.py` | totéž v terminálu, bez ukládání | +| `build.sh`, `kostky.spec` | stavba binárky | Terminálová verze zůstává funkční jako záloha: @@ -93,6 +126,19 @@ sudo dnf install python3-flask python3-qrcode python3-tabulate python3 web.py ``` +## Vlastní binárka + +```bash +./build.sh +``` + +Staví se v kontejneru (podman nebo docker) na AlmaLinux 8, ne na hostiteli. +Binárka slinkovaná proti glibc z Fedory 44 by nešla spustit nikde se starším +systémem — glibc drží zpětnou kompatibilitu, ne dopřednou. Výsledek je +v `dist/kostky`. + +Terminálová verze součástí binárky není; ta se pouští ze zdrojáků. + ## Testy ```bash diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..7238fe5 --- /dev/null +++ b/build.sh @@ -0,0 +1,30 @@ +#!/usr/bin/env bash +# Postaví jednosouborovou binárku pro Linux do dist/kostky. +# +# Staví se v kontejneru s glibc 2.28 (AlmaLinux 8), ne na hostiteli. Binárka +# slinkovaná proti glibc z Fedory 44 by nešla spustit nikde se starším +# systémem — glibc drží zpětnou kompatibilitu, ne dopřednou. Postavené na +# staré glibc poběží i na nové. +set -euo pipefail + +IMAGE=${IMAGE:-docker.io/library/almalinux:8} +PYTHON=${PYTHON:-python3.11} +ENGINE=${ENGINE:-$(command -v podman || command -v docker)} + +# Rootless podman mapuje roota v kontejneru na tebe, docker ne — tam by +# dist/ zůstalo cizí. +FIXUP=":" +[[ "$ENGINE" == *docker ]] && FIXUP="chown -R $(id -u):$(id -g) dist" + +"$ENGINE" run --rm -v "$PWD:/src:z" -w /src "$IMAGE" bash -euc " + dnf install -y -q python3.11 binutils + $PYTHON -m venv /tmp/build + /tmp/build/bin/pip install --quiet --upgrade pip + /tmp/build/bin/pip install --quiet flask qrcode pyinstaller + /tmp/build/bin/pyinstaller --clean --noconfirm \ + --distpath dist --workpath /tmp/work kostky.spec + $FIXUP +" + +echo +ls -lh dist/kostky diff --git a/kostky.spec b/kostky.spec new file mode 100644 index 0000000..857bd78 --- /dev/null +++ b/kostky.spec @@ -0,0 +1,26 @@ +# -*- mode: python ; coding: utf-8 -*- +"""Jedna binárka se vším: Python, Flask, šablony i styly. + +Staví se přes `./build.sh`, ne ručně — ta se stará o to, aby výsledek jel +i na starších systémech, než je ten tvůj. +""" + +analysis = Analysis( + ["web.py"], + pathex=["."], + datas=[("templates", "templates"), ("static", "static")], + noarchive=False, +) + +pyz = PYZ(analysis.pure) + +exe = EXE( + pyz, + analysis.scripts, + analysis.binaries, + analysis.datas, + name="kostky", + console=True, + strip=True, + upx=False, +) diff --git a/storage.py b/storage.py index 12723da..35cce6e 100644 --- a/storage.py +++ b/storage.py @@ -10,12 +10,30 @@ Součty se nikde neukládají; stav hry se rekonstruuje načtením tahů do import os import re import sqlite3 +import sys from datetime import datetime from pathlib import Path from core import FINAL_SCORE, Game, Turn -DB_PATH = Path(os.environ.get("DICE_DB", "dice.db")) +def default_db() -> Path: + """Kam se ukládá databáze. + + Ze zdrojáků do pracovního adresáře — dá se tak mít víc rozehraných + sad vedle sebe a je vidět, s čím se pracuje. Z binárky do datového + adresáře podle XDG: binárka se spouští odkudkoli a rozbaluje se do + dočasného adresáře, takže relativní cesta by databázi rozsypala po disku. + """ + chosen = os.environ.get("DICE_DB") + if chosen: + return Path(chosen) + if getattr(sys, "frozen", False): + share = os.environ.get("XDG_DATA_HOME") or Path.home() / ".local" / "share" + return Path(share) / "kostky" / "dice.db" + return Path("dice.db") + + +DB_PATH = default_db() STATUS_RUNNING = "probíhá" STATUS_FINISHED = "dohráno" @@ -60,7 +78,9 @@ CREATE INDEX IF NOT EXISTS turns_game ON turns(game_id, id); def connect(path: Path | str | None = None) -> sqlite3.Connection: - conn = sqlite3.connect(path or DB_PATH) + target = Path(path or DB_PATH) + target.parent.mkdir(parents=True, exist_ok=True) + conn = sqlite3.connect(target) conn.row_factory = sqlite3.Row conn.execute("PRAGMA foreign_keys = ON") conn.executescript(SCHEMA) diff --git a/tests/test_storage.py b/tests/test_storage.py index af390b6..603d8df 100644 --- a/tests/test_storage.py +++ b/tests/test_storage.py @@ -1,3 +1,5 @@ +from pathlib import Path + import pytest import storage @@ -132,3 +134,34 @@ def test_hrac_nesmi_sedet_u_stolu_dvakrat(conn): def test_prazdne_jmeno(conn): with pytest.raises(ValueError): storage.get_or_create_player(conn, " ") + + +def test_umisteni_databaze_ze_zdrojaku(monkeypatch): + """Ze zdrojáků se ukládá do pracovního adresáře.""" + monkeypatch.delenv("DICE_DB", raising=False) + monkeypatch.delattr(storage.sys, "frozen", raising=False) + + assert storage.default_db() == Path("dice.db") + + +def test_umisteni_databaze_z_binarky(monkeypatch, tmp_path): + """Binárka se rozbaluje do dočasného adresáře, databáze tam nesmí.""" + monkeypatch.delenv("DICE_DB", raising=False) + monkeypatch.setattr(storage.sys, "frozen", True, raising=False) + monkeypatch.setenv("XDG_DATA_HOME", str(tmp_path)) + + assert storage.default_db() == tmp_path / "kostky" / "dice.db" + + +def test_dice_db_prebije_oboje(monkeypatch, tmp_path): + monkeypatch.setattr(storage.sys, "frozen", True, raising=False) + monkeypatch.setenv("DICE_DB", str(tmp_path / "jinde.db")) + + assert storage.default_db() == tmp_path / "jinde.db" + + +def test_connect_zalozi_chybejici_adresar(tmp_path): + conn = storage.connect(tmp_path / "novy" / "adresar" / "dice.db") + conn.close() + + assert (tmp_path / "novy" / "adresar" / "dice.db").exists() diff --git a/web.py b/web.py index 93bba45..2d66572 100644 --- a/web.py +++ b/web.py @@ -10,6 +10,8 @@ Server-rendered HTML, formuláře přes POST/redirect/GET, žádný build step. import os import socket +import sys +from pathlib import Path import qrcode from flask import (Flask, abort, g, redirect, render_template, request, @@ -21,7 +23,19 @@ import stats import storage from core import FINAL_SCORE, Game, GameOver -app = Flask(__name__) +def bundled(folder: str) -> str: + """Cesta k šablonám a stylům. + + Ze zdrojáků leží vedle tohoto souboru. V binárce je PyInstaller rozbalí + do dočasného adresáře, na který ukazuje `sys._MEIPASS`. + """ + root = Path(getattr(sys, "_MEIPASS", Path(__file__).parent)) + return str(root / folder) + + +app = Flask(__name__, + template_folder=bundled("templates"), + static_folder=bundled("static")) PORT = int(os.environ.get("DICE_PORT", 8000)) @@ -298,4 +312,6 @@ if __name__ == "__main__": else: print("Adresu se nepodařilo zjistit. Spusť server s DICE_HOST=.") + print(f"\nDatabáze: {storage.DB_PATH.resolve()}") + app.run(host="0.0.0.0", port=PORT, threaded=True) From cb3903672445f350f76eee1e3958e8bb6ebd4960 Mon Sep 17 00:00:00 2001 From: gitea-actions Date: Fri, 21 Aug 2026 17:18:39 +0200 Subject: [PATCH 2/5] =?UTF-8?q?release=20se=20stav=C3=AD=20s=C3=A1m=20z=20?= =?UTF-8?q?tagu?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Otagovaný commit postaví binárku na runneru, projede testy, zkusí ji nastartovat a pověsí ji na release. Nad pull requesty běží totéž bez posledního kroku, ať se rozbitá stavba pozná dřív než na tagu. Staví se v kontejneru na AlmaLinux 8 ze stejného důvodu jako build.sh — kvůli glibc. Záměrně bez hotových akcí z marketplace: na vlastním runneru tím odpadá starost, jestli je v kontejneru node. Co-Authored-By: Claude Opus 5 --- .gitea/workflows/binarka.yml | 93 ++++++++++++++++++++++++++++++++++++ README.md | 10 ++++ 2 files changed, 103 insertions(+) create mode 100644 .gitea/workflows/binarka.yml diff --git a/.gitea/workflows/binarka.yml b/.gitea/workflows/binarka.yml new file mode 100644 index 0000000..ca393de --- /dev/null +++ b/.gitea/workflows/binarka.yml @@ -0,0 +1,93 @@ +# Postaví binárku a pověsí ji na release. +# +# Běží v kontejneru na AlmaLinux 8, ne na runneru: binárka slinkovaná proti +# novější glibc by nešla spustit na starších systémech. Záměrně bez hotových +# akcí z marketplace — na vlastním runneru odpadá starost, jestli v kontejneru +# je node. +name: binárka + +on: + push: + tags: ["v*"] + pull_request: + workflow_dispatch: + +jobs: + linux: + runs-on: homelab + container: almalinux:8 + + steps: + - name: Nástroje + run: dnf install -y -q git python3.11 binutils + + - name: Zdrojáky + env: + TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + git init -q . + git remote add origin "${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}.git" + git config http.extraheader "Authorization: token ${TOKEN}" + git fetch -q --depth 1 origin "${GITHUB_REF}" + git checkout -q FETCH_HEAD + git config --unset http.extraheader + + - name: Prostředí + run: | + python3.11 -m venv /tmp/build + /tmp/build/bin/pip install --quiet --upgrade pip + /tmp/build/bin/pip install --quiet -r requirements.txt pyinstaller + + - name: Testy + run: /tmp/build/bin/pytest -q + + - name: Binárka + run: | + /tmp/build/bin/pyinstaller --clean --noconfirm \ + --distpath dist --workpath /tmp/work kostky.spec + ls -lh dist/kostky + + - name: Zkouška + run: | + XDG_DATA_HOME=/tmp/data DICE_PORT=8321 DICE_HOST=127.0.0.1 \ + ./dist/kostky > /tmp/server.log 2>&1 & + for _ in $(seq 30); do + curl -sf -o /dev/null http://127.0.0.1:8321/ && break + sleep 1 + done + curl -sf -o /dev/null http://127.0.0.1:8321/ + curl -sf -o /dev/null http://127.0.0.1:8321/static/style.css + curl -sf -o /dev/null http://127.0.0.1:8321/board + test -f /tmp/data/kostky/dice.db + cat /tmp/server.log + + - name: Na release + if: startsWith(github.ref, 'refs/tags/') + env: + TOKEN: ${{ secrets.GITHUB_TOKEN }} + ASSET: kostky-linux-x86_64 + run: | + api="${GITHUB_SERVER_URL}/api/v1/repos/${GITHUB_REPOSITORY}" + auth="Authorization: token ${TOKEN}" + + # Release může, ale nemusí existovat — na tag se dá kliknout ručně. + release=$(curl -s -H "$auth" "$api/releases/tags/${GITHUB_REF_NAME}") + id=$(printf '%s' "$release" | python3.11 -c \ + 'import json,sys; print(json.load(sys.stdin).get("id",""))' || true) + + if [ -z "$id" ]; then + id=$(curl -sf -X POST -H "$auth" -H "Content-Type: application/json" \ + -d "{\"tag_name\":\"${GITHUB_REF_NAME}\",\"name\":\"${GITHUB_REF_NAME}\"}" \ + "$api/releases" | python3.11 -c \ + 'import json,sys; print(json.load(sys.stdin)["id"])') + fi + + # Aby se dal build zopakovat, stará příloha stejného jména jde pryč. + curl -s -H "$auth" "$api/releases/$id/assets" | python3.11 -c \ + 'import json,os,sys; [print(a["id"]) for a in json.load(sys.stdin) if a["name"] == os.environ["ASSET"]]' \ + | while read -r old; do + curl -sf -X DELETE -H "$auth" "$api/releases/$id/assets/$old" + done + + curl -sf -X POST -H "$auth" -F "attachment=@dist/kostky" \ + "$api/releases/$id/assets?name=${ASSET}" diff --git a/README.md b/README.md index d4cdec2..a9fb15a 100644 --- a/README.md +++ b/README.md @@ -139,6 +139,16 @@ v `dist/kostky`. Terminálová verze součástí binárky není; ta se pouští ze zdrojáků. +Releasy si tohle dělají samy. Otagovaný commit spustí +`.gitea/workflows/binarka.yml`, ta binárku postaví, projede testy, zkusí +ji nastartovat a pověsí ji na release jako `kostky-linux-x86_64`: + +```bash +git tag v1.0.0 && git push origin v1.0.0 +``` + +Stejná stavba běží i nad každým pull requestem, jen se nikam nevěší. + ## Testy ```bash From 8aa54dc7a0b45cd16ca7bf87d24f99a769e2f9d3 Mon Sep 17 00:00:00 2001 From: gitea-actions Date: Fri, 21 Aug 2026 17:23:09 +0200 Subject: [PATCH 3/5] stavba bez kontejneru, proti Pythonu od uv MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Runner běží v host módu, bez dockeru, takže se v něm kontejner na AlmaLinux spustit nedá. Ukázalo se ale, že kontejner potřeba není: stačí stavět proti samostatnému CPythonu od uv. Ten je slinkovaný s glibc 2.17 a nic z hostitele se do binárky nedostane — změřeno objdumpem přes všechno, co se z ní rozbaluje. Výsledek je tedy přenositelnější než přes AlmaLinux 8 (2.28) a build.sh je o polovinu kratší. Co-Authored-By: Claude Opus 5 --- .gitea/workflows/binarka.yml | 57 ++++++++++++++++++------------------ .gitignore | 1 + README.md | 12 ++++---- build.sh | 32 ++++++++------------ 4 files changed, 47 insertions(+), 55 deletions(-) diff --git a/.gitea/workflows/binarka.yml b/.gitea/workflows/binarka.yml index ca393de..cb4d6bd 100644 --- a/.gitea/workflows/binarka.yml +++ b/.gitea/workflows/binarka.yml @@ -1,9 +1,8 @@ # Postaví binárku a pověsí ji na release. # -# Běží v kontejneru na AlmaLinux 8, ne na runneru: binárka slinkovaná proti -# novější glibc by nešla spustit na starších systémech. Záměrně bez hotových -# akcí z marketplace — na vlastním runneru odpadá starost, jestli v kontejneru -# je node. +# Runner běží v host módu, takže tu není kontejner ani hotové akce +# z marketplace — nic se nespoléhá na to, co má hostitel nainstalováno. +# O starou glibc se stará build.sh přes samostatný CPython od uv. name: binárka on: @@ -15,50 +14,50 @@ on: jobs: linux: runs-on: homelab - container: almalinux:8 steps: - - name: Nástroje - run: dnf install -y -q git python3.11 binutils - - name: Zdrojáky env: TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | git init -q . + git remote remove origin 2>/dev/null || true git remote add origin "${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}.git" git config http.extraheader "Authorization: token ${TOKEN}" git fetch -q --depth 1 origin "${GITHUB_REF}" - git checkout -q FETCH_HEAD git config --unset http.extraheader + git checkout -qf FETCH_HEAD + git clean -qfd - - name: Prostředí + - name: uv run: | - python3.11 -m venv /tmp/build - /tmp/build/bin/pip install --quiet --upgrade pip - /tmp/build/bin/pip install --quiet -r requirements.txt pyinstaller + echo "$HOME/.local/bin" >> "$GITHUB_PATH" + export PATH="$HOME/.local/bin:$PATH" + command -v uv >/dev/null || curl -LsSf https://astral.sh/uv/install.sh | sh + uv --version - name: Testy - run: /tmp/build/bin/pytest -q + run: uv run --with-requirements requirements.txt --no-project pytest -q - name: Binárka - run: | - /tmp/build/bin/pyinstaller --clean --noconfirm \ - --distpath dist --workpath /tmp/work kostky.spec - ls -lh dist/kostky + run: ./build.sh - name: Zkouška run: | - XDG_DATA_HOME=/tmp/data DICE_PORT=8321 DICE_HOST=127.0.0.1 \ + XDG_DATA_HOME=/tmp/zkouska DICE_PORT=8321 DICE_HOST=127.0.0.1 \ ./dist/kostky > /tmp/server.log 2>&1 & + server=$! + trap 'kill $server 2>/dev/null || true' EXIT + for _ in $(seq 30); do curl -sf -o /dev/null http://127.0.0.1:8321/ && break sleep 1 done + curl -sf -o /dev/null http://127.0.0.1:8321/ curl -sf -o /dev/null http://127.0.0.1:8321/static/style.css curl -sf -o /dev/null http://127.0.0.1:8321/board - test -f /tmp/data/kostky/dice.db + test -f /tmp/zkouska/kostky/dice.db cat /tmp/server.log - name: Na release @@ -70,24 +69,24 @@ jobs: api="${GITHUB_SERVER_URL}/api/v1/repos/${GITHUB_REPOSITORY}" auth="Authorization: token ${TOKEN}" - # Release může, ale nemusí existovat — na tag se dá kliknout ručně. - release=$(curl -s -H "$auth" "$api/releases/tags/${GITHUB_REF_NAME}") - id=$(printf '%s' "$release" | python3.11 -c \ - 'import json,sys; print(json.load(sys.stdin).get("id",""))' || true) + # Release u tagu být může, ale nemusí — dá se založit i ručně. + id=$(curl -s -H "$auth" "$api/releases/tags/${GITHUB_REF_NAME}" \ + | python3 -c 'import json,sys; print(json.load(sys.stdin).get("id",""))' || true) if [ -z "$id" ]; then id=$(curl -sf -X POST -H "$auth" -H "Content-Type: application/json" \ -d "{\"tag_name\":\"${GITHUB_REF_NAME}\",\"name\":\"${GITHUB_REF_NAME}\"}" \ - "$api/releases" | python3.11 -c \ - 'import json,sys; print(json.load(sys.stdin)["id"])') + "$api/releases" \ + | python3 -c 'import json,sys; print(json.load(sys.stdin)["id"])') fi # Aby se dal build zopakovat, stará příloha stejného jména jde pryč. - curl -s -H "$auth" "$api/releases/$id/assets" | python3.11 -c \ - 'import json,os,sys; [print(a["id"]) for a in json.load(sys.stdin) if a["name"] == os.environ["ASSET"]]' \ + curl -s -H "$auth" "$api/releases/$id/assets" \ + | python3 -c 'import json,os,sys; [print(a["id"]) for a in json.load(sys.stdin) if a["name"] == os.environ["ASSET"]]' \ | while read -r old; do curl -sf -X DELETE -H "$auth" "$api/releases/$id/assets/$old" done - curl -sf -X POST -H "$auth" -F "attachment=@dist/kostky" \ + curl -sf -o /dev/null -X POST -H "$auth" -F "attachment=@dist/kostky" \ "$api/releases/$id/assets?name=${ASSET}" + echo "$ASSET pověšen na ${GITHUB_REF_NAME}" diff --git a/.gitignore b/.gitignore index d2f10d9..a9551e5 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ __pycache__/ .pytest_cache/ dist/ build/ +.build/ diff --git a/README.md b/README.md index a9fb15a..74599e8 100644 --- a/README.md +++ b/README.md @@ -17,8 +17,7 @@ chmod +x kostky ``` Nic se neinstaluje — Python, Flask i šablony jsou uvnitř. Zatím jen pro Linux -na x86-64; postavené proti glibc 2.28, takže jede na všem od RHEL 8, -Debianu 10 a Ubuntu 20.04 výš. +na x86-64; postavené proti glibc 2.17, takže jede prakticky všude. Ze zdrojáků to je stejně krátké: @@ -132,10 +131,11 @@ python3 web.py ./build.sh ``` -Staví se v kontejneru (podman nebo docker) na AlmaLinux 8, ne na hostiteli. -Binárka slinkovaná proti glibc z Fedory 44 by nešla spustit nikde se starším -systémem — glibc drží zpětnou kompatibilitu, ne dopřednou. Výsledek je -v `dist/kostky`. +Staví se proti samostatnému CPythonu od `uv`, ne proti systémovému. Ten je +slinkovaný s glibc 2.17; postavené proti Pythonu z Fedory 44 by to chtělo +glibc 2.43 a nešlo by spustit skoro nikde — glibc drží zpětnou kompatibilitu, +ne dopřednou. Nic z hostitele se do binárky nedostane, takže kontejner k tomu +potřeba není. Výsledek je v `dist/kostky`. Terminálová verze součástí binárky není; ta se pouští ze zdrojáků. diff --git a/build.sh b/build.sh index 7238fe5..ea0f553 100755 --- a/build.sh +++ b/build.sh @@ -1,30 +1,22 @@ #!/usr/bin/env bash # Postaví jednosouborovou binárku pro Linux do dist/kostky. # -# Staví se v kontejneru s glibc 2.28 (AlmaLinux 8), ne na hostiteli. Binárka -# slinkovaná proti glibc z Fedory 44 by nešla spustit nikde se starším -# systémem — glibc drží zpětnou kompatibilitu, ne dopřednou. Postavené na -# staré glibc poběží i na nové. +# Staví se proti samostatnému CPythonu od uv, ne proti systémovému. Ten je +# slinkovaný s glibc 2.17, takže binárka jede i na letitých systémech. +# Postavená proti Pythonu z Fedory 44 by chtěla glibc 2.43 a nešla by +# spustit skoro nikde — glibc drží zpětnou kompatibilitu, ne dopřednou. +# Nic z hostitele se do ní nedostane, takže kontejner k tomu není potřeba. set -euo pipefail -IMAGE=${IMAGE:-docker.io/library/almalinux:8} -PYTHON=${PYTHON:-python3.11} -ENGINE=${ENGINE:-$(command -v podman || command -v docker)} +VERSION=${VERSION:-3.11} +BUILD=${BUILD:-.build} -# Rootless podman mapuje roota v kontejneru na tebe, docker ne — tam by -# dist/ zůstalo cizí. -FIXUP=":" -[[ "$ENGINE" == *docker ]] && FIXUP="chown -R $(id -u):$(id -g) dist" +uv python install "$VERSION" +uv venv --quiet --managed-python --python "$VERSION" "$BUILD" +uv pip install --quiet --python "$BUILD/bin/python" -r requirements.txt pyinstaller -"$ENGINE" run --rm -v "$PWD:/src:z" -w /src "$IMAGE" bash -euc " - dnf install -y -q python3.11 binutils - $PYTHON -m venv /tmp/build - /tmp/build/bin/pip install --quiet --upgrade pip - /tmp/build/bin/pip install --quiet flask qrcode pyinstaller - /tmp/build/bin/pyinstaller --clean --noconfirm \ - --distpath dist --workpath /tmp/work kostky.spec - $FIXUP -" +"$BUILD/bin/pyinstaller" --clean --noconfirm \ + --distpath dist --workpath "$BUILD/work" kostky.spec echo ls -lh dist/kostky From b4864981ade52a4227ff4136d982262ba3ee49d8 Mon Sep 17 00:00:00 2001 From: gitea-actions Date: Fri, 21 Aug 2026 17:24:37 +0200 Subject: [PATCH 4/5] =?UTF-8?q?runner=20pot=C5=99ebuje=20binutils,=20doins?= =?UTF-8?q?taluje=20si=20je=20s=C3=A1m?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PyInstaller hledá objdumpem, na kterých knihovnách binárka visí. Runner v host módu ho nemá — je to jediné, co musí být na hostiteli, zbytek si obstará uv. Co-Authored-By: Claude Opus 5 --- .gitea/workflows/binarka.yml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/.gitea/workflows/binarka.yml b/.gitea/workflows/binarka.yml index cb4d6bd..fa4bebc 100644 --- a/.gitea/workflows/binarka.yml +++ b/.gitea/workflows/binarka.yml @@ -16,6 +16,22 @@ jobs: runs-on: homelab steps: + - name: objdump + run: | + # Jediné, co musí být na hostiteli — PyInstaller jím hledá, na + # kterých knihovnách binárka visí. Zbytek si obstará uv. + command -v objdump >/dev/null && exit 0 + sudo=""; [ "$(id -u)" -ne 0 ] && sudo=sudo + if command -v apt-get >/dev/null; then + $sudo apt-get update -qq + $sudo apt-get install -y -qq binutils + elif command -v dnf >/dev/null; then + $sudo dnf install -y -q binutils + else + echo "Nainstaluj binutils, PyInstaller bez objdumpu nestaví." >&2 + exit 1 + fi + - name: Zdrojáky env: TOKEN: ${{ secrets.GITHUB_TOKEN }} From 2a03474af8af3a201b599b045b63b8e903dc94b8 Mon Sep 17 00:00:00 2001 From: gitea-actions Date: Fri, 21 Aug 2026 17:36:01 +0200 Subject: [PATCH 5/5] testy nad pull requesty a nad main MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Matice Python 3.11 až 3.14 — u projektu, který deklaruje >=3.11, se hodí vědět, že to na všech čtyřech opravdu jede. Pythony obstará uv, na hostiteli nemusí být žádný. K pytestu ještě skriptovaná partie v terminálové verzi. Pytest sahá na core.py, prompty v dice.py nikdo netestoval; tohle projde dvě nuly, třetí s vynulováním a výhru tak, jak to odehraje člověk. Runner sdílí pracovní adresář mezi variantami matice, proto max-parallel 1. Co-Authored-By: Claude Opus 5 --- .gitea/workflows/testy.yml | 69 ++++++++++++++++++++++++++++++++++++++ README.md | 4 +++ 2 files changed, 73 insertions(+) create mode 100644 .gitea/workflows/testy.yml diff --git a/.gitea/workflows/testy.yml b/.gitea/workflows/testy.yml new file mode 100644 index 0000000..10d723c --- /dev/null +++ b/.gitea/workflows/testy.yml @@ -0,0 +1,69 @@ +# Testy nad každým pull requestem a nad main. +# +# Runner běží v host módu, takže tu není kontejner ani hotové akce +# z marketplace. Pythony obstará uv, na hostiteli nemusí být žádný. +name: testy + +on: + push: + branches: [main] + pull_request: + workflow_dispatch: + +concurrency: + group: testy-${{ github.ref }} + cancel-in-progress: true + +jobs: + pytest: + runs-on: homelab + + strategy: + fail-fast: false + # Runner sdílí pracovní adresář mezi variantami, takže po jedné. + max-parallel: 1 + matrix: + python: ["3.11", "3.12", "3.13", "3.14"] + + steps: + - name: Zdrojáky + env: + TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + git init -q . + git remote remove origin 2>/dev/null || true + git remote add origin "${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}.git" + git config http.extraheader "Authorization: token ${TOKEN}" + git fetch -q --depth 1 origin "${GITHUB_REF}" + git config --unset http.extraheader + git checkout -qf FETCH_HEAD + git clean -qfd + + - name: uv + run: | + echo "$HOME/.local/bin" >> "$GITHUB_PATH" + export PATH="$HOME/.local/bin:$PATH" + command -v uv >/dev/null || curl -LsSf https://astral.sh/uv/install.sh | sh + uv python install "${{ matrix.python }}" + + - name: Testy + run: | + uv run --managed-python --python "${{ matrix.python }}" \ + --with-requirements requirements.txt --no-project \ + pytest -q + + - name: Terminálová verze + run: | + # Pytest sahá na core.py, ne na prompty. Tohle projde celou partii + # tak, jak ji odehraje člověk: dvě nuly, třetí vynuluje, výhra. + out=$(printf 'A\nB\n\n1000\n\n200\n10\n0\n10\n0\n10\n0\n\n1000\n' \ + | TERM=dumb uv run --managed-python --python "${{ matrix.python }}" dice.py) + + for expected in "200xx" "Po třech nulových hodech" "Vítězem se stává B"; do + printf '%s' "$out" | grep -qF "$expected" || { + echo "chybí: $expected" + printf '%s\n' "$out" + exit 1 + } + done + echo "terminálová verze v pořádku" diff --git a/README.md b/README.md index 74599e8..069a63b 100644 --- a/README.md +++ b/README.md @@ -156,3 +156,7 @@ uv run --with-requirements requirements.txt --no-project pytest ``` Nebo z připraveného prostředí prostě `pytest`. + +Nad každým pull requestem a nad `main` běží `.gitea/workflows/testy.yml` — +84 testů na Pythonu 3.11, 3.12, 3.13 i 3.14, a k tomu skriptovaná partie +v terminálové verzi, na kterou pytest nesahá.