From 5c052e718d23910f89f94d3f8da46a71c3310d60 Mon Sep 17 00:00:00 2001 From: Vadym Samoilenko Date: Thu, 19 Mar 2026 21:25:16 +0000 Subject: [PATCH] Fix deploy port conflict: auto-detect free port + persist to .env - Read API/WEB/PG/REDIS ports from .env on startup (Docker Compose loads .env directly, shell exports are ignored by it) - check_port: auto-find next free port instead of interactive prompt - set_env_port: write chosen port back to .env so it persists across runs - port_taken_by_other: use 'ss | grep' instead of ss sport filter (more reliable) Co-Authored-By: Claude Sonnet 4.6 --- deploy.sh | 78 +++++++++++++++++++++++++++++++++---------------------- 1 file changed, 47 insertions(+), 31 deletions(-) diff --git a/deploy.sh b/deploy.sh index 3a8ed6b..7f2b117 100755 --- a/deploy.sh +++ b/deploy.sh @@ -18,11 +18,12 @@ fi SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" cd "$SCRIPT_DIR" -# ── Default ports ───────────────────────────────────────────────────────────── -API_PORT=${API_PORT:-8000} -WEB_PORT=${WEB_PORT:-3000} -PG_PORT=${PG_PORT:-5432} -REDIS_PORT=${REDIS_PORT:-6379} +# ── Default ports — read from .env first, then env, then hardcoded default ──── +env_val() { local key=$1 def=$2; grep -E "^${key}=" .env 2>/dev/null | cut -d= -f2 | tr -d '"' | head -1 | grep -v '^$' || echo "$def"; } +API_PORT=${API_PORT:-$(env_val API_PORT 8000)} +WEB_PORT=${WEB_PORT:-$(env_val WEB_PORT 3000)} +PG_PORT=${PG_PORT:-$(env_val PG_PORT 5432)} +REDIS_PORT=${REDIS_PORT:-$(env_val REDIS_PORT 6379)} # ───────────────────────────────────────────────────────────────────────────── # STEP 1: Prerequisites — install missing packages @@ -68,11 +69,41 @@ sudo a2enmod proxy proxy_http proxy_wstunnel headers rewrite -q # ───────────────────────────────────────────────────────────────────────────── info "Step 1.5: Checking port availability..." -# Returns the PID+process using a port, empty if free or used by our containers -port_owner() { +# Check if a port is in use by a NON-deckforge process +port_taken_by_other() { local port=$1 - # ss -tlnp output: "users:(("process",pid=NNN,...))" - ss -tlnp "sport = :${port}" 2>/dev/null | awk 'NR>1 {print $NF}' | grep -oP '"[^"]+",pid=\K[0-9]+' | head -1 + # Check if anything is listening on this port + ss -tlnp 2>/dev/null | grep -qw ":${port}" || return 1 + + # Check if it belongs to OUR docker-compose project + local our_ids + our_ids=$(docker compose -f docker-compose.yml -f docker-compose.prod.yml ps -q 2>/dev/null || true) + if [[ -n "$our_ids" ]]; then + # shellcheck disable=SC2086 + if docker inspect $our_ids 2>/dev/null | grep -q "\"HostPort\": \"${port}\""; then + return 1 # It's ours — will be replaced on restart + fi + fi + return 0 # Taken by someone else +} + +# Find next free port starting from given port +find_free_port() { + local port=$1 + while port_taken_by_other "$port"; do + port=$((port + 1)) + done + echo "$port" +} + +# Write or update a key in .env +set_env_port() { + local key=$1 val=$2 + if grep -qE "^${key}=" .env 2>/dev/null; then + sed -i "s|^${key}=.*|${key}=${val}|" .env + else + echo "${key}=${val}" >> .env + fi } check_port() { @@ -80,32 +111,17 @@ check_port() { local varname=$2 local service=$3 - local pid - pid=$(port_owner "$port" || true) - - if [[ -z "$pid" ]]; then - info " Port $port ($service): free" + if ! port_taken_by_other "$port"; then + info " Port $port ($service): OK" return fi - # Check if it belongs to OUR docker-compose project (not other apps on server) - local our_ids - our_ids=$(docker compose -f docker-compose.yml -f docker-compose.prod.yml ps -q 2>/dev/null || true) - - if [[ -n "$our_ids" ]]; then - # shellcheck disable=SC2086 - if docker inspect $our_ids 2>/dev/null \ - | grep -q "\"HostPort\": \"${port}\""; then - info " Port $port ($service): used by our container (will be replaced on restart)" - return - fi - fi - - warn " Port $port ($service) is in use by PID $pid ($proc_name)" - read -rp " Enter alternative port for $service [$port]: " new_port - new_port="${new_port:-$port}" + local new_port + new_port=$(find_free_port "$((port + 1))") + warn " Port $port ($service) is taken by another process — using $new_port instead" eval "$varname=$new_port" - info " Using port $new_port for $service" + set_env_port "$varname" "$new_port" + info " Saved ${varname}=${new_port} to .env" } check_port "$API_PORT" API_PORT "api"