feat(scripts): add develop.sh port flag (#22961)

## Summary
- add a `--port` flag to `scripts/develop.sh` so the API can run on a
non-default port
- make the script use the selected API port for access URL defaults,
readiness checks, login, proxy wiring, and printed URLs
- reject oversized `--port` values early and treat an existing dev
frontend on port 8080 as a conflict when the requested API is not
already running
- pin the frontend dev server to port 8080 so inherited `PORT`
environment variables do not move it to a different port

## Testing
- `bash -n scripts/develop.sh`
- `shellcheck -x scripts/develop.sh`
- `bash scripts/develop.sh --port abc`
- `bash scripts/develop.sh --port 8080`
- `bash scripts/develop.sh --port 999999999999999999999`
- started `./scripts/develop.sh --port 3001` and verified:
  - `http://127.0.0.1:3001/healthz`
  - `http://127.0.0.1:3001/api/v2/buildinfo`
  - `http://127.0.0.1:8080/healthz`
  - `http://127.0.0.1:8080/api/v2/buildinfo`
- simulated an existing dev frontend on `127.0.0.1:8080` and verified
`./scripts/develop.sh --port 3001` exits with a conflict error
This commit is contained in:
Michael Suchacz
2026-03-11 20:11:03 +01:00
committed by GitHub
parent 1f37df4db3
commit 51198744ff
+67 -22
View File
@@ -1,9 +1,10 @@
#!/usr/bin/env bash
# Usage: ./develop.sh [--agpl]
# Usage: ./develop.sh [--agpl] [--port <port>]
#
# If the --agpl parameter is specified, builds only the AGPL-licensed code (no
# Coder enterprise features).
# Coder enterprise features). The --port parameter changes the API port. The
# frontend dev server still listens on port 8080.
SCRIPT_DIR=$(dirname "${BASH_SOURCE[0]}")
# shellcheck source=scripts/lib.sh
@@ -13,7 +14,14 @@ source "${SCRIPT_DIR}/lib.sh"
[[ -n ${VERBOSE:-} ]] && set -x
set -euo pipefail
CODER_DEV_ACCESS_URL="${CODER_DEV_ACCESS_URL:-http://127.0.0.1:3000}"
api_port=3000
web_port=8080
proxy_port=3010
CODER_DEV_ACCESS_URL="${CODER_DEV_ACCESS_URL:-}"
access_url_set=0
if [ -n "${CODER_DEV_ACCESS_URL}" ]; then
access_url_set=1
fi
DEVELOP_IN_CODER="${DEVELOP_IN_CODER:-0}"
debug=0
DEFAULT_PASSWORD="SomeSecurePassword!"
@@ -26,12 +34,17 @@ multi_org=0
unset CODER_SESSION_TOKEN
unset CODER_URL
args="$(getopt -o "" -l access-url:,use-proxy,agpl,debug,password:,multi-organization -- "$@")"
args="$(getopt -o "" -l access-url:,use-proxy,agpl,debug,password:,multi-organization,port: -- "$@")"
eval set -- "$args"
while true; do
case "$1" in
--access-url)
CODER_DEV_ACCESS_URL="$2"
access_url_set=1
shift 2
;;
--port)
api_port="$2"
shift 2
;;
--agpl)
@@ -72,28 +85,59 @@ if [ "${CODER_BUILD_AGPL:-0}" -gt "0" ] && [ "${multi_org}" -gt "0" ]; then
echo '== ERROR: cannot use both multi-organizations and APGL build.' && exit 1
fi
validate_port() {
local port=$1
local flag=$2
if ! [[ "${port}" =~ ^[0-9]+$ ]]; then
error "${flag} must be an integer between 1 and 65535"
fi
if [ "${#port}" -gt 5 ]; then
error "${flag} must be an integer between 1 and 65535"
fi
if ((10#${port} < 1 || 10#${port} > 65535)); then
error "${flag} must be an integer between 1 and 65535"
fi
}
validate_port "${api_port}" "--port"
if [ "${api_port}" -eq "${web_port}" ]; then
error "--port cannot use ${web_port} because the frontend dev server uses that port"
fi
if [ "${use_proxy}" -gt "0" ] && [ "${api_port}" -eq "${proxy_port}" ]; then
error "--port cannot use ${proxy_port} when --use-proxy is enabled because the workspace proxy uses that port"
fi
if [ "${access_url_set}" -eq 0 ]; then
CODER_DEV_ACCESS_URL="http://127.0.0.1:${api_port}"
fi
api_url="http://127.0.0.1:${api_port}"
api_local_url="http://localhost:${api_port}"
web_url="http://127.0.0.1:${web_port}"
if [ -n "${CODER_AGENT_URL:-}" ]; then
DEVELOP_IN_CODER=1
fi
# Preflight checks: ensure we have our required dependencies, and make sure nothing is listening on port 3000 or 8080
# Preflight checks: ensure we have our required dependencies, and make sure
# nothing is listening on the configured API or frontend ports.
dependencies curl git go jq make pnpm
if curl --silent --fail http://127.0.0.1:3000; then
if curl --silent --fail "${api_url}" >/dev/null 2>&1; then
# Check if this is the Coder development server.
if curl --silent --fail http://127.0.0.1:3000/api/v2/buildinfo 2>&1 | jq -r '.version' >/dev/null 2>&1; then
echo '== INFO: Coder development server is already running on port 3000!' && exit 0
if curl --silent --fail "${api_url}/api/v2/buildinfo" 2>&1 | jq -r '.version' >/dev/null 2>&1; then
echo "== INFO: Coder development server is already running on port ${api_port}!" && exit 0
else
echo '== ERROR: something is listening on port 3000. Kill it and re-run this script.' && exit 1
echo "== ERROR: something is listening on port ${api_port}. Kill it and re-run this script." && exit 1
fi
fi
if curl --fail http://127.0.0.1:8080 >/dev/null 2>&1; then
if curl --fail "${web_url}" >/dev/null 2>&1; then
# Check if this is the Coder development frontend.
if curl --silent --fail http://127.0.0.1:8080/api/v2/buildinfo 2>&1 | jq -r '.version' >/dev/null 2>&1; then
echo '== INFO: Coder development frontend is already running on port 8080!' && exit 0
if curl --silent --fail "${web_url}/api/v2/buildinfo" 2>&1 | jq -r '.version' >/dev/null 2>&1; then
echo "== ERROR: Coder development frontend is already running on port ${web_port}, but the requested API on port ${api_port} is not already running. Stop the frontend and re-run this script." && exit 1
else
echo '== ERROR: something is listening on port 8080. Kill it and re-run this script.' && exit 1
echo "== ERROR: something is listening on port ${web_port}. Kill it and re-run this script." && exit 1
fi
fi
@@ -176,12 +220,12 @@ fatal() {
trap 'fatal "Script encountered an error"' ERR
cdroot
DEBUG_DELVE="${debug}" DEVELOP_IN_CODER="${DEVELOP_IN_CODER}" start_cmd API "" "${CODER_DEV_SHIM}" server --http-address 0.0.0.0:3000 --swagger-enable --access-url "${CODER_DEV_ACCESS_URL}" --dangerous-allow-cors-requests=true --enable-terraform-debug-mode "$@"
DEBUG_DELVE="${debug}" DEVELOP_IN_CODER="${DEVELOP_IN_CODER}" start_cmd API "" "${CODER_DEV_SHIM}" server --http-address "0.0.0.0:${api_port}" --swagger-enable --access-url "${CODER_DEV_ACCESS_URL}" --dangerous-allow-cors-requests=true --enable-terraform-debug-mode "$@"
echo '== Waiting for Coder to become ready'
# Start the timeout in the background so interrupting this script
# doesn't hang for 60s.
timeout 60s bash -c 'until curl -s --fail http://localhost:3000/healthz > /dev/null 2>&1; do sleep 0.5; done' ||
timeout 60s bash -c "until curl -s --fail ${api_local_url}/healthz > /dev/null 2>&1; do sleep 0.5; done" ||
fatal 'Coder did not become ready in time' &
wait $!
@@ -192,7 +236,7 @@ fatal() {
# Try to create the initial admin user.
echo "Login required; use admin@coder.com and password '${password}'" >&2
if "${CODER_DEV_SHIM}" login http://127.0.0.1:3000 --first-user-username=admin --first-user-email=admin@coder.com --first-user-password="${password}" --first-user-full-name="Admin User" --first-user-trial=false; then
if "${CODER_DEV_SHIM}" login "${api_url}" --first-user-username=admin --first-user-email=admin@coder.com --first-user-password="${password}" --first-user-full-name="Admin User" --first-user-trial=false; then
# Only create this file if an admin user was successfully
# created, otherwise we won't retry on a later attempt.
touch "${PROJECT_ROOT}/.coderv2/developsh-did-first-setup"
@@ -266,12 +310,13 @@ fatal() {
# Create the proxy
proxy_session_token=$("${CODER_DEV_SHIM}" wsproxy create --name=local-proxy --display-name="Local Proxy" --icon="/emojis/1f4bb.png" --only-token)
# Start the proxy
start_cmd PROXY "" "${CODER_DEV_SHIM}" wsproxy server --dangerous-allow-cors-requests=true --http-address=localhost:3010 --proxy-session-token="${proxy_session_token}" --primary-access-url=http://localhost:3000
start_cmd PROXY "" "${CODER_DEV_SHIM}" wsproxy server --dangerous-allow-cors-requests=true --http-address="localhost:${proxy_port}" --proxy-session-token="${proxy_session_token}" --primary-access-url="${api_local_url}"
) || echo "Failed to create workspace proxy. No workspace proxy created."
fi
# Start the frontend once we have a template up and running
CODER_HOST=http://127.0.0.1:3000 start_cmd SITE date pnpm --dir ./site dev --host
# Start the frontend once we have a template up and running. We pin the
# port because some environments export PORT for unrelated services.
PORT="${web_port}" CODER_HOST="${api_url}" start_cmd SITE date pnpm --dir ./site dev --host
interfaces=(localhost)
if command -v ip >/dev/null; then
@@ -289,14 +334,14 @@ fatal() {
log "== =="
log "== Coder is now running in development mode. =="
for iface in "${interfaces[@]}"; do
log "$(printf "== API: http://%s:3000%$((space_padding - ${#iface}))s==" "$iface" "")"
log "$(printf "== API: http://%s:${api_port}%$((space_padding - ${#iface}))s==" "$iface" "")"
done
for iface in "${interfaces[@]}"; do
log "$(printf "== Web UI: http://%s:8080%$((space_padding - ${#iface}))s==" "$iface" "")"
log "$(printf "== Web UI: http://%s:${web_port}%$((space_padding - ${#iface}))s==" "$iface" "")"
done
if [ "${use_proxy}" -gt "0" ]; then
for iface in "${interfaces[@]}"; do
log "$(printf "== Proxy: http://%s:3010%$((space_padding - ${#iface}))s==" "$iface" "")"
log "$(printf "== Proxy: http://%s:${proxy_port}%$((space_padding - ${#iface}))s==" "$iface" "")"
done
fi
log "== =="