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
15 changes: 13 additions & 2 deletions .woodpecker.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,17 @@ steps:
- name: build-and-package
image: golang:1.24
commands:
- apt-get update && apt-get install -y --no-install-recommends zip && rm -rf /var/lib/apt/lists/*
- apt-get update && apt-get install -y --no-install-recommends curl ca-certificates zip && rm -rf /var/lib/apt/lists/*
- |
TAG="$${CI_COMMIT_TAG:-$${CI_COMMIT_REF##refs/tags/}}"
if [ -z "$${TAG}" ]; then TAG="$(bash scripts/release/resolve-tag.sh)"; fi
test -n "$${TAG}" || (echo "TAG is required" && exit 1)
TOOL_DIR="$$(pwd)/.release-tools"
bash scripts/release/install-tool.sh syft "$${TOOL_DIR}"
export PATH="$${TOOL_DIR}:$${PATH}"
bash scripts/release/build-artifacts.sh "$${TAG}"
bash scripts/release/create-checksums.sh "$${TAG}"
bash scripts/release/generate-sboms.sh "$${TAG}"

- name: publish-release-and-vps
image: alpine:3.20
Expand All @@ -24,12 +28,16 @@ steps:
from_secret: doppler_token
commands:
- test -n "$${DOPPLER_TOKEN}" || (echo "DOPPLER_TOKEN is required" && exit 1)
- apk add --no-cache bash curl openssh-client rsync git gnupg github-cli
- apk add --no-cache bash curl openssh-client rsync git github-cli
- curl -Ls https://cli.doppler.com/install.sh | sh
- |
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 security curl | sh installer runs before signing secrets are injected

curl -Ls https://cli.doppler.com/install.sh | sh executes an unverified remote script in the same shell session that subsequently calls doppler run ... -- bash scripts/release/sign-release-assets.sh. A compromised or MITMed installer could plant a doppler shim on PATH or create bash hooks that exfiltrate COSIGN_PRIVATE_KEY and COSIGN_PASSWORD the moment doppler run injects them. The same pattern appears in the smoke-published-release step (line ~53). Pinning the Doppler CLI to a known version and verifying its checksum before execution, or pre-baking it into the CI image, would remove this risk.

Prompt To Fix With AI
This is a comment left during a code review.
Path: .woodpecker.yml
Line: 33

Comment:
**`curl | sh` installer runs before signing secrets are injected**

`curl -Ls https://cli.doppler.com/install.sh | sh` executes an unverified remote script in the same shell session that subsequently calls `doppler run ... -- bash scripts/release/sign-release-assets.sh`. A compromised or MITMed installer could plant a `doppler` shim on `PATH` or create bash hooks that exfiltrate `COSIGN_PRIVATE_KEY` and `COSIGN_PASSWORD` the moment `doppler run` injects them. The same pattern appears in the `smoke-published-release` step (line ~53). Pinning the Doppler CLI to a known version and verifying its checksum before execution, or pre-baking it into the CI image, would remove this risk.

How can I resolve this? If you propose a fix, please make it concise.

TAG="$${CI_COMMIT_TAG:-$${CI_COMMIT_REF##refs/tags/}}"
if [ -z "$${TAG}" ]; then TAG="$(bash scripts/release/resolve-tag.sh)"; fi
test -n "$${TAG}" || (echo "TAG is required" && exit 1)
TOOL_DIR="$$(pwd)/.release-tools"
bash scripts/release/install-tool.sh cosign "$${TOOL_DIR}"
export PATH="$${TOOL_DIR}:$${PATH}"
doppler run --project profitctl --config prd_ci_woodpecker -- bash scripts/release/sign-release-assets.sh "$${TAG}"
doppler run --project profitctl --config prd_ci_woodpecker -- bash scripts/release/publish-github-release.sh "$${TAG}"
doppler run --project profitctl --config prd_ci_woodpecker -- bash deployment/releases/publish-to-vps.sh "$${TAG}"

Expand All @@ -46,6 +54,9 @@ steps:
TAG="$${CI_COMMIT_TAG:-$${CI_COMMIT_REF##refs/tags/}}"
if [ -z "$${TAG}" ]; then TAG="$(bash scripts/release/resolve-tag.sh)"; fi
test -n "$${TAG}" || (echo "TAG is required" && exit 1)
TOOL_DIR="$$(pwd)/.release-tools"
bash scripts/release/install-tool.sh cosign "$${TOOL_DIR}"
export PATH="$${TOOL_DIR}:$${PATH}"
doppler run --project profitctl --config prd_ci_woodpecker -- bash scripts/release/smoke-published-release.sh "$${TAG}"

---
Expand Down
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ All notable changes to this project will be documented in this file.
- GitHub Actions PR/main verification workflow with stable `verify-go` and `verify-install-smoke` checks.
- Homebrew formula and tap-publish script for the public release channel.
- Committed benchmark comparison reports for open-core and hybrid pricing scenarios.
- Release SBOM generation, detached Cosign signatures, and published verification key assets.

### Changed
- Canonical module/repository identity aligned to `IntelIP/ProfitCtl`.
Expand All @@ -22,3 +23,4 @@ All notable changes to this project will be documented in this file.
- Public installer defaults now use GitHub Releases as the canonical source, with the Hostinger mirror available as an explicit override.
- Open-core packaging docs now define who the product is for, what stays free, and what the first paid layer should cover.
- Install docs now include Homebrew and the Quick Start includes concrete output snippets.
- Release docs now include explicit archive, SBOM, and checksum verification steps.
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,12 @@ OPENROUTER_API_KEY=... profitctl detect --path . --out detect-report.json
Canonical public release artifacts are published on GitHub Releases:

- `https://github.com/IntelIP/ProfitCtl/releases`
- each release includes:
- per-platform archives
- per-platform SPDX JSON SBOMs
- detached Cosign signatures
- `SHA256SUMS` plus a detached signature
- `profitctl-release-cosign.pub` for offline verification

Operational mirrors may also publish to:
- `https://downloads.intelip.co/profitctl/releases/<tag>/`
Expand Down
4 changes: 3 additions & 1 deletion docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@

- Woodpecker pipeline file: `.woodpecker.yml`
- PR/main verification jobs on `pool=shared-kvm`
- Tag release jobs on `pool=builder`
- Tag release jobs on `pool=shared-kvm`
- GitHub Actions provides required public repo checks: `verify-go` and `verify-install-smoke`
- Canonical public release channel: GitHub Releases
- Public releases include detached Cosign signatures, SPDX JSON SBOMs, and the release verification public key
- Release artifacts mirrored to Hostinger VPS at `/opt/profitctl/releases`
- Optional mirror endpoint: `https://downloads.intelip.co/profitctl`
29 changes: 29 additions & 0 deletions docs/INSTALL.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,35 @@ profitctl --help
profitctl validate -f examples/mix_profit.yml
```

## Verify Release Integrity

Public GitHub releases include:

- detached Cosign signatures for each archive
- SPDX JSON SBOMs for each archive
- a signed `SHA256SUMS` manifest
- `profitctl-release-cosign.pub`

Example verification flow for `darwin_arm64`:

```bash
TAG=<tag>
curl -fsSLO "https://github.com/IntelIP/ProfitCtl/releases/download/${TAG}/profitctl_${TAG}_darwin_arm64.tar.gz"
curl -fsSLO "https://github.com/IntelIP/ProfitCtl/releases/download/${TAG}/profitctl_${TAG}_darwin_arm64.tar.gz.sig"
curl -fsSLO "https://github.com/IntelIP/ProfitCtl/releases/download/${TAG}/profitctl_${TAG}_darwin_arm64.spdx.json"
curl -fsSLO "https://github.com/IntelIP/ProfitCtl/releases/download/${TAG}/profitctl_${TAG}_darwin_arm64.spdx.json.sig"
curl -fsSLO "https://github.com/IntelIP/ProfitCtl/releases/download/${TAG}/SHA256SUMS"
curl -fsSLO "https://github.com/IntelIP/ProfitCtl/releases/download/${TAG}/SHA256SUMS.sig"
curl -fsSLO "https://github.com/IntelIP/ProfitCtl/releases/download/${TAG}/profitctl-release-cosign.pub"

cosign verify-blob --key profitctl-release-cosign.pub --signature "profitctl_${TAG}_darwin_arm64.tar.gz.sig" --insecure-ignore-tlog=true "profitctl_${TAG}_darwin_arm64.tar.gz"
cosign verify-blob --key profitctl-release-cosign.pub --signature "profitctl_${TAG}_darwin_arm64.spdx.json.sig" --insecure-ignore-tlog=true "profitctl_${TAG}_darwin_arm64.spdx.json"
cosign verify-blob --key profitctl-release-cosign.pub --signature SHA256SUMS.sig --insecure-ignore-tlog=true SHA256SUMS
shasum -a 256 -c SHA256SUMS
```

Use `brew install cosign` or the upstream Sigstore install path if `cosign` is not already available.

## Exit Codes

- `0`: success
Expand Down
6 changes: 4 additions & 2 deletions docs/deployment/WOODPECKER_HOSTINGER_SETUP.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ This runbook onboards `IntelIP/ProfitCtl` into your existing Woodpecker infrastr
## Required Secrets (Doppler: `profitctl` / `prd_ci_woodpecker`)

- `GITHUB_TOKEN_RELEASE`
- `COSIGN_PRIVATE_KEY`
- `COSIGN_PASSWORD`
- `VPS_HOST`
- `VPS_USER`
- `VPS_SSH_PRIVATE_KEY`
Expand Down Expand Up @@ -79,10 +81,10 @@ Tag push only (semver):

## GitHub Actions

Actions are disabled to enforce Woodpecker-only CI/CD.
GitHub Actions is enabled for repository-facing verification (`verify-go` and `verify-install-smoke`), while Woodpecker remains the authoritative tag release pipeline.

```bash
printf '{"enabled":false}' | gh api repos/IntelIP/ProfitCtl/actions/permissions -X PUT --input -
gh api repos/IntelIP/ProfitCtl/actions/permissions
```

## Rollback
Expand Down
24 changes: 20 additions & 4 deletions docs/release/MVP_PRICING_RELEASE.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,18 +31,34 @@ Use `GOCACHE` and `GOTMPDIR` overrides in restricted environments.
2. Run the smoke tests above.
3. Run the simulation benchmark command so the core economics path has a fresh baseline.
4. Review benchmark scenario outputs in `benchmark_scenarios/README.md`.
5. Tag the release and publish binaries.
5. Tag the release and publish binaries, SBOMs, and detached signatures.
6. Run the published-artifact smoke path:
- `bash scripts/release/smoke-published-release.sh <tag>`
7. Point downstream docs or GTM collateral to:
7. Verify the release contains:
- `profitctl_<tag>_<os>_<arch>.spdx.json`
- `profitctl_<tag>_<os>_<arch>.tar.gz.sig` or `profitctl_<tag>_<os>_<arch>.zip.sig`
- `SHA256SUMS.sig`
- `profitctl-release-cosign.pub`
8. Confirm the public verification instructions in `docs/INSTALL.md` still match the published assets.
9. Point downstream docs or GTM collateral to:
- `compare` for pricing review
- `calibrate` plus `calibration_file` for assumption grounding
- `operating_margin` covenants for contract safety checks
8. Verify the exact public install path from the README against the new tag:
10. Verify the exact public install path from the README against the new tag:
- `curl -fsSL https://raw.githubusercontent.com/IntelIP/ProfitCtl/main/scripts/install.sh | env -u PROFITCTL_DOWNLOAD_BASE_URL PROFITCTL_VERSION=<tag> bash`
9. Update and publish the Homebrew tap if `Formula/profitctl.rb` changed:
11. Update and publish the Homebrew tap if `Formula/profitctl.rb` changed:
- `bash scripts/release/publish-homebrew-tap.sh`

## Verification Model

The current release pipeline uses Cosign key-pair signing because the authoritative release pipeline runs in Woodpecker, not GitHub Actions. That means releases do not currently use GitHub OIDC keyless signing or Rekor-backed transparency bundles. Consumers verify with the committed and published `profitctl-release-cosign.pub` key instead.

This is a deliberate tradeoff:

- it fits the current release infrastructure
- it provides deterministic offline verification for archives, SBOMs, and checksum manifests
- it keeps the upgrade path open if release publishing moves to an OIDC-capable environment later

## Suggested Release Notes

`ProfitCtl` now supports open-core pricing comparison and calibration workflows end to end. Teams can model tiered, mix, and hybrid contracts; separate booked from operating economics; ingest normalized calibration exports; and enforce recurring-margin guardrails in covenant checks.
4 changes: 4 additions & 0 deletions keys/profitctl-release-cosign.pub
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEqanVKBvzbrySoIvurg2hvQ/BYcVX
oE457ag3IX77JbP3qCXoYD4Ws9NCMwhbIwE/Cu5LcZnpzxn3IyUAW95zUw==
-----END PUBLIC KEY-----
59 changes: 59 additions & 0 deletions scripts/release/generate-sboms.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
#!/usr/bin/env bash
set -euo pipefail

TAG="${1:-${CI_COMMIT_TAG:-}}"
if [[ -z "${TAG}" ]]; then
echo "TAG is required (arg1 or CI_COMMIT_TAG)" >&2
exit 1
fi

ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
OUT_DIR="${ROOT}/dist/${TAG}"

command -v syft >/dev/null 2>&1 || {
echo "syft is required on PATH" >&2
exit 1
}

mkdir -p "${OUT_DIR}"
TMP_DIR="$(mktemp -d)"
trap 'rm -rf "${TMP_DIR}"' EXIT

export SYFT_CHECK_FOR_APP_UPDATE=false
export XDG_CACHE_HOME="${TMP_DIR}/cache"
mkdir -p "${XDG_CACHE_HOME}"

artifact_stem() {
local file_name="$1"
file_name="${file_name%.tar.gz}"
file_name="${file_name%.zip}"
printf '%s\n' "${file_name}"
}

shopt -s nullglob
archives=("${OUT_DIR}"/profitctl_"${TAG}"_*.tar.gz "${OUT_DIR}"/profitctl_"${TAG}"_*.zip)
shopt -u nullglob

if [[ "${#archives[@]}" -eq 0 ]]; then
echo "no release archives found in ${OUT_DIR}" >&2
exit 1
fi

for archive in "${archives[@]}"; do
stem="$(artifact_stem "$(basename "${archive}")")"
syft scan "file:${archive}" \
--quiet \
--source-name "${stem}" \
--source-version "${TAG}" \
--output "spdx-json=${OUT_DIR}/${stem}.spdx.json"
done

syft scan "dir:${ROOT}" \
--quiet \
--exclude "./.git" \
--exclude "./dist" \
--source-name "profitctl-source" \
--source-version "${TAG}" \
--output "spdx-json=${OUT_DIR}/profitctl_${TAG}_source.spdx.json"

echo "SBOMs written in ${OUT_DIR}"
62 changes: 62 additions & 0 deletions scripts/release/install-tool.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
#!/usr/bin/env bash
set -euo pipefail

TOOL="${1:-}"
INSTALL_DIR="${2:-$(pwd)/.release-tools}"

if [[ -z "${TOOL}" ]]; then
echo "usage: $0 <syft|cosign> [install-dir]" >&2
exit 1
fi

OS_NAME="$(uname -s | tr '[:upper:]' '[:lower:]')"
case "${OS_NAME}" in
linux|darwin) ;;
*)
echo "unsupported operating system for ${TOOL}: $(uname -s)" >&2
exit 1
;;
esac

case "$(uname -m)" in
x86_64|amd64) ARCH_NAME="amd64" ;;
arm64|aarch64) ARCH_NAME="arm64" ;;
*)
echo "unsupported architecture for ${TOOL}: $(uname -m)" >&2
exit 1
;;
esac

mkdir -p "${INSTALL_DIR}"
TMP_DIR="$(mktemp -d)"
trap 'rm -rf "${TMP_DIR}"' EXIT

download() {
local url="$1"
local output="$2"
curl -fsSL --retry 5 --retry-all-errors "${url}" -o "${output}"
}

case "${TOOL}" in
syft)
VERSION="${SYFT_VERSION:-v1.42.4}"
VERSION_NO_V="${VERSION#v}"
ARCHIVE="syft_${VERSION_NO_V}_${OS_NAME}_${ARCH_NAME}.tar.gz"
URL="https://github.com/anchore/syft/releases/download/${VERSION}/${ARCHIVE}"
download "${URL}" "${TMP_DIR}/${ARCHIVE}"
tar -xzf "${TMP_DIR}/${ARCHIVE}" -C "${INSTALL_DIR}" syft
chmod +x "${INSTALL_DIR}/syft"
;;
cosign)
VERSION="${COSIGN_VERSION:-v3.0.6}"
URL="https://github.com/sigstore/cosign/releases/download/${VERSION}/cosign-${OS_NAME}-${ARCH_NAME}"
download "${URL}" "${INSTALL_DIR}/cosign"
chmod +x "${INSTALL_DIR}/cosign"
Comment on lines +50 to +54
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 security cosign binary downloaded without integrity verification

The cosign binary is fetched over HTTPS and made executable immediately, with no checksum or signature check. If the GitHub release asset is tampered with or the CDN is compromised, the binary used to sign every subsequent release artifact would be malicious — undermining the entire signing chain. Sigstore publishes cosign-checksums.txt alongside each release; verifying the SHA-256 of the downloaded binary against that file (or a pinned hash in this repo) would close this gap.

Suggested change
cosign)
VERSION="${COSIGN_VERSION:-v3.0.6}"
URL="https://github.com/sigstore/cosign/releases/download/${VERSION}/cosign-${OS_NAME}-${ARCH_NAME}"
download "${URL}" "${INSTALL_DIR}/cosign"
chmod +x "${INSTALL_DIR}/cosign"
cosign)
VERSION="${COSIGN_VERSION:-v3.0.6}"
CHECKSUM_URL="https://github.com/sigstore/cosign/releases/download/${VERSION}/cosign-checksums.txt"
BINARY="cosign-${OS_NAME}-${ARCH_NAME}"
URL="https://github.com/sigstore/cosign/releases/download/${VERSION}/${BINARY}"
download "${URL}" "${TMP_DIR}/${BINARY}"
download "${CHECKSUM_URL}" "${TMP_DIR}/cosign-checksums.txt"
(cd "${TMP_DIR}" && grep "${BINARY}" cosign-checksums.txt | sha256sum --check --status)
cp "${TMP_DIR}/${BINARY}" "${INSTALL_DIR}/cosign"
chmod +x "${INSTALL_DIR}/cosign"
;;
Prompt To Fix With AI
This is a comment left during a code review.
Path: scripts/release/install-tool.sh
Line: 50-54

Comment:
**cosign binary downloaded without integrity verification**

The cosign binary is fetched over HTTPS and made executable immediately, with no checksum or signature check. If the GitHub release asset is tampered with or the CDN is compromised, the binary used to sign every subsequent release artifact would be malicious — undermining the entire signing chain. Sigstore publishes `cosign-checksums.txt` alongside each release; verifying the SHA-256 of the downloaded binary against that file (or a pinned hash in this repo) would close this gap.

```suggestion
  cosign)
    VERSION="${COSIGN_VERSION:-v3.0.6}"
    CHECKSUM_URL="https://github.com/sigstore/cosign/releases/download/${VERSION}/cosign-checksums.txt"
    BINARY="cosign-${OS_NAME}-${ARCH_NAME}"
    URL="https://github.com/sigstore/cosign/releases/download/${VERSION}/${BINARY}"
    download "${URL}" "${TMP_DIR}/${BINARY}"
    download "${CHECKSUM_URL}" "${TMP_DIR}/cosign-checksums.txt"
    (cd "${TMP_DIR}" && grep "${BINARY}" cosign-checksums.txt | sha256sum --check --status)
    cp "${TMP_DIR}/${BINARY}" "${INSTALL_DIR}/cosign"
    chmod +x "${INSTALL_DIR}/cosign"
    ;;
```

How can I resolve this? If you propose a fix, please make it concise.

;;
*)
echo "unsupported tool: ${TOOL}" >&2
exit 1
;;
esac

echo "${INSTALL_DIR}/${TOOL}"
8 changes: 7 additions & 1 deletion scripts/release/publish-github-release.sh
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,16 @@ export GH_TOKEN="${GITHUB_TOKEN_RELEASE}"

ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
OUT_DIR="${ROOT}/dist/${TAG}"
PUBLIC_KEY="${ROOT}/keys/profitctl-release-cosign.pub"

[[ -f "${PUBLIC_KEY}" ]] || {
echo "missing release public key: ${PUBLIC_KEY}" >&2
exit 1
}

if ! gh release view "${TAG}" --repo IntelIP/ProfitCtl >/dev/null 2>&1; then
gh release create "${TAG}" --repo IntelIP/ProfitCtl --title "${TAG}" --notes "Automated Woodpecker release for ${TAG}."
fi

gh release upload "${TAG}" "${OUT_DIR}"/profitctl_* "${OUT_DIR}"/SHA256SUMS --repo IntelIP/ProfitCtl --clobber
gh release upload "${TAG}" "${OUT_DIR}"/profitctl_* "${OUT_DIR}"/SHA256SUMS "${OUT_DIR}"/SHA256SUMS.sig "${PUBLIC_KEY}" --repo IntelIP/ProfitCtl --clobber
echo "Published GitHub release assets for ${TAG}"
64 changes: 64 additions & 0 deletions scripts/release/sign-release-assets.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
#!/usr/bin/env bash
set -euo pipefail

TAG="${1:-${CI_COMMIT_TAG:-}}"
if [[ -z "${TAG}" ]]; then
echo "TAG is required (arg1 or CI_COMMIT_TAG)" >&2
exit 1
fi

if [[ -z "${COSIGN_PRIVATE_KEY:-}" ]]; then
echo "COSIGN_PRIVATE_KEY is required" >&2
exit 1
fi

if [[ -z "${COSIGN_PASSWORD:-}" ]]; then
echo "COSIGN_PASSWORD is required" >&2
exit 1
fi

ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
OUT_DIR="${ROOT}/dist/${TAG}"

command -v cosign >/dev/null 2>&1 || {
echo "cosign is required on PATH" >&2
exit 1
}

TMP_DIR="$(mktemp -d)"
trap 'rm -rf "${TMP_DIR}"' EXIT
export HOME="${TMP_DIR}/home"
mkdir -p "${HOME}"

sign_blob() {
local blob_path="$1"
rm -f "${blob_path}.sig"
cosign sign-blob \
--key env://COSIGN_PRIVATE_KEY \
--tlog-upload=false \
--use-signing-config=false \
--new-bundle-format=false \
--output-signature "${blob_path}.sig" \
--yes \
"${blob_path}" >/dev/null
}

targets=()
while IFS= read -r target; do
targets+=("${target}")
done < <(
find "${OUT_DIR}" -maxdepth 1 -type f \
\( -name "profitctl_${TAG}_*.tar.gz" -o -name "profitctl_${TAG}_*.zip" -o -name "profitctl_${TAG}_*.spdx.json" -o -name "SHA256SUMS" \) \
| sort
)

if [[ "${#targets[@]}" -eq 0 ]]; then
echo "no release assets found to sign in ${OUT_DIR}" >&2
exit 1
fi

for target in "${targets[@]}"; do
sign_blob "${target}"
done

echo "Signatures written in ${OUT_DIR}"
Loading
Loading