Files
argus-wp-defence/bin/build-release.sh
T
root df0f2fccb8 ARGUS WordPress Defence 1.0.0 — first production release
Automatic WordPress security: local firewall, malware and file-integrity
scanning, vulnerability protection, quarantine, scheduled backups, an
optional page cache, and automatic global threat intelligence.

See README.md for installation, update, and uninstall instructions.
2026-08-09 13:40:16 +00:00

98 lines
3.6 KiB
Bash
Executable File

#!/usr/bin/env bash
#
# Production release packaging -- docs/design/production-distribution-and-updates.md
# sections 1-3, 16, 18. Builds the public download ZIP from a clean git
# checkout (git archive -- never the working tree, so uncommitted local
# files can never leak into a release), strips comments/whitespace
# (not obfuscation -- see strip-comments.php's own header), and audits
# the result against an explicit allowlist before packaging, failing
# closed on anything unexpected rather than silently shipping it.
#
# Usage: bin/build-release.sh [git-ref] (defaults to HEAD)
#
set -euo pipefail
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
REF="${1:-HEAD}"
BUILD_DIR="$(mktemp -d)"
STAGE_DIR="${BUILD_DIR}/argus-wordpress-defence"
OUT_DIR="${REPO_ROOT}/dist"
cleanup() { rm -rf "${BUILD_DIR}"; }
trap cleanup EXIT
echo "==> Packaging ${REF} from a clean checkout (git archive, not the working tree)"
mkdir -p "${STAGE_DIR}"
git -C "${REPO_ROOT}" archive "${REF}" | tar -x -C "${STAGE_DIR}"
echo "==> Removing dev-only paths not meant for the production artifact"
rm -rf \
"${STAGE_DIR}/docs" \
"${STAGE_DIR}/tests" \
"${STAGE_DIR}/README.md" \
"${STAGE_DIR}/ARGUS_WORDPRESS_SECURITY_ARCHITECTURE.md" \
"${STAGE_DIR}/.gitignore" \
"${STAGE_DIR}/bin"
echo "==> Allowlist audit -- fail closed on anything unexpected"
UNEXPECTED=0
while IFS= read -r -d '' item; do
rel="${item#"${STAGE_DIR}"/}"
case "$rel" in
argus-wordpress-defence.php|uninstall.php|readme.txt) ;;
admin|admin/*|includes|includes/*|mu-loader|mu-loader/*|assets|assets/*|languages|languages/*) ;;
*)
if [ -f "$item" ]; then
echo " UNEXPECTED FILE: ${rel}"
UNEXPECTED=1
fi
;;
esac
done < <(find "${STAGE_DIR}" -print0)
# Reject known-forbidden patterns explicitly, even inside allowlisted dirs.
if find "${STAGE_DIR}" \( -name "*.key" -o -name "*.pem" -o -name ".env*" -o -name "*.map" \) | grep -q .; then
echo " FORBIDDEN FILE TYPE found (key/pem/.env/source map)"
UNEXPECTED=1
fi
if [ "${UNEXPECTED}" -ne 0 ]; then
echo "==> BUILD FAILED: allowlist audit found unexpected content. Nothing was packaged."
exit 1
fi
echo " clean -- only allowlisted paths present"
# Read the version BEFORE stripping -- it lives inside the plugin
# header's own DocBlock comment, which php_strip_whitespace() below
# would otherwise remove before this ever got a chance to read it.
VERSION="$(grep -oP '(?<=Version:)\s*\K\S+' "${STAGE_DIR}/argus-wordpress-defence.php" | head -1)"
if [ -z "${VERSION}" ]; then
echo "==> BUILD FAILED: could not read plugin version from argus-wordpress-defence.php"
exit 1
fi
echo "==> Packaging version ${VERSION}"
echo "==> Stripping comments/whitespace (not obfuscation -- see bin/strip-comments.php)"
php "${REPO_ROOT}/bin/strip-comments.php" "${STAGE_DIR}"
mkdir -p "${OUT_DIR}"
ZIP_PATH="${OUT_DIR}/argus-wordpress-defence-${VERSION}.zip"
rm -f "${ZIP_PATH}"
# Prefer the `zip` CLI when present (most CI runners have it); fall
# back to PHP's ZipArchive (bin/zip-directory.php) for a host that
# only has the PHP extension -- either is a real, complete archive, no
# feature difference between the two paths.
if command -v zip >/dev/null 2>&1; then
( cd "${BUILD_DIR}" && zip -rq "${ZIP_PATH}" "argus-wordpress-defence" )
else
php "${REPO_ROOT}/bin/zip-directory.php" "${STAGE_DIR}" "${ZIP_PATH}" "argus-wordpress-defence"
fi
SHA256="$(sha256sum "${ZIP_PATH}" | cut -d' ' -f1)"
echo "${SHA256} $(basename "${ZIP_PATH}")" > "${ZIP_PATH}.sha256"
echo "==> Done"
echo " ${ZIP_PATH}"
echo " SHA-256: ${SHA256}"
echo " Size: $(du -h "${ZIP_PATH}" | cut -f1)"