Skip to content

Commit ad7c618

Browse files
committed
feat(dev): add --preview-db=N flag to nix run .#dev
Connects to the database used by an opencouncil preview deployment. Detects whether the PR has an isolated DB (SSH tunnel) or uses the shared staging DB (reads URL from server), and forces external DB mode to skip local postgres. Requires OC_PREVIEW_SSH to be set to the preview server SSH target. Can be used independently or alongside --preview-tasks=M.
1 parent 233f25f commit ad7c618

File tree

3 files changed

+147
-1
lines changed

3 files changed

+147
-1
lines changed

.env.example

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,13 @@ CDN_URL=https://data.opencouncil.gr
6464
# Generate with: openssl rand -base64 32
6565
CRON_SECRET=
6666

67+
# ---------------------------------
68+
# Preview Server (for --preview-db flag)
69+
# ---------------------------------
70+
# SSH target for connecting to the preview server's database.
71+
# Only needed when using: nix run .#dev -- --preview-db=N
72+
# OC_PREVIEW_SSH=root@159.89.98.26
73+
6774
# ---------------------------------
6875
# Optional Variables
6976
# ---------------------------------

docs/nix-usage.md

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,48 @@ When `TASK_API_URL` is configured in `.env`, the dev runner will check if the ta
9393
⚠ Task server not reachable (start it separately for E2E testing)
9494
```
9595

96+
### Preview Database (--preview-db=N)
97+
98+
When testing against a preview deployment's database locally (e.g., running cron jobs or admin tools against a PR's data), use `--preview-db=N` where N is the **opencouncil** PR number:
99+
100+
```bash
101+
nix run .#dev -- --preview-db=193
102+
```
103+
104+
Can be combined with `--preview-tasks=M` to also connect to a tasks preview (where M is the **opencouncil-tasks** PR number):
105+
106+
```bash
107+
nix run .#dev -- --preview-db=193 --preview-tasks=26
108+
```
109+
110+
**Prerequisites:**
111+
- `OC_PREVIEW_SSH` must be set to the SSH target for the preview server:
112+
```bash
113+
# In .env or exported
114+
OC_PREVIEW_SSH=root@159.89.98.26
115+
```
116+
- Your SSH key must be in the server's `authorized_keys`
117+
118+
**How it works:**
119+
120+
The flag detects whether the PR has an isolated database (migration PRs with `.has-local-db` marker) or uses the shared staging database:
121+
122+
- **Isolated DB**: Opens an SSH tunnel to the per-PR PostgreSQL instance on the server
123+
- **Shared staging DB**: Reads `DATABASE_URL` from the server's `.env` and uses it directly
124+
125+
In both cases, the local postgres is skipped (`--db=external` mode is forced).
126+
127+
**Startup output:**
128+
```
129+
🗄️ Connecting to preview database for PR #193...
130+
✓ SSH connection OK
131+
✓ Isolated database detected (port 5625)
132+
✓ SSH tunnel active: localhost:5625 → 127.0.0.1:5625
133+
Database: postgresql://opencouncil@localhost:5625/opencouncil
134+
```
135+
136+
The SSH tunnel is automatically cleaned up when the dev server exits.
137+
96138
### Mobile Preview (QR code for phone testing)
97139

98140
The dev server binds to `0.0.0.0` by default, making it accessible from other devices on the same Wi-Fi. Click the **QR button** next to the DEV panel (bottom-right) to see a QR code encoding the LAN URL for the current page.

flake.nix

Lines changed: 98 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -511,7 +511,7 @@ USAGE
511511
usage() {
512512
cat <<'USAGE'
513513
Usage:
514-
nix run .#dev -- [--db=remote|external|nix|docker] [--db-url URL] [--direct-url URL] [--migrate] [--no-studio] [--locked] [--no-lan] [--preview-tasks=N]
514+
nix run .#dev -- [--db=remote|external|nix|docker] [--db-url URL] [--direct-url URL] [--migrate] [--no-studio] [--locked] [--no-lan] [--preview-tasks=N] [--preview-db=N]
515515
516516
DB modes:
517517
--db=nix Start Postgres+PostGIS via Nix + app (process-compose TUI) (default)
@@ -525,6 +525,7 @@ Flags:
525525
--fast Use pre-built PostGIS from binary cache (faster first build, but may not match production)
526526
--no-lan Bind dev server to localhost only (default: binds to 0.0.0.0 for mobile preview)
527527
--preview-tasks=N Connect to opencouncil-tasks preview for PR #N (starts ngrok tunnel for callbacks)
528+
--preview-db=N Connect to the database used by opencouncil preview PR #N (requires OC_PREVIEW_SSH)
528529
USAGE
529530
}
530531
@@ -539,6 +540,7 @@ USAGE
539540
postgis_locked="''${OC_POSTGIS_LOCKED:-1}"
540541
lan_enabled="''${OC_LAN:-1}"
541542
tasks_preview_pr="''${OC_PREVIEW_TASKS:-}"
543+
preview_db_pr=""
542544
543545
for arg in "$@"; do
544546
case "$arg" in
@@ -550,6 +552,7 @@ USAGE
550552
--fast) postgis_locked="0" ;;
551553
--no-lan) lan_enabled="0" ;;
552554
--preview-tasks=*) tasks_preview_pr="''${arg#--preview-tasks=}" ;;
555+
--preview-db=*) preview_db_pr="''${arg#--preview-db=}" ;;
553556
--help|-h) usage; exit 0 ;;
554557
*)
555558
echo "Unknown argument: $arg" >&2
@@ -701,6 +704,94 @@ USAGE
701704
echo ""
702705
fi
703706
707+
# --preview-db=N: connect to the database used by opencouncil preview PR #N
708+
ssh_tunnel_pid=""
709+
if [ -n "$preview_db_pr" ]; then
710+
if [ -z "''${OC_PREVIEW_SSH:-}" ]; then
711+
echo " ✗ OC_PREVIEW_SSH is not set." >&2
712+
echo " Set it to the SSH target for the preview server:" >&2
713+
echo " export OC_PREVIEW_SSH=root@159.89.98.26" >&2
714+
echo " Or add to .env: OC_PREVIEW_SSH=root@159.89.98.26" >&2
715+
exit 1
716+
fi
717+
718+
echo "🗄️ Connecting to preview database for PR #$preview_db_pr..."
719+
720+
# Validate SSH connectivity
721+
if ! ssh -o ConnectTimeout=5 -o BatchMode=yes "$OC_PREVIEW_SSH" true 2>/dev/null; then
722+
echo " ✗ Cannot connect to $OC_PREVIEW_SSH" >&2
723+
echo " Check that your SSH key is in the server's authorized_keys" >&2
724+
exit 1
725+
fi
726+
echo " ✓ SSH connection OK"
727+
728+
# Detect DB type: isolated (has .has-local-db marker) vs shared staging
729+
preview_base_dir="/var/lib/opencouncil-previews"
730+
# shellcheck disable=SC2029 # Intentional: expand variables locally before sending to server
731+
if ssh "$OC_PREVIEW_SSH" "test -f $preview_base_dir/pr-$preview_db_pr/.has-local-db" 2>/dev/null; then
732+
# Isolated DB: tunnel to the per-PR postgres
733+
preview_db_port=$((5432 + preview_db_pr))
734+
735+
# Verify the DB service is running
736+
# shellcheck disable=SC2029
737+
if ! ssh "$OC_PREVIEW_SSH" "systemctl is-active opencouncil-preview-db@$preview_db_pr" >/dev/null 2>&1; then
738+
echo " ✗ Isolated database service is not running on the server." >&2
739+
echo " Start it with: ssh $OC_PREVIEW_SSH systemctl start opencouncil-preview-db@$preview_db_pr" >&2
740+
exit 1
741+
fi
742+
743+
echo " ✓ Isolated database detected (port $preview_db_port)"
744+
745+
# Check port is free before opening tunnel
746+
if is_port_in_use "$preview_db_port"; then
747+
echo " ✗ Local port $preview_db_port is already in use." >&2
748+
echo " Kill the existing process (e.g., a leftover SSH tunnel) and retry." >&2
749+
exit 1
750+
fi
751+
752+
# Open SSH tunnel in background
753+
ssh -N -L "$preview_db_port:127.0.0.1:$preview_db_port" "$OC_PREVIEW_SSH" &
754+
ssh_tunnel_pid=$!
755+
756+
# Wait for tunnel to bind the local port
757+
for _i in $(seq 1 10); do
758+
if is_port_in_use "$preview_db_port"; then
759+
break
760+
fi
761+
sleep 0.5
762+
done
763+
if ! is_port_in_use "$preview_db_port"; then
764+
echo " ✗ SSH tunnel failed to bind port $preview_db_port." >&2
765+
kill "$ssh_tunnel_pid" 2>/dev/null || true
766+
exit 1
767+
fi
768+
769+
echo " ✓ SSH tunnel active: localhost:$preview_db_port → 127.0.0.1:$preview_db_port"
770+
771+
db_url="postgresql://opencouncil@localhost:$preview_db_port/opencouncil"
772+
direct_url="$db_url"
773+
echo " Database: $db_url"
774+
else
775+
# Shared staging DB: read DATABASE_URL from server's .env
776+
echo " ✓ Shared staging database detected"
777+
# shellcheck disable=SC2029
778+
preview_db_url=$(ssh "$OC_PREVIEW_SSH" "grep '^DATABASE_URL=' $preview_base_dir/.env 2>/dev/null | head -1 | cut -d= -f2-")
779+
if [ -z "$preview_db_url" ]; then
780+
echo " ✗ Could not read DATABASE_URL from $preview_base_dir/.env on server." >&2
781+
exit 1
782+
fi
783+
784+
db_url="$preview_db_url"
785+
direct_url="$preview_db_url"
786+
# Mask credentials in output
787+
echo " Database: ''${preview_db_url%%@*}@***"
788+
fi
789+
790+
echo ""
791+
# Force external DB mode (skip local postgres)
792+
db_mode="external"
793+
fi
794+
704795
# Check if TASK_API_URL is configured and reachable (non-blocking warning)
705796
if [ -z "$tasks_preview_pr" ] && [ -n "''${TASK_API_URL:-}" ]; then
706797
echo "🔗 Task API configured: $TASK_API_URL"
@@ -852,6 +943,12 @@ EOF
852943
cleanup_cmds="''${cleanup_cmds}kill $ngrok_pid 2>/dev/null || true; echo \"Stopped ngrok tunnel\";"
853944
fi
854945
946+
# If SSH tunnel is running, ensure it gets cleaned up on exit.
947+
if [ -n "$ssh_tunnel_pid" ]; then
948+
needs_cleanup=true
949+
cleanup_cmds="''${cleanup_cmds}kill $ssh_tunnel_pid 2>/dev/null || true; echo \"Stopped SSH tunnel\";"
950+
fi
951+
855952
# Brief pause so startup messages are readable before TUI takes over
856953
echo "Starting process-compose..."
857954
sleep 5

0 commit comments

Comments
 (0)