Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
9 changes: 7 additions & 2 deletions ENVIRONMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,13 @@

Use `.env.example` as the source of defaults.

When running inside Conductor, `CONDUCTOR_PORT` is treated as the first port in
the workspace's 10-port range for unset worktree defaults: Redis uses `+0`,
When running inside PASEO, `PASEO_PORT_BASE` and `PASEO_PORT_END` reserve the
inclusive range used for unset worktree defaults. The range must contain at
least seven ports: Redis uses `+0`, Postgres `+1`, the Compose web port `+2`,
MinIO API `+3`, MinIO Console `+4`, host-run web/API `+5`, and the bot health
check `+6`. PASEO takes precedence over Conductor. When running inside
Conductor, `CONDUCTOR_PORT` is treated as the first port in the workspace's
10-port range for unset worktree defaults: Redis uses `+0`,
Postgres `+1`, Compose web `+2`, MinIO API `+3`, MinIO console `+4`, host-run
web/API `+5`, and bot health `+6`. Explicit service port overrides keep their
current precedence rules.
Expand Down
9 changes: 7 additions & 2 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,8 +112,13 @@ For local dev, `./scripts/dev.sh` and `./scripts/docker-compose.sh` compute
deterministic per-worktree ports unless values are pinned in `.env` or the
invoking shell.

Inside Conductor, `CONDUCTOR_PORT` is treated as the first port in the
workspace's 10-port range for unset worktree defaults: Redis uses `+0`,
Inside PASEO, `PASEO_PORT_BASE` and `PASEO_PORT_END` reserve the inclusive
range used for unset worktree defaults. The range must contain at least seven
ports: Redis uses `+0`, Postgres `+1`, the Compose web port `+2`, MinIO API
`+3`, MinIO Console `+4`, host-run web/API `+5`, and the bot health check `+6`.
PASEO takes precedence over Conductor. Inside Conductor, `CONDUCTOR_PORT` is
treated as the first port in the workspace's 10-port range for unset worktree
defaults: Redis uses `+0`,
Postgres `+1`, Compose web `+2`, MinIO API `+3`, MinIO console `+4`,
host-run web/API `+5`, and bot health `+6`. Explicit service port overrides keep
their current precedence rules.
Expand Down
8 changes: 8 additions & 0 deletions paseo.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"scripts": {
"dev": {
"type": "service",
"command": "./scripts/dev.sh all"
}
}
}
58 changes: 46 additions & 12 deletions scripts/archive-workspace.sh
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,17 @@ normalize_decimal() {
printf '%s' "$value"
}

paseo_port_range_is_valid() {
local base end
base=${PASEO_PORT_BASE:-}
end=${PASEO_PORT_END:-}
[[ "$base" =~ ^[0-9]+$ && "$end" =~ ^[0-9]+$ ]] || return 1

base=$(normalize_decimal "$base")
end=$(normalize_decimal "$end")
[ "$base" -ge 1 ] && [ "$end" -le 65535 ] && [ "$end" -ge $((base + 6)) ]
}

while [ "$#" -gt 0 ]; do
case "$1" in
-n|--dry-run)
Expand Down Expand Up @@ -237,17 +248,30 @@ def listening_pids_for_port(port: int) -> list[int]:
return [int(line) for line in result.stdout.splitlines() if line.strip().isdigit()]


def conductor_ports() -> list[int]:
value = os.environ.get("CONDUCTOR_PORT")
if not value:
return []
def allocated_port_range() -> tuple[str, list[int]]:
paseo_base = os.environ.get("PASEO_PORT_BASE")
paseo_end = os.environ.get("PASEO_PORT_END")
if (
paseo_base
and paseo_end
and re.fullmatch(r"[0-9]+", paseo_base)
and re.fullmatch(r"[0-9]+", paseo_end)
):
base = int(paseo_base)
end = int(paseo_end)
if 1 <= base <= end <= 65535 and end >= base + 6:
return "PASEO_PORT range", [base + offset for offset in range(7)]

conductor_port = os.environ.get("CONDUCTOR_PORT")
if not conductor_port:
return "", []
try:
base = int(value)
base = int(conductor_port)
except ValueError:
return []
return "", []
if base < 1 or base > 65526:
return []
return [base + offset for offset in range(10)]
return "", []
return "CONDUCTOR_PORT range", [base + offset for offset in range(10)]


processes = list_processes()
Expand Down Expand Up @@ -281,7 +305,8 @@ for pid in list(selected):
"descendant of workspace dev process",
)

for port in conductor_ports():
port_range_name, allocated_ports = allocated_port_range()
for port in allocated_ports:
for pid in listening_pids_for_port(port):
process = processes.get(pid)
if process is None or not is_alive(pid):
Expand All @@ -291,13 +316,13 @@ for port in conductor_ports():
if command_mentions_workspace(process.command) or is_under_workspace(
cached_cwd(pid)
):
selected.setdefault(pid, f"listening on CONDUCTOR_PORT range port {port}")
selected.setdefault(pid, f"listening on {port_range_name} port {port}")
add_descendants(
pid,
processes,
children,
selected,
f"descendant of listener on CONDUCTOR_PORT range port {port}",
f"descendant of listener on {port_range_name} port {port}",
)

for pid in sorted(selected):
Expand Down Expand Up @@ -335,7 +360,13 @@ signal_processes() {
}

echo "Archiving workspace: $workspace"
if [[ "${CONDUCTOR_PORT:-}" =~ ^[0-9]+$ ]]; then
if [[ "${PASEO_PORT_BASE:-}" =~ ^[0-9]+$ && "${PASEO_PORT_END:-}" =~ ^[0-9]+$ ]]; then
paseo_port_base=$(normalize_decimal "$PASEO_PORT_BASE")
paseo_port_end=$(normalize_decimal "$PASEO_PORT_END")
if [ "$paseo_port_base" -ge 1 ] && [ "$paseo_port_end" -le 65535 ] && [ "$paseo_port_end" -ge $((paseo_port_base + 6)) ]; then
echo "PASEO port range: ${paseo_port_base}..${paseo_port_end}"
fi
elif [[ "${CONDUCTOR_PORT:-}" =~ ^[0-9]+$ ]]; then
conductor_port_base=$(normalize_decimal "$CONDUCTOR_PORT")
echo "Conductor port range: ${conductor_port_base}..$((conductor_port_base + 9))"
fi
Expand All @@ -356,5 +387,8 @@ if [ "$skip_docker" -eq 1 ]; then
elif [ "$dry_run" -eq 1 ]; then
echo "[dry-run] ./scripts/docker-compose.sh down --remove-orphans"
else
if ! paseo_port_range_is_valid; then
unset PASEO_PORT_BASE PASEO_PORT_END
fi
./scripts/docker-compose.sh down --remove-orphans
fi
91 changes: 68 additions & 23 deletions scripts/worktree-env.sh
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,32 @@ worktree_env_resolve_conductor_port_base() {
printf '%s' "$conductor_port_base"
}

worktree_env_resolve_paseo_port_base() {
paseo_port_base=${PASEO_PORT_BASE-}
paseo_port_end=${PASEO_PORT_END-}

if [ -z "$paseo_port_base" ] && [ -z "$paseo_port_end" ]; then
return 1
fi

if [ -z "$paseo_port_base" ] || [ -z "$paseo_port_end" ]; then
echo "PASEO_PORT_BASE and PASEO_PORT_END must be set together." >&2
return 2
fi

worktree_env_validate_port_number "$paseo_port_base" "PASEO_PORT_BASE" || return 2
worktree_env_validate_port_number "$paseo_port_end" "PASEO_PORT_END" || return 2
paseo_port_base=$(worktree_env_decimal_value "$paseo_port_base")
paseo_port_end=$(worktree_env_decimal_value "$paseo_port_end")

if [ "$paseo_port_end" -lt $((paseo_port_base + 6)) ]; then
echo "PASEO_PORT_BASE through PASEO_PORT_END must include at least seven ports, got '${PASEO_PORT_BASE}' through '${PASEO_PORT_END}'." >&2
return 2
fi

printf '%s' "$paseo_port_base"
}

worktree_env_validate_port_number() {
port_value=$1
port_label=$2
Expand Down Expand Up @@ -154,8 +180,12 @@ worktree_env_finalize_browser_safe_port() {
resolved_value=$(worktree_env_decimal_value "$resolved_value")

if [ "$source_label" = "default" ]; then
if [ "${WORKTREE_ENV_PORT_DEFAULT_SOURCE-}" = "conductor" ] && worktree_env_is_browser_unsafe_port "$resolved_value"; then
echo "$port_label defaults to browser-unsafe port '$resolved_value' from CONDUCTOR_PORT; set $port_label explicitly or use a different CONDUCTOR_PORT." \
if { [ "${WORKTREE_ENV_PORT_DEFAULT_SOURCE-}" = "conductor" ] || [ "${WORKTREE_ENV_PORT_DEFAULT_SOURCE-}" = "paseo" ]; } && worktree_env_is_browser_unsafe_port "$resolved_value"; then
source_name=CONDUCTOR_PORT
if [ "${WORKTREE_ENV_PORT_DEFAULT_SOURCE-}" = "paseo" ]; then
source_name=PASEO_PORT_BASE
fi
echo "$port_label defaults to browser-unsafe port '$resolved_value' from $source_name; set $port_label explicitly or use a different $source_name." \
>&2
return 1
fi
Expand Down Expand Up @@ -285,29 +315,44 @@ worktree_env_load() {
fi

COMPOSE_PROJECT_NAME=$(worktree_env_resolve_value COMPOSE_PROJECT_NAME "${project_name}-$(printf '%04d' "$WORKTREE_ENV_SLOT")" "$WORKTREE_ENV_FILE")
if conductor_port_base=$(worktree_env_resolve_conductor_port_base); then
WORKTREE_ENV_PORT_DEFAULT_SOURCE=conductor
REDIS_HOST_PORT_DEFAULT=$conductor_port_base
POSTGRES_HOST_PORT_DEFAULT=$((conductor_port_base + 1))
WEB_HOST_PORT_DEFAULT=$((conductor_port_base + 2))
MINIO_API_HOST_PORT_DEFAULT=$((conductor_port_base + 3))
MINIO_CONSOLE_HOST_PORT_DEFAULT=$((conductor_port_base + 4))
WEB_PORT_DEFAULT=$((conductor_port_base + 5))
HEALTHCHECK_PORT_DEFAULT=$((conductor_port_base + 6))
if paseo_port_base=$(worktree_env_resolve_paseo_port_base); then
WORKTREE_ENV_PORT_DEFAULT_SOURCE=paseo
Comment thread
michaelmwu marked this conversation as resolved.
REDIS_HOST_PORT_DEFAULT=$paseo_port_base
POSTGRES_HOST_PORT_DEFAULT=$((paseo_port_base + 1))
WEB_HOST_PORT_DEFAULT=$((paseo_port_base + 2))
MINIO_API_HOST_PORT_DEFAULT=$((paseo_port_base + 3))
MINIO_CONSOLE_HOST_PORT_DEFAULT=$((paseo_port_base + 4))
WEB_PORT_DEFAULT=$((paseo_port_base + 5))
HEALTHCHECK_PORT_DEFAULT=$((paseo_port_base + 6))
else
conductor_port_status=$?
if [ "$conductor_port_status" -ne 1 ]; then
return "$conductor_port_status"
paseo_port_status=$?
if [ "$paseo_port_status" -ne 1 ]; then
return "$paseo_port_status"
Comment thread
michaelmwu marked this conversation as resolved.
fi
if conductor_port_base=$(worktree_env_resolve_conductor_port_base); then
WORKTREE_ENV_PORT_DEFAULT_SOURCE=conductor
REDIS_HOST_PORT_DEFAULT=$conductor_port_base
POSTGRES_HOST_PORT_DEFAULT=$((conductor_port_base + 1))
WEB_HOST_PORT_DEFAULT=$((conductor_port_base + 2))
MINIO_API_HOST_PORT_DEFAULT=$((conductor_port_base + 3))
MINIO_CONSOLE_HOST_PORT_DEFAULT=$((conductor_port_base + 4))
WEB_PORT_DEFAULT=$((conductor_port_base + 5))
HEALTHCHECK_PORT_DEFAULT=$((conductor_port_base + 6))
else
conductor_port_status=$?
if [ "$conductor_port_status" -ne 1 ]; then
return "$conductor_port_status"
fi

WORKTREE_ENV_PORT_DEFAULT_SOURCE=worktree
REDIS_HOST_PORT_DEFAULT=$((12000 + WORKTREE_ENV_SLOT))
POSTGRES_HOST_PORT_DEFAULT=$((15432 + WORKTREE_ENV_SLOT))
WEB_HOST_PORT_DEFAULT=$((20080 + WORKTREE_ENV_SLOT))
MINIO_API_HOST_PORT_DEFAULT=$((24000 + WORKTREE_ENV_SLOT))
MINIO_CONSOLE_HOST_PORT_DEFAULT=$((28000 + WORKTREE_ENV_SLOT))
WEB_PORT_DEFAULT=$((18080 + WORKTREE_ENV_SLOT))
HEALTHCHECK_PORT_DEFAULT=$((30000 + WORKTREE_ENV_SLOT))
fi

WORKTREE_ENV_PORT_DEFAULT_SOURCE=worktree
REDIS_HOST_PORT_DEFAULT=$((12000 + WORKTREE_ENV_SLOT))
POSTGRES_HOST_PORT_DEFAULT=$((15432 + WORKTREE_ENV_SLOT))
WEB_HOST_PORT_DEFAULT=$((20080 + WORKTREE_ENV_SLOT))
MINIO_API_HOST_PORT_DEFAULT=$((24000 + WORKTREE_ENV_SLOT))
MINIO_CONSOLE_HOST_PORT_DEFAULT=$((28000 + WORKTREE_ENV_SLOT))
WEB_PORT_DEFAULT=$((18080 + WORKTREE_ENV_SLOT))
HEALTHCHECK_PORT_DEFAULT=$((30000 + WORKTREE_ENV_SLOT))
fi

REDIS_HOST_PORT=$(worktree_env_resolve_value REDIS_HOST_PORT "$REDIS_HOST_PORT_DEFAULT" "$WORKTREE_ENV_FILE")
Expand Down
Loading