Skip to content

chore(release): version 2.1.1 #5

chore(release): version 2.1.1

chore(release): version 2.1.1 #5

Workflow file for this run

name: CI
# Runs the project's existing self-contained test suites — no live server,
# no API keys, no network calls to an LLM. Compatible with both GitHub
# Actions and Gitea Actions (same workflow syntax).
on:
push:
branches: [main]
pull_request:
jobs:
backend:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# actions/setup-python only ships prebuilt interpreters for GitHub's
# hosted Ubuntu images; in a minimal Gitea job container it fails with
# "version '3.11' ... was not found for this operating system". Use the
# platform's own Python instead: already present on GitHub runners,
# apt-installed in the container otherwise. Both paths give Python 3.11.
- name: Set up Python
run: |
if ! command -v python3 >/dev/null 2>&1; then
apt-get update && apt-get install -y --no-install-recommends python3 python3-pip
fi
# node:*-bookworm ships python3 but no pip module — ensure pip too.
if ! python3 -m pip --version >/dev/null 2>&1; then
if command -v apt-get >/dev/null 2>&1; then
apt-get update && apt-get install -y --no-install-recommends python3-pip
else
python3 -m ensurepip --upgrade
fi
fi
python3 --version && python3 -m pip --version
- name: Install dependencies
# Debian's system Python is PEP 668 "externally managed"; the flag is
# safe in this ephemeral CI env and is supported by modern pip on both.
run: python3 -m pip install --break-system-packages -r requirements.txt
- name: Audit Python deps
# pip-audit catches CVEs in pinned transitive deps. The default
# pip-audit behavior (no flags) creates an ephemeral venv via
# ensurepip to resolve each requirement — and that fails on minimal
# Gitea job containers that don't have python3-venv / ensurepip.
# We avoid that by:
# 1) generating a *pinned* requirements file with pip-compile
# (from pip-tools; one extra dep, but deterministic & fast)
# 2) installing requirements into the current env so pip-audit
# can read installed versions directly
# 3) running pip-audit with --disable-pip --no-deps so it scans
# the current env's installed packages instead of spinning up
# another venv
# --strict requires pinned (==) versions; --disable-pip requires
# either --no-deps or --require-hashes; both are satisfied here.
run: |
set -e
python3 -m pip install --break-system-packages 'pip-audit==2.10.1' 'pip-tools==7.5.3'
python3 -m pip install --break-system-packages -r requirements.txt
# Pin transitive deps for audit. --quiet keeps the log readable.
python3 -m piptools compile --quiet --output-file=requirements-pinned.txt --no-header --strip-extras requirements.txt
python3 -m pip_audit -r requirements-pinned.txt --strict --disable-pip --no-deps
- run: python3 -m py_compile server.py translator.py cache.py
- run: python3 test_translation.py
- run: python3 test_hardening.py
frontend:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "20"
- run: node -c static/translator.js
- run: node -c static/loader.js
- run: npm install
- run: npm audit --omit=dev
- run: npm test
# Smoke test: build the image, run it, hit /ping, expect 200.
# Catches Dockerfile regressions (syntax, missing deps, broken entrypoint,
# USER appuser) that pure Python tests can't. Skips when Docker isn't
# available — some Gitea job containers ship without dind, and we'd
# rather get a green run with a "skipped" note than a hard fail that
# blocks legitimate PRs. The pure-Python backend suite above already
# exercises the application code on a mock LLM, so losing this smoke
# step in those environments doesn't drop coverage to zero.
docker-smoke:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Detect Docker
id: docker
run: |
if command -v docker >/dev/null 2>&1; then
echo "available=true" >> "$GITHUB_OUTPUT"
docker --version
else
echo "available=false" >> "$GITHUB_OUTPUT"
echo "::notice::docker not installed in runner; skipping docker-smoke job"
fi
- name: Build image
if: steps.docker.outputs.available == 'true'
run: docker build -t bt-audit:ci .
- name: Run container
if: steps.docker.outputs.available == 'true'
run: |
docker run -d --name bt-smoke -p 8390:8390 bt-audit:ci
# Wait for /ping to come up (the image has start-period=10s in its
# HEALTHCHECK, but we don't want to depend on Docker's healthcheck
# state for the smoke test — poll the endpoint directly).
for i in $(seq 1 30); do
if curl -sf http://127.0.0.1:8390/ping >/dev/null 2>&1; then
echo "ping ok on try $i"
break
fi
sleep 1
done
# The actual assertion: /ping returns 200 with {"status":"ok"}.
curl -sf http://127.0.0.1:8390/ping | grep -q '"status":"ok"'
- name: Verify non-root API process
# The /ping endpoint serves from gunicorn. The `appuser` should own
# the gunicorn process; the entrypoint (and nginx if it were started)
# can stay root.
if: steps.docker.outputs.available == 'true'
run: |
# BusyBox `ps` (Alpine) has no -p flag — list user+args and grep the
# gunicorn master instead. [g]unicorn avoids matching the grep itself.
USER=$(docker exec bt-smoke ps -o user,args | grep '[g]unicorn' | head -1 | awk '{print $1}')
if [ "$USER" = "appuser" ]; then
echo "gunicorn runs as appuser (unprivileged) — OK"
else
echo "gunicorn runs as '$USER', expected appuser"
docker exec bt-smoke ps -o user,args | head -10
exit 1
fi
- name: Cleanup
if: always() && steps.docker.outputs.available == 'true'
run: docker rm -f bt-smoke 2>/dev/null || true