binárka ke stažení a jasné místo pro databázi
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 <noreply@anthropic.com>
This commit is contained in:
1 parent
f33104b5c2
commit
fcfa0e489b
7 files changed
+180
-7
No files matched your search
@@ -2,3 +2,5 @@ __pycache__/
|
||||
.venv/
|
||||
*.db
|
||||
.pytest_cache/
|
||||
dist/
|
||||
build/
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
+26
@@ -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,
|
||||
)
|
||||
+22
-2
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
@@ -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=<adresa>.")
|
||||
|
||||
print(f"\nDatabáze: {storage.DB_PATH.resolve()}")
|
||||
|
||||
app.run(host="0.0.0.0", port=PORT, threaded=True)
|
||||
Reference in new issue
Block a user