Extract console pipeline scripts

This commit is contained in:
Haitao Pan 2026-04-12 15:42:02 +08:00
parent 9c6cc4ade5
commit 68102491e2
10 changed files with 172 additions and 64 deletions

View File

@ -79,19 +79,7 @@ jobs:
EVENT_NAME: ${{ github.event_name }}
INPUT_TARGET_HOST: ${{ inputs.target_host }}
INPUT_RUN_APPLY: ${{ inputs.run_apply }}
run: |
if [[ "${EVENT_NAME}" == "workflow_dispatch" ]]; then
target_host="${INPUT_TARGET_HOST}"
run_apply="${INPUT_RUN_APPLY}"
else
target_host="jp-xhttp-contabo.svc.plus"
run_apply="true"
fi
{
printf 'target_host=%s\n' "${target_host}"
printf 'run_apply=%s\n' "${run_apply}"
} >> "${GITHUB_OUTPUT}"
run: bash scripts/github-actions/resolve-workflow-inputs.sh
- name: Compute Image Metadata
id: metadata
@ -102,42 +90,35 @@ jobs:
id: push
env:
REF: ${{ github.ref }}
run: |
if [[ "${REF}" == "refs/heads/main" ]]; then
echo "push_latest=true" >> "${GITHUB_OUTPUT}"
else
echo "push_latest=false" >> "${GITHUB_OUTPUT}"
fi
run: bash scripts/github-actions/resolve-push-latest.sh
build:
name: Build
runs-on: ubuntu-latest
needs: prep
outputs:
image_ref: ${{ needs.prep.outputs.image_ref }}
image_tag: ${{ needs.prep.outputs.image_tag }}
image_ref: ${{ steps.publish.outputs.image_ref }}
image_tag: ${{ steps.publish.outputs.image_tag }}
steps:
- name: Check Out Repository
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Set Up Docker Buildx
run: |
docker buildx create --name console-builder --use >/dev/null 2>&1 || docker buildx use console-builder
docker buildx inspect --bootstrap
run: bash scripts/github-actions/setup-docker-buildx.sh
- name: Log In To GHCR
env:
GHCR_TOKEN: ${{ secrets.GHCR_TOKEN }}
run: |
echo "${GHCR_TOKEN}" | docker login ghcr.io -u "${GHCR_USERNAME}" --password-stdin
run: bash scripts/github-actions/login-ghcr.sh
- name: Publish Frontend Image
id: publish
env:
IMAGE_REF: ${{ needs.prep.outputs.image_ref }}
IMAGE_TAG: ${{ needs.prep.outputs.image_tag }}
IMAGE_LATEST_REF: ${{ needs.prep.outputs.image_latest_ref }}
PUSH_LATEST: ${{ needs.prep.outputs.push_latest }}
run: |
bash scripts/github-actions/build-and-push-frontend-image.sh
run: bash scripts/github-actions/publish-frontend-image.sh
deploy:
name: Deploy
@ -148,7 +129,7 @@ jobs:
env:
TARGET_HOST: ${{ needs.prep.outputs.target_host }}
RUN_APPLY: ${{ needs.prep.outputs.run_apply }}
FRONTEND_IMAGE: ${{ needs.prep.outputs.image_ref }}
FRONTEND_IMAGE: ${{ needs.build.outputs.image_ref }}
steps:
- name: Check Out Repository
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
@ -174,45 +155,13 @@ jobs:
env:
SINGLE_NODE_VPS_SSH_PRIVATE_KEY: ${{ secrets.SINGLE_NODE_VPS_SSH_PRIVATE_KEY }}
TARGET_HOST: ${{ needs.prep.outputs.target_host }}
run: |
mkdir -p ~/.ssh
chmod 700 ~/.ssh
printf '%s\n' "${SINGLE_NODE_VPS_SSH_PRIVATE_KEY}" | tr -d '\r' > ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_rsa
ssh-keyscan -H "${TARGET_HOST}" >> ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts
run: bash scripts/github-actions/configure-ssh-for-deploy.sh
- name: Run Deploy Playbook
working-directory: playbooks
env:
ANSIBLE_HOST_KEY_CHECKING: "False"
run: |
ansible_args=(
-i inventory.ini
deploy_console_svc_plus.yml
-D
-l "${TARGET_HOST}"
-e "FRONTEND_IMAGE=${FRONTEND_IMAGE}"
-e "GHCR_USERNAME=${GHCR_USERNAME}"
-e "GHCR_PASSWORD=${GHCR_PASSWORD}"
-e "INTERNAL_SERVICE_TOKEN=${INTERNAL_SERVICE_TOKEN}"
-e "ACCOUNT_SERVICE_URL=${ACCOUNT_SERVICE_URL}"
-e "PRIMARY_DOMAIN=${PRIMARY_DOMAIN}"
-e "SECONDARY_DOMAIN=${SECONDARY_DOMAIN}"
-e "NEXT_PUBLIC_RUNTIME_ENVIRONMENT=${NEXT_PUBLIC_RUNTIME_ENVIRONMENT}"
-e "NEXT_PUBLIC_RUNTIME_REGION=${NEXT_PUBLIC_RUNTIME_REGION}"
-e "CLOUDFLARE_ZONE_TAG=${CLOUDFLARE_ZONE_TAG}"
-e "CLOUDFLARE_WEB_ANALYTICS_SITE_TAG=${CLOUDFLARE_WEB_ANALYTICS_SITE_TAG}"
-e "CLOUDFLARE_ACCOUNT_ID=${CLOUDFLARE_ACCOUNT_ID}"
-e "CLOUDFLARE_API_TOKEN=${CLOUDFLARE_API_TOKEN}"
-e "CLOUDFLARE_DNS_API_TOKEN=${CLOUDFLARE_DNS_API_TOKEN}"
)
if [[ "${RUN_APPLY}" != "true" ]]; then
ansible_args=(-C "${ansible_args[@]}")
fi
ansible-playbook "${ansible_args[@]}"
run: bash ../scripts/github-actions/run-console-deploy-playbook.sh
validate:
name: Validate
@ -227,4 +176,7 @@ jobs:
- name: Verify Frontend Release
run: |
bash scripts/github-actions/verify-frontend-release.sh "${PRIMARY_DOMAIN}" "${SECONDARY_DOMAIN}"
bash scripts/github-actions/verify-frontend-release.sh \
"${PRIMARY_DOMAIN}" \
"${SECONDARY_DOMAIN}" \
"${{ needs.build.outputs.image_ref }}"

View File

@ -0,0 +1,12 @@
#!/usr/bin/env bash
set -euo pipefail
target_host="${TARGET_HOST:?TARGET_HOST is required}"
private_key="${SINGLE_NODE_VPS_SSH_PRIVATE_KEY:?SINGLE_NODE_VPS_SSH_PRIVATE_KEY is required}"
mkdir -p ~/.ssh
chmod 700 ~/.ssh
printf '%s\n' "${private_key}" | tr -d '\r' > ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_rsa
ssh-keyscan -H "${target_host}" >> ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts

View File

@ -26,6 +26,15 @@ require_env SECONDARY_DOMAIN
GHCR_REGISTRY="${GHCR_REGISTRY:-ghcr.io}"
reject_remote_build_configuration() {
local compose_file="$1"
if grep -Eq '^[[:space:]]*(build|dockerfile):' "${compose_file}"; then
echo "Deployment package must reference prebuilt images only; compose build directives are forbidden." >&2
exit 1
fi
}
WORK_DIR="$(mktemp -d)"
trap 'rm -rf "${WORK_DIR}"' EXIT
@ -40,6 +49,8 @@ bash "${SCRIPT_DIR}/render-frontend-runtime-env.sh" "${RUNTIME_ENV_FILE}"
cp "${DEPLOY_SOURCE_DIR}/docker-compose.yml" "${WORK_DIR}/docker-compose.yml"
cp "${DEPLOY_SOURCE_DIR}/Caddyfile" "${WORK_DIR}/Caddyfile"
reject_remote_build_configuration "${WORK_DIR}/docker-compose.yml"
tar -C "${WORK_DIR}" -czf "${RELEASE_ARCHIVE}" \
docker-compose.yml \
Caddyfile \

View File

@ -0,0 +1,7 @@
#!/usr/bin/env bash
set -euo pipefail
ghcr_token="${GHCR_TOKEN:?GHCR_TOKEN is required}"
ghcr_username="${GHCR_USERNAME:?GHCR_USERNAME is required}"
printf '%s' "${ghcr_token}" | docker login ghcr.io -u "${ghcr_username}" --password-stdin

View File

@ -0,0 +1,7 @@
#!/usr/bin/env bash
set -euo pipefail
bash scripts/github-actions/build-and-push-frontend-image.sh
printf 'image_ref=%s\n' "${IMAGE_REF:?IMAGE_REF is required}" >> "${GITHUB_OUTPUT}"
printf 'image_tag=%s\n' "${IMAGE_TAG:?IMAGE_TAG is required}" >> "${GITHUB_OUTPUT}"

View File

@ -0,0 +1,10 @@
#!/usr/bin/env bash
set -euo pipefail
ref="${REF:?REF is required}"
if [[ "${ref}" == "refs/heads/main" ]]; then
echo "push_latest=true" >> "${GITHUB_OUTPUT}"
else
echo "push_latest=false" >> "${GITHUB_OUTPUT}"
fi

View File

@ -0,0 +1,17 @@
#!/usr/bin/env bash
set -euo pipefail
event_name="${EVENT_NAME:?EVENT_NAME is required}"
input_target_host="${INPUT_TARGET_HOST:-}"
input_run_apply="${INPUT_RUN_APPLY:-}"
if [[ "${event_name}" == "workflow_dispatch" ]]; then
target_host="${input_target_host}"
run_apply="${input_run_apply}"
else
target_host="jp-xhttp-contabo.svc.plus"
run_apply="true"
fi
printf 'target_host=%s\n' "${target_host}" >> "${GITHUB_OUTPUT}"
printf 'run_apply=%s\n' "${run_apply}" >> "${GITHUB_OUTPUT}"

View File

@ -0,0 +1,33 @@
#!/usr/bin/env bash
set -euo pipefail
target_host="${TARGET_HOST:?TARGET_HOST is required}"
run_apply="${RUN_APPLY:?RUN_APPLY is required}"
frontend_image="${FRONTEND_IMAGE:?FRONTEND_IMAGE is required}"
ansible_args=(
-i inventory.ini
deploy_console_svc_plus.yml
-D
-l "${target_host}"
-e "FRONTEND_IMAGE=${frontend_image}"
-e "GHCR_USERNAME=${GHCR_USERNAME:?GHCR_USERNAME is required}"
-e "GHCR_PASSWORD=${GHCR_PASSWORD:?GHCR_PASSWORD is required}"
-e "INTERNAL_SERVICE_TOKEN=${INTERNAL_SERVICE_TOKEN:?INTERNAL_SERVICE_TOKEN is required}"
-e "ACCOUNT_SERVICE_URL=${ACCOUNT_SERVICE_URL:?ACCOUNT_SERVICE_URL is required}"
-e "PRIMARY_DOMAIN=${PRIMARY_DOMAIN:?PRIMARY_DOMAIN is required}"
-e "SECONDARY_DOMAIN=${SECONDARY_DOMAIN:?SECONDARY_DOMAIN is required}"
-e "NEXT_PUBLIC_RUNTIME_ENVIRONMENT=${NEXT_PUBLIC_RUNTIME_ENVIRONMENT:?NEXT_PUBLIC_RUNTIME_ENVIRONMENT is required}"
-e "NEXT_PUBLIC_RUNTIME_REGION=${NEXT_PUBLIC_RUNTIME_REGION:?NEXT_PUBLIC_RUNTIME_REGION is required}"
-e "CLOUDFLARE_ZONE_TAG=${CLOUDFLARE_ZONE_TAG:?CLOUDFLARE_ZONE_TAG is required}"
-e "CLOUDFLARE_WEB_ANALYTICS_SITE_TAG=${CLOUDFLARE_WEB_ANALYTICS_SITE_TAG:?CLOUDFLARE_WEB_ANALYTICS_SITE_TAG is required}"
-e "CLOUDFLARE_ACCOUNT_ID=${CLOUDFLARE_ACCOUNT_ID:?CLOUDFLARE_ACCOUNT_ID is required}"
-e "CLOUDFLARE_API_TOKEN=${CLOUDFLARE_API_TOKEN:?CLOUDFLARE_API_TOKEN is required}"
-e "CLOUDFLARE_DNS_API_TOKEN=${CLOUDFLARE_DNS_API_TOKEN:?CLOUDFLARE_DNS_API_TOKEN is required}"
)
if [[ "${run_apply}" != "true" ]]; then
ansible_args=(-C "${ansible_args[@]}")
fi
ansible-playbook "${ansible_args[@]}"

View File

@ -0,0 +1,5 @@
#!/usr/bin/env bash
set -euo pipefail
docker buildx create --name console-builder --use >/dev/null 2>&1 || docker buildx use console-builder
docker buildx inspect --bootstrap

View File

@ -3,9 +3,22 @@ set -euo pipefail
PRIMARY_DOMAIN="${1:?usage: verify-frontend-release.sh <primary-domain> <secondary-domain>}"
SECONDARY_DOMAIN="${2:?usage: verify-frontend-release.sh <primary-domain> <secondary-domain>}"
EXPECTED_IMAGE_REF="${3:?usage: verify-frontend-release.sh <primary-domain> <secondary-domain> <expected-image-ref>}"
primary_url="https://${PRIMARY_DOMAIN}"
secondary_url="https://${SECONDARY_DOMAIN}"
expected_image_tag_match="$(printf '%s' "${EXPECTED_IMAGE_REF}" | sed -nE 's#^.+:([^:@]+)$#\1#p')"
EXPECTED_IMAGE_TAG="${expected_image_tag_match}"
if [[ -z "${EXPECTED_IMAGE_TAG}" ]]; then
echo "Expected image ref must include a tag, got: ${EXPECTED_IMAGE_REF}" >&2
exit 1
fi
if [[ ! "${EXPECTED_IMAGE_TAG}" =~ ^[0-9a-f]{7,40}$ ]]; then
echo "Expected image tag must contain a commit id, got: ${EXPECTED_IMAGE_TAG}" >&2
exit 1
fi
curl -fsSIL "${primary_url}" >/dev/null
@ -25,3 +38,44 @@ fi
curl -fsSIL "${primary_url}${asset_path}" >/dev/null
printf 'verified static asset: %s%s\n' "${primary_url}" "${asset_path}"
release_payload="$(curl -fsSL "${primary_url}/api/ping")"
release_metadata="$(
RELEASE_PAYLOAD="${release_payload}" python3 - <<'PY'
import json
import os
payload = json.loads(os.environ["RELEASE_PAYLOAD"])
print(payload.get("releaseImageRef", ""))
print(payload.get("releaseImageTag", ""))
print(payload.get("releaseCommit", ""))
PY
)"
mapfile -t release_lines <<< "${release_metadata}"
actual_image_ref="${release_lines[0]-}"
actual_image_tag="${release_lines[1]-}"
actual_release_commit="${release_lines[2]-}"
if [[ -z "${actual_image_ref}" || -z "${actual_image_tag}" || -z "${actual_release_commit}" ]]; then
echo "Remote release metadata is incomplete: ${release_payload}" >&2
exit 1
fi
if [[ "${actual_image_ref}" != "${EXPECTED_IMAGE_REF}" ]]; then
echo "Remote image ref mismatch: expected ${EXPECTED_IMAGE_REF}, got ${actual_image_ref}" >&2
exit 1
fi
if [[ "${actual_image_tag}" != "${EXPECTED_IMAGE_TAG}" ]]; then
echo "Remote image tag mismatch: expected ${EXPECTED_IMAGE_TAG}, got ${actual_image_tag}" >&2
exit 1
fi
if [[ "${actual_release_commit}" != "${EXPECTED_IMAGE_TAG}" ]]; then
echo "Remote release commit mismatch: expected ${EXPECTED_IMAGE_TAG}, got ${actual_release_commit}" >&2
exit 1
fi
printf 'verified release image: %s\n' "${actual_image_ref}"
printf 'verified release commit: %s\n' "${actual_release_commit}"