#!/usr/bin/env bash set -euo pipefail PROJECT_ROOT="$(cd "$(dirname "$0")/../.." && pwd)" BUILD_DIR="${PROJECT_ROOT}/build" DIST_DIR="${PROJECT_ROOT}/dist/linux" VERSION="${VERSION:-$(git -C "$PROJECT_ROOT" describe --tags --dirty 2>/dev/null || echo "0.7.1")}" PACKAGE_ROOT="${PROJECT_ROOT}/deploy/packaging/linux" GO="${GO:-$(command -v go 2>/dev/null || echo "/home/jianf/go1.26.5/go/bin/go")}" ARCH="${1:-amd64}" # amd64 or arm64 ACTION="${2:-all}" # all, build, deb, tar, rpm DEB_ARCH="$ARCH" RPM_ARCH="$ARCH" TAR_ARCH="$ARCH" case "$ARCH" in amd64) DEB_ARCH="amd64"; RPM_ARCH="x86_64"; TAR_ARCH="amd64" ;; arm64) DEB_ARCH="arm64"; RPM_ARCH="aarch64"; TAR_ARCH="arm64" ;; *) echo "Unknown arch: $ARCH (use amd64 or arm64)"; exit 1 ;; esac echo "=== HomeAgent Linux Packager ===" echo "Version: $VERSION" echo "Arch: $ARCH" echo "" # ---- prepare go.mod for Linux build: replace Windows SDK path with local clone ---- prepare_gomod() { local gomod="$PROJECT_ROOT/go.mod" local sdk_clone="/tmp/homeagent-sdk" local patched=0 if grep -q 'replace gitcode.com/JianFeeeee/homeagent-sdk' "$gomod"; then echo ">>> Updating go.mod: replacing Windows SDK path with local clone..." if [ ! -d "$sdk_clone" ]; then echo ">>> Cloning SDK to $sdk_clone..." git clone git@gitcode.com:JianFeeeee/homeagent-sdk.git "$sdk_clone" 2>/dev/null || \ git clone https://gitcode.com/JianFeeeee/homeagent-sdk.git "$sdk_clone" 2>/dev/null || true fi if [ -d "$sdk_clone" ]; then sed -i.bak "s|^replace gitcode.com/JianFeeeee/homeagent-sdk => .*|replace gitcode.com/JianFeeeee/homeagent-sdk => ${sdk_clone}|" "$gomod" patched=1 else echo "WARNING: Cannot clone SDK. Build may fail." fi fi return $patched } restore_gomod() { if [ -f "$PROJECT_ROOT/go.mod.bak" ]; then echo ">>> Restoring original go.mod..." mv "$PROJECT_ROOT/go.mod.bak" "$PROJECT_ROOT/go.mod" fi } # ---- temporarily move .syso files (Windows COFF objects) for Linux builds ---- hide_syso() { echo ">>> Hiding .syso files (Windows-only object files)..." for dir in homed waiter; do if [ -f "$PROJECT_ROOT/cmd/$dir/$dir.syso" ]; then mv "$PROJECT_ROOT/cmd/$dir/$dir.syso" "$PROJECT_ROOT/cmd/$dir/$dir.syso.bak" fi done } restore_syso() { for dir in homed waiter; do if [ -f "$PROJECT_ROOT/cmd/$dir/$dir.syso.bak" ]; then mv "$PROJECT_ROOT/cmd/$dir/$dir.syso.bak" "$PROJECT_ROOT/cmd/$dir/$dir.syso" fi done } # ensure both are always restored on exit restore_all() { restore_gomod; restore_syso; } trap restore_all EXIT # ---- build Go binaries via existing build.sh ---- build_go() { echo ">>> Building Go binaries for linux/$ARCH..." prepare_gomod || true hide_syso bash "$PROJECT_ROOT/deploy/packaging/build.sh" "linux/$ARCH" "homed" 2>&1 || { echo "WARNING: homed build failed (CGO/sqlite3 issue). Server/full packages may be incomplete." } bash "$PROJECT_ROOT/deploy/packaging/build.sh" "linux/$ARCH" "waiter" 2>&1 || { echo "WARNING: waiter build failed." } local suffix="linux_${ARCH}" local homed_bin="$BUILD_DIR/homed_$suffix" local waiter_bin="$BUILD_DIR/waiter_$suffix" if [ ! -f "$homed_bin" ]; then echo "ERROR: homed binary not found at $homed_bin" exit 1 fi if [ ! -f "$waiter_bin" ]; then echo "ERROR: waiter binary not found at $waiter_bin" exit 1 fi echo " homed: $homed_bin ($(du -h "$homed_bin" | cut -f1))" echo " waiter: $waiter_bin ($(du -h "$waiter_bin" | cut -f1))" echo "" } # ---- build GUI (manual directory assembly, avoids electron-packager network issues) ---- build_gui() { local gui_dir="$PROJECT_ROOT/cmd/gui" local gui_out="$BUILD_DIR/homeagent-gui-linux-${TAR_ARCH}" if [ -d "$gui_out" ]; then echo ">>> GUI already built at $gui_out (delete to rebuild)" return fi echo ">>> Building GUI directory for linux/$ARCH..." if [ ! -d "$gui_dir/node_modules" ]; then echo " npm install..." (cd "$gui_dir" && npm install --production) fi local electron_dir="$gui_dir/node_modules/electron/dist" if [ ! -f "$electron_dir/electron" ]; then echo " WARNING: electron binary not found at $electron_dir. GUI will be skipped." return fi mkdir -p "$gui_out/resources/app/node_modules" mkdir -p "$gui_out/resources/app/renderer" # copy electron runtime (binary + shared libs) cp -r "$electron_dir"/* "$gui_out/" 2>/dev/null rm -f "$gui_out/resources/default_app.asar" 2>/dev/null # copy app source cp "$gui_dir/main.js" "$gui_out/resources/app/" cp "$gui_dir/preload.js" "$gui_out/resources/app/" cp "$gui_dir/package.json" "$gui_out/resources/app/" cp "$gui_dir/renderer/index.html" "$gui_out/resources/app/renderer/" cp "$gui_dir/renderer/app.js" "$gui_out/resources/app/renderer/" cp "$gui_dir/renderer/style.css" "$gui_out/resources/app/renderer/" 2>/dev/null || true cp "$gui_dir/renderer/mascot.svg" "$gui_out/resources/app/renderer/" 2>/dev/null || true # production node_modules for app if [ -d "$gui_dir/node_modules" ]; then for mod in icojs; do if [ -d "$gui_dir/node_modules/$mod" ]; then cp -r "$gui_dir/node_modules/$mod" "$gui_out/resources/app/node_modules/" fi done fi # create desktop entry and symlink cat > "$gui_out/resources/app/homeagent-gui.desktop" << DESKTOP [Desktop Entry] Name=HomeAgent Comment=HomeAgent Desktop GUI Exec=$gui_out/homeagent-gui Terminal=false Type=Application Categories=Utility; Icon=$gui_out/resources/app/icon.svg DESKTOP # create launcher script cat > "$gui_out/homeagent-gui" << 'LAUNCHER' #!/bin/sh DIR="$(cd "$(dirname "$0")" && pwd)" exec "$DIR/electron" "$DIR/resources/app" "$@" LAUNCHER chmod +x "$gui_out/homeagent-gui" chmod +x "$gui_out/electron" echo " GUI built: $gui_out ($(du -sh "$gui_out" | cut -f1))" echo "" } # ---- stage files for a variant ---- stage_variant() { local variant="$1" local staging="$2" local suffix="linux_${ARCH}" echo ">>> Staging $variant..." mkdir -p "$staging/usr/bin" mkdir -p "$staging/etc/systemd/system" mkdir -p "$staging/var/lib/homeagent" local initconfig_bin="$BUILD_DIR/initconfig_$suffix" case "$variant" in full) cp "$BUILD_DIR/homed_$suffix" "$staging/usr/bin/homed" cp "$BUILD_DIR/waiter_$suffix" "$staging/usr/bin/waiter" cp "$PROJECT_ROOT/deploy/homeagent.service" "$staging/etc/systemd/system/homeagent.service" [ -f "$initconfig_bin" ] && cp "$initconfig_bin" "$staging/usr/bin/initconfig" stage_setup "$staging" stage_gui "$staging" ;; server) cp "$BUILD_DIR/homed_$suffix" "$staging/usr/bin/homed" cp "$BUILD_DIR/waiter_$suffix" "$staging/usr/bin/waiter" cp "$PROJECT_ROOT/deploy/homeagent.service" "$staging/etc/systemd/system/homeagent.service" [ -f "$initconfig_bin" ] && cp "$initconfig_bin" "$staging/usr/bin/initconfig" stage_setup "$staging" ;; client) cp "$BUILD_DIR/waiter_$suffix" "$staging/usr/bin/waiter" stage_gui "$staging" ;; esac chmod 755 "$staging/usr/bin/"* 2>/dev/null || true echo " OK" } stage_gui() { local staging="$1" local gui_src="$BUILD_DIR/homeagent-gui-linux-${TAR_ARCH}" if [ -d "$gui_src" ]; then mkdir -p "$staging/usr/lib/homeagent-gui" cp -r "$gui_src"/* "$staging/usr/lib/homeagent-gui/" cat > "$staging/usr/bin/homeagent-gui" << 'SCRIPT' #!/bin/sh exec /usr/lib/homeagent-gui/homeagent-gui "$@" SCRIPT chmod 755 "$staging/usr/bin/homeagent-gui" else echo " WARNING: GUI not built, skipping GUI staging" fi } stage_setup() { local staging="$1" local setup_src="$PROJECT_ROOT/deploy/packaging/linux/setup.sh" if [ -f "$setup_src" ]; then mkdir -p "$staging/usr/lib/homeagent" cp "$setup_src" "$staging/usr/lib/homeagent/setup.sh" chmod 755 "$staging/usr/lib/homeagent/setup.sh" fi } # ---- create .deb ---- build_deb() { local variant="$1" local staging="$2" local deb_dir="${DIST_DIR}/deb" mkdir -p "$deb_dir" local pkg_name="homeagent-${variant}_${VERSION}_${DEB_ARCH}.deb" local deb_root deb_root="$(mktemp -d)" mkdir -p "$deb_root/DEBIAN" local control_file="$PACKAGE_ROOT/deb/control-${variant}" local installed_size_kb installed_size_kb=$(du -sk "$staging" | cut -f1) sed -e "s/VERSION_PLACEHOLDER/$VERSION/g" \ -e "s/ARCH_PLACEHOLDER/$DEB_ARCH/g" \ -e "s/INSTALLED_SIZE_PLACEHOLDER/$installed_size_kb/g" \ "$control_file" > "$deb_root/DEBIAN/control" if [ -f "$PACKAGE_ROOT/deb/postinst" ]; then cp "$PACKAGE_ROOT/deb/postinst" "$deb_root/DEBIAN/postinst" chmod 755 "$deb_root/DEBIAN/postinst" fi if [ -f "$PACKAGE_ROOT/deb/prerm" ]; then cp "$PACKAGE_ROOT/deb/prerm" "$deb_root/DEBIAN/prerm" chmod 755 "$deb_root/DEBIAN/prerm" fi cp -r "$staging"/* "$deb_root/" 2>/dev/null || true echo ">>> Building .deb: $pkg_name" fakeroot dpkg-deb --build "$deb_root" "$deb_dir/$pkg_name" 2>/dev/null || \ dpkg-deb --build "$deb_root" "$deb_dir/$pkg_name" 2>&1 rm -rf "$deb_root" echo " Created: $deb_dir/$pkg_name ($(du -h "$deb_dir/$pkg_name" | cut -f1))" } # ---- create combined .tar.gz (all binaries, no variant split) ---- build_tar() { local tar_dir="${DIST_DIR}/tar" mkdir -p "$tar_dir" local archive_name="homeagent_${VERSION}_linux_${TAR_ARCH}.tar.gz" local archive_dir="homeagent-${VERSION}-linux-${TAR_ARCH}" # build combined staging local staging staging="$(mktemp -d)" mkdir -p "$staging/usr/bin" "$staging/usr/lib/homeagent" # copy all available binaries for bin in homed waiter initconfig; do local src="$BUILD_DIR/${bin}_linux_${TAR_ARCH}" [ -f "$src" ] && cp "$src" "$staging/usr/bin/$bin" done # setup script local setup_src="$PROJECT_ROOT/deploy/packaging/linux/setup.sh" [ -f "$setup_src" ] && cp "$setup_src" "$staging/usr/lib/homeagent/setup.sh" # GUI if available local gui_src="$BUILD_DIR/homeagent-gui-linux-${TAR_ARCH}" if [ -d "$gui_src" ]; then mkdir -p "$staging/usr/lib/homeagent-gui" cp -r "$gui_src"/* "$staging/usr/lib/homeagent-gui/" cat > "$staging/usr/bin/homeagent-gui" << 'SCRIPT' #!/bin/sh exec /usr/lib/homeagent-gui/homeagent-gui "$@" SCRIPT chmod 755 "$staging/usr/bin/homeagent-gui" fi chmod 755 "$staging/usr/bin/"* 2>/dev/null || true echo ">>> Building .tar.gz: $archive_name" (cd "$staging" && tar czf "$tar_dir/$archive_name" --transform "s|^\.|${archive_dir}|" .) echo " Created: $tar_dir/$archive_name ($(du -h "$tar_dir/$archive_name" | cut -f1))" rm -rf "$staging" } # ---- create .rpm (via fpm if available) ---- build_rpm() { local variant="$1" local staging="$2" local rpm_dir="${DIST_DIR}/rpm" mkdir -p "$rpm_dir" local pkg_name="homeagent-${variant}-${VERSION}-1.${RPM_ARCH}.rpm" # find fpm local fpm_bin="$(command -v fpm 2>/dev/null || true)" if [ -z "$fpm_bin" ]; then fpm_bin="$(find /home -name "fpm" -type f -path "*/bin/*" 2>/dev/null | head -1 || true)" fi if [ -z "$fpm_bin" ]; then echo " SKIP .rpm: fpm not installed. Install it with: gem install fpm" echo " Alternatively, build RPM on Fedora/RHEL using:" echo " rpmbuild -ba deploy/packaging/linux/homeagent.spec" return fi # find or extract rpmbuild (fpm needs it) local rpmbuild_dir="/tmp/rpmext" if [ ! -f "$rpmbuild_dir/usr/bin/rpmbuild" ]; then # try to extract from cached deb packages local rpm_deb rpm_deb="$(find /tmp -name "rpm_*.deb" -type f 2>/dev/null | head -1)" if [ -z "$rpm_deb" ]; then rpm_deb="$(find "$PROJECT_ROOT" -name "rpm_*.deb" -type f 2>/dev/null | head -1)" fi if [ -n "$rpm_deb" ]; then mkdir -p "$rpmbuild_dir" (cd "$rpmbuild_dir" && ar x "$rpm_deb" 2>/dev/null && \ tar --no-same-permissions -xf data.tar.zst --zstd 2>/dev/null) || true for libdeb in /tmp/librpm*.deb; do [ -f "$libdeb" ] && (cd "$rpmbuild_dir" && ar x "$libdeb" 2>/dev/null && \ tar --no-same-permissions -xf data.tar.zst --zstd 2>/dev/null) || true done fi fi if [ -f "$rpmbuild_dir/usr/bin/rpmbuild" ]; then export PATH="$rpmbuild_dir/usr/bin:$PATH" export LD_LIBRARY_PATH="$rpmbuild_dir/usr/lib/x86_64-linux-gnu${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" export RPM_CONFIGDIR="$rpmbuild_dir/usr/lib/rpm" fi echo ">>> Building .rpm via fpm: $pkg_name" "$fpm_bin" -s dir -t rpm \ -n "homeagent-${variant}" \ -v "$VERSION" \ --iteration 1 \ -a "$RPM_ARCH" \ --description "HomeAgent ${variant^} package" \ --url "https://github.com/trueagent/HomeAgent" \ --license "Proprietary" \ -C "$staging" \ -p "$rpm_dir/$pkg_name" \ . 2>&1 echo " Created: $rpm_dir/$pkg_name" } # ---- main ---- main() { local target_arch="$ARCH" mkdir -p "$BUILD_DIR" case "$ACTION" in all|build) build_go build_gui ;; esac if [ "$ACTION" = "build" ]; then echo "" echo "=== Build complete. Binaries in $BUILD_DIR ===" exit 0 fi mkdir -p "$DIST_DIR" for variant in full server client; do echo "" echo "==============================================" echo " Packaging: $variant" echo "==============================================" local staging staging=$(mktemp -d) stage_variant "$variant" "$staging" case "$ACTION" in all|deb) build_deb "$variant" "$staging" ;; esac case "$ACTION" in all|rpm) build_rpm "$variant" "$staging" ;; esac rm -rf "$staging" done case "$ACTION" in all|tar) build_tar ;; esac echo "" echo "=== Done! Packages in: $DIST_DIR ===" echo "" echo "Summary:" find "$DIST_DIR" -type f \( -name "*.deb" -o -name "homeagent_*.tar.gz" -o -name "*.rpm" \) 2>/dev/null | sort | while read -r f; do echo " $(du -h "$f" | cut -f1) $f" done } main