first commit

This commit is contained in:
Daniel
2026-04-13 14:56:51 +08:00
commit d25bb3efd5
5 changed files with 334 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
# 本地密钥与数据(配置模板请用 hermes-docker.env.example
hermes-docker.env
open-webui-data/

37
docker-compose.cn.yml Normal file
View 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

Submodule hermes-agent added at fff237e111

54
hermes-docker.env.example Normal file
View 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) 首次启动等 13 分钟再刷新;日志: 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
View 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 " 首次启动可能要 13 分钟(下载嵌入模型等),白页请多等一会或看日志。"
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"