Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,17 @@ npx dev-pm restart <service> # Restart a service
npx dev-pm shutdown # Stop all services
```

#### Running multiple instances in parallel

To run several checkouts at once without host port collisions, assign a free port offset before starting:

```bash
./assign-ports.sh # find a free +100..+900 offset and write base+offset ports to .env.local
npm run dev
```

`assign-ports.sh` reads every `*_PORT` entry from `.env`, finds an offset where all ports are free, and writes the offset ports to `.env.local` (leaving `.env` untouched). `npm run dev` runs `dev-pm start` via `dotenv -c secrets`, which exports the cascaded env (including `.env.local`) so both the Node services and the Docker containers (`docker compose` prefers real env vars over the `.env` file) bind the offset ports.

### Building

```bash
Expand Down
80 changes: 80 additions & 0 deletions assign-ports.sh
Comment thread
VPS-Obi marked this conversation as resolved.
Outdated
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
#!/bin/bash
# Assigns unique ports so multiple instances of this project can run in parallel.
# Prefers the base ports (no offset) and only falls back to an offset when those are taken.

set -euo pipefail

if [ ! -f ".env" ]; then
echo "Error: No .env file found in $PWD" >&2
exit 1
fi

# Read all *_PORT entries from .env
declare -a PORT_NAMES=()
declare -a BASE_PORTS=()
while IFS='=' read -r key value; do
PORT_NAMES+=("$key")
BASE_PORTS+=("$value")
done < <(grep -E '^[A-Z_]+_PORT=[0-9]+$' .env)

if [ ${#BASE_PORTS[@]} -eq 0 ]; then
echo "Error: No *_PORT entries found in .env" >&2
exit 1
fi

port_is_free() {
! lsof -iTCP:"$1" -sTCP:LISTEN -t >/dev/null 2>&1
}

CONFLICT_NAME=""
CONFLICT_PORT=""
all_ports_free() {
local offset=$1
for i in "${!BASE_PORTS[@]}"; do
local port=$((BASE_PORTS[i] + offset))
if ! port_is_free "$port"; then
CONFLICT_NAME="${PORT_NAMES[$i]}"
CONFLICT_PORT="$port"
return 1
fi
done
return 0
}

# Prefer no offset (base ports); fall back to +100, +200, ..., +900 if those are taken
PORT_OFFSET=""
for candidate in $(seq 0 100 900); do
if all_ports_free "$candidate"; then
PORT_OFFSET=$candidate
break
fi
echo "Offset +$candidate has a port conflict (${CONFLICT_NAME}=${CONFLICT_PORT} in use), trying next..." >&2
done

if [ -z "$PORT_OFFSET" ]; then
echo "Error: Could not find a free port offset (tried 0-900)" >&2
exit 1
fi

# Remove any existing port assignments from .env.local first to avoid duplicates/stale offsets.
# Single portable pass (works with both GNU and BSD tools, unlike `sed -i ''`).
if [ -f .env.local ]; then
names_pattern="^($(IFS='|'; echo "${PORT_NAMES[*]}"))="
grep -Ev "$names_pattern" .env.local > .env.local.tmp || true
mv .env.local.tmp .env.local
fi

if [ "$PORT_OFFSET" -eq 0 ]; then
# Base ports are free: leave .env.local without port overrides so the .env defaults apply
echo "All base ports are free, using no offset (.env defaults apply)" >&2
else
# Write offset ports to .env.local (leaves .env unchanged)
{
for i in "${!PORT_NAMES[@]}"; do
name="${PORT_NAMES[$i]}"
base="${BASE_PORTS[$i]}"
echo "${name}=$((base + PORT_OFFSET))"
done
} >> .env.local
echo "Ports offset by +$PORT_OFFSET written to .env.local" >&2
fi
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"private": true,
"scripts": {
"create-site-configs-env": "dotenv -e .env.secrets -e .env.local -e .env -- npx @comet/cli inject-site-configs -f site-configs/site-configs.ts -i .env.site-configs.tpl -o .env.site-configs --base64",
"dev": "npm run create-site-configs-env && dev-pm start",
"dev": "npm run create-site-configs-env && dotenv -c secrets -- dev-pm start",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is necessary for Docker to pick up the overridden ports. However, this sets the variables for all scripts in dev-pm config. I'd prefer only setting it for Docker. Maybe we need a dev:docker script? Also, -c secrets isn't necessary, simply dotenv should be enough.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please don't call dotenv here. Instead, change the docker script in the dev-pm config to set -a; . .env; . .env.local; set +a; docker compose up (we did this in Demo and projects).

"dev:auth-proxy": "dotenv -- ./node_modules/.bin/oauth2-proxy --cookie-secret=$(head -c 16 /dev/random | base64)",
"dev:auth-provider": "dotenv -- dev-oidc-provider",
"setup-project-files": "node setup-project-files.js",
Expand Down
Loading