feat(studio): add :latest-studio variant (PR-3)
Bundle pi-studio (omaclaren/pi-studio) as a new -studio image variant: browser prompt editor, KaTeX/Mermaid preview, tmux-backed literate REPLs, /studio command + studio_* agent tools. - Dockerfile.variant: INSTALL_STUDIO + PI_STUDIO_REPO/REF args; vendor pi-studio to /opt/pi-studio (no build step — prebuilt client in git; npm install --omit=dev for 3 prod deps). STUDIO_PORT=8765 advisory. - entrypoint-user.sh: register /opt/pi-studio via the existing pi install local-path loop (auto-skips in non-studio variant). - smoke-test.sh: auto-detected studio assertions (clone + prebuilt client + pi install registration). - CI: resolve PI_STUDIO_REF to a SHA; independent smoke-studio + build-variant-studio jobs that gate ONLY the -studio tags, so a studio failure never blocks the core :latest release. - README: 'Using pi-studio' section documenting the container access reality — pi-studio hard-binds 127.0.0.1 (index.ts .listen(port, '127.0.0.1'), no --host flag), so -p publish alone can't reach it. Documents host-networking and loopback-bridge paths, the remote ssh -L forward, and the mosh caveat (no port forwarding; run parallel ssh -L). - CHANGELOG/AGENTS/DOCKER_HUB updated. Will tag as v1.1.0 (minor). No tag created — stopping for review.
This commit is contained in:
@@ -116,6 +116,7 @@ jobs:
|
||||
obsmem_ref: ${{ steps.resolve.outputs.obsmem_ref }}
|
||||
toolkit_ref: ${{ steps.resolve.outputs.toolkit_ref }}
|
||||
extensions_ref: ${{ steps.resolve.outputs.extensions_ref }}
|
||||
studio_ref: ${{ steps.resolve.outputs.studio_ref }}
|
||||
steps:
|
||||
- name: Resolve pi version + companion refs
|
||||
id: resolve
|
||||
@@ -150,9 +151,16 @@ jobs:
|
||||
[ -n "$EXTENSIONS_REF" ] || EXTENSIONS_REF=main
|
||||
echo "toolkit_ref=${TOOLKIT_REF}" >> "$GITHUB_OUTPUT"
|
||||
echo "extensions_ref=${EXTENSIONS_REF}" >> "$GITHUB_OUTPUT"
|
||||
# Resolve pi-studio (omaclaren/pi-studio) main HEAD to a SHA for
|
||||
# the :latest-studio variant — same cache-busting rationale.
|
||||
STUDIO_REF=$(curl -sf -H "Accept: application/vnd.github.sha" \
|
||||
"https://api.github.com/repos/omaclaren/pi-studio/commits/main" || echo "main")
|
||||
[ -n "$STUDIO_REF" ] || STUDIO_REF=main
|
||||
echo "studio_ref=${STUDIO_REF}" >> "$GITHUB_OUTPUT"
|
||||
echo "Resolved PI_VERSION=${PI_VERSION}"
|
||||
echo "Resolved PI_FORK_REF=${FORK_REF}, PI_OBSMEM_REF=${OBSMEM_REF}"
|
||||
echo "Resolved PI_TOOLKIT_REF=${TOOLKIT_REF}, PI_EXTENSIONS_REF=${EXTENSIONS_REF}"
|
||||
echo "Resolved PI_STUDIO_REF=${STUDIO_REF}"
|
||||
|
||||
# ── Phase 2: build & push base (multi-arch), only when needed ──────
|
||||
build-base:
|
||||
@@ -277,6 +285,62 @@ jobs:
|
||||
EXPECTED_PI_VERSION: ${{ needs.resolve-versions.outputs.pi_version }}
|
||||
run: bash scripts/smoke-test.sh pi-devbox:smoke
|
||||
|
||||
# ── Phase 3b: amd64 smoke for the studio variant ────────────────────
|
||||
# Additive + independent of the core `smoke` job: gates ONLY
|
||||
# build-variant-studio, never the core build-variant. A studio build or
|
||||
# smoke failure therefore cannot block the :latest / :vX.Y.Z release.
|
||||
smoke-studio:
|
||||
needs: [base-decide, build-base, resolve-versions]
|
||||
if: |
|
||||
always() &&
|
||||
needs.base-decide.result == 'success' &&
|
||||
needs.resolve-versions.result == 'success' &&
|
||||
(needs.build-base.result == 'success' || needs.build-base.result == 'skipped')
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: catthehacker/ubuntu:act-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Force IPv4 for Docker Hub
|
||||
run: echo 'precedence ::ffff:0:0/96 100' >> /etc/gai.conf
|
||||
- name: Reclaim runner disk
|
||||
run: |
|
||||
rm -rf /opt/hostedtoolcache /opt/microsoft /opt/az /opt/ghc \
|
||||
/usr/local/.ghcup /usr/share/dotnet /usr/share/swift \
|
||||
/usr/local/lib/android /usr/local/share/powershell \
|
||||
/usr/local/share/chromium /usr/local/share/boost \
|
||||
/usr/lib/jvm 2>/dev/null || true
|
||||
docker system prune -af --volumes || true
|
||||
docker builder prune -af || true
|
||||
- uses: docker/setup-buildx-action@v4
|
||||
with: {driver-opts: network=host}
|
||||
- uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ vars.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
- name: Build amd64 studio variant for smoke
|
||||
uses: docker/build-push-action@v7
|
||||
with:
|
||||
context: .
|
||||
file: Dockerfile.variant
|
||||
platforms: linux/amd64
|
||||
push: false
|
||||
load: true
|
||||
tags: pi-devbox:smoke-studio
|
||||
build-args: |
|
||||
BASE_IMAGE=${{ env.IMAGE }}:${{ needs.base-decide.outputs.base_tag }}
|
||||
PI_VERSION=${{ needs.resolve-versions.outputs.pi_version }}
|
||||
PI_FORK_REF=${{ needs.resolve-versions.outputs.fork_ref }}
|
||||
PI_OBSMEM_REF=${{ needs.resolve-versions.outputs.obsmem_ref }}
|
||||
PI_TOOLKIT_REF=${{ needs.resolve-versions.outputs.toolkit_ref }}
|
||||
PI_EXTENSIONS_REF=${{ needs.resolve-versions.outputs.extensions_ref }}
|
||||
INSTALL_STUDIO=true
|
||||
PI_STUDIO_REF=${{ needs.resolve-versions.outputs.studio_ref }}
|
||||
- name: Smoke test studio (amd64)
|
||||
env:
|
||||
EXPECTED_PI_VERSION: ${{ needs.resolve-versions.outputs.pi_version }}
|
||||
run: bash scripts/smoke-test.sh pi-devbox:smoke-studio
|
||||
|
||||
# ── Phase 4: multi-arch publish ─────────────────────────────────────
|
||||
build-variant:
|
||||
needs: [base-decide, smoke, resolve-versions]
|
||||
@@ -354,6 +418,89 @@ jobs:
|
||||
echo "==> All 3 build+push attempts failed"
|
||||
exit 1
|
||||
|
||||
# ── Phase 4b: multi-arch publish of the studio variant ───────────────
|
||||
# Additive: publishes :vX.Y.Z-studio (+ :latest-studio on release). Gated
|
||||
# on its own smoke-studio, NOT on the core build-variant, so it can ship
|
||||
# or fail independently of the core release.
|
||||
build-variant-studio:
|
||||
needs: [base-decide, smoke-studio, resolve-versions]
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: catthehacker/ubuntu:act-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- run: echo 'precedence ::ffff:0:0/96 100' >> /etc/gai.conf
|
||||
- run: |
|
||||
rm -rf /opt/hostedtoolcache /opt/microsoft /opt/az /opt/ghc \
|
||||
/usr/local/.ghcup /usr/share/dotnet /usr/share/swift \
|
||||
/usr/local/lib/android /usr/local/share/powershell \
|
||||
/usr/local/share/chromium /usr/local/share/boost \
|
||||
/usr/lib/jvm 2>/dev/null || true
|
||||
docker system prune -af --volumes || true
|
||||
docker builder prune -af || true
|
||||
- uses: docker/setup-qemu-action@v3
|
||||
with: {platforms: arm64}
|
||||
- uses: docker/setup-buildx-action@v4
|
||||
with: {driver-opts: network=host}
|
||||
- uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ vars.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
- name: Compute studio version-specific tags
|
||||
id: tags
|
||||
run: |
|
||||
VERSION="${{ env.RELEASE_TAG }}"
|
||||
{ echo "tags<<EOF"
|
||||
echo "${IMAGE}:${VERSION}-studio"
|
||||
if [ "${{ env.PROMOTE_LATEST }}" = "true" ]; then
|
||||
echo "${IMAGE}:latest-studio"
|
||||
fi
|
||||
echo "EOF"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
- name: Build and push studio variant (with retry)
|
||||
shell: bash
|
||||
env:
|
||||
TAGS: ${{ steps.tags.outputs.tags }}
|
||||
BASE_IMAGE_FULL: ${{ env.IMAGE }}:${{ needs.base-decide.outputs.base_tag }}
|
||||
PI_VERSION: ${{ needs.resolve-versions.outputs.pi_version }}
|
||||
FORK_REF: ${{ needs.resolve-versions.outputs.fork_ref }}
|
||||
OBSMEM_REF: ${{ needs.resolve-versions.outputs.obsmem_ref }}
|
||||
TOOLKIT_REF: ${{ needs.resolve-versions.outputs.toolkit_ref }}
|
||||
EXTENSIONS_REF: ${{ needs.resolve-versions.outputs.extensions_ref }}
|
||||
STUDIO_REF: ${{ needs.resolve-versions.outputs.studio_ref }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
TAG_FLAGS=()
|
||||
while IFS= read -r t; do [[ -n "$t" ]] && TAG_FLAGS+=( -t "$t" ); done <<< "${TAGS}"
|
||||
# 3-attempt retry (see build-base step for rationale).
|
||||
for attempt in 1 2 3; do
|
||||
echo "==> Build+push attempt ${attempt}/3"
|
||||
if docker buildx build \
|
||||
--platform linux/amd64,linux/arm64 \
|
||||
--file Dockerfile.variant \
|
||||
--push \
|
||||
--build-arg "BASE_IMAGE=${BASE_IMAGE_FULL}" \
|
||||
--build-arg "PI_VERSION=${PI_VERSION}" \
|
||||
--build-arg "PI_FORK_REF=${FORK_REF}" \
|
||||
--build-arg "PI_OBSMEM_REF=${OBSMEM_REF}" \
|
||||
--build-arg "PI_TOOLKIT_REF=${TOOLKIT_REF}" \
|
||||
--build-arg "PI_EXTENSIONS_REF=${EXTENSIONS_REF}" \
|
||||
--build-arg "INSTALL_STUDIO=true" \
|
||||
--build-arg "PI_STUDIO_REF=${STUDIO_REF}" \
|
||||
"${TAG_FLAGS[@]}" \
|
||||
.; then
|
||||
echo "==> Attempt ${attempt} succeeded"
|
||||
exit 0
|
||||
fi
|
||||
if [[ "${attempt}" -lt 3 ]]; then
|
||||
backoff=$(( attempt * 15 ))
|
||||
echo "==> Attempt ${attempt} failed, sleeping ${backoff}s before retry"
|
||||
sleep "${backoff}"
|
||||
fi
|
||||
done
|
||||
echo "==> All 3 build+push attempts failed"
|
||||
exit 1
|
||||
|
||||
# ── Phase 5: promote base-<hash> → base-latest (manifest copy only) ─
|
||||
promote-base-latest:
|
||||
needs:
|
||||
|
||||
Reference in New Issue
Block a user