diff --git a/entrypoint.sh b/entrypoint.sh new file mode 100755 index 0000000..fe56a1f --- /dev/null +++ b/entrypoint.sh @@ -0,0 +1,52 @@ +#!/bin/bash +set -e + +export DISPLAY=:99 + +echo "=== Starting Research Workbench ===" + +# (1) Start Xvfb +Xvfb :99 -screen 0 1280x720x24 -ac & +sleep 1 +echo "[OK] Xvfb display :99" + +# (2) Start x11vnc (suppress stderr) +x11vnc -display :99 -forever -nopw -listen 0.0.0.0 -rfbport 5900 -shared 2>/dev/null & +sleep 1 +echo "[OK] x11vnc on :5900" + +# (3) Start noVNC via websockify +websockify --web=/usr/share/novnc/ 6080 localhost:5900 & +sleep 1 +echo "[OK] noVNC on :6080" + +# (4) Start amplifierd and wait for it to be healthy +cd /app +amplifierd --port 8410 --bundle /app/bundle/bundle.md & +AMPLIFIERD_PID=$! + +echo "Waiting for amplifierd..." +for i in $(seq 1 30); do + if curl -s http://localhost:8410/health > /dev/null 2>&1; then + echo "[OK] amplifierd on :8410" + break + fi + sleep 1 +done + +# (5) Start FastAPI backend +cd /app/backend +uv run python -m uvicorn app:app --host 0.0.0.0 --port 8080 --log-level info & +sleep 2 +echo "[OK] FastAPI on :8080" + +echo "" +echo "Research Workbench: http://localhost:8080" +echo "Browser VNC: http://localhost:6080" + +# CMD passthrough or wait on amplifierd +if [ $# -eq 0 ]; then + wait $AMPLIFIERD_PID +else + exec "$@" +fi diff --git a/tests/test_entrypoint.sh b/tests/test_entrypoint.sh new file mode 100755 index 0000000..06dcca1 --- /dev/null +++ b/tests/test_entrypoint.sh @@ -0,0 +1,134 @@ +#!/bin/bash +# test_entrypoint.sh - Verify entrypoint.sh matches spec for task-8-entrypoint-script +# RED: Run before entrypoint.sh exists (should fail) +# GREEN: Run after entrypoint.sh is created (should pass) + +REPO=/home/ken/workspace/research-workbench +ENTRYPOINT="$REPO/entrypoint.sh" +PASS=0 +FAIL=0 + +check_file() { + if [ -f "$1" ]; then + echo "PASS: file '$1' exists" + ((PASS++)) + else + echo "FAIL: file '$1' should exist but is missing" + ((FAIL++)) + fi +} + +check_executable() { + if [ -x "$1" ]; then + echo "PASS: '$1' is executable" + ((PASS++)) + else + echo "FAIL: '$1' should be executable" + ((FAIL++)) + fi +} + +check_contains() { + local pattern="$1" + local label="$2" + if grep -qF -- "$pattern" "$ENTRYPOINT" 2>/dev/null; then + echo "PASS: entrypoint.sh contains $label" + ((PASS++)) + else + echo "FAIL: entrypoint.sh missing $label" + ((FAIL++)) + fi +} + +check_regex() { + local pattern="$1" + local label="$2" + if grep -qE "$pattern" "$ENTRYPOINT" 2>/dev/null; then + echo "PASS: entrypoint.sh contains $label" + ((PASS++)) + else + echo "FAIL: entrypoint.sh missing $label" + ((FAIL++)) + fi +} + +echo "=== Checking entrypoint.sh exists ===" +check_file "$ENTRYPOINT" + +echo "" +echo "=== Checking shebang and shell settings ===" +check_regex "^#!/bin/bash" "bash shebang" +check_contains "set -e" "set -e" + +echo "" +echo "=== Checking DISPLAY export ===" +check_contains "export DISPLAY=:99" "export DISPLAY=:99" + +echo "" +echo "=== Checking startup banner ===" +check_contains "=== Starting Research Workbench ===" "startup banner" + +echo "" +echo "=== Checking Xvfb startup ===" +check_contains "Xvfb :99 -screen 0 1280x720x24 -ac" "Xvfb command" +check_regex "Xvfb.*&" "Xvfb in background" +check_contains "[OK] Xvfb display :99" "[OK] Xvfb message" + +echo "" +echo "=== Checking x11vnc startup ===" +check_contains "x11vnc -display :99 -forever -nopw -listen 0.0.0.0 -rfbport 5900 -shared" "x11vnc command" +check_regex "x11vnc.*&" "x11vnc in background" +check_contains "[OK] x11vnc on :5900" "[OK] x11vnc message" + +echo "" +echo "=== Checking noVNC/websockify startup ===" +check_regex "websockify.*--web.*6080.*5900" "websockify command" +check_regex "websockify.*&" "websockify in background" +check_contains "[OK] noVNC on :6080" "[OK] noVNC message" + +echo "" +echo "=== Checking amplifierd startup ===" +check_regex "cd /app" "cd /app" +check_contains "amplifierd --port 8410 --bundle /app/bundle/bundle.md" "amplifierd command" +check_regex "amplifierd.*&" "amplifierd in background" +check_contains "AMPLIFIERD_PID" "AMPLIFIERD_PID variable" +check_regex "AMPLIFIERD_PID=\\\$!" "AMPLIFIERD_PID=\$!" + +echo "" +echo "=== Checking amplifierd health-check wait loop ===" +check_regex "for|while" "loop construct" +check_contains "curl -s http://localhost:8410/health" "health check curl" +check_regex "30" "30 second timeout" +check_contains "[OK] amplifierd on :8410" "[OK] amplifierd message" + +echo "" +echo "=== Checking FastAPI startup ===" +check_regex "cd /app/backend" "cd /app/backend" +check_contains "uv run python -m uvicorn app:app --host 0.0.0.0 --port 8080 --log-level info" "uvicorn command" +check_regex "uvicorn.*&" "uvicorn in background" +check_contains "[OK] FastAPI on :8080" "[OK] FastAPI message" + +echo "" +echo "=== Checking final banner ===" +check_contains "Research Workbench" "Research Workbench banner" +check_contains "http://localhost:8080" "FastAPI URL in banner" +check_contains "http://localhost:6080" "noVNC URL in banner" + +echo "" +echo "=== Checking CMD passthrough ===" +check_regex 'exec "\$@"' 'exec "$@" for CMD passthrough' + +echo "" +echo "=== Checking wait on AMPLIFIERD_PID ===" +check_regex 'wait \$AMPLIFIERD_PID' 'wait $AMPLIFIERD_PID' + +echo "" +echo "=== Checking conditional logic (no args → wait, args → exec) ===" +check_regex '\$#' 'argument count check' + +echo "" +echo "==============================" +echo "Results: $PASS passed, $FAIL failed" +echo "==============================" + +[ $FAIL -eq 0 ] && exit 0 || exit 1