Files
Thomas Faour f1fda9bdee Move make-widget/run-server skills to root .claude/skills/
Nested .claude/skills/ dirs (previously under server/) are only
auto-discovered on-demand once a file under that subdirectory is
touched, so /make-widget and /run-server weren't invocable from a
fresh session. Root .claude/skills/ is scanned at session start.

Fixes setup.sh/start-server.sh's relative cd-depth math (was hardcoded
for the old server/.claude/skills/run-server/ depth) to instead
resolve the repo root via git and cd into server/ explicitly, and
updates SKILL.md/driver.py's path references to match the new layout.
2026-07-25 12:09:39 +00:00

111 lines
5.2 KiB
Bash
Executable File

#!/usr/bin/env bash
# One-time (idempotent) environment bootstrap for running the
# espresso_frame FastAPI server and browser-driving its UI, in a
# container that ships with NO Python/Node/Docker/browser and NO sudo.
# Re-run any time; every step checks whether it already happened.
set -euo pipefail
cd "$(git -C "$(dirname "${BASH_SOURCE[0]}")" rev-parse --show-toplevel)/server"
UV_BIN="$HOME/.local/bin/uv"
DEPS_ROOT="/tmp/run-server-chromium-deps"
APT_WORK="/tmp/apt-work-run-server"
SKILL_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ENV_FILE="$SKILL_DIR/env.sh"
# 1. uv: a static Rust binary that can fetch its own Python build with
# no C compiler needed (this container has none).
if [ ! -x "$UV_BIN" ]; then
echo "installing uv..."
curl -LsSf https://astral.sh/uv/install.sh | sh
fi
# 2. Python 3.12 + venv + server deps
if [ ! -x .venv/bin/uvicorn ]; then
echo "creating venv + installing server deps..."
"$UV_BIN" python install 3.12
"$UV_BIN" venv --python 3.12 .venv
"$UV_BIN" pip install -r requirements.txt
fi
# 3. Playwright (Python) + its Chromium download (~280MB: full chrome +
# chrome-headless-shell + ffmpeg)
if ! .venv/bin/python -c "import playwright" 2>/dev/null; then
echo "installing playwright..."
"$UV_BIN" pip install playwright
fi
if ! ls "$HOME"/.cache/ms-playwright/chromium-*/chrome-linux64/chrome >/dev/null 2>&1; then
echo "downloading chromium..."
.venv/bin/playwright install chromium
fi
# 4. Chromium's shared libs + fonts. `playwright install-deps` and
# `apt-get install` both need root; neither is available. Instead:
# download the .deb files directly (apt-get download works read-only
# without root once given a user-writable state dir) and extract
# (not install) them with dpkg-deb -x, which needs no root either.
if [ ! -f "$DEPS_ROOT/usr/lib/x86_64-linux-gnu/libglib-2.0.so.0" ]; then
echo "fetching chromium's shared libs + fonts (non-root)..."
mkdir -p "$APT_WORK/lists" "$APT_WORK/cache/archives/partial" "$APT_WORK/debs" "$DEPS_ROOT"
apt-get -o Dir::State::Lists="$APT_WORK/lists" -o Dir::Cache="$APT_WORK/cache" \
-o Dir::Etc::SourceParts=/dev/null update
PKGS="libglib2.0-0t64 libnspr4 libnss3 libatk1.0-0t64 libatk-bridge2.0-0t64
libdbus-1-3 libxcomposite1 libxdamage1 libxfixes3 libxrandr2 libgbm1
libxkbcommon0 libasound2t64 libatspi2.0-0t64 libcups2t64 libcairo2
libpango-1.0-0 libpangocairo-1.0-0 libx11-6 libxcb1 libxext6
fonts-liberation fontconfig libfontconfig1"
# ^ first row: what chrome-headless-shell's ldd reported missing.
# second row: what the FULL chrome binary additionally needed (we use
# full chrome, not headless-shell -- see Gotchas in SKILL.md).
apt-get -o Dir::State::Lists="$APT_WORK/lists" -o Dir::Cache="$APT_WORK/cache" \
-o Dir::Etc::SourceParts=/dev/null install --download-only --reinstall -y \
--print-uris $PKGS | grep -oP "^'[^']+'" | tr -d "'" > "$APT_WORK/urls.txt"
(cd "$APT_WORK/debs" && xargs -n1 -P8 curl -sS -O --max-time 30) < "$APT_WORK/urls.txt"
for f in "$APT_WORK"/debs/*.deb; do dpkg-deb -x "$f" "$DEPS_ROOT"; done
# fonts.conf as shipped points at the real /usr/share/fonts, which is
# root-owned and has nothing extracted into it. Point it at our
# extracted copy instead, and give it a writable cache dir.
mkdir -p /tmp/run-server-fontcache
sed -i "s#<dir>/usr/share/fonts</dir>#<dir>$DEPS_ROOT/usr/share/fonts</dir>#" \
"$DEPS_ROOT/etc/fonts/fonts.conf"
sed -i "s#<cachedir>.*</cachedir>#<cachedir>/tmp/run-server-fontcache</cachedir>#" \
"$DEPS_ROOT/etc/fonts/fonts.conf"
PATH="$DEPS_ROOT/usr/bin:$PATH" \
LD_LIBRARY_PATH="$DEPS_ROOT/usr/lib/x86_64-linux-gnu:$DEPS_ROOT/lib/x86_64-linux-gnu" \
FONTCONFIG_PATH="$DEPS_ROOT/etc/fonts" \
"$DEPS_ROOT/usr/bin/fc-cache" -f
fi
# 5. tmux -- also missing, also no apt/sudo. Same non-root download +
# dpkg-deb -x trick, into the same extracted root (so its `usr/bin` is
# already on PATH via env.sh).
if [ ! -f "$DEPS_ROOT/usr/bin/tmux" ]; then
echo "fetching tmux (non-root)..."
mkdir -p "$APT_WORK/lists" "$APT_WORK/cache/archives/partial" "$APT_WORK/debs" "$DEPS_ROOT"
apt-get -o Dir::State::Lists="$APT_WORK/lists" -o Dir::Cache="$APT_WORK/cache" \
-o Dir::Etc::SourceParts=/dev/null install --download-only --reinstall -y \
--print-uris tmux | grep -oP "^'[^']+'" | tr -d "'" > "$APT_WORK/tmux_urls.txt"
(cd "$APT_WORK/debs" && xargs -n1 -P3 curl -sS -O --max-time 30) < "$APT_WORK/tmux_urls.txt"
for f in $(sed -E 's#.*/##' "$APT_WORK/tmux_urls.txt"); do dpkg-deb -x "$APT_WORK/debs/$f" "$DEPS_ROOT"; done
fi
CHROME_BIN="$(ls "$HOME"/.cache/ms-playwright/chromium-*/chrome-linux64/chrome | head -1)"
cat > "$ENV_FILE" <<EOF
# Generated by setup.sh. Source this before running driver.py (it
# needs LD_LIBRARY_PATH/FONTCONFIG_PATH set before the Chromium
# subprocess launches -- driver.py does not source it for you).
# start-server.sh does NOT need this file -- uvicorn has no such deps.
export PATH="\$HOME/.local/bin:$DEPS_ROOT/usr/bin:\$PATH"
export LD_LIBRARY_PATH="$DEPS_ROOT/usr/lib/x86_64-linux-gnu:$DEPS_ROOT/lib/x86_64-linux-gnu"
export FONTCONFIG_PATH="$DEPS_ROOT/etc/fonts"
export RUN_SERVER_CHROME_BIN="$CHROME_BIN"
EOF
echo "setup complete -> $ENV_FILE"