scripts: add onboard-product.sh, teach doctor-channels.sh to check signing secrets
breadcast shipped with a full bakery.toml + CI workflows but was missing from registry/bread-ecosystem.toml and had zero Forgejo Actions secrets configured, so its release workflows would have failed closed (or worse, published unsigned on an older workflow shape) the first time they ran. Neither gap was visible until checked by hand. doctor-channels.sh now also flags any registry product's repo missing the BAKERY_MINISIGN_SEC_KEY_PATH secret (soft-skipped without a local Forgejo token). onboard-product.sh handles the one genuine write step — adding a [[products]] entry — then runs doctor-channels.sh so nothing else gets missed silently again. Also fixes a pre-existing false positive where the local-checkout drift scan didn't recognize worktree checkouts of bread-ecosystem itself beyond the one literal "-fix-worktree" suffix it special-cased.
This commit is contained in:
parent
227247907b
commit
a4f0c96b90
2 changed files with 117 additions and 2 deletions
|
|
@ -14,9 +14,20 @@
|
|||
#
|
||||
# scripts/doctor-channels.sh ~/Projects
|
||||
#
|
||||
# Also checks every registry product's Forgejo repo for the
|
||||
# BAKERY_MINISIGN_SEC_KEY_PATH Actions secret (via the Forgejo API) — a
|
||||
# split-out product repo silently missing this secret is exactly the gotcha
|
||||
# that bit breadcast's onboarding: its release workflows would either fail
|
||||
# the hard-fail guard (dev/rc-style workflows) or, worse, publish unsigned
|
||||
# (older release.yml-style workflows without that guard). Skipped with a
|
||||
# warning (not a failure) if ~/.config/forgejo/token doesn't exist, so this
|
||||
# still runs for anyone without API access — set SKIP_SECRETS_CHECK=1 to
|
||||
# skip it deliberately (e.g. offline).
|
||||
#
|
||||
# Exits 0 if no drift found, 1 if any repo has drift (so it's CI-friendly).
|
||||
#
|
||||
# Requires: python3 (tomllib, stdlib since 3.11)
|
||||
# Requires: python3 (tomllib, stdlib since 3.11); curl + a Forgejo token at
|
||||
# ~/.config/forgejo/token for the secrets check (soft-skipped without one).
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
|
|
@ -72,7 +83,10 @@ for dir in "${BASE_DIR}"/*/; do
|
|||
# Skip bread-ecosystem itself — it's a multi-product repo the registry
|
||||
# membership check above doesn't map 1:1, and it's already reviewed by
|
||||
# hand above (bakery + bread-theme products).
|
||||
[[ "${name}" == "bread-ecosystem" ]] && continue
|
||||
# Prefix match, not exact: bread-ecosystem-breadcast, bread-ecosystem-onboard,
|
||||
# etc. are all worktree checkouts of this same multi-product repo, not
|
||||
# separate products the registry-membership check below applies to.
|
||||
[[ "${name}" == bread-ecosystem* ]] && continue
|
||||
|
||||
checked=$((checked + 1))
|
||||
has_bakery_toml=0
|
||||
|
|
@ -125,6 +139,55 @@ done
|
|||
|
||||
echo
|
||||
echo "checked ${checked} repos under ${BASE_DIR}"
|
||||
|
||||
# --- Secrets check ---------------------------------------------------------
|
||||
TOKEN_FILE="${HOME}/.config/forgejo/token"
|
||||
if [[ -n "${SKIP_SECRETS_CHECK:-}" ]]; then
|
||||
echo "skipping secrets check (SKIP_SECRETS_CHECK set)"
|
||||
elif [[ ! -f "${TOKEN_FILE}" ]]; then
|
||||
echo "skipping secrets check (no token at ${TOKEN_FILE})"
|
||||
else
|
||||
TOKEN="$(cat "${TOKEN_FILE}")"
|
||||
FORGEJO_API="https://git.breadway.dev/api/v1"
|
||||
|
||||
# Full "owner/repo" slugs, deduplicated (bakery + bread-theme both point
|
||||
# at Breadway/bread-ecosystem, checking it twice is wasted API calls).
|
||||
mapfile -t registry_slugs < <(python3 -c "
|
||||
import tomllib
|
||||
with open('${REGISTRY}', 'rb') as f:
|
||||
d = tomllib.load(f)
|
||||
seen = set()
|
||||
for p in d['products']:
|
||||
if p['repo'] not in seen:
|
||||
seen.add(p['repo'])
|
||||
print(p['repo'])
|
||||
")
|
||||
|
||||
secrets_missing=0
|
||||
for slug in "${registry_slugs[@]}"; do
|
||||
has_key="$(curl -s -H "Authorization: token ${TOKEN}" \
|
||||
"${FORGEJO_API}/repos/${slug}/actions/secrets" \
|
||||
| python3 -c "
|
||||
import json, sys
|
||||
try:
|
||||
secrets = json.load(sys.stdin)
|
||||
except json.JSONDecodeError:
|
||||
secrets = []
|
||||
print(1 if any(s.get('name') == 'BAKERY_MINISIGN_SEC_KEY_PATH' for s in secrets) else 0)
|
||||
" 2>/dev/null || echo 0)"
|
||||
if [[ "${has_key}" != 1 ]]; then
|
||||
echo "${slug}: missing BAKERY_MINISIGN_SEC_KEY_PATH Actions secret — release CI will fail closed (or worse, publish unsigned on an older workflow shape) until it's set"
|
||||
secrets_missing=1
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ "${secrets_missing}" == 0 ]]; then
|
||||
echo "no missing signing secrets found across ${#registry_slugs[@]} registry repos"
|
||||
else
|
||||
drift=1
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ "${drift}" == 0 ]]; then
|
||||
echo "no channel drift found"
|
||||
else
|
||||
|
|
|
|||
52
scripts/onboard-product.sh
Executable file
52
scripts/onboard-product.sh
Executable file
|
|
@ -0,0 +1,52 @@
|
|||
#!/usr/bin/env bash
|
||||
# onboard-product.sh — register a new product in registry/bread-ecosystem.toml.
|
||||
#
|
||||
# This is the one step in bringing a product under bakery that's a genuine
|
||||
# write action; everything else (bakery.toml, CI workflows, the
|
||||
# BAKERY_MINISIGN_SEC_KEY_PATH Actions secret) is either copied from an
|
||||
# existing product repo or diagnosed by scripts/doctor-channels.sh, which
|
||||
# this script runs at the end so nothing gets missed the way breadcast's
|
||||
# missing secret did.
|
||||
#
|
||||
# Usage:
|
||||
# scripts/onboard-product.sh <name> <owner/repo> <description>
|
||||
#
|
||||
# Example:
|
||||
# scripts/onboard-product.sh breadcast Breadway/breadcast \
|
||||
# "Cast your screen to any Chromecast/Google TV or DLNA renderer — daemon + GTK4 popup"
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
if [[ $# -ne 3 ]]; then
|
||||
echo "usage: $0 <name> <owner/repo> <description>" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
NAME="$1"
|
||||
REPO="$2"
|
||||
DESCRIPTION="$3"
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
REGISTRY="${SCRIPT_DIR}/registry/bread-ecosystem.toml"
|
||||
|
||||
if python3 -c "
|
||||
import tomllib, sys
|
||||
with open('${REGISTRY}', 'rb') as f:
|
||||
d = tomllib.load(f)
|
||||
sys.exit(0 if any(p['name'] == '${NAME}' for p in d['products']) else 1)
|
||||
"; then
|
||||
echo "${NAME} is already registered in ${REGISTRY}, skipping"
|
||||
else
|
||||
cat >> "${REGISTRY}" <<EOF
|
||||
|
||||
[[products]]
|
||||
name = "${NAME}"
|
||||
repo = "${REPO}"
|
||||
description = "${DESCRIPTION}"
|
||||
EOF
|
||||
echo "added ${NAME} (${REPO}) to ${REGISTRY}"
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "running doctor-channels.sh to check bakery.toml / CI workflows / signing secret are all in place for ${NAME}..."
|
||||
bash "${SCRIPT_DIR}/scripts/doctor-channels.sh" || true
|
||||
Loading…
Add table
Add a link
Reference in a new issue