refactor(ci): matrix frontend dns updates
This commit is contained in:
parent
1708a233e9
commit
8c9c83c845
@ -43,6 +43,8 @@ NEXT_PUBLIC_GISCUS_CATEGORY_ID=DIC_kwDOQoiZ_s4Clj_q
|
||||
INTERNAL_SERVICE_TOKEN=
|
||||
|
||||
# Cloudflare Web Analytics GraphQL credentials
|
||||
CLOUDFLARE_DNS_API_TOKEN=
|
||||
CLOUDFLARE_DNS_ZONE_TAG=
|
||||
CLOUDFLARE_API_TOKEN=
|
||||
CLOUDFLARE_ACCOUNT_ID=
|
||||
CLOUDFLARE_WEB_ANALYTICS_SITE_TAG=
|
||||
|
||||
@ -111,10 +111,16 @@ jobs:
|
||||
build-args: ${{ steps.build_args.outputs.build_args }}
|
||||
|
||||
stage-2-update-dns:
|
||||
name: "2. Update Frontend DNS"
|
||||
name: "2. Update Frontend DNS (${{ matrix.domain }})"
|
||||
runs-on: ubuntu-latest
|
||||
needs: stage-1-build-image
|
||||
environment: production
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
domain:
|
||||
- cn.svc.plus
|
||||
- cn.onwalk.net
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
|
||||
@ -122,12 +128,10 @@ jobs:
|
||||
- name: Ensure Cloudflare DNS Records
|
||||
env:
|
||||
CLOUDFLARE_DNS_API_TOKEN: ${{ secrets.CLOUDFLARE_DNS_API_TOKEN }}
|
||||
CLOUDFLARE_ZONE_TAG: ${{ vars.CLOUDFLARE_ZONE_TAG }}
|
||||
run: >
|
||||
bash scripts/github-actions/ensure-frontend-dns.sh
|
||||
"${{ env.DEPLOY_HOST }}"
|
||||
"${{ env.PRIMARY_DOMAIN }}"
|
||||
"${{ env.SECONDARY_DOMAIN }}"
|
||||
"${{ matrix.domain }}"
|
||||
|
||||
stage-3-deploy:
|
||||
name: "3. Deploy Frontend Stack"
|
||||
|
||||
@ -88,6 +88,8 @@ Repository/environment variables recommended:
|
||||
- `NEXT_PUBLIC_GISCUS_*`
|
||||
- `NEXT_PUBLIC_STRIPE_*`
|
||||
- `NEXT_PUBLIC_PAYPAL_CLIENT_ID`
|
||||
- `CLOUDFLARE_ZONE_TAG` if homepage Cloudflare analytics are enabled at runtime
|
||||
- `CLOUDFLARE_DNS_ZONE_TAG` only for single-domain manual DNS override; the GitHub Actions DNS stage resolves zones from each domain automatically
|
||||
|
||||
## Release Flow
|
||||
|
||||
@ -95,10 +97,11 @@ Repository/environment variables recommended:
|
||||
2. GitHub Actions clones `knowledge/`.
|
||||
3. Docker builds the frontend image with the public `NEXT_PUBLIC_*` values needed at build time.
|
||||
4. The image is pushed to GHCR.
|
||||
5. The workflow renders `.env.runtime`.
|
||||
6. The workflow uploads `docker-compose.yml`, `Caddyfile`, and `.env.runtime` to the host.
|
||||
7. The host pulls the new image, refreshes the static asset volume, and starts `dashboard + caddy`.
|
||||
8. The workflow verifies `cn.svc.plus` and `cn.onwalk.net`.
|
||||
5. The workflow runs a matrix DNS stage, updating one public domain per job.
|
||||
6. The workflow renders `.env.runtime`.
|
||||
7. The workflow uploads `docker-compose.yml`, `Caddyfile`, and `.env.runtime` to the host.
|
||||
8. The host pulls the new image, refreshes the static asset volume, and starts `dashboard + caddy`.
|
||||
9. The workflow verifies `cn.svc.plus` and `cn.onwalk.net`.
|
||||
|
||||
## Verification Commands
|
||||
|
||||
|
||||
@ -7,9 +7,14 @@ if [[ "${1-}" == "--dry-run" ]]; then
|
||||
shift
|
||||
fi
|
||||
|
||||
TARGET_IP="${1:?usage: ensure-frontend-dns.sh [--dry-run] <target-ip> <primary-domain> <secondary-domain>}"
|
||||
PRIMARY_DOMAIN="${2:?usage: ensure-frontend-dns.sh [--dry-run] <target-ip> <primary-domain> <secondary-domain>}"
|
||||
SECONDARY_DOMAIN="${3:?usage: ensure-frontend-dns.sh [--dry-run] <target-ip> <primary-domain> <secondary-domain>}"
|
||||
TARGET_IP="${1:?usage: ensure-frontend-dns.sh [--dry-run] <target-ip> <domain> [domain...]}"
|
||||
shift
|
||||
|
||||
if [[ "$#" -lt 1 ]]; then
|
||||
echo "usage: ensure-frontend-dns.sh [--dry-run] <target-ip> <domain> [domain...]" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
PROXIED="${CLOUDFLARE_PROXIED:-true}"
|
||||
|
||||
require_env() {
|
||||
@ -26,6 +31,45 @@ json_get() {
|
||||
python3 -c "import json,sys; data=json.load(sys.stdin); ${expression}"
|
||||
}
|
||||
|
||||
cloudflare_api_get() {
|
||||
local path="$1"
|
||||
curl -fsS \
|
||||
-H "Authorization: Bearer ${CLOUDFLARE_DNS_API_TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
"https://api.cloudflare.com/client/v4${path}"
|
||||
}
|
||||
|
||||
resolve_zone_for_domain() {
|
||||
local domain="$1"
|
||||
local candidate="${domain%.}"
|
||||
local response
|
||||
local zone_id
|
||||
|
||||
if [[ -n "${CLOUDFLARE_DNS_ZONE_TAG-}" ]]; then
|
||||
printf '%s\t%s\n' "${CLOUDFLARE_DNS_ZONE_TAG}" "override"
|
||||
return 0
|
||||
fi
|
||||
|
||||
while [[ "${candidate}" == *.* ]]; do
|
||||
response="$(cloudflare_api_get "/zones?name=${candidate}")"
|
||||
if [[ "$(printf '%s' "${response}" | json_get 'print("true" if data.get("success") else "false")')" != "true" ]]; then
|
||||
echo "Failed to query Cloudflare zones for ${candidate}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
zone_id="$(printf '%s' "${response}" | json_get 'result=data.get("result", []); print(result[0]["id"] if result else "")')"
|
||||
if [[ -n "${zone_id}" ]]; then
|
||||
printf '%s\t%s\n' "${zone_id}" "${candidate}"
|
||||
return 0
|
||||
fi
|
||||
|
||||
candidate="${candidate#*.}"
|
||||
done
|
||||
|
||||
echo "Unable to resolve Cloudflare zone for ${domain}" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
dns_payload() {
|
||||
local domain="$1"
|
||||
DOMAIN="${domain}" TARGET_IP="${TARGET_IP}" PROXIED="${PROXIED}" python3 -c \
|
||||
@ -37,6 +81,8 @@ upsert_record() {
|
||||
local payload
|
||||
local response
|
||||
local record_id
|
||||
local zone_id
|
||||
local zone_name
|
||||
|
||||
payload="$(dns_payload "${domain}")"
|
||||
|
||||
@ -46,12 +92,11 @@ upsert_record() {
|
||||
return 0
|
||||
fi
|
||||
|
||||
response="$(
|
||||
curl -fsS \
|
||||
-H "Authorization: Bearer ${CLOUDFLARE_DNS_API_TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
"https://api.cloudflare.com/client/v4/zones/${CLOUDFLARE_ZONE_TAG}/dns_records?type=A&name=${domain}"
|
||||
)"
|
||||
IFS=$'\t' read -r zone_id zone_name <<EOF
|
||||
$(resolve_zone_for_domain "${domain}")
|
||||
EOF
|
||||
|
||||
response="$(cloudflare_api_get "/zones/${zone_id}/dns_records?type=A&name=${domain}")"
|
||||
|
||||
if [[ "$(printf '%s' "${response}" | json_get 'print("true" if data.get("success") else "false")')" != "true" ]]; then
|
||||
echo "Failed to query Cloudflare DNS records for ${domain}" >&2
|
||||
@ -66,7 +111,7 @@ upsert_record() {
|
||||
-H "Authorization: Bearer ${CLOUDFLARE_DNS_API_TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
--data "${payload}" \
|
||||
"https://api.cloudflare.com/client/v4/zones/${CLOUDFLARE_ZONE_TAG}/dns_records/${record_id}"
|
||||
"https://api.cloudflare.com/client/v4/zones/${zone_id}/dns_records/${record_id}"
|
||||
)"
|
||||
else
|
||||
response="$(
|
||||
@ -74,7 +119,7 @@ upsert_record() {
|
||||
-H "Authorization: Bearer ${CLOUDFLARE_DNS_API_TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
--data "${payload}" \
|
||||
"https://api.cloudflare.com/client/v4/zones/${CLOUDFLARE_ZONE_TAG}/dns_records"
|
||||
"https://api.cloudflare.com/client/v4/zones/${zone_id}/dns_records"
|
||||
)"
|
||||
fi
|
||||
|
||||
@ -83,13 +128,13 @@ upsert_record() {
|
||||
exit 1
|
||||
fi
|
||||
|
||||
printf 'updated: %s -> %s\n' "${domain}" "${TARGET_IP}"
|
||||
printf 'updated: %s -> %s (zone %s)\n' "${domain}" "${TARGET_IP}" "${zone_name}"
|
||||
}
|
||||
|
||||
if [[ "${DRY_RUN}" != "true" ]]; then
|
||||
require_env CLOUDFLARE_DNS_API_TOKEN
|
||||
require_env CLOUDFLARE_ZONE_TAG
|
||||
fi
|
||||
|
||||
upsert_record "${PRIMARY_DOMAIN}"
|
||||
upsert_record "${SECONDARY_DOMAIN}"
|
||||
for domain in "$@"; do
|
||||
upsert_record "${domain}"
|
||||
done
|
||||
|
||||
Loading…
Reference in New Issue
Block a user