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
25 changes: 23 additions & 2 deletions .github/workflows/cd-prod.yml
Original file line number Diff line number Diff line change
Expand Up @@ -82,10 +82,31 @@ jobs:

- name: Deploy Cloudflare infrastructure
run: npm run iac:deploy -- --stage prod

- name: Attach apex domain to Pages project
run: |
chmod +x scripts/attach-pages-domain.sh
./scripts/attach-pages-domain.sh codecolony.online

- name: Verify apex domain
# First Pages deploy of a new custom domain can take 30-90s for
# Cloudflare to issue the TLS cert; retry briefly. --fail-with-body
# makes curl exit non-zero on 4xx/5xx so the workflow surfaces real
# outages.
run: |
for attempt in 1 2 3 4 5 6; do
if curl --fail-with-body -sS -o /dev/null \
-w 'HTTP %{http_code} (attempt '$attempt')\n' \
https://codecolony.online/; then
Comment on lines +97 to +99

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Enforce 2xx in apex check instead of relying on --fail-with-body

This check currently passes on HTTP 3xx responses because curl --fail-with-body only fails for response codes 400 or greater (per curl --manual), so a redirect from codecolony.online will exit 0 on the first attempt. Since the step message and error text describe a 2xx readiness gate, not explicitly validating %{http_code} allows misrouted/incorrect redirects to be reported as healthy deploys.

Useful? React with 👍 / 👎.

exit 0
fi
echo "Apex not ready yet, sleeping..." >&2
sleep 15
done
echo "::error::Apex codecolony.online never returned 2xx after 6 attempts." >&2
exit 1
- if: always() && env.SLACK_WEBHOOK_URL != ''
name: Notify Slack
run: |
Comment thread
LaloLalo1999 marked this conversation as resolved.
curl -X POST -H 'Content-type: application/json' \
--data "{\"text\":\"Code Colony prod deploy finished with status: ${{ job.status }}\"}" \
"$SLACK_WEBHOOK_URL"
"$SLACK_WEBHOOK_URL"
13 changes: 10 additions & 3 deletions alchemy/dns.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,17 @@ export async function dnsStack(
`verify delegation before redeploying.`,
);
}
const zoneId = zone.id;
void zoneId;

void zone.id;

// Landing apex/www: deliberately not managed by alchemy.
// Landing apex/www: deliberately not managed by alchemy DnsRecords.
// Cloudflare's zone-create flow auto-provisioned `@`/`www` CNAME records
// (standard zone bootstrap), and recreating them collides with code 81053.
// Instead, the apex domain is attached as a custom domain to the Pages
// project (code-colony-landing) via the Cloudflare API in cd-prod.yml.
// Cloudflare then manages the apex DNS record and TLS automatically.
// See scripts/attach-pages-domain.sh for the API call.
void args.landing;

if (args.app.hostname) {
await CustomDomain("app", {
Expand Down
7 changes: 4 additions & 3 deletions alchemy/landing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@
* still deployed by the Pages workflow outside this Alchemy stack.
*
* Alchemy's currently installed Cloudflare provider does not expose a Pages
* resource. DNS therefore CNAMEs the apex subdomain to the stable Pages
* hostname returned here, while previews keep using Cloudflare's default
* Pages/Workers preview URLs.
* resource. The apex domain (codecolony.online) is attached as a custom domain
* to the Pages project via the Cloudflare API in cd-prod.yml. Cloudflare then
* manages the apex DNS record and TLS automatically.
* Previews keep using Cloudflare's default Pages/Workers preview URLs.
*/
import type Alchemy from "alchemy";

Expand Down
81 changes: 81 additions & 0 deletions scripts/attach-pages-domain.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
#!/usr/bin/env bash
# Attach codecolony.online as a custom domain to the Cloudflare Pages project
# code-colony-landing. Idempotent: re-attaches/repairs if the domain is present
# but not in a healthy verified state.
#
# Environment:
# CLOUDFLARE_ACCOUNT_ID (required)
# CLOUDFLARE_API_TOKEN (required)
#
# This is used by cd-prod.yml because Alchemy's Cloudflare provider does not
# expose a Pages resource, so we attach the apex domain directly via the
# Cloudflare API. Once attached, Cloudflare manages the apex DNS record
# automatically (proxying + TLS).

set -euo pipefail

DOMAIN="${1:-codecolony.online}"
PROJECT="code-colony-landing"
ACCOUNT_ID="${CLOUDFLARE_ACCOUNT_ID:?CLOUDFLARE_ACCOUNT_ID is required}"
TOKEN="${CLOUDFLARE_API_TOKEN:?CLOUDFLARE_API_TOKEN is required}"

API_BASE="https://api.cloudflare.com/client/v4"
AUTH_HDR="Authorization: Bearer ${TOKEN}"

echo "==> Checking existing domains on Pages project '${PROJECT}' ..."

# Fetch the existing domain entry once and inspect both name (canonical) and
# status. The API response field is `.result[].name`; older docs reference
# `.domain`, but the live shape is `name`. We accept either to be safe.
EXISTING_JSON=$(curl -sS -H "${AUTH_HDR}" \
"${API_BASE}/accounts/${ACCOUNT_ID}/pages/projects/${PROJECT}/domains")

EXISTING_ENTRY=$(echo "${EXISTING_JSON}" | jq -c \
--arg dom "${DOMAIN}" \
'.result[]? | select((.name // .domain) == $dom)')

if [ -n "${EXISTING_ENTRY}" ]; then
STATUS=$(echo "${EXISTING_ENTRY}" | jq -r '.status // .verification_data.status // "unknown"')
echo "==> Domain '${DOMAIN}' already attached (status=${STATUS})."
case "${STATUS}" in
active|pending|verifying|ssl_pending|deployment_pending|initializing)
echo "==> Status is healthy/in-progress. Nothing to do."
exit 0
;;
*)
echo "==> Status '${STATUS}' is not healthy; will attempt re-attach."
;;
esac
fi

echo "==> Attaching '${DOMAIN}' to Pages project '${PROJECT}' ..."

RESPONSE=$(curl -sS -H "${AUTH_HDR}" -H "Content-Type: application/json" \
-X POST \
"${API_BASE}/accounts/${ACCOUNT_ID}/pages/projects/${PROJECT}/domains" \
-d "{\"name\":\"${DOMAIN}\"}")

SUCCESS=$(echo "${RESPONSE}" | jq -r '.success')

if [ "${SUCCESS}" != "true" ]; then
ERRORS=$(echo "${RESPONSE}" | jq -r '.errors[]?.message // .errors // empty')
# If the error says the domain already exists, treat as success
if echo "${ERRORS}" | grep -qi "already exists\|already added\|duplicate"; then
echo "==> Domain '${DOMAIN}' already exists (race or previous run). Treating as success."
exit 0
Comment on lines +63 to +65

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 Repair unhealthy Pages domains with PATCH before succeeding

When an existing domain is found in an unhealthy state (deactivated/blocked/error), this script falls through to a POST create call and then treats duplicate-name errors as success. That means the unhealthy domain record is never actually retried/revalidated, so the outage mode can persist across deploys even though this step exits 0. Cloudflare Pages exposes a domain PATCH endpoint specifically to retry validation of an existing domain; without using that path, the intended “repair” behavior is skipped.

Useful? React with 👍 / 👎.

fi
echo "ERROR: Failed to attach domain '${DOMAIN}': ${ERRORS}" >&2
echo "Full response: ${RESPONSE}" >&2
exit 1
fi

echo "==> Domain '${DOMAIN}' attached successfully."
echo "==> Cloudflare will provision the apex DNS record and TLS certificate automatically."

# Verify by listing domains again
echo "==> Current domains on '${PROJECT}':"
curl -sS -H "${AUTH_HDR}" \
"${API_BASE}/accounts/${ACCOUNT_ID}/pages/projects/${PROJECT}/domains" \
| jq -r '.result[]? | "\(.name // .domain // "?") (\(.status // "?"))"'

exit 0
Loading