mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-09-23 01:46:37 +08:00
feat(fnos): run entirely without root and harden process lifecycle
- Switch privilege model to run-as: package (zero root, per fnOS guide) - Borrow App Store python312 instead of bundling CPython; keep bundled uv - Move venv, HOME and caches onto the persistent data share - Start service via setsid; stop/upgrade kill the whole process group - Sweep stray runtime/box orphans in install/upgrade init hooks - Track LangBot 4.10.11 (manifest baseline + upstream merge)
This commit is contained in:
@@ -19,7 +19,30 @@ cd "${APP_DIR}" || {
|
||||
}
|
||||
|
||||
# --- Ensure data directory exists ---
|
||||
mkdir -p "${DATA_DIR}/plugins" "${DATA_DIR}/box" "${DATA_DIR}/logs" 2>/dev/null || true
|
||||
mkdir -p "${DATA_DIR}/plugins" "${DATA_DIR}/box" "${DATA_DIR}/logs" || {
|
||||
echo "Data share not writable: ${DATA_DIR}" > "${TRIM_TEMP_LOGFILE}"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# --- Writable HOME and tool caches ---
|
||||
# Under run-as: package the generated user's system HOME and the app install
|
||||
# dir (TRIM_APPDEST) can be read-only; uv aborts if it cannot initialise its
|
||||
# cache. Point HOME / UV_CACHE_DIR at the persistent data share and keep the
|
||||
# venv there too (UV_PROJECT_ENVIRONMENT), instead of inside APP_DIR.
|
||||
VENV_DIR="${DATA_DIR}/.venv"
|
||||
export HOME="${DATA_DIR}/.home"
|
||||
export UV_CACHE_DIR="${DATA_DIR}/.cache/uv"
|
||||
# Never download a managed CPython (the download host is unreachable on many
|
||||
# NAS networks); use the distro Python only — fail loudly if it is missing.
|
||||
export UV_PYTHON_DOWNLOADS=never
|
||||
mkdir -p "${HOME}" "${UV_CACHE_DIR}" || {
|
||||
echo "Data share not writable: ${DATA_DIR}" > "${TRIM_TEMP_LOGFILE}"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Real uv/pip errors are captured here for diagnosis (the installer popup
|
||||
# only shows the short message we write to TRIM_TEMP_LOGFILE).
|
||||
DEBUG_LOG="${DATA_DIR}/logs/install-debug.log"
|
||||
|
||||
# --- Pre-seed config.yaml with the user-selected web port ---
|
||||
# Data root points at the persistent share (LANGBOT_DATA_ROOT is exported by
|
||||
@@ -35,7 +58,10 @@ TEMPLATE_FILE="${APP_DIR}/src/langbot/templates/config.yaml"
|
||||
_patch_config() {
|
||||
local cfg_dir="$1"
|
||||
local cfg_file="${cfg_dir}/config.yaml"
|
||||
mkdir -p "${cfg_dir}" 2>/dev/null || true
|
||||
mkdir -p "${cfg_dir}" || {
|
||||
echo "Cannot create config directory: ${cfg_dir}" > "${TRIM_TEMP_LOGFILE}"
|
||||
exit 1
|
||||
}
|
||||
if [ ! -f "${cfg_file}" ] && [ -f "${TEMPLATE_FILE}" ]; then
|
||||
cp "${TEMPLATE_FILE}" "${cfg_file}"
|
||||
fi
|
||||
@@ -68,15 +94,27 @@ if [ ! -d "/var/apps/nodejs_v${NODE_VERSION}" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# --- Python check ---
|
||||
PYTHON_BIN="python3"
|
||||
if ! command -v "${PYTHON_BIN}" >/dev/null 2>&1; then
|
||||
PYTHON_BIN="python"
|
||||
fi
|
||||
if ! command -v "${PYTHON_BIN}" >/dev/null 2>&1; then
|
||||
echo "Python not found on this system" > "${TRIM_TEMP_LOGFILE}"
|
||||
# --- CPU architecture (must be resolved before locating bundled binaries) ---
|
||||
ARCH=$(uname -m)
|
||||
case "${ARCH}" in
|
||||
x86_64|aarch64) ;;
|
||||
*)
|
||||
echo "Unsupported CPU architecture: ${ARCH}" > "${TRIM_TEMP_LOGFILE}"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
# --- Python: official python312 App Store app (same borrow pattern as Node.js) ---
|
||||
PYTHON_APP="python312"
|
||||
PYTHON_BIN="/var/apps/${PYTHON_APP}/target/bin/python3"
|
||||
if [ ! -d "/var/apps/${PYTHON_APP}" ]; then
|
||||
echo "未找到官方 Python 环境:请先在应用中心安装 ${PYTHON_APP},再重新安装本应用。" > "${TRIM_TEMP_LOGFILE}"
|
||||
exit 1
|
||||
fi
|
||||
[ -x "${PYTHON_BIN}" ] || {
|
||||
echo "Python 解释器缺失或不可执行:${PYTHON_BIN}" > "${TRIM_TEMP_LOGFILE}"
|
||||
exit 1
|
||||
}
|
||||
|
||||
PY_VER=$("${PYTHON_BIN}" -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")' 2>/dev/null)
|
||||
if [ -z "${PY_VER}" ]; then
|
||||
@@ -90,55 +128,32 @@ if [ "${PY_MAJOR}" -lt 3 ] || { [ "${PY_MAJOR}" -eq 3 ] && [ "${PY_MINOR}" -lt 1
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# --- Resolve uv: bundled binary first, then online fallbacks ---
|
||||
UV_BIN=""
|
||||
ARCH=$(uname -m)
|
||||
case "${ARCH}" in
|
||||
x86_64) BUNDLED_UV="${TRIM_APPDEST}/bin/uv-x86_64" ;;
|
||||
aarch64) BUNDLED_UV="${TRIM_APPDEST}/bin/uv-aarch64" ;;
|
||||
*) BUNDLED_UV="" ;;
|
||||
esac
|
||||
|
||||
if [ -n "${BUNDLED_UV}" ] && [ -x "${BUNDLED_UV}" ]; then
|
||||
mkdir -p "${TRIM_PKGVAR}/bin"
|
||||
cp "${BUNDLED_UV}" "${TRIM_PKGVAR}/bin/uv" && chmod +x "${TRIM_PKGVAR}/bin/uv"
|
||||
UV_BIN="${TRIM_PKGVAR}/bin/uv"
|
||||
fi
|
||||
|
||||
if [ -z "${UV_BIN}" ] && command -v uv >/dev/null 2>&1; then
|
||||
UV_BIN="uv"
|
||||
fi
|
||||
|
||||
if [ -z "${UV_BIN}" ]; then
|
||||
"${PYTHON_BIN}" -m pip install --user --no-cache-dir uv 2>/dev/null || \
|
||||
"${PYTHON_BIN}" -m pip install --no-cache-dir uv 2>/dev/null || \
|
||||
curl -LsSf https://astral.sh/uv/install.sh | sh 2>/dev/null || true
|
||||
export PATH="${HOME}/.local/bin:${PATH}"
|
||||
if command -v uv >/dev/null 2>&1; then
|
||||
UV_BIN="uv"
|
||||
elif [ -x "${HOME}/.local/bin/uv" ]; then
|
||||
UV_BIN="${HOME}/.local/bin/uv"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -z "${UV_BIN}" ]; then
|
||||
echo "无法获取 uv:内置二进制缺失且在线安装失败。请检查网络后重新安装。" > "${TRIM_TEMP_LOGFILE}"
|
||||
# --- Resolve uv: run the bundled binary in place (single canonical path) ---
|
||||
UV_BIN="${TRIM_APPDEST}/bin/uv-${ARCH}"
|
||||
[ -x "${UV_BIN}" ] || {
|
||||
echo "Bundled uv binary missing or not executable: ${UV_BIN}" > "${TRIM_TEMP_LOGFILE}"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# --- Create venv via uv ---
|
||||
if [ ! -d ".venv" ]; then
|
||||
"${UV_BIN}" venv .venv --python "${PYTHON_BIN}" || {
|
||||
echo "Failed to create Python virtual environment via uv" > "${TRIM_TEMP_LOGFILE}"
|
||||
# --- Create venv via uv (on the writable data share, not APP_DIR) ---
|
||||
if [ ! -d "${VENV_DIR}" ]; then
|
||||
{
|
||||
echo "== whoami: $(id 2>&1)"
|
||||
echo "== APP_DIR perms: $(ls -ld "${APP_DIR}" 2>&1)"
|
||||
echo "== DATA_DIR perms: $(ls -ld "${DATA_DIR}" 2>&1)"
|
||||
echo "== HOME=${HOME} UV_CACHE_DIR=${UV_CACHE_DIR}"
|
||||
"${UV_BIN}" venv "${VENV_DIR}" --python "${PYTHON_BIN}"
|
||||
} >> "${DEBUG_LOG}" 2>&1 || {
|
||||
echo "Failed to create Python virtual environment via uv. See ${DEBUG_LOG}" > "${TRIM_TEMP_LOGFILE}"
|
||||
exit 1
|
||||
}
|
||||
fi
|
||||
|
||||
# --- Sync dependencies ---
|
||||
"${UV_BIN}" sync --extra seekdb || {
|
||||
echo "Dependency sync failed. Check network connectivity." > "${TRIM_TEMP_LOGFILE}"
|
||||
# --- Sync dependencies (install into the relocated venv) ---
|
||||
if ! UV_PROJECT_ENVIRONMENT="${VENV_DIR}" "${UV_BIN}" sync --extra seekdb >> "${DEBUG_LOG}" 2>&1; then
|
||||
echo "Dependency sync failed. Check network connectivity. See ${DEBUG_LOG}" > "${TRIM_TEMP_LOGFILE}"
|
||||
exit 1
|
||||
}
|
||||
fi
|
||||
|
||||
# --- Verify frontend dist ---
|
||||
if [ ! -d "web/dist" ] || [ -z "$(ls -A web/dist 2>/dev/null)" ]; then
|
||||
@@ -146,4 +161,9 @@ if [ ! -d "web/dist" ] || [ -z "$(ls -A web/dist 2>/dev/null)" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# --- Prepare a writable HOME inside the data dir for tool caches (uv,
|
||||
# npm/npx MCP). Everything already runs as the package user (run-as:
|
||||
# package), so no chown is needed — files created here belong to it.
|
||||
mkdir -p "${DATA_DIR}/.home" 2>/dev/null || true
|
||||
|
||||
exit 0
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
#!/bin/bash
|
||||
# cmd/install_init - pre-install hook
|
||||
# Nothing special to do before extraction.
|
||||
# cmd/install_init - pre-install hook (runs before files are applied)
|
||||
# Sweep stray processes from a previous failed/killed install: orphans whose
|
||||
# parent was hard-killed keep running and hold the runtime/box ws ports,
|
||||
# which breaks the new instance. No match is the normal case on a clean
|
||||
# install — pkill exits 1.
|
||||
|
||||
RUN_USER=$(id -un)
|
||||
pkill -KILL -u "${RUN_USER}" -f "appcenter/langbot" 2>/dev/null || true
|
||||
pkill -KILL -u "${RUN_USER}" -f "appshare/langbot" 2>/dev/null || true
|
||||
|
||||
exit 0
|
||||
|
||||
+69
-24
@@ -27,27 +27,48 @@ if [ -z "${DATA_DIR}" ]; then
|
||||
DATA_DIR="${TRIM_PKGVAR}/data"
|
||||
fi
|
||||
export LANGBOT_DATA_ROOT="${DATA_DIR}"
|
||||
mkdir -p "${DATA_DIR}" 2>/dev/null || true
|
||||
mkdir -p "${DATA_DIR}" || {
|
||||
echo "Data share not writable: ${DATA_DIR}" > "${TRIM_TEMP_LOGFILE}"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# --- Locate Python ---
|
||||
PYTHON_BIN="python3"
|
||||
! command -v "${PYTHON_BIN}" >/dev/null 2>&1 && PYTHON_BIN="python"
|
||||
# --- Writable HOME / tool caches / venv on the data share ---
|
||||
# Must match cmd/install_callback: under run-as: package neither the system
|
||||
# HOME nor TRIM_APPDEST are guaranteed writable, and the venv lives at
|
||||
# ${DATA_DIR}/.venv instead of inside the app dir.
|
||||
export HOME="${DATA_DIR}/.home"
|
||||
export UV_CACHE_DIR="${DATA_DIR}/.cache/uv"
|
||||
# Never download a managed CPython — distro Python only (see install_callback)
|
||||
export UV_PYTHON_DOWNLOADS=never
|
||||
export UV_PROJECT_ENVIRONMENT="${DATA_DIR}/.venv"
|
||||
mkdir -p "${HOME}" "${UV_CACHE_DIR}" || {
|
||||
echo "Data share not writable: ${DATA_DIR}" > "${TRIM_TEMP_LOGFILE}"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# --- Locate uv ---
|
||||
# install_callback puts the bundled uv binary at ${TRIM_PKGVAR}/bin/uv
|
||||
UV_BIN="${TRIM_PKGVAR}/bin/uv"
|
||||
if [ ! -x "${UV_BIN}" ]; then
|
||||
UV_BIN="uv"
|
||||
fi
|
||||
if ! command -v "${UV_BIN}" >/dev/null 2>&1; then
|
||||
UV_BIN="${HOME}/.local/bin/uv"
|
||||
fi
|
||||
if ! command -v "${UV_BIN}" >/dev/null 2>&1 && [ ! -x "${UV_BIN}" ]; then
|
||||
UV_BIN="${HOME}/.cargo/bin/uv"
|
||||
fi
|
||||
if ! command -v "${UV_BIN}" >/dev/null 2>&1 && [ ! -x "${UV_BIN}" ]; then
|
||||
UV_BIN="${APP_DIR}/.venv/bin/uv"
|
||||
fi
|
||||
# --- CPU architecture (must be resolved before locating bundled binaries) ---
|
||||
ARCH=$(uname -m)
|
||||
case "${ARCH}" in
|
||||
x86_64|aarch64) ;;
|
||||
*)
|
||||
echo "Unsupported CPU architecture: ${ARCH}" > "${TRIM_TEMP_LOGFILE}"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
# --- Locate Python: official python312 App Store app (same borrow pattern as Node.js) ---
|
||||
PYTHON_BIN="/var/apps/python312/target/bin/python3"
|
||||
[ -x "${PYTHON_BIN}" ] || {
|
||||
echo "Python interpreter missing or not executable: ${PYTHON_BIN} (install the python312 app)" > "${TRIM_TEMP_LOGFILE}"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# --- Locate uv: bundled binary at its single canonical path in the app dir ---
|
||||
UV_BIN="${TRIM_APPDEST}/bin/uv-${ARCH}"
|
||||
[ -x "${UV_BIN}" ] || {
|
||||
echo "Bundled uv binary missing or not executable: ${UV_BIN}" > "${TRIM_TEMP_LOGFILE}"
|
||||
exit 1
|
||||
}
|
||||
|
||||
case $1 in
|
||||
start)
|
||||
@@ -69,7 +90,7 @@ case $1 in
|
||||
exit 1
|
||||
}
|
||||
|
||||
if [ ! -d ".venv" ]; then
|
||||
if [ ! -d "${DATA_DIR}/.venv" ]; then
|
||||
echo "Python virtual environment not found. Please reinstall LangBot." > "${TRIM_TEMP_LOGFILE}"
|
||||
exit 1
|
||||
fi
|
||||
@@ -79,6 +100,9 @@ case $1 in
|
||||
# fresh data/ with a default 5300 config gets recreated inside target/ on
|
||||
# every install/upgrade. The symlink keeps everything on the persistent
|
||||
# share; it is recreated here on each start (upgrades wipe target/).
|
||||
# Requires the package user to have write permission on the app dir — if
|
||||
# fnOS ever mounts it read-only, this fails loudly instead of silently
|
||||
# running against an ephemeral data directory.
|
||||
APP_DATA="${APP_DIR}/data"
|
||||
if [ -L "${APP_DATA}" ]; then
|
||||
# already a symlink; re-point if the persistent dir changed
|
||||
@@ -87,7 +111,7 @@ case $1 in
|
||||
# legacy real dir (created by LangBot before this fix): merge into the
|
||||
# persistent dir without overwriting newer files already there
|
||||
mkdir -p "${DATA_DIR}"
|
||||
cp -an "${APP_DATA}/." "${DATA_DIR}/" 2>/dev/null || cp -a "${APP_DATA}/." "${DATA_DIR}/"
|
||||
cp -an "${APP_DATA}/." "${DATA_DIR}/." || cp -a "${APP_DATA}/." "${DATA_DIR}/"
|
||||
rm -rf "${APP_DATA}"
|
||||
ln -s "${DATA_DIR}" "${APP_DATA}"
|
||||
else
|
||||
@@ -146,7 +170,22 @@ except Exception:
|
||||
# (--standalone-runtime would require an external runtime at
|
||||
# ws://langbot_plugin_runtime:5400, which only exists in Docker Compose.)
|
||||
# --standalone-box omitted: Box sandbox defaults off, users enable via Web UI
|
||||
nohup "${UV_BIN}" run --no-sync main.py \
|
||||
#
|
||||
# Privilege model: the whole app runs as the generated package user
|
||||
# (run-as: package, see config/privilege) — no root anywhere. HOME /
|
||||
# UV_CACHE_DIR / UV_PROJECT_ENVIRONMENT are exported at the top and point
|
||||
# at the persistent share so tool caches (uv, npm/npx) and the relocated
|
||||
# venv stay writable regardless of the generated user's system home.
|
||||
#
|
||||
# Process-group lifecycle: setsid makes the main process a session/group
|
||||
# leader, so PID == PGID. "stop" kills the whole group — stdio children
|
||||
# (plugin runtime, Box) die with the parent and can never survive as
|
||||
# orphans holding their ws ports after a crash, stop or upgrade.
|
||||
command -v setsid >/dev/null 2>&1 || {
|
||||
echo "setsid not found (util-linux required for process-group lifecycle)" > "${TRIM_TEMP_LOGFILE}"
|
||||
exit 1
|
||||
}
|
||||
setsid nohup "${UV_BIN}" run --no-sync main.py \
|
||||
> "${LOG_FILE}" 2>&1 &
|
||||
echo $! > "${PID_FILE}"
|
||||
|
||||
@@ -165,12 +204,18 @@ except Exception:
|
||||
if [ -f "${PID_FILE}" ]; then
|
||||
PID=$(cat "${PID_FILE}" | tr -d '[:space:]')
|
||||
if [ -n "${PID}" ]; then
|
||||
kill "${PID}" 2>/dev/null
|
||||
# Started with setsid, so PID == PGID: kill the whole group so
|
||||
# stdio children (plugin runtime, Box) die with the parent. The
|
||||
# plain-PID kill covers instances started before the setsid
|
||||
# change (group kill is a no-op for them).
|
||||
kill -TERM -- "-${PID}" 2>/dev/null
|
||||
kill -TERM "${PID}" 2>/dev/null
|
||||
for _ in 1 2 3 4 5 6 7 8 9 10; do
|
||||
kill -0 "${PID}" 2>/dev/null || break
|
||||
sleep 1
|
||||
done
|
||||
kill -9 "${PID}" 2>/dev/null
|
||||
kill -KILL -- "-${PID}" 2>/dev/null
|
||||
kill -KILL "${PID}" 2>/dev/null
|
||||
fi
|
||||
rm -f "${PID_FILE}"
|
||||
fi
|
||||
|
||||
@@ -7,8 +7,7 @@ if [ "${wizard_keep_data:-yes}" = "no" ]; then
|
||||
# 应用运行数据(pid、日志等)
|
||||
if [ -n "${TRIM_PKGVAR}" ]; then
|
||||
rm -rf "${TRIM_PKGVAR:?}"/langbot.pid \
|
||||
"${TRIM_PKGVAR:?}"/langbot.log \
|
||||
"${TRIM_PKGVAR:?}"/bin 2>/dev/null || true
|
||||
"${TRIM_PKGVAR:?}"/langbot.log 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# 共享数据目录(langbot/data)
|
||||
|
||||
@@ -8,6 +8,20 @@ APP_DIR="${TRIM_APPDEST}/langbot"
|
||||
DATA_DIR="${TRIM_DATA_SHARE_PATHS%%:*}"
|
||||
[ -z "${DATA_DIR}" ] && DATA_DIR="${TRIM_PKGVAR}/data"
|
||||
|
||||
# Writable HOME / caches / venv on the data share (must match
|
||||
# cmd/install_callback — venv is relocated under DATA_DIR).
|
||||
VENV_DIR="${DATA_DIR}/.venv"
|
||||
export HOME="${DATA_DIR}/.home"
|
||||
export UV_CACHE_DIR="${DATA_DIR}/.cache/uv"
|
||||
# Never download a managed CPython — distro Python only (see install_callback)
|
||||
export UV_PYTHON_DOWNLOADS=never
|
||||
export UV_PROJECT_ENVIRONMENT="${VENV_DIR}"
|
||||
mkdir -p "${HOME}" "${UV_CACHE_DIR}" "${DATA_DIR}/logs" || {
|
||||
echo "Data share not writable: ${DATA_DIR}" > "${TRIM_TEMP_LOGFILE}"
|
||||
exit 1
|
||||
}
|
||||
DEBUG_LOG="${DATA_DIR}/logs/upgrade-debug.log"
|
||||
|
||||
# Apply port from upgrade wizard (config persists across upgrades; this
|
||||
# only rewrites it when the user changed the value in the upgrade wizard)
|
||||
CONFIG_FILE="${DATA_DIR}/config.yaml"
|
||||
@@ -26,52 +40,56 @@ cd "${APP_DIR}" || {
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Find uv (bundled first, then PATH / ~/.local/bin / ~/.cargo/bin)
|
||||
UV_BIN="${TRIM_PKGVAR}/bin/uv"
|
||||
if [ ! -x "${UV_BIN}" ]; then
|
||||
UV_BIN="uv"
|
||||
fi
|
||||
if ! command -v "${UV_BIN}" >/dev/null 2>&1; then
|
||||
UV_BIN="${HOME}/.local/bin/uv"
|
||||
fi
|
||||
if ! command -v "${UV_BIN}" >/dev/null 2>&1 && [ ! -x "${UV_BIN}" ]; then
|
||||
UV_BIN="${HOME}/.cargo/bin/uv"
|
||||
fi
|
||||
# --- CPU architecture (must be resolved before locating bundled binaries) ---
|
||||
ARCH=$(uname -m)
|
||||
case "${ARCH}" in
|
||||
x86_64|aarch64) ;;
|
||||
*)
|
||||
echo "Unsupported CPU architecture: ${ARCH}" > "${TRIM_TEMP_LOGFILE}"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
PYTHON_BIN="python3"
|
||||
! command -v "${PYTHON_BIN}" >/dev/null 2>&1 && PYTHON_BIN="python"
|
||||
# uv: bundled binary at its single canonical path in the app dir
|
||||
UV_BIN="${TRIM_APPDEST}/bin/uv-${ARCH}"
|
||||
[ -x "${UV_BIN}" ] || {
|
||||
echo "Bundled uv binary missing or not executable: ${UV_BIN}" > "${TRIM_TEMP_LOGFILE}"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Re-sync deps
|
||||
if [ -d ".venv" ]; then
|
||||
"${UV_BIN}" sync --extra seekdb 2>/dev/null || {
|
||||
echo "Dependency sync failed after upgrade" > "${TRIM_TEMP_LOGFILE}"
|
||||
# Python: official python312 App Store app (same borrow pattern as Node.js)
|
||||
PYTHON_BIN="/var/apps/python312/target/bin/python3"
|
||||
[ -x "${PYTHON_BIN}" ] || {
|
||||
echo "Python interpreter missing or not executable: ${PYTHON_BIN} (install the python312 app)" > "${TRIM_TEMP_LOGFILE}"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Re-sync deps (venv lives at ${DATA_DIR}/.venv, see install_callback)
|
||||
if [ -d "${VENV_DIR}" ]; then
|
||||
UV_PROJECT_ENVIRONMENT="${VENV_DIR}" "${UV_BIN}" sync --extra seekdb >> "${DEBUG_LOG}" 2>&1 || {
|
||||
echo "Dependency sync failed after upgrade. See ${DEBUG_LOG}" > "${TRIM_TEMP_LOGFILE}"
|
||||
exit 1
|
||||
}
|
||||
else
|
||||
# Venv was lost, recreate via uv
|
||||
if ! command -v "${UV_BIN}" >/dev/null 2>&1 && [ ! -x "${UV_BIN}" ]; then
|
||||
"${PYTHON_BIN}" -m pip install --user --no-cache-dir uv 2>/dev/null || \
|
||||
"${PYTHON_BIN}" -m pip install --no-cache-dir uv 2>/dev/null || {
|
||||
echo "Failed to install uv" > "${TRIM_TEMP_LOGFILE}"
|
||||
exit 1
|
||||
}
|
||||
export PATH="${HOME}/.local/bin:${PATH}"
|
||||
UV_BIN="uv"
|
||||
fi
|
||||
"${UV_BIN}" venv .venv --python "${PYTHON_BIN}" || {
|
||||
echo "Failed to recreate virtual environment" > "${TRIM_TEMP_LOGFILE}"
|
||||
# Venv was lost, recreate it with the bundled uv on the data share
|
||||
"${UV_BIN}" venv "${VENV_DIR}" --python "${PYTHON_BIN}" >> "${DEBUG_LOG}" 2>&1 || {
|
||||
echo "Failed to recreate virtual environment. See ${DEBUG_LOG}" > "${TRIM_TEMP_LOGFILE}"
|
||||
exit 1
|
||||
}
|
||||
"${UV_BIN}" sync --extra seekdb || {
|
||||
echo "Dependency sync failed" > "${TRIM_TEMP_LOGFILE}"
|
||||
UV_PROJECT_ENVIRONMENT="${VENV_DIR}" "${UV_BIN}" sync --extra seekdb >> "${DEBUG_LOG}" 2>&1 || {
|
||||
echo "Dependency sync failed. See ${DEBUG_LOG}" > "${TRIM_TEMP_LOGFILE}"
|
||||
exit 1
|
||||
}
|
||||
fi
|
||||
|
||||
# Verify frontend dist still present
|
||||
if [ ! -d "web/dist" ] || [ -z "$(ls -A web/dist 2>/dev/null)" ]; then
|
||||
echo "Frontend dist missing after upgrade! Web UI will not be available." > "${TRIM_TEMP_LOGFILE}"
|
||||
echo "Frontend dist missing! Web UI will not be available." > "${TRIM_TEMP_LOGFILE}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Everything runs as the package user (run-as: package); the venv recreated
|
||||
# above and the config rewritten via sed already belong to it. Cache HOME is
|
||||
# prepared at the top of this script (see cmd/install_callback).
|
||||
|
||||
exit 0
|
||||
|
||||
@@ -1,20 +1,36 @@
|
||||
#!/bin/bash
|
||||
# cmd/upgrade_init - pre-upgrade hook
|
||||
# Stop the running LangBot process before files are replaced.
|
||||
# cmd/upgrade_init - pre-upgrade hook (runs before files are replaced)
|
||||
# 1. Stop the running LangBot instance — whole process group, so stdio
|
||||
# children (plugin runtime, Box) die with the parent.
|
||||
# 2. Sweep stray processes from a previous crash/upgrade: orphans whose
|
||||
# parent was hard-killed keep running and hold the runtime/box ws ports,
|
||||
# which breaks the next start.
|
||||
|
||||
PID_FILE="${TRIM_PKGVAR}/langbot.pid"
|
||||
|
||||
if [ -f "${PID_FILE}" ]; then
|
||||
PID=$(cat "${PID_FILE}" | tr -d '[:space:]')
|
||||
if [ -n "${PID}" ] && kill -0 "${PID}" 2>/dev/null; then
|
||||
kill "${PID}" 2>/dev/null
|
||||
# Instances started with setsid have PID == PGID; the plain-PID kill
|
||||
# covers instances started before that change.
|
||||
kill -TERM -- "-${PID}" 2>/dev/null
|
||||
kill -TERM "${PID}" 2>/dev/null
|
||||
for _ in 1 2 3 4 5 6 7 8 9 10; do
|
||||
kill -0 "${PID}" 2>/dev/null || break
|
||||
sleep 1
|
||||
done
|
||||
kill -9 "${PID}" 2>/dev/null
|
||||
kill -KILL -- "-${PID}" 2>/dev/null
|
||||
kill -KILL "${PID}" 2>/dev/null
|
||||
fi
|
||||
rm -f "${PID_FILE}"
|
||||
fi
|
||||
|
||||
# Stray sweep: match only our volume paths (venv/uv under the app dir on
|
||||
# @appcenter, venv/HOME/caches under the data share on @appshare). This
|
||||
# script itself runs from /var/apps/<appname>/cmd/, so it never matches
|
||||
# itself. No match is the normal case on a clean upgrade — pkill exits 1.
|
||||
RUN_USER=$(id -un)
|
||||
pkill -KILL -u "${RUN_USER}" -f "appcenter/langbot" 2>/dev/null || true
|
||||
pkill -KILL -u "${RUN_USER}" -f "appshare/langbot" 2>/dev/null || true
|
||||
|
||||
exit 0
|
||||
|
||||
Reference in New Issue
Block a user