Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 60 additions & 16 deletions .github/workflows/deploy-staging.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
36 changes: 28 additions & 8 deletions apps/web/.env.staging.example
Original file line number Diff line number Diff line change
@@ -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
116 changes: 116 additions & 0 deletions docs/STAGING.md
Original file line number Diff line number Diff line change
@@ -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 <staging-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.
14 changes: 14 additions & 0 deletions docs/deployments.md
Original file line number Diff line number Diff line change
Expand Up @@ -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/<CONTRACT_ID>`

---

## Testnet

| Contract | Contract ID | Deployed At | Deployed By |
Expand Down
Loading