From 62bdb52eaa98533c9ab023dd198bf3f1e895ba66 Mon Sep 17 00:00:00 2001 From: Mohamed Boudra Date: Sat, 27 Jun 2026 05:17:04 +0000 Subject: [PATCH] ci: build beta Docker images from source --- .dockerignore | 21 +++++++ .github/workflows/docker.yml | 26 ++++++--- docker/base/Dockerfile.source | 100 ++++++++++++++++++++++++++++++++++ docs/release.md | 2 +- 4 files changed, 140 insertions(+), 9 deletions(-) create mode 100644 .dockerignore create mode 100644 docker/base/Dockerfile.source diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 000000000..bbe22672d --- /dev/null +++ b/.dockerignore @@ -0,0 +1,21 @@ +.git +.dev +.playwright-mcp +.paseo +.tasks +.wrangler +**/.wrangler +**/.tanstack +**/node_modules +**/dist +**/build +**/.cache +**/.expo +**/test-results +**/*.tsbuildinfo +artifacts +packages/app/android +packages/desktop/release +plan.*.log +*.log +CLAUDE.local.md diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 5abaa56b2..02023d966 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -32,6 +32,7 @@ jobs: image: ${{ steps.values.outputs.image }} install_version: ${{ steps.values.outputs.install_version }} publish: ${{ steps.values.outputs.publish }} + source_build: ${{ steps.values.outputs.source_build }} check_tag: ${{ steps.values.outputs.check_tag }} publish_tags: ${{ steps.values.outputs.publish_tags }} steps: @@ -49,28 +50,37 @@ jobs: image="ghcr.io/${owner}/paseo" install_version="${INPUT_PASEO_VERSION:-latest}" publish=false + source_build=false + prerelease=false if [[ "${GITHUB_REF}" == refs/tags/v* ]]; then install_version="${REF_NAME#v}" - if [[ "${REF_NAME}" != *-* ]]; then - publish=true + publish=true + if [[ "${REF_NAME}" == *-* ]]; then + prerelease=true + source_build=true fi fi check_tag="${image}:check-${GITHUB_SHA::12}" - publish_tags="${image}:${install_version}"$'\n'"${image}:latest" + if [[ "${prerelease}" == "true" ]]; then + publish_tags="${image}:${install_version}" + else + publish_tags="${image}:${install_version}"$'\n'"${image}:latest" + fi { echo "image=${image}" echo "install_version=${install_version}" echo "publish=${publish}" + echo "source_build=${source_build}" echo "check_tag=${check_tag}" echo "publish_tags<> "$GITHUB_OUTPUT" - echo "Resolved image=${image} install_version=${install_version} publish=${publish}" + echo "Resolved image=${image} install_version=${install_version} publish=${publish} source_build=${source_build}" build: needs: setup @@ -86,8 +96,8 @@ jobs: - uses: docker/build-push-action@v7 with: - context: docker/base - file: docker/base/Dockerfile + context: ${{ needs.setup.outputs.source_build == 'true' && '.' || 'docker/base' }} + file: ${{ needs.setup.outputs.source_build == 'true' && 'docker/base/Dockerfile.source' || 'docker/base/Dockerfile' }} platforms: ${{ env.PLATFORMS }} build-args: | PASEO_VERSION=${{ needs.setup.outputs.install_version }} @@ -119,8 +129,8 @@ jobs: - uses: docker/build-push-action@v7 with: - context: docker/base - file: docker/base/Dockerfile + context: ${{ needs.setup.outputs.source_build == 'true' && '.' || 'docker/base' }} + file: ${{ needs.setup.outputs.source_build == 'true' && 'docker/base/Dockerfile.source' || 'docker/base/Dockerfile' }} platforms: ${{ env.PLATFORMS }} build-args: | PASEO_VERSION=${{ needs.setup.outputs.install_version }} diff --git a/docker/base/Dockerfile.source b/docker/base/Dockerfile.source new file mode 100644 index 000000000..4680ee4be --- /dev/null +++ b/docker/base/Dockerfile.source @@ -0,0 +1,100 @@ +# syntax=docker/dockerfile:1 + +ARG NODE_IMAGE=node:22-bookworm-slim +FROM --platform=$BUILDPLATFORM ${NODE_IMAGE} AS source-pack + +ARG PASEO_VERSION + +ENV ONNXRUNTIME_NODE_INSTALL=skip + +WORKDIR /tmp/paseo-src +COPY . . + +RUN set -eux; \ + test "$(node -p "require('./package.json').version")" = "${PASEO_VERSION}"; \ + node -e 'const fs=require("node:fs"); const pkg=JSON.parse(fs.readFileSync("package.json","utf8")); delete pkg.scripts.prepare; fs.writeFileSync("package.json", `${JSON.stringify(pkg)}\n`);'; \ + npm ci + +RUN set -eux; \ + mkdir -p /tmp/paseo-packs; \ + npm pack --workspace=@getpaseo/highlight --pack-destination /tmp/paseo-packs; \ + npm pack --workspace=@getpaseo/relay --pack-destination /tmp/paseo-packs; \ + npm pack --workspace=@getpaseo/protocol --pack-destination /tmp/paseo-packs; \ + npm pack --workspace=@getpaseo/client --pack-destination /tmp/paseo-packs; \ + npm pack --workspace=@getpaseo/server --pack-destination /tmp/paseo-packs; \ + npm pack --workspace=@getpaseo/cli --pack-destination /tmp/paseo-packs + +FROM ${NODE_IMAGE} + +ENV HOME=/home/paseo \ + PASEO_HOME=/home/paseo/.paseo \ + PASEO_LISTEN=0.0.0.0:6767 \ + PASEO_WEB_UI_ENABLED=true \ + PASEO_LOG_FORMAT=json \ + PASEO_LOG_LEVEL=info \ + CLAUDE_CONFIG_DIR=/home/paseo/.claude \ + CODEX_HOME=/home/paseo/.codex \ + XDG_CONFIG_HOME=/home/paseo/.config \ + XDG_DATA_HOME=/home/paseo/.local/share \ + XDG_STATE_HOME=/home/paseo/.local/state \ + XDG_CACHE_HOME=/home/paseo/.cache \ + ONNXRUNTIME_NODE_INSTALL=skip + +RUN set -eux; \ + apt-get update; \ + apt-get install -y --no-install-recommends \ + bash \ + ca-certificates \ + curl \ + git \ + gosu \ + openssh-client \ + tini; \ + rm -rf /var/lib/apt/lists/* + +COPY --from=source-pack /tmp/paseo-packs /tmp/paseo-packs +RUN set -eux; \ + npm install -g --omit=optional /tmp/paseo-packs/*.tgz; \ + rm -rf /tmp/paseo-packs; \ + npm cache clean --force; \ + server_entry="$(npm root -g)/@getpaseo/server/dist/scripts/supervisor-entrypoint.js"; \ + test -f "$server_entry"; \ + printf '%s\n' "$server_entry" > /etc/paseo-server-entry; \ + node --check "$server_entry" + +RUN set -eux; \ + existing_group="$(getent group 1000 | cut -d: -f1 || true)"; \ + if [ -n "$existing_group" ] && [ "$existing_group" != "paseo" ]; then \ + groupmod --new-name paseo "$existing_group"; \ + elif [ -z "$existing_group" ]; then \ + groupadd --gid 1000 paseo; \ + fi; \ + existing_user="$(getent passwd 1000 | cut -d: -f1 || true)"; \ + if [ -n "$existing_user" ] && [ "$existing_user" != "paseo" ]; then \ + usermod --login paseo --gid paseo --home /home/paseo --shell /bin/bash "$existing_user"; \ + elif [ -z "$existing_user" ]; then \ + useradd --uid 1000 --gid paseo --create-home --home-dir /home/paseo --shell /bin/bash paseo; \ + fi; \ + mkdir -p \ + /workspace \ + "$PASEO_HOME" \ + "$CLAUDE_CONFIG_DIR" \ + "$CODEX_HOME" \ + "$XDG_CONFIG_HOME" \ + "$XDG_DATA_HOME" \ + "$XDG_STATE_HOME" \ + "$XDG_CACHE_HOME"; \ + chown -R paseo:paseo /home/paseo /workspace + +COPY docker/base/rootfs/ / +RUN chmod +x /usr/local/bin/paseo-docker-entrypoint + +WORKDIR /workspace + +EXPOSE 6767 +VOLUME ["/home/paseo"] + +HEALTHCHECK --interval=30s --timeout=5s --start-period=30s --retries=3 \ + CMD node -e "const listen=process.env.PASEO_LISTEN||'0.0.0.0:6767'; const m=listen.match(/:(\\d+)$/); const port=m?Number(m[1]):6767; require('http').get({hostname:'127.0.0.1',port,path:'/api/health'},r=>process.exit(r.statusCode===200?0:1)).on('error',()=>process.exit(1))" + +ENTRYPOINT ["/usr/bin/tini", "--", "/usr/local/bin/paseo-docker-entrypoint"] diff --git a/docs/release.md b/docs/release.md index 32c6369c2..5203feea3 100644 --- a/docs/release.md +++ b/docs/release.md @@ -50,7 +50,7 @@ npm run release:patch This bumps the version across all workspaces, runs checks, publishes to npm, and pushes the branch + tag. The tag push triggers `Desktop Release`, `Android APK Release`, `Docker`, and `Release Notes Sync` on GitHub Actions. EAS picks up the same tag via the EAS GitHub app and starts the iOS + Android store builds in parallel (see "Mobile builds (EAS)" below) — there is no `release-mobile.yml` in this repo. -The Docker workflow builds images on pull requests and on `main` as non-publishing checks. Only stable `vX.Y.Z` tag pushes publish `ghcr.io/getpaseo/paseo:X.Y.Z` and `ghcr.io/getpaseo/paseo:latest`; beta tags and manual workflow dispatches build for validation only. +The Docker workflow builds images on pull requests and on `main` as non-publishing checks. Stable `vX.Y.Z` tag pushes publish `ghcr.io/getpaseo/paseo:X.Y.Z` and `ghcr.io/getpaseo/paseo:latest`; beta `vX.Y.Z-beta.N` tag pushes publish only `ghcr.io/getpaseo/paseo:X.Y.Z-beta.N` and never move `latest`. Beta Docker images build from the checked-out source tree so the beta flow can intentionally skip npm publishing. **Releases are always patch.** "Release paseo", "release stable", "ship stable", and similar always mean a patch bump from the previous stable. Never bump minor or major to trigger a build, ever — minor and major bumps are reserved for genuinely larger product cuts and require an explicit user instruction with the word "minor" or "major". If you find yourself reaching for `release:minor` to retrigger a failed build, you are doing the wrong thing — push a retry tag instead (see "Fixing a failed release build" below).