.github/workflows/manual_phala-envs-update-prod.yml #3
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Push sealed environment variables to a specific Phala CVM and start it (PROD). | |
| # | |
| # Prod-flavored sibling of manual_phala-envs-update.yml. Used during CVM | |
| # migrations (e.g. prod6 → prod2) to bootstrap a freshly-provisioned chipotle-prod | |
| # replica with the same live env vars CI would normally push during the | |
| # tag-triggered deploy-prod-* flow. | |
| # | |
| # Differences from the next/staging variant: | |
| # - Stripe LIVE keys (STRIPE_SECRET_KEY / STRIPE_PUBLISHABLE_KEY), not sandbox | |
| # - Defaults: CERTBOT_DOMAIN=api.chipotle.litprotocol.com, GCP_PROJECT_ID=chipotle-prod | |
| # - Encrypts env vars client-side and pushes via `phala envs update --encrypted-env`. | |
| # The CLI's internal pubkey lookup is broken for Safe-owned apps (it passes | |
| # kms_type "base" instead of a real slug like "kms-base-prod2"); see the | |
| # same workaround in deploy-prod-1-propose.yml:307-383. | |
| # | |
| # Env block mirrors deploy-prod-1-propose.yml:341-372 (encryptEnvVars step). | |
| # | |
| # Required secrets: | |
| # PHALA_CLOUD_API_KEY - Phala Cloud API key | |
| # BASE_CHAIN_RPC - Base mainnet RPC URL | |
| # GCP_SERVICE_ACCOUNT_JSON - GCP service account key | |
| # STRIPE_SECRET_KEY - Stripe LIVE secret key | |
| # STRIPE_PUBLISHABLE_KEY - Stripe LIVE publishable key | |
| # CERTBOT_AWS_ACCESS_KEY_ID - Route 53 IAM access key | |
| # CERTBOT_AWS_SECRET_ACCESS_KEY - Route 53 IAM secret key | |
| # CERTBOT_AWS_ROLE_ARN - IAM role ARN for STS assumption | |
| # | |
| # Required vars: | |
| # CERTBOT_AWS_REGION - AWS region for STS endpoint | |
| name: Phala Envs Update + Start (Prod, manual) | |
| permissions: | |
| contents: read | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| cvm_id: | |
| description: "Target CVM (UUID, app_id, instance_id, or name) — e.g. cvm_qwrMBqKl" | |
| required: true | |
| type: string | |
| certbot_domain: | |
| description: "CERTBOT_DOMAIN for dstack-ingress. Leave blank to skip ingress cert acquisition." | |
| required: false | |
| type: string | |
| default: "api.chipotle.litprotocol.com" | |
| gcp_project_id: | |
| description: "GCP_PROJECT_ID for otel-collector" | |
| required: false | |
| type: string | |
| default: "chipotle-prod" | |
| kms_slug: | |
| description: "KMS slug to fetch the encryption pubkey from (e.g. kms-base-prod2)" | |
| required: false | |
| type: string | |
| default: "kms-base-prod2" | |
| start: | |
| description: "Start the CVM after pushing envs" | |
| required: false | |
| type: boolean | |
| default: true | |
| jobs: | |
| envs-update: | |
| runs-on: self-hosted | |
| steps: | |
| - uses: actions/setup-node@v4 | |
| with: | |
| node-version: "20" | |
| - name: Install Phala CLI | |
| run: npm install -g phala | |
| - name: Resolve CVM app_id | |
| id: cvm | |
| env: | |
| PHALA_CLOUD_API_KEY: ${{ secrets.PHALA_CLOUD_API_KEY }} | |
| run: | | |
| set -euo pipefail | |
| APP_ID=$(phala cvms get "${{ inputs.cvm_id }}" --json | jq -r '.app_id') | |
| if [ -z "$APP_ID" ] || [ "$APP_ID" = "null" ]; then | |
| echo "::error::Could not resolve app_id for CVM ${{ inputs.cvm_id }}" | |
| exit 1 | |
| fi | |
| echo "Resolved app_id: $APP_ID" | |
| echo "app_id=$APP_ID" >> "$GITHUB_OUTPUT" | |
| - name: Fetch CVM encryption public key | |
| id: pubkey | |
| env: | |
| PHALA_CLOUD_API_KEY: ${{ secrets.PHALA_CLOUD_API_KEY }} | |
| KMS_SLUG: ${{ inputs.kms_slug }} | |
| APP_ID: ${{ steps.cvm.outputs.app_id }} | |
| run: | | |
| set -euo pipefail | |
| # Workaround: `phala envs update` passes kms_type ("base") to the | |
| # /kms/{kms}/pubkey/{app_id} API, but the API expects a KMS slug | |
| # (e.g. "kms-base-prod2"). Fetch the pubkey directly via the correct | |
| # slug, then encrypt client-side. | |
| echo "Fetching encryption pubkey via /kms/$KMS_SLUG/pubkey/$APP_ID ..." | |
| PUBKEY_RESPONSE=$(phala api "/kms/$KMS_SLUG/pubkey/$APP_ID" --json) | |
| PUBKEY=$(echo "$PUBKEY_RESPONSE" | jq -r '.public_key') | |
| if [ -z "$PUBKEY" ] || [ "$PUBKEY" = "null" ]; then | |
| echo "::error::Failed to extract public_key from KMS response: $PUBKEY_RESPONSE" | |
| exit 1 | |
| fi | |
| echo "Got encryption pubkey: ${PUBKEY:0:16}..." | |
| echo "encrypt_pubkey=$PUBKEY" >> "$GITHUB_OUTPUT" | |
| - name: Encrypt environment variables for CVM | |
| id: encrypt | |
| env: | |
| STRIPE_SECRET_KEY: ${{ secrets.STRIPE_SECRET_KEY }} | |
| STRIPE_PUBLISHABLE_KEY: ${{ secrets.STRIPE_PUBLISHABLE_KEY }} | |
| GCP_SERVICE_ACCOUNT_JSON: ${{ secrets.GCP_SERVICE_ACCOUNT_JSON }} | |
| BASE_CHAIN_RPC: ${{ secrets.BASE_CHAIN_RPC }} | |
| CERTBOT_AWS_ACCESS_KEY_ID: ${{ secrets.CERTBOT_AWS_ACCESS_KEY_ID }} | |
| CERTBOT_AWS_SECRET_ACCESS_KEY: ${{ secrets.CERTBOT_AWS_SECRET_ACCESS_KEY }} | |
| CERTBOT_AWS_ROLE_ARN: ${{ secrets.CERTBOT_AWS_ROLE_ARN }} | |
| CERTBOT_AWS_REGION: ${{ vars.CERTBOT_AWS_REGION }} | |
| GCP_PROJECT_ID: ${{ inputs.gcp_project_id }} | |
| CERTBOT_DOMAIN: ${{ inputs.certbot_domain }} | |
| ENCRYPT_PUBKEY: ${{ steps.pubkey.outputs.encrypt_pubkey }} | |
| run: | | |
| set -euo pipefail | |
| ENCRYPTED_ENV=$(node -e ' | |
| const path = require("path"); | |
| const prefix = require("child_process").execSync("npm prefix -g").toString().trim(); | |
| const { encryptEnvVars } = require( | |
| require.resolve("@phala/cloud", { paths: [path.join(prefix, "lib/node_modules/phala")] }) | |
| ); | |
| const envs = [ | |
| { key: "STRIPE_SECRET_KEY", value: process.env.STRIPE_SECRET_KEY }, | |
| { key: "STRIPE_PUBLISHABLE_KEY", value: process.env.STRIPE_PUBLISHABLE_KEY }, | |
| { key: "GCP_SERVICE_ACCOUNT_JSON", value: process.env.GCP_SERVICE_ACCOUNT_JSON }, | |
| { key: "GCP_PROJECT_ID", value: process.env.GCP_PROJECT_ID }, | |
| { key: "BASE_CHAIN_RPC", value: process.env.BASE_CHAIN_RPC }, | |
| { key: "CERTBOT_DOMAIN", value: process.env.CERTBOT_DOMAIN }, | |
| { key: "CERTBOT_AWS_ACCESS_KEY_ID", value: process.env.CERTBOT_AWS_ACCESS_KEY_ID }, | |
| { key: "CERTBOT_AWS_SECRET_ACCESS_KEY", value: process.env.CERTBOT_AWS_SECRET_ACCESS_KEY }, | |
| { key: "CERTBOT_AWS_ROLE_ARN", value: process.env.CERTBOT_AWS_ROLE_ARN }, | |
| { key: "CERTBOT_AWS_REGION", value: process.env.CERTBOT_AWS_REGION }, | |
| ]; | |
| encryptEnvVars(envs, process.env.ENCRYPT_PUBKEY).then(hex => process.stdout.write(hex)); | |
| ') | |
| if [ -z "$ENCRYPTED_ENV" ]; then | |
| echo "::error::encryptEnvVars returned empty output" | |
| exit 1 | |
| fi | |
| echo "Encrypted env length: ${#ENCRYPTED_ENV} chars" | |
| echo "encrypted_env=$ENCRYPTED_ENV" >> "$GITHUB_OUTPUT" | |
| - name: Push sealed envs to CVM | |
| env: | |
| PHALA_CLOUD_API_KEY: ${{ secrets.PHALA_CLOUD_API_KEY }} | |
| ENCRYPTED_ENV: ${{ steps.encrypt.outputs.encrypted_env }} | |
| run: | | |
| phala envs update "${{ inputs.cvm_id }}" --encrypted-env "$ENCRYPTED_ENV" | |
| - name: Start CVM | |
| if: inputs.start | |
| env: | |
| PHALA_CLOUD_API_KEY: ${{ secrets.PHALA_CLOUD_API_KEY }} | |
| run: | | |
| phala cvms start "${{ inputs.cvm_id }}" | |
| - name: Show CVM status | |
| env: | |
| PHALA_CLOUD_API_KEY: ${{ secrets.PHALA_CLOUD_API_KEY }} | |
| run: | | |
| phala cvms get "${{ inputs.cvm_id }}" |