create-release-zip.sh 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  1. #!/usr/bin/env bash
  2. #
  3. # Builds a release package of this shop for the Manage server.
  4. #
  5. # Run it from the project root:
  6. #
  7. # ./scripts/create-release-zip.sh v1.1.0
  8. #
  9. # It writes the version into includes/version.php, packs every git-tracked file
  10. # minus the exclusions below, and prints the SHA-256 and size. Upload the
  11. # resulting ZIP in the Manage server under "Releases"; the shop then offers it
  12. # on Admin -> Einstellungen.
  13. #
  14. # The root of the ZIP is the shop root - no wrapping directory, otherwise the
  15. # update would create a subfolder instead of updating anything.
  16. #
  17. # See docs/BACKUP_UPDATE.md.
  18. set -euo pipefail
  19. # --- CONFIGURATION ----------------------------------------------------------
  20. # Package name prefix. Must match MANAGE_PACKAGE_PREFIX on the Manage server.
  21. PRODUCT="feuerwehr-shop"
  22. # File holding the installed version, relative to the project root.
  23. VERSION_FILE="includes/version.php"
  24. # Name of the constant inside that file. Empty means a plain text file that
  25. # contains nothing but the version.
  26. VERSION_CONSTANT="APP_VERSION"
  27. # Output directory for built packages, relative to the project root.
  28. BUILD_DIR="build/releases"
  29. # Paths excluded from the package. Anything holding credentials or runtime data
  30. # of the target installation MUST be listed here.
  31. # config.php holds this installation's admin hashes, cookie secret and Manage
  32. # token; data/ holds the operational data of whoever built the package. Both are
  33. # additionally covered by MANAGE_UPDATE_PROTECTED_PATHS, but they must not end
  34. # up in a package that gets downloaded from the Manage server in the first place.
  35. EXCLUDES=(
  36. ".gitignore"
  37. "config.php"
  38. "data/"
  39. "build/"
  40. "scripts/"
  41. ".codex/"
  42. )
  43. # --- END CONFIGURATION ------------------------------------------------------
  44. usage() {
  45. cat <<USAGE
  46. Usage: $(basename "$0") vX.Y.Z
  47. Builds ${BUILD_DIR}/${PRODUCT}-vX.Y.Z.zip from the git-tracked files of the
  48. current repository and writes the version into ${VERSION_FILE}.
  49. USAGE
  50. }
  51. VERSION="${1:-}"
  52. if [[ -z "$VERSION" || "$VERSION" == "-h" || "$VERSION" == "--help" ]]; then
  53. usage
  54. exit 1
  55. fi
  56. if [[ ! "$VERSION" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
  57. echo "Error: version must look like v1.3.0" >&2
  58. exit 1
  59. fi
  60. for tool in git zip sed awk; do
  61. if ! command -v "$tool" >/dev/null 2>&1; then
  62. echo "Error: required tool not found: $tool" >&2
  63. exit 1
  64. fi
  65. done
  66. if ! git rev-parse --show-toplevel >/dev/null 2>&1; then
  67. echo "Error: not inside a git repository. Run this from the project root." >&2
  68. exit 1
  69. fi
  70. REPO_ROOT="$(git rev-parse --show-toplevel)"
  71. cd "$REPO_ROOT"
  72. if [[ ! -f "$VERSION_FILE" ]]; then
  73. echo "Error: version file not found: $VERSION_FILE" >&2
  74. exit 1
  75. fi
  76. # The file list comes from `git ls-files`, so anything uncommitted silently
  77. # stays out of the package. Warn rather than refuse: building from a dirty tree
  78. # is sometimes deliberate.
  79. if [[ -n "$(git status --porcelain --untracked-files=no)" ]]; then
  80. echo "Warning: the working tree has uncommitted changes." >&2
  81. echo " Only committed content is packaged." >&2
  82. fi
  83. # Untracked files are the nastier case: a new file added by the release but
  84. # never `git add`ed is missing from the package with nothing else to show for it.
  85. UNTRACKED="$(git ls-files --others --exclude-standard)"
  86. if [[ -n "$UNTRACKED" ]]; then
  87. echo "Warning: untracked files will NOT be in the package:" >&2
  88. printf ' %s\n' $UNTRACKED >&2
  89. fi
  90. # --- write the version ------------------------------------------------------
  91. write_version() {
  92. if [[ -n "$VERSION_CONSTANT" ]]; then
  93. # PHP file with a define(). Replace only the version literal.
  94. sed -i.bak -E \
  95. "s/(define\\(\\s*[\"']${VERSION_CONSTANT}[\"']\\s*,\\s*[\"'])[^\"']*([\"'])/\\1${VERSION}\\2/" \
  96. "$VERSION_FILE"
  97. rm -f "${VERSION_FILE}.bak"
  98. else
  99. printf '%s\n' "$VERSION" > "$VERSION_FILE"
  100. fi
  101. }
  102. read_version() {
  103. if [[ -n "$VERSION_CONSTANT" ]]; then
  104. grep -oE "define\\(\\s*[\"']${VERSION_CONSTANT}[\"']\\s*,\\s*[\"']v[0-9]+\\.[0-9]+\\.[0-9]+[\"']" \
  105. "$VERSION_FILE" | grep -oE 'v[0-9]+\.[0-9]+\.[0-9]+' | head -1
  106. else
  107. tr -d '[:space:]' < "$VERSION_FILE"
  108. fi
  109. }
  110. write_version
  111. WRITTEN="$(read_version)"
  112. if [[ "$WRITTEN" != "$VERSION" ]]; then
  113. echo "Error: could not write the version into $VERSION_FILE (found: '${WRITTEN}')." >&2
  114. echo " Check VERSION_CONSTANT and the file's format." >&2
  115. exit 1
  116. fi
  117. echo "Version written to ${VERSION_FILE}: ${VERSION}"
  118. # --- collect the files ------------------------------------------------------
  119. is_excluded() {
  120. local path="$1"
  121. local pattern
  122. for pattern in "${EXCLUDES[@]}"; do
  123. if [[ "$pattern" == */ ]]; then
  124. [[ "$path" == "${pattern}"* ]] && return 0
  125. else
  126. [[ "$path" == "$pattern" ]] && return 0
  127. fi
  128. done
  129. return 1
  130. }
  131. FILE_LIST="$(mktemp)"
  132. trap 'rm -f "$FILE_LIST"' EXIT
  133. COUNT=0
  134. while IFS= read -r path; do
  135. if is_excluded "$path"; then
  136. continue
  137. fi
  138. [[ -f "$path" ]] || continue
  139. printf '%s\n' "$path" >> "$FILE_LIST"
  140. COUNT=$((COUNT + 1))
  141. done < <(git ls-files)
  142. if [[ "$COUNT" -eq 0 ]]; then
  143. echo "Error: no files to package." >&2
  144. exit 1
  145. fi
  146. # --- build ------------------------------------------------------------------
  147. mkdir -p "$BUILD_DIR"
  148. ARCHIVE="${BUILD_DIR}/${PRODUCT}-${VERSION}.zip"
  149. rm -f "$ARCHIVE"
  150. zip -q -X "$ARCHIVE" -@ < "$FILE_LIST"
  151. if command -v sha256sum >/dev/null 2>&1; then
  152. SHA="$(sha256sum "$ARCHIVE" | awk '{print $1}')"
  153. else
  154. SHA="$(shasum -a 256 "$ARCHIVE" | awk '{print $1}')"
  155. fi
  156. SIZE="$(wc -c < "$ARCHIVE" | tr -d '[:space:]')"
  157. cat <<SUMMARY
  158. Package: ${ARCHIVE}
  159. Files: ${COUNT}
  160. Size: ${SIZE} bytes
  161. SHA-256: ${SHA}
  162. Next: upload it in the Manage server under "Releases" with version ${VERSION}.
  163. Checksum and size are recomputed there; the values above are for checking.
  164. SUMMARY