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.