From 8169df0f18540653ad0df9e852651429be0ffdc3 Mon Sep 17 00:00:00 2001 From: ladinoraa Date: Thu, 4 Jun 2026 02:05:00 +0000 Subject: [PATCH] feat: staging environment deployment pipeline (#295) - Rewrite deploy-staging.yml: - Triggered on push to develop - CI gate (pnpm test + Redis service) must pass before deploy - Deploys to separate Vercel staging project via VERCEL_PROJECT_ID_STAGING - Health check (5 retries) against /api/health after deploy - Staging Stellar network hardcoded to testnet; production uses mainnet - Expand .env.staging.example with all required variables including REDIS_URL, CORS_ALLOWED_ORIGINS, separate Supabase project values - Add docs/STAGING.md: full setup guide (Vercel project, Supabase project, testnet contract deploy, GitHub secrets, local simulation, smoke test) - Update docs/deployments.md: add staging contract ID table Closes #295 --- .github/workflows/deploy-staging.yml | 76 ++++++++++++++---- apps/web/.env.staging.example | 36 +++++++-- docs/STAGING.md | 116 +++++++++++++++++++++++++++ docs/deployments.md | 14 ++++ 4 files changed, 218 insertions(+), 24 deletions(-) create mode 100644 docs/STAGING.md diff --git a/.github/workflows/deploy-staging.yml b/.github/workflows/deploy-staging.yml index 7c7363f..113b5be 100644 --- a/.github/workflows/deploy-staging.yml +++ b/.github/workflows/deploy-staging.yml @@ -4,10 +4,43 @@ on: push: branches: [develop] +concurrency: + group: deploy-staging + cancel-in-progress: true + jobs: + # ── 1. Run CI gate before deploying ───────────────────────────────────────── + test: + name: Test before staging deploy + runs-on: ubuntu-latest + services: + redis: + image: redis:7-alpine + ports: + - 6379:6379 + options: >- + --health-cmd "redis-cli ping" + --health-interval 10s + --health-timeout 5s + --health-retries 3 + steps: + - uses: actions/checkout@v4 + - uses: pnpm/action-setup@v4 + - uses: actions/setup-node@v4 + with: + node-version: 22 + cache: pnpm + - run: pnpm install --frozen-lockfile + - run: pnpm test + working-directory: apps/web + env: + REDIS_URL: redis://localhost:6379 + + # ── 2. Deploy to Vercel staging ────────────────────────────────────────────── deploy-staging: - name: Deploy to Staging (Vercel) + name: Deploy to Vercel (staging) runs-on: ubuntu-latest + needs: test environment: staging steps: - uses: actions/checkout@v4 @@ -21,22 +54,33 @@ jobs: - run: pnpm install --frozen-lockfile - - name: Deploy to Vercel (staging) + - name: Deploy to Vercel (staging project) + id: deploy + env: + VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }} + VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} + VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID_STAGING }} run: | - pnpm dlx vercel deploy \ - --token ${{ secrets.VERCEL_TOKEN }} \ - --scope ${{ secrets.VERCEL_ORG_ID }} \ + url=$(pnpm dlx vercel deploy \ + --token "$VERCEL_TOKEN" \ + --scope "$VERCEL_ORG_ID" \ --yes \ - --env NEXT_PUBLIC_SUPABASE_URL=${{ secrets.STAGING_SUPABASE_URL }} \ - --env NEXT_PUBLIC_SUPABASE_ANON_KEY=${{ secrets.STAGING_SUPABASE_ANON_KEY }} \ - --env NEXT_PUBLIC_STELLAR_NETWORK=testnet \ - --env NEXT_PUBLIC_ENERGY_TOKEN_ID=${{ secrets.STAGING_ENERGY_TOKEN_ID }} \ - --env NEXT_PUBLIC_AUDIT_REGISTRY_ID=${{ secrets.STAGING_AUDIT_REGISTRY_ID }} \ - --env NEXT_PUBLIC_COMMUNITY_GOVERNANCE_ID=${{ secrets.STAGING_COMMUNITY_GOVERNANCE_ID }} \ - --env MINTER_SECRET_KEY=${{ secrets.STAGING_MINTER_SECRET_KEY }} \ - apps/web - id: deploy + apps/web 2>&1 | tail -1) + echo "url=$url" >> "$GITHUB_OUTPUT" - - name: Comment staging URL on commit + - name: Health check staging run: | - echo "Staging deployed: ${{ steps.deploy.outputs.url }}" >> $GITHUB_STEP_SUMMARY + URL="${{ steps.deploy.outputs.url }}/api/health" + for i in $(seq 1 5); do + STATUS=$(curl -s -o /dev/null -w "%{http_code}" --max-time 10 "$URL") + if [ "$STATUS" = "200" ]; then + echo "Health check $i passed ✓" + exit 0 + fi + echo "Health check $i: HTTP $STATUS — retrying in 15 s" + sleep 15 + done + echo "Staging health check failed" && exit 1 + + - name: Post staging URL to summary + run: echo "### 🚀 Staging deployed" >> $GITHUB_STEP_SUMMARY && echo "${{ steps.deploy.outputs.url }}" >> $GITHUB_STEP_SUMMARY diff --git a/apps/web/.env.staging.example b/apps/web/.env.staging.example index 0ddfc6f..4eb359f 100644 --- a/apps/web/.env.staging.example +++ b/apps/web/.env.staging.example @@ -1,22 +1,42 @@ -# Staging environment variables -# Copy to .env.staging.local for local staging simulation. -# These are set as GitHub Actions secrets for the `staging` environment. +# ───────────────────────────────────────────────────────────────────────────── +# SolarProof — staging environment variables +# +# These variables are set as GitHub Actions secrets under the `staging` +# environment (Settings → Environments → staging → Secrets). +# They are also configured in the separate Vercel staging project's +# Environment Variables panel. +# +# Copy to apps/web/.env.staging.local to simulate staging locally. +# Never commit .env.staging.local. +# ───────────────────────────────────────────────────────────────────────────── -# Supabase — dedicated staging project (separate from production) +# ── Supabase — dedicated staging project (separate from production) ─────────── NEXT_PUBLIC_SUPABASE_URL=https://your-staging-project.supabase.co NEXT_PUBLIC_SUPABASE_ANON_KEY=your-staging-anon-key SUPABASE_SERVICE_ROLE_KEY=your-staging-service-role-key -# Stellar testnet contract IDs (staging deployments) +# ── Stellar — testnet only for staging ─────────────────────────────────────── NEXT_PUBLIC_STELLAR_NETWORK=testnet +NEXT_PUBLIC_STELLAR_RPC_URL=https://soroban-testnet.stellar.org + +# Staging contract IDs (deployed on Stellar Testnet) NEXT_PUBLIC_ENERGY_TOKEN_ID= NEXT_PUBLIC_AUDIT_REGISTRY_ID= NEXT_PUBLIC_COMMUNITY_GOVERNANCE_ID= -# Minter keypair — staging only, never share production key +# Minter keypair — staging Stellar testnet account only +# Never reuse a production key here. MINTER_SECRET_KEY= -# Vercel (set in GitHub Actions secrets) +# ── Redis ───────────────────────────────────────────────────────────────────── +REDIS_URL=redis://localhost:6379 +UPSTASH_REDIS_REST_URL=https://your-staging-redis.upstash.io +UPSTASH_REDIS_REST_TOKEN=your-staging-token + +# ── CORS ────────────────────────────────────────────────────────────────────── +CORS_ALLOWED_ORIGINS=https://solarproof-staging.vercel.app + +# ── Vercel (set in GitHub Actions secrets, not in Vercel itself) ────────────── # VERCEL_TOKEN= # VERCEL_ORG_ID= -# VERCEL_PROJECT_ID= +# VERCEL_PROJECT_ID_STAGING= ← Note: separate project from production diff --git a/docs/STAGING.md b/docs/STAGING.md new file mode 100644 index 0000000..1700792 --- /dev/null +++ b/docs/STAGING.md @@ -0,0 +1,116 @@ +# Staging Environment + +SolarProof maintains a staging environment that mirrors production to catch regressions before they reach users. + +## Architecture + +| Concern | Staging | Production | +|---|---|---| +| Deployment platform | Vercel (separate project) | Vercel | +| Stellar network | **Testnet** | Mainnet | +| Database | Separate Supabase project | Production Supabase project | +| Minter key | Testnet keypair (disposable) | AWS Secrets Manager ARN | +| URL | `https://solarproof-staging.vercel.app` | `https://solarproof.vercel.app` | +| Branch | `develop` | `main` | + +## Deployment pipeline + +``` +Push to develop + │ + ▼ +[test] pnpm test (with Redis service) + │ passes + ▼ +[deploy-staging] vercel deploy → staging Vercel project + │ + ▼ +[health check] GET /api/health → must return 200 (5 retries) + │ passes + ▼ +Staging URL posted to GitHub Actions summary +``` + +Production uses a separate blue-green pipeline (`.github/workflows/blue-green-deploy.yml`) triggered on pushes to `main`. + +## Setting up the staging environment + +### 1. Create a Vercel staging project + +```bash +# In the Vercel dashboard, create a new project linked to this repo. +# Name it "solarproof-staging" (separate from the production project). +# Note the project ID — you will need VERCEL_PROJECT_ID_STAGING. +``` + +### 2. Create a Supabase staging project + +1. Create a new Supabase project at https://supabase.com/dashboard. +2. Run migrations: `supabase db push --db-url ` or apply them manually. +3. Note the URL, anon key, and service-role key. + +### 3. Deploy staging contracts to Stellar Testnet + +```bash +cd apps/contracts +stellar contract build + +# Fund a staging deployer account +stellar keys generate staging-deployer --network testnet +stellar keys fund staging-deployer --network testnet + +# Deploy +TOKEN_ID=$(stellar contract deploy \ + --wasm target/wasm32-unknown-unknown/release/energy_token.wasm \ + --source staging-deployer --network testnet) + +REGISTRY_ID=$(stellar contract deploy \ + --wasm target/wasm32-unknown-unknown/release/audit_registry.wasm \ + --source staging-deployer --network testnet) + +GOV_ID=$(stellar contract deploy \ + --wasm target/wasm32-unknown-unknown/release/community_governance.wasm \ + --source staging-deployer --network testnet) +``` + +Record the IDs in `docs/deployments.md`. + +### 4. Configure GitHub Actions secrets + +Under **Settings → Environments → staging**, add: + +| Secret | Value | +|---|---| +| `VERCEL_TOKEN` | Your Vercel personal access token | +| `VERCEL_ORG_ID` | Your Vercel team/org ID | +| `VERCEL_PROJECT_ID_STAGING` | Staging Vercel project ID | +| `STAGING_SUPABASE_URL` | Staging Supabase URL | +| `STAGING_SUPABASE_ANON_KEY` | Staging anon key | +| `STAGING_SUPABASE_SERVICE_ROLE_KEY` | Staging service-role key | +| `STAGING_MINTER_SECRET_KEY` | Testnet Stellar secret key | +| `STAGING_ENERGY_TOKEN_ID` | Testnet contract ID | +| `STAGING_AUDIT_REGISTRY_ID` | Testnet contract ID | +| `STAGING_COMMUNITY_GOVERNANCE_ID` | Testnet contract ID | + +### 5. Configure Vercel staging project environment variables + +In the Vercel staging project's **Settings → Environment Variables**, set the same values. Vercel stores them encrypted and injects them at build/runtime. + +## Local staging simulation + +```bash +cp apps/web/.env.staging.example apps/web/.env.staging.local +# Fill in your staging values +NODE_ENV=production pnpm --filter web start +``` + +## Smoke testing staging + +```bash +# After a staging deploy, run the smoke test script against the staging URL +SOLARPROOF_URL=https://solarproof-staging.vercel.app node scripts/smoke-test.mjs +``` + +## Promoting to production + +Merge `develop` → `main`. The blue-green deploy workflow (`deploy-contracts.yml`, `blue-green-deploy.yml`) takes over automatically. diff --git a/docs/deployments.md b/docs/deployments.md index 5699022..3cd513a 100644 --- a/docs/deployments.md +++ b/docs/deployments.md @@ -4,6 +4,20 @@ Deployed contract addresses for each environment. Update this file after every d --- +## Staging (Testnet) + +Staging uses Stellar **Testnet** and a separate Supabase project. See [docs/STAGING.md](./STAGING.md) for setup. + +| Contract | Contract ID | Deployed At | Deployed By | +|---|---|---|---| +| `energy_token` | _(set after first staging deploy)_ | — | — | +| `audit_registry` | _(set after first staging deploy)_ | — | — | +| `community_governance` | _(set after first staging deploy)_ | — | — | + +Explorer: `https://stellar.expert/explorer/testnet/contract/` + +--- + ## Testnet | Contract | Contract ID | Deployed At | Deployed By |