Skip to content
Closed
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
29 changes: 29 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,20 @@ PORT=8000
# Postgres and Redis. These defaults match docker-compose.yml (local only,
# not secrets). Point them at your own servers for anything shared.
DATABASE_URL=postgresql://oracledesk:oracledesk@127.0.0.1:5433/oracledesk
# Alternatively, mount the URL from a file: DATABASE_URL_FILE=/run/secrets/database_url
# Every secret below also accepts <NAME>_FILE (env/file provider) — see #145.

# ── Secret loading (#145) ──────────────────────────────────────────────────
# env: process environment (default); file: <NAME>_FILE mounts; vault: Vault KV v2.
SECRETS_PROVIDER=env
# Refresh interval for file/vault providers (minimum 30000 ms).
SECRETS_REFRESH_MS=300000
# How long a rotated JWT_SECRET remains valid for already-issued tokens.
JWT_PREVIOUS_SECRET_GRACE_SECS=86400
# Required only when SECRETS_PROVIDER=vault.
VAULT_ADDR=
VAULT_TOKEN_FILE=
VAULT_KV_PATH=
REDIS_URL=redis://127.0.0.1:6379
# Comma-separated browser origins allowed to call the API.
CORS_ORIGINS=http://localhost:3000
Expand Down Expand Up @@ -72,7 +86,22 @@ GEMINI_API_KEY=
PINATA_API_KEY=
PINATA_SECRET_API_KEY=
IPFS_GATEWAY_URL=https://gateway.pinata.cloud/ipfs
# pinata (default) or local (development only; stores bytes in .ipfs-local/).
IPFS_TRANSPORT=pinata
IPFS_LOCAL_DIR=.ipfs-local

# ── Data sources ───────────────────────────────────────────────────────────
NEWSAPI_KEY=
FRED_API_KEY=

# ── Seed / snapshot (#147) ─────────────────────────────────────────────────
# The seed refuses production and non-local databases. Set to 1 only when you
# intentionally seed a remote development database.
SEED_ALLOW_REMOTE=0
# Base id for seeded on-chain market/trace rows, and the starting ledger.
SEED_ONCHAIN_BASE=1000000
SEED_BASE_LEDGER=6000000
# Optional comma-separated real testnet ids to link (read-only).
SEED_TESTNET_MARKET_IDS=
# Required by scripts/snapshot-export.ts; never commit the value.
SNAPSHOT_SALT=
135 changes: 135 additions & 0 deletions .github/workflows/cd.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
name: CD

on:
push:
branches: [main]
workflow_dispatch:

permissions:
contents: read
packages: write
id-token: write
pull-requests: read

concurrency:
group: cd-main
cancel-in-progress: false

jobs:
verify:
name: Verify release commit
uses: ./.github/workflows/ci.yml

image:
name: Build, sign and attest image
needs: verify
runs-on: ubuntu-latest
outputs:
digest: ${{ steps.build.outputs.digest }}
tag: ${{ steps.meta.outputs.tag }}
steps:
- uses: actions/checkout@v4
with:
submodules: true
- uses: docker/setup-buildx-action@v3
- uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- id: meta
run: echo "tag=ghcr.io/${GITHUB_REPOSITORY,,}:${GITHUB_SHA::12}" >> "$GITHUB_OUTPUT"
- id: build
uses: docker/build-push-action@v6
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tag }}
provenance: true
sbom: true
- name: Install cosign
uses: sigstore/cosign-installer@v3
- name: Sign image
run: cosign sign --yes "${{ steps.meta.outputs.tag }}@${{ steps.build.outputs.digest }}"
- name: SBOM (SPDX)
uses: anchore/sbom-action@v0
with:
image: ${{ steps.meta.outputs.tag }}@${{ steps.build.outputs.digest }}
format: spdx-json
output-file: sbom.spdx.json
- uses: actions/upload-artifact@v4
with:
name: sbom
path: sbom.spdx.json

staging:
name: Deploy staging
needs: image
uses: ./.github/workflows/deploy-env.yml
with:
environment: staging
namespace: oracledesk-staging
image: ghcr.io/${{ github.repository }}@${{ needs.image.outputs.digest }}
digest: ${{ needs.image.outputs.digest }}
values_file: values-staging.yaml
secrets: inherit

staging-checks:
name: Staging post-deploy checks
needs: [image, staging]
runs-on: ubuntu-latest
environment: staging
steps:
- uses: actions/checkout@v4
- uses: azure/setup-kubectl@v4
- name: Checks
env:
API_URL: ${{ vars.API_URL }}
READY_CHECKS: "3"
INDEXER_STATUS_URL: ${{ vars.INDEXER_STATUS_URL }}
MAX_INDEXER_LAG_LEDGERS: "120"
PROMETHEUS_URL: ${{ vars.PROMETHEUS_URL }}
KUBE_NAMESPACE: oracledesk-staging
DEPLOYMENTS: api indexer worker
DIGEST: ${{ needs.image.outputs.digest }}
KUBECONFIG_DATA: ${{ secrets.KUBECONFIG }}
run: |
echo "$KUBECONFIG_DATA" > "$RUNNER_TEMP/kubeconfig"
export KUBECONFIG="$RUNNER_TEMP/kubeconfig"
./scripts/post-deploy-checks.sh

production:
name: Deploy production
needs: [image, staging-checks]
uses: ./.github/workflows/deploy-env.yml
with:
environment: production
namespace: oracledesk
image: ghcr.io/${{ github.repository }}@${{ needs.image.outputs.digest }}
digest: ${{ needs.image.outputs.digest }}
values_file: values-production.yaml
secrets: inherit

production-checks:
name: Production post-deploy checks
needs: [image, production]
runs-on: ubuntu-latest
environment: production
steps:
- uses: actions/checkout@v4
- uses: azure/setup-kubectl@v4
- name: Checks
env:
API_URL: ${{ vars.API_URL }}
READY_CHECKS: "5"
INDEXER_STATUS_URL: ${{ vars.INDEXER_STATUS_URL }}
MAX_INDEXER_LAG_LEDGERS: "120"
PROMETHEUS_URL: ${{ vars.PROMETHEUS_URL }}
KUBE_NAMESPACE: oracledesk
DEPLOYMENTS: api indexer worker
DIGEST: ${{ needs.image.outputs.digest }}
KUBECONFIG_DATA: ${{ secrets.KUBECONFIG }}
run: |
echo "$KUBECONFIG_DATA" > "$RUNNER_TEMP/kubeconfig"
export KUBECONFIG="$RUNNER_TEMP/kubeconfig"
./scripts/post-deploy-checks.sh
27 changes: 27 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ on:
push:
branches: [main]
pull_request:
workflow_call:

jobs:
backend:
Expand Down Expand Up @@ -55,3 +56,29 @@ jobs:
- run: npx prisma migrate deploy
- run: npm run build
- run: npm test

- name: Migration compatibility (new migrations only)
if: github.event_name == 'pull_request'
run: |
git fetch origin "${{ github.base_ref }}" --depth=1
changed="$(git diff --name-only "origin/${{ github.base_ref }}...HEAD" -- prisma/migrations || true)"
if [[ -n "$changed" ]]; then
printf '%s\n' "$changed" | xargs node scripts/check-migration-compat.mjs
else
echo "No migration files changed"
fi

lint-workflows:
name: Workflow and script lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: actionlint
uses: docker://rhysd/actionlint:latest
with:
args: -color
- name: shellcheck
uses: ludeeus/action-shellcheck@master
with:
scandir: ./scripts
additional_files: ./scripts/backup.sh ./scripts/restore.sh
80 changes: 80 additions & 0 deletions .github/workflows/deploy-env.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
name: Deploy environment

on:
workflow_call:
inputs:
environment:
required: true
type: string
namespace:
required: true
type: string
image:
required: true
type: string
digest:
required: true
type: string
values_file:
required: true
type: string
secrets:
KUBECONFIG:
required: true
DATABASE_URL:
required: true
HELM_VALUES:
required: false

permissions:
contents: read
id-token: write

jobs:
deploy:
runs-on: ubuntu-latest
environment: ${{ inputs.environment }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-node@v4
with:
node-version-file: .nvmrc
cache: npm
- run: npm ci
- uses: azure/setup-helm@v4
- name: kubeconfig
run: |
echo "${{ secrets.KUBECONFIG }}" > "$RUNNER_TEMP/kubeconfig"
echo "KUBECONFIG=$RUNNER_TEMP/kubeconfig" >> "$GITHUB_ENV"
- name: Verify image signature
uses: sigstore/cosign-installer@v3
- run: cosign verify "${{ inputs.image }}" --certificate-identity-regexp "https://github.com/${GITHUB_REPOSITORY}/.github/workflows/cd.yml@refs/heads/main" --certificate-oidc-issuer https://token.actions.githubusercontent.com
- name: Migration lint (changed migrations only)
run: |
changed="$(git diff --name-only HEAD^ HEAD -- prisma/migrations || true)"
if [[ -n "$changed" ]]; then
printf '%s\n' "$changed" | xargs node scripts/check-migration-compat.mjs
else
echo "No migration files changed in this release"
fi
- name: Apply migrations
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }}
run: npx prisma migrate deploy
- name: Helm upgrade
env:
HELM_VALUES: ${{ secrets.HELM_VALUES }}
NAMESPACE: ${{ inputs.namespace }}
IMAGE: ${{ inputs.image }}
VALUES_FILE: ${{ inputs.values_file }}
run: |
test -d deploy/helm/oracledesk-backend || { echo "Helm chart missing (deploy/helm/oracledesk-backend)"; exit 1; }
extra=()
if [[ -n "$HELM_VALUES" ]]; then printf '%s' "$HELM_VALUES" > "$RUNNER_TEMP/values-secret.yaml"; extra=(-f "$RUNNER_TEMP/values-secret.yaml"); fi
helm upgrade --install oracledesk deploy/helm/oracledesk-backend \
--namespace "$NAMESPACE" --create-namespace \
-f "deploy/helm/oracledesk-backend/$VALUES_FILE" "${extra[@]}" \
--set image.repository="${IMAGE%@*}" --set image.digest="${IMAGE#*@}" \
--atomic --timeout 10m
62 changes: 62 additions & 0 deletions .github/workflows/restore-drill.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
name: Restore drill

on:
schedule:
- cron: "0 3 1 * *"
workflow_dispatch:

permissions:
contents: read

jobs:
restore:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:16-alpine
env:
POSTGRES_USER: oracledesk
POSTGRES_PASSWORD: oracledesk
POSTGRES_DB: oracledesk
ports: ["5432:5432"]
options: >-
--health-cmd "pg_isready -U oracledesk"
--health-interval 5s --health-timeout 5s --health-retries 10
env:
DATABASE_URL: postgresql://oracledesk:oracledesk@localhost:5432/oracledesk
NODE_ENV: test
CHAIN_EXECUTION_MODE: dry-run
IPFS_TRANSPORT: local
SEED_ALLOW_REMOTE: "0"
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: .nvmrc
cache: npm
- run: npm ci
- run: npx prisma generate
- run: npx prisma migrate deploy
- name: Seed fixture data
run: psql "$DATABASE_URL" -v ON_ERROR_STOP=1 -f tests/fixtures/restore-seed.sql
- name: Backup
run: BACKUP_DIR="$RUNNER_TEMP/backups" ./scripts/backup.sh
- name: Wipe and restore
run: |
psql "$DATABASE_URL" -v ON_ERROR_STOP=1 -c 'DROP SCHEMA public CASCADE; CREATE SCHEMA public;'
DUMP="$(find "$RUNNER_TEMP/backups" -name "oracledesk-*.dump" -type f | head -1)"
./scripts/restore.sh "$DUMP" --force
- name: Reconcile against recorded RPC events
env:
RECONCILE_FAKE_RPC: tests/fixtures/events.json
run: |
npx tsx scripts/post-restore-reconcile.ts \
--manifest "$RUNNER_TEMP"/backups/oracledesk-*-manifest.json \
--rewind-ledgers 720 --scan-transfers \
--out "$RUNNER_TEMP/reconcile-report.json"
- uses: actions/upload-artifact@v4
if: always()
with:
name: reconcile-report
path: ${{ runner.temp }}/reconcile-report.json
if-no-files-found: ignore
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ dist-tests
recovery.dat

# Build output and local artefacts
.ipfs-local/
dist/
*.log
.DS_Store
Loading
Loading