diff --git a/README.md b/README.md index 34555dda..9dbe9264 100644 --- a/README.md +++ b/README.md @@ -246,6 +246,9 @@ curl -fsSL https://imageless.oss-cn-beijing.aliyuncs.com/runtime.sh | sh -s -- u # Aliyun mirror (recommended for users in China) curl -fsSL https://imageless.oss-cn-beijing.aliyuncs.com/runtime.sh | sh -s -- up --aliyun --public-url https://skillhub.your-company.com --version latest + +# Sub-path deployment behind a reverse proxy +curl -fsSL https://imageless.oss-cn-beijing.aliyuncs.com/runtime.sh | sh -s -- up --public-url https://skillhub.your-company.com/skillhub --base-path /skillhub/ ``` **Deployment parameters:** @@ -253,6 +256,7 @@ curl -fsSL https://imageless.oss-cn-beijing.aliyuncs.com/runtime.sh | sh -s -- u | Parameter | Description | Example | |-----------|-------------|---------| | `--public-url ` | Public access URL (recommended) | `--public-url https://skill.example.com` | +| `--base-path ` | Serve the Web UI under a reverse-proxy sub-path. Also sets same-origin API routing for that path. | `--base-path /skillhub/` | | `--version ` | Specific image tag | `--version v0.2.0` | | `--aliyun` | Use Aliyun mirror (China) | `--aliyun` | | `--home ` | Runtime directory | `--home /opt/skillhub` | @@ -260,6 +264,12 @@ curl -fsSL https://imageless.oss-cn-beijing.aliyuncs.com/runtime.sh | sh -s -- u > **Important**: Configure `--public-url` for production deployments to ensure CLI install commands and Agent setup instructions display the correct URLs. +For sub-path deployments, keep the public URL and base path aligned. For example, +`--public-url https://skill.example.com/skillhub --base-path /skillhub/` writes +`SKILLHUB_PUBLIC_BASE_URL=https://skill.example.com/skillhub`, +`SKILLHUB_WEB_BASE_PATH=/skillhub/`, and `SKILLHUB_WEB_API_BASE_URL=/skillhub` +into the runtime environment. + **Manual deployment:** 1. Copy the runtime environment template. @@ -308,6 +318,9 @@ enables the bootstrap admin by default, so zero-config quickstart via Recommended production baseline: - set `SKILLHUB_PUBLIC_BASE_URL` to the final HTTPS entrypoint +- if the service is published under a sub-path such as `/skillhub/`, set + `SKILLHUB_WEB_BASE_PATH=/skillhub/` and `SKILLHUB_WEB_API_BASE_URL=/skillhub` + as well, or use `runtime.sh --base-path /skillhub/` - keep PostgreSQL / Redis bound to `127.0.0.1` - use external S3 / OSS via `SKILLHUB_STORAGE_S3_*` - change `BOOTSTRAP_ADMIN_PASSWORD` to a strong password (`validate-release-config.sh` rejects the default `ChangeMe!2026`) diff --git a/README_zh.md b/README_zh.md index c8132525..e7eb5787 100644 --- a/README_zh.md +++ b/README_zh.md @@ -235,6 +235,9 @@ curl -fsSL https://imageless.oss-cn-beijing.aliyuncs.com/runtime.sh | sh -s -- u # 阿里云镜像(国内推荐) curl -fsSL https://imageless.oss-cn-beijing.aliyuncs.com/runtime.sh | sh -s -- up --aliyun --public-url https://skillhub.your-company.com --version latest + +# 通过反向代理部署到子路径 +curl -fsSL https://imageless.oss-cn-beijing.aliyuncs.com/runtime.sh | sh -s -- up --public-url https://skillhub.your-company.com/skillhub --base-path /skillhub/ ``` ### 配置参数说明 @@ -242,6 +245,7 @@ curl -fsSL https://imageless.oss-cn-beijing.aliyuncs.com/runtime.sh | sh -s -- u | 参数 | 说明 | 示例 | |------|------|------| | `--public-url ` | 公网访问地址(推荐配置) | `--public-url https://skill.example.com` | +| `--base-path ` | 将 Web UI 发布到反向代理子路径,并同步配置同源 API 路由 | `--base-path /skillhub/` | | `--version ` | 指定镜像版本 | `--version v0.2.0` | | `--aliyun` | 使用阿里云镜像(国内推荐) | `--aliyun` | | `--home ` | 指定运行时目录 | `--home /opt/skillhub` | @@ -249,6 +253,11 @@ curl -fsSL https://imageless.oss-cn-beijing.aliyuncs.com/runtime.sh | sh -s -- u > **重要**:生产环境请务必配置 `--public-url`,确保 CLI 安装命令和 Agent 设置指引显示正确的地址。 +如果通过 `/skillhub/` 这类子路径对外发布,需要让公网地址和前端基础路径保持一致。 +例如 `--public-url https://skill.example.com/skillhub --base-path /skillhub/` +会写入 `SKILLHUB_PUBLIC_BASE_URL=https://skill.example.com/skillhub`、 +`SKILLHUB_WEB_BASE_PATH=/skillhub/` 和 `SKILLHUB_WEB_API_BASE_URL=/skillhub`。 + ### 使用 Kubernetes ```bash diff --git a/scripts/runtime.sh b/scripts/runtime.sh index fec6375e..c8b3fcdb 100755 --- a/scripts/runtime.sh +++ b/scripts/runtime.sh @@ -13,6 +13,7 @@ SKILLHUB_HOME_DEFAULT="${TMPDIR:-/tmp}/skillhub-runtime" SKILLHUB_HOME="${SKILLHUB_HOME:-$SKILLHUB_HOME_DEFAULT}" SKILLHUB_VERSION_VALUE="${SKILLHUB_VERSION:-}" SKILLHUB_PUBLIC_BASE_URL_VALUE="${SKILLHUB_PUBLIC_BASE_URL:-}" +SKILLHUB_WEB_BASE_PATH_VALUE="${SKILLHUB_WEB_BASE_PATH:-}" SKILLHUB_ALIYUN_REGISTRY="${SKILLHUB_ALIYUN_REGISTRY:-crpi-ptu2rqimrigtq0qx.cn-hangzhou.personal.cr.aliyuncs.com}" SKILLHUB_ALIYUN_NAMESPACE="${SKILLHUB_ALIYUN_NAMESPACE:-skill_hub}" SKILLHUB_MIRROR_REGISTRY_VALUE="${SKILLHUB_MIRROR_REGISTRY:-}" @@ -89,6 +90,11 @@ while [ "$#" -gt 0 ]; do SKILLHUB_PUBLIC_BASE_URL_VALUE="$2" shift 2 ;; + --base-path) + [ "$#" -ge 2 ] || { echo "Missing value for --base-path" >&2; exit 1; } + SKILLHUB_WEB_BASE_PATH_VALUE="$2" + shift 2 + ;; --help|-h) cat < Store runtime files in a specific directory --ref Download runtime files from a specific Git ref --public-url Public access URL (e.g. https://skill.example.com) + --base-path Serve Web UI under a sub-path, e.g. /skillhub/ --server-image Override backend image repository --web-image Override frontend image repository --scanner-image Override scanner image repository @@ -183,6 +190,46 @@ set_env_value() { secure_env_file } +normalize_base_path() { + value="$1" + [ -n "$value" ] || { echo "--base-path must not be empty" >&2; exit 1; } + + case "$value" in + /) + printf '/' + return 0 + ;; + /*/) ;; + /*) value="$value/" ;; + *) value="/$value/" ;; + esac + + case "$value" in + *//*|*[!A-Za-z0-9._~/-]*) + echo "--base-path contains unsupported characters: $value" >&2 + exit 1 + ;; + esac + + case "$value" in + */./*|*/../*) + echo "--base-path must not contain '.' or '..' path segments: $value" >&2 + exit 1 + ;; + esac + + first_segment=${value#/} + first_segment=${first_segment%%/*} + case "$first_segment" in + api|oauth2|login|assets|registry|nginx-health|.well-known|runtime-config.js) + echo "--base-path must not start with a reserved SkillHub path segment: $first_segment" >&2 + exit 1 + ;; + esac + + printf '%s' "$value" +} + secure_env_file() { if [ -f "$ENV_FILE" ]; then chmod 600 "$ENV_FILE" @@ -359,6 +406,16 @@ prepare_runtime_files() { set_env_value "SKILLHUB_PUBLIC_BASE_URL" "$SKILLHUB_PUBLIC_BASE_URL_VALUE" fi + if [ -n "$SKILLHUB_WEB_BASE_PATH_VALUE" ]; then + normalized_base_path="$(normalize_base_path "$SKILLHUB_WEB_BASE_PATH_VALUE")" + set_env_value "SKILLHUB_WEB_BASE_PATH" "$normalized_base_path" + if [ "$normalized_base_path" = "/" ]; then + set_env_value "SKILLHUB_WEB_API_BASE_URL" "" + else + set_env_value "SKILLHUB_WEB_API_BASE_URL" "${normalized_base_path%/}" + fi + fi + if [ "$DISABLE_SCANNER" = "true" ]; then set_env_value "SKILLHUB_SECURITY_SCANNER_ENABLED" "false" fi diff --git a/scripts/tests/runtime-secret-test.sh b/scripts/tests/runtime-secret-test.sh index 506b58fd..81d56a86 100755 --- a/scripts/tests/runtime-secret-test.sh +++ b/scripts/tests/runtime-secret-test.sh @@ -120,4 +120,18 @@ grep -Fq "SKILLHUB_SECURITY_SCANNER_ENABLED=false" "$home_no_scanner/.env.releas grep -Fq -- "up -d --no-deps --scale skill-scanner=0 server web" "$home_no_scanner/docker.log" \ || fail "runtime --no-scanner should start server/web without waiting on scanner dependencies" +home_sub_path="$tmp/sub-path" +stdout_sub_path="$tmp/sub-path.out" +mkdir -p "$home_sub_path" +run_runtime "$home_sub_path" "$bin_dir" "$stdout_sub_path" \ + --public-url http://localhost/skillhub \ + --base-path skillhub + +grep -Fq "SKILLHUB_PUBLIC_BASE_URL=http://localhost/skillhub" "$home_sub_path/.env.release" \ + || fail "runtime should persist the public URL for sub-path deployments" +grep -Fq "SKILLHUB_WEB_BASE_PATH=/skillhub/" "$home_sub_path/.env.release" \ + || fail "runtime should normalize and persist SKILLHUB_WEB_BASE_PATH" +grep -Fq "SKILLHUB_WEB_API_BASE_URL=/skillhub" "$home_sub_path/.env.release" \ + || fail "runtime should align SKILLHUB_WEB_API_BASE_URL with the base path" + echo "runtime-secret-test passed"