#!/bin/bash # Export the working repo to the GitHub publication repo and emit a bundle. # # Pipeline: working repo (full history, internal refs) -> pruned/scrubbed # tree -> commit in ~/projects/shopdb-flask-pub (the local mirror of what # enterprise GitHub holds) -> full git bundle in /home/camp/pxe-images/ for # transfer to the work PC, which pushes it to GitHub. # # Usage: # tools/export-github.sh "Commit message for the publication commit" # tools/export-github.sh --dist # also rebuild BOTH frontend dists # # (/ops = dev, /shopdb = prod) # # This script lives in tools/, which is itself excluded from publication. set -euo pipefail WORK=/home/camp/projects/shopdb-flask PUB=/home/camp/projects/shopdb-flask-pub OUT=/home/camp/pxe-images/github BUILD_DIST=0 MSG="" for arg in "$@"; do case "$arg" in --dist) BUILD_DIST=1 ;; *) MSG="$arg" ;; esac done [ -n "$MSG" ] || { echo "usage: $0 [--dist] \"commit message\""; exit 1; } [ -d "$PUB/.git" ] || { echo "publication repo missing at $PUB"; exit 1; } # --- 1. sync the tree (working -> pub), minus everything never published --- # # NOTE the deploy/windows/installer/bundle exclude below. That is ~100MB of build # output holding a COPY of the whole application tree plus the wheels and vendor # installers. It is gitignored, but this sync walks the WORKING TREE rather than # git, so it came through anyway - and its copies of config.py and # requirements.txt then tripped the scrub gate. Anything else generated into the # working tree needs excluding here too, for the same reason. rsync -a --delete \ --exclude '.git' \ --exclude '.gitea' \ --exclude 'docs' \ --exclude 'tools' \ --exclude 'mcp' \ --exclude 'start-api.sh' \ --exclude 'start-ui.sh' \ --exclude 'CLAUDE.md' \ --exclude 'frontend/CLAUDE.md' \ --exclude 'tests/test_docs_contract.py' \ --exclude 'tests/test_plugins/test_geenforce_parity.py' \ --exclude 'tests/test_plugins/test_zabbix_live.py' \ --exclude 'venv' \ --exclude 'node_modules' \ --exclude 'deploy/windows/installer/bundle' \ --exclude 'deploy/windows/installer/Output' \ --exclude 'deploy/windows/installer/plugins.iss' \ --exclude 'deploy/windows/installer/version.iss' \ --exclude 'frontend/dist*' \ --exclude 'frontend/src/.plugins-staged' \ --exclude 'frontend/src/router/routes.gen.js' \ --exclude 'instance' \ --exclude '.env' \ --exclude '__pycache__' \ --exclude '*.pyc' \ --exclude 'scripts/site_imports/wjf/idmap.json' \ "$WORK/" "$PUB/" cd "$PUB" # --- 2. re-apply the publication wording (idempotent) --- # docs/ lives only in the wiki on the GitHub side. grep -rlZ 'docs/' --include='*.py' --include='*.md' --include='*.sh' \ --include='*.tmpl' --include='*.vue' --include='*.example' . 2>/dev/null | while IFS= read -r -d '' f; do case "$f" in ./CHANGELOG.md) continue ;; esac sed -i -E \ -e 's/\[`?docs\/([A-Za-z0-9_-]+)\.md`?\]\((\.\.\/)*docs\/[A-Za-z0-9_-]+\.md\)/the \1 page in the project wiki/g' \ -e 's/docs\/proposals\/ge-enforce-plugin\.md/the ge-enforce-plugin proposal in the project wiki/g' \ -e 's/`docs\/([A-Za-z0-9_-]+)\.md`/the \1 wiki page/g' \ -e 's/docs\/([A-Za-z0-9_-]+)\.md/the \1 wiki page/g' "$f" done sed -i 's|docs/GE-ENFORCE.md|the GE-ENFORCE page in the project wiki|' \ frontend/src/views/geenforce/ManifestEditor.vue 2>/dev/null || true # internal infra never named on GitHub; this repo's own URL maps to the # real GitHub home, anything else degrades to a placeholder. GITHUB_URL='https://github.com/ge-aero/shopdb-flask' grep -rlZ 'gitea\.proudtech\.net' . 2>/dev/null | while IFS= read -r -d '' f; do sed -i -e "s|https://gitea\.proudtech\.net/ge-aerospace/shopdb-flask|$GITHUB_URL|g" \ -e 's|gitea\.proudtech\.net||g' "$f" done grep -rlZi 'gitea' --exclude-dir=.git . 2>/dev/null | while IFS= read -r -d '' f; do sed -i -e 's/the GE Aerospace Gitea/the internal GE Aerospace git server/g' \ -e 's/Gitea Actions CI/CI/g' \ -e 's/Gitea Actions/CI/g' "$f" done # CHANGELOG compare/release link definitions reference tags that do not # exist on GitHub (history is squashed there) - drop them. sed -i '/^\[[^]]*\]: .*\/\(compare\|releases\)\//d' CHANGELOG.md # frontend/CLAUDE.md publishes under a neutral name if [ -f "$WORK/frontend/CLAUDE.md" ]; then cp "$WORK/frontend/CLAUDE.md" frontend/DEVELOPMENT-STANDARDS.md fi sed -i "s/rootpassword/changeme/g" shopdb/config.py 2>/dev/null || true # --- 3. scrub gate: refuse to commit if anything internal leaks --- LEAKS=$(grep -rlIiE 'claude|anthropic|fable 5|gitea|proudtech|home/camp|rootpassword' \ --exclude-dir=.git . || true) if [ -n "$LEAKS" ]; then echo "SCRUB GATE FAILED - internal references in:"; echo "$LEAKS"; exit 1 fi # --- 4. commit (no-op safe) --- git add -A if git diff --cached --quiet; then echo "no changes vs publication tree - nothing to export" else git commit -m "$MSG" fi git log --oneline -3 # --- 5. bundle for transfer (full bundle: stateless, fetch takes only new) --- git bundle create "$OUT/shopdb-flask-pub.bundle" HEAD main --tags git bundle verify "$OUT/shopdb-flask-pub.bundle" >/dev/null echo "bundle: $OUT/shopdb-flask-pub.bundle" # --- 6. optional per-instance dists (both bases: /ops = dev, /shopdb = prod) --- # Two instances run on the box; each needs its own base-path build. Always # rebuild BOTH so prod never ships a stale frontend. if [ "$BUILD_DIST" = 1 ]; then cd "$WORK/frontend" for base in ops shopdb; do VITE_BASE_PATH=/$base/ npm run build --silent rm -rf "$OUT/frontend-dist-subpath-$base" cp -r dist "$OUT/frontend-dist-subpath-$base" echo "dist: $OUT/frontend-dist-subpath-$base/ (base /$base/)" done fi cat <<'EOF' On the work PC (in the shopdb-flask-pub clone): git fetch \shopdb-flask-pub.bundle main git merge --ff-only FETCH_HEAD git push origin main EOF