fix: add --public-url parameter for docker deployment (#216)

* docs: add VitePress bilingual documentation site

- Add VitePress-based documentation with Chinese (root) and English (/en/) locales
- Include 6 feature guides: skill-publish, skill-discovery, namespace, review, scanner, social
- Add quickstart, introduction, and FAQ pages
- Include AI-generated diagrams and screenshots
- Add GitHub Pages deployment workflow
- Add Makefile targets: docs-dev, docs-build, docs-preview

* docs: rename docs/claude to docs/skillhub

- Rename documentation directory from docs/claude to docs/skillhub
- Update Makefile paths for docs-dev, docs-build, docs-preview
- Update GitHub workflow paths for deploy-docs.yml

* fix: add enablement parameter to auto-enable GitHub Pages

* Revert "fix: add enablement parameter to auto-enable GitHub Pages"

This reverts commit 11096b1a9b.

* docs: add documentation link to README

Add link to GitHub Pages documentation (https://iflytek.github.io/skillhub/)
under Quick Start section in both English and Chinese README files.

* fix: add --public-url parameter for docker deployment

- Add --public-url parameter to runtime.sh for configuring public access URL
- Create skill.md.template for dynamic URL substitution at container startup
- Update getBaseUrl() to fallback to window.location.host when appBaseUrl is localhost
- Update landing-quick-start.tsx to dynamically generate agent command URL
- Add commandTemplate to i18n files for URL placeholder support
- Update README.md and README_zh.md with deployment parameter documentation

Fixes: Docker deployment shows localhost in install commands and skill.md
This commit is contained in:
XiaoSeS 2026-04-02 17:52:42 +08:00 committed by GitHub
parent cbc1ef25f7
commit 612b69c2f9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 289 additions and 14 deletions

View file

@ -65,6 +65,8 @@ firewall, with the same polish you'd expect from a public registry.
## Quick Start
📖 **[User Documentation →](https://iflytek.github.io/skillhub/)**
Start the full local stack with one of the following commands:
Official images:
@ -76,10 +78,22 @@ curl -fsSL https://imageless.oss-cn-beijing.aliyuncs.com/runtime-github.sh | sh
The default command pulls the `latest` stable release images. Use
`--version edge` if you want the newest build from `main`.
**Configure public URL (recommended for production):**
```bash
curl -fsSL https://imageless.oss-cn-beijing.aliyuncs.com/runtime-github.sh | sh -s -- up --public-url https://skillhub.your-company.com
```
The `--public-url` parameter sets the public access URL for your SkillHub instance.
This ensures:
- CLI install commands show the correct registry URL
- Agent setup instructions display the correct skill.md URL
- OAuth callbacks and device auth links work properly
Aliyun mirror shortcut:
```bash
rm -rf /tmp/skillhub-aliyun
curl -fsSL https://imageless.oss-cn-beijing.aliyuncs.com/runtime.sh | sh -s -- up --home /tmp/skillhub-aliyun --aliyun --version latest
curl -fsSL https://imageless.oss-cn-beijing.aliyuncs.com/runtime.sh | sh -s -- up --home /tmp/skillhub-aliyun --aliyun --version latest --public-url https://skillhub.your-company.com
```
If deployment runs into problems, clear the existing runtime home and retry.
@ -170,6 +184,30 @@ This is the supported path for anyone who wants a ready-to-use local
environment without building the backend or frontend on their machine.
Published images target both `linux/amd64` and `linux/arm64`.
**Quick deployment with curl:**
```bash
# Official images
curl -fsSL https://imageless.oss-cn-beijing.aliyuncs.com/runtime-github.sh | sh -s -- up --public-url https://skillhub.your-company.com
# 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
```
**Deployment parameters:**
| Parameter | Description | Example |
|-----------|-------------|---------|
| `--public-url <url>` | Public access URL (recommended) | `--public-url https://skill.example.com` |
| `--version <tag>` | Specific image tag | `--version v0.2.0` |
| `--aliyun` | Use Aliyun mirror (China) | `--aliyun` |
| `--home <dir>` | Runtime directory | `--home /opt/skillhub` |
| `--no-scanner` | Disable security scanner | `--no-scanner` |
> **Important**: Configure `--public-url` for production deployments to ensure CLI install commands and Agent setup instructions display the correct URLs.
**Manual deployment:**
1. Copy the runtime environment template.
2. Pick an image tag.
3. Start the stack with Docker Compose.

View file

@ -37,6 +37,8 @@ SkillHub 是一个自托管平台,为团队提供私有的、受治理的智
## 快速开始
📖 **[用户使用文档 →](https://iflytek.github.io/skillhub/)**
使用以下命令之一启动完整的本地环境:
官方镜像:
@ -48,10 +50,21 @@ curl -fsSL https://raw.githubusercontent.com/iflytek/skillhub/main/scripts/runti
默认命令会拉取 `latest` 稳定版镜像;如果你想跟随 `main`
的最新构建,请显式传 `--version edge`
**配置公网访问地址(生产环境推荐):**
```bash
curl -fsSL https://raw.githubusercontent.com/iflytek/skillhub/main/scripts/runtime.sh | sh -s -- up --public-url https://skillhub.your-company.com
```
`--public-url` 参数用于设置 SkillHub 实例的公网访问地址。配置后:
- CLI 安装命令会显示正确的注册中心地址
- Agent 设置指引会显示正确的 skill.md URL
- OAuth 回调和设备认证链接能正常工作
阿里云镜像快捷方式:
```bash
rm -rf /tmp/skillhub-aliyun
curl -fsSL https://imageless.oss-cn-beijing.aliyuncs.com/runtime.sh | sh -s -- up --home /tmp/skillhub-aliyun --aliyun --version latest
curl -fsSL https://imageless.oss-cn-beijing.aliyuncs.com/runtime.sh | sh -s -- up --home /tmp/skillhub-aliyun --aliyun --version latest --public-url https://skillhub.your-company.com
```
如果部署遇到问题,请清除现有的运行时目录并重试。
@ -160,12 +173,24 @@ skillhub/
```bash
# 使用官方镜像
curl -fsSL https://raw.githubusercontent.com/iflytek/skillhub/main/scripts/runtime.sh | sh -s -- up
curl -fsSL https://raw.githubusercontent.com/iflytek/skillhub/main/scripts/runtime.sh | sh -s -- up --public-url https://skillhub.your-company.com
# 使用阿里云镜像
curl -fsSL https://imageless.oss-cn-beijing.aliyuncs.com/runtime.sh | sh -s -- up --aliyun
curl -fsSL https://imageless.oss-cn-beijing.aliyuncs.com/runtime.sh | sh -s -- up --aliyun --public-url https://skillhub.your-company.com
```
### 配置参数说明
| 参数 | 说明 | 示例 |
|------|------|------|
| `--public-url <url>` | 公网访问地址(推荐配置) | `--public-url https://skill.example.com` |
| `--version <tag>` | 指定镜像版本 | `--version v0.2.0` |
| `--aliyun` | 使用阿里云镜像(国内推荐) | `--aliyun` |
| `--home <dir>` | 指定运行时目录 | `--home /opt/skillhub` |
| `--no-scanner` | 禁用安全扫描服务 | `--no-scanner` |
> **重要**:生产环境请务必配置 `--public-url`,确保 CLI 安装命令和 Agent 设置指引显示正确的地址。
### 使用 Kubernetes
```bash

14
scripts/runtime.sh Normal file → Executable file
View file

@ -12,6 +12,7 @@ SKILLHUB_REF="${SKILLHUB_REF:-main}"
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_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:-}"
@ -81,6 +82,11 @@ while [ "$#" -gt 0 ]; do
DISABLE_SCANNER=true
shift
;;
--public-url)
[ "$#" -ge 2 ] || { echo "Missing value for --public-url" >&2; exit 1; }
SKILLHUB_PUBLIC_BASE_URL_VALUE="$2"
shift 2
;;
--help|-h)
cat <<EOF
Usage: sh runtime.sh [up|down|clean|ps|logs|pull] [options]
@ -91,6 +97,7 @@ Options:
--mirror-registry <r> Use mirrored images from <registry>/<namespace>
--home <dir> Store runtime files in a specific directory
--ref <git-ref> Download runtime files from a specific Git ref
--public-url <url> Public access URL (e.g. https://skill.example.com)
--server-image <img> Override backend image repository
--web-image <img> Override frontend image repository
--scanner-image <img> Override scanner image repository
@ -204,6 +211,10 @@ prepare_runtime_files() {
if [ -n "$SKILLHUB_SCANNER_IMAGE_VALUE" ]; then
set_env_value "SKILLHUB_SCANNER_IMAGE" "$SKILLHUB_SCANNER_IMAGE_VALUE"
fi
if [ -n "$SKILLHUB_PUBLIC_BASE_URL_VALUE" ]; then
set_env_value "SKILLHUB_PUBLIC_BASE_URL" "$SKILLHUB_PUBLIC_BASE_URL_VALUE"
fi
}
run_compose() {
@ -221,9 +232,10 @@ case "$COMMAND" in
else
run_compose up -d
fi
PUBLIC_URL="${SKILLHUB_PUBLIC_BASE_URL_VALUE:-http://localhost}"
cat <<EOF
SkillHub runtime started.
Web UI: http://localhost
Web UI: $PUBLIC_URL
Backend API: http://localhost:8080
Runtime dir: $SKILLHUB_HOME
Stop with:

View file

@ -8,7 +8,7 @@ RUN pnpm build
FROM nginx:alpine
COPY --from=build /app/dist /usr/share/nginx/html
COPY --from=build /app/src/docs/skill.md /usr/share/nginx/html/registry/skill.md
COPY --from=build /app/src/docs/skill.md.template /usr/share/nginx/html/registry/skill.md.template
COPY nginx.conf.template /etc/nginx/templates/default.conf.template
COPY runtime-config.js.template /usr/share/nginx/html/runtime-config.js.template
COPY docker-entrypoint.d/30-runtime-config.sh /docker-entrypoint.d/30-runtime-config.sh

View file

@ -4,6 +4,12 @@ set -eu
: "${SKILLHUB_WEB_API_BASE_URL:=}"
: "${SKILLHUB_PUBLIC_BASE_URL:=}"
# Generate runtime-config.js
envsubst '${SKILLHUB_WEB_API_BASE_URL} ${SKILLHUB_PUBLIC_BASE_URL}' \
< /usr/share/nginx/html/runtime-config.js.template \
> /usr/share/nginx/html/runtime-config.js
# Generate registry/skill.md with actual public URL
envsubst '${SKILLHUB_PUBLIC_BASE_URL}' \
< /usr/share/nginx/html/registry/skill.md.template \
> /usr/share/nginx/html/registry/skill.md

View file

@ -0,0 +1,145 @@
---
name: skillhub-registry
description: Use this when you need to search, inspect, install, or publish agent skills against a SkillHub registry. SkillHub is a skill registry with a ClawHub-compatible API layer, so prefer the `clawhub` CLI for registry operations instead of making raw HTTP calls.
---
# SkillHub Registry
Use this skill when you need to work with a SkillHub registry: search skills, inspect metadata, install a package, or publish a new version.
> Important: Prefer the `clawhub` CLI for registry workflows. SkillHub exposes a ClawHub-compatible API surface and a discovery endpoint at `/.well-known/clawhub.json`, so the CLI is the safest path for auth, resolution, and download behavior. Only fall back to raw HTTP when debugging the server itself.
## What SkillHub Is
SkillHub is an enterprise-oriented skill registry. It stores versioned skill packages, supports namespace-based skill management, and keeps `SKILL.md` compatibility with OpenSkills-style packages.
Key facts:
- Internal coordinates use `@{namespace}/{skill_slug}`.
- If using the clawhub CLI, the compatible format is `{namespace}--{skill_slug}`.
- ClawHub-compatible clients use a `{namespace}--{skill_slug}` slug instead.
- `latest` always means the latest published version, never draft or pending review.
- Public skills in `@global` can be downloaded anonymously.
- If no namespace is specified, it defaults to `@global`.
- `{skill_slug}` can be used instead of `global--{skill_slug}`
- Team namespace skills and non-public skills require authentication.
## Configure The CLI
Point `clawhub` at the SkillHub base URL:
```bash
export CLAWHUB_REGISTRY=${SKILLHUB_PUBLIC_BASE_URL}
```
Alternatively, use the `--registry` parameter every time, for example:
```bash
npx clawhub install my-skill --registry ${SKILLHUB_PUBLIC_BASE_URL}
```
If you need authenticated access, provide an API token:
```bash
clawhub login --token sk_your_api_token_here
```
Optional local check:
```bash
curl ${SKILLHUB_PUBLIC_BASE_URL}/.well-known/clawhub.json
```
Expected response:
```json
{"apiBase":"/api/v1"}
```
## Coordinate Rules - IMPORTANT
SkillHub has two naming forms:
| SkillHub coordinate | Canonical slug for `clawhub` |
|---|---|
| `@global/my-skill` | `my-skill` |
| `@team-name/my-skill` | `team-name--my-skill` |
Rules:
- `--` is the namespace separator in the compatibility layer.
- If there is no `--`, the skill is treated as `@global/...`.
- `latest` resolves to the latest published version only.
Examples:
```bash
npx clawhub install my-skill
npx clawhub install my-skill@1.2.0
npx clawhub install team-name--my-skill
```
## Common Workflows
### Search
```bash
npx clawhub search email
```
Use an empty query when you want a broad listing:
```bash
npx clawhub search ""
```
### Inspect A Skill
```bash
npx clawhub info my-skill
npx clawhub info team-name--my-skill
```
### Install
```bash
npx clawhub install my-skill
npx clawhub install my-skill@1.2.0
npx clawhub install team-name--my-skill
```
### Publish
Prepare a skill package directory, then publish it:
```bash
npx clawhub publish ./my-skill
```
Publishing requires authentication and sufficient permissions in the target namespace.
## Authentication And Visibility
Download and search permissions depend on namespace and visibility:
- `@global` + `PUBLIC`: anonymous search, inspect, and download are allowed.
- Team namespace + `PUBLIC`: authentication required for download.
- `NAMESPACE_ONLY`: authenticated namespace members only.
- `PRIVATE`: owner or explicitly authorized users only.
- Publish, star, and other write operations always require authentication.
If a request fails with `403`, check:
- whether the skill belongs to a team namespace,
- whether the skill is `NAMESPACE_ONLY` or `PRIVATE`,
- whether your token is valid,
- whether you have namespace publish permissions.
## Skill Package Contract
SkillHub expects OpenSkills-style packages with `SKILL.md` as the entry point.
## Publishing Guidance
Just need to follow the OpenSkills-style standards.

View file

@ -73,6 +73,16 @@ describe('install-command', () => {
expect(getBaseUrl()).toBe('https://fallback.example.com')
})
it('falls back to browser origin when app base url is localhost', () => {
setMockWindow('http://localhost')
expect(getBaseUrl()).toBe('https://fallback.example.com')
})
it('falls back to browser origin when app base url contains localhost', () => {
setMockWindow('http://localhost:8080')
expect(getBaseUrl()).toBe('https://fallback.example.com')
})
it('renders the install command in a more compact code block', () => {
setMockWindow('http://localhost:3000')

View file

@ -19,9 +19,12 @@ export function getBaseUrl(): string {
return ''
}
const runtimeConfig = window.__SKILLHUB_RUNTIME_CONFIG__
if (runtimeConfig?.appBaseUrl) {
return runtimeConfig.appBaseUrl
const configuredUrl = runtimeConfig?.appBaseUrl
// Use configured URL only if it's set and not localhost
if (configuredUrl && !configuredUrl.includes('localhost')) {
return configuredUrl
}
// Fallback to current page origin
return `${window.location.protocol}//${window.location.host}`
}

View file

@ -7,4 +7,9 @@ describe('landing quick start locales', () => {
expect(zh.landing.quickStart.agent.command).toBe('阅读 https://www.example.com/registry/skill.md并按照说明完成 SkillHub Skills Registry 的配置')
expect(en.landing.quickStart.agent.command).toBe('Read https://www.example.com/registry/skill.md and follow the instructions to setup SkillHub Skills Registry')
})
it('provides command templates with url placeholder for dynamic rendering', () => {
expect(zh.landing.quickStart.agent.commandTemplate).toBe('阅读 {{url}},并按照说明完成 SkillHub Skills Registry 的配置')
expect(en.landing.quickStart.agent.commandTemplate).toBe('Read {{url}} and follow the instructions to setup SkillHub Skills Registry')
})
})

View file

@ -107,7 +107,8 @@
},
"agent": {
"description": "Send a prompt to your Agent to set up the SkillHub Registry",
"command": "Read https://www.example.com/registry/skill.md and follow the instructions to setup SkillHub Skills Registry"
"command": "Read https://www.example.com/registry/skill.md and follow the instructions to setup SkillHub Skills Registry",
"commandTemplate": "Read {{url}} and follow the instructions to setup SkillHub Skills Registry"
},
"human": {
"description": "Use the CLI tool to install Skills",

View file

@ -107,7 +107,8 @@
},
"agent": {
"description": "发送提示词给你的 Agent以设置SkillHub Registry",
"command": "阅读 https://www.example.com/registry/skill.md并按照说明完成 SkillHub Skills Registry 的配置"
"command": "阅读 https://www.example.com/registry/skill.md并按照说明完成 SkillHub Skills Registry 的配置",
"commandTemplate": "阅读 {{url}},并按照说明完成 SkillHub Skills Registry 的配置"
},
"human": {
"description": "使用CLI工具安装Skills",

View file

@ -1,4 +1,4 @@
import { useState } from 'react'
import { useState, useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import { Bot, Check, Copy, UserRound } from 'lucide-react'
import { useCopyToClipboard } from '@/shared/lib/clipboard'
@ -12,6 +12,25 @@ interface LandingQuickStartTab {
command: string
}
/**
* Get the base URL for the application.
* Prefers the runtime config if set and not localhost.
* Falls back to the current page origin.
*/
function getAppBaseUrl(): string {
if (typeof window === 'undefined') {
return ''
}
const runtimeConfig = window.__SKILLHUB_RUNTIME_CONFIG__
const configuredUrl = runtimeConfig?.appBaseUrl
// Use configured URL only if it's set and not localhost
if (configuredUrl && !configuredUrl.includes('localhost')) {
return configuredUrl
}
// Fallback to current page origin
return `${window.location.protocol}//${window.location.host}`
}
function CompactCopyButton({ text }: { text: string }) {
const { t } = useTranslation()
const [copied, copy] = useCopyToClipboard()
@ -43,13 +62,20 @@ function CompactCopyButton({ text }: { text: string }) {
export function LandingQuickStartSection() {
const { t } = useTranslation()
const [activeTab, setActiveTab] = useState<LandingQuickStartTabId>('agent')
const baseUrl = useMemo(() => getAppBaseUrl(), [])
// Build dynamic agent command with actual registry URL
const agentCommand = t('landing.quickStart.agent.commandTemplate', {
defaultValue: t('landing.quickStart.agent.command'),
url: `${baseUrl}/registry/skill.md`,
})
const tabs: LandingQuickStartTab[] = [
{
id: 'agent',
label: t('landing.quickStart.tabs.agent'),
description: t('landing.quickStart.agent.description'),
command: t('landing.quickStart.agent.command'),
command: agentCommand,
},
{
id: 'human',

View file

@ -8,9 +8,12 @@ function getAppBaseUrl(): string {
return 'https://skill.xfyun.cn'
}
const runtimeConfig = (window as unknown as Record<string, unknown>).__SKILLHUB_RUNTIME_CONFIG__ as { appBaseUrl?: string } | undefined
if (runtimeConfig?.appBaseUrl) {
return runtimeConfig.appBaseUrl
const configuredUrl = runtimeConfig?.appBaseUrl
// Use configured URL only if it's set and not localhost
if (configuredUrl && !configuredUrl.includes('localhost')) {
return configuredUrl
}
// Fallback to current page origin
return `${window.location.protocol}//${window.location.host}`
}