From 0f0e0577797cea3844eedbef1f8c03f7722920d9 Mon Sep 17 00:00:00 2001 From: XiaoSeS <87064762+XiaoSeS@users.noreply.github.com> Date: Thu, 6 Aug 2026 15:54:45 +0800 Subject: [PATCH] fix(deploy): configure HK sub-path validation Signed-off-by: XiaoSeS <87064762+XiaoSeS@users.noreply.github.com> --- docs/pr-batch-test-runtime.md | 33 ++++++++++++- scripts/deploy-test-runtime.sh | 84 +++++++++++++++++++++++++++++++--- 2 files changed, 108 insertions(+), 9 deletions(-) diff --git a/docs/pr-batch-test-runtime.md b/docs/pr-batch-test-runtime.md index 6f6d1c88..318ff1ac 100644 --- a/docs/pr-batch-test-runtime.md +++ b/docs/pr-batch-test-runtime.md @@ -20,8 +20,9 @@ When you trigger the workflow manually, it: 6. builds `server`, `web`, and `scanner` images for `linux/amd64` 7. pushes both a floating tag and an immutable tag to GHCR 8. SSHes into the HK test machine as a dedicated deploy user -9. calls a root-owned deployment wrapper through `sudo` -10. updates `/opt/skillhub-runtime/.env.release` and runs `docker compose pull && docker compose up -d` +9. updates the selected non-secret runtime fields in `/opt/skillhub-runtime/.env.release` +10. calls a root-owned deployment wrapper through `sudo` +11. runs `docker compose pull && docker compose up -d` through the remote wrapper The floating tag is the shared environment channel. By default it is `manual-test-hk`. Each run also pushes an immutable tag for traceability: @@ -59,9 +60,37 @@ Open the workflow in GitHub Actions and fill in: - `pr_numbers`: a comma-separated or newline-separated list such as `123, 124, 130` - `base_ref`: usually `main` - `deploy_channel`: keep the default `manual-test-hk` for the shared test machine +- `public_url`: the public URL to write into `SKILLHUB_PUBLIC_BASE_URL` +- `web_base_path`: optional sub-path to write into `SKILLHUB_WEB_BASE_PATH`; when set, + the workflow also writes `SKILLHUB_WEB_API_BASE_URL` to the same path without the + trailing slash The merge order matters. If PR `124` depends on `123`, list `123` first. +For sub-path verification, use a unique deploy channel so Compose recreates the +containers even if the shared floating tag already exists: + +```text +deploy_channel=manual-test-hk- +public_url=https://skill.xf-yun.com.cn/skillhub +web_base_path=/skillhub/ +``` + +The workflow must verify that `/skillhub/runtime-config.js` returns the generated +JavaScript runtime config, not the SPA fallback HTML. + +## OSS quickstart synchronization + +The one-line quickstart downloads `runtime.sh`, and that script downloads: + +- `compose.release.yml` +- `.env.release.example` + +When runtime flags, release Compose wiring, or release env defaults change, publish +all three files to the OSS origin together. If `--aliyun` or another mirror registry +is used, the corresponding `skillhub-server`, `skillhub-web`, and +`skillhub-scanner` image tags must also be mirrored. + ## Runtime metadata on the server After deployment, the workflow writes a small metadata file here: diff --git a/scripts/deploy-test-runtime.sh b/scripts/deploy-test-runtime.sh index f0d74627..7b5a25ec 100755 --- a/scripts/deploy-test-runtime.sh +++ b/scripts/deploy-test-runtime.sh @@ -136,6 +136,82 @@ pr_csv="$4" run_url="${5:-}" public_url="${6:-}" web_base_path="${7:-}" +runtime_dir="/opt/skillhub-runtime" +env_file="${runtime_dir}/.env.release" + +set_env_value() { + local key="$1" + local value="$2" + local tmp + + tmp="$(mktemp "${env_file}.tmp.XXXXXX")" + if grep -q "^${key}=" "${env_file}"; then + sed "s|^${key}=.*|${key}=${value}|" "${env_file}" >"${tmp}" + else + cp "${env_file}" "${tmp}" + printf '%s=%s\n' "${key}" "${value}" >>"${tmp}" + fi + mv "${tmp}" "${env_file}" +} + +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}" +} + +[[ -f "${env_file}" ]] || { echo "Missing HK runtime env file: ${env_file}" >&2; exit 1; } + +if [[ -n "${public_url}" ]]; then + if [[ ! "${public_url}" =~ ^https?://[^[:space:]/?#]+(:[0-9]+)?(/[^[:space:]?#]*)?$ ]]; then + echo "Invalid public URL: ${public_url}" >&2 + exit 1 + fi + 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 deploy_status=0 sudo /usr/local/bin/skillhub-test-deploy \ @@ -153,13 +229,7 @@ if [[ ! -r /opt/skillhub-runtime/manual-test-deployment.txt ]] || \ fi web_health_paths=("/nginx-health") -if [[ -n "${web_base_path}" && "${web_base_path}" != "/" ]]; then - normalized_web_base_path="${web_base_path}" - case "${normalized_web_base_path}" in - /*/) ;; - /*) normalized_web_base_path="${normalized_web_base_path}/" ;; - *) normalized_web_base_path="/${normalized_web_base_path}/" ;; - esac +if [[ -n "${normalized_web_base_path}" && "${normalized_web_base_path}" != "/" ]]; then web_health_paths+=("${normalized_web_base_path%/}/nginx-health") fi