Some checks failed
Secret scan / Scan diff for credential-shaped strings (pull_request) Successful in 6s
CI / Shellcheck (E2E scripts) (pull_request) Successful in 7s
E2E Staging Canvas (Playwright) / Canvas tabs E2E (pull_request) Successful in 5s
CodeQL / Analyze (${{ matrix.language }}) (go) (pull_request) Failing after 54s
CI / Canvas Deploy Reminder (pull_request) Has been skipped
Block internal-flavored paths / Block forbidden paths (pull_request) Successful in 5s
CI / Detect changes (pull_request) Successful in 5s
E2E API Smoke Test / detect-changes (pull_request) Successful in 6s
E2E Staging Canvas (Playwright) / detect-changes (pull_request) Successful in 6s
Handlers Postgres Integration / detect-changes (pull_request) Successful in 6s
Runtime PR-Built Compatibility / detect-changes (pull_request) Successful in 6s
CI / Platform (Go) (pull_request) Successful in 3s
CI / Python Lint & Test (pull_request) Successful in 3s
E2E API Smoke Test / E2E API Smoke Test (pull_request) Successful in 3s
Handlers Postgres Integration / Handlers Postgres Integration (pull_request) Successful in 5s
Runtime PR-Built Compatibility / PR-built wheel + import smoke (pull_request) Successful in 4s
Ops Scripts Tests / Ops scripts (unittest) (pull_request) Failing after 13s
CI / Canvas (Next.js) (pull_request) Successful in 42s
CodeQL / Analyze (${{ matrix.language }}) (javascript-typescript) (pull_request) Failing after 1m18s
CodeQL / Analyze (${{ matrix.language }}) (python) (pull_request) Failing after 1m20s
Per documentation-specialist's grep agent (2026-05-07T07:30, see internal#46): runtime-breaking ghcr.io references in shell scripts + docker-compose + the slip-past-workflow lint_secret_pattern_drift.py all need migration. These were missed by security-auditor's workflow-only audit. Files (6): - .github/scripts/lint_secret_pattern_drift.py:40 — workspace-runtime pre-commit-checks.sh consumer URL: raw.githubusercontent.com → Gitea raw URL (https://git.moleculesai.app/molecule-ai/.../raw/ branch/main/...). The lint job runs in CI and would 404 today. - scripts/refresh-workspace-images.sh:54 — workspace-template image pull URL: ghcr.io → ECR (153263036946.dkr.ecr.us-east-2.amazonaws.com). - scripts/rollback-latest.sh — full rewrite of header + auth flow: * ghcr.io/molecule-ai/{platform,platform-tenant} → ECR * GITHUB_TOKEN with write:packages → AWS ECR auth (aws ecr get-login-password). Per saved memory reference_post_suspension_pipeline, prod cutover is to ECR. * Updated header docs to match new auth flow + prereqs. - scripts/demo-freeze.sh:13,17 — comment-only ghcr → ECR (the script doesn't currently exec these URLs, but the comments describe the cascade and need to match reality). - docker-compose.yml:215-216 — canvas image: ghcr.io → ECR + updated the auth comment to describe `aws ecr get-login-password` flow. - tools/check-template-parity.sh:21 — inline curl install instructions: raw.githubusercontent.com → Gitea raw URL. Hostile self-review: 1. rollback-latest.sh's GITHUB_TOKEN→aws-cli auth swap is a behavior change. Operators using this script now need aws CLI authenticated for region us-east-2 with ECR pull/push perms. Documented in updated header. Operators who don't have aws CLI will get 'aws: command not installed' which is a clear failure mode (not silent). 2. The Gitea raw URL shape (/raw/branch/main/) differs from GitHub's raw.githubusercontent.com structure. Verified pattern by inspecting other Gitea raw URLs in the codebase. If Gitea's URL changes (1.23+), update via the same one-line edit. 3. Doesn't touch packer/scripts/install-base.sh which has a similar ghcr.io ref per the grep agent's findings — that's bigger-scope (packer build pipeline) and lives in molecule-controlplane-ish territory; filing as parked follow-up under #46 if not already. Refs: molecule-ai/internal#46, molecule-ai/internal#37, molecule-ai/internal#38, saved memory reference_post_suspension_pipeline
215 lines
7.3 KiB
Bash
Executable File
215 lines
7.3 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# demo-freeze.sh — disable the runtime + template image publish cascades
|
|
# during a demo-prep window so a stray staging merge can't auto-rebuild
|
|
# `:latest` for the 8 workspace-template images mid-demo.
|
|
#
|
|
# Demo prep typically runs T-48h to T+1h. During that window:
|
|
#
|
|
# PATH 1: any merge to molecule-core/staging that touches workspace/**
|
|
# → publish-runtime.yml fires
|
|
# → PyPI auto-bumps molecule-ai-workspace-runtime patch version
|
|
# → repository_dispatch fans out to 8 workspace-template-* repos
|
|
# → each template repo rebuilds and re-tags
|
|
# 153263036946.dkr.ecr.us-east-2.amazonaws.com/molecule-ai/workspace-template-<runtime>:latest
|
|
#
|
|
# PATH 2: any merge to a workspace-template-* repo's main branch
|
|
# → that repo's publish-image.yml fires
|
|
# → 153263036946.dkr.ecr.us-east-2.amazonaws.com/molecule-ai/workspace-template-<runtime>:latest
|
|
# gets re-tagged
|
|
#
|
|
# provisioner.go:296 RuntimeImages[runtime] reads `:latest` at every
|
|
# workspace boot. A new workspace provision during demo pulls whatever
|
|
# `:latest` resolved to seconds earlier — so a bad merge minutes
|
|
# before the demo can break a tenant the funder is about to see.
|
|
#
|
|
# This script captures the current good `:latest` digests for all 8
|
|
# templates and disables both cascade vectors. The complementary
|
|
# demo-thaw.sh re-enables them.
|
|
#
|
|
# Usage:
|
|
# scripts/demo-freeze.sh # dry run — print what would happen
|
|
# scripts/demo-freeze.sh --execute # actually disable workflows + snapshot
|
|
#
|
|
# Prereqs:
|
|
# - gh CLI authenticated with workflow:write scope on Molecule-AI org
|
|
# - curl + jq (for digest snapshot via GHCR anonymous registry API)
|
|
#
|
|
# Output:
|
|
# <snapshot dir>/digests-YYYYMMDD-HHMMSS.txt
|
|
# One line per template: "<runtime>: <digest>"
|
|
# <snapshot dir>/disabled-workflows-YYYYMMDD-HHMMSS.txt
|
|
# One line per disabled workflow: "<repo>: <workflow>"
|
|
#
|
|
# Exit codes:
|
|
# 0 — freeze complete (or dry-run successful)
|
|
# 1 — pre-flight failure (missing tooling, missing auth, etc.)
|
|
# 2 — partial freeze (some workflows did not disable cleanly; see log)
|
|
|
|
set -euo pipefail
|
|
|
|
usage() {
|
|
cat <<'USAGE'
|
|
demo-freeze.sh — disable the runtime + template image publish cascades
|
|
during a demo-prep window.
|
|
|
|
Captures current :latest digests for all 8 workspace-template-* images
|
|
and disables the workflows that would otherwise re-tag them.
|
|
|
|
Usage:
|
|
scripts/demo-freeze.sh # dry run — print what would happen
|
|
scripts/demo-freeze.sh --execute # actually disable workflows + snapshot
|
|
|
|
See the comment block at the top of this script for the full procedure.
|
|
USAGE
|
|
}
|
|
|
|
EXECUTE=0
|
|
case "${1:-}" in
|
|
--execute)
|
|
EXECUTE=1
|
|
;;
|
|
--help|-h)
|
|
usage
|
|
exit 0
|
|
;;
|
|
"")
|
|
;;
|
|
*)
|
|
echo "unknown arg: $1" >&2
|
|
usage >&2
|
|
exit 2
|
|
;;
|
|
esac
|
|
|
|
# Templates and their GHCR repository slugs. Source of truth for the
|
|
# runtime → image map is workspace-server/internal/provisioner/provisioner.go
|
|
# RuntimeImages — keep this list in sync if a runtime is added.
|
|
TEMPLATES=(
|
|
"claude-code"
|
|
"hermes"
|
|
"openclaw"
|
|
"langgraph"
|
|
"deepagents"
|
|
"crewai"
|
|
"autogen"
|
|
"gemini-cli"
|
|
)
|
|
|
|
# Pre-flight: required tooling.
|
|
need() {
|
|
command -v "$1" >/dev/null || { echo "ERROR: missing required tool: $1" >&2; exit 1; }
|
|
}
|
|
need gh
|
|
need curl
|
|
need jq
|
|
|
|
# Pre-flight: gh auth. Snapshot via anonymous GHCR token works without
|
|
# org auth, but workflow disable needs an authenticated gh.
|
|
if ! gh auth status >/dev/null 2>&1; then
|
|
echo "ERROR: gh not authenticated. Run 'gh auth login' first." >&2
|
|
exit 1
|
|
fi
|
|
|
|
# Snapshot location relative to this script. Keeping it under scripts/
|
|
# rather than a temp dir means freeze receipts are easy to find again
|
|
# during the actual demo.
|
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
SNAPSHOT_DIR="${SCRIPT_DIR}/demo-freeze-snapshots"
|
|
mkdir -p "$SNAPSHOT_DIR"
|
|
TS="$(date -u +%Y%m%d-%H%M%S)"
|
|
DIGESTS_FILE="${SNAPSHOT_DIR}/digests-${TS}.txt"
|
|
WORKFLOWS_FILE="${SNAPSHOT_DIR}/disabled-workflows-${TS}.txt"
|
|
|
|
if [ $EXECUTE -eq 0 ]; then
|
|
echo "=== DRY RUN (no changes will be made; pass --execute to apply) ==="
|
|
else
|
|
echo "=== EXECUTING FREEZE — workflows will be disabled ==="
|
|
fi
|
|
echo "Snapshot timestamp: $TS"
|
|
echo "Digest log: $DIGESTS_FILE"
|
|
echo "Workflow log: $WORKFLOWS_FILE"
|
|
echo
|
|
|
|
# Step 1: capture current :latest digest for each template.
|
|
echo "→ Capturing current :latest digests"
|
|
for tpl in "${TEMPLATES[@]}"; do
|
|
token=$(curl -fsS "https://ghcr.io/token?scope=repository:molecule-ai/workspace-template-${tpl}:pull" | jq -r .token 2>/dev/null || true)
|
|
if [ -z "$token" ] || [ "$token" = "null" ]; then
|
|
echo " WARN: token fetch failed for $tpl — skipping digest capture"
|
|
continue
|
|
fi
|
|
digest=$(curl -fsSI \
|
|
-H "Authorization: Bearer $token" \
|
|
-H "Accept: application/vnd.oci.image.index.v1+json" \
|
|
-H "Accept: application/vnd.docker.distribution.manifest.v2+json" \
|
|
"https://ghcr.io/v2/molecule-ai/workspace-template-${tpl}/manifests/latest" 2>/dev/null \
|
|
| grep -i 'docker-content-digest' \
|
|
| awk '{print $2}' \
|
|
| tr -d '\r')
|
|
if [ -z "$digest" ]; then
|
|
echo " WARN: digest fetch failed for $tpl"
|
|
continue
|
|
fi
|
|
echo " $tpl: $digest"
|
|
if [ $EXECUTE -eq 1 ]; then
|
|
echo "$tpl: $digest" >> "$DIGESTS_FILE"
|
|
fi
|
|
done
|
|
echo
|
|
|
|
# Step 2: disable publish-runtime.yml in molecule-core (PATH 1 source).
|
|
echo "→ Disabling publish-runtime.yml in molecule-core (kills runtime → 8-template cascade)"
|
|
if [ $EXECUTE -eq 1 ]; then
|
|
if gh workflow disable publish-runtime.yml -R Molecule-AI/molecule-core 2>/tmp/freeze.err; then
|
|
echo " OK molecule-core/publish-runtime.yml disabled"
|
|
echo "Molecule-AI/molecule-core: publish-runtime.yml" >> "$WORKFLOWS_FILE"
|
|
else
|
|
echo " FAIL molecule-core/publish-runtime.yml: $(cat /tmp/freeze.err)" >&2
|
|
fi
|
|
else
|
|
echo " (dry-run) would disable: gh workflow disable publish-runtime.yml -R Molecule-AI/molecule-core"
|
|
fi
|
|
echo
|
|
|
|
# Step 3: disable publish-image.yml in each of the 8 template repos (PATH 2 sources).
|
|
echo "→ Disabling publish-image.yml in each workspace-template-* repo"
|
|
PARTIAL_FAIL=0
|
|
for tpl in "${TEMPLATES[@]}"; do
|
|
repo="Molecule-AI/molecule-ai-workspace-template-${tpl}"
|
|
if [ $EXECUTE -eq 1 ]; then
|
|
if gh workflow disable publish-image.yml -R "$repo" 2>/tmp/freeze.err; then
|
|
echo " OK $repo/publish-image.yml disabled"
|
|
echo "${repo}: publish-image.yml" >> "$WORKFLOWS_FILE"
|
|
else
|
|
echo " FAIL $repo/publish-image.yml: $(cat /tmp/freeze.err)" >&2
|
|
PARTIAL_FAIL=1
|
|
fi
|
|
else
|
|
echo " (dry-run) would disable: gh workflow disable publish-image.yml -R $repo"
|
|
fi
|
|
done
|
|
echo
|
|
|
|
if [ $EXECUTE -eq 0 ]; then
|
|
echo "=== DRY RUN COMPLETE ==="
|
|
echo "Re-run with --execute to apply the freeze."
|
|
exit 0
|
|
fi
|
|
|
|
echo "=== FREEZE COMPLETE ==="
|
|
echo "Receipts: $DIGESTS_FILE"
|
|
echo " $WORKFLOWS_FILE"
|
|
echo
|
|
echo "Next steps:"
|
|
echo " - Verify by running: gh workflow list -R Molecule-AI/molecule-core | grep publish-runtime"
|
|
echo " Status should be 'disabled_manually'."
|
|
echo " - Demo proceeds; new workspaces pull the snapshotted :latest digests."
|
|
echo " - Post-demo, run: scripts/demo-thaw.sh ${TS}"
|
|
echo " to re-enable every workflow this freeze disabled."
|
|
echo
|
|
if [ $PARTIAL_FAIL -ne 0 ]; then
|
|
echo "WARNING: one or more workflows did not disable cleanly. Re-run after fixing." >&2
|
|
exit 2
|
|
fi
|
|
exit 0
|