screentinker/scripts/finalize-release.sh
ScreenTinker 24067c39c4 chore(release): exclude local config from the release tarball, and gate on it
The complete-tarball step archives `server/` wholesale, and tar includes dotfiles,
so any gitignored local config living under it (.env, .mcp.json, key material) is
packaged unless excluded by name - gitignore has no effect on tar.

Two changes:
- Add excludes for .env / .env.* / .mcp.json and jks|keystore|pem|key|p12|pfx.
- Follow the tar with an audit that inspects what is actually IN the archive and
  refuses to upload when anything credential-shaped is present.

The exclude list fails OPEN (a new file added under server/ ships unless someone
remembers to exclude it); the audit fails CLOSED, which is the property that
matters. .env.example is deliberately shipped and is not matched.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-24 21:27:30 -05:00

69 lines
3.5 KiB
Bash
Executable file

#!/bin/bash
# Finalize a release with the artifacts that need the LOCAL signing keystore
# (which never goes into CI). After the release workflow has published the tag's
# GitHub Release (source tarball + unsigned .wgt + docker image), run this to:
# 1. build the SIGNED Android APK locally,
# 2. pull the CI-built unsigned .wgt back down from the release,
# 3. assemble a COMPLETE source tarball that bundles BOTH binaries
# (extract it and ScreenTinker.apk sits at the root, ready for /download/apk),
# 4. upload the APK + the complete tarball to the release (replacing the
# source-only tarball CI uploaded).
#
# KEYSTORE_PASSWORD=... KEY_PASSWORD=... scripts/finalize-release.sh
#
# Requires: Android SDK + the release keystore (android/release-key.jks), the
# Tizen .wgt already on the release, and an authenticated gh CLI.
set -euo pipefail
cd "$(dirname "$0")/.."
VERSION="$(cat VERSION)"
TAG="v$VERSION"
: "${KEYSTORE_PASSWORD:?set KEYSTORE_PASSWORD}"
: "${KEY_PASSWORD:?set KEY_PASSWORD}"
cleanup() { rm -f ScreenTinker.apk ScreenTinker.wgt "screentinker-$VERSION.tar.gz"; }
trap cleanup EXIT
echo "==> Building signed APK $VERSION"
( cd android && KEYSTORE_PASSWORD="$KEYSTORE_PASSWORD" KEY_PASSWORD="$KEY_PASSWORD" ./gradlew assembleRelease )
cp android/app/build/outputs/apk/release/app-release.apk ScreenTinker.apk
echo "==> Pulling the CI-built unsigned .wgt from release $TAG"
gh release download "$TAG" -p ScreenTinker.wgt --clobber
echo "==> Assembling complete tarball (source + apk + wgt)"
OUT="screentinker-$VERSION.tar.gz"
# NOTE: `tar` archives DOTFILES too, so anything secret sitting under server/ ships
# unless it is excluded by name. server/.env (Graph credentials) is gitignored, which
# is precisely why it never showed up in a diff - the exclude list is the only thing
# standing between it and a public release asset. Keep .env* and the local tooling
# configs here, and see the audit gate below, which is the real backstop.
tar czf "$OUT" \
--exclude='node_modules' --exclude='.git' --exclude='.github' \
--exclude='*.db' --exclude='*.db-wal' --exclude='*.db-shm' --exclude='*.db.*' \
--exclude='server/uploads' --exclude='server/certs' --exclude='server/test' \
--exclude='.env' --exclude='.env.*' --exclude='*/.env' --exclude='*/.env.*' \
--exclude='.mcp.json' --exclude='*/.mcp.json' \
--exclude='*.jks' --exclude='*.keystore' --exclude='*.pem' --exclude='*.key' \
--exclude='.jwt_secret' --exclude='*/.jwt_secret' \
server frontend scripts VERSION README.md LICENSE .env.example \
ScreenTinker.apk ScreenTinker.wgt
# Secret gate. The exclude list above fails OPEN - a new secret file added under
# server/ ships unless someone remembers to add it. This gate fails CLOSED: it
# inspects what is actually IN the archive and refuses to upload if anything
# credential-shaped made it in. .env.example is deliberately shipped and allowed.
echo "==> Auditing $OUT for credential-shaped files"
BAD="$(tar tzf "$OUT" | grep -E '(^|/)(\.env|\.env\..*|\.mcp\.json|\.jwt_secret)$|\.(jks|keystore|pem|key|p12|pfx)$' || true)"
if [ -n "$BAD" ]; then
echo "ERROR: refusing to upload - the archive contains credential-shaped files:" >&2
printf ' %s\n' $BAD >&2
echo " Add an --exclude for each, then re-run." >&2
exit 1
fi
echo " clean ($(tar tzf "$OUT" | wc -l) files)"
echo "==> Uploading APK + complete tarball to $TAG"
gh release upload "$TAG" "$OUT" ScreenTinker.apk --clobber
echo "==> Done: $TAG now carries the standalone APK and a tarball bundling apk + wgt."