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:
1 parent
a269f8bd2e
commit
2ecf7bb45c
6 files changed
+116
-5
No files matched your search
@@ -37,6 +37,13 @@ every change carries a descriptive commit message.
|
|||||||
exits 2; `start.sh` runs the same access pre-flight before launching. An
|
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
|
explicit `-d <device>` keeps the serial path, and a reader present but no card
|
||||||
is still a normal cardless start.
|
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
|
## [3.38.0] — 2026-10-24
|
||||||
|
|
||||||
|
|||||||
@@ -748,6 +748,7 @@ The bundled Python server wraps [pySim](https://osmocom.org/projects/pysim/wiki)
|
|||||||
### Prerequisites
|
### 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 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).
|
- **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.
|
- **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 |
|
| 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`). |
|
| `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`. |
|
| `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`).
|
- **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`).
|
- **Смарт-картридер** (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 сознательно не ставится.
|
- **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`). |
|
| `start.sh` / `start.bat` | Запускает сервер из venv (раздаёт PWA + API на `:8080`). |
|
||||||
| `update.sh` / `update.bat` | Обновляет пакетную установку на месте: скачивает последний релизный пакет, заменяет файлы приложения и переустанавливает приложение в существующий `.venv` (pysim сохраняется). `--check`, `--version X.Y.Z`, `--zip FILE`, `--no-pip`, `--force`. |
|
| `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
|
git --version
|
||||||
echo.
|
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%" (
|
if not exist "%VENV_DIR%" (
|
||||||
echo Creating virtual environment...
|
echo Creating virtual environment...
|
||||||
python -m venv "%VENV_DIR%"
|
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"
|
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}")')
|
PY_VER=$(python3 -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")')
|
||||||
echo "Found Python $PY_VER"
|
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
|
if [ ! -d "$VENV_DIR" ]; then
|
||||||
echo "Creating virtual environment..."
|
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
|
fi
|
||||||
source "$VENV_DIR/bin/activate"
|
source "$VENV_DIR/bin/activate"
|
||||||
PIP="$VENV_DIR/bin/pip"
|
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