mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-09-17 13:42:21 +02:00
Squash Odysseus development history
This commit is contained in:
Executable
+70
@@ -0,0 +1,70 @@
|
||||
#!/usr/bin/env bash
|
||||
# Build patched wheels for Real-ESRGAN's unmaintained dependencies.
|
||||
#
|
||||
# basicsr / gfpgan / facexlib (xinntao, last released 2022) read their version
|
||||
# in setup.py with:
|
||||
#
|
||||
# exec(compile(f.read(), version_file, 'exec'))
|
||||
# return locals()['__version__']
|
||||
#
|
||||
# Python 3.13+ implements PEP 667: locals() inside a function returns an
|
||||
# independent snapshot that exec() can no longer mutate, so the read raises
|
||||
# `KeyError: '__version__'` and the sdist build fails. That is why the Cookbook
|
||||
# "install realesrgan" button dies on the python:3.14 image. The packages have
|
||||
# no fixed release, so we patch get_version() to exec into an explicit namespace
|
||||
# dict (works on every Python) and build wheels from the patched source.
|
||||
#
|
||||
# Usage: build-realesrgan-wheels.sh [OUTPUT_DIR] (default: /wheels)
|
||||
set -euo pipefail
|
||||
|
||||
OUT="${1:-/wheels}"
|
||||
mkdir -p "$OUT"
|
||||
|
||||
work="$(mktemp -d)"
|
||||
trap 'rm -rf "$work"' EXIT
|
||||
cd "$work"
|
||||
|
||||
# Pinned to the versions Real-ESRGAN 0.3.0 resolves to.
|
||||
SPECS="basicsr==1.4.2 gfpgan==1.3.8 facexlib==0.3.0"
|
||||
|
||||
for spec in $SPECS; do
|
||||
name="${spec%%==*}"
|
||||
ver="${spec##*==}"
|
||||
# pip download builds metadata (and trips the same bug), so fetch the raw
|
||||
# sdist URL from the PyPI JSON API instead.
|
||||
url="$(python - "$name" "$ver" <<'PY'
|
||||
import json, sys, urllib.request
|
||||
name, ver = sys.argv[1], sys.argv[2]
|
||||
data = json.load(urllib.request.urlopen(f"https://pypi.org/pypi/{name}/{ver}/json"))
|
||||
for f in data["urls"]:
|
||||
if f["packagetype"] == "sdist":
|
||||
print(f["url"]); break
|
||||
else:
|
||||
sys.exit(f"no sdist found for {name}=={ver}")
|
||||
PY
|
||||
)"
|
||||
echo ">> fetching ${name} ${ver}: ${url}"
|
||||
curl -fsSL "$url" -o "${name}.tar.gz"
|
||||
tar xzf "${name}.tar.gz"
|
||||
done
|
||||
|
||||
echo ">> patching get_version()"
|
||||
python - <<'PY'
|
||||
import pathlib
|
||||
old_exec = "exec(compile(f.read(), version_file, 'exec'))"
|
||||
new_exec = "_ver_ns = {}\n exec(compile(f.read(), version_file, 'exec'), _ver_ns)"
|
||||
old_ret = "return locals()['__version__']"
|
||||
new_ret = "return _ver_ns['__version__']"
|
||||
patched = 0
|
||||
for setup in pathlib.Path(".").glob("*/setup.py"):
|
||||
s = setup.read_text()
|
||||
if old_exec in s and old_ret in s:
|
||||
setup.write_text(s.replace(old_exec, new_exec).replace(old_ret, new_ret))
|
||||
print(" patched", setup)
|
||||
patched += 1
|
||||
assert patched == 3, f"expected to patch 3 setup.py files, patched {patched}"
|
||||
PY
|
||||
|
||||
echo ">> building wheels into ${OUT}"
|
||||
pip wheel --no-deps -w "$OUT" ./basicsr-* ./gfpgan-* ./facexlib-*
|
||||
ls -l "$OUT"
|
||||
+112
-18
@@ -13,6 +13,8 @@ set -e
|
||||
|
||||
PUID="${PUID:-1000}"
|
||||
PGID="${PGID:-1000}"
|
||||
GOSU_BIN="$(command -v gosu)"
|
||||
PYTHON_BIN="$(command -v python)"
|
||||
|
||||
# Reuse an existing matching group/user if the host's UID/GID already
|
||||
# corresponds to one in /etc/passwd (e.g. when the image is rebuilt
|
||||
@@ -24,29 +26,121 @@ if ! getent passwd "$PUID" >/dev/null 2>&1; then
|
||||
useradd -u "$PUID" -g "$PGID" -M -s /bin/sh -d /app odysseus
|
||||
fi
|
||||
|
||||
# Repair ownership on every writable path the app touches at runtime.
|
||||
#
|
||||
# Bind-mounted dirs (/app/data, /app/logs) are the obvious ones, but
|
||||
# the app ALSO writes inside the image's own source tree at runtime:
|
||||
# - services/cache/{search,content}/* (search cache LRU)
|
||||
# - services/search_analytics.json
|
||||
# - services/search_engine_error.log
|
||||
# - services/tts cache, etc.
|
||||
# These dirs were created as root during `docker build`, so dropping
|
||||
# to PUID:PGID would otherwise crash on the first import that tries
|
||||
# to mkdir them. Chown the whole /app tree — fast (<1s on this size)
|
||||
# and idempotent via the `-not -uid` filter so we only touch files
|
||||
# that need fixing.
|
||||
for dir in /app /app/data /app/logs; do
|
||||
ODY_USER="$(getent passwd "$PUID" | cut -d: -f1)"
|
||||
[ -z "$ODY_USER" ] && ODY_USER=odysseus
|
||||
|
||||
# Docker-socket group plumbing for the explicit host-Docker overlay. When
|
||||
# opted in, the socket is owned by root:<host docker gid>. Add the app user
|
||||
# to that group and later call gosu by username so supplementary groups are
|
||||
# retained.
|
||||
DOCKER_SOCK="${DOCKER_SOCK:-/var/run/docker.sock}"
|
||||
if [ "${ODYSSEUS_ENABLE_HOST_DOCKER:-}" = "true" ] && [ -S "$DOCKER_SOCK" ]; then
|
||||
SOCK_GID="$(stat -c '%g' "$DOCKER_SOCK" 2>/dev/null || echo '')"
|
||||
if [ -n "$SOCK_GID" ] && [ "$SOCK_GID" != "0" ]; then
|
||||
if ! getent group "$SOCK_GID" >/dev/null 2>&1; then
|
||||
groupadd -g "$SOCK_GID" docker_host || true
|
||||
fi
|
||||
SOCK_GROUP="$(getent group "$SOCK_GID" | cut -d: -f1)"
|
||||
if [ -n "$SOCK_GROUP" ]; then
|
||||
usermod -aG "$SOCK_GROUP" "$ODY_USER" 2>/dev/null || true
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
mount_root_for() {
|
||||
awk -v target="$1" '$5 == target { print $4; exit }' /proc/self/mountinfo 2>/dev/null || true
|
||||
}
|
||||
|
||||
is_broad_mount_root() {
|
||||
case "$1" in
|
||||
/|/home|/srv|/var|/usr|/opt|/tmp|/mnt|/media)
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
return 1
|
||||
}
|
||||
|
||||
repair_tree_ownership() {
|
||||
dir="$1"
|
||||
if [ -d "$dir" ]; then
|
||||
# `find ... -not -uid` keeps this O(touched-files), not
|
||||
# O(everything), so terabyte-sized maildirs don't slow startup.
|
||||
find "$dir" -not -uid "$PUID" -print0 2>/dev/null \
|
||||
find "$dir" -xdev -not -uid "$PUID" -print0 2>/dev/null \
|
||||
| xargs -0 -r chown "$PUID:$PGID" 2>/dev/null || true
|
||||
fi
|
||||
}
|
||||
|
||||
repair_app_tree_ownership() {
|
||||
if [ -d /app ]; then
|
||||
find /app -xdev \
|
||||
\( -path /app/data -o -path /app/logs -o -path /app/.ssh -o -path /app/.cache -o -path /app/.local \) -prune \
|
||||
-o -not -uid "$PUID" -print0 2>/dev/null \
|
||||
| xargs -0 -r chown "$PUID:$PGID" 2>/dev/null || true
|
||||
fi
|
||||
}
|
||||
|
||||
repair_bind_mount_ownership() {
|
||||
dir="$1"
|
||||
if [ ! -d "$dir" ]; then
|
||||
return
|
||||
fi
|
||||
|
||||
mount_root="$(mount_root_for "$dir")"
|
||||
if is_broad_mount_root "$mount_root"; then
|
||||
echo "Skipping recursive ownership repair for $dir because it maps to broad host path $mount_root" >&2
|
||||
chown "$PUID:$PGID" "$dir" 2>/dev/null || true
|
||||
return
|
||||
fi
|
||||
|
||||
repair_tree_ownership "$dir"
|
||||
}
|
||||
|
||||
# Repair image-owned writable paths without walking into bind-mounted host
|
||||
# trees, then repair the app-owned mount roots separately.
|
||||
repair_app_tree_ownership
|
||||
for dir in /app/data /app/logs /app/.ssh /app/.cache/huggingface /app/.local; do
|
||||
repair_bind_mount_ownership "$dir"
|
||||
done
|
||||
|
||||
# Cookbook installs vllm/etc. via `pip install --user`, which pulls
|
||||
# nvidia-cuda-* wheels into /app/.local but does not set CUDA_HOME or
|
||||
# symlink /usr/local/cuda. vllm 0.22+ then crashes during engine init
|
||||
# when FlashInfer tries to JIT a sampler kernel ("Could not find nvcc",
|
||||
# then "CUDA compiler and toolkit headers are incompatible" on the
|
||||
# mixed cuda-nvcc 13.3 / cuda-runtime 13.0 wheel combo).
|
||||
#
|
||||
# Auto-set CUDA_HOME if a pip-installed nvcc is present, and disable the
|
||||
# FlashInfer JIT sampler — sampler only, no impact on attention path.
|
||||
# No-op when vllm isn't installed.
|
||||
#
|
||||
# Checked layouts (all are real pip-wheel install paths):
|
||||
# nvidia/cu13 — nvidia-nvcc-cu13 (CUDA 13.x wheel style)
|
||||
# nvidia/cu12 — nvidia-nvcc-cu12 (CUDA 12.x wheel style)
|
||||
# nvidia/cuda_nvcc — nvidia-cuda-nvcc-cu12 (older cu12 sub-package style)
|
||||
for cu in \
|
||||
/app/.local/lib/python*/site-packages/nvidia/cu13 \
|
||||
/app/.local/lib/python*/site-packages/nvidia/cu12 \
|
||||
/app/.local/lib/python*/site-packages/nvidia/cuda_nvcc; do
|
||||
if [ -x "$cu/bin/nvcc" ]; then
|
||||
export CUDA_HOME="$cu"
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
# Disable the FlashInfer JIT sampler unconditionally — it is sampler-only
|
||||
# and has no impact on the attention path, but requires nvcc + matching
|
||||
# CUDA headers at startup. Without this, vLLM crashes with "Could not find
|
||||
# nvcc" even when the GPU itself is fully visible to the container.
|
||||
export VLLM_USE_FLASHINFER_SAMPLER="${VLLM_USE_FLASHINFER_SAMPLER:-0}"
|
||||
|
||||
# Make Cookbook-installed Python CLIs visible after `pip install --user`.
|
||||
# vLLM and helper scripts land here because /app is the non-root user's HOME.
|
||||
export PATH="/app/.local/bin:$PATH"
|
||||
|
||||
# Run first-time setup as the app user so data/ files get the right ownership.
|
||||
# setup.py is idempotent — skips auth.json / .env if they already exist.
|
||||
# || true so a setup failure never prevents the container from starting.
|
||||
"$GOSU_BIN" "$ODY_USER" "$PYTHON_BIN" /app/setup.py || true
|
||||
|
||||
# Drop root and run the actual app. `gosu` is preferred over `su` /
|
||||
# `sudo` because it cleans up the process tree (no extra shell layer)
|
||||
# so signals (SIGTERM from `docker stop`) reach uvicorn directly.
|
||||
exec gosu "$PUID:$PGID" "$@"
|
||||
exec "$GOSU_BIN" "$ODY_USER" "$@"
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
# AMD ROCm GPU overlay. Enable by setting COMPOSE_FILE in .env:
|
||||
# COMPOSE_FILE=docker-compose.yml:docker/gpu.amd.yml
|
||||
# RENDER_GID=<numeric output of: getent group render | cut -d: -f3>
|
||||
#
|
||||
# Requires ROCm drivers on the host (kfd + DRI devices). The host user
|
||||
# running Docker must be in the `video` and `render` groups.
|
||||
#
|
||||
# This overlay only passes the host GPU through to the container.
|
||||
# The slim Odysseus image does not bundle ROCm userspace or inference
|
||||
# engines — install ROCm-compatible builds of vLLM / llama-cpp-python
|
||||
# via Cookbook -> Dependencies (or pip) before serving GPU models.
|
||||
services:
|
||||
odysseus:
|
||||
devices:
|
||||
- /dev/kfd
|
||||
- /dev/dri
|
||||
group_add:
|
||||
- video
|
||||
- ${RENDER_GID:-render}
|
||||
@@ -0,0 +1,34 @@
|
||||
# NVIDIA GPU overlay. Enable by setting COMPOSE_FILE in .env:
|
||||
# COMPOSE_FILE=docker-compose.yml:docker/gpu.nvidia.yml
|
||||
#
|
||||
# Use scripts/check-docker-gpu.sh to diagnose GPU passthrough, optionally
|
||||
# install the NVIDIA Container Toolkit (Ubuntu/Debian), and write COMPOSE_FILE
|
||||
# to .env. The script is read-only by default — it installs nothing and never
|
||||
# edits .env unless explicitly asked.
|
||||
#
|
||||
# Requires the NVIDIA Container Toolkit on the host.
|
||||
# Arch: sudo pacman -S nvidia-container-toolkit
|
||||
# Debian: sudo apt install nvidia-container-toolkit
|
||||
# Fedora: sudo dnf install nvidia-container-toolkit
|
||||
# Then:
|
||||
# sudo nvidia-ctk runtime configure --runtime=docker
|
||||
# sudo systemctl restart docker
|
||||
# Verify with:
|
||||
# docker info | grep -i nvidia
|
||||
#
|
||||
# This overlay only passes the host GPU through to the container.
|
||||
# The slim Odysseus image does not bundle CUDA userspace or inference
|
||||
# engines — install vLLM / llama-cpp-python / SGLang via
|
||||
# Cookbook -> Dependencies (or pip) before serving GPU models.
|
||||
services:
|
||||
odysseus:
|
||||
environment:
|
||||
- NVIDIA_VISIBLE_DEVICES=all
|
||||
- NVIDIA_DRIVER_CAPABILITIES=compute,utility
|
||||
deploy:
|
||||
resources:
|
||||
reservations:
|
||||
devices:
|
||||
- driver: nvidia
|
||||
count: all
|
||||
capabilities: [gpu]
|
||||
@@ -0,0 +1,12 @@
|
||||
# High-trust host Docker access. Enable only when local Docker-daemon
|
||||
# management from Cookbook is required and you accept that raw socket access
|
||||
# grants broad control over the host Docker daemon.
|
||||
# COMPOSE_FILE=docker-compose.yml:docker/host-docker.yml
|
||||
# DOCKER_GID=<numeric host Docker group id>
|
||||
services:
|
||||
odysseus:
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
group_add: ["${DOCKER_GID:-963}"]
|
||||
environment:
|
||||
- ODYSSEUS_ENABLE_HOST_DOCKER=true
|
||||
@@ -0,0 +1,21 @@
|
||||
# High-trust host network access. Enable only when the Odysseus agent needs
|
||||
# host-native LAN/VPN/mDNS behavior that Docker bridge networking cannot
|
||||
# provide. Linux only; Docker Desktop does not provide equivalent host
|
||||
# networking semantics.
|
||||
# COMPOSE_FILE=docker-compose.yml:docker/host-workspace.yml:docker/host-network.yml
|
||||
# APP_PORT=7011
|
||||
services:
|
||||
odysseus:
|
||||
network_mode: host
|
||||
ports: !reset []
|
||||
environment:
|
||||
- APP_PORT=${APP_PORT:-7011}
|
||||
- APP_BIND=${APP_BIND:-0.0.0.0}
|
||||
- SEARXNG_INSTANCE=${ODYSSEUS_HOST_NETWORK_SEARXNG_INSTANCE:-http://127.0.0.1:8080}
|
||||
- CHROMADB_HOST=${ODYSSEUS_HOST_NETWORK_CHROMADB_HOST:-127.0.0.1}
|
||||
- CHROMADB_PORT=${ODYSSEUS_HOST_NETWORK_CHROMADB_PORT:-8100}
|
||||
- ODYSSEUS_CONTAINER_NETWORK_MODE=host
|
||||
command:
|
||||
- sh
|
||||
- -c
|
||||
- exec uvicorn app:app --host "$${APP_BIND:-0.0.0.0}" --port "$${APP_PORT:-7011}"
|
||||
@@ -0,0 +1,11 @@
|
||||
# High-trust host workspace access. Enable only when the Odysseus agent should
|
||||
# work on a host directory outside the container's normal /app/data sandbox.
|
||||
# COMPOSE_FILE=docker-compose.yml:docker/host-workspace.yml
|
||||
# ODYSSEUS_HOST_WORKSPACE_DIR=/absolute/host/path
|
||||
# ODYSSEUS_HOST_WORKSPACE_MOUNT=/host/workspace
|
||||
services:
|
||||
odysseus:
|
||||
volumes:
|
||||
- ${ODYSSEUS_HOST_WORKSPACE_DIR:?set ODYSSEUS_HOST_WORKSPACE_DIR}:${ODYSSEUS_HOST_WORKSPACE_MOUNT:-/host/workspace}:rw,z
|
||||
environment:
|
||||
- ODYSSEUS_HOST_WORKSPACE_MOUNT=${ODYSSEUS_HOST_WORKSPACE_MOUNT:-/host/workspace}
|
||||
Reference in New Issue
Block a user