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
197 changes: 83 additions & 114 deletions .github/workflows/ci-cd.yml

Large diffs are not rendered by default.

191 changes: 191 additions & 0 deletions .github/workflows/destroy-staging.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
name: Destroy Staging on PR Close

# Triggered when a PR targeting main is closed. The plan-destroy job is gated
# on `merged == true` so only merged PRs trigger a destroy (abandoned PRs
# leave staging alone).
#
# The destroy is split into two jobs so the destroy plan can be reviewed
# in the job logs before the apply runs:
#
# 1. plan-destroy-staging — generates and prints the destroy plan
# 2. destroy-staging — applies the plan; gated on the
# `obsrvr-radar-staging-destroy` environment,
# which is configured with required reviewers
# in GitHub → Settings → Environments.
#
# Once you are comfortable with the destroys, remove the required reviewer
# from the environment (no workflow change needed).

on:
pull_request:
branches:
- main
types: [closed]

permissions:
contents: read

jobs:
plan-destroy-staging:
name: 'Plan Staging Destroy'
if: github.event.pull_request.merged == true
runs-on: ubuntu-latest
environment: obsrvr-radar-staging
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
AWS_REGION: ${{ secrets.AWS_REGION }}
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup Terraform
uses: hashicorp/setup-terraform@v3

- name: Terraform Init - Staging
working-directory: ./terraform/environments/staging
run:
terraform init -backend-config="access_key=${{
secrets.AWS_ACCESS_KEY_ID }}" -backend-config="secret_key=${{
secrets.AWS_SECRET_ACCESS_KEY }}"

# Use the same shared script as plan-staging so the tfvars stay in sync.
- name: Generate tfvars for Staging
working-directory: ./terraform/environments/staging
env:
DIGITALOCEAN_ACCESS_TOKEN: ${{ secrets.DIGITALOCEAN_ACCESS_TOKEN }}
GIT_BRANCH: ${{ github.head_ref || github.ref_name }}
DEPLOYED_SHA: ${{ github.event.pull_request.head.sha || github.sha }}
REGION: ${{ vars.REGION || 'nyc3' }}
REPO_URL: ${{ github.server_url }}/${{ github.repository }}.git
AWS_BUCKET_NAME: ${{ secrets.AWS_BUCKET_NAME }}
STAGING_API_KEY: ${{ secrets.STAGING_API_KEY }}
STAGING_JWT_SECRET: ${{ secrets.STAGING_JWT_SECRET }}
STAGING_DOMAIN: ${{ secrets.STAGING_DOMAIN }}
IPSTACK_ACCESS_KEY: ${{ secrets.IPSTACK_ACCESS_KEY }}
MAILGUN_SECRET: ${{ secrets.MAILGUN_SECRET }}
MAILGUN_DOMAIN: ${{ secrets.MAILGUN_DOMAIN }}
MAILGUN_FROM: ${{ secrets.MAILGUN_FROM }}
MAILGUN_BASE_URL: ${{ secrets.MAILGUN_BASE_URL }}
ENCRYPTION_SECRET: ${{ secrets.ENCRYPTION_SECRET }}
HASH_SECRET: ${{ secrets.HASH_SECRET }}
CONSUMER_SECRET: ${{ secrets.CONSUMER_SECRET }}
CONSUMER_NAME: ${{ secrets.CONSUMER_NAME }}
DATABASE_POOL_SIZE: ${{ vars.DATABASE_POOL_SIZE }}
DATABASE_TEST_URL: ${{ secrets.DATABASE_TEST_URL }}
DEBUG: ${{ secrets.DEBUG }}
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
DEADMAN_URL: ${{ secrets.DEADMAN_URL }}
COORDINATOR_API_USERNAME: ${{ secrets.COORDINATOR_API_USERNAME }}
COORDINATOR_API_PASSWORD: ${{ secrets.COORDINATOR_API_PASSWORD }}
COORDINATOR_API_BASE_URL: ${{ secrets.COORDINATOR_API_BASE_URL }}
HISTORY_SCAN_API_USERNAME: ${{ secrets.HISTORY_SCAN_API_USERNAME }}
HISTORY_SCAN_API_PASSWORD: ${{ secrets.HISTORY_SCAN_API_PASSWORD }}
USER_SERVICE_USERNAME: ${{ secrets.USER_SERVICE_USERNAME }}
USER_SERVICE_PASSWORD: ${{ secrets.USER_SERVICE_PASSWORD }}
INSTANCE_SIZE: ${{ secrets.INSTANCE_SIZE }}
HISTORY_SCANNER_INSTANCE_SIZE: ${{ secrets.HISTORY_SCANNER_INSTANCE_SIZE }}
HISTORY_SCANNER_WORKER_COUNT: ${{ vars.HISTORY_SCANNER_WORKER_COUNT }}
CRAWLER_BLACKLIST: ${{ secrets.CRAWLER_BLACKLIST }}
USER_SERVICE_BASE_URL: ${{ secrets.USER_SERVICE_BASE_URL }}
FRONTEND_BASE_URL: ${{ secrets.FRONTEND_BASE_URL }}
HORIZON_URL: ${{ secrets.HORIZON_URL }}
NETWORK_PASSPHRASE: ${{ secrets.NETWORK_PASSPHRASE }}
NETWORK_ID: ${{ secrets.NETWORK_ID }}
NETWORK_NAME: ${{ secrets.NETWORK_NAME }}
NETWORK_OVERLAY_VERSION: ${{ secrets.NETWORK_OVERLAY_VERSION }}
NETWORK_LEDGER_VERSION: ${{ secrets.NETWORK_LEDGER_VERSION }}
NETWORK_OVERLAY_MIN_VERSION: ${{ secrets.NETWORK_OVERLAY_MIN_VERSION }}
NETWORK_STELLAR_CORE_VERSION: ${{ secrets.NETWORK_STELLAR_CORE_VERSION }}
NETWORK_QUORUM_SET: ${{ secrets.NETWORK_QUORUM_SET }}
NETWORK_KNOWN_PEERS: ${{ secrets.NETWORK_KNOWN_PEERS }}
TESTNET_HORIZON_URL: ${{ secrets.TESTNET_HORIZON_URL }}
TESTNET_NETWORK_PASSPHRASE: ${{ secrets.TESTNET_NETWORK_PASSPHRASE }}
TESTNET_NETWORK_ID: ${{ secrets.TESTNET_NETWORK_ID }}
TESTNET_NETWORK_NAME: ${{ secrets.TESTNET_NETWORK_NAME }}
TESTNET_NETWORK_OVERLAY_VERSION: ${{ secrets.TESTNET_NETWORK_OVERLAY_VERSION }}
TESTNET_NETWORK_LEDGER_VERSION: ${{ secrets.TESTNET_NETWORK_LEDGER_VERSION }}
TESTNET_NETWORK_OVERLAY_MIN_VERSION: ${{ secrets.TESTNET_NETWORK_OVERLAY_MIN_VERSION }}
TESTNET_NETWORK_STELLAR_CORE_VERSION: ${{ secrets.TESTNET_NETWORK_STELLAR_CORE_VERSION }}
TESTNET_NETWORK_QUORUM_SET: ${{ secrets.TESTNET_NETWORK_QUORUM_SET }}
TESTNET_NETWORK_KNOWN_PEERS: ${{ secrets.TESTNET_NETWORK_KNOWN_PEERS }}
run: ./write-tfvars.sh

- name: Terraform Plan Destroy - Staging
working-directory: ./terraform/environments/staging
run: terraform plan -destroy -input=false -no-color -out=tf-plan-destroy-staging

# Print the destroy plan to the job logs so reviewers can read it
# before approving the destroy-staging job.
- name: Show Destroy Plan
working-directory: ./terraform/environments/staging
run: terraform show -no-color tf-plan-destroy-staging

- name: Encrypt Terraform plan
working-directory: ./terraform/environments/staging
env:
PLAN_ENCRYPTION_KEY: ${{ secrets.PLAN_ENCRYPTION_KEY }}
run: |
echo "$PLAN_ENCRYPTION_KEY" > passphrase.txt
gpg --batch --yes --symmetric --cipher-algo AES256 --passphrase-file passphrase.txt --output tf-plan-destroy-staging.gpg tf-plan-destroy-staging
rm tf-plan-destroy-staging passphrase.txt

- name: Save Destroy Plan Artifact
uses: actions/upload-artifact@v4
with:
name: terraform-plan-destroy-staging
path: ./terraform/environments/staging/tf-plan-destroy-staging.gpg
retention-days: 1
if-no-files-found: error
overwrite: true

destroy-staging:
name: 'Destroy Staging Environment'
needs: [plan-destroy-staging]
if: needs.plan-destroy-staging.result == 'success'
runs-on: ubuntu-latest
# Share the staging-deploy concurrency group so a destroy can't race
# against a deploy that's in flight.
concurrency:
group: staging-deploy
cancel-in-progress: false
# Dedicated environment so you can require reviewers on destroys
# without gating normal staging deploys. Configure required reviewers
# in GitHub → Settings → Environments → obsrvr-radar-staging-destroy.
environment: obsrvr-radar-staging
permissions:
contents: read
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
AWS_REGION: ${{ secrets.AWS_REGION }}
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup Terraform
uses: hashicorp/setup-terraform@v3

- name: Terraform Init - Staging
working-directory: ./terraform/environments/staging
run: |
terraform init -backend-config="access_key=${{ secrets.AWS_ACCESS_KEY_ID }}" -backend-config="secret_key=${{ secrets.AWS_SECRET_ACCESS_KEY }}"

- name: Download Destroy Plan
uses: actions/download-artifact@v4
with:
name: terraform-plan-destroy-staging
path: ./terraform/environments/staging

- name: Decrypt Terraform plan
working-directory: ./terraform/environments/staging
env:
PLAN_ENCRYPTION_KEY: ${{ secrets.PLAN_ENCRYPTION_KEY }}
run: |
echo "$PLAN_ENCRYPTION_KEY" > passphrase.txt
gpg --batch --yes --decrypt --passphrase-file passphrase.txt --output tf-plan-destroy-staging tf-plan-destroy-staging.gpg
rm passphrase.txt

- name: Apply Destroy Plan
working-directory: ./terraform/environments/staging
run: terraform apply -auto-approve tf-plan-destroy-staging
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,4 @@ CODEBASE_RESEARCH_AND_RECOMMENDATIONS.md
REVENUE-STRATEGY.md
docs/OBSRVR_Radar_Quarterly_Report_Jan_2026 (1).pdf
docs/OBSRVR_Radar_Quarterly_Report_Q1_2026.pdf
terraform/environments/staging/terraform.auto_copy.tfvars_old
32 changes: 16 additions & 16 deletions apps/backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,39 +17,43 @@
"author": "pieterjan84@github",
"license": "MIT",
"dependencies": {
"express-basic-auth": "^1.2.1",
"crawler": "workspace:*",
"@aws-sdk/client-s3": "^3.665.0",
"@sentry/node": "^7.119.1",
"shared": "workspace:*",
"job-monitor": "workspace:*",
"exception-logger": "workspace:*",
"logger": "workspace:*",
"@withobsrvr/stellar-history-archive-hasher": "^0.8.1",
"@stellarbeat/stellar_analysis_nodejs": "^0.6.2",
"@withobsrvr/stellar-history-archive-hasher": "^0.8.1",
"ajv": "^8.17.1",
"async": "^3.2.6",
"await-fs": "^1.0.0",
"axios": "1.8.2",
"blocked-at": "^1.2.0",
"body-parser": "^1.20.3",
"class-transformer": "^0.5.1",
"crawler": "workspace:*",
"custom-error": "workspace:*",
"dotenv": "^10.0.0",
"ejs": "^3.1.10",
"exception-logger": "workspace:*",
"express": "^4.21.0",
"express-basic-auth": "^1.2.1",
"express-validator": "^6.15.0",
"helmet": "^4.6.0",
"history-scanner-dto": "workspace:*",
"http-helper": "workspace:*",
"inversify": "^6.0.2",
"job-monitor": "workspace:*",
"latest-semver": "^3.0.0",
"logger": "workspace:*",
"lru-cache": "^7.18.3",
"mathjs": "^10.6.4",
"moment": "^2.30.1",
"neverthrow": "^6.2.2",
"neverthrow": "^8.2.0",
"pg": "^8.13.0",
"pg-query-stream": "^2.1.2",
"pino": "9.4.0",
"reflect-metadata": "0.2.2",
"semver": "^7.6.3",
"shallow-equal-object": "^1.1.1",
"shared": "workspace:*",
"swagger-ui-express": "^4.6.3",
"to-semver": "^3.0.0",
"toml": "^3.0.0",
Expand All @@ -58,23 +62,19 @@
"validator": "^13.12.0",
"winston": "^3.15.0",
"workerpool": "^6.5.1",
"http-helper": "workspace:*",
"custom-error": "workspace:*",
"history-scanner-dto": "workspace:*"
"yn": "^4.0.0"
},
"devDependencies": {
"@types/async": "^3.2.24",
"@types/body-parser": "^1.19.5",
"@types/ejs": "^3.1.5",
"@types/express": "^4.17.21",
"@types/semver": "^7.5.8",
"@types/supertest": "6.0.2",
"@types/swagger-ui-express": "^3.0.1",
"@types/uuid": "^8.3.4",
"@types/validator": "^13.12.2",
"@types/workerpool": "^6.4.7",
"yn": "^4.0.0",
"supertest": "^7.0.0",
"@types/supertest": "6.0.2"
},
"packageManager": "pnpm@9.11.0+sha512.0a203ffaed5a3f63242cd064c8fb5892366c103e328079318f78062f24ea8c9d50bc6a47aa3567cabefd824d170e78fa2745ed1f16b132e16436146b7688f19b"
"supertest": "^7.0.0"
}
}
4 changes: 2 additions & 2 deletions apps/history-scanner/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ FROM node:20-alpine AS app-builder
WORKDIR /app

# Install pnpm
RUN npm install -g pnpm@9.15.0
RUN npm install -g pnpm@10.33.0

# Copy workspace files
COPY pnpm-workspace.yaml ./
Expand Down Expand Up @@ -38,7 +38,7 @@ RUN pnpm --filter shared build && \
RUN mkdir -p /prod/history-scanner

# Use pnpm deploy for dependencies
RUN pnpm --filter history-scanner deploy --prod /prod/history-scanner
RUN pnpm --filter history-scanner deploy --legacy --prod /prod/history-scanner

# Explicitly copy the built lib/ directory (pnpm deploy may not include it)
RUN cp -r /app/apps/history-scanner/lib /prod/history-scanner/lib
Expand Down
24 changes: 13 additions & 11 deletions apps/history-scanner/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,26 +13,28 @@
"license": "MIT",
"private": true,
"dependencies": {
"@stellar/stellar-base": "15.0.0",
"@withobsrvr/stellar-history-archive-hasher": "^0.9.1",
"ajv": "^8.17.1",
"custom-error": "workspace:*",
"dotenv": "^10.0.0",
"validator": "^13.12.0",
"inversify": "^6.0.2",
"shared": "workspace:*",
"neverthrow": "^6.2.2",
"exception-logger": "workspace:*",
"history-scanner-dto": "workspace:*",
"http-helper": "workspace:*",
"inversify": "^6.0.2",
"job-monitor": "workspace:*",
"logger": "workspace:*",
"ajv": "^8.17.1",
"yn": "^3.1.1",
"custom-error": "workspace:*",
"http-helper": "workspace:*",
"history-scanner-dto": "workspace:*",
"neverthrow": "^8.2.0",
"reflect-metadata": "0.2.2",
"shared": "workspace:*",
"validator": "^13.12.0",
"workerpool": "^6.5.1",
"@withobsrvr/stellar-history-archive-hasher": "^0.9.1"
"yn": "^3.1.1"
},
"devDependencies": {
"@types/express": "^4.17.21",
"@types/workerpool": "^6.4.7",
"express": "^4.21.2",
"@types/express": "^4.17.21",
"express-rate-limit": "^7.5.0",
"supertest": "^7.0.0"
}
Expand Down
2 changes: 1 addition & 1 deletion apps/users/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"license": "MIT",
"engines": {
"node": "20.x",
"pnpm": "9.15.0"
"pnpm": "10.33.0"
},
"private": true,
"devDependencies": {
Expand Down
2 changes: 0 additions & 2 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import eslintJs from '@eslint/js';
import eslintTs from 'typescript-eslint';
import pluginVue from 'eslint-plugin-vue';
import skipFormatting from '@vue/eslint-config-prettier/skip-formatting';
import vueTsEslintConfig from '@vue/eslint-config-typescript';
Expand All @@ -26,7 +25,6 @@ const customTypescriptConfig = {
export default [
{ ignores: ['**/lib/*', '**/dist/*', 'node_modules', '**/venv/**'] }, // global ignores
eslintJs.configs.recommended,
...eslintTs.configs.recommended,
...pluginVue.configs['flat/vue2-recommended'],
...vueTsEslintConfig(),
skipFormatting,
Expand Down
8 changes: 4 additions & 4 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@
};
};
nodejs = pkgs.nodejs_20;
# Pin pnpm to version 9.15.0
# Pin pnpm to version 10.33.0
pnpm = (pkgs.nodePackages.pnpm.override { nodejs = nodejs; }).overrideAttrs (old: {
version = "9.15.0";
version = "10.33.0";
src = pkgs.fetchurl {
url = "https://registry.npmjs.org/pnpm/-/pnpm-9.15.0.tgz";
sha256 = "0a7xy0qwixjfs9035yfzvbvcwk1g03s1j1k8aiip1njglcqzxa09";
url = "https://registry.npmjs.org/pnpm/-/pnpm-10.33.0.tgz";
sha256 = "100sfl5ya58h858ijynm8m5r1djq7idsfin42sji76r7mp5ipk5z";
};
});

Expand Down
Loading
Loading