Development
This guide is for working on texas2step itself: running it from source, testing, building the documentation site and the Debian package, and making a release. To install texas2step on a server, see Install.
Prerequisites#
- Python 3.11 or newer, and uv.
- Node.js 20 or newer (24 LTS recommended), only to change the web UI.
- For the
.deb: an Ubuntu 26.04 amd64 host withdpkg-deb,binutilsand, optionally,lintian.
Run from source#
git clone https://github.com/robert-patrick-texas/texas2step.git
cd texas2step
uv sync # creates .venv with the app plus the dev and docs tools
make dev # http://127.0.0.1:8080
The built UI is committed under src/texas2step/static/, so the server runs without
Node. To work on the UI, run make web-watch in a second terminal. It rebuilds the
bundle on every change, unminified and with source maps.
To try the server under a path prefix the way nginx publishes it, send the header nginx would:
curl -s -H 'X-Forwarded-Prefix: /texas2step' http://127.0.0.1:8080/ | grep '<base'
# <base href="/texas2step/" />
Make targets#
| Target | What it does |
|---|---|
make dev |
Run the server on 127.0.0.1:8080 |
make web |
Install Node dependencies, typecheck and bundle the UI into src/texas2step/static |
make web-watch |
Rebuild the UI on change |
make test |
pytest, including WebRTC round trips on loopback |
make lint / make fmt |
ruff and tsc / ruff format and autofix |
make check |
lint and test |
make docs |
Render every .md file into the static site in html/ |
make docs-watch |
Re-render html/ whenever a .md file changes |
make deb |
Build dist/texas2step-<version>-1-amd64.deb and dist/texas2step-setup |
make version |
Print the version |
make upgrade |
Move every Python and npm dependency to its newest release, then rebuild the UI |
Project layout#
src/texas2step/ Python package
app.py FastAPI app factory, path-prefix support, static UI, SPA routes
api/wire.py measurement primitives: ping, down, up, ws, rtc/offer
api/meta.py info, whoami, locations, health
api/measure.py server-side measurement functions
api/results.py result history
engine.py Python measurement engine (CLI + /measure)
rtc.py WebRTC data-channel echo (aiortc), UDP port range
page.py HTML shell: path prefix, version, default theme, branding
config_template.toml the settings file the package installs, every setting documented
config_merge.py `texas2step config merge`: carries settings into a new template
config_edit.py `texas2step config set` / `add-location`: edits in place
stats.py shared statistical definitions
store.py SQLite result store
static/ built web UI (generated by web/build.mjs; committed)
web/ TypeScript client (no runtime dependencies)
src/engine/ browser measurement engine
src/ui/ gauge, link animation, charts, views
tests/ pytest: stats, config, API, store, WebRTC, end-to-end
docs/ the documentation, in Markdown
scripts/build_docs.py Markdown โ html/ (the GitHub Pages site)
deploy/ files the package installs: systemd unit, defaults, nginx snippets
packaging/ Debian packaging: build-deb.sh, control, maintainer scripts,
and texas2step-setup.py, the operators' check-and-install tool
.github/workflows/ GitHub Pages publishing
legacy/ the original single-file prototype, kept for reference
Code conventions#
- Server-side code is Python. Browser code is TypeScript, bundled by esbuild with no runtime dependencies and no CDN, so the page works on networks without internet access.
- Statistical definitions live in
stats.pyandweb/src/engine/stats.tsand must stay in step. Methodology documents them. - After changing anything under
web/, runmake weband commit the regeneratedsrc/texas2step/static/along with the source. - The one piece of browser code that isn't ours is Swagger UI, the OpenAPI explorer at
/api/docs.web/build.mjscopies it, with its license files, from the pinnedswagger-ui-distnpm package intostatic/assets/swagger/, so the explorer needs no CDN. That package depends on@scarf/scarf, an install-time analytics reporter; its install script is denied inweb/package.json(allowScripts), so it never runs. - Dependencies are pinned exactly (
uv.lock,web/package.jsonandweb/package-lock.json), so builds are repeatable.make upgrademoves them all to their newest releases.
Tests#
make test runs the whole suite in about 15 seconds:
- Unit tests: statistics, settings, the result store.
- API tests: a FastAPI test client, including the path-prefix handling.
- End-to-end tests against a real uvicorn server on loopback: HTTP latency, multi-stream throughput, and WebRTC packet loss, including the pinned UDP port range.
The WebRTC tests need a non-loopback address on the host, because WebRTC never offers 127.0.0.1 as a candidate.
Documentation site#
scripts/build_docs.py turns every .md file in the repository into a static site in
html/:
README.mdbecomes the index page, and every page has a sidebar that links to all the others.- Links between
.mdfiles become links between pages. - A broken link or anchor fails the build.
html/ isn't committed. On this development server it is served for review by nginx at
/texas2step/, through the symlink /var/www/html/texas2step โ html/. Run make docs,
or keep make docs-watch running while you edit.
On GitHub, .github/workflows/docs.yml builds the same site and publishes it to GitHub
Pages on every push to main that touches Markdown. It needs Pages enabled once: in the
repository settings, Pages โ Source: GitHub Actions. Until then the workflow builds
the site and skips the deploy with a notice.
Build the Debian package#
make deb # or: packaging/build-deb.sh
packaging/build-deb.sh:
- Builds the wheel.
- Creates a relocatable virtualenv on the system
/usr/bin/python3, and installs the exact dependency versions fromuv.lockinto it. - Adds the files from
deploy/. - Writes
dist/texas2step-<version>-<revision>-amd64.deb, then runs lintian. - Writes
dist/texas2step-setup, the operators' setup tool, frompackaging/texas2step-setup.py. It stamps in the package's file name and SHA-256, so the tool refuses any other package. Ship the two files together.
The virtualenv holds compiled wheels and links to the system Python, so build on the
Ubuntu release you're packaging for. The package then depends on that release's Python
minor version (3.14 for 26.04). For a packaging-only fix of the same version, use
REVISION=2 make deb.
To test an install without touching the build host, use a throwaway systemd container:
cat > Dockerfile.test <<'EOF'
FROM ubuntu:26.04
RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
systemd systemd-sysv dbus nginx curl ca-certificates python3 adduser
STOPSIGNAL SIGRTMIN+3
CMD ["/sbin/init"]
EOF
docker build -t t2s-test -f Dockerfile.test . && rm Dockerfile.test
docker run -d --name t2s-test --privileged --cgroupns=host \
-v /sys/fs/cgroup:/sys/fs/cgroup:rw -p 127.0.0.1:18080:80 t2s-test
docker exec t2s-test rm -f /usr/sbin/policy-rc.d # the image blocks service starts
docker cp dist/texas2step-*-amd64.deb t2s-test:/root/
docker exec t2s-test sh -c 'apt-get install -y /root/texas2step-*-amd64.deb'
Then add the include lines from Install to
/etc/nginx/sites-available/default in the container, reload nginx, and open
http://127.0.0.1:18080/texas2step/.
To test the setup tool instead, copy it in beside the package and use it in place of
apt-get install. The container's default site is plain HTTP, so name it:
docker cp dist/texas2step-setup t2s-test:/root/
docker exec -w /root t2s-test ./texas2step-setup --id austin \
--site /etc/nginx/sites-enabled/default
docker exec -w /root t2s-test ./texas2step-install.sh
For an upgrade, install the previous release's .deb first and edit its settings, then
run the new setup tool. Add ufw to the image and enable it, after allowing 22 and 443,
to see the firewall step. packaging/texas2step-setup.py keeps its checks in pure
functions, which tests/test_setup.py covers with sample nginx -T, ufw, iptables and
nftables output.
Release#
-
Set the version in
src/texas2step/__init__.py, the only place it is written. Everything else reads it from there:pyproject.toml, through hatch's dynamic version, so the wheel andpip showtoo.- The server (
/api/v1/info,/api/v1/health), and the web UI, which the server tells the version in each page it serves. No UI rebuild is needed for a new version. - The CLI (
texas2step --version). packaging/build-deb.sh, for the package version and file name.make version, which prints it for scripts.
The documentation doesn't repeat it.
-
If the release changes
src/texas2step/config_template.toml, keep one setting per line, which is the layouttexas2step config mergerelies on. If it renames a setting, add the old and new names toRENAMEDinconfig_merge.py, so upgrades carry the value across. -
Run
make upgrade, so the release ships every dependency's newest version, with its security and bug fixes. -
Run
make checkandmake deb. The package installs exactly the versions inuv.lock. -
Commit (including the updated lockfiles), tag and publish the package:
v=$(make -s version)
git commit -am "Release $v" && git tag "v$v" && git push origin main "v$v"
gh release create "v$v" "dist/texas2step-$v-1-amd64.deb" dist/texas2step-setup \
--title "texas2step $v" --generate-notes
License#
texas2step is licensed under the Apache License 2.0. Contributions are accepted under the same license.