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
8 changes: 6 additions & 2 deletions .github/workflows/master.yml
Original file line number Diff line number Diff line change
Expand Up @@ -143,12 +143,13 @@ jobs:
SEO_CRON_SECRET: ${{secrets.SEO_CRON_SECRET}}
TURNSTILE_SECRET: ${{secrets.TURNSTILE_SECRET}}
SSR_INTERNAL_SECRET: ${{secrets.SSR_INTERNAL_SECRET}}
ENOTIFY_INTERNAL_TOKEN: ${{secrets.ENOTIFY_INTERNAL_TOKEN}}
with:
host: ${{ secrets.SSH_HOST_EU }}
username: ${{ secrets.SSH_USERNAME }}
key: ${{ secrets.SSH_KEY }}
port: ${{ secrets.SSH_PORT }}
envs: USE_PRIVATE,PRIVATE_API_ADDR,PRIVATE_API_AUTH,HIVESIGNER_SECRET,SEARCH_API_ADDR,SEARCH_API_SECRET,STRIPE_INTERNAL_SECRET,HOSTING_INTERNAL_SECRET,HIVESEARCHER_ORIGIN_IP,PLAUSIBLE_API_KEY,BLOCKSTREAM_CLIENT_ID,BLOCKSTREAM_CLIENT_SECRET,MATTERMOST_TEAM_ID,MATTERMOST_ADMIN_TOKEN,MATTERMOST_BASE_URL,MATTERMOST_WS_ALLOWED_ORIGINS,THREESPEAK_EMBED_API_KEY,SEO_CRON_SECRET,TURNSTILE_SECRET,NEWSLETTER_API_URL,NEWSLETTER_SERVICE_TOKEN,SSR_INTERNAL_SECRET
envs: USE_PRIVATE,PRIVATE_API_ADDR,PRIVATE_API_AUTH,HIVESIGNER_SECRET,SEARCH_API_ADDR,SEARCH_API_SECRET,STRIPE_INTERNAL_SECRET,HOSTING_INTERNAL_SECRET,HIVESEARCHER_ORIGIN_IP,PLAUSIBLE_API_KEY,BLOCKSTREAM_CLIENT_ID,BLOCKSTREAM_CLIENT_SECRET,MATTERMOST_TEAM_ID,MATTERMOST_ADMIN_TOKEN,MATTERMOST_BASE_URL,MATTERMOST_WS_ALLOWED_ORIGINS,THREESPEAK_EMBED_API_KEY,SEO_CRON_SECRET,TURNSTILE_SECRET,NEWSLETTER_API_URL,NEWSLETTER_SERVICE_TOKEN,SSR_INTERNAL_SECRET,ENOTIFY_INTERNAL_TOKEN
script: |
set -eo pipefail
export USE_PRIVATE=$USE_PRIVATE
Expand All @@ -173,6 +174,7 @@ jobs:
export SEO_CRON_SECRET=$SEO_CRON_SECRET
export TURNSTILE_SECRET=$TURNSTILE_SECRET
export SSR_INTERNAL_SECRET=$SSR_INTERNAL_SECRET
export ENOTIFY_INTERNAL_TOKEN=$ENOTIFY_INTERNAL_TOKEN

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Reject an empty enotify token before deploying

When ENOTIFY_INTERNAL_TOKEN is missing from the repository secrets, this export still succeeds and the production compose render deploys vapi with an empty token. As the new compose comment states, vapi then fails closed and silently omits favorites, bookmarks, Points transfers, and aggregates for every user, while the workflow remains green. Add a nonempty preflight check in both production deploy jobs, analogous to the adjacent newsletter checks; staging should at least fail or warn explicitly as intended.

Useful? React with 👍 / 👎.

# The web sizing knobs default in compose, so `docker-compose config`
# would otherwise read whatever the HOST happens to have exported and
# this region's memory profile could change with no commit behind it.
Expand Down Expand Up @@ -248,6 +250,7 @@ jobs:
SEO_CRON_SECRET: ${{secrets.SEO_CRON_SECRET}}
TURNSTILE_SECRET: ${{secrets.TURNSTILE_SECRET}}
SSR_INTERNAL_SECRET: ${{secrets.SSR_INTERNAL_SECRET}}
ENOTIFY_INTERNAL_TOKEN: ${{secrets.ENOTIFY_INTERNAL_TOKEN}}
# Newsletter service lives on the EU box; the US tier reaches it through the TLS relay
# on the EU origin (api.blogs.ecency.com/newsletter-relay, IP-allowlisted), so this
# region's URL is its own secret. Same token.
Expand Down Expand Up @@ -281,7 +284,7 @@ jobs:
username: ${{ secrets.SSH_USERNAME }}
key: ${{ secrets.SSH_KEY }}
port: ${{ secrets.SSH_PORT }}
envs: USE_PRIVATE,PRIVATE_API_ADDR,PRIVATE_API_AUTH,HIVESIGNER_SECRET,SEARCH_API_ADDR,SEARCH_API_SECRET,STRIPE_INTERNAL_SECRET,HOSTING_INTERNAL_SECRET,HIVESEARCHER_ORIGIN_IP,PLAUSIBLE_API_KEY,BLOCKSTREAM_CLIENT_ID,BLOCKSTREAM_CLIENT_SECRET,MATTERMOST_TEAM_ID,MATTERMOST_ADMIN_TOKEN,MATTERMOST_BASE_URL,MATTERMOST_WS_ALLOWED_ORIGINS,THREESPEAK_EMBED_API_KEY,SEO_CRON_SECRET,TURNSTILE_SECRET,NEWSLETTER_API_URL,NEWSLETTER_SERVICE_TOKEN,WEB_REPLICAS,WEB_HEAP_MB,WEB_MEM_LIMIT,WEB_UPDATE_ORDER,SSR_INTERNAL_SECRET
envs: USE_PRIVATE,PRIVATE_API_ADDR,PRIVATE_API_AUTH,HIVESIGNER_SECRET,SEARCH_API_ADDR,SEARCH_API_SECRET,STRIPE_INTERNAL_SECRET,HOSTING_INTERNAL_SECRET,HIVESEARCHER_ORIGIN_IP,PLAUSIBLE_API_KEY,BLOCKSTREAM_CLIENT_ID,BLOCKSTREAM_CLIENT_SECRET,MATTERMOST_TEAM_ID,MATTERMOST_ADMIN_TOKEN,MATTERMOST_BASE_URL,MATTERMOST_WS_ALLOWED_ORIGINS,THREESPEAK_EMBED_API_KEY,SEO_CRON_SECRET,TURNSTILE_SECRET,NEWSLETTER_API_URL,NEWSLETTER_SERVICE_TOKEN,WEB_REPLICAS,WEB_HEAP_MB,WEB_MEM_LIMIT,WEB_UPDATE_ORDER,SSR_INTERNAL_SECRET,ENOTIFY_INTERNAL_TOKEN
script: |
set -eo pipefail
export USE_PRIVATE=$USE_PRIVATE
Expand All @@ -300,6 +303,7 @@ jobs:
export SEO_CRON_SECRET=$SEO_CRON_SECRET
export TURNSTILE_SECRET=$TURNSTILE_SECRET
export SSR_INTERNAL_SECRET=$SSR_INTERNAL_SECRET
export ENOTIFY_INTERNAL_TOKEN=$ENOTIFY_INTERNAL_TOKEN
# The web tier decides per request whether the newsletter service is configured, so a
# missing secret would ship a silent no-show. Fail loudly instead.
: "${NEWSLETTER_API_URL:?NEWSLETTER_API_URL is required for the US deploy}"
Expand Down
4 changes: 3 additions & 1 deletion .github/workflows/staging.yml
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ jobs:
SEO_CRON_SECRET: ${{secrets.SEO_CRON_SECRET}}
TURNSTILE_SECRET: ${{secrets.TURNSTILE_SECRET}}
SSR_INTERNAL_SECRET: ${{secrets.SSR_INTERNAL_SECRET}}
ENOTIFY_INTERNAL_TOKEN: ${{secrets.ENOTIFY_INTERNAL_TOKEN}}
# Newsletter service (ecency/news) lives on the EU production box; staging reaches
# it through the IP-allowlisted TLS relay on the EU origin, same as US, with its own
# URL secret. There is one service and one database, so subscriptions made on alpha
Expand All @@ -153,7 +154,7 @@ jobs:
username: ${{ secrets.SSH_USERNAME }}
key: ${{ secrets.SSH_KEY }}
port: ${{ secrets.SSH_PORT }}
envs: USE_PRIVATE,PRIVATE_API_ADDR,PRIVATE_API_AUTH,HIVESIGNER_SECRET,SEARCH_API_ADDR,SEARCH_API_SECRET,STRIPE_INTERNAL_SECRET,HOSTING_INTERNAL_SECRET,HIVESEARCHER_ORIGIN_IP,PLAUSIBLE_API_KEY,BLOCKSTREAM_CLIENT_ID,BLOCKSTREAM_CLIENT_SECRET,MATTERMOST_TEAM_ID,MATTERMOST_ADMIN_TOKEN,MATTERMOST_BASE_URL,MATTERMOST_WS_ALLOWED_ORIGINS,THREESPEAK_EMBED_API_KEY,SEO_CRON_SECRET,TURNSTILE_SECRET,NEWSLETTER_API_URL,NEWSLETTER_SERVICE_TOKEN,SSR_INTERNAL_SECRET
envs: USE_PRIVATE,PRIVATE_API_ADDR,PRIVATE_API_AUTH,HIVESIGNER_SECRET,SEARCH_API_ADDR,SEARCH_API_SECRET,STRIPE_INTERNAL_SECRET,HOSTING_INTERNAL_SECRET,HIVESEARCHER_ORIGIN_IP,PLAUSIBLE_API_KEY,BLOCKSTREAM_CLIENT_ID,BLOCKSTREAM_CLIENT_SECRET,MATTERMOST_TEAM_ID,MATTERMOST_ADMIN_TOKEN,MATTERMOST_BASE_URL,MATTERMOST_WS_ALLOWED_ORIGINS,THREESPEAK_EMBED_API_KEY,SEO_CRON_SECRET,TURNSTILE_SECRET,NEWSLETTER_API_URL,NEWSLETTER_SERVICE_TOKEN,SSR_INTERNAL_SECRET,ENOTIFY_INTERNAL_TOKEN
script: |
export USE_PRIVATE=$USE_PRIVATE
export PRIVATE_API_ADDR=$PRIVATE_API_ADDR
Expand All @@ -171,6 +172,7 @@ jobs:
export SEO_CRON_SECRET=$SEO_CRON_SECRET
export TURNSTILE_SECRET=$TURNSTILE_SECRET
export SSR_INTERNAL_SECRET=$SSR_INTERNAL_SECRET
export ENOTIFY_INTERNAL_TOKEN=$ENOTIFY_INTERNAL_TOKEN
# Unlike production this does not fail without the newsletter settings: staging may run
# without the feature. It says so, because the web hides the controls silently otherwise.
if [ -z "$NEWSLETTER_API_URL" ] || [ -z "$NEWSLETTER_SERVICE_TOKEN" ]; then
Expand Down
5 changes: 5 additions & 0 deletions apps/web/docker-compose.production.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ services:
# Shared secret for the internal SSR RPC cache (/private-api/ssr/*); the
# web service sends it on every proxied read. Unset = those routes are off.
- SSR_INTERNAL_SECRET
# Shared secret vapi presents to enotify to unlock a user's OWN complete
# notification feed. enotify defaults to chain-derived activity only and
# fails closed, so unset means every user silently loses favorites,
# bookmarks, Points transfers and the aggregates from their own list.
- ENOTIFY_INTERNAL_TOKEN

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

1. Missing token deploys silently 🐞 Bug ☼ Reliability

The bare Compose passthrough accepts an absent or empty ENOTIFY_INTERNAL_TOKEN, so deployments
succeed while vapi silently omits favorites, bookmarks, Points transfers, and notification
aggregates. This also lets a later missing-secret deployment reset a previously working service to
the degraded state without any CI failure.
Agent Prompt
## Issue description
An unset or empty `ENOTIFY_INTERNAL_TOKEN` currently passes through Compose without failing, allowing a green deployment that disables parts of users' notification feeds.

## Issue Context
Both production and staging Compose configurations describe this token as necessary for the complete feed. Use required-variable interpolation, consistent with the existing `HIVESEARCHER_ORIGIN_IP` handling, so Compose rendering fails before stack deployment when the token is absent or empty.

## Fix Focus Areas
- apps/web/docker-compose.production.yml[23-27]
- apps/web/docker-compose.yml[23-27]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

# Upstream fills the SSR RPC cache may run at once, and how many may wait
# for a slot. An origin with ~90 misses/s against a 200-900ms node path
# needs 45-85 fills in flight; the defaults (64/256) queued fills past the
Expand Down
5 changes: 5 additions & 0 deletions apps/web/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ services:
# Shared secret for the internal SSR RPC cache (/private-api/ssr/*); the
# web service sends it on every proxied read. Unset = those routes are off.
- SSR_INTERNAL_SECRET
# Shared secret vapi presents to enotify to unlock a user's OWN complete
# notification feed. enotify defaults to chain-derived activity only and
# fails closed, so unset means every user silently loses favorites,
# bookmarks, Points transfers and the aggregates from their own list.
- ENOTIFY_INTERNAL_TOKEN
# Upstream fills the SSR RPC cache may run at once, and how many may wait
# for a slot. An origin with ~90 misses/s against a 200-900ms node path
# needs 45-85 fills in flight; the defaults (64/256) queued fills past the
Expand Down
Loading