ContextStudioWizard/lib/container.sh
Karamelmar 3a7dfb6b1a Fix Electron not opening: run npm install on core + check DISPLAY
- core.sh: run npm install after cloning (downloads Electron binary
  into app/node_modules — required for the UI to launch)
- start.sh template: check if app/node_modules exists and run
  npm install on first start if missing
- start.sh template: warn if DISPLAY/WAYLAND_DISPLAY not set

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-09 13:13:15 +01:00

221 lines
9.7 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env bash
# container.sh — generate Option B container scripts for a project
generate_container_scripts() {
local project_dir="$1"
local project_name="$2"
local slug="$3"
local container_name="cs-${slug}"
info "Generating container scripts (Option B)..."
mkdir -p "$project_dir/bin"
# ── bin/claude — wrapper that runs claude inside the agents container ──
cat > "$project_dir/bin/claude" <<WRAPPER
#!/usr/bin/env bash
# Claude Code wrapper — runs claude inside the agents container.
# CS Core on the host calls this instead of the real claude binary.
CONTAINER_NAME="${container_name}"
RUNTIME="\$(command -v podman || command -v docker || true)"
if [[ -z "\$RUNTIME" ]]; then
echo "[claude-wrapper] Error: podman or docker not found" >&2
exit 1
fi
if ! "\$RUNTIME" container inspect "\$CONTAINER_NAME" --format '{{.State.Running}}' 2>/dev/null | grep -q true; then
echo "[claude-wrapper] Error: container '\$CONTAINER_NAME' is not running." >&2
echo "[claude-wrapper] Run ./start.sh from your project directory first." >&2
exit 1
fi
# Pass through TTY if available, relay working directory into container
if [ -t 0 ]; then
exec "\$RUNTIME" exec -it --workdir "\$PWD" "\$CONTAINER_NAME" claude "\$@"
else
exec "\$RUNTIME" exec -i --workdir "\$PWD" "\$CONTAINER_NAME" claude "\$@"
fi
WRAPPER
chmod +x "$project_dir/bin/claude"
# ── start.sh — build image, start container, launch CS Core ──────────
cat > "$project_dir/start.sh" <<START
#!/usr/bin/env bash
set -uo pipefail
SCRIPT_DIR="\$(cd "\$(dirname "\${BASH_SOURCE[0]}")" && pwd)"
PROJECT_DIR="\$SCRIPT_DIR"
CONTAINER_NAME="${container_name}"
IMAGE_NAME="${slug}"
RUNTIME="\$(command -v podman || command -v docker || true)"
CS_CORE="\${CS_CORE_DIR:-\$HOME/.context-studio/core}"
if [[ -z "\$RUNTIME" ]]; then
echo "Error: podman or docker not found." >&2; exit 1
fi
if [[ ! -d "\$CS_CORE" ]]; then
echo "Error: context-studio-core not found at \$CS_CORE" >&2
echo "Run the Context Studio Wizard first." >&2; exit 1
fi
# ── Build image if missing ───────────────────────────────────────────────
if ! "\$RUNTIME" image exists "\$IMAGE_NAME" 2>/dev/null; then
echo "→ Building container image '\$IMAGE_NAME'..."
"\$RUNTIME" build -t "\$IMAGE_NAME" "\$PROJECT_DIR/.devcontainer/" \
|| { echo "Image build failed." >&2; exit 1; }
fi
# ── Stop stale container ─────────────────────────────────────────────────
"\$RUNTIME" rm -f "\$CONTAINER_NAME" 2>/dev/null || true
# ── Ensure ~/.anthropic exists (Claude Code stores auth/config here) ─────
mkdir -p "\$HOME/.anthropic"
# ── Start agents container ───────────────────────────────────────────────
# Mount project at the same absolute path so host and container paths match.
# CS Core sets agent working dirs to host paths; the wrapper relays PWD.
echo "→ Starting agents container '\$CONTAINER_NAME'..."
"\$RUNTIME" run -d \\
--name "\$CONTAINER_NAME" \\
-v "\$PROJECT_DIR:\$PROJECT_DIR" \\
-v "\$HOME/.anthropic:\$HOME/.anthropic:ro" \\
-e ANTHROPIC_API_KEY="\${ANTHROPIC_API_KEY:-}" \\
-e CS_WORKFLOW_DIR="\$PROJECT_DIR/workflow" \\
-e PROJECT_ROOT_DIR="\$PROJECT_DIR" \\
"\$IMAGE_NAME" \\
sleep infinity
# ── Wait for container to be running ────────────────────────────────────
echo -n "→ Waiting for container..."
for _i in 1 2 3 4 5 6 7 8 9 10; do
if "\$RUNTIME" container inspect "\$CONTAINER_NAME" --format '{{.State.Running}}' 2>/dev/null | grep -q true; then
echo " ready."
break
fi
echo -n "."
sleep 0.5
done
if ! "\$RUNTIME" container inspect "\$CONTAINER_NAME" --format '{{.State.Running}}' 2>/dev/null | grep -q true; then
echo ""
echo "Error: container failed to start." >&2; exit 1
fi
# ── Ensure core deps are installed (Electron binary lives here) ─────────
if [[ ! -d "\$CS_CORE/app/node_modules" ]]; then
echo "→ Installing context-studio-core dependencies (first run)..."
(cd "\$CS_CORE" && npm install) || { echo "npm install failed." >&2; exit 1; }
fi
# ── Check display for Electron UI ───────────────────────────────────────
if [[ -z "\${DISPLAY:-}" && -z "\${WAYLAND_DISPLAY:-}" ]]; then
echo "⚠ No display detected (DISPLAY / WAYLAND_DISPLAY not set)."
echo " Electron UI may not open. Set DISPLAY=:0 or run from a desktop terminal."
fi
# ── Put claude wrapper first on PATH ────────────────────────────────────
export PATH="\$PROJECT_DIR/bin:\$PATH"
# ── Launch CS Core on the host ───────────────────────────────────────────
echo "→ Starting Context Studio Core..."
CS_CORE_DIR="\$CS_CORE" \\
CS_WORKFLOW_DIR="\$PROJECT_DIR/workflow" \\
PROJECT_ROOT_DIR="\$PROJECT_DIR" \\
node "\$CS_CORE/core/start.js"
# ── CS Core exited — stop container ─────────────────────────────────────
echo "→ Stopping agents container..."
"\$RUNTIME" rm -f "\$CONTAINER_NAME" 2>/dev/null || true
START
chmod +x "$project_dir/start.sh"
# ── stop.sh — forcefully stop the container ───────────────────────────
cat > "$project_dir/stop.sh" <<STOP
#!/usr/bin/env bash
CONTAINER_NAME="${container_name}"
RUNTIME="\$(command -v podman || command -v docker || true)"
echo "→ Stopping \$CONTAINER_NAME..."
"\$RUNTIME" rm -f "\$CONTAINER_NAME" 2>/dev/null && echo "→ Done." || echo "→ Container was not running."
STOP
chmod +x "$project_dir/stop.sh"
# ── update.sh — update core, claude-code, and optionally OS packages ──
cat > "$project_dir/update.sh" <<UPDATE
#!/usr/bin/env bash
set -uo pipefail
SCRIPT_DIR="\$(cd "\$(dirname "\${BASH_SOURCE[0]}")" && pwd)"
PROJECT_DIR="\$SCRIPT_DIR"
CONTAINER_NAME="${container_name}"
IMAGE_NAME="${slug}"
RUNTIME="\$(command -v podman || command -v docker || true)"
CS_CORE="\${CS_CORE_DIR:-\$HOME/.context-studio/core}"
GREEN='\033[0;32m'; CYAN='\033[0;36m'; YELLOW='\033[1;33m'; BOLD='\033[1m'; RESET='\033[0m'
info() { echo -e "\${CYAN}\${BOLD}[update]\${RESET} \$*"; }
success() { echo -e "\${GREEN}\${BOLD}[ok]\${RESET} \$*"; }
warn() { echo -e "\${YELLOW}\${BOLD}[warn]\${RESET} \$*"; }
CONTAINER_RUNNING=false
if "\$RUNTIME" container inspect "\$CONTAINER_NAME" --format '{{.State.Running}}' 2>/dev/null | grep -q true; then
CONTAINER_RUNNING=true
fi
# ── 1. Update Context Studio Core ───────────────────────────────────────
info "Updating Context Studio Core..."
if [[ -d "\$CS_CORE/.git" ]]; then
git -C "\$CS_CORE" pull --ff-only && success "Core updated." || warn "Core pull failed — continuing with current version."
else
warn "Core not found at \$CS_CORE — skipping."
fi
# ── 2. Update Claude Code in container ──────────────────────────────────
info "Updating Claude Code (claude-code)..."
if \$CONTAINER_RUNNING; then
"\$RUNTIME" exec "\$CONTAINER_NAME" npm install -g @anthropic-ai/claude-code \
&& success "Claude Code updated in running container." \
|| warn "Claude Code update failed."
else
warn "Container not running — Claude Code will be updated on next image build."
fi
# ── 3. Update OS packages in container ──────────────────────────────────
info "Updating OS packages in container..."
if \$CONTAINER_RUNNING; then
"\$RUNTIME" exec "\$CONTAINER_NAME" bash -c "apt-get update -qq && apt-get upgrade -y" \
&& success "OS packages updated in running container." \
|| warn "OS package update failed."
else
warn "Container not running — OS packages will be updated on next image build."
fi
# ── 4. Rebuild image to persist updates ─────────────────────────────────
echo ""
echo -e "\${BOLD}Rebuild container image?\${RESET}"
echo -e " Rebuilds from scratch with latest base image + all packages."
echo -e " ${YELLOW}⚠ Takes 515 minutes. Stops the running container.\${RESET}"
echo -ne "Rebuild now? \${CYAN}[y/N]\${RESET}: "
read -r _rebuild || true
_rebuild="\${_rebuild:-n}"
if [[ "\$_rebuild" =~ ^[Yy]\$ ]]; then
info "Stopping container..."
"\$RUNTIME" rm -f "\$CONTAINER_NAME" 2>/dev/null || true
info "Rebuilding image '\$IMAGE_NAME' (--pull --no-cache)..."
"\$RUNTIME" build --pull --no-cache -t "\$IMAGE_NAME" "\$PROJECT_DIR/.devcontainer/" \
&& success "Image rebuilt. Run ./start.sh to start with the fresh image." \
|| { echo "Image build failed." >&2; exit 1; }
else
info "Skipped image rebuild."
if \$CONTAINER_RUNNING; then
warn "In-container updates are temporary — they will be lost when the container is recreated."
warn "Run ./update.sh again and choose 'y' to rebuild, or run ./start.sh (which recreates the container from the old image)."
fi
fi
echo ""
success "Update complete."
UPDATE
chmod +x "$project_dir/update.sh"
success "Generated: start.sh stop.sh update.sh bin/claude"
}