mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
fix(docker): repair copied AI venv paths (#390)
AI feature installs now keep copied Python venv metadata (bin/pip shebang, bin/activate, pyvenv.cfg) pointed at /data/ai/venv, so scripts no longer silently fall back to the baked, read-only /opt/venv after the venv is bootstrapped into /data. Fixes #127 (AI tools incompatible with PUID/PGID). The entrypoint repairs both fresh bootstraps and already-stamped runtime venvs (self-heals existing deployments on next restart, no reinstall needed), with regression coverage for literal path replacement and binary file safety. Independently reviewed and verified: traced chown/gosu ordering in entrypoint.sh to confirm no permission regression, reproduced the exact issue #127 scenario (custom PUID + manual venv activation) in a live container both before and after the fix, and ran the PR's own test suite locally (16/16 passing). Co-authored-by: SyntaxSawdust
This commit is contained in:
co-authored by
SyntaxSawdust
parent
a0d1c70172
commit
7e01d3637e
@@ -55,3 +55,55 @@ ensure_writable() {
|
||||
done
|
||||
return "$_ew_failed"
|
||||
}
|
||||
|
||||
# rewrite_venv_paths <venv> <from> <to>
|
||||
# A Python venv is not fully relocatable after a raw copy: console scripts and
|
||||
# activation files keep the source venv path. Patch only text files that still
|
||||
# contain that path so feature installs do not fall back to the baked /opt/venv.
|
||||
rewrite_venv_paths() {
|
||||
_rv_venv="$1"
|
||||
_rv_from="$2"
|
||||
_rv_to="$3"
|
||||
|
||||
if [ ! -d "$_rv_venv" ] || [ -z "$_rv_from" ] || [ -z "$_rv_to" ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
grep -Il -- "$_rv_from" "$_rv_venv"/bin/* "$_rv_venv/pyvenv.cfg" 2>/dev/null |
|
||||
while IFS= read -r _rv_file; do
|
||||
[ -f "$_rv_file" ] || continue
|
||||
[ -L "$_rv_file" ] && continue
|
||||
python3 - "$_rv_file" "$_rv_from" "$_rv_to" <<'PY'
|
||||
import os
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
path, old, new = sys.argv[1:]
|
||||
old_bytes = old.encode()
|
||||
new_bytes = new.encode()
|
||||
|
||||
with open(path, "rb") as source:
|
||||
data = source.read()
|
||||
|
||||
if old_bytes not in data:
|
||||
raise SystemExit(0)
|
||||
|
||||
stat = os.stat(path)
|
||||
directory = os.path.dirname(path) or "."
|
||||
prefix = f".{os.path.basename(path)}.snapotter-rewrite."
|
||||
fd, tmp_path = tempfile.mkstemp(prefix=prefix, dir=directory)
|
||||
|
||||
try:
|
||||
with os.fdopen(fd, "wb") as target:
|
||||
target.write(data.replace(old_bytes, new_bytes))
|
||||
os.chmod(tmp_path, stat.st_mode & 0o7777)
|
||||
os.replace(tmp_path, path)
|
||||
except Exception:
|
||||
try:
|
||||
os.unlink(tmp_path)
|
||||
except OSError:
|
||||
pass
|
||||
raise
|
||||
PY
|
||||
done
|
||||
}
|
||||
|
||||
@@ -121,6 +121,7 @@ if [ -d "/opt/venv" ]; then
|
||||
rm -rf "$AI_VENV"
|
||||
cp -r /opt/venv "$AI_VENV_TMP"
|
||||
mv "$AI_VENV_TMP" "$AI_VENV"
|
||||
rewrite_venv_paths "$AI_VENV" "/opt/venv" "$AI_VENV"
|
||||
# Reset installed-bundle state: their packages lived in the old venv.
|
||||
# Models in /data/ai/models survive, so reinstalling a bundle only
|
||||
# reruns pip (model downloads are idempotent and skip existing files).
|
||||
@@ -129,6 +130,8 @@ if [ -d "/opt/venv" ]; then
|
||||
echo "WARNING: Installed AI feature bundles were reset after base venv upgrade. Reinstall them from the Settings page."
|
||||
fi
|
||||
echo "AI venv ready at $AI_VENV"
|
||||
elif [ -d "$AI_VENV" ]; then
|
||||
rewrite_venv_paths "$AI_VENV" "/opt/venv" "$AI_VENV"
|
||||
fi
|
||||
fi
|
||||
|
||||
|
||||
Reference in New Issue
Block a user