# SkillHub 内网 Docker Compose 部署手册

文档版本：1.0  
适用项目：<https://github.com/iflytek/skillhub>  
推荐固定版本：`v0.2.8`（GitHub Release 于 2026-05-14 标记为 Latest）  
部署模式：内网单机 Docker Compose，自带应用、数据库、缓存和扫描服务，不依赖公网运行。

## 1. 部署目标

本手册用于在企业内网部署 SkillHub，提供统一的 Agent Skill 注册中心，支持团队发布、搜索、安装、审核和治理 Skill 包。

部署后提供：

- Web UI：`http://skillhub.intra.example.com`
- Backend API：`http://skillhub.intra.example.com:8080`
- PostgreSQL：仅容器网络内部访问
- Redis：仅容器网络内部访问
- Skill Scanner：仅容器网络内部访问
- 文件存储：默认使用本机 Docker volume；如需对象存储，可替换为内网 MinIO

官方说明中，SkillHub 支持自托管部署，发布运行镜像由 GitHub Actions 构建并推送到 GHCR；官方运行栈包含 Web UI、Backend API、PostgreSQL、Redis 和 Skill Scanner。

## 2. 部署架构

```text
内网用户 / CLI
    |
    | HTTP 80 / 8080
    v
+-----------------------+
| Docker Host           |
|                       |
|  web                  |
|   |                   |
|   v                   |
|  server  ---> redis   |
|   |  |                |
|   |  +--> postgres    |
|   |                   |
|   +--> skill-scanner  |
|   |                   |
|   +--> skillhub_storage volume
+-----------------------+
```

默认只向内网开放 `80` 和 `8080`。PostgreSQL、Redis、Scanner 不映射到宿主机端口，降低误暴露风险。

## 3. 服务器要求

最低配置：

| 项目 | 要求 |
| --- | --- |
| CPU | 4 核 |
| 内存 | 8 GB |
| 磁盘 | 100 GB SSD，建议单独挂载 `/opt/skillhub` |
| 操作系统 | Linux x86_64 或 arm64 |
| Docker | Docker Engine 24+ |
| Compose | Docker Compose v2 |

建议配置：

| 项目 | 要求 |
| --- | --- |
| CPU | 8 核 |
| 内存 | 16 GB |
| 磁盘 | 300 GB+，按 Skill 包数量扩容 |
| 备份 | 每日备份 PostgreSQL 和 `skillhub_storage` |

## 4. 内网部署前准备

内网服务器本身不需要访问公网，但需要提前在一台可访问公网的准备机上完成离线包制作。

### 4.1 准备目录

在外网准备机执行：

```bash
mkdir -p skillhub-offline/{images,config,cli,seed-skills}
cd skillhub-offline
```

### 4.2 下载部署文件

```bash
curl -fsSL https://raw.githubusercontent.com/iflytek/skillhub/main/compose.release.yml \
  -o config/compose.release.yml

curl -fsSL https://raw.githubusercontent.com/iflytek/skillhub/main/.env.release.example \
  -o config/.env.release.example
```

生产部署建议固定版本，不建议长期使用 `latest`：

```bash
export SKILLHUB_VERSION=v0.2.8
```

### 4.3 拉取并导出镜像

```bash
docker pull ghcr.io/iflytek/skillhub-server:${SKILLHUB_VERSION}
docker pull ghcr.io/iflytek/skillhub-web:${SKILLHUB_VERSION}
docker pull ghcr.io/iflytek/skillhub-scanner:${SKILLHUB_VERSION}
docker pull postgres:16-alpine
docker pull redis:7-alpine

docker save \
  ghcr.io/iflytek/skillhub-server:${SKILLHUB_VERSION} \
  ghcr.io/iflytek/skillhub-web:${SKILLHUB_VERSION} \
  ghcr.io/iflytek/skillhub-scanner:${SKILLHUB_VERSION} \
  postgres:16-alpine \
  redis:7-alpine \
  -o images/skillhub-images-${SKILLHUB_VERSION}.tar
```

如内网有 Harbor、Nexus、JFrog 等镜像仓库，也可以在外网准备机或边界机上重新打标并推送到内网仓库：

```bash
docker tag ghcr.io/iflytek/skillhub-server:${SKILLHUB_VERSION} registry.intra.example.com/skillhub/skillhub-server:${SKILLHUB_VERSION}
docker tag ghcr.io/iflytek/skillhub-web:${SKILLHUB_VERSION} registry.intra.example.com/skillhub/skillhub-web:${SKILLHUB_VERSION}
docker tag ghcr.io/iflytek/skillhub-scanner:${SKILLHUB_VERSION} registry.intra.example.com/skillhub/skillhub-scanner:${SKILLHUB_VERSION}
docker tag postgres:16-alpine registry.intra.example.com/base/postgres:16-alpine
docker tag redis:7-alpine registry.intra.example.com/base/redis:7-alpine

docker push registry.intra.example.com/skillhub/skillhub-server:${SKILLHUB_VERSION}
docker push registry.intra.example.com/skillhub/skillhub-web:${SKILLHUB_VERSION}
docker push registry.intra.example.com/skillhub/skillhub-scanner:${SKILLHUB_VERSION}
docker push registry.intra.example.com/base/postgres:16-alpine
docker push registry.intra.example.com/base/redis:7-alpine
```

### 4.4 准备离线 CLI

SkillHub README 中给出的 CLI 安装方式是 `npm install -g @astron-team/skillhub`，中文快速开始也说明兼容 OpenClaw CLI，可使用 `clawhub` 命令管理 Skill 包。

如果内网没有 npm 源，在外网准备机下载 CLI tarball：

```bash
npm pack @astron-team/skillhub@latest --pack-destination cli
```

传入内网后可执行：

```bash
npm install -g ./cli/astron-team-skillhub-*.tgz
```

如企业已有内网 npm registry，建议把该包同步到内网 npm registry，再通过内网源安装。

### 4.5 打包离线交付物

```bash
cd ..
tar czf skillhub-offline-v0.2.8.tgz skillhub-offline
```

将 `skillhub-offline-v0.2.8.tgz` 通过企业允许的介质传入内网服务器。

## 5. 内网服务器部署

### 5.1 解压离线包

```bash
sudo mkdir -p /opt/skillhub
sudo chown -R "$USER":"$USER" /opt/skillhub

tar xzf skillhub-offline-v0.2.8.tgz -C /opt/skillhub --strip-components=1
cd /opt/skillhub
```

### 5.2 导入镜像

如果使用镜像 tar 包：

```bash
docker load -i images/skillhub-images-v0.2.8.tar
```

如果使用内网镜像仓库，确认内网服务器已登录：

```bash
docker login registry.intra.example.com
```

### 5.3 编写 `.env.release`

新建 `/opt/skillhub/.env.release`：

```dotenv
# 固定版本
SKILLHUB_VERSION=v0.2.8

# 如直接 docker load GHCR 镜像，保留以下 GHCR 镜像名
SKILLHUB_SERVER_IMAGE=ghcr.io/iflytek/skillhub-server
SKILLHUB_WEB_IMAGE=ghcr.io/iflytek/skillhub-web
SKILLHUB_SCANNER_IMAGE=ghcr.io/iflytek/skillhub-scanner
POSTGRES_IMAGE=postgres:16-alpine
REDIS_IMAGE=redis:7-alpine

# 如使用内网镜像仓库，改为以下形式
# SKILLHUB_SERVER_IMAGE=registry.intra.example.com/skillhub/skillhub-server
# SKILLHUB_WEB_IMAGE=registry.intra.example.com/skillhub/skillhub-web
# SKILLHUB_SCANNER_IMAGE=registry.intra.example.com/skillhub/skillhub-scanner
# POSTGRES_IMAGE=registry.intra.example.com/base/postgres
# REDIS_IMAGE=registry.intra.example.com/base/redis

# 内网访问地址，不要以 / 结尾
SKILLHUB_PUBLIC_BASE_URL=http://skillhub.intra.example.com
SKILLHUB_WEB_API_BASE_URL=
SKILLHUB_API_UPSTREAM=http://server:8080

# 宿主机开放端口
WEB_PORT=80
API_PORT=8080

# PostgreSQL
POSTGRES_DB=skillhub
POSTGRES_USER=skillhub
POSTGRES_PASSWORD=replace-with-a-strong-postgres-password

# Redis
REDIS_PORT=6379

# 内网单机默认使用本地 Docker volume 存储 Skill 包
SKILLHUB_STORAGE_PROVIDER=local
STORAGE_BASE_PATH=/var/lib/skillhub/storage

# 初始管理员。上线后立即修改密码，或完成初始化后关闭 BOOTSTRAP_ADMIN_ENABLED。
BOOTSTRAP_ADMIN_ENABLED=true
BOOTSTRAP_ADMIN_USER_ID=docker-admin
BOOTSTRAP_ADMIN_USERNAME=admin
BOOTSTRAP_ADMIN_PASSWORD=replace-with-a-strong-admin-password
BOOTSTRAP_ADMIN_DISPLAY_NAME=Platform Admin
BOOTSTRAP_ADMIN_EMAIL=admin@skillhub.local

# 内网无 OAuth 时启用用户名/密码登录
SKILLHUB_AUTH_DIRECT_ENABLED=true
SKILLHUB_WEB_AUTH_DIRECT_ENABLED=true
SKILLHUB_WEB_AUTH_DIRECT_PROVIDER=local

# Scanner 默认启用。如没有内网大模型服务，LLM 增强扫描留空即可。
SKILLHUB_SECURITY_SCANNER_ENABLED=true
SKILL_SCANNER_LLM_API_KEY=
SKILL_SCANNER_LLM_BASE_URL=
SKILL_SCANNER_LLM_MODEL=

# 内网 HTTP 部署保持 false；如前置 HTTPS 网关再改为 true。
SESSION_COOKIE_SECURE=false

# 不使用外部 OAuth
OAUTH2_GITHUB_CLIENT_ID=
OAUTH2_GITHUB_CLIENT_SECRET=
OAUTH2_GITLAB_CLIENT_ID=
OAUTH2_GITLAB_CLIENT_SECRET=
OAUTH2_GITLAB_BASE_URI=
```

### 5.4 使用内网 Compose 文件

新建 `/opt/skillhub/compose.intranet.yml`：

```yaml
services:
  skill-scanner:
    image: ${SKILLHUB_SCANNER_IMAGE}:${SKILLHUB_VERSION}
    restart: unless-stopped
    environment:
      SKILL_SCANNER_LLM_API_KEY: ${SKILL_SCANNER_LLM_API_KEY:-}
      SKILL_SCANNER_LLM_BASE_URL: ${SKILL_SCANNER_LLM_BASE_URL:-}
      SKILL_SCANNER_LLM_MODEL: ${SKILL_SCANNER_LLM_MODEL:-}
    healthcheck:
      test: ["CMD", "wget", "-qO-", "http://127.0.0.1:8000/health"]
      interval: 10s
      timeout: 5s
      retries: 10
    networks:
      - skillhub

  postgres:
    image: ${POSTGRES_IMAGE}
    restart: unless-stopped
    environment:
      POSTGRES_DB: ${POSTGRES_DB}
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
    volumes:
      - postgres_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
      interval: 5s
      timeout: 5s
      retries: 10
    networks:
      - skillhub

  redis:
    image: ${REDIS_IMAGE}
    restart: unless-stopped
    command: ["redis-server", "--appendonly", "yes"]
    volumes:
      - redis_data:/data
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 5s
      timeout: 5s
      retries: 10
    networks:
      - skillhub

  server:
    image: ${SKILLHUB_SERVER_IMAGE}:${SKILLHUB_VERSION}
    restart: unless-stopped
    ports:
      - "${API_PORT:-8080}:8080"
    environment:
      SPRING_PROFILES_ACTIVE: docker
      SPRING_DATASOURCE_URL: jdbc:postgresql://postgres:5432/${POSTGRES_DB}
      SPRING_DATASOURCE_USERNAME: ${POSTGRES_USER}
      SPRING_DATASOURCE_PASSWORD: ${POSTGRES_PASSWORD}
      REDIS_HOST: redis
      REDIS_PORT: 6379
      SESSION_COOKIE_SECURE: ${SESSION_COOKIE_SECURE:-false}
      SKILLHUB_PUBLIC_BASE_URL: ${SKILLHUB_PUBLIC_BASE_URL}
      SKILLHUB_STORAGE_PROVIDER: ${SKILLHUB_STORAGE_PROVIDER:-local}
      STORAGE_BASE_PATH: /var/lib/skillhub/storage
      SKILLHUB_SECURITY_SCANNER_ENABLED: ${SKILLHUB_SECURITY_SCANNER_ENABLED:-true}
      SKILLHUB_SECURITY_SCANNER_URL: http://skill-scanner:8000
      SKILLHUB_SECURITY_SCANNER_MODE: upload
      SKILLHUB_AUTH_DIRECT_ENABLED: ${SKILLHUB_AUTH_DIRECT_ENABLED:-true}
      BOOTSTRAP_ADMIN_ENABLED: ${BOOTSTRAP_ADMIN_ENABLED:-true}
      BOOTSTRAP_ADMIN_USER_ID: ${BOOTSTRAP_ADMIN_USER_ID:-docker-admin}
      BOOTSTRAP_ADMIN_USERNAME: ${BOOTSTRAP_ADMIN_USERNAME:-admin}
      BOOTSTRAP_ADMIN_PASSWORD: ${BOOTSTRAP_ADMIN_PASSWORD}
      BOOTSTRAP_ADMIN_DISPLAY_NAME: ${BOOTSTRAP_ADMIN_DISPLAY_NAME:-Platform Admin}
      BOOTSTRAP_ADMIN_EMAIL: ${BOOTSTRAP_ADMIN_EMAIL:-admin@skillhub.local}
      OAUTH2_GITHUB_CLIENT_ID: ${OAUTH2_GITHUB_CLIENT_ID:-}
      OAUTH2_GITHUB_CLIENT_SECRET: ${OAUTH2_GITHUB_CLIENT_SECRET:-}
      OAUTH2_GITLAB_CLIENT_ID: ${OAUTH2_GITLAB_CLIENT_ID:-}
      OAUTH2_GITLAB_CLIENT_SECRET: ${OAUTH2_GITLAB_CLIENT_SECRET:-}
      OAUTH2_GITLAB_BASE_URI: ${OAUTH2_GITLAB_BASE_URI:-}
    volumes:
      - skillhub_storage:/var/lib/skillhub/storage
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy
      skill-scanner:
        condition: service_healthy
    healthcheck:
      test: ["CMD", "wget", "-qO-", "http://localhost:8080/actuator/health"]
      interval: 10s
      timeout: 5s
      retries: 12
      start_period: 60s
    networks:
      - skillhub

  web:
    image: ${SKILLHUB_WEB_IMAGE}:${SKILLHUB_VERSION}
    restart: unless-stopped
    ports:
      - "${WEB_PORT:-80}:80"
    environment:
      SKILLHUB_API_UPSTREAM: ${SKILLHUB_API_UPSTREAM:-http://server:8080}
      SKILLHUB_WEB_API_BASE_URL: ${SKILLHUB_WEB_API_BASE_URL:-}
      SKILLHUB_PUBLIC_BASE_URL: ${SKILLHUB_PUBLIC_BASE_URL}
      SKILLHUB_WEB_AUTH_DIRECT_ENABLED: ${SKILLHUB_WEB_AUTH_DIRECT_ENABLED:-true}
      SKILLHUB_WEB_AUTH_DIRECT_PROVIDER: ${SKILLHUB_WEB_AUTH_DIRECT_PROVIDER:-local}
    depends_on:
      server:
        condition: service_healthy
    healthcheck:
      test: ["CMD", "wget", "-qO-", "http://127.0.0.1/nginx-health"]
      interval: 10s
      timeout: 5s
      retries: 12
      start_period: 10s
    networks:
      - skillhub

volumes:
  postgres_data:
  redis_data:
  skillhub_storage:

networks:
  skillhub:
    driver: bridge
```

说明：

- 该文件基于官方 `compose.release.yml` 精简为内网单机部署。
- 数据库、缓存和扫描服务不暴露宿主端口。
- Skill 包文件默认保存在 Docker volume `skillhub_storage`。
- 如果必须使用内网 MinIO，可参考第 10 节切换对象存储。

### 5.5 启动服务

```bash
cd /opt/skillhub
docker compose --env-file .env.release -f compose.intranet.yml up -d
```

查看状态：

```bash
docker compose --env-file .env.release -f compose.intranet.yml ps
```

查看日志：

```bash
docker compose --env-file .env.release -f compose.intranet.yml logs -f server
docker compose --env-file .env.release -f compose.intranet.yml logs -f web
docker compose --env-file .env.release -f compose.intranet.yml logs -f skill-scanner
```

### 5.6 验证服务

```bash
curl -f http://127.0.0.1:8080/actuator/health
curl -f http://127.0.0.1/
```

浏览器访问：

```text
http://skillhub.intra.example.com
```

初始登录：

```text
用户名：admin
密码：.env.release 中 BOOTSTRAP_ADMIN_PASSWORD 的值
```

首次登录后必须完成：

1. 修改管理员密码。
2. 创建团队命名空间。
3. 添加团队成员并分配 Owner、Admin、Member 角色。
4. 根据企业规范开启审核策略。
5. 如已完成初始化，可将 `BOOTSTRAP_ADMIN_ENABLED=false` 后重启服务。

## 6. 内网 DNS 和访问配置

建议由内网 DNS 解析：

```text
skillhub.intra.example.com -> Docker Host 内网 IP
```

如果暂时没有 DNS，可在客户端 `/etc/hosts` 或 Windows hosts 中配置：

```text
10.10.10.20 skillhub.intra.example.com
```

防火墙仅开放：

| 端口 | 用途 | 来源 |
| --- | --- | --- |
| 80 | Web UI / Nginx 反代 API | 内网用户网段 |
| 8080 | Backend API / CLI 直连 | 内网用户网段或 CI 网段 |
| 22 | 运维 SSH | 运维网段 |

如通过企业 HTTPS 网关访问，网关转发到宿主机 `80`，并将 `.env.release` 中：

```dotenv
SKILLHUB_PUBLIC_BASE_URL=https://skillhub.intra.example.com
SESSION_COOKIE_SECURE=true
```

## 7. CLI 使用

安装内网离线 CLI：

```bash
npm install -g /opt/skillhub/cli/astron-team-skillhub-*.tgz
```

配置注册中心：

```bash
skillhub login --token <your-token> --registry http://skillhub.intra.example.com
skillhub search pdf
skillhub install pdf-parser --agent codex
skillhub list
```

如果团队使用 OpenClaw 兼容 CLI，则按官方中文文档示例：

```bash
export CLAWHUB_REGISTRY=http://skillhub.intra.example.com:8080
clawhub search email
clawhub publish ./my-skill --namespace dev-tools
clawhub install dev-tools--code-review-helper
```

## 8. 发布第一个 Skill 包

SkillHub Skill 包至少应包含：

```text
my-skill/
├── skill.md
├── package.json
└── scripts/
    └── main.py
```

示例 `package.json`：

```json
{
  "name": "code-review-helper",
  "version": "0.1.0",
  "description": "Review code changes and produce actionable findings.",
  "keywords": ["dev", "review", "quality"],
  "license": "INTERNAL"
}
```

示例 `skill.md`：

```markdown
# Code Review Helper

Use this skill when reviewing a code diff before merge.

## Workflow

1. Read the diff and touched files.
2. Prioritize correctness, security, data loss, compatibility and missing tests.
3. Report findings first with file and line references.
4. Keep summaries brief.
```

发布：

```bash
export CLAWHUB_REGISTRY=http://skillhub.intra.example.com:8080
clawhub publish ./code-review-helper --namespace dev-tools
```

## 9. 建议预置的开发常用 Skill

建议创建 `dev-tools` 命名空间，预置以下 Skill，方便研发团队统一工作流。

| Skill 名称 | 用途 | 建议标签 |
| --- | --- | --- |
| `code-review-helper` | 代码评审，输出缺陷、风险和测试缺口 | `review`, `quality` |
| `api-debug-helper` | API 调试，整理 curl、请求头、响应码和排障路径 | `api`, `debug` |
| `sql-safety-review` | SQL 变更检查，关注锁表、索引、回滚和数据破坏 | `sql`, `database` |
| `docker-compose-troubleshooter` | Compose 部署排障，检查端口、镜像、健康检查和日志 | `docker`, `ops` |
| `test-case-generator` | 根据变更生成单元测试、集成测试和回归用例 | `test`, `qa` |
| `release-note-writer` | 根据提交或 PR 生成变更日志和升级说明 | `release`, `docs` |
| `openapi-contract-checker` | 检查前后端 API 契约漂移和兼容性 | `openapi`, `contract` |
| `incident-summary-writer` | 故障复盘，沉淀时间线、根因、影响和行动项 | `incident`, `sre` |

### 9.1 批量创建 Skill 目录模板

```bash
mkdir -p seed-skills/dev-tools
cd seed-skills/dev-tools

for skill in \
  code-review-helper \
  api-debug-helper \
  sql-safety-review \
  docker-compose-troubleshooter \
  test-case-generator \
  release-note-writer \
  openapi-contract-checker \
  incident-summary-writer
do
  mkdir -p "$skill/scripts"
  cat > "$skill/package.json" <<EOF
{
  "name": "$skill",
  "version": "0.1.0",
  "description": "$skill for internal development workflow.",
  "keywords": ["dev-tools"],
  "license": "INTERNAL"
}
EOF
  cat > "$skill/skill.md" <<EOF
# $skill

Use this skill for internal development workflow.

## Inputs

- Goal or task description
- Relevant files, logs, diffs or commands

## Output

- Findings or recommended actions
- Commands to run, when applicable
- Risks and verification steps
EOF
done
```

### 9.2 批量发布

```bash
cd /opt/skillhub
export CLAWHUB_REGISTRY=http://skillhub.intra.example.com:8080

for d in seed-skills/dev-tools/*; do
  clawhub publish "$d" --namespace dev-tools
done
```

如果启用了审核，发布后由 `dev-tools` 命名空间 Owner/Admin 到 Web UI 的审核页面批准。

## 10. 可选：切换到内网 MinIO

单机部署可以使用本地 volume。若希望 Skill 包文件独立备份、跨节点迁移或由存储团队统一管理，建议使用内网 MinIO 或其他 S3 兼容对象存储。

新增 MinIO 服务：

```yaml
  minio:
    image: registry.intra.example.com/base/minio:RELEASE.2026-01-01T00-00-00Z
    restart: unless-stopped
    command: server /data --console-address ":9001"
    environment:
      MINIO_ROOT_USER: skillhub
      MINIO_ROOT_PASSWORD: replace-with-a-strong-minio-password
    volumes:
      - minio_data:/data
    ports:
      - "127.0.0.1:9001:9001"
    networks:
      - skillhub
```

调整 `.env.release`：

```dotenv
SKILLHUB_STORAGE_PROVIDER=s3
SKILLHUB_STORAGE_S3_ENDPOINT=http://minio:9000
SKILLHUB_STORAGE_S3_PUBLIC_ENDPOINT=http://skillhub-minio.intra.example.com:9000
SKILLHUB_STORAGE_S3_BUCKET=skillhub
SKILLHUB_STORAGE_S3_ACCESS_KEY=skillhub
SKILLHUB_STORAGE_S3_SECRET_KEY=replace-with-a-strong-minio-password
SKILLHUB_STORAGE_S3_REGION=us-east-1
SKILLHUB_STORAGE_S3_FORCE_PATH_STYLE=true
SKILLHUB_STORAGE_S3_AUTO_CREATE_BUCKET=true
```

注意：如果返回给浏览器或 CLI 的下载地址是预签名 S3 地址，则 `SKILLHUB_STORAGE_S3_PUBLIC_ENDPOINT` 必须是客户端可访问的内网地址。

## 11. 启停和运维

启动：

```bash
docker compose --env-file .env.release -f compose.intranet.yml up -d
```

停止：

```bash
docker compose --env-file .env.release -f compose.intranet.yml down
```

重启某个服务：

```bash
docker compose --env-file .env.release -f compose.intranet.yml restart server
```

查看资源：

```bash
docker stats
docker system df
```

查看健康状态：

```bash
docker compose --env-file .env.release -f compose.intranet.yml ps
curl http://127.0.0.1:8080/actuator/health
```

## 12. 备份和恢复

### 12.1 备份 PostgreSQL

```bash
cd /opt/skillhub
mkdir -p backups/$(date +%F)

docker compose --env-file .env.release -f compose.intranet.yml exec -T postgres \
  pg_dump -U skillhub -d skillhub \
  > backups/$(date +%F)/skillhub.sql
```

### 12.2 备份 Skill 文件

```bash
docker run --rm \
  -v skillhub_skillhub_storage:/data:ro \
  -v /opt/skillhub/backups/$(date +%F):/backup \
  alpine:3.20 \
  tar czf /backup/skillhub_storage.tgz -C /data .
```

### 12.3 恢复 PostgreSQL

```bash
docker compose --env-file .env.release -f compose.intranet.yml exec -T postgres \
  psql -U skillhub -d skillhub \
  < backups/2026-06-04/skillhub.sql
```

### 12.4 恢复 Skill 文件

```bash
docker run --rm \
  -v skillhub_skillhub_storage:/data \
  -v /opt/skillhub/backups/2026-06-04:/backup \
  alpine:3.20 \
  sh -c 'cd /data && tar xzf /backup/skillhub_storage.tgz'
```

恢复前应停止 `server` 和 `web`，避免写入冲突：

```bash
docker compose --env-file .env.release -f compose.intranet.yml stop web server
```

## 13. 升级流程

1. 在测试环境验证新版本。
2. 外网准备机拉取新版本镜像并导出离线包。
3. 内网服务器备份 PostgreSQL 和 Skill 文件。
4. 修改 `.env.release` 的 `SKILLHUB_VERSION`。
5. 导入新镜像。
6. 重启服务。

命令示例：

```bash
docker load -i images/skillhub-images-v0.2.9.tar

sed -i 's/^SKILLHUB_VERSION=.*/SKILLHUB_VERSION=v0.2.9/' .env.release

docker compose --env-file .env.release -f compose.intranet.yml up -d
docker compose --env-file .env.release -f compose.intranet.yml ps
curl -f http://127.0.0.1:8080/actuator/health
```

如升级失败：

```bash
sed -i 's/^SKILLHUB_VERSION=.*/SKILLHUB_VERSION=v0.2.8/' .env.release
docker compose --env-file .env.release -f compose.intranet.yml up -d
```

如果新版本已经执行数据库迁移，回滚前需评估 Flyway 迁移是否可逆，必要时恢复数据库备份。

## 14. 常见问题

### 14.1 Web UI 可以打开，但登录失败

检查：

```bash
docker compose --env-file .env.release -f compose.intranet.yml logs server | tail -200
grep -E 'SKILLHUB_AUTH_DIRECT_ENABLED|SKILLHUB_WEB_AUTH_DIRECT_ENABLED|BOOTSTRAP_ADMIN' .env.release
```

确认：

- `SKILLHUB_AUTH_DIRECT_ENABLED=true`
- `SKILLHUB_WEB_AUTH_DIRECT_ENABLED=true`
- `BOOTSTRAP_ADMIN_ENABLED=true`
- 管理员密码不是空值

### 14.2 CLI 安装命令里出现 localhost

检查：

```bash
grep '^SKILLHUB_PUBLIC_BASE_URL=' .env.release
```

必须设置为最终内网访问地址，例如：

```dotenv
SKILLHUB_PUBLIC_BASE_URL=http://skillhub.intra.example.com
```

修改后重启：

```bash
docker compose --env-file .env.release -f compose.intranet.yml up -d
```

### 14.3 server 健康检查失败

检查数据库和缓存：

```bash
docker compose --env-file .env.release -f compose.intranet.yml ps postgres redis
docker compose --env-file .env.release -f compose.intranet.yml logs postgres
docker compose --env-file .env.release -f compose.intranet.yml logs redis
```

常见原因：

- `POSTGRES_PASSWORD` 与已有 volume 中数据库用户密码不一致。
- 服务器磁盘满。
- 端口 `8080` 已被宿主机其他进程占用。
- 镜像版本和数据库迁移状态不匹配。

### 14.4 上传 Skill 后扫描一直等待

检查 Scanner：

```bash
docker compose --env-file .env.release -f compose.intranet.yml ps skill-scanner
docker compose --env-file .env.release -f compose.intranet.yml logs -f skill-scanner
```

如果暂时不需要扫描，可关闭：

```dotenv
SKILLHUB_SECURITY_SCANNER_ENABLED=false
```

然后重启：

```bash
docker compose --env-file .env.release -f compose.intranet.yml up -d
```

## 15. 安全基线

上线前必须完成：

- 固定 `SKILLHUB_VERSION`，不要使用浮动 `latest`。
- 修改 `POSTGRES_PASSWORD` 和 `BOOTSTRAP_ADMIN_PASSWORD`。
- 初始化后关闭或轮换 bootstrap admin。
- PostgreSQL、Redis、Scanner 不暴露宿主机端口。
- 只允许内网网段访问 `80` 和 `8080`。
- 配置每日数据库和文件备份。
- 开启命名空间审核流程。
- 发布 Skill 前检查是否包含密钥、账号、内网地址、生产数据样例。
- 对 `dev-tools` 等公共命名空间设置 Owner/Admin，避免任意人员发布全局可见 Skill。

## 16. 参考资料

- GitHub 项目：<https://github.com/iflytek/skillhub>
- 官方快速开始：<https://iflytek.github.io/skillhub/quickstart.html>
- Skill 发布与版本管理：<https://iflytek.github.io/skillhub/guide/skill-publish.html>
- 命名空间与团队管理：<https://iflytek.github.io/skillhub/guide/namespace.html>
- 审核与治理：<https://iflytek.github.io/skillhub/guide/review.html>
- 官方 `compose.release.yml`：<https://raw.githubusercontent.com/iflytek/skillhub/main/compose.release.yml>
- 官方 `.env.release.example`：<https://raw.githubusercontent.com/iflytek/skillhub/main/.env.release.example>
