open-swe/.github/scripts/package-artifacts.sh
Adam Moussa 9e2b215f08
Some checks are pending
Build & publish app artifacts / Publish + deploy (dev) (push) Waiting to run
Build & publish app artifacts / Publish + deploy (prod) (push) Waiting to run
Infra CD / Infra CI (pre-deploy) (push) Waiting to run
Infra CD / Deploy open-swe-dev (push) Blocked by required conditions
Infra CD / Deploy open-swe-prod (push) Blocked by required conditions
fix(ci): avoid tar|grep -q SIGPIPE false-failure in package-artifacts (#20)
`tar -tzf app.tar.gz | grep -qx` under set -o pipefail fails the pipeline when
grep -q matches and exits early (SIGPIPEs tar -> 'write error' -> non-zero), a
false 'missing agent/server.py'. List the archive once into a var, then grep the
var. Same fix for the secret-guard pipe (which was also silently broken).
2026-06-26 18:57:59 -04:00

52 lines
2.3 KiB
Bash
Executable file

#!/usr/bin/env bash
# Package the two release artifacts (run from the repo root by build-artifacts.yml):
#
# spa.tar.gz = CONTENTS of the built SPA dir (ui/.output/public/*), so it extracts
# straight into the nginx web root with _shell.html at the root.
# app.tar.gz = the Python source the box runs `uv sync` against. `git archive`
# gives a clean tree (no node_modules, no .venv, no local cruft);
# ui/ is intentionally excluded (it ships as spa.tar.gz).
set -euo pipefail
SPA_DIR="ui/.output/public"
[ -f "${SPA_DIR}/_shell.html" ] || {
echo "ERROR: SPA build output missing ${SPA_DIR}/_shell.html (did 'bun run build' run?)" >&2
exit 1
}
echo "==> spa.tar.gz from ${SPA_DIR}"
tar -C "${SPA_DIR}" -czf spa.tar.gz .
echo "==> app.tar.gz from source (git archive HEAD)"
git archive --format=tar.gz -o app.tar.gz HEAD \
agent deploy langgraph.json pyproject.toml uv.lock README.md
# List the archive ONCE into a variable. (`tar -tzf ... | grep -q ...` is unsafe
# under `set -o pipefail`: grep -q exits on first match, SIGPIPEs tar -> "write
# error" -> the pipeline reports non-zero even though grep succeeded, a false
# failure. Listing once avoids the pipe entirely.)
APP_LIST="$(tar -tzf app.tar.gz)"
# Sanity: the box's `uv sync --frozen` needs pyproject.toml + uv.lock at the root,
# and the package itself (agent/). Fail loudly here rather than on the box.
for required in pyproject.toml uv.lock agent/server.py langgraph.json; do
printf '%s\n' "${APP_LIST}" | grep -qx "${required}" || {
echo "ERROR: app.tar.gz is missing ${required}" >&2
exit 1
}
done
# Fail-closed secret guard: the source is git-archived wholesale, so reject the
# release if a secret-shaped FILE slipped into the tracked tree (defense in depth
# on top of .gitignore — the artifact lands on the box + in S3). Scoped to data
# extensions so credential-handling *source* (e.g. team_credentials.py) is not a
# false positive.
SECRET_RE='(^|/)(\.env(\..+)?|id_rsa|.*\.(pem|key|p12|pfx)|.*(secret|credential|password|token)s?\.(json|ya?ml|txt|env|ini|cfg))$'
if printf '%s\n' "${APP_LIST}" | grep -qiE "${SECRET_RE}"; then
echo "ERROR: app.tar.gz contains a secret-shaped file — refusing to publish:" >&2
printf '%s\n' "${APP_LIST}" | grep -iE "${SECRET_RE}" >&2
exit 1
fi
echo "==> artifacts:"
ls -la spa.tar.gz app.tar.gz