#!/bin/bash # Usage: # 1) Edit the PORT_FORWARDS list below with entries of: # # 2) Run: ./init_port_forward.sh # # The script will start all port-forwards in the background and keep running # until interrupted (Ctrl+C). On exit, it will clean up started port-forward processes. set -euo pipefail # Define your namespace/service/port combinations here # Example entries: # "default my-service 8080 80" # "observability grafana 3000 3000" PORT_FORWARDS=( "mongodb my-release-mongodb 27017 27017" "paradedb paradedb-rw 5432 5432" "redis redis-master 6379 6379" "temporal temporal-frontend 7233 7233" ) if [ ${#PORT_FORWARDS[@]} -eq 0 ]; then echo "No port-forward entries defined. Edit PORT_FORWARDS in $(basename "$0")." exit 1 fi PIDS=() cleanup() { echo "\nStopping port-forward processes..." for pid in "${PIDS[@]}"; do if kill -0 "$pid" >/dev/null 2>&1; then kill "$pid" >/dev/null 2>&1 || true fi done } trap cleanup EXIT INT TERM timestamp() { date '+%Y-%m-%d %H:%M:%S'; } # Allow overriding kubectl binary if needed KUBECTL=${KUBECTL:-kubectl} is_port_free() { local port="$1" # Consider port free if nothing is listening locally on it if command -v ss >/dev/null 2>&1; then ! ss -ltn | awk '{print $4}' | grep -E "(^|:|\\])${port}$" >/dev/null 2>&1 else if command -v lsof >/dev/null 2>&1; then ! lsof -tiTCP:"${port}" -sTCP:LISTEN >/dev/null 2>&1 else # Fallback: attempt to open a TCP connection; expect failure when nothing is listening ! (exec 3<>"/dev/tcp/127.0.0.1/${port}") 2>/dev/null fi fi } free_port_if_stuck() { local port="$1" # Try multiple tools to free a stuck listener (often old kubectl PF) if command -v lsof >/dev/null 2>&1; then local pids pids=$(lsof -tiTCP:"${port}" -sTCP:LISTEN 2>/dev/null || true) if [ -n "${pids}" ]; then echo "[$(timestamp)] Found listeners on ${port}: ${pids}; terminating" kill ${pids} 2>/dev/null || true sleep 0.5 fi fi if ! is_port_free "${port}"; then if command -v fuser >/dev/null 2>&1; then echo "[$(timestamp)] Forcing free of ${port} via fuser" fuser -k "${port}/tcp" 2>/dev/null || true sleep 0.5 fi fi } run_port_forward() { local namespace="$1" local service_name="$2" local local_port="$3" local service_port="$4" # simple and robust supervisor loop with gentle backoff on failures local delay=2 local max_delay=20 while true; do free_port_if_stuck "${local_port}" if ! is_port_free "${local_port}"; then echo "[$(timestamp)] ns=${namespace} svc=${service_name} ${local_port}:${service_port} -> local port busy, retrying in 3s" sleep 3 continue fi echo "[$(timestamp)] Starting port-forward: ns=${namespace} svc=${service_name} ${local_port}:${service_port}" ${KUBECTL} -n "${namespace}" port-forward "svc/${service_name}" "${local_port}:${service_port}" \ --address=127.0.0.1 --pod-running-timeout=2m --request-timeout=0 rc=$? # If kubectl exits (e.g., connection reset by peer), wait a bit and retry echo "[$(timestamp)] Port-forward exited (rc=${rc}): ns=${namespace} svc=${service_name} ${local_port}:${service_port}" sleep "${delay}" # Exponential backoff up to max_delay if [ ${delay} -lt ${max_delay} ]; then delay=$(( delay * 2 )) if [ ${delay} -gt ${max_delay} ]; then delay=${max_delay} fi fi done } ONLY_SERVICE_NAME="${ONLY_SERVICE_NAME:-}" for entry in "${PORT_FORWARDS[@]}"; do read -r NAMESPACE SERVICE_NAME LOCAL_PORT SERVICE_PORT <<< "$entry" if [ -n "${ONLY_SERVICE_NAME}" ] && [ "${SERVICE_NAME}" != "${ONLY_SERVICE_NAME}" ]; then continue fi run_port_forward "${NAMESPACE}" "${SERVICE_NAME}" "${LOCAL_PORT}" "${SERVICE_PORT}" & PIDS+=("$!") done echo "All port-forwards started: ${#PIDS[@]} process(es). Press Ctrl+C to stop." # Do not exit the script if one port-forward fails; they self-restart set +e wait