diff --git a/scripts/develop.sh b/scripts/develop.sh index 8df69bfc11..a1e225ecd3 100755 --- a/scripts/develop.sh +++ b/scripts/develop.sh @@ -1,9 +1,10 @@ #!/usr/bin/env bash -# Usage: ./develop.sh [--agpl] +# Usage: ./develop.sh [--agpl] [--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 "== =="