#!/usr/bin/env bash set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" MODULE_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" BUILD_ROOT="$MODULE_ROOT/build" DIST_DIR="$MODULE_ROOT/dist" PYINSTALLER_BUILD="$BUILD_ROOT/pyinstaller" PYINSTALLER_SPEC="$PYINSTALLER_BUILD/spec" PYINSTALLER_WORK="$PYINSTALLER_BUILD/work" VENV_DIR="$BUILD_ROOT/venv" AGENT_BUILD_IMAGE="${AGENT_BUILD_IMAGE:-python:3.11-slim-bullseye}" AGENT_BUILD_USE_DOCKER="${AGENT_BUILD_USE_DOCKER:-1}" USED_DOCKER=0 run_host_build() { echo "[INFO] Using host Python environment for build" >&2 rm -rf "$BUILD_ROOT" "$DIST_DIR" mkdir -p "$PYINSTALLER_BUILD" "$DIST_DIR" python3 -m venv --copies "$VENV_DIR" # shellcheck disable=SC1091 source "$VENV_DIR/bin/activate" pip install --upgrade pip pip install . pip install "pyinstaller==6.6.0" pyinstaller \ --clean \ --onefile \ --name argus-agent \ --distpath "$DIST_DIR" \ --workpath "$PYINSTALLER_WORK" \ --specpath "$PYINSTALLER_SPEC" \ --add-data "$MODULE_ROOT/pyproject.toml:." \ "$MODULE_ROOT/entry.py" chmod +x "$DIST_DIR/argus-agent" deactivate } run_docker_build() { if ! command -v docker >/dev/null 2>&1; then echo "[ERROR] docker 命令不存在,无法在容器内构建。请安装 Docker 或设置 AGENT_BUILD_USE_DOCKER=0" >&2 exit 1 fi USED_DOCKER=1 echo "[INFO] Building agent binary inside $AGENT_BUILD_IMAGE" >&2 local host_uid host_gid host_uid="$(id -u)" host_gid="$(id -g)" docker_env=("--rm" "-v" "$MODULE_ROOT:/workspace" "-w" "/workspace" "--env" "TARGET_UID=${host_uid}" "--env" "TARGET_GID=${host_gid}") pass_env_if_set() { local var="$1" local value="${!var:-}" if [[ -n "$value" ]]; then docker_env+=("--env" "$var=$value") fi } pass_env_if_set PIP_INDEX_URL pass_env_if_set PIP_EXTRA_INDEX_URL pass_env_if_set PIP_TRUSTED_HOST pass_env_if_set HTTP_PROXY pass_env_if_set HTTPS_PROXY pass_env_if_set NO_PROXY pass_env_if_set http_proxy pass_env_if_set https_proxy pass_env_if_set no_proxy build_script=$(cat <<'INNER' set -euo pipefail cd /workspace apt-get update >/dev/null apt-get install -y --no-install-recommends binutils >/dev/null rm -rf /var/lib/apt/lists/* rm -rf build dist mkdir -p build/pyinstaller dist python3 -m venv --copies build/venv source build/venv/bin/activate pip install --upgrade pip pip install . pip install pyinstaller==6.6.0 pyinstaller \ --clean \ --onefile \ --name argus-agent \ --distpath dist \ --workpath build/pyinstaller/work \ --specpath build/pyinstaller/spec \ --add-data /workspace/pyproject.toml:. \ entry.py chmod +x dist/argus-agent TARGET_UID="${TARGET_UID:-0}" TARGET_GID="${TARGET_GID:-0}" chown -R "$TARGET_UID:$TARGET_GID" dist build 2>/dev/null || true python3 - <<'PY' from pathlib import Path from PyInstaller.archive.readers import CArchiveReader import sys archive = Path('dist/argus-agent') out_dir = Path('build/compat_check') out_dir.mkdir(parents=True, exist_ok=True) major, minor = sys.version_info[:2] libpython = f'libpython{major}.{minor}.so.1.0' expected_libs = [ libpython, 'libssl.so.3', 'libcrypto.so.3', ] reader = CArchiveReader(str(archive)) extracted = [] missing = [] for name in expected_libs: try: data = reader.extract(name) except KeyError: missing.append(name) continue (out_dir / name).write_bytes(data) extracted.append(name) (out_dir / 'manifest').write_text('\n'.join(extracted)) if extracted: print('[INFO] Extracted libraries: ' + ', '.join(extracted)) if missing: print('[WARN] Missing expected libraries in bundle: ' + ', '.join(missing)) PY compat_check() { local lib_path="$1" if [[ ! -f "$lib_path" ]]; then echo "[WARN] Missing $lib_path for GLIBC check" return fi local max_glibc max_glibc=$(strings -a "$lib_path" | grep -Eo 'GLIBC_[0-9]+\.[0-9]+' | sort -Vu | tail -n 1 || true) if [[ -n "$max_glibc" ]]; then echo "[INFO] $lib_path references up to $max_glibc" else echo "[INFO] $lib_path does not expose GLIBC version strings" fi } compat_libs=() if [[ -f build/compat_check/manifest ]]; then mapfile -t compat_libs < build/compat_check/manifest fi if [[ ${#compat_libs[@]} -eq 0 ]]; then echo "[WARN] No libraries captured for GLIBC inspection" else for lib in "${compat_libs[@]}"; do compat_check "build/compat_check/$lib" done fi deactivate INNER ) if ! docker run "${docker_env[@]}" "$AGENT_BUILD_IMAGE" bash -lc "$build_script"; then echo "[ERROR] Docker 构建失败,请检查 Docker 权限或设置 AGENT_BUILD_USE_DOCKER=0 在兼容主机上构建" >&2 exit 1 fi } if [[ "$AGENT_BUILD_USE_DOCKER" == "1" ]]; then run_docker_build else run_host_build fi if [[ ! -f "$DIST_DIR/argus-agent" ]]; then echo "[ERROR] Agent binary was not produced" >&2 exit 1 fi if [[ "$USED_DOCKER" != "1" ]]; then if [[ ! -x "$VENV_DIR/bin/python" ]]; then echo "[WARN] PyInstaller virtualenv missing at $VENV_DIR; skipping compatibility check" >&2 else COMPAT_DIR="$BUILD_ROOT/compat_check" rm -rf "$COMPAT_DIR" mkdir -p "$COMPAT_DIR" EXTRACT_SCRIPT=$(cat <<'PY' from pathlib import Path from PyInstaller.archive.readers import CArchiveReader import sys archive = Path('dist/argus-agent') out_dir = Path('build/compat_check') out_dir.mkdir(parents=True, exist_ok=True) major, minor = sys.version_info[:2] libpython = f'libpython{major}.{minor}.so.1.0' expected_libs = [ libpython, 'libssl.so.3', 'libcrypto.so.3', ] reader = CArchiveReader(str(archive)) extracted = [] missing = [] for name in expected_libs: try: data = reader.extract(name) except KeyError: missing.append(name) continue (out_dir / name).write_bytes(data) extracted.append(name) (out_dir / 'manifest').write_text('\n'.join(extracted)) if extracted: print('[INFO] Extracted libraries: ' + ', '.join(extracted)) if missing: print('[WARN] Missing expected libraries in bundle: ' + ', '.join(missing)) PY ) "$VENV_DIR/bin/python" - <&2 return fi if command -v strings >/dev/null 2>&1; then local max_glibc max_glibc=$(strings -a "$lib_path" | grep -Eo 'GLIBC_[0-9]+\.[0-9]+' | sort -Vu | tail -n 1 || true) if [[ -n "$max_glibc" ]]; then echo "[INFO] $lib_path references up to $max_glibc" else echo "[INFO] $lib_path does not expose GLIBC version strings" fi else echo "[WARN] strings command unavailable; cannot inspect $lib_path" >&2 fi } if [[ ${#compat_libs[@]} -eq 0 ]]; then echo "[WARN] No libraries captured for GLIBC inspection" >&2 else for lib in "${compat_libs[@]}"; do check_glibc_version "$COMPAT_DIR/$lib" done fi fi else echo "[INFO] Compatibility check executed inside container" fi echo "[INFO] Agent binary generated at $DIST_DIR/argus-agent"