#!/usr/bin/env bash # ============================================================ # 打包为 .deb(安装到 /opt/fpv-relay) # - CLI 的运行库通过 Depends 声明(由 apt 安装) # - GUI 自带的 Qt6 运行库一并放进 /opt/fpv-relay/lib # 用法: # ./packaging/build-deb.sh # 仅命令行版 # ./packaging/build-deb.sh --with-gui # 含 GUI(需 QT_PREFIX) # 产物: dist/fpv-relay_<版本>_amd64.deb # ============================================================ set -e REPO="$(cd "$(dirname "$0")/.." && pwd)" WITH_GUI=0 [ "$1" = "--with-gui" ] && WITH_GUI=1 VERSION="$(cd "$REPO" && git describe --tags --always 2>/dev/null | tr -d 'v' || true)" [ -z "$VERSION" ] && VERSION="$(date +%Y%m%d)" case "$VERSION" in [0-9]*) ;; *) VERSION="0.0.0+git$(date +%Y%m%d).${VERSION}" ;; esac DEB_VER="${VERSION}-1" PKG="fpv-relay" ARCH="amd64" OUT="${REPO}/dist" ROOT="$(mktemp -d)" APP="${ROOT}/opt/fpv-relay" echo "==> 构建(CLI)" cd "${REPO}" cmake --preset linux-cli >/dev/null cmake --build --preset linux-cli -j"$(nproc)" >/dev/null GUI_DIST="" if [ "$WITH_GUI" = "1" ]; then : "${QT_PREFIX:?--with-gui 需要设置 QT_PREFIX 指向 Qt 安装目录}" echo "==> 构建(GUI)" cmake --preset linux-gui -DFPC_QT_PREFIX="${QT_PREFIX}" >/dev/null cmake --build --preset linux-gui -j"$(nproc)" >/dev/null GUI_DIST="${REPO}/build-gui/dist" fi echo "==> 准备 ${APP}" mkdir -p "${APP}/bin" "${APP}/lib" "${APP}/tools" "${ROOT}/DEBIAN" "${ROOT}/usr/bin" cp "${REPO}/build/dist/fpv-relay" "${APP}/bin/" cp "${REPO}/config.example.yaml" "${APP}/" 2>/dev/null || true [ -f "${REPO}/ass/gs.key" ] && cp "${REPO}/ass/gs.key" "${APP}/gs.key" cp -r "${REPO}/tools/." "${APP}/tools/" 2>/dev/null || true cp "${REPO}/README.md" "${APP}/" 2>/dev/null || true # 只打包这些非系统库(Qt 自带;其余系统库由 Depends 提供) bundle_libs() { local lp="${LD_LIBRARY_PATH}" [ -n "${QT_PREFIX:-}" ] && lp="${QT_PREFIX}/lib:${lp}" LD_LIBRARY_PATH="$lp" ldd "$1" 2>/dev/null | awk '{print $3}' | grep '^/' | while read -r lib; do base="$(basename "$lib")" case "$base" in libQt6*|libicu*) ;; *) continue ;; esac if [ ! -e "${APP}/lib/${base}" ]; then cp -L "$lib" "${APP}/lib/${base}" bundle_libs "$lib" fi done } # CLI 运行库一律用系统包,不打包 DEPS="libc6, libstdc++6, libgcc-s1, libpcap0.8 | libpcap0.8t64, libsodium23, libnl-3-200, libnl-route-3-200, libcap2, libgcrypt20, libgpg-error0, libibverbs1, liblz4-1, liblzma5, libzstd1, libsystemd0, libdbus-1-3, iw, ffmpeg" if [ "$WITH_GUI" = "1" ]; then echo "==> 打包 GUI 与 Qt 运行环境" cp "${GUI_DIST}/fpv-relay-gui" "${APP}/bin/" cp -rL "${GUI_DIST}/FpvRelay" "${APP}/bin/" 2>/dev/null || true mkdir -p "${APP}/plugins/platforms" "${APP}/plugins/xcbglintegrations" \ "${APP}/plugins/imageformats" "${APP}/plugins/iconengines" "${APP}/qml" for p in libqxcb.so libqminimal.so libqoffscreen.so; do [ -f "${QT_PREFIX}/plugins/platforms/${p}" ] && cp -L "${QT_PREFIX}/plugins/platforms/${p}" "${APP}/plugins/platforms/" done cp -L "${QT_PREFIX}/plugins/xcbglintegrations/"*.so "${APP}/plugins/xcbglintegrations/" 2>/dev/null || true cp -L "${QT_PREFIX}/plugins/imageformats/"*.so "${APP}/plugins/imageformats/" 2>/dev/null || true cp -L "${QT_PREFIX}/plugins/iconengines/"*.so "${APP}/plugins/iconengines/" 2>/dev/null || true # 只拷 FluentUI/本项目用到的 QML 模块(避免把整个 Qt qml 目录塞进去) cp -rL "${QT_PREFIX}/qml/QtQml" "${APP}/qml/" mkdir -p "${APP}/qml/Qt/labs" "${APP}/qml/Qt5Compat" "${APP}/qml/QtQuick" cp -rL "${QT_PREFIX}/qml/Qt/labs/qmlmodels" "${APP}/qml/Qt/labs/" 2>/dev/null || true cp -rL "${QT_PREFIX}/qml/Qt/labs/folderlistmodel" "${APP}/qml/Qt/labs/" 2>/dev/null || true cp -rL "${QT_PREFIX}/qml/Qt5Compat/GraphicalEffects" "${APP}/qml/Qt5Compat/" 2>/dev/null || true find "${QT_PREFIX}/qml/QtQuick" -maxdepth 1 -type f -exec cp -L {} "${APP}/qml/QtQuick/" \; for sub in Controls Dialogs Layouts Shapes Templates Window; do [ -d "${QT_PREFIX}/qml/QtQuick/${sub}" ] && cp -rL "${QT_PREFIX}/qml/QtQuick/${sub}" "${APP}/qml/QtQuick/" done # 按实际依赖收集 Qt6/ICU 库 bundle_libs "${APP}/bin/fpv-relay-gui" find "${APP}/qml" "${APP}/plugins" -name "*.so" 2>/dev/null | while read -r so; do bundle_libs "$so" done # qt.conf:让 Qt 从安装目录找插件与 QML cat > "${APP}/bin/qt.conf" <<'EOF' [Paths] Prefix=.. Plugins=plugins QmlImports=qml EOF DEPS="${DEPS}, libglib2.0-0 | libglib2.0-0t64, libgl1, libegl1, libglx0, libopengl0, libglvnd0, libx11-6, libxcb1, libxau6, libxdmcp6, libxkbcommon0, libxkbcommon-x11-0, libfontconfig1, libfreetype6, libexpat1, libpng16-16 | libpng16-16t64, zlib1g, libbz2-1.0, libbrotli1, libpcre2-8-0, libgssapi-krb5-2, libkrb5-3, libk5crypto3, libkrb5support0, libkeyutils1, libcom-err2, libbsd0, libmd0, libxcb-icccm4, libxcb-image0, libxcb-keysyms1, libxcb-randr0, libxcb-render-util0, libxcb-shape0, libxcb-xinerama0, libxcb-xkb1" fi cat > "${APP}/run.sh" <<'EOF' #!/usr/bin/env bash DIR="$(cd "$(dirname "$0")" && pwd)" export LD_LIBRARY_PATH="$DIR/lib:${LD_LIBRARY_PATH}" exec "$DIR/bin/fpv-relay" "$@" EOF chmod 755 "${APP}/run.sh" if [ "$WITH_GUI" = "1" ]; then cat > "${APP}/run-gui.sh" <<'EOF' #!/usr/bin/env bash DIR="$(cd "$(dirname "$0")" && pwd)" export LD_LIBRARY_PATH="$DIR/lib:${LD_LIBRARY_PATH}" export QT_PLUGIN_PATH="$DIR/plugins" export QT_QPA_PLATFORM_PLUGIN_PATH="$DIR/plugins/platforms" export QML2_IMPORT_PATH="$DIR/qml" export QML_IMPORT_PATH="$DIR/qml" exec "$DIR/bin/fpv-relay-gui" "$@" EOF chmod 755 "${APP}/run-gui.sh" fi # /usr/bin 下的启动器 cat > "${ROOT}/usr/bin/fpv-relay" <<'EOF' #!/bin/sh exec /opt/fpv-relay/run.sh "$@" EOF chmod 755 "${ROOT}/usr/bin/fpv-relay" if [ "$WITH_GUI" = "1" ]; then cat > "${ROOT}/usr/bin/fpv-relay-gui" <<'EOF' #!/bin/sh exec /opt/fpv-relay/run-gui.sh "$@" EOF chmod 755 "${ROOT}/usr/bin/fpv-relay-gui" fi cat > "${ROOT}/DEBIAN/control" < Section: net Priority: optional Depends: ${DEPS} Description: Multi-input FPV video relay (capture + low-latency forward) RTL8812AU capture (wfb-ng / devourer), low-latency UDP forwarding, DVR recording, RTSP output, mavlink uplink/downlink.${WITH_GUI:+ Includes Qt GUI with bundled Qt runtime.} EOF mkdir -p "${OUT}" dpkg-deb --build --root-owner-group "${ROOT}" "${OUT}/${PKG}_${DEB_VER}_${ARCH}.deb" rm -rf "${ROOT}" echo echo "完成: ${OUT}/${PKG}_${DEB_VER}_${ARCH}.deb" du -sh "${OUT}/${PKG}_${DEB_VER}_${ARCH}.deb"