Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ To update a component version: change `BUILD_<name>` and `BUILD_<name>_SRCURL` i
```
nginx (reverse proxy, SSL termination, static files)
└─> gunicorn (WSGI unix socket)
└─> Django/parkour2 backend (wui.settings.prod)
└─> Django/parkour2 backend (config.settings.prod)
frontend/dist/ (Vue3/Vite built by npm run build, served as static by nginx)
postgresql (unix socket in $DEVSHM)
```
Expand Down Expand Up @@ -107,7 +107,7 @@ sqlite → python → uv → node → postgresql → nginx

### Build

`nginx.build.sh` compiles nginx (`--with-http_ssl_module`, `--with-http_v2_module`, most optional modules disabled, `--with-debug`, all temp/pid/lock paths under `$DEVSHM`). It applies `nginx.chicken.patch` before `./configure` (`patch ... --forward || true`, so it's idempotent — a stale nginx source tree that already has the patch is fine); the patch is a bit of build-identity fun (`--build=bwakbwakbwak`, `nginx.h` version-string hunk) and its hunk context is nginx-version-specific, so bumping `BUILD_nginx` can require re-fitting the patch (done for 1.31.3 in `cc59d00`, 1.31.4 in `b067910`). If `NGINX_SSL_DHPARAM` is set, the build also generates that 2048-bit dhparam file once if missing.
`nginx.build.sh` compiles nginx (`--with-http_ssl_module`, `--with-http_v2_module`, most optional modules disabled, `--with-debug`, all temp/pid/lock paths under `$DEVSHM`). It applies `nginx.chicken.patch` before `./configure` (`patch ... --forward || true`, so it's idempotent — a stale nginx source tree that already has the patch is fine); the patch is a bit of build-identity fun (`--build=bwakbwakbwak`, `nginx.h` version-string hunk) and its hunk context is nginx-version-specific, so **every** `BUILD_nginx` bump requires re-fitting the `nginx.h` hunk's `-` context lines to the new real version. Because of the `|| true`, a stale hunk fails *silently* (1.31.4 shipped reporting `nginx/1.31.4` instead of `chicken/1.32.2`), so dry-run it first: `patch -p1 --dry-run --forward --input=nginx.chicken.patch` in the extracted source. If `NGINX_SSL_DHPARAM` is set, the build also generates that 2048-bit dhparam file once if missing.

### Routing

Expand Down
26 changes: 13 additions & 13 deletions build.profile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
BUILD_TAG=26.08.31
BUILD_TAG=26.10.05

BUILD_parkour2=parkour2-develop

Expand Down Expand Up @@ -37,23 +37,23 @@ PGDATABASE=parkour2
BUILD_sqlite=sqlite-3.53.4
BUILD_sqlite_SRCURL="https://beehive.molgen.mpg.de/ee54db5a5b04e77d4883ce0c051a1eba/sqlite-autoconf-3530400.tar.gz"

# md5repo.sh "https://www.python.org/ftp/python/3.13.15/Python-3.13.15.tgz"
BUILD_python=Python-3.13.15
BUILD_python_SRCURL="https://beehive.molgen.mpg.de/7d2f36334007c01c034ad37c965ff535/${BUILD_python}.tgz"
# md5repo.sh "https://www.python.org/ftp/python/3.13.16/Python-3.13.16.tgz"
BUILD_python=Python-3.13.16
BUILD_python_SRCURL="https://beehive.molgen.mpg.de/1c314524d94004d7e51148a4a819d191/${BUILD_python}.tgz"

# https://github.com/astral-sh/uv/releases
# wget "https://github.com/astral-sh/uv/releases/download/0.12.8/uv-x86_64-unknown-linux-gnu.tar.gz"
BUILD_uv=uv-0.12.8
BUILD_uv_SRCURL="https://beehive.molgen.mpg.de/e84aacc82a3b7e2cd99fbaa0cb796b55/uv-x86_64-unknown-linux-gnu.tar.gz"
# wget "https://github.com/astral-sh/uv/releases/download/0.12.23/uv-x86_64-unknown-linux-gnu.tar.gz"
BUILD_uv=uv-0.12.23
BUILD_uv_SRCURL="https://beehive.molgen.mpg.de/48de2ba157b7aacb58edda6ff2872898/uv-x86_64-unknown-linux-gnu.tar.gz"

# wget "https://nodejs.org/download/release/v24.20.0/node-v24.20.0.tar.gz"
BUILD_node=node-24.20.0
BUILD_node_SRCURL="https://beehive.molgen.mpg.de/6d782834cb05d397ec78d98f59ef716e/${BUILD_node/-/-v}.tar.gz"
# wget "https://nodejs.org/download/release/v24.21.0/node-v24.21.0.tar.gz"
BUILD_node=node-24.21.0
BUILD_node_SRCURL="https://beehive.molgen.mpg.de/a0d4131e52d9055f8428439542018d44/${BUILD_node/-/-v}.tar.gz"

# wget "https://ftp.postgresql.org/pub/source/v16.15/postgresql-16.15.tar.gz"
BUILD_postgresql=postgresql-16.15
BUILD_postgresql_SRCURL="https://beehive.molgen.mpg.de/ac0e604676bda7a306f704b916a4e194/${BUILD_postgresql}.tar.gz"

# wget "https://nginx.org/download/nginx-1.31.4.tar.gz"
BUILD_nginx=nginx-1.31.4
BUILD_nginx_SRCURL="https://beehive.molgen.mpg.de/f73114f560071ae341e57ed123322f4c/${BUILD_nginx}.tar.gz"
# wget "https://nginx.org/download/nginx-1.31.6.tar.gz"
BUILD_nginx=nginx-1.31.6
BUILD_nginx_SRCURL="https://beehive.molgen.mpg.de/6d59f919382fae46812c44ffbc87498a/${BUILD_nginx}.tar.gz"
6 changes: 5 additions & 1 deletion buildall.sh
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ build() {
touch "$_tag"
else
echo "${_pkg}.build.sh failed exit code $r"
exit
exit $r
fi
fi
}
Expand All @@ -145,3 +145,7 @@ done
echo "built target ${PROJECT} done."

} 2>&1 | tee "${LOGS}/${logfile}"
# The tee would otherwise mask any failure inside the braces as success.
r=${PIPESTATUS[0]}
[[ $r == 0 ]] || echo "# build FAILED (exit $r), see ${LOGS}/${logfile}" >&2
exit $r
4 changes: 2 additions & 2 deletions nginx.chicken.patch
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,8 @@ index f4ccc61..8ff917c 100644
#define _NGINX_H_INCLUDED_


-#define nginx_version 1031003
-#define NGINX_VERSION "1.31.3"
-#define nginx_version 1031006
-#define NGINX_VERSION "1.31.6"
-#define NGINX_VER "nginx/" NGINX_VERSION
+#define nginx_version 1032002
+#define NGINX_VERSION "1.32.2"
Expand Down
2 changes: 1 addition & 1 deletion parkour2.build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ fi
cat <<_EOC_
# generated by $0 in $PWD
DJANGO_SETTINGS_MODULE=wui.settings.prod
DJANGO_SETTINGS_MODULE=config.settings.prod
SECRET_KEY=${PARKOUR2_SECRET_KEY}
Expand Down
2 changes: 1 addition & 1 deletion profile.build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ PGDATABASE=${PGDATABASE}
DB_USERNAME=${DB_USERNAME}
DB_PASSWORD=${DB_PASSWORD}

DJANGO_SETTINGS_MODULE=wui.settings.prod
DJANGO_SETTINGS_MODULE=config.settings.prod
DATABASE_URL=postgres://${DB_USERNAME}:${DB_PASSWORD}@/${PGDATABASE}?host=${PGHOST}

PARKOUR2_BIND_ADDR=${PARKOUR2_BIND_ADDR}
Expand Down
2 changes: 1 addition & 1 deletion startstop.build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ if not User.objects.filter(email='${PARKOUR2_ADMIN_EMAIL}').exists():
--pid "${DEVSHM}/gunicorn.pid"
--access-logfile "${LOGDIR}/gunicorn-access.log"
--error-logfile "${LOGDIR}/gunicorn-error.log"
wui.wsgi:application
config.wsgi:application
)

gunicorn "\${_opts[@]}"
Expand Down
41 changes: 41 additions & 0 deletions test-parkour2.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#!/bin/bash
# Run this repo's parkour2 regression tests (tests/parkour2/<app>/test_*.py)
# against the built parkour2 checkout in ${PROJECT}/${BUILD_parkour2}.
#
# Each test file is copied into the matching backend app for the run and
# removed afterwards, so the checkout stays clean for `git pull`. Postgres
# must be running (`./startstop.sh start postgresql`); Django creates and
# drops its own test_* database, the real one is not touched.
#
# ./test-parkour2.sh # all tests
# ./test-parkour2.sh -v 2 # extra args go to `manage.py test`

. build.profile

set -u
set -e

BACKEND=${PROJECT}/${BUILD_parkour2}/backend
SRC=$(pwd)/tests/parkour2

copied=()
cleanup() { rm -f "${copied[@]}"; }
trap cleanup EXIT

labels=()
for f in "${SRC}"/*/test_*.py; do
rel=${f#${SRC}/}
dst=${BACKEND}/${rel}
if [ -e "${dst}" ]; then
echo "# ${dst} already exists in checkout, refusing to overwrite" >&2
exit 1
fi
cp "${f}" "${dst}"
copied+=("${dst}")
rel=${rel%.py}
labels+=("${rel//\//.}")
done

. ${PROJECT}/profile
cd "${BACKEND}"
python manage.py test --noinput "$@" "${labels[@]}"
52 changes: 52 additions & 0 deletions tests/parkour2/library_sample_shared/test_indexpair_orphan.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
"""Regression test: IndexPair whose IndexType was deleted.

IndexPair.index_type is ForeignKey(on_delete=SET_NULL), so deleting an
IndexType leaves its pairs behind with index_type=None. IndexPair.__str__
then dereferences self.index_type.is_dual and raises AttributeError, which
makes /admin/library_sample_shared/indexpair/ return HTTP 500 for everyone
(seen on parkour-dev 2026-10-02 after a plate IndexType with pairs was
deleted and re-created).

Either upstream fix is accepted: a None-safe __str__ (pair survives as an
orphan) or on_delete=CASCADE (pair is deleted along with its type).
"""

from common.tests import BaseTestCase
from django.urls import reverse

from .models import IndexI7, IndexPair, IndexType


class OrphanedIndexPairTest(BaseTestCase):
def setUp(self):
index_type = IndexType.objects.create(
name="ONT Native Barcoding 96", format="plate"
)
index1 = IndexI7.objects.create(prefix="NB", number="01", index="ACGT")
index_type.indices_i7.add(index1)
IndexPair.objects.create(
index_type=index_type, index1=index1, char_coord="A", num_coord=1
)

# Same sequence as in the admin: delete the index, then the type.
index1.delete()
index_type.delete()

self.pair = IndexPair.objects.first()

def test_str_without_index_type(self):
if self.pair is None:
self.skipTest("pair was cascade-deleted with its IndexType")
self.assertIsNone(self.pair.index_type)
self.assertEqual(str(self.pair), "")

def test_admin_changelist_with_orphaned_pair(self):
user = self.create_user(email="admin@test.io")
user.is_superuser = True
user.save()
self.client.force_login(user)

response = self.client.get(
reverse("admin:library_sample_shared_indexpair_changelist")
)
self.assertEqual(response.status_code, 200)