Skip to content

fix: use artifacts to pass TX hashes between matrix jobs #63

fix: use artifacts to pass TX hashes between matrix jobs

fix: use artifacts to pass TX hashes between matrix jobs #63

Workflow file for this run

# Toy Example App - Build Workflow
#
# Builds and pushes Docker images for both mock-api and enclave.
# Triggered on:
# - Pushes to main and feat/toy-example-app branches
# - Version tags (v*) for automatic deployment to prod5 and prod9
#
# Images are pushed to GHCR with commit SHA and version tags for traceability.
name: Build Toy Example
on:
push:
branches: [main, feat/toy-example-app]
paths:
- 'toy-example-app/**'
- '.github/workflows/toy-build.yml'
tags:
- 'v[0-9]+.[0-9]+.[0-9]+'
pull_request:
branches: [main, feat/toy-example-app]
paths:
- 'toy-example-app/**'
workflow_dispatch:
inputs:
auto_deploy:
description: 'Automatically deploy to staging after build'
required: false
default: false
type: boolean
env:
REGISTRY: ghcr.io
jobs:
build-mock-api:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
outputs:
image_digest: ${{ steps.build.outputs.digest }}
sha_short: ${{ steps.vars.outputs.sha_short }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set output variables
id: vars
run: |
echo "sha_short=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
echo "sha_full=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT
echo "timestamp=$(date -u +%Y-%m-%dT%H:%M:%SZ)" >> $GITHUB_OUTPUT
OWNER_LC=$(echo "${{ github.repository_owner }}" | tr '[:upper:]' '[:lower:]')
echo "image=ghcr.io/${OWNER_LC}/toy-example-mock-api" >> $GITHUB_OUTPUT
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to GHCR
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push mock-api
id: build
uses: docker/build-push-action@v5
with:
context: ./toy-example-app/mock-api
file: ./toy-example-app/mock-api/Dockerfile
push: ${{ github.event_name != 'pull_request' }}
tags: |
${{ steps.vars.outputs.image }}:latest
${{ steps.vars.outputs.image }}:${{ steps.vars.outputs.sha_short }}
labels: |
org.opencontainers.image.source=${{ github.repositoryUrl }}
org.opencontainers.image.revision=${{ steps.vars.outputs.sha_full }}
org.opencontainers.image.created=${{ steps.vars.outputs.timestamp }}
cache-from: type=gha,scope=mock-api
cache-to: type=gha,mode=max,scope=mock-api
- name: Record build info
run: |
echo "## Mock API Build" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "| Item | Value |" >> $GITHUB_STEP_SUMMARY
echo "|------|-------|" >> $GITHUB_STEP_SUMMARY
echo "| Image | \`${{ steps.vars.outputs.image }}:${{ steps.vars.outputs.sha_short }}\` |" >> $GITHUB_STEP_SUMMARY
echo "| Digest | \`${{ steps.build.outputs.digest }}\` |" >> $GITHUB_STEP_SUMMARY
build-enclave:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
outputs:
image_digest: ${{ steps.build.outputs.digest }}
sha_short: ${{ steps.vars.outputs.sha_short }}
version: ${{ steps.vars.outputs.version }}
is_release: ${{ steps.vars.outputs.is_release }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set output variables
id: vars
run: |
echo "sha_short=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
echo "sha_full=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT
echo "timestamp=$(date -u +%Y-%m-%dT%H:%M:%SZ)" >> $GITHUB_OUTPUT
OWNER_LC=$(echo "${{ github.repository_owner }}" | tr '[:upper:]' '[:lower:]')
echo "image=ghcr.io/${OWNER_LC}/toy-example-enclave" >> $GITHUB_OUTPUT
# Extract version from tag if present (e.g., v1.2.0 -> 1.2.0)
if [[ "${{ github.ref }}" == refs/tags/v* ]]; then
VERSION="${GITHUB_REF#refs/tags/v}"
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "is_release=true" >> $GITHUB_OUTPUT
else
echo "version=dev" >> $GITHUB_OUTPUT
echo "is_release=false" >> $GITHUB_OUTPUT
fi
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to GHCR
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push enclave
id: build
uses: docker/build-push-action@v5
with:
context: ./toy-example-app/enclave
file: ./toy-example-app/enclave/Dockerfile
push: ${{ github.event_name != 'pull_request' }}
tags: |
${{ steps.vars.outputs.image }}:latest
${{ steps.vars.outputs.image }}:${{ steps.vars.outputs.sha_short }}
${{ steps.vars.outputs.is_release == 'true' && format('{0}:{1}', steps.vars.outputs.image, steps.vars.outputs.version) || '' }}
build-args: |
BUILD_SHA=${{ steps.vars.outputs.sha_full }}
BUILD_TIME=${{ steps.vars.outputs.timestamp }}
ENVIRONMENT=${{ steps.vars.outputs.is_release == 'true' && 'production' || 'staging' }}
labels: |
org.opencontainers.image.source=${{ github.repositoryUrl }}
org.opencontainers.image.revision=${{ steps.vars.outputs.sha_full }}
org.opencontainers.image.created=${{ steps.vars.outputs.timestamp }}
org.opencontainers.image.version=${{ steps.vars.outputs.version }}
cache-from: type=gha,scope=enclave
cache-to: type=gha,mode=max,scope=enclave
- name: Record build info
run: |
echo "## Enclave Build" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "| Item | Value |" >> $GITHUB_STEP_SUMMARY
echo "|------|-------|" >> $GITHUB_STEP_SUMMARY
echo "| Image | \`${{ steps.vars.outputs.image }}:${{ steps.vars.outputs.sha_short }}\` |" >> $GITHUB_STEP_SUMMARY
echo "| Digest | \`${{ steps.build.outputs.digest }}\` |" >> $GITHUB_STEP_SUMMARY
verify-code:
runs-on: ubuntu-latest
needs: [build-enclave]
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Verify no direct_messages API calls
run: |
echo "## Security Verification" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
# Check for actual API call patterns to direct_messages endpoint
# Looking for URL patterns, not documentation comments
if grep -rE '(/api/direct_messages|getDirectMessages|fetchDirectMessages|\.direct_messages)' toy-example-app/enclave/src/*.ts 2>/dev/null; then
echo "❌ FAIL: Found direct_messages API call patterns in enclave code" >> $GITHUB_STEP_SUMMARY
exit 1
else
echo "✅ PASS: No direct_messages API calls in enclave code" >> $GITHUB_STEP_SUMMARY
fi
# Also verify no fetch/http calls outside tiktok-client.ts reference direct_messages
NON_CLIENT_FILES=$(find toy-example-app/enclave/src -name "*.ts" ! -name "tiktok-client.ts")
for f in $NON_CLIENT_FILES; do
if grep -E 'direct_message' "$f" | grep -vE '^[[:space:]]*//' | grep -q .; then
echo "❌ FAIL: Non-comment direct_messages reference in $f" >> $GITHUB_STEP_SUMMARY
exit 1
fi
done
echo "✅ PASS: No non-comment direct_messages references outside tiktok-client.ts" >> $GITHUB_STEP_SUMMARY
- name: Verify API calls are isolated
run: |
# Check that only tiktok-client.ts makes external API calls
FETCH_FILES=$(grep -rl "fetch\(" toy-example-app/enclave/src/ || true)
echo "" >> $GITHUB_STEP_SUMMARY
echo "### External API Call Analysis" >> $GITHUB_STEP_SUMMARY
if [ -z "$FETCH_FILES" ]; then
echo "No fetch calls found" >> $GITHUB_STEP_SUMMARY
else
echo "Files with fetch calls:" >> $GITHUB_STEP_SUMMARY
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
echo "$FETCH_FILES" >> $GITHUB_STEP_SUMMARY
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
# Verify only tiktok-client.ts
if echo "$FETCH_FILES" | grep -v "tiktok-client.ts"; then
echo "⚠️ WARNING: fetch() found in files other than tiktok-client.ts" >> $GITHUB_STEP_SUMMARY
else
echo "✅ All external API calls isolated to tiktok-client.ts" >> $GITHUB_STEP_SUMMARY
fi
fi
summary:
runs-on: ubuntu-latest
needs: [build-mock-api, build-enclave, verify-code]
if: always()
outputs:
sha_short: ${{ needs.build-enclave.outputs.sha_short }}
version: ${{ needs.build-enclave.outputs.version }}
is_release: ${{ needs.build-enclave.outputs.is_release }}
all_success: ${{ needs.build-mock-api.result == 'success' && needs.build-enclave.result == 'success' && needs.verify-code.result == 'success' }}
steps:
- name: Create summary
run: |
echo "## Build Summary" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "| Component | Status | SHA |" >> $GITHUB_STEP_SUMMARY
echo "|-----------|--------|-----|" >> $GITHUB_STEP_SUMMARY
echo "| Mock API | ${{ needs.build-mock-api.result }} | ${{ needs.build-mock-api.outputs.sha_short }} |" >> $GITHUB_STEP_SUMMARY
echo "| Enclave | ${{ needs.build-enclave.result }} | ${{ needs.build-enclave.outputs.sha_short }} |" >> $GITHUB_STEP_SUMMARY
echo "| Verify | ${{ needs.verify-code.result }} | - |" >> $GITHUB_STEP_SUMMARY
deploy:
runs-on: ubuntu-latest
needs: [summary, build-enclave]
if: |
(github.event_name == 'push' || github.event_name == 'workflow_dispatch') &&
needs.summary.outputs.all_success == 'true'
environment: staging
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set image name
id: vars
run: |
OWNER_LC=$(echo "${{ github.repository_owner }}" | tr '[:upper:]' '[:lower:]')
echo "enclave_image=ghcr.io/${OWNER_LC}/toy-example-enclave" >> $GITHUB_OUTPUT
- name: Update docker-compose with image tag
run: |
cd toy-example-app/enclave
sed -i "s|image:.*toy-example-enclave.*|image: ${{ steps.vars.outputs.enclave_image }}:${{ needs.build-enclave.outputs.sha_short }}|g" docker-compose.yml
echo "Updated docker-compose.yml:"
cat docker-compose.yml
- name: Calculate compose hash
id: hash
run: |
HASH=$(sha256sum toy-example-app/enclave/docker-compose.yml | cut -d' ' -f1)
echo "compose_hash=$HASH" >> $GITHUB_OUTPUT
echo "Compose hash: $HASH"
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install Phala CLI
run: npm install -g phala
- name: Check if enclave files changed
id: changes
run: |
# Check if any files in toy-example-app/enclave changed since the last successful deploy
# We use git diff to compare with the parent commit
# If only workflow or README changed, we can skip deployment
echo "Checking for enclave source changes..."
# Get list of changed files in this commit
CHANGED_FILES=$(git diff --name-only HEAD~1 HEAD 2>/dev/null || echo "")
echo "Changed files in this commit:"
echo "$CHANGED_FILES"
# Check if any enclave source files changed (excluding compose which changes every time)
ENCLAVE_CHANGES=$(echo "$CHANGED_FILES" | grep -E '^toy-example-app/enclave/(src/|Dockerfile|package|tsconfig)' || echo "")
if [ -n "$ENCLAVE_CHANGES" ]; then
echo "Enclave source files changed:"
echo "$ENCLAVE_CHANGES"
echo "enclave_changed=true" >> $GITHUB_OUTPUT
else
echo "No enclave source files changed"
echo "enclave_changed=false" >> $GITHUB_OUTPUT
fi
- name: Check if deployment needed
id: check
env:
PHALA_CLOUD_API_KEY: ${{ secrets.PHALA_CLOUD_API_KEY }}
run: |
echo "Checking deployment status..."
# Check if CVM exists
CURRENT_INFO=$(phala cvms get toy-example-staging --json 2>/dev/null || echo '{"success":false}')
if echo "$CURRENT_INFO" | jq -e '.success == true' > /dev/null 2>&1; then
CURRENT_STATUS=$(echo "$CURRENT_INFO" | jq -r '.status // "unknown"')
echo "CVM exists with status: $CURRENT_STATUS"
echo "cvm_exists=true" >> $GITHUB_OUTPUT
# If enclave source didn't change and CVM is running, skip deployment
if [ "${{ steps.changes.outputs.enclave_changed }}" = "false" ] && [ "$CURRENT_STATUS" = "running" ]; then
echo "Enclave source unchanged and CVM is running - skipping deployment"
echo "skip_deploy=true" >> $GITHUB_OUTPUT
exit 0
fi
else
echo "No existing CVM found"
echo "cvm_exists=false" >> $GITHUB_OUTPUT
fi
echo "skip_deploy=false" >> $GITHUB_OUTPUT
- name: Deploy to Phala Cloud
id: deploy
if: steps.check.outputs.skip_deploy != 'true'
env:
PHALA_CLOUD_API_KEY: ${{ secrets.PHALA_CLOUD_API_KEY }}
MOCK_API_URL: ${{ secrets.TOY_MOCK_API_URL }}
MOCK_API_TOKEN: ${{ secrets.TOY_MOCK_API_TOKEN }}
SIGNING_KEY: ${{ secrets.TOY_SIGNING_KEY }}
GHCR_USERNAME: ${{ github.repository_owner }}
GHCR_TOKEN: ${{ secrets.GHCR_PAT }}
PRIVATE_KEY: ${{ secrets.TOY_DEPLOYER_PRIVATE_KEY }}
run: |
cd toy-example-app/enclave
CVM_NAME="toy-example-staging"
# Use --cvm-id to upgrade existing CVM, or create new if doesn't exist
if [ "${{ steps.check.outputs.cvm_exists }}" = "true" ]; then
echo "Upgrading existing CVM..."
DEPLOY_ARGS="--cvm-id $CVM_NAME --wait"
else
echo "Creating new CVM..."
DEPLOY_ARGS="-n $CVM_NAME"
fi
# Deploy/upgrade with Base KMS for on-chain transparency logging
phala deploy \
-c docker-compose.yml \
$DEPLOY_ARGS \
--kms base \
--private-key "$PRIVATE_KEY" \
-e MOCK_API_URL="$MOCK_API_URL" \
-e MOCK_API_TOKEN="$MOCK_API_TOKEN" \
-e SIGNING_KEY="$SIGNING_KEY" \
-e DSTACK_DOCKER_REGISTRY="ghcr.io" \
-e DSTACK_DOCKER_USERNAME="$GHCR_USERNAME" \
-e DSTACK_DOCKER_PASSWORD="$GHCR_TOKEN" \
2>&1 | tee deploy.log
echo "deployment_time=$(date -u +%Y-%m-%dT%H:%M:%SZ)" >> $GITHUB_OUTPUT
- name: Skip deployment notice
if: steps.check.outputs.skip_deploy == 'true'
run: |
echo "## Deployment Skipped" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "The compose hash has not changed since the last deployment." >> $GITHUB_STEP_SUMMARY
echo "Current deployment is already running the latest image." >> $GITHUB_STEP_SUMMARY
- name: Get deployment info
if: steps.check.outputs.skip_deploy != 'true'
env:
PHALA_CLOUD_API_KEY: ${{ secrets.PHALA_CLOUD_API_KEY }}
run: |
echo "Getting deployment status..."
phala cvms get toy-example-staging --json 2>&1 | tee deployment-info.json || true
- name: Record deployment
if: steps.check.outputs.skip_deploy != 'true'
run: |
echo "## Deployment Summary" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "| Item | Value |" >> $GITHUB_STEP_SUMMARY
echo "|------|-------|" >> $GITHUB_STEP_SUMMARY
echo "| Image | \`${{ steps.vars.outputs.enclave_image }}:${{ needs.build-enclave.outputs.sha_short }}\` |" >> $GITHUB_STEP_SUMMARY
echo "| Compose Hash | \`${{ steps.hash.outputs.compose_hash }}\` |" >> $GITHUB_STEP_SUMMARY
echo "| Deploy Time | ${{ steps.deploy.outputs.deployment_time }} |" >> $GITHUB_STEP_SUMMARY
# Multi-machine release deployment (triggered by version tags)
deploy-release:
runs-on: ubuntu-latest
needs: [summary, build-enclave]
if: |
startsWith(github.ref, 'refs/tags/v') &&
needs.summary.outputs.all_success == 'true'
environment: production
strategy:
matrix:
include:
- cluster: prod5
node_id_secret: TOY_NODE_ID_PROD5
- cluster: prod9
node_id_secret: TOY_NODE_ID_PROD9
fail-fast: false
max-parallel: 1 # Deploy sequentially to avoid race conditions
outputs:
prod5_tx: ${{ steps.output.outputs.prod5_tx }}
prod5_hash: ${{ steps.output.outputs.prod5_hash }}
prod5_time: ${{ steps.output.outputs.prod5_time }}
prod9_tx: ${{ steps.output.outputs.prod9_tx }}
prod9_hash: ${{ steps.output.outputs.prod9_hash }}
prod9_time: ${{ steps.output.outputs.prod9_time }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set variables
id: vars
run: |
OWNER_LC=$(echo "${{ github.repository_owner }}" | tr '[:upper:]' '[:lower:]')
echo "enclave_image=ghcr.io/${OWNER_LC}/toy-example-enclave" >> $GITHUB_OUTPUT
echo "version=${{ needs.summary.outputs.version }}" >> $GITHUB_OUTPUT
echo "timestamp=$(date -u +%Y-%m-%dT%H:%M:%SZ)" >> $GITHUB_OUTPUT
- name: Update docker-compose with image tag
run: |
cd toy-example-app/enclave
# Update image tag only - cluster is selected via --node-id
sed -i "s|image:.*toy-example-enclave.*|image: ${{ steps.vars.outputs.enclave_image }}:${{ needs.build-enclave.outputs.sha_short }}|g" docker-compose.yml
echo "Updated docker-compose.yml:"
cat docker-compose.yml
- name: Calculate compose hash
id: hash
run: |
HASH=$(sha256sum toy-example-app/enclave/docker-compose.yml | cut -d' ' -f1)
echo "compose_hash=$HASH" >> $GITHUB_OUTPUT
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install Phala CLI
run: npm install -g phala
- name: Check if CVM exists
id: check_cvm
env:
PHALA_CLOUD_API_KEY: ${{ secrets.PHALA_CLOUD_API_KEY }}
run: |
CVM_NAME="toy-example-${{ matrix.cluster }}"
if phala cvms get "$CVM_NAME" --json 2>/dev/null | jq -e '.success == true' > /dev/null 2>&1; then
echo "cvm_exists=true" >> $GITHUB_OUTPUT
else
echo "cvm_exists=false" >> $GITHUB_OUTPUT
fi
- name: Deploy to ${{ matrix.cluster }}
id: deploy
env:
PHALA_CLOUD_API_KEY: ${{ secrets.PHALA_CLOUD_API_KEY }}
MOCK_API_URL: ${{ secrets.TOY_MOCK_API_URL }}
MOCK_API_TOKEN: ${{ secrets.TOY_MOCK_API_TOKEN }}
SIGNING_KEY: ${{ secrets.TOY_SIGNING_KEY }}
GHCR_USERNAME: ${{ github.repository_owner }}
GHCR_TOKEN: ${{ secrets.GHCR_PAT }}
PRIVATE_KEY: ${{ secrets.TOY_DEPLOYER_PRIVATE_KEY }}
NODE_ID_PROD5: ${{ secrets.TOY_NODE_ID_PROD5 }}
NODE_ID_PROD9: ${{ secrets.TOY_NODE_ID_PROD9 }}
run: |
cd toy-example-app/enclave
CVM_NAME="toy-example-${{ matrix.cluster }}"
# Select node ID based on cluster
if [ "${{ matrix.cluster }}" = "prod5" ]; then
NODE_ID="$NODE_ID_PROD5"
else
NODE_ID="$NODE_ID_PROD9"
fi
# Use --cvm-id to upgrade existing CVM, or create new if doesn't exist
if [ "${{ steps.check_cvm.outputs.cvm_exists }}" = "true" ]; then
echo "Upgrading existing CVM on ${{ matrix.cluster }}..."
DEPLOY_ARGS="--cvm-id $CVM_NAME --wait"
else
echo "Creating new CVM on ${{ matrix.cluster }}..."
# Note: --custom-app-id and --nonce only work with PHALA KMS, not Base KMS
# Each deployment will get its own app_id from Base KMS
DEPLOY_ARGS="-n $CVM_NAME --node-id $NODE_ID"
fi
# Deploy/upgrade with Base KMS for on-chain transparency logging
phala deploy \
-c docker-compose.yml \
$DEPLOY_ARGS \
--kms base \
--private-key "$PRIVATE_KEY" \
-e MOCK_API_URL="$MOCK_API_URL" \
-e MOCK_API_TOKEN="$MOCK_API_TOKEN" \
-e SIGNING_KEY="$SIGNING_KEY" \
-e ENVIRONMENT="${{ matrix.cluster }}" \
-e DSTACK_DOCKER_REGISTRY="ghcr.io" \
-e DSTACK_DOCKER_USERNAME="$GHCR_USERNAME" \
-e DSTACK_DOCKER_PASSWORD="$GHCR_TOKEN" \
2>&1 | tee deploy-${{ matrix.cluster }}.log
# Try to extract TX hash from deploy output
TX_HASH=$(grep -oE '0x[a-fA-F0-9]{64}' deploy-${{ matrix.cluster }}.log | head -1 || echo "pending")
echo "tx_hash=$TX_HASH" >> $GITHUB_OUTPUT
echo "deployment_time=$(date -u +%Y-%m-%dT%H:%M:%SZ)" >> $GITHUB_OUTPUT
- name: Save deployment info for consolidation
run: |
# Write deployment info to a file for artifact upload
mkdir -p deploy-info
cat > deploy-info/${{ matrix.cluster }}.json <<EOF
{
"cluster": "${{ matrix.cluster }}",
"tx_hash": "${{ steps.deploy.outputs.tx_hash }}",
"compose_hash": "${{ steps.hash.outputs.compose_hash }}",
"timestamp": "${{ steps.vars.outputs.timestamp }}",
"version": "${{ steps.vars.outputs.version }}"
}
EOF
cat deploy-info/${{ matrix.cluster }}.json
- name: Upload deployment info
uses: actions/upload-artifact@v4
with:
name: deploy-info-${{ matrix.cluster }}
path: deploy-info/${{ matrix.cluster }}.json
retention-days: 7
- name: Record release deployment
run: |
echo "## Release Deployment - ${{ matrix.cluster }}" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "| Item | Value |" >> $GITHUB_STEP_SUMMARY
echo "|------|-------|" >> $GITHUB_STEP_SUMMARY
echo "| Version | \`${{ steps.vars.outputs.version }}\` |" >> $GITHUB_STEP_SUMMARY
echo "| Cluster | ${{ matrix.cluster }} |" >> $GITHUB_STEP_SUMMARY
echo "| Image | \`${{ steps.vars.outputs.enclave_image }}:${{ needs.build-enclave.outputs.sha_short }}\` |" >> $GITHUB_STEP_SUMMARY
echo "| Compose Hash | \`${{ steps.hash.outputs.compose_hash }}\` |" >> $GITHUB_STEP_SUMMARY
echo "| On-Chain TX | ${{ steps.deploy.outputs.tx_hash }} |" >> $GITHUB_STEP_SUMMARY
echo "| Deploy Time | ${{ steps.deploy.outputs.deployment_time }} |" >> $GITHUB_STEP_SUMMARY
# Consolidate deployment records after all machines are deployed
record-deployments:
runs-on: ubuntu-latest
needs: [summary, build-enclave, deploy-release]
if: |
startsWith(github.ref, 'refs/tags/v') &&
always() &&
(needs.deploy-release.result == 'success' || needs.deploy-release.result == 'failure')
permissions:
contents: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
ref: feat/toy-example-app
token: ${{ secrets.GITHUB_TOKEN }}
- name: Set variables
id: vars
run: |
OWNER_LC=$(echo "${{ github.repository_owner }}" | tr '[:upper:]' '[:lower:]')
echo "enclave_image=ghcr.io/${OWNER_LC}/toy-example-enclave" >> $GITHUB_OUTPUT
echo "version=${{ needs.summary.outputs.version }}" >> $GITHUB_OUTPUT
echo "timestamp=$(date -u +%Y-%m-%dT%H:%M:%SZ)" >> $GITHUB_OUTPUT
- name: Download deployment artifacts
uses: actions/download-artifact@v4
with:
pattern: deploy-info-*
path: deploy-artifacts
merge-multiple: false
- name: List downloaded artifacts
run: |
echo "Downloaded artifacts:"
find deploy-artifacts -type f -name "*.json" -exec cat {} \;
- name: Update DEPLOYMENTS.md
run: |
# Use absolute paths to avoid directory navigation issues
ROOT_DIR="${GITHUB_WORKSPACE}"
DEPLOYMENTS_FILE="${ROOT_DIR}/toy-example-app/DEPLOYMENTS.md"
ENCLAVE_DIR="${ROOT_DIR}/toy-example-app/enclave"
VERSION="${{ steps.vars.outputs.version }}"
TIMESTAMP="${{ steps.vars.outputs.timestamp }}"
OWNER_LC=$(echo "${{ github.repository_owner }}" | tr '[:upper:]' '[:lower:]')
SHA_SHORT="${{ needs.build-enclave.outputs.sha_short }}"
# Create file if it doesn't exist
if [ ! -f "$DEPLOYMENTS_FILE" ]; then
{
echo "# Deployment History"
echo ""
echo "Auto-generated deployment log. Each entry represents a verified deployment to Phala Cloud with on-chain transparency logging via Base KMS."
echo ""
echo "## Active Deployments"
echo ""
echo "| Timestamp | Version | Machine | Compose Hash | On-Chain TX | Status |"
echo "|-----------|---------|---------|--------------|-------------|--------|"
} > "$DEPLOYMENTS_FILE"
fi
# Calculate compose hash once - same for all clusters since we use --node-id
git -C "${ENCLAVE_DIR}" checkout docker-compose.yml
sed -i "s|image:.*toy-example-enclave.*|image: ghcr.io/${OWNER_LC}/toy-example-enclave:${SHA_SHORT}|g" "${ENCLAVE_DIR}/docker-compose.yml"
HASH=$(sha256sum "${ENCLAVE_DIR}/docker-compose.yml" | cut -d' ' -f1)
# Add entries for each cluster - same hash proves identical code
for CLUSTER in prod5 prod9; do
# Read TX hash from artifact if available
ARTIFACT_FILE="${ROOT_DIR}/deploy-artifacts/deploy-info-${CLUSTER}/${CLUSTER}.json"
if [ -f "$ARTIFACT_FILE" ]; then
TX_HASH=$(jq -r '.tx_hash // "pending"' "$ARTIFACT_FILE")
echo "Found TX hash for ${CLUSTER}: ${TX_HASH}"
else
TX_HASH="pending"
echo "No artifact found for ${CLUSTER}, using pending"
fi
# Format TX as link if it's a valid hash
if [[ "$TX_HASH" =~ ^0x[a-fA-F0-9]{64}$ ]]; then
TX_LINK="[View](https://basescan.org/tx/${TX_HASH})"
else
TX_LINK="pending"
fi
NEW_ENTRY="| ${TIMESTAMP} | ${VERSION} | ${CLUSTER} | \`${HASH}\` | ${TX_LINK} | Active |"
# Insert after the header separator row
sed -i "/^|-----------|/a ${NEW_ENTRY}" "$DEPLOYMENTS_FILE"
done
# Reset docker-compose.yml
git -C "${ENCLAVE_DIR}" checkout docker-compose.yml
echo "Updated DEPLOYMENTS.md:"
cat "$DEPLOYMENTS_FILE"
- name: Commit DEPLOYMENTS.md
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add toy-example-app/DEPLOYMENTS.md
git commit -m "chore: record deployments v${{ steps.vars.outputs.version }}
Version: ${{ steps.vars.outputs.version }}
Machines: prod5, prod9
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>" || echo "No changes to commit"
git push origin feat/toy-example-app
- name: Record consolidation summary
run: |
echo "## Deployment Records Consolidated" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "DEPLOYMENTS.md has been updated with entries for all deployed machines." >> $GITHUB_STEP_SUMMARY