mirror of
https://github.com/Quad4-Software/MeshChatX.git
synced 2026-08-18 09:49:09 -04:00
283 lines
8.1 KiB
Bash
Executable file
283 lines
8.1 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
# Fail if release artifacts contain paths that should not ship
|
|
# (dev trees, duplicate frontend sources, vendor offline caches, etc.).
|
|
#
|
|
# Usage:
|
|
# verify-package-contents.sh frozen [build/exe]
|
|
# verify-package-contents.sh docker [image:tag]
|
|
# verify-package-contents.sh wheel [path/to.whl]
|
|
# verify-package-contents.sh dir [path]
|
|
# verify-package-contents.sh appimage [path/to.AppImage]
|
|
# verify-package-contents.sh deb [path/to.deb]
|
|
# verify-package-contents.sh apk [path/to.apk]
|
|
#
|
|
# Env:
|
|
# PACKAGE_BLOAT_MAX_HITS stop after N hits (default 40)
|
|
set -euo pipefail
|
|
|
|
ROOT="$(CDPATH='' cd -- "$(dirname "$0")/../.." && pwd)"
|
|
MODE="${1:-}"
|
|
TARGET="${2:-}"
|
|
MAX_HITS="${PACKAGE_BLOAT_MAX_HITS:-40}"
|
|
|
|
usage() {
|
|
cat <<'EOF' >&2
|
|
Usage:
|
|
verify-package-contents.sh frozen [build/exe]
|
|
verify-package-contents.sh docker [image:tag]
|
|
verify-package-contents.sh wheel [file.whl]
|
|
verify-package-contents.sh dir [path]
|
|
verify-package-contents.sh appimage [file.AppImage]
|
|
verify-package-contents.sh deb [file.deb]
|
|
verify-package-contents.sh apk [file.apk]
|
|
EOF
|
|
exit 2
|
|
}
|
|
|
|
[ -n "$MODE" ] || usage
|
|
|
|
hits=0
|
|
hit_lines=()
|
|
|
|
record_hit() {
|
|
hits=$((hits + 1))
|
|
if [ "${#hit_lines[@]}" -lt "$MAX_HITS" ]; then
|
|
hit_lines+=("$1")
|
|
fi
|
|
}
|
|
|
|
# Shared denylist patterns (grep -E against relative paths).
|
|
# Bytecode (__pycache__) is denied for frozen/wheel/dir but not docker:
|
|
# Dockerfiles run compileall on purpose for faster cold start.
|
|
#
|
|
# Do not use a bare /(^|/)android(/|$)/ pattern: LXST ships
|
|
# LXST/Platforms/android for mobile hosts. That path must remain allowed in
|
|
# APKs and must not false-fail Docker scans of site-packages.
|
|
COMMON_DENY_RE='(^|/)\.git(/|$)|(^|/)node_modules(/|$)|(^|/)\.pnpm-store(/|$)|(^|/)\.venv(/|$)|(^|/)vendor/offline(/|$)|(^|/)vendor/lxmfy/tests(/|$)|(^|/)vendor/lxmfy/docs(/|$)|(^|/)vendor/lxmfy/docker(/|$)|(^|/)vendor/rns_filesync/tests(/|$)|(^|/)vendor/rns_filesync/docs(/|$)|(^|/)vendor/rns_filesync/docker(/|$)|(^|/)vendor/rns_filesync/packaging(/|$)|(^|/)vendor/rns_filesync/sideband(/|$)|(^|/)vendor/rns_filesync/man(/|$)|(^|/)vendor/rns_filesync/scripts(/|$)|(^|/)\.github(/|$)|(^|/)\.agents(/|$)|(^|/)screenshots(/|$)|(^|/)\.pytest_cache(/|$)|(^|/)mutants(/|$)|(^|/)coverage(/|$)'
|
|
|
|
BYTECODE_DENY_RE='(^|/)__pycache__(/|$)'
|
|
|
|
# MeshChatX Android app sources (gradle/app tree), not LXST.Platforms.android.
|
|
ANDROID_APP_DENY_RE='(^|/)android/(app|gradle)(/|$)|(^|/)android/[^/]+\.(gradle|kts|properties)$|(^)android(/|$)'
|
|
|
|
FROZEN_DENY_RE="${COMMON_DENY_RE}|${BYTECODE_DENY_RE}|(^|/)lib/meshchatx/public(/|$)|(^|/)lib/meshchatx/src/frontend/.+\.vue$|(^|/)lib/meshchatx/src/frontend/.+\.css$|(^|/)lib/setuptools(/|$)|(^|/)lib/pydoc_data(/|$)|(^|/)lib/numpy/.*/tests(/|$)|(^|/)lib/numpy/tests(/|$)"
|
|
|
|
DOCKER_DENY_RE="${COMMON_DENY_RE}|${ANDROID_APP_DENY_RE}|(^|/)meshchatx/src/frontend/.+\.vue$|(^|/)meshchatx/src/frontend/.+\.css$|(^|/)tests(/|$)|(^|/)electron(/|$)|(^|/)LXST/Platforms/android(/|$)"
|
|
|
|
WHEEL_DENY_RE="${COMMON_DENY_RE}|${BYTECODE_DENY_RE}|(^|/)meshchatx/src/frontend/.+\.vue$|(^|/)meshchatx/src/frontend/.+\.css$|(^|/)tests(/|$)"
|
|
|
|
APK_DENY_RE="${COMMON_DENY_RE}|${BYTECODE_DENY_RE}|${ANDROID_APP_DENY_RE}|(^|/)tests(/|$)|(^|/)electron(/|$)|(^|/)\.github(/|$)"
|
|
|
|
DIR_DENY_RE="${COMMON_DENY_RE}|${BYTECODE_DENY_RE}"
|
|
|
|
# Read relative paths from stdin. Must not run in a pipe subshell so hits persist.
|
|
scan_path_list() {
|
|
deny_re="$1"
|
|
while IFS= read -r rel || [ -n "${rel:-}" ]; do
|
|
[ -n "$rel" ] || continue
|
|
rel="${rel#./}"
|
|
case "$rel" in
|
|
"") continue ;;
|
|
esac
|
|
if printf '%s\n' "$rel" | grep -Eq "$deny_re"; then
|
|
record_hit "$rel"
|
|
fi
|
|
done
|
|
}
|
|
|
|
resolve_frozen_root() {
|
|
base="${1:-}"
|
|
if [ -z "$base" ]; then
|
|
base="$ROOT/build/exe"
|
|
fi
|
|
if [ ! -d "$base" ]; then
|
|
echo "verify-package-contents.sh: frozen root missing: $base" >&2
|
|
exit 1
|
|
fi
|
|
if [ -d "$base/lib" ]; then
|
|
printf '%s\n' "$base"
|
|
return 0
|
|
fi
|
|
for sub in "$base"/*; do
|
|
if [ -d "$sub/lib" ]; then
|
|
printf '%s\n' "$sub"
|
|
return 0
|
|
fi
|
|
done
|
|
echo "verify-package-contents.sh: no lib/ under $base" >&2
|
|
exit 1
|
|
}
|
|
|
|
scan_directory_tree() {
|
|
root="$1"
|
|
deny_re="$2"
|
|
# Process substitution keeps scan_path_list in this shell (hits accumulate).
|
|
scan_path_list "$deny_re" < <(
|
|
CDPATH='' cd -- "$root" || exit 1
|
|
find . -print 2>/dev/null | sed 's|^\./||'
|
|
)
|
|
}
|
|
|
|
scan_archive_listing() {
|
|
archive="$1"
|
|
deny_re="$2"
|
|
if command -v unzip >/dev/null 2>&1; then
|
|
scan_path_list "$deny_re" < <(unzip -Z1 "$archive" 2>/dev/null)
|
|
return 0
|
|
fi
|
|
echo "verify-package-contents.sh: unzip required to inspect $archive" >&2
|
|
exit 1
|
|
}
|
|
|
|
scan_frozen() {
|
|
root="$(resolve_frozen_root "${TARGET:-}")"
|
|
echo "verify-package-contents.sh: scanning frozen tree $root"
|
|
scan_directory_tree "$root" "$FROZEN_DENY_RE"
|
|
}
|
|
|
|
scan_dir() {
|
|
root="${TARGET:-}"
|
|
[ -n "$root" ] || usage
|
|
[ -d "$root" ] || {
|
|
echo "verify-package-contents.sh: missing dir $root" >&2
|
|
exit 1
|
|
}
|
|
echo "verify-package-contents.sh: scanning dir $root"
|
|
scan_directory_tree "$root" "$DIR_DENY_RE"
|
|
}
|
|
|
|
scan_wheel() {
|
|
whl="${TARGET:-}"
|
|
if [ -z "$whl" ]; then
|
|
whl="$(find "$ROOT/python-dist" -maxdepth 1 -type f -name '*.whl' -print 2>/dev/null | head -n 1 || true)"
|
|
fi
|
|
[ -n "$whl" ] && [ -f "$whl" ] || {
|
|
echo "verify-package-contents.sh: wheel not found" >&2
|
|
exit 1
|
|
}
|
|
echo "verify-package-contents.sh: scanning wheel $whl"
|
|
scan_archive_listing "$whl" "$WHEEL_DENY_RE"
|
|
}
|
|
|
|
scan_apk() {
|
|
apk="${TARGET:-}"
|
|
[ -n "$apk" ] && [ -f "$apk" ] || {
|
|
echo "verify-package-contents.sh: apk not found" >&2
|
|
exit 1
|
|
}
|
|
echo "verify-package-contents.sh: scanning apk $apk"
|
|
scan_archive_listing "$apk" "$APK_DENY_RE"
|
|
}
|
|
|
|
scan_deb() {
|
|
deb="${TARGET:-}"
|
|
[ -n "$deb" ] && [ -f "$deb" ] || {
|
|
echo "verify-package-contents.sh: deb not found" >&2
|
|
exit 1
|
|
}
|
|
tmp="$(mktemp -d "${TMPDIR:-/tmp}/pkg-bloat-deb.XXXXXX")"
|
|
trap 'rm -rf "$tmp"' EXIT INT
|
|
echo "verify-package-contents.sh: extracting deb $deb"
|
|
dpkg-deb -x "$deb" "$tmp"
|
|
scan_directory_tree "$tmp" "$FROZEN_DENY_RE"
|
|
}
|
|
|
|
scan_appimage() {
|
|
ai="${TARGET:-}"
|
|
[ -n "$ai" ] && [ -f "$ai" ] || {
|
|
echo "verify-package-contents.sh: AppImage not found" >&2
|
|
exit 1
|
|
}
|
|
tmp="$(mktemp -d "${TMPDIR:-/tmp}/pkg-bloat-ai.XXXXXX")"
|
|
trap 'rm -rf "$tmp"' EXIT INT
|
|
echo "verify-package-contents.sh: extracting AppImage $ai"
|
|
chmod +x "$ai" || true
|
|
(
|
|
CDPATH='' cd -- "$tmp"
|
|
"$ai" --appimage-extract >/dev/null
|
|
)
|
|
scan_directory_tree "$tmp/squashfs-root" "$FROZEN_DENY_RE"
|
|
}
|
|
|
|
scan_docker() {
|
|
image="${TARGET:-}"
|
|
[ -n "$image" ] || {
|
|
echo "verify-package-contents.sh: docker image tag required" >&2
|
|
exit 1
|
|
}
|
|
if ! command -v docker >/dev/null 2>&1; then
|
|
echo "verify-package-contents.sh: docker not available" >&2
|
|
exit 1
|
|
fi
|
|
echo "verify-package-contents.sh: scanning docker image $image (/opt/venv)"
|
|
# Use python (always present) so Alpine and Chainguard/hardened images both work.
|
|
# Emit paths relative to /opt/venv or /app so denylist matches cleanly.
|
|
list="$(
|
|
docker run --rm --user 0 --entrypoint python "$image" -c '
|
|
import os
|
|
roots = [p for p in ("/opt/venv", "/app") if os.path.isdir(p)]
|
|
if not roots:
|
|
roots = ["/"]
|
|
skip_top = {"proc", "sys", "dev", "tmp", "run"}
|
|
for root in roots:
|
|
for dirpath, dirnames, filenames in os.walk(root):
|
|
if root == "/":
|
|
if dirpath == "/":
|
|
dirnames[:] = [d for d in dirnames if d not in skip_top]
|
|
rel_dir = dirpath.lstrip("/") or "."
|
|
elif dirpath == root:
|
|
rel_dir = "."
|
|
else:
|
|
rel_dir = dirpath[len(root) + 1 :]
|
|
print(rel_dir)
|
|
for name in filenames:
|
|
if rel_dir == ".":
|
|
print(name)
|
|
else:
|
|
print(rel_dir + "/" + name)
|
|
' 2>/dev/null || true
|
|
)"
|
|
if [ -z "$list" ]; then
|
|
echo "verify-package-contents.sh: could not list files in $image" >&2
|
|
exit 1
|
|
fi
|
|
scan_path_list "$DOCKER_DENY_RE" <<<"$list"
|
|
}
|
|
|
|
case "$MODE" in
|
|
frozen)
|
|
scan_frozen
|
|
;;
|
|
dir)
|
|
scan_dir
|
|
;;
|
|
wheel)
|
|
scan_wheel
|
|
;;
|
|
apk)
|
|
scan_apk
|
|
;;
|
|
deb)
|
|
scan_deb
|
|
;;
|
|
appimage)
|
|
scan_appimage
|
|
;;
|
|
docker)
|
|
scan_docker
|
|
;;
|
|
*)
|
|
usage
|
|
;;
|
|
esac
|
|
|
|
if [ "$hits" -gt 0 ]; then
|
|
shown="${#hit_lines[@]}"
|
|
echo "verify-package-contents.sh: FAIL ($hits forbidden path(s), showing $shown)" >&2
|
|
for line in "${hit_lines[@]}"; do
|
|
echo " - $line" >&2
|
|
done
|
|
exit 1
|
|
fi
|
|
|
|
echo "verify-package-contents.sh: OK ($MODE)"
|