mirror of
https://github.com/jhd3197/ServerKit.git
synced 2026-10-23 03:27:43 +09:00
The panel runs async_mode='threading' under a plain threaded gunicorn worker; nothing imports gevent. It was still installed everywhere, and on Python 3.14 (no wheel yet) its source build is what broke installs in issue #148. backend/Dockerfile was unreferenced and still launched the gevent-websocket worker that double-answers the WS handshake; the run.py hint recommended the same worker.
198 lines
8.7 KiB
Bash
198 lines
8.7 KiB
Bash
#!/usr/bin/env bash
|
||
#
|
||
# Does install.sh actually obtain a working Python ON THIS DISTRO?
|
||
#
|
||
# Every other suite here runs against stubs, which is the right trade for logic
|
||
# but means the one thing that talks to the distro's real package repos was
|
||
# never exercised. Issue #99: Debian 13 ships only python3.13, the version gate
|
||
# accepted 3.11-3.12, so both `apt-get install python3.11` and `python3.12`
|
||
# failed and the installer fell through to a source build that cannot work
|
||
# (nothing in install.sh ever installs a compiler). Seven distro images in CI
|
||
# and none of them caught it, because none of them ran this code for real.
|
||
#
|
||
# So: run identify_system + choose_pkg_manager + provision_python and the
|
||
# local-build prerequisites against real repos, then prove the result is
|
||
# usable. No systemd, no nginx, no network
|
||
# services — this fits in the plain distro containers the matrix already pulls.
|
||
#
|
||
# Destructive: installs packages. Intended for a container or a throwaway VM.
|
||
# Refuses to run on a box that looks like somebody's machine unless forced.
|
||
#
|
||
# Run: bash scripts/test/test_provision_python.sh
|
||
# SERVERKIT_ALLOW_DESTRUCTIVE=1 bash scripts/test/test_provision_python.sh
|
||
set -uo pipefail
|
||
|
||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||
REPO_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||
INSTALL_SH="$REPO_DIR/install.sh"
|
||
|
||
PASS=0
|
||
FAIL=0
|
||
SKIP=0
|
||
ok() { PASS=$((PASS + 1)); printf ' \033[32m✔\033[0m %s\n' "$1"; }
|
||
bad() { FAIL=$((FAIL + 1)); printf ' \033[31m✘\033[0m %s\n' "$1"; }
|
||
skip() { SKIP=$((SKIP + 1)); printf ' \033[33m∼\033[0m %s (skipped)\n' "$1"; }
|
||
|
||
# --------------------------------------------------------------------------
|
||
# Guard rails. This one really does apt/dnf install.
|
||
# --------------------------------------------------------------------------
|
||
if [ "${SERVERKIT_ALLOW_DESTRUCTIVE:-0}" != "1" ] \
|
||
&& [ ! -f /.dockerenv ] && [ -z "${CI:-}" ]; then
|
||
printf 'Refusing to run: this installs packages system-wide.\n' >&2
|
||
printf 'Run it in a container, or set SERVERKIT_ALLOW_DESTRUCTIVE=1.\n' >&2
|
||
exit 2
|
||
fi
|
||
if [ "$(id -u)" != "0" ]; then
|
||
printf 'Refusing to run: package installation needs root.\n' >&2
|
||
exit 2
|
||
fi
|
||
|
||
printf '\nprovision_python against real package repos\n'
|
||
|
||
# shellcheck source=/dev/null
|
||
source "$INSTALL_SH" >/dev/null 2>&1
|
||
# install.sh runs `set -euo pipefail` at the top, and sourcing applies that to
|
||
# THIS shell. Under -e the first probe that returns non-zero (which is the
|
||
# normal way half of these functions report "not found") would kill the run
|
||
# before printing a single assertion. Put it back the way a test harness needs.
|
||
set +e
|
||
|
||
# The installer's own detection, not a guess — provision_python branches on
|
||
# OS_FAMILY and ID, and choose_pkg_manager is what makes pkg_add do anything.
|
||
identify_system >/dev/null 2>&1 || true
|
||
choose_pkg_manager >/dev/null 2>&1 || true
|
||
|
||
# main()'s order is identify_system -> choose_pkg_manager -> ensure_bootstrap_tools
|
||
# -> ... -> provision_python, and this MUST match it.
|
||
#
|
||
# ensure_bootstrap_tools is what refreshes the package index (its own comment
|
||
# says minimal images "ship with empty package indexes"). Omitting it made this
|
||
# test fail on ubuntu:22.04 for a reason the real installer does not have: a
|
||
# fresh container has empty apt lists, so the very first `apt-get install` finds
|
||
# no candidate for anything — including software-properties-common, which
|
||
# killed the deadsnakes fallback and dumped provision_python into the source
|
||
# build. The installer was never broken there; the test was skipping a step.
|
||
#
|
||
# The lesson generalises: a test that calls production functions out of order
|
||
# invents failures, and "fixing" install.sh to satisfy it would have made the
|
||
# real thing worse.
|
||
ensure_bootstrap_tools >/dev/null 2>&1 || true
|
||
|
||
printf ' distro: %s (ID=%s, family=%s, pkg=%s)\n' \
|
||
"${PRETTY_NAME:-unknown}" "${ID:-?}" "${OS_FAMILY:-?}" "${PKG_MGR:-?}"
|
||
printf ' gate: Python %s-%s\n' "$PYTHON_MIN" "$PYTHON_MAX"
|
||
|
||
if [ "${PKG_MGR:-}" = "" ]; then
|
||
skip "no package manager detected — nothing to provision with"
|
||
printf '\n%d passed, %d failed, %d skipped\n\n' "$PASS" "$FAIL" "$SKIP"
|
||
[ "$FAIL" -eq 0 ]
|
||
exit $?
|
||
fi
|
||
|
||
# --------------------------------------------------------------------------
|
||
# 1. A supported interpreter must be obtainable from the distro's own repos.
|
||
#
|
||
# The source-build fallback is deliberately NOT accepted as a pass here. It
|
||
# takes 20+ minutes with --enable-optimizations, needs a toolchain the
|
||
# installer never installs, and swallows its own errors through `| tail -1`.
|
||
# A distro that reaches it is a distro we have not really added support for.
|
||
# --------------------------------------------------------------------------
|
||
before_build_from_source=0
|
||
build_python_from_source() {
|
||
before_build_from_source=1
|
||
return 1
|
||
}
|
||
|
||
# provision_python's last line is `... || halt "Could not install a supported
|
||
# Python"`, and halt exits. Unstubbed it would take this harness down with it
|
||
# before a single assertion printed, turning a real failure into silence.
|
||
# Returning 1 instead lets provision_python report failure normally.
|
||
halted=""
|
||
halt() {
|
||
halted="$*"
|
||
return 1
|
||
}
|
||
|
||
if { provision_python && ensure_python_build_deps; } >/tmp/pp.log 2>&1; then
|
||
prov_rc=0
|
||
else
|
||
prov_rc=$?
|
||
fi
|
||
sed 's/^/ | /' /tmp/pp.log | tail -25
|
||
|
||
if [ "$before_build_from_source" = "1" ]; then
|
||
bad "no packaged Python in $PYTHON_MIN-$PYTHON_MAX on ${ID:-this distro} — the installer resorts to compiling one"
|
||
[ -n "$halted" ] && printf ' installer would abort with: %s\n' "$halted"
|
||
elif [ "$prov_rc" != "0" ]; then
|
||
bad "provision_python failed (exit $prov_rc)${halted:+ — $halted}"
|
||
else
|
||
ok "provision_python obtained a supported Python from the distro's packages"
|
||
fi
|
||
|
||
# --------------------------------------------------------------------------
|
||
# 2. Whatever it picked has to actually be usable.
|
||
# A python that cannot build a venv or run pip fails the install later, at a
|
||
# much more confusing moment.
|
||
# --------------------------------------------------------------------------
|
||
if [ -z "${PYTHON_BIN:-}" ] || ! command -v "$PYTHON_BIN" >/dev/null 2>&1; then
|
||
bad "PYTHON_BIN is unset or not on PATH after provision_python"
|
||
else
|
||
pyver="$("$PYTHON_BIN" -c 'import sys;print(".".join(map(str,sys.version_info[:2])))' 2>/dev/null || echo '?')"
|
||
if ver_in_range "$pyver"; then
|
||
ok "$PYTHON_BIN reports $pyver, inside the supported range"
|
||
else
|
||
bad "$PYTHON_BIN reports $pyver, outside $PYTHON_MIN-$PYTHON_MAX"
|
||
fi
|
||
|
||
if "$PYTHON_BIN" -m venv /tmp/pp-venv >/dev/null 2>&1 \
|
||
&& [ -x /tmp/pp-venv/bin/python ]; then
|
||
ok "it can create a virtualenv"
|
||
if /tmp/pp-venv/bin/python -m pip --version >/dev/null 2>&1; then
|
||
ok "the virtualenv has a working pip"
|
||
else
|
||
bad "the virtualenv has no working pip (missing ensurepip?)"
|
||
fi
|
||
# ssl and sqlite3 are the two stdlib modules a hand-built or
|
||
# stripped-down Python most often lacks, and both fail far away from
|
||
# here: no ssl means every HTTPS fetch dies, no sqlite3 means the
|
||
# database never opens.
|
||
for mod in ssl sqlite3 ctypes; do
|
||
if /tmp/pp-venv/bin/python -c "import $mod" >/dev/null 2>&1; then
|
||
ok "stdlib $mod is present"
|
||
else
|
||
bad "stdlib $mod is MISSING — the panel will fail later, not here"
|
||
fi
|
||
done
|
||
else
|
||
bad "it cannot create a virtualenv"
|
||
fi
|
||
# Force a source build: a cached/platform wheel would hide missing Python.h
|
||
# and compilers, the exact gap behind issue #148. gevent is no longer a
|
||
# panel dependency; it stays here as a heavy C build that proves the
|
||
# toolchain works for whatever native dependency comes next.
|
||
native_requirement='gevent==25.4.2'
|
||
if /tmp/pp-venv/bin/python -m pip install --no-cache-dir --no-binary=gevent \
|
||
"$native_requirement" >/tmp/pp-native.log 2>&1 \
|
||
&& /tmp/pp-venv/bin/python -c 'import gevent' >/dev/null 2>&1; then
|
||
ok "a native C dependency (gevent) builds from source and imports"
|
||
else
|
||
bad "native dependency build failed"
|
||
tail -40 /tmp/pp-native.log
|
||
fi
|
||
# Ubuntu 26.04 is the regression target; validate all backend pins there.
|
||
if [ "${ID:-}" = ubuntu ] && [ "${VERSION_ID:-}" = 26.04 ]; then
|
||
if /tmp/pp-venv/bin/python -m pip install \
|
||
-r "$REPO_DIR/backend/requirements.txt" >/tmp/pp-requirements.log 2>&1 \
|
||
&& /tmp/pp-venv/bin/python -m pip check >>/tmp/pp-requirements.log 2>&1; then
|
||
ok "all backend requirements install with consistent dependencies"
|
||
else
|
||
bad "backend requirements failed"
|
||
tail -40 /tmp/pp-requirements.log
|
||
fi
|
||
fi
|
||
rm -rf /tmp/pp-venv
|
||
fi
|
||
|
||
printf '\n%d passed, %d failed, %d skipped\n\n' "$PASS" "$FAIL" "$SKIP"
|
||
[ "$FAIL" -eq 0 ]
|