setup: recreate an incomplete .venv instead of activating it
A failed venv creation (e.g. the Debian/Ubuntu python3-venv package missing) left the .venv directory behind; the next run saw it, skipped creation and failed to source/activate a venv without activate/pip. setup.sh now validates bin/activate, bin/python and bin/pip, removes an incomplete directory and creates it again; a creation failure removes the partial directory and names the venv package. setup.bat does the same with Scripts\activate.bat/python.exe/pip.exe and !errorlevel!. READMEs (prerequisites + scripts table), CHANGELOG (folded into the unreleased 3.38.1) and AGENTS updated. Tests: tests/test_setup_scripts.py (5: the shell/bat source pins and bash -n). 1088 Python + 1075 frontend.
This commit is contained in:
@@ -37,6 +37,13 @@ every change carries a descriptive commit message.
|
||||
exits 2; `start.sh` runs the same access pre-flight before launching. An
|
||||
explicit `-d <device>` keeps the serial path, and a reader present but no card
|
||||
is still a normal cardless start.
|
||||
- **`setup.sh`/`setup.bat` recover from an incomplete `.venv`.** A failed venv
|
||||
creation (e.g. the Debian/Ubuntu `python3-venv` package missing) left the
|
||||
directory behind; the next run saw `.venv` and skipped creation, then tried to
|
||||
activate a venv without `activate`/`pip`. The scripts now validate
|
||||
`bin/activate`/`bin/python`/`bin/pip` (`Scripts\…` on Windows), remove an
|
||||
incomplete directory and create it again; a creation failure removes the
|
||||
partial directory and points at the venv package. The READMEs note it.
|
||||
|
||||
## [3.38.0] — 2026-10-24
|
||||
|
||||
|
||||
@@ -748,6 +748,7 @@ The bundled Python server wraps [pySim](https://osmocom.org/projects/pysim/wiki)
|
||||
### Prerequisites
|
||||
|
||||
- **Python 3.9+** with `pip`, and **Git** — the app runs on 3.9+, but the **SCP81 TLS-PSK listener needs Python 3.13+** (`ssl`'s PSK callbacks).
|
||||
- **Python venv support** — `setup.sh` creates `.venv`; if that fails with `ensurepip is not available` (Debian/Ubuntu ship the venv module separately), install it (`sudo apt install python3-venv`) and run `./setup.sh` again. An incomplete `.venv` left by the failed run is removed automatically.
|
||||
- **Smart card reader** (PC/SC or serial/FTDI) — PC/SC is preferable (`pcsc-lite` + `ccid` on Linux). `pyscard` has no Linux wheels, so it builds from source: install the PC/SC header package too (`libpcsclite-dev` on Debian/Ubuntu, `pcsc-lite-devel` on Fedora; on Arch `pcsclite` includes them).
|
||||
- **Windows** — use **Python 3.13 (strongly recommended)**: `pyscard` has precompiled wheels up to 3.13 and the SCP81 TLS-PSK listener needs 3.13+. On 3.14 `pyscard` and some pysim dependency wheels are not available and build from source (needs MSVC C++ Build Tools). 3.9–3.12 run the app but not the SCP81 PSK listener. The SMPP bridge (`smpp.twisted3`) is intentionally skipped on Windows.
|
||||
|
||||
@@ -755,7 +756,7 @@ The bundled Python server wraps [pySim](https://osmocom.org/projects/pysim/wiki)
|
||||
|
||||
| Script | What it does |
|
||||
|--------|-------------|
|
||||
| `setup.sh` / `setup.bat` | Creates `.venv/`, installs pysim and the server. Run once after cloning or after unzipping the release package. |
|
||||
| `setup.sh` / `setup.bat` | Creates `.venv/` (or repairs an incomplete one), installs pysim and the server. Run once after cloning or after unzipping the release package. |
|
||||
| `start.sh` / `start.bat` | Starts the server from the venv (serves the PWA + API on `:8080`). |
|
||||
| `update.sh` / `update.bat` | Updates a packaged install in place: fetches the latest release package, replaces the app files and re-installs the app into the existing `.venv` (pysim is kept). `--check`, `--version X.Y.Z`, `--zip FILE`, `--no-pip`, `--force`. |
|
||||
|
||||
|
||||
+2
-1
@@ -732,6 +732,7 @@ PWA проверяет версию сервера при подключении
|
||||
### Требования
|
||||
|
||||
- **Python 3.9+** с `pip`, и **Git** — приложение работает на 3.9+, но **SCP81 TLS-PSK-слушателю нужен Python 3.13+** (PSK-callback'и `ssl`).
|
||||
- **Поддержка venv в Python** — `setup.sh` создаёт `.venv`; если создание завершается с `ensurepip is not available` (в Debian/Ubuntu модуль venv поставляется отдельно), установите его (`sudo apt install python3-venv`) и запустите `./setup.sh` снова. Неполный `.venv`, оставшийся от неудачного запуска, удаляется автоматически.
|
||||
- **Смарт-картридер** (PC/SC или serial/FTDI) — предпочтителен PC/SC (`pcsc-lite` + `ccid` на Linux). У `pyscard` нет Linux-wheel, поэтому он собирается из исходников: поставьте и пакет с заголовками PC/SC (`libpcsclite-dev` в Debian/Ubuntu, `pcsc-lite-devel` в Fedora; в Arch они входят в `pcsclite`).
|
||||
- **Windows** — используйте **Python 3.13 (настоятельно рекомендуется)**: у `pyscard` есть готовые wheel до 3.13, и SCP81 TLS-PSK-слушателю нужен 3.13+. На 3.14 wheel для `pyscard` и части зависимостей pysim отсутствуют, они собираются из исходников (нужны MSVC C++ Build Tools). 3.9–3.12 запускают приложение, но не SCP81 PSK-слушатель. SMPP-мост (`smpp.twisted3`) в Windows сознательно не ставится.
|
||||
|
||||
@@ -739,7 +740,7 @@ PWA проверяет версию сервера при подключении
|
||||
|
||||
| Скрипт | Назначение |
|
||||
|--------|-------------|
|
||||
| `setup.sh` / `setup.bat` | Создаёт `.venv/`, ставит pysim и сервер. Запускать один раз после клонирования или распаковки релизного пакета. |
|
||||
| `setup.sh` / `setup.bat` | Создаёт `.venv/` (или исправляет неполный), ставит pysim и сервер. Запускать один раз после клонирования или распаковки релизного пакета. |
|
||||
| `start.sh` / `start.bat` | Запускает сервер из venv (раздаёт PWA + API на `:8080`). |
|
||||
| `update.sh` / `update.bat` | Обновляет пакетную установку на месте: скачивает последний релизный пакет, заменяет файлы приложения и переустанавливает приложение в существующий `.venv` (pysim сохраняется). `--check`, `--version X.Y.Z`, `--zip FILE`, `--no-pip`, `--force`. |
|
||||
|
||||
|
||||
@@ -34,10 +34,35 @@ echo Found Git
|
||||
git --version
|
||||
echo.
|
||||
|
||||
REM Create venv
|
||||
REM Create the venv. A failed creation leaves a partial .venv behind: remove
|
||||
REM it so the next run starts over (v3.38.1).
|
||||
set "VENV_OK="
|
||||
if exist "%VENV_DIR%" (
|
||||
set "VENV_OK=1"
|
||||
if not exist "%VENV_DIR%\Scripts\activate.bat" set "VENV_OK="
|
||||
if not exist "%VENV_DIR%\Scripts\python.exe" set "VENV_OK="
|
||||
if not exist "%VENV_DIR%\Scripts\pip.exe" set "VENV_OK="
|
||||
if not defined VENV_OK (
|
||||
echo Removing an incomplete virtual environment (a previous setup did not finish)...
|
||||
rmdir /s /q "%VENV_DIR%"
|
||||
)
|
||||
)
|
||||
if not exist "%VENV_DIR%" (
|
||||
echo Creating virtual environment...
|
||||
python -m venv "%VENV_DIR%"
|
||||
if !errorlevel! neq 0 (
|
||||
if exist "%VENV_DIR%" rmdir /s /q "%VENV_DIR%"
|
||||
echo Error: could not create the virtual environment.
|
||||
echo Make sure the Python installation includes venv and pip.
|
||||
echo Run the Python installer, choose Modify, enable pip/venv, then run setup.bat again.
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
)
|
||||
if not exist "%VENV_DIR%\Scripts\activate.bat" (
|
||||
echo Error: the virtual environment has no Scripts\activate.bat.
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
call "%VENV_DIR%\Scripts\activate.bat"
|
||||
|
||||
|
||||
@@ -14,10 +14,24 @@ command -v git >/dev/null 2>&1 || { echo "Error: Git is required but not found.
|
||||
PY_VER=$(python3 -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")')
|
||||
echo "Found Python $PY_VER"
|
||||
|
||||
# Create venv
|
||||
# Create the venv. A failed creation (missing venv/ensurepip) leaves a partial
|
||||
# .venv behind: validate it and remove it so the next run starts over (v3.38.1).
|
||||
if [ -d "$VENV_DIR" ] && { [ ! -f "$VENV_DIR/bin/activate" ] || [ ! -x "$VENV_DIR/bin/python" ] || [ ! -x "$VENV_DIR/bin/pip" ]; }; then
|
||||
echo "Removing an incomplete virtual environment (a previous setup did not finish)..."
|
||||
rm -rf "$VENV_DIR"
|
||||
fi
|
||||
if [ ! -d "$VENV_DIR" ]; then
|
||||
echo "Creating virtual environment..."
|
||||
python3 -m venv "$VENV_DIR"
|
||||
if ! python3 -m venv "$VENV_DIR"; then
|
||||
rm -rf "$VENV_DIR"
|
||||
echo "Error: could not create the virtual environment."
|
||||
echo " The Python venv/ensurepip component is missing or broken."
|
||||
echo " Debian/Ubuntu: sudo apt install python3-venv"
|
||||
echo " Fedora: sudo dnf install python3-libs"
|
||||
echo " Arch: venv and ensurepip are part of python"
|
||||
echo " Install it and run ./setup.sh again."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
source "$VENV_DIR/bin/activate"
|
||||
PIP="$VENV_DIR/bin/pip"
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
"""The setup scripts recover from an incomplete .venv (v3.38.1).
|
||||
|
||||
A failed venv creation (the Debian/Ubuntu python3-venv package missing) used to
|
||||
leave the .venv directory behind; the next run saw it, skipped creation and
|
||||
failed to activate a venv without activate/pip.
|
||||
"""
|
||||
|
||||
import shutil
|
||||
import subprocess
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
|
||||
|
||||
class SetupShTests(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.src = (ROOT / 'setup.sh').read_text()
|
||||
|
||||
def test_an_incomplete_venv_is_removed_before_creation(self):
|
||||
self.assertIn('Removing an incomplete virtual environment', self.src)
|
||||
self.assertIn('rm -rf "$VENV_DIR"', self.src)
|
||||
self.assertIn('bin/activate', self.src)
|
||||
self.assertIn('bin/python', self.src)
|
||||
self.assertIn('bin/pip', self.src)
|
||||
|
||||
def test_a_failed_creation_removes_the_partial_dir_and_guides(self):
|
||||
self.assertIn('if ! python3 -m venv "$VENV_DIR"; then', self.src)
|
||||
self.assertIn('python3-venv', self.src)
|
||||
fails = self.src[self.src.index('if ! python3 -m venv'):]
|
||||
self.assertLess(fails.index('rm -rf "$VENV_DIR"'),
|
||||
fails.index('could not create the virtual environment'))
|
||||
|
||||
def test_the_script_is_valid_bash(self):
|
||||
bash = shutil.which('bash')
|
||||
if not bash:
|
||||
self.skipTest('bash not available')
|
||||
res = subprocess.run([bash, '-n', str(ROOT / 'setup.sh')],
|
||||
capture_output=True, text=True)
|
||||
self.assertEqual(res.returncode, 0, res.stderr)
|
||||
|
||||
|
||||
class SetupBatTests(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.src = (ROOT / 'setup.bat').read_text()
|
||||
|
||||
def test_an_incomplete_venv_is_removed_before_creation(self):
|
||||
self.assertIn('Removing an incomplete virtual environment', self.src)
|
||||
self.assertIn('rmdir /s /q "%VENV_DIR%"', self.src)
|
||||
for marker in ('Scripts\\activate.bat', 'Scripts\\python.exe',
|
||||
'Scripts\\pip.exe'):
|
||||
self.assertIn(marker, self.src)
|
||||
|
||||
def test_a_failed_creation_removes_the_partial_dir_and_guides(self):
|
||||
self.assertIn('if !errorlevel! neq 0', self.src)
|
||||
self.assertIn('python -m venv "%VENV_DIR%"', self.src)
|
||||
fails = self.src[self.src.index('if !errorlevel! neq 0'):]
|
||||
self.assertLess(fails.index('rmdir /s /q "%VENV_DIR%"'),
|
||||
fails.index('could not create the virtual environment'))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user