From b1c307e1fe3ce82a8e4f81285926a5e35fa8be0e Mon Sep 17 00:00:00 2001 From: Sense T Date: Fri, 29 May 2026 13:09:15 +0000 Subject: [PATCH] =?UTF-8?q?hermes=E6=9B=B4=E6=96=B0=E5=88=B0v2026.05.29?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- hermes/config/entrypoint.sh | 120 -------------- hermes/config/stage2-hook.sh | 305 +++++++++++++++++++++++++++++++++++ hermes/deploy.yaml | 8 +- hermes/kustomization.yaml | 7 +- 4 files changed, 315 insertions(+), 125 deletions(-) delete mode 100644 hermes/config/entrypoint.sh create mode 100644 hermes/config/stage2-hook.sh diff --git a/hermes/config/entrypoint.sh b/hermes/config/entrypoint.sh deleted file mode 100644 index 05e40a9..0000000 --- a/hermes/config/entrypoint.sh +++ /dev/null @@ -1,120 +0,0 @@ -#!/bin/bash -# Docker/Podman entrypoint: bootstrap config files into the mounted volume, then run hermes. -set -e - -HERMES_HOME="${HERMES_HOME:-/opt/data}" -INSTALL_DIR="/opt/hermes" - -# 这个脚本太烂了,我重新写一个逻辑好了 -# 原本的脚本根本没有考虑用户可以自行修改目录属主的问题 -if [ "$(id -u)" = "0" ]; then - groupadd -g ${HERMES_UID} runner - useradd -u ${HERMES_UID} -g ${HERMES_UID} -d ${HERMES_HOME} runner - echo "!!! Changing ${INSTALL_DIR}/.venv to ${HERMES_UID}:${HERMES_GID}" - chown -R ${HERMES_UID}:${HERMES_GID} "${INSTALL_DIR}/.venv" || exit 1 - # Drop root privilege - - echo "Dropping root privileges" - exec gosu ${HERMES_UID} "$0" "$@" -fi - -# --- Running as hermes from here --- -source "${INSTALL_DIR}/.venv/bin/activate" - -# 这狗*的环境变量! -export PATH=${HERMES_HOME}/home/.local/bin:${HERMES_HOME}/.local/bin:${INSTALL_DIR}/bin:${PATH} - -# Create essential directory structure. Cache and platform directories -# (cache/images, cache/audio, platforms/whatsapp, etc.) are created on -# demand by the application — don't pre-create them here so new installs -# get the consolidated layout from get_hermes_dir(). -# The "home/" subdirectory is a per-profile HOME for subprocesses (git, -# ssh, gh, npm …). Without it those tools write to /root which is -# ephemeral and shared across profiles. See issue #4426. -mkdir -p "$HERMES_HOME"/{cron,sessions,logs,hooks,memories,skills,skins,plans,workspace,home} - -# .env -if [ ! -f "$HERMES_HOME/.env" ]; then - cp "$INSTALL_DIR/.env.example" "$HERMES_HOME/.env" -fi - -# config.yaml -if [ ! -f "$HERMES_HOME/config.yaml" ]; then - cp "$INSTALL_DIR/cli-config.yaml.example" "$HERMES_HOME/config.yaml" -fi - -# SOUL.md -if [ ! -f "$HERMES_HOME/SOUL.md" ]; then - cp "$INSTALL_DIR/docker/SOUL.md" "$HERMES_HOME/SOUL.md" -fi - -# auth.json: bootstrap from env on first boot only. Used by orchestrators -# (e.g. provisioning a Hermes VPS from an account-management service) that -# need to seed the OAuth refresh credential non-interactively, instead of -# walking the user through `hermes setup` + the device-flow login dance. -# Subsequent token rotations write back to the same file, which lives on a -# persistent volume — so this env var is consumed exactly once at first -# boot. The `[ ! -f ... ]` guard is critical: without it, a container -# restart would clobber a rotated refresh token with the now-stale value -# the orchestrator originally seeded. -if [ ! -f "$HERMES_HOME/auth.json" ] && [ -n "$HERMES_AUTH_JSON_BOOTSTRAP" ]; then - printf '%s' "$HERMES_AUTH_JSON_BOOTSTRAP" > "$HERMES_HOME/auth.json" - chmod 600 "$HERMES_HOME/auth.json" -fi - -# Sync bundled skills (manifest-based so user edits are preserved) -if [ -d "$INSTALL_DIR/skills" ]; then - python3 "$INSTALL_DIR/tools/skills_sync.py" -fi - -# Optionally start `hermes dashboard` as a side-process. -# -# Toggled by HERMES_DASHBOARD=1 (also accepts "true"/"yes", case-insensitive). -# Host/port/TUI can be overridden via: -# HERMES_DASHBOARD_HOST (default 0.0.0.0 — exposed outside the container) -# HERMES_DASHBOARD_PORT (default 9119, matches `hermes dashboard` default) -# HERMES_DASHBOARD_TUI (already honored by `hermes dashboard` itself) -# -# The dashboard is a long-lived server. We background it *before* the final -# `exec hermes "$@"` so the user's chosen foreground command (chat, gateway, -# sleep infinity, …) remains PID-of-interest for the container runtime. When -# the container stops the whole process tree is torn down, so no explicit -# cleanup is needed. -case "${HERMES_DASHBOARD:-}" in - 1|true|TRUE|True|yes|YES|Yes) - dash_host="${HERMES_DASHBOARD_HOST:-0.0.0.0}" - dash_port="${HERMES_DASHBOARD_PORT:-9119}" - dash_args=(--host "$dash_host" --port "$dash_port" --no-open) - # Binding to anything other than localhost requires --insecure — the - # dashboard refuses otherwise because it exposes API keys. Inside a - # container this is the expected deployment (host reaches it via - # published port), so opt in automatically. - if [ "$dash_host" != "127.0.0.1" ] && [ "$dash_host" != "localhost" ]; then - dash_args+=(--insecure) - fi - echo "Starting hermes dashboard on ${dash_host}:${dash_port} (background)" - # Prefix dashboard output so it's distinguishable from the main - # process in `docker logs`. stdbuf keeps the pipe line-buffered. - ( - stdbuf -oL -eL hermes dashboard "${dash_args[@]}" 2>&1 \ - | sed -u 's/^/[dashboard] /' - ) & - ;; -esac - -# Final exec: two supported invocation patterns. -# -# docker run -> exec `hermes` with no args (legacy default) -# docker run chat -q "..." -> exec `hermes chat -q "..."` (legacy wrap) -# docker run sleep infinity -> exec `sleep infinity` directly -# docker run bash -> exec `bash` directly -# -# If the first positional arg resolves to an executable on PATH, we assume the -# caller wants to run it directly (needed by the launcher which runs long-lived -# `sleep infinity` sandbox containers — see tools/environments/docker.py). -# Otherwise we treat the args as a hermes subcommand and wrap with `hermes`, -# preserving the documented `docker run ` behavior. -if [ $# -gt 0 ] && command -v "$1" >/dev/null 2>&1; then - exec "$@" -fi -exec hermes "$@" \ No newline at end of file diff --git a/hermes/config/stage2-hook.sh b/hermes/config/stage2-hook.sh new file mode 100644 index 0000000..91e2fc5 --- /dev/null +++ b/hermes/config/stage2-hook.sh @@ -0,0 +1,305 @@ +#!/bin/sh +# s6-overlay stage2 hook — runs as root after the supervision tree is +# up but before user services start. Handles UID/GID remap, volume +# chown, config seeding, and skills sync. +# +# Per-service privilege drop happens inside each service's `run` script +# (and in main-wrapper.sh) via s6-setuidgid, not here. +# +# Wired into the image as /etc/cont-init.d/01-hermes-setup by the +# Dockerfile. The shim at docker/entrypoint.sh forwards to this script +# so external references to docker/entrypoint.sh still work. +# +# NB: cont-init.d scripts run with no arguments — the user's CMD args +# are NOT visible here. That's fine: we use Architecture B (s6-overlay +# main-program model), so main-wrapper.sh runs the CMD with full +# stdin/stdout/stderr access and handles arg parsing there. + +set -eu + +HERMES_HOME="${HERMES_HOME:-/opt/data}" +INSTALL_DIR="/opt/hermes" + +# --- Bootstrap HERMES_HOME as root --- +# Create the directory (and any missing parents) while we still have root +# privileges so the chown checks below see real metadata and the later +# `s6-setuidgid hermes mkdir -p` block doesn't EACCES on root-owned +# ancestors. Without this, custom HERMES_HOME paths whose parents only +# root can create (e.g. `HERMES_HOME=/home/hermes/.hermes` in a Compose +# file, or any path under a fresh / not pre-populated by the image) +# fail on first boot with `mkdir: cannot create directory '/...': Permission +# denied` and the cont-init hook exits non-zero. Idempotent — `mkdir -p` +# is a no-op if the dir already exists. (#18482, salvages #18488) +mkdir -p "$HERMES_HOME" + +# --- UID/GID remap --- +# Accept PUID/PGID as aliases for HERMES_UID/HERMES_GID. NAS users (UGOS, +# Synology, unRAID) expect the LinuxServer.io PUID/PGID convention and +# bind-mount /opt/data from a host directory owned by their own UID; without +# this alias those vars are silently ignored and the s6-setuidgid drop to +# UID 10000 leaves the runtime unable to read the volume. HERMES_UID/ +# HERMES_GID still win when both are set. See #15290, salvages #25872. +HERMES_UID="${HERMES_UID:-${PUID:-}}" +HERMES_GID="${HERMES_GID:-${PGID:-}}" + +if [ -n "${HERMES_UID:-}" ] && [ "$HERMES_UID" != "$(id -u hermes)" ]; then + echo "[stage2] Changing hermes UID to $HERMES_UID" + # FIX: HOME 先转移到一个临时目录,改完UID再改回原 HOME + usermod -d /tmp/tmp-home hermes + usermod -u "$HERMES_UID" hermes + usermod -d "$HERMES_HOME" hermes +fi +if [ -n "${HERMES_GID:-}" ] && [ "$HERMES_GID" != "$(id -g hermes)" ]; then + echo "[stage2] Changing hermes GID to $HERMES_GID" + # -o allows non-unique GID (e.g. macOS GID 20 "staff" may already + # exist as "dialout" in the Debian-based container image). + groupmod -o -g "$HERMES_GID" hermes 2>/dev/null || true +fi + +# --- Docker socket group membership (docker-in-docker / DooD) --- +# When the user bind-mounts the host Docker daemon socket +# (`-v /var/run/docker.sock:/var/run/docker.sock`) to use the `docker` +# terminal backend from inside the container, the socket is owned by the +# host's `docker` group (or root). The supervised hermes user (UID 10000) +# is not a member of any group that matches the socket's GID, so every +# `docker` invocation EACCES'es and `check_terminal_requirements()` fails. +# See #16703. +# +# Granting the supp group via `docker run --group-add ` alone is +# NOT sufficient with our s6-setuidgid privilege drop: s6-setuidgid (and +# gosu, the older shim) calls initgroups() for the target user, which +# rebuilds the supplementary group list from /etc/group. Without an +# /etc/group entry whose GID matches the socket, the kernel-granted +# supp group is silently wiped between PID 1 and the dropped process. +# Confirmed empirically: `--group-add 998` alone leaves the dropped +# hermes process with `Groups: 10000` (998 gone); after this hook adds +# the entry, the dropped process has `Groups: 998 10000` as expected. +# +# Fix: detect the socket's GID at boot and ensure /etc/group has a +# matching entry that includes hermes. Idempotent across container +# restarts. Skipped silently when no socket is bind-mounted. +# +# Handles the awkward corner cases: +# - socket owned by GID 0 (root) — some Podman setups; usermod -aG root +# - socket GID already used by a known container group (e.g. tty=5): +# reuse that group's name rather than creating a duplicate +# - hermes is already a member of the right group (idempotent restart) +# - chown/groupadd failures under rootless containers — non-fatal +for sock in /var/run/docker.sock /run/docker.sock; do + [ -S "$sock" ] || continue + sock_gid=$(stat -c '%g' "$sock" 2>/dev/null) || continue + [ -n "$sock_gid" ] || continue + # Already a member? Nothing to do. + if id -G hermes 2>/dev/null | tr ' ' '\n' | grep -qx "$sock_gid"; then + echo "[stage2] hermes already in group $sock_gid for $sock" + break + fi + # Resolve or create a group name for this GID. + sock_group=$(getent group "$sock_gid" 2>/dev/null | cut -d: -f1) + if [ -z "$sock_group" ]; then + sock_group="hostdocker" + if ! groupadd -g "$sock_gid" "$sock_group" 2>/dev/null; then + echo "[stage2] Warning: groupadd -g $sock_gid $sock_group failed; skipping docker socket group setup" + break + fi + echo "[stage2] Created group $sock_group (GID $sock_gid) for Docker socket" + fi + if usermod -aG "$sock_group" hermes 2>/dev/null; then + echo "[stage2] Added hermes to group $sock_group (GID $sock_gid) for $sock" + else + echo "[stage2] Warning: usermod -aG $sock_group hermes failed; docker backend may fail with EACCES" + fi + break +done + +# --- Fix ownership of data volume --- +# When HERMES_UID is remapped or the top-level $HERMES_HOME isn't owned by +# the runtime hermes UID, restore ownership to hermes — but ONLY for the +# directories hermes actually writes to. The full $HERMES_HOME may be a +# host-mounted bind containing unrelated user files; `chown -R` would +# silently destroy host ownership of those (see issue #19788). +# +# The canonical list of hermes-owned subdirs is the same one the s6-setuidgid +# mkdir -p block below seeds. Keep them in sync if the seed list changes. +actual_hermes_uid=$(id -u hermes) +needs_chown=false +if [ -n "${HERMES_UID:-}" ] && [ "$HERMES_UID" != "10000" ]; then + needs_chown=true +elif [ "$(stat -c %u "$HERMES_HOME" 2>/dev/null)" != "$actual_hermes_uid" ]; then + needs_chown=true +fi + +# FIX: K8S 环境下多此一举了 +needs_chown=false +if [ "$needs_chown" = true ]; then + echo "[stage2] Fixing ownership of $HERMES_HOME (targeted) to hermes ($actual_hermes_uid)" + # In rootless Podman the container's "root" is mapped to an + # unprivileged host UID — chown will fail. That's fine: the volume + # is already owned by the mapped user on the host side. + # + # Top-level $HERMES_HOME: chown the directory itself (not its contents) + # so hermes can mkdir new subdirs but bind-mounted host files keep + # their existing ownership. + chown hermes:hermes "$HERMES_HOME" 2>/dev/null || \ + echo "[stage2] Warning: chown $HERMES_HOME failed (rootless container?) — continuing" + # Hermes-owned subdirs: recursive chown is safe here because these are + # created and managed exclusively by hermes (see the s6-setuidgid mkdir + # -p block below for the canonical list). + for sub in cron sessions logs hooks memories skills skins plans workspace home profiles; do + if [ -e "$HERMES_HOME/$sub" ]; then + chown -R hermes:hermes "$HERMES_HOME/$sub" 2>/dev/null || \ + echo "[stage2] Warning: chown $HERMES_HOME/$sub failed (rootless container?) — continuing" + fi + done + # Hermes-owned trees under $INSTALL_DIR must be re-chowned when the UID + # is remapped — otherwise: + # - .venv: lazy_deps.py cannot install platform packages (discord.py, + # telegram, slack, etc.) with EACCES (#15012, #21100) + # - ui-tui: esbuild rebuilds dist/entry.js on every TUI launch (when + # the source mtime is newer than dist/ or when HERMES_TUI_FORCE_BUILD + # is set) and writes to ui-tui/dist/. Without this chown the new + # hermes UID can't write the build output (#28851). + # - node_modules: root-level dependencies (puppeteer, web tooling) + # that runtime code may walk/update. + # The set mirrors the build-time `chown -R hermes:hermes` line in the + # Dockerfile — keep them in sync if the Dockerfile chown set changes. + # These are under $INSTALL_DIR (not $HERMES_HOME), so the bind-mount + # concern doesn't apply — recursive is fine. + chown -R hermes:hermes \ + "$INSTALL_DIR/.venv" \ + "$INSTALL_DIR/ui-tui" \ + "$INSTALL_DIR/node_modules" \ + 2>/dev/null || \ + echo "[stage2] Warning: chown of build trees failed (rootless container?) — continuing" +fi + +# Always reset ownership of $HERMES_HOME/profiles to hermes on every +# boot. Profile dirs and files can land owned by root when commands +# are invoked via `docker exec hermes …` (which defaults +# to root unless `-u` is passed), and that breaks the cont-init +# reconciler (02-reconcile-profiles) which runs as hermes and walks +# the profiles dir. Idempotent; skipped on rootless containers where +# chown would fail. +if [ -d "$HERMES_HOME/profiles" ]; then + chown -R hermes:hermes "$HERMES_HOME/profiles" 2>/dev/null || true +fi + +# --- config.yaml permissions --- +# Ensure config.yaml is readable by the hermes runtime user even if it +# was edited on the host after initial ownership setup. +if [ -f "$HERMES_HOME/config.yaml" ]; then + chown hermes:hermes "$HERMES_HOME/config.yaml" 2>/dev/null || true + chmod 640 "$HERMES_HOME/config.yaml" 2>/dev/null || true +fi + +# --- Seed directory structure as hermes user --- +# Run as hermes via s6-setuidgid so dirs end up owned correctly (matters +# under rootless Podman where chown back to root would fail). +# +# Use direct `mkdir -p` invocation (no `sh -c "..."` wrapper) so the +# shell isn't a second interpreter — defends against $HERMES_HOME values +# containing shell metacharacters. PR #30136 review item O2. +s6-setuidgid hermes mkdir -p \ + "$HERMES_HOME/cron" \ + "$HERMES_HOME/sessions" \ + "$HERMES_HOME/logs" \ + "$HERMES_HOME/hooks" \ + "$HERMES_HOME/memories" \ + "$HERMES_HOME/skills" \ + "$HERMES_HOME/skins" \ + "$HERMES_HOME/plans" \ + "$HERMES_HOME/workspace" \ + "$HERMES_HOME/home" + +# --- Install-method stamp (read by detect_install_method() in hermes status) --- +# Preserved from the tini-era entrypoint (PR #27843). Must be written as +# the hermes user so ownership matches the file's documented owner. +# tee is invoked directly via s6-setuidgid (no `sh -c` wrapper) for the +# same shell-metacharacter safety described above. +printf 'docker\n' | s6-setuidgid hermes tee "$HERMES_HOME/.install_method" >/dev/null \ + || true + +# --- Seed config files (only on first boot) --- +seed_one() { + dest=$1 + src=$2 + if [ ! -f "$HERMES_HOME/$dest" ] && [ -f "$INSTALL_DIR/$src" ]; then + s6-setuidgid hermes cp "$INSTALL_DIR/$src" "$HERMES_HOME/$dest" + fi +} +seed_one ".env" ".env.example" +seed_one "config.yaml" "cli-config.yaml.example" +seed_one "SOUL.md" "docker/SOUL.md" + +# .env holds API keys and secrets — restrict to owner-only access. Applied +# unconditionally (not only on first-seed) so a host-mounted .env that was +# created with a permissive umask gets tightened on every container start. +if [ -f "$HERMES_HOME/.env" ]; then + chown hermes:hermes "$HERMES_HOME/.env" 2>/dev/null || true + chmod 600 "$HERMES_HOME/.env" 2>/dev/null || true +fi + +# auth.json: bootstrap from env on first boot only. Same semantics as the +# pre-s6 entrypoint — the [ ! -f ] guard is critical to avoid clobbering +# rotated refresh tokens on container restart. +if [ ! -f "$HERMES_HOME/auth.json" ] && [ -n "${HERMES_AUTH_JSON_BOOTSTRAP:-}" ]; then + printf '%s' "$HERMES_AUTH_JSON_BOOTSTRAP" > "$HERMES_HOME/auth.json" + chown hermes:hermes "$HERMES_HOME/auth.json" 2>/dev/null || true + chmod 600 "$HERMES_HOME/auth.json" +fi + +# --- Sync bundled skills --- +# Invoke the venv's python by absolute path so we don't need a `sh -c` +# wrapper to source the activate script. This is safe because +# skills_sync.py doesn't depend on any environment exports beyond what +# the python binary's own bin-stub already sets up (sys.path is rooted +# at the venv's site-packages by virtue of running .venv/bin/python). +if [ -d "$INSTALL_DIR/skills" ]; then + s6-setuidgid hermes "$INSTALL_DIR/.venv/bin/python" "$INSTALL_DIR/tools/skills_sync.py" \ + || echo "[stage2] Warning: skills_sync.py failed; continuing" +fi + +# --- Discover agent-browser's Chromium binary --- +# The image's Dockerfile runs `npx playwright install chromium`, which +# populates ``$PLAYWRIGHT_BROWSERS_PATH`` (=/opt/hermes/.playwright) with +# a ``chromium_headless_shell-/chrome-headless-shell-linux64/`` +# directory. agent-browser (the runtime CLI Hermes spawns for the +# browser tool) doesn't recognise this layout in its own cache scan and +# fails with "Auto-launch failed: Chrome not found" — even though the +# binary is right there (#15697). +# +# Fix: locate the binary at boot and export ``AGENT_BROWSER_EXECUTABLE_PATH`` +# via /run/s6/container_environment so the `with-contenv` shebang on +# main-wrapper.sh propagates it into the supervised ``hermes`` process +# and thence to agent-browser subprocesses. +# +# - Skipped when the user has already set ``AGENT_BROWSER_EXECUTABLE_PATH`` +# (lets users override with a system Chrome install). +# - Filename-matched (not path-matched): the chromium dir contains many +# shared libraries (libGLESv2.so, libEGL.so, ...) which inherit the +# executable bit from Playwright's tarball but are NOT browser binaries. +# We only accept files whose basename is chrome / chromium / +# chrome-headless-shell / chromium-browser. Compare PR #18635's earlier +# ``find | grep -Ei 'chrome|chromium'`` which would match the path +# ``.../chrome-headless-shell-linux64/libGLESv2.so`` and pick a .so. +# - Quietly skipped when $PLAYWRIGHT_BROWSERS_PATH doesn't exist (e.g. +# custom builds that strip Playwright). +if [ -z "${AGENT_BROWSER_EXECUTABLE_PATH:-}" ] && \ + [ -n "${PLAYWRIGHT_BROWSERS_PATH:-}" ] && \ + [ -d "$PLAYWRIGHT_BROWSERS_PATH" ]; then + browser_bin=$(find "$PLAYWRIGHT_BROWSERS_PATH" -type f -executable \ + \( -name 'chrome' -o -name 'chromium' \ + -o -name 'chrome-headless-shell' -o -name 'chromium-browser' \) \ + 2>/dev/null | head -n 1) + if [ -n "$browser_bin" ]; then + echo "[stage2] Found agent-browser Chromium binary: $browser_bin" + # Write to s6's container_environment so with-contenv picks it + # up for all supervised services (main-hermes, dashboard, etc.). + # Idempotent: each boot overwrites with the current path. + printf '%s' "$browser_bin" > /run/s6/container_environment/AGENT_BROWSER_EXECUTABLE_PATH + else + echo "[stage2] Warning: no Chromium binary under $PLAYWRIGHT_BROWSERS_PATH; browser tool may fail" + fi +fi + +echo "[stage2] Setup complete; starting user services" \ No newline at end of file diff --git a/hermes/deploy.yaml b/hermes/deploy.yaml index a504178..5301ebd 100644 --- a/hermes/deploy.yaml +++ b/hermes/deploy.yaml @@ -42,7 +42,7 @@ spec: httpGet: path: /health port: 8642 - initialDelaySeconds: 60 + initialDelaySeconds: 180 periodSeconds: 10 successThreshold: 1 failureThreshold: 3 @@ -81,6 +81,8 @@ spec: value: '::' - name: HERMES_DASHBOARD_HOST value: '9119' + - name: HERMES_DASHBOARD_INSECURE + value: "1" envFrom: - secretRef: name: hermes @@ -101,8 +103,8 @@ spec: - name: tmp mountPath: /tmp - name: start-sh - mountPath: /opt/hermes/docker/entrypoint.sh - subPath: entrypoint.sh + mountPath: /opt/hermes/docker/stage2-hook.sh + subPath: stage2-hook.sh resources: requests: memory: "1Gi" diff --git a/hermes/kustomization.yaml b/hermes/kustomization.yaml index 472d8b4..9469d54 100644 --- a/hermes/kustomization.yaml +++ b/hermes/kustomization.yaml @@ -10,7 +10,7 @@ resources: images: - name: image newName: cr.wetofu.me/nousresearch/hermes-agent - newTag: v2026.5.16 + newTag: v2026.5.29 secretGenerator: - name: hermes files: @@ -29,6 +29,9 @@ configMapGenerator: - config/TELEGRAM_REQUIRE_MENTION - config/SIYUAN_URL - config/PATH + - config/NPM_CONFIG_REGISTRY + - config/UV_INDEX_URL + - config/PIP_INDEX_URL - name: hermes-start files: - - config/entrypoint.sh \ No newline at end of file + - config/stage2-hook.sh \ No newline at end of file