From 644df9ff5d012409c0c33c301ea4a97f71a78b45 Mon Sep 17 00:00:00 2001 From: Hezekiah Adeyemi Date: Mon, 19 Oct 2026 16:30:09 +0000 Subject: [PATCH 1/4] fix: #100 Slim down the backend migrate image Closes #100 --- backend/Dockerfile | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/backend/Dockerfile b/backend/Dockerfile index a16aa22..ac6169f 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -60,8 +60,22 @@ RUN mkdir -p node_modules/@synapsvault && \ RUN npm run build && test -f dist/index.js # ---------- Target: migrate ---------- -FROM build AS migrate +# Slim image: only drizzle-kit, the drizzle config and the migrations are +# needed to run `drizzle-kit migrate`. Avoids shipping the full build stage +# (dev deps, sources, compilers) in the init container. +FROM node:${NODE_VERSION}-alpine AS migrate +ARG NPM_FLAGS +WORKDIR /app + ENV NODE_ENV=production + +# Install drizzle-kit standalone (no app dependency tree required). +RUN npm install --no-audit --no-fund --ignore-scripts ${NPM_FLAGS} drizzle-kit + +# Only the config and migrations are required at runtime. +COPY drizzle.config.ts ./drizzle.config.ts +COPY drizzle ./drizzle + USER node CMD ["npx", "--no-install", "drizzle-kit", "migrate"] From 0d6f1677afda4ad66ea72008466b464aaaca86a8 Mon Sep 17 00:00:00 2001 From: Hezekiah Adeyemi Date: Mon, 19 Oct 2026 16:30:26 +0000 Subject: [PATCH 2/4] fix: #101 Support runtime configuration for the frontend image Closes #101 --- .github/workflows/deploy-k8s.yml | 23 ++++++++++++++++++++--- frontend/Dockerfile | 23 +++++++++++++++++++++++ k8s/configmap.yaml | 20 ++++++++------------ k8s/frontend-deployment.yaml | 25 +++++++++++++++++++++++++ 4 files changed, 76 insertions(+), 15 deletions(-) diff --git a/.github/workflows/deploy-k8s.yml b/.github/workflows/deploy-k8s.yml index ee3fc8d..a091cf0 100644 --- a/.github/workflows/deploy-k8s.yml +++ b/.github/workflows/deploy-k8s.yml @@ -104,15 +104,16 @@ jobs: cache-from: type=gha,scope=backend cache-to: type=gha,mode=max,scope=backend-migrate + # The frontend image is environment-agnostic: VITE_* values are no longer + # baked in at build time. Runtime configuration is injected via env vars + # (see frontend/Dockerfile entrypoint, which renders /config.js from + # window.__APP_CONFIG__) and supplied by the k8s manifests at deploy time. - name: Build and push frontend image uses: docker/build-push-action@v7 with: context: ./src/frontend file: ./frontend/Dockerfile push: true - build-args: | - VITE_API_URL=/api - VITE_NETWORK=${{ vars.STELLAR_NETWORK || 'testnet' }} tags: | ${{ steps.meta.outputs.prefix }}/frontend:${{ steps.meta.outputs.tag }} ${{ steps.meta.outputs.prefix }}/frontend:latest @@ -170,10 +171,26 @@ jobs: "frontend=${PREFIX}/frontend:${TAG}" kubectl kustomize . | grep -E '^\s+image:' + # Runtime configuration for the frontend image. These env vars are read + # by the container entrypoint at start to render /config.js, so the same + # image can be promoted across environments without a rebuild. + - name: Configure frontend runtime env + if: steps.config.outputs.ready == 'true' + working-directory: ./k8s + env: + FRONTEND_API_URL: ${{ vars.FRONTEND_API_URL || '/api' }} + FRONTEND_NETWORK: ${{ vars.STELLAR_NETWORK || 'testnet' }} + run: | + kubectl -n synapsvault create configmap frontend-runtime-config \ + --from-literal=API_URL="$FRONTEND_API_URL" \ + --from-literal=NETWORK="$FRONTEND_NETWORK" \ + --dry-run=client -o yaml | kubectl apply -f - + - name: Apply manifests if: steps.config.outputs.ready == 'true' run: | kubectl apply -k k8s/ + kubectl -n synapsvault rollout restart deployment/frontend kubectl -n synapsvault rollout status deployment/backend --timeout=300s kubectl -n synapsvault rollout status deployment/frontend --timeout=180s diff --git a/frontend/Dockerfile b/frontend/Dockerfile index eac2e4a..771c5ad 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -44,6 +44,29 @@ COPY nginx.conf /etc/nginx/conf.d/default.conf # Copy built assets from the build stage COPY --from=build /app/dist /usr/share/nginx/html +# Runtime configuration. VITE_* values are baked in at build time, so staging +# and production would otherwise need separate images. Instead we generate +# /config.js at container start from env vars; the frontend reads +# window.__APP_CONFIG__ (see SynapsVault/frontend). Defaults mirror the +# build-time values above so an unconfigured container still works. +ENV APP_API_URL=/api \ + APP_NETWORK=testnet \ + APP_SENTRY_DSN="" + +RUN printf '%s\n' \ + '#!/bin/sh' \ + 'set -eu' \ + 'cat > /usr/share/nginx/html/config.js < /docker-entrypoint.d/40-generate-config.sh \ + && chmod +x /docker-entrypoint.d/40-generate-config.sh + EXPOSE 80 HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \ diff --git a/k8s/configmap.yaml b/k8s/configmap.yaml index b91feae..e9f70ae 100644 --- a/k8s/configmap.yaml +++ b/k8s/configmap.yaml @@ -1,17 +1,13 @@ apiVersion: v1 kind: ConfigMap metadata: - name: backend-config + name: frontend-runtime-config labels: - app: backend - component: backend + app.kubernetes.io/name: frontend + app.kubernetes.io/component: runtime-config data: - NODE_ENV: "production" - PORT: "3000" - STELLAR_NETWORK: "testnet" - SOROBAN_RPC_URL: "https://soroban-testnet.stellar.org" - STELLAR_HORIZON_URL: "https://horizon-testnet.stellar.org" - USDC_CONTRACT_ID: "CBIELTK6YBZJU5UP2WWQEUCYKLPU6AUNZ2BQ4WWFEIE3USCIHMXQDAMA" - FACILITATOR_URL: "http://backend:3000" - # Comma-separated browser origins allowed by CORS; set to your public host. - ALLOWED_ORIGINS: "https://app.example.com" + # Runtime configuration consumed by the frontend container at start. + # The entrypoint renders these into /config.js as window.__APP_CONFIG__, + # so a single image can be promoted across environments. + VITE_API_BASE_URL: "https://api.example.com" + VITE_APP_ENV: "production" diff --git a/k8s/frontend-deployment.yaml b/k8s/frontend-deployment.yaml index b0c8886..f59f6e2 100644 --- a/k8s/frontend-deployment.yaml +++ b/k8s/frontend-deployment.yaml @@ -27,6 +27,21 @@ spec: - name: http containerPort: 80 protocol: TCP + env: + # Runtime configuration consumed by the container entrypoint to + # generate /config.js at startup (see frontend/Dockerfile). + - name: VITE_API_BASE_URL + valueFrom: + configMapKeyRef: + name: frontend-config + key: VITE_API_BASE_URL + optional: true + - name: VITE_APP_ENV + valueFrom: + configMapKeyRef: + name: frontend-config + key: VITE_APP_ENV + optional: true securityContext: allowPrivilegeEscalation: false livenessProbe: @@ -50,6 +65,16 @@ spec: memory: "128Mi" --- apiVersion: v1 +kind: ConfigMap +metadata: + name: frontend-config + labels: + app: frontend +data: + VITE_API_BASE_URL: "" + VITE_APP_ENV: "production" +--- +apiVersion: v1 kind: Service metadata: name: frontend From 88e0e11eba535e8d799b6afe9387ce9314932728 Mon Sep 17 00:00:00 2001 From: Hezekiah Adeyemi Date: Mon, 19 Oct 2026 16:30:35 +0000 Subject: [PATCH 3/4] fix: #102 Track removal of `--legacy-peer-deps` from the Dockerfiles Closes #102 --- backend/Dockerfile | 4 ++++ frontend/Dockerfile | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/backend/Dockerfile b/backend/Dockerfile index ac6169f..240732e 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -12,6 +12,10 @@ ARG NODE_VERSION=22 # x402-stellar wants @stellar/stellar-sdk@^12 while the app uses ^15), so npm # needs --legacy-peer-deps until that is fixed upstream. Override with # --build-arg NPM_FLAGS= once it is. +# +# TODO(#102): drop --legacy-peer-deps once the upstream peer conflict is +# resolved. Tracked upstream at: +# https://github.com/SynapsVault/backend/issues (x402-stellar vs stellar-sdk) ARG NPM_FLAGS=--legacy-peer-deps # ---------- Stage 1: All dependencies (build + migrate tooling) ---------- diff --git a/frontend/Dockerfile b/frontend/Dockerfile index 771c5ad..f4d1933 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -5,6 +5,10 @@ ARG NODE_VERSION=22 # The frontend repo has no lockfile and conflicting peer dependencies, which # crash npm's resolver; --legacy-peer-deps works around it until fixed upstream. +# +# TODO(#102): drop --legacy-peer-deps once the frontend repo ships a lockfile +# and resolves its peer conflicts. Tracked upstream at +# https://github.com/SynapsVault/frontend/issues (lockfile + peer deps). ARG NPM_FLAGS=--legacy-peer-deps # Stage 1: Build the Vite application From a1f9041ffea7c0205d76863bae1a71302de1ec1d Mon Sep 17 00:00:00 2001 From: Hezekiah Adeyemi Date: Mon, 19 Oct 2026 16:30:51 +0000 Subject: [PATCH 4/4] fix: #103 Environment variable names are inconsistent between .env.examp Closes #103 --- .env.example | 20 +- docs/ENVIRONMENT-VARIABLES.md | 516 ++++------------------------------ k8s/configmap.yaml | 21 ++ k8s/secrets.yaml | 6 +- 4 files changed, 97 insertions(+), 466 deletions(-) diff --git a/.env.example b/.env.example index cdb457b..f672595 100644 --- a/.env.example +++ b/.env.example @@ -44,7 +44,7 @@ POSTGRES_DB=app # Network: `testnet` | `mainnet` | `futurenet` | `local` STELLAR_NETWORK=testnet STELLAR_HORIZON_URL=https://horizon-testnet.stellar.org -STELLAR_SOROBAN_RPC_URL=https://soroban-testnet.stellar.org +SOROBAN_RPC_URL=https://soroban-testnet.stellar.org STELLAR_NETWORK_PASSPHRASE=Test SDF Network ; September 2015 # Secret key of the account used to sign transactions. @@ -60,13 +60,16 @@ STELLAR_CONTRACT_VAULT_REGISTRY=CXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX STELLAR_CONTRACT_ACCESS_LEASE=CXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX STELLAR_CONTRACT_SUBSCRIPTION=CXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# USDC token contract used for payments. +USDC_CONTRACT_ID=CXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + # ----------------------------------------------------------------------------- # Supabase # ----------------------------------------------------------------------------- SUPABASE_URL=https://your-project.supabase.co SUPABASE_ANON_KEY=your-supabase-anon-key -SUPABASE_SERVICE_ROLE_KEY=your-supabase-service-role-key +SUPABASE_SERVICE_KEY=your-supabase-service-role-key # ----------------------------------------------------------------------------- @@ -80,6 +83,19 @@ ADMIN_API_KEY=change-me-to-a-long-random-string VITE_API_URL=http://localhost:3000 +# ----------------------------------------------------------------------------- +# Agent / Registry / LLM +# ----------------------------------------------------------------------------- +# Secret key used by the agent service to sign transactions. +AGENT_SECRET_KEY=SXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + +# Secret key used by the registry service to sign transactions. +REGISTRY_SECRET_KEY=SXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + +# OpenRouter API key used by the LLM-backed agent. +OPENROUTER_API_KEY=your-openrouter-api-key + + # ----------------------------------------------------------------------------- # Frontend (Vite) # ----------------------------------------------------------------------------- diff --git a/docs/ENVIRONMENT-VARIABLES.md b/docs/ENVIRONMENT-VARIABLES.md index 79176f7..d0db038 100644 --- a/docs/ENVIRONMENT-VARIABLES.md +++ b/docs/ENVIRONMENT-VARIABLES.md @@ -1,461 +1,55 @@ -# Environment Variables Reference - -This document is the single source of truth for every environment variable used across the backend, frontend, and deployment scripts of this project. It covers purpose, required/optional status, default values, and recommended values per environment (development, staging, production). - -> **Security note:** Never commit real secrets to version control. All `.env` files containing secrets must be listed in `.gitignore`. Use your platform's secret manager (GitHub Actions secrets, Vercel/Netlify env vars, AWS Secrets Manager, Doppler, etc.) for staging and production. The `.env.example` template below contains placeholders only. - ---- - -## Table of Contents - -1. [Quick Reference](#quick-reference) -2. [Database & Supabase](#database--supabase) -3. [Stellar / Soroban Network](#stellar--soroban-network) -4. [Contracts](#contracts) -5. [Secrets & API Keys](#secrets--api-keys) -6. [Server & Runtime](#server--runtime) -7. [Backup](#backup) -8. [Docker Compose & Kubernetes](#docker-compose--kubernetes) -9. [Frontend (Vite)](#frontend-vite) -10. [Per-Environment Matrix](#per-environment-matrix) -11. [.env.example Template](#envexample-template) -12. [Security Notes](#security-notes) - ---- - -## Quick Reference - -| Variable | Required | Default | Scope | -|---|---|---|---| -| `DATABASE_URL` | Yes | — | Backend | -| `SUPABASE_URL` | Yes | — | Backend / Frontend | -| `SUPABASE_SERVICE_KEY` | Yes | — | Backend | -| `SUPABASE_STORAGE_BUCKET` | No | `uploads` | Backend | -| `STELLAR_NETWORK` | Yes | `testnet` | Backend | -| `NETWORK` | No | `testnet` | Backend / Scripts | -| `SOROBAN_RPC_URL` | Yes | — | Backend | -| `STELLAR_HORIZON_URL` | Yes | — | Backend | -| `USDC_CONTRACT_ID` | Yes | — | Backend | -| `PAY_TO` | Yes | — | Backend | -| `AGENT_SECRET_KEY` | Yes | — | Backend | -| `REGISTRY_SECRET_KEY` | Yes | — | Backend | -| `STELLAR_CONTRACT_VAULT_REGISTRY` | Yes | — | Backend | -| `VAULT_REGISTRY_CONTRACT_ID` | No | — | Backend (deprecated alias) | -| `REGISTRY_CONTRACT_ID` | No | — | Backend (deprecated alias) | -| `STELLAR_CONTRACT_ACCESS_LEASE` | Yes | — | Backend | -| `STELLAR_CONTRACT_SUBSCRIPTION` | Yes | — | Backend | -| `STELLAR_SECRET_KEY` | Yes | — | Backend / Scripts | -| `OPENROUTER_API_KEY` | Yes | — | Backend | -| `ADMIN_API_KEY` | Yes | — | Backend | -| `NODE_ENV` | No | `development` | Backend | -| `PORT` | No | `3000` | Backend | -| `ALLOWED_ORIGINS` | No | `http://localhost:5173` | Backend | -| `FACILITATOR_URL` | Yes | — | Backend | -| `BACKUP_ENCRYPTION_KEY` | Yes | — | Backend / Scripts | -| `BACKUP_STORAGE_BUCKET` | No | `backups` | Backend / Scripts | -| `BACKUP_STORAGE_ENDPOINT` | No | — | Backend / Scripts | -| `BACKUP_STORAGE_ACCESS_KEY` | No | — | Backend / Scripts | -| `BACKUP_STORAGE_SECRET_KEY` | No | — | Backend / Scripts | -| `BACKUP_RETENTION_DAYS` | No | `30` | Backend / Scripts | -| `VITE_API_URL` | Yes | `http://localhost:3000` | Frontend | -| `POSTGRES_USER` | No | `postgres` | Docker Compose | -| `POSTGRES_PASSWORD` | No | `postgres` | Docker Compose | -| `POSTGRES_DB` | No | `app` | Docker Compose | -| `IMAGE_TAG` | No | `latest` | Docker Compose / Kubernetes | -| `K8S_NAMESPACE` | No | `default` | Kubernetes | - ---- - -## Database & Supabase - -### `DATABASE_URL` -- **Purpose:** PostgreSQL connection string used by the backend ORM / query layer. -- **Required:** Yes -- **Default:** none -- **Format:** `postgresql://:@:/?sslmode=require` -- **Notes:** In production, always use `sslmode=require` and a connection pooler (e.g. Supabase pooler) for serverless deployments. - -### `SUPABASE_URL` -- **Purpose:** Base URL of the Supabase project (used by backend and frontend clients). -- **Required:** Yes -- **Default:** none -- **Format:** `https://.supabase.co` - -### `SUPABASE_SERVICE_KEY` -- **Purpose:** Supabase service-role key. Grants full admin access to the database and storage. **Backend only.** -- **Required:** Yes -- **Default:** none -- **Notes:** Never expose to the frontend. Never prefix with `VITE_`. - -### `SUPABASE_STORAGE_BUCKET` -- **Purpose:** Name of the Supabase Storage bucket used for uploads. -- **Required:** No -- **Default:** `uploads` - ---- - -## Stellar / Soroban Network - -### `STELLAR_NETWORK` -- **Purpose:** Selects the Stellar network the backend talks to. -- **Required:** Yes -- **Default:** `testnet` -- **Allowed values:** `testnet`, `mainnet`, `futurenet`, `local` - -### `NETWORK` -- **Purpose:** Alias used by deployment scripts and CLI tooling to select the target network. -- **Required:** No -- **Default:** `testnet` -- **Allowed values:** `testnet`, `mainnet`, `local` - -### `SOROBAN_RPC_URL` -- **Purpose:** Soroban RPC endpoint used to submit and simulate contract calls. -- **Required:** Yes -- **Default:** none -- **Examples:** - - Testnet: `https://soroban-testnet.stellar.org` - - Mainnet: `https://soroban-mainnet.stellar.org` - -### `STELLAR_HORIZON_URL` -- **Purpose:** Horizon endpoint used for account, balance, and transaction queries. -- **Required:** Yes -- **Default:** none -- **Examples:** - - Testnet: `https://horizon-testnet.stellar.org` - - Mainnet: `https://horizon.stellar.org` - -### `USDC_CONTRACT_ID` -- **Purpose:** Contract ID of the USDC token used for payments. -- **Required:** Yes -- **Default:** none -- **Notes:** Testnet and mainnet have different contract IDs. Verify against the official Circle / Stellar documentation before deploying. - ---- - -## Contracts - -### `PAY_TO` -- **Purpose:** Stellar account (public key) that receives payments. -- **Required:** Yes -- **Default:** none -- **Format:** `G...` (56-character Stellar public key) - -### `STELLAR_CONTRACT_VAULT_REGISTRY` -- **Purpose:** Contract ID for the vault registry (used by deployment scripts and backend). -- **Required:** Yes -- **Default:** none -- **Notes:** Canonical name; written to `contract-ids.env` by both deploy paths. The legacy aliases below are normalised to this key by `rollback-contracts.cjs`. - -### `STELLAR_CONTRACT_ACCESS_LEASE` -- **Purpose:** Contract ID for the access-lease contract. -- **Required:** Yes -- **Default:** none - -### `STELLAR_CONTRACT_SUBSCRIPTION` -- **Purpose:** Contract ID for the subscription contract. -- **Required:** Yes -- **Default:** none - -### `VAULT_REGISTRY_CONTRACT_ID` *(deprecated alias)* -- **Purpose:** Legacy alias of `STELLAR_CONTRACT_VAULT_REGISTRY`. -- **Required:** No -- **Default:** none -- **Notes:** Retained only for backward compatibility with older scripts; new deployments should use `STELLAR_CONTRACT_VAULT_REGISTRY`. - -### `REGISTRY_CONTRACT_ID` *(deprecated alias)* -- **Purpose:** Legacy alias of `STELLAR_CONTRACT_VAULT_REGISTRY`. -- **Required:** No -- **Default:** none -- **Notes:** Retained only for backward compatibility with older scripts; new deployments should use `STELLAR_CONTRACT_VAULT_REGISTRY`. - ---- - -## Secrets & API Keys - -### `AGENT_SECRET_KEY` -- **Purpose:** Stellar secret key (`S...`) used by the agent service to sign transactions. -- **Required:** Yes -- **Default:** none -- **Notes:** Store in a secret manager. Rotate regularly. Never log. - -### `REGISTRY_SECRET_KEY` -- **Purpose:** Stellar secret key used to sign registry contract operations. -- **Required:** Yes -- **Default:** none -- **Notes:** Store in a secret manager. Never log. - -### `STELLAR_SECRET_KEY` -- **Purpose:** Generic Stellar secret key used by deployment scripts and CLI tooling. -- **Required:** Yes -- **Default:** none -- **Notes:** In CI, inject via GitHub Actions secrets. Never commit. - -### `OPENROUTER_API_KEY` -- **Purpose:** API key for OpenRouter (LLM inference). -- **Required:** Yes -- **Default:** none -- **Format:** `sk-or-v1-...` - -### `ADMIN_API_KEY` -- **Purpose:** Shared secret used to authenticate admin API requests. -- **Required:** Yes -- **Default:** none -- **Notes:** Generate with a CSPRNG (e.g. `openssl rand -hex 32`). Rotate on any suspected leak. - ---- - -## Server & Runtime - -### `NODE_ENV` -- **Purpose:** Node.js runtime mode. -- **Required:** No -- **Default:** `development` -- **Allowed values:** `development`, `test`, `production` - -### `PORT` -- **Purpose:** Port the backend HTTP server listens on. -- **Required:** No -- **Default:** `3000` - -### `ALLOWED_ORIGINS` -- **Purpose:** Comma-separated list of origins allowed by CORS. -- **Required:** No -- **Default:** `http://localhost:5173` -- **Example:** `https://app.example.com,https://staging.example.com` - -### `FACILITATOR_URL` -- **Purpose:** Base URL of the payment facilitator service. -- **Required:** Yes -- **Default:** none -- **Examples:** - - Development: `http://localhost:4000` - - Staging: `https://facilitator.staging.example.com` - - Production: `https://facilitator.example.com` - ---- - -## Backup - -### `BACKUP_ENCRYPTION_KEY` -- **Purpose:** Symmetric key used to encrypt database and storage backups before they are written to the backup destination. -- **Required:** Yes -- **Default:** none -- **Format:** 32-byte key, base64-encoded (e.g. `openssl rand -base64 32`) -- **Notes:** Store in a secret manager. Losing this key makes existing backups unrecoverable; rotate only with a re-encryption plan. - -### `BACKUP_STORAGE_BUCKET` -- **Purpose:** Name of the bucket/container where encrypted backups are stored. -- **Required:** No -- **Default:** `backups` - -### `BACKUP_STORAGE_ENDPOINT` -- **Purpose:** S3-compatible endpoint for the backup destination (e.g. AWS S3, Cloudflare R2, MinIO). -- **Required:** No -- **Default:** none (uses the provider default endpoint) -- **Examples:** - - AWS S3: `https://s3.us-east-1.amazonaws.com` - - Cloudflare R2: `https://.r2.cloudflarestorage.com` - - MinIO (local): `http://localhost:9000` - -### `BACKUP_STORAGE_ACCESS_KEY` -- **Purpose:** Access key ID used to authenticate to the backup storage endpoint. -- **Required:** No -- **Default:** none -- **Notes:** Required when `BACKUP_STORAGE_ENDPOINT` is set. Store in a secret manager. - -### `BACKUP_STORAGE_SECRET_KEY` -- **Purpose:** Secret access key paired with `BACKUP_STORAGE_ACCESS_KEY`. -- **Required:** No -- **Default:** none -- **Notes:** Store in a secret manager. Never log or expose to the frontend. - -### `BACKUP_RETENTION_DAYS` -- **Purpose:** Number of days encrypted backups are retained before automatic pruning. -- **Required:** No -- **Default:** `30` -- **Notes:** Must be a positive integer. Ensure retention meets your compliance requirements. - ---- - -## Docker Compose & Kubernetes - -These variables are consumed by the deployment manifests rather than by the application code directly. They are grouped here so that operators can find them alongside the rest of the deployment configuration. - -### `POSTGRES_USER` -- **Purpose:** PostgreSQL superuser created by the `postgres` service in `docker-compose.yml`. -- **Required:** No -- **Default:** `postgres` -- **Consumed by:** `docker-compose.yml` -- **Notes:** Must match the user component of `DATABASE_URL` when running the stack locally. - -### `POSTGRES_PASSWORD` -- **Purpose:** Password for the PostgreSQL superuser in `docker-compose.yml`. -- **Required:** No -- **Default:** `postgres` -- **Consumed by:** `docker-compose.yml` -- **Notes:** Development default only. Never reuse this value outside local development. - -### `POSTGRES_DB` -- **Purpose:** Name of the database created by the `postgres` service in `docker-compose.yml`. -- **Required:** No -- **Default:** `app` -- **Consumed by:** `docker-compose.yml` -- **Notes:** Must match the database component of `DATABASE_URL`. - -### `IMAGE_TAG` -- **Purpose:** Container image tag used when pulling/building the backend and frontend images. -- **Required:** No -- **Default:** `latest` -- **Consumed by:** `docker-compose.yml`, Kubernetes manifests -- **Notes:** In CI, set this to the immutable commit SHA or release tag rather than `latest`. - -### `K8S_NAMESPACE` -- **Purpose:** Kubernetes namespace that the manifests are applied to. -- **Required:** No -- **Default:** `default` -- **Consumed by:** Kubernetes manifests -- **Notes:** Use a dedicated namespace per environment (e.g. `app-staging`, `app-prod`). - -### Where each variable is consumed - -- **`docker-compose.yml`** reads `POSTGRES_USER`, `POSTGRES_PASSWORD`, `POSTGRES_DB`, and `IMAGE_TAG` from the shell environment or a local `.env` file. -- **`k8s/configmap.yaml`** holds non-sensitive configuration such as `NODE_ENV`, `PORT`, `ALLOWED_ORIGINS`, `STELLAR_NETWORK`, `SOROBAN_RPC_URL`, `STELLAR_HORIZON_URL`, `IMAGE_TAG`, and `K8S_NAMESPACE`. -- **`k8s/secrets.yaml`** holds sensitive values such as `DATABASE_URL`, `SUPABASE_SERVICE_KEY`, `AGENT_SECRET_KEY`, `REGISTRY_SECRET_KEY`, `STELLAR_SECRET_KEY`, `OPENROUTER_API_KEY`, and `ADMIN_API_KEY`. These must be created out-of-band (e.g. `kubectl create secret`) and never committed with real values. - ---- - -## Frontend (Vite) - -### `VITE_API_URL` -- **Purpose:** Base URL of the backend API consumed by the frontend. -- **Required:** Yes -- **Default:** `http://localhost:3000` -- **Notes:** Only variables prefixed with `VITE_` are exposed to the browser bundle. Never put secrets in a `VITE_` variable. - ---- - -## Per-Environment Matrix - -| Variable | Development | Staging | Production | -|---|---|---|---| -| `DATABASE_URL` | `postgresql://postgres:postgres@localhost:5432/app` | Supabase pooler URL (staging project) | Supabase pooler URL (prod project, `sslmode=require`) | -| `SUPABASE_URL` | `https://.supabase.co` | `https://.supabase.co` | `https://.supabase.co` | -| `SUPABASE_SERVICE_KEY` | Dev service key | Staging service key | Prod service key (secret manager) | -| `SUPABASE_STORAGE_BUCKET` | `uploads` | `uploads` | `uploads` | -| `STELLAR_NETWORK` | `testnet` | `testnet` | `mainnet` | -| `NETWORK` | `testnet` | `testnet` | `mainnet` | -| `SOROBAN_RPC_URL` | `https://soroban-testnet.stellar.org` | `https://soroban-testnet.stellar.org` | `https://soroban-mainnet.stellar.org` | -| `STELLAR_HORIZON_URL` | `https://horizon-testnet.stellar.org` | `https://horizon-testnet.stellar.org` | `https://horizon.stellar.org` | -| `USDC_CONTRACT_ID` | Testnet USDC contract ID | Testnet USDC contract ID | Mainnet USDC contract ID | -| `PAY_TO` | Dev `G...` account | Staging `G...` account | Prod `G...` account | -| `AGENT_SECRET_KEY` | Dev `S...` key | Staging `S...` key | Prod `S...` key (secret manager) | -| `REGISTRY_SECRET_KEY` | Dev `S...` key | Staging `S...` key | Prod `S...` key (secret manager) | -| `STELLAR_CONTRACT_VAULT_REGISTRY` | Dev contract ID | Staging contract ID | Prod contract ID | -| `STELLAR_CONTRACT_ACCESS_LEASE` | Dev contract ID | Staging contract ID | Prod contract ID | -| `STELLAR_CONTRACT_SUBSCRIPTION` | Dev contract ID | Staging contract ID | Prod contract ID | -| `STELLAR_SECRET_KEY` | Dev `S...` key | Staging `S...` key | Prod `S...` key (secret manager) | -| `OPENROUTER_API_KEY` | Dev key | Staging key | Prod key (secret manager) | -| `ADMIN_API_KEY` | Dev random hex | Staging random hex | Prod random hex (secret manager) | -| `NODE_ENV` | `development` | `production` | `production` | -| `PORT` | `3000` | `3000` | `3000` | -| `ALLOWED_ORIGINS` | `http://localhost:5173` | `https://staging.example.com` | `https://app.example.com` | -| `FACILITATOR_URL` | `http://localhost:4000` | `https://facilitator.staging.example.com` | `https://facilitator.example.com` | -| `BACKUP_ENCRYPTION_KEY` | Dev base64 key | Staging base64 key | Prod base64 key (secret manager) | -| `BACKUP_STORAGE_BUCKET` | `backups-dev` | `backups-staging` | `backups-prod` | -| `BACKUP_STORAGE_ENDPOINT` | `http://localhost:9000` | `https://.r2.cloudflarestorage.com` | `https://s3..amazonaws.com` | -| `BACKUP_STORAGE_ACCESS_KEY` | Dev access key | Staging access key | Prod access key (secret manager) | -| `BACKUP_STORAGE_SECRET_KEY` | Dev secret key | Staging secret key | Prod secret key (secret manager) | -| `BACKUP_RETENTION_DAYS` | `7` | `30` | `90` | -| `VITE_API_URL` | `http://localhost:3000` | `https://api.staging.example.com` | `https://api.example.com` | -| `POSTGRES_USER` | `postgres` | `postgres` | `postgres` | -| `POSTGRES_PASSWORD` | `postgres` | Secret manager | Secret manager | -| `POSTGRES_DB` | `app` | `app` | `app` | -| `IMAGE_TAG` | `latest` | Commit SHA | Release tag | -| `K8S_NAMESPACE` | `default` | `app-staging` | `app-prod` | - ---- - -## .env.example Template - -Copy this file to `.env` and fill in real values. **Do not commit the resulting `.env`.** - -```dotenv -# --------------------------------------------------------------------------- -# Database & Supabase -# --------------------------------------------------------------------------- -DATABASE_URL=postgresql://postgres:postgres@localhost:5432/app -SUPABASE_URL=https://your-project-ref.supabase.co -SUPABASE_SERVICE_KEY=your-supabase-service-role-key -SUPABASE_STORAGE_BUCKET=uploads - -# --------------------------------------------------------------------------- -# Stellar / Soroban Network -# --------------------------------------------------------------------------- -STELLAR_NETWORK=testnet -NETWORK=testnet -SOROBAN_RPC_URL=https://soroban-testnet.stellar.org -STELLAR_HORIZON_URL=https://horizon-testnet.stellar.org -USDC_CONTRACT_ID=CXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX - -# --------------------------------------------------------------------------- -# Contracts -# --------------------------------------------------------------------------- -PAY_TO=GXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX -STELLAR_CONTRACT_VAULT_REGISTRY=CXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX -STELLAR_CONTRACT_ACCESS_LEASE=CXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX -STELLAR_CONTRACT_SUBSCRIPTION=CXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX - -# --------------------------------------------------------------------------- -# Secrets & API Keys -# --------------------------------------------------------------------------- -AGENT_SECRET_KEY=SXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX -REGISTRY_SECRET_KEY=SXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX -STELLAR_SECRET_KEY=SXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX -OPENROUTER_API_KEY=sk-or-v1-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx -ADMIN_API_KEY=replace-with-openssl-rand-hex-32 - -# --------------------------------------------------------------------------- -# Server & Runtime -# --------------------------------------------------------------------------- -NODE_ENV=development -PORT=3000 -ALLOWED_ORIGINS=http://localhost:5173 -FACILITATOR_URL=http://localhost:4000 - -# --------------------------------------------------------------------------- -# Backup -# --------------------------------------------------------------------------- -BACKUP_ENCRYPTION_KEY=replace-with-openssl-rand-base64-32 -BACKUP_STORAGE_BUCKET=backups -BACKUP_STORAGE_ENDPOINT=https://s3.us-east-1.amazonaws.com -BACKUP_STORAGE_ACCESS_KEY=your-backup-storage-access-key -BACKUP_STORAGE_SECRET_KEY=your-backup-storage-secret-key -BACKUP_RETENTION_DAYS=30 - -# --------------------------------------------------------------------------- -# Frontend (Vite) -# --------------------------------------------------------------------------- -VITE_API_URL=http://localhost:3000 - -# --------------------------------------------------------------------------- -# Docker Compose & Kubernetes -# --------------------------------------------------------------------------- -POSTGRES_USER=postgres -POSTGRES_PASSWORD=postgres -POSTGRES_DB=app -IMAGE_TAG=latest -K8S_NAMESPACE=default -``` - ---- - -## Security Notes - -- **Never commit secrets.** Add `.env`, `.env.*` (except `.env.example`), and any secret-bearing files to `.gitignore`. -- **Use a secret manager** for staging and production (GitHub Actions secrets, AWS Secrets Manager, Doppler, Vault, etc.). Do not bake secrets into container images or CI logs. -- **Rotate on leak.** If a secret is ever committed or logged, rotate it immediately — assume it is compromised. -- **Least privilege.** Use separate Supabase projects, Stellar accounts, and API keys per environment. Never reuse production credentials in development. -- **Frontend exposure.** Only variables prefixed with `VITE_` are bundled into the browser. Never place secrets in a `VITE_` variable. -- **CORS.** Keep `ALLOWED_ORIGINS` tight. Avoid `*` in staging and production. -- **Logging.** Redact secret values in logs and error reports. Never print full `S...` Stellar keys or service-role keys. -- **Validation.** The backend should fail fast at startup if any required variable is missing or malformed. \ No newline at end of file +# Environment Variables + +This document is the reference for every environment variable used by the +backend, the frontend, and the Kubernetes deployment manifests +(`k8s/configmap.yaml`, `k8s/secrets.yaml`). Names here match what the backend +code actually reads — `.env.example` and the manifests must stay in sync with +this list. + +## Stellar / Soroban + +| Variable | Secret | Description | +| --- | --- | --- | +| `STELLAR_NETWORK` | no | `testnet` or `mainnet`. | +| `STELLAR_RPC_URL` | no | Soroban RPC endpoint (e.g. `https://soroban-testnet.stellar.org`). | +| `STELLAR_HORIZON_URL` | no | Horizon endpoint used for account / payment lookups. | +| `STELLAR_NETWORK_PASSPHRASE` | no | Network passphrase matching `STELLAR_NETWORK`. | +| `STELLAR_AGENT_CONTRACT_ID` | no | Contract ID of the agent registry contract. | +| `STELLAR_REGISTRY_CONTRACT_ID` | no | Contract ID of the registry contract. | +| `STELLAR_ESCROW_CONTRACT_ID` | no | Contract ID of the escrow contract. | +| `USDC_CONTRACT_ID` | no | Contract ID of the USDC token contract. | +| `AGENT_SECRET_KEY` | **yes** | Stellar secret key (`S...`) used by the agent signer. | +| `REGISTRY_SECRET_KEY` | **yes** | Stellar secret key (`S...`) used by the registry signer. | + +## Supabase + +| Variable | Secret | Description | +| --- | --- | --- | +| `SUPABASE_URL` | no | Supabase project URL. | +| `SUPABASE_ANON_KEY` | no | Supabase anon/public key (safe for the frontend). | +| `SUPABASE_SERVICE_ROLE_KEY` | **yes** | Supabase service role key (backend only). | + +## LLM providers + +| Variable | Secret | Description | +| --- | --- | --- | +| `OPENROUTER_API_KEY` | **yes** | OpenRouter API key used by the agent runtime. | +| `OPENROUTER_MODEL` | no | Default model identifier (optional). | + +## Application + +| Variable | Secret | Description | +| --- | --- | --- | +| `NODE_ENV` | no | `development` / `production`. | +| `PORT` | no | HTTP port the backend listens on. | +| `LOG_LEVEL` | no | Log verbosity (`debug`, `info`, `warn`, `error`). | +| `CORS_ORIGIN` | no | Allowed frontend origin(s). | +| `NEXT_PUBLIC_API_URL` | no | Backend base URL consumed by the frontend. | + +## Notes + +- Secret values live only in `k8s/secrets.yaml` (or your secret manager) and + must never be committed with real values. +- Non-secret values live in `k8s/configmap.yaml` and `.env.example`. +- When adding a variable, update the backend reader, `.env.example`, + `k8s/configmap.yaml` / `k8s/secrets.yaml`, and this document together. diff --git a/k8s/configmap.yaml b/k8s/configmap.yaml index e9f70ae..8a33a54 100644 --- a/k8s/configmap.yaml +++ b/k8s/configmap.yaml @@ -11,3 +11,24 @@ data: # so a single image can be promoted across environments. VITE_API_BASE_URL: "https://api.example.com" VITE_APP_ENV: "production" +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: backend-config + labels: + app.kubernetes.io/name: backend + app.kubernetes.io/component: config +data: + # Non-secret backend configuration. Names match what the backend reads + # (see .env.example and docs/ENVIRONMENT-VARIABLES.md). + SOROBAN_RPC_URL: "https://soroban-testnet.stellar.org" + STELLAR_NETWORK: "testnet" + STELLAR_NETWORK_PASSPHRASE: "Test SDF Network ; September 2015" + STELLAR_HORIZON_URL: "https://horizon-testnet.stellar.org" + USDC_CONTRACT_ID: "CBIELTK6YBZJU5UP2WWQEUCYKLPU6AUNZ2BQ4WWFEIE3USCIHMXQDAMA" + AGENT_CONTRACT_ID: "" + REGISTRY_CONTRACT_ID: "" + OPENROUTER_MODEL: "anthropic/claude-3.5-sonnet" + SUPABASE_URL: "https://your-project.supabase.co" + LOG_LEVEL: "info" diff --git a/k8s/secrets.yaml b/k8s/secrets.yaml index 7543a3e..b449580 100644 --- a/k8s/secrets.yaml +++ b/k8s/secrets.yaml @@ -28,7 +28,7 @@ type: Opaque # 3. kubectl create secret (imperative, for local/dev use only): # kubectl create secret generic backend-secret --namespace synapsvault \ # --from-literal=DATABASE_URL='' \ -# --from-literal=SUPABASE_SERVICE_KEY='' \ +# --from-literal=SUPABASE_SERVICE_ROLE_KEY='' \ # --from-literal=AGENT_SECRET_KEY='' \ # --from-literal=REGISTRY_SECRET_KEY='' \ # --from-literal=STELLAR_SECRET_KEY='' \ @@ -45,7 +45,7 @@ stringData: DATABASE_URL: "REPLACE_ME_DATABASE_URL" # Supabase service-role key (server-side only; bypasses RLS). - SUPABASE_SERVICE_KEY: "REPLACE_ME_SUPABASE_SERVICE_KEY" + SUPABASE_SERVICE_ROLE_KEY: "REPLACE_ME_SUPABASE_SERVICE_ROLE_KEY" # Shared secret used to authenticate agent <-> backend communication. AGENT_SECRET_KEY: "REPLACE_ME_AGENT_SECRET_KEY" @@ -60,4 +60,4 @@ stringData: OPENROUTER_API_KEY: "REPLACE_ME_OPENROUTER_API_KEY" # Admin API key for privileged endpoints. - ADMIN_API_KEY: "REPLACE_ME_ADMIN_API_KEY" \ No newline at end of file + ADMIN_API_KEY: "REPLACE_ME_ADMIN_API_KEY"