SHA256
318 lines
8.4 KiB
Bash
Executable File
318 lines
8.4 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -Eeuo pipefail
|
|
|
|
# Build an offline-ready source bundle ZIP.
|
|
# In addition to the normal source tree, this variant can attach a local
|
|
# Gradle distribution zip and a helper script that rewrites wrapper URLs to
|
|
# that local file so the bundle can be used without internet access.
|
|
#
|
|
# Usage:
|
|
# ./bundle-offline.sh
|
|
# ./bundle-offline.sh path/to/output.zip
|
|
#
|
|
# Expected local asset:
|
|
# offline/gradle-offline.zip
|
|
# or a custom path via BUNDLE_OFFLINE_GRADLE_ZIP
|
|
|
|
ROOT="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
|
cd "$ROOT"
|
|
|
|
OUT="${1:-SHiNE-bundle-offline-$(date +%Y%m%d-%H%M%S).zip}"
|
|
case "$OUT" in
|
|
/*) ;;
|
|
*) OUT="$ROOT/$OUT" ;;
|
|
esac
|
|
|
|
if ! command -v zip >/dev/null 2>&1; then
|
|
echo "ERROR: 'zip' is required." >&2
|
|
exit 1
|
|
fi
|
|
|
|
TMP="$(mktemp -d)"
|
|
LIST="$TMP/files.txt"
|
|
SAFE_LIST="$TMP/safe-files.txt"
|
|
STAGE="$TMP/stage"
|
|
trap 'rm -rf "$TMP"' EXIT
|
|
|
|
mkdir -p "$STAGE"
|
|
|
|
# Paths / filenames that must never be bundled.
|
|
is_denied_path() {
|
|
local p="/$1"
|
|
|
|
case "$p" in
|
|
*/.git/*|*/.git|\
|
|
*/.gradle/*|*/.gradle|\
|
|
*/.gradle-home/*|*/.gradle-home|\
|
|
*/.idea/*|*/.idea|\
|
|
*/.vscode/*|*/.vscode|\
|
|
*/node_modules/*|*/node_modules|\
|
|
*/target/*|*/target|\
|
|
*/build/*|*/build|\
|
|
*/out/*|*/out|\
|
|
*/bin/*|*/bin|\
|
|
*/logs/*|*/logs|\
|
|
*/data/*|*/data|\
|
|
*/test-ledger/*|*/test-ledger|\
|
|
*/.anchor/*|*/.anchor|\
|
|
*/.yarn/*|*/.yarn|\
|
|
*/.vendor/*|*/.vendor|\
|
|
*/.agents/*|*/.agents|\
|
|
*/.codex/*|*/.codex|\
|
|
*/.claude/*|*/.claude|\
|
|
*/deploy/backup/archive/*|\
|
|
*/scripts/*/runs/*|\
|
|
*/scripts/*/keypairs/*|\
|
|
*/keys/*|\
|
|
*/.git-local-backup/*|\
|
|
*/SHiNE-bundle-*.zip|\
|
|
*/bundle-offline*.zip)
|
|
return 0
|
|
;;
|
|
esac
|
|
|
|
local base="${p##*/}"
|
|
local lower
|
|
lower="$(printf '%s' "$base" | tr '[:upper:]' '[:lower:]')"
|
|
|
|
case "$lower" in
|
|
.env|.env.*|\
|
|
.debug-token|\
|
|
.npmrc|.pypirc|.netrc|\
|
|
credentials|credentials.*|\
|
|
secrets|secrets.*|\
|
|
secret|secret.*|\
|
|
id_rsa|id_dsa|id_ecdsa|id_ed25519|\
|
|
*.pem|*.key|*.p12|*.pfx|*.jks|*.keystore|\
|
|
*keypair*.json|\
|
|
service-account*.json|\
|
|
firebase-adminsdk*.json|\
|
|
google-services.json|\
|
|
validator.log)
|
|
return 0
|
|
;;
|
|
esac
|
|
|
|
case "$lower" in
|
|
*.class|*.jar|*.war|*.ear|*.o|*.a|*.so|*.dll|*.dylib|\
|
|
*.elf|*.map|*.uf2|*.bin|*.merged.bin|\
|
|
*.log|*.bak|*.bak.png|*.tmp|*.swp|*.swo|\
|
|
.ds_store)
|
|
return 0
|
|
;;
|
|
esac
|
|
|
|
return 1
|
|
}
|
|
|
|
find_offline_gradle_zip() {
|
|
local candidate="${BUNDLE_OFFLINE_GRADLE_ZIP:-}"
|
|
if [[ -n "$candidate" && -f "$candidate" ]]; then
|
|
printf '%s\n' "$candidate"
|
|
return 0
|
|
fi
|
|
|
|
for candidate in \
|
|
"$ROOT/offline/gradle-offline.zip" \
|
|
"$ROOT/offline/gradle-8.14-bin.zip" \
|
|
"$ROOT/offline/gradle.zip"
|
|
do
|
|
if [[ -f "$candidate" ]]; then
|
|
printf '%s\n' "$candidate"
|
|
return 0
|
|
fi
|
|
done
|
|
|
|
local extracted_dir=""
|
|
if [[ -d "$HOME/.gradle/wrapper/dists/gradle-8.14-bin" ]]; then
|
|
extracted_dir="$(find "$HOME/.gradle/wrapper/dists/gradle-8.14-bin" -type d -name 'gradle-8.14' | sort | head -n 1 || true)"
|
|
fi
|
|
if [[ -n "$extracted_dir" && -d "$extracted_dir" ]]; then
|
|
local generated_zip="$TMP/gradle-offline.zip"
|
|
(
|
|
cd "$(dirname -- "$extracted_dir")"
|
|
rm -f -- "$generated_zip"
|
|
zip -qr "$generated_zip" "gradle-8.14"
|
|
)
|
|
if [[ -f "$generated_zip" ]]; then
|
|
printf '%s\n' "$generated_zip"
|
|
return 0
|
|
fi
|
|
fi
|
|
|
|
return 1
|
|
}
|
|
|
|
create_offline_helper() {
|
|
local zip_name="$1"
|
|
local helper="$STAGE/offline/prepare-local-gradle.sh"
|
|
local readme="$STAGE/offline/README.txt"
|
|
|
|
mkdir -p "$STAGE/offline"
|
|
|
|
cat > "$helper" <<EOF
|
|
#!/usr/bin/env bash
|
|
set -Eeuo pipefail
|
|
|
|
ROOT="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd -P)"
|
|
ZIP_PATH="\${1:-\$ROOT/offline/$zip_name}"
|
|
|
|
if [[ ! -f "\$ZIP_PATH" ]]; then
|
|
echo "ERROR: offline Gradle zip not found: \$ZIP_PATH" >&2
|
|
exit 1
|
|
fi
|
|
|
|
ABS_ZIP="\$(cd -- "\$(dirname -- "\$ZIP_PATH")" && pwd -P)/\$(basename -- "\$ZIP_PATH")"
|
|
ESCAPED_ABS_ZIP="\${ABS_ZIP//\\\\/\\\\\\\\}"
|
|
ESCAPED_ABS_ZIP="\${ESCAPED_ABS_ZIP//&/\\\\&}"
|
|
ESCAPED_ABS_ZIP="\${ESCAPED_ABS_ZIP//|/\\\\|}"
|
|
|
|
while IFS= read -r props; do
|
|
[[ -f "\$props" ]] || continue
|
|
cp -p "\$props" "\$props.bak"
|
|
sed -i -e "s|^distributionUrl=.*\$|distributionUrl=file://\$ESCAPED_ABS_ZIP|" "\$props"
|
|
done < <(find "\$ROOT" -path '*/gradle/wrapper/gradle-wrapper.properties' -type f | sort)
|
|
|
|
cat <<'MSG'
|
|
Gradle wrapper URLs rewritten to the local offline zip.
|
|
Run now:
|
|
./gradlew --offline test
|
|
MSG
|
|
EOF
|
|
chmod +x "$helper"
|
|
|
|
cat > "$readme" <<EOF
|
|
Offline Gradle helper
|
|
|
|
Included archive:
|
|
offline/$zip_name
|
|
|
|
Helper:
|
|
offline/prepare-local-gradle.sh
|
|
|
|
What it does:
|
|
- backs up each gradle-wrapper.properties as .bak
|
|
- rewrites wrapper distributionUrl to the local zip in this bundle
|
|
|
|
Recommended flow after unpacking:
|
|
1. cd into the unpacked bundle root
|
|
2. run ./offline/prepare-local-gradle.sh
|
|
3. run ./gradlew --offline test
|
|
|
|
This bundle is intended for local, network-free verification.
|
|
EOF
|
|
}
|
|
|
|
# Collect files. Prefer Git because it naturally avoids most ignored local files.
|
|
if command -v git >/dev/null 2>&1 && git -C "$ROOT" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
|
git -C "$ROOT" ls-files -co --exclude-standard -z > "$TMP/files.z"
|
|
else
|
|
find "$ROOT" -type f -print0 > "$TMP/files.z"
|
|
fi
|
|
|
|
# Convert to project-relative paths and enforce hard deny rules.
|
|
: > "$LIST"
|
|
while IFS= read -r -d '' f; do
|
|
if [[ "$f" = /* ]]; then
|
|
rel="${f#"$ROOT"/}"
|
|
else
|
|
rel="$f"
|
|
fi
|
|
|
|
[[ "$rel" == "$OUT" ]] && continue
|
|
[[ -z "$rel" ]] && continue
|
|
|
|
if is_denied_path "$rel"; then
|
|
continue
|
|
fi
|
|
|
|
printf '%s\n' "$rel" >> "$LIST"
|
|
done < "$TMP/files.z"
|
|
|
|
sort -u "$LIST" -o "$LIST"
|
|
|
|
# Always include Gradle wrapper bootstrap, even though generic JARs are denied.
|
|
for wrapper_jar in \
|
|
'SHiNE-server/gradle/wrapper/gradle-wrapper.jar' \
|
|
'SHiNE-browser-plugin-wallet/gradle/wrapper/gradle-wrapper.jar'
|
|
do
|
|
if [[ -f "$ROOT/$wrapper_jar" ]] && ! grep -Fxq "$wrapper_jar" "$LIST"; then
|
|
printf '%s\n' "$wrapper_jar" >> "$LIST"
|
|
fi
|
|
done
|
|
|
|
sort -u "$LIST" -o "$LIST"
|
|
|
|
# Content scan: fail closed on common credential/private-key patterns.
|
|
# We scan only text-ish files; grep -I skips binary data.
|
|
SECRET_RE='-----BEGIN ([A-Z0-9 ]+ )?PRIVATE KEY-----|AKIA[0-9A-Z]{16}|ASIA[0-9A-Z]{16}|gh[pousr]_[A-Za-z0-9_]{20,}|github_pat_[A-Za-z0-9_]{20,}|sk-[A-Za-z0-9_-]{20,}|xox[baprs]-[A-Za-z0-9-]{10,}|AIza[0-9A-Za-z_-]{30,}|(^|[^A-Za-z0-9])(password|passwd|pwd|secret|api[_-]?key|access[_-]?token|auth[_-]?token|private[_-]?key)[[:space:]]*[:=][[:space:]]*["'\'']?[^${[:space:]]{][^[:space:]]{7,}'
|
|
|
|
: > "$SAFE_LIST"
|
|
found_secret=0
|
|
|
|
while IFS= read -r rel; do
|
|
[[ -f "$ROOT/$rel" ]] || continue
|
|
|
|
# Files that contain examples/templates can legitimately mention secret keys
|
|
# with placeholders. They are scanned too, but placeholder-looking values
|
|
# are less likely to match the regex above.
|
|
if LC_ALL=C grep -IEnq "$SECRET_RE" "$ROOT/$rel" 2>/dev/null; then
|
|
echo "BLOCKED: possible secret in $rel" >&2
|
|
LC_ALL=C grep -IEn "$SECRET_RE" "$ROOT/$rel" 2>/dev/null \
|
|
| sed -E 's/(:[[:space:]]*).*/\1[REDACTED]/' \
|
|
| head -n 3 >&2 || true
|
|
found_secret=1
|
|
continue
|
|
fi
|
|
|
|
printf '%s\n' "$rel" >> "$SAFE_LIST"
|
|
done < "$LIST"
|
|
|
|
if (( found_secret != 0 )); then
|
|
echo >&2
|
|
echo "Bundle NOT created because possible secrets were detected." >&2
|
|
echo "Move secrets to ignored/local files or adjust the scanner only after review." >&2
|
|
exit 2
|
|
fi
|
|
|
|
if [[ ! -s "$SAFE_LIST" ]]; then
|
|
echo "ERROR: no files left to bundle." >&2
|
|
exit 3
|
|
fi
|
|
|
|
OFFLINE_ZIP_SRC=""
|
|
OFFLINE_ZIP_NAME=""
|
|
if OFFLINE_ZIP_SRC="$(find_offline_gradle_zip)"; then
|
|
OFFLINE_ZIP_NAME="gradle-offline.zip"
|
|
else
|
|
echo "ERROR: offline Gradle zip not found." >&2
|
|
echo "Place it at ./offline/gradle-offline.zip, set BUNDLE_OFFLINE_GRADLE_ZIP, or install Gradle 8.14 locally." >&2
|
|
echo "The bundle is not created because this variant is meant to be offline-ready." >&2
|
|
exit 4
|
|
fi
|
|
|
|
rm -rf "$STAGE"
|
|
mkdir -p "$STAGE"
|
|
|
|
while IFS= read -r rel; do
|
|
src="$ROOT/$rel"
|
|
dst="$STAGE/$rel"
|
|
mkdir -p "$(dirname -- "$dst")"
|
|
cp -p "$src" "$dst"
|
|
done < "$SAFE_LIST"
|
|
|
|
mkdir -p "$STAGE/offline"
|
|
cp -p "$OFFLINE_ZIP_SRC" "$STAGE/offline/$OFFLINE_ZIP_NAME"
|
|
create_offline_helper "$OFFLINE_ZIP_NAME"
|
|
|
|
rm -f -- "$OUT"
|
|
|
|
(
|
|
cd "$STAGE"
|
|
find . -type f -print | sort | zip -q -9 "$OUT" -@
|
|
)
|
|
|
|
echo "Created: $OUT"
|
|
echo "Files: $(cd "$STAGE" && find . -type f | wc -l | tr -d ' ')"
|
|
echo "Size: $(du -h "$OUT" | awk '{print $1}')"
|