mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-09-16 14:57:15 +00:00
Fnos packaging (#2524)
* feat(packaging): add fnOS FPK packaging and CI workflow Add packaging/fnos/ shell (manifest, lifecycle cmd scripts, install/ upgrade/uninstall wizards, desktop entry, EULA) plus in-repo build script. A release now auto-builds langbot-<tag>-fnos.fpk via .github/workflows/build-fnos-fpk.yaml, uploaded to the release assets. * ci(fnos): skip release upload on manual dispatch github.event.release.tag_name is empty when triggered via workflow_dispatch, causing 'gh release upload' to fail with 'requires at least 2 arg(s)'. Restrict the step to release events. * ci(fnos): normalize release asset name Strip a trailing -fnos from the tag-derived version before appending the suffix, avoiding langbot-<v>-fnos-fnos.fpk when the tag itself already carries -fnos. * ci(fnos): use release tag version for auto build, manifest for manual Auto build (release/tag) reads version from tag like other release workflows; build.sh strips the v prefix when injecting into manifest. Manual dispatch falls back to the version maintained in manifest. * feat(fnos): add post-install deployment notice to install wizard Last wizard step now informs users that first startup takes about 5-10 minutes for dependency setup before the web UI is ready. * docs(fnos): add packaging directory README * refactor(fnos): rename app from ai.langbot to langbot Rename appname, desktop entry, data share, build artifacts, and all references from ai.langbot to langbot across packaging files. --------- Co-authored-by: dadachann <185672915+dadachann@users.noreply.github.com>
This commit is contained in:
Executable
+6
@@ -0,0 +1,6 @@
|
||||
#!/bin/bash
|
||||
# cmd/config_callback - post-config hook
|
||||
# Applies wizard-provided settings (e.g. Node.js version) that cmd/main reads.
|
||||
# Nothing to persist currently; cmd/main reads wizard_node_version at runtime.
|
||||
|
||||
exit 0
|
||||
Executable
+4
@@ -0,0 +1,4 @@
|
||||
#!/bin/bash
|
||||
# cmd/config_init - pre-config hook
|
||||
|
||||
exit 0
|
||||
Executable
+149
@@ -0,0 +1,149 @@
|
||||
#!/bin/bash
|
||||
# cmd/install_callback - post-install hook
|
||||
# Prefers bundled uv binary, creates Python venv, syncs deps, verifies dist.
|
||||
# Also validates the user-selected Node.js version is actually installed.
|
||||
|
||||
APP_DIR="${TRIM_APPDEST}/langbot"
|
||||
|
||||
# --- Resolve data directory ---
|
||||
# LangBot loads config CWD-relative (data/config.yaml); cmd/main replaces
|
||||
# APP_DIR/data with a symlink to this persistent dir on every start.
|
||||
DATA_DIR="${TRIM_DATA_SHARE_PATHS%%:*}"
|
||||
if [ -z "${DATA_DIR}" ]; then
|
||||
DATA_DIR="${TRIM_PKGVAR}/data"
|
||||
fi
|
||||
|
||||
cd "${APP_DIR}" || {
|
||||
echo "App directory missing after install" > "${TRIM_TEMP_LOGFILE}"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# --- Ensure data directory exists ---
|
||||
mkdir -p "${DATA_DIR}/plugins" "${DATA_DIR}/box" "${DATA_DIR}/logs" 2>/dev/null || true
|
||||
|
||||
# --- Pre-seed config.yaml with the user-selected web port ---
|
||||
# Data root points at the persistent share (LANGBOT_DATA_ROOT is exported by
|
||||
# cmd/main at start), so this config survives app upgrades.
|
||||
# LangBot copies templates/config.yaml there on first boot only if missing,
|
||||
# so we seed it ourselves with the chosen port.
|
||||
PORT="${wizard_port:-5300}"
|
||||
case "${PORT}" in
|
||||
''|*[!0-9]*) PORT="5300" ;;
|
||||
esac
|
||||
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
|
||||
if [ ! -f "${cfg_file}" ] && [ -f "${TEMPLATE_FILE}" ]; then
|
||||
cp "${TEMPLATE_FILE}" "${cfg_file}"
|
||||
fi
|
||||
if [ -f "${cfg_file}" ]; then
|
||||
sed -i -E "/^api:/,/^[a-z_]+:/ s/^([[:space:]]*port:).*/\1 ${PORT}/" "${cfg_file}"
|
||||
sed -i "s#webhook_prefix: 'http://127\.0\.0\.1:[0-9]*'#webhook_prefix: 'http://127.0.0.1:${PORT}'#" "${cfg_file}"
|
||||
fi
|
||||
}
|
||||
|
||||
# Seed the persistent dir AND the CWD-relative APP_DIR/data (cmd/main merges
|
||||
# the latter into the persistent dir via symlink on first start, so the port
|
||||
# survives regardless of which path ends up being read).
|
||||
_patch_config "${DATA_DIR}"
|
||||
if [ "${APP_DIR}/data" != "${DATA_DIR}" ]; then
|
||||
_patch_config "${APP_DIR}/data"
|
||||
fi
|
||||
|
||||
# --- Fallback patch for desktop entry port ---
|
||||
# fnOS natively substitutes ${wizard_port} in ui/config at install time.
|
||||
# This only kicks in if the placeholder somehow survived (e.g. CLI install).
|
||||
UI_CONFIG="${TRIM_APPDEST}/ui/config"
|
||||
if [ -f "${UI_CONFIG}" ] && grep -q 'wizard_port\|{port}' "${UI_CONFIG}"; then
|
||||
sed -i "s/\${wizard_port}/${PORT}/g; s/{port}/${PORT}/g" "${UI_CONFIG}"
|
||||
fi
|
||||
|
||||
# --- Validate user-selected Node.js version is installed ---
|
||||
NODE_VERSION="${wizard_node_version:-22}"
|
||||
if [ ! -d "/var/apps/nodejs_v${NODE_VERSION}" ]; then
|
||||
echo "Node.js v${NODE_VERSION} 未安装:请先在应用中心安装 nodejs_v${NODE_VERSION},再重新安装本应用。" > "${TRIM_TEMP_LOGFILE}"
|
||||
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}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
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
|
||||
echo "Python 3.11+ required but not found on this system" > "${TRIM_TEMP_LOGFILE}"
|
||||
exit 1
|
||||
fi
|
||||
PY_MAJOR=$(echo "${PY_VER}" | cut -d. -f1)
|
||||
PY_MINOR=$(echo "${PY_VER}" | cut -d. -f2)
|
||||
if [ "${PY_MAJOR}" -lt 3 ] || { [ "${PY_MAJOR}" -eq 3 ] && [ "${PY_MINOR}" -lt 11 ]; }; then
|
||||
echo "Python 3.11+ required, found ${PY_VER}" > "${TRIM_TEMP_LOGFILE}"
|
||||
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}"
|
||||
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}"
|
||||
exit 1
|
||||
}
|
||||
fi
|
||||
|
||||
# --- Sync dependencies ---
|
||||
"${UV_BIN}" sync --extra seekdb || {
|
||||
echo "Dependency sync failed. Check network connectivity." > "${TRIM_TEMP_LOGFILE}"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# --- Verify frontend dist ---
|
||||
if [ ! -d "web/dist" ] || [ -z "$(ls -A web/dist 2>/dev/null)" ]; then
|
||||
echo "Frontend dist missing! Web UI will not be available." > "${TRIM_TEMP_LOGFILE}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
exit 0
|
||||
Executable
+5
@@ -0,0 +1,5 @@
|
||||
#!/bin/bash
|
||||
# cmd/install_init - pre-install hook
|
||||
# Nothing special to do before extraction.
|
||||
|
||||
exit 0
|
||||
Executable
+194
@@ -0,0 +1,194 @@
|
||||
#!/bin/bash
|
||||
# cmd/main - LangBot lifecycle manager for fnOS
|
||||
# Handles start / stop / status via standalone-runtime Python process.
|
||||
# Node.js path is injected into PATH so Box sandbox npx MCP servers can run.
|
||||
|
||||
PID_FILE="${TRIM_PKGVAR}/langbot.pid"
|
||||
APP_DIR="${TRIM_APPDEST}/langbot"
|
||||
LOG_FILE="${TRIM_PKGVAR}/langbot.log"
|
||||
|
||||
# --- Locate fnOS Node.js bin path ---
|
||||
# fnOS appname-based path: /var/apps/nodejs_vXX/target/bin
|
||||
# This is a stable symlink regardless of which volume the app is on.
|
||||
NODE_VERSION="${wizard_node_version:-22}"
|
||||
NODE_BIN_DIR="/var/apps/nodejs_v${NODE_VERSION}/target/bin"
|
||||
|
||||
if [ -d "${NODE_BIN_DIR}" ]; then
|
||||
export PATH="${NODE_BIN_DIR}:${PATH}"
|
||||
fi
|
||||
|
||||
# --- Persistent data root ---
|
||||
# LangBot loads data/config.yaml CWD-RELATIVE (see core/stages/load_config.py:
|
||||
# load_yaml_config('data/config.yaml', ...)) and resolves its data root to
|
||||
# <CWD>/data in source-install mode — it does NOT honour LANGBOT_DATA_ROOT for
|
||||
# config.yaml. So the real fix is the symlink below: APP_DIR/data -> DATA_DIR.
|
||||
DATA_DIR="${TRIM_DATA_SHARE_PATHS%%:*}"
|
||||
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
|
||||
|
||||
# --- Locate Python ---
|
||||
PYTHON_BIN="python3"
|
||||
! command -v "${PYTHON_BIN}" >/dev/null 2>&1 && PYTHON_BIN="python"
|
||||
|
||||
# --- 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
|
||||
|
||||
case $1 in
|
||||
start)
|
||||
if [ -f "${PID_FILE}" ]; then
|
||||
PID=$(cat "${PID_FILE}" | tr -d '[:space:]')
|
||||
if [ -n "${PID}" ] && kill -0 "${PID}" 2>/dev/null; then
|
||||
exit 0
|
||||
fi
|
||||
rm -f "${PID_FILE}"
|
||||
fi
|
||||
|
||||
if [ ! -d "${APP_DIR}" ]; then
|
||||
echo "LangBot app directory missing: ${APP_DIR}" > "${TRIM_TEMP_LOGFILE}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cd "${APP_DIR}" || {
|
||||
echo "Cannot enter app directory: ${APP_DIR}" > "${TRIM_TEMP_LOGFILE}"
|
||||
exit 1
|
||||
}
|
||||
|
||||
if [ ! -d ".venv" ]; then
|
||||
echo "Python virtual environment not found. Please reinstall LangBot." > "${TRIM_TEMP_LOGFILE}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# --- Unify data location: APP_DIR/data -> symlink to persistent DATA_DIR ---
|
||||
# LangBot reads config CWD-relative (data/config.yaml), so without this a
|
||||
# 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/).
|
||||
APP_DATA="${APP_DIR}/data"
|
||||
if [ -L "${APP_DATA}" ]; then
|
||||
# already a symlink; re-point if the persistent dir changed
|
||||
[ "$(readlink "${APP_DATA}")" != "${DATA_DIR}" ] && ln -sfn "${DATA_DIR}" "${APP_DATA}"
|
||||
elif [ -d "${APP_DATA}" ]; then
|
||||
# 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}/"
|
||||
rm -rf "${APP_DATA}"
|
||||
ln -s "${DATA_DIR}" "${APP_DATA}"
|
||||
else
|
||||
ln -s "${DATA_DIR}" "${APP_DATA}"
|
||||
fi
|
||||
|
||||
# Ensure LangBot's actual listen port always matches what fnOS shows in
|
||||
# "应用设置 → 访问端口" (which is the single source of truth from the user's
|
||||
# POV). Two sources, checked in priority order:
|
||||
# 1. ${wizard_port} — only set during install/upgrade callbacks (not on
|
||||
# normal `start`; kept for completeness).
|
||||
# 2. target/ui/config — read the "port" field written by fnOS after
|
||||
# ${wizard_port} substitution AND any later edit the user made via
|
||||
# "应用设置 → 自定义 URL" pencil button.
|
||||
# Without this: user picks 5303 in wizard, LangBot still listens on its
|
||||
# default 5300, desktop shortcut hits 5303 → connection refused.
|
||||
CONFIG_FILE="${DATA_DIR}/config.yaml"
|
||||
_patch_port() {
|
||||
local _port="$1"
|
||||
case "${_port}" in
|
||||
''|*[!0-9]*) return ;;
|
||||
esac
|
||||
if [ ! -f "${CONFIG_FILE}" ]; then
|
||||
local _tmpl="${APP_DIR}/src/langbot/templates/config.yaml"
|
||||
[ -f "${_tmpl}" ] && cp "${_tmpl}" "${CONFIG_FILE}"
|
||||
fi
|
||||
if [ -f "${CONFIG_FILE}" ]; then
|
||||
sed -i -E "/^api:/,/^[a-z_]+:/ s/^([[:space:]]*port:).*/\1 ${_port}/" "${CONFIG_FILE}"
|
||||
sed -i "s#webhook_prefix: 'http://127\.0\.0\.1:[0-9]*'#webhook_prefix: 'http://127.0.0.1:${_port}'#" "${CONFIG_FILE}"
|
||||
fi
|
||||
}
|
||||
if [ -n "${wizard_port:-}" ]; then
|
||||
_patch_port "${wizard_port}"
|
||||
fi
|
||||
if [ -f "${TRIM_APPDEST}/ui/config" ]; then
|
||||
_port_from_ui=$(python3 -c 'import json,sys
|
||||
try:
|
||||
d = json.load(open(sys.argv[1]))
|
||||
for _name, _entry in (d.get(".url") or {}).items():
|
||||
p = _entry.get("port")
|
||||
if isinstance(p, (int, float)):
|
||||
print(int(p))
|
||||
elif isinstance(p, str) and p.isdigit():
|
||||
print(int(p))
|
||||
break
|
||||
except Exception:
|
||||
pass
|
||||
' "${TRIM_APPDEST}/ui/config" 2>/dev/null)
|
||||
if [ -n "${_port_from_ui}" ]; then
|
||||
_patch_port "${_port_from_ui}"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Native deployment: no --standalone-runtime flag, LangBot spawns the
|
||||
# plugin runtime as a stdio subprocess (same as official `uv run main.py`).
|
||||
# (--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 \
|
||||
> "${LOG_FILE}" 2>&1 &
|
||||
echo $! > "${PID_FILE}"
|
||||
|
||||
sleep 3
|
||||
if [ -f "${PID_FILE}" ]; then
|
||||
PID=$(cat "${PID_FILE}" | tr -d '[:space:]')
|
||||
if [ -n "${PID}" ] && kill -0 "${PID}" 2>/dev/null; then
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
echo "LangBot failed to start. Check ${LOG_FILE}" > "${TRIM_TEMP_LOGFILE}"
|
||||
exit 1
|
||||
;;
|
||||
|
||||
stop)
|
||||
if [ -f "${PID_FILE}" ]; then
|
||||
PID=$(cat "${PID_FILE}" | tr -d '[:space:]')
|
||||
if [ -n "${PID}" ]; then
|
||||
kill "${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
|
||||
fi
|
||||
rm -f "${PID_FILE}"
|
||||
fi
|
||||
exit 0
|
||||
;;
|
||||
|
||||
status)
|
||||
if [ -f "${PID_FILE}" ]; then
|
||||
PID=$(cat "${PID_FILE}" | tr -d '[:space:]')
|
||||
if [ -n "${PID}" ] && kill -0 "${PID}" 2>/dev/null; then
|
||||
exit 0
|
||||
fi
|
||||
rm -f "${PID_FILE}"
|
||||
fi
|
||||
exit 3
|
||||
;;
|
||||
|
||||
*)
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
Executable
+21
@@ -0,0 +1,21 @@
|
||||
#!/bin/bash
|
||||
# cmd/uninstall_callback - post-uninstall hook
|
||||
# The system preserves var/ and shares/ by default. Honor the user's
|
||||
# wizard_keep_data choice: delete data only when explicitly requested.
|
||||
|
||||
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
|
||||
fi
|
||||
|
||||
# 共享数据目录(langbot/data)
|
||||
DATA_DIR="${TRIM_DATA_SHARE_PATHS%%:*}"
|
||||
if [ -n "${DATA_DIR}" ]; then
|
||||
rm -rf "${DATA_DIR:?}" 2>/dev/null || true
|
||||
fi
|
||||
fi
|
||||
|
||||
exit 0
|
||||
Executable
+20
@@ -0,0 +1,20 @@
|
||||
#!/bin/bash
|
||||
# cmd/uninstall_init - pre-uninstall hook
|
||||
# Stop LangBot before files are removed.
|
||||
|
||||
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
|
||||
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
|
||||
fi
|
||||
rm -f "${PID_FILE}"
|
||||
fi
|
||||
|
||||
exit 0
|
||||
Executable
+77
@@ -0,0 +1,77 @@
|
||||
#!/bin/bash
|
||||
# cmd/upgrade_callback - post-upgrade hook
|
||||
# Re-sync dependencies after code replacement using uv.
|
||||
|
||||
APP_DIR="${TRIM_APPDEST}/langbot"
|
||||
|
||||
# Persistent data root (must match cmd/main)
|
||||
DATA_DIR="${TRIM_DATA_SHARE_PATHS%%:*}"
|
||||
[ -z "${DATA_DIR}" ] && DATA_DIR="${TRIM_PKGVAR}/data"
|
||||
|
||||
# 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"
|
||||
if [ -n "${wizard_port:-}" ] && [ -f "${CONFIG_FILE}" ]; then
|
||||
case "${wizard_port}" in
|
||||
''|*[!0-9]*) ;;
|
||||
*)
|
||||
sed -i -E "/^api:/,/^[a-z_]+:/ s/^([[:space:]]*port:).*/\1 ${wizard_port}/" "${CONFIG_FILE}"
|
||||
sed -i "s#webhook_prefix: 'http://127\.0\.0\.1:[0-9]*'#webhook_prefix: 'http://127.0.0.1:${wizard_port}'#" "${CONFIG_FILE}"
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
cd "${APP_DIR}" || {
|
||||
echo "App directory missing after upgrade" > "${TRIM_TEMP_LOGFILE}"
|
||||
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
|
||||
|
||||
PYTHON_BIN="python3"
|
||||
! command -v "${PYTHON_BIN}" >/dev/null 2>&1 && PYTHON_BIN="python"
|
||||
|
||||
# Re-sync deps
|
||||
if [ -d ".venv" ]; then
|
||||
"${UV_BIN}" sync --extra seekdb 2>/dev/null || {
|
||||
echo "Dependency sync failed after upgrade" > "${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}"
|
||||
exit 1
|
||||
}
|
||||
"${UV_BIN}" sync --extra seekdb || {
|
||||
echo "Dependency sync failed" > "${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}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
exit 0
|
||||
Executable
+20
@@ -0,0 +1,20 @@
|
||||
#!/bin/bash
|
||||
# cmd/upgrade_init - pre-upgrade hook
|
||||
# Stop the running LangBot process before files are replaced.
|
||||
|
||||
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
|
||||
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
|
||||
fi
|
||||
rm -f "${PID_FILE}"
|
||||
fi
|
||||
|
||||
exit 0
|
||||
Reference in New Issue
Block a user