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 <noreply@anthropic.com>
This commit is contained in:
Vadym Samoilenko 2026-03-19 21:25:16 +00:00
parent 2b31281be5
commit 5c052e718d

View file

@ -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"