Skip to content
Draft
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
13 changes: 4 additions & 9 deletions .github/actions/promote_vercel_deployment/action.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: Promote Vercel Deployment
description: Promotes the Vercel production deployment whose commit SHA matches
the active ENSIndexer image in the given Railway environment. Ensures exact
the active ENSIndexer image in the given ENSNode environment. Ensures exact
version matching between the active ENSNode and the Vercel project.

inputs:
Expand All @@ -16,12 +16,8 @@ inputs:
description: "Vercel API token"
required: true

railway_token:
description: "Railway API token"
required: true

railway_environment_id:
description: "Railway environment ID containing the active ENSIndexer image"
ensnode_environment:
description: "ENSNode environment to target (green, blue, or yellow)"
required: true

runs:
Expand All @@ -33,6 +29,5 @@ runs:
VERCEL_PROJECT_ID: ${{ inputs.vercel_project_id }}
VERCEL_TEAM_SLUG: ${{ inputs.vercel_team_slug }}
VERCEL_TOKEN: ${{ inputs.vercel_token }}
RAILWAY_TOKEN: ${{ inputs.railway_token }}
RAILWAY_ENVIRONMENT_ID: ${{ inputs.railway_environment_id }}
ENSNODE_ENVIRONMENT: ${{ inputs.ensnode_environment }}
run: ${{ github.action_path }}/promote_vercel_deployment.sh
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/bin/bash

# Identifies the commit sha of the ENSIndexer image deployed to the active Railway environment
# Identifies the commit sha of the ENSIndexer image deployed to the active ENSNode environment
# and promotes the Vercel deployment with that sha to production for the given Vercel project.
# Ensures exact version matching between the active ENSNode and the production Vercel deployment.

Expand All @@ -21,45 +21,44 @@ if [ -z "${VERCEL_TOKEN:-}" ]; then
exit 1
fi

if [ -z "${RAILWAY_TOKEN:-}" ]; then
echo "Error: RAILWAY_TOKEN is not set or is empty"
if [ -z "${ENSNODE_ENVIRONMENT:-}" ]; then
echo "Error: ENSNODE_ENVIRONMENT is not set or is empty"
exit 1
fi

if [ -z "${RAILWAY_ENVIRONMENT_ID:-}" ]; then
echo "Error: RAILWAY_ENVIRONMENT_ID is not set or is empty"
exit 1
fi
case "$ENSNODE_ENVIRONMENT" in
green|blue|yellow)
;;
*)
echo "Error: ENSNODE_ENVIRONMENT must be one of: green, blue, yellow (got: $ENSNODE_ENVIRONMENT)"
exit 1
;;
esac

echo "Targeting Railway Environment: $RAILWAY_ENVIRONMENT_ID"
echo "Targeting ENSNode Environment: $ENSNODE_ENVIRONMENT"
echo "Targeting Vercel Project: $VERCEL_PROJECT_ID"

# first, get deployed ENSIndexer image from Railway Environment
RAILWAY_SERVICES_OUTPUT=$(curl \
--request POST \
# first, get the deployed ENSNode version from the ENSApi indexing-status endpoint
INDEXING_STATUS_URL="https://api.alpha.${ENSNODE_ENVIRONMENT}.ensnode.io/api/indexing-status"
ENSNODE_VERSION=$(curl \
--silent \
--show-error \
--url https://backboard.railway.app/graphql/v2 \
--header "Authorization: Bearer $RAILWAY_TOKEN" \
--header 'Content-Type: application/json' \
--data "{\"query\": \"{ environment(id: \\\"$RAILWAY_ENVIRONMENT_ID\\\") { serviceInstances { edges { node { source { image } } } } } }\"}")

if [ $? -ne 0 ]; then
echo "Error: curl command failed. Output:"
echo "$RAILWAY_SERVICES_OUTPUT"
exit 1
fi
--fail \
--url "$INDEXING_STATUS_URL" | \
jq -r '.stackInfo.ensIndexer.versionInfo.ensIndexer')

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

promote_vercel_deployment.sh reconstructs the ENSIndexer image tag from the reported semver version (ensindexer:<version>), which does not exist for release-candidate/preview deployments, so skopeo inspect fails — and because the promote steps run after the irreversible Traefik routing switch, the workflow aborts with production already moved to the new backend but the frontend left un-promoted.

Fix on Vercel

Comment on lines +41 to +48

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Bound the ENSNode indexing-status request.

curl --fail still has no connection or total timeout, so a stalled endpoint can hang this production promotion job until the workflow timeout. Add bounded timeouts and a small retry budget.

Proposed fix
 ENSNODE_VERSION=$(curl \
   --silent \
   --show-error \
   --fail \
+  --connect-timeout 10 \
+  --max-time 30 \
+  --retry 3 \
+  --retry-delay 2 \
+  --retry-connrefused \
   --url "$INDEXING_STATUS_URL" | \
   jq -r '.stackInfo.ensIndexer.versionInfo.ensIndexer')
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# first, get the deployed ENSNode version from the ENSApi indexing-status endpoint
INDEXING_STATUS_URL="https://api.alpha.${ENSNODE_ENVIRONMENT}.ensnode.io/api/indexing-status"
ENSNODE_VERSION=$(curl \
--silent \
--show-error \
--url https://backboard.railway.app/graphql/v2 \
--header "Authorization: Bearer $RAILWAY_TOKEN" \
--header 'Content-Type: application/json' \
--data "{\"query\": \"{ environment(id: \\\"$RAILWAY_ENVIRONMENT_ID\\\") { serviceInstances { edges { node { source { image } } } } } }\"}")
if [ $? -ne 0 ]; then
echo "Error: curl command failed. Output:"
echo "$RAILWAY_SERVICES_OUTPUT"
exit 1
fi
--fail \
--url "$INDEXING_STATUS_URL" | \
jq -r '.stackInfo.ensIndexer.versionInfo.ensIndexer')
# first, get the deployed ENSNode version from the ENSApi indexing-status endpoint
INDEXING_STATUS_URL="https://api.alpha.${ENSNODE_ENVIRONMENT}.ensnode.io/api/indexing-status"
ENSNODE_VERSION=$(curl \
--silent \
--show-error \
--fail \
--connect-timeout 10 \
--max-time 30 \
--retry 3 \
--retry-delay 2 \
--retry-connrefused \
--url "$INDEXING_STATUS_URL" | \
jq -r '.stackInfo.ensIndexer.versionInfo.ensIndexer')
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/actions/promote_vercel_deployment/promote_vercel_deployment.sh
around lines 41 - 48, The ENSNode indexing-status fetch in
promote_vercel_deployment.sh is unbounded and can hang the promotion job if the
endpoint stalls. Update the curl call used to populate ENSNODE_VERSION with
explicit connection and total timeouts, plus a small retry budget, while keeping
the existing INDEXING_STATUS_URL and jq parsing flow intact. Use the existing
promote_vercel_deployment.sh request block as the place to apply the
timeout/retry settings so the workflow fails fast instead of waiting for the
full job timeout.


# get the first ensindexer image
ENSINDEXER_IMAGE=$(echo "$RAILWAY_SERVICES_OUTPUT" | jq -r '.data.environment.serviceInstances.edges[].node.source.image | select(type == "string" and startswith("ghcr.io/namehash/ensnode/ensindexer"))' | head -n1)
echo "Found ENSNode version: $ENSNODE_VERSION"

echo "Found ENSIndexer image: $ENSINDEXER_IMAGE"

if [ -z "$ENSINDEXER_IMAGE" ]; then
echo "Error: Could not find ENSIndexer image for the environment."
if [ -z "$ENSNODE_VERSION" ] || [ "$ENSNODE_VERSION" = "null" ]; then
echo "Error: Could not resolve ENSNode version from $INDEXING_STATUS_URL"
exit 1
fi

# the version is the image tag for the deployed ENSNode images
ENSINDEXER_IMAGE="ghcr.io/namehash/ensnode/ensindexer:${ENSNODE_VERSION}"

echo "Using ENSIndexer image: $ENSINDEXER_IMAGE"

# get commit sha from labels of the docker image
ENSINDEXER_COMMIT_SHA=$(skopeo inspect docker://$ENSINDEXER_IMAGE --override-arch amd64 --override-os linux | jq -r '.Labels."org.opencontainers.image.revision"')

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
name: "Deploy: Switch ENSDb Playground Environment"

permissions:
contents: read

on:
workflow_dispatch:
inputs:
target:
description: "New ENSDb Playground environment"
required: true
type: choice
options:
- green
- blue

jobs:
switch-environment:
# NOTE: this needs to run on GH runner because of some obscure skopeo permissions thing
runs-on: ubuntu-latest
Comment on lines +6 to +20

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Serialize ENSDb Playground environment switches.

This workflow mutates a single Traefik production routing key; overlapping manual dispatches can race and leave routing determined by the last Redis write, not operator intent. Add a workflow-level deployment lock.

Proposed fix
 on:
   workflow_dispatch:
     inputs:
       target:
         description: "New ENSDb Playground environment"
         required: true
         type: choice
         options:
           - green
           - blue
+
+concurrency:
+  group: deploy-switch-ensdb-playground-environment
+  cancel-in-progress: false
 
 jobs:
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
on:
workflow_dispatch:
inputs:
target:
description: "New ENSDb Playground environment"
required: true
type: choice
options:
- green
- blue
jobs:
switch-environment:
# NOTE: this needs to run on GH runner because of some obscure skopeo permissions thing
runs-on: ubuntu-latest
on:
workflow_dispatch:
inputs:
target:
description: "New ENSDb Playground environment"
required: true
type: choice
options:
- green
- blue
concurrency:
group: deploy-switch-ensdb-playground-environment
cancel-in-progress: false
jobs:
switch-environment:
# NOTE: this needs to run on GH runner because of some obscure skopeo permissions thing
runs-on: ubuntu-latest
🧰 Tools
🪛 zizmor (1.26.1)

[warning] 6-15: insufficient job-level concurrency limits (concurrency-limits): workflow is missing concurrency setting

(concurrency-limits)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/deploy_switch_ensdb_playground_environment.yml around
lines 6 - 20, The manual environment switch workflow can race because multiple
dispatches of switch-environment mutate the same Traefik routing key at once.
Add a workflow-level deployment lock using the workflow’s concurrency controls
so only one run can execute or queue at a time, and apply it to the
deploy_switch_ensdb_playground_environment workflow around the existing
workflow_dispatch/job setup.

Source: Linters/SAST tools

name: Switch Environment to ${{ inputs.target }}
env:
TARGET_ENVIRONMENT: ${{ inputs.target }}
REDIS_URL: ${{ secrets.TRAEFIK_REDIS_URL }}

steps:
- name: Checkout repository
uses: actions/checkout@v6
with:
fetch-depth: 1
Comment on lines +27 to +30

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

ref_json="$(gh api repos/actions/checkout/git/ref/tags/v6)"
sha="$(jq -r '.object.sha' <<<"$ref_json")"
type="$(jq -r '.object.type' <<<"$ref_json")"

if [ "$type" = "tag" ]; then
  gh api "repos/actions/checkout/git/tags/$sha" --jq '.object.sha'
else
  echo "$sha"
fi

Repository: namehash/ensnode

Length of output: 1984


🌐 Web query:

actions/checkout persist-credentials default documentation hash pinning recommendation

💡 Result:

The actions/checkout action, maintained by GitHub, includes a persist-credentials input that controls whether authentication credentials (the GitHub token or a provided SSH key) are configured in the local Git configuration [1][2]. Default Behavior and Recommendation: The default value for persist-credentials is true [3][4]. When enabled, the action configures the runner's local Git environment with credentials, which allows subsequent steps in the same job to execute authenticated Git commands [1][5]. Security experts and industry guidance, including the OWASP GitHub Actions Security Cheat Sheet, strongly recommend setting persist-credentials: false unless your workflow explicitly requires authenticated Git operations [6][7]. This practice is recommended to minimize the attack surface by preventing credentials from being stored in the environment, thereby reducing the risk of accidental exposure if the workflow is compromised [6][7]. Action Versioning and Hash Pinning: Regarding hash pinning, it is a recommended security best practice to pin all GitHub Actions to a specific commit SHA rather than using tags (like v4) [6][7]. This ensures that your workflow consistently runs the exact same code, protecting against potential supply chain attacks where a tag might be updated to point to malicious code [7]. Example Configuration: To implement these security recommendations, use the following pattern in your workflow YAML: - uses: actions/checkout@<FULL_COMMIT_SHA> with: persist-credentials: false Replace <FULL_COMMIT_SHA> with the specific 40-character commit hash of the version you intend to use [6][7].

Citations:


Harden the checkout step.

actions/checkout@v6 is tag-pinned instead of SHA-pinned, and persist-credentials defaults to true, which leaves GITHUB_TOKEN in the local git config. Pin to a full commit SHA and set persist-credentials: false unless later steps need authenticated git access.

🧰 Tools
🪛 zizmor (1.26.1)

[warning] 27-30: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false

(artipacked)


[error] 28-28: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/deploy_switch_ensdb_playground_environment.yml around
lines 27 - 30, The Checkout repository step in the deploy workflow needs
hardening: `actions/checkout` is currently tag-pinned and leaves credentials in
git config by default. Update the `Checkout repository` step to use a full
commit SHA for `actions/checkout` and set `persist-credentials` to false unless
later steps in the workflow require authenticated git access.

Source: Linters/SAST tools


- name: Calculate env variables
run: |
case "$TARGET_ENVIRONMENT" in
"green")
echo "SLACK_TITLE=:large_green_circle: ENSDb Playground GREEN Environment Activated" >> "$GITHUB_ENV"
;;
"blue")
echo "SLACK_TITLE=:large_blue_circle: ENSDb Playground BLUE Environment Activated" >> "$GITHUB_ENV"
;;
*)
echo "SLACK_TITLE=:large_red_circle: Unrecognized ENSDb Playground Environment Activated" >> "$GITHUB_ENV"
exit 1
;;
esac
Comment thread
tk-o marked this conversation as resolved.
Comment thread
tk-o marked this conversation as resolved.

- name: Switch Traefik Routing
run: |
sudo apt update
sudo apt install -y redis-tools

# ENSDB PLAYGROUND
redis-cli -u $REDIS_URL SET traefik/http/routers/ensdb-playground-router/service "${TARGET_ENVIRONMENT}-ensdb-playground"

- name: Send Slack Notification
uses: ./.github/actions/send_slack_notification
Comment on lines +55 to +56

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== Workflow snippet ==\n'
sed -n '45,65p' .github/workflows/deploy_switch_ensdb_playground_environment.yml

printf '\n== Action metadata ==\n'
sed -n '1,80p' .github/actions/send_slack_notification/action.yml

printf '\n== References to send_slack_notification ==\n'
rg -n "send_slack_notification|actionlint|description:" .github -S

Repository: namehash/ensnode

Length of output: 1955


🌐 Web query:

GitHub Actions action.yml top-level description required local action metadata actionlint local action missing description

💡 Result:

In GitHub Actions, the description field in the action.yml metadata file is required [1][2]. The official GitHub Actions metadata syntax documentation explicitly lists description as a required top-level key for defining an action [1][3]. Regarding actionlint, this static analysis tool enforces metadata requirements for local actions. Since version 1.7.0, actionlint has included checks for the action metadata file (action.yml or action.yaml) [4]. If the top-level description field is missing from your action.yml file, actionlint will report an error (e.g., 'description' section is missing) [4]. Because actionlint primarily focuses on checking workflow files rather than independently validating action.yml files in isolation, you may need to ensure your action is being referenced in a workflow or test file that actionlint scans, or verify your linting configuration, to see the error reported [4].

Citations:


Add the missing description in .github/actions/send_slack_notification/action.yml
actionlint treats local actions without a top-level description as invalid, so this workflow step can fail linting until the action metadata is updated.

🧰 Tools
🪛 actionlint (1.7.12)

[error] 56-56: description is required in metadata of "Send Slack notification" action at "/home/jailuser/git/.github/actions/send_slack_notification/action.yml"

(action)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/deploy_switch_ensdb_playground_environment.yml around
lines 55 - 56, The local action referenced by Send Slack Notification is missing
required metadata, causing actionlint to reject the workflow. Update
.github/actions/send_slack_notification/action.yml to add a top-level
description field alongside the existing action metadata so the uses step for
send_slack_notification is lint-valid.

Source: Linters/SAST tools

with:
slack_webhook: ${{ secrets.SLACK_WEBHOOK_URL }}
slack_title: ${{ env.SLACK_TITLE }}
slack_message: "✅ Switch ENSDb Playground environment completed"
26 changes: 12 additions & 14 deletions .github/workflows/deploy_switch_ensnode_environment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,13 @@ on:
workflow_dispatch:
inputs:
target:
description: "New main environment (green or blue)"
description: "New main environment"
required: true
type: choice
options:
- green
- blue
- yellow

jobs:
switch-environment:
Expand All @@ -23,7 +24,6 @@ jobs:
TARGET_ENVIRONMENT: ${{ inputs.target }}
REDIS_URL: ${{ secrets.TRAEFIK_REDIS_URL }}
VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}
RAILWAY_TOKEN: ${{ secrets.RAILWAY_TOKEN }}

steps:
- name: Checkout repository
Expand All @@ -35,15 +35,18 @@ jobs:
run: |
case "$TARGET_ENVIRONMENT" in
"green")
echo "SLACK_TITLE=:large_green_circle: GREEN Environment Activated" >> "$GITHUB_ENV"
echo "RAILWAY_ENVIRONMENT_ID=${{ secrets.GREEN_RAILWAY_ENVIRONMENT_ID }}" >> $GITHUB_ENV
echo "SLACK_TITLE=:large_green_circle: ENSNode GREEN Environment Activated" >> "$GITHUB_ENV"
;;
"blue")
echo "SLACK_TITLE=:large_blue_circle: BLUE Environment Activated" >> "$GITHUB_ENV"
echo "RAILWAY_ENVIRONMENT_ID=${{ secrets.BLUE_RAILWAY_ENVIRONMENT_ID }}" >> $GITHUB_ENV
echo "SLACK_TITLE=:large_blue_circle: ENSNode BLUE Environment Activated" >> "$GITHUB_ENV"
;;
"yellow")
echo "SLACK_TITLE=:large_yellow_circle: ENSNode YELLOW Environment Activated" >> "$GITHUB_ENV"
;;
*)
echo "SLACK_TITLE=:large_red_circle: Unrecognized Environment Activated" >> "$GITHUB_ENV"
echo "SLACK_TITLE=:large_red_circle: Unrecognized ENSNode Environment Activated" >> "$GITHUB_ENV"
exit 1
;;
esac
Comment thread
tk-o marked this conversation as resolved.
Comment thread
tk-o marked this conversation as resolved.

- name: Switch Traefik Routing
Expand Down Expand Up @@ -86,9 +89,6 @@ jobs:
redis-cli -u $REDIS_URL SET traefik/http/routers/lb-header-sepolia-api-router/service "${TARGET_ENVIRONMENT}-sepolia-api"
redis-cli -u $REDIS_URL SET traefik/http/routers/lb-header-sepolia-indexer-router/service "${TARGET_ENVIRONMENT}-sepolia-indexer"

# ENSDB PLAYGROUND
redis-cli -u $REDIS_URL SET traefik/http/routers/ensdb-playground-router/service "${TARGET_ENVIRONMENT}-ensdb-playground"

# ENSRAINBOW
redis-cli -u $REDIS_URL SET traefik/http/routers/ensrainbow-api-router/service "${TARGET_ENVIRONMENT}-ensrainbow-api"

Expand All @@ -101,17 +101,15 @@ jobs:
vercel_team_slug: namehash
vercel_project_id: prj_nKcHTO12hq9kcgascQMq4xokRhwp
vercel_token: ${{ secrets.VERCEL_TOKEN }}
railway_token: ${{ secrets.RAILWAY_TOKEN }}
railway_environment_id: ${{ env.RAILWAY_ENVIRONMENT_ID }}
ensnode_environment: ${{ inputs.target }}

- name: Promote Example App Vercel Deployment
uses: ./.github/actions/promote_vercel_deployment
with:
vercel_team_slug: namehash
vercel_project_id: prj_Ux80vjjNyDfXMNvPEQo4zlx6Eq6D
vercel_token: ${{ secrets.VERCEL_TOKEN }}
railway_token: ${{ secrets.RAILWAY_TOKEN }}
railway_environment_id: ${{ env.RAILWAY_ENVIRONMENT_ID }}
ensnode_environment: ${{ inputs.target }}

- name: Send Slack Notification
uses: ./.github/actions/send_slack_notification
Expand Down
Loading