diff --git a/deploy/deploy.sh b/deploy/deploy.sh index 03d8aab..ec74f3b 100755 --- a/deploy/deploy.sh +++ b/deploy/deploy.sh @@ -84,17 +84,35 @@ port_in_use() { [[ -n "$pid" ]] } +# Echo the compose project name of the docker container publishing $port, +# or empty string if no docker container holds it (might be a host process +# or a non-published container). +port_owner_project() { + local port=$1 + docker ps --format '{{.Label "com.docker.compose.project"}}|{{.Ports}}' 2>/dev/null \ + | awk -F'|' -v p=":${port}->" '$2 ~ p {print $1; exit}' +} + +# True (0) if the port is free for us to bind: either nothing's listening, +# or one of our own compose containers is publishing it (recreating the +# stack will hand it back to us). Other-process listeners → false. +port_available_for_us() { + local port=$1 + if ! port_in_use "$port"; then return 0; fi + [[ "$(port_owner_project "$port")" == "$COMPOSE_PROJECT" ]] +} + find_free_port() { local preferred=$1 local start=$2 local end=$3 - if ! port_in_use "$preferred"; then + if port_available_for_us "$preferred"; then printf '%s' "$preferred" return 0 fi local p for ((p=start; p<=end; p++)); do - if ! port_in_use "$p"; then + if port_available_for_us "$p"; then printf '%s' "$p" return 0 fi @@ -162,26 +180,25 @@ PREV_APP_PORT="$APP_PORT" log "Resolving host ports (preferred: app=$APP_PORT db=$DB_PORT)…" -RUNNING=$(docker compose ps -q 2>/dev/null | wc -l | tr -d ' ') -if [[ "$RUNNING" -gt 0 ]]; then - ok "Project '$COMPOSE_PROJECT' has $RUNNING container(s) running — keeping current port assignment." -else - NEW_APP_PORT=$(find_free_port "$APP_PORT" 3102 3199) || NEW_APP_PORT="" - NEW_DB_PORT=$(find_free_port "$DB_PORT" 5435 5499) || NEW_DB_PORT="" - if [[ -z "$NEW_APP_PORT" || -z "$NEW_DB_PORT" ]]; then - err "Could not find free ports." - err " app desired=$APP_PORT scanned=3102-3199" - err " db desired=$DB_PORT scanned=5435-5499" - exit 1 - fi - [[ "$NEW_APP_PORT" != "$APP_PORT" ]] && warn "app port $APP_PORT busy → using $NEW_APP_PORT" - [[ "$NEW_DB_PORT" != "$DB_PORT" ]] && warn "db port $DB_PORT busy → using $NEW_DB_PORT" - APP_PORT=$NEW_APP_PORT - DB_PORT=$NEW_DB_PORT - set_env_var ADEO_PORT "$APP_PORT" - set_env_var ADEO_DB_PORT "$DB_PORT" - ok "Ports: app=$APP_PORT db=$DB_PORT (persisted to .env)" +# Always validate against live host state. `find_free_port` returns the +# preferred port if it's free OR already held by one of our own compose +# containers (so re-deploys don't shuffle ports). If another app on the +# shared host has grabbed the port, we re-pick. +NEW_APP_PORT=$(find_free_port "$APP_PORT" 3102 3199) || NEW_APP_PORT="" +NEW_DB_PORT=$(find_free_port "$DB_PORT" 5435 5499) || NEW_DB_PORT="" +if [[ -z "$NEW_APP_PORT" || -z "$NEW_DB_PORT" ]]; then + err "Could not find free ports." + err " app desired=$APP_PORT scanned=3102-3199" + err " db desired=$DB_PORT scanned=5435-5499" + exit 1 fi +[[ "$NEW_APP_PORT" != "$APP_PORT" ]] && warn "app port $APP_PORT taken by another app → using $NEW_APP_PORT" +[[ "$NEW_DB_PORT" != "$DB_PORT" ]] && warn "db port $DB_PORT taken by another app → using $NEW_DB_PORT" +APP_PORT=$NEW_APP_PORT +DB_PORT=$NEW_DB_PORT +set_env_var ADEO_PORT "$APP_PORT" +set_env_var ADEO_DB_PORT "$DB_PORT" +ok "Ports: app=$APP_PORT db=$DB_PORT (persisted to .env)" # ---------- 4. Render apache-adeo.conf from template ----------