You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@skywalking.apache.org by wu...@apache.org on 2026/10/01 06:42:19 UTC

(skywalking-horizon-ui) branch main updated: fix(release): pack tarballs without macOS metadata, stop rewriting the image docs, protect 1.0.x (#184)

This is an automated email from the ASF dual-hosted git repository.

wu-sheng pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/skywalking-horizon-ui.git


The following commit(s) were added to refs/heads/main by this push:
     new 4fd8fc66 fix(release): pack tarballs without macOS metadata, stop rewriting the image docs, protect 1.0.x (#184)
4fd8fc66 is described below

commit 4fd8fc6603e74f10420060ddb427e196bc922d00
Author: 吴晟 Wu Sheng <wu...@foxmail.com>
AuthorDate: Thu Oct 1 14:42:13 2026 +0800

    fix(release): pack tarballs without macOS metadata, stop rewriting the image docs, protect 1.0.x (#184)
    
    * fix(release): pack both tarballs without macOS metadata
    
    On macOS, tar writes each file's extended attributes as a `._` AppleDouble
    entry beside it, plus pax xattr headers. The 1.0.0 binary tarball carried
    one for every file; on Linux, node-gyp-build loaded `._argon2.glibc.node`
    and the server stopped at boot with "invalid ELF header"
    (apache/skywalking#14119).
    
    Pack with COPYFILE_DISABLE=1 and --no-xattrs, and fail Step 11 when either
    tarball still carries a `._` entry. The check lists with `tar:!mac-ext`,
    since macOS tar hides those entries from a plain `tar -t`.
    
    A patch release (X.Y.Z, Z > 0) now proposes the next patch, not the next
    minor, as the following version.
    
    (cherry picked from commit a94f93314122714efb90a4d63525631a5de00442)
    
    * docs(image): name the image tag as <version>, and stop the release rewriting it
    
    How to run the image rarely changes between releases, yet every release
    rewrote docs/setup/container-image.md from the prior tag to the new one,
    and Step 4 refused to start unless the page named the newest tag. Use the
    <version> placeholder the rest of docs/ already uses, and drop both the
    check and the rewrite. The release commit now touches version markers only.
    
    * ci(asf): protect the 1.0.x patch line like main
    
    Pull requests into 1.0.x need the same rolled-up Required and E2E Required
    checks and one approving review. Both workflows on 1.0.x carry those jobs,
    and its e2e images are pinned by commit, so the gate is reproducible there.
---
 .asf.yaml                     | 11 ++++++
 CLAUDE.md                     |  4 +-
 docs/setup/container-image.md | 14 ++++---
 scripts/release.sh            | 90 ++++++++++++++++++++++++-------------------
 4 files changed, 70 insertions(+), 49 deletions(-)

diff --git a/.asf.yaml b/.asf.yaml
index d12c89b3..a3bc4342 100644
--- a/.asf.yaml
+++ b/.asf.yaml
@@ -55,3 +55,14 @@ github:
         # Drop stale approvals when a PR gets new commits, so a reviewer's
         # LGTM doesn't carry over to code they haven't seen.
         dismiss_stale_reviews: true
+    # Patch line for 1.0.x releases, gated like main. Branch names are exact —
+    # .asf.yaml takes no patterns, so each release line is listed on its own.
+    1.0.x:
+      required_status_checks:
+        strict: true
+        contexts:
+          - Required
+          - E2E Required
+      required_pull_request_reviews:
+        required_approving_review_count: 1
+        dismiss_stale_reviews: true
diff --git a/CLAUDE.md b/CLAUDE.md
index d725dc3d..cc4c8120 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -194,9 +194,7 @@ There is **no raw-JSON editor on these pages.** Never tell the reader to "edit t
 
 ### 3. Don't hardcode the Horizon version
 
-The website serves these docs **pinned per release**, so a version string in the body is redundant and silently goes stale on the next release. By default, write version-neutral: use a `<version>` placeholder in install commands, or phrase around it. State a concrete version only when the content is genuinely version-specific (a compatibility note, or a "new in X" call-out you deliberately want pinned).
-
-The **one maintained exception** is `docs/setup/container-image.md`: its image tags track the current release and are advanced automatically by the release tooling — leave them as concrete versions.
+The website serves these docs **pinned per release**, so a version string in the body is redundant and silently goes stale on the next release. By default, write version-neutral: use a `<version>` placeholder in install commands, or phrase around it. State a concrete version only when the content is genuinely version-specific (a compatibility note, or a "new in X" call-out you deliberately want pinned). Image tags are no exception — `ghcr.io/apache/skywalking-horizon-ui:<version>` — and  [...]
 
 ### 4. Other house rules
 
diff --git a/docs/setup/container-image.md b/docs/setup/container-image.md
index 2c76edf7..aa43a436 100644
--- a/docs/setup/container-image.md
+++ b/docs/setup/container-image.md
@@ -14,8 +14,10 @@ Registry: **GitHub Container Registry (GHCR)** at `ghcr.io/apache/skywalking-hor
 | `latest` | The newest released version. Moves. | Demos / dev only — do not pin production to `latest`. |
 | `main` | Head of `main`. Moves on every merge. | Smoke-test the development branch. |
 
+In the examples on this page, replace `<version>` with the release you run.
+
 ```sh
-docker pull ghcr.io/apache/skywalking-horizon-ui:1.0.0
+docker pull ghcr.io/apache/skywalking-horizon-ui:<version>
 docker pull ghcr.io/apache/skywalking-horizon-ui:<sha>
 ```
 
@@ -129,7 +131,7 @@ docker run -d --name horizon \
   -p 8081:8081 \
   -e NODE_OPTIONS=--max-old-space-size=1536 \
   -v "$PWD/horizon.yaml:/app/horizon.yaml:ro" \
-  ghcr.io/apache/skywalking-horizon-ui:1.0.0
+  ghcr.io/apache/skywalking-horizon-ui:<version>
 ```
 
 ## How to load `horizon.yaml` into the container
@@ -145,7 +147,7 @@ docker run -d \
   --name horizon \
   -p 8081:8081 \
   -v "$PWD/horizon.yaml:/app/horizon.yaml:ro" \
-  ghcr.io/apache/skywalking-horizon-ui:1.0.0
+  ghcr.io/apache/skywalking-horizon-ui:<version>
 ```
 
 Notes:
@@ -158,7 +160,7 @@ Notes:
 For immutable single-tenant deployments, build a child image that includes your config:
 
 ```dockerfile
-FROM ghcr.io/apache/skywalking-horizon-ui:1.0.0
+FROM ghcr.io/apache/skywalking-horizon-ui:<version>
 COPY horizon.yaml /app/horizon.yaml
 ```
 
@@ -226,7 +228,7 @@ spec:
         fsGroup: 101
       containers:
         - name: horizon
-          image: ghcr.io/apache/skywalking-horizon-ui:1.0.0
+          image: ghcr.io/apache/skywalking-horizon-ui:<version>
           ports:
             - containerPort: 8081
           envFrom:
@@ -272,7 +274,7 @@ docker run -d --name horizon \
   -p 8081:8081 \
   -v "$PWD/horizon.yaml:/app/horizon.yaml:ro" \
   -v horizon-state:/data \
-  ghcr.io/apache/skywalking-horizon-ui:1.0.0
+  ghcr.io/apache/skywalking-horizon-ui:<version>
 ```
 
 Without a mounted volume the writes still land in the container's writable layer at `/data/` (ephemeral, but at least non-failing). Mounting a volume is what makes them durable.
diff --git a/scripts/release.sh b/scripts/release.sh
index b74d1729..e019bbcc 100755
--- a/scripts/release.sh
+++ b/scripts/release.sh
@@ -63,6 +63,35 @@ file_has() {
     grep -F -q -- "$2" "$1"
 }
 
+# pack_tar <parent-dir> <tarball> <entry>
+# macOS tar stores each file's extended attributes (com.apple.provenance is on
+# anything git or cp wrote) as a `._` AppleDouble entry beside the file, plus
+# pax xattr headers. GNU tar unpacks those entries as real files, and on Linux
+# node-gyp-build then loads `._argon2.glibc.node` as argon2's native module, so
+# the binary tarball does not boot. COPYFILE_DISABLE drops the `._` entries,
+# --no-xattrs the headers; GNU tar accepts both.
+pack_tar() {
+    COPYFILE_DISABLE=1 tar --no-xattrs -C "$1" -czf "$2" "$3"
+}
+
+# Fails when a tarball carries a `._` entry. macOS tar folds those entries into
+# the file they describe when it LISTS an archive, so a plain `tar -t` on the
+# release machine shows none; `tar:!mac-ext` lists them as stored.
+assert_no_mac_metadata() {
+    local listing
+    if tar --version 2>/dev/null | grep -q bsdtar; then
+        listing=$(tar --options 'tar:!mac-ext' -tzf "$1")
+    else
+        listing=$(tar -tzf "$1")
+    fi
+    # A here-string, not a pipe: `grep -q` exits on the first match, and under
+    # pipefail the writer's SIGPIPE would turn that match into a failed test.
+    if grep -qE '(^|/)\._' <<< "${listing}"; then
+        err "$(basename "$1") carries macOS metadata (._ entries) — it would not boot on Linux."
+        exit 1
+    fi
+}
+
 # ========================== Step 1: GPG signer ==========================
 note "Step 1 — GPG signer check"
 
@@ -150,12 +179,13 @@ if [ "${CURRENT_VERSION}" = "${RELEASE_VERSION}" ]; then
     err "main should carry the dev-suffixed version between releases — bump it before running this script."
     exit 1
 fi
-# Proposed next version: the next minor. Computed in a pipeline on purpose —
-# nothing derived from the version may survive as a variable across the prompt
-# below, where the operator can replace RELEASE_VERSION. Anything split out up
-# here (MAJOR / MINOR / …) would still describe the rejected version and be
-# read as current further down.
-NEXT_RELEASE_VERSION=$(echo "$RELEASE_VERSION" | awk -F. '{ printf "%d.%d.0", $1, $2 + 1 }')
+# Proposed next version: the next minor — or, for a patch release (X.Y.Z with
+# Z > 0, cut from its own X.Y.x branch), the next patch on that line. Computed
+# in a pipeline on purpose — nothing derived from the version may survive as a
+# variable across the prompt below, where the operator can replace
+# RELEASE_VERSION. Anything split out up here (MAJOR / MINOR / …) would still
+# describe the rejected version and be read as current further down.
+NEXT_RELEASE_VERSION=$(echo "$RELEASE_VERSION" | awk -F. '{ if ($3 > 0) printf "%d.%d.%d", $1, $2, $3 + 1; else printf "%d.%d.0", $1, $2 + 1 }')
 
 echo "Current (in package.json): ${CURRENT_VERSION}"
 echo "Release:                   ${RELEASE_VERSION}"
@@ -211,25 +241,11 @@ for pj in package.json packages/api-client/package.json packages/design-tokens/p
 done
 check_file_has_version "apps/bff/src/server.ts" "'${CURRENT_VERSION}'"
 
-# Docs normally reference the last released image tag on main, then the
-# release commit advances them to the new tag. If the docs were already
-# prepared for the release version, accept that too.
-PRIOR_RELEASE=$(cd "${PROJECT_DIR}" && git tag --list 'v*' --sort=-version:refname | head -1 | sed 's/^v//')
-if [ -z "${PRIOR_RELEASE}" ]; then
-    err "No prior release tag (vX.Y.Z) found. Tag the first release manually before using this script."
-    exit 1
-fi
-if ! file_has "${PROJECT_DIR}/docs/setup/container-image.md" "ghcr.io/apache/skywalking-horizon-ui:${PRIOR_RELEASE}" &&
-   ! file_has "${PROJECT_DIR}/docs/setup/container-image.md" "ghcr.io/apache/skywalking-horizon-ui:${RELEASE_VERSION}"; then
-    err "docs/setup/container-image.md must reference either prior image tag ${PRIOR_RELEASE} or release tag ${RELEASE_VERSION}."
-    CONSISTENT=false
-fi
-
 if ! $CONSISTENT; then
     err "Version drift across files. Fix before continuing."
     exit 1
 fi
-echo "Code markers all at ${CURRENT_VERSION}; container docs are release-check compatible."
+echo "Code markers all at ${CURRENT_VERSION}."
 
 # ========================== Step 5: Release-file check ==========================
 note "Step 5 — Release-file check"
@@ -264,7 +280,7 @@ if [ "${CLONE_VERSION}" != "${CURRENT_VERSION}" ]; then
     exit 1
 fi
 
-# ========================== Step 8: Strip -dev, advance docs, commit, tag ==========================
+# ========================== Step 8: Strip -dev, commit, tag ==========================
 note "Step 8 — Prepare release commit + tag ${TAG}"
 
 cd "${CLONE_DIR}"
@@ -303,12 +319,7 @@ for (const f of files) {
 sed -i.bak "s/'${CURRENT_VERSION}'/'${RELEASE_VERSION}'/g" apps/bff/src/server.ts
 rm apps/bff/src/server.ts.bak
 
-# Advance docs from the prior release tag to the new one so the image
-# tag references in the release tarball match the release being cut.
-sed -i.bak "s|ghcr.io/apache/skywalking-horizon-ui:${PRIOR_RELEASE}|ghcr.io/apache/skywalking-horizon-ui:${RELEASE_VERSION}|g" docs/setup/container-image.md
-rm docs/setup/container-image.md.bak
-
-git add package.json packages/*/package.json apps/*/package.json apps/bff/src/server.ts docs/setup/container-image.md
+git add package.json packages/*/package.json apps/*/package.json apps/bff/src/server.ts
 git commit -m "Prepare release ${RELEASE_VERSION}"
 
 if git ls-remote --tags origin | grep -q "refs/tags/${TAG}$"; then
@@ -368,7 +379,7 @@ SRC_STAGE_NAME="${PRODUCT_NAME}-${RELEASE_VERSION}-src"
 # files at the release tag — no .git, no node_modules, no dist, no editor
 # leftovers (git archive emits only tracked files, which excludes all of
 # those by .gitignore). We stage via `git archive` (portable — git does the
-# archiving) then repackage with plain `tar`, so this works the same on
+# archiving) then repackage with `pack_tar`, so this works the same on
 # macOS (bsdtar) and Linux (GNU tar). GNU-only `--transform` is avoided;
 # the tarball's top-level dir comes from `--prefix`.
 rm -rf "${WORK_DIR}/${SRC_STAGE_NAME}"
@@ -382,7 +393,7 @@ rm -f "${WORK_DIR}/${SRC_STAGE_NAME}/.github/workflows/publish-image.yaml"
 # artifact only; it is never committed to git.
 cp "${WORK_DIR}/${SRC_STAGE_NAME}/docs/changelog/${RELEASE_VERSION}.md" \
    "${WORK_DIR}/${SRC_STAGE_NAME}/CHANGELOG.md"
-tar -C "${WORK_DIR}" -czf "${SRC_TAR}" "${SRC_STAGE_NAME}"
+pack_tar "${WORK_DIR}" "${SRC_TAR}" "${SRC_STAGE_NAME}"
 
 echo "Source tarball: ${SRC_TAR}"
 
@@ -417,12 +428,16 @@ cp "${CLONE_DIR}/README.md"    "${BIN_STAGE}/README.md"
 # Do NOT overwrite.
 
 BIN_TAR="${WORK_DIR}/${PRODUCT_NAME}-${RELEASE_VERSION}-bin.tar.gz"
-tar -C "${WORK_DIR}" -czf "${BIN_TAR}" "${PRODUCT_NAME}-${RELEASE_VERSION}-bin"
+pack_tar "${WORK_DIR}" "${BIN_TAR}" "${PRODUCT_NAME}-${RELEASE_VERSION}-bin"
 
 echo "Binary tarball: ${BIN_TAR}"
 
-# ========================== Step 11: Compare LICENSE/NOTICE in tarballs ==========================
-note "Step 11 — Verify LICENSE/NOTICE in src + bin tarballs"
+# ========================== Step 11: Verify src + bin tarballs ==========================
+note "Step 11 — Verify src + bin tarballs (macOS metadata, LICENSE/NOTICE)"
+
+assert_no_mac_metadata "${SRC_TAR}"
+assert_no_mac_metadata "${BIN_TAR}"
+echo "No macOS metadata in either tarball."
 
 # Both tarballs must carry a LICENSE and NOTICE at their root, and the
 # binary version must be the expanded one (contains the "Subcomponents"
@@ -632,10 +647,6 @@ for (const f of files) {
 sed -i.bak "s/'${RELEASE_VERSION}'/'${NEXT_DEV_VERSION}'/g" apps/bff/src/server.ts
 rm apps/bff/src/server.ts.bak
 
-# Container-image docs already point at ${RELEASE_VERSION} (the release
-# commit just bumped them). They stay there — docs always reference the
-# last released tag, not the in-flight dev version.
-
 # Open the next cycle: an empty docs/changelog/${NEXT_RELEASE_VERSION}.md plus
 # its docs/menu.yml entry. The released version's file is not touched.
 node "${CLONE_DIR}/scripts/changelog-version.mjs" seed "${NEXT_RELEASE_VERSION}" --repo-root "${CLONE_DIR}"
@@ -649,9 +660,8 @@ gh pr create --title "Release ${RELEASE_VERSION}, bump to ${NEXT_DEV_VERSION}" \
 Release branch for ${RELEASE_VERSION}. Two commits:
 
 1. \`Prepare release ${RELEASE_VERSION}\` — strips \`-dev\` from every package
-   marker + \`apps/bff/src/server.ts\`; advances container-image docs to
-   \`${RELEASE_VERSION}\`. Tagged \`${TAG}\` (the release-candidate commit the
-   vote runs against).
+   marker + \`apps/bff/src/server.ts\`. Tagged \`${TAG}\` (the
+   release-candidate commit the vote runs against).
 2. \`Prepare next release ${NEXT_DEV_VERSION}\` — bumps every marker to
    \`${NEXT_DEV_VERSION}\` and seeds \`docs/changelog/${NEXT_RELEASE_VERSION}.md\`
    (with its \`docs/menu.yml\` entry) for the next cycle.