From 87e61484f09624f1aacaf8ea47a63fcae2f16f6b Mon Sep 17 00:00:00 2001 From: lczyk Date: Tue, 28 Jul 2026 10:30:03 +0100 Subject: [PATCH 1/8] fix?: macos appledouble sidecars in packed project macOS tar stamps an AppleDouble ._* sidecar next to every member (com.apple.provenance is set on effectively all files), and they unpack as real files on the remote. The exclude list cannot suppress them -- tar synthesises them in the copyfile path, below its own glob filter -- so the only lever inside a spread.yaml is repack, which extracts and re-tars with COPYFILE_DISABLE=1. Non-Darwin hosts get an identity pass-through, but declaring repack at all switches spread's local pack from tar cz to tar c plus its own gzip writer, so the linux path changes too. --- demo/spread.yaml | 13 +++++++++++++ inlined/bread-22.04.yaml | 13 +++++++++++++ inlined/bread-24.04.yaml | 13 +++++++++++++ inlined/bread-25.10.yaml | 13 +++++++++++++ inlined/bread-26.04.yaml | 13 +++++++++++++ inlined/bread-26.10.yaml | 13 +++++++++++++ inlined/bread-chisel-releases-22.04.yaml | 13 +++++++++++++ inlined/bread-chisel-releases-24.04.yaml | 13 +++++++++++++ inlined/bread-chisel-releases-25.10.yaml | 13 +++++++++++++ inlined/bread-chisel-releases-26.04.yaml | 13 +++++++++++++ inlined/bread-chisel-releases-26.10.yaml | 13 +++++++++++++ templates/bread-22.04.yaml.in | 13 +++++++++++++ templates/bread-24.04.yaml.in | 13 +++++++++++++ templates/bread-25.10.yaml.in | 13 +++++++++++++ templates/bread-26.04.yaml.in | 13 +++++++++++++ templates/bread-26.10.yaml.in | 13 +++++++++++++ templates/bread-chisel-releases-22.04.yaml.in | 13 +++++++++++++ templates/bread-chisel-releases-24.04.yaml.in | 13 +++++++++++++ templates/bread-chisel-releases-25.10.yaml.in | 13 +++++++++++++ templates/bread-chisel-releases-26.04.yaml.in | 13 +++++++++++++ templates/bread-chisel-releases-26.10.yaml.in | 13 +++++++++++++ tests/spread.yaml | 13 +++++++++++++ 22 files changed, 286 insertions(+) diff --git a/demo/spread.yaml b/demo/spread.yaml index e610401..afd11c1 100644 --- a/demo/spread.yaml +++ b/demo/spread.yaml @@ -11,6 +11,19 @@ exclude: - .git - .github +# macOS tar packs an AppleDouble `._*` sidecar next to every file; they unpack as +# real files on the remote. `exclude:` can't drop them (tar synthesises them below +# its glob filter), so extract and repack with COPYFILE_DISABLE=1. +repack: | + if [ "$(uname -s)" = Darwin ]; then + tmp=$(mktemp -d) + trap 'rm -rf "$tmp"' EXIT + tar -xf - -C "$tmp" <&3 + ( cd "$tmp" && shopt -s dotglob && COPYFILE_DISABLE=1 tar -cf - -- * ) >&4 + else + cat <&3 >&4 + fi + backends: docker: type: adhoc diff --git a/inlined/bread-22.04.yaml b/inlined/bread-22.04.yaml index bbb2308..b323657 100644 --- a/inlined/bread-22.04.yaml +++ b/inlined/bread-22.04.yaml @@ -11,6 +11,19 @@ exclude: - .git - .github +# macOS tar packs an AppleDouble `._*` sidecar next to every file; they unpack as +# real files on the remote. `exclude:` can't drop them (tar synthesises them below +# its glob filter), so extract and repack with COPYFILE_DISABLE=1. +repack: | + if [ "$(uname -s)" = Darwin ]; then + tmp=$(mktemp -d) + trap 'rm -rf "$tmp"' EXIT + tar -xf - -C "$tmp" <&3 + ( cd "$tmp" && shopt -s dotglob && COPYFILE_DISABLE=1 tar -cf - -- * ) >&4 + else + cat <&3 >&4 + fi + backends: docker: type: adhoc diff --git a/inlined/bread-24.04.yaml b/inlined/bread-24.04.yaml index 81a2c69..f5d14dd 100644 --- a/inlined/bread-24.04.yaml +++ b/inlined/bread-24.04.yaml @@ -11,6 +11,19 @@ exclude: - .git - .github +# macOS tar packs an AppleDouble `._*` sidecar next to every file; they unpack as +# real files on the remote. `exclude:` can't drop them (tar synthesises them below +# its glob filter), so extract and repack with COPYFILE_DISABLE=1. +repack: | + if [ "$(uname -s)" = Darwin ]; then + tmp=$(mktemp -d) + trap 'rm -rf "$tmp"' EXIT + tar -xf - -C "$tmp" <&3 + ( cd "$tmp" && shopt -s dotglob && COPYFILE_DISABLE=1 tar -cf - -- * ) >&4 + else + cat <&3 >&4 + fi + backends: docker: type: adhoc diff --git a/inlined/bread-25.10.yaml b/inlined/bread-25.10.yaml index fdf60ec..279b276 100644 --- a/inlined/bread-25.10.yaml +++ b/inlined/bread-25.10.yaml @@ -11,6 +11,19 @@ exclude: - .git - .github +# macOS tar packs an AppleDouble `._*` sidecar next to every file; they unpack as +# real files on the remote. `exclude:` can't drop them (tar synthesises them below +# its glob filter), so extract and repack with COPYFILE_DISABLE=1. +repack: | + if [ "$(uname -s)" = Darwin ]; then + tmp=$(mktemp -d) + trap 'rm -rf "$tmp"' EXIT + tar -xf - -C "$tmp" <&3 + ( cd "$tmp" && shopt -s dotglob && COPYFILE_DISABLE=1 tar -cf - -- * ) >&4 + else + cat <&3 >&4 + fi + backends: docker: type: adhoc diff --git a/inlined/bread-26.04.yaml b/inlined/bread-26.04.yaml index 4a71f83..673d3f8 100644 --- a/inlined/bread-26.04.yaml +++ b/inlined/bread-26.04.yaml @@ -11,6 +11,19 @@ exclude: - .git - .github +# macOS tar packs an AppleDouble `._*` sidecar next to every file; they unpack as +# real files on the remote. `exclude:` can't drop them (tar synthesises them below +# its glob filter), so extract and repack with COPYFILE_DISABLE=1. +repack: | + if [ "$(uname -s)" = Darwin ]; then + tmp=$(mktemp -d) + trap 'rm -rf "$tmp"' EXIT + tar -xf - -C "$tmp" <&3 + ( cd "$tmp" && shopt -s dotglob && COPYFILE_DISABLE=1 tar -cf - -- * ) >&4 + else + cat <&3 >&4 + fi + backends: docker: type: adhoc diff --git a/inlined/bread-26.10.yaml b/inlined/bread-26.10.yaml index 5d74d30..ca36204 100644 --- a/inlined/bread-26.10.yaml +++ b/inlined/bread-26.10.yaml @@ -11,6 +11,19 @@ exclude: - .git - .github +# macOS tar packs an AppleDouble `._*` sidecar next to every file; they unpack as +# real files on the remote. `exclude:` can't drop them (tar synthesises them below +# its glob filter), so extract and repack with COPYFILE_DISABLE=1. +repack: | + if [ "$(uname -s)" = Darwin ]; then + tmp=$(mktemp -d) + trap 'rm -rf "$tmp"' EXIT + tar -xf - -C "$tmp" <&3 + ( cd "$tmp" && shopt -s dotglob && COPYFILE_DISABLE=1 tar -cf - -- * ) >&4 + else + cat <&3 >&4 + fi + backends: docker: type: adhoc diff --git a/inlined/bread-chisel-releases-22.04.yaml b/inlined/bread-chisel-releases-22.04.yaml index 1f9610f..1574cb7 100644 --- a/inlined/bread-chisel-releases-22.04.yaml +++ b/inlined/bread-chisel-releases-22.04.yaml @@ -18,6 +18,19 @@ exclude: - work - rootfs +# macOS tar packs an AppleDouble `._*` sidecar next to every file; they unpack as +# real files on the remote. `exclude:` can't drop them (tar synthesises them below +# its glob filter), so extract and repack with COPYFILE_DISABLE=1. +repack: | + if [ "$(uname -s)" = Darwin ]; then + tmp=$(mktemp -d) + trap 'rm -rf "$tmp"' EXIT + tar -xf - -C "$tmp" <&3 + ( cd "$tmp" && shopt -s dotglob && COPYFILE_DISABLE=1 tar -cf - -- * ) >&4 + else + cat <&3 >&4 + fi + backends: docker: type: adhoc diff --git a/inlined/bread-chisel-releases-24.04.yaml b/inlined/bread-chisel-releases-24.04.yaml index 84f217f..538ad36 100644 --- a/inlined/bread-chisel-releases-24.04.yaml +++ b/inlined/bread-chisel-releases-24.04.yaml @@ -18,6 +18,19 @@ exclude: - work - rootfs +# macOS tar packs an AppleDouble `._*` sidecar next to every file; they unpack as +# real files on the remote. `exclude:` can't drop them (tar synthesises them below +# its glob filter), so extract and repack with COPYFILE_DISABLE=1. +repack: | + if [ "$(uname -s)" = Darwin ]; then + tmp=$(mktemp -d) + trap 'rm -rf "$tmp"' EXIT + tar -xf - -C "$tmp" <&3 + ( cd "$tmp" && shopt -s dotglob && COPYFILE_DISABLE=1 tar -cf - -- * ) >&4 + else + cat <&3 >&4 + fi + backends: docker: type: adhoc diff --git a/inlined/bread-chisel-releases-25.10.yaml b/inlined/bread-chisel-releases-25.10.yaml index 3c6916e..ce7857e 100644 --- a/inlined/bread-chisel-releases-25.10.yaml +++ b/inlined/bread-chisel-releases-25.10.yaml @@ -18,6 +18,19 @@ exclude: - work - rootfs +# macOS tar packs an AppleDouble `._*` sidecar next to every file; they unpack as +# real files on the remote. `exclude:` can't drop them (tar synthesises them below +# its glob filter), so extract and repack with COPYFILE_DISABLE=1. +repack: | + if [ "$(uname -s)" = Darwin ]; then + tmp=$(mktemp -d) + trap 'rm -rf "$tmp"' EXIT + tar -xf - -C "$tmp" <&3 + ( cd "$tmp" && shopt -s dotglob && COPYFILE_DISABLE=1 tar -cf - -- * ) >&4 + else + cat <&3 >&4 + fi + backends: docker: type: adhoc diff --git a/inlined/bread-chisel-releases-26.04.yaml b/inlined/bread-chisel-releases-26.04.yaml index 01227d7..3ff54e0 100644 --- a/inlined/bread-chisel-releases-26.04.yaml +++ b/inlined/bread-chisel-releases-26.04.yaml @@ -18,6 +18,19 @@ exclude: - work - rootfs +# macOS tar packs an AppleDouble `._*` sidecar next to every file; they unpack as +# real files on the remote. `exclude:` can't drop them (tar synthesises them below +# its glob filter), so extract and repack with COPYFILE_DISABLE=1. +repack: | + if [ "$(uname -s)" = Darwin ]; then + tmp=$(mktemp -d) + trap 'rm -rf "$tmp"' EXIT + tar -xf - -C "$tmp" <&3 + ( cd "$tmp" && shopt -s dotglob && COPYFILE_DISABLE=1 tar -cf - -- * ) >&4 + else + cat <&3 >&4 + fi + backends: docker: type: adhoc diff --git a/inlined/bread-chisel-releases-26.10.yaml b/inlined/bread-chisel-releases-26.10.yaml index 3ce2cdb..a7a289d 100644 --- a/inlined/bread-chisel-releases-26.10.yaml +++ b/inlined/bread-chisel-releases-26.10.yaml @@ -18,6 +18,19 @@ exclude: - work - rootfs +# macOS tar packs an AppleDouble `._*` sidecar next to every file; they unpack as +# real files on the remote. `exclude:` can't drop them (tar synthesises them below +# its glob filter), so extract and repack with COPYFILE_DISABLE=1. +repack: | + if [ "$(uname -s)" = Darwin ]; then + tmp=$(mktemp -d) + trap 'rm -rf "$tmp"' EXIT + tar -xf - -C "$tmp" <&3 + ( cd "$tmp" && shopt -s dotglob && COPYFILE_DISABLE=1 tar -cf - -- * ) >&4 + else + cat <&3 >&4 + fi + backends: docker: type: adhoc diff --git a/templates/bread-22.04.yaml.in b/templates/bread-22.04.yaml.in index bf8e8f5..bb23197 100644 --- a/templates/bread-22.04.yaml.in +++ b/templates/bread-22.04.yaml.in @@ -11,6 +11,19 @@ exclude: - .git - .github +# macOS tar packs an AppleDouble `._*` sidecar next to every file; they unpack as +# real files on the remote. `exclude:` can't drop them (tar synthesises them below +# its glob filter), so extract and repack with COPYFILE_DISABLE=1. +repack: | + if [ "$(uname -s)" = Darwin ]; then + tmp=$(mktemp -d) + trap 'rm -rf "$tmp"' EXIT + tar -xf - -C "$tmp" <&3 + ( cd "$tmp" && shopt -s dotglob && COPYFILE_DISABLE=1 tar -cf - -- * ) >&4 + else + cat <&3 >&4 + fi + backends: docker: type: adhoc diff --git a/templates/bread-24.04.yaml.in b/templates/bread-24.04.yaml.in index 9f6a798..766d570 100644 --- a/templates/bread-24.04.yaml.in +++ b/templates/bread-24.04.yaml.in @@ -11,6 +11,19 @@ exclude: - .git - .github +# macOS tar packs an AppleDouble `._*` sidecar next to every file; they unpack as +# real files on the remote. `exclude:` can't drop them (tar synthesises them below +# its glob filter), so extract and repack with COPYFILE_DISABLE=1. +repack: | + if [ "$(uname -s)" = Darwin ]; then + tmp=$(mktemp -d) + trap 'rm -rf "$tmp"' EXIT + tar -xf - -C "$tmp" <&3 + ( cd "$tmp" && shopt -s dotglob && COPYFILE_DISABLE=1 tar -cf - -- * ) >&4 + else + cat <&3 >&4 + fi + backends: docker: type: adhoc diff --git a/templates/bread-25.10.yaml.in b/templates/bread-25.10.yaml.in index 8c5219d..4f33b4e 100644 --- a/templates/bread-25.10.yaml.in +++ b/templates/bread-25.10.yaml.in @@ -11,6 +11,19 @@ exclude: - .git - .github +# macOS tar packs an AppleDouble `._*` sidecar next to every file; they unpack as +# real files on the remote. `exclude:` can't drop them (tar synthesises them below +# its glob filter), so extract and repack with COPYFILE_DISABLE=1. +repack: | + if [ "$(uname -s)" = Darwin ]; then + tmp=$(mktemp -d) + trap 'rm -rf "$tmp"' EXIT + tar -xf - -C "$tmp" <&3 + ( cd "$tmp" && shopt -s dotglob && COPYFILE_DISABLE=1 tar -cf - -- * ) >&4 + else + cat <&3 >&4 + fi + backends: docker: type: adhoc diff --git a/templates/bread-26.04.yaml.in b/templates/bread-26.04.yaml.in index 9aabed8..ac0f155 100644 --- a/templates/bread-26.04.yaml.in +++ b/templates/bread-26.04.yaml.in @@ -11,6 +11,19 @@ exclude: - .git - .github +# macOS tar packs an AppleDouble `._*` sidecar next to every file; they unpack as +# real files on the remote. `exclude:` can't drop them (tar synthesises them below +# its glob filter), so extract and repack with COPYFILE_DISABLE=1. +repack: | + if [ "$(uname -s)" = Darwin ]; then + tmp=$(mktemp -d) + trap 'rm -rf "$tmp"' EXIT + tar -xf - -C "$tmp" <&3 + ( cd "$tmp" && shopt -s dotglob && COPYFILE_DISABLE=1 tar -cf - -- * ) >&4 + else + cat <&3 >&4 + fi + backends: docker: type: adhoc diff --git a/templates/bread-26.10.yaml.in b/templates/bread-26.10.yaml.in index b808a76..92282ca 100644 --- a/templates/bread-26.10.yaml.in +++ b/templates/bread-26.10.yaml.in @@ -11,6 +11,19 @@ exclude: - .git - .github +# macOS tar packs an AppleDouble `._*` sidecar next to every file; they unpack as +# real files on the remote. `exclude:` can't drop them (tar synthesises them below +# its glob filter), so extract and repack with COPYFILE_DISABLE=1. +repack: | + if [ "$(uname -s)" = Darwin ]; then + tmp=$(mktemp -d) + trap 'rm -rf "$tmp"' EXIT + tar -xf - -C "$tmp" <&3 + ( cd "$tmp" && shopt -s dotglob && COPYFILE_DISABLE=1 tar -cf - -- * ) >&4 + else + cat <&3 >&4 + fi + backends: docker: type: adhoc diff --git a/templates/bread-chisel-releases-22.04.yaml.in b/templates/bread-chisel-releases-22.04.yaml.in index 9f2f923..f371c5d 100644 --- a/templates/bread-chisel-releases-22.04.yaml.in +++ b/templates/bread-chisel-releases-22.04.yaml.in @@ -18,6 +18,19 @@ exclude: - work - rootfs +# macOS tar packs an AppleDouble `._*` sidecar next to every file; they unpack as +# real files on the remote. `exclude:` can't drop them (tar synthesises them below +# its glob filter), so extract and repack with COPYFILE_DISABLE=1. +repack: | + if [ "$(uname -s)" = Darwin ]; then + tmp=$(mktemp -d) + trap 'rm -rf "$tmp"' EXIT + tar -xf - -C "$tmp" <&3 + ( cd "$tmp" && shopt -s dotglob && COPYFILE_DISABLE=1 tar -cf - -- * ) >&4 + else + cat <&3 >&4 + fi + backends: docker: type: adhoc diff --git a/templates/bread-chisel-releases-24.04.yaml.in b/templates/bread-chisel-releases-24.04.yaml.in index 791d80b..ea12d74 100644 --- a/templates/bread-chisel-releases-24.04.yaml.in +++ b/templates/bread-chisel-releases-24.04.yaml.in @@ -18,6 +18,19 @@ exclude: - work - rootfs +# macOS tar packs an AppleDouble `._*` sidecar next to every file; they unpack as +# real files on the remote. `exclude:` can't drop them (tar synthesises them below +# its glob filter), so extract and repack with COPYFILE_DISABLE=1. +repack: | + if [ "$(uname -s)" = Darwin ]; then + tmp=$(mktemp -d) + trap 'rm -rf "$tmp"' EXIT + tar -xf - -C "$tmp" <&3 + ( cd "$tmp" && shopt -s dotglob && COPYFILE_DISABLE=1 tar -cf - -- * ) >&4 + else + cat <&3 >&4 + fi + backends: docker: type: adhoc diff --git a/templates/bread-chisel-releases-25.10.yaml.in b/templates/bread-chisel-releases-25.10.yaml.in index a461dda..f8411ca 100644 --- a/templates/bread-chisel-releases-25.10.yaml.in +++ b/templates/bread-chisel-releases-25.10.yaml.in @@ -18,6 +18,19 @@ exclude: - work - rootfs +# macOS tar packs an AppleDouble `._*` sidecar next to every file; they unpack as +# real files on the remote. `exclude:` can't drop them (tar synthesises them below +# its glob filter), so extract and repack with COPYFILE_DISABLE=1. +repack: | + if [ "$(uname -s)" = Darwin ]; then + tmp=$(mktemp -d) + trap 'rm -rf "$tmp"' EXIT + tar -xf - -C "$tmp" <&3 + ( cd "$tmp" && shopt -s dotglob && COPYFILE_DISABLE=1 tar -cf - -- * ) >&4 + else + cat <&3 >&4 + fi + backends: docker: type: adhoc diff --git a/templates/bread-chisel-releases-26.04.yaml.in b/templates/bread-chisel-releases-26.04.yaml.in index 2e8940a..ed45a8e 100644 --- a/templates/bread-chisel-releases-26.04.yaml.in +++ b/templates/bread-chisel-releases-26.04.yaml.in @@ -18,6 +18,19 @@ exclude: - work - rootfs +# macOS tar packs an AppleDouble `._*` sidecar next to every file; they unpack as +# real files on the remote. `exclude:` can't drop them (tar synthesises them below +# its glob filter), so extract and repack with COPYFILE_DISABLE=1. +repack: | + if [ "$(uname -s)" = Darwin ]; then + tmp=$(mktemp -d) + trap 'rm -rf "$tmp"' EXIT + tar -xf - -C "$tmp" <&3 + ( cd "$tmp" && shopt -s dotglob && COPYFILE_DISABLE=1 tar -cf - -- * ) >&4 + else + cat <&3 >&4 + fi + backends: docker: type: adhoc diff --git a/templates/bread-chisel-releases-26.10.yaml.in b/templates/bread-chisel-releases-26.10.yaml.in index b14aca6..001d4a2 100644 --- a/templates/bread-chisel-releases-26.10.yaml.in +++ b/templates/bread-chisel-releases-26.10.yaml.in @@ -18,6 +18,19 @@ exclude: - work - rootfs +# macOS tar packs an AppleDouble `._*` sidecar next to every file; they unpack as +# real files on the remote. `exclude:` can't drop them (tar synthesises them below +# its glob filter), so extract and repack with COPYFILE_DISABLE=1. +repack: | + if [ "$(uname -s)" = Darwin ]; then + tmp=$(mktemp -d) + trap 'rm -rf "$tmp"' EXIT + tar -xf - -C "$tmp" <&3 + ( cd "$tmp" && shopt -s dotglob && COPYFILE_DISABLE=1 tar -cf - -- * ) >&4 + else + cat <&3 >&4 + fi + backends: docker: type: adhoc diff --git a/tests/spread.yaml b/tests/spread.yaml index a798942..718753b 100644 --- a/tests/spread.yaml +++ b/tests/spread.yaml @@ -12,6 +12,19 @@ exclude: - .git - .github +# macOS tar packs an AppleDouble `._*` sidecar next to every file; they unpack as +# real files on the remote. `exclude:` can't drop them (tar synthesises them below +# its glob filter), so extract and repack with COPYFILE_DISABLE=1. +repack: | + if [ "$(uname -s)" = Darwin ]; then + tmp=$(mktemp -d) + trap 'rm -rf "$tmp"' EXIT + tar -xf - -C "$tmp" <&3 + ( cd "$tmp" && shopt -s dotglob && COPYFILE_DISABLE=1 tar -cf - -- * ) >&4 + else + cat <&3 >&4 + fi + backends: outer: type: adhoc From 814f96ab9a6edab21611197becdae9ee5cd764f3 Mon Sep 17 00:00:00 2001 From: lczyk Date: Tue, 28 Jul 2026 10:30:24 +0100 Subject: [PATCH 2/8] docs: macos sidecar workaround is no longer manual Keeps the why-not-exclude note so the repack block does not get simplified away later. --- README.md | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/README.md b/README.md index 4d138d2..55f8281 100644 --- a/README.md +++ b/README.md @@ -55,12 +55,7 @@ BREAD_NET=publish spread # force port-publishing (e.g. to test the macOS path BREAD_NET=bridge spread # force bridge IPs ``` -one more macOS gotcha: spread packs the project with the host `tar`, and macOS `tar` injects `._*` AppleDouble sidecar files (resource-fork metadata) into the archive. they unpack as real files in the container and can break tools that scan for `*.yaml` etc. export `COPYFILE_DISABLE=1` so macOS `tar` skips them: - -``` -export COPYFILE_DISABLE=1 -spread -``` +one more macOS gotcha, already handled inside the yamls: spread packs the project with the host `tar`, and macOS `tar` injects a `._*` AppleDouble sidecar (resource-fork metadata) next to every file. they unpack as real files in the container and can break tools that scan for `*.yaml` etc. each yaml carries a `repack:` hook that extracts and re-tars the archive with `COPYFILE_DISABLE=1` when the host is macOS, so nothing needs setting in your environment. note `exclude:` cannot do this -- tar synthesises the sidecars below its own glob filter, so they survive any `--exclude` pattern. ## install spread From 35c22642cc6c7de5de9fad13b261aadef9d4338e Mon Sep 17 00:00:00 2001 From: lczyk Date: Tue, 28 Jul 2026 10:33:21 +0100 Subject: [PATCH 3/8] docs: remove macos tar sidecar explanation the detail about AppleDouble sidecars and the COPYFILE_DISABLE workaround is implementation-specific to spread's internal yaml hooks. it's not actionable for users and belongs in spread's own docs, not the README. --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index 55f8281..cb5d826 100644 --- a/README.md +++ b/README.md @@ -55,8 +55,6 @@ BREAD_NET=publish spread # force port-publishing (e.g. to test the macOS path BREAD_NET=bridge spread # force bridge IPs ``` -one more macOS gotcha, already handled inside the yamls: spread packs the project with the host `tar`, and macOS `tar` injects a `._*` AppleDouble sidecar (resource-fork metadata) next to every file. they unpack as real files in the container and can break tools that scan for `*.yaml` etc. each yaml carries a `repack:` hook that extracts and re-tars the archive with `COPYFILE_DISABLE=1` when the host is macOS, so nothing needs setting in your environment. note `exclude:` cannot do this -- tar synthesises the sidecars below its own glob filter, so they survive any `--exclude` pattern. - ## install spread prefer a precompiled spread CLI over `go install`? same release ships statically-linked binaries for linux amd64 / arm64 / s390x / ppc64le: From 990c55af4b414a2e486481d3a8f58cb1322a6567 Mon Sep 17 00:00:00 2001 From: lczyk Date: Tue, 28 Jul 2026 10:39:14 +0100 Subject: [PATCH 4/8] refactor: extract repack script for bread templates move the macOS tar repack logic into a standalone script and reference it from templates via `source scripts/spread_repack_bread.sh`. this deduplicates the logic across all bread variants, simplifies template maintenance, and adds better inline documentation for the tar glob workaround. --- hack/inline_scripts.rb | 7 +++---- inlined/bread-22.04.yaml | 10 +++++++--- inlined/bread-24.04.yaml | 10 +++++++--- inlined/bread-25.10.yaml | 10 +++++++--- inlined/bread-26.04.yaml | 10 +++++++--- inlined/bread-26.10.yaml | 10 +++++++--- inlined/bread-chisel-releases-22.04.yaml | 10 +++++++--- inlined/bread-chisel-releases-24.04.yaml | 10 +++++++--- inlined/bread-chisel-releases-25.10.yaml | 10 +++++++--- inlined/bread-chisel-releases-26.04.yaml | 10 +++++++--- inlined/bread-chisel-releases-26.10.yaml | 10 +++++++--- scripts/spread_repack_bread.sh | 15 +++++++++++++++ templates/bread-22.04.yaml.in | 13 +------------ templates/bread-24.04.yaml.in | 13 +------------ templates/bread-25.10.yaml.in | 13 +------------ templates/bread-26.04.yaml.in | 13 +------------ templates/bread-26.10.yaml.in | 13 +------------ templates/bread-chisel-releases-22.04.yaml.in | 13 +------------ templates/bread-chisel-releases-24.04.yaml.in | 13 +------------ templates/bread-chisel-releases-25.10.yaml.in | 13 +------------ templates/bread-chisel-releases-26.04.yaml.in | 13 +------------ templates/bread-chisel-releases-26.10.yaml.in | 13 +------------ 22 files changed, 98 insertions(+), 154 deletions(-) create mode 100644 scripts/spread_repack_bread.sh diff --git a/hack/inline_scripts.rb b/hack/inline_scripts.rb index 7c4f7b8..0762110 100755 --- a/hack/inline_scripts.rb +++ b/hack/inline_scripts.rb @@ -1,13 +1,12 @@ #!/usr/bin/env ruby # Inline `source scripts/.sh` references inside spread yaml templates. # -# For each line matching `allocate: source scripts/.sh` or -# `discard: source scripts/.sh`, substitute the script's content as a -# yaml block scalar under the matching key. +# For each line matching `: source scripts/.sh`, substitute the +# script's content as a yaml block scalar under the matching key. require "fileutils" -LINE_RE = /\A(?\s*)(?allocate|discard):\s*source\s+(?scripts\/[\w.-]+\.sh)\s*\z/ +LINE_RE = /\A(?\s*)(?allocate|discard|repack):\s*source\s+(?scripts\/[\w.-]+\.sh)\s*\z/ def inline_scripts(content, yaml_indent: 2) out = [] diff --git a/inlined/bread-22.04.yaml b/inlined/bread-22.04.yaml index b323657..f8e0924 100644 --- a/inlined/bread-22.04.yaml +++ b/inlined/bread-22.04.yaml @@ -11,14 +11,18 @@ exclude: - .git - .github -# macOS tar packs an AppleDouble `._*` sidecar next to every file; they unpack as -# real files on the remote. `exclude:` can't drop them (tar synthesises them below -# its glob filter), so extract and repack with COPYFILE_DISABLE=1. repack: | + # macOS tar packs an AppleDouble ._* sidecar next to every file; they unpack as + # real files on the remote. `exclude:` can't drop them (tar synthesises them + # below its glob filter), so extract and repack with COPYFILE_DISABLE=1. + # Spread hands the raw pre-gzip tar stream on fd 3 and takes the replacement + # on fd 4. if [ "$(uname -s)" = Darwin ]; then tmp=$(mktemp -d) trap 'rm -rf "$tmp"' EXIT tar -xf - -C "$tmp" <&3 + # `-- *` + dotglob rather than `.`: retarring `.` emits a ./ entry carrying + # mktemp's 0700, which would chmod $SPREAD_PATH on the remote. ( cd "$tmp" && shopt -s dotglob && COPYFILE_DISABLE=1 tar -cf - -- * ) >&4 else cat <&3 >&4 diff --git a/inlined/bread-24.04.yaml b/inlined/bread-24.04.yaml index f5d14dd..3b12915 100644 --- a/inlined/bread-24.04.yaml +++ b/inlined/bread-24.04.yaml @@ -11,14 +11,18 @@ exclude: - .git - .github -# macOS tar packs an AppleDouble `._*` sidecar next to every file; they unpack as -# real files on the remote. `exclude:` can't drop them (tar synthesises them below -# its glob filter), so extract and repack with COPYFILE_DISABLE=1. repack: | + # macOS tar packs an AppleDouble ._* sidecar next to every file; they unpack as + # real files on the remote. `exclude:` can't drop them (tar synthesises them + # below its glob filter), so extract and repack with COPYFILE_DISABLE=1. + # Spread hands the raw pre-gzip tar stream on fd 3 and takes the replacement + # on fd 4. if [ "$(uname -s)" = Darwin ]; then tmp=$(mktemp -d) trap 'rm -rf "$tmp"' EXIT tar -xf - -C "$tmp" <&3 + # `-- *` + dotglob rather than `.`: retarring `.` emits a ./ entry carrying + # mktemp's 0700, which would chmod $SPREAD_PATH on the remote. ( cd "$tmp" && shopt -s dotglob && COPYFILE_DISABLE=1 tar -cf - -- * ) >&4 else cat <&3 >&4 diff --git a/inlined/bread-25.10.yaml b/inlined/bread-25.10.yaml index 279b276..b815b7c 100644 --- a/inlined/bread-25.10.yaml +++ b/inlined/bread-25.10.yaml @@ -11,14 +11,18 @@ exclude: - .git - .github -# macOS tar packs an AppleDouble `._*` sidecar next to every file; they unpack as -# real files on the remote. `exclude:` can't drop them (tar synthesises them below -# its glob filter), so extract and repack with COPYFILE_DISABLE=1. repack: | + # macOS tar packs an AppleDouble ._* sidecar next to every file; they unpack as + # real files on the remote. `exclude:` can't drop them (tar synthesises them + # below its glob filter), so extract and repack with COPYFILE_DISABLE=1. + # Spread hands the raw pre-gzip tar stream on fd 3 and takes the replacement + # on fd 4. if [ "$(uname -s)" = Darwin ]; then tmp=$(mktemp -d) trap 'rm -rf "$tmp"' EXIT tar -xf - -C "$tmp" <&3 + # `-- *` + dotglob rather than `.`: retarring `.` emits a ./ entry carrying + # mktemp's 0700, which would chmod $SPREAD_PATH on the remote. ( cd "$tmp" && shopt -s dotglob && COPYFILE_DISABLE=1 tar -cf - -- * ) >&4 else cat <&3 >&4 diff --git a/inlined/bread-26.04.yaml b/inlined/bread-26.04.yaml index 673d3f8..d774351 100644 --- a/inlined/bread-26.04.yaml +++ b/inlined/bread-26.04.yaml @@ -11,14 +11,18 @@ exclude: - .git - .github -# macOS tar packs an AppleDouble `._*` sidecar next to every file; they unpack as -# real files on the remote. `exclude:` can't drop them (tar synthesises them below -# its glob filter), so extract and repack with COPYFILE_DISABLE=1. repack: | + # macOS tar packs an AppleDouble ._* sidecar next to every file; they unpack as + # real files on the remote. `exclude:` can't drop them (tar synthesises them + # below its glob filter), so extract and repack with COPYFILE_DISABLE=1. + # Spread hands the raw pre-gzip tar stream on fd 3 and takes the replacement + # on fd 4. if [ "$(uname -s)" = Darwin ]; then tmp=$(mktemp -d) trap 'rm -rf "$tmp"' EXIT tar -xf - -C "$tmp" <&3 + # `-- *` + dotglob rather than `.`: retarring `.` emits a ./ entry carrying + # mktemp's 0700, which would chmod $SPREAD_PATH on the remote. ( cd "$tmp" && shopt -s dotglob && COPYFILE_DISABLE=1 tar -cf - -- * ) >&4 else cat <&3 >&4 diff --git a/inlined/bread-26.10.yaml b/inlined/bread-26.10.yaml index ca36204..e564ac7 100644 --- a/inlined/bread-26.10.yaml +++ b/inlined/bread-26.10.yaml @@ -11,14 +11,18 @@ exclude: - .git - .github -# macOS tar packs an AppleDouble `._*` sidecar next to every file; they unpack as -# real files on the remote. `exclude:` can't drop them (tar synthesises them below -# its glob filter), so extract and repack with COPYFILE_DISABLE=1. repack: | + # macOS tar packs an AppleDouble ._* sidecar next to every file; they unpack as + # real files on the remote. `exclude:` can't drop them (tar synthesises them + # below its glob filter), so extract and repack with COPYFILE_DISABLE=1. + # Spread hands the raw pre-gzip tar stream on fd 3 and takes the replacement + # on fd 4. if [ "$(uname -s)" = Darwin ]; then tmp=$(mktemp -d) trap 'rm -rf "$tmp"' EXIT tar -xf - -C "$tmp" <&3 + # `-- *` + dotglob rather than `.`: retarring `.` emits a ./ entry carrying + # mktemp's 0700, which would chmod $SPREAD_PATH on the remote. ( cd "$tmp" && shopt -s dotglob && COPYFILE_DISABLE=1 tar -cf - -- * ) >&4 else cat <&3 >&4 diff --git a/inlined/bread-chisel-releases-22.04.yaml b/inlined/bread-chisel-releases-22.04.yaml index 1574cb7..f5eb98a 100644 --- a/inlined/bread-chisel-releases-22.04.yaml +++ b/inlined/bread-chisel-releases-22.04.yaml @@ -18,14 +18,18 @@ exclude: - work - rootfs -# macOS tar packs an AppleDouble `._*` sidecar next to every file; they unpack as -# real files on the remote. `exclude:` can't drop them (tar synthesises them below -# its glob filter), so extract and repack with COPYFILE_DISABLE=1. repack: | + # macOS tar packs an AppleDouble ._* sidecar next to every file; they unpack as + # real files on the remote. `exclude:` can't drop them (tar synthesises them + # below its glob filter), so extract and repack with COPYFILE_DISABLE=1. + # Spread hands the raw pre-gzip tar stream on fd 3 and takes the replacement + # on fd 4. if [ "$(uname -s)" = Darwin ]; then tmp=$(mktemp -d) trap 'rm -rf "$tmp"' EXIT tar -xf - -C "$tmp" <&3 + # `-- *` + dotglob rather than `.`: retarring `.` emits a ./ entry carrying + # mktemp's 0700, which would chmod $SPREAD_PATH on the remote. ( cd "$tmp" && shopt -s dotglob && COPYFILE_DISABLE=1 tar -cf - -- * ) >&4 else cat <&3 >&4 diff --git a/inlined/bread-chisel-releases-24.04.yaml b/inlined/bread-chisel-releases-24.04.yaml index 538ad36..3fc274f 100644 --- a/inlined/bread-chisel-releases-24.04.yaml +++ b/inlined/bread-chisel-releases-24.04.yaml @@ -18,14 +18,18 @@ exclude: - work - rootfs -# macOS tar packs an AppleDouble `._*` sidecar next to every file; they unpack as -# real files on the remote. `exclude:` can't drop them (tar synthesises them below -# its glob filter), so extract and repack with COPYFILE_DISABLE=1. repack: | + # macOS tar packs an AppleDouble ._* sidecar next to every file; they unpack as + # real files on the remote. `exclude:` can't drop them (tar synthesises them + # below its glob filter), so extract and repack with COPYFILE_DISABLE=1. + # Spread hands the raw pre-gzip tar stream on fd 3 and takes the replacement + # on fd 4. if [ "$(uname -s)" = Darwin ]; then tmp=$(mktemp -d) trap 'rm -rf "$tmp"' EXIT tar -xf - -C "$tmp" <&3 + # `-- *` + dotglob rather than `.`: retarring `.` emits a ./ entry carrying + # mktemp's 0700, which would chmod $SPREAD_PATH on the remote. ( cd "$tmp" && shopt -s dotglob && COPYFILE_DISABLE=1 tar -cf - -- * ) >&4 else cat <&3 >&4 diff --git a/inlined/bread-chisel-releases-25.10.yaml b/inlined/bread-chisel-releases-25.10.yaml index ce7857e..dbc895a 100644 --- a/inlined/bread-chisel-releases-25.10.yaml +++ b/inlined/bread-chisel-releases-25.10.yaml @@ -18,14 +18,18 @@ exclude: - work - rootfs -# macOS tar packs an AppleDouble `._*` sidecar next to every file; they unpack as -# real files on the remote. `exclude:` can't drop them (tar synthesises them below -# its glob filter), so extract and repack with COPYFILE_DISABLE=1. repack: | + # macOS tar packs an AppleDouble ._* sidecar next to every file; they unpack as + # real files on the remote. `exclude:` can't drop them (tar synthesises them + # below its glob filter), so extract and repack with COPYFILE_DISABLE=1. + # Spread hands the raw pre-gzip tar stream on fd 3 and takes the replacement + # on fd 4. if [ "$(uname -s)" = Darwin ]; then tmp=$(mktemp -d) trap 'rm -rf "$tmp"' EXIT tar -xf - -C "$tmp" <&3 + # `-- *` + dotglob rather than `.`: retarring `.` emits a ./ entry carrying + # mktemp's 0700, which would chmod $SPREAD_PATH on the remote. ( cd "$tmp" && shopt -s dotglob && COPYFILE_DISABLE=1 tar -cf - -- * ) >&4 else cat <&3 >&4 diff --git a/inlined/bread-chisel-releases-26.04.yaml b/inlined/bread-chisel-releases-26.04.yaml index 3ff54e0..811360d 100644 --- a/inlined/bread-chisel-releases-26.04.yaml +++ b/inlined/bread-chisel-releases-26.04.yaml @@ -18,14 +18,18 @@ exclude: - work - rootfs -# macOS tar packs an AppleDouble `._*` sidecar next to every file; they unpack as -# real files on the remote. `exclude:` can't drop them (tar synthesises them below -# its glob filter), so extract and repack with COPYFILE_DISABLE=1. repack: | + # macOS tar packs an AppleDouble ._* sidecar next to every file; they unpack as + # real files on the remote. `exclude:` can't drop them (tar synthesises them + # below its glob filter), so extract and repack with COPYFILE_DISABLE=1. + # Spread hands the raw pre-gzip tar stream on fd 3 and takes the replacement + # on fd 4. if [ "$(uname -s)" = Darwin ]; then tmp=$(mktemp -d) trap 'rm -rf "$tmp"' EXIT tar -xf - -C "$tmp" <&3 + # `-- *` + dotglob rather than `.`: retarring `.` emits a ./ entry carrying + # mktemp's 0700, which would chmod $SPREAD_PATH on the remote. ( cd "$tmp" && shopt -s dotglob && COPYFILE_DISABLE=1 tar -cf - -- * ) >&4 else cat <&3 >&4 diff --git a/inlined/bread-chisel-releases-26.10.yaml b/inlined/bread-chisel-releases-26.10.yaml index a7a289d..751b671 100644 --- a/inlined/bread-chisel-releases-26.10.yaml +++ b/inlined/bread-chisel-releases-26.10.yaml @@ -18,14 +18,18 @@ exclude: - work - rootfs -# macOS tar packs an AppleDouble `._*` sidecar next to every file; they unpack as -# real files on the remote. `exclude:` can't drop them (tar synthesises them below -# its glob filter), so extract and repack with COPYFILE_DISABLE=1. repack: | + # macOS tar packs an AppleDouble ._* sidecar next to every file; they unpack as + # real files on the remote. `exclude:` can't drop them (tar synthesises them + # below its glob filter), so extract and repack with COPYFILE_DISABLE=1. + # Spread hands the raw pre-gzip tar stream on fd 3 and takes the replacement + # on fd 4. if [ "$(uname -s)" = Darwin ]; then tmp=$(mktemp -d) trap 'rm -rf "$tmp"' EXIT tar -xf - -C "$tmp" <&3 + # `-- *` + dotglob rather than `.`: retarring `.` emits a ./ entry carrying + # mktemp's 0700, which would chmod $SPREAD_PATH on the remote. ( cd "$tmp" && shopt -s dotglob && COPYFILE_DISABLE=1 tar -cf - -- * ) >&4 else cat <&3 >&4 diff --git a/scripts/spread_repack_bread.sh b/scripts/spread_repack_bread.sh new file mode 100644 index 0000000..2029a35 --- /dev/null +++ b/scripts/spread_repack_bread.sh @@ -0,0 +1,15 @@ +# macOS tar packs an AppleDouble ._* sidecar next to every file; they unpack as +# real files on the remote. `exclude:` can't drop them (tar synthesises them +# below its glob filter), so extract and repack with COPYFILE_DISABLE=1. +# Spread hands the raw pre-gzip tar stream on fd 3 and takes the replacement +# on fd 4. +if [ "$(uname -s)" = Darwin ]; then + tmp=$(mktemp -d) + trap 'rm -rf "$tmp"' EXIT + tar -xf - -C "$tmp" <&3 + # `-- *` + dotglob rather than `.`: retarring `.` emits a ./ entry carrying + # mktemp's 0700, which would chmod $SPREAD_PATH on the remote. + ( cd "$tmp" && shopt -s dotglob && COPYFILE_DISABLE=1 tar -cf - -- * ) >&4 +else + cat <&3 >&4 +fi diff --git a/templates/bread-22.04.yaml.in b/templates/bread-22.04.yaml.in index bb23197..d73e867 100644 --- a/templates/bread-22.04.yaml.in +++ b/templates/bread-22.04.yaml.in @@ -11,18 +11,7 @@ exclude: - .git - .github -# macOS tar packs an AppleDouble `._*` sidecar next to every file; they unpack as -# real files on the remote. `exclude:` can't drop them (tar synthesises them below -# its glob filter), so extract and repack with COPYFILE_DISABLE=1. -repack: | - if [ "$(uname -s)" = Darwin ]; then - tmp=$(mktemp -d) - trap 'rm -rf "$tmp"' EXIT - tar -xf - -C "$tmp" <&3 - ( cd "$tmp" && shopt -s dotglob && COPYFILE_DISABLE=1 tar -cf - -- * ) >&4 - else - cat <&3 >&4 - fi +repack: source scripts/spread_repack_bread.sh backends: docker: diff --git a/templates/bread-24.04.yaml.in b/templates/bread-24.04.yaml.in index 766d570..bda04df 100644 --- a/templates/bread-24.04.yaml.in +++ b/templates/bread-24.04.yaml.in @@ -11,18 +11,7 @@ exclude: - .git - .github -# macOS tar packs an AppleDouble `._*` sidecar next to every file; they unpack as -# real files on the remote. `exclude:` can't drop them (tar synthesises them below -# its glob filter), so extract and repack with COPYFILE_DISABLE=1. -repack: | - if [ "$(uname -s)" = Darwin ]; then - tmp=$(mktemp -d) - trap 'rm -rf "$tmp"' EXIT - tar -xf - -C "$tmp" <&3 - ( cd "$tmp" && shopt -s dotglob && COPYFILE_DISABLE=1 tar -cf - -- * ) >&4 - else - cat <&3 >&4 - fi +repack: source scripts/spread_repack_bread.sh backends: docker: diff --git a/templates/bread-25.10.yaml.in b/templates/bread-25.10.yaml.in index 4f33b4e..bb6f415 100644 --- a/templates/bread-25.10.yaml.in +++ b/templates/bread-25.10.yaml.in @@ -11,18 +11,7 @@ exclude: - .git - .github -# macOS tar packs an AppleDouble `._*` sidecar next to every file; they unpack as -# real files on the remote. `exclude:` can't drop them (tar synthesises them below -# its glob filter), so extract and repack with COPYFILE_DISABLE=1. -repack: | - if [ "$(uname -s)" = Darwin ]; then - tmp=$(mktemp -d) - trap 'rm -rf "$tmp"' EXIT - tar -xf - -C "$tmp" <&3 - ( cd "$tmp" && shopt -s dotglob && COPYFILE_DISABLE=1 tar -cf - -- * ) >&4 - else - cat <&3 >&4 - fi +repack: source scripts/spread_repack_bread.sh backends: docker: diff --git a/templates/bread-26.04.yaml.in b/templates/bread-26.04.yaml.in index ac0f155..c2e2de7 100644 --- a/templates/bread-26.04.yaml.in +++ b/templates/bread-26.04.yaml.in @@ -11,18 +11,7 @@ exclude: - .git - .github -# macOS tar packs an AppleDouble `._*` sidecar next to every file; they unpack as -# real files on the remote. `exclude:` can't drop them (tar synthesises them below -# its glob filter), so extract and repack with COPYFILE_DISABLE=1. -repack: | - if [ "$(uname -s)" = Darwin ]; then - tmp=$(mktemp -d) - trap 'rm -rf "$tmp"' EXIT - tar -xf - -C "$tmp" <&3 - ( cd "$tmp" && shopt -s dotglob && COPYFILE_DISABLE=1 tar -cf - -- * ) >&4 - else - cat <&3 >&4 - fi +repack: source scripts/spread_repack_bread.sh backends: docker: diff --git a/templates/bread-26.10.yaml.in b/templates/bread-26.10.yaml.in index 92282ca..2710a04 100644 --- a/templates/bread-26.10.yaml.in +++ b/templates/bread-26.10.yaml.in @@ -11,18 +11,7 @@ exclude: - .git - .github -# macOS tar packs an AppleDouble `._*` sidecar next to every file; they unpack as -# real files on the remote. `exclude:` can't drop them (tar synthesises them below -# its glob filter), so extract and repack with COPYFILE_DISABLE=1. -repack: | - if [ "$(uname -s)" = Darwin ]; then - tmp=$(mktemp -d) - trap 'rm -rf "$tmp"' EXIT - tar -xf - -C "$tmp" <&3 - ( cd "$tmp" && shopt -s dotglob && COPYFILE_DISABLE=1 tar -cf - -- * ) >&4 - else - cat <&3 >&4 - fi +repack: source scripts/spread_repack_bread.sh backends: docker: diff --git a/templates/bread-chisel-releases-22.04.yaml.in b/templates/bread-chisel-releases-22.04.yaml.in index f371c5d..51fbb34 100644 --- a/templates/bread-chisel-releases-22.04.yaml.in +++ b/templates/bread-chisel-releases-22.04.yaml.in @@ -18,18 +18,7 @@ exclude: - work - rootfs -# macOS tar packs an AppleDouble `._*` sidecar next to every file; they unpack as -# real files on the remote. `exclude:` can't drop them (tar synthesises them below -# its glob filter), so extract and repack with COPYFILE_DISABLE=1. -repack: | - if [ "$(uname -s)" = Darwin ]; then - tmp=$(mktemp -d) - trap 'rm -rf "$tmp"' EXIT - tar -xf - -C "$tmp" <&3 - ( cd "$tmp" && shopt -s dotglob && COPYFILE_DISABLE=1 tar -cf - -- * ) >&4 - else - cat <&3 >&4 - fi +repack: source scripts/spread_repack_bread.sh backends: docker: diff --git a/templates/bread-chisel-releases-24.04.yaml.in b/templates/bread-chisel-releases-24.04.yaml.in index ea12d74..080edfe 100644 --- a/templates/bread-chisel-releases-24.04.yaml.in +++ b/templates/bread-chisel-releases-24.04.yaml.in @@ -18,18 +18,7 @@ exclude: - work - rootfs -# macOS tar packs an AppleDouble `._*` sidecar next to every file; they unpack as -# real files on the remote. `exclude:` can't drop them (tar synthesises them below -# its glob filter), so extract and repack with COPYFILE_DISABLE=1. -repack: | - if [ "$(uname -s)" = Darwin ]; then - tmp=$(mktemp -d) - trap 'rm -rf "$tmp"' EXIT - tar -xf - -C "$tmp" <&3 - ( cd "$tmp" && shopt -s dotglob && COPYFILE_DISABLE=1 tar -cf - -- * ) >&4 - else - cat <&3 >&4 - fi +repack: source scripts/spread_repack_bread.sh backends: docker: diff --git a/templates/bread-chisel-releases-25.10.yaml.in b/templates/bread-chisel-releases-25.10.yaml.in index f8411ca..bccf42a 100644 --- a/templates/bread-chisel-releases-25.10.yaml.in +++ b/templates/bread-chisel-releases-25.10.yaml.in @@ -18,18 +18,7 @@ exclude: - work - rootfs -# macOS tar packs an AppleDouble `._*` sidecar next to every file; they unpack as -# real files on the remote. `exclude:` can't drop them (tar synthesises them below -# its glob filter), so extract and repack with COPYFILE_DISABLE=1. -repack: | - if [ "$(uname -s)" = Darwin ]; then - tmp=$(mktemp -d) - trap 'rm -rf "$tmp"' EXIT - tar -xf - -C "$tmp" <&3 - ( cd "$tmp" && shopt -s dotglob && COPYFILE_DISABLE=1 tar -cf - -- * ) >&4 - else - cat <&3 >&4 - fi +repack: source scripts/spread_repack_bread.sh backends: docker: diff --git a/templates/bread-chisel-releases-26.04.yaml.in b/templates/bread-chisel-releases-26.04.yaml.in index ed45a8e..0174650 100644 --- a/templates/bread-chisel-releases-26.04.yaml.in +++ b/templates/bread-chisel-releases-26.04.yaml.in @@ -18,18 +18,7 @@ exclude: - work - rootfs -# macOS tar packs an AppleDouble `._*` sidecar next to every file; they unpack as -# real files on the remote. `exclude:` can't drop them (tar synthesises them below -# its glob filter), so extract and repack with COPYFILE_DISABLE=1. -repack: | - if [ "$(uname -s)" = Darwin ]; then - tmp=$(mktemp -d) - trap 'rm -rf "$tmp"' EXIT - tar -xf - -C "$tmp" <&3 - ( cd "$tmp" && shopt -s dotglob && COPYFILE_DISABLE=1 tar -cf - -- * ) >&4 - else - cat <&3 >&4 - fi +repack: source scripts/spread_repack_bread.sh backends: docker: diff --git a/templates/bread-chisel-releases-26.10.yaml.in b/templates/bread-chisel-releases-26.10.yaml.in index 001d4a2..53fc3dd 100644 --- a/templates/bread-chisel-releases-26.10.yaml.in +++ b/templates/bread-chisel-releases-26.10.yaml.in @@ -18,18 +18,7 @@ exclude: - work - rootfs -# macOS tar packs an AppleDouble `._*` sidecar next to every file; they unpack as -# real files on the remote. `exclude:` can't drop them (tar synthesises them below -# its glob filter), so extract and repack with COPYFILE_DISABLE=1. -repack: | - if [ "$(uname -s)" = Darwin ]; then - tmp=$(mktemp -d) - trap 'rm -rf "$tmp"' EXIT - tar -xf - -C "$tmp" <&3 - ( cd "$tmp" && shopt -s dotglob && COPYFILE_DISABLE=1 tar -cf - -- * ) >&4 - else - cat <&3 >&4 - fi +repack: source scripts/spread_repack_bread.sh backends: docker: From 83f062a6fa8f412bb3d16f6789f2fb04496ff8a8 Mon Sep 17 00:00:00 2001 From: lczyk Date: Tue, 28 Jul 2026 10:41:13 +0100 Subject: [PATCH 5/8] chore: appease shellcheck or cleanup comments in spread_repack_bread.sh The comments were overly detailed about internal mechanics (fd handling, glob workarounds) that don't belong in this script's header. Trimmed to just the essential why: macOS tar creates sidecar files that need removing. --- scripts/spread_repack_bread.sh | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/scripts/spread_repack_bread.sh b/scripts/spread_repack_bread.sh index 2029a35..6389089 100644 --- a/scripts/spread_repack_bread.sh +++ b/scripts/spread_repack_bread.sh @@ -1,14 +1,9 @@ # macOS tar packs an AppleDouble ._* sidecar next to every file; they unpack as -# real files on the remote. `exclude:` can't drop them (tar synthesises them -# below its glob filter), so extract and repack with COPYFILE_DISABLE=1. -# Spread hands the raw pre-gzip tar stream on fd 3 and takes the replacement -# on fd 4. +# real files on the remote. Repack to remove them. if [ "$(uname -s)" = Darwin ]; then tmp=$(mktemp -d) trap 'rm -rf "$tmp"' EXIT tar -xf - -C "$tmp" <&3 - # `-- *` + dotglob rather than `.`: retarring `.` emits a ./ entry carrying - # mktemp's 0700, which would chmod $SPREAD_PATH on the remote. ( cd "$tmp" && shopt -s dotglob && COPYFILE_DISABLE=1 tar -cf - -- * ) >&4 else cat <&3 >&4 From ed64ecee31180ad54929cd9108dd7833ed848098 Mon Sep 17 00:00:00 2001 From: lczyk Date: Tue, 28 Jul 2026 10:42:08 +0100 Subject: [PATCH 6/8] docs: simplify repack comment explain what happens, not the implementation details of how to work around it. --- tests/spread.yaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/spread.yaml b/tests/spread.yaml index 718753b..45db283 100644 --- a/tests/spread.yaml +++ b/tests/spread.yaml @@ -13,8 +13,7 @@ exclude: - .github # macOS tar packs an AppleDouble `._*` sidecar next to every file; they unpack as -# real files on the remote. `exclude:` can't drop them (tar synthesises them below -# its glob filter), so extract and repack with COPYFILE_DISABLE=1. +# real files on the remote. Repack to remove them. repack: | if [ "$(uname -s)" = Darwin ]; then tmp=$(mktemp -d) From 28536ece67b9c6498737fa674110f74b3ec03086 Mon Sep 17 00:00:00 2001 From: lczyk Date: Tue, 28 Jul 2026 10:43:03 +0100 Subject: [PATCH 7/8] docs: simplify macOS tar repack comments across bread configs --- inlined/bread-22.04.yaml | 7 +------ inlined/bread-24.04.yaml | 7 +------ inlined/bread-25.10.yaml | 7 +------ inlined/bread-26.04.yaml | 7 +------ inlined/bread-26.10.yaml | 7 +------ inlined/bread-chisel-releases-22.04.yaml | 7 +------ inlined/bread-chisel-releases-24.04.yaml | 7 +------ inlined/bread-chisel-releases-25.10.yaml | 7 +------ inlined/bread-chisel-releases-26.04.yaml | 7 +------ inlined/bread-chisel-releases-26.10.yaml | 7 +------ 10 files changed, 10 insertions(+), 60 deletions(-) diff --git a/inlined/bread-22.04.yaml b/inlined/bread-22.04.yaml index f8e0924..2671c1c 100644 --- a/inlined/bread-22.04.yaml +++ b/inlined/bread-22.04.yaml @@ -13,16 +13,11 @@ exclude: repack: | # macOS tar packs an AppleDouble ._* sidecar next to every file; they unpack as - # real files on the remote. `exclude:` can't drop them (tar synthesises them - # below its glob filter), so extract and repack with COPYFILE_DISABLE=1. - # Spread hands the raw pre-gzip tar stream on fd 3 and takes the replacement - # on fd 4. + # real files on the remote. Repack to remove them. if [ "$(uname -s)" = Darwin ]; then tmp=$(mktemp -d) trap 'rm -rf "$tmp"' EXIT tar -xf - -C "$tmp" <&3 - # `-- *` + dotglob rather than `.`: retarring `.` emits a ./ entry carrying - # mktemp's 0700, which would chmod $SPREAD_PATH on the remote. ( cd "$tmp" && shopt -s dotglob && COPYFILE_DISABLE=1 tar -cf - -- * ) >&4 else cat <&3 >&4 diff --git a/inlined/bread-24.04.yaml b/inlined/bread-24.04.yaml index 3b12915..810aec9 100644 --- a/inlined/bread-24.04.yaml +++ b/inlined/bread-24.04.yaml @@ -13,16 +13,11 @@ exclude: repack: | # macOS tar packs an AppleDouble ._* sidecar next to every file; they unpack as - # real files on the remote. `exclude:` can't drop them (tar synthesises them - # below its glob filter), so extract and repack with COPYFILE_DISABLE=1. - # Spread hands the raw pre-gzip tar stream on fd 3 and takes the replacement - # on fd 4. + # real files on the remote. Repack to remove them. if [ "$(uname -s)" = Darwin ]; then tmp=$(mktemp -d) trap 'rm -rf "$tmp"' EXIT tar -xf - -C "$tmp" <&3 - # `-- *` + dotglob rather than `.`: retarring `.` emits a ./ entry carrying - # mktemp's 0700, which would chmod $SPREAD_PATH on the remote. ( cd "$tmp" && shopt -s dotglob && COPYFILE_DISABLE=1 tar -cf - -- * ) >&4 else cat <&3 >&4 diff --git a/inlined/bread-25.10.yaml b/inlined/bread-25.10.yaml index b815b7c..98adee1 100644 --- a/inlined/bread-25.10.yaml +++ b/inlined/bread-25.10.yaml @@ -13,16 +13,11 @@ exclude: repack: | # macOS tar packs an AppleDouble ._* sidecar next to every file; they unpack as - # real files on the remote. `exclude:` can't drop them (tar synthesises them - # below its glob filter), so extract and repack with COPYFILE_DISABLE=1. - # Spread hands the raw pre-gzip tar stream on fd 3 and takes the replacement - # on fd 4. + # real files on the remote. Repack to remove them. if [ "$(uname -s)" = Darwin ]; then tmp=$(mktemp -d) trap 'rm -rf "$tmp"' EXIT tar -xf - -C "$tmp" <&3 - # `-- *` + dotglob rather than `.`: retarring `.` emits a ./ entry carrying - # mktemp's 0700, which would chmod $SPREAD_PATH on the remote. ( cd "$tmp" && shopt -s dotglob && COPYFILE_DISABLE=1 tar -cf - -- * ) >&4 else cat <&3 >&4 diff --git a/inlined/bread-26.04.yaml b/inlined/bread-26.04.yaml index d774351..58a873a 100644 --- a/inlined/bread-26.04.yaml +++ b/inlined/bread-26.04.yaml @@ -13,16 +13,11 @@ exclude: repack: | # macOS tar packs an AppleDouble ._* sidecar next to every file; they unpack as - # real files on the remote. `exclude:` can't drop them (tar synthesises them - # below its glob filter), so extract and repack with COPYFILE_DISABLE=1. - # Spread hands the raw pre-gzip tar stream on fd 3 and takes the replacement - # on fd 4. + # real files on the remote. Repack to remove them. if [ "$(uname -s)" = Darwin ]; then tmp=$(mktemp -d) trap 'rm -rf "$tmp"' EXIT tar -xf - -C "$tmp" <&3 - # `-- *` + dotglob rather than `.`: retarring `.` emits a ./ entry carrying - # mktemp's 0700, which would chmod $SPREAD_PATH on the remote. ( cd "$tmp" && shopt -s dotglob && COPYFILE_DISABLE=1 tar -cf - -- * ) >&4 else cat <&3 >&4 diff --git a/inlined/bread-26.10.yaml b/inlined/bread-26.10.yaml index e564ac7..490932c 100644 --- a/inlined/bread-26.10.yaml +++ b/inlined/bread-26.10.yaml @@ -13,16 +13,11 @@ exclude: repack: | # macOS tar packs an AppleDouble ._* sidecar next to every file; they unpack as - # real files on the remote. `exclude:` can't drop them (tar synthesises them - # below its glob filter), so extract and repack with COPYFILE_DISABLE=1. - # Spread hands the raw pre-gzip tar stream on fd 3 and takes the replacement - # on fd 4. + # real files on the remote. Repack to remove them. if [ "$(uname -s)" = Darwin ]; then tmp=$(mktemp -d) trap 'rm -rf "$tmp"' EXIT tar -xf - -C "$tmp" <&3 - # `-- *` + dotglob rather than `.`: retarring `.` emits a ./ entry carrying - # mktemp's 0700, which would chmod $SPREAD_PATH on the remote. ( cd "$tmp" && shopt -s dotglob && COPYFILE_DISABLE=1 tar -cf - -- * ) >&4 else cat <&3 >&4 diff --git a/inlined/bread-chisel-releases-22.04.yaml b/inlined/bread-chisel-releases-22.04.yaml index f5eb98a..fa3b434 100644 --- a/inlined/bread-chisel-releases-22.04.yaml +++ b/inlined/bread-chisel-releases-22.04.yaml @@ -20,16 +20,11 @@ exclude: repack: | # macOS tar packs an AppleDouble ._* sidecar next to every file; they unpack as - # real files on the remote. `exclude:` can't drop them (tar synthesises them - # below its glob filter), so extract and repack with COPYFILE_DISABLE=1. - # Spread hands the raw pre-gzip tar stream on fd 3 and takes the replacement - # on fd 4. + # real files on the remote. Repack to remove them. if [ "$(uname -s)" = Darwin ]; then tmp=$(mktemp -d) trap 'rm -rf "$tmp"' EXIT tar -xf - -C "$tmp" <&3 - # `-- *` + dotglob rather than `.`: retarring `.` emits a ./ entry carrying - # mktemp's 0700, which would chmod $SPREAD_PATH on the remote. ( cd "$tmp" && shopt -s dotglob && COPYFILE_DISABLE=1 tar -cf - -- * ) >&4 else cat <&3 >&4 diff --git a/inlined/bread-chisel-releases-24.04.yaml b/inlined/bread-chisel-releases-24.04.yaml index 3fc274f..f6578c9 100644 --- a/inlined/bread-chisel-releases-24.04.yaml +++ b/inlined/bread-chisel-releases-24.04.yaml @@ -20,16 +20,11 @@ exclude: repack: | # macOS tar packs an AppleDouble ._* sidecar next to every file; they unpack as - # real files on the remote. `exclude:` can't drop them (tar synthesises them - # below its glob filter), so extract and repack with COPYFILE_DISABLE=1. - # Spread hands the raw pre-gzip tar stream on fd 3 and takes the replacement - # on fd 4. + # real files on the remote. Repack to remove them. if [ "$(uname -s)" = Darwin ]; then tmp=$(mktemp -d) trap 'rm -rf "$tmp"' EXIT tar -xf - -C "$tmp" <&3 - # `-- *` + dotglob rather than `.`: retarring `.` emits a ./ entry carrying - # mktemp's 0700, which would chmod $SPREAD_PATH on the remote. ( cd "$tmp" && shopt -s dotglob && COPYFILE_DISABLE=1 tar -cf - -- * ) >&4 else cat <&3 >&4 diff --git a/inlined/bread-chisel-releases-25.10.yaml b/inlined/bread-chisel-releases-25.10.yaml index dbc895a..8786ab9 100644 --- a/inlined/bread-chisel-releases-25.10.yaml +++ b/inlined/bread-chisel-releases-25.10.yaml @@ -20,16 +20,11 @@ exclude: repack: | # macOS tar packs an AppleDouble ._* sidecar next to every file; they unpack as - # real files on the remote. `exclude:` can't drop them (tar synthesises them - # below its glob filter), so extract and repack with COPYFILE_DISABLE=1. - # Spread hands the raw pre-gzip tar stream on fd 3 and takes the replacement - # on fd 4. + # real files on the remote. Repack to remove them. if [ "$(uname -s)" = Darwin ]; then tmp=$(mktemp -d) trap 'rm -rf "$tmp"' EXIT tar -xf - -C "$tmp" <&3 - # `-- *` + dotglob rather than `.`: retarring `.` emits a ./ entry carrying - # mktemp's 0700, which would chmod $SPREAD_PATH on the remote. ( cd "$tmp" && shopt -s dotglob && COPYFILE_DISABLE=1 tar -cf - -- * ) >&4 else cat <&3 >&4 diff --git a/inlined/bread-chisel-releases-26.04.yaml b/inlined/bread-chisel-releases-26.04.yaml index 811360d..9f3e26f 100644 --- a/inlined/bread-chisel-releases-26.04.yaml +++ b/inlined/bread-chisel-releases-26.04.yaml @@ -20,16 +20,11 @@ exclude: repack: | # macOS tar packs an AppleDouble ._* sidecar next to every file; they unpack as - # real files on the remote. `exclude:` can't drop them (tar synthesises them - # below its glob filter), so extract and repack with COPYFILE_DISABLE=1. - # Spread hands the raw pre-gzip tar stream on fd 3 and takes the replacement - # on fd 4. + # real files on the remote. Repack to remove them. if [ "$(uname -s)" = Darwin ]; then tmp=$(mktemp -d) trap 'rm -rf "$tmp"' EXIT tar -xf - -C "$tmp" <&3 - # `-- *` + dotglob rather than `.`: retarring `.` emits a ./ entry carrying - # mktemp's 0700, which would chmod $SPREAD_PATH on the remote. ( cd "$tmp" && shopt -s dotglob && COPYFILE_DISABLE=1 tar -cf - -- * ) >&4 else cat <&3 >&4 diff --git a/inlined/bread-chisel-releases-26.10.yaml b/inlined/bread-chisel-releases-26.10.yaml index 751b671..16f319b 100644 --- a/inlined/bread-chisel-releases-26.10.yaml +++ b/inlined/bread-chisel-releases-26.10.yaml @@ -20,16 +20,11 @@ exclude: repack: | # macOS tar packs an AppleDouble ._* sidecar next to every file; they unpack as - # real files on the remote. `exclude:` can't drop them (tar synthesises them - # below its glob filter), so extract and repack with COPYFILE_DISABLE=1. - # Spread hands the raw pre-gzip tar stream on fd 3 and takes the replacement - # on fd 4. + # real files on the remote. Repack to remove them. if [ "$(uname -s)" = Darwin ]; then tmp=$(mktemp -d) trap 'rm -rf "$tmp"' EXIT tar -xf - -C "$tmp" <&3 - # `-- *` + dotglob rather than `.`: retarring `.` emits a ./ entry carrying - # mktemp's 0700, which would chmod $SPREAD_PATH on the remote. ( cd "$tmp" && shopt -s dotglob && COPYFILE_DISABLE=1 tar -cf - -- * ) >&4 else cat <&3 >&4 From 0963dfd5e61da707f22e3833cc86949f329aec72 Mon Sep 17 00:00:00 2001 From: lczyk Date: Tue, 28 Jul 2026 10:44:24 +0100 Subject: [PATCH 8/8] chore: rename makefile target from inlined-yaml-files to inline --- .github/workflows/ci.yaml | 4 ++-- .github/workflows/release.yaml | 2 +- README.md | 2 +- makefile | 8 ++++---- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 8f349fa..47d03a2 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -50,8 +50,8 @@ jobs: - name: Assert committed inlined yamls match templates + scripts run: | - make inlined-yaml-files + make inline if ! git diff --exit-code inlined/; then - echo "::error::inlined/*.yaml is stale -- run 'make inlined-yaml-files' and commit the result" + echo "::error::inlined/*.yaml is stale -- run 'make inline' and commit the result" exit 1 fi diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 4065176..aa36afd 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -49,7 +49,7 @@ jobs: uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Regenerate inlined yamls - run: make inlined-yaml-files + run: make inline - name: Restore cached binaries uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 diff --git a/README.md b/README.md index cb5d826..aa19df6 100644 --- a/README.md +++ b/README.md @@ -130,7 +130,7 @@ make build-bread-chisel-releases ARCH=arm64 regenerate distribution yamls (already committed under `inlined/`, but regenerate after script edits): ``` -make inlined-yaml-files +make inline ``` run the demo (LTS systems only -- 24.04 + 26.04 x amd64 / arm64). builds the required `bread` images first if missing: diff --git a/makefile b/makefile index 8449f82..a70c018 100644 --- a/makefile +++ b/makefile @@ -56,7 +56,7 @@ help: ## Show this help awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-40s\033[0m %s\n", $$1, $$2}' .PHONY: all -all: build-all inlined-yaml-files ## Build all images + generate inlined yamls +all: build-all inline ## Build all images + generate inlined yamls .PHONY: build-all build-all: build-bread build-bread-chisel-releases ## Build all images (narrow via VER=... ARCH=...) @@ -84,7 +84,7 @@ SPREAD_ARGS ?= TEST_STAMPS := .stamp/bread-test-26.04-$(SELECTED_ARCH) \ $(foreach v,$(VERSIONS),.stamp/bread-$(v)-$(SELECTED_ARCH) .stamp/bread-chisel-releases-$(v)-$(SELECTED_ARCH)) .PHONY: test -test: $(TEST_STAMPS) inlined-yaml-files ## Run the spread test suite (host arch; ARCH=... to override, SPREAD_ARGS=... for flags) +test: $(TEST_STAMPS) inline ## Run the spread test suite (host arch; ARCH=... to override, SPREAD_ARGS=... for flags) cd tests && spread $(SPREAD_ARGS) outer:ubuntu-26.04-$(SELECTED_ARCH) .PHONY: check-base @@ -102,8 +102,8 @@ DEMO_STAMPS := $(foreach a,$(NATIVE_ARCHES),.stamp/bread-24.04-$(a) .stamp/bread demo: $(DEMO_STAMPS) ## Run the spread demo on LTS systems (24.04 + 26.04, native arches) $(MAKE) -C demo run -.PHONY: inlined-yaml-files -inlined-yaml-files: $(INLINED) ## Generate inlined/*.yaml from templates/*.yaml.in +.PHONY: inline +inline: $(INLINED) ## Generate inlined/*.yaml from templates/*.yaml.in # Hash-stamp pattern: stamp file contents = hash of all inputs that affect # this image. FORCE-dep makes us recompute hash each run; stamp content only