Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 83 additions & 15 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,8 @@ jobs:
fi
AMD64_ARCHIVE="container-bin-${VERSION}-windows-amd64.zip"
ARM64_ARCHIVE="container-bin-${VERSION}-windows-arm64.zip"
mkdir -p dist/package-amd64 dist/package-arm64
WSL_AMD64_ARCHIVE="container-bin-${VERSION}-linux-amd64.tar.gz"
mkdir -p dist/package-amd64 dist/package-arm64 dist/package-linux-amd64

GOOS=windows GOARCH=amd64 CGO_ENABLED=0 \
go build -trimpath -buildvcs=false \
Expand All @@ -69,16 +70,23 @@ jobs:
go build -trimpath -buildvcs=false \
-ldflags "-s -w -X main.version=${VERSION}" \
-o dist/package-arm64/cb.exe .
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 \
go build -trimpath -buildvcs=false \
-ldflags "-s -w -X main.version=${VERSION}" \
-o dist/package-linux-amd64/cb .

cp dist/cb.exe dist/package-amd64/cb.exe
cp LICENSE README.md dist/package-amd64/
cp LICENSE README.md dist/package-arm64/
cp LICENSE README.md dist/package-linux-amd64/
test "$(dist/package-linux-amd64/cb version)" = "container-bin ${VERSION}"
# ZIP stores DOS timestamps. Normalize them and strip extra metadata so
# separate runners/checkouts produce byte-identical archives.
touch -d '1980-01-01 00:00:00 UTC' \
dist/cb.exe \
dist/package-amd64/cb.exe dist/package-amd64/LICENSE dist/package-amd64/README.md \
dist/package-arm64/cb.exe dist/package-arm64/LICENSE dist/package-arm64/README.md
dist/package-arm64/cb.exe dist/package-arm64/LICENSE dist/package-arm64/README.md \
dist/package-linux-amd64/cb dist/package-linux-amd64/LICENSE dist/package-linux-amd64/README.md
(
cd dist/package-amd64
zip -X "../${AMD64_ARCHIVE}" cb.exe LICENSE README.md
Expand All @@ -87,30 +95,51 @@ jobs:
cd dist/package-arm64
zip -X "../${ARM64_ARCHIVE}" cb.exe LICENSE README.md
)
(
cd dist/package-linux-amd64
tar --format=ustar --owner=0 --group=0 --numeric-owner --mtime='@0' \
-cf - cb LICENSE README.md | gzip -n > "../${WSL_AMD64_ARCHIVE}"
)
diff -u \
<(printf 'LICENSE\nREADME.md\ncb\n') \
<(tar -tzf "dist/${WSL_AMD64_ARCHIVE}" | sort)
(
cd dist
sha256sum \
cb.exe \
"${AMD64_ARCHIVE}" \
"${ARM64_ARCHIVE}" > SHA256SUMS
sha256sum "${WSL_AMD64_ARCHIVE}" > SHA256SUMS-WSL
test "$(wc -l < SHA256SUMS)" -eq 3
test "$(wc -l < SHA256SUMS-WSL)" -eq 1
test "$(awk '{print $2}' SHA256SUMS-WSL)" = "${WSL_AMD64_ARCHIVE}"
)
rm -rf dist/package-amd64 dist/package-arm64
rm -rf dist/package-amd64 dist/package-arm64 dist/package-linux-amd64
cat dist/SHA256SUMS
cat dist/SHA256SUMS-WSL
echo "VERSION=${VERSION}" >> "$GITHUB_ENV"
echo "AMD64_ARCHIVE=${AMD64_ARCHIVE}" >> "$GITHUB_ENV"
echo "ARM64_ARCHIVE=${ARM64_ARCHIVE}" >> "$GITHUB_ENV"
echo "WSL_AMD64_ARCHIVE=${WSL_AMD64_ARCHIVE}" >> "$GITHUB_ENV"

- name: Verify checksums
shell: bash
run: |
set -euo pipefail
(cd dist && sha256sum -c SHA256SUMS)
(
cd dist
sha256sum -c SHA256SUMS
sha256sum -c SHA256SUMS-WSL
)

- name: Verify reproducibility
shell: bash
run: |
set -euo pipefail
mkdir -p "$RUNNER_TEMP/repro/package-amd64" "$RUNNER_TEMP/repro/package-arm64"
mkdir -p \
"$RUNNER_TEMP/repro/package-amd64" \
"$RUNNER_TEMP/repro/package-arm64" \
"$RUNNER_TEMP/repro/package-linux-amd64"
GOOS=windows GOARCH=amd64 CGO_ENABLED=0 \
go build -trimpath -buildvcs=false \
-ldflags "-s -w -X main.version=${VERSION}" \
Expand All @@ -119,17 +148,25 @@ jobs:
go build -trimpath -buildvcs=false \
-ldflags "-s -w -X main.version=${VERSION}" \
-o "$RUNNER_TEMP/repro/package-arm64/cb.exe" .
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 \
go build -trimpath -buildvcs=false \
-ldflags "-s -w -X main.version=${VERSION}" \
-o "$RUNNER_TEMP/repro/package-linux-amd64/cb" .
cp "$RUNNER_TEMP/repro/cb.exe" "$RUNNER_TEMP/repro/package-amd64/cb.exe"
cp LICENSE README.md "$RUNNER_TEMP/repro/package-amd64/"
cp LICENSE README.md "$RUNNER_TEMP/repro/package-arm64/"
cp LICENSE README.md "$RUNNER_TEMP/repro/package-linux-amd64/"
touch -d '1980-01-01 00:00:00 UTC' \
"$RUNNER_TEMP/repro/cb.exe" \
"$RUNNER_TEMP/repro/package-amd64/cb.exe" \
"$RUNNER_TEMP/repro/package-amd64/LICENSE" \
"$RUNNER_TEMP/repro/package-amd64/README.md" \
"$RUNNER_TEMP/repro/package-arm64/cb.exe" \
"$RUNNER_TEMP/repro/package-arm64/LICENSE" \
"$RUNNER_TEMP/repro/package-arm64/README.md"
"$RUNNER_TEMP/repro/package-arm64/README.md" \
"$RUNNER_TEMP/repro/package-linux-amd64/cb" \
"$RUNNER_TEMP/repro/package-linux-amd64/LICENSE" \
"$RUNNER_TEMP/repro/package-linux-amd64/README.md"
(
cd "$RUNNER_TEMP/repro/package-amd64"
zip -X "../${AMD64_ARCHIVE}" cb.exe LICENSE README.md >/dev/null
Expand All @@ -138,18 +175,24 @@ jobs:
cd "$RUNNER_TEMP/repro/package-arm64"
zip -X "../${ARM64_ARCHIVE}" cb.exe LICENSE README.md >/dev/null
)
(
cd "$RUNNER_TEMP/repro/package-linux-amd64"
tar --format=ustar --owner=0 --group=0 --numeric-owner --mtime='@0' \
-cf - cb LICENSE README.md | gzip -n > "../${WSL_AMD64_ARCHIVE}"
)
(
cd "$RUNNER_TEMP/repro"
sha256sum \
cb.exe \
"${AMD64_ARCHIVE}" \
"${ARM64_ARCHIVE}" > SHA256SUMS
sha256sum "${WSL_AMD64_ARCHIVE}" > SHA256SUMS-WSL
)
for artifact in cb.exe "${AMD64_ARCHIVE}" "${ARM64_ARCHIVE}" SHA256SUMS; do
for artifact in cb.exe "${AMD64_ARCHIVE}" "${ARM64_ARCHIVE}" "${WSL_AMD64_ARCHIVE}" SHA256SUMS SHA256SUMS-WSL; do
sha256sum "dist/${artifact}" "$RUNNER_TEMP/repro/${artifact}"
cmp "dist/${artifact}" "$RUNNER_TEMP/repro/${artifact}"
done
echo 'amd64 and arm64 binaries, archives and checksum manifest are reproducible'
echo 'Windows and native WSL artifacts plus checksum manifest are reproducible'

- name: Upload verified release bundle
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
Expand All @@ -158,7 +201,8 @@ jobs:
path: |
dist/cb.exe
dist/*.zip
dist/SHA256SUMS
dist/*.tar.gz
dist/SHA256SUMS*
if-no-files-found: error
retention-days: 14

Expand Down Expand Up @@ -197,7 +241,8 @@ jobs:
fi
AMD64_ARCHIVE="container-bin-${VERSION}-windows-amd64.zip"
ARM64_ARCHIVE="container-bin-${VERSION}-windows-arm64.zip"
mkdir -p second/package-amd64 second/package-arm64
WSL_AMD64_ARCHIVE="container-bin-${VERSION}-linux-amd64.tar.gz"
mkdir -p second/package-amd64 second/package-arm64 second/package-linux-amd64
GOOS=windows GOARCH=amd64 CGO_ENABLED=0 \
go build -trimpath -buildvcs=false \
-ldflags "-s -w -X main.version=${VERSION}" \
Expand All @@ -206,13 +251,19 @@ jobs:
go build -trimpath -buildvcs=false \
-ldflags "-s -w -X main.version=${VERSION}" \
-o second/package-arm64/cb.exe .
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 \
go build -trimpath -buildvcs=false \
-ldflags "-s -w -X main.version=${VERSION}" \
-o second/package-linux-amd64/cb .
cp second/cb.exe second/package-amd64/cb.exe
cp LICENSE README.md second/package-amd64/
cp LICENSE README.md second/package-arm64/
cp LICENSE README.md second/package-linux-amd64/
touch -d '1980-01-01 00:00:00 UTC' \
second/cb.exe \
second/package-amd64/cb.exe second/package-amd64/LICENSE second/package-amd64/README.md \
second/package-arm64/cb.exe second/package-arm64/LICENSE second/package-arm64/README.md
second/package-arm64/cb.exe second/package-arm64/LICENSE second/package-arm64/README.md \
second/package-linux-amd64/cb second/package-linux-amd64/LICENSE second/package-linux-amd64/README.md
(
cd second/package-amd64
zip -X "../${AMD64_ARCHIVE}" cb.exe LICENSE README.md >/dev/null
Expand All @@ -221,17 +272,23 @@ jobs:
cd second/package-arm64
zip -X "../${ARM64_ARCHIVE}" cb.exe LICENSE README.md >/dev/null
)
(
cd second/package-linux-amd64
tar --format=ustar --owner=0 --group=0 --numeric-owner --mtime='@0' \
-cf - cb LICENSE README.md | gzip -n > "../${WSL_AMD64_ARCHIVE}"
)
(
cd second
sha256sum \
cb.exe \
"${AMD64_ARCHIVE}" \
"${ARM64_ARCHIVE}" > SHA256SUMS
sha256sum "${WSL_AMD64_ARCHIVE}" > SHA256SUMS-WSL
)
for artifact in cb.exe "${AMD64_ARCHIVE}" "${ARM64_ARCHIVE}" SHA256SUMS; do
for artifact in cb.exe "${AMD64_ARCHIVE}" "${ARM64_ARCHIVE}" "${WSL_AMD64_ARCHIVE}" SHA256SUMS SHA256SUMS-WSL; do
cmp "first/${artifact}" "second/${artifact}"
done
echo 'independent runner reproduced binaries, archives and checksum manifest byte-for-byte'
echo 'independent runner reproduced Windows and native WSL artifacts plus checksum manifest byte-for-byte'

release:
name: Draft release
Expand All @@ -255,12 +312,22 @@ jobs:
run: |
set -euo pipefail
test "$(find dist -maxdepth 1 -type f -name '*.zip' | wc -l)" -eq 2
test "$(find dist -maxdepth 1 -type f -name '*.tar.gz' | wc -l)" -eq 1
test "$(find dist -maxdepth 1 -type f -name '*.exe' | wc -l)" -eq 1
test -f dist/cb.exe
test -f "dist/container-bin-${GITHUB_REF_NAME}-windows-amd64.zip"
test -f "dist/container-bin-${GITHUB_REF_NAME}-windows-arm64.zip"
test -f "dist/container-bin-${GITHUB_REF_NAME}-linux-amd64.tar.gz"
test -f dist/SHA256SUMS
(cd dist && sha256sum -c SHA256SUMS)
test -f dist/SHA256SUMS-WSL
(
cd dist
test "$(wc -l < SHA256SUMS)" -eq 3
test "$(wc -l < SHA256SUMS-WSL)" -eq 1
test "$(awk '{print $2}' SHA256SUMS-WSL)" = "container-bin-${GITHUB_REF_NAME}-linux-amd64.tar.gz"
sha256sum -c SHA256SUMS
sha256sum -c SHA256SUMS-WSL
)

- name: Refuse to release an untrusted tag commit
env:
Expand Down Expand Up @@ -312,6 +379,7 @@ jobs:
subject-path: |
dist/cb.exe
dist/*.zip
dist/*.tar.gz

- name: Create immutable-ready draft release
env:
Expand Down Expand Up @@ -354,4 +422,4 @@ jobs:
fi
gh release create "${GITHUB_REF_NAME}" \
"${RELEASE_ARGS[@]}" \
dist/cb.exe dist/*.zip dist/SHA256SUMS
dist/cb.exe dist/*.zip dist/*.tar.gz dist/SHA256SUMS dist/SHA256SUMS-WSL
13 changes: 12 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1214,9 +1214,20 @@ download. The ARM64 archive is release-provenance coverage, not a support
claim: full Windows ARM64 support still requires real Docker Desktop
qualification.

The v2 release also includes
`container-bin-VERSION-linux-amd64.tar.gz`, containing exactly the native `cb`
binary, `LICENSE` and `README.md`. It is the bootstrap artifact for the supported
WSL2 frontend, not a standalone-Linux support claim. Verify the archive itself
with `gh attestation verify ... --repo AviBackToBlack/container-bin` before
extracting it in the distribution-local Linux filesystem; the explicit
bootstrap/install sequence is documented in [docs/wsl.md](docs/wsl.md).

`cb self-update --check` selects the raw `cb.exe` on Windows amd64 and the
ARM64 archive on Windows arm64 directly from Go's native `GOARCH`; unsupported
architectures fail explicitly. The verifier authenticates the selected asset
before extracting the ARM64 `cb.exe`, accepts the legacy two-entry checksum
manifest for pre-ARM64 amd64 releases, and requires the canonical three-entry
manifest for dual-architecture releases.
`SHA256SUMS` manifest for dual-Windows-architecture releases. The WSL archive
uses a separate one-entry `SHA256SUMS-WSL` manifest so already-released Windows
clients retain direct self-update compatibility with v2; this changes no
Windows artifact selection, verification or replacement behavior.
19 changes: 19 additions & 0 deletions docs/release-matrix.md
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,25 @@ So CI validates compilation and pure/unit logic on a GitHub-hosted Windows
runner. The matrix is what validates the `docs/shell-contract.md` semantics on a
real Windows 11 + Docker Desktop host before a release.

### Native WSL release artifact

V2 release candidates add one deterministic
Comment thread
AviBackToBlack marked this conversation as resolved.
`container-bin-VERSION-linux-amd64.tar.gz` archive containing exactly `cb`,
`LICENSE` and `README.md`. The release workflow executes the contained binary,
checks its injected version, binds it in the separate one-entry
`SHA256SUMS-WSL` manifest, independently reproduces it byte-for-byte and attests
it with the Windows assets. The existing three-entry Windows `SHA256SUMS`
contract stays unchanged so released v1.x clients can self-update directly to
v2.

For the real WSL2 qualification cell, retain the archive checksum and
`gh attestation verify` output, extract it only on the distribution-local Linux
filesystem, run its `cb version`, and use that exact binary for
`cb wsl prepare --apply` and `cb wsl install --apply`. Confirm the installed
`~/.local/bin/cb version` matches before exercising managed tool shims. The
archive is not standalone-Linux evidence and does not qualify WSL on Windows
ARM64 hardware.

### Live published-release self-update qualification

The repository includes a Windows/amd64 harness that builds the current source
Expand Down
8 changes: 5 additions & 3 deletions docs/roadmap-decisions.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ items from being repeatedly rediscovered as if they were immediately actionable.
| RM-24 Python / uv | **Keep both** | Decision complete. Built-in `python`/`pip` keep the dedicated Python provider; `uv`/`uvx` remain separate opt-in stateful profiles. |
| RM-26 Python global CLI exposure | **pipx yes; plain pip expose no** | Completed in PR #74. The separate stateful pipx profile and managed store shipped; project/compat `/venv/bin` remains intentionally unexposed. |
| RM-34 Cargo expose enhancement | **Intentionally deferred** | Existing expose-all and explicit binary selection are sufficient. Reopen only for a concrete unmet use case. |
| WSL2 | **Native WSL frontend** | Fixed-layout install, managed-tool runtime/Docker composition, proof-bound retained-container reconciliation, and the v2-required state/GC lifecycle are wired. Integration corpus and real WSL qualification remain before activation. No Windows↔WSL path/state guessing. |
| WSL2 | **Native WSL frontend** | Fixed-layout install, managed-tool runtime/Docker composition, proof-bound retained-container reconciliation, v2-required state/GC, and the deterministic Linux/amd64 bootstrap artifact are wired. Integration corpus and real WSL qualification remain before activation. No Windows↔WSL path/state guessing. |
| Enterprise policy | **Machine-owned constraint layer** | Foundation shipped in PR #75. Authenticated registry and image-trust follow-ups must extend this boundary and cannot be weakened by lower layers. |
| Image trust | **Policy-driven Sigstore/cosign at lock time** | Ready after signed-registry policy. Digest locking remains default where policy permits. Required trust never silently falls back to digest-only. |
| Per-project overlays | **Explicit digest-bound, add-only trust model** | Implementation-ready on the merged policy foundation. Initial overlays exclude host mounts, env prefixes and shared cross-project volumes. |
Expand Down Expand Up @@ -206,8 +206,10 @@ and exactly one explicit `GH_TOKEN` or `GITHUB_TOKEN`. These host paths come
from Windows APIs rather than inherited variables. GitHub host, config-directory,
proxy, custom-CA and other inherited settings are not passed through. It
accepts the legacy two-entry `SHA256SUMS` layout for pre-ARM64 amd64 releases
and requires the canonical three-entry layout for dual-architecture releases,
invokes
and requires the canonical three-entry layout for dual-Windows-architecture
releases. Native WSL bootstrap archives use the separate one-entry
`SHA256SUMS-WSL` manifest so Windows self-update compatibility remains
unchanged. The verifier invokes
`gh attestation verify` with the repository, exact workflow-and-tag certificate
identity, tag ref and SLSA provenance predicate fixed in argv, validates the
reported subject digest and re-hashes the selected artifact after verification.
Expand Down
2 changes: 1 addition & 1 deletion docs/roadmap-implementation-requirements.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ The minimum delivery gate for a code change is:
| Enterprise policy | **Foundation and signed registry shipped / image trust remains** | PRs #75 and #84 shipped the machine-owned constraint layer and authenticated registry; image trust remains |
| Image trust | **Online/offline production and runtime authorization implemented / private-registry work remains** | Add an explicit private-registry credential bridge |
| Plugin/provider architecture | **Intentionally deferred** | Reopen only after at least two real integrations cannot fit the declarative model |
| WSL2 | **Native runtime/state wired / activation remaining** | The fixed install/config/shim lifecycle, project proof/mapping, namespaced tool-time volumes, direct Docker Desktop Engine lifecycle, stdin/output framing, raw TTY, resize, signal forwarding, retained-container cleanup, proof-bound state/GC, orphan reconciliation and exit propagation are composed. Integration corpus and real WSL2 + Docker Desktop qualification remain before activation. |
| WSL2 | **Native runtime/state/artifact wired / activation remaining** | The fixed install/config/shim lifecycle, project proof/mapping, namespaced tool-time volumes, direct Docker Desktop Engine lifecycle, stdin/output framing, raw TTY, resize, signal forwarding, retained-container cleanup, proof-bound state/GC, orphan reconciliation, exit propagation and deterministic Linux/amd64 bootstrap artifact are composed. Integration corpus and real WSL2 + Docker Desktop qualification remain before activation. |
| Per-project overlays | **Completed in PR #80** | Add-only digest-bound trust model shipped on the merged enterprise-policy foundation |
| Release SBOM | **Conditionally deferred** | Trigger on shipped third-party/runtime dependencies or concrete compliance/consumer demand |
| Snyk | **Conditionally deferred** | Trigger only for a real coverage gap plus owner/account/token and triage/outage policy |
Expand Down
Loading
Loading