From 79be943f0f74fe78136e36f084e653f65e3b39d7 Mon Sep 17 00:00:00 2001 From: ylhu16 Date: Thu, 6 Aug 2026 15:38:25 +0800 Subject: [PATCH] fix(deploy): validate HK sub-path runtime Signed-off-by: ylhu16 --- .github/workflows/pr-batch-test-deploy.yml | 16 ++- .github/workflows/pr-scripts.yml | 1 + scripts/deploy-test-runtime.sh | 39 ++++---- scripts/skillhub-test-deploy-remote.sh | 109 ++++++++++++++++++++- scripts/tests/workflow-security-test.sh | 2 + 5 files changed, 148 insertions(+), 19 deletions(-) diff --git a/.github/workflows/pr-batch-test-deploy.yml b/.github/workflows/pr-batch-test-deploy.yml index f61cdef6..c908ba64 100644 --- a/.github/workflows/pr-batch-test-deploy.yml +++ b/.github/workflows/pr-batch-test-deploy.yml @@ -17,6 +17,16 @@ on: required: false default: manual-test-hk type: string + public_url: + description: "Public URL configured on the HK runtime" + required: false + default: https://skill.xf-yun.com.cn + type: string + web_base_path: + description: "Optional Web UI base path for HK runtime, for example /skillhub/" + required: false + default: "" + type: string concurrency: group: pr-batch-test-runtime @@ -145,7 +155,9 @@ jobs: --immutable-tag "${{ steps.batch.outputs.immutable_tag }}" \ --merged-sha "${{ steps.batch.outputs.merged_sha }}" \ --pr-csv "${{ steps.batch.outputs.pr_csv }}" \ - --run-url "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}" + --run-url "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}" \ + --public-url "${{ inputs.public_url }}" \ + --web-base-path "${{ inputs.web_base_path }}" - name: Publish final summary run: | @@ -153,6 +165,8 @@ jobs: echo "### HK manual test runtime updated" echo echo "- URL: \`https://skill.xf-yun.com.cn\`" + echo "- Public URL: \`${{ inputs.public_url }}\`" + echo "- Web base path: \`${{ inputs.web_base_path || '/' }}\`" echo "- Base ref: \`${{ steps.batch.outputs.base_ref }}\`" echo "- Floating tag: \`${{ steps.batch.outputs.deploy_tag }}\`" echo "- Immutable tag: \`${{ steps.batch.outputs.immutable_tag }}\`" diff --git a/.github/workflows/pr-scripts.yml b/.github/workflows/pr-scripts.yml index 937dc05d..1972987f 100644 --- a/.github/workflows/pr-scripts.yml +++ b/.github/workflows/pr-scripts.yml @@ -13,6 +13,7 @@ on: - 'web/docker-entrypoint.d/**' - '.github/workflows/pr-cli.yml' - '.github/workflows/pr-e2e.yml' + - '.github/workflows/pr-batch-test-deploy.yml' - '.github/workflows/pr-helm-chart.yml' - '.github/workflows/pr-tests.yml' - '.github/workflows/publish-chart.yml' diff --git a/scripts/deploy-test-runtime.sh b/scripts/deploy-test-runtime.sh index 47a2f3cc..51afcf90 100755 --- a/scripts/deploy-test-runtime.sh +++ b/scripts/deploy-test-runtime.sh @@ -16,6 +16,8 @@ Options: --merged-sha Synthetic merge commit SHA --pr-csv Comma-separated PR numbers --run-url GitHub Actions run URL + --public-url Public URL configured on the remote runtime + --web-base-path Optional Web UI base path, for example /skillhub/ EOF } @@ -28,6 +30,8 @@ immutable_tag="" merged_sha="" pr_csv="" run_url="" +public_url="" +web_base_path="" while [[ $# -gt 0 ]]; do case "$1" in @@ -76,6 +80,16 @@ while [[ $# -gt 0 ]]; do run_url="$2" shift 2 ;; + --public-url) + [[ $# -ge 2 ]] || { echo "Missing value for --public-url" >&2; exit 1; } + public_url="$2" + shift 2 + ;; + --web-base-path) + [[ $# -ge 2 ]] || { echo "Missing value for --web-base-path" >&2; exit 1; } + web_base_path="$2" + shift 2 + ;; --help|-h) usage exit 0 @@ -105,24 +119,15 @@ ssh_opts=( -p "${ssh_port}" ) -ssh "${ssh_opts[@]}" "${ssh_user}@${ssh_host}" bash -s -- \ +script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +remote_script="${script_dir}/skillhub-test-deploy-remote.sh" +[[ -f "${remote_script}" ]] || { echo "Missing remote deploy script: ${remote_script}" >&2; exit 1; } + +ssh "${ssh_opts[@]}" "${ssh_user}@${ssh_host}" sudo bash -s -- \ "${deploy_tag}" \ "${immutable_tag}" \ "${merged_sha}" \ "${pr_csv}" \ - "${run_url}" <<'EOF' -set -euo pipefail - -deploy_tag="$1" -immutable_tag="$2" -merged_sha="$3" -pr_csv="$4" -run_url="${5:-}" - -sudo /usr/local/bin/skillhub-test-deploy \ - --deploy-tag "${deploy_tag}" \ - --immutable-tag "${immutable_tag}" \ - --merged-sha "${merged_sha}" \ - --pr-csv "${pr_csv}" \ - --run-url "${run_url}" -EOF + "${run_url}" \ + "${public_url}" \ + "${web_base_path}" <"${remote_script}" diff --git a/scripts/skillhub-test-deploy-remote.sh b/scripts/skillhub-test-deploy-remote.sh index 08dde944..8b1a1551 100644 --- a/scripts/skillhub-test-deploy-remote.sh +++ b/scripts/skillhub-test-deploy-remote.sh @@ -12,6 +12,8 @@ Options: --merged-sha Synthetic merge commit SHA --pr-csv Comma-separated PR numbers --run-url GitHub Actions run URL + --public-url Public URL configured on the remote runtime + --web-base-path Optional Web UI base path, for example /skillhub/ EOF } @@ -21,6 +23,20 @@ immutable_tag="" merged_sha="" pr_csv="" run_url="" +public_url="" +web_base_path="" + +if [[ $# -ge 7 && "${1:-}" != --* ]]; then + set -- \ + --deploy-tag "$1" \ + --immutable-tag "$2" \ + --merged-sha "$3" \ + --pr-csv "$4" \ + --run-url "$5" \ + --public-url "$6" \ + --web-base-path "$7" \ + "${@:8}" +fi while [[ $# -gt 0 ]]; do case "$1" in @@ -49,6 +65,16 @@ while [[ $# -gt 0 ]]; do run_url="$2" shift 2 ;; + --public-url) + [[ $# -ge 2 ]] || { echo "Missing value for --public-url" >&2; exit 1; } + public_url="$2" + shift 2 + ;; + --web-base-path) + [[ $# -ge 2 ]] || { echo "Missing value for --web-base-path" >&2; exit 1; } + web_base_path="$2" + shift 2 + ;; --help|-h) usage exit 0 @@ -89,6 +115,11 @@ if [[ -n "${run_url}" && ! "${run_url}" =~ ^https://github\.com/.+/actions/runs/ exit 1 fi +if [[ -n "${public_url}" && ! "${public_url}" =~ ^https?://[^[:space:]/?#]+(:[0-9]+)?(/[^[:space:]?#]*)?$ ]]; then + echo "Invalid public URL: ${public_url}" >&2 + exit 1 +fi + set_env_value() { key="$1" value="$2" @@ -116,6 +147,44 @@ get_env_value() { fi } +normalize_base_path() { + local value="$1" + + if [[ -z "${value}" || "${value}" == "/" ]]; then + printf '/' + return 0 + fi + + case "${value}" in + /*/) ;; + /*) value="${value}/" ;; + *) value="/${value}/" ;; + esac + + case "${value}" in + *//*|*[!A-Za-z0-9._~/-]*) + echo "Invalid web base path: ${value}" >&2 + exit 1 + ;; + */./*|*/../*) + echo "Web base path must not contain '.' or '..' path segments: ${value}" >&2 + exit 1 + ;; + esac + + local first_segment + first_segment="${value#/}" + first_segment="${first_segment%%/*}" + case "${first_segment}" in + api|oauth2|login|assets|registry|nginx-health|.well-known|runtime-config.js) + echo "Web base path must not start with reserved SkillHub path segment: ${first_segment}" >&2 + exit 1 + ;; + esac + + printf '%s' "${value}" +} + wait_for_postgres_ready() { postgres_user="$1" postgres_db="$2" @@ -134,6 +203,23 @@ wait_for_postgres_ready() { exit 1 } +wait_for_web_ready() { + local web_port="$1" + local health_path="$2" + + for attempt in $(seq 1 60); do + if curl -fsS "http://127.0.0.1:${web_port}${health_path}" >/dev/null 2>&1; then + return 0 + fi + + sleep 2 + done + + echo "Web did not become ready in time: http://127.0.0.1:${web_port}${health_path}" >&2 + docker compose --env-file .env.release -f compose.release.yml logs web >&2 || true + exit 1 +} + ensure_postgres_password_matches_env() { postgres_user="$(get_env_value "POSTGRES_USER" "skillhub")" postgres_db="$(get_env_value "POSTGRES_DB" "skillhub")" @@ -170,6 +256,21 @@ cp .env.release ".env.release.bak.$(date +%Y%m%d%H%M%S)" set_env_value "SKILLHUB_VERSION" "${deploy_tag}" +if [[ -n "${public_url}" ]]; then + set_env_value "SKILLHUB_PUBLIC_BASE_URL" "${public_url%/}" +fi + +normalized_web_base_path="" +if [[ -n "${web_base_path}" ]]; then + normalized_web_base_path="$(normalize_base_path "${web_base_path}")" + set_env_value "SKILLHUB_WEB_BASE_PATH" "${normalized_web_base_path}" + if [[ "${normalized_web_base_path}" == "/" ]]; then + set_env_value "SKILLHUB_WEB_API_BASE_URL" "" + else + set_env_value "SKILLHUB_WEB_API_BASE_URL" "${normalized_web_base_path%/}" + fi +fi + cat > manual-test-deployment.txt </dev/null -curl -fsS "http://127.0.0.1:${web_port}/nginx-health" >/dev/null +web_health_path="/nginx-health" +if [[ -n "${normalized_web_base_path}" && "${normalized_web_base_path}" != "/" ]]; then + web_health_path="${normalized_web_base_path%/}/nginx-health" +fi +wait_for_web_ready "${web_port}" "${web_health_path}" diff --git a/scripts/tests/workflow-security-test.sh b/scripts/tests/workflow-security-test.sh index 281d4092..2d60cc1e 100755 --- a/scripts/tests/workflow-security-test.sh +++ b/scripts/tests/workflow-security-test.sh @@ -55,6 +55,8 @@ grep -Fq '.github/workflows/pr-cli.yml' "$PR_SCRIPTS_WORKFLOW" \ || fail "pr-scripts must run when PR CLI workflow changes" grep -Fq '.github/workflows/pr-e2e.yml' "$PR_SCRIPTS_WORKFLOW" \ || fail "pr-scripts must run when PR E2E workflow changes" +grep -Fq '.github/workflows/pr-batch-test-deploy.yml' "$PR_SCRIPTS_WORKFLOW" \ + || fail "pr-scripts must run when PR batch deploy workflow changes" grep -Fq '.github/workflows/pr-helm-chart.yml' "$PR_SCRIPTS_WORKFLOW" \ || fail "pr-scripts must run when PR Helm Chart workflow changes" grep -Fq '.github/workflows/pr-tests.yml' "$PR_SCRIPTS_WORKFLOW" \