← back to Dw Vendor Microsites
lib/port-alloc.sh
69 lines
#!/usr/bin/env bash
# port-alloc.sh <vendor_code>
#
# Allocates the lowest free port >= 10000, checked against (a) ports already in
# manifest.json, (b) live remote `ss -ltn`, (c) live remote `pm2 jlist`. flock-atomic
# so concurrent iTerm sessions don't collide. If the vendor already has a port in the
# manifest, returns that (idempotent). Prints just the chosen port to stdout.
set -euo pipefail
VENDOR="${1:?usage: port-alloc.sh <vendor_code>}"
REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
MANIFEST="$REPO/manifest.json"
LOCKFILE="$REPO/.port-alloc.lock"
LOW=10000; HIGH=10999
# idempotent: if this vendor already has a port recorded, reuse it
if [ -f "$MANIFEST" ]; then
existing="$(python3 -c "
import json,sys
try:
m=json.load(open('$MANIFEST'))
except: sys.exit()
v=(m.get('vendors') or {}).get('$VENDOR') or {}
p=v.get('port')
print(p if p else '')
" 2>/dev/null || true)"
if [ -n "$existing" ]; then echo "$existing"; exit 0; fi
fi
# macOS has no flock(1); use a portable mkdir-based mutex (atomic create).
LOCKDIR="${LOCKFILE}.d"
for _i in $(seq 1 100); do
if mkdir "$LOCKDIR" 2>/dev/null; then break; fi
sleep 0.1
done
trap 'rmdir "$LOCKDIR" 2>/dev/null || true' EXIT
# ports already claimed in the manifest
USED_MANIFEST="$(python3 -c "
import json
try:
m=json.load(open('$MANIFEST'))
print(' '.join(str(v.get('port')) for v in (m.get('vendors') or {}).values() if v.get('port')))
except: print('')
" 2>/dev/null || true)"
# live remote ports (ss + pm2). Soft-fail to empty on ssh hiccup.
USED_REMOTE="$(ssh -o ConnectTimeout=10 my-server '
(ss -ltn 2>/dev/null | awk "{print \$4}" | sed -E "s/.*:([0-9]+)\$/\1/" | grep -E "^[0-9]+\$");
(pm2 jlist 2>/dev/null | python3 -c "import sys,json;
try:
d=json.load(sys.stdin)
print(\"\n\".join(str((p.get(\"pm2_env\",{}).get(\"PORT\") or \"\")) for p in d))
except: pass" 2>/dev/null)
' 2>/dev/null | grep -E '^[0-9]+$' | sort -u || true)"
ALL_USED=" $USED_MANIFEST $USED_REMOTE "
port=""
for ((p=LOW; p<=HIGH; p++)); do
case "$ALL_USED" in
*" $p "*) continue ;;
*) port=$p; break ;;
esac
done
[ -z "$port" ] && { echo "port-alloc: no free port in $LOW-$HIGH" >&2; exit 1; }
echo "$port"