The scanner has been reporting the same count for weeks, which is what a rule that only prints becomes. It now FAILS the build, and it looks where the leaks actually were: PowerShell, the installer, the seeds, generated JSON, the frontend - case-insensitively, across plugins, shopdb, scripts, deploy, tools. A line that is deliberate declares itself with an ADR-015-OK marker and a reason, so the claim is visible in review instead of tolerated in silence. What it found, fixed here: - The shadow client wrote one site's ShopDB URL into HKLM whenever the registry disagreed. At the site it was written for that reads as healing drift; anywhere else it overwrites the site's own address on every enforce cycle, and the site cannot win because the cycle repeats. The bay's value now wins, an explicit -BaseUrl seeds it, and with neither there is nothing honest to write, so it says so and skips. - The kiosk dispatcher fell back to one plant's host when HKLM was unset, so a kiosk elsewhere quietly opened a server it has no business reaching. The fallback is now this site's site_base_url, baked in at seed time, and the dispatcher refuses rather than guessing when neither is set. Its legacy shortcut matcher derives the host from that URL instead of naming one. - The OpenAPI generator hardcoded a production hostname into every spec it generated, which then published to a public wiki. The relative mount is the only server it can honestly name; a site passes its own by environment. - Placeholders and examples in the UI and the client help offered real internal subnets and a real production URL. They now use documentation ranges. Both publication gates - the export scrub and the docs publishability test - carry the site patterns, which neither did. One plant's hostname, FQDN and internal networks are out of the documentation and the generated specs. Comments naming the reference site are reworded rather than deleted: the reasoning is worth keeping, the plant name is not what makes it true.
186 lines
7.9 KiB
Bash
Executable File
186 lines
7.9 KiB
Bash
Executable File
#!/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 --exclude also PROTECTS a path from --delete, so anything that reached
|
|
# the publication tree before its exclude existed stays there forever, invisible
|
|
# to the sync and caught only by the scrub gate. Purge the generated paths first
|
|
# so adding an exclude is enough on its own.
|
|
for stale in .pytest_cache .ruff_cache htmlcov .coverage \
|
|
deploy/windows/installer/bundle deploy/windows/installer/Output \
|
|
deploy/windows/installer/plugins.iss deploy/windows/installer/version.iss; do
|
|
rm -rf "${PUB:?}/$stale"
|
|
done
|
|
|
|
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 '.pytest_cache' \
|
|
--exclude '.ruff_cache' \
|
|
--exclude '.coverage' \
|
|
--exclude 'htmlcov' \
|
|
--exclude '*.pyc' \
|
|
--exclude 'scripts/site_imports/wjf/idmap.json' \
|
|
"$WORK/" "$PUB/"
|
|
|
|
# --- 1b. every bundled plugin must have survived the sync ---
|
|
#
|
|
# The excludes above are rsync patterns, and a pattern with no leading slash
|
|
# matches at ANY depth. 'tools' was meant to drop the repo-root tools/ dir (this
|
|
# script lives in it) and silently dropped plugins/tools/ as well, so a plugin
|
|
# was committed, exported, bundled and deployed without ever being in the
|
|
# payload - the failure only surfaced as a missing manifest.json on the server.
|
|
# The excludes are anchored now; this check is what makes a recurrence loud.
|
|
missing=""
|
|
for manifest in "$WORK"/plugins/*/manifest.json; do
|
|
[ -e "$manifest" ] || continue
|
|
plugin=$(basename "$(dirname "$manifest")")
|
|
[ -f "$PUB/plugins/$plugin/manifest.json" ] || missing="$missing $plugin"
|
|
done
|
|
if [ -n "$missing" ]; then
|
|
echo "ERROR: bundled plugin(s) missing from the publication tree:$missing" >&2
|
|
echo " an rsync --exclude above is matching them; anchor it with a leading slash." >&2
|
|
exit 1
|
|
fi
|
|
|
|
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|<code>docs/GE-ENFORCE.md</code>|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|<git-host>|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 ---
|
|
# The site patterns are ADR-015's, and they belong here as much as the internal
|
|
# tooling names do: this repository is public and the product is multi-site, so
|
|
# one plant's server name or internal networks reaching it is both a disclosure
|
|
# and a lie to every other site. They were absent, and a bundled plugin shipped
|
|
# a production UNC path to GitHub for a fortnight before anyone noticed.
|
|
LEAKS=$(grep -rlIiE 'claude|anthropic|fable 5|gitea|proudtech|home/camp|rootpassword|tsgwp00525|wjs\.geaerospace\.net|10\.134\.48\.|10\.48\.249\.' \
|
|
--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 <path-to>\shopdb-flask-pub.bundle main
|
|
git merge --ff-only FETCH_HEAD
|
|
git push origin main
|
|
EOF
|