first commit
This commit is contained in:
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# 本地密钥与数据(配置模板请用 hermes-docker.env.example)
|
||||||
|
hermes-docker.env
|
||||||
|
open-webui-data/
|
||||||
37
docker-compose.cn.yml
Normal file
37
docker-compose.cn.yml
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
services:
|
||||||
|
hermes:
|
||||||
|
image: hermes-agent:cn-local
|
||||||
|
container_name: hermes
|
||||||
|
restart: unless-stopped
|
||||||
|
command: gateway run
|
||||||
|
ports:
|
||||||
|
- "8642:8642"
|
||||||
|
volumes:
|
||||||
|
- /Users/dannier/.hermes:/opt/data
|
||||||
|
shm_size: "1gb"
|
||||||
|
env_file:
|
||||||
|
- /Users/dannier/Desktop/living/hh/hermes-docker.env
|
||||||
|
environment:
|
||||||
|
- TZ=Asia/Shanghai
|
||||||
|
- API_SERVER_ENABLED=true
|
||||||
|
- API_SERVER_HOST=0.0.0.0
|
||||||
|
- "API_SERVER_KEY=change-me-local-dev"
|
||||||
|
- "API_SERVER_CORS_ORIGINS=http://127.0.0.1:8642,http://localhost:8642,http://localhost:3000,http://127.0.0.1:3000"
|
||||||
|
|
||||||
|
open-webui:
|
||||||
|
image: ghcr.io/open-webui/open-webui:main
|
||||||
|
container_name: hermes-open-webui
|
||||||
|
restart: unless-stopped
|
||||||
|
depends_on:
|
||||||
|
- hermes
|
||||||
|
ports:
|
||||||
|
- "3000:8080"
|
||||||
|
environment:
|
||||||
|
- "OPENAI_API_BASE_URL=http://hermes:8642/v1"
|
||||||
|
- "OPENAI_API_KEY=change-me-local-dev"
|
||||||
|
- "WEBUI_SECRET_KEY=dev-change-me-in-production"
|
||||||
|
- ENABLE_SIGNUP=true
|
||||||
|
- ENABLE_OLLAMA_API=false
|
||||||
|
volumes:
|
||||||
|
- /Users/dannier/Desktop/living/hh/open-webui-data:/app/backend/data
|
||||||
|
|
||||||
1
hermes-agent
Submodule
1
hermes-agent
Submodule
Submodule hermes-agent added at fff237e111
54
hermes-docker.env.example
Normal file
54
hermes-docker.env.example
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
# =============================================================================
|
||||||
|
# Hermes 本地 Docker 配置(可见、可改)
|
||||||
|
# 用法:复制为 hermes-docker.env 后编辑;start-docker-cn.sh 会自动读取
|
||||||
|
# cp hermes-docker.env.example hermes-docker.env
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
# --- Hermes HTTP API(给 Open WebUI / curl 使用)---
|
||||||
|
API_PORT=8642
|
||||||
|
HERMES_API_KEY=change-me-local-dev
|
||||||
|
|
||||||
|
# --- LLM 推理(必填其一,否则对话报错:No inference provider configured)---
|
||||||
|
# start-docker-cn.sh 会把下面非空变量同步到 ~/.hermes/.env(必须:Hermes 启动时用该文件
|
||||||
|
# override Docker 注入的环境变量,只写在 hermes-docker.env 而不同步时会出现 401)
|
||||||
|
# OpenAI: https://platform.openai.com/api-keys
|
||||||
|
# OPENAI_API_KEY=sk-proj-...
|
||||||
|
# OpenRouter: https://openrouter.ai/keys
|
||||||
|
# OPENROUTER_API_KEY=sk-or-v1-...
|
||||||
|
# 其他见 hermes-agent/.env.example
|
||||||
|
|
||||||
|
# --- Open WebUI(浏览器聊天界面)---
|
||||||
|
WEBUI_PORT=3000
|
||||||
|
ENABLE_WEBUI=true
|
||||||
|
# macOS 启动脚本完成后是否自动打开浏览器(Linux 忽略)
|
||||||
|
OPEN_BROWSER=true
|
||||||
|
# 会话加密用,本地可保留;若暴露到公网请改成随机长字符串
|
||||||
|
WEBUI_SECRET_KEY=dev-change-me-in-production
|
||||||
|
|
||||||
|
# Open WebUI 镜像(国内拉不动时可换镜像代理前缀,见脚本注释)
|
||||||
|
OPEN_WEBUI_IMAGE=ghcr.io/open-webui/open-webui:main
|
||||||
|
|
||||||
|
# --- 数据目录(宿主机)---
|
||||||
|
# 留空则使用 ~/.hermes
|
||||||
|
HERMES_DATA_DIR=
|
||||||
|
|
||||||
|
# Open WebUI 持久化数据目录(留空则使用项目下 open-webui-data/)
|
||||||
|
OPEN_WEBUI_DATA_DIR=
|
||||||
|
|
||||||
|
# --- 构建镜像用(一般不用改)---
|
||||||
|
# BASE_IMAGE=docker.m.daocloud.io/library/debian:13.4
|
||||||
|
# APT_MIRROR_CANDIDATES=mirrors.aliyun.com,mirrors.ustc.edu.cn,mirrors.huaweicloud.com,mirrors.tuna.tsinghua.edu.cn
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# 看不到 Web 界面时排查:
|
||||||
|
# 1) 地址必须是 http://127.0.0.1:与 WEBUI_PORT 一致(不要用 https://)
|
||||||
|
# 2) 首次启动等 1~3 分钟再刷新;日志: docker logs -f hermes-open-webui
|
||||||
|
# 3) 自检: curl -sS -o /dev/null -w "%{http_code}\n" http://127.0.0.1:3000/
|
||||||
|
# 返回 200 说明服务已好,多半是浏览器缓存或输错端口
|
||||||
|
# 4) 端口被占用可改 WEBUI_PORT,再执行 ./start-docker-cn.sh
|
||||||
|
#
|
||||||
|
# 浏览器控制台若出现:
|
||||||
|
# - Manifest / tiptap 警告:多为 Open WebUI 前端已知提示,可忽略
|
||||||
|
# - /ollama/api/version 500:已用 ENABLE_OLLAMA_API=false;若仍有,可清浏览器缓存或删 open-webui-data 后重建容器
|
||||||
|
# - Hermes 日志 401 Missing Authentication:在 ~/.hermes/.env 或本文件填写 OPENROUTER_API_KEY(勿留空行)
|
||||||
|
# =============================================================================
|
||||||
239
start-docker-cn.sh
Executable file
239
start-docker-cn.sh
Executable file
@@ -0,0 +1,239 @@
|
|||||||
|
#!/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 <<EOF
|
||||||
|
services:
|
||||||
|
hermes:
|
||||||
|
image: ${IMAGE_NAME}
|
||||||
|
container_name: ${CONTAINER_NAME}
|
||||||
|
restart: unless-stopped
|
||||||
|
command: gateway run
|
||||||
|
ports:
|
||||||
|
- "${API_PORT}:8642"
|
||||||
|
volumes:
|
||||||
|
- ${HERMES_DATA_DIR}:/opt/data
|
||||||
|
shm_size: "1gb"
|
||||||
|
env_file:
|
||||||
|
- ${ENV_FILE}
|
||||||
|
environment:
|
||||||
|
- TZ=Asia/Shanghai
|
||||||
|
- API_SERVER_ENABLED=true
|
||||||
|
- API_SERVER_HOST=0.0.0.0
|
||||||
|
- "API_SERVER_KEY=${HERMES_API_KEY}"
|
||||||
|
- "API_SERVER_CORS_ORIGINS=${CORS_ORIGINS}"
|
||||||
|
EOF
|
||||||
|
|
||||||
|
if [ "${ENABLE_WEBUI}" = "true" ]; then
|
||||||
|
cat <<EOF
|
||||||
|
|
||||||
|
open-webui:
|
||||||
|
image: ${OPEN_WEBUI_IMAGE}
|
||||||
|
container_name: hermes-open-webui
|
||||||
|
restart: unless-stopped
|
||||||
|
depends_on:
|
||||||
|
- hermes
|
||||||
|
ports:
|
||||||
|
- "${WEBUI_PORT}:8080"
|
||||||
|
environment:
|
||||||
|
- "OPENAI_API_BASE_URL=http://hermes:8642/v1"
|
||||||
|
- "OPENAI_API_KEY=${HERMES_API_KEY}"
|
||||||
|
- "WEBUI_SECRET_KEY=${WEBUI_SECRET_KEY}"
|
||||||
|
- ENABLE_SIGNUP=true
|
||||||
|
- ENABLE_OLLAMA_API=false
|
||||||
|
volumes:
|
||||||
|
- ${OPEN_WEBUI_DATA_DIR}:/app/backend/data
|
||||||
|
EOF
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
} > "${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"
|
||||||
Reference in New Issue
Block a user