Files
rarelens/scripts/seed-remote.sh
T
Kemal Yaylali c25fb53666 feat(deploy): a Railway deployment of the analysed cases, behind one credential
Three services -- Postgres, API, UI -- with the API on Railway's private
network only, so the UI's /api proxy is the single public entry point and
there is no CORS.

The pipeline cannot run there. Nextflow shells out to `docker run` for VEP
and bcftools, and Railway gives you a container, not a Docker daemon. Rather
than leave a button that always fails, cases are annotated locally and copied
up by scripts/seed-remote.sh, and PUBLIC_PIPELINE_ENABLED=false hides the
analyse/score actions and the create-case form.

DATABASE_IDLE_CONNECTIONS=false is what makes idling work. Railway decides a
service is idle from its *outbound* traffic and sleeps it after ~5-10 minutes;
a pooled database connection is outbound traffic, so SQLAlchemy's default pool
would have kept the API awake and billable for ever. Setting it false switches
to NullPool, which costs a connection per request -- nothing at demo traffic,
the wrong trade under real load, hence the flag rather than a rewrite.

BASIC_AUTH_USER / BASIC_AUTH_PASSWORD put one shared credential in front of
the site. Nothing deployed is patient data, so this stops the URL being
wandered into rather than protecting anyone's privacy; unset, the site is
open, which is what local development wants. Compared in constant time, and
both halves of the credential are checked even when the first fails.
2026-09-12 12:23:19 +01:00

61 lines
2.7 KiB
Bash
Executable File

#!/usr/bin/env bash
# Copy the analysed demo cases and the HPO reference data into a remote database.
#
# The deployed demo has no Nextflow and no Docker daemon, so it cannot run VEP. Instead the cases
# are annotated here, where the pipeline works, and the results are copied up. Visitors get real
# analysed cases -- funnel, ranking, evidence, decisions, report -- against a database that is
# never asked to produce them.
#
# Everything copied is derived from public, openly licensed sources (docs/data.md). There is no
# patient data in this dump, and there must never be.
#
# scripts/seed-remote.sh "$(railway variables -s Postgres --kv | grep DATABASE_PUBLIC_URL | cut -d= -f2-)"
set -euo pipefail
TARGET=${1:-${TARGET_DATABASE_URL:-}}
SOURCE=${SOURCE_DATABASE_URL:-postgresql://rarelens:rarelens@localhost:5432/rarelens}
IMAGE=${PG_IMAGE:-postgres:16-alpine}
# Reference data first, then a case's own rows: variants reference jobs, jobs reference cases.
TABLES=(hpo_terms gene_phenotypes cases case_phenotypes jobs variants predictions variant_decisions)
if [ -z "$TARGET" ]; then
echo "usage: $0 <target postgres URL> (or set TARGET_DATABASE_URL)" >&2
echo "the target must already have the schema: run 'alembic upgrade head' against it first" >&2
exit 2
fi
case "$TARGET" in
postgres://*|postgresql://*) ;;
*) echo "target must be a postgres:// URL, got ${TARGET%%:*}:..." >&2; exit 2 ;;
esac
echo "==> source: ${SOURCE%%\?*}" >&2
echo "==> target: ${TARGET%%:*}://…${TARGET##*@}" >&2 # host only: the password stays out of the log
echo "==> tables: ${TABLES[*]}" >&2
# --data-only: the target's schema comes from Alembic, so the two can never disagree about it.
# Truncating first makes the script repeatable; cascade because variants hang off jobs.
docker run --rm -i --add-host=host.docker.internal:host-gateway \
-e PGPASSWORD_UNUSED=1 "$IMAGE" \
pg_dump --data-only --no-owner --no-privileges \
$(printf -- '--table=%s ' "${TABLES[@]}") \
"${SOURCE/localhost/host.docker.internal}" \
> /tmp/rarelens-seed.sql
lines=$(wc -l < /tmp/rarelens-seed.sql)
echo "==> dumped $lines lines" >&2
docker run --rm -i "$IMAGE" psql "$TARGET" -v ON_ERROR_STOP=1 \
-c "TRUNCATE $(IFS=,; echo "${TABLES[*]}") RESTART IDENTITY CASCADE" >&2
docker run --rm -i "$IMAGE" psql "$TARGET" -v ON_ERROR_STOP=1 -q < /tmp/rarelens-seed.sql
rm -f /tmp/rarelens-seed.sql
echo "==> loaded. counts on the target:" >&2
docker run --rm -i "$IMAGE" psql "$TARGET" -At -c "
select 'cases: '||count(*) from cases
union all select 'variants: '||count(*) from variants
union all select 'gene_phenotypes: '||count(*) from gene_phenotypes
union all select 'hpo_terms: '||count(*) from hpo_terms"