ContextStudioWizard/lib/container.sh
Karamelmar 8a82d27dae Add update.sh to generated project scripts
Updates three things:
1. Context Studio Core — git pull on ~/.context-studio/core
2. Claude Code — npm install -g @anthropic-ai/claude-code in container
3. OS packages — apt-get update + upgrade in container

If container is not running, in-container steps are skipped with a warning.
Offers optional full image rebuild (--pull --no-cache) for a clean slate.
Warns that in-container updates are ephemeral without a rebuild.

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

206 lines
8.9 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
# ── 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
# ── 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"
}