magisk-zapret2/build.sh
loop-uh 386a5f757f fix
2026-07-24 21:04:33 +03:00

268 lines
10 KiB
Shell

#!/bin/bash
##########################################################################################
# Zapret2 Root Module - Local Build Script
##########################################################################################
set -e
REQUESTED_VERSION="${1:-}"
OUTPUT_DIR="${2:-.artifacts/module}"
PRERELEASE_ID="${3:-}"
VERSION_METADATA=$(sh tools/release-version.sh)
VERSION=$(printf '%s\n' "$VERSION_METADATA" | sed -n 's/^version=//p')
VERSION_CODE=$(printf '%s\n' "$VERSION_METADATA" | sed -n 's/^version_code=//p')
if [ -z "$VERSION" ] || [ -z "$VERSION_CODE" ]; then
echo "ERROR: canonical release metadata is incomplete"
exit 1
fi
if [ -n "$REQUESTED_VERSION" ] &&
[ "${REQUESTED_VERSION#v}" != "$VERSION" ]; then
echo "ERROR: requested version '$REQUESTED_VERSION' does not match version.properties ($VERSION)"
exit 1
fi
echo "Building Zapret2 Root Module v$VERSION"
echo "=========================================="
VERSION_LABEL="v$VERSION"
if [ -n "$PRERELEASE_ID" ]; then
case "$PRERELEASE_ID" in
dev.*)
case "$PRERELEASE_ID" in
*[!0-9A-Za-z.-]*|*..*|.*|*.) echo "ERROR: invalid dev prerelease identifier"; exit 1 ;;
esac
;;
*) echo "ERROR: only dev prerelease identifiers are supported"; exit 1 ;;
esac
VERSION_LABEL="$VERSION_LABEL-$PRERELEASE_ID"
fi
# Create output directory
mkdir -p "$OUTPUT_DIR"
OUTPUT_DIR_ABS="$(cd "$OUTPUT_DIR" && pwd -P)"
# Check for binaries
MISSING_BIN=0
for arch in arm64-v8a armeabi-v7a; do
if [ ! -s "zapret2/bin/$arch/nfqws2" ] || [ -L "zapret2/bin/$arch/nfqws2" ]; then
echo "ERROR: Missing, empty, or unsafe binary for $arch"
MISSING_BIN=1
fi
done
if [ $MISSING_BIN -eq 1 ]; then
echo "Refusing to create a module ZIP without both supported ABI binaries."
exit 1
fi
# Update version in module.prop
sed -i "s/^version=.*/version=$VERSION_LABEL/" module.prop
sed -i "s/^versionCode=.*/versionCode=$VERSION_CODE/" module.prop
# Make scripts executable
chmod +x customize.sh service.sh uninstall.sh action.sh 2>/dev/null || true
chmod +x zapret2/scripts/*.sh 2>/dev/null || true
chmod +x zapret2/scripts/lifecycle/*.sh 2>/dev/null || true
chmod 0755 system/bin/zapret2-start system/bin/zapret2-stop system/bin/zapret2-status system/bin/zapret2-restart system/bin/zapret2-full-rollback
EXPECTED_OWNER_PROTOCOL_LINE='owner_protocol|7|zapret2-firewall'
ACTUAL_OWNER_PROTOCOL_LINE="$(sed -n '2p' zapret2/runtime-manifest.tsv)"
ACTUAL_OWNER_PROTOCOL_LINE="${ACTUAL_OWNER_PROTOCOL_LINE%"$(printf '\r')"}"
[ "$ACTUAL_OWNER_PROTOCOL_LINE" = "$EXPECTED_OWNER_PROTOCOL_LINE" ] || {
echo "ERROR: incompatible owner protocol in zapret2/runtime-manifest.tsv"
exit 1
}
. ./zapret2/scripts/package-contract.sh
package_contract_apply_modes "$PWD" package || {
echo "ERROR: source tree violates runtime manifest: $PACKAGE_CONTRACT_CODE $PACKAGE_CONTRACT_DETAIL"
exit 1
}
# Runtime machine validators deliberately accept only root-owned installed files.
# Validate content against a private root-owned package copy instead of weakening
# that runtime trust boundary for a developer-owned source checkout.
root_own_validation_tree() {
local tree="$1"
[ -d "$tree" ] && [ ! -L "$tree" ] || return 1
case "$tree" in */zapret2-package-validation.*) ;; *) return 1 ;; esac
if [ "$(id -u)" -eq 0 ]; then
chown -R 0:0 "$tree" && find "$tree" -type d -exec chmod 0755 {} +
elif command -v sudo >/dev/null 2>&1 && sudo -n true 2>/dev/null; then
sudo -n chown -R 0:0 "$tree" &&
sudo -n find "$tree" -type d -exec chmod 0755 {} +
else
echo "ERROR: root access is required to validate the installed package ownership contract" >&2
return 1
fi
}
remove_root_validation_tree() {
local tree="${1:-}"
[ -n "$tree" ] || return 0
case "$tree" in */zapret2-package-validation.*) ;; *) return 1 ;; esac
[ -e "$tree" ] || [ -L "$tree" ] || return 0
[ -d "$tree" ] && [ ! -L "$tree" ] || return 1
if [ "$(id -u)" -eq 0 ]; then
rm -rf "$tree"
else
sudo -n rm -rf "$tree"
fi
}
PACKAGE_SOURCE_VALIDATE_ROOT="$(mktemp -d "${TMPDIR:-/tmp}/zapret2-package-validation.XXXXXX")"
trap 'remove_root_validation_tree "${PACKAGE_SOURCE_VALIDATE_ROOT:-}"' EXIT
trap 'exit 1' HUP INT TERM
package_contract_assemble_package "$PWD" "$PACKAGE_SOURCE_VALIDATE_ROOT" || {
echo "ERROR: cannot assemble source validation tree: $PACKAGE_CONTRACT_CODE $PACKAGE_CONTRACT_DETAIL"
exit 1
}
root_own_validation_tree "$PACKAGE_SOURCE_VALIDATE_ROOT" || exit 1
package_contract_validate_all "$PACKAGE_SOURCE_VALIDATE_ROOT" package || {
echo "ERROR: source catalog violates runtime manifest: $PACKAGE_CONTRACT_CODE $PACKAGE_CONTRACT_DETAIL"
exit 1
}
package_contract_validate_modes "$PACKAGE_SOURCE_VALIDATE_ROOT" package || {
echo "ERROR: source modes violate runtime manifest: $PACKAGE_CONTRACT_CODE $PACKAGE_CONTRACT_DETAIL"
exit 1
}
remove_root_validation_tree "$PACKAGE_SOURCE_VALIDATE_ROOT" || exit 1
PACKAGE_SOURCE_VALIDATE_ROOT=""
trap - EXIT HUP INT TERM
WRAPPER_EXPECTED="$(mktemp)"
WRAPPER_ACTUAL="$(mktemp)"
ZIP_LIST="$(mktemp)"
PACKAGE_ASSEMBLY_ROOT="$(mktemp -d)"
PACKAGE_VALIDATE_ROOT="$(mktemp -d "${TMPDIR:-/tmp}/zapret2-package-validation.XXXXXX")"
cleanup_build_temps() {
rm -f "$WRAPPER_EXPECTED" "$WRAPPER_ACTUAL" "$ZIP_LIST"
[ -z "${PACKAGE_SOURCE_VALIDATE_ROOT:-}" ] || remove_root_validation_tree "$PACKAGE_SOURCE_VALIDATE_ROOT"
[ -z "${PACKAGE_ASSEMBLY_ROOT:-}" ] || rm -rf "$PACKAGE_ASSEMBLY_ROOT"
[ -z "${PACKAGE_VALIDATE_ROOT:-}" ] || remove_root_validation_tree "$PACKAGE_VALIDATE_ROOT"
}
trap cleanup_build_temps EXIT
trap 'exit 1' HUP INT TERM
# Match the immutable command-entry contract enforced by the release workflow.
test -d system/bin
test ! -L system
test ! -L system/bin
test -f zapret2/scripts/zapret-full-rollback.sh
test ! -L zapret2/scripts/zapret-full-rollback.sh
test -x zapret2/scripts/zapret-full-rollback.sh
test "$(sed -n '1p' zapret2/scripts/zapret-full-rollback.sh)" = '#!/system/bin/sh'
if LC_ALL=C grep -q "$(printf '\r')" zapret2/scripts/zapret-full-rollback.sh; then exit 1; fi
for purge_script in zapret2/scripts/lifecycle/purge-contract.sh zapret2/scripts/lifecycle/zapret-purge.sh; do
test -f "$purge_script"
test ! -L "$purge_script"
test -s "$purge_script"
test -x "$purge_script"
test "$(sed -n '1p' "$purge_script")" = '#!/system/bin/sh'
if LC_ALL=C grep -q "$(printf '\r')" "$purge_script"; then exit 1; fi
done
for command_name in start stop status restart full-rollback; do
wrapper="system/bin/zapret2-$command_name"
target="$PACKAGE_CONTRACT_ACTIVE_MODULE_DIR/zapret2/scripts/zapret-$command_name.sh"
test -f "$wrapper"
test ! -L "$wrapper"
test -x "$wrapper"
{
printf '%s\n' '#!/system/bin/sh'
printf 'exec %s "$@"\n' "$target"
} > "$WRAPPER_EXPECTED"
cmp -s "$WRAPPER_EXPECTED" "$wrapper"
done
# Create ZIP
ZIP_NAME="zapret2-magisk-$VERSION_LABEL.zip"
ZIP_PATH="$OUTPUT_DIR_ABS/$ZIP_NAME"
echo "Creating $ZIP_NAME..."
package_contract_assemble_package "$PWD" "$PACKAGE_ASSEMBLY_ROOT" || {
echo "ERROR: cannot assemble exact runtime-manifest package: $PACKAGE_CONTRACT_CODE $PACKAGE_CONTRACT_DETAIL"
exit 1
}
# Never update an existing archive in place: Info-ZIP otherwise retains files
# deleted from the exact manifest-derived assembly tree.
rm -f "$ZIP_PATH"
(
cd "$PACKAGE_ASSEMBLY_ROOT"
zip -r "$ZIP_PATH" module.prop customize.sh service.sh uninstall.sh action.sh system zapret2
)
# Fail locally on the same missing/duplicate/type/mode/content regressions that
# are release blockers in CI.
zipinfo -1 "$ZIP_PATH" | tee "$ZIP_LIST"
package_contract_validate_zip_names "$PWD" "$ZIP_LIST" || {
echo "ERROR: ZIP entries violate runtime manifest: $PACKAGE_CONTRACT_CODE $PACKAGE_CONTRACT_DETAIL"
exit 1
}
unzip -q "$ZIP_PATH" -d "$PACKAGE_VALIDATE_ROOT"
root_own_validation_tree "$PACKAGE_VALIDATE_ROOT" || exit 1
package_contract_validate_all "$PACKAGE_VALIDATE_ROOT" package || {
echo "ERROR: extracted ZIP violates runtime manifest: $PACKAGE_CONTRACT_CODE $PACKAGE_CONTRACT_DETAIL"
exit 1
}
package_contract_validate_exact_tree "$PACKAGE_VALIDATE_ROOT" package || {
echo "ERROR: extracted ZIP contains undeclared entries: $PACKAGE_CONTRACT_CODE $PACKAGE_CONTRACT_DETAIL"
exit 1
}
package_contract_validate_modes "$PACKAGE_VALIDATE_ROOT" package || {
echo "ERROR: ZIP modes violate runtime manifest: $PACKAGE_CONTRACT_CODE $PACKAGE_CONTRACT_DETAIL"
exit 1
}
grep -Fxq 'system/bin/' "$ZIP_LIST"
for command_name in start stop status restart full-rollback; do
entry="system/bin/zapret2-$command_name"
target="$PACKAGE_CONTRACT_ACTIVE_MODULE_DIR/zapret2/scripts/zapret-$command_name.sh"
test "$(grep -Fxc "$entry" "$ZIP_LIST")" -eq 1
zipinfo -l "$ZIP_PATH" "$entry" | grep -Eq '^-rwxr-xr-x[[:space:]]'
{
printf '%s\n' '#!/system/bin/sh'
printf 'exec %s "$@"\n' "$target"
} > "$WRAPPER_EXPECTED"
unzip -p "$ZIP_PATH" "$entry" > "$WRAPPER_ACTUAL"
cmp -s "$WRAPPER_EXPECTED" "$WRAPPER_ACTUAL"
done
ROLLBACK_SCRIPT_ENTRY='zapret2/scripts/zapret-full-rollback.sh'
test "$(grep -Fxc "$ROLLBACK_SCRIPT_ENTRY" "$ZIP_LIST")" -eq 1
zipinfo -l "$ZIP_PATH" "$ROLLBACK_SCRIPT_ENTRY" | grep -Eq '^-rwxr-xr-x[[:space:]]'
unzip -p "$ZIP_PATH" "$ROLLBACK_SCRIPT_ENTRY" > "$WRAPPER_ACTUAL"
cmp -s "$ROLLBACK_SCRIPT_ENTRY" "$WRAPPER_ACTUAL"
if LC_ALL=C grep -q "$(printf '\r')" "$WRAPPER_ACTUAL"; then exit 1; fi
for PURGE_SCRIPT_ENTRY in \
zapret2/scripts/lifecycle/purge-contract.sh \
zapret2/scripts/lifecycle/zapret-purge.sh; do
test "$(grep -Fxc "$PURGE_SCRIPT_ENTRY" "$ZIP_LIST")" -eq 1
zipinfo -l "$ZIP_PATH" "$PURGE_SCRIPT_ENTRY" | grep -Eq '^-rwxr-xr-x[[:space:]]'
unzip -p "$ZIP_PATH" "$PURGE_SCRIPT_ENTRY" > "$WRAPPER_ACTUAL"
test -s "$WRAPPER_ACTUAL"
cmp -s "$PURGE_SCRIPT_ENTRY" "$WRAPPER_ACTUAL"
test "$(sed -n '1p' "$WRAPPER_ACTUAL")" = '#!/system/bin/sh'
if LC_ALL=C grep -q "$(printf '\r')" "$WRAPPER_ACTUAL"; then exit 1; fi
done
test "$(grep -Fxc 'action.sh' "$ZIP_LIST")" -eq 1
zipinfo -l "$ZIP_PATH" action.sh | grep -Eq '^-rwxr-xr-x[[:space:]]'
if grep -Eq '^META-INF(/|$)' "$ZIP_LIST"; then
echo "ERROR: recovery flashing metadata must not be published" >&2
exit 1
fi
echo ""
echo "Build complete: $ZIP_PATH"
echo ""
# Show file size
ls -lh "$ZIP_PATH"
# Verify ZIP structure
echo ""
echo "ZIP contents:"
unzip -l "$ZIP_PATH" | head -30
cleanup_build_temps
trap - EXIT HUP INT TERM