diff --git a/.github/workflows/evo-deploy.yml b/.github/workflows/evo-deploy.yml index 697afc9..7aad6e8 100644 --- a/.github/workflows/evo-deploy.yml +++ b/.github/workflows/evo-deploy.yml @@ -1,9 +1,11 @@ name: evo-deploy -# Deploy evOo-local on the Evo host when main advances, or on manual dispatch. +# Deploy evOo-local on the Evo host once Product CI has passed on main, or on manual dispatch. # Runs ON the self-hosted Evo runner (no SSH, no deploy secrets in the workflow). on: - push: + workflow_run: + workflows: [ci] + types: [completed] branches: [main] workflow_dispatch: inputs: @@ -29,7 +31,7 @@ concurrency: jobs: deploy: - if: vars.EVO_DEPLOY_ENABLED == 'true' + if: vars.EVO_DEPLOY_ENABLED == 'true' && (github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success') runs-on: [self-hosted, evoo-ci, Linux] timeout-minutes: 30 steps: @@ -37,12 +39,12 @@ jobs: uses: actions/checkout@v5 with: persist-credentials: false - ref: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.ref || github.sha }} + ref: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.ref || github.event.workflow_run.head_sha }} - name: Deploy evOo-local on Evo env: EVO_DEPLOY_DIR: ${{ vars.EVO_DEPLOY_DIR }} - EVO_DEPLOY_REF: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.ref || github.sha }} + EVO_DEPLOY_REF: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.ref || github.event.workflow_run.head_sha }} EVO_DRY_RUN: ${{ github.event.inputs.dry_run || 'false' }} EVO_MIGRATE: ${{ github.event.inputs.skip_migrate == true && 'false' || 'true' }} EVO_RESTART_MODE: ${{ vars.EVO_RESTART_MODE || 'auto' }} @@ -72,7 +74,7 @@ jobs: { echo "### Evo deploy" echo - echo "- Ref: \`${{ github.event_name == 'workflow_dispatch' && github.event.inputs.ref || github.sha }}\`" + echo "- Ref: \`${{ github.event_name == 'workflow_dispatch' && github.event.inputs.ref || github.event.workflow_run.head_sha }}\`" echo "- Dir: \`$EVO_DEPLOY_DIR\`" if [[ -f "$EVO_STATE_FILE" ]]; then echo "- State:" diff --git a/.github/workflows/publish-server-image.yml b/.github/workflows/publish-server-image.yml index 3c58ad7..d1904b5 100644 --- a/.github/workflows/publish-server-image.yml +++ b/.github/workflows/publish-server-image.yml @@ -71,10 +71,14 @@ jobs: with: persist-credentials: false - uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0 + - name: Resolve the lowercase image namespace + id: ns + # GHCR rejects repository names with capitals; the GitHub repository slug has them. + run: echo "repo=${GITHUB_REPOSITORY,,}" >> "$GITHUB_OUTPUT" - id: meta uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5.10.0 with: - images: ghcr.io/${{ github.repository }}/${{ matrix.name }} + images: ghcr.io/${{ steps.ns.outputs.repo }}/${{ matrix.name }} tags: type=sha,prefix=sha- - uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6.19.2 with: @@ -93,8 +97,8 @@ jobs: name: Build ${{ matrix.name }} ${{ matrix.arch }} if: github.event_name != 'pull_request' # Each architecture builds natively on its own runner and pushes by digest only. - # amd64 on evo Linux runners; arm64 on Apple Silicon macOS runners (linux/arm64 via buildx). - # No QEMU: emulated arm64 took hours for the desktop-sized computer image. + # amd64 on evo Linux runners. arm64 is paused: the macOS runners have no Docker buildx, + # and QEMU emulation took hours for the desktop-sized computer image. # See docs/self-host.md (Published images and tags). runs-on: ${{ matrix.runner }} timeout-minutes: 90 @@ -105,7 +109,7 @@ jobs: fail-fast: false matrix: name: [app, updater, computer] - arch: [amd64, arm64] + arch: [amd64] include: # Only the updater image contains the Docker CLI; the application image stays unprivileged. # The supervisor is not a separate published image: it runs from `app` on the internal network. @@ -120,8 +124,6 @@ jobs: context: infra/sandboxes/computer - arch: amd64 runner: [self-hosted, evoo-ci, Linux] - - arch: arm64 - runner: [self-hosted, evoo-ci, macOS] steps: - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5.1.0 with: @@ -133,11 +135,15 @@ jobs: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} + - name: Resolve the lowercase image namespace + id: ns + # GHCR rejects repository names with capitals; the GitHub repository slug has them. + run: echo "repo=${GITHUB_REPOSITORY,,}" >> "$GITHUB_OUTPUT" - id: meta uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5.10.0 with: # Labels only; the tagged manifest is assembled in the publish job. - images: ghcr.io/${{ github.repository }}/${{ matrix.name }} + images: ghcr.io/${{ steps.ns.outputs.repo }}/${{ matrix.name }} tags: type=sha,prefix=sha- - id: build uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6.19.2 @@ -147,7 +153,7 @@ jobs: platforms: linux/${{ matrix.arch }} labels: ${{ steps.meta.outputs.labels }} # A fork can publish only to its own namespace; no credential can redirect this value. - outputs: type=image,name=ghcr.io/${{ github.repository }}/${{ matrix.name }},push-by-digest=true,name-canonical=true,push=true + outputs: type=image,name=ghcr.io/${{ steps.ns.outputs.repo }}/${{ matrix.name }},push-by-digest=true,name-canonical=true,push=true # GET /health can report the exact source commit without a deployment-supplied override. build-args: ${{ matrix.name == 'app' && format('GIT_SHA={0}', github.sha) || '' }} cache-from: type=gha,scope=${{ matrix.name }}-${{ matrix.arch }} @@ -199,11 +205,15 @@ jobs: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} + - name: Resolve the lowercase image namespace + id: ns + # GHCR rejects repository names with capitals; the GitHub repository slug has them. + run: echo "repo=${GITHUB_REPOSITORY,,}" >> "$GITHUB_OUTPUT" - id: meta uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5.10.0 with: # A fork can publish only to its own namespace; no credential can redirect this value. - images: ghcr.io/${{ github.repository }}/${{ matrix.name }} + images: ghcr.io/${{ steps.ns.outputs.repo }}/${{ matrix.name }} tags: | type=semver,pattern=v{{version}} type=semver,pattern=v{{major}}.{{minor}} @@ -211,11 +221,11 @@ jobs: type=raw,value=edge,enable=${{ github.ref == 'refs/heads/main' }} # Stable releases only: a hyphen marks a prerelease (v1.0.0-rc.1) that must not move latest. type=raw,value=latest,enable=${{ startsWith(github.ref, 'refs/tags/v') && !contains(github.ref, '-') }} - - name: Assemble and verify the multi-arch manifest + - name: Assemble and verify the manifest id: manifest working-directory: /tmp/digests env: - IMAGE: ghcr.io/${{ github.repository }}/${{ matrix.name }} + IMAGE: ghcr.io/${{ steps.ns.outputs.repo }}/${{ matrix.name }} TAGS: ${{ steps.meta.outputs.tags }} run: | set -euo pipefail @@ -227,7 +237,7 @@ jobs: for digest in *; do sources+=("${IMAGE}@sha256:${digest}") done - if [[ "${#sources[@]}" -ne 2 ]]; then + if [[ "${#sources[@]}" -ne 1 ]]; then echo "Expected one digest per architecture, found ${#sources[@]}." >&2 exit 1 fi @@ -236,13 +246,13 @@ jobs: first_tag="$(head -n 1 <<< "$TAGS")" platforms="$(docker buildx imagetools inspect "$first_tag" \ --format '{{range .Manifest.Manifests}}{{.Platform.OS}}/{{.Platform.Architecture}}{{"\n"}}{{end}}')" - for want in linux/amd64 linux/arm64; do + for want in linux/amd64; do grep -Fqx "$want" <<< "$platforms" || { echo "$first_tag is missing $want" >&2; exit 1; } done echo "digest=$(docker buildx imagetools inspect "$first_tag" --format '{{.Manifest.Digest}}')" >> "$GITHUB_OUTPUT" - name: Attest the published image uses: actions/attest-build-provenance@4d101475d8b20a2381f78447822ac1eab6504dd8 # v4.2.2 with: - subject-name: ghcr.io/${{ github.repository }}/${{ matrix.name }} + subject-name: ghcr.io/${{ steps.ns.outputs.repo }}/${{ matrix.name }} subject-digest: ${{ steps.manifest.outputs.digest }} push-to-registry: true diff --git a/docs/self-host.md b/docs/self-host.md index ce91160..8510b82 100644 --- a/docs/self-host.md +++ b/docs/self-host.md @@ -36,8 +36,9 @@ Compose stack requires `SANDBOX_SUPERVISOR_TOKEN` for every provider; leave it e Optional: set `OPENROUTER_API_KEY` or connect a model in the UI after signup. -The example defaults to `edge` (main builds). Every publish is multi-arch (`amd64` + `arm64`), so -arm64 hosts need no special tag. Do not assume `latest` is present until a stable release exists. +The example defaults to `edge` (main builds). Published images are `linux/amd64` only for now; +arm64 publishing is paused until an arm64 runner with Docker buildx exists. Do not assume `latest` +is present until a stable release exists. Open [http://127.0.0.1:5173](http://127.0.0.1:5173). The first registered user becomes the deployment owner. Put TLS in front of `:5173` for a public host and set the three public origins to @@ -483,8 +484,9 @@ your CI cannot publish into someone else's. | `sha-` | every push and manual run | source-addressed; used by the updater sidecar | | `edge` | pushes to main | yes, to the newest main build | -Every publish, including `edge` from main merges, is multi-arch (`amd64` + `arm64`): each -architecture builds natively on its own runner and one manifest is assembled per image. Until a +Every publish, including `edge` from main merges, is `linux/amd64` only for now: the image builds +natively on the Linux runner and one manifest is assembled per image. arm64 publishing is paused +until an arm64 runner with Docker buildx exists. Until a stable `vX.Y.Z` has been published, GHCR may only have `edge` and `sha-*` tags; do not pin `latest` unless that tag exists in the registry. diff --git a/scripts/publish-playwright-report.sh b/scripts/publish-playwright-report.sh index ffdbdcf..afbc04a 100755 --- a/scripts/publish-playwright-report.sh +++ b/scripts/publish-playwright-report.sh @@ -1,11 +1,13 @@ #!/usr/bin/env bash set -euo pipefail -: "${AWS_ACCESS_KEY_ID:?AWS_ACCESS_KEY_ID is required}" -: "${AWS_SECRET_ACCESS_KEY:?AWS_SECRET_ACCESS_KEY is required}" -: "${S3_BUCKET:?S3_BUCKET is required}" -: "${S3_ENDPOINT:?S3_ENDPOINT is required}" -: "${PLAYWRIGHT_PUBLIC_BASE_URL:?PLAYWRIGHT_PUBLIC_BASE_URL is required}" +# Publishing is optional: without the S3 settings the gallery is skipped and the job stays green. +for setting in AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY S3_BUCKET S3_ENDPOINT PLAYWRIGHT_PUBLIC_BASE_URL; do + if [[ -z "${!setting:-}" ]]; then + echo "::notice::Skipping the Playwright report publish: $setting is not configured." + exit 0 + fi +done : "${PLAYWRIGHT_RESULT:?PLAYWRIGHT_RESULT is required}" : "${PLAYWRIGHT_RUN_ATTEMPT:?PLAYWRIGHT_RUN_ATTEMPT is required}" : "${PLAYWRIGHT_RUN_ID:?PLAYWRIGHT_RUN_ID is required}"