Skip to content

Commit

Permalink
feat: implement dynamic port configuration
Browse files Browse the repository at this point in the history
- Add .ports configuration file for clean port mapping
- Separate internal from external ports
- Add port availability validation
- Update all components to use configured ports
- Add gettext-base for template processing

This change prevents port conflicts when running multiple instances
and provides a cleaner separation between internal and external ports.

Fixes anthropics#147
  • Loading branch information
demeisen-avatar committed Nov 3, 2024
1 parent 8f734fd commit a09cb84
Show file tree
Hide file tree
Showing 8 changed files with 210 additions and 36 deletions.
11 changes: 11 additions & 0 deletions .ports
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# INTERNAL ports end in 1, EXTERNAL ports end in 0
PORT_VNC_INTERNAL=5901
PORT_VNC_EXTERNAL=5900

PORT_NOVNC_INTERNAL=6081
PORT_NOVNC_EXTERNAL=6080

PORT_HTTP_INTERNAL=8081
PORT_HTTP_EXTERNAL=8080
PORT_STREAMLIT_INTERNAL=8501
PORT_STREAMLIT_EXTERNAL=8500
2 changes: 2 additions & 0 deletions computer-use-demo/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ RUN apt-get update && \
apt-get -y upgrade && \
apt-get -y install \
build-essential \
gettext-base \
# UI Requirements
xvfb \
xterm \
Expand All @@ -18,6 +19,7 @@ RUN apt-get update && \
x11vnc \
# Python/pyenv reqs
build-essential \
gettext-base \
libssl-dev \
zlib1g-dev \
libbz2-dev \
Expand Down
13 changes: 10 additions & 3 deletions computer-use-demo/image/entrypoint.sh
Original file line number Diff line number Diff line change
@@ -1,15 +1,22 @@
#!/bin/bash
set -e

# Source and export all variables from .ports
set -a
source /host/.ports || exit 1
set +a

./start_all.sh
./novnc_startup.sh

python http_server.py > /tmp/server_logs.txt 2>&1 &
# Generate index.html from template
envsubst < static_content/index.html.template > static_content/index.html

STREAMLIT_SERVER_PORT=8501 python -m streamlit run computer_use_demo/streamlit.py > /tmp/streamlit_stdout.log &
python http_server.py > /tmp/server_logs.txt 2>&1 &
STREAMLIT_SERVER_PORT=$PORT_STREAMLIT_INTERNAL python -m streamlit run computer_use_demo/streamlit.py > /tmp/streamlit_stdout.log &

echo "✨ Computer Use Demo is ready!"
echo "➡️ Open http://localhost:8080 in your browser to begin"
echo "➡️ Open http://localhost:$PORT_HTTP_EXTERNAL in your browser to begin"

# Keep the container running
tail -f /dev/null
6 changes: 4 additions & 2 deletions computer-use-demo/image/http_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,18 @@
import socket
from http.server import HTTPServer, SimpleHTTPRequestHandler

PORT = int(os.environ['PORT_HTTP_INTERNAL'])


class HTTPServerV6(HTTPServer):
address_family = socket.AF_INET6


def run_server():
os.chdir(os.path.dirname(__file__) + "/static_content")
server_address = ("::", 8080)
server_address = ("::", PORT)
httpd = HTTPServerV6(server_address, SimpleHTTPRequestHandler)
print("Starting HTTP server on port 8080...") # noqa: T201
print(f"Starting HTTP server on port {PORT}...") # noqa: T201
httpd.serve_forever()


Expand Down
13 changes: 8 additions & 5 deletions computer-use-demo/image/novnc_startup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,22 @@ echo "starting noVNC"

# Start noVNC with explicit websocket settings
/opt/noVNC/utils/novnc_proxy \
--vnc localhost:5900 \
--listen 6080 \
--vnc localhost:$PORT_VNC_INTERNAL \
--listen $PORT_NOVNC_INTERNAL \
--web /opt/noVNC \
> /tmp/novnc.log 2>&1 &

# Wait for noVNC to start
timeout=10
while [ $timeout -gt 0 ]; do
if netstat -tuln | grep -q ":6080 "; then
if netstat -tuln | grep -q ":$PORT_NOVNC_INTERNAL "; then
break
fi
sleep 1
((timeout--))
timeout=$((timeout - 1))
done

echo "noVNC started successfully"
if [ $timeout -eq 0 ]; then
echo "Failed to start noVNC" >&2
exit 1
fi
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,13 @@
<body>
<div class="container">
<iframe
src="http://localhost:8501"
src="http://localhost:${PORT_STREAMLIT_EXTERNAL}"
class="left"
allow="fullscreen"
></iframe>
<iframe
id="vnc"
src="http://127.0.0.1:6080/vnc.html?&resize=scale&autoconnect=1&view_only=1&reconnect=1&reconnect_delay=2000"
src="http://127.0.0.1:${PORT_NOVNC_EXTERNAL}/vnc.html?&resize=scale&autoconnect=1&view_only=1&reconnect=1&reconnect_delay=2000"
class="right"
allow="fullscreen"
></iframe>
Expand Down
29 changes: 5 additions & 24 deletions computer-use-demo/image/x11vnc_startup.sh
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
#!/bin/bash
echo "starting vnc"
echo "starting vnc $PORT_VNC_INTERNAL"

(x11vnc -display $DISPLAY \
-forever \
-shared \
-wait 50 \
-rfbport 5900 \
-rfbport "$PORT_VNC_INTERNAL" \
-nopw \
2>/tmp/x11vnc_stderr.log) &

Expand All @@ -14,33 +14,14 @@ x11vnc_pid=$!
# Wait for x11vnc to start
timeout=10
while [ $timeout -gt 0 ]; do
if netstat -tuln | grep -q ":5900 "; then
if netstat -tuln | grep -q ":$PORT_VNC_INTERNAL "; then
break
fi
sleep 1
((timeout--))
timeout=$((timeout - 1))
done

if [ $timeout -eq 0 ]; then
echo "x11vnc failed to start, stderr output:" >&2
cat /tmp/x11vnc_stderr.log >&2
echo "Failed to start x11vnc" >&2
exit 1
fi

: > /tmp/x11vnc_stderr.log

# Monitor x11vnc process in the background
(
while true; do
if ! kill -0 $x11vnc_pid 2>/dev/null; then
echo "x11vnc process crashed, restarting..." >&2
if [ -f /tmp/x11vnc_stderr.log ]; then
echo "x11vnc stderr output:" >&2
cat /tmp/x11vnc_stderr.log >&2
rm /tmp/x11vnc_stderr.log
fi
exec "$0"
fi
sleep 5
done
) &
168 changes: 168 additions & 0 deletions computer-use-demo/run.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
#!/bin/bash
set -e

# Directory containing this script
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"

# Source port configuration
source .ports || {
echo "Error: Failed to load port configuration from .ports" >&2
exit 1
}

# Function to check if a port is available
check_port() {
local port=$1
if netstat -tuln | grep -q ":${port} "; then
echo "Error: Port ${port} is already in use" >&2
return 1
fi
return 0
}

# Check all external ports
echo "Checking port availability..."
for port in $PORT_VNC_EXTERNAL $PORT_NOVNC_EXTERNAL $PORT_HTTP_EXTERNAL $PORT_STREAMLIT_EXTERNAL; do
check_port $port || exit 1
done
echo "All ports are available."

IMAGE_NAME=""
CONTAINER_NAME=""

# Parse command line arguments
while [[ $# -gt 0 ]]; do
case $1 in
--image)
IMAGE_NAME="$2"
shift 2
;;
--container)
CONTAINER_NAME="$2"
shift 2
;;
*)
echo "Usage: $0 --image NAME --container NAME"
exit 1
;;
esac
done

# Verify required arguments
if [ -z "$IMAGE_NAME" ] || [ -z "$CONTAINER_NAME" ]; then
echo "Error: --image NAME and --container NAME are required"
echo "Usage: $0 --image NAME --container NAME"
exit 1
fi

# Change to script directory and source .env
cd "$(dirname "$0")"
if [ ! -f .env ]; then
echo "Error: .env file not found"
exit 1
fi
source .env

if [ -z "$ANTHROPIC_API_KEY" ]; then
echo "Error: ANTHROPIC_API_KEY not set in .env"
exit 1
fi

# Helper functions for ID formatting
short_id() {
echo "$1" | cut -c1-4
}

container_name() {
local id=$1
local name=$2
echo "$name ($(short_id $id)..)"
}

# Function to check if container is running
container_is_running() {
docker inspect -f '{{.State.Running}}' "$CONTAINER_ID" 2>/dev/null | grep -q true
return $?
}

# Remove existing container if it exists
if docker ps -a --format '{{.Names}}' | grep -q "^$CONTAINER_NAME$"; then
echo "Removing existing $CONTAINER_NAME container..."
docker rm -f "$CONTAINER_NAME" >/dev/null 2>&1
fi

# Start container and get container ID
CONTAINER_ID=$(docker run -d \
-e ANTHROPIC_API_KEY="$ANTHROPIC_API_KEY" \
-v "$(pwd)":/host \
-p ${PORT_VNC_EXTERNAL}:${PORT_VNC_INTERNAL} \
-p ${PORT_STREAMLIT_EXTERNAL}:${PORT_STREAMLIT_INTERNAL} \
-p ${PORT_NOVNC_EXTERNAL}:${PORT_NOVNC_INTERNAL} \
-p ${PORT_HTTP_EXTERNAL}:${PORT_HTTP_INTERNAL} \
--name "$CONTAINER_NAME" \
-it "$IMAGE_NAME")

if [ -z "$CONTAINER_ID" ]; then
echo "Error: Failed to start container."
exit 1
fi

# Get image ID for nice display
IMAGE_ID=$(docker inspect --format='{{.Id}}' "$IMAGE_NAME" 2>/dev/null | sed 's/sha256://')

echo "Container $(container_name "$CONTAINER_ID" "$CONTAINER_NAME") started from image $IMAGE_NAME ($(short_id "$IMAGE_ID")..)"

# Wait until the container is fully running
echo "Waiting for container to reach running state..."
until container_is_running; do
sleep 1
done
echo "Container is now running."

# Start log streaming in background
docker logs -f "$CONTAINER_ID" &
LOG_PID=$!

# Initialize signal handling
SIGNAL_RECEIVED=0
CLEANUP_DONE=0

# Cleanup function
cleanup() {
if [ "$CLEANUP_DONE" -eq 1 ]; then
return
fi
CLEANUP_DONE=1

echo -e "\nShutting down..."
echo "Stopping container $(container_name "$CONTAINER_ID" "$CONTAINER_NAME")..."
result=$(docker stop "$CONTAINER_ID" 2>&1)
if [ "$result" = "$CONTAINER_ID" ]; then
echo "✅ Stopped"
else
echo "⚠️ Unexpected output: $result"
fi

echo "Cleaning up background processes..."
kill $LOG_PID 2>/dev/null

echo "Shutdown complete."
}
trap 'SIGNAL_RECEIVED=1; cleanup' EXIT INT TERM

echo "Container started. Access points:"
echo "- Combined interface: http://localhost:${PORT_HTTP_EXTERNAL}"
echo "- Streamlit interface: http://localhost:${PORT_STREAMLIT_EXTERNAL}"
echo "- Desktop view: http://localhost:${PORT_NOVNC_EXTERNAL}/vnc.html"
echo "- Direct VNC: vnc://localhost:${PORT_VNC_EXTERNAL}"

# Monitor loop
while container_is_running; do
sleep 1
done

# Only show error if we didn't receive SIGINT/SIGTERM
if [ "${SIGNAL_RECEIVED:-0}" != "1" ]; then
echo "Container stopped unexpectedly."
exit 1
fi

0 comments on commit a09cb84

Please sign in to comment.