From 4ddf4b9933131b608b0a0108d76afe71bd274547 Mon Sep 17 00:00:00 2001 From: saadaltafofficial Date: Thu, 23 Jul 2026 12:57:19 +0500 Subject: [PATCH] fix(dev): allow configurable Postgres and Redis host ports Defaults stay 5432/6379; remap via PGPORT / BUZZ_REDIS_HOST_PORT when local services already own the standard ports. Setup validates URL/port pairs and checks collisions on the configured ports. Closes #2479 Signed-off-by: saadaltafofficial --- .env.example | 16 +++++++-- docker-compose.yml | 7 ++-- scripts/dev-setup.sh | 85 +++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 100 insertions(+), 8 deletions(-) diff --git a/.env.example b/.env.example index 1e69c6a0e3..18cb5c70f9 100644 --- a/.env.example +++ b/.env.example @@ -7,11 +7,18 @@ # All defaults here work with `docker compose up` out of the box. # # Service ports (defaults): -# Postgres → localhost:5432 -# Redis → localhost:6379 +# Postgres → localhost:5432 (host publish via PGPORT) +# Redis → localhost:6379 (host publish via BUZZ_REDIS_HOST_PORT) # Typesense → localhost:8108 # Adminer → localhost:8082 (DB browser UI) # +# If Homebrew (or another local) Postgres/Redis already owns 5432/6379, remap +# the Docker host ports and keep each connection URL in sync, e.g.: +# PGPORT=55432 +# DATABASE_URL=postgres://buzz:buzz_dev@localhost:55432/buzz +# BUZZ_REDIS_HOST_PORT=56379 +# REDIS_URL=redis://localhost:56379 +# # Note: If port 8082 conflicts, change the adminer port in docker-compose.yml # ============================================================================= @@ -22,6 +29,8 @@ DATABASE_URL=postgres://buzz:buzz_dev@localhost:5432/buzz # Optional read-replica URL; unset/blank keeps all reads on the writer. # READ_DATABASE_URL=postgres://buzz:buzz_dev@localhost:5433/buzz PGHOST=localhost +# Host port published by docker-compose.yml (`${PGPORT:-5432}:5432`). +# Must match the port embedded in DATABASE_URL. PGPORT=5432 PGUSER=buzz PGPASSWORD=buzz_dev @@ -31,6 +40,9 @@ PGDATABASE=buzz # Redis 7 # ----------------------------------------------------------------------------- REDIS_URL=redis://localhost:6379 +# Host port published by docker-compose.yml (`${BUZZ_REDIS_HOST_PORT:-6379}:6379`). +# Must match the port embedded in REDIS_URL. +BUZZ_REDIS_HOST_PORT=6379 # ----------------------------------------------------------------------------- # Typesense (search) diff --git a/docker-compose.yml b/docker-compose.yml index 0056ea6a42..9d7ea39729 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -10,7 +10,9 @@ services: POSTGRES_DB: buzz PGDATA: /var/lib/postgresql/data ports: - - "5432:5432" + # Host port is configurable via PGPORT (see .env.example). Keep the + # container-side 5432 so healthchecks and `docker exec` stay stable. + - "${PGPORT:-5432}:5432" volumes: - postgres-data:/var/lib/postgresql/data networks: @@ -34,7 +36,8 @@ services: image: redis:7-alpine container_name: buzz-redis ports: - - "6379:6379" + # Host port is configurable via BUZZ_REDIS_HOST_PORT (see .env.example). + - "${BUZZ_REDIS_HOST_PORT:-6379}:6379" networks: - buzz-net healthcheck: diff --git a/scripts/dev-setup.sh b/scripts/dev-setup.sh index ae358517b3..d092738c01 100755 --- a/scripts/dev-setup.sh +++ b/scripts/dev-setup.sh @@ -40,6 +40,19 @@ cd "${REPO_ROOT}" # ---- Load environment ------------------------------------------------------- +# Extract the host port from a postgres:// or redis:// URL. Empty when absent +# or unparsable (e.g. unix sockets / missing authority). +url_host_port() { + local url="$1" + local authority + authority="${url#*://}" + authority="${authority%%/*}" + authority="${authority##*@}" + if [[ "${authority}" == *:* ]]; then + echo "${authority##*:}" + fi +} + load_env() { if [[ -f ".env" ]]; then log "Loading .env..." @@ -67,6 +80,33 @@ load_env() { export PGPASSWORD="${PGPASSWORD:-buzz_dev}" export PGDATABASE="${PGDATABASE:-buzz}" export REDIS_URL="${REDIS_URL:-redis://localhost:6379}" + + # Host publish port for buzz-redis. Prefer an explicit override; otherwise + # derive from REDIS_URL so a remapped URL alone is enough. + if [[ -z "${BUZZ_REDIS_HOST_PORT:-}" ]]; then + BUZZ_REDIS_HOST_PORT="$(url_host_port "${REDIS_URL}")" + fi + export BUZZ_REDIS_HOST_PORT="${BUZZ_REDIS_HOST_PORT:-6379}" +} + +# Fail when host-port knobs disagree with the connection URLs Compose clients +# will actually dial. Keeps "I changed PGPORT but forgot DATABASE_URL" from +# becoming a mysterious connection refused after a healthy `docker compose up`. +validate_host_port_urls() { + local db_port redis_port + db_port="$(url_host_port "${DATABASE_URL}")" + if [[ -n "${db_port}" && "${db_port}" != "${PGPORT}" ]]; then + error "PGPORT (${PGPORT}) does not match the port in DATABASE_URL (${db_port})." + error "Update both together, e.g. PGPORT=${db_port} and DATABASE_URL=...@localhost:${db_port}/buzz" + exit 1 + fi + + redis_port="$(url_host_port "${REDIS_URL}")" + if [[ -n "${redis_port}" && "${redis_port}" != "${BUZZ_REDIS_HOST_PORT}" ]]; then + error "BUZZ_REDIS_HOST_PORT (${BUZZ_REDIS_HOST_PORT}) does not match the port in REDIS_URL (${redis_port})." + error "Update both together, e.g. BUZZ_REDIS_HOST_PORT=${redis_port} and REDIS_URL=redis://localhost:${redis_port}" + exit 1 + fi } cleanup_legacy_sprout_containers() { @@ -82,30 +122,63 @@ cleanup_legacy_sprout_containers() { success "Legacy sprout-* containers removed (volumes preserved)" } -fail_if_local_redis_blocks_compose() { +# List PIDs listening on a TCP port that look like a host Redis server (not +# Docker's published buzz-redis). Empty when none / lsof unavailable. +host_redis_listener_pids() { + local port="$1" if ! command -v lsof >/dev/null 2>&1; then return fi + lsof -nP -iTCP:"${port}" -sTCP:LISTEN 2>/dev/null \ + | awk 'NR > 1 && $1 == "redis-ser" {print $2}' \ + | sort -u \ + | tr '\n' ' ' +} + +fail_if_local_redis_blocks_compose() { if docker ps --format '{{.Names}}' | grep -qx 'buzz-redis'; then return fi local redis_pids - redis_pids=$(lsof -nP -iTCP:6379 -sTCP:LISTEN 2>/dev/null | awk 'NR > 1 && $1 == "redis-ser" {print $2}' | sort -u | tr ' -' ' ' || true) + redis_pids="$(host_redis_listener_pids "${BUZZ_REDIS_HOST_PORT}")" if [[ -n "${redis_pids}" ]]; then - error "Local Redis is already listening on port 6379 (pid(s): ${redis_pids}). Stop it before running setup: brew services stop redis" + error "Local Redis is already listening on port ${BUZZ_REDIS_HOST_PORT} (pid(s): ${redis_pids})." + error "Stop it, or remap Buzz by setting BUZZ_REDIS_HOST_PORT and REDIS_URL together (see .env.example)." + exit 1 + fi +} + +fail_if_local_postgres_blocks_compose() { + if ! command -v lsof >/dev/null 2>&1; then + return + fi + if docker ps --format '{{.Names}}' | grep -qx 'buzz-postgres'; then + return + fi + local pg_pids + # Match both "postgres" and truncated "postgre" COMMAND names from lsof. + pg_pids=$(lsof -nP -iTCP:"${PGPORT}" -sTCP:LISTEN 2>/dev/null \ + | awk 'NR > 1 && ($1 == "postgres" || $1 == "postgre") {print $2}' \ + | sort -u \ + | tr '\n' ' ' || true) + if [[ -n "${pg_pids}" ]]; then + error "Local Postgres is already listening on port ${PGPORT} (pid(s): ${pg_pids})." + error "Stop it, or remap Buzz by setting PGPORT and DATABASE_URL together (see .env.example)." exit 1 fi } postgres_accepting_connections() { + # Always probe the container-internal port — host PGPORT only affects publish. docker exec buzz-postgres \ pg_isready -h localhost -p 5432 -U "${PGUSER}" -d "${PGDATABASE}" \ >/dev/null 2>&1 } load_env +validate_host_port_urls cleanup_legacy_sprout_containers +fail_if_local_postgres_blocks_compose fail_if_local_redis_blocks_compose # ---- Start services --------------------------------------------------------- @@ -190,6 +263,10 @@ echo -e " ${BLUE}Redis${NC} ${REDIS_URL}" echo -e " ${BLUE}Adminer${NC} http://localhost:8082 (DB browser)" echo -e " ${BLUE}Keycloak${NC} http://localhost:8180 (admin / admin — local OAuth testing)" echo "" +if [[ "${PGPORT}" != "5432" || "${BUZZ_REDIS_HOST_PORT}" != "6379" ]]; then + echo -e " ${YELLOW}Host ports remapped:${NC} Postgres ${PGPORT}, Redis ${BUZZ_REDIS_HOST_PORT}" + echo "" +fi echo -e " ${YELLOW}Next steps:${NC}" echo -e " just relay # start the relay (terminal 1)" echo -e " just dev # start the desktop app (terminal 2)"