#!/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#/usr/share/fonts#$DEPS_ROOT/usr/share/fonts#" \ "$DEPS_ROOT/etc/fonts/fonts.conf" sed -i "s#.*#/tmp/run-server-fontcache#" \ "$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" < $ENV_FILE"