Files
LangBot/packaging/fnos/cmd/install_callback
T
sheetung 60bb67f025 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>
2026-09-14 00:31:19 +08:00

150 lines
5.2 KiB
Bash
Executable File

#!/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