mirror of
https://github.com/tinyhumansai/openhuman.git
synced 2026-07-28 05:12:33 +00:00
+16




![github-actions[bot] <github-actions[bot]@users.noreply.github.com>](/assets/img/avatar_default.png)




Mega Mind
GitHub
YellowSnnowmann
Steven Enamakel
github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Cyrus Gray
Horst1993
Cursor
James Gentes
Sam
Sami Rusani
oxoxDev
Muhammad Ismail
Claude Fable 5
nb213
binyangzhu000-sudo
Steven Enamakel
CodeGhost21
sanil-23
M3gA-Mind
oxoxDev
mysma-9403
mwakidenis
NgoQuocViet2001
viet.ngo
Maciej Myszkiewicz
2e5b5e7b23
Co-authored-by: YellowSnnowmann <167776381+YellowSnnowmann@users.noreply.github.com> Co-authored-by: Steven Enamakel <31011319+senamakel@users.noreply.github.com> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Cyrus Gray <144336577+graycyrus@users.noreply.github.com> Co-authored-by: Horst1993 <horst.w@gmicloud.ai> Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: James Gentes <jgentes@users.noreply.github.com> Co-authored-by: Sam <samrusani@users.noreply.github.com> Co-authored-by: Sami Rusani <14844597+samrusani@users.noreply.github.com> Co-authored-by: oxoxDev <164490987+oxoxDev@users.noreply.github.com> Co-authored-by: Muhammad Ismail <78064250+myi1@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: nb213 <binyangzhu000@gmail.com> Co-authored-by: binyangzhu000-sudo <224954946+binyangzhu000-sudo@users.noreply.github.com> Co-authored-by: Steven Enamakel <enamakel@tinyhumans.ai> Co-authored-by: CodeGhost21 <164498022+CodeGhost21@users.noreply.github.com> Co-authored-by: sanil-23 <sanil@tinyhumans.ai> Co-authored-by: M3gA-Mind <elvin@mahadao.com> Co-authored-by: oxoxDev <oxoxdev@users.noreply.github.com> Co-authored-by: mysma-9403 <64923976+mysma-9403@users.noreply.github.com> Co-authored-by: mwakidenis <mwakidenice@gmail.com> Co-authored-by: NgoQuocViet2001 <123613986+NgoQuocViet2001@users.noreply.github.com> Co-authored-by: viet.ngo <viet.ngo@sotatek.com> Co-authored-by: Maciej Myszkiewicz <mmyszkiewicz@bwcoders.com>
200 lines
10 KiB
Bash
200 lines
10 KiB
Bash
#!/bin/sh
|
|
# docker-entrypoint-core.sh — runtime entrypoint for the openhuman-core container.
|
|
#
|
|
# Problem: Docker named volumes are created owned root:root, even when the image
|
|
# has a non-root USER. The first write openhuman-core makes after the banner
|
|
# (init_rpc_token → write_token_file → create_dir_all) hits EACCES and the
|
|
# process exits with code 1.
|
|
#
|
|
# Fix (gosu pattern):
|
|
# 1. Start as root so we can chown the mount point(s).
|
|
# 2. mkdir -p + chown the workspace directory *and everything inside it*
|
|
# before any application code runs, so the openhuman user owns it
|
|
# regardless of whether Docker created the volume as root.
|
|
# 3. exec gosu openhuman to drop privileges and hand off to the binary.
|
|
#
|
|
# The chown MUST be recursive. Healing only the directory inode leaves any
|
|
# file inside it — most importantly `config.toml`, which the core writes at
|
|
# mode 0600 — owned by whichever uid created it. A workspace volume that was
|
|
# seeded by an older image, restored from a backup, or written by a root
|
|
# `docker exec` therefore produced:
|
|
#
|
|
# Failed to read config file: /home/openhuman/.openhuman/config.toml:
|
|
# Permission denied (os error 13)
|
|
#
|
|
# on every config-dependent RPC (sign-in included) while the container kept
|
|
# answering /health with 200.
|
|
#
|
|
# This is idempotent: files already owned by the openhuman user are skipped, so
|
|
# a healthy re-used volume costs one `find` traversal. No manual
|
|
# "docker volume rm" is required when upgrading from a previously broken image.
|
|
#
|
|
# Requirements: gosu must be installed in the image (see Dockerfile), and the
|
|
# container needs CAP_CHOWN + CAP_SETUID + CAP_SETGID. A capability-denied
|
|
# chown is a warning, not a fatal error — the readability preflight below is
|
|
# what decides whether the core can actually start.
|
|
# POSIX sh — no bashisms.
|
|
set -e
|
|
|
|
OPENHUMAN_USER="openhuman"
|
|
OPENHUMAN_UID="$(id -u "${OPENHUMAN_USER}" 2>/dev/null || echo '')"
|
|
OPENHUMAN_GID="$(id -g "${OPENHUMAN_USER}" 2>/dev/null || echo '')"
|
|
|
|
# The workspace path the core will actually write to.
|
|
# Prefer the env var if set; otherwise fall back to the image default.
|
|
WORKSPACE_DIR="${OPENHUMAN_WORKSPACE:-/home/openhuman/.openhuman}"
|
|
# The home directory (where core.token is written when OPENHUMAN_CORE_TOKEN is
|
|
# unset — see src/core/auth.rs default_root_openhuman_dir()).
|
|
HOME_OPENHUMAN_DIR="/home/openhuman/.openhuman"
|
|
|
|
echo "[docker-entrypoint] uid=$(id -u), gid=$(id -g), user=$(id -un 2>/dev/null || echo unknown)"
|
|
echo "[docker-entrypoint] target user=${OPENHUMAN_USER} uid=${OPENHUMAN_UID} gid=${OPENHUMAN_GID}"
|
|
echo "[docker-entrypoint] WORKSPACE_DIR=${WORKSPACE_DIR}"
|
|
echo "[docker-entrypoint] HOME_OPENHUMAN_DIR=${HOME_OPENHUMAN_DIR}"
|
|
|
|
# Make DIR and every entry under it owned by the openhuman user.
|
|
heal_dir() {
|
|
_dir="$1"
|
|
|
|
if ! mkdir -p "${_dir}" 2>/dev/null; then
|
|
echo "[docker-entrypoint] WARN mkdir -p ${_dir} failed (read-only filesystem?)"
|
|
fi
|
|
|
|
# Numeric owner/mode of the dir and of config.toml, before any repair.
|
|
# These three numbers are what turn a bare "Permission denied" into a
|
|
# diagnosis, and they are the first thing to ask a reporter for.
|
|
ls -ldn "${_dir}" 2>/dev/null | sed 's/^/[docker-entrypoint] pre-heal /' || true
|
|
if [ -e "${_dir}/config.toml" ]; then
|
|
ls -ln "${_dir}/config.toml" 2>/dev/null \
|
|
| sed 's/^/[docker-entrypoint] pre-heal /' || true
|
|
fi
|
|
|
|
# Only touch entries that are actually mis-owned: on a healthy volume this
|
|
# is a no-op traversal, and it avoids rewriting ctime on every file in a
|
|
# large workspace (memory DBs, caches) at each container start.
|
|
#
|
|
# `-h` (lchown) is load-bearing, not a nicety. Plain `chown` DEREFERENCES
|
|
# symlinks, and this runs as root over a directory the (unprivileged) core
|
|
# can write: a symlink planted at `${_dir}/x -> /etc/shadow` would otherwise
|
|
# hand uid 10001 ownership of the target on the next container start.
|
|
# `find` itself does not follow symlinks (-P is the default), so `-h`
|
|
# closes the last dereference. Hardlinks are not a path here: the workspace
|
|
# volume is its own filesystem, and hardlinks cannot cross devices.
|
|
# Numeric uid:gid rather than `openhuman:openhuman`: the name pair only
|
|
# works while the primary group happens to share the user's name, and it
|
|
# keeps this symmetric with the numeric remedy printed on failure below.
|
|
if find "${_dir}" ! -user "${OPENHUMAN_USER}" -exec \
|
|
chown -h "${OPENHUMAN_UID}:${OPENHUMAN_GID}" {} + 2>/dev/null; then
|
|
echo "[docker-entrypoint] heal ${_dir} -> ${OPENHUMAN_UID}:${OPENHUMAN_GID} done"
|
|
else
|
|
echo "[docker-entrypoint] WARN chown under ${_dir} failed — no CAP_CHOWN? (cap_drop: ALL)"
|
|
echo "[docker-entrypoint] WARN files not owned by uid=${OPENHUMAN_UID} will be unreadable"
|
|
fi
|
|
}
|
|
|
|
# Every directory the core may resolve a config.toml out of, deduplicated.
|
|
#
|
|
# WORKSPACE_DIR OPENHUMAN_WORKSPACE, the primary candidate.
|
|
# HOME_OPENHUMAN_DIR core.token always lands in $HOME/.openhuman, whatever
|
|
# OPENHUMAN_WORKSPACE says.
|
|
# LEGACY_DIR `resolve_config_dir_for_workspace`
|
|
# (src/openhuman/config/schema/load/dirs.rs) falls back to
|
|
# `<parent-of-workspace>/.openhuman` when the workspace
|
|
# itself holds no config.toml. For the image default the
|
|
# three collapse to one path; a custom OPENHUMAN_WORKSPACE
|
|
# makes them diverge, and healing only the first left the
|
|
# actually-resolved config untouched.
|
|
#
|
|
# Paths are assumed free of whitespace (they are container paths baked into the
|
|
# image or set via env); POSIX sh has no arrays to do better cheaply.
|
|
CONFIG_DIRS=""
|
|
add_config_dir() {
|
|
_candidate="$1"
|
|
if [ -z "${_candidate}" ]; then
|
|
return 0
|
|
fi
|
|
for _existing in ${CONFIG_DIRS}; do
|
|
if [ "${_existing}" = "${_candidate}" ]; then
|
|
return 0
|
|
fi
|
|
done
|
|
CONFIG_DIRS="${CONFIG_DIRS} ${_candidate}"
|
|
return 0
|
|
}
|
|
|
|
add_config_dir "${WORKSPACE_DIR}"
|
|
add_config_dir "${HOME_OPENHUMAN_DIR}"
|
|
# The legacy candidate is DERIVED, not configured, so it is only a candidate
|
|
# when it already exists: `heal_dir` runs `mkdir -p`, and materializing an empty
|
|
# sibling `.openhuman` that the core may never resolve into is a side effect a
|
|
# healthy start should not have.
|
|
LEGACY_DIR="$(dirname "${WORKSPACE_DIR}")/.openhuman"
|
|
if [ -d "${LEGACY_DIR}" ]; then
|
|
add_config_dir "${LEGACY_DIR}"
|
|
fi
|
|
|
|
# An explicit non-root `user:` / `--user` means the operator has already pinned
|
|
# the runtime identity. We cannot chown without CAP_CHOWN, and gosu needs
|
|
# CAP_SETUID/CAP_SETGID we do not have, so the heal is genuinely unavailable
|
|
# here. The *diagnosis* is not: `test -r` as ourselves needs no privileges, and
|
|
# skipping it would reproduce exactly the silent dead-end this script exists to
|
|
# prevent (core boots, every config RPC returns EACCES, nothing in the log).
|
|
if [ "$(id -u)" -ne 0 ]; then
|
|
for _dir in ${CONFIG_DIRS}; do
|
|
if [ -e "${_dir}/config.toml" ] && [ ! -r "${_dir}/config.toml" ]; then
|
|
echo "[docker-entrypoint] FATAL ${_dir}/config.toml is not readable by uid=$(id -u)" >&2
|
|
ls -ln "${_dir}/config.toml" >&2 || true
|
|
echo "[docker-entrypoint] FATAL running with a pinned non-root user, so the workspace cannot be healed in-container" >&2
|
|
echo "[docker-entrypoint] FATAL remedy: chown -Rh $(id -u):$(id -g) ${_dir} on the host, or drop the pinned 'user:' so the entrypoint can repair it" >&2
|
|
exit 1
|
|
fi
|
|
done
|
|
echo "[docker-entrypoint] already running non-root — skipping heal, exec direct"
|
|
exec openhuman-core "$@"
|
|
fi
|
|
|
|
# Only the root path needs these: the heal chowns to them and gosu drops to
|
|
# them. A pinned non-root run never touches them, so it must not be blocked by
|
|
# an image that happens not to define the user.
|
|
if [ -z "${OPENHUMAN_UID}" ] || [ -z "${OPENHUMAN_GID}" ]; then
|
|
echo "[docker-entrypoint] FATAL user '${OPENHUMAN_USER}' does not exist in this image" >&2
|
|
echo "[docker-entrypoint] FATAL the image must create it (see Dockerfile: groupadd/useradd)" >&2
|
|
exit 1
|
|
fi
|
|
|
|
for _dir in ${CONFIG_DIRS}; do
|
|
heal_dir "${_dir}"
|
|
done
|
|
|
|
# Preflight 1: prove gosu can actually drop privileges before we blame the
|
|
# workspace for anything. Without CAP_SETUID/CAP_SETGID (e.g. `cap_drop: ALL`
|
|
# with no matching `cap_add`) every gosu call fails, and attributing that to
|
|
# file ownership would send the operator chasing the wrong `chown`.
|
|
if ! gosu "${OPENHUMAN_USER}" true 2>/dev/null; then
|
|
echo "[docker-entrypoint] FATAL cannot drop privileges to ${OPENHUMAN_USER} via gosu" >&2
|
|
echo "[docker-entrypoint] FATAL the container needs CAP_SETUID + CAP_SETGID (and CAP_CHOWN to heal a root-owned volume)" >&2
|
|
echo "[docker-entrypoint] FATAL with 'cap_drop: ALL', add: cap_add: [CHOWN, SETUID, SETGID] — or pin 'user: \"${OPENHUMAN_UID}:${OPENHUMAN_GID}\"'" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# Preflight 2: refuse to boot a container that would answer /health with 200
|
|
# while every config-dependent RPC returns EACCES. A restart loop carrying a
|
|
# one-line remedy in `docker logs` is far easier to diagnose than a green
|
|
# container whose sign-in screen dead-ends on an os-error-13 string.
|
|
#
|
|
# Checked across every candidate dir, not just the workspace: which one the core
|
|
# resolves depends on where a config.toml actually exists, and an unreadable
|
|
# config in any directory we manage is broken regardless of which one wins.
|
|
for _dir in ${CONFIG_DIRS}; do
|
|
if [ -e "${_dir}/config.toml" ] \
|
|
&& ! gosu "${OPENHUMAN_USER}" test -r "${_dir}/config.toml"; then
|
|
echo "[docker-entrypoint] FATAL ${_dir}/config.toml is not readable by ${OPENHUMAN_USER} (uid=${OPENHUMAN_UID})" >&2
|
|
ls -ln "${_dir}/config.toml" >&2 || true
|
|
echo "[docker-entrypoint] FATAL remedy: docker exec -u 0 <container> chown -Rh ${OPENHUMAN_UID}:${OPENHUMAN_GID} ${_dir}" >&2
|
|
exit 1
|
|
fi
|
|
done
|
|
|
|
echo "[docker-entrypoint] dropping privileges -> exec gosu ${OPENHUMAN_USER} openhuman-core"
|
|
exec gosu "${OPENHUMAN_USER}" openhuman-core "$@"
|