#!/usr/bin/env bash set -euo pipefail # One-click local Docker deployment using China-friendly mirrors. # Run from workspace root: /Users/dannier/Desktop/living/hh # # 配置:编辑同目录下的 hermes-docker.env(可由 hermes-docker.env.example 复制) # Usage: # bash start-docker-cn.sh WORKSPACE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" ENV_FILE="${WORKSPACE_DIR}/hermes-docker.env" ENV_EXAMPLE="${WORKSPACE_DIR}/hermes-docker.env.example" if [ ! -f "${ENV_FILE}" ] && [ -f "${ENV_EXAMPLE}" ]; then cp "${ENV_EXAMPLE}" "${ENV_FILE}" echo "==> Created ${ENV_FILE} from example — edit API keys if needed" fi if [ -f "${ENV_FILE}" ]; then set -a # shellcheck disable=SC1090 source "${ENV_FILE}" set +a fi HERMES_PROJECT_DIR="${HERMES_PROJECT_DIR:-${WORKSPACE_DIR}/hermes-agent}" HERMES_DATA_DIR="${HERMES_DATA_DIR:-$HOME/.hermes}" IMAGE_NAME="${IMAGE_NAME:-hermes-agent:cn-local}" CONTAINER_NAME="${CONTAINER_NAME:-hermes}" COMPOSE_FILE="${WORKSPACE_DIR}/docker-compose.cn.yml" API_PORT="${API_PORT:-8642}" HERMES_API_KEY="${HERMES_API_KEY:-change-me-local-dev}" WEBUI_PORT="${WEBUI_PORT:-3000}" ENABLE_WEBUI="${ENABLE_WEBUI:-true}" WEBUI_SECRET_KEY="${WEBUI_SECRET_KEY:-dev-change-me-in-production}" OPEN_WEBUI_IMAGE="${OPEN_WEBUI_IMAGE:-ghcr.io/open-webui/open-webui:main}" OPEN_WEBUI_DATA_DIR="${OPEN_WEBUI_DATA_DIR:-${WORKSPACE_DIR}/open-webui-data}" # macOS 下启动完成后是否自动打开浏览器(OPEN_BROWSER=false 可关闭) OPEN_BROWSER="${OPEN_BROWSER:-true}" # China mirrors (can be overridden by env vars or hermes-docker.env) BASE_IMAGE="${BASE_IMAGE:-}" BASE_IMAGE_CANDIDATES="${BASE_IMAGE_CANDIDATES:-docker.m.daocloud.io/library/debian:13.4}" APT_MIRROR_CANDIDATES="${APT_MIRROR_CANDIDATES:-mirrors.aliyun.com,mirrors.ustc.edu.cn,mirrors.huaweicloud.com,mirrors.tuna.tsinghua.edu.cn}" PIP_INDEX_URL="${PIP_INDEX_URL:-https://pypi.tuna.tsinghua.edu.cn/simple}" NPM_REGISTRY="${NPM_REGISTRY:-https://registry.npmmirror.com}" PLAYWRIGHT_DOWNLOAD_HOSTS="${PLAYWRIGHT_DOWNLOAD_HOSTS:-https://npmmirror.com/mirrors/playwright,https://registry.npmmirror.com/-/binary/playwright}" INSTALL_PLAYWRIGHT_BROWSER="${INSTALL_PLAYWRIGHT_BROWSER:-0}" PLAYWRIGHT_ONLY_SHELL="${PLAYWRIGHT_ONLY_SHELL:-0}" # Hermes 在容器内会 load ~/.hermes/.env 且 override=True,会覆盖 Docker env_file 注入的密钥。 # 将 hermes-docker.env 里已解析的 LLM 变量写入 ~/.hermes/.env,避免「Docker 有 key、运行时仍 401」。 sync_llm_keys_to_hermes_dotenv() { local h="${HERMES_DATA_DIR}/.env" local keys=( OPENAI_API_KEY OPENROUTER_API_KEY GOOGLE_API_KEY GEMINI_API_KEY GLM_API_KEY KIMI_API_KEY ANTHROPIC_API_KEY MINIMAX_API_KEY HF_TOKEN DASHSCOPE_API_KEY DASHSCOPE_BASE_URL ) local key val any=0 touch "$h" for key in "${keys[@]}"; do val="${!key:-}" [ -z "$val" ] && continue any=1 if [ -s "$h" ]; then grep -v "^${key}=" "$h" > "${h}.tmp.$$" && mv "${h}.tmp.$$" "$h" fi printf '%s=%s\n' "$key" "$val" >> "$h" done if [ "$any" -eq 1 ]; then echo "==> Synced LLM keys from ${ENV_FILE} -> ${h} (required: Hermes overrides Docker env with this file)" fi } echo "==> Checking project path: ${HERMES_PROJECT_DIR}" if [ ! -f "${HERMES_PROJECT_DIR}/Dockerfile" ]; then echo "ERROR: Dockerfile not found under ${HERMES_PROJECT_DIR}" exit 1 fi echo "==> Checking Docker availability" if ! command -v docker >/dev/null 2>&1; then echo "ERROR: docker not found. Please install Docker first." exit 1 fi if docker compose version >/dev/null 2>&1; then COMPOSE_CMD=(docker compose) elif command -v docker-compose >/dev/null 2>&1; then COMPOSE_CMD=(docker-compose) else echo "ERROR: docker compose not found (docker compose or docker-compose)." exit 1 fi echo "==> Preparing data directories" mkdir -p "${HERMES_DATA_DIR}" mkdir -p "${OPEN_WEBUI_DATA_DIR}" sync_llm_keys_to_hermes_dotenv echo "==> Building image with China mirrors: ${IMAGE_NAME}" if [ -n "${BASE_IMAGE}" ]; then BUILD_BASE_IMAGES=("${BASE_IMAGE}") else IFS=',' read -r -a BUILD_BASE_IMAGES <<< "${BASE_IMAGE_CANDIDATES}" fi BUILD_OK=0 for base in "${BUILD_BASE_IMAGES[@]}"; do echo "==> Trying BASE_IMAGE: ${base}" if docker build \ --build-arg BASE_IMAGE="${base}" \ --build-arg APT_MIRROR_CANDIDATES="${APT_MIRROR_CANDIDATES}" \ --build-arg PIP_INDEX_URL="${PIP_INDEX_URL}" \ --build-arg NPM_REGISTRY="${NPM_REGISTRY}" \ --build-arg PLAYWRIGHT_DOWNLOAD_HOSTS="${PLAYWRIGHT_DOWNLOAD_HOSTS}" \ --build-arg INSTALL_PLAYWRIGHT_BROWSER="${INSTALL_PLAYWRIGHT_BROWSER}" \ --build-arg PLAYWRIGHT_ONLY_SHELL="${PLAYWRIGHT_ONLY_SHELL}" \ -t "${IMAGE_NAME}" \ "${HERMES_PROJECT_DIR}"; then BUILD_OK=1 break fi echo "WARN: Build failed with BASE_IMAGE=${base}, trying next candidate..." done if [ "${BUILD_OK}" -ne 1 ]; then echo "ERROR: image build failed for all BASE_IMAGE candidates." echo "You can manually specify one, e.g.:" echo " BASE_IMAGE=docker.m.daocloud.io/library/debian:13.4 bash start-docker-cn.sh" exit 1 fi echo "==> Writing compose file: ${COMPOSE_FILE}" CORS_ORIGINS="http://127.0.0.1:${API_PORT},http://localhost:${API_PORT},http://localhost:${WEBUI_PORT},http://127.0.0.1:${WEBUI_PORT}" { cat < "${COMPOSE_FILE}" echo "==> Starting containers" if [ "${ENABLE_WEBUI}" = "true" ]; then echo "==> Pulling Open WebUI image if needed: ${OPEN_WEBUI_IMAGE}" docker pull "${OPEN_WEBUI_IMAGE}" || true fi "${COMPOSE_CMD[@]}" -f "${COMPOSE_FILE}" up -d --remove-orphans if [ "${ENABLE_WEBUI}" = "true" ]; then echo "==> Waiting for Web UI to listen on port ${WEBUI_PORT}..." for _i in $(seq 1 90); do if curl -sf "http://127.0.0.1:${WEBUI_PORT}/" >/dev/null 2>&1; then echo "==> Web UI is responding." break fi sleep 1 done if [ "${OPEN_BROWSER}" = "true" ] && [ "$(uname -s)" = "Darwin" ]; then open "http://127.0.0.1:${WEBUI_PORT}" 2>/dev/null || true fi fi echo "==> Done" echo "Config file : ${ENV_FILE}" echo "Hermes data : ${HERMES_DATA_DIR}" echo "Container : ${CONTAINER_NAME}" echo echo "Hermes API (OpenAI-compatible):" echo " http://127.0.0.1:${API_PORT}/v1" echo " API key: ${HERMES_API_KEY}" echo if [ "${ENABLE_WEBUI}" = "true" ]; then echo "Web UI (Open WebUI) — 在浏览器地址栏手动打开(须 http,不要用 https):" echo " http://127.0.0.1:${WEBUI_PORT}" echo " 首次启动可能要 1~3 分钟(下载嵌入模型等),白页请多等一会或看日志。" echo " 自检: curl -sS -o /dev/null -w '%{http_code}\\n' http://127.0.0.1:${WEBUI_PORT}/" echo " 首次进入: 注册本地账号(数据在 ${OPEN_WEBUI_DATA_DIR})" echo fi echo "Quick test:" echo " curl -sS http://127.0.0.1:${API_PORT}/v1/models -H \"Authorization: Bearer ${HERMES_API_KEY}\"" echo echo "Useful commands:" echo " ${COMPOSE_CMD[*]} -f ${COMPOSE_FILE} logs -f hermes" echo " ${COMPOSE_CMD[*]} -f ${COMPOSE_FILE} logs -f open-webui" echo " ${COMPOSE_CMD[*]} -f ${COMPOSE_FILE} down" echo echo "LLM 密钥(必填,否则 Open WebUI 对话会报 No inference provider):" echo " 编辑 ${ENV_FILE} 取消注释并填写 OPENROUTER_API_KEY=(或见同文件说明)" echo " 然后: ${COMPOSE_CMD[*]} -f ${COMPOSE_FILE} up -d" echo " 亦可写入 ${HERMES_DATA_DIR}/.env 后: ${COMPOSE_CMD[*]} -f ${COMPOSE_FILE} restart hermes" echo "Optional: docker exec -it ${CONTAINER_NAME} hermes model" echo " docker exec -it ${CONTAINER_NAME} hermes setup"