149 lines
5.8 KiB
Bash
Executable File
149 lines
5.8 KiB
Bash
Executable File
#!/bin/bash
|
||
set -eu
|
||
|
||
PACKAGE_ROOT=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
|
||
APP_ROOT="${TRIM_APPDEST:-$PACKAGE_ROOT/app}"
|
||
DATA_ROOT="${TRIM_PKGVAR:-$PACKAGE_ROOT/var}"
|
||
VERSION_FILE="$APP_ROOT/runtime/VERSION"
|
||
WHEEL_ROOT="$APP_ROOT/runtime/wheels"
|
||
CORE_REQUIREMENTS="$APP_ROOT/runtime/runtime-core.txt"
|
||
RUNTIME_ROOT="$DATA_ROOT/runtime"
|
||
|
||
fail() {
|
||
printf '%s\n' "$1" >&2
|
||
exit 1
|
||
}
|
||
|
||
[ -f "$VERSION_FILE" ] || fail "ImageFind 运行环境初始化失败:安装包缺少版本信息。"
|
||
[ -d "$WHEEL_ROOT" ] || fail "ImageFind 运行环境初始化失败:安装包缺少核心 wheelhouse。"
|
||
[ -f "$CORE_REQUIREMENTS" ] || fail "ImageFind 运行环境初始化失败:安装包缺少核心依赖锁文件。"
|
||
|
||
PACKAGE_VERSION=$(sed -n '1p' "$VERSION_FILE")
|
||
case "$PACKAGE_VERSION" in
|
||
''|*[!0-9.]* ) fail "ImageFind 运行环境初始化失败:版本信息无效。" ;;
|
||
esac
|
||
APP_VERSION=$(sed -n '2p' "$VERSION_FILE")
|
||
case "$APP_VERSION" in
|
||
''|*[!0-9.]* ) fail "ImageFind 运行环境初始化失败:应用版本信息无效。" ;;
|
||
esac
|
||
PACKAGE_ID=$(sed -n '3p' "$VERSION_FILE")
|
||
case "$PACKAGE_ID" in
|
||
''|*[!0-9a-f]* ) fail "ImageFind 运行环境初始化失败:安装包指纹无效。" ;;
|
||
esac
|
||
[ "${#PACKAGE_ID}" -eq 64 ] || fail "ImageFind 运行环境初始化失败:安装包指纹长度无效。"
|
||
|
||
if [ -n "${IMAGEFIND_PYTHON_PATH:-}" ]; then
|
||
SYSTEM_PYTHON="$IMAGEFIND_PYTHON_PATH"
|
||
else
|
||
SYSTEM_PYTHON="/var/apps/python312/target/bin/python3"
|
||
fi
|
||
[ -x "$SYSTEM_PYTHON" ] || fail "ImageFind 需要 fnOS Python 3.12 依赖,请先确认 python312 已安装。"
|
||
PYTHON_VERSION=$($SYSTEM_PYTHON -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")' 2>/dev/null) \
|
||
|| fail "ImageFind 无法运行 fnOS Python 3.12。"
|
||
[ "$PYTHON_VERSION" = "3.12" ] || fail "ImageFind 需要 Python 3.12,当前为 $PYTHON_VERSION。"
|
||
|
||
mkdir -p "$RUNTIME_ROOT"
|
||
RUNTIME_ID=$(printf '%s' "$PACKAGE_ID" | cut -c1-16)
|
||
TARGET="$RUNTIME_ROOT/core-$APP_VERSION-$RUNTIME_ID"
|
||
STAGE="$RUNTIME_ROOT/.core-$APP_VERSION-$RUNTIME_ID.tmp.$$"
|
||
CURRENT="$RUNTIME_ROOT/current"
|
||
|
||
runtime_valid() {
|
||
[ -x "$1/bin/python" ] || return 1
|
||
[ -f "$1/.imagefind-package-id" ] || return 1
|
||
[ "$(sed -n '1p' "$1/.imagefind-package-id")" = "$PACKAGE_ID" ] || return 1
|
||
RUNTIME_VERSION=$(
|
||
IMAGEFIND_DATA_DIR="$DATA_ROOT/data" "$1/bin/python" -c \
|
||
'import imagefind; print(imagefind.__version__)' 2>"$RUNTIME_ROOT/version-check.err"
|
||
) || return 1
|
||
[ "$RUNTIME_VERSION" = "$APP_VERSION" ]
|
||
}
|
||
|
||
if ! runtime_valid "$TARGET"; then
|
||
rm -rf "$STAGE"
|
||
"$SYSTEM_PYTHON" -m venv "$STAGE" \
|
||
|| { rm -rf "$STAGE"; fail "ImageFind 无法创建 Python 3.12 核心运行环境。"; }
|
||
APP_WHEEL=""
|
||
for candidate in "$APP_ROOT"/runtime/imagefind-*.whl; do
|
||
if [ -f "$candidate" ]; then
|
||
APP_WHEEL="$candidate"
|
||
break
|
||
fi
|
||
done
|
||
[ -n "$APP_WHEEL" ] \
|
||
|| { rm -rf "$STAGE"; fail "ImageFind 运行环境初始化失败:安装包缺少应用 wheel。"; }
|
||
if ! "$STAGE/bin/python" -m pip install \
|
||
--disable-pip-version-check --no-input --no-index \
|
||
--find-links "$WHEEL_ROOT" --requirement "$CORE_REQUIREMENTS" "$APP_WHEEL"; then
|
||
rm -rf "$STAGE"
|
||
fail "ImageFind 核心运行环境离线安装失败,请重新安装应用。"
|
||
fi
|
||
printf '%s\n' "$PACKAGE_ID" >"$STAGE/.imagefind-package-id"
|
||
runtime_valid "$STAGE" \
|
||
|| {
|
||
if [ -s "$RUNTIME_ROOT/version-check.err" ]; then
|
||
cat "$RUNTIME_ROOT/version-check.err" >&2 || true
|
||
fi
|
||
rm -rf "$STAGE"
|
||
fail "ImageFind 核心运行环境校验失败。"
|
||
}
|
||
if [ -e "$TARGET" ]; then
|
||
BACKUP="$RUNTIME_ROOT/core-$APP_VERSION-$RUNTIME_ID.invalid.$(date +%s)"
|
||
mv "$TARGET" "$BACKUP"
|
||
fi
|
||
mv "$STAGE" "$TARGET"
|
||
fi
|
||
|
||
LINK="$RUNTIME_ROOT/.current.$$"
|
||
rm -f "$LINK"
|
||
ln -s "$TARGET" "$LINK"
|
||
if [ -d "$CURRENT" ] && [ ! -L "$CURRENT" ]; then
|
||
mv "$CURRENT" "$RUNTIME_ROOT/current.previous.$(date +%s)"
|
||
fi
|
||
# mv follows a destination symlink that points at a directory and would place
|
||
# LINK *inside* the old runtime. Replace the directory entry itself so an
|
||
# upgrade atomically switches current even when it is a valid or dangling
|
||
# symlink left by an earlier release.
|
||
if ! "$SYSTEM_PYTHON" -c \
|
||
'import os,sys; os.replace(sys.argv[1], sys.argv[2])' \
|
||
"$LINK" "$CURRENT"; then
|
||
rm -f "$LINK"
|
||
fail "ImageFind 核心运行环境切换失败:无法原子更新 current。"
|
||
fi
|
||
if ! runtime_valid "$CURRENT"; then
|
||
if [ -L "$CURRENT" ]; then
|
||
CURRENT_LINK=$(readlink "$CURRENT" 2>/dev/null || printf '%s' '无法读取')
|
||
elif [ -e "$CURRENT" ]; then
|
||
CURRENT_LINK='不是符号链接'
|
||
else
|
||
CURRENT_LINK='不存在'
|
||
fi
|
||
if [ -x "$CURRENT/bin/python" ]; then
|
||
ACTUAL_VERSION=$(
|
||
IMAGEFIND_DATA_DIR="$DATA_ROOT/data" "$CURRENT/bin/python" -c \
|
||
'import imagefind; print(imagefind.__version__)' 2>"$RUNTIME_ROOT/version-check.err" \
|
||
|| printf '%s' '无法读取'
|
||
)
|
||
else
|
||
ACTUAL_VERSION='Python 不可执行'
|
||
fi
|
||
fail "ImageFind 核心运行环境切换失败:期望版本 $APP_VERSION,current=$CURRENT_LINK,实际版本=$ACTUAL_VERSION。"
|
||
fi
|
||
|
||
# Keep the active runtime plus one most-recent rollback candidate. fnOS package
|
||
# build numbers change on every release, so retaining every historical venv
|
||
# would otherwise grow the application data directory without bound.
|
||
ROLLBACK_KEPT=0
|
||
for OLD_RUNTIME in $(ls -dt "$RUNTIME_ROOT"/core-* 2>/dev/null || true); do
|
||
[ -d "$OLD_RUNTIME" ] || continue
|
||
[ "$OLD_RUNTIME" = "$TARGET" ] && continue
|
||
case "$OLD_RUNTIME" in
|
||
*.invalid.*) rm -rf -- "$OLD_RUNTIME"; continue ;;
|
||
esac
|
||
if [ "$ROLLBACK_KEPT" -eq 0 ]; then
|
||
ROLLBACK_KEPT=1
|
||
else
|
||
rm -rf -- "$OLD_RUNTIME"
|
||
fi
|
||
done
|